ctioga 1.11.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. data/COPYING +340 -0
  2. data/ctioga/bin/ctable +28 -0
  3. data/ctioga/bin/ctioga +37 -0
  4. data/ctioga/doc/ctable.1 +156 -0
  5. data/ctioga/doc/ctioga.1 +2363 -0
  6. data/ctioga/examples/README +46 -0
  7. data/ctioga/examples/ctioga.gnuplot +4 -0
  8. data/ctioga/examples/ctioga_within_tioga.rb +53 -0
  9. data/ctioga/examples/ctiogarc.rb +24 -0
  10. data/ctioga/examples/include_1.rb +15 -0
  11. data/ctioga/examples/noise.dat +100 -0
  12. data/ctioga/examples/noise.rb +13 -0
  13. data/ctioga/examples/trig.csv +100 -0
  14. data/ctioga/examples/trig.dat +100 -0
  15. data/ctioga/examples/trig.rb +14 -0
  16. data/ctioga/examples/trigh.dat +100 -0
  17. data/ctioga/examples/trigh.rb +10 -0
  18. data/ctioga/examples/tutorial +763 -0
  19. data/ctioga/examples/tutorial.sh +269 -0
  20. data/ctioga/tests/README +14 -0
  21. data/ctioga/tests/axes.sh +40 -0
  22. data/ctioga/tests/basic.sh +11 -0
  23. data/ctioga/tests/draw.sh +24 -0
  24. data/ctioga/tests/histograms.sh +14 -0
  25. data/ctioga/tests/insets.sh +41 -0
  26. data/ctioga/tests/layouts.sh +29 -0
  27. data/ctioga/tests/legends.sh +113 -0
  28. data/ctioga/tests/styles.sh +43 -0
  29. data/ctioga/tests/test_style.sh +8 -0
  30. data/ctioga/tests/tests.sh +24 -0
  31. data/ctioga/tests/text_backend.sh +83 -0
  32. data/ctioga/tests/tioga_defaults.rb +18 -0
  33. data/lib/CTioga/axes.rb +904 -0
  34. data/lib/CTioga/backends.rb +88 -0
  35. data/lib/CTioga/boundaries.rb +224 -0
  36. data/lib/CTioga/ctable.rb +134 -0
  37. data/lib/CTioga/curve_style.rb +246 -0
  38. data/lib/CTioga/debug.rb +199 -0
  39. data/lib/CTioga/dimension.rb +133 -0
  40. data/lib/CTioga/elements.rb +17 -0
  41. data/lib/CTioga/elements/base.rb +84 -0
  42. data/lib/CTioga/elements/containers.rb +578 -0
  43. data/lib/CTioga/elements/curves.rb +368 -0
  44. data/lib/CTioga/elements/tioga_primitives.rb +440 -0
  45. data/lib/CTioga/layout.rb +595 -0
  46. data/lib/CTioga/legends.rb +29 -0
  47. data/lib/CTioga/legends/cmdline.rb +187 -0
  48. data/lib/CTioga/legends/item.rb +164 -0
  49. data/lib/CTioga/legends/style.rb +257 -0
  50. data/lib/CTioga/log.rb +73 -0
  51. data/lib/CTioga/movingarrays.rb +131 -0
  52. data/lib/CTioga/partition.rb +271 -0
  53. data/lib/CTioga/plot_style.rb +230 -0
  54. data/lib/CTioga/plotmaker.rb +1677 -0
  55. data/lib/CTioga/shortcuts.rb +69 -0
  56. data/lib/CTioga/structures.rb +82 -0
  57. data/lib/CTioga/styles.rb +140 -0
  58. data/lib/CTioga/themes.rb +581 -0
  59. data/lib/CTioga/themes/classical.rb +82 -0
  60. data/lib/CTioga/themes/demo.rb +63 -0
  61. data/lib/CTioga/themes/fits.rb +91 -0
  62. data/lib/CTioga/themes/mono.rb +33 -0
  63. data/lib/CTioga/tioga.rb +32 -0
  64. data/lib/CTioga/utils.rb +173 -0
  65. data/lib/MetaBuilder/Parameters/dates.rb +38 -0
  66. data/lib/MetaBuilder/Parameters/lists.rb +132 -0
  67. data/lib/MetaBuilder/Parameters/numbers.rb +69 -0
  68. data/lib/MetaBuilder/Parameters/strings.rb +86 -0
  69. data/lib/MetaBuilder/Parameters/styles.rb +75 -0
  70. data/lib/MetaBuilder/Qt4/Parameters/dates.rb +51 -0
  71. data/lib/MetaBuilder/Qt4/Parameters/numbers.rb +65 -0
  72. data/lib/MetaBuilder/Qt4/Parameters/strings.rb +106 -0
  73. data/lib/MetaBuilder/Qt4/parameter.rb +172 -0
  74. data/lib/MetaBuilder/Qt4/parameters.rb +9 -0
  75. data/lib/MetaBuilder/descriptions.rb +603 -0
  76. data/lib/MetaBuilder/factory.rb +101 -0
  77. data/lib/MetaBuilder/group.rb +57 -0
  78. data/lib/MetaBuilder/metabuilder.rb +10 -0
  79. data/lib/MetaBuilder/parameter.rb +374 -0
  80. data/lib/MetaBuilder/parameters.rb +11 -0
  81. data/lib/MetaBuilder/qt4.rb +8 -0
  82. data/lib/SciYAG/Backends/backend.rb +379 -0
  83. data/lib/SciYAG/Backends/binner.rb +168 -0
  84. data/lib/SciYAG/Backends/cache.rb +102 -0
  85. data/lib/SciYAG/Backends/dataset.rb +158 -0
  86. data/lib/SciYAG/Backends/descriptions.rb +469 -0
  87. data/lib/SciYAG/Backends/filters.rb +25 -0
  88. data/lib/SciYAG/Backends/filters/average.rb +134 -0
  89. data/lib/SciYAG/Backends/filters/cumulate.rb +37 -0
  90. data/lib/SciYAG/Backends/filters/filter.rb +70 -0
  91. data/lib/SciYAG/Backends/filters/norm.rb +39 -0
  92. data/lib/SciYAG/Backends/filters/smooth.rb +63 -0
  93. data/lib/SciYAG/Backends/filters/sort.rb +43 -0
  94. data/lib/SciYAG/Backends/filters/strip.rb +34 -0
  95. data/lib/SciYAG/Backends/filters/trim.rb +64 -0
  96. data/lib/SciYAG/Backends/gnuplot.rb +131 -0
  97. data/lib/SciYAG/Backends/math.rb +108 -0
  98. data/lib/SciYAG/Backends/mdb.rb +462 -0
  99. data/lib/SciYAG/Backends/multitext.rb +96 -0
  100. data/lib/SciYAG/Backends/source.rb +64 -0
  101. data/lib/SciYAG/Backends/text.rb +339 -0
  102. data/lib/SciYAG/backends.rb +16 -0
  103. metadata +191 -0
@@ -0,0 +1,368 @@
1
+ # curves.rb, copyright (c) 2006 by Vincent Fourmond:
2
+ # The class describing a curve to be plotted.
3
+
4
+ # This program is free software; you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation; either version 2 of the License, or
7
+ # (at your option) any later version.
8
+
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details (in the COPYING file).
13
+
14
+ require 'CTioga/elements'
15
+ require 'CTioga/utils'
16
+ require 'CTioga/boundaries'
17
+ require 'CTioga/curve_style'
18
+
19
+ module CTioga
20
+
21
+ Version::register_svn_info('$Revision: 879 $', '$Date: 2009-02-23 23:58:20 +0100 (Mon, 23 Feb 2009) $')
22
+
23
+ # The class Curve2D stores both the data and the way it
24
+ # should be plotted, such as it's legend, it's color, it's line
25
+ # style and so on...
26
+ class Curve2D < TiogaElement
27
+
28
+ # for Dvectors:
29
+ include Dobjects
30
+
31
+ # The CurveStyle object representing the curve's style.
32
+ attr_reader :style
33
+
34
+ # The underlying Function object:
35
+ attr_reader :function
36
+
37
+ def need_style?
38
+ return true
39
+ end
40
+
41
+ def set_style(style)
42
+ @style = style.dup
43
+ end
44
+
45
+ def initialize(style = nil, data = nil)
46
+ # style is a CurveStyle object
47
+ set_style(style) if style
48
+ set_data(data) if data
49
+ @line_cap = nil
50
+ end
51
+
52
+ def has_legend?
53
+ if @style.legend
54
+ return true
55
+ else
56
+ return false
57
+ end
58
+ end
59
+
60
+ # Sets the data for the curve
61
+ def set_data(ar)
62
+ @function = ar
63
+ end
64
+
65
+ # This function returns the index of a point
66
+ # from the curve according to the given _spec_, a string.
67
+ # * if _spec_ is a number >= 1, it represents the index of the point
68
+ # in the Function object.
69
+ # * if _spec_ is a number <1, it represents the relative position of the
70
+ # point in the object (it is then multiplied by the size to get
71
+ # the actual index).
72
+ # * if _spec_ is of the form 'x,y', the closest point belonging to the
73
+ # function is taken. Not implemented yet.
74
+ def parse_position(spec)
75
+ if spec =~ /(.+),(.+)/
76
+ raise "The (x,y) point position is not implemented yet"
77
+ else
78
+ val = Float(spec)
79
+ if val < 1
80
+ index = (@function.size * val).round
81
+ else
82
+ index = val.round
83
+ end
84
+ index
85
+ end
86
+ end
87
+
88
+ # Returns the tangent of the curve at the given point, that is a vector
89
+ # parallel to it. _dir_ specifies if it is a left tangent or a right
90
+ # tangent or an average of both. Nothing else than the latter is
91
+ # implemented for now.
92
+ def tangent(index, dir = :both)
93
+ before = @function.point(index - 1)
94
+ point = @function.point(index)
95
+ after = @function.point(index + 1)
96
+ raise "Point invalid" unless point
97
+ tangent = Dvector[0,0]
98
+ if index > 0
99
+ tangent += (point - before)
100
+ end
101
+ if index < (@function.size - 1)
102
+ tangent += (after - point)
103
+ end
104
+ return tangent
105
+ end
106
+
107
+ # This function returns the bouding box of the specified graphes
108
+ # No margin adjustment is done here, as it can lead to very uneven
109
+ # graphs
110
+ def get_boundaries
111
+ top = @function.y.max
112
+ bottom = @function.y.min
113
+ left = @function.x.min
114
+ right = @function.x.max
115
+
116
+ width = (left == right) ? 1 : right - left
117
+ height = (top == bottom) ? 1 : top - bottom
118
+
119
+ return [left,right,top,bottom]
120
+ end
121
+
122
+ # Computes the outmost boundaries of the given boundaries.
123
+ # Any NaN in here will happily get ignored.
124
+ def Curve2D.compute_boundaries(bounds)
125
+ left = Dvector.new
126
+ right = Dvector.new
127
+ top = Dvector.new
128
+ bottom = Dvector.new
129
+ bounds.each do |a|
130
+ left.push(a[0])
131
+ right.push(a[1])
132
+ top.push(a[2])
133
+ bottom.push(a[3])
134
+ end
135
+ return [left.min, right.max, top.max, bottom.min]
136
+ end
137
+
138
+ # Creates a path for the given curve. This should be defined
139
+ # with care, as it will be used for instance for region coloring
140
+ # and stroking. The function should only append to the current
141
+ # path, not attempt to create a new path or empty what was done
142
+ # before.
143
+ def make_path(t)
144
+ bnds = Utils::Boundaries.new(parent.effective_bounds)
145
+ if @style.interpolate
146
+ for f in @function.split_monotonic
147
+ new_f = f.bound_values(*bnds.real_bounds)
148
+ t.append_interpolant_to_path(f.make_interpolant)
149
+ end
150
+ else
151
+ f = @function.bound_values(*bnds.real_bounds)
152
+ t.append_points_to_path(f.x, f.y)
153
+ end
154
+ end
155
+
156
+ # Draw the path
157
+ def draw_path(t)
158
+ t.line_width = @style.linewidth if @style.linewidth
159
+ if @style.color && @style.line_style
160
+ t.line_type = @style.line_style
161
+ t.stroke_transparency = @style.transparency || 0
162
+ t.stroke_color = @style.color
163
+ if @line_cap
164
+ t.line_cap = @line_cap
165
+ end
166
+ make_path(t)
167
+ t.stroke
168
+ end
169
+ end
170
+
171
+ def draw_markers(t)
172
+ xs = @function.x
173
+ ys = @function.y
174
+
175
+ if @style.marker
176
+ t.line_type = [[], 0] # Always solid for striking markers
177
+ t.stroke_transparency = @style.marker_transparency || 0
178
+ t.fill_transparency = @style.marker_transparency || 0
179
+ t.show_marker('Xs' => xs, 'Ys' => ys,
180
+ 'marker' => @style.marker,
181
+ 'scale' => @style.marker_scale,
182
+ 'color' => @style.marker_color)
183
+ end
184
+ end
185
+
186
+ # Returns a y value suitable for fills/histograms or other kinds of
187
+ # stuff based on a specification:
188
+ # * false/nil: returns nil
189
+ # * to_y_axis: y = 0
190
+ # * to_bottom: the bottom of the plot
191
+ # * to_top: the top of the plot
192
+ # * "y = ....": the given value.
193
+ def y_value(spec)
194
+ case spec
195
+ when false, nil, :old_style
196
+ return false
197
+ when Float # If that is already a Float, fine !
198
+ return spec
199
+ when :to_y_axis
200
+ return 0.0
201
+ when :to_bottom
202
+ return parent.effective_bounds[3] # bottom
203
+ when :to_top
204
+ return parent.effective_bounds[2] # top
205
+ when /y\s*=\s*(.*)/
206
+ return Float($1)
207
+ else
208
+ warn "Y value #{spec} not understood"
209
+ return false # We don't have anything to do, then.
210
+ end
211
+ end
212
+
213
+ # A function to close the path created by make_path.
214
+ # Overridden in the histogram code.
215
+ def close_path(t, y)
216
+ t.append_point_to_path(@function.x.last, y)
217
+ t.append_point_to_path(@function.x.first, y)
218
+ t.close_path
219
+ end
220
+
221
+ # Draws the filled region according to the :fill_type element
222
+ # of the style pseudo-hash. It can be:
223
+ def draw_fill(t)
224
+ y = y_value(@style.fill_type)
225
+ return unless y
226
+
227
+ t.fill_transparency = @style.fill_transparency || 0
228
+ # Now is the tricky part. To do the actual fill, we first make a
229
+ # path according to the make_path function.
230
+ make_path(t)
231
+
232
+ # Then we add two line segments that go from the end to the
233
+ # beginning.
234
+ close_path(t, y)
235
+
236
+ # Now the path is ready. Just strike -- or, rather, fill !
237
+ t.fill_color = @style.fill_color
238
+ t.fill
239
+ end
240
+
241
+ def plot(t = nil)
242
+ debug "Plotting curve #{inspect}"
243
+ t.context do
244
+
245
+ # The fill is always first
246
+ draw_fill(t)
247
+
248
+ for op in CurveStyle::DrawingOrder[@style[:drawing_order]]
249
+ self.send("draw_#{op}".to_sym, t)
250
+ end
251
+ end
252
+ end
253
+
254
+ # The function that plots error bars.
255
+ def draw_error_bars(t = nil)
256
+ # We first check that we actually need to do anything
257
+ t.context do
258
+ t.stroke_transparency = @style.error_bars_transparency || 0
259
+ if @function.errors.key?(:xmin) or @function.errors.key?(:ymin)
260
+ error_bar = {} # Just create it once, anyway
261
+ error_bar['color'] = @style.error_bar_color
262
+ errors = @function.errors # So we won't have to worry
263
+ # some data should be shared
264
+ @function.errors[:x].each_index do |i|
265
+ error_bar['x'] = errors[:x][i]
266
+ if errors.key?(:xmin) &&
267
+ ((errors[:xmax][i] - errors[:xmin][i]) != 0)
268
+ error_bar['dx_plus'] = errors[:xmax][i] - errors[:x][i]
269
+ error_bar['dx_minus'] = errors[:x][i] - errors[:xmin][i]
270
+ error_bar.delete('dx')
271
+ else
272
+ %w(dx_plus dx_minus).each do |el|
273
+ error_bar.delete(el)
274
+ end
275
+ error_bar['dx'] = 0
276
+ end
277
+ error_bar['y'] = errors[:y][i]
278
+ if errors.key?(:ymin) &&
279
+ ((errors[:ymax][i] - errors[:ymin][i]) != 0)
280
+ error_bar['dy_plus'] = errors[:ymax][i] - errors[:y][i]
281
+ error_bar['dy_minus'] = errors[:y][i] - errors[:ymin][i]
282
+ error_bar.delete('dy')
283
+ else
284
+ %w(dy_plus dy_minus).each do |el|
285
+ error_bar.delete(el)
286
+ end
287
+ error_bar['dy'] = 0
288
+ end
289
+ if (error_bar['dx'] != 0 || error_bar['dy'] != 0)
290
+ t.show_error_bars(error_bar)
291
+ end
292
+ end
293
+ end
294
+ end
295
+ end
296
+
297
+ # plot is the 'real_do' method.
298
+ alias :do :plot
299
+ end
300
+
301
+ # The basic class to create histograms.
302
+ class Histogram2D < Curve2D
303
+
304
+ def initialize(*args)
305
+ super
306
+ @line_cap = LINE_CAP_BUTT
307
+ end
308
+
309
+ # Creates a path for the given curve. This should be defined
310
+ # with care, as it will be used for instance for region coloring
311
+ # and stroking. The function should only append to the current
312
+ # path, not attempt to create a new path or empty what was done
313
+ # before.
314
+ def make_path(t)
315
+ # vectors to store the resulting path
316
+ x_res = Dvector.new
317
+ y_res = Dvector.new
318
+ x_first = 2 * @function.x[0] - @function.x[1]
319
+ y_first = 2 * @function.y[0] - @function.y[1]
320
+ n = @function.size - 1
321
+ x_last = 2 * @function.x[n] - @function.x[n-1]
322
+ y_last = 2 * @function.y[n] - @function.y[n-1]
323
+
324
+ t.make_steps('xs' => @function.x, 'ys' => @function.y,
325
+ 'dest_xs' => x_res, 'dest_ys' => y_res,
326
+ 'x_first' => x_first, 'y_first' => y_first,
327
+ 'x_last' => x_last, 'y_last' => y_last)
328
+ y_base = y_value(@style.hist_type)
329
+ if y_base
330
+ x_prev = y_prev = nil
331
+ # We remove outer elements, not needed.
332
+ x_res.shift
333
+ y_res.shift
334
+ x_res.pop
335
+ y_res.pop
336
+ for x,y in Function.new(x_res, y_res)
337
+ if x_prev
338
+ # We create a path according to the specs
339
+ x_left = (x_prev + (x - x_prev) * @style.hist_left)
340
+ x_right = (x_prev + (x - x_prev) * @style.hist_right)
341
+ x_p = Dvector[x_left, x_right, x_right]
342
+ y_p = Dvector[y,y,y_base]
343
+ t.move_to_point(x_left,y_base)
344
+ t.append_points_to_path(x_p,y_p)
345
+ y_prev = x_prev = nil
346
+ else
347
+ # Accumulate to get a full step.
348
+ x_prev = x
349
+ y_prev = y
350
+ end
351
+ end
352
+ else
353
+ t.append_points_to_path(x_res, y_res)
354
+ end
355
+ end
356
+
357
+ # In the case of histograms with a defined level, we ignore the
358
+ # fill_type setting for the y value, we use the same level.
359
+ def close_path(t,y)
360
+ if y_value(@style.hist_type)
361
+ t.close_path
362
+ else
363
+ super
364
+ end
365
+ end
366
+
367
+ end
368
+ end
@@ -0,0 +1,440 @@
1
+ # tioga_primitives.rb, direct inclusion of graphics primitives in the graphs.
2
+ # copyright (c) 2007, 2008 by Vincent Fourmond:
3
+
4
+ # This program is free software; you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation; either version 2 of the License, or
7
+ # (at your option) any later version.
8
+
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details (in the COPYING file).
13
+
14
+ require 'Dobjects/Dvector'
15
+ require 'CTioga/debug'
16
+ require 'CTioga/log'
17
+ require 'MetaBuilder/parameters'
18
+ require 'shellwords'
19
+
20
+ module CTioga
21
+
22
+ Version::register_svn_info('$Revision: 858 $', '$Date: 2009-01-07 13:39:07 +0100 (Wed, 07 Jan 2009) $')
23
+
24
+ # A class to parse the format of graphics primitives, which is something
25
+ # like:
26
+ # text: 12,34 "nice text" angle=35
27
+ #
28
+ class TiogaPrimitiveMaker
29
+
30
+ extend Debug
31
+ include Log
32
+
33
+ # TODO: add a framed_text graphics primitive.
34
+
35
+ # TODO: primitives should not be funcalls, but elements in their
36
+ # own right.
37
+
38
+ # TODO: provided this is true, we can implement a much more complex
39
+ # mechanism for positioning, that would allow fine placement such
40
+ # as 0+12pt,36-2mm (should be easy to do).
41
+
42
+ # TODO: extend the position pick stuff for other things than tangents,
43
+ # allowing something like @x=0+0,12pt -- @index=12 pr @index=0.45.
44
+ # All these would be relative to the previous curve.
45
+
46
+ # TODO: in the same spirit, allow 'absolute' positioning, such as
47
+ # frame:0.5,0.2 for frame position.
48
+
49
+ # TODO: allow non-clipping elements (that would come in the same context,
50
+ # but after the axes have been drawn, and not clipped
51
+
52
+ # TODO: add a 'pictogram' pseudo-primitive that would enable one to
53
+ # draw the legend pictogram of the last curve used.
54
+
55
+
56
+ # Internal structure to represent the syntax of a primitive call
57
+
58
+ # The symbol for the funcall
59
+ attr_accessor :symbol
60
+ # An array of [name, types] for compulsory arguments
61
+ attr_accessor :compulsory
62
+ # A hash of the optional arguments
63
+ attr_accessor :optional
64
+
65
+ # Creates a TiogaPrimitiveMaker object, from the specifications
66
+ def initialize(symb, comp, opt)
67
+ @symbol = symb
68
+ @compulsory = comp
69
+ @optional = opt
70
+ end
71
+
72
+ # This function is fed with an array of Strings. The example in
73
+ # TiogaPrimitiveMaker would look like
74
+ # ["12,34", "nice text", "angle=35"]
75
+ # It returns the hash that can be fed to the appropriate
76
+ # Tioga primitive. It is fed the plot
77
+ def parse_args(args)
78
+ ret = {}
79
+ for name, type_spec in @compulsory
80
+ type_spec ||= {:type => :string} # Absent means string
81
+ type = MetaBuilder::ParameterType.get_type(type_spec)
82
+ val = type.string_to_type(args.shift)
83
+ ret[name] = val
84
+ end
85
+ # We now parse the rest of the arguments:
86
+ for opt in args
87
+ if opt =~ /^([^=]+)=(.*)/
88
+ name = $1
89
+ arg = $2
90
+ type_spec = @optional[name] || {:type => :string}
91
+ type = MetaBuilder::ParameterType.get_type(type_spec)
92
+ val = type.string_to_type(arg)
93
+ ret[name] = val
94
+ else
95
+ warn "Malformed optional argument #{opt}"
96
+ end
97
+ end
98
+ return ret
99
+ end
100
+
101
+ # Turns a graphics spefication into a Tioga Funcall.
102
+ def make_funcall(args, plotmaker)
103
+ dict = parse_args(args)
104
+ return TiogaFuncall.new(@symbol, dict)
105
+ end
106
+
107
+ # A few predefined types to make it all more readable -
108
+ # and easier to maintain !!
109
+ FloatArray = {:type => :array, :subtype => :float}
110
+ Point = FloatArray
111
+ Boolean = {:type => :boolean}
112
+ Marker = { # Damn useful !!
113
+ :type => :array,
114
+ :subtype => {:type => :integer, :namespace => Tioga::MarkerConstants},
115
+ :namespace => Tioga::MarkerConstants,
116
+ :shortcuts => {'No' => 'None', 'None' => 'None',
117
+ 'no' => 'None', 'none' => 'None',
118
+ } # For no/none to work fine
119
+ }
120
+ Number = {:type => :float}
121
+ Color = {
122
+ :type => :array, :subtype => :float,
123
+ :namespace => Tioga::ColorConstants
124
+ }
125
+ # Very bad definition, but, well...
126
+ LineStyle = {:type => :array, :subtype => :integer,
127
+ :namespace => Styles,
128
+ }
129
+
130
+ Justification = {:type => :integer,
131
+ :shortcuts => {
132
+ 'Left' => Tioga::FigureConstants::LEFT_JUSTIFIED,
133
+ 'left' => Tioga::FigureConstants::LEFT_JUSTIFIED,
134
+ 'l' => Tioga::FigureConstants::LEFT_JUSTIFIED,
135
+ 'Center' => Tioga::FigureConstants::CENTERED,
136
+ 'center' => Tioga::FigureConstants::CENTERED,
137
+ 'c' => Tioga::FigureConstants::CENTERED,
138
+ 'Right' => Tioga::FigureConstants::RIGHT_JUSTIFIED,
139
+ 'right' => Tioga::FigureConstants::RIGHT_JUSTIFIED,
140
+ 'r' => Tioga::FigureConstants::RIGHT_JUSTIFIED,
141
+ }
142
+ }
143
+
144
+ Alignment = {:type => :integer,
145
+ :shortcuts => {
146
+ 'Top' => Tioga::FigureConstants::ALIGNED_AT_TOP,
147
+ 'top' => Tioga::FigureConstants::ALIGNED_AT_TOP,
148
+ 't' => Tioga::FigureConstants::ALIGNED_AT_TOP,
149
+ 'Mid' => Tioga::FigureConstants::ALIGNED_AT_MIDHEIGHT,
150
+ 'mid' => Tioga::FigureConstants::ALIGNED_AT_MIDHEIGHT,
151
+ 'Midheight' => Tioga::FigureConstants::ALIGNED_AT_MIDHEIGHT,
152
+ 'midheight' => Tioga::FigureConstants::ALIGNED_AT_MIDHEIGHT,
153
+ 'm' => Tioga::FigureConstants::ALIGNED_AT_MIDHEIGHT,
154
+ # Take center too !
155
+ 'Center' => Tioga::FigureConstants::ALIGNED_AT_MIDHEIGHT,
156
+ 'center' => Tioga::FigureConstants::ALIGNED_AT_MIDHEIGHT,
157
+ 'c' => Tioga::FigureConstants::ALIGNED_AT_MIDHEIGHT,
158
+ 'Base' => Tioga::FigureConstants::ALIGNED_AT_BASELINE,
159
+ 'base' => Tioga::FigureConstants::ALIGNED_AT_BASELINE,
160
+ 'B' => Tioga::FigureConstants::ALIGNED_AT_BASELINE,
161
+ 'Bottom' => Tioga::FigureConstants::ALIGNED_AT_BOTTOM,
162
+ 'bottom' => Tioga::FigureConstants::ALIGNED_AT_BOTTOM,
163
+ 'b' => Tioga::FigureConstants::ALIGNED_AT_BOTTOM,
164
+ }
165
+ }
166
+
167
+ # Available primitives:
168
+ PRIMITIVES = {
169
+ "text" => self.new(:show_text,
170
+ [
171
+ ['point', Point],
172
+ ['text']
173
+ ],
174
+ {
175
+ 'angle' => Number,
176
+ 'color' => Color,
177
+ 'scale' => Number,
178
+ 'justification' => Justification,
179
+ 'alignment' => Alignment,
180
+ }),
181
+ "arrow" => self.new(:show_arrow,
182
+ [
183
+ ['tail', Point ],
184
+ ['head', Point ],
185
+ ],
186
+ {
187
+ 'head_marker' => Marker,
188
+ 'tail_marker' => Marker,
189
+ 'head_scale' => Number,
190
+ 'tail_scale' => Number,
191
+ 'line_width' => Number,
192
+ 'line_style' => LineStyle,
193
+ 'color' => Color,
194
+ }),
195
+ "marker" => self.new(:show_marker,
196
+ [
197
+ ['point', Point ],
198
+ ['marker', Marker ],
199
+ ],
200
+ {
201
+ 'color' => Color,
202
+ 'angle' => Number,
203
+ 'scale' => Number,
204
+ }),
205
+ }
206
+
207
+
208
+ # Parse a specification such as
209
+ # text: 12,34 "nice text" angle=35
210
+ # plotmaker is a pointer to the plotmaker instance.
211
+ def self.parse_spec(spec, plotmaker)
212
+ spec =~ /^([^:]+):(.*)/
213
+ name = $1
214
+ args = Shellwords.shellwords($2)
215
+ if PRIMITIVES.key? name
216
+ ret = PRIMITIVES[name].make_funcall(args, plotmaker)
217
+ debug "draw: #{name} -> #{ret.inspect}"
218
+ else
219
+ error "Unkown graphic primitive: #{name}"
220
+ end
221
+ ret
222
+ end
223
+
224
+ # Returns a small descriptive text about the currently known
225
+ # graphics primitives.
226
+ def self.introspect(details = true)
227
+ str = ""
228
+ for name, spec in PRIMITIVES
229
+ str += "\t#{name}: " +
230
+ spec.compulsory.map do |a|
231
+ a[0]
232
+ end. join(' ') +
233
+ if details
234
+ "\n\t\toptions: #{spec.optional.keys.join(',')}\n\t\t"
235
+ else
236
+ " [options]\t "
237
+ end + "see Tioga function #{spec.symbol}\n"
238
+ end
239
+ return str
240
+ end
241
+ end
242
+
243
+ class TangentSemiPrimitive < TiogaPrimitiveMaker
244
+ TiogaPrimitiveMaker::PRIMITIVES["tangent"] =
245
+ self.new(:show_arrow,
246
+ [
247
+ ['spec', :string],
248
+ ],
249
+ {
250
+ 'xextent' => FloatArray,
251
+ 'yextent' => FloatArray,
252
+ 'xuntil' => FloatArray,
253
+ 'yuntil' => FloatArray,
254
+ }.merge(TiogaPrimitiveMaker::PRIMITIVES['arrow'].optional)
255
+ # We automatically add stuff from the arrow specs,
256
+ # as they share a lot of keys...
257
+ )
258
+
259
+ def make_funcall(args, plotmaker)
260
+ dict = parse_args(args)
261
+ curve = plotmaker.last_curve
262
+ raise 'The tangent drawing command needs to be specified *after* the curve it applies to' unless curve
263
+ # We now need to transform
264
+ index = curve.parse_position(dict["spec"])
265
+ dict.delete('spec')
266
+ debug "Tangent point index is #{index}"
267
+ tangent = plotmaker.last_curve.tangent(index)
268
+ debug "Tangent slope is #{tangent}"
269
+ point = curve.function.point(index)
270
+ debug "Tangent point is #{point}"
271
+
272
+ # We are now preparing the coordinates of the arrow:
273
+ # * if markonly is on, it doesn't matter much
274
+ # * if xextent, we take the array as X extents in either
275
+ # direction
276
+ # * the same obviously applies for yextent.
277
+ # Only one spec can be used at a time
278
+ if dict['xextent']
279
+ fact = dict['xextent'][0]/tangent[0]
280
+ dict['head'] = point + (tangent * fact)
281
+ fact = (dict['xextent'][1] || 0.0)/tangent[0]
282
+ dict['tail'] = point - (tangent * fact)
283
+ elsif dict['yextent']
284
+ fact = dict['yextent'][0]/tangent[1]
285
+ dict['head'] = point + (tangent * fact)
286
+ fact = (dict['yextent'][1] || 0.0)/tangent[1]
287
+ dict['tail'] = point - (tangent * fact)
288
+ # We prolong the tangent until it intersects the given
289
+ # X= or Y= positions
290
+ elsif dict['xuntil']
291
+ fact = (dict['xuntil'][0] - point[0])/tangent[0]
292
+ dict['head'] = point + (tangent * fact)
293
+ if dict['xuntil'][1]
294
+ fact = (dict['xuntil'][1] - point[0])/tangent[0]
295
+ else
296
+ fact = 0
297
+ end
298
+ dict['tail'] = point + (tangent * fact)
299
+ elsif dict['yuntil']
300
+ fact = (dict['yuntil'][0] - point[1])/tangent[1]
301
+ dict['head'] = point + (tangent * fact)
302
+ if dict['yuntil'][1]
303
+ fact = (dict['yuntil'][1] - point[1])/tangent[1]
304
+ else
305
+ fact = 0
306
+ end
307
+ dict['tail'] = point + (tangent * fact)
308
+ else
309
+ dict['line_width'] = 0
310
+ dict['head'] = point
311
+ dict['tail'] = point - tangent
312
+ end
313
+ # Remove unnecessary keys...
314
+ %w(spec xextent yextent xuntil yuntil).map {|k| dict.delete(k)}
315
+
316
+ # We setup other defaults than the usual ones, from
317
+ # the current curve.
318
+ dict['color'] ||= curve.style.color # color from curve
319
+ dict['line_width'] ||= curve.style.linewidth
320
+
321
+ dict['tail_marker'] ||= 'None' # No tail by default.
322
+
323
+ debug "Tangent coordinates: #{dict['head']} -> #{dict['tail']}"
324
+
325
+
326
+ # And we return the Funcall...
327
+ return TiogaFuncall.new(@symbol, dict)
328
+ end
329
+ end
330
+
331
+ # A ruler
332
+ class RulerSemiPrimitive < TiogaPrimitiveMaker
333
+ TiogaPrimitiveMaker::PRIMITIVES["vrule"] =
334
+ self.new(:vrule,
335
+ [
336
+ ['point', Point],
337
+ ['size', :float],
338
+ ],
339
+ {
340
+ 'label' => :string,
341
+ }.merge(TiogaPrimitiveMaker::PRIMITIVES['arrow'].optional)
342
+ )
343
+
344
+ TiogaPrimitiveMaker::PRIMITIVES["hrule"] =
345
+ self.new(:hrule,
346
+ [
347
+ ['point', Point],
348
+ ['size', :float],
349
+ ],
350
+ {
351
+ 'label' => :string,
352
+ }.merge(TiogaPrimitiveMaker::PRIMITIVES['arrow'].optional)
353
+ )
354
+
355
+ include Tioga::FigureConstants
356
+
357
+ def make_funcall(args, plotmaker)
358
+ dict = parse_args(args)
359
+
360
+ ret = []
361
+
362
+ line_dict = dict.dup
363
+ line_dict['head_marker'] ||= Tioga::FigureConstants::BarThin
364
+ line_dict['tail_marker'] ||= Tioga::FigureConstants::BarThin
365
+
366
+ line_dict['tail_scale'] ||= 0.5
367
+ line_dict['head_scale'] ||= 0.5
368
+
369
+
370
+ line_dict['tail'] = dict['point']
371
+ # We start from point and go on
372
+ line_dict['head'] = dict['point'].dup
373
+ if @symbol == :vrule
374
+ line_dict['head'][1] += dict['size']
375
+ else
376
+ line_dict['head'][0] += dict['size']
377
+ end
378
+
379
+ for k in %w(label size point)
380
+ line_dict.delete k
381
+ end
382
+
383
+ ret << TiogaFuncall.new(:show_arrow, line_dict)
384
+
385
+ # For the label, we tweak the aligment
386
+ if dict.key? 'label'
387
+ label = {}
388
+ label['at'] = (Dobjects::Dvector.new(line_dict['head']) +
389
+ Dobjects::Dvector.new(line_dict['tail'])) * 0.5
390
+ label['text'] = dict['label']
391
+ # We place the label on the left/bottom
392
+ if @symbol == :vrule
393
+ label['justification'] = RIGHT_JUSTIFIED
394
+ label['alignment'] = ALIGNED_AT_MIDHEIGHT
395
+ else
396
+ label['justification'] = CENTERED
397
+ label['alignment'] = ALIGNED_AT_TOP
398
+ end
399
+ ret << TiogaFuncall.new(:show_text, label)
400
+ end
401
+
402
+
403
+ # And we return the Funcall(s)...
404
+ return ret
405
+ end
406
+ end
407
+
408
+ # A line
409
+ class LineSemiPrimitive < TiogaPrimitiveMaker
410
+
411
+ TiogaPrimitiveMaker::PRIMITIVES["line"] =
412
+ self.new(:show_arrow,
413
+ [
414
+ ['tail', Point ],
415
+ ['head', Point ],
416
+ ],
417
+ TiogaPrimitiveMaker::PRIMITIVES['arrow'].optional
418
+ )
419
+
420
+
421
+ include Tioga::FigureConstants
422
+
423
+ def make_funcall(args, plotmaker)
424
+ dict = parse_args(args)
425
+
426
+ ret = []
427
+
428
+ line_dict = dict.dup
429
+ line_dict['head_marker'] ||= "None"
430
+ line_dict['tail_marker'] ||= "None"
431
+
432
+ ret << TiogaFuncall.new(:show_arrow, line_dict)
433
+
434
+ # And we return the Funcall(s)...
435
+ return ret
436
+ end
437
+
438
+ end
439
+
440
+ end