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.
- 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
|