├── .gitignore ├── .rubocop.yml ├── .travis.yml ├── Gemfile ├── LICENSE ├── README.rdoc ├── Rakefile ├── examples ├── multiplot │ ├── gnuplot.png │ ├── gnuplot_gem.png │ ├── plot.gnuplot │ └── plot.rb ├── plot_3d_surface │ ├── gnuplot.png │ ├── gnuplot_gem.png │ ├── plot.gnuplot │ └── plot.rb ├── plot_fit_exp │ ├── gnuplot.png │ ├── gnuplot_gem.png │ ├── plot.gnuplot │ ├── plot.rb │ └── points.data ├── plot_fit_general │ ├── gnuplot.png │ ├── gnuplot_gem.png │ ├── plot.gnuplot │ ├── plot.rb │ └── points.data ├── plot_fit_poly │ ├── gnuplot.png │ ├── gnuplot_gem.png │ ├── plot.gnuplot │ ├── plot.rb │ └── points.data ├── plot_from_datafile │ ├── gnuplot.png │ ├── gnuplot_gem.png │ ├── plot.gnuplot │ ├── plot.rb │ └── points.data ├── plot_histogram │ ├── gnuplot.png │ ├── gnuplot_gem.png │ ├── plot.gnuplot │ └── plot.rb ├── plot_in_parametric │ ├── gnuplot.png │ ├── gnuplot_gem.png │ ├── plot.gnuplot │ └── plot.rb ├── plot_in_polar │ ├── gnuplot.png │ ├── gnuplot_gem.png │ ├── plot.gnuplot │ └── plot.rb ├── plot_math_function │ ├── gnuplot.png │ ├── gnuplot_gem.png │ ├── plot.gnuplot │ └── plot.rb ├── plot_points │ ├── gnuplot.png │ ├── gnuplot_gem.png │ ├── plot.gnuplot │ └── plot.rb ├── plot_several_datasets │ ├── gnuplot.png │ ├── gnuplot_gem.png │ ├── plot.gnuplot │ └── plot.rb ├── plot_test_term │ ├── gnuplot.png │ ├── gnuplot_gem.png │ ├── plot.gnuplot │ └── plot.rb ├── plot_to_image_file │ ├── gnuplot.png │ ├── gnuplot_gem.png │ ├── plot.gnuplot │ ├── plot.rb │ ├── points.data │ ├── real_result.html │ ├── real_result.svg │ └── real_result.txt ├── plot_to_string │ ├── gnuplot.png │ ├── gnuplot_gem.png │ ├── plot.gnuplot │ ├── plot.rb │ └── points.data ├── tons_of_data │ ├── generate_data.rb │ ├── plot.rb │ └── result_screenshot.png └── use_multi_word_option_keys │ ├── gnuplot.png │ ├── gnuplot_gem.png │ ├── plot.gnuplot │ └── plot.rb ├── gnuplotrb.gemspec ├── lib ├── gnuplotrb.rb └── gnuplotrb │ ├── animation.rb │ ├── external_classes │ ├── array.rb │ ├── daru.rb │ └── string.rb │ ├── fit.rb │ ├── mixins │ ├── error_handling.rb │ ├── option_handling.rb │ └── plottable.rb │ ├── multiplot.rb │ ├── plot.rb │ ├── splot.rb │ ├── staff │ ├── datablock.rb │ ├── dataset.rb │ ├── settings.rb │ └── terminal.rb │ └── version.rb ├── notebooks ├── .gitignore ├── 3d_plot.ipynb ├── Gemfile ├── README.rdoc ├── animated_plots.ipynb ├── basic_usage.ipynb ├── fitting_data.ipynb ├── heatmaps.ipynb ├── histogram.ipynb ├── math_plots.ipynb ├── multiplot_layout.ipynb ├── plotting_from_daru.ipynb ├── points_from_different_sources.ipynb ├── time_series_from_daru.ipynb ├── updating_data.ipynb └── vector_field.ipynb └── spec ├── animation_spec.rb ├── dataset_spec.rb ├── fit_spec.rb ├── gnuplot_spec.rb ├── multiplot_spec.rb ├── plot_spec.rb ├── points.data ├── spec_helper.rb ├── splot_spec.rb └── terminal_spec.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /html/ 9 | /spec/reports/ 10 | /tmp/ 11 | /.idea/ -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | Include: 3 | - 'lib/**/*' 4 | Exclude: 5 | - 'examples/**/*' 6 | DisplayCopNames: true 7 | DisplayStyleGuide: true 8 | Metrics/LineLength: 9 | Max: 100 10 | Style/GlobalVars: 11 | AllowedVariables: [$RSPEC_TEST] 12 | Style/MultilineOperationIndentation: 13 | Enabled: false 14 | Metrics/MethodLength: 15 | Max: 15 16 | Metrics/AbcSize: 17 | Max: 20 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | notifications: 2 | email: false 3 | language: ruby 4 | rvm: 5 | - ruby-2.0 6 | - ruby-2.1.5 7 | - ruby-2.2.2 8 | - jruby-9.0.0.0.pre2 9 | env: 10 | - JRUBY_OPTS="-Xcli.debug=true --debug" 11 | addons: 12 | code_climate: 13 | repo_token: bbd2a5e077e4b62bd8ba21a74a2ba901defc43506812f6192efc6b2a25c20d49 14 | before_install: 15 | - sudo add-apt-repository "deb http://cz.archive.ubuntu.com/ubuntu vivid main universe" 16 | - sudo apt-get update -q 17 | - sudo rm -rf /etc/dpkg/dpkg.cfg.d/multiarch 18 | - sudo apt-get install gnuplot5 19 | - bundle install 20 | install: 21 | - bundle exec rake install 22 | script: 23 | - bundle exec rake spec 24 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in gnuplotrb.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Ivan Evgrafov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = GnuplotRB 2 | 3 | GnuplotRB is a plot generator for Ruby based on {Gnuplot}[http://www.gnuplot.info]. 4 | 5 | This software has been developed as a product in Google Summer of Code 2015 (GSoC2015). Its progress may be saw in {SciRuby mailing list}[https://groups.google.com/forum/?fromgroups#!topic/sciruby-dev/lhWvb5hWc3k] or in {project's blog}[http://www.evgrafov.work/gnuplotrb/]. 6 | 7 | {Gem Version}[https://rubygems.org/gems/gnuplotrb] 8 | 9 | {Dependency Status}[https://gemnasium.com/dilcom/gnuplotrb] 10 | 11 | {Build Status}[https://travis-ci.org/dilcom/gnuplotrb] 12 | {Code quality}[https://codeclimate.com/github/dilcom/gnuplotrb] 13 | {Test coverage}[https://codeclimate.com/github/dilcom/gnuplotrb] 14 | 15 | == Table of contents 16 | * {Installation}[https://github.com/dilcom/gnuplotrb#installation] 17 | * {Getting started}[https://github.com/dilcom/gnuplotrb#getting-started] 18 | * {Plottable classes}[https://github.com/dilcom/gnuplotrb#plottable-classes] 19 | * {Notebooks}[https://github.com/dilcom/gnuplotrb#notebooks] 20 | * {Plain examples}[https://github.com/dilcom/gnuplotrb#plain-examples] 21 | * {Contributing}[https://github.com/dilcom/gnuplotrb#contributing] 22 | 23 | == Installation 24 | === Dependencies 25 | * Ruby 2.0+ 26 | * It is required to install {gnuplot 5.0}[http://www.gnuplot.info/download.html] to use that gem. 27 | === Gem installation 28 | ==== Install latest stable version from Rubygems 29 | gem install gnuplotrb 30 | ==== Install latest stable version using bundler 31 | * add 32 | gem 'gnuplotrb' 33 | to your Gemfile 34 | * run 35 | bundle install 36 | ==== Install latest version from source (may be unstable) 37 | git clone https://github.com/dilcom/gnuplotrb.git 38 | cd gnuplotrb 39 | bundle install 40 | rake install 41 | 42 | == Getting started 43 | 44 | GnuplotRB gem is based on {Gnuplot}[http://www.gnuplot.info/] so I would recommend to use {Gnuplot doc}[http://www.gnuplot.info/docs_5.0/gnuplot.pdf] and {GnuplotRB doc at Rubydoc}[https://rubygems.org/gems/gnuplotrb] in cases when docs and examples (as notebooks and plain examples) present here are not enough to explain how to plot something. 45 | 46 | === Plottable classes 47 | 48 | Each of plottable classes may be outputted to image file using ```#to_png```, ```#to_svg```, ```#to_gif``` and so on methods. You can read more about it in {GnuplotRB doc}[https://rubygems.org/gems/gnuplotrb] related to Plottable module or see examples in {beginners notebook}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/basic_usage.ipynb]. 49 | 50 | ==== Dataset 51 | Single dataset may be created with math formula ('sin(x)') or some data. If your data is stored in a file you can just pass a filename (e.g. 'gnuplotrb.data'). Dataset may also be constructed out of data contained in Ruby classes (Array, Daru containers), see {example notebooks}[https://github.com/dilcom/gnuplotrb#possible-datasources]. 52 | 53 | Dataset have several possible options which are explained in {gnuplot doc}[http://www.gnuplot.info/docs_5.0/gnuplot.pdf] (pp. 80-102). Options are passed to Dataset.new as hash and are tranlated into gnuplot format before plotting: 54 | Dataset.new(data, with: 'lines', using: '1:2') 55 | Examples of option translation (nested containers allowed): 56 | * Hash: 57 | { key1: "value1", key2: { nested_key1: "nested_value1" } } 58 | # => 59 | "key1 value1 key2 nested key1 nested_value1" 60 | * Hashes with underscored keys (see {#7}[https://github.com/dilcom/gnuplotrb/issues/7]): 61 | { style_data: 'histograms' } 62 | #=> 63 | "style data histograms" 64 | * Range: 65 | { xrange: 0..100 } 66 | # => 67 | "xrange [0:100]" 68 | * Array (often used with nested hashes) and Array of Numeric 69 | ['png', { size: [400, 500] }] 70 | # => 71 | "png size 400,500" 72 | * Others 73 | some_object 74 | # => 75 | some_object.to_s 76 | 77 | Once Dataset created, it may be updated with new data or options. Methods related to updating are explained in {a notebook}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/updating_data.ipynb]. 78 | 79 | Just as other Plottable object Dataset has several plotting methods which are desribed in {beginners notebook}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/basic_usage.ipynb]. 80 | 81 | ==== Plot 82 | Plot is a container for several datasets and layout options: 83 | Plot.new(ds1, ds2, ds2, xrange: 1..10) 84 | 85 | Datasets contained bu Plot are outputted on single xy plain. 86 | 87 | Plot's options are explained in {gnuplot doc}[http://www.gnuplot.info/docs_5.0/gnuplot.pdf] (pp. 105-181). Plot options are translated into gnuplot format the same way as Dataset's (except adding 'set' before each option). Plot's datasets and Plot itself may be updated with almost the same methods as desribed in Dataset section above. 88 | 89 | ==== Splot 90 | Almost the same as Plot but for 3-D plots. See Plot section. 91 | 92 | ==== Multiplot 93 | 94 | Container for several Plot or Splot objects, each of them is plotted in its own xy(z) space. So Multiplot offers single layout (image file\window) for several plots. It's grid is tuned by :layout option, and you can also set layout's title: 95 | Multiplot.new(plot1, plot2, splot1, layout: [3, 1], title: 'Three plots on a layout') 96 | 97 | Updating methods for Multiplot are almost the same as Plot's and Dataset's and are covered in Multiplot's docs and {multiplot notebook}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/multiplot_layout.ipynb]. See examples there. 98 | 99 | ==== Animation 100 | 101 | Animation is a container for several Plot, Splot or Multiplot objects. Each of contained items is considered as frame in gif animation which is creating by ```#plot``` call. Animation's frames and options may be modifyed or updated just as other classes above. Animation does not support methods like ```#to_png``` and may be plotted only with ```#plot``` method. Please see {related notebook}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/animated_plots.ipynb] and docs at RubyDoc for examples. 102 | 103 | === Notebooks 104 | 105 | This notebooks are powered by {Ruby kernel}[https://github.com/SciRuby/iruby/] for {IPython/Jupyter}[https://jupyter.org/]. 106 | I placed them here to show some GnuplotRB's capabilities and ways of using it together with iRuby. 107 | 108 | To use GnuplotRB gem with iRuby you need to install them both. 109 | 110 | * iRuby installation is covered in its {README}[https://github.com/SciRuby/iruby/blob/master/README.md]. 111 | It also covers installation of iPython and other dependecies. 112 | * GnuplotRB gem installation covered in {README}[https://github.com/dilcom/gnuplotrb#installation] too. 113 | 114 | ==== Embedding plots into iRuby 115 | Using GnuplotRB inside iRuby notebooks is covered in: 116 | 117 | * {Basic usage notebook}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/basic_usage.ipynb]. 118 | 119 | ==== 2D and 3D plots 120 | GnuplotRB is capable to plot vast range of plots from histograms to 3D heatmaps. Gem's repository contains examples of several plot types: 121 | 122 | * {Heatmaps}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/heatmaps.ipynb] 123 | * {Vector field}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/vector_field.ipynb] (Thanks, {Alexej}[https://github.com/agisga]) 124 | * {Math equations}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/math_plots.ipynb] 125 | * {3D visualizations}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/3d_plot.ipynb] 126 | * {Histogram}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/histogram.ipynb] 127 | 128 | ==== Possible datasources 129 | GnuplotRB may take data in Ruby container or in a file. Supported containers for now are Arrays, Daru::Vector and Daru::DataFrame. 130 | When data given in file, GnuplotRB pass filename to Gnuplot *without* reading the file into memory. 131 | 132 | Examples of using different datasources: 133 | 134 | * {Data given in file or Ruby Array}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/points_from_different_sources.ipynb] 135 | * {Data given in Daru containers}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/plotting_from_daru.ipynb] 136 | * {Data given in Daru containers (with timeseries)}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/time_series_from_daru.ipynb] 137 | * {Updating plots with new data}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/updating_data.ipynb] 138 | 139 | ==== Multiplot 140 | You can not only plot several datasets in single coordinate system but place several coordinate systems on a canvas. 141 | 142 | * {Multiplot example notebook}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/multiplot_layout.ipynb]. 143 | 144 | ==== Animation 145 | It's possible to use several plots (Plot, Splot or Multiplot objects) to create gif animation. 146 | 147 | * {Animation example notebook}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/animated_plots.ipynb]. 148 | 149 | ==== Fitting data with formula 150 | GnuplotRB also may be used to fit some data with given math formula. 151 | 152 | * {Fitting data}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/fitting_data.ipynb] 153 | 154 | === Plain examples 155 | You may find several examples in {examples directory}[https://github.com/dilcom/gnuplotrb/tree/master/examples]. 156 | 157 | == Contributing 158 | 159 | 1. {Fork repository}[https://github.com/dilcom/gnuplotrb/fork] 160 | 2. Create your feature branch (`git checkout -b my-new-feature`) 161 | 3. Commit your changes (`git commit -am 'Add some feature'`) 162 | 4. Push to the branch (`git push origin my-new-feature`) 163 | 5. Create a new Pull Request 164 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rspec/core/rake_task' 3 | require 'rspec/core' 4 | require 'yard' 5 | require 'rubocop/rake_task' 6 | 7 | RSpec::Core::RakeTask.new(:spec) do |spec| 8 | spec.pattern = FileList['spec/**/*_spec.rb'].uniq 9 | spec.rspec_opts = '--format documentation' 10 | end 11 | 12 | YARD::Rake::YardocTask.new(:doc) do |t| 13 | t.files = %w(README.rdoc lib) # optional 14 | end 15 | 16 | RuboCop::RakeTask.new(:cop) 17 | -------------------------------------------------------------------------------- /examples/multiplot/gnuplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/multiplot/gnuplot.png -------------------------------------------------------------------------------- /examples/multiplot/gnuplot_gem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/multiplot/gnuplot_gem.png -------------------------------------------------------------------------------- /examples/multiplot/plot.gnuplot: -------------------------------------------------------------------------------- 1 | set style data histograms 2 | set output './gnuplot.png' 3 | set term png size 400,800 4 | set xtics nomirror rotate by -45 5 | $DATA << EOD 6 | "1891-1900" 234081 181288 18167 7 | "1901-1910" 668209 808511 41635 8 | "1911-1920" 453649 442693 33746 9 | "1921-1930" 32868 30680 15846 10 | "1931-1940" 3563 7861 4817 11 | "1941-1950" 24860 3469 12189 12 | "1951-1960" 67106 36637 18575 13 | "1961-1970" 20621 5401 9192 14 | EOD 15 | set multiplot layout 3,1 title 'Histograms' 16 | plot $DATA using 2:xtic(1) title 'Austria' 17 | plot $DATA using 3:xtic(1) title 'Hungary' 18 | plot $DATA using 4:xtic(1) title 'Belgium' 19 | unset multiplot 20 | unset output 21 | -------------------------------------------------------------------------------- /examples/multiplot/plot.rb: -------------------------------------------------------------------------------- 1 | require 'gnuplotrb' 2 | include GnuplotRB 3 | 4 | titles = %w{decade Austria Hungary Belgium} 5 | data = [ 6 | ['1891-1900', 234081, 181288, 18167], 7 | ['1901-1910', 668209, 808511, 41635], 8 | ['1911-1920', 453649, 442693, 33746], 9 | ['1921-1930', 32868, 30680, 15846], 10 | ['1931-1940', 3563, 7861, 4817], 11 | ['1941-1950', 24860, 3469, 12189], 12 | ['1951-1960', 67106, 36637, 18575], 13 | ['1961-1970', 20621, 5401, 9192], 14 | ] 15 | x = data.map(&:first) 16 | plots = (1..3).map do |col| 17 | y = data.map { |row| row[col] } 18 | Plot.new([[x, y], using: '2:xtic(1)', title: titles[col]]) 19 | end 20 | mp = Multiplot.new( 21 | *plots, 22 | layout: [plots.size, 1], 23 | style_data: 'histograms', 24 | title: 'Histograms', 25 | xtics: 'nomirror rotate by -45' 26 | ) 27 | 28 | mp.to_png('./gnuplot_gem.png', size: [400,800]) 29 | -------------------------------------------------------------------------------- /examples/plot_3d_surface/gnuplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/plot_3d_surface/gnuplot.png -------------------------------------------------------------------------------- /examples/plot_3d_surface/gnuplot_gem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/plot_3d_surface/gnuplot_gem.png -------------------------------------------------------------------------------- /examples/plot_3d_surface/plot.gnuplot: -------------------------------------------------------------------------------- 1 | set output './gnuplot.png' 2 | set term png size 600,600 3 | set hidden3d 4 | set xrange [-pi*2:pi*2] 5 | set yrange [-pi*2:pi*2] 6 | set style function lp 7 | splot cos(x)*cos(y) 8 | unset output -------------------------------------------------------------------------------- /examples/plot_3d_surface/plot.rb: -------------------------------------------------------------------------------- 1 | require 'gnuplotrb' 2 | include GnuplotRB 3 | 4 | double_pi = Math::PI * 2 5 | 6 | plot3d = Splot.new('cos(x)*cos(y)', xrange: -double_pi..double_pi, yrange: -double_pi..double_pi, style: 'function lp', hidden3d: true) 7 | 8 | plot3d.to_png('./gnuplot_gem.png', size: [600, 600]) 9 | -------------------------------------------------------------------------------- /examples/plot_fit_exp/gnuplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/plot_fit_exp/gnuplot.png -------------------------------------------------------------------------------- /examples/plot_fit_exp/gnuplot_gem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/plot_fit_exp/gnuplot_gem.png -------------------------------------------------------------------------------- /examples/plot_fit_exp/plot.gnuplot: -------------------------------------------------------------------------------- 1 | set term png size 600,600 2 | set output './gnuplot.png' 3 | xoffset = 0.1 4 | xscale = 1 5 | yoffset = 0.1 6 | yscale = 1 7 | fit yscale * (yoffset + exp((x - xoffset) / xscale)) 'points.data' using 1:2:3 zerror via yscale,yoffset,xoffset,xscale 8 | plot 'points.data' with yerr, yscale * (yoffset + exp((x - xoffset) / xscale)) title 'Fit formula' lw 3 9 | unset output 10 | -------------------------------------------------------------------------------- /examples/plot_fit_exp/plot.rb: -------------------------------------------------------------------------------- 1 | require 'gnuplotrb' 2 | include GnuplotRB 3 | 4 | fit_result = fit_exp('points.data', using: '1:2:3') 5 | 6 | plot = Plot.new(fit_result[:data].with('yerr'), fit_result[:formula_ds].lw(3)) 7 | 8 | plot.to_png('./gnuplot_gem.png', size: [600, 600]) 9 | -------------------------------------------------------------------------------- /examples/plot_fit_general/gnuplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/plot_fit_general/gnuplot.png -------------------------------------------------------------------------------- /examples/plot_fit_general/gnuplot_gem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/plot_fit_general/gnuplot_gem.png -------------------------------------------------------------------------------- /examples/plot_fit_general/plot.gnuplot: -------------------------------------------------------------------------------- 1 | set term png size 600,600 2 | set output './gnuplot.png' 3 | a = 1 4 | b = 1 5 | set xrange [0:15] 6 | fit a*x*x + b 'points.data' using 1:2:3 zerror via a,b 7 | unset xrange 8 | plot 'points.data' with yerr, a*x*x+b title 'Fit formula' lw 3 9 | unset output 10 | -------------------------------------------------------------------------------- /examples/plot_fit_general/plot.rb: -------------------------------------------------------------------------------- 1 | require 'gnuplotrb' 2 | include GnuplotRB 3 | 4 | fit_result = fit('points.data', using: '1:2:3', function: 'a*x*x + b', initials: { a: 1, b: 1 }, term_options: { xrange: 0..15 }) 5 | 6 | plot = Plot.new(fit_result[:data].with('yerr'), fit_result[:formula_ds].lw(3)) 7 | 8 | plot.to_png('./gnuplot_gem.png', size: [600, 600]) 9 | -------------------------------------------------------------------------------- /examples/plot_fit_poly/gnuplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/plot_fit_poly/gnuplot.png -------------------------------------------------------------------------------- /examples/plot_fit_poly/gnuplot_gem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/plot_fit_poly/gnuplot_gem.png -------------------------------------------------------------------------------- /examples/plot_fit_poly/plot.gnuplot: -------------------------------------------------------------------------------- 1 | set term png size 600,600 2 | set output './gnuplot.png' 3 | a3 = 1 4 | a2 = 1 5 | a1 = 1 6 | a0 = 1 7 | fit a3*x**3 + a2*x**2 + a1*x + a0 'points.data' using 1:2:3 zerror via a3,a2,a1,a0 8 | plot 'points.data' with yerr, a3*x**3 + a2*x**2 + a1*x + a0 title 'Fit formula' lw 3 9 | unset output 10 | -------------------------------------------------------------------------------- /examples/plot_fit_poly/plot.rb: -------------------------------------------------------------------------------- 1 | require 'gnuplotrb' 2 | include GnuplotRB 3 | 4 | fit_result = fit_poly('points.data', degree: 3, using: '1:2:3') 5 | 6 | plot = Plot.new(fit_result[:data].with('yerr'), fit_result[:formula_ds].lw(3)) 7 | 8 | plot.to_png('./gnuplot_gem.png', size: [600, 600]) 9 | -------------------------------------------------------------------------------- /examples/plot_from_datafile/gnuplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/plot_from_datafile/gnuplot.png -------------------------------------------------------------------------------- /examples/plot_from_datafile/gnuplot_gem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/plot_from_datafile/gnuplot_gem.png -------------------------------------------------------------------------------- /examples/plot_from_datafile/plot.gnuplot: -------------------------------------------------------------------------------- 1 | set term png size 600,600 2 | set output './gnuplot.png' 3 | plot 'points.data' with lines title 'Points from file' 4 | unset output -------------------------------------------------------------------------------- /examples/plot_from_datafile/plot.rb: -------------------------------------------------------------------------------- 1 | require 'gnuplotrb' 2 | include GnuplotRB 3 | 4 | plot = Plot.new(['points.data', with: 'lines', title: 'Points from file']) 5 | 6 | plot.to_png('./gnuplot_gem.png', size: [600, 600]) -------------------------------------------------------------------------------- /examples/plot_from_datafile/points.data: -------------------------------------------------------------------------------- 1 | 0.0 1.0 2 | 0.1 0.9048374180359595 3 | 0.2 0.8187307530779818 4 | 0.3 0.7408182206817179 5 | 0.4 0.6703200460356393 6 | 0.5 0.6065306597126334 7 | 0.6 0.5488116360940264 8 | 0.7 0.4965853037914095 9 | 0.8 0.44932896411722156 10 | 0.9 0.4065696597405991 11 | 1.0 0.36787944117144233 12 | 1.1 0.33287108369807955 13 | 1.2 0.30119421191220214 14 | 1.3 0.2725317930340126 15 | 1.4 0.2465969639416065 16 | 1.5 0.22313016014842982 17 | 1.6 0.20189651799465538 18 | 1.7 0.18268352405273466 19 | 1.8 0.16529888822158653 20 | 1.9 0.14956861922263506 21 | 2.0 0.1353352832366127 22 | 2.1 0.1224564282529819 23 | 2.2 0.11080315836233387 24 | 2.3 0.10025884372280375 25 | 2.4 0.09071795328941251 26 | 2.5 0.0820849986238988 27 | 2.6 0.07427357821433388 28 | 2.7 0.06720551273974976 29 | 2.8 0.06081006262521797 30 | 2.9 0.05502322005640723 31 | 3.0 0.049787068367863944 32 | 3.1 0.0450492023935578 33 | 3.2 0.04076220397836621 34 | 3.3 0.036883167401240015 35 | 3.4 0.03337326996032608 36 | 3.5 0.0301973834223185 37 | 3.6 0.02732372244729256 38 | 3.7 0.024723526470339388 39 | 3.8 0.0223707718561656 40 | 3.9 0.02024191144580439 41 | 4.0 0.01831563888873418 42 | 4.1 0.016572675401761255 43 | 4.2 0.014995576820477703 44 | 4.3 0.013568559012200934 45 | 4.4 0.012277339903068436 46 | 4.5 0.011108996538242306 47 | 4.6 0.010051835744633586 48 | 4.7 0.009095277101695816 49 | 4.8 0.00822974704902003 50 | 4.9 0.007446583070924338 51 | 5.0 0.006737946999085467 52 | 5.1 0.006096746565515638 53 | 5.2 0.0055165644207607716 54 | 5.3 0.004991593906910217 55 | 5.4 0.004516580942612666 56 | 5.5 0.004086771438464067 57 | 5.6 0.003697863716482932 58 | 5.7 0.003345965457471272 59 | 5.8 0.0030275547453758153 60 | 5.9 0.0027394448187683684 61 | 6.0 0.0024787521766663585 62 | 6.1 0.0022428677194858034 63 | 6.2 0.002029430636295734 64 | 6.3 0.0018363047770289071 65 | 6.4 0.001661557273173934 66 | 6.5 0.0015034391929775724 67 | 6.6 0.0013603680375478939 68 | 6.7 0.001230911902673481 69 | 6.8 0.0011137751478448032 70 | 6.9 0.0010077854290485105 71 | 7.0 0.0009118819655545162 72 | 7.1 0.0008251049232659046 73 | 7.2 0.0007465858083766792 74 | 7.3 0.0006755387751938444 75 | 7.4 0.0006112527611295723 76 | 7.5 0.0005530843701478336 77 | 7.6 0.0005004514334406108 78 | 7.7 0.00045282718288679695 79 | 7.8 0.0004097349789797868 80 | 7.9 0.0003707435404590882 81 | 8.0 0.00033546262790251185 82 | 8.1 0.0003035391380788668 83 | 8.2 0.00027465356997214254 84 | 8.3 0.00024851682710795185 85 | 8.4 0.0002248673241788482 86 | 8.5 0.00020346836901064417 87 | 8.6 0.0001841057936675792 88 | 8.7 0.00016658581098763354 89 | 8.8 0.0001507330750954765 90 | 8.9 0.0001363889264820114 91 | 9.0 0.00012340980408667956 92 | 9.1 0.00011166580849011478 93 | 9.2 0.00010103940183709342 94 | 9.3 9.142423147817327e-05 95 | 9.4 8.272406555663223e-05 96 | 9.5 7.48518298877006e-05 97 | 9.6 6.77287364908539e-05 98 | 9.7 6.128349505322213e-05 99 | 9.8 5.5451599432176945e-05 100 | 9.9 5.017468205617528e-05 101 | 10.0 4.5399929762484854e-05 -------------------------------------------------------------------------------- /examples/plot_histogram/gnuplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/plot_histogram/gnuplot.png -------------------------------------------------------------------------------- /examples/plot_histogram/gnuplot_gem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/plot_histogram/gnuplot_gem.png -------------------------------------------------------------------------------- /examples/plot_histogram/plot.gnuplot: -------------------------------------------------------------------------------- 1 | set term png size 600,600 2 | set output './gnuplot.png' 3 | set title "Histogram example" 4 | set style data histograms 5 | set xtics nomirror rotate by -45 6 | $DATA << EOD 7 | "1891-1900" 234081 181288 18167 8 | "1901-1910" 668209 808511 41635 9 | "1911-1920" 453649 442693 33746 10 | "1921-1930" 32868 30680 15846 11 | "1931-1940" 3563 7861 4817 12 | "1941-1950" 24860 3469 12189 13 | "1951-1960" 67106 36637 18575 14 | "1961-1970" 20621 5401 9192 15 | EOD 16 | plot $DATA using 2:xtic(1) title 'Austria',\ 17 | $DATA using 3:xtic(1) title 'Hungary',\ 18 | $DATA using 4:xtic(1) title 'Belgium' 19 | unset output -------------------------------------------------------------------------------- /examples/plot_histogram/plot.rb: -------------------------------------------------------------------------------- 1 | require 'gnuplotrb' 2 | include GnuplotRB 3 | 4 | titles = %w{decade Austria Hungary Belgium} 5 | data = [ 6 | ['1891-1900', 234081, 181288, 18167], 7 | ['1901-1910', 668209, 808511, 41635], 8 | ['1911-1920', 453649, 442693, 33746], 9 | ['1921-1930', 32868, 30680, 15846], 10 | ['1931-1940', 3563, 7861, 4817], 11 | ['1941-1950', 24860, 3469, 12189], 12 | ['1951-1960', 67106, 36637, 18575], 13 | ['1961-1970', 20621, 5401, 9192], 14 | ] 15 | x = data.map(&:first) 16 | datasets = (1..3).map do |col| 17 | y = data.map { |row| row[col] } 18 | Dataset.new([x, y], using: '2:xtic(1)', title: titles[col], file: true) 19 | end 20 | plot = Plot.new(*datasets, title: 'Histogram example', style: 'data histograms', xtics: 'nomirror rotate by -45') 21 | 22 | plot.to_png('./gnuplot_gem.png', size: [600, 600]) -------------------------------------------------------------------------------- /examples/plot_in_parametric/gnuplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/plot_in_parametric/gnuplot.png -------------------------------------------------------------------------------- /examples/plot_in_parametric/gnuplot_gem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/plot_in_parametric/gnuplot_gem.png -------------------------------------------------------------------------------- /examples/plot_in_parametric/plot.gnuplot: -------------------------------------------------------------------------------- 1 | set title 'Parametric plot example' 2 | set parametric 3 | set samples 3000 4 | set term png size 600,600 5 | set output './gnuplot.png' 6 | plot 1.5*cos(t) - cos(30*t), 1.5*sin(t) - sin(30*t) title 'Parametric curve' 7 | unset output -------------------------------------------------------------------------------- /examples/plot_in_parametric/plot.rb: -------------------------------------------------------------------------------- 1 | require 'gnuplotrb' 2 | include GnuplotRB 3 | 4 | plot = Plot.new(['1.5*cos(t) - cos(30*t), 1.5*sin(t) - sin(30*t)', title: 'Parametric curve'], title: 'Parametric plot example', parametric: true, samples: 3000) 5 | 6 | plot.to_png('./gnuplot_gem.png', size: [600, 600]) 7 | -------------------------------------------------------------------------------- /examples/plot_in_polar/gnuplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/plot_in_polar/gnuplot.png -------------------------------------------------------------------------------- /examples/plot_in_polar/gnuplot_gem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/plot_in_polar/gnuplot_gem.png -------------------------------------------------------------------------------- /examples/plot_in_polar/plot.gnuplot: -------------------------------------------------------------------------------- 1 | set term png size 600,600 2 | set output './gnuplot.png' 3 | set title 'Plot in polar example' 4 | set polar 5 | set samples 1000 6 | plot abs(sin(3*t)) with filledcurves 7 | unset output -------------------------------------------------------------------------------- /examples/plot_in_polar/plot.rb: -------------------------------------------------------------------------------- 1 | require 'gnuplotrb' 2 | include GnuplotRB 3 | 4 | plot = Plot.new(['abs(sin(3*t))', with: 'filledcurves'], title: 'Plot in polar example', polar: true, samples: 1000) 5 | 6 | plot.to_png('./gnuplot_gem.png', size: [600, 600]) 7 | -------------------------------------------------------------------------------- /examples/plot_math_function/gnuplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/plot_math_function/gnuplot.png -------------------------------------------------------------------------------- /examples/plot_math_function/gnuplot_gem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/plot_math_function/gnuplot_gem.png -------------------------------------------------------------------------------- /examples/plot_math_function/plot.gnuplot: -------------------------------------------------------------------------------- 1 | set term png size 600,600 2 | set output './gnuplot.png' 3 | set xrange [-10:10] 4 | set title "Math function example" 5 | set ylabel "x" 6 | set xlabel "x*sin(x)" 7 | plot x*sin(x) with lines lw 4 8 | unset output -------------------------------------------------------------------------------- /examples/plot_math_function/plot.rb: -------------------------------------------------------------------------------- 1 | require 'gnuplotrb' 2 | include GnuplotRB 3 | 4 | plot = Plot.new(['x*sin(x)', with: 'lines', lw: 4], xrange: -10..10, title: 'Math function example', ylabel: 'x', xlabel: 'x*sin(x)') 5 | 6 | plot.to_png('./gnuplot_gem.png', size: [600, 600]) -------------------------------------------------------------------------------- /examples/plot_points/gnuplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/plot_points/gnuplot.png -------------------------------------------------------------------------------- /examples/plot_points/gnuplot_gem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/plot_points/gnuplot_gem.png -------------------------------------------------------------------------------- /examples/plot_points/plot.gnuplot: -------------------------------------------------------------------------------- 1 | set term png size 600,600 2 | set output './gnuplot.png' 3 | $DATA << EOD 4 | 0 0 5 | 1 1 6 | 2 4 7 | 3 9 8 | 4 16 9 | 5 25 10 | EOD 11 | plot $DATA with points title 'Points' 12 | unset output -------------------------------------------------------------------------------- /examples/plot_points/plot.rb: -------------------------------------------------------------------------------- 1 | require 'gnuplotrb' 2 | include GnuplotRB 3 | 4 | x = (0..5).to_a 5 | y = x.map {|xx| xx*xx } 6 | points = [x, y] 7 | 8 | plot = Plot.new([points, with: 'points', title: 'Points']) 9 | 10 | plot.to_png('./gnuplot_gem.png', size: [600, 600]) 11 | -------------------------------------------------------------------------------- /examples/plot_several_datasets/gnuplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/plot_several_datasets/gnuplot.png -------------------------------------------------------------------------------- /examples/plot_several_datasets/gnuplot_gem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/plot_several_datasets/gnuplot_gem.png -------------------------------------------------------------------------------- /examples/plot_several_datasets/plot.gnuplot: -------------------------------------------------------------------------------- 1 | set term png size 600,600 2 | set output './gnuplot.png' 3 | $DATA << EOD 4 | 0 0 5 | 1 1 6 | 2 4 7 | 3 9 8 | 4 16 9 | 5 25 10 | EOD 11 | set xrange [0:5] 12 | plot x*x title 'True curve', $DATA title 'Points' with lines 13 | unset output -------------------------------------------------------------------------------- /examples/plot_several_datasets/plot.rb: -------------------------------------------------------------------------------- 1 | require 'gnuplotrb' 2 | include GnuplotRB 3 | 4 | x = (0..5).to_a 5 | y = x.map {|xx| xx*xx } 6 | points = [x, y] 7 | 8 | plot = Plot.new(['x*x', title: 'True curve'], [points, with: 'lines', title: 'Points']) 9 | 10 | plot.to_png('./gnuplot_gem.png', size: [600, 600]) 11 | -------------------------------------------------------------------------------- /examples/plot_test_term/gnuplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/plot_test_term/gnuplot.png -------------------------------------------------------------------------------- /examples/plot_test_term/gnuplot_gem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/plot_test_term/gnuplot_gem.png -------------------------------------------------------------------------------- /examples/plot_test_term/plot.gnuplot: -------------------------------------------------------------------------------- 1 | set term png 2 | set output './gnuplot.png' 3 | test 4 | unset output -------------------------------------------------------------------------------- /examples/plot_test_term/plot.rb: -------------------------------------------------------------------------------- 1 | require 'gnuplotrb' 2 | include GnuplotRB 3 | 4 | Terminal::test('png', 'gnuplot_gem.png') 5 | -------------------------------------------------------------------------------- /examples/plot_to_image_file/gnuplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/plot_to_image_file/gnuplot.png -------------------------------------------------------------------------------- /examples/plot_to_image_file/gnuplot_gem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/plot_to_image_file/gnuplot_gem.png -------------------------------------------------------------------------------- /examples/plot_to_image_file/plot.gnuplot: -------------------------------------------------------------------------------- 1 | set term png size 600,600 2 | set output './gnuplot.png' 3 | set title 'Plotting to png' 4 | plot 'points.data' with lines title 'Points from file' 5 | unset output -------------------------------------------------------------------------------- /examples/plot_to_image_file/plot.rb: -------------------------------------------------------------------------------- 1 | require 'gnuplotrb' 2 | include GnuplotRB 3 | 4 | plot = Plot.new(['points.data', with: 'lines', title: 'Points from file'], title: 'Plotting to png') 5 | 6 | plot.to_png('./gnuplot_gem.png', size: [600, 600]) 7 | 8 | unless defined?(RSpec) 9 | plot.to_svg('./real_result.svg', size: [600, 600]) 10 | plot.to_canvas('./real_result.html', size: [600, 600]) 11 | 12 | # You can also just get contents of image (or not image) file 13 | contents = plot.to_dumb(size: [60, 40]) 14 | File.write('./real_result.txt', contents) 15 | end 16 | -------------------------------------------------------------------------------- /examples/plot_to_image_file/points.data: -------------------------------------------------------------------------------- 1 | 0.0 1.0 2 | 0.1 0.9048374180359595 3 | 0.2 0.8187307530779818 4 | 0.3 0.7408182206817179 5 | 0.4 0.6703200460356393 6 | 0.5 0.6065306597126334 7 | 0.6 0.5488116360940264 8 | 0.7 0.4965853037914095 9 | 0.8 0.44932896411722156 10 | 0.9 0.4065696597405991 11 | 1.0 0.36787944117144233 12 | 1.1 0.33287108369807955 13 | 1.2 0.30119421191220214 14 | 1.3 0.2725317930340126 15 | 1.4 0.2465969639416065 16 | 1.5 0.22313016014842982 17 | 1.6 0.20189651799465538 18 | 1.7 0.18268352405273466 19 | 1.8 0.16529888822158653 20 | 1.9 0.14956861922263506 21 | 2.0 0.1353352832366127 22 | 2.1 0.1224564282529819 23 | 2.2 0.11080315836233387 24 | 2.3 0.10025884372280375 25 | 2.4 0.09071795328941251 26 | 2.5 0.0820849986238988 27 | 2.6 0.07427357821433388 28 | 2.7 0.06720551273974976 29 | 2.8 0.06081006262521797 30 | 2.9 0.05502322005640723 31 | 3.0 0.049787068367863944 32 | 3.1 0.0450492023935578 33 | 3.2 0.04076220397836621 34 | 3.3 0.036883167401240015 35 | 3.4 0.03337326996032608 36 | 3.5 0.0301973834223185 37 | 3.6 0.02732372244729256 38 | 3.7 0.024723526470339388 39 | 3.8 0.0223707718561656 40 | 3.9 0.02024191144580439 41 | 4.0 0.01831563888873418 42 | 4.1 0.016572675401761255 43 | 4.2 0.014995576820477703 44 | 4.3 0.013568559012200934 45 | 4.4 0.012277339903068436 46 | 4.5 0.011108996538242306 47 | 4.6 0.010051835744633586 48 | 4.7 0.009095277101695816 49 | 4.8 0.00822974704902003 50 | 4.9 0.007446583070924338 51 | 5.0 0.006737946999085467 52 | 5.1 0.006096746565515638 53 | 5.2 0.0055165644207607716 54 | 5.3 0.004991593906910217 55 | 5.4 0.004516580942612666 56 | 5.5 0.004086771438464067 57 | 5.6 0.003697863716482932 58 | 5.7 0.003345965457471272 59 | 5.8 0.0030275547453758153 60 | 5.9 0.0027394448187683684 61 | 6.0 0.0024787521766663585 62 | 6.1 0.0022428677194858034 63 | 6.2 0.002029430636295734 64 | 6.3 0.0018363047770289071 65 | 6.4 0.001661557273173934 66 | 6.5 0.0015034391929775724 67 | 6.6 0.0013603680375478939 68 | 6.7 0.001230911902673481 69 | 6.8 0.0011137751478448032 70 | 6.9 0.0010077854290485105 71 | 7.0 0.0009118819655545162 72 | 7.1 0.0008251049232659046 73 | 7.2 0.0007465858083766792 74 | 7.3 0.0006755387751938444 75 | 7.4 0.0006112527611295723 76 | 7.5 0.0005530843701478336 77 | 7.6 0.0005004514334406108 78 | 7.7 0.00045282718288679695 79 | 7.8 0.0004097349789797868 80 | 7.9 0.0003707435404590882 81 | 8.0 0.00033546262790251185 82 | 8.1 0.0003035391380788668 83 | 8.2 0.00027465356997214254 84 | 8.3 0.00024851682710795185 85 | 8.4 0.0002248673241788482 86 | 8.5 0.00020346836901064417 87 | 8.6 0.0001841057936675792 88 | 8.7 0.00016658581098763354 89 | 8.8 0.0001507330750954765 90 | 8.9 0.0001363889264820114 91 | 9.0 0.00012340980408667956 92 | 9.1 0.00011166580849011478 93 | 9.2 0.00010103940183709342 94 | 9.3 9.142423147817327e-05 95 | 9.4 8.272406555663223e-05 96 | 9.5 7.48518298877006e-05 97 | 9.6 6.77287364908539e-05 98 | 9.7 6.128349505322213e-05 99 | 9.8 5.5451599432176945e-05 100 | 9.9 5.017468205617528e-05 101 | 10.0 4.5399929762484854e-05 -------------------------------------------------------------------------------- /examples/plot_to_image_file/real_result.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Gnuplot Canvas Graph 5 | 6 | 7 | 8 | 9 | 10 | 11 | 344 | 345 | 346 | 347 | 348 |
349 | 350 | 351 | 356 |
352 | 353 | Sorry, your browser seems not to support the HTML 5 canvas element 354 | 355 |
357 |
358 | 359 | 360 | 361 | -------------------------------------------------------------------------------- /examples/plot_to_image_file/real_result.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 10 | 11 | Gnuplot 12 | Produced by GNUPLOT 5.0 patchlevel 0 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 0 49 | 50 | 51 | 52 | 53 | 0.1 54 | 55 | 56 | 57 | 58 | 0.2 59 | 60 | 61 | 62 | 63 | 0.3 64 | 65 | 66 | 67 | 68 | 0.4 69 | 70 | 71 | 72 | 73 | 0.5 74 | 75 | 76 | 77 | 78 | 0.6 79 | 80 | 81 | 82 | 83 | 0.7 84 | 85 | 86 | 87 | 88 | 0.8 89 | 90 | 91 | 92 | 93 | 0.9 94 | 95 | 96 | 97 | 98 | 1 99 | 100 | 101 | 102 | 103 | 0 104 | 105 | 106 | 107 | 108 | 2 109 | 110 | 111 | 112 | 113 | 4 114 | 115 | 116 | 117 | 118 | 6 119 | 120 | 121 | 122 | 123 | 8 124 | 125 | 126 | 127 | 128 | 10 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | Plotting to png 138 | 139 | 140 | Points from file 141 | 142 | 143 | Points from file 144 | 145 | 146 | 147 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /examples/plot_to_image_file/real_result.txt: -------------------------------------------------------------------------------- 1 | 2 | Plotting to png 3 | 4 | 1 +-+-------+---------+---------+---------+-------+-+ 5 | + + + + + + 6 | || Points from file +-----+ | 7 | 0.9 +-+ +-+ 8 | || | 9 | || | 10 | |+ | 11 | 0.8 +-+ +-+ 12 | | | | 13 | | + | 14 | 0.7 +-+ +-+ 15 | | + | 16 | | | | 17 | | + | 18 | 0.6 +-+| +-+ 19 | | + | 20 | | | | 21 | 0.5 +-+ + +-+ 22 | | | | 23 | | + | 24 | 0.4 +-+ + +-+ 25 | | + | 26 | | | | 27 | | + | 28 | 0.3 +-+ + +-+ 29 | | + | 30 | | ++ | 31 | 0.2 +-+ + +-+ 32 | | + | 33 | | + | 34 | | + | 35 | 0.1 +-+ ++ +-+ 36 | | +++ | 37 | + + ++++++ + + + 38 | 0 +-+-------+---------+---------+---------+-------+-+ 39 | 0 2 4 6 8 10 40 | 41 | -------------------------------------------------------------------------------- /examples/plot_to_string/gnuplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/plot_to_string/gnuplot.png -------------------------------------------------------------------------------- /examples/plot_to_string/gnuplot_gem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/plot_to_string/gnuplot_gem.png -------------------------------------------------------------------------------- /examples/plot_to_string/plot.gnuplot: -------------------------------------------------------------------------------- 1 | set term png size 600,600 2 | set output './gnuplot.png' 3 | set title 'Plotting to png' 4 | plot 'points.data' with lines title 'Points from file' 5 | unset output -------------------------------------------------------------------------------- /examples/plot_to_string/plot.rb: -------------------------------------------------------------------------------- 1 | require 'gnuplotrb' 2 | include GnuplotRB 3 | 4 | plot = Plot.new(['points.data', with: 'lines', title: 'Points from file'], title: 'Plotting to png') 5 | 6 | plot_contents = plot.to_png(size: [600, 600]) 7 | 8 | File.binwrite('./gnuplot_gem.png', plot_contents) 9 | -------------------------------------------------------------------------------- /examples/plot_to_string/points.data: -------------------------------------------------------------------------------- 1 | 0.0 1.0 2 | 0.1 0.9048374180359595 3 | 0.2 0.8187307530779818 4 | 0.3 0.7408182206817179 5 | 0.4 0.6703200460356393 6 | 0.5 0.6065306597126334 7 | 0.6 0.5488116360940264 8 | 0.7 0.4965853037914095 9 | 0.8 0.44932896411722156 10 | 0.9 0.4065696597405991 11 | 1.0 0.36787944117144233 12 | 1.1 0.33287108369807955 13 | 1.2 0.30119421191220214 14 | 1.3 0.2725317930340126 15 | 1.4 0.2465969639416065 16 | 1.5 0.22313016014842982 17 | 1.6 0.20189651799465538 18 | 1.7 0.18268352405273466 19 | 1.8 0.16529888822158653 20 | 1.9 0.14956861922263506 21 | 2.0 0.1353352832366127 22 | 2.1 0.1224564282529819 23 | 2.2 0.11080315836233387 24 | 2.3 0.10025884372280375 25 | 2.4 0.09071795328941251 26 | 2.5 0.0820849986238988 27 | 2.6 0.07427357821433388 28 | 2.7 0.06720551273974976 29 | 2.8 0.06081006262521797 30 | 2.9 0.05502322005640723 31 | 3.0 0.049787068367863944 32 | 3.1 0.0450492023935578 33 | 3.2 0.04076220397836621 34 | 3.3 0.036883167401240015 35 | 3.4 0.03337326996032608 36 | 3.5 0.0301973834223185 37 | 3.6 0.02732372244729256 38 | 3.7 0.024723526470339388 39 | 3.8 0.0223707718561656 40 | 3.9 0.02024191144580439 41 | 4.0 0.01831563888873418 42 | 4.1 0.016572675401761255 43 | 4.2 0.014995576820477703 44 | 4.3 0.013568559012200934 45 | 4.4 0.012277339903068436 46 | 4.5 0.011108996538242306 47 | 4.6 0.010051835744633586 48 | 4.7 0.009095277101695816 49 | 4.8 0.00822974704902003 50 | 4.9 0.007446583070924338 51 | 5.0 0.006737946999085467 52 | 5.1 0.006096746565515638 53 | 5.2 0.0055165644207607716 54 | 5.3 0.004991593906910217 55 | 5.4 0.004516580942612666 56 | 5.5 0.004086771438464067 57 | 5.6 0.003697863716482932 58 | 5.7 0.003345965457471272 59 | 5.8 0.0030275547453758153 60 | 5.9 0.0027394448187683684 61 | 6.0 0.0024787521766663585 62 | 6.1 0.0022428677194858034 63 | 6.2 0.002029430636295734 64 | 6.3 0.0018363047770289071 65 | 6.4 0.001661557273173934 66 | 6.5 0.0015034391929775724 67 | 6.6 0.0013603680375478939 68 | 6.7 0.001230911902673481 69 | 6.8 0.0011137751478448032 70 | 6.9 0.0010077854290485105 71 | 7.0 0.0009118819655545162 72 | 7.1 0.0008251049232659046 73 | 7.2 0.0007465858083766792 74 | 7.3 0.0006755387751938444 75 | 7.4 0.0006112527611295723 76 | 7.5 0.0005530843701478336 77 | 7.6 0.0005004514334406108 78 | 7.7 0.00045282718288679695 79 | 7.8 0.0004097349789797868 80 | 7.9 0.0003707435404590882 81 | 8.0 0.00033546262790251185 82 | 8.1 0.0003035391380788668 83 | 8.2 0.00027465356997214254 84 | 8.3 0.00024851682710795185 85 | 8.4 0.0002248673241788482 86 | 8.5 0.00020346836901064417 87 | 8.6 0.0001841057936675792 88 | 8.7 0.00016658581098763354 89 | 8.8 0.0001507330750954765 90 | 8.9 0.0001363889264820114 91 | 9.0 0.00012340980408667956 92 | 9.1 0.00011166580849011478 93 | 9.2 0.00010103940183709342 94 | 9.3 9.142423147817327e-05 95 | 9.4 8.272406555663223e-05 96 | 9.5 7.48518298877006e-05 97 | 9.6 6.77287364908539e-05 98 | 9.7 6.128349505322213e-05 99 | 9.8 5.5451599432176945e-05 100 | 9.9 5.017468205617528e-05 101 | 10.0 4.5399929762484854e-05 -------------------------------------------------------------------------------- /examples/tons_of_data/generate_data.rb: -------------------------------------------------------------------------------- 1 | File.open('tons_of_data', 'w') do |f| 2 | (1..10000000).each do |x| 3 | xx = x/100000.0 4 | yy = Math.exp(Math.sin(xx)) 5 | f.puts "#{xx} #{yy}" 6 | end 7 | end -------------------------------------------------------------------------------- /examples/tons_of_data/plot.rb: -------------------------------------------------------------------------------- 1 | require 'gnuplotrb' 2 | include GnuplotRB 3 | 4 | 5 | graph = Plot.new(['tons_of_data', title: 'Tons of data', with: 'lines'], term: ['qt', persist: true]) 6 | graph.plot 7 | #Need to change some dataset options? Ok: 8 | plot_with_points = graph.update_dataset(with: 'points', title: 'Plot with points') 9 | plot_with_points.plot 10 | #Need to change the whole plot options? Ok: 11 | plot_interval = graph.options(title: 'Plot on [1..3]', xrange: 1..3) 12 | plot_interval .plot -------------------------------------------------------------------------------- /examples/tons_of_data/result_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/tons_of_data/result_screenshot.png -------------------------------------------------------------------------------- /examples/use_multi_word_option_keys/gnuplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/use_multi_word_option_keys/gnuplot.png -------------------------------------------------------------------------------- /examples/use_multi_word_option_keys/gnuplot_gem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ievgrafov/gnuplotrb/0a3146386ae28fcbe2c09cb6e266fe40ebb659f4/examples/use_multi_word_option_keys/gnuplot_gem.png -------------------------------------------------------------------------------- /examples/use_multi_word_option_keys/plot.gnuplot: -------------------------------------------------------------------------------- 1 | set term png size 600,600 2 | set output './gnuplot.png' 3 | set title "Time spent to run deploy pipeline" 4 | set style data histograms 5 | set style fill pattern border 6 | set yrange [0:2200] 7 | set xlabel 'Number of test' 8 | set ylabel 'Time, s' 9 | $DATA << EOD 10 | 1 312 525 215 1052 11 | 2 630 1050 441 2121 12 | 3 315 701 370 1386 13 | 4 312 514 220 1046 14 | EOD 15 | plot $DATA using 2:xtic(1) title 'Build',\ 16 | $DATA using 3:xtic(1) title 'Test',\ 17 | $DATA using 4:xtic(1) title 'Deploy',\ 18 | $DATA using 5:xtic(1) title 'Overall' 19 | unset output -------------------------------------------------------------------------------- /examples/use_multi_word_option_keys/plot.rb: -------------------------------------------------------------------------------- 1 | require 'gnuplotrb' 2 | include GnuplotRB 3 | 4 | titles = %w{decade Build Test Deploy Overall} 5 | data = [ 6 | [1, 312, 525, 215, 1052], 7 | [2, 630, 1050, 441, 2121], 8 | [3, 315, 701, 370, 1386], 9 | [4, 312, 514, 220, 1046] 10 | ] 11 | x = data.map(&:first) 12 | datasets = (1..4).map do |col| 13 | y = data.map { |row| row[col] } 14 | Dataset.new([x, y], using: '2:xtic(1)', title: titles[col], file: true) 15 | end 16 | 17 | plot = Plot.new( 18 | *datasets, 19 | style_data: 'histograms', 20 | style_fill: 'pattern border', 21 | yrange: 0..2200, 22 | xlabel: 'Number of test', 23 | ylabel: 'Time, s', 24 | title: 'Time spent to run deploy pipeline' 25 | ) 26 | 27 | plot.to_png('./gnuplot_gem.png', size: [600, 600]) 28 | -------------------------------------------------------------------------------- /gnuplotrb.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'gnuplotrb/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = 'gnuplotrb' 8 | spec.version = GnuplotRB::VERSION 9 | spec.authors = ['Ivan Evgrafov'] 10 | spec.email = ['dilcom3107@gmail.com'] 11 | 12 | spec.summary = 'Ruby bindings for gnuplot' 13 | spec.description = 'Renewed ruby bindings for gnuplot. Started at GSoC 2015.' 14 | spec.homepage = 'https://github.com/dilcom/gnuplotrb' 15 | spec.license = 'MIT' 16 | spec.required_ruby_version = '>= 2.0' 17 | 18 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(/^(test|spec|unimplemented_features|examples|future_work|notebooks|\..+)/) } 19 | spec.require_paths = ['lib'] 20 | 21 | spec.add_runtime_dependency 'hamster', '~> 1.0' 22 | spec.add_development_dependency 'bundler', '~> 1.7' 23 | spec.add_development_dependency 'rake', '~> 10.0' 24 | spec.add_development_dependency 'rspec', '~> 3.2' 25 | spec.add_development_dependency 'yard', '~> 0.8' 26 | spec.add_development_dependency 'rubocop', '~> 0.29' 27 | spec.add_development_dependency 'codeclimate-test-reporter' 28 | spec.add_development_dependency 'chunky_png' 29 | spec.add_development_dependency 'daru' 30 | end 31 | -------------------------------------------------------------------------------- /lib/gnuplotrb.rb: -------------------------------------------------------------------------------- 1 | require 'tempfile' 2 | require 'hamster' 3 | require 'open3' 4 | require 'base64' 5 | 6 | ## 7 | # Require gem if it's available in current gemspace. 8 | # 9 | # @param name [String] gem name 10 | # @return [Boolean] true if gem was loaded, false otherwise 11 | def require_if_available(name) 12 | require name 13 | rescue LoadError 14 | false 15 | end 16 | 17 | require_if_available('daru') 18 | 19 | require 'gnuplotrb/external_classes/string' 20 | require 'gnuplotrb/external_classes/array' 21 | require 'gnuplotrb/external_classes/daru' 22 | 23 | require 'gnuplotrb/version' 24 | require 'gnuplotrb/staff/settings' 25 | require 'gnuplotrb/mixins/option_handling' 26 | require 'gnuplotrb/mixins/error_handling' 27 | require 'gnuplotrb/mixins/plottable' 28 | require 'gnuplotrb/staff/terminal' 29 | require 'gnuplotrb/staff/datablock' 30 | require 'gnuplotrb/staff/dataset' 31 | require 'gnuplotrb/fit' 32 | require 'gnuplotrb/plot' 33 | require 'gnuplotrb/splot' 34 | require 'gnuplotrb/multiplot' 35 | require 'gnuplotrb/animation' 36 | -------------------------------------------------------------------------------- /lib/gnuplotrb/animation.rb: -------------------------------------------------------------------------------- 1 | module GnuplotRB 2 | ## 3 | # Animation allows to create gif animation with given plots 4 | # as frames. Possible frames: Plot, Splot, Multiplot. 5 | # More about its usage in 6 | # {animation notebook}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/animated_plots.ipynb]. 7 | # 8 | # == Options 9 | # Animations has several specific options: 10 | # * animate - allows to get animated gif's. Possible values are true (just turn on animation), 11 | # ot hash with suboptions (:loop - count of loops, default 0 - infinity$; 12 | # :delay - delay between frames; :optimize - boolean, reduces file size). 13 | # * size - size of gif file in pixels (size: [500, 500]) or (size: 500) 14 | # * background - background color 15 | # * transparent 16 | # * enhanced 17 | # * font 18 | # * fontscale 19 | # * crop 20 | # 21 | # Animation ignores :term option and does not have methods like #to_png or #to_svg. 22 | # One can also set animation any options related to Plot and they will be considered 23 | # by all nested plots (if they does not override it with their own values). 24 | # 25 | # Animation inherits all plot array handling methods from Multiplot 26 | # and adds aliases for them (#plots -> #frames; #update_frame! -> #update_plot!; etc). 27 | class Animation < Multiplot 28 | ## 29 | # *Plot* here is also named as *frame* 30 | alias_method :frames, :plots 31 | alias_method :update_frame, :update_plot 32 | alias_method :replace_frame, :replace_plot 33 | alias_method :add_frame, :add_plot 34 | alias_method :add_frames, :add_plots 35 | alias_method :remove_frame, :remove_plot 36 | alias_method :update_frame!, :update_plot! 37 | alias_method :replace_frame!, :replace_plot! 38 | alias_method :add_frame!, :add_plot! 39 | alias_method :add_frames!, :add_plots! 40 | alias_method :remove_frame!, :remove_plot! 41 | 42 | ## 43 | # This method creates a gif animation where frames are plots 44 | # already contained by Animation object. 45 | # 46 | # Options passed in #plot have priority over those which were set before. 47 | # 48 | # Inner options of Plots have the highest priority (except 49 | # :term and :output which are ignored). 50 | # 51 | # @param path [String] path to new gif file that will be created as a result 52 | # @param options [Hash] see note about available options in top class documentation 53 | # @return [nil] if path to output file given 54 | # @return [String] gif file contents if no path to output file given 55 | def plot(path = nil, **options) 56 | options[:output] ||= path 57 | plot_options = mix_options(options) do |plot_opts, anim_opts| 58 | plot_opts.merge(term: ['gif', anim_opts]) 59 | end.to_h 60 | need_output = plot_options[:output].nil? 61 | plot_options[:output] = Dir::Tmpname.make_tmpname('anim', 0) if need_output 62 | terminal = Terminal.new 63 | multiplot(terminal, plot_options) 64 | # guaranteed wait for plotting to finish 65 | terminal.close 66 | if need_output 67 | result = File.binread(plot_options[:output]) 68 | File.delete(plot_options[:output]) 69 | else 70 | result = nil 71 | end 72 | result 73 | end 74 | 75 | ## 76 | # #to_|term_name| methods are not supported by animation 77 | def to_specific_term(*_) 78 | fail 'Specific terminals are not supported by Animation' 79 | end 80 | 81 | ## 82 | # This method is used to embed gif animations 83 | # into iRuby notebooks. 84 | def to_iruby 85 | gif_base64 = Base64.encode64(plot) 86 | ['text/html', ""] 87 | end 88 | 89 | private 90 | 91 | ## 92 | # Dafault options to be used for that plot 93 | def default_options 94 | { 95 | animate: { 96 | delay: 10, 97 | loop: 0, 98 | optimize: true 99 | } 100 | } 101 | end 102 | 103 | ## 104 | # This plot have some specific options which 105 | # should be handled different way than others. 106 | # Here are keys of this options. 107 | def specific_keys 108 | %w( 109 | animate 110 | size 111 | background 112 | transparent 113 | enhanced 114 | rounded 115 | butt 116 | linewidth 117 | dashlength 118 | tiny 119 | small 120 | medium 121 | large 122 | giant 123 | font 124 | fontscale 125 | crop 126 | ) 127 | end 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /lib/gnuplotrb/external_classes/array.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # Methods to take data for GnuplotRB plots. 3 | class Array 4 | # taken for example from current gnuplot bindings 5 | # @return [String] array converted to Gnuplot format 6 | def to_gnuplot_points 7 | return '' if self.empty? 8 | case self[0] 9 | when Array 10 | self[0].zip(*self[1..-1]).map { |a| a.join(' ') }.join("\n") 11 | when Numeric 12 | join("\n") 13 | else 14 | self[0].zip(*self[1..-1]).to_gnuplot_points 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/gnuplotrb/external_classes/daru.rb: -------------------------------------------------------------------------------- 1 | if defined? Daru 2 | ## 3 | # See {daru}[https://github.com/v0dro/daru] and 4 | # {plotting from daru}[https://github.com/dilcom/gnuplotrb/blob/master/notebooks/plotting_from_daru.ipynb] 5 | module Daru 6 | ## 7 | # Methods to take data for GnuplotRB plots. 8 | class DataFrame 9 | ## 10 | # Convert DataFrame to Gnuplot format. 11 | # 12 | # @return [String] data converted to Gnuplot format 13 | def to_gnuplot_points 14 | result = '' 15 | each_row_with_index do |row, index| 16 | quoted = (index.is_a?(String) || index.is_a?(Symbol)) && index.length > 0 17 | result += quoted ? "\"#{index}\" " : "#{index} " 18 | result += row.to_a.join(' ') 19 | result += "\n" 20 | end 21 | result 22 | end 23 | end 24 | 25 | ## 26 | # Methods to take data for GnuplotRB plots. 27 | class Vector 28 | ## 29 | # Convert Vector to Gnuplot format. 30 | # 31 | # @return [String] data converted to Gnuplot format 32 | def to_gnuplot_points 33 | result = '' 34 | each_with_index do |value, index| 35 | quoted = (index.is_a?(String) || index.is_a?(Symbol)) && index.length > 0 36 | result += quoted ? "\"#{index}\" " : "#{index} " 37 | result += "#{value}\n" 38 | end 39 | result 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/gnuplotrb/external_classes/string.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # Methods to take data for GnuplotRB plots. 3 | class String 4 | # @return [String] data converted to Gnuplot format 5 | alias_method :to_gnuplot_points, :clone 6 | end 7 | -------------------------------------------------------------------------------- /lib/gnuplotrb/fit.rb: -------------------------------------------------------------------------------- 1 | module GnuplotRB 2 | ## 3 | # Contains methods relating to Gnuplot's fit function. Covered in 4 | # {fit notebook}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/fitting_data.ipynb]. 5 | # 6 | # You can also see original gnuplot's fit in 7 | # {gnuplot doc}[http://www.gnuplot.info/docs_5.0/gnuplot.pdf] p. 122. 8 | module Fit 9 | ## 10 | # Fit given data with function. 11 | # 12 | # Fit waits for output from gnuplot Settings.max_fit_delay and throw exception if gets nothing. 13 | # One can change this value in order to wait longer (if huge datasets is fitted). 14 | # 15 | # @param data [#to_gnuplot_points] method accepts the same sources as Dataset.new 16 | # and Dataset object 17 | # @param :function [String] function to fit data with 18 | # @param :initials [Hash] initial values for coefficients used in fitting 19 | # @param :term_options [Hash] terminal options that should be setted to terminal before fit. 20 | # You can see them in Plot's documentation (or even better in gnuplot doc) 21 | # Most useful here are ranges (xrange, yrange etc) and fit option which tunes fit parameters 22 | # (see {gnuplot doc}[http://www.gnuplot.info/docs_5.0/gnuplot.pdf] p. 122) 23 | # @param options [Hash] options passed to Gnuplot's fit such as *using*. They are covered in 24 | # {gnuplot doc}[http://www.gnuplot.info/docs_5.0/gnuplot.pdf] (pp. 69-74) 25 | # 26 | # @return [Hash] hash with four elements: 27 | # - :formula_ds - dataset with best fit curve as data 28 | # - :coefficients - hash of calculated coefficients. So if you gave 29 | # ``{ initials: {a: 1, b: 1, c: 1} }`` it will return hash with keys :a, :b, :c and its values 30 | # - :deltas - Gnuplot calculates possible deltas for coefficients during fitting and 31 | # deltas hash contains this deltas 32 | # - :data - pointer to Datablock with given data 33 | # @example 34 | # fit(some_data, function: 'exp(a/x)', initials: {a: 10}, term_option: { xrange: 1..100 }) 35 | # fit(some_dataset, using: '1:2:3') 36 | def fit(data, function: 'a2*x*x+a1*x+a0', initials: { a2: 1, a1: 1, a0: 1 }, term_options: {}, **options) 37 | dataset = data.is_a?(Dataset) ? Dataset.new(data.data) : Dataset.new(data) 38 | opts_str = OptionHandling.ruby_class_to_gnuplot(options) 39 | output = gnuplot_fit(function, dataset, opts_str, initials, term_options) 40 | res = parse_output(initials.keys, function, output) 41 | { 42 | formula_ds: Dataset.new(res[2], title: 'Fit formula'), 43 | coefficients: res[0], 44 | deltas: res[1], 45 | data: dataset 46 | } 47 | end 48 | 49 | ## 50 | # Shortcut for fit with polynomial. Degree here is max power of *x* in polynomial. 51 | # 52 | # @param data [#to_gnuplot_points] method accepts the same sources as Dataset.new 53 | # and Dataset object 54 | # @param :degree [Integer] degree of polynomial 55 | # @param options [Hash] all of this options will be passed to #fit so you 56 | # can set here any options listed in its docs. If you pass here :initials hash, it 57 | # will be merged into default initals hash. Formula by default is 58 | # "xn*x**n + ... + x0*x**0", initials by default "{ an: 1, ..., a0: 1 }" 59 | # 60 | # @return [Hash] hash with four elements: 61 | # - :formula_ds - dataset with best fit curve as data 62 | # - :coefficients - hash of calculated coefficients. So for degree = 3 63 | # it will return hash with keys :a3, :a2, :a1, :a0 and calculated values 64 | # - :deltas - Gnuplot calculates possible deltas for coefficients during fitting and 65 | # deltas hash contains this deltas 66 | # - :data - pointer to Datablock with given data 67 | # @example 68 | # fit_poly(some_data, degree: 5, initials: { a4: 10, a2: -1 }, term_option: { xrange: 1..100 }) 69 | # #=> The same as: 70 | # #=> fit( 71 | # #=> some_data, 72 | # #=> function: 'a5*x**5 + a4*x**4 + ... + a0*x**0', 73 | # #=> initals: {a5: 1, a4: 10, a3: 1, a2: -1, a1: 1, a0: 1}, 74 | # #=> term_option: { xrange: 1..100 } 75 | # #=> ) 76 | def fit_poly(data, degree: 2, **options) 77 | sum_count = degree + 1 78 | initials = {} 79 | sum_count.times { |i| initials["a#{i}".to_sym] = 1 } 80 | options[:initials] = initials.merge(options[:initials] || {}) 81 | function = sum_count.times.map { |i| "a#{i}*x**#{i}" }.join(' + ') 82 | fit(data, **options, function: function) 83 | end 84 | 85 | ## 86 | # @!method fit_exp(data, **options) 87 | # @!method fit_log(data, **options) 88 | # @!method fit_sin(data, **options) 89 | # 90 | # Shortcuts for fitting with several math functions (exp, log, sin). 91 | # 92 | # @param data [#to_gnuplot_points] method accepts the same sources as Dataset.new 93 | # and Dataset object 94 | # @param options [Hash] all of this options will be passed to #fit so you 95 | # can set here any options listed in its docs. If you pass here :initials hash, it 96 | # will be merged into default initals hash. Formula by default is 97 | # "yscale * (yoffset + #{function name}((x - xoffset) / xscale))", initials by default 98 | # "{ yoffset: 0.1, xoffset: 0.1, yscale: 1, xscale: 1 }" 99 | # 100 | # @return [Hash] hash with four elements: 101 | # - :formula_ds - dataset with best fit curve as data 102 | # - :coefficients - hash of calculated coefficients. So for this case 103 | # it will return hash with keys :yoffset, :xoffset, :yscale, :xscale and calculated values 104 | # - :deltas - Gnuplot calculates possible deltas for coefficients during fitting and 105 | # deltas hash contains this deltas 106 | # - :data - pointer to Datablock with given data 107 | # 108 | # @example 109 | # fit_exp(some_data, initials: { yoffset: -11 }, term_option: { xrange: 1..100 }) 110 | # #=> The same as: 111 | # #=> fit( 112 | # #=> some_data, 113 | # #=> function: 'yscale * (yoffset + exp((x - xoffset) / xscale))', 114 | # #=> initals: { yoffset: -11, xoffset: 0.1, yscale: 1, xscale: 1 }, 115 | # #=> term_option: { xrange: 1..100 } 116 | # #=> ) 117 | # fit_log(...) 118 | # fit_sin(...) 119 | %w(exp log sin).map do |fname| 120 | define_method("fit_#{fname}".to_sym) do |data, **options| 121 | options[:initials] = { 122 | yoffset: 0.1, 123 | xoffset: 0.1, 124 | yscale: 1, 125 | xscale: 1 126 | }.merge(options[:initials] || {}) 127 | function = "yscale * (yoffset + #{fname} ((x - xoffset) / xscale))" 128 | fit(data, **options, function: function) 129 | end 130 | end 131 | 132 | private 133 | 134 | ## 135 | # It takes some time to produce output so here we need 136 | # to wait for it. 137 | # 138 | # Max time to wait is stored in Settings.max_fit_delay, so one 139 | # can change it in order to wait longer. 140 | def wait_for_output(term, variables) 141 | # now we should catch 'error' from terminal: it will contain approximation data 142 | # but we can get a real error instead of output, so lets wait for limited time 143 | start = Time.now 144 | output = '' 145 | until output_ready?(output, variables) 146 | begin 147 | term.check_errors(raw: true) 148 | rescue GnuplotRB::GnuplotError => e 149 | output += e.message 150 | end 151 | if Time.now - start > Settings.max_fit_delay 152 | fail GnuplotError, "Seems like there is an error in gnuplotrb: #{output}" 153 | end 154 | end 155 | output 156 | end 157 | 158 | ## 159 | # Check if current output contains all the 160 | # variables given to fit. 161 | def output_ready?(output, variables) 162 | output =~ /Final set .*#{variables.join('.*')}/ 163 | end 164 | 165 | ## 166 | # Parse Gnuplot's output to get coefficients and their deltas 167 | # from it. Also replaces coefficients in given function with 168 | # exact values. 169 | def parse_output(variables, function, output) 170 | plottable_function = " #{function.clone} " 171 | coefficients = {} 172 | deltas = {} 173 | variables.each do |var| 174 | value, error = output.scan(%r{#{var} *= ([^ ]+) *\+/\- ([^ ]+)})[0] 175 | plottable_function.gsub!(/#{var}([^0-9a-zA-Z])/) { value + Regexp.last_match(1) } 176 | coefficients[var] = value.to_f 177 | deltas[var] = error.to_f 178 | end 179 | [coefficients, deltas, plottable_function] 180 | end 181 | 182 | ## 183 | # Make fit command and send it to gnuplot 184 | def gnuplot_fit(function, data, options, initials, term_options) 185 | variables = initials.keys 186 | term = Terminal.new 187 | term.set(term_options) 188 | initials.each { |var_name, value| term.stream_puts "#{var_name} = #{value}" } 189 | command = "fit #{function} #{data.to_s(term, without_options: true)} " \ 190 | "#{options} via #{variables.join(',')}" 191 | term.stream_puts(command) 192 | output = wait_for_output(term, variables) 193 | begin 194 | term.close 195 | rescue GnuplotError 196 | # Nothing interesting here. 197 | # If we had an error, we never reach this line. 198 | # Error here may be only additional information 199 | # such as correlation matrix. 200 | end 201 | output 202 | end 203 | end 204 | end 205 | -------------------------------------------------------------------------------- /lib/gnuplotrb/mixins/error_handling.rb: -------------------------------------------------------------------------------- 1 | module GnuplotRB 2 | ## 3 | # Just a new error name 4 | class GnuplotError < ArgumentError 5 | end 6 | 7 | ## 8 | # Mixin for classes that need to run subprocess and 9 | # handle errors from its stderr. 10 | module ErrorHandling 11 | ## 12 | # Check if there were errors in previous commands. 13 | # Throws GnuplotError in case of any errors. 14 | def check_errors(raw: false) 15 | return if @err_array.empty? 16 | command = '' 17 | rest = '' 18 | @semaphore.synchronize do 19 | command = @err_array.first 20 | rest = @err_array[1..-1].join('; ') 21 | @err_array.clear 22 | end 23 | message = if raw 24 | "#{command};#{rest}}" 25 | else 26 | "Error in previous command (\"#{command}\"): \"#{rest}\"" 27 | end 28 | fail GnuplotError, message 29 | end 30 | 31 | private 32 | 33 | ## 34 | # Start new thread that will read stderr given as stream 35 | # and add errors into @err_array. 36 | def handle_stderr(stream) 37 | @err_array = [] 38 | # synchronize access to @err_array 39 | @semaphore = Mutex.new 40 | Thread.new do 41 | until (line = stream.gets).nil? 42 | line.strip! 43 | @semaphore.synchronize { @err_array << line if line.size > 3 } 44 | end 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/gnuplotrb/mixins/option_handling.rb: -------------------------------------------------------------------------------- 1 | module GnuplotRB 2 | ## 3 | # This module contains methods which are mixed into several classes 4 | # to set, get and convert their options. 5 | module OptionHandling 6 | class << self 7 | # Some values of options should be quoted to be read by gnuplot properly 8 | # 9 | # @todo update list with data from gnuplot documentation !!! 10 | QUOTED_OPTIONS = %w( 11 | title 12 | output 13 | xlabel 14 | x2label 15 | ylabel 16 | y2label 17 | clabel 18 | cblabel 19 | zlabel 20 | rgb 21 | font 22 | background 23 | format 24 | format_x 25 | format_y 26 | format_xy 27 | format_x2 28 | format_y2 29 | format_z 30 | format_cb 31 | timefmt 32 | dt 33 | dashtype 34 | ) 35 | 36 | private_constant :QUOTED_OPTIONS 37 | 38 | ## 39 | # Replace '_' with ' ' is made to allow passing several options 40 | # with the same first word of key. See issue #7 for more info. 41 | # @param key [Symbol, String] key to modify 42 | # @return [String] given key with '_' replaced with ' ' 43 | def string_key(key) 44 | key.to_s.gsub(/_/) { ' ' } + ' ' 45 | end 46 | 47 | ## 48 | # Recursive function that converts Ruby option to gnuplot string 49 | # 50 | # @param key [Symbol] name of option in gnuplot 51 | # @param option an option that should be converted 52 | # @example 53 | # option_to_string(['png', size: [300, 300]]) 54 | # #=> 'png size 300,300' 55 | # option_to_string(xrange: 0..100) 56 | # #=> 'xrange [0:100]' 57 | # option_to_string(multiplot: true) 58 | # #=> 'multiplot' 59 | def option_to_string(key = nil, option) 60 | return string_key(key) if !!option == option # check for boolean 61 | value = ruby_class_to_gnuplot(option) 62 | value = "\"#{value}\"" if QUOTED_OPTIONS.include?(key.to_s) 63 | ## :+ here is necessary, because using #{value} will remove quotes 64 | value = string_key(key) + value if key 65 | value 66 | end 67 | 68 | ## 69 | # @private 70 | # Method for inner use. 71 | # Needed to convert several ruby classes into 72 | # value that should be piped to gnuplot. 73 | def ruby_class_to_gnuplot(option_object) 74 | case option_object 75 | when Array 76 | option_object.map { |el| option_to_string(el) } 77 | .join(option_object[0].is_a?(Numeric) ? ',' : ' ') 78 | when Hash 79 | option_object.map { |i_key, i_val| option_to_string(i_key, i_val) } 80 | .join(' ') 81 | when Range 82 | "[#{option_object.begin}:#{option_object.end}]" 83 | else 84 | option_object.to_s 85 | end 86 | end 87 | 88 | ## 89 | # Check if given terminal available for use. 90 | # 91 | # @param terminal [String] terminal to check (e.g. 'png', 'qt', 'gif') 92 | # @return [Boolean] true or false 93 | def valid_terminal?(terminal) 94 | Settings.available_terminals.include?(terminal) 95 | end 96 | 97 | ## 98 | # Check if given options are valid for gnuplot. 99 | # Raises ArgumentError if invalid options found. 100 | # Now checks only terminal name. 101 | # 102 | # @param options [Hash] options to check (e.g. "{ term: 'qt', title: 'Plot title' }") 103 | def validate_terminal_options(options) 104 | terminal = options[:term] 105 | return unless terminal 106 | terminal = terminal[0] if terminal.is_a?(Array) 107 | message = 'Seems like your Gnuplot does not ' \ 108 | "support that terminal (#{terminal}), please see " \ 109 | 'supported terminals with Settings::available_terminals' 110 | fail(ArgumentError, message) unless valid_terminal?(terminal) 111 | end 112 | end 113 | 114 | ## 115 | # @private 116 | # You should implement #initialize in classes that use OptionsHelper 117 | def initialize(*_) 118 | fail NotImplementedError, 'You should implement #initialize' \ 119 | ' in classes that use OptionsHelper!' 120 | end 121 | 122 | ## 123 | # @private 124 | # You should implement #new_with_options in classes that use OptionsHelper 125 | def new_with_options(*_) 126 | fail NotImplementedError, 'You should implement #new_with_options' \ 127 | ' in classes that use OptionsHelper!' 128 | end 129 | 130 | ## 131 | # Create new Plot (or Dataset or Splot or Multiplot) object where current 132 | # options are merged with given. If no options 133 | # given it will just return existing set of options. 134 | # 135 | # @param options [Hash] options to add 136 | # @return [Dataset, Splot, Multiplot] new object created with given options 137 | # @return [Hamster::Hash] current options if given options empty 138 | # @example 139 | # sin_graph = Plot.new(['sin(x)', title: 'Sin'], title: 'Sin on [0:3]', xrange: 0..3) 140 | # sin_graph.plot 141 | # sin_graph_update = sin_graph.options(title: 'Sin on [-10:10]', xrange: -10..10) 142 | # sin_graph_update.plot 143 | # # sin_graph IS NOT affected 144 | def options(**options) 145 | @options ||= Hamster::Hash.new 146 | if options.empty? 147 | @options 148 | else 149 | new_with_options(@options.merge(options)) 150 | end 151 | end 152 | 153 | ## 154 | # Update existing Plot (or Dataset or Splot or Multiplot) object with given options. 155 | # 156 | # @param options [Hash] options to add 157 | # @return [Dataset, Splot, Multiplot] self 158 | # @example 159 | # sin_graph = Plot.new(['sin(x)', title: 'Sin'], title: 'Sin on [0:3]', xrange: 0..3) 160 | # sin_graph.plot 161 | # sin_graph.options!(title: 'Sin on [-10:10]', xrange: -10..10) 162 | # sin_graph.plot 163 | # # second #plot call will plot not the same as first, sin_graph IS affected 164 | def options!(**options) 165 | @options = @options ? @options.merge(options) : Hamster::Hash.new(options) 166 | self 167 | end 168 | 169 | private 170 | 171 | ## 172 | # Return current option value if no value given. Create new 173 | # object with given option set if value given. 174 | def option(key, *value) 175 | if value.empty? 176 | value = options[key] 177 | value = value[0] if value && value.size == 1 178 | value 179 | else 180 | options(key => value) 181 | end 182 | end 183 | 184 | ## 185 | # Just set an option. 186 | def option!(key, *value) 187 | options!(key => value) 188 | end 189 | end 190 | end 191 | -------------------------------------------------------------------------------- /lib/gnuplotrb/mixins/plottable.rb: -------------------------------------------------------------------------------- 1 | module GnuplotRB 2 | ## 3 | # This module contains methods that should be mixed into 4 | # plottable classes. It includes OptionHandling and 5 | # implements several plotting methods. 6 | module Plottable 7 | include OptionHandling 8 | 9 | ## 10 | # @private 11 | # You should implement #plot in classes that are Plottable 12 | def plot(*_) 13 | fail NotImplementedError, 'You should implement #plot in classes that are Plottable!' 14 | end 15 | 16 | ## 17 | # In this gem #method_missing is used both to handle 18 | # options and to handle plotting to specific terminal. 19 | # 20 | # == Options handling 21 | # === Overview 22 | # You may set options using #option_name(option_value) method. 23 | # A new object will be constructed with selected option set. 24 | # And finally you can get current value of any option using 25 | # #options_name without arguments. 26 | # === Arguments 27 | # * *option_value* - value to set an option. If none given 28 | # method will just return current option's value 29 | # === Examples 30 | # plot = Splot.new 31 | # new_plot = plot.title('Awesome plot') 32 | # plot.title #=> nil 33 | # new_plot.title #=> 'Awesome plot' 34 | # 35 | # == Plotting to specific term 36 | # === Overview 37 | # Gnuplot offers possibility to output graphics to many image formats. 38 | # The easiest way to to so is to use #to_ methods. 39 | # === Arguments 40 | # * *options* - set of options related to terminal (size, font etc). 41 | # Be careful, some terminals have their own specific options. 42 | # === Examples 43 | # # font options specific for png term 44 | # multiplot.to_png('./result.png', size: [300, 500], font: ['arial', 12]) 45 | # # font options specific for svg term 46 | # content = multiplot.to_svg(size: [100, 100], fname: 'Arial', fsize: 12) 47 | def method_missing(meth_id, *args) 48 | meth = meth_id.id2name 49 | case 50 | when meth[0..2] == 'to_' 51 | term = meth[3..-1] 52 | super unless OptionHandling.valid_terminal?(term) 53 | to_specific_term(term, *args) 54 | when meth[-1] == '!' 55 | option!(meth[0..-2].to_sym, *args) 56 | when meth[-1] == '=' 57 | option!(meth[0..-2].to_sym, *args) 58 | option(meth[0..-2].to_sym) 59 | else 60 | option(meth_id, *args) 61 | end 62 | end 63 | 64 | ## 65 | # @return [true] for existing methods and 66 | # #to_|term_name| when name is a valid terminal type. 67 | # @return [false] otherwise 68 | def respond_to?(meth_id) 69 | # Next line is here to force iRuby use #to_iruby 70 | # instead of #to_svg. 71 | return super if defined? IRuby 72 | meth = meth_id.id2name 73 | term = meth[0..2] == 'to_' && OptionHandling.valid_terminal?(meth[3..-1]) 74 | term || super 75 | end 76 | 77 | ## 78 | # This method is used to embed plottable objects into iRuby notebooks. There is 79 | # {a notebook}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/basic_usage.ipynb] 80 | # with examples of its usage. 81 | def to_iruby 82 | available_terminals = { 83 | 'png' => 'image/png', 84 | 'pngcairo' => 'image/png', 85 | 'jpeg' => 'image/jpeg', 86 | 'svg' => 'image/svg+xml', 87 | 'dumb' => 'text/plain' 88 | } 89 | terminal, options = term.is_a?(Array) ? [term[0], term[1]] : [term, {}] 90 | terminal = 'svg' unless available_terminals.keys.include?(terminal) 91 | [available_terminals[terminal], send("to_#{terminal}".to_sym, **options)] 92 | end 93 | 94 | ## 95 | # @private 96 | # Output plot to specific terminal (possibly some file). 97 | # Explicit use should be avoided. This method is called from #method_missing 98 | # when it handles method names like #to_png(options). 99 | # 100 | # @param trminal [String] terminal name ('png', 'svg' etc) 101 | # @param path [String] path to output file, if none given it will output to temp file 102 | # and then read it and return binary contents of file 103 | # @param options [Hash] used in #plot 104 | # @example 105 | # ## plot here may be Plot, Splot, Multiplot or any other plottable class 106 | # plot.to_png('./result.png', size: [300, 500]) 107 | # contents = plot.to_svg(size: [100, 100]) 108 | # plot.to_dumb('./result.txt', size: [30, 15]) 109 | def to_specific_term(terminal, path = nil, **options) 110 | if path 111 | result = plot(term: [terminal, options], output: path) 112 | else 113 | path = Dir::Tmpname.make_tmpname(terminal, 0) 114 | plot(term: [terminal, options], output: path) 115 | result = File.binread(path) 116 | File.delete(path) 117 | end 118 | result 119 | end 120 | 121 | ## 122 | # @return [Terminal] terminal object linked with this Plottable object 123 | def own_terminal 124 | @terminal ||= Terminal.new 125 | end 126 | 127 | ## 128 | # @!method xrange(value = nil) 129 | # @!method yrange(value = nil) 130 | # @!method title(value = nil) 131 | # @!method option_name(value = nil) 132 | # Clone existing object and set new options value in created one or just return 133 | # existing value if nil given. 134 | # 135 | # Method is handled by #method_missing. 136 | # 137 | # You may set options using #option_name(option_value) method. 138 | # A new object will be constructed with selected option set. 139 | # And finally you can get current value of any option using 140 | # #options_name without arguments. 141 | # 142 | # Available options are listed in Plot, Splot, Multiplot etc class top level doc. 143 | # 144 | # @param value new value for option 145 | # @return new object with option_name set to *value* if value given 146 | # @return old option value if no value given 147 | # 148 | # @example 149 | # plot = Splot.new 150 | # new_plot = plot.title('Awesome plot') 151 | # plot.title #=> nil 152 | # new_plot.title #=> 'Awesome plot' 153 | 154 | ## 155 | # @!method xrange!(value) 156 | # @!method yrange!(value) 157 | # @!method title!(value) 158 | # @!method option_name!(value) 159 | # Set value for an option. 160 | # 161 | # Method is handled by #method_missing. 162 | # 163 | # You may set options using obj.option_name!(option_value) or 164 | # obj.option_name = option_value methods. 165 | # 166 | # Available options are listed in Plot, Splot, Multiplot etc class top level doc. 167 | # 168 | # @param value new value for option 169 | # @return self 170 | # 171 | # @example 172 | # plot = Splot.new 173 | # plot.title #=> nil 174 | # plot.title!('Awesome plot') 175 | # plot.title #=> 'Awesome plot' 176 | # 177 | # @example 178 | # plot = Splot.new 179 | # plot.title #=> nil 180 | # plot.title = 'Awesome plot' 181 | # plot.title #=> 'Awesome plot' 182 | 183 | ## 184 | # @!method to_png(path = nil, **options) 185 | # @!method to_svg(path = nil, **options) 186 | # @!method to_gif(path = nil, **options) 187 | # @!method to_canvas(path = nil, **options) 188 | # Output to plot to according image format. 189 | # 190 | # All of #to_|terminal_name| methods are handled with #method_missing. 191 | # 192 | # Gnuplot offers possibility to output graphics to many image formats. 193 | # The easiest way to to so is to use #to_ methods. 194 | # 195 | # @param path [String] path to save plot file to. 196 | # @param options [Hash] specific terminal options like 'size', 197 | # 'font' etc 198 | # 199 | # @return [String] contents of plotted file unless path given 200 | # @return self if path given 201 | # 202 | # @example 203 | # # font options specific for png term 204 | # multiplot.to_png('./result.png', size: [300, 500], font: ['arial', 12]) 205 | # # font options specific for svg term 206 | # content = multiplot.to_svg(size: [100, 100], fname: 'Arial', fsize: 12) 207 | end 208 | end 209 | -------------------------------------------------------------------------------- /lib/gnuplotrb/multiplot.rb: -------------------------------------------------------------------------------- 1 | module GnuplotRB 2 | ## 3 | # Multiplot allows to place several plots on one layout. 4 | # It's usage is covered in 5 | # {multiplot notebook}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/multiplot_layout.ipynb]. 6 | # 7 | # == Options 8 | # Most of Multiplot options are the same as in Plot so one can also set any options related 9 | # to Plot and they will be considered by all nested plots 10 | # (if they does not override it with their own values). 11 | # 12 | # There are only 2 specific options: 13 | # * title - set title for the whole layout (above all the plots) 14 | # * layout - set layout size, examples: 15 | # { layout : [1, 3] } # 3 plots, 1 row, 3 columns 16 | # { layout : [2, 2] } # 4 plots, 2 rows, 2 columns 17 | class Multiplot 18 | include Plottable 19 | ## 20 | # @return [Array] Array of plots contained by this object 21 | attr_reader :plots 22 | 23 | ## 24 | # @param plots [Plot, Splot, Hamster::Vector] Hamster vector (or just sequence) with Plot 25 | # or Splot objects which should be placed on this multiplot layout 26 | # @param options [Hash] see options in top class docs 27 | def initialize(*plots, **options) 28 | @plots = plots[0].is_a?(Hamster::Vector) ? plots[0] : Hamster::Vector.new(plots) 29 | @options = Hamster.hash(options) 30 | OptionHandling.validate_terminal_options(@options) 31 | yield(self) if block_given? 32 | end 33 | 34 | ## 35 | # Output all the plots to term (if given) or to this Multiplot's own terminal. 36 | # 37 | # @param term [Terminal] Terminal to plot to 38 | # @param multiplot_part [Boolean] placeholder, does not really needed and should not be used 39 | # @param options [Hash] see options in top class docs. 40 | # Options passed here have priority over already set. 41 | # @return [Multiplot] self 42 | def plot(term = nil, multiplot_part: false, **options) 43 | plot_options = mix_options(options) do |plot_opts, mp_opts| 44 | plot_opts.merge(multiplot: mp_opts.to_h) 45 | end 46 | terminal = term || (plot_options[:output] ? Terminal.new : own_terminal) 47 | multiplot(terminal, plot_options) 48 | if plot_options[:output] 49 | # guaranteed wait for plotting to finish 50 | terminal.close unless term 51 | # not guaranteed wait for plotting to finish 52 | # work bad with terminals like svg and html 53 | sleep 0.01 until File.size?(plot_options[:output]) 54 | end 55 | self 56 | end 57 | 58 | ## 59 | # Create new updated Multiplot object 60 | # where plot (Plot or Splot object) at *position* will 61 | # be replaced with the new one created from it by updating. 62 | # To update a plot you can pass some options for it or a 63 | # block, that should take existing plot (with new options if 64 | # you gave them) and return a plot too. 65 | # 66 | # Method yields new created Plot or Splot to allow you update it manually. 67 | # 68 | # @param position [Integer] position of plot which you need to update 69 | # (by default first plot is updated) 70 | # @param options [Hash] options to set into updated plot 71 | # @return [Multiplot] self 72 | # @yieldparam plot [Plot, Splot] a new plot 73 | # @yieldreturn [Plot, Splot] changed plot 74 | # @example 75 | # mp = Multiplot.new(Plot.new('sin(x)'), Plot.new('cos(x)'), layout: [2,1]) 76 | # updated_mp = mp.update_plot(title: 'Sin(x) and Exp(x)') { |sinx| sinx.add!('exp(x)') } 77 | # # mp IS NOT affected 78 | def update_plot(position = 0, **options) 79 | return self unless block_given? if options.empty? 80 | replacement = @plots[position].options(options) 81 | replacement = yield(replacement) if block_given? 82 | replace_plot(position, replacement) 83 | end 84 | 85 | alias_method :update, :update_plot 86 | 87 | ## 88 | # Destructive version of #update_plot. 89 | # 90 | # @return [Multiplot] self 91 | # @example 92 | # Multiplot.new(Plot.new('sin(x)'), Plot.new('cos(x)'), layout: [2,1]) 93 | # mp.update_plot!(title: 'Sin(x) and Exp(x)') { |sinx| sinx.add!('exp(x)') } 94 | # # mp IS affected 95 | def update_plot!(position = 0, **options) 96 | return self unless block_given? if options.empty? 97 | replacement = @plots[position].options!(options) 98 | yield(replacement) if block_given? 99 | self 100 | end 101 | 102 | alias_method :update!, :update_plot! 103 | 104 | ## 105 | # Create new Multiplot object where plot (Plot or Splot object) 106 | # at *position* will be replaced with the given one. 107 | # 108 | # @param position [Integer] position of plot which you need to replace 109 | # (by default first plot is replace) 110 | # @param plot [Plot, Splot] replacement 111 | # @return [Multiplot] self 112 | # @example 113 | # mp = Multiplot.new(Plot.new('sin(x)'), Plot.new('cos(x)'), layout: [2,1]) 114 | # mp_with_replaced_plot = mp.replace_plot(Plot.new('exp(x)', title: 'exp instead of sin')) 115 | # # mp IS NOT affected 116 | def replace_plot(position = 0, plot) 117 | self.class.new(@plots.set(position, plot), @options) 118 | end 119 | 120 | alias_method :replace, :replace_plot 121 | 122 | ## 123 | # Destructive version of #replace_plot. 124 | # 125 | # @return [Multiplot] self 126 | # @example 127 | # mp = Multiplot.new(Plot.new('sin(x)'), Plot.new('cos(x)'), layout: [2,1]) 128 | # mp.replace_plot!(Plot.new('exp(x)', title: 'exp instead of sin')) 129 | # # mp IS affected 130 | def replace_plot!(position = 0, plot) 131 | @plots = @plots.set(position, plot) 132 | self 133 | end 134 | 135 | alias_method :replace!, :replace_plot! 136 | alias_method :[]=, :replace_plot! 137 | 138 | ## 139 | # Create new Multiplot with given *plots* added before plot at given *position*. 140 | # (by default it adds plot at the front). 141 | # 142 | # @param position [Integer] position of plot which you need to replace 143 | # (by default first plot is replace) 144 | # @param plots [Sequence of Plot or Splot] plots you want to add 145 | # @return [Multiplot] self 146 | # @example 147 | # mp = Multiplot.new(Plot.new('sin(x)'), Plot.new('cos(x)'), layout: [2,1]) 148 | # enlarged_mp = mp.add_plots(Plot.new('exp(x)')).layout([3,1]) 149 | # # mp IS NOT affected 150 | def add_plots(*plots) 151 | plots.unshift(0) unless plots[0].is_a?(Numeric) 152 | self.class.new(@plots.insert(*plots), @options) 153 | end 154 | 155 | alias_method :add_plot, :add_plots 156 | alias_method :<<, :add_plots 157 | alias_method :add, :add_plots 158 | 159 | ## 160 | # Destructive version of #add_plots. 161 | # 162 | # @return [Multiplot] self 163 | # @example 164 | # mp = Multiplot.new(Plot.new('sin(x)'), Plot.new('cos(x)'), layout: [2,1]) 165 | # mp.add_plots!(Plot.new('exp(x)')).layout([3,1]) 166 | # # mp IS affected 167 | def add_plots!(*plots) 168 | plots.unshift(0) unless plots[0].is_a?(Numeric) 169 | @plots = @plots.insert(*plots) 170 | self 171 | end 172 | 173 | alias_method :add_plot!, :add_plots! 174 | alias_method :add!, :add_plots! 175 | 176 | ## 177 | # Create new Multiplot without plot at given position 178 | # (by default last plot is removed). 179 | # 180 | # @param position [Integer] position of plot which you need to remove 181 | # (by default last plot is removed) 182 | # @return [Multiplot] self 183 | # @example 184 | # mp = Multiplot.new(Plot.new('sin(x)'), Plot.new('cos(x)'), layout: [2,1]) 185 | # mp_with_only_cos = mp.remove_plot(0) 186 | # # mp IS NOT affected 187 | def remove_plot(position = -1) 188 | self.class.new(@plots.delete_at(position), @options) 189 | end 190 | 191 | alias_method :remove, :remove_plot 192 | 193 | ## 194 | # Destructive version of #remove_plot. 195 | # 196 | # @return [Multiplot] self 197 | # @example 198 | # mp = Multiplot.new(Plot.new('sin(x)'), Plot.new('cos(x)'), layout: [2,1]) 199 | # mp.remove_plot!(0) 200 | # # mp IS affected 201 | def remove_plot!(position = -1) 202 | @plots = @plots.delete_at(position) 203 | self 204 | end 205 | 206 | alias_method :remove!, :remove_plot! 207 | 208 | ## 209 | # Equal to #plots[*args] 210 | def [](*args) 211 | @plots[*args] 212 | end 213 | 214 | private 215 | 216 | ## 217 | # Default options to be used for that plot 218 | def default_options 219 | { 220 | layout: [2, 2], 221 | title: 'Multiplot' 222 | } 223 | end 224 | 225 | ## 226 | # This plot have some specific options which 227 | # should be handled different way than others. 228 | # Here are keys of this options. 229 | def specific_keys 230 | %w( 231 | title 232 | layout 233 | ) 234 | end 235 | 236 | ## 237 | # Create new Multiplot object with the same set of plots and 238 | # given options. 239 | # Used in OptionHandling module. 240 | def new_with_options(options) 241 | self.class.new(@plots, options) 242 | end 243 | 244 | ## 245 | # Check if given options corresponds to multiplot. 246 | # Uses #specific_keys to check. 247 | def specific_option?(key) 248 | specific_keys.include?(key.to_s) 249 | end 250 | 251 | ## 252 | # Takes all options and splits them into specific and 253 | # others. Requires a block where this two classes should 254 | # be mixed. 255 | def mix_options(options) 256 | all_options = @options.merge(options) 257 | specific_options, plot_options = all_options.partition { |key, _value| specific_option?(key) } 258 | yield(plot_options, default_options.merge(specific_options)) 259 | end 260 | 261 | ## 262 | # Just a part of #plot. 263 | def multiplot(terminal, options) 264 | terminal.set(options) 265 | @plots.each { |graph| graph.plot(terminal, multiplot_part: true) } 266 | terminal.unset(options.keys) 267 | end 268 | end 269 | end 270 | -------------------------------------------------------------------------------- /lib/gnuplotrb/plot.rb: -------------------------------------------------------------------------------- 1 | module GnuplotRB 2 | ## 3 | # Class corresponding to simple 2D visualisation. 4 | # 5 | # == Notebooks 6 | # 7 | # * {Heatmaps}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/heatmaps.ipynb] 8 | # * {Vector field}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/vector_field.ipynb] 9 | # * {Math equations}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/math_plots.ipynb] 10 | # * {Histogram}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/histogram.ipynb] 11 | # * {Updating plots with new data}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/updating_data.ipynb] 12 | # 13 | # == Options 14 | # All possible options are exaplained in 15 | # {gnuplot docs}[http://www.gnuplot.info/docs_5.0/gnuplot.pdf] (pp. 105-190). 16 | # 17 | # Several common ones: 18 | # 19 | # * xrange(yrange, zrange, urange, vrange) - set range for a variable. Takes 20 | # Range (xrange: 0..100), or String (yrange: '[0:100]'). 21 | # * title - plot's title. Takes String (title: 'Some new plot'). 22 | # * polar (parametric) - plot in polar or parametric space. Takes boolean (true). 23 | # * style_data - set style for plotting data. Takes string, possible values: histogram, 24 | # points, lines, linespoints, boxes etc. See gnuplot docs for more. 25 | # * term - select terminal used by gnuplot. Examples: { term: 'png' }, 26 | # { term: ['svg', size: [600, 600]] }. Deprecated due to existance of #to_ methods. 27 | # One can use #to_png and #to_svg(size: [600, 600]) instead of passing previous options. 28 | # * output - select filename to output plot to. Should be used together with term. Deprecated 29 | # due to existance of #to_ methods. One should use #to_png('file.png') instead of 30 | # passing { term: 'png', output: 'file.png' }. 31 | # Every option may be passed to constructor in order to create plot with it. 32 | # 33 | # Methods #options(several: options, ...) and bunch of #option_name(only_an: option) such as 34 | # #xrange, #using, #polar etc create new Plot object based on existing but with a new options. 35 | # 36 | # Methods with the same names ending with '!' or '=' ('plot.xrange!(1..3)', 37 | # 'plot.title = "New title"') are destructive and modify state of existing object just as 38 | # "Array#sort!" do with Array object. See notebooks for examples. 39 | class Plot 40 | include Plottable 41 | ## 42 | # Array of datasets which are plotted by this object. 43 | attr_reader :datasets 44 | ## 45 | # @param *datasets [Sequence of Dataset or Array] either instances of Dataset class or 46 | # "[data, **dataset_options]"" arrays 47 | # @param options [Hash] see Plot top level doc for options examples 48 | def initialize(*datasets) 49 | # had to relace **options arg with this because in some cases 50 | # Daru::DataFrame was mentioned as hash and added to options 51 | # instead of plots 52 | @options = Hamster.hash 53 | if datasets[-1].is_a?(Hamster::Hash) || datasets[-1].is_a?(Hash) 54 | @options = Hamster.hash(datasets[-1]) 55 | datasets = datasets[0..-2] 56 | end 57 | @datasets = parse_datasets_array(datasets) 58 | @cmd = 'plot ' 59 | OptionHandling.validate_terminal_options(@options) 60 | yield(self) if block_given? 61 | end 62 | 63 | ## 64 | # Output plot to term (if given) or to this plot's own terminal. 65 | # 66 | # @param term [Terminal] Terminal object to plot to 67 | # @param :multiplot_part [Boolean] true if this plot is part of a multiplot. For inner use! 68 | # @param options [Hash] see options in Plot top level doc. 69 | # Options passed here have priority over already existing. 70 | # @return [Plot] self 71 | def plot(term = nil, multiplot_part: false, **options) 72 | fail ArgumentError, 'Empty plots are not supported!' if @datasets.empty? 73 | inner_opts = if multiplot_part 74 | @options.merge(options).reject { |key, _| [:term, :output].include?(key) } 75 | else 76 | @options.merge(options) 77 | end 78 | terminal = term || (inner_opts[:output] ? Terminal.new : own_terminal) 79 | ds_string = @datasets.map { |dataset| dataset.to_s(terminal) }.join(' , ') 80 | full_command = @cmd + ds_string 81 | terminal.set(inner_opts).stream_puts(full_command).unset(inner_opts.keys) 82 | if inner_opts[:output] 83 | # guaranteed wait for plotting to finish 84 | terminal.close unless term 85 | # not guaranteed wait for plotting to finish 86 | # work bad with terminals like svg and html 87 | sleep 0.01 until File.size?(inner_opts[:output]) 88 | end 89 | self 90 | end 91 | 92 | alias_method :replot, :plot 93 | 94 | ## 95 | # Create new Plot object where dataset at *position* will 96 | # be replaced with the new one created from it by updating. 97 | # 98 | # @param position [Integer] position of dataset which you need to update 99 | # (by default first dataset is updated) 100 | # @param data [#to_gnuplot_points] data to update dataset with 101 | # @param options [Hash] options to update dataset with, see Dataset top level doc 102 | # 103 | # @example 104 | # updated_plot = plot.update_dataset(data: [x1,y1], title: 'After update') 105 | # # plot IS NOT affected (if dataset did not store data in a file) 106 | def update_dataset(position = 0, data: nil, **options) 107 | old_ds = @datasets[position] 108 | new_ds = old_ds.update(data, options) 109 | new_ds.equal?(old_ds) ? self : replace_dataset(position, new_ds) 110 | end 111 | 112 | ## 113 | # Updates existing Plot object by replacing dataset at *position* 114 | # with the new one created from it by updating. 115 | # 116 | # @param position [Integer] position of dataset which you need to update 117 | # (by default first dataset is updated) 118 | # @param data [#to_gnuplot_points] data to update dataset with 119 | # @param options [Hash] options to update dataset with, see Dataset top level doc 120 | # 121 | # @example 122 | # plot.update_dataset!(data: [x1,y1], title: 'After update') 123 | # # plot IS affected anyway 124 | def update_dataset!(position = 0, data: nil, **options) 125 | @datasets[position].update!(data, options) 126 | self 127 | end 128 | 129 | ## 130 | # Create new Plot object where dataset at *position* will 131 | # be replaced with the given one. 132 | # 133 | # @param position [Integer] position of dataset which you need to replace 134 | # (by default first dataset is replaced) 135 | # @param dataset [Dataset, Array] dataset to replace the old one. You can also 136 | # give here "[data, **dataset_options]"" array from which Dataset may be created. 137 | # @example 138 | # sinx = Plot.new('sin(x)') 139 | # cosx = sinx.replace_dataset(['cos(x)']) 140 | # # sinx IS NOT affected 141 | def replace_dataset(position = 0, dataset) 142 | self.class.new(@datasets.set(position, dataset_from_any(dataset)), @options) 143 | end 144 | 145 | ## 146 | # Updates existing Plot object by replacing dataset at *position* 147 | # with the given one. 148 | # 149 | # @param position [Integer] position of dataset which you need to replace 150 | # (by default first dataset is replaced) 151 | # @param dataset [Dataset, Array] dataset to replace the old one. You can also 152 | # give here "[data, **dataset_options]"" array from which Dataset may be created. 153 | # @example 154 | # sinx = Plot.new('sin(x)') 155 | # sinx.replace_dataset!(['cos(x)']) 156 | # # sinx IS affected 157 | def replace_dataset!(position = 0, dataset) 158 | @datasets = @datasets.set(position, dataset_from_any(dataset)) 159 | self 160 | end 161 | 162 | alias_method :[]=, :replace_dataset! 163 | 164 | ## 165 | # Create new Plot object where given datasets will 166 | # be inserted into dataset list before given position 167 | # (position = 0 by default). 168 | # 169 | # @param position [Integer] position of dataset BEFORE which datasets will be placed. 170 | # 0 by default. 171 | # @param *datasets [ Sequence of Dataset or Array] datasets to insert 172 | # @example 173 | # sinx = Plot.new('sin(x)') 174 | # sinx_and_cosx_with_expx = sinx.add(['cos(x)'], ['exp(x)']) 175 | # 176 | # cosx_and_sinx = sinx << ['cos(x)'] 177 | # # sinx IS NOT affected in both cases 178 | def add_datasets(*datasets) 179 | datasets.map! { |ds| ds.is_a?(Numeric) ? ds : dataset_from_any(ds) } 180 | # first element is position where to add datasets 181 | datasets.unshift(0) unless datasets[0].is_a?(Numeric) 182 | self.class.new(@datasets.insert(*datasets), @options) 183 | end 184 | 185 | alias_method :add_dataset, :add_datasets 186 | alias_method :<<, :add_datasets 187 | 188 | ## 189 | # Updates existing Plot object by inserting given datasets 190 | # into dataset list before given position (position = 0 by default). 191 | # 192 | # @param position [Integer] position of dataset BEFORE which datasets will be placed. 193 | # 0 by default. 194 | # @param *datasets [ Sequence of Dataset or Array] datasets to insert 195 | # @example 196 | # sinx = Plot.new('sin(x)') 197 | # sinx.add!(['cos(x)'], ['exp(x)']) 198 | # # sinx IS affected 199 | def add_datasets!(*datasets) 200 | datasets.map! { |ds| ds.is_a?(Numeric) ? ds : dataset_from_any(ds) } 201 | # first element is position where to add datasets 202 | datasets.unshift(0) unless datasets[0].is_a?(Numeric) 203 | @datasets = @datasets.insert(*datasets) 204 | self 205 | end 206 | 207 | alias_method :add_dataset!, :add_datasets! 208 | 209 | ## 210 | # Create new Plot object where dataset at given position 211 | # will be removed from dataset list. 212 | # 213 | # @param position [Integer] position of dataset that should be 214 | # removed (by default last dataset is removed) 215 | # @example 216 | # sinx_and_cosx = Plot.new('sin(x)', 'cos(x)') 217 | # sinx = sinx_and_cosx.remove_dataset 218 | # cosx = sinx_and_cosx.remove_dataset(0) 219 | # # sinx_and_cosx IS NOT affected in both cases 220 | def remove_dataset(position = -1) 221 | self.class.new(@datasets.delete_at(position), @options) 222 | end 223 | 224 | ## 225 | # Updates existing Plot object by removing dataset at given position. 226 | # 227 | # @param position [Integer] position of dataset that should be 228 | # removed (by default last dataset is removed) 229 | # @example 230 | # sinx_and_cosx = Plot.new('sin(x)', 'cos(x)') 231 | # sinx_and_cosx!.remove_dataset 232 | # sinx_and_cosx!.remove_dataset 233 | # # sinx_and_cosx IS affected and now is empty 234 | def remove_dataset!(position = -1) 235 | @datasets = @datasets.delete_at(position) 236 | self 237 | end 238 | 239 | ## 240 | # The same as #datasets[*args] 241 | def [](*args) 242 | @datasets[*args] 243 | end 244 | 245 | private 246 | 247 | ## 248 | # Checks several conditions and set options needed 249 | # to handle DateTime indexes properly. 250 | def provide_with_datetime_format(data, using) 251 | return unless defined?(Daru) 252 | return unless data.is_a?(Daru::DataFrame) || data.is_a?(Daru::Vector) 253 | return unless data.index.first.is_a?(DateTime) 254 | return if using[0..1] != '1:' 255 | @options = Hamster::Hash.new( 256 | xdata: 'time', 257 | timefmt: '%Y-%m-%dT%H:%M:%S', 258 | format_x: '%d\n%b\n%Y' 259 | ).merge(@options) 260 | end 261 | 262 | ## 263 | # Check if given args is a dataset and returns it. Creates 264 | # new dataset from given args otherwise. 265 | def dataset_from_any(source) 266 | ds = case source 267 | # when initialized with dataframe (it passes here several vectors) 268 | when (defined?(Daru) ? Daru::Vector : nil) 269 | Dataset.new(source) 270 | when Dataset 271 | source.clone 272 | else 273 | Dataset.new(*source) 274 | end 275 | data = source.is_a?(Array) ? source[0] : source 276 | provide_with_datetime_format(data, ds.using) 277 | ds 278 | end 279 | 280 | ## 281 | # Parses given array and returns Hamster::Vector of Datasets 282 | def parse_datasets_array(datasets) 283 | case datasets[0] 284 | when Hamster::Vector 285 | datasets[0] 286 | when (defined?(Daru) ? Daru::DataFrame : nil) 287 | Hamster::Vector.new(datasets[0].map { |ds| dataset_from_any(ds) }) 288 | else 289 | Hamster::Vector.new(datasets.map { |ds| dataset_from_any(ds) }) 290 | end 291 | end 292 | 293 | ## 294 | # Creates new Plot with existing data and given options. 295 | def new_with_options(options) 296 | self.class.new(@datasets, options) 297 | end 298 | end 299 | end 300 | -------------------------------------------------------------------------------- /lib/gnuplotrb/splot.rb: -------------------------------------------------------------------------------- 1 | module GnuplotRB 2 | ## 3 | # Splot class correspond to simple 3D visualisation. 4 | # Most of Plot's docs are right for Splot too. 5 | # 6 | # Examples of usage are in 7 | # {a notebook}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/3d_plot.ipynb] 8 | class Splot < Plot 9 | ## 10 | # @param *datasets [Sequence of Dataset or Array] either instances of Dataset class or 11 | # "[data, **dataset_options]"" arrays 12 | # @param options [Hash] see Plot top level doc for options examples 13 | def initialize(*datasets, **options) 14 | super 15 | @cmd = 'splot ' 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/gnuplotrb/staff/datablock.rb: -------------------------------------------------------------------------------- 1 | module GnuplotRB 2 | ## 3 | # This class corresponds to points we want to plot. It may be 4 | # stored in temporary file (to allow fast update) or inside 5 | # "$DATA << EOD ... EOD" construction. Datablock stores data passed 6 | # to constructor and keeps datablock name or path to file where it is stored. 7 | class Datablock 8 | ## 9 | # @param data [#to_gnuplot_points] anything with #to_gnuplot_points method 10 | # @param stored_in_file [Boolean] true here will force this datablock to store its data 11 | # in temporary file. 12 | def initialize(data, stored_in_file = false) 13 | @stored_in_file = stored_in_file 14 | data_str = data.to_gnuplot_points 15 | if @stored_in_file 16 | @file_name = Dir::Tmpname.make_tmpname('tmp_data', 0) 17 | File.write(@file_name, data_str) 18 | name = File.join(Dir.pwd, @file_name) 19 | ObjectSpace.define_finalizer(self, proc { File.delete(name) }) 20 | else 21 | @data = data_str 22 | end 23 | end 24 | 25 | ## 26 | # Instantiate one more Datablock with updated data 27 | # if data stored in here-doc. Append update to file 28 | # if data stored there. 29 | # 30 | # @param data [#to_gnuplot_points] anything with #to_gnuplot_points method 31 | # @return [Datablock] self if data stored in file (see constructor) 32 | # @return [Datablock] new datablock with updated data otherwise 33 | # 34 | # @example 35 | # data = [[0, 1, 2, 3], [0, 1, 4, 9]] # y = x**2 36 | # db = Datablock.new(data, false) 37 | # update = [[4, 5], [16, 25]] 38 | # updated_db = db.update(update) 39 | # # now db and updated_db contain DIFFERENT data 40 | # # db - points with x from 0 up to 3 41 | # # updated_db - points with x from 0 to 5 42 | # 43 | # @example 44 | # data = [[0, 1, 2, 3], [0, 1, 4, 9]] # y = x**2 45 | # db = Datablock.new(data, true) 46 | # update = [[4, 5], [16, 25]] 47 | # updated_db = db.update(update) 48 | # # now db and updated_db contain THE SAME data 49 | # # because they linked with the same temporary file 50 | # # db - points with x from 0 up to 5 51 | # # updated_db - points with x from 0 to 5 52 | def update(data) 53 | data_str = data.to_gnuplot_points 54 | if @stored_in_file 55 | File.open(@file_name, 'a') { |f| f.puts "\n#{data_str}" } 56 | self 57 | else 58 | Datablock.new("#{@data}\n#{data_str}", false) 59 | end 60 | end 61 | 62 | ## 63 | # Update existing Datablock with new data. 64 | # Destructive version of #update. 65 | # 66 | # @param data [#to_gnuplot_points] anything with #to_gnuplot_points method 67 | # @return [Datablock] self 68 | # 69 | # @example 70 | # data = [[0, 1, 2, 3], [0, 1, 4, 9]] # y = x**2 71 | # db = Datablock.new(data, false) 72 | # update = [[4, 5], [16, 25]] 73 | # db.update!(update) 74 | # # now db contains points with x from 0 up to 5 75 | def update!(data) 76 | data_str = data.to_gnuplot_points 77 | if @stored_in_file 78 | File.open(@file_name, 'a') { |f| f.puts "\n#{data_str}" } 79 | else 80 | @data = "#{@data}\n#{data_str}" 81 | end 82 | self 83 | end 84 | 85 | ## 86 | # Get quoted filename if datablock stored in file or output 87 | # datablock to gnuplot and return its name otherwise. 88 | # 89 | # @param gnuplot_term [Terminal] should be given if datablock not stored in file 90 | # @return [String] quoted filename if data stored in file (see contructor) 91 | # @return [String] Gnuplot's datablock name otherwise 92 | def name(gnuplot_term = nil) 93 | if @stored_in_file 94 | "'#{@file_name}'" 95 | else 96 | fail(ArgumentError, 'No terminal given to output datablock') unless gnuplot_term 97 | gnuplot_term.store_datablock(@data) 98 | end 99 | end 100 | 101 | alias_method :to_s, :name 102 | 103 | ## 104 | # Overridden #clone. Since datablock which store data 105 | # in temporary files should not be cloned (otherwise it will cause 106 | # double attempt to delete file), this #clone returns self for such 107 | # cases. For other cases it just calls default #clone. 108 | def clone 109 | @stored_in_file ? self : super 110 | end 111 | end 112 | end 113 | -------------------------------------------------------------------------------- /lib/gnuplotrb/staff/dataset.rb: -------------------------------------------------------------------------------- 1 | module GnuplotRB 2 | ## 3 | # Dataset keeps control of Datablock or String (some math functions like 4 | # this 'x*sin(x)' or filename) and options related to original dataset 5 | # in gnuplot (with, title, using etc). 6 | # 7 | # == Options 8 | # Dataset options are explained in 9 | # {gnuplot docs}[http://www.gnuplot.info/docs_5.0/gnuplot.pdf] (pp. 80-101). 10 | # Several common options: 11 | # * with - set plot style for dataset ('lines', 'points', 'impulses' etc) 12 | # * using - choose which columns of input data gnuplot should use. Takes String 13 | # (using: 'xtic(1):2:3'). If Daru::Dataframe passed one can use column names 14 | # instead of numbers (using: 'index:value1:summ' - value1 and summ here are column names). 15 | # * linewidth (lw) - integer line width 16 | # * dashtype (dt) - takes pattern with dash style. Examples: '.. ', '-- ', '.- '. 17 | # * pointtype (pt) - takes integer number of point type (works only when :with option is set to 18 | # 'points'). One can call Terminal::test(term_name) 19 | # or Terminal#test in order to see which point types are supported by terminal. 20 | class Dataset 21 | include Plottable 22 | ## 23 | # Data represented by this dataset 24 | attr_reader :data 25 | 26 | ## 27 | # Order is significant for some options 28 | OPTION_ORDER = %w(index using axes title) 29 | 30 | private_constant :OPTION_ORDER 31 | 32 | ## 33 | # Hash of init handlers for data given in 34 | # different containers. 35 | INIT_HANDLERS = Hash.new(:init_default).merge( 36 | String => :init_string, 37 | Datablock => :init_dblock 38 | ) 39 | INIT_HANDLERS.merge!( 40 | Daru::DataFrame => :init_daru_frame, 41 | Daru::Vector => :init_daru_vector 42 | ) if defined? Daru 43 | 44 | ## 45 | # Create new dataset out of given string with math function or filename. 46 | # If *data* isn't a string it will create datablock to store data. 47 | # 48 | # @param data [String, Datablock, #to_gnuplot_points] String, Datablock or something acceptable 49 | # by Datablock.new as data (e.g. [x,y] where x and y are arrays) 50 | # @param options [Hash] options specific for gnuplot 51 | # dataset (see Dataset top level doc), and some special options ('file: true' will 52 | # make data to be stored inside temporary file) 53 | # 54 | # @example Math function: 55 | # Dataset.new('x*sin(x)', with: 'lines', lw: 4) 56 | # @example File with points: 57 | # Dataset.new('points.data', with: 'lines', title: 'Points from file') 58 | # @example Some data (creates datablock stored in memory): 59 | # x = (0..5000).to_a 60 | # y = x.map {|xx| xx*xx } 61 | # points = [x, y] 62 | # Dataset.new(points, with: 'points', title: 'Points') 63 | # @example The same data but datablock stores it in temp file: 64 | # Dataset.new(points, with: 'points', title: 'Points', file: true) 65 | def initialize(data, **options) 66 | # run method by name 67 | send(INIT_HANDLERS[data.class], data, options) 68 | end 69 | 70 | ## 71 | # Convert Dataset to string containing gnuplot dataset. 72 | # 73 | # @param terminal [Terminal] must be given if data given as Datablock and 74 | # it does not use temp file so data should be piped out 75 | # to gnuplot via terminal before use 76 | # @param :without_options [Boolean] do not add options to dataset if set 77 | # to true. Used by Fit::fit 78 | # @return [String] gnuplot dataset 79 | # @example 80 | # Dataset.new('points.data', with: 'lines', title: 'Points from file').to_s 81 | # #=> "'points.data' with lines title 'Points from file'" 82 | # Dataset.new(points, with: 'points', title: 'Points').to_s 83 | # #=> "$DATA1 with points title 'Points'" 84 | def to_s(terminal = nil, without_options: false) 85 | result = "#{@type == :datablock ? @data.name(terminal) : @data} " 86 | result += options_to_string unless without_options 87 | result 88 | end 89 | 90 | ## 91 | # Create new dataset with updated data and merged options. 92 | # 93 | # Given data is appended to existing. 94 | # Data is updated only if Dataset stores it in Datablock. 95 | # Method does nothing if no options given and data isn't stored 96 | # in in-memory Datablock. 97 | # 98 | # @param data [#to_gnuplot_points] data to append to existing 99 | # @param options [Hash] new options to merge with existing options 100 | # @return self if dataset corresponds to math formula or file 101 | # (filename or temporary file if datablock) 102 | # @return [Dataset] new dataset if data is stored in 'in-memory' Datablock 103 | # @example Updating dataset with Math formula or filename given: 104 | # dataset = Dataset.new('file.data') 105 | # dataset.update(data: 'asd') 106 | # #=> nothing updated 107 | # dataset.update(data: 'asd', title: 'File') 108 | # #=> Dataset.new('file.data', title: 'File') 109 | # @example Updating dataset with data stored in Datablock (in-memory): 110 | # in_memory_points = Dataset.new(points, title: 'Old one') 111 | # in_memory_points.update(data: some_update, title: 'Updated') 112 | # #=> Dataset.new(points + some_update, title: 'Updated') 113 | # @example Updating dataset with data stored in Datablock (in-file): 114 | # temp_file_points = Dataset.new(points, title: 'Old one', file: true) 115 | # temp_file_points.update(data: some_update) 116 | # #=> data updated but no new dataset created 117 | # temp_file_points.update(data: some_update, title: 'Updated') 118 | # #=> data updated and new dataset with title 'Updated' returned 119 | def update(data = nil, **options) 120 | if data && @type == :datablock 121 | new_datablock = @data.update(data) 122 | if new_datablock == @data 123 | update_options(options) 124 | else 125 | self.class.new(new_datablock, options) 126 | end 127 | else 128 | update_options(options) 129 | end 130 | end 131 | 132 | ## 133 | # Update Dataset with new data and options. 134 | # 135 | # Given data is appended to existing. 136 | # Data is updated only if Dataset stores it in Datablock. 137 | # Method does nothing if no options given and data isn't stored 138 | # in in-memory Datablock. 139 | # 140 | # @param data [#to_gnuplot_points] data to append to existing 141 | # @param options [Hash] new options to merge with existing options 142 | # @return self 143 | # @example Updating dataset with Math formula or filename given: 144 | # dataset = Dataset.new('file.data') 145 | # dataset.update!(data: 'asd') 146 | # #=> nothing updated 147 | # dataset.update!(data: 'asd', title: 'File') 148 | # dataset.title 149 | # #=> 'File' # data isn't updated 150 | # @example Updating dataset with data stored in Datablock (in-memory): 151 | # in_memory_points = Dataset.new(points, title: 'Old one') 152 | # in_memory_points.update!(data: some_update, title: 'Updated') 153 | # in_memory_points.data 154 | # #=> points + some_update 155 | # in_memory_points.title 156 | # #=> 'Updated' 157 | # @example Updating dataset with data stored in Datablock (in-file): 158 | # temp_file_points = Dataset.new(points, title: 'Old one', file: true) 159 | # temp_file_points.update!(data: some_update) 160 | # #=> data updated but no new dataset created 161 | # temp_file_points.update!(data: some_update, title: 'Updated') 162 | # #=> data and options updated 163 | def update!(data = nil, **options) 164 | @data.update!(data) if data 165 | options!(options) 166 | self 167 | end 168 | 169 | ## 170 | # Own implementation of #clone. Creates new Dataset if 171 | # data stored in datablock and calls super otherwise. 172 | def clone 173 | if @type == :datablock 174 | new_with_options(@options) 175 | else 176 | super 177 | end 178 | end 179 | 180 | ## 181 | # Create new Plot object with only one Dataset given - self. 182 | # Calls #plot on created Plot. All arguments given to this #plot 183 | # will be sent to Plot#plot instead. 184 | # @param args sequence of arguments all of which will be passed to Plot#plot, 185 | # see docs there 186 | # @return [Plot] new Plot object with only one Dataset - self 187 | # @example 188 | # sin = Dataset.new('sin(x)') 189 | # sin.plot(term: [qt, size: [300, 300]]) 190 | # #=> shows qt window 300x300 with sin(x) 191 | # sin.to_png('./plot.png') 192 | # #=> creates png file with sin(x) plotted 193 | def plot(*args) 194 | Plot.new(self).plot(*args) 195 | end 196 | 197 | private 198 | 199 | ## 200 | # Create new dataset with existing options merged with 201 | # the given ones. Does nothing if no options given. 202 | # 203 | # @param options [Hash] new options to merge with existing options 204 | # @return [Dataset] self if options empty 205 | # @return [Dataset] new Dataset with updated options otherwise 206 | # @example Updating dataset with Math formula or filename given: 207 | # dataset = Dataset.new('file.data') 208 | # dataset.update_options(title: 'File') 209 | # #=> Dataset.new('file.data', title: 'File') 210 | def update_options(**options) 211 | if options.empty? 212 | self 213 | else 214 | new_with_options(@options.merge(options)) 215 | end 216 | end 217 | 218 | ## 219 | # Create string from own options 220 | # @return [String] options converted to Gnuplot format 221 | def options_to_string 222 | options.sort_by { |key, _| OPTION_ORDER.find_index(key.to_s) || 999 } 223 | .map { |key, value| OptionHandling.option_to_string(key, value) } 224 | .join(' ') 225 | end 226 | 227 | ## 228 | # Needed by OptionHandling to create new object when options are changed. 229 | def new_with_options(options) 230 | self.class.new(@data, options) 231 | end 232 | 233 | ## 234 | # Initialize Dataset from given String 235 | def init_string(data, options) 236 | @type, @data = File.exist?(data) ? [:datafile, "'#{data}'"] : [:math_function, data.clone] 237 | @options = Hamster.hash(options) 238 | end 239 | 240 | ## 241 | # Initialize Dataset from given Datablock 242 | def init_dblock(data, options) 243 | @type = :datablock 244 | @data = data.clone 245 | @options = Hamster.hash(options) 246 | end 247 | 248 | ## 249 | # Create new value for 'using' option based on column count 250 | def get_daru_columns(data, cnt) 251 | new_opt = (2..cnt).to_a.join(':') 252 | if data.index[0].is_a?(DateTime) || data.index[0].is_a?(Numeric) 253 | "1:#{new_opt}" 254 | else 255 | "#{new_opt}:xtic(1)" 256 | end 257 | end 258 | 259 | ## 260 | # Initialize Dataset from given Daru::DataFrame 261 | def init_daru_frame(data, options) 262 | options[:title] ||= data.name 263 | if options[:using] 264 | options[:using] = " #{options[:using]} " 265 | data.vectors.to_a.each_with_index do |daru_index, array_index| 266 | options[:using].gsub!(/([\:\(\$ ])#{daru_index}([\:\) ])/) do 267 | "#{Regexp.last_match(1)}#{array_index + 2}#{Regexp.last_match(2)}" 268 | end 269 | end 270 | options[:using].gsub!('index', '1') 271 | options[:using].strip! 272 | else 273 | options[:using] = get_daru_columns(data, data.vectors.size + 1) 274 | end 275 | init_default(data, options) 276 | end 277 | 278 | ## 279 | # Initialize Dataset from given Daru::Vector 280 | def init_daru_vector(data, options) 281 | options[:using] ||= get_daru_columns(data, 2) 282 | options[:title] ||= data.name 283 | init_default(data, options) 284 | end 285 | 286 | ## 287 | # Initialize Dataset from given data with #to_gnuplot_points method 288 | def init_default(data, file: false, **options) 289 | @type = :datablock 290 | @data = Datablock.new(data, file) 291 | @options = Hamster.hash(options) 292 | end 293 | end 294 | end 295 | -------------------------------------------------------------------------------- /lib/gnuplotrb/staff/settings.rb: -------------------------------------------------------------------------------- 1 | module GnuplotRB 2 | ## 3 | # This module takes care of path to gnuplot executable and checking its version. 4 | module Settings 5 | ## 6 | # GnuplotRB can work with Gnuplot 5.0+ 7 | MIN_GNUPLOT_VERSION = 5.0 8 | 9 | class << self 10 | ## 11 | # For heavy calculations max_fit_delay may be increased. 12 | attr_writer :max_fit_delay 13 | ## 14 | # Get max fit delay. 15 | # 16 | # Max fit delay (5s by default) is used inside Fit::fit function. 17 | # If it waits for output more than max_fit_delay seconds 18 | # this behaviour is considered as errorneus. 19 | # @return [Integer] seconds to wait for output 20 | def max_fit_delay 21 | @max_fit_delay ||= 5 22 | end 23 | 24 | ## 25 | # Get path that should be used to run gnuplot executable. 26 | # Default value: 'gnuplot'. 27 | # @return [String] path to gnuplot executable 28 | def gnuplot_path 29 | self.gnuplot_path = 'gnuplot' unless defined?(@gnuplot_path) 30 | @gnuplot_path 31 | end 32 | 33 | ## 34 | # Set path to gnuplot executable. 35 | # @param path [String] path to gnuplot executable 36 | # @return given path 37 | def gnuplot_path=(path) 38 | validate_version(path) 39 | opts = { stdin_data: "set term\n" } 40 | @available_terminals = Open3.capture2e(path, **opts) 41 | .first 42 | .scan(/[:\n] +([a-z][^ ]+)/) 43 | .map(&:first) 44 | @gnuplot_path = path 45 | end 46 | 47 | ## 48 | # Get list of terminals (png, html, qt, jpeg etc) available for this gnuplot. 49 | # @return [Array of String] array of terminals available for this gnuplot 50 | def available_terminals 51 | gnuplot_path 52 | @available_terminals 53 | end 54 | 55 | ## 56 | # Get gnuplot version. Uses gnuplot_path to find gnuplot executable. 57 | # @return [Numeric] gnuplot version 58 | def version 59 | gnuplot_path 60 | @version 61 | end 62 | 63 | ## 64 | # @private 65 | # Validate gnuplot version. Compares current gnuplot's 66 | # version with ::MIN_GNUPLOT_VERSION. Throws exception if version is 67 | # less than min. 68 | # 69 | # @param path [String] path to gnuplot executable 70 | def validate_version(path) 71 | @version = IO.popen("#{path} --version") 72 | .read 73 | .match(/gnuplot ([^ ]+)/)[1] 74 | .to_f 75 | message = "Your Gnuplot version is #{@version}, please update it to at least 5.0" 76 | fail(ArgumentError, message) if @version < MIN_GNUPLOT_VERSION 77 | end 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /lib/gnuplotrb/staff/terminal.rb: -------------------------------------------------------------------------------- 1 | module GnuplotRB 2 | ## 3 | # Terminal keeps open pipe to gnuplot process, cares about naming in-memory 4 | # datablocks (just indexing with sequential integers). All the output 5 | # to gnuplot handled by this class. Terminal also handles options passed 6 | # to gnuplot as 'set key value'. 7 | class Terminal 8 | include ErrorHandling 9 | 10 | # order is important for some options 11 | OPTION_ORDER = [:term, :output, :multiplot, :timefmt, :xrange] 12 | 13 | private_constant :OPTION_ORDER 14 | 15 | class << self 16 | ## 17 | # Close given gnuplot pipe 18 | # @param stream [IO] pipe to close 19 | def close_arg(stream) 20 | stream.puts 21 | stream.puts 'exit' 22 | Process.waitpid(stream.pid) 23 | end 24 | 25 | ## 26 | # Plot test page for given term_name into file 27 | # with file_name (optional). 28 | # 29 | # Test page contains possibilities of the term. 30 | # @param term_name [String] terminal name ('png', 'gif', 'svg' etc) 31 | # @param file_name [String] filename to output image if needed 32 | # and chosen terminal supports image output 33 | # @return nil 34 | def test(term_name, file_name = nil) 35 | Terminal.new.set(term: term_name).test(file_name) 36 | end 37 | end 38 | 39 | ## 40 | # Create new Terminal connected with gnuplot. 41 | # Uses Settings::gnuplot_path to find gnuplot 42 | # executable. Each time you create Terminal it starts new 43 | # gnuplot subprocess which is closed after GC deletes 44 | # linked Terminal object. 45 | # 46 | # @param :persist [Boolean] gnuplot's "-persist" option 47 | def initialize(persist: false) 48 | @cmd = Settings.gnuplot_path 49 | @current_datablock = 0 50 | @cmd += ' -persist' if persist 51 | @cmd += ' 2>&1' 52 | stream = IO.popen(@cmd, 'w+') 53 | handle_stderr(stream) 54 | ObjectSpace.define_finalizer(self, proc { Terminal.close_arg(stream) }) 55 | @in = stream 56 | yield(self) if block_given? 57 | end 58 | 59 | ## 60 | # Output datablock to this gnuplot terminal. 61 | # 62 | # @param data [String] data stored in datablock 63 | # @example 64 | # data = "1 1\n2 4\n3 9" 65 | # Terminal.new.store_datablock(data) 66 | # #=> returns '$DATA1' 67 | # #=> outputs to gnuplot: 68 | # #=> $DATA1 << EOD 69 | # #=> 1 1 70 | # #=> 2 4 71 | # #=> 3 9 72 | # #=> EOD 73 | def store_datablock(data) 74 | name = "$DATA#{@current_datablock += 1}" 75 | stream_puts "#{name} << EOD" 76 | stream_puts data 77 | stream_puts 'EOD' 78 | name 79 | end 80 | 81 | ## 82 | # Convert given options to gnuplot format. 83 | # 84 | # For "{ opt1: val1, .. , optN: valN }" it returns 85 | # set opt1 val1 86 | # .. 87 | # set optN valN 88 | # 89 | # @param ptions [Hash] options to convert 90 | # @return [String] options in Gnuplot format 91 | def options_hash_to_string(options) 92 | result = '' 93 | options.sort_by { |key, _| OPTION_ORDER.find_index(key) || -1 }.each do |key, value| 94 | if value 95 | result += "set #{OptionHandling.option_to_string(key, value)}\n" 96 | else 97 | result += "unset #{key}\n" 98 | end 99 | end 100 | result 101 | end 102 | 103 | ## 104 | # Applie given options to current gnuplot instance. 105 | # 106 | # For "{ opt1: val1, .. , optN: valN }" it will output to gnuplot 107 | # set opt1 val1 108 | # .. 109 | # set optN valN 110 | # 111 | # @param options [Hash] options to set 112 | # @return [Terminal] self 113 | # @example 114 | # set({term: ['qt', size: [100, 100]]}) 115 | # #=> outputs to gnuplot: "set term qt size 100,100\n" 116 | def set(options) 117 | OptionHandling.validate_terminal_options(options) 118 | stream_puts(options_hash_to_string(options)) 119 | end 120 | 121 | ## 122 | # Unset options 123 | # 124 | # @param *options [Sequence of Symbol] each symbol considered as option key 125 | # @return [Terminal] self 126 | def unset(*options) 127 | options.flatten 128 | .sort_by { |key| OPTION_ORDER.find_index(key) || -1 } 129 | .each { |key| stream_puts "unset #{OptionHandling.string_key(key)}" } 130 | self 131 | end 132 | 133 | ## 134 | # Short way to plot Datablock, Plot or Splot object. 135 | # Other items will be just piped out to gnuplot. 136 | # @param item Object that should be outputted to Gnuplot 137 | # @return [Terminal] self 138 | def <<(item) 139 | if item.is_a? Plottable 140 | item.plot(self) 141 | else 142 | stream_print(item.to_s) 143 | end 144 | self 145 | end 146 | 147 | ## 148 | # Just put *command* + "\n" to gnuplot pipe. 149 | # @param command [String] command to send 150 | # @return [Terminal] self 151 | def stream_puts(command) 152 | stream_print("#{command}\n") 153 | end 154 | 155 | ## 156 | # Just print *command* to gnuplot pipe. 157 | # @param command [String] command to send 158 | # @return [Terminal] self 159 | def stream_print(command) 160 | check_errors 161 | @in.print(command) 162 | self 163 | end 164 | 165 | ## 166 | # @deprecated 167 | # Call replot on gnuplot. This will execute last plot once again 168 | # with rereading data. 169 | # @param options [Hash] options will be set before replotting 170 | # @return [Terminal] self 171 | def replot(**options) 172 | set(options) 173 | stream_puts('replot') 174 | unset(options.keys) 175 | sleep 0.01 until File.size?(options[:output]) if options[:output] 176 | self 177 | end 178 | 179 | ## 180 | # Send gnuplot command to turn it off and for its Process to quit. 181 | # Closes pipe so Terminal object should not be used after #close call. 182 | def close 183 | check_errors 184 | Terminal.close_arg(@in) 185 | end 186 | 187 | 188 | ## 189 | # Plot test page into file with file_name (optional). 190 | # 191 | # Test page contains possibilities of the term. 192 | # @param file_name [String] filename to output image if needed 193 | # and chosen terminal supports image output 194 | # @return nil 195 | def test(file_name = nil) 196 | set(output: file_name) if file_name 197 | stream_puts('test') 198 | unset(:output) 199 | nil 200 | end 201 | end 202 | end 203 | -------------------------------------------------------------------------------- /lib/gnuplotrb/version.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # === Overview 3 | # Ruby bindings for gnuplot. 4 | module GnuplotRB 5 | ## 6 | # Gem version 7 | VERSION = '0.3.1' 8 | end 9 | -------------------------------------------------------------------------------- /notebooks/.gitignore: -------------------------------------------------------------------------------- 1 | .ipynb_checkpoints 2 | -------------------------------------------------------------------------------- /notebooks/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'iruby' 4 | # path option is used only for development stage 5 | gem 'gnuplotrb', :path => '../' 6 | -------------------------------------------------------------------------------- /notebooks/README.rdoc: -------------------------------------------------------------------------------- 1 | == iRuby notebooks 2 | 3 | This notebooks are powered by {Ruby kernel}[https://github.com/SciRuby/iruby/] for {IPython/Jupyter}[https://jupyter.org/]. 4 | I placed them here to show some GnuplotRB's capabilities and ways of using it together with iRuby. 5 | 6 | === Installation 7 | To use GnuplotRB gem with iRuby you need to install them both. 8 | 9 | * iRuby installation is covered in its {README}[https://github.com/SciRuby/iruby/blob/master/README.md]. 10 | It also covers installation of iPython and other dependecies. 11 | * GnuplotRB gem installation covered in {README}[https://github.com/dilcom/gnuplotrb#installation] too. 12 | 13 | === List of notebooks 14 | 15 | ==== Embedding plots into iRuby 16 | Using GnuplotRB inside iRuby notebooks is covered in: 17 | 18 | * {basic usage notebook}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/basic_usage.ipynb]. 19 | 20 | ==== 2D and 3D plots 21 | GnuplotRB is capable to plot vast range of plots from histograms to 3D heatmaps. Gem's repository contains examples of several plot types: 22 | 23 | * {heatmaps}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/heatmaps.ipynb] 24 | * {vector field}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/vector_field.ipynb] (Thanks, {Alexej}[https://github.com/agisga]) 25 | * {math equations}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/math_plots.ipynb] 26 | * {3D visualizations}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/3d_plot.ipynb] 27 | * {histogram}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/histogram.ipynb] 28 | 29 | ==== Possible datasources 30 | GnuplotRB may take data in Ruby container or in a file. Supported containers for now are Arrays, Daru::Vector and Daru::DataFrame. 31 | When data given in file, GnuplotRB pass filename to Gnuplot *without* reading the file into memory. 32 | 33 | Examples of using different datasources: 34 | 35 | * {data given in file or Ruby Array}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/points_from_different_sources.ipynb] 36 | * {data given in Daru containers}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/plotting_from_daru.ipynb] 37 | * {data given in Daru containers (with timeseries)}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/time_series_from_daru.ipynb] 38 | * {updating plots with new data}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/updating_data.ipynb] 39 | 40 | ==== Multiplot 41 | You can not only plot several datasets in single coordinate system but place several coordinate systems on a canvas. 42 | 43 | * {Multiplot example notebook}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/multiplot_layout.ipynb]. 44 | 45 | ==== Animation 46 | It's possible to use several plots (Plot, Splot or Multiplot objects) to create gif animation. 47 | 48 | * {Animation example notebook}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/animated_plots.ipynb]. 49 | 50 | ==== Fitting data with formula 51 | GnuplotRB also may be used to fit some data with given math formula. 52 | 53 | * {Fitting data}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/fitting_data.ipynb] 54 | -------------------------------------------------------------------------------- /spec/animation_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper.rb' 2 | 3 | describe Animation do 4 | before(:all) do 5 | @tmp_dir = File.join('spec', 'tmp') 6 | Dir.mkdir(@tmp_dir) 7 | @datafile_path = File.join('spec', 'points.data') 8 | end 9 | 10 | after(:all) do 11 | FileUtils.rm_r(@tmp_dir) 12 | end 13 | 14 | context 'creation' do 15 | before do 16 | @title = 'Animation spec' 17 | @formula = %w(sin(x) cos(x) exp(-x)) 18 | @plots = @formula.map { |f| Plot.new(f) } 19 | @plots << Splot.new('sin(x) * cos(y)') 20 | @options = { title: @title, layout: [2, 2] } 21 | end 22 | 23 | it 'should be created out of sequence of plots' do 24 | expect(Animation.new(*@plots)).to be_an_instance_of(Animation) 25 | end 26 | 27 | it 'should set options passed to constructor' do 28 | anim = Animation.new(*@plots, **@options) 29 | expect(anim).to be_an_instance_of(Animation) 30 | expect(anim.title).to eql(@title) 31 | end 32 | end 33 | 34 | context 'option handling' do 35 | before do 36 | @options = Hamster.hash(title: 'GnuplotRB::Animation', yrange: 0..3) 37 | @anim = Animation.new(**@options) 38 | end 39 | 40 | it 'should allow to get option value by name' do 41 | expect(@anim.title).to eql(@options[:title]) 42 | end 43 | 44 | it 'should know which options are Animation specific' do 45 | right = [:size, :tiny, :animate, :background] 46 | wrong = [:layout, :title, :xrange, :term] 47 | right.each { |opt| expect(@anim.send(:specific_option?, opt)).to be_truthy } 48 | wrong.each { |opt| expect(@anim.send(:specific_option?, opt)).to be_falsey } 49 | end 50 | 51 | it 'should return Animation object' do 52 | new_options = { title: 'Another title', xrange: 1..5 } 53 | new_plot = @anim.options(new_options) 54 | expect(new_plot).to_not equal(@anim) 55 | expect(new_plot).to be_an_instance_of(Animation) 56 | end 57 | end 58 | 59 | context 'handling plots as container' do 60 | before :each do 61 | @sinx = Plot.new('sin(x)') 62 | @plot3d = Splot.new('sin(x)*cos(y)') 63 | @exp = Plot.new('exp(x)') 64 | @paths = (0..2).map { |i| File.join(@tmp_dir, "#{i}plot.gif") } 65 | @anim = Animation.new(@sinx, @plot3d, @exp) 66 | end 67 | 68 | it 'should allow to remove plot from animation' do 69 | Animation.new(@sinx, @exp).plot(@paths[0]) 70 | @anim.remove_plot(1).plot(@paths[1]) 71 | @anim.remove_frame(1).plot(@paths[2]) 72 | expect(same_files?(*@paths)).to be_truthy 73 | end 74 | 75 | it 'should allow to add plot to animation' do 76 | Animation.new(@plot3d, @sinx, @plot3d, @exp).plot(@paths[0]) 77 | @anim.add_plot(@plot3d).plot(@paths[1]) 78 | @anim.add_frame(@plot3d).plot(@paths[2]) 79 | expect(same_files?(*@paths)).to be_truthy 80 | end 81 | 82 | it 'should allow to replace plot in animation' do 83 | Animation.new(@plot3d, @exp).plot(@paths[0]) 84 | @anim.replace_plot(@plot3d).plot(@paths[1]) 85 | @anim.replace_frame(@plot3d).plot(@paths[2]) 86 | expect(same_files?(*@paths)).to be_truthy 87 | end 88 | 89 | it 'should allow to update options of any plot in animation' do 90 | ttl = "Wow, it's sin(x)!" 91 | Animation.new(@sinx.title(ttl), @plot3d, @exp).plot(@paths[0]) 92 | @anim.update_plot(0, title: ttl).plot(@paths[1]) 93 | @anim.update_frame(0, title: ttl).plot(@paths[2]) 94 | expect(same_files?(*@paths)).to be_truthy 95 | end 96 | 97 | it 'should allow to update datasets of any plot in animation' do 98 | ds_ttl = 'Some dataset' 99 | ttl = "Wow, it's sin(x)!" 100 | Animation.new(@sinx.update_dataset(title: ds_ttl).title(ttl), @plot3d, @exp).plot(@paths[0]) 101 | @anim.update_plot(0, title: ttl) do |new_plot| 102 | new_plot.update_dataset(title: ds_ttl) 103 | end.plot(@paths[1]) 104 | @anim.update_frame(0, title: ttl) do |new_plot| 105 | new_plot.update_dataset(title: ds_ttl) 106 | end.plot(@paths[2]) 107 | expect(same_files?(*@paths)).to be_truthy 108 | end 109 | 110 | it 'should allow to get plots using []' do 111 | (0..2).each { |i| expect(@anim[i]).to be_equal(@anim.plots[i]) } 112 | expect(@anim[0..-1]).to be_eql(@anim.plots) 113 | (0..2).each { |i| expect(@anim[i]).to be_equal(@anim.frames[i]) } 114 | expect(@anim[0..-1]).to be_eql(@anim.frames) 115 | end 116 | end 117 | 118 | it 'should not use terminal other than gif' do 119 | anim = Animation.new(Plot.new('sin(x)')) 120 | terminals = %w(png dumb svg) 121 | terminals.each { |term| expect { anim.send("to_#{term}".to_sym) }.to raise_error(RuntimeError) } 122 | end 123 | 124 | it 'should output html to iruby' do 125 | anim = Animation.new(Plot.new('sin(x)')) 126 | expect(anim.to_iruby[0]).to eql('text/html') 127 | end 128 | end 129 | -------------------------------------------------------------------------------- /spec/dataset_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper.rb' 2 | 3 | describe Dataset do 4 | before(:all) do 5 | @tmp_dir = File.join('spec', 'tmp') 6 | Dir.mkdir(@tmp_dir) 7 | @datafile_path = File.join('spec', 'points.data') 8 | end 9 | 10 | after(:all) do 11 | FileUtils.rm_r(@tmp_dir) 12 | end 13 | 14 | context 'creation' do 15 | before do 16 | x = (0..10).to_a 17 | y = x.map { |xx| Math.exp(-xx) } 18 | @data = [x, y] 19 | end 20 | 21 | it 'may be created with math function as data' do 22 | dataset = Dataset.new('sin(x)') 23 | expect(dataset.to_s).to eql('sin(x) ') 24 | end 25 | 26 | it 'may be created with datafile as data' do 27 | dataset = Dataset.new('spec/points.data') 28 | expect(dataset.to_s).to eql("'spec/points.data' ") 29 | end 30 | 31 | it 'may be created with some class with #to_points' do 32 | dataset = Dataset.new(@data) 33 | expect(dataset.data).to be_an_instance_of(Datablock) 34 | expect(dataset.to_s(Terminal.new)).to eql('$DATA1 ') 35 | end 36 | 37 | it 'may be created with clone of existing datablock as data' do 38 | datablock = Datablock.new(@data) 39 | dataset = Dataset.new(datablock) 40 | expect(dataset.data).to be_an_instance_of(Datablock) 41 | expect(datablock).to_not equal(dataset.data) # given datablock should be cloned 42 | end 43 | 44 | it 'may be created with existing stored in file datablock' do 45 | datablock = Datablock.new(@data, true) 46 | dataset = Dataset.new(datablock) 47 | expect(dataset.data).to be_an_instance_of(Datablock) 48 | # since given datablock's data stored in file, it should not be cloned 49 | expect(datablock).to equal(dataset.data) 50 | expect(dataset.to_s(Terminal.new)).to eql("#{datablock.name} ") 51 | end 52 | 53 | it 'may be created with given gnuplot options' do 54 | dataset = Dataset.new(@data, title: 'Dataset title', with: 'linespoints') 55 | expect(dataset.to_s(Terminal.new)).to eql("$DATA1 title \"Dataset title\" with linespoints") 56 | end 57 | 58 | it 'may be created with special :file option' do 59 | # {:file => true} will force creation of stored in file datablock 60 | dataset = Dataset.new(@data, title: 'Dataset title', file: true) 61 | expect(dataset.data.name).to match(/tmp_data/) 62 | expect(dataset.to_s).to eql("#{dataset.data.name} title \"Dataset title\"") 63 | end 64 | end 65 | 66 | context 'creation with Daru' do 67 | before do 68 | @xtic = %w(1991 1993 1995 1997) 69 | @y = [2453, 2343, 2454, 2254] 70 | @yerr = [120, 133, 123, 113] 71 | @title = :plot_from_daru 72 | @data = (0..3).map { |i| "\"#{@xtic[i]}\" #{@y[i]} #{@yerr[i]}\n" }.join 73 | @df = Daru::DataFrame.new([@y, @yerr], order: [:y, :yerr], index: @xtic, name: @title) 74 | @alt_title = 'Some other given title' 75 | @vector = Daru::Vector.new(@y, index: @xtic, name: @title) 76 | end 77 | 78 | it 'may be created with Daru::Vector given' do 79 | ds = Dataset.new(@vector) 80 | expect(ds.title).to eql(@title) 81 | expect(ds.using).to eql('2:xtic(1)') 82 | data = (0..3).map { |i| "\"#{@xtic[i]}\" #{@y[i]}\n" }.join 83 | expect(ds.data.instance_variable_get(:@data)).to eql(data) 84 | end 85 | 86 | it 'may be created with Daru::DataFrame given' do 87 | ds = Dataset.new(@df) 88 | expect(ds.title).to eql(@title) 89 | expect(ds.using).to eql('2:3:xtic(1)') 90 | expect(ds.data.instance_variable_get(:@data)).to eql(@data) 91 | end 92 | 93 | it "should use given title instead of Daru's" do 94 | expect(Dataset.new(@vector, title: @alt_title).title).to eql(@alt_title) 95 | expect(Dataset.new(@df, title: @alt_title).title).to eql(@alt_title) 96 | end 97 | 98 | it 'may be created with Daru::DataFrame and *using* option given' do 99 | ds = Dataset.new(@df, using: 'index:yerr:xtic(y)') 100 | expect(ds.using).to eql('1:3:xtic(2)') 101 | expect(ds.data.instance_variable_get(:@data)).to eql(@data) 102 | end 103 | end 104 | 105 | context 'options handling' do 106 | before do 107 | @options = Hamster.hash(title: 'GnuplotRB::Dataset', with: 'lines') 108 | @dataset = Dataset.new('sin(x)', @options) 109 | end 110 | 111 | it 'should allow to get option value by name' do 112 | @options.each { |key, value| expect(@dataset.send(key)).to eql(value) } 113 | end 114 | 115 | it 'should allow to safely set option value by name' do 116 | new_with = 'points' 117 | new_dataset = @dataset.with(new_with) 118 | expect(@dataset.with).to equal(@options[:with]) 119 | expect(new_dataset.with).to equal(new_with) 120 | end 121 | 122 | it 'should allow to get all the options' do 123 | expect(@dataset.options).to eql(@options) 124 | end 125 | 126 | it 'should allow to safely set several options at once' do 127 | new_options = Hamster.hash(title: 'Some new title', with: 'lines', lt: 3) 128 | new_dataset = @dataset.options(new_options) 129 | @options.each { |key, value| expect(@dataset.send(key)).to eql(value) } 130 | new_options.each { |key, value| expect(new_dataset.send(key)).to eql(value) } 131 | end 132 | end 133 | 134 | context 'safe update' do 135 | before do 136 | x = (0..10).to_a 137 | y = x.map { |xx| Math.exp(-xx) } 138 | @data = [x, y] 139 | @options = Hamster.hash(title: 'GnuplotRB::Dataset', with: 'lines') 140 | @sinx = Dataset.new('sin(x)', @options) 141 | @dataset = Dataset.new([x, y]) 142 | @temp_file_dataset = Dataset.new([x, y], file: true) 143 | end 144 | 145 | it 'should update options' do 146 | # works just as Dataset#options(...) 147 | new_options = Hamster.hash(title: 'Some new title', with: 'lines', lt: 3) 148 | new_dataset = @sinx.update(new_options) 149 | @options.each { |key, value| expect(@sinx.send(key)).to eql(value) } 150 | new_options.each { |key, value| expect(new_dataset.send(key)).to eql(value) } 151 | end 152 | 153 | it 'should do nothing if no options given and no data update needed' do 154 | expect(@dataset.update).to equal(@dataset) 155 | expect(@sinx.update('some new data')).to equal(@sinx) 156 | end 157 | 158 | it 'should update if options are given and no data update needed' do 159 | updated = @dataset.update(@options) 160 | expect(updated).to_not equal(@dataset) 161 | @options.each { |key, value| expect(updated.send(key)).to eql(value) } 162 | end 163 | 164 | it 'should update datablock stored in here-doc' do 165 | updated = @dataset.update(@data) 166 | expect(updated.data).to_not equal(@dataset.data) 167 | end 168 | 169 | it 'should update datablock stored in temp file' do 170 | filename = @temp_file_dataset.data.name[1..-2] # avoid '' on ends 171 | size_before_update = File.size(filename) 172 | updated = @temp_file_dataset.update(@data) 173 | size_after_update = File.size(filename) 174 | expect(updated.data).to equal(@temp_file_dataset.data) 175 | expect(size_after_update).to be > size_before_update 176 | end 177 | end 178 | 179 | context 'destructive update' do 180 | before :each do 181 | x = (0..10).to_a 182 | y = x.map { |xx| Math.exp(-xx) } 183 | @data = [x, y] 184 | @ds = Dataset.new(@data) 185 | @ds_file = Dataset.new(@data, file: true) 186 | end 187 | 188 | it 'should update an option of existing object' do 189 | expect(@ds.lw!(3)).to equal(@ds) 190 | expect(@ds.lw).to eql(3) 191 | @ds.pt = 8 192 | expect(@ds.pt).to eql(8) 193 | end 194 | 195 | it 'should update several options of existing object at once via #options!' do 196 | expect(@ds.options!(lw: 3, pt: 8)).to equal(@ds) 197 | expect(@ds.lw).to eql(3) 198 | expect(@ds.pt).to eql(8) 199 | end 200 | 201 | it 'should update several options of existing object at once via #update!' do 202 | expect(@ds.update!(lw: 3, pt: 8)).to equal(@ds) 203 | expect(@ds.lw).to eql(3) 204 | expect(@ds.pt).to eql(8) 205 | end 206 | 207 | it 'should update data of existing in-memory datablock at once' do 208 | paths = (0..1).map { |i| File.join(@tmp_dir, "#{i}plot.png") } 209 | options0 = { term: ['png', size: [300, 300]], output: paths[0] } 210 | options1 = { term: ['png', size: [300, 300]], output: paths[1] } 211 | x1 = (11..15).to_a 212 | y1 = x1.map { |xx| Math.exp(-xx) } 213 | @ds.plot(options0) 214 | expect(@ds.update!([x1, y1])).to equal(@ds) 215 | @ds.plot(options1) 216 | expect(same_images?(*paths)).to be_falsey 217 | end 218 | 219 | it 'should update data of existing in-file datablock at once' do 220 | x1 = (11..15).to_a 221 | y1 = x1.map { |xx| Math.exp(-xx) } 222 | size_before = File.size(@ds_file.data.to_s[1..-2]) 223 | expect(@ds_file.update!([x1, y1])).to equal(@ds_file) 224 | size_after = File.size(@ds_file.data.to_s[1..-2]) 225 | expect(size_after).to be > size_before 226 | end 227 | end 228 | end 229 | -------------------------------------------------------------------------------- /spec/fit_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper.rb' 2 | 3 | describe GnuplotRB::Fit do 4 | before(:all) do 5 | @tmp_dir = File.join('spec', 'tmp') 6 | Dir.mkdir(@tmp_dir) 7 | @datafile_path = File.join('spec', 'points.data') 8 | x = (1..100).map { |xx| xx / 10.0 } 9 | y = x.map { |xx| 3.0 * Math.exp(xx / 3) } 10 | @data = [x, y] 11 | end 12 | 13 | after(:all) do 14 | FileUtils.rm_r(@tmp_dir) 15 | end 16 | 17 | context 'input' do 18 | it 'should take Dataset as input' do 19 | ds = Dataset.new(@data) 20 | expect(fit(ds)).to be_instance_of(Hash) 21 | end 22 | 23 | it 'should take Datablock as input' do 24 | db = Datablock.new(@data) 25 | expect(fit(db)).to be_instance_of(Hash) 26 | db_in_file = Datablock.new(@data, true) 27 | expect(fit(db_in_file)).to be_instance_of(Hash) 28 | end 29 | 30 | it 'should take something out of which Dataset may be constructed as input' do 31 | expect(fit(@data)).to be_instance_of(Hash) 32 | expect(fit(@datafile_path)).to be_instance_of(Hash) 33 | end 34 | end 35 | 36 | context 'fitting data' do 37 | it 'should throw error in case of wrong input' do 38 | expect { fit(@data, formula: 'wrong_formula') }.to raise_error(GnuplotError) 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/gnuplot_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper.rb' 2 | 3 | describe GnuplotRB do 4 | it 'should be awesome' do 5 | expect(awesome?).to be_truthy 6 | end 7 | 8 | it 'should know its version' do 9 | expect(Settings.version).to be_a(Numeric) 10 | end 11 | 12 | context 'check examples' do 13 | samples = Dir.glob('./examples/*plot*') 14 | samples.each do |path| 15 | name = path[11..-1] 16 | it "should work with #{name} example" do 17 | expect(run_example_at path).to be_truthy 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/multiplot_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper.rb' 2 | 3 | describe Multiplot do 4 | before(:all) do 5 | @tmp_dir = File.join('spec', 'tmp') 6 | Dir.mkdir(@tmp_dir) 7 | @datafile_path = File.join('spec', 'points.data') 8 | end 9 | 10 | after(:all) do 11 | FileUtils.rm_r(@tmp_dir) 12 | end 13 | 14 | context 'creation' do 15 | before do 16 | @title = 'Multiplot spec' 17 | @formula = %w(sin(x) cos(x) exp(-x)) 18 | @plots = @formula.map { |f| Plot.new(f) } 19 | @plots << Splot.new('sin(x) * cos(y)') 20 | @options = { title: @title, layout: [2, 2] } 21 | end 22 | 23 | it 'should be created out of sequence of plots' do 24 | expect(Multiplot.new(*@plots)).to be_an_instance_of(Multiplot) 25 | end 26 | 27 | it 'should set options passed to constructor' do 28 | mp = Multiplot.new(*@plots, **@options) 29 | expect(mp).to be_an_instance_of(Multiplot) 30 | expect(mp.title).to eql(@title) 31 | end 32 | end 33 | 34 | context 'option handling' do 35 | before do 36 | @options = Hamster.hash(title: 'GnuplotRB::Multiplot', yrange: 0..3) 37 | @mp = Multiplot.new(**@options) 38 | end 39 | 40 | it 'should allow to get option value by name' do 41 | expect(@mp.title).to eql(@options[:title]) 42 | end 43 | 44 | it 'should know which options are Multiplot special' do 45 | expect(@mp.send(:specific_option?, :layout)).to be_truthy 46 | expect(@mp.send(:specific_option?, :title)).to be_truthy 47 | expect(@mp.send(:specific_option?, :xrange)).to be_falsey 48 | end 49 | 50 | it 'should return Multiplot object' do 51 | new_options = { title: 'Another title', xrange: 1..5 } 52 | new_plot = @mp.options(new_options) 53 | expect(new_plot).to_not equal(@mp) 54 | expect(new_plot).to be_an_instance_of(Multiplot) 55 | end 56 | end 57 | 58 | context 'safe plot array update' do 59 | before :each do 60 | @sinx = Plot.new('sin(x)') 61 | @plot3d = Splot.new('sin(x)*cos(y)') 62 | @exp = Plot.new('exp(x)') 63 | @paths = (0..1).map { |i| File.join(@tmp_dir, "#{i}plot.png") } 64 | @options0 = { term: ['png', size: [300, 300]], output: @paths[0] } 65 | @options1 = { term: ['png', size: [300, 300]], output: @paths[1] } 66 | end 67 | 68 | it 'should allow to remove plot from Mp' do 69 | mp = Multiplot.new(@sinx, @plot3d, @exp) 70 | updated_mp = mp.remove_plot(1) 71 | expect(mp).to_not equal(updated_mp) 72 | updated_mp.plot(@options0) 73 | Multiplot.new(@sinx, @exp).plot(@options1) 74 | expect(same_images?(*@paths)).to be_truthy 75 | end 76 | 77 | it 'should allow to add plot to Mp' do 78 | mp = Multiplot.new(@sinx, @exp) 79 | updated_mp = mp.add_plot(@plot3d) 80 | expect(mp).to_not equal(updated_mp) 81 | updated_mp.plot(@options0) 82 | Multiplot.new(@plot3d, @sinx, @exp).plot(@options1) 83 | expect(same_images?(*@paths)).to be_truthy 84 | end 85 | 86 | it 'should allow to replace plot in Mp' do 87 | mp = Multiplot.new(@sinx, @exp) 88 | updated_mp = mp.replace_plot(@plot3d) 89 | expect(mp).to_not equal(updated_mp) 90 | updated_mp.plot(@options0) 91 | Multiplot.new(@plot3d, @exp).plot(@options1) 92 | expect(same_images?(*@paths)).to be_truthy 93 | end 94 | 95 | it 'should allow to update options of any plot in mp' do 96 | ttl = "Wow, it's sin(x)!" 97 | mp = Multiplot.new(@sinx, @exp) 98 | updated_mp = mp.update_plot(0, title: ttl) 99 | expect(mp).to_not equal(updated_mp) 100 | updated_mp.plot(@options0) 101 | Multiplot.new(@sinx.title(ttl), @exp).plot(@options1) 102 | expect(same_images?(*@paths)).to be_truthy 103 | end 104 | 105 | it 'should allow to update datasets of any plot in mp' do 106 | ds_ttl = 'Some dataset' 107 | ttl = "Wow, it's sin(x)!" 108 | mp = Multiplot.new(@sinx, @exp) 109 | updated_mp = mp.update_plot(0, title: ttl) do |new_plot| 110 | new_plot.update_dataset(title: ds_ttl) 111 | end 112 | expect(mp).to_not equal(updated_mp) 113 | updated_mp.plot(@options0) 114 | udpdated_sinx = @sinx.title(ttl).update_dataset(title: ds_ttl) 115 | Multiplot.new(udpdated_sinx, @exp).plot(@options1) 116 | expect(same_images?(*@paths)).to be_truthy 117 | end 118 | 119 | it 'should allow to get plots using []' do 120 | mp = Multiplot.new(@sinx, @exp, @plot3d) 121 | (0..2).each { |i| expect(mp[i]).to be_equal(mp.plots[i]) } 122 | expect(mp[0..-1]).to be_eql(mp.plots) 123 | end 124 | end 125 | 126 | context 'destructive plot array update' do 127 | before :each do 128 | plots = [ 129 | Plot.new('sin(x)'), 130 | Splot.new('sin(x)*cos(y)'), 131 | Plot.new('exp(x)') 132 | ] 133 | @mp = Multiplot.new(*plots, layout: [1, 3]) 134 | end 135 | 136 | it 'should update plots in the existing Plot' do 137 | @mp.update!(0) do |plot| 138 | plot.options!(title: 'Updated plot') 139 | plot.replace_dataset!('exp(x)') 140 | plot[0].lw!(3) 141 | end 142 | expect(@mp[0].title).to eql('Updated plot') 143 | expect(@mp[0][0].data).to eql('exp(x)') 144 | expect(@mp[0][0].lw).to eql(3) 145 | end 146 | 147 | it 'should replace plot in the existing Multiplot' do 148 | plot = Plot.new('cos(x)', title: 'Second plot') 149 | expect(@mp.replace!(1, plot)).to equal(@mp) 150 | expect(@mp[1].title).to eql('Second plot') 151 | @mp[0] = Plot.new('cos(x)', title: 'First plot') 152 | expect(@mp[0].title).to eql('First plot') 153 | end 154 | 155 | it 'should add plots to the existing Multiplot' do 156 | plot = Plot.new('cos(x)', title: 'First plot') 157 | expect(@mp.add!(plot)).to equal(@mp) 158 | expect(@mp[0].title).to eql('First plot') 159 | expect(@mp.plots.size).to eql(4) 160 | end 161 | 162 | it 'should remove plot from the existing Multiplot' do 163 | plot = Plot.new('cos(x)', title: 'Last plot') 164 | expect(@mp.add!(-1, plot)).to equal(@mp) 165 | expect(@mp.plots.size).to eql(4) 166 | expect(@mp.remove!).to equal(@mp) 167 | expect(@mp.plots.size).to eql(3) 168 | expect(@mp.plots.last.title).to eql('Last plot') 169 | end 170 | end 171 | end 172 | -------------------------------------------------------------------------------- /spec/plot_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper.rb' 2 | 3 | describe Plot do 4 | before(:all) do 5 | @tmp_dir = File.join('spec', 'tmp') 6 | Dir.mkdir(@tmp_dir) 7 | @datafile_path = File.join('spec', 'points.data') 8 | end 9 | 10 | after(:all) do 11 | FileUtils.rm_r(@tmp_dir) 12 | end 13 | 14 | context 'creation' do 15 | before do 16 | @title = 'Awesome spec' 17 | @formula = %w(sin(x) cos(x) exp(-x)) 18 | @options = { title: @title, term: 'dumb' } 19 | @df = Daru::DataFrame.new( 20 | Build: [312, 630, 315, 312], 21 | Test: [525, 1050, 701, 514], 22 | Deploy: [215, 441, 370, 220] 23 | ) 24 | end 25 | 26 | it 'should be created out of sequence of datasets' do 27 | datasets = @formula.map { |formula| Dataset.new(formula) } 28 | expect(Plot.new(*datasets)).to be_an_instance_of(Plot) 29 | end 30 | 31 | it 'should be created out of sequence of arrays' do 32 | expect(Plot.new(*@formula)).to be_an_instance_of(Plot) 33 | end 34 | 35 | it 'should set options passed to constructor' do 36 | plot = Plot.new(*@formula, **@options) 37 | expect(plot).to be_an_instance_of(Plot) 38 | expect(plot.title).to eql(@title) 39 | end 40 | 41 | it 'should be created out of Daru::DataFrame' do 42 | p = Plot.new(@df) 43 | expect(p).to be_an_instance_of(Plot) 44 | expect(p.datasets.size).to be_eql(3) 45 | end 46 | end 47 | 48 | context 'options handling' do 49 | before do 50 | @options = Hamster.hash(title: 'GnuplotRB::Plot', yrange: 0..3) 51 | @plot = Plot.new(**@options) 52 | end 53 | 54 | it 'should allow to get option value by name' do 55 | expect(@plot.title).to eql(@options[:title]) 56 | end 57 | 58 | it 'should allow to safely set option value by name' do 59 | another_title = 'Some new titile' 60 | new_plot = @plot.title(another_title) 61 | expect(@plot).not_to equal(new_plot) 62 | expect(new_plot.title).to eql(another_title) 63 | expect(@plot.title).to eql(@options[:title]) 64 | end 65 | 66 | it 'should allow to get terminal' do 67 | expect(@plot.own_terminal).to be_an_instance_of(Terminal) 68 | end 69 | 70 | it 'should allow to get all the options' do 71 | expect(@plot.options).to eql(@options) 72 | end 73 | 74 | it 'should allow to safely set several options at once' do 75 | new_options = { title: 'Another title', xrange: 1..5 } 76 | new_plot = @plot.options(new_options) 77 | expect(new_plot).to_not equal(@plot) 78 | expect(new_plot).to be_an_instance_of(Plot) 79 | expect(new_plot.options).to eql(@options.merge(new_options)) 80 | end 81 | end 82 | 83 | context 'safe datasets update' do 84 | before do 85 | @plot_math = Plot.new(['sin(x)', title: 'Just a sin']) 86 | @dataset = Dataset.new('exp(-x)') 87 | @plot_two_ds = Plot.new(['cos(x)'], ['x*x']) 88 | @options = { title: 'Example dataset' } 89 | @plot_datafile = Plot.new([@datafile_path]) 90 | @data = [1, 2, 3, 4] 91 | @plot_data_inmemory = Plot.new([@data]) 92 | @plot_data_tempfile = Plot.new([@data, file: true]) 93 | end 94 | 95 | it 'should create new Plot when user adds a dataset' do 96 | new_plot = @plot_math.add_dataset(@dataset) 97 | expect(new_plot).to_not be_equal(@plot_math) 98 | end 99 | 100 | it 'should create new Plot when user adds a dataset using #<<' do 101 | new_plot = @plot_math << @dataset 102 | expect(new_plot).to_not be_equal(@plot_math) 103 | end 104 | 105 | it 'should create new Plot when user removes a dataset' do 106 | new_plot = @plot_two_ds.remove_dataset 107 | expect(new_plot).to_not be_equal(@plot_two_ds) 108 | end 109 | 110 | it 'should remove dataset exactly at given position' do 111 | (0..1).each do |i| 112 | j = i == 0 ? 1 : 0 113 | new_plot = @plot_two_ds.remove_dataset(i) 114 | expect(new_plot.datasets[0].data).to be_eql(@plot_two_ds.datasets[j].data) 115 | end 116 | end 117 | 118 | it 'should create new Plot when user replaces a dataset' do 119 | new_plot = @plot_two_ds.replace_dataset(@dataset) 120 | expect(new_plot).to_not be_equal(@plot_two_ds) 121 | end 122 | 123 | it 'should remplace dataset exactly at given position' do 124 | (0..1).each do |i| 125 | new_plot = @plot_two_ds.replace_dataset(i, @dataset) 126 | expect(new_plot.datasets[i].data).to be_eql(@dataset.data) 127 | end 128 | end 129 | 130 | it 'should allow to update dataset at given position with options' do 131 | (0..1).each do |i| 132 | new_plot = @plot_two_ds.update_dataset(i, @options) 133 | expect(new_plot.datasets[i].options.to_h).to be_eql(@options) 134 | expect(new_plot.datasets[i]).to_not equal(@plot_two_ds.datasets[i]) 135 | end 136 | end 137 | 138 | it 'should not update Plot if neither data nor options update needed' do 139 | # data and options are empty so no update needed 140 | expect(@plot_math.update_dataset).to be_equal(@plot_math) 141 | # dataset with math formula could not to be updated 142 | expect(@plot_math.update_dataset(data: @data)).to be_equal(@plot_math) 143 | # dataset with data from existing file could not to be updated 144 | expect(@plot_datafile.update_dataset(data: @data)).to be_equal(@plot_datafile) 145 | end 146 | 147 | it 'should create new Plot (and new datablock) if you update data stored in memory' do 148 | current = File.join(@tmp_dir, 'plot.png') 149 | updated = File.join(@tmp_dir, 'updated_plot.png') 150 | new_plot = @plot_data_inmemory.update_dataset(data: @data) 151 | expect(new_plot).to_not be_equal(@plot_data_inmemory) 152 | @plot_data_inmemory.to_png(current, size: [200, 200]) 153 | new_plot.to_png(updated, size: [200, 200]) 154 | expect(same_images?(current, updated)).to be_falsy 155 | end 156 | 157 | it 'should not create new Plot (and new datablock) if you update data stored in temp file' do 158 | old = File.join(@tmp_dir, 'old_plot.png') 159 | current = File.join(@tmp_dir, 'plot.png') 160 | updated = File.join(@tmp_dir, 'updated_plot.png') 161 | @plot_data_tempfile.to_png(old, size: [200, 200]) 162 | new_plot = @plot_data_tempfile.update_dataset(data: @data) 163 | expect(new_plot).to be_equal(@plot_data_tempfile) 164 | @plot_data_tempfile.to_png(current, size: [200, 200]) 165 | new_plot.to_png(updated, size: [200, 200]) 166 | expect(same_images?(current, updated)).to be_truthy 167 | expect(same_images?(current, old)).to be_falsy 168 | end 169 | 170 | it 'should allow to get datasets using []' do 171 | (0..1).each { |i| expect(@plot_two_ds[i]).to be_equal(@plot_two_ds.datasets[i]) } 172 | expect(@plot_two_ds[0..-1]).to be_eql(@plot_two_ds.datasets) 173 | end 174 | end 175 | 176 | context 'destructive datasets update' do 177 | before :each do 178 | @plot = Plot.new('sin(x)') 179 | end 180 | 181 | it 'should update datasets in the existing Plot' do 182 | expect(@plot.update_dataset!(lw: 3)).to equal(@plot) 183 | expect(@plot.datasets[0].lw).to eql(3) 184 | end 185 | 186 | it 'should replace dataset in the existing Plot' do 187 | expect(@plot.replace_dataset!('exp(x)')).to equal(@plot) 188 | expect(@plot.datasets[0].data).to eql('exp(x)') 189 | @plot[0] = 'cos(x)' 190 | expect(@plot.datasets[0].data).to eql('cos(x)') 191 | end 192 | 193 | it 'should add datasets to the existing Plot' do 194 | expect(@plot.add_dataset!('exp(x)')).to equal(@plot) 195 | expect(@plot.datasets[0].data).to eql('exp(x)') 196 | expect(@plot.datasets[1].data).to eql('sin(x)') 197 | end 198 | 199 | it 'should remove dataset from the existing Plot' do 200 | @plot.add_dataset!(1, 'exp(x)', 'cos(x)') 201 | expect(@plot.datasets.size).to eql(3) 202 | expect(@plot.remove_dataset!).to equal(@plot) 203 | expect(@plot.datasets.size).to eql(2) 204 | expect(@plot.datasets[0].data).to eql('sin(x)') 205 | expect(@plot.datasets[1].data).to eql('exp(x)') 206 | expect(@plot.datasets[2]).to be nil 207 | end 208 | end 209 | 210 | context '#to_iruby' do 211 | it 'should handle output to iRuby' do 212 | available_terminals = { 213 | 'png' => 'image/png', 214 | 'pngcairo' => 'image/png', 215 | 'jpeg' => 'image/jpeg', 216 | 'svg' => 'image/svg+xml', 217 | 'dumb' => 'text/plain' 218 | } 219 | available_terminals.each do |term, type| 220 | if OptionHandling::valid_terminal?(term) 221 | expect(Plot.new('sin(x)', term: term).to_iruby[0]).to eql(type) 222 | end 223 | end 224 | end 225 | 226 | it 'should use svg as default iruby terminal' do 227 | expect(Plot.new('sin(x)').to_iruby[0]).to eql('image/svg+xml') 228 | end 229 | end 230 | end 231 | -------------------------------------------------------------------------------- /spec/points.data: -------------------------------------------------------------------------------- 1 | 0.0 1.0 2 | 0.1 0.9048374180359595 3 | 0.2 0.8187307530779818 4 | 0.3 0.7408182206817179 5 | 0.4 0.6703200460356393 6 | 0.5 0.6065306597126334 7 | 0.6 0.5488116360940264 8 | 0.7 0.4965853037914095 9 | 0.8 0.44932896411722156 10 | 0.9 0.4065696597405991 11 | 1.0 0.36787944117144233 12 | 1.1 0.33287108369807955 13 | 1.2 0.30119421191220214 14 | 1.3 0.2725317930340126 15 | 1.4 0.2465969639416065 16 | 1.5 0.22313016014842982 17 | 1.6 0.20189651799465538 18 | 1.7 0.18268352405273466 19 | 1.8 0.16529888822158653 20 | 1.9 0.14956861922263506 21 | 2.0 0.1353352832366127 22 | 2.1 0.1224564282529819 23 | 2.2 0.11080315836233387 24 | 2.3 0.10025884372280375 25 | 2.4 0.09071795328941251 26 | 2.5 0.0820849986238988 27 | 2.6 0.07427357821433388 28 | 2.7 0.06720551273974976 29 | 2.8 0.06081006262521797 30 | 2.9 0.05502322005640723 31 | 3.0 0.049787068367863944 32 | 3.1 0.0450492023935578 33 | 3.2 0.04076220397836621 34 | 3.3 0.036883167401240015 35 | 3.4 0.03337326996032608 36 | 3.5 0.0301973834223185 37 | 3.6 0.02732372244729256 38 | 3.7 0.024723526470339388 39 | 3.8 0.0223707718561656 40 | 3.9 0.02024191144580439 41 | 4.0 0.01831563888873418 42 | 4.1 0.016572675401761255 43 | 4.2 0.014995576820477703 44 | 4.3 0.013568559012200934 45 | 4.4 0.012277339903068436 46 | 4.5 0.011108996538242306 47 | 4.6 0.010051835744633586 48 | 4.7 0.009095277101695816 49 | 4.8 0.00822974704902003 50 | 4.9 0.007446583070924338 51 | 5.0 0.006737946999085467 -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | require 'codeclimate-test-reporter' 3 | require 'digest' 4 | require 'chunky_png' 5 | require 'digest/md5' 6 | SimpleCov.add_filter 'vendor' 7 | SimpleCov.add_filter 'examples' 8 | SimpleCov.formatter = CodeClimate::TestReporter::Formatter 9 | SimpleCov.start CodeClimate::TestReporter.configuration.profile 10 | 11 | require 'gnuplotrb' 12 | 13 | include ChunkyPNG::Color 14 | include GnuplotRB 15 | include GnuplotRB::Fit 16 | 17 | $RSPEC_TEST = true 18 | 19 | def same_images?(*imgs) 20 | images = imgs.map { |img| Digest::MD5.digest(ChunkyPNG::Image.from_file(img).pixels.to_s) } 21 | images.all? { |img| img == images[0] } 22 | end 23 | 24 | # may be errorneuos in comparing images 25 | def same_files?(*files) 26 | images = files.map { |fname| Digest::MD5.digest(File.binread(fname)) } 27 | images.all? { |img| img == images[0] } 28 | end 29 | 30 | def awesome? 31 | # sure! 32 | true 33 | end 34 | 35 | def run_example_at(path) 36 | Dir.chdir(path) do 37 | FileUtils.rm(Dir["#{Dir.pwd}/*.png"]) 38 | require "#{Dir.pwd}/plot.rb" 39 | # run gnuplot without output to current console 40 | `gnuplot plot.gnuplot 2>&1` 41 | same_images?('gnuplot.png', 'gnuplot_gem.png') 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/splot_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper.rb' 2 | 3 | describe Splot do 4 | before(:all) do 5 | @tmp_dir = File.join('spec', 'tmp') 6 | Dir.mkdir(@tmp_dir) 7 | @datafile_path = File.join('spec', 'points.data') 8 | end 9 | 10 | after(:all) do 11 | FileUtils.rm_r(@tmp_dir) 12 | end 13 | 14 | context 'creation' do 15 | before do 16 | @title = 'Awesome spec' 17 | @formula = %w(sin(x) cos(x) exp(-x)) 18 | @options = { title: @title, term: 'dumb' } 19 | end 20 | 21 | it 'should use *splot* command instead of *plot*' do 22 | plot = Splot.new(*@formula, **@options) 23 | expect(plot).to be_an_instance_of(Splot) 24 | expect(plot.instance_variable_get(:@cmd)).to eql('splot ') 25 | end 26 | end 27 | 28 | context 'options handling' do 29 | before do 30 | @options = Hamster.hash(title: 'GnuplotRB::Plot', yrange: 0..3) 31 | @plot = Splot.new(**@options) 32 | end 33 | 34 | it 'should return Splot object' do 35 | new_options = { title: 'Another title', xrange: 1..5 } 36 | new_plot = @plot.options(new_options) 37 | expect(new_plot).to_not equal(@plot) 38 | expect(new_plot).to be_an_instance_of(Splot) 39 | end 40 | end 41 | 42 | context 'modifying datasets' do 43 | before do 44 | @plot_math = Splot.new(['sin(x)', title: 'Just a sin']) 45 | @dataset = Dataset.new('exp(-x)') 46 | @plot_two_ds = Splot.new(['cos(x)'], ['x*x']) 47 | end 48 | 49 | it 'should create new *Splot* when user adds a dataset' do 50 | new_plot = @plot_math.add_dataset(@dataset) 51 | expect(new_plot).to be_instance_of(Splot) 52 | end 53 | 54 | it 'should create new *Splot* when user adds a dataset using #<<' do 55 | new_plot = @plot_math << @dataset 56 | expect(new_plot).to be_instance_of(Splot) 57 | end 58 | 59 | it 'should create new *Splot* when user removes a dataset' do 60 | new_plot = @plot_two_ds.remove_dataset 61 | expect(new_plot).to be_instance_of(Splot) 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /spec/terminal_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper.rb' 2 | 3 | describe Terminal do 4 | before(:all) do 5 | @tmp_dir = File.join('spec', 'tmp') 6 | Dir.mkdir(@tmp_dir) 7 | @datafile_path = File.join('spec', 'points.data') 8 | end 9 | 10 | after(:all) do 11 | FileUtils.rm_r(@tmp_dir) 12 | end 13 | 14 | before(:each) do 15 | @terminal = Terminal.new 16 | @paths = (0..1).map { |i| File.join(@tmp_dir, "#{i}plot.png") } 17 | @options0 = { term: ['png', size: [300, 300]], output: @paths[0] } 18 | @options1 = { term: ['png', size: [300, 300]], output: @paths[1] } 19 | @dataset = Dataset.new('sin(x)') 20 | @plot = Plot.new(@dataset, @options0) 21 | end 22 | 23 | context 'options handling' do 24 | it 'should work with String as option value' do 25 | options = { term: 'qt' } 26 | string = @terminal.options_hash_to_string(options) 27 | expect(string.strip).to be_eql('set term qt') 28 | end 29 | 30 | it 'should work with Boolean and nil as option value' do 31 | [ 32 | [{ multiplot: true }, 'set multiplot'], 33 | [{ multiplot: false }, 'unset multiplot'], 34 | [{ multiplot: nil }, 'unset multiplot'] 35 | ].each do |variant| 36 | string = @terminal.options_hash_to_string(variant[0]) 37 | expect(string.strip).to be_eql(variant[1]) 38 | end 39 | end 40 | 41 | it 'should work with Array and Hash as option value' do 42 | # it works with arrays of numbers different way 43 | options = { term: ['qt', size: [100, 100]] } 44 | string = @terminal.options_hash_to_string(options) 45 | expect(string.strip).to be_eql('set term qt size 100,100') 46 | end 47 | end 48 | 49 | context '#<<' do 50 | it 'should output Plots' do 51 | @plot.plot 52 | FileUtils.mv(@paths[0], @paths[1]) 53 | @terminal << @plot 54 | expect(same_images?(*@paths)).to be_truthy 55 | end 56 | 57 | it 'should output Datasets' do 58 | @plot.plot 59 | @terminal.set(@options1) 60 | @terminal << @dataset 61 | expect(same_images?(*@paths)).to be_truthy 62 | end 63 | 64 | it 'should output whatever you want just as string' do 65 | @plot.plot 66 | @terminal << @terminal.options_hash_to_string(@options1) 67 | @terminal << @dataset 68 | expect(same_images?(*@paths)).to be_truthy 69 | end 70 | end 71 | 72 | context '#stream_puts' do 73 | it 'should output whatever you want just as string' do 74 | @plot.plot 75 | @terminal.stream_puts(@terminal.options_hash_to_string(@options1)) 76 | @terminal << @dataset 77 | expect(same_images?(*@paths)).to be_truthy 78 | end 79 | end 80 | 81 | context '#replot' do 82 | it 'should replot last plotted graph on #replot call' do 83 | @plot.plot(@terminal) 84 | @terminal.replot(@options1) 85 | expect(same_images?(*@paths)).to be_truthy 86 | end 87 | end 88 | 89 | context 'check correctness of a terminal' do 90 | it 'should raise an error when trying to use incorrect terminal' do 91 | expect { Plot.new(term: 'incorrect_term') }.to raise_error(ArgumentError) 92 | expect { Plot.new(term: ['incorrect_term', size: [300, 300]]) }.to raise_error(ArgumentError) 93 | expect { Plot.new.to_incorrect_term }.to raise_error(NoMethodError) 94 | expect { Plot.new.plot(term: 'incorrect_term') }.to raise_error(ArgumentError) 95 | expect { Plot.new.plot(term: ['incorrect_term']) }.to raise_error(ArgumentError) 96 | end 97 | end 98 | 99 | context 'gnuplot error handling' do 100 | it 'should raise exception only in next command after a delay' do 101 | @terminal.set(wrong_option: 'wrong_value') 102 | # it takes gnuplot some time to find error 103 | # and output it to stderr 104 | sleep 0.3 105 | expect { @terminal.set(polar: true) }.to raise_error(GnuplotError) 106 | expect { @terminal.set(polar: true) }.to_not raise_error 107 | end 108 | end 109 | end 110 | --------------------------------------------------------------------------------