ctioga 1.11.1
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +340 -0
- data/ctioga/bin/ctable +28 -0
- data/ctioga/bin/ctioga +37 -0
- data/ctioga/doc/ctable.1 +156 -0
- data/ctioga/doc/ctioga.1 +2363 -0
- data/ctioga/examples/README +46 -0
- data/ctioga/examples/ctioga.gnuplot +4 -0
- data/ctioga/examples/ctioga_within_tioga.rb +53 -0
- data/ctioga/examples/ctiogarc.rb +24 -0
- data/ctioga/examples/include_1.rb +15 -0
- data/ctioga/examples/noise.dat +100 -0
- data/ctioga/examples/noise.rb +13 -0
- data/ctioga/examples/trig.csv +100 -0
- data/ctioga/examples/trig.dat +100 -0
- data/ctioga/examples/trig.rb +14 -0
- data/ctioga/examples/trigh.dat +100 -0
- data/ctioga/examples/trigh.rb +10 -0
- data/ctioga/examples/tutorial +763 -0
- data/ctioga/examples/tutorial.sh +269 -0
- data/ctioga/tests/README +14 -0
- data/ctioga/tests/axes.sh +40 -0
- data/ctioga/tests/basic.sh +11 -0
- data/ctioga/tests/draw.sh +24 -0
- data/ctioga/tests/histograms.sh +14 -0
- data/ctioga/tests/insets.sh +41 -0
- data/ctioga/tests/layouts.sh +29 -0
- data/ctioga/tests/legends.sh +113 -0
- data/ctioga/tests/styles.sh +43 -0
- data/ctioga/tests/test_style.sh +8 -0
- data/ctioga/tests/tests.sh +24 -0
- data/ctioga/tests/text_backend.sh +83 -0
- data/ctioga/tests/tioga_defaults.rb +18 -0
- data/lib/CTioga/axes.rb +904 -0
- data/lib/CTioga/backends.rb +88 -0
- data/lib/CTioga/boundaries.rb +224 -0
- data/lib/CTioga/ctable.rb +134 -0
- data/lib/CTioga/curve_style.rb +246 -0
- data/lib/CTioga/debug.rb +199 -0
- data/lib/CTioga/dimension.rb +133 -0
- data/lib/CTioga/elements.rb +17 -0
- data/lib/CTioga/elements/base.rb +84 -0
- data/lib/CTioga/elements/containers.rb +578 -0
- data/lib/CTioga/elements/curves.rb +368 -0
- data/lib/CTioga/elements/tioga_primitives.rb +440 -0
- data/lib/CTioga/layout.rb +595 -0
- data/lib/CTioga/legends.rb +29 -0
- data/lib/CTioga/legends/cmdline.rb +187 -0
- data/lib/CTioga/legends/item.rb +164 -0
- data/lib/CTioga/legends/style.rb +257 -0
- data/lib/CTioga/log.rb +73 -0
- data/lib/CTioga/movingarrays.rb +131 -0
- data/lib/CTioga/partition.rb +271 -0
- data/lib/CTioga/plot_style.rb +230 -0
- data/lib/CTioga/plotmaker.rb +1677 -0
- data/lib/CTioga/shortcuts.rb +69 -0
- data/lib/CTioga/structures.rb +82 -0
- data/lib/CTioga/styles.rb +140 -0
- data/lib/CTioga/themes.rb +581 -0
- data/lib/CTioga/themes/classical.rb +82 -0
- data/lib/CTioga/themes/demo.rb +63 -0
- data/lib/CTioga/themes/fits.rb +91 -0
- data/lib/CTioga/themes/mono.rb +33 -0
- data/lib/CTioga/tioga.rb +32 -0
- data/lib/CTioga/utils.rb +173 -0
- data/lib/MetaBuilder/Parameters/dates.rb +38 -0
- data/lib/MetaBuilder/Parameters/lists.rb +132 -0
- data/lib/MetaBuilder/Parameters/numbers.rb +69 -0
- data/lib/MetaBuilder/Parameters/strings.rb +86 -0
- data/lib/MetaBuilder/Parameters/styles.rb +75 -0
- data/lib/MetaBuilder/Qt4/Parameters/dates.rb +51 -0
- data/lib/MetaBuilder/Qt4/Parameters/numbers.rb +65 -0
- data/lib/MetaBuilder/Qt4/Parameters/strings.rb +106 -0
- data/lib/MetaBuilder/Qt4/parameter.rb +172 -0
- data/lib/MetaBuilder/Qt4/parameters.rb +9 -0
- data/lib/MetaBuilder/descriptions.rb +603 -0
- data/lib/MetaBuilder/factory.rb +101 -0
- data/lib/MetaBuilder/group.rb +57 -0
- data/lib/MetaBuilder/metabuilder.rb +10 -0
- data/lib/MetaBuilder/parameter.rb +374 -0
- data/lib/MetaBuilder/parameters.rb +11 -0
- data/lib/MetaBuilder/qt4.rb +8 -0
- data/lib/SciYAG/Backends/backend.rb +379 -0
- data/lib/SciYAG/Backends/binner.rb +168 -0
- data/lib/SciYAG/Backends/cache.rb +102 -0
- data/lib/SciYAG/Backends/dataset.rb +158 -0
- data/lib/SciYAG/Backends/descriptions.rb +469 -0
- data/lib/SciYAG/Backends/filters.rb +25 -0
- data/lib/SciYAG/Backends/filters/average.rb +134 -0
- data/lib/SciYAG/Backends/filters/cumulate.rb +37 -0
- data/lib/SciYAG/Backends/filters/filter.rb +70 -0
- data/lib/SciYAG/Backends/filters/norm.rb +39 -0
- data/lib/SciYAG/Backends/filters/smooth.rb +63 -0
- data/lib/SciYAG/Backends/filters/sort.rb +43 -0
- data/lib/SciYAG/Backends/filters/strip.rb +34 -0
- data/lib/SciYAG/Backends/filters/trim.rb +64 -0
- data/lib/SciYAG/Backends/gnuplot.rb +131 -0
- data/lib/SciYAG/Backends/math.rb +108 -0
- data/lib/SciYAG/Backends/mdb.rb +462 -0
- data/lib/SciYAG/Backends/multitext.rb +96 -0
- data/lib/SciYAG/Backends/source.rb +64 -0
- data/lib/SciYAG/Backends/text.rb +339 -0
- data/lib/SciYAG/backends.rb +16 -0
- 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
|