gnuplotrb 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,44 +6,12 @@ module GnuplotRB
6
6
  module Plottable
7
7
  include OptionHandling
8
8
 
9
- ##
10
- # Terminal object used by this Plottable to pipe data to gnuplot.
11
- attr_reader :terminal
12
-
13
9
  ##
14
10
  # You should implement #plot in classes that are Plottable
15
- def plot(*args)
11
+ def plot(*_)
16
12
  fail NotImplementedError, 'You should implement #plot in classes that are Plottable!'
17
13
  end
18
14
 
19
- ##
20
- # Method for inner use.
21
- # ====== Overview
22
- # Method which outputs plot to specific terminal (possibly some file).
23
- # Explicit use should be avoided. This method is called from #method_missing
24
- # when it handles method names like #to_png(options).
25
- # ====== Arguments
26
- # * *terminal* - string corresponding to terminal type (png, html, jpeg etc)
27
- # * *path* - path to output file, if none given it will output to temp file
28
- # and then read it and return binary contents of file
29
- # * *options* - used in 'set term <term type> <options here>'
30
- # ====== Examples
31
- # ## plot here may be Plot, Splot, Multiplot or any other plottable class
32
- # plot.to_png('./result.png', size: [300, 500])
33
- # contents = plot.to_svg(size: [100, 100])
34
- # plot.to_dumb('./result.txt', size: [30, 15])
35
- def to_specific_term(terminal, path = nil, **options)
36
- if path
37
- result = plot(term: [terminal, options], output: path)
38
- else
39
- path = Dir::Tmpname.make_tmpname(terminal, 0)
40
- plot(term: [terminal, options], output: path)
41
- result = File.binread(path)
42
- File.delete(path)
43
- end
44
- result
45
- end
46
-
47
15
  ##
48
16
  # ====== Overview
49
17
  # In this gem #method_missing is used both to handle
@@ -98,5 +66,60 @@ module GnuplotRB
98
66
  term = meth[0..2] == 'to_' && OptionHandling.valid_terminal?(meth[3..-1])
99
67
  term || super
100
68
  end
69
+
70
+ ##
71
+ # This method is used to embed plottable objects
72
+ # into iRuby notebooks. There is
73
+ # {a notebook}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/basic_usage.ipynb]
74
+ # with examples of its usage.
75
+ def to_iruby
76
+ available_terminals = {
77
+ 'png' => 'image/png',
78
+ 'pngcairo' => 'image/png',
79
+ 'jpeg' => 'image/jpeg',
80
+ 'svg' => 'image/svg+xml',
81
+ 'dumb' => 'text/plain'
82
+ }
83
+ terminal, options = term.is_a?(Array) ? [term[0], term[1]] : [term, {}]
84
+ terminal = 'svg' unless available_terminals.keys.include?(terminal)
85
+ [available_terminals[terminal], send("to_#{terminal}".to_sym, **options)]
86
+ end
87
+
88
+ ##
89
+ # :call-seq:
90
+ # to_png('file.png') -> creates file with plot
91
+ # to_svg -> svg file contents
92
+ # to_canvas('plot.html', size: [300,300]) -> creates file with plot
93
+ #
94
+ # ====== Overview
95
+ # Method which outputs plot to specific terminal (possibly some file).
96
+ # Explicit use should be avoided. This method is called from #method_missing
97
+ # when it handles method names like #to_png(options).
98
+ # ====== Arguments
99
+ # * *path* - path to output file, if none given it will output to temp file
100
+ # and then read it and return binary contents of file
101
+ # * *options* - used in #plot
102
+ # ====== Examples
103
+ # ## plot here may be Plot, Splot, Multiplot or any other plottable class
104
+ # plot.to_png('./result.png', size: [300, 500])
105
+ # contents = plot.to_svg(size: [100, 100])
106
+ # plot.to_dumb('./result.txt', size: [30, 15])
107
+ def to_specific_term(terminal, path = nil, **options)
108
+ if path
109
+ result = plot(term: [terminal, options], output: path)
110
+ else
111
+ path = Dir::Tmpname.make_tmpname(terminal, 0)
112
+ plot(term: [terminal, options], output: path)
113
+ result = File.binread(path)
114
+ File.delete(path)
115
+ end
116
+ result
117
+ end
118
+
119
+ ##
120
+ # Returns terminal object linked with this Plottable object.
121
+ def own_terminal
122
+ @terminal ||= Terminal.new
123
+ end
101
124
  end
102
125
  end
@@ -2,6 +2,7 @@ module GnuplotRB
2
2
  ##
3
3
  # === Overview
4
4
  # Multiplot allows to place several plots on one layout.
5
+ # It's usage is covered in {multiplot notebook}[http://nbviewer.ipython.org/github/dilcom/gnuplotrb/blob/master/notebooks/multiplot_layout.ipynb].
5
6
  class Multiplot
6
7
  include Plottable
7
8
  ##
@@ -11,32 +12,16 @@ module GnuplotRB
11
12
  ##
12
13
  # ====== Arguments
13
14
  # * *plots* are Plot or Splot objects which should be placed
14
- # on this multiplot
15
+ # on this multiplot layout
15
16
  # * *options* will be considered as 'settable' options of gnuplot
16
- # ('set xrange [1:10]' for { xrange: 1..10 },
17
- # "set title 'plot'" for { title: 'plot' } etc) just as in Plot.
17
+ # ('set xrange [1:10]' for { xrange: 1..10 } etc) just as in Plot.
18
18
  # Special options of Multiplot are :layout and :title.
19
19
  def initialize(*plots, **options)
20
20
  @plots = plots[0].is_a?(Hamster::Vector) ? plots[0] : Hamster::Vector.new(plots)
21
21
  @options = Hamster.hash(options)
22
- @terminal = Terminal.new
23
22
  OptionHandling.validate_terminal_options(@options)
24
23
  end
25
24
 
26
- ##
27
- # Create new Multiplot object with the same set of plots and
28
- # given options.
29
- def new_with_options(options)
30
- self.class.new(@plots, options)
31
- end
32
-
33
- ##
34
- # Check if given options corresponds to multiplot.
35
- # Multiplot special options are :title and :layout.
36
- def mp_option?(key)
37
- %w(title layout).include?(key.to_s)
38
- end
39
-
40
25
  ##
41
26
  # ====== Overview
42
27
  # This outputs all the plots to term (if given) or to this
@@ -47,15 +32,13 @@ module GnuplotRB
47
32
  # ('set xrange [1:10]', 'set title 'plot'' etc)
48
33
  # Options passed here have priority over already existing.
49
34
  # Inner options of Plots have the highest priority (except
50
- # :term and :output which are ignored).
51
- def plot(term = nil, **options)
52
- all_options = @options.merge(options)
53
- mp_options, plot_options = all_options.partition { |key, _value| mp_option?(key) }
54
- plot_options = plot_options.merge(multiplot: mp_options.to_h)
55
- terminal = term || (plot_options[:output] ? Terminal.new : @terminal)
56
- terminal.set(plot_options)
57
- @plots.each { |graph| graph.plot(terminal, multiplot_part: true) }
58
- terminal.unset(plot_options.keys)
35
+ # :term and :output which are ignored in this case).
36
+ def plot(term = nil, multiplot_part: false, **options)
37
+ plot_options = mix_options(options) do |plot_opts, mp_opts|
38
+ plot_opts.merge(multiplot: mp_opts.to_h)
39
+ end
40
+ terminal = term || (plot_options[:output] ? Terminal.new : own_terminal)
41
+ multiplot(terminal, plot_options)
59
42
  if plot_options[:output]
60
43
  # guaranteed wait for plotting to finish
61
44
  terminal.close unless term
@@ -78,10 +61,10 @@ module GnuplotRB
78
61
  # * *position* - position of plot which you need to update
79
62
  # (by default first plot is updated)
80
63
  # * *options* - options to update plot with
81
- # * method also may take a block which returns a plot
64
+ # * *&block* - method also may take a block which returns a plot
82
65
  # ====== Example
83
66
  # mp = Multiplot.new(Plot.new('sin(x)'), Plot.new('cos(x)'), layout: [2,1])
84
- # updated_mp = mp.update_plot(title: 'Sin(x) and Exp(x)') { |sinx| sinx.add_dataset('exp(x)') }
67
+ # updated_mp = mp.update_plot(title: 'Sin(x) and Exp(x)') { |sinx| sinx.add('exp(x)') }
85
68
  def update_plot(position = 0, **options)
86
69
  return self unless block_given? if options.empty?
87
70
  replacement = @plots[position].options(options)
@@ -110,20 +93,22 @@ module GnuplotRB
110
93
 
111
94
  ##
112
95
  # ====== Overview
113
- # Create new Multiplot with given *plot* added before plot at given *position*.
96
+ # Create new Multiplot with given *plots* added before plot at given *position*.
114
97
  # (by default it adds plot at the front).
115
98
  # ====== Arguments
116
99
  # * *position* - position before which you want to add a plot
117
- # * *plot* - plot you want to add
100
+ # * *plots* - sequence of plots you want to add
118
101
  # ====== Example
119
102
  # mp = Multiplot.new(Plot.new('sin(x)'), Plot.new('cos(x)'), layout: [2,1])
120
- # enlarged_mp = mp.add_plot(Plot.new('exp(x)')).layout([3,1])
121
- def add_plot(position = 0, plot)
122
- self.class.new(@plots.insert(position, plot), @options)
103
+ # enlarged_mp = mp.add_plots(Plot.new('exp(x)')).layout([3,1])
104
+ def add_plots(*plots)
105
+ plots.unshift(0) unless plots[0].is_a?(Numeric)
106
+ self.class.new(@plots.insert(*plots), @options)
123
107
  end
124
108
 
125
- alias_method :<<, :add_plot
126
- alias_method :add, :add_plot
109
+ alias_method :add_plot, :add_plots
110
+ alias_method :<<, :add_plots
111
+ alias_method :add, :add_plots
127
112
 
128
113
  ##
129
114
  # ====== Overview
@@ -142,9 +127,64 @@ module GnuplotRB
142
127
 
143
128
  ##
144
129
  # ====== Overview
145
- # Equal to #plots[*args]
130
+ # Equal to #plots[*args]
146
131
  def [](*args)
147
132
  @plots[*args]
148
133
  end
134
+
135
+ private
136
+
137
+ ##
138
+ # Default options to be used for that plot
139
+ def default_options
140
+ {
141
+ layout: [2, 2],
142
+ title: 'Multiplot'
143
+ }
144
+ end
145
+
146
+ ##
147
+ # This plot have some specific options which
148
+ # should be handled different way than others.
149
+ # Here are keys of this options.
150
+ def specific_keys
151
+ %w(
152
+ title
153
+ layout
154
+ )
155
+ end
156
+
157
+ ##
158
+ # Create new Multiplot object with the same set of plots and
159
+ # given options.
160
+ # Used in OptionHandling module.
161
+ def new_with_options(options)
162
+ self.class.new(@plots, options)
163
+ end
164
+
165
+ ##
166
+ # Check if given options corresponds to multiplot.
167
+ # Uses #specific_keys to check.
168
+ def specific_option?(key)
169
+ specific_keys.include?(key.to_s)
170
+ end
171
+
172
+ ##
173
+ # Takes all options and splits them into specific and
174
+ # others. Requires a block where this two classes should
175
+ # be mixed.
176
+ def mix_options(options)
177
+ all_options = @options.merge(options)
178
+ specific_options, plot_options = all_options.partition { |key, _value| specific_option?(key) }
179
+ yield(plot_options, default_options.merge(specific_options))
180
+ end
181
+
182
+ ##
183
+ # Just a part of #plot.
184
+ def multiplot(terminal, options)
185
+ terminal.set(options)
186
+ @plots.each { |graph| graph.plot(terminal, multiplot_part: true) }
187
+ terminal.unset(options.keys)
188
+ end
149
189
  end
150
190
  end
@@ -14,27 +14,21 @@ module GnuplotRB
14
14
  # * *options* will be considered as 'settable' options of gnuplot
15
15
  # ('set xrange [1:10]' for { xrange: 1..10 },
16
16
  # "set title 'plot'" for { title: 'plot' } etc)
17
- def initialize(*datasets, **options)
18
- @datasets = if datasets[0].is_a? Hamster::Vector
19
- datasets[0]
20
- else
21
- Hamster::Vector.new(datasets).map { |ds| dataset_from_any(ds) }
22
- end
23
- @options = Hamster.hash(options)
24
- @already_plotted = false
17
+ def initialize(*datasets)
18
+ # had to relace **options arg with this because in some cases
19
+ # Daru::DataFrame was mentioned as hash and added to options
20
+ # instead of plots
21
+ @options = Hamster.hash
22
+ if datasets[-1].is_a?(Hamster::Hash) || datasets[-1].is_a?(Hash)
23
+ @options = Hamster.hash(datasets[-1])
24
+ datasets = datasets[0..-2]
25
+ end
26
+ @datasets = parse_datasets_array(datasets)
25
27
  @cmd = 'plot '
26
- @terminal = Terminal.new
27
28
  OptionHandling.validate_terminal_options(@options)
28
29
  yield(self) if block_given?
29
30
  end
30
31
 
31
- ##
32
- # For inner use!
33
- # Creates new Plot with existing data and given options.
34
- def new_with_options(options)
35
- self.class.new(@datasets, options)
36
- end
37
-
38
32
  ##
39
33
  # ====== Overview
40
34
  # This outputs plot to term (if given) or to this plot's own terminal.
@@ -45,21 +39,23 @@ module GnuplotRB
45
39
  # ('set xrange [1:10]', 'set title 'plot'' etc)
46
40
  # Options passed here have priority over already existing.
47
41
  def plot(term = nil, multiplot_part: false, **options)
48
- opts = @options.merge(options)
49
- opts = opts.reject { |key, _value| [:term, :output].include?(key) } if multiplot_part
50
- terminal = term || (opts[:output] ? Terminal.new : @terminal)
51
- full_command = @cmd + @datasets.map { |dataset| dataset.to_s(terminal) }.join(' , ')
52
- terminal.set(opts)
53
- .puts(full_command)
54
- .unset(opts.keys)
55
- if opts[:output]
42
+ fail ArgumentError, 'Empty plots are not supported!' if @datasets.empty?
43
+ inner_opts = if multiplot_part
44
+ @options.merge(options).reject { |key, _| [:term, :output].include?(key) }
45
+ else
46
+ @options.merge(options)
47
+ end
48
+ terminal = term || (inner_opts[:output] ? Terminal.new : own_terminal)
49
+ ds_string = @datasets.map { |dataset| dataset.to_s(terminal) }.join(' , ')
50
+ full_command = @cmd + ds_string
51
+ terminal.set(inner_opts).stream_puts(full_command).unset(inner_opts.keys)
52
+ if inner_opts[:output]
56
53
  # guaranteed wait for plotting to finish
57
54
  terminal.close unless term
58
55
  # not guaranteed wait for plotting to finish
59
56
  # work bad with terminals like svg and html
60
- sleep 0.01 until File.size?(opts[:output])
57
+ sleep 0.01 until File.size?(inner_opts[:output])
61
58
  end
62
- @already_plotted = true
63
59
  self
64
60
  end
65
61
 
@@ -101,20 +97,25 @@ module GnuplotRB
101
97
 
102
98
  ##
103
99
  # ====== Overview
104
- # Create new Plot object where given dataset will
105
- # be appended to dataset list.
100
+ # Create new Plot object where given datasets will
101
+ # be inserted into dataset list before given position
102
+ # (position = 0 by default).
106
103
  # ====== Arguments
107
- # * *dataset* - dataset to add
104
+ # * *position* - position where to insert given datasets
105
+ # * *datasets* - sequence of datasets to add
108
106
  # ====== Example
109
107
  # sinx = Plot.new('sin(x)')
110
- # sinx_and_cosx = sinx.add(['cos(x)'])
108
+ # sinx_and_cosx_with_expx = sinx.add(['cos(x)'], ['exp(x)'])
111
109
  #
112
110
  # cosx_and_sinx = sinx << ['cos(x)']
113
- def add_dataset(dataset)
114
- self.class.new(@datasets.add(dataset_from_any(dataset)), @options)
111
+ def add_datasets(*datasets)
112
+ datasets.map! { |ds| ds.is_a?(Numeric) ? ds : dataset_from_any(ds) }
113
+ datasets.unshift(0) unless datasets[0].is_a?(Numeric)
114
+ self.class.new(@datasets.insert(*datasets), @options)
115
115
  end
116
116
 
117
- alias_method :<<, :add_dataset
117
+ alias_method :add_dataset, :add_datasets
118
+ alias_method :<<, :add_datasets
118
119
 
119
120
  ##
120
121
  # ====== Overview
@@ -138,15 +139,58 @@ module GnuplotRB
138
139
  @datasets[*args]
139
140
  end
140
141
 
142
+ private
143
+
144
+ ##
145
+ # Checks several conditions and set options needed
146
+ # to handle DateTime indexes properly.
147
+ def provide_with_datetime_format(data, using)
148
+ return unless defined?(Daru)
149
+ return unless data.is_a?(Daru::DataFrame) || data.is_a?(Daru::Vector)
150
+ return unless data.index.first.is_a?(DateTime)
151
+ return if using[0..1] != '1:'
152
+ @options = @options.merge(
153
+ xdata: 'time',
154
+ timefmt: '%Y-%m-%dT%H:%M:%S',
155
+ format_x: '%d\n%b\n%Y'
156
+ )
157
+ end
158
+
141
159
  ##
142
- # Method for inner use.
143
160
  # Check if given args is a dataset and returns it. Creates
144
161
  # new dataset from given args otherwise.
145
162
  def dataset_from_any(source)
146
- source.is_a?(Dataset) ? source.clone : Dataset.new(*source)
163
+ ds = case source
164
+ # when initialized with dataframe (it passes here several vectors)
165
+ when (defined?(Daru) ? Daru::Vector : nil)
166
+ Dataset.new(source)
167
+ when Dataset
168
+ source.clone
169
+ else
170
+ Dataset.new(*source)
171
+ end
172
+ data = source.is_a?(Array) ? source[0] : source
173
+ provide_with_datetime_format(data, ds.using)
174
+ ds
175
+ end
176
+
177
+ ##
178
+ # Parses given array and returns Hamster::Vector of Datasets
179
+ def parse_datasets_array(datasets)
180
+ case datasets[0]
181
+ when Hamster::Vector
182
+ datasets[0]
183
+ when (defined?(Daru) ? Daru::DataFrame : nil)
184
+ Hamster::Vector.new(datasets[0].map { |ds| dataset_from_any(ds) })
185
+ else
186
+ Hamster::Vector.new(datasets.map { |ds| dataset_from_any(ds) })
187
+ end
147
188
  end
148
189
 
149
- private :dataset_from_any,
150
- :new_with_options
190
+ ##
191
+ # Creates new Plot with existing data and given options.
192
+ def new_with_options(options)
193
+ self.class.new(@datasets, options)
194
+ end
151
195
  end
152
196
  end