ctioga 1.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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