numplot 0.0.3
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.
- checksums.yaml +7 -0
- data/lib/numplot/option.rb +83 -0
- data/lib/numplot.rb +667 -0
- data/sample/sample1.rb +34 -0
- data/test/run_test.rb +5 -0
- data/test/test_labelparser.rb +14 -0
- data/test/test_options.rb +20 -0
- data/test/test_plotter.rb +14 -0
- data/test/test_rgb.rb +13 -0
- metadata +66 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2d354b2cc2c06f15dfb5130ea2ee5c48f1f157f2
|
4
|
+
data.tar.gz: 04ddb3b2c06dc8f02b43d9920b019df571abc151
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7aad96c6901b48822dcc03bc3ae5dd6bc2f0c90e5662c3dab090295d553096e9a08321e56b050bbdac9586f0265646a66ee885b6a5045c033bb213e1d768a444
|
7
|
+
data.tar.gz: e9acde2f4a6165047da57a44de4f223a751a89c084d284fe7508f2467fffcf81ee598d64897f812517483a95c749976b94fa0decbd22813d3f3597b81156867d
|
@@ -0,0 +1,83 @@
|
|
1
|
+
|
2
|
+
module NumPlot
|
3
|
+
# The class handling options on gnuplot command.
|
4
|
+
# @!visibility private
|
5
|
+
class Option
|
6
|
+
def initialize(name, converter, aliases)
|
7
|
+
@name = name
|
8
|
+
@converter = converter
|
9
|
+
@aliases = aliases
|
10
|
+
end
|
11
|
+
|
12
|
+
def plot_arg(h)
|
13
|
+
([@name] + @aliases).each do |key|
|
14
|
+
if h.has_key? (key)
|
15
|
+
return " #{@name} #{@converter[h[key]]}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
""
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.[](name, *aliases)
|
23
|
+
new(name, proc{|x| x }, aliases)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# The class handling color options on gnuplot command.
|
28
|
+
# @!visibility private
|
29
|
+
class ColorOption < Option
|
30
|
+
def self.[](name, *aliases)
|
31
|
+
new(name, Conversion.method(:convert_color), aliases)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# The class handling font options on gnuplot command.
|
36
|
+
# @!visibility private
|
37
|
+
class FontOption < Option
|
38
|
+
def self.[](name, *aliases)
|
39
|
+
new(name, Conversion.method(:convert_font), aliases)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# The class handling on/off options on gnuplot command.
|
44
|
+
# @!visibility private
|
45
|
+
class BoolOption
|
46
|
+
def initialize(name, *aliases)
|
47
|
+
@name = name; @aliases = aliases
|
48
|
+
end
|
49
|
+
|
50
|
+
def plot_arg(h)
|
51
|
+
([@name] + @aliases).each do |key|
|
52
|
+
return " #{@name}" if h.has_key?(key)
|
53
|
+
end
|
54
|
+
|
55
|
+
""
|
56
|
+
end
|
57
|
+
|
58
|
+
class << self
|
59
|
+
alias [] new
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# The class handling XXX/noXXX options on gnuplot command
|
64
|
+
# @!visibility private
|
65
|
+
class YesNoOption
|
66
|
+
def initialize(name)
|
67
|
+
@name = name
|
68
|
+
@no_name = "no#{name}".intern
|
69
|
+
end
|
70
|
+
|
71
|
+
def plot_arg(h)
|
72
|
+
return " #{@name}" if h.has_key?(@name) && h[@name]
|
73
|
+
return " #{@name}" if h.has_key?(@no_name) && !h[@no_name]
|
74
|
+
return " #{@no_name}" if h.has_key?(@no_name) && h[@no_name]
|
75
|
+
return " #{@no_name}" if h.has_key?(@name) && !h[@name]
|
76
|
+
""
|
77
|
+
end
|
78
|
+
|
79
|
+
class << self
|
80
|
+
alias [] new
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/numplot.rb
ADDED
@@ -0,0 +1,667 @@
|
|
1
|
+
require 'numplot/option'
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
# Call the given block once.
|
6
|
+
# This method is used to make yard to ignore the code in a block.
|
7
|
+
def __yard_ignore_here; yield; end
|
8
|
+
|
9
|
+
# A module wrapping gnuplot {http://www.gnuplot.info/}.
|
10
|
+
module NumPlot
|
11
|
+
# The collection of methods converting ruby objects to gnuplot strings.
|
12
|
+
#
|
13
|
+
# @!visibility private
|
14
|
+
module Conversion
|
15
|
+
module_function
|
16
|
+
|
17
|
+
def convert_range(range)
|
18
|
+
case range
|
19
|
+
when String then range
|
20
|
+
when Range then "[#{range.begin}:#{range.end}]"
|
21
|
+
when Array
|
22
|
+
if range.size != 2
|
23
|
+
raise TypeError, "Cannot convert #{range.inspect} to gnuplot's range"
|
24
|
+
end
|
25
|
+
"[#{range[0]}:#{range[1]}]"
|
26
|
+
else
|
27
|
+
raise TypeError, "Cannot convert #{range.inspect} to gnuplot's range"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def convert_color(color)
|
32
|
+
case color
|
33
|
+
when RGB then color.to_gnuplot_string
|
34
|
+
when String, Integer then color
|
35
|
+
else
|
36
|
+
raise TypeError, "Cannot convert #{color.inspect} to gnuplot's color"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def convert_font(font)
|
41
|
+
case font
|
42
|
+
when String then font
|
43
|
+
when Font then font.to_gnuplot_string
|
44
|
+
else
|
45
|
+
raise TypeError, "Cannot convert #{font.inspect} to gnuplot's font"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def quote(str)
|
50
|
+
"\"#{str}\""
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# The class representing colors in gnuplot.
|
55
|
+
#
|
56
|
+
# You can use three types of color specifications like the following
|
57
|
+
# examples.
|
58
|
+
#
|
59
|
+
# Example:
|
60
|
+
# RGB[255, 0, 0] # => Red
|
61
|
+
# RGB["#00ff00"] # => Green
|
62
|
+
# RGB["blue"] # => blue
|
63
|
+
#
|
64
|
+
class RGB
|
65
|
+
# Create a new instance of RGB class.
|
66
|
+
#
|
67
|
+
# You can give three styles of arguments to specify a color.
|
68
|
+
# * an RGB component as three integers like NumPlot::RGB.new(0, 255, 0)
|
69
|
+
# * a "#xxyyzz" style string like NumPlot::RGB.new("#ff007f")
|
70
|
+
# * a string of the name of a color like NumPlot::RGB.new("blue")
|
71
|
+
def initialize(*args)
|
72
|
+
if args.size == 1 && String === args[0]
|
73
|
+
@s = args[0]
|
74
|
+
elsif args.size == 3
|
75
|
+
@s = "#%02x%02x%02x" % args
|
76
|
+
else
|
77
|
+
raise TypeError, "Unknown color format: #{args.inspect}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Convert self to gnuplot "rgbcolor" string.
|
82
|
+
# @visibility private
|
83
|
+
def to_gnuplot_string
|
84
|
+
"rgbcolor \"#{@s}\""
|
85
|
+
end
|
86
|
+
|
87
|
+
class << self
|
88
|
+
# @method [](*args)
|
89
|
+
# Create a new RGB object. See {#initialize}.
|
90
|
+
# @return [RGB] A new RGB object
|
91
|
+
__yard_ignore_here{ alias [] new }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# The class representing a font in gnuplot.
|
96
|
+
#
|
97
|
+
# Example:
|
98
|
+
# Font["ariel"] # ariel font, default size
|
99
|
+
# Font["ariel", 12] # ariel font, the size is 12 point
|
100
|
+
class Font
|
101
|
+
# Create a new Font object.
|
102
|
+
# @param fontname[String] the name of font
|
103
|
+
# @param size[String, nil] the size of font, nil to default
|
104
|
+
# @example
|
105
|
+
# Font.new("ariel") # ariel font, default size
|
106
|
+
# Font.new("ariel", 12) # ariel font, the size is 12 point
|
107
|
+
def initialize(fontname, size=nil)
|
108
|
+
@s = size ? "#{fontname},#{size}" : fontname
|
109
|
+
end
|
110
|
+
|
111
|
+
# Convert self to gnuplot font (quoted) string.
|
112
|
+
# @visibility private
|
113
|
+
def to_gnuplot_string
|
114
|
+
"\"#{@s}\""
|
115
|
+
end
|
116
|
+
|
117
|
+
class << self
|
118
|
+
# @!method [](fontname, size=nil)
|
119
|
+
# Create a new Font object. See {#initialize}.
|
120
|
+
# @return [Font] A new Font object
|
121
|
+
__yard_ignore_here{ alias [] new }
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# The class communicating a gnuplot subprocess.
|
126
|
+
#
|
127
|
+
# In many cases, you use {Plotter#plot} and you don't need to use
|
128
|
+
# this class directly.
|
129
|
+
class Process
|
130
|
+
# Create a Process object for already opened pipe.
|
131
|
+
# Normally, you had better to use {.open} to create a new object.
|
132
|
+
# @param pipe a pipe to communicate to a subprocess
|
133
|
+
# @param wait wait for the output from a subprocess
|
134
|
+
def initialize(pipe, wait)
|
135
|
+
@pipe = pipe
|
136
|
+
@wait = wait
|
137
|
+
@output = nil
|
138
|
+
end
|
139
|
+
|
140
|
+
# Output string from a gnuplot subprocess.
|
141
|
+
# @return [String, nil]
|
142
|
+
attr_reader :output
|
143
|
+
|
144
|
+
# Close the pipe and terminate the subprocess.
|
145
|
+
#
|
146
|
+
# If you specify wait=true when the object is constructed,
|
147
|
+
# @return [void]
|
148
|
+
def close
|
149
|
+
if @wait
|
150
|
+
@pipe.close_write
|
151
|
+
@output = @pipe.read
|
152
|
+
@pipe.close
|
153
|
+
else
|
154
|
+
@pipe.close
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Write +str+ to the subprocess.
|
159
|
+
# @return [void]
|
160
|
+
def write(str)
|
161
|
+
@pipe.write(str)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Write +str+ and a newline to the subprocess
|
165
|
+
# @return [void]
|
166
|
+
def puts(str)
|
167
|
+
@pipe.puts(str)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Open a gnuplot process.
|
171
|
+
#
|
172
|
+
# If a block is given, the block is invoked with the opened
|
173
|
+
# Process object. Otherwise, opened Process object is returned.
|
174
|
+
# If you give true as +persist+, -persist option is added to the
|
175
|
+
# command line of gnuplot.
|
176
|
+
# @param persist add -persist option to gnuplot as a command line option
|
177
|
+
# @param waiting wait for termination of stdout of gnuplot pipe
|
178
|
+
# @param name the name of the gnuplot executable file
|
179
|
+
def self.open(persist=true, waiting=true, name="gnuplot")
|
180
|
+
pr = new(IO.popen(command(name, persist), "w+"), waiting)
|
181
|
+
|
182
|
+
if block_given?
|
183
|
+
begin
|
184
|
+
yield pr
|
185
|
+
ensure
|
186
|
+
pr.close
|
187
|
+
end
|
188
|
+
return pr.output
|
189
|
+
else
|
190
|
+
return pr
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Return a command line string to invoke a subprocess.
|
195
|
+
# @!visibility private
|
196
|
+
def self.command(name, persist)
|
197
|
+
name += " -persist" if persist
|
198
|
+
name
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# The plotter class.
|
203
|
+
# To plot something, you need to create a Plotter object,
|
204
|
+
# set parameters to the object, add data sets to the object,
|
205
|
+
# and call {#plot} as in the example below.
|
206
|
+
# @example
|
207
|
+
# # An array of data to plot
|
208
|
+
# data = -2.0.step(2.0, 0.05).map{|x| [x, Math.sin(x)]}
|
209
|
+
# # Create a new Plotter object
|
210
|
+
# plotter = NumPlot::Plotter.new
|
211
|
+
# # Set parameters
|
212
|
+
# plotter.set("term", "png")
|
213
|
+
# plotter.set("output", "\"sin.png\"") # Need to quote
|
214
|
+
# plotter.xrange -2.0 .. 2.0
|
215
|
+
# # Set datasets
|
216
|
+
# plotter.datasets << NumPlot::Dataset.rows(data, title: "sin(x)", with: line)
|
217
|
+
# # Plot (output to sin.png)
|
218
|
+
# plotter.plot
|
219
|
+
class Plotter
|
220
|
+
include Conversion
|
221
|
+
|
222
|
+
# Create a new Plotter object.
|
223
|
+
# You can specifiy the plot command (plot/splot)
|
224
|
+
# by plot_command parameter.
|
225
|
+
# @param plot_command[String] "plot" or "splot"
|
226
|
+
def initialize(plot_command = "plot")
|
227
|
+
@plot_command = plot_command
|
228
|
+
@commands = []
|
229
|
+
@datasets = []
|
230
|
+
end
|
231
|
+
|
232
|
+
# An array of all data sets.
|
233
|
+
# @deprecated
|
234
|
+
# @return [Array]
|
235
|
+
attr_reader :datasets
|
236
|
+
|
237
|
+
# Add a command string to the list of commands.
|
238
|
+
# The commands are executed when {#plot} is called.
|
239
|
+
# @param command[String] a command to add
|
240
|
+
# @return [self]
|
241
|
+
def add_command(command)
|
242
|
+
@commands << command
|
243
|
+
self
|
244
|
+
end
|
245
|
+
private :add_command
|
246
|
+
|
247
|
+
# Gnuplot "set" command.
|
248
|
+
# This method is generic, but
|
249
|
+
# you can use some useful methods like {#xrange} and {#xlabel} for
|
250
|
+
# "set" command.
|
251
|
+
# @param key[String]
|
252
|
+
# the first argument for gnuplot "set" command like "term", "output".
|
253
|
+
# @param values[Array<String>] the rest arguments for gnuplot "set" command
|
254
|
+
# @return [self]
|
255
|
+
def set(key, *values)
|
256
|
+
add_command("set #{key} #{values.join(' ')}")
|
257
|
+
end
|
258
|
+
|
259
|
+
# Gnuplot "unset" command.
|
260
|
+
# @param key[String]
|
261
|
+
# the first argument for gnuplot "unset" command
|
262
|
+
# @param values[Array<String>] the rest arguments for gnuplot "unset" command
|
263
|
+
# @see #set
|
264
|
+
# @return [self]
|
265
|
+
def unset(key, *values)
|
266
|
+
add_command("unset #{key} #{values.join(' ')}")
|
267
|
+
end
|
268
|
+
|
269
|
+
# Add a dataset to the plotter.
|
270
|
+
# @param dataset[Dataset] a dataset
|
271
|
+
# @return [self]
|
272
|
+
def add_dataset(dataset)
|
273
|
+
@datasets << dataset
|
274
|
+
self
|
275
|
+
end
|
276
|
+
|
277
|
+
alias << add_dataset
|
278
|
+
|
279
|
+
# @!macro [new] range
|
280
|
+
# @overload $0(range, *opts)
|
281
|
+
# Set $0 parameter.
|
282
|
+
# You can specify the range with one of three styles.
|
283
|
+
# * a string such as: "[-1.0:1.0]" or "[:2.0]"
|
284
|
+
# * a range such as: -1.0 .. 1.0 or -Float::INFINITY .. 2.0
|
285
|
+
# * an array such as: [-1.0, 1.0] or [nil, 2.0]
|
286
|
+
# You can also specify the following options as args:
|
287
|
+
# * "reverse"
|
288
|
+
# * "no reverse"
|
289
|
+
# * "writeback"
|
290
|
+
# * "no writeback"
|
291
|
+
# @param range[String,Range,Array] the range of x axis
|
292
|
+
# @param opts[Array<String>] options
|
293
|
+
# @return [self]
|
294
|
+
|
295
|
+
# @macro range
|
296
|
+
def xrange(*args); _range("xrange", *args); end
|
297
|
+
# @macro range
|
298
|
+
def yrange(*args); _range("yrange", *args); end
|
299
|
+
# @macro range
|
300
|
+
def zrange(*args); _range("zrange", *args); end
|
301
|
+
# @macro range
|
302
|
+
def x2range(*args); _range("x2range", *args); end
|
303
|
+
# @macro range
|
304
|
+
def y2range(*args); _range("y2range", *args); end
|
305
|
+
|
306
|
+
def _range(type, r, opts={})
|
307
|
+
gnuplot_options = ""
|
308
|
+
RANGE_OPTIONS.each do |opt|
|
309
|
+
gnuplot_options << opt.plot_arg(opts)
|
310
|
+
end
|
311
|
+
|
312
|
+
set(type, convert_range(r), gnuplot_options)
|
313
|
+
end
|
314
|
+
private :_range
|
315
|
+
|
316
|
+
# @!visibility private
|
317
|
+
RANGE_OPTIONS = [YesNoOption[:reverse], YesNoOption[:writeback]]
|
318
|
+
|
319
|
+
# @!macro [new] label
|
320
|
+
# @overload $0(label, opts={})
|
321
|
+
# Set $0 parameter.
|
322
|
+
# You can specify the following options as a hash:
|
323
|
+
# * offset: OFFSET
|
324
|
+
# * font: FONT
|
325
|
+
# * textcolor: COLOR (tc: is also available)
|
326
|
+
# * enhanced: true OR noenhanced: true
|
327
|
+
# * rotate: (TODO: FIX IT) OR norotate: true
|
328
|
+
# @param label[String] label string
|
329
|
+
# @param opts[{Symbol => Object}] options
|
330
|
+
# @return [self]
|
331
|
+
|
332
|
+
# @macro label
|
333
|
+
def xlabel(*args); _label("xlabel", *args); end
|
334
|
+
# @macro label
|
335
|
+
def ylabel(*args); _label("ylabel", *args); end
|
336
|
+
# @macro label
|
337
|
+
def zlabel(*args); _label("zlabel", *args); end
|
338
|
+
# @macro label
|
339
|
+
def x2label(*args); _label("x2label", *args); end
|
340
|
+
# @macro label
|
341
|
+
def y2label(*args); _label("y2label", *args); end
|
342
|
+
|
343
|
+
def _label(type, *args)
|
344
|
+
set(type, LabelParser.parse(*args))
|
345
|
+
end
|
346
|
+
private :_label
|
347
|
+
|
348
|
+
# Plot given datasets with given parameters.
|
349
|
+
# @overload plot(opts={})
|
350
|
+
# Open a gnuplot process and write commands and data to the process.
|
351
|
+
# You get the result of plotting on a window or an image file
|
352
|
+
#
|
353
|
+
# You can specify the following options:
|
354
|
+
# * :persist - Add -persist to gnuplot command.
|
355
|
+
# The default value is true.
|
356
|
+
# * :waiting - Wait a gnuplot process to be terminate.
|
357
|
+
# The default value is true. If this value is true and
|
358
|
+
# the diagram is drawed on a new window, the ruby program
|
359
|
+
# is stopped until the window is closed. On the other hand,
|
360
|
+
# if this value is false, the ruby program continue
|
361
|
+
# although the window remains opened.
|
362
|
+
# * :gnuplot_command - gnuplot command name.
|
363
|
+
#
|
364
|
+
# @param opts options
|
365
|
+
# @return [void]
|
366
|
+
#
|
367
|
+
# @overload plot(io)
|
368
|
+
# Output all commands and data to IO object.
|
369
|
+
#
|
370
|
+
# Normally, you specify {Process} object as io.
|
371
|
+
# For debug use, you can specify the StringIO object as io.
|
372
|
+
#
|
373
|
+
# @param io[#write, #puts] output target
|
374
|
+
# @return [void]
|
375
|
+
def plot(arg={})
|
376
|
+
|
377
|
+
if Hash === arg
|
378
|
+
Process.open(arg.fetch(:persist, true),
|
379
|
+
arg.fetch(:waiting, true),
|
380
|
+
arg.fetch(:gnuplot_command, "gnuplot")) do |pr|
|
381
|
+
plot_to_io(pr)
|
382
|
+
end
|
383
|
+
else
|
384
|
+
plot_to_io(arg)
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
# Output all commands and data to IO object.
|
389
|
+
def plot_to_io(pr)
|
390
|
+
@commands.each{|cmd| pr.puts(cmd) }
|
391
|
+
pr.write(@plot_command)
|
392
|
+
pr.write(" ")
|
393
|
+
pr.write(@datasets.map{|e| e.to_plot_arg }.join(", "))
|
394
|
+
pr.write("\n")
|
395
|
+
pr.write(@datasets.map{|e| e.to_plot_dataset }.compact.join("e\n"))
|
396
|
+
end
|
397
|
+
private :plot_to_io
|
398
|
+
end
|
399
|
+
|
400
|
+
# The dataset class plotted by {Plotter}.
|
401
|
+
#
|
402
|
+
# You can construct a dataset object using one of the following three methods:
|
403
|
+
# * {.columns}: construct a dataset from columns
|
404
|
+
# * {.rows}: construct a dataset from rows
|
405
|
+
# * {.histogram}: construct a histogram dataset
|
406
|
+
#
|
407
|
+
# You need to choose from {.columns} and {.rows} according to
|
408
|
+
# the arrangement of your data.
|
409
|
+
#
|
410
|
+
# The dataset object is registered to {Plotter} object using
|
411
|
+
# {Plotter#add_dataset}.
|
412
|
+
#
|
413
|
+
# == Parameters for a dataset
|
414
|
+
# When you call {.columns}, {.rows}, or {.histogram},
|
415
|
+
# you can give some parameters to determine the style of
|
416
|
+
# plotting.
|
417
|
+
# The parameters are as follows. If you know the details
|
418
|
+
# of these parameters, please read the gnuplot documents.
|
419
|
+
# * title: "TITLE" or notitle: true
|
420
|
+
# * with: "points", "dots", etc.
|
421
|
+
# * axis:
|
422
|
+
# * linestyle: a style number (ls: is also available)
|
423
|
+
# * linetype: a line type number
|
424
|
+
# * linewidth: a line width multiplier (default is 1.0),
|
425
|
+
# (lw: is also available)
|
426
|
+
# * linecolor: a color number or an {RGB} object.
|
427
|
+
# (lc: is also available)
|
428
|
+
# * pointtype: a point type number (pt: is also available)
|
429
|
+
# * pointsize: a point size multiplier (default is 1.0),
|
430
|
+
# (ls: is also available)
|
431
|
+
# * fill: a fill style (fs: is also available)
|
432
|
+
# * nohidden3d: true if you need to activate this option
|
433
|
+
# * nocontours: true if you need to activate this option
|
434
|
+
# * nosurface: true if you need to activate this option
|
435
|
+
class Dataset
|
436
|
+
def initialize(data, opts={})
|
437
|
+
@data = data
|
438
|
+
@opts = opts
|
439
|
+
end
|
440
|
+
|
441
|
+
# Construct a dataset object form columns.
|
442
|
+
#
|
443
|
+
# If your data are xs = [x0, x1, x2, ... ] and ys = [y0, y1, y2, ...],
|
444
|
+
# you should use this method as:
|
445
|
+
# Dataset.columns([xs, ys])
|
446
|
+
#
|
447
|
+
# If your data is transposed, you need to use {.columns} instead of
|
448
|
+
# this method.
|
449
|
+
#
|
450
|
+
# You can give some parameters as a hash
|
451
|
+
# to set a plotting style. Please see
|
452
|
+
# {Dataset}.
|
453
|
+
# @param data[Enumerable<Enumerable<Numeric>>]
|
454
|
+
# @param opts[{Symbol => object}] options
|
455
|
+
# @return [Dataset]
|
456
|
+
def self.columns(data, opts={})
|
457
|
+
new(data.map(&:to_a).transpose, opts)
|
458
|
+
end
|
459
|
+
|
460
|
+
# Construct a dataset object form rows.
|
461
|
+
#
|
462
|
+
# If your data is [[x0, y0], [x1, y1], ...],
|
463
|
+
# you should use this method as:
|
464
|
+
# Dataset.rows(data)
|
465
|
+
#
|
466
|
+
# If your data is transposed, you need to use {.rows} instead of
|
467
|
+
# this method.
|
468
|
+
#
|
469
|
+
# You can give some parameters as a hash
|
470
|
+
# to set a plotting style. Please see
|
471
|
+
# {Dataset} for details.
|
472
|
+
# @param data[Enumerable<Enumerable<Numeric>>]
|
473
|
+
# @param opts[{Symbol => object}] options
|
474
|
+
# @return [Dataset]
|
475
|
+
def self.rows(data, opts={})
|
476
|
+
new(data.map(&:to_a), opts)
|
477
|
+
end
|
478
|
+
|
479
|
+
|
480
|
+
# Construct a histogram dataset object.
|
481
|
+
#
|
482
|
+
# You need to give some parameters to construct a histogram.
|
483
|
+
# * dim: the dimension of your data, now only 1-dim data is supported
|
484
|
+
# (default is 1)
|
485
|
+
# * binsize: the bin size (required, Numeric)
|
486
|
+
# * base: the coordinate of a left side of a bin (default is 0.0).
|
487
|
+
# If binsize = 1.0 and base = 0.0, the bins are
|
488
|
+
# ..., [-1.0, 0.0), [0.0, 1.0), ... .
|
489
|
+
# If binsize = 1.0 and base = 0.5, the bins are
|
490
|
+
# ..., [-1.5, -0.5), [-0.5, 0.5), [0.5, 1.5), ... .
|
491
|
+
# * histogram_style: You can choose one of the following normalization
|
492
|
+
# scheme:
|
493
|
+
# * :count not normalized
|
494
|
+
# * :ratio normailized to the number of data points
|
495
|
+
# * :density normalized to (the number of data points)*(binsize).
|
496
|
+
# You choose this option if you want to show the probability density
|
497
|
+
# function from the data.
|
498
|
+
# * cumulative: construct a cumulative histogram if true.
|
499
|
+
# If the cumulative option is true, the meaning of
|
500
|
+
# histogram_style option is changed.
|
501
|
+
# * :count not normalized
|
502
|
+
# * :ratio, :density normalized by the number of data, this means that
|
503
|
+
# the maximum value of a cumulative histogram is normalized to 1.
|
504
|
+
#
|
505
|
+
# You can also give some other parameters to determine
|
506
|
+
# a plotting style. See {Dataset} for details.
|
507
|
+
#
|
508
|
+
# @example
|
509
|
+
# require 'randomext' # install randomext gem
|
510
|
+
# # 1000 random samples from the standard normal distribution.
|
511
|
+
# rng = Random.new
|
512
|
+
# data = Array.new(1000){ rng.standard_normal }
|
513
|
+
# datasets = Dataset.histo(data, binsize: 0.1, histogram_style: :density,
|
514
|
+
# title: "Random samples from the normal distribution")
|
515
|
+
#
|
516
|
+
# @param data[Enumerable<Numeric>] a sequence of numbers
|
517
|
+
# @param opts[{Symbol => Object}] options
|
518
|
+
# @return [Dataset]
|
519
|
+
def self.histogram(data, opts={})
|
520
|
+
case opts.fetch(:dim, 1)
|
521
|
+
when 1
|
522
|
+
histo1d(data, opts)
|
523
|
+
when 2
|
524
|
+
raise NotImplementedError, "Histogram for 2D data is not implemented yet"
|
525
|
+
else
|
526
|
+
raise ArgumentError, "You can use only 1D or 2D histogram data"
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
530
|
+
# @!visibility private
|
531
|
+
def self.histo1d(data, opts)
|
532
|
+
binsize = opts.fetch(:binsize).to_f
|
533
|
+
basepoint = opts.fetch(:base, 0).to_f
|
534
|
+
histo_style = opts.fetch(:histogram_style, :count)
|
535
|
+
cumulative = opts.fetch(:cumulative, false)
|
536
|
+
|
537
|
+
bins = Hash.new(0)
|
538
|
+
data.each {|point| bins[bin1d(point, binsize, basepoint)] += 1 }
|
539
|
+
|
540
|
+
if cumulative
|
541
|
+
plotdata = cumulative_1d(bins, histo_style, data.size, binsize)
|
542
|
+
else
|
543
|
+
plotdata = histogram_plotdata_1d(bins, histo_style, data.size, binsize)
|
544
|
+
end
|
545
|
+
|
546
|
+
opts[:with] ||= "boxes"
|
547
|
+
Dataset.rows(plotdata, opts)
|
548
|
+
end
|
549
|
+
|
550
|
+
# @!visibility private
|
551
|
+
def self.bin1d(point, binsize, basepoint)
|
552
|
+
(point + binsize/2).div(binsize) * binsize
|
553
|
+
end
|
554
|
+
|
555
|
+
# @!visibility private
|
556
|
+
def self.histogram_plotdata_1d(bins, style, numdata, binsize)
|
557
|
+
r = case style
|
558
|
+
when :count then 1.0
|
559
|
+
when :ratio then numdata.to_f
|
560
|
+
when :density then numdata*binsize
|
561
|
+
end
|
562
|
+
bins.map{|k, v| [k, v/r, binsize]}
|
563
|
+
end
|
564
|
+
|
565
|
+
# @!visibility private
|
566
|
+
def self.cumulative_1d(bins, style, numdata, binsize)
|
567
|
+
cumulative_data = []
|
568
|
+
bins.sort_by{|k, v| k }.inject(0.0){|sum, (k, v)|
|
569
|
+
cumulative_data << [k, sum+v]
|
570
|
+
sum + v
|
571
|
+
}
|
572
|
+
r = case style
|
573
|
+
when :count then 1.0
|
574
|
+
when :ratio, :density then numdata
|
575
|
+
end
|
576
|
+
cumulative_data.map{|k, v| [k, v/r, binsize] }
|
577
|
+
end
|
578
|
+
|
579
|
+
private_class_method :bin1d, :histo1d, :histogram_plotdata_1d, :cumulative_1d
|
580
|
+
|
581
|
+
class << self; alias histo histogram; end
|
582
|
+
|
583
|
+
# Return a stirng for gnuplot's "plot" or "splot" like "'-' with dots"
|
584
|
+
# @return [String]
|
585
|
+
# @!visibility private
|
586
|
+
def to_plot_arg
|
587
|
+
ret = "'-'"
|
588
|
+
OPTIONS.each do |opt|
|
589
|
+
ret << opt.plot_arg(@opts)
|
590
|
+
end
|
591
|
+
|
592
|
+
ret
|
593
|
+
end
|
594
|
+
|
595
|
+
# Return a string for gnuplot's data like "1 2 3\n4 4 6\ne"
|
596
|
+
# @!visibility private
|
597
|
+
def to_plot_dataset
|
598
|
+
@data.map{|dataline| dataline.join(" ")}.join("\n") + "\ne"
|
599
|
+
end
|
600
|
+
|
601
|
+
# @!visibility private
|
602
|
+
class Title
|
603
|
+
def plot_arg(h)
|
604
|
+
if h[:notitle]
|
605
|
+
" notitle"
|
606
|
+
elsif h.has_key?(:title)
|
607
|
+
" title \'#{h[:title]}\'"
|
608
|
+
else
|
609
|
+
""
|
610
|
+
end
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
614
|
+
# @!visibility private
|
615
|
+
OPTIONS = [Title.new,
|
616
|
+
Option[:axis],
|
617
|
+
Option[:with],
|
618
|
+
Option[:linestyle, :ls],
|
619
|
+
Option[:linetype, :lt],
|
620
|
+
Option[:linewidth, :lw],
|
621
|
+
ColorOption[:linecolor, :lc],
|
622
|
+
Option[:pointtype, :pt],
|
623
|
+
Option[:pointsize, :ps],
|
624
|
+
Option[:fill, :fs],
|
625
|
+
BoolOption[:nohidden3d],
|
626
|
+
BoolOption[:nocontours],
|
627
|
+
BoolOption[:nosurface],
|
628
|
+
Option[:palette],
|
629
|
+
]
|
630
|
+
|
631
|
+
|
632
|
+
end
|
633
|
+
|
634
|
+
# @!visibility private
|
635
|
+
class LabelParser
|
636
|
+
def self.parse(*args)
|
637
|
+
if Hash === args.last
|
638
|
+
opts = args.pop
|
639
|
+
else
|
640
|
+
opts = {}
|
641
|
+
end
|
642
|
+
|
643
|
+
label = args.shift
|
644
|
+
|
645
|
+
raise ArgumentError, "Unknown label argument #{args.inspect}" if !args.empty?
|
646
|
+
raise ArgumentError, "No label text" if label.nil?
|
647
|
+
|
648
|
+
gnuplot_string = Conversion.quote(label)
|
649
|
+
|
650
|
+
OPTIONS.each do |opt|
|
651
|
+
gnuplot_string << opt.plot_arg(opts)
|
652
|
+
end
|
653
|
+
|
654
|
+
gnuplot_string
|
655
|
+
end
|
656
|
+
|
657
|
+
OPTIONS = [Option[:offset],
|
658
|
+
FontOption[:font],
|
659
|
+
ColorOption[:textcolor, :tc],
|
660
|
+
YesNoOption[:enhanced],
|
661
|
+
Option[:rotate],
|
662
|
+
BoolOption[:norotate],
|
663
|
+
]
|
664
|
+
end
|
665
|
+
|
666
|
+
|
667
|
+
end
|
data/sample/sample1.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'numplot'
|
2
|
+
|
3
|
+
# Draw a random sample from the normal distribution (sigma = 0.3)
|
4
|
+
# with Box-Muller's method.
|
5
|
+
def rand_normal
|
6
|
+
x = rand
|
7
|
+
y = rand
|
8
|
+
Math.sqrt(-2*Math.log(x))*Math.cos(2*Math::PI*y)*0.3
|
9
|
+
end
|
10
|
+
|
11
|
+
plotter = NumPlot::Plotter.new
|
12
|
+
|
13
|
+
x_sin_pi_x = (-1.0 .. 1.0).step(0.05).map{|x| [x, Math.sin(Math::PI*x)] }
|
14
|
+
plotter <<
|
15
|
+
NumPlot::Dataset.rows(x_sin_pi_x, with: "points", title: "sin(x)")
|
16
|
+
|
17
|
+
xs = (0.0 .. 1.0).step(0.05)
|
18
|
+
squares = xs.map{|x| x**2 }
|
19
|
+
plotter <<
|
20
|
+
NumPlot::Dataset.columns([xs, squares], with: "lines", title: "x^2")
|
21
|
+
|
22
|
+
normal_random_dist = Array.new(10000){ rand_normal }
|
23
|
+
plotter <<
|
24
|
+
NumPlot::Dataset.histo(normal_random_dist,
|
25
|
+
binsize: 0.1, histogram_style: :density,
|
26
|
+
title: "a normal distribution")
|
27
|
+
|
28
|
+
plotter.xrange(-1.0 .. 1.0)
|
29
|
+
plotter.xlabel("x", font: NumPlot::Font["ariel", 12])
|
30
|
+
plotter.ylabel("y", font: NumPlot::Font["ariel"])
|
31
|
+
|
32
|
+
plotter.set("key", "below")
|
33
|
+
|
34
|
+
plotter.plot
|
data/test/run_test.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'numplot'
|
2
|
+
|
3
|
+
require 'test-unit'
|
4
|
+
|
5
|
+
class TestRGB < Test::Unit::TestCase
|
6
|
+
include NumPlot
|
7
|
+
|
8
|
+
def test_s_parse
|
9
|
+
assert_equal("\"foo\"", LabelParser.parse("foo"))
|
10
|
+
assert_equal("\"foo\" offset 32 font \"bar,14\"",
|
11
|
+
LabelParser.parse("foo", offset: 32, font: Font["bar",14]))
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'numplot'
|
2
|
+
|
3
|
+
require 'test-unit'
|
4
|
+
|
5
|
+
class TestYesNoOption < Test::Unit::TestCase
|
6
|
+
include NumPlot
|
7
|
+
|
8
|
+
def test_plot_arg
|
9
|
+
assert_equal("", YesNoOption[:foo].plot_arg({}))
|
10
|
+
assert_equal("", YesNoOption[:foo].plot_arg({bar:true}))
|
11
|
+
assert_equal(" foo",
|
12
|
+
YesNoOption[:foo].plot_arg({bar:true, foo:true}))
|
13
|
+
assert_equal(" nofoo",
|
14
|
+
YesNoOption[:foo].plot_arg({bar:true, foo:false}))
|
15
|
+
assert_equal(" nofoo",
|
16
|
+
YesNoOption[:foo].plot_arg({bar:true, nofoo:true}))
|
17
|
+
assert_equal(" foo",
|
18
|
+
YesNoOption[:foo].plot_arg({bar:true, nofoo:false}))
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'numplot'
|
2
|
+
|
3
|
+
require 'test-unit'
|
4
|
+
require 'stringio'
|
5
|
+
|
6
|
+
class TestRGB < Test::Unit::TestCase
|
7
|
+
include NumPlot
|
8
|
+
|
9
|
+
def test_xrange
|
10
|
+
strio = StringIO.new
|
11
|
+
Plotter.new.xrange(1 .. 2, noreverse: false, nowriteback: true).plot(strio)
|
12
|
+
assert_equal("set xrange [1:2] reverse nowriteback\nplot \n", strio.string)
|
13
|
+
end
|
14
|
+
end
|
data/test/test_rgb.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'numplot'
|
2
|
+
|
3
|
+
require 'test-unit'
|
4
|
+
|
5
|
+
class TestRGB < Test::Unit::TestCase
|
6
|
+
include NumPlot
|
7
|
+
|
8
|
+
def test_to_gnuplot_string
|
9
|
+
assert_equal("rgbcolor \"black\"", RGB["black"].to_gnuplot_string)
|
10
|
+
assert_equal("rgbcolor \"#0f12af\"", RGB["#0f12af"].to_gnuplot_string)
|
11
|
+
assert_equal("rgbcolor \"#0f12af\"", RGB[0xf, 0x12, 0xaf].to_gnuplot_string)
|
12
|
+
end
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: numplot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ippei Obayashi
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-03-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: test-unit
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: |
|
28
|
+
This library is an wrapper for gnuplot.
|
29
|
+
email: ohai@kmc.gr.jp
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- lib/numplot.rb
|
35
|
+
- lib/numplot/option.rb
|
36
|
+
- sample/sample1.rb
|
37
|
+
- test/run_test.rb
|
38
|
+
- test/test_labelparser.rb
|
39
|
+
- test/test_options.rb
|
40
|
+
- test/test_plotter.rb
|
41
|
+
- test/test_rgb.rb
|
42
|
+
homepage: http://www.kmc.gr.jp
|
43
|
+
licenses: []
|
44
|
+
metadata: {}
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - '>='
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
requirements: []
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 2.0.0
|
62
|
+
signing_key:
|
63
|
+
specification_version: 4
|
64
|
+
summary: An wrapper for gnuplot
|
65
|
+
test_files: []
|
66
|
+
has_rdoc:
|