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