gnuplotrb 0.3.1 → 0.3.2

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.
@@ -1,269 +1,269 @@
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
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