graphkit 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/graphkit.rb +852 -0
- data/lib/graphkit/gnuplot.rb +412 -0
- metadata +54 -0
data/lib/graphkit.rb
ADDED
@@ -0,0 +1,852 @@
|
|
1
|
+
script_folder = File.dirname(File.expand_path(__FILE__))
|
2
|
+
|
3
|
+
require 'pp'
|
4
|
+
require script_folder + '/box_of_tricks.rb'
|
5
|
+
require script_folder + '/gnuplot.rb'
|
6
|
+
|
7
|
+
class Matrix
|
8
|
+
def shape
|
9
|
+
return[row_size, column_size]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Hash
|
14
|
+
def modify(hash, excludes=[])
|
15
|
+
hash.each do |key, value|
|
16
|
+
# p key
|
17
|
+
# ep key, value if value #if option == :xrange
|
18
|
+
|
19
|
+
begin
|
20
|
+
self[key] = value.dup unless excludes.include? key
|
21
|
+
rescue TypeError #immediate values cannot be dup'd
|
22
|
+
self[key] = value unless excludes.include? key
|
23
|
+
end
|
24
|
+
end
|
25
|
+
self
|
26
|
+
end
|
27
|
+
alias :absorb :modify
|
28
|
+
end
|
29
|
+
|
30
|
+
# class Array
|
31
|
+
# def to_sparse_tensor_key
|
32
|
+
# return SparseTensor::Key.new)
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
|
36
|
+
|
37
|
+
# A simple sparse tensor
|
38
|
+
|
39
|
+
class SparseTensor < Hash
|
40
|
+
class RankError < StandardError
|
41
|
+
end
|
42
|
+
|
43
|
+
# class Key < Array
|
44
|
+
# def ==
|
45
|
+
#
|
46
|
+
# end
|
47
|
+
|
48
|
+
|
49
|
+
attr_reader :rank, :shape
|
50
|
+
|
51
|
+
# Create a new tensor.
|
52
|
+
|
53
|
+
def initialize(rank = 2)
|
54
|
+
@rank = rank
|
55
|
+
@shape = [0]*rank
|
56
|
+
super()
|
57
|
+
end
|
58
|
+
|
59
|
+
# Create a new diagonal tensor from an array. E.g. if rank was 2, then
|
60
|
+
# tensor[0,0] = array[0]
|
61
|
+
# tensor[1,1] = array[1]
|
62
|
+
# Etc.
|
63
|
+
|
64
|
+
def self.diagonal(rank, array)
|
65
|
+
tensor = new(rank)
|
66
|
+
for index in 0...array.size
|
67
|
+
tensor[[index] * rank] = array[index]
|
68
|
+
end
|
69
|
+
tensor
|
70
|
+
end
|
71
|
+
|
72
|
+
# Access an element of the tensor. E.g. for a rank 2 tensor
|
73
|
+
#
|
74
|
+
# a = tensor[1,3]
|
75
|
+
|
76
|
+
def [](*args)
|
77
|
+
args = args[0] if args.size == 1 and not args.size == @rank and args[0].size == @rank
|
78
|
+
# p args
|
79
|
+
|
80
|
+
raise RankError.new("Rank is #@rank, not #{args.size}") unless args.size == @rank
|
81
|
+
return super(args)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Set an element of the tensor. E.g. for a rank 2 tensor
|
85
|
+
#
|
86
|
+
# tensor[1,3] = a_variable
|
87
|
+
|
88
|
+
def []=(*args)
|
89
|
+
value = args.pop
|
90
|
+
args = args[0] if args.size == 1 and args[0].size == @rank
|
91
|
+
raise RankError.new("Rank is #@rank, not #{args.size}") unless args.size == @rank
|
92
|
+
args.each_with_index do |arg, index|
|
93
|
+
@shape[index] = [@shape[index], arg + 1].max
|
94
|
+
end
|
95
|
+
super(args, value)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Perform some action involving all the elements of this tensor and another.
|
99
|
+
#
|
100
|
+
# E.g.
|
101
|
+
# tensor_1.scalar_binary(tensor_2) do |element_1, element_2|
|
102
|
+
# element_1 + element_2
|
103
|
+
# end
|
104
|
+
#
|
105
|
+
# will add every element of tensor_1 to every corresponding element of tensor_2.
|
106
|
+
|
107
|
+
|
108
|
+
|
109
|
+
def scalar_binary(other, &block)
|
110
|
+
raise ArgumentError unless other.class == self.class
|
111
|
+
raise RankError.new("Different ranks: #@rank, #{other.rank}") unless other.rank == @rank
|
112
|
+
|
113
|
+
new = self.class.new(@rank)
|
114
|
+
self.keys.each do |key|
|
115
|
+
if other[key]
|
116
|
+
new[key] = yield(self[key], other[key])
|
117
|
+
else
|
118
|
+
new[key] = self[key]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
(other.keys - self.keys).each{|key| new[key] = other[key]}
|
122
|
+
new
|
123
|
+
end
|
124
|
+
def +(other)
|
125
|
+
scalar_binary(other){|a, b| a + b}
|
126
|
+
end
|
127
|
+
def -(other)
|
128
|
+
scalar_binary(other){|a, b| a - b}
|
129
|
+
end
|
130
|
+
|
131
|
+
# Find the maximum element of the tensor. See Enumerable#max.
|
132
|
+
|
133
|
+
def max(&block)
|
134
|
+
return self.values.max(&block)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Find the minimum element of the tensor. See Enumerable#max.
|
138
|
+
|
139
|
+
def min(&block)
|
140
|
+
return self.values.min(&block)
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
|
149
|
+
|
150
|
+
# To be mixed in to a Hash. Basically allows access the elements of a hash via method names rather than brackets.
|
151
|
+
|
152
|
+
|
153
|
+
module Kit
|
154
|
+
|
155
|
+
class IntegrityError < StandardError
|
156
|
+
end
|
157
|
+
|
158
|
+
def method_missing(method, *args)
|
159
|
+
# p method, args
|
160
|
+
m = method.to_s
|
161
|
+
if m =~ /=$/
|
162
|
+
name = m.chop.to_s
|
163
|
+
self.class.send(:define_method, method){|arg| self[name.to_sym] = arg}
|
164
|
+
return send(method, args[0])
|
165
|
+
else
|
166
|
+
self.class.send(:define_method, method){self[method]}
|
167
|
+
return send(method)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def check(*values, &block)
|
172
|
+
values.each do |arr|
|
173
|
+
case arr.size
|
174
|
+
when 2
|
175
|
+
expression, test_data = arr
|
176
|
+
if block
|
177
|
+
raise IntegrityError.new("#{expression} failed argument correctness test (value given was '#{self[expression].inspect}')") unless yield(instance_eval(expression), test_data)
|
178
|
+
else
|
179
|
+
is_array = test_data.class == Array
|
180
|
+
raise IntegrityError.new("#{expression} was #{instance_eval(expression)} instead of #{test_data.inspect}") unless (is_array ? test_data.include?(instance_eval(expression)) : instance_eval(expression) == test_data)
|
181
|
+
end
|
182
|
+
when 3
|
183
|
+
string, value, test_data = arr
|
184
|
+
if block
|
185
|
+
raise IntegrityError.new("#{string} failed argument correctness test (value given was '#{value.inspect}')") unless yield(value, test_data)
|
186
|
+
else
|
187
|
+
is_array = test_data.class == Array
|
188
|
+
raise IntegrityError.new("#{string} was #{value.inspect} instead of #{test_data.inspect}") unless (is_array ? test_data.include?(value) : value == test_data)
|
189
|
+
end
|
190
|
+
else
|
191
|
+
raise "Bad value checking data: #{arr.inspect}"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
|
197
|
+
|
198
|
+
|
199
|
+
|
200
|
+
end
|
201
|
+
|
202
|
+
# See Kit
|
203
|
+
|
204
|
+
class KitHash < Hash
|
205
|
+
include Kit
|
206
|
+
aliold :inspect
|
207
|
+
def inspect
|
208
|
+
return (%[#{self.class}.from_hash(#{old_inspect})])
|
209
|
+
end
|
210
|
+
def self.from_hash(hash)
|
211
|
+
kit = new
|
212
|
+
hash.each do |key, val|
|
213
|
+
kit[key] = val
|
214
|
+
end
|
215
|
+
kit
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# A GraphKit is 'a complete kit of things needed to make a graph'. By graph, a 1d, 2d, 3d, or 4d (3 + colouring/shading) visualisation is meant. The first thee axes are given the traditional labels :x, :y, :z, and the fourth :f, meaning fill, or function.
|
220
|
+
#
|
221
|
+
# Time variation can be can be achieved by an array of GraphKits each at a different time.
|
222
|
+
#
|
223
|
+
# A GraphKit is a hash where some of the keys must be specific things, and it provides a method, check_integrity, to ensure that the keys are assigned correctly. This method also checks that dimensions and ranks of all the data are consistent, allowing a graph to be plotted.
|
224
|
+
#
|
225
|
+
# GraphKit also allows you access any property, e.g. title, as
|
226
|
+
#
|
227
|
+
# graphkit.title
|
228
|
+
#
|
229
|
+
# graphkit.title =
|
230
|
+
#
|
231
|
+
# as well as the hash form:
|
232
|
+
#
|
233
|
+
# graphkit[:title]
|
234
|
+
# graphkit[:title] =
|
235
|
+
#
|
236
|
+
# GraphKits have methods which allow the graphs to be rendered using standard visualisation packages. At present only gnuplot is supported, but others are planned.
|
237
|
+
#
|
238
|
+
# GraphKits overload certain operators, for example <tt>+</tt>, which mean that they can be combined easily and intuitively. This makes plotting graphs from different sets of results on the same page as easy as adding 2+2!
|
239
|
+
#
|
240
|
+
# GraphKits define a minimum set of keys which are guaranteed to be meaningful and work on all platforms. If you stick to using just these properties, you'll be able to easily plot basic graphs. If you need more control and customisation, you need to look at the documentation both for the visualisation package (e.g. gnuplot) and the module which allows a GraphKit to interface with that package (e.g. GraphKit::Gnuplot).
|
241
|
+
#
|
242
|
+
# Here is the specification for the standard keys:
|
243
|
+
#
|
244
|
+
# * title (String): the title of the graph
|
245
|
+
# * xlabel, ylabel, zlabel (String): axis labels
|
246
|
+
# * xrange, yrange, zrange, frange (Array): ranges of the three dimensions and possibly the function as well.
|
247
|
+
# * data (Array of GraphKit::DataKits): the lines of data to be plotted.
|
248
|
+
|
249
|
+
|
250
|
+
class GraphKit < KitHash
|
251
|
+
|
252
|
+
|
253
|
+
include Kit
|
254
|
+
include Log
|
255
|
+
AXES = [:x, :y, :z, :f]
|
256
|
+
|
257
|
+
# attr_reader :gnuplot_options
|
258
|
+
|
259
|
+
alias :hash_key :key
|
260
|
+
undef :key
|
261
|
+
|
262
|
+
# Greate a new graphkit. Rarely used: see GraphKit.autocreate and GraphKit.quick_create
|
263
|
+
|
264
|
+
def initialize(naxes=0, hash = {})
|
265
|
+
logf :initialize
|
266
|
+
super()
|
267
|
+
self[:naxes] = naxes
|
268
|
+
self[:data] = []
|
269
|
+
hash
|
270
|
+
# @gnuplot_options = GnuplotOptions.new
|
271
|
+
end
|
272
|
+
|
273
|
+
# def gnuplot_options
|
274
|
+
# @gnuplot_options ||= GnuplotOptions.new
|
275
|
+
# @gnuplot_options
|
276
|
+
# end
|
277
|
+
|
278
|
+
# alias :gp :gnuplot_options
|
279
|
+
|
280
|
+
class DataError < StandardError
|
281
|
+
end
|
282
|
+
|
283
|
+
# Create a new graphkit with one hash for every datakit (each datakit corresponds to a line or surface on the graph). Each hash should contain specifications for the axes of the graph (see AxisKit#autocreate).
|
284
|
+
#
|
285
|
+
# E.g.
|
286
|
+
# kit = GraphKit.autocreate(
|
287
|
+
# {
|
288
|
+
# x: {data: [1,2,3], title 'x', units: 'm'},
|
289
|
+
# y: {data: [1,4,9], title 'x^2', units: 'm^2'}
|
290
|
+
# }
|
291
|
+
# )
|
292
|
+
#
|
293
|
+
# will create a two dimensional graph that plots x^2 against x.
|
294
|
+
|
295
|
+
def self.autocreate(*hashes)
|
296
|
+
Log.logf :autocreate
|
297
|
+
new(hashes[0].size).autocreate(*hashes)
|
298
|
+
end
|
299
|
+
|
300
|
+
|
301
|
+
def lx(*args) # :nodoc:
|
302
|
+
log_axis(*args)
|
303
|
+
end
|
304
|
+
|
305
|
+
def lx=(val) # :nodoc: (deprecated)
|
306
|
+
self.log_axis = val
|
307
|
+
end
|
308
|
+
|
309
|
+
# Create a new graphkit without providing any labels.
|
310
|
+
#
|
311
|
+
# E.g.
|
312
|
+
# kit = GraphKit.quick_create(
|
313
|
+
# [
|
314
|
+
# [1,2,3],
|
315
|
+
# [1,4,9]
|
316
|
+
# ]
|
317
|
+
# )
|
318
|
+
#
|
319
|
+
# will create a two dimensional graph that plots x^2 against x.
|
320
|
+
|
321
|
+
def self.quick_create(*datasets)
|
322
|
+
hashes = datasets.map do |data|
|
323
|
+
hash = {}
|
324
|
+
data.each_with_index do |dat, i|
|
325
|
+
hash[AXES[i]] = {data: dat}
|
326
|
+
end
|
327
|
+
hash
|
328
|
+
end
|
329
|
+
autocreate(*hashes)
|
330
|
+
end
|
331
|
+
|
332
|
+
|
333
|
+
|
334
|
+
def autocreate(*hashes) # :nodoc: (see GraphKit.autocreate)
|
335
|
+
logf :autocreate
|
336
|
+
hashes.each{|hash| data.push DataKit.autocreate(hash)}
|
337
|
+
# pp data
|
338
|
+
[:title, :label, :units, :range].each do |option|
|
339
|
+
data[0].axes.each do |key, axiskit|
|
340
|
+
# next unless AXES.include? key
|
341
|
+
self[key + option] = axiskit[option].dup if axiskit[option]
|
342
|
+
end
|
343
|
+
end
|
344
|
+
self.title = data[0].title
|
345
|
+
check_integrity
|
346
|
+
self
|
347
|
+
end
|
348
|
+
|
349
|
+
# Check that the graphkit conforms to specification; that the data has dimensions that make sense and that the titles and ranges have the right types.
|
350
|
+
|
351
|
+
def check_integrity
|
352
|
+
logf :check_integrity
|
353
|
+
check(['data.class', Array], ['title.class', [String, NilClass]], ['has_legend.class', [Hash, NilClass]])
|
354
|
+
[[:units, [String, NilClass]], [:range, [Array, NilClass]], [:label, [String, NilClass]], [:title, [String, NilClass]]].each do |prop, klass|
|
355
|
+
# next unless self[prop]
|
356
|
+
AXES.each do |key|
|
357
|
+
# p prop, klass
|
358
|
+
# p instance_eval(prop.to_s + "[#{key.inspect}]"), 'ebb'
|
359
|
+
check(["#{key + prop}.class", klass])
|
360
|
+
# check(["a key from #{prop}", key, AXES + [:f]])
|
361
|
+
end
|
362
|
+
end
|
363
|
+
data.each do |datakit|
|
364
|
+
check(['class of a member of the data array', datakit.class, DataKit])
|
365
|
+
check(['datakit.axes.size', datakit.axes.size, naxes])
|
366
|
+
datakit.check_integrity
|
367
|
+
end
|
368
|
+
return true
|
369
|
+
end
|
370
|
+
|
371
|
+
# AXES.each do |axisname|
|
372
|
+
# [:name, :label, :units, :range].each do |option|
|
373
|
+
# define_method(axisname + option){self[option][axisname]}
|
374
|
+
# define_method(axisname + option + '='.to_sym){|value| self[option][axisname] = value}
|
375
|
+
# end
|
376
|
+
# end
|
377
|
+
|
378
|
+
@@old_gnuplot_sets = [ :dgrid3d, :title, :style, :term, :terminal, :pointsize, :log_axis, :key, :pm3d, :palette, :view, :cbrange, :contour, :nosurface, :cntrparam, :preamble, :xtics, :ytics]
|
379
|
+
|
380
|
+
|
381
|
+
# @@gnuplot_sets.uniq!
|
382
|
+
|
383
|
+
# Duplicate the graphkit.
|
384
|
+
|
385
|
+
def dup
|
386
|
+
logf :dup
|
387
|
+
self.class.new(naxes, self)
|
388
|
+
end
|
389
|
+
|
390
|
+
# Combine with another graph; titles and labels from the first graph will override the second.
|
391
|
+
|
392
|
+
def +(other)
|
393
|
+
check(['other.naxes', other.naxes, self.naxes])
|
394
|
+
new = self.class.new(naxes)
|
395
|
+
new.modify(other, [:data])
|
396
|
+
new.modify(self, [:data])
|
397
|
+
new.data = self.data + other.data
|
398
|
+
new
|
399
|
+
end
|
400
|
+
|
401
|
+
def extend_using(other, mapping = nil)
|
402
|
+
if mapping
|
403
|
+
mapping.each do |mine, others|
|
404
|
+
data[mine].extend_using(other.data[others])
|
405
|
+
end
|
406
|
+
else
|
407
|
+
raise TypeError.new("A graph can only be extended by a graph with the same number of datasets unless a mapping is provided: this graph has #{data.size} and the other graph has #{other.data.size}") unless data.size == other.data.size
|
408
|
+
data.each_with_index do |dataset, index|
|
409
|
+
dataset.extend_using(other.data[index])
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
def each_axiskit(*axes, &block)
|
415
|
+
axes = AXES unless axes.size > 0
|
416
|
+
axes.each do |axis|
|
417
|
+
data.each do |datakit|
|
418
|
+
yield(datakit.axes[axis])
|
419
|
+
end
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
def plot_area_size
|
424
|
+
logf :plot_area_size
|
425
|
+
shapes = data.map{|datakit| datakit.plot_area_size}.inject do |old,new|
|
426
|
+
for i in 0...old.size
|
427
|
+
old[i][0] = [old[i][0], new[i][0]].min
|
428
|
+
old[i][1] = [old[i][1], new[i][1]].max
|
429
|
+
end
|
430
|
+
old
|
431
|
+
end
|
432
|
+
shapes
|
433
|
+
|
434
|
+
end
|
435
|
+
|
436
|
+
def transpose!
|
437
|
+
data.each do |datakit|
|
438
|
+
datakit.transpose!
|
439
|
+
end
|
440
|
+
self.xlabel, self.ylabel = ylabel, xlabel
|
441
|
+
self.xrange, self.yrange = xrange, yrange
|
442
|
+
end
|
443
|
+
|
444
|
+
# end #class GraphKit
|
445
|
+
|
446
|
+
class DataKit < KitHash
|
447
|
+
|
448
|
+
# include Kit
|
449
|
+
include Log
|
450
|
+
AXES = GraphKit::AXES
|
451
|
+
|
452
|
+
# attr_accessor :labels, :ranges, :has_legend, :units, :dimensions
|
453
|
+
|
454
|
+
def initialize(options = {})
|
455
|
+
super()
|
456
|
+
self[:axes] = {}
|
457
|
+
absorb options
|
458
|
+
end
|
459
|
+
|
460
|
+
def self.autocreate(hash)
|
461
|
+
new.autocreate(hash)
|
462
|
+
end
|
463
|
+
|
464
|
+
def autocreate(hash)
|
465
|
+
logf :autocreate
|
466
|
+
hash.each do |key, value|
|
467
|
+
# puts value.inspect
|
468
|
+
if AXES.include? key
|
469
|
+
self[:axes][key] = AxisKit.autocreate(value)
|
470
|
+
else
|
471
|
+
raise ArgumentError.new("bad key value pair in autocreate: #{key.inspect}, #{value.inspect}")
|
472
|
+
end
|
473
|
+
# puts self[key].inspect
|
474
|
+
end
|
475
|
+
# pp self
|
476
|
+
first = true
|
477
|
+
self.title = AXES.map{|axis| axes[axis] ? axes[axis].title : nil}.compact.reverse.inject("") do |str, name|
|
478
|
+
str + name + (first ? (first = false; ' vs ') : ', ')
|
479
|
+
end
|
480
|
+
self.title = self.title.sub(/, $/, '').sub(/ vs $/, '')
|
481
|
+
check_integrity
|
482
|
+
self
|
483
|
+
end
|
484
|
+
|
485
|
+
def check_integrity
|
486
|
+
logf :check_integrity
|
487
|
+
check(['title.class', [String, NilClass]], ['with.class', [String, NilClass]], ['axes.class', Hash])
|
488
|
+
axes.keys.each do |key|
|
489
|
+
check(["key #{key} from a datakit.axes", key, AXES])
|
490
|
+
check(["self.axes[#{key.inspect}].class", AxisKit])
|
491
|
+
self.axes[key].check_integrity
|
492
|
+
end
|
493
|
+
# axes.values.map{|axiskit| axiskit.data.to_a.size}.each_with_index do |size, index|
|
494
|
+
# raise IntegrityError.new("Axis data sets in this datakit have different sizes than the function #{size}, #{f.shape[0]}") unless size == new_size
|
495
|
+
# size
|
496
|
+
# end
|
497
|
+
# puts 'checking f.class', f.class
|
498
|
+
# check(['f.class', CodeRunner::FunctionKit])
|
499
|
+
|
500
|
+
# shape = f.shape
|
501
|
+
log 'checking ranks'
|
502
|
+
allowed_ranks = [[1], [1,1], [1,1,1], [1,1,2], [1,1,1,1], [1,1,2,2], [1,1,1,3]]
|
503
|
+
rnks = ranks
|
504
|
+
log rnks
|
505
|
+
raise IntegrityError.new("The combination of ranks of your data cannot be plotted. Your data has a set of axes with ranks #{rnks.inspect}. (NB, rank 1 corresponds to a vector, rank 2 to a matrix and 3 to a third rank tensor). The only possible sets of types are #{allowed_ranks.inspect}") unless allowed_ranks.include? rnks
|
506
|
+
passed = true
|
507
|
+
case rnks
|
508
|
+
when [1], [1,1], [1,1,1], [1,1,1,1]
|
509
|
+
axes.values.map{|axiskit| axiskit.shape}.inject do |old, new|
|
510
|
+
# puts old, new
|
511
|
+
passed = false unless new == old
|
512
|
+
old
|
513
|
+
end
|
514
|
+
when [1,1,2], [1,1,2,2]
|
515
|
+
# passed = false unless axes[:x].shape == axes[:y].shape
|
516
|
+
passed = false unless axes[:z].shape == [axes[:x].shape[0], axes[:y].shape[0]]
|
517
|
+
passed = false unless axes[:z].shape == axes[:f].shape if axes[:f]
|
518
|
+
when [1,1,1,3]
|
519
|
+
axes.values_at(:x, :y, :z).map{|axiskit| axiskit.shape}.inject do |old, new|
|
520
|
+
passed = false unless new == old
|
521
|
+
old
|
522
|
+
end
|
523
|
+
passed = false unless axes[:f].shape == [axes[:x].shape[0], axes[:y].shape[0], axes[:z].shape[0]]
|
524
|
+
end
|
525
|
+
raise IntegrityError.new(%[The dimensions of this data do not match: \n#{axes.inject(""){|str, (axis, axiskit)| str + "#{axis}: #{axiskit.shape}\n"}}\nranks: #{rnks}]) unless passed
|
526
|
+
# log 'finished checking ranks'
|
527
|
+
logfc :check_integrity
|
528
|
+
# raise IntegrityError.new("function data must be a vector, or have the correct dimensions (or shape) for the axes: function dimensions: #{shape}; axes dimesions: #{axes_shape}") unless shape.size == 1 or axes_shape == shape
|
529
|
+
return true
|
530
|
+
end
|
531
|
+
|
532
|
+
def shapes
|
533
|
+
logf :shapes
|
534
|
+
ans = axes.values_at(*AXES).compact.inject([]){|arr, axis| arr.push axis.shape}
|
535
|
+
logfc :shapes
|
536
|
+
return ans
|
537
|
+
end
|
538
|
+
|
539
|
+
def ranks
|
540
|
+
logf :ranks
|
541
|
+
ans = shapes.map{|shape| shape.size}
|
542
|
+
logfc :ranks
|
543
|
+
return ans
|
544
|
+
end
|
545
|
+
|
546
|
+
def extend_using(other)
|
547
|
+
raise "A dataset can only be extended using another dataset with the same ranks: the ranks of this dataset are #{ranks} and the ranks of the other dataset are #{other.ranks}" unless ranks == other.ranks
|
548
|
+
axes.each do |key, axiskit|
|
549
|
+
axiskit.extend_using(other.axes[key])
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
553
|
+
# def gnuplot_ranks
|
554
|
+
# case axes.size
|
555
|
+
# when 1,2
|
556
|
+
# return ranks
|
557
|
+
# when 3
|
558
|
+
# return [1,1,2]
|
559
|
+
# when 4
|
560
|
+
# case ranks[2]
|
561
|
+
# when 1
|
562
|
+
# return [1,1,1,3]
|
563
|
+
# when 2
|
564
|
+
# return [1,1,2,2]
|
565
|
+
# end
|
566
|
+
# end
|
567
|
+
# end
|
568
|
+
#
|
569
|
+
# def gnuplot
|
570
|
+
# # p axes.values_at(*AXES).compact.zip(gnuplot_ranks)
|
571
|
+
# Gnuplot::DataSet.new(axes.values_at(*AXES).compact.zip(gnuplot_ranks).map{|axis, rank| axis.data_for_gnuplot(rank) }) do |ds|
|
572
|
+
# (keys - [:axes, :outlier_tolerance, :outliers, :gnuplot_options]).each do |key|
|
573
|
+
# ds.set(key, self[key])
|
574
|
+
# end
|
575
|
+
# if @gnuplot_options
|
576
|
+
# @gnuplot_options.each do |opt, val|
|
577
|
+
# ds.set(opt, val)
|
578
|
+
# end
|
579
|
+
# end
|
580
|
+
# # ds.title = title
|
581
|
+
# # ds.with = [:lines, :points].inject(""){|str, opt| self[opt] ? str + opt.to_s : str }
|
582
|
+
# # p ds.with
|
583
|
+
# # ds.with = "lines"
|
584
|
+
# # ds.linewidth = 4
|
585
|
+
# end
|
586
|
+
# end
|
587
|
+
#
|
588
|
+
# def gnuplot_options
|
589
|
+
# @gnuplot_options ||= GnuplotPlotOptions.new
|
590
|
+
# @gnuplot_options
|
591
|
+
# end
|
592
|
+
#
|
593
|
+
# alias :gp :gnuplot_options
|
594
|
+
#
|
595
|
+
|
596
|
+
AXES.each do |axisname|
|
597
|
+
define_method(axisname + :axis){self[:axes][axisname]}
|
598
|
+
define_method(axisname + :axis + '='.to_sym){|value| self[:axes][axisname] = value}
|
599
|
+
end
|
600
|
+
|
601
|
+
def dup
|
602
|
+
# puts 'Datakit.dup'
|
603
|
+
new = self.class.new(self)
|
604
|
+
new.axes.each do |axis, value|
|
605
|
+
new.axes[axis] = value.dup
|
606
|
+
end
|
607
|
+
new
|
608
|
+
end
|
609
|
+
|
610
|
+
def plot_area_size
|
611
|
+
ans = []
|
612
|
+
# p data
|
613
|
+
(axes.values_at(*AXES).compact).each do |axiskit|
|
614
|
+
if range = axiskit.range
|
615
|
+
ans.push range
|
616
|
+
next
|
617
|
+
else
|
618
|
+
# p 'hello'
|
619
|
+
# p data[0].axes[key]
|
620
|
+
axdata = axiskit.data #(key == :f) ? data[0].f.data : (next unless data[0].axes[key]; data[0].axes[key].data)
|
621
|
+
next unless axdata
|
622
|
+
ans.push [axdata.min, axdata.max]
|
623
|
+
end
|
624
|
+
end
|
625
|
+
ans
|
626
|
+
end
|
627
|
+
|
628
|
+
def exclude_outliers
|
629
|
+
raise "Can only get rid of outliers for 1D or 2D data" if axes.size > 2
|
630
|
+
# self.outliers = []
|
631
|
+
if axes.size == 1
|
632
|
+
data = axes[:x].data
|
633
|
+
i = 0
|
634
|
+
loop do
|
635
|
+
break if i > data.size - 2
|
636
|
+
should_be = (data[i+1] + data[i-1]) / 2.0
|
637
|
+
deviation = (should_be - data[i]).abs / data[i].abs
|
638
|
+
if deviation > outlier_tolerance
|
639
|
+
data.delete_at(i)
|
640
|
+
i-=1
|
641
|
+
end
|
642
|
+
i+=1
|
643
|
+
end
|
644
|
+
else
|
645
|
+
x_data = axes[:x].data
|
646
|
+
data = axes[:y].data
|
647
|
+
i = 0
|
648
|
+
loop do
|
649
|
+
jump = 1
|
650
|
+
loop do
|
651
|
+
break if i > data.size - 1 - jump
|
652
|
+
break unless x_data[i+jump] == x_data[i-jump]
|
653
|
+
jump += 1
|
654
|
+
end
|
655
|
+
break if i > data.size - 1 - jump
|
656
|
+
should_be = data[i-jump] + (data[i+jump] - data[i-jump]) / (x_data[i+jump] - x_data[i-jump]) * (x_data[i] - x_data[i-jump]) #ie y1 + gradient * delta x
|
657
|
+
deviation = (should_be - data[i]).abs / data[i].abs
|
658
|
+
if deviation > outlier_tolerance
|
659
|
+
data.delete_at(i)
|
660
|
+
x_data.delete_at(i)
|
661
|
+
i-=1
|
662
|
+
end
|
663
|
+
i+=1
|
664
|
+
end
|
665
|
+
end
|
666
|
+
# p self.outliers
|
667
|
+
end
|
668
|
+
|
669
|
+
# def exclude_outliers
|
670
|
+
# raise "Can only get rid of outliers for 1D or 2D data" if axes.size > 2
|
671
|
+
# # self.outliers = []
|
672
|
+
# if axes.size == 1
|
673
|
+
# outliers.sort.reverse_each do |index|
|
674
|
+
# axes[:x].data.delete_at(index)
|
675
|
+
# end
|
676
|
+
# else
|
677
|
+
# outliers.sort.reverse_each do |index|
|
678
|
+
# axes[:x].data.delete_at(index)
|
679
|
+
# axes[:y].data.delete_at(index)
|
680
|
+
# end
|
681
|
+
#
|
682
|
+
# end
|
683
|
+
# check_integrity
|
684
|
+
# end
|
685
|
+
|
686
|
+
|
687
|
+
|
688
|
+
end
|
689
|
+
|
690
|
+
|
691
|
+
class AxisKit < KitHash
|
692
|
+
|
693
|
+
# include Kit
|
694
|
+
include Log
|
695
|
+
AXES = GraphKit::AXES
|
696
|
+
|
697
|
+
|
698
|
+
# attr_accessor :labels, :ranges, :has_legend, :units, :dimensions
|
699
|
+
|
700
|
+
def initialize(hash = {})
|
701
|
+
super()
|
702
|
+
self.title = ""
|
703
|
+
self.units = ""
|
704
|
+
self.
|
705
|
+
absorb hash
|
706
|
+
end
|
707
|
+
|
708
|
+
def check_integrity
|
709
|
+
check(['units.class', [String]], ['scaling.class', [Float, NilClass]], ['label.class', [String, NilClass]], ['title.class', [String]])
|
710
|
+
check(['data.to_a.class', Array])
|
711
|
+
end
|
712
|
+
|
713
|
+
def dup
|
714
|
+
# puts 'i was called'
|
715
|
+
new = self.class.new(self)
|
716
|
+
new.data = data.dup
|
717
|
+
new
|
718
|
+
end
|
719
|
+
|
720
|
+
def self.autocreate(hash)
|
721
|
+
new_kit = new(hash)
|
722
|
+
new_kit.label = "#{new_kit.title} (#{new_kit.units})"
|
723
|
+
new_kit
|
724
|
+
end
|
725
|
+
|
726
|
+
def shape
|
727
|
+
logf :shape
|
728
|
+
if data.methods.include? :shape
|
729
|
+
ans = data.shape
|
730
|
+
elsif data.methods.include? :size
|
731
|
+
ans = [data.size]
|
732
|
+
elsif data.methods.include? :dimensions
|
733
|
+
ans = data.dimensions
|
734
|
+
else
|
735
|
+
raise 'data does not implement size or shape or dimensions methods'
|
736
|
+
end
|
737
|
+
logfc :shape
|
738
|
+
return ans
|
739
|
+
end
|
740
|
+
# def data_for_gnuplot(rank)
|
741
|
+
# case rank
|
742
|
+
# when 0, 1
|
743
|
+
# return data
|
744
|
+
# when Fixnum
|
745
|
+
# if shape.size == 1
|
746
|
+
# return SparseTensor.diagonal(rank, data)
|
747
|
+
# else
|
748
|
+
# return data
|
749
|
+
# end
|
750
|
+
# else
|
751
|
+
# raise TypeError("Bad Rank")
|
752
|
+
# end
|
753
|
+
# end
|
754
|
+
|
755
|
+
def extend_using(other)
|
756
|
+
raise TypeError.new("Can only extend axes if data have the same ranks: #{shape.size}, #{other.shape.size}") unless shape.size == other.shape.size
|
757
|
+
raise TypeError.new("Can only extend axes if data have the same class") unless data.class == other.data.class
|
758
|
+
case shape.size
|
759
|
+
when 1
|
760
|
+
desired_length = shape[0] + other.shape[0]
|
761
|
+
if data.methods.include? :connect
|
762
|
+
self.data = data.connect(other.data)
|
763
|
+
elsif data.methods.include? "+".to_sym
|
764
|
+
data += other
|
765
|
+
else
|
766
|
+
raise TypeError("Extending this type of data is currently not implemented.")
|
767
|
+
end
|
768
|
+
raise "Something went wrong: the length of the extended data #{shape[0]} is not the sum of the lengths of the two original pieces of data #{desired_length}." unless shape[0] == desired_length
|
769
|
+
else
|
770
|
+
raise TypeError("Extending data with this rank: #{shape.size} is currently not implemented.")
|
771
|
+
end
|
772
|
+
end
|
773
|
+
|
774
|
+
end
|
775
|
+
|
776
|
+
end # class GraphKit
|
777
|
+
|
778
|
+
# end #class CodeRunner
|
779
|
+
|
780
|
+
|
781
|
+
if $0 == __FILE__
|
782
|
+
|
783
|
+
a = GraphKit.autocreate({x: {data: [1,3,5,6], units: 'feet', title: 'Height'}})
|
784
|
+
a.gnuplot
|
785
|
+
gets
|
786
|
+
a.close
|
787
|
+
a = CodeRunner::GraphKit.autocreate({x: {data: [2, 5, 11, 22], units: 'years', title: 'Age'}, y: {data: [1,3,5,6], units: 'feet', title: 'Height'}})
|
788
|
+
|
789
|
+
puts a.pretty_inspect
|
790
|
+
|
791
|
+
p a.title
|
792
|
+
p a.label
|
793
|
+
p a.chox
|
794
|
+
p a.xlabel
|
795
|
+
p a.yunits
|
796
|
+
|
797
|
+
# a.gnuplot
|
798
|
+
# gets
|
799
|
+
# a.close
|
800
|
+
a.data[0].with = 'lp'
|
801
|
+
datakit = a.data[0].dup
|
802
|
+
datakit.axes[:y].data.map!{|value| value * 0.85}
|
803
|
+
datakit.title += ' of women'
|
804
|
+
a.data.push datakit
|
805
|
+
a.data[0].title += ' of men'
|
806
|
+
pp a
|
807
|
+
a.gnuplot
|
808
|
+
gets
|
809
|
+
a.close
|
810
|
+
# Gnuplot.open{a.to_gnuplot}
|
811
|
+
|
812
|
+
b = CodeRunner::GraphKit.autocreate({x: {data: [2, 5, 11, 22], units: 'years', title: 'Age'}, y: {data: [1,3,5,6], units: 'feet', title: 'Height'}, z: {data: [2,4,8,12], units: 'stone', title: 'Weight'}})
|
813
|
+
b.data[0].modify({with: 'lp'})
|
814
|
+
pp b
|
815
|
+
# d = b.data[0].f.data_for_gnuplot(2)
|
816
|
+
# p d
|
817
|
+
# p d[0,1]
|
818
|
+
# d.delete([0,0])
|
819
|
+
# p d
|
820
|
+
# p d[1,1]
|
821
|
+
# p d[1,2]
|
822
|
+
# d = SparseTensor.new(3)
|
823
|
+
# p d
|
824
|
+
# p d[0,1,4]
|
825
|
+
# p d[3, 4,6]
|
826
|
+
#
|
827
|
+
b.gnuplot
|
828
|
+
gets
|
829
|
+
b.close
|
830
|
+
b.gnuplot_write('heights.ps')
|
831
|
+
|
832
|
+
p b.data[0].plot_area_size
|
833
|
+
|
834
|
+
c = SparseTensor.new(3)
|
835
|
+
c[1,3,9]= 4
|
836
|
+
c[3,3,34] = 4.346
|
837
|
+
c[23, 234, 293] = 9.234
|
838
|
+
|
839
|
+
p c
|
840
|
+
|
841
|
+
d = SparseTensor.new(3)
|
842
|
+
d[1,3,9]= 4
|
843
|
+
d[3,3,34] = 4.346
|
844
|
+
d[23, 234, 294] = 9.234
|
845
|
+
|
846
|
+
p c + d
|
847
|
+
|
848
|
+
end
|
849
|
+
|
850
|
+
|
851
|
+
# A graph kit is 'everything you need..
|
852
|
+
#A graph kit is, in fact, a very intelligent hash
|
@@ -0,0 +1,412 @@
|
|
1
|
+
|
2
|
+
class GraphKit
|
3
|
+
|
4
|
+
class GnuplotSetOptions < KitHash
|
5
|
+
alias :hash_key :key
|
6
|
+
undef :key
|
7
|
+
GNUPLOT_SETS = %w[
|
8
|
+
angles arrow autoscale bars
|
9
|
+
bmargin border boxwidth cbdata
|
10
|
+
cbdtics cblabel cbmtics cbrange
|
11
|
+
cbtics clabel clip cntrparam
|
12
|
+
colorbox contour data datafile
|
13
|
+
date_specifiers decimalsign dgrid3d dummy
|
14
|
+
encoding fit fontpath format
|
15
|
+
function grid hidden3d historysize
|
16
|
+
isosamples key label lmargin
|
17
|
+
loadpath locale log logscale
|
18
|
+
macros mapping margin missing
|
19
|
+
mouse multiplot mx2tics mxtics
|
20
|
+
my2tics mytics mztics object
|
21
|
+
offsets origin output palette
|
22
|
+
parametric pm3d pointsize polar
|
23
|
+
print rmargin rrange samples
|
24
|
+
size style surface table
|
25
|
+
term terminal termoption tics
|
26
|
+
ticscale ticslevel time_specifiers timefmt
|
27
|
+
timestamp title tmargin trange
|
28
|
+
urange view vrange x2data
|
29
|
+
x2dtics x2label x2mtics x2range
|
30
|
+
x2tics x2zeroaxis xdata xdtics
|
31
|
+
xlabel xmtics xrange xtics
|
32
|
+
xyplane xzeroaxis y2data y2dtics
|
33
|
+
y2label y2mtics y2range y2tics
|
34
|
+
y2zeroaxis ydata ydtics ylabel
|
35
|
+
ymtics yrange ytics yzeroaxis
|
36
|
+
zdata zdtics zero zeroaxis
|
37
|
+
zlabel zmtics zrange ztics].map{|s| s.to_sym}
|
38
|
+
# p instance_methods.sort
|
39
|
+
GNUPLOT_SETS.each do |opt|
|
40
|
+
define_method(opt + "=".to_sym) do |str|
|
41
|
+
check(["Class of #{str} supplied to #{opt}", str.class, [Array, String, FalseClass, NilClass]])
|
42
|
+
self[opt] = str
|
43
|
+
end
|
44
|
+
define_method(opt) do
|
45
|
+
self[opt]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
def []=(opt, val)
|
51
|
+
raise "#{opt} is not a valid gnuplot set option" unless GNUPLOT_SETS.include? opt
|
52
|
+
super
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class GnuplotPlotOptions < KitHash
|
57
|
+
alias :hash_key :key
|
58
|
+
undef :key
|
59
|
+
GNUPLOT_SETS = %w[
|
60
|
+
acsplines axes bezier binary
|
61
|
+
csplines cumulative datafile errorbars
|
62
|
+
errorlines every example frequency
|
63
|
+
index iteration kdensity matrix
|
64
|
+
parametric ranges sbezier smooth
|
65
|
+
special-filenames style thru title
|
66
|
+
unique using with].map{|s| s.to_sym}
|
67
|
+
# p instance_methods.sort
|
68
|
+
GNUPLOT_SETS.each do |opt|
|
69
|
+
define_method(opt + "=".to_sym) do |str|
|
70
|
+
check(["Class of #{str} supplied to #{opt}", str.class, [Array, String, FalseClass, NilClass]])
|
71
|
+
self[opt] = str
|
72
|
+
end
|
73
|
+
define_method(opt) do
|
74
|
+
self[opt]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
def []=(opt, val)
|
80
|
+
raise "#{opt} is not a valid gnuplot set option" unless GNUPLOT_SETS.include? opt
|
81
|
+
super
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# class GraphKit < KitHash
|
86
|
+
|
87
|
+
def gnuplot_options
|
88
|
+
@gnuplot_options ||= GnuplotSetOptions.new
|
89
|
+
@gnuplot_options
|
90
|
+
end
|
91
|
+
|
92
|
+
alias :gp :gnuplot_options
|
93
|
+
|
94
|
+
@@old_gnuplot_sets = [:output, :dgrid3d, :title, :style, :term, :terminal, :pointsize, :log_axis, :key, :pm3d, :palette, :view, :cbrange, :contour, :nosurface, :cntrparam, :preamble, :xtics, :ytics]
|
95
|
+
|
96
|
+
|
97
|
+
# @@gnuplot_sets.uniq!
|
98
|
+
|
99
|
+
def gnuplot(options = {})
|
100
|
+
logf :gnuplot
|
101
|
+
processes = %x[ps | grep 'gnuplot'].scan(/^\s*\d+/).map{|match| match.to_i}
|
102
|
+
if options[:outlier_tolerance]
|
103
|
+
raise "Can only get rid of outliers for 1D or 2D data" if naxes > 2
|
104
|
+
data.each do |datakit|
|
105
|
+
datakit.outlier_tolerance = options[:outlier_tolerance]
|
106
|
+
# datakit.calculate_outliers
|
107
|
+
datakit.exclude_outliers
|
108
|
+
end
|
109
|
+
options.delete(:outlier_tolerance)
|
110
|
+
end
|
111
|
+
live = options[:live]
|
112
|
+
options.delete(:live) if live
|
113
|
+
check_integrity
|
114
|
+
if (self.view =~ /map/ or self.pm3d =~ /map/) and naxes < 4
|
115
|
+
self.cbrange ||= self.zrange
|
116
|
+
options[:cbrange] ||= options[:zrange] if options[:zrange]
|
117
|
+
end
|
118
|
+
options.each do |k,v|
|
119
|
+
# ep option, val if option == :xrange
|
120
|
+
|
121
|
+
# ep k, v
|
122
|
+
set(k, v)
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
Gnuplot.open do |gp|
|
127
|
+
log 'opened gnuplot'
|
128
|
+
plotproc = Proc.new do |plot|
|
129
|
+
log 'creating plotproc'
|
130
|
+
[:label, :range].each do |property|
|
131
|
+
|
132
|
+
(AXES - [:f]).each do |axis|
|
133
|
+
option = axis + property
|
134
|
+
val = self.send(option)
|
135
|
+
# ep option, val if option == :xrange
|
136
|
+
if val
|
137
|
+
|
138
|
+
if property == :range
|
139
|
+
val = "[#{val[0]}:#{val[1]}]"
|
140
|
+
end
|
141
|
+
plot.send(option, val)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
|
148
|
+
@@old_gnuplot_sets.each do |option|
|
149
|
+
# p option
|
150
|
+
next unless val = send(option) # single = deliberate
|
151
|
+
val = "[#{val[0]}:#{val[1]}]" if option == :cbrange
|
152
|
+
plot.send(option, val)
|
153
|
+
end
|
154
|
+
|
155
|
+
#Should change this to keys
|
156
|
+
|
157
|
+
GnuplotSetOptions::GNUPLOT_SETS.each do |option|
|
158
|
+
# p option
|
159
|
+
next unless val = gnuplot_options.send(option) # single = deliberate
|
160
|
+
val = "[#{val[0]}:#{val[1]}]" if option == :cbrange
|
161
|
+
plot.send(option, val)
|
162
|
+
end
|
163
|
+
|
164
|
+
# plot.ylabel ylabel
|
165
|
+
# plot.xlabel xlabel
|
166
|
+
data.each do |datakit|
|
167
|
+
plot.data << datakit.gnuplot
|
168
|
+
end
|
169
|
+
log 'created plotproc'
|
170
|
+
|
171
|
+
end
|
172
|
+
log 'sending plotproc'
|
173
|
+
case naxes
|
174
|
+
when 1,2
|
175
|
+
# puts '2 naxes'
|
176
|
+
Gnuplot::Plot.new(gp){|plot| plotproc.call(plot)}
|
177
|
+
when 3,4
|
178
|
+
Gnuplot::SPlot.new(gp){|plot| plotproc.call(plot)}
|
179
|
+
else
|
180
|
+
raise IntegrityError.new("This number of axes: #{axes} is not supported")
|
181
|
+
end
|
182
|
+
log 'sent plotproc'
|
183
|
+
(STDIN.gets) if live
|
184
|
+
|
185
|
+
end
|
186
|
+
self.pid = (%x[ps | grep 'gnuplot'].scan(/^\s*\d+/).map{|match| match.to_i} - processes)[-1]
|
187
|
+
# p pid
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
def gnuplot_write(file_name, options={})
|
192
|
+
logf :gnuplot_write
|
193
|
+
if file_name
|
194
|
+
options[:output] = file_name
|
195
|
+
unless options[:term] or options[:terminal]
|
196
|
+
case File.extname(file_name)
|
197
|
+
when '.pdf'
|
198
|
+
options[:term] = 'pdf size 20cm,15cm'
|
199
|
+
when '.ps'
|
200
|
+
options[:term] = 'post color'
|
201
|
+
when '.eps'
|
202
|
+
unless options[:latex]
|
203
|
+
options[:term] = %[post eps color enhanced size #{options[:size] or "3.5in,2.33in"}]
|
204
|
+
else
|
205
|
+
options[:term] ||= "epslatex color dashed size #{options[:size] or "3.5in,#{options[:height] or "2.0in"}"} colortext standalone 8"
|
206
|
+
options[:output] = file_name.sub(/\.eps/, '.tex')
|
207
|
+
end
|
208
|
+
when '.jpg'
|
209
|
+
options[:term] = "jpeg size #{options[:size] or "3.5in,2.33in"}"
|
210
|
+
when '.png'
|
211
|
+
options[:term] = "png size #{options[:size] or "10in,7.5in"}"
|
212
|
+
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
options.delete(:size)
|
217
|
+
gnuplot(options)
|
218
|
+
if options[:latex]
|
219
|
+
name = file_name.sub(/\.eps$/, '')
|
220
|
+
raise 'latex failed' unless system "latex #{name}.tex"
|
221
|
+
raise 'dvips failed' unless system "dvips #{name}.dvi"
|
222
|
+
FileUtils.rm "#{name}.eps" if FileTest.exist? "#{name}.eps"
|
223
|
+
raise 'ps2eps failed' unless system "ps2eps #{name}.ps"
|
224
|
+
end
|
225
|
+
# ep file_name
|
226
|
+
return File.basename(file_name, File.extname(file_name))
|
227
|
+
end
|
228
|
+
|
229
|
+
def self.latex_multiplot(name, options={})
|
230
|
+
name = name.sub(/\.eps$/, '')
|
231
|
+
figure_preamble = options[:preamble] || <<EOF
|
232
|
+
\\documentclass[prl,graphicx,reprint,twocolumn, superscriptaddress]{revtex4}
|
233
|
+
%\documentclass[aip,reprint]{revtex4-1}
|
234
|
+
\\usepackage{graphics,bm,overpic,subfigure,color}
|
235
|
+
|
236
|
+
\\pagestyle{empty}
|
237
|
+
\\begin{document}
|
238
|
+
\\begin{figure}
|
239
|
+
EOF
|
240
|
+
|
241
|
+
figure_postamble = options[:postamble] || <<EOF
|
242
|
+
\\end{figure}
|
243
|
+
\\end{document}
|
244
|
+
EOF
|
245
|
+
text = <<EOF
|
246
|
+
#{figure_preamble}
|
247
|
+
#{yield}
|
248
|
+
#{figure_postamble}
|
249
|
+
EOF
|
250
|
+
File.open("#{name}.tex", 'w'){|f| f.puts text}
|
251
|
+
raise 'latex failed' unless system "latex #{name}.tex"
|
252
|
+
raise 'dvips failed' unless system "dvips #{name}.dvi"
|
253
|
+
FileUtils.rm "#{name}.eps" if FileTest.exist? "#{name}.eps"
|
254
|
+
raise 'ps2eps failed' unless system "ps2eps #{name}.ps"
|
255
|
+
end
|
256
|
+
|
257
|
+
def close
|
258
|
+
logf :close
|
259
|
+
begin
|
260
|
+
Process.kill('TERM', pid) if pid
|
261
|
+
rescue => err
|
262
|
+
puts err
|
263
|
+
end
|
264
|
+
self.pid = nil
|
265
|
+
end
|
266
|
+
|
267
|
+
|
268
|
+
|
269
|
+
# end
|
270
|
+
|
271
|
+
class DataKit < KitHash
|
272
|
+
|
273
|
+
|
274
|
+
def gnuplot_ranks
|
275
|
+
case axes.size
|
276
|
+
when 1,2
|
277
|
+
return ranks
|
278
|
+
when 3
|
279
|
+
return [1,1,2]
|
280
|
+
when 4
|
281
|
+
case ranks[2]
|
282
|
+
when 1
|
283
|
+
return [1,1,1,3]
|
284
|
+
when 2
|
285
|
+
return [1,1,2,2]
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def gnuplot
|
291
|
+
# p axes.values_at(*AXES).compact.zip(gnuplot_ranks)
|
292
|
+
Gnuplot::DataSet.new(axes.values_at(*AXES).compact.zip(gnuplot_ranks).map{|axis, rank| axis.data_for_gnuplot(rank) }) do |ds|
|
293
|
+
(keys - [:axes, :outlier_tolerance, :outliers, :gnuplot_options]).each do |key|
|
294
|
+
ds.set(key, self[key])
|
295
|
+
end
|
296
|
+
if @gnuplot_options
|
297
|
+
@gnuplot_options.each do |opt, val|
|
298
|
+
ds.set(opt, val)
|
299
|
+
end
|
300
|
+
end
|
301
|
+
# ds.title = title
|
302
|
+
# ds.with = [:lines, :points].inject(""){|str, opt| self[opt] ? str + opt.to_s : str }
|
303
|
+
# p ds.with
|
304
|
+
# ds.with = "lines"
|
305
|
+
# ds.linewidth = 4
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def gnuplot_options
|
310
|
+
@gnuplot_options ||= GnuplotPlotOptions.new
|
311
|
+
@gnuplot_options
|
312
|
+
end
|
313
|
+
|
314
|
+
alias :gp :gnuplot_options
|
315
|
+
|
316
|
+
|
317
|
+
end
|
318
|
+
|
319
|
+
|
320
|
+
class AxisKit < KitHash
|
321
|
+
|
322
|
+
|
323
|
+
def data_for_gnuplot(rank)
|
324
|
+
case rank
|
325
|
+
when 0, 1
|
326
|
+
return data
|
327
|
+
when Fixnum
|
328
|
+
if shape.size == 1
|
329
|
+
return SparseTensor.diagonal(rank, data)
|
330
|
+
else
|
331
|
+
return data
|
332
|
+
end
|
333
|
+
else
|
334
|
+
raise TypeError("Bad Rank")
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
end
|
339
|
+
|
340
|
+
end # class GraphKit
|
341
|
+
# end #class CodeRunner
|
342
|
+
|
343
|
+
|
344
|
+
if $0 == __FILE__
|
345
|
+
|
346
|
+
a = CodeRunner::GraphKit.autocreate({x: {data: [1,3,5,6], units: 'feet', title: 'Height'}})
|
347
|
+
a.gnuplot
|
348
|
+
gets
|
349
|
+
a.close
|
350
|
+
a = CodeRunner::GraphKit.autocreate({x: {data: [2, 5, 11, 22], units: 'years', title: 'Age'}, y: {data: [1,3,5,6], units: 'feet', title: 'Height'}})
|
351
|
+
|
352
|
+
puts a.pretty_inspect
|
353
|
+
|
354
|
+
p a.title
|
355
|
+
p a.label
|
356
|
+
p a.chox
|
357
|
+
p a.xlabel
|
358
|
+
p a.yunits
|
359
|
+
|
360
|
+
# a.gnuplot
|
361
|
+
# gets
|
362
|
+
# a.close
|
363
|
+
a.data[0].with = 'lp'
|
364
|
+
datakit = a.data[0].dup
|
365
|
+
datakit.axes[:y].data.map!{|value| value * 0.85}
|
366
|
+
datakit.title += ' of women'
|
367
|
+
a.data.push datakit
|
368
|
+
a.data[0].title += ' of men'
|
369
|
+
pp a
|
370
|
+
a.gnuplot
|
371
|
+
gets
|
372
|
+
a.close
|
373
|
+
# Gnuplot.open{a.to_gnuplot}
|
374
|
+
|
375
|
+
b = CodeRunner::GraphKit.autocreate({x: {data: [2, 5, 11, 22], units: 'years', title: 'Age'}, y: {data: [1,3,5,6], units: 'feet', title: 'Height'}, z: {data: [2,4,8,12], units: 'stone', title: 'Weight'}})
|
376
|
+
b.data[0].modify({with: 'lp'})
|
377
|
+
pp b
|
378
|
+
# d = b.data[0].f.data_for_gnuplot(2)
|
379
|
+
# p d
|
380
|
+
# p d[0,1]
|
381
|
+
# d.delete([0,0])
|
382
|
+
# p d
|
383
|
+
# p d[1,1]
|
384
|
+
# p d[1,2]
|
385
|
+
# d = SparseTensor.new(3)
|
386
|
+
# p d
|
387
|
+
# p d[0,1,4]
|
388
|
+
# p d[3, 4,6]
|
389
|
+
#
|
390
|
+
b.gnuplot
|
391
|
+
gets
|
392
|
+
b.close
|
393
|
+
b.gnuplot_write('heights.ps')
|
394
|
+
|
395
|
+
p b.data[0].plot_area_size
|
396
|
+
|
397
|
+
c = SparseTensor.new(3)
|
398
|
+
c[1,3,9]= 4
|
399
|
+
c[3,3,34] = 4.346
|
400
|
+
c[23, 234, 293] = 9.234
|
401
|
+
|
402
|
+
p c
|
403
|
+
|
404
|
+
d = SparseTensor.new(3)
|
405
|
+
d[1,3,9]= 4
|
406
|
+
d[3,3,34] = 4.346
|
407
|
+
d[23, 234, 294] = 9.234
|
408
|
+
|
409
|
+
p c + d
|
410
|
+
|
411
|
+
end
|
412
|
+
|
metadata
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: graphkit
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.1"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Edmund Highcock
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-07-26 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: A GraphKit is a device independent intelligent data container that allows the easy sharing, combining and plotting of graphic data representations. Easily created from data, they can be output in a variety of formats using packages such as gnuplot
|
17
|
+
email:
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- lib/graphkit.rb
|
26
|
+
- lib/graphkit/gnuplot.rb
|
27
|
+
has_rdoc: true
|
28
|
+
homepage: http://graphkit.rubyforge.org/
|
29
|
+
post_install_message:
|
30
|
+
rdoc_options: []
|
31
|
+
|
32
|
+
require_paths:
|
33
|
+
- lib
|
34
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: 1.9.0
|
39
|
+
version:
|
40
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: "0"
|
45
|
+
version:
|
46
|
+
requirements: []
|
47
|
+
|
48
|
+
rubyforge_project: graphkit
|
49
|
+
rubygems_version: 1.3.1
|
50
|
+
signing_key:
|
51
|
+
specification_version: 2
|
52
|
+
summary: Graph object with intuitive combining and plotting functions.
|
53
|
+
test_files: []
|
54
|
+
|