gnuplotrb 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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