graphkit 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/lib/graphkit.rb +852 -0
  2. data/lib/graphkit/gnuplot.rb +412 -0
  3. metadata +54 -0
@@ -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
+