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,230 @@
|
|
1
|
+
# plot_style.rb : an abstraction for styles of whole plots
|
2
|
+
# Copyright (C) 2008 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.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program; if not, write to the Free Software
|
16
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
17
|
+
|
18
|
+
require 'CTioga/utils'
|
19
|
+
require 'CTioga/debug'
|
20
|
+
require 'CTioga/log'
|
21
|
+
require 'CTioga/axes'
|
22
|
+
require 'CTioga/legends'
|
23
|
+
|
24
|
+
module CTioga
|
25
|
+
|
26
|
+
Version::register_svn_info('$Revision: 966 $', '$Date: 2009-06-02 19:02:50 +0200 (Tue, 02 Jun 2009) $')
|
27
|
+
|
28
|
+
# The PlotStyle class is an abstraction for whole-plot styles,
|
29
|
+
# storing for instance information about axes/ticks (delegated
|
30
|
+
# to an EdgesAndAxes object), background color, and so on...
|
31
|
+
class PlotStyle
|
32
|
+
|
33
|
+
include Debug
|
34
|
+
include Log
|
35
|
+
|
36
|
+
# An EdgesAndAxes object attached to the plot
|
37
|
+
attr_accessor :edges
|
38
|
+
|
39
|
+
# Various textual objects laying around:
|
40
|
+
attr_accessor :title, :xlabel, :ylabel
|
41
|
+
|
42
|
+
# X and Y tick labels
|
43
|
+
attr_accessor :xticks, :yticks
|
44
|
+
|
45
|
+
# The target SubPlot object
|
46
|
+
attr_accessor :target_plot
|
47
|
+
|
48
|
+
# The background color, if applicable
|
49
|
+
attr_accessor :background_color
|
50
|
+
|
51
|
+
# The style with which to display legends
|
52
|
+
attr_accessor :legend_style
|
53
|
+
|
54
|
+
# A watermark in the background
|
55
|
+
attr_accessor :watermark_text
|
56
|
+
|
57
|
+
# The color of the watermark
|
58
|
+
attr_accessor :watermark_color
|
59
|
+
|
60
|
+
# The scale of the watermark
|
61
|
+
attr_accessor :watermark_scale
|
62
|
+
|
63
|
+
def initialize(target_plot = nil, subplot = false)
|
64
|
+
@title = Label.new(:title)
|
65
|
+
@title.label = "A nice plot" unless subplot
|
66
|
+
|
67
|
+
@xlabel = Label.new(:xlabel)
|
68
|
+
@xlabel.label = "$x$" unless subplot
|
69
|
+
|
70
|
+
@ylabel = Label.new(:ylabel)
|
71
|
+
@ylabel.label = "$y$" unless subplot
|
72
|
+
|
73
|
+
@xticks = TickLabels.new(:xaxis_numeric_label)
|
74
|
+
@yticks = TickLabels.new(:yaxis_numeric_label)
|
75
|
+
|
76
|
+
@edges = EdgesAndAxes.new(@xticks, @yticks)
|
77
|
+
|
78
|
+
@target_plot = target_plot
|
79
|
+
|
80
|
+
@background_color = false
|
81
|
+
|
82
|
+
@legend_style = LegendStyle.new
|
83
|
+
|
84
|
+
@watermark_scale = 0.15
|
85
|
+
end
|
86
|
+
|
87
|
+
# Displays edges and ticks for the given object
|
88
|
+
def show_edges(t, container = @target_plot)
|
89
|
+
@edges.setup(t, container)
|
90
|
+
for l in [@xticks, @yticks]
|
91
|
+
l.show(t)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Sets up the various parameters for titles and labels
|
96
|
+
def show_labels(t, container = @target_plot)
|
97
|
+
# Show labels
|
98
|
+
for l in [@title, @xlabel, @ylabel]
|
99
|
+
l.show(t)
|
100
|
+
debug "Extension -> #{l.extension(t).inspect}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Displays background (background color, grids)
|
105
|
+
def show_background(t, container = @target_plot)
|
106
|
+
if @background_color
|
107
|
+
t.fill_color = @background_color
|
108
|
+
t.fill_frame
|
109
|
+
end
|
110
|
+
|
111
|
+
# We draw a watermark text at the back of the plot.
|
112
|
+
if @watermark_text
|
113
|
+
x = t.convert_frame_to_figure_x(0.5)
|
114
|
+
y = t.convert_frame_to_figure_y(0.5)
|
115
|
+
|
116
|
+
delta_y = t.convert_frame_to_figure_dy(@watermark_scale)
|
117
|
+
text_scale = delta_y/t.default_text_height_dy
|
118
|
+
|
119
|
+
lines = @watermark_text.split(/\n|\\n/)
|
120
|
+
i = + (lines.size-1)/2.0
|
121
|
+
for text in lines
|
122
|
+
t.show_marker('string' => text,
|
123
|
+
'color' => @watermark_color || [0.5,0.5,0.5],
|
124
|
+
'x' => x, 'y' => y + delta_y * i,
|
125
|
+
'scale' => text_scale)
|
126
|
+
i -= 1
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
edges.show_axis_lines(t, container)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Hides axis and all edges for the given sides.
|
134
|
+
# Careful, as this also disables the children's axes and edges
|
135
|
+
def disable_all_axis_and_edges(*which)
|
136
|
+
for w in which
|
137
|
+
@edges.set_edges_visibility(w, false)
|
138
|
+
@edges.axis(w).visible = false
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
# Sets quickly all X and Y labels
|
144
|
+
def set_xy_labels(xlabel, ylabel)
|
145
|
+
@xlabel.label = xlabel
|
146
|
+
@ylabel.label = ylabel
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
def hide_axis_and_edges(*which)
|
151
|
+
for w in which
|
152
|
+
@edges.set_axis_and_edges_style(w, AXIS_HIDDEN)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
# TODO (IMPORTANT !): simplify the notion of edges and axes: the user
|
158
|
+
# shouldn't need to know the difference between edges and axes.
|
159
|
+
|
160
|
+
|
161
|
+
# A very nice-and-convenient way to set quickly axes properties.
|
162
|
+
# _which_ is either :x or :y, *or* :left, :top, :right, and :bottom,
|
163
|
+
# in which case the style applies only to the given edge or axis
|
164
|
+
#
|
165
|
+
def set_axis_style(which, style)
|
166
|
+
# We set various parameters according to the given style
|
167
|
+
for s in style.split(/,/)
|
168
|
+
case s
|
169
|
+
when /none/i
|
170
|
+
hide_axis_and_edges(which)
|
171
|
+
when /[xy]?=?0/i, /ori?g(in)?/i
|
172
|
+
@edges.set_edges_visibility(which, false)
|
173
|
+
@edges.axis(which).ticks_inside = false
|
174
|
+
@edges.axis(which).ticks_outside = true
|
175
|
+
@edges.axis(which).loc = (which == :x ? AT_Y_ORIGIN : AT_X_ORIGIN)
|
176
|
+
when /both/ # Both sides visible
|
177
|
+
# We make sure the edges are visible
|
178
|
+
@edges.set_edges_visibility(which, true)
|
179
|
+
when /left/i, /right/i
|
180
|
+
if which == :x
|
181
|
+
warn "Axis style #{s} can only apply to the Y axis, ignoring"
|
182
|
+
else
|
183
|
+
@edges.set_edges_visibility(which, false)
|
184
|
+
@edges.axis(which).loc = ( s =~ /left/i ? LEFT : RIGHT)
|
185
|
+
end
|
186
|
+
when /top/i, /bottom/i
|
187
|
+
if which == :y
|
188
|
+
warn "Axis style #{s} can only apply to the X axis, ignoring"
|
189
|
+
else
|
190
|
+
@edges.set_edges_visibility(which, false)
|
191
|
+
@edges.axis(which).loc = ( s =~ /top/i ? TOP : BOTTOM)
|
192
|
+
end
|
193
|
+
# Now, stylistic information rather than position:
|
194
|
+
when /hidden/i
|
195
|
+
@edges.set_axis_and_edges_style(which, AXIS_HIDDEN)
|
196
|
+
when /line/i
|
197
|
+
@edges.set_axis_and_edges_style(which, AXIS_LINE_ONLY)
|
198
|
+
when /ticks/i
|
199
|
+
@edges.set_axis_and_edges_style(which, AXIS_WITH_TICKS_ONLY)
|
200
|
+
when /majornum/i
|
201
|
+
@edges.
|
202
|
+
set_axis_and_edges_style(which,
|
203
|
+
AXIS_WITH_MAJOR_TICKS_AND_NUMERIC_LABELS)
|
204
|
+
when /major/i
|
205
|
+
@edges.set_axis_and_edges_style(which, AXIS_WITH_MAJOR_TICKS_ONLY)
|
206
|
+
when /full/i
|
207
|
+
@edges.set_axis_and_edges_style(which,
|
208
|
+
AXIS_WITH_TICKS_AND_NUMERIC_LABELS)
|
209
|
+
else
|
210
|
+
warn "Axis style #{s} not understood, ignoring"
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# Creates a deep copy of the style object, and give it a new
|
216
|
+
# container.
|
217
|
+
def deep_copy(new_target = nil)
|
218
|
+
old_target = @target_plot
|
219
|
+
@target_plot = nil
|
220
|
+
new_object = Marshal::load(Marshal::dump(self))
|
221
|
+
new_object.target_plot = new_target
|
222
|
+
@target_plot = old_target
|
223
|
+
return new_object
|
224
|
+
end
|
225
|
+
|
226
|
+
|
227
|
+
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|
@@ -0,0 +1,1677 @@
|
|
1
|
+
# plotmaker.rb: The main class for making plots
|
2
|
+
# copyright (c) 2006, 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
|
+
|
15
|
+
# TODO, the main one:
|
16
|
+
#
|
17
|
+
# It currently is a pain to make complex plots with ctioga. A real
|
18
|
+
# pain. What could be done to improve the situation ?
|
19
|
+
#
|
20
|
+
# * hide the difference between edges and axes.
|
21
|
+
# * the layout mechanism is not comfortable enough to work with, especially
|
22
|
+
# with the need for relative positioning.
|
23
|
+
#
|
24
|
+
# Would it be possible to allow for the 'real size' to be determined
|
25
|
+
# *afterwards* ?
|
26
|
+
|
27
|
+
# TODO, an even bigger one:
|
28
|
+
# Switch to a real command-based plotting program:
|
29
|
+
# - any single operation that is realized by ctioga would be a command
|
30
|
+
# - every single of these commands would take a given (fixed) number of
|
31
|
+
# parameters (we should take care about boolean stuff)
|
32
|
+
# - every command would be of course reachable as command-line options
|
33
|
+
# but it could also be within files
|
34
|
+
# - in these files, provide an additional mechanism for quickly defining
|
35
|
+
# variables and do variable substitution.
|
36
|
+
# - one command (plus arguments) per line, with provisions for
|
37
|
+
# line-splitting
|
38
|
+
# - allow some kind of 'include' directives (that would also be used for
|
39
|
+
# cmdline inclusion of files)
|
40
|
+
# - command-line arguments and command files could intermix (that *would*
|
41
|
+
# be fun, since it would allow very little changes to a command-line
|
42
|
+
# to change significantly the look of a file...!)
|
43
|
+
# - command files would be specified using @file ?
|
44
|
+
# - in the absence of --name commands, output would go to the named file ?
|
45
|
+
# - LONG TERM: allow conditionals and variable
|
46
|
+
# definition/substitution on command-line ?
|
47
|
+
#
|
48
|
+
# Each command could take *typed* arguments. That would allow typed
|
49
|
+
# variables along with a string-to-type conversion ? (is that useful ?)
|
50
|
+
#
|
51
|
+
# Provide *optional* hash-like arguments that probably could not be used
|
52
|
+
# in the command-line, but could be in the file.
|
53
|
+
#
|
54
|
+
# Provide self-documentation in each and every command
|
55
|
+
#
|
56
|
+
# Manipulations of a buffer stack - including mathematical
|
57
|
+
# expressions; provide commands to only *load* a file, but not
|
58
|
+
# necessarily import it.
|
59
|
+
#
|
60
|
+
# Provide a way to 'save' a command-line into a command-file.
|
61
|
+
#
|
62
|
+
# Write as many test suites as possible ??
|
63
|
+
#
|
64
|
+
# Merge Metabuilder and Backends into the ctioga code base. There's
|
65
|
+
# no need for extra complexity.
|
66
|
+
#
|
67
|
+
# That requires a huge amount of work, but on the other hand, that
|
68
|
+
# would be much more satisfactory than the current mess.
|
69
|
+
#
|
70
|
+
# Commands would be part of "groups".
|
71
|
+
#
|
72
|
+
# Release a new version of ctioga before that.
|
73
|
+
#
|
74
|
+
# Don't rely on huge mess of things !
|
75
|
+
#
|
76
|
+
# Then, I could have some small fun and write a
|
77
|
+
|
78
|
+
|
79
|
+
|
80
|
+
# Very important for command-line parsing
|
81
|
+
require 'optparse'
|
82
|
+
require 'Tioga/tioga'
|
83
|
+
require 'Tioga/Utils'
|
84
|
+
|
85
|
+
# Information about style
|
86
|
+
require 'CTioga/styles'
|
87
|
+
# Moving arrays
|
88
|
+
# require 'CTioga/movingarrays'
|
89
|
+
# And, most important of all, elements
|
90
|
+
require 'CTioga/elements'
|
91
|
+
|
92
|
+
# The debugging facility
|
93
|
+
require 'CTioga/debug'
|
94
|
+
require 'CTioga/log'
|
95
|
+
require 'CTioga/utils'
|
96
|
+
require 'CTioga/layout'
|
97
|
+
|
98
|
+
|
99
|
+
# The backends
|
100
|
+
require 'CTioga/backends'
|
101
|
+
|
102
|
+
# Support for themes
|
103
|
+
require 'CTioga/themes'
|
104
|
+
require 'CTioga/axes'
|
105
|
+
require 'CTioga/structures'
|
106
|
+
|
107
|
+
# Legends
|
108
|
+
require 'CTioga/legends'
|
109
|
+
|
110
|
+
|
111
|
+
# for interpreting the CTIOGA environment variable
|
112
|
+
require 'shellwords'
|
113
|
+
require 'MetaBuilder/metabuilder'
|
114
|
+
|
115
|
+
|
116
|
+
module CTioga
|
117
|
+
|
118
|
+
Version::register_svn_info('$Revision: 966 $', '$Date: 2009-06-02 19:02:50 +0200 (Tue, 02 Jun 2009) $')
|
119
|
+
|
120
|
+
|
121
|
+
|
122
|
+
# The regular expression saying that what we see on the command-line
|
123
|
+
# means "use default value".
|
124
|
+
DEFAULT_RE = /^\s*(auto|default)\s*$/i
|
125
|
+
|
126
|
+
# The regular expression saying that what we see on the command-line
|
127
|
+
# means "disable".
|
128
|
+
DISABLE_RE = /^\s*(no(ne)?|off)\s*$/i
|
129
|
+
|
130
|
+
# The regular expression saying that what we see on the command-line
|
131
|
+
# means "true".
|
132
|
+
TRUE_RE = /^\s*(true|yes|on)\s*$/i
|
133
|
+
|
134
|
+
# A small function to help quoting a string for inclusion
|
135
|
+
# in a pdfTeX primitive.
|
136
|
+
def self.pdftex_quote_string(str)
|
137
|
+
return str.gsub(/([%#])|([()])|([{}~_^])|\\/) do
|
138
|
+
if $1
|
139
|
+
"\\#{$1}"
|
140
|
+
elsif $2 # Quoting (), as they can be quite nasty !!
|
141
|
+
"\\string\\#{$2}"
|
142
|
+
elsif $3
|
143
|
+
"\\string#{$3}"
|
144
|
+
else # Quoting \
|
145
|
+
"\\string\\\\"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
# This class is responsible for reading the command-line, via it's parse
|
152
|
+
# function, and to actually turn it into nice Tioga commands to make
|
153
|
+
# even nicer graphes.
|
154
|
+
class PlotMaker
|
155
|
+
|
156
|
+
# PlotMaker is now handled (at least partially) by MetaBuilder.
|
157
|
+
# That should save a lot of code.
|
158
|
+
|
159
|
+
include MetaBuilder::DescriptionInclude
|
160
|
+
extend MetaBuilder::DescriptionExtend
|
161
|
+
|
162
|
+
describe 'test', "A test class", <<EOD
|
163
|
+
A class to test visually the effects of different stuff
|
164
|
+
EOD
|
165
|
+
|
166
|
+
include SciYAG
|
167
|
+
include Tioga
|
168
|
+
|
169
|
+
include Debug
|
170
|
+
include Log
|
171
|
+
include Utils # For the safe_float function
|
172
|
+
|
173
|
+
# For dimension conversion and TeX quoting.
|
174
|
+
include Tioga::Utils
|
175
|
+
|
176
|
+
# For the backend handling:
|
177
|
+
include CTioga::Backends
|
178
|
+
|
179
|
+
# Support for Themes:
|
180
|
+
include Themes
|
181
|
+
|
182
|
+
# Axes, edges, labels, tick labels:
|
183
|
+
include Axes
|
184
|
+
|
185
|
+
|
186
|
+
# these are basically the attributes which are modified directly
|
187
|
+
# on the command-line
|
188
|
+
attr_writer :fig_name, :cleanup, :legend
|
189
|
+
|
190
|
+
# The command-line parser
|
191
|
+
attr_reader :parser
|
192
|
+
|
193
|
+
# The last Curve object on the stack
|
194
|
+
attr_reader :last_curve
|
195
|
+
|
196
|
+
# Whether we are trying to do real-size PDF or not. When set, it
|
197
|
+
# is the size of the PDF
|
198
|
+
attr_accessor :real_size
|
199
|
+
|
200
|
+
# The current object
|
201
|
+
attr_accessor :current_object
|
202
|
+
|
203
|
+
# Whether we are making a PNG file
|
204
|
+
attr_accessor :png
|
205
|
+
|
206
|
+
# The PNG's size:
|
207
|
+
attr_accessor :png_size
|
208
|
+
|
209
|
+
# Whether to create separate legends
|
210
|
+
attr_accessor :separate_legends
|
211
|
+
|
212
|
+
# Whether to automatically add a legend to all curves
|
213
|
+
attr_accessor :autolegends
|
214
|
+
|
215
|
+
def initialize
|
216
|
+
# The first thing to do is to setup logging, as you really
|
217
|
+
# want to be able to communicate with users, don't you ?
|
218
|
+
init_logger
|
219
|
+
|
220
|
+
|
221
|
+
@args = [] # Holding the command-line
|
222
|
+
@parser = OptionParser.new
|
223
|
+
|
224
|
+
initialize_themes
|
225
|
+
|
226
|
+
# Whether the plots should be interpolated.
|
227
|
+
@interpolate = false
|
228
|
+
|
229
|
+
@line_width = nil
|
230
|
+
|
231
|
+
@fig_name = "Plot"
|
232
|
+
|
233
|
+
# Initialize the backend structure:
|
234
|
+
init_backend_structure
|
235
|
+
|
236
|
+
init_axes
|
237
|
+
|
238
|
+
# now, the elements for the structure of the plot:
|
239
|
+
@root_object = SubPlot.new
|
240
|
+
# We start with a very simple layout
|
241
|
+
SimpleLayout.new(@root_object)
|
242
|
+
|
243
|
+
# the current object is the one to which we'll add plot elements.
|
244
|
+
@current_object = @root_object
|
245
|
+
|
246
|
+
# @legend is holding the text attached to the current item being
|
247
|
+
# plotted; it has to be cleared manually after every item processed
|
248
|
+
@legend = nil
|
249
|
+
|
250
|
+
# general purpose variables:
|
251
|
+
@cleanup = true # Now, cleaning is done by default !
|
252
|
+
@viewer = false
|
253
|
+
|
254
|
+
# Some options we could set on the command line
|
255
|
+
@init_funcalls = []
|
256
|
+
|
257
|
+
# Whether to provide default legends. On by default
|
258
|
+
@autolegends = true
|
259
|
+
|
260
|
+
@command_line = ""
|
261
|
+
|
262
|
+
@real_size = "12cmx12cm"
|
263
|
+
|
264
|
+
# If set, should be the size of the TeX font
|
265
|
+
|
266
|
+
@tex_fontsize = nil
|
267
|
+
|
268
|
+
# The frame sides for the setup_real_size function
|
269
|
+
@frame_sides = [0.1,0.9,0.9,0.1]
|
270
|
+
# Override this, as the layout scheme makes it more or less
|
271
|
+
# obsolete
|
272
|
+
@frame_sides = [0,1,1,0]
|
273
|
+
|
274
|
+
# If set, we create separate legend informations:
|
275
|
+
@separate_legend = false
|
276
|
+
|
277
|
+
# The array holding the styles.
|
278
|
+
@separate_legend_styles = []
|
279
|
+
# The size of the produced PDF in PDF points.
|
280
|
+
@separate_legend_width = 12
|
281
|
+
@separate_legend_height = 8
|
282
|
+
|
283
|
+
# Whether to mark the command-line in the PDF file
|
284
|
+
@mark = true # On by default, really useful !!!
|
285
|
+
|
286
|
+
# The LaTeX preamble:
|
287
|
+
@preamble = ""
|
288
|
+
|
289
|
+
# The last curve used:
|
290
|
+
@last_curve = nil
|
291
|
+
|
292
|
+
# Specifications for the --next stuff: an array, the first
|
293
|
+
# element is the class to be created for children and the
|
294
|
+
# rest are arguments to be added at the beginning.
|
295
|
+
@next_specs = [SubPlot, :subplot]
|
296
|
+
|
297
|
+
# A block to be run on both the old and new object
|
298
|
+
# when --next is encountered.
|
299
|
+
@next_block = nil
|
300
|
+
|
301
|
+
# Whether we automatically start a --next stuff on each spec
|
302
|
+
# or even on each dataset
|
303
|
+
@auto_next = false
|
304
|
+
|
305
|
+
# We don't make any PNG file:
|
306
|
+
@png = false
|
307
|
+
|
308
|
+
# The standard PNG density...
|
309
|
+
@png_oversampling = 2
|
310
|
+
|
311
|
+
# We don't produce SVG output by default
|
312
|
+
@svg = false
|
313
|
+
|
314
|
+
# The default padding;
|
315
|
+
@default_padding = []
|
316
|
+
4.times do
|
317
|
+
@default_padding << Dimension.new(0.05)
|
318
|
+
end
|
319
|
+
|
320
|
+
# And we use it :
|
321
|
+
use_default_padding
|
322
|
+
|
323
|
+
prepare_option_parser
|
324
|
+
end
|
325
|
+
|
326
|
+
# Sets the given's object padding to the current default:
|
327
|
+
def use_default_padding(obj = nil)
|
328
|
+
obj = current_object unless obj
|
329
|
+
4.times do |i|
|
330
|
+
obj.layout_preferences.padding[i] = @default_padding[i].dup
|
331
|
+
end
|
332
|
+
debug "Setting padding for #{identify(obj)} to " +
|
333
|
+
obj.layout_preferences.padding.inspect
|
334
|
+
end
|
335
|
+
|
336
|
+
|
337
|
+
# Adds the given object to the current's stack and set it
|
338
|
+
# as the current object, so that it will receive further
|
339
|
+
# children
|
340
|
+
def enter_child_object(object)
|
341
|
+
@current_object.add_elem(object)
|
342
|
+
self.current_object = object
|
343
|
+
end
|
344
|
+
|
345
|
+
# Goes out from a child object to its parent. Returns the child
|
346
|
+
# object. Issues a warning if already at top level (in which case
|
347
|
+
# it returns the current object)
|
348
|
+
def leave_child_object
|
349
|
+
prev = current_object
|
350
|
+
if current_object.parent
|
351
|
+
self.current_object = current_object.parent
|
352
|
+
else
|
353
|
+
warn "--end while in top level"
|
354
|
+
end
|
355
|
+
return prev
|
356
|
+
end
|
357
|
+
|
358
|
+
|
359
|
+
# Returns the current object's plot style
|
360
|
+
def current_plot_style
|
361
|
+
return current_object.plot_style
|
362
|
+
end
|
363
|
+
|
364
|
+
|
365
|
+
|
366
|
+
# This function reads a configuration file and executes it's
|
367
|
+
# statements in the scope of a local module, which is then
|
368
|
+
# included in this instance, and in the backend's instances.
|
369
|
+
def read_config_file(file)
|
370
|
+
f = File.open(file)
|
371
|
+
info "Reading config file #{file}"
|
372
|
+
lines = f.readlines
|
373
|
+
lines << "\nend\n"
|
374
|
+
lines.unshift "module CTiogaRC\n"
|
375
|
+
code = lines.join
|
376
|
+
eval code
|
377
|
+
extend CTiogaRC
|
378
|
+
for b_e in backends.values
|
379
|
+
b_e.extend CTiogaRC
|
380
|
+
end
|
381
|
+
# This is for the compute_formula function.
|
382
|
+
Dvector.extend CTiogaRC
|
383
|
+
end
|
384
|
+
|
385
|
+
CONFIG_FILE_NAME = ".ctiogarc"
|
386
|
+
|
387
|
+
# Looks for a configuration file, either in the current directory
|
388
|
+
# or in the HOME directory.
|
389
|
+
def lookup_config_file
|
390
|
+
if File.readable? CONFIG_FILE_NAME
|
391
|
+
return CONFIG_FILE_NAME
|
392
|
+
end
|
393
|
+
|
394
|
+
if ENV.has_key?('HOME')
|
395
|
+
home_rc = File.join(ENV['HOME'], CONFIG_FILE_NAME)
|
396
|
+
if File.readable?(home_rc)
|
397
|
+
return home_rc
|
398
|
+
end
|
399
|
+
end
|
400
|
+
return nil
|
401
|
+
end
|
402
|
+
|
403
|
+
# Sets the frame of the root object. This is absolutely
|
404
|
+
# necessary for layout computation. Failure to do so will
|
405
|
+
# result in failed plots.
|
406
|
+
def set_root_frame(*frames)
|
407
|
+
@root_object.root_frame = frames
|
408
|
+
end
|
409
|
+
|
410
|
+
# sets up the FigureMaker object to use for real size. Uses the
|
411
|
+
# @real_size instance variable as a source for the size
|
412
|
+
def setup_real_size(t)
|
413
|
+
# Get the width and height from @real_size
|
414
|
+
sizes = @real_size.split("x").collect {|s|
|
415
|
+
tex_dimension_to_bp(s)
|
416
|
+
}
|
417
|
+
|
418
|
+
t.def_enter_page_function {
|
419
|
+
t.page_setup(*sizes)
|
420
|
+
t.set_frame_sides(*@frame_sides)
|
421
|
+
set_root_frame(sizes[0] * @frame_sides[0],
|
422
|
+
sizes[0] * @frame_sides[1],
|
423
|
+
sizes[1] * @frame_sides[2],
|
424
|
+
sizes[1] * @frame_sides[3])
|
425
|
+
}
|
426
|
+
|
427
|
+
# Setting label and title scale to 1
|
428
|
+
t.title_scale = 1
|
429
|
+
t.xlabel_scale = 1
|
430
|
+
t.ylabel_scale = 1
|
431
|
+
end
|
432
|
+
|
433
|
+
# Reads a configuration file if one is found.
|
434
|
+
def read_config
|
435
|
+
if lookup_config_file
|
436
|
+
read_config_file(lookup_config_file)
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
|
441
|
+
# Push a function call onto the stack of the current element
|
442
|
+
def add_elem_funcall(sym, *args)
|
443
|
+
@current_object.add_funcall(TiogaFuncall.new(sym, *args))
|
444
|
+
end
|
445
|
+
|
446
|
+
# Push a function call onto the stack of things we should do
|
447
|
+
# just after creating the FigureMakerobject
|
448
|
+
def add_init_funcall(sym, *args)
|
449
|
+
@init_funcalls << TiogaFuncall.new(sym, *args)
|
450
|
+
end
|
451
|
+
|
452
|
+
# Forwards the boundary settings to the current object.
|
453
|
+
def set_bounds(which, val)
|
454
|
+
method = ("bound_#{which}=").to_sym
|
455
|
+
@current_object.send(method, val)
|
456
|
+
end
|
457
|
+
|
458
|
+
def set_range(a,b,str)
|
459
|
+
first,last = str.split(/\s*:\s*/)
|
460
|
+
first = first.to_f if first
|
461
|
+
last = last.to_f if last
|
462
|
+
set_bounds(a,first)
|
463
|
+
set_bounds(b,last)
|
464
|
+
end
|
465
|
+
|
466
|
+
# Splits a text into four components, expanding if necessary:
|
467
|
+
# * if there is only one element, all four become this one
|
468
|
+
# * if there are two: w,h, it becomes w,w,h,h
|
469
|
+
# * if there are three, the last element is duplicated.
|
470
|
+
def expand_sides(txt)
|
471
|
+
ary = txt.split(/\s*,\s*/)
|
472
|
+
case ary.size
|
473
|
+
when 1
|
474
|
+
return [ary[0], ary[0], ary[0], ary[0]]
|
475
|
+
when 2
|
476
|
+
return [ary[0], ary[0], ary[1], ary[1]]
|
477
|
+
when 3
|
478
|
+
return [ary[0], ary[1], ary[2], ary[2]]
|
479
|
+
else
|
480
|
+
return ary
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
def margins_from_text(txt)
|
485
|
+
ary = expand_sides(txt).map {|s| s.to_f}
|
486
|
+
return ary
|
487
|
+
end
|
488
|
+
|
489
|
+
# In the order: left, right, bottom, top, to suit Tioga's
|
490
|
+
# default positioning of the text along the axis.
|
491
|
+
def set_frame_margins(val)
|
492
|
+
@frame_sides = [val[0], 1.0 - val[1], 1.0 - val[3], val[2]]
|
493
|
+
end
|
494
|
+
|
495
|
+
# This function prepares the parser by giving it the arguments and
|
496
|
+
# some small help text going along with them. This function will
|
497
|
+
# unfortunately get really large with time, but there's nothing
|
498
|
+
# to do about it.
|
499
|
+
def prepare_option_parser
|
500
|
+
theme_prepare_parser(@parser)
|
501
|
+
|
502
|
+
@parser.separator "\nLaTeX options"
|
503
|
+
@parser.on("--use PACKAGE",
|
504
|
+
"Adds PACKAGE to the LaTeX preamble") do |w|
|
505
|
+
@preamble += "\\usepackage{#{w}}\n"
|
506
|
+
end
|
507
|
+
|
508
|
+
@parser.on("--preamble STRING",
|
509
|
+
"Adds STRING to the LaTeX preamble") do |w|
|
510
|
+
@preamble += "#{w}\n"
|
511
|
+
end
|
512
|
+
|
513
|
+
@parser.separator "\nGlobal look:"
|
514
|
+
|
515
|
+
@parser.on("--[no-]background [COLOR]",
|
516
|
+
"Sets the background color for plots") do |c|
|
517
|
+
# Keep it to false if only false
|
518
|
+
c = CTioga.get_tioga_color(c) if c
|
519
|
+
current_plot_style.background_color = c
|
520
|
+
end
|
521
|
+
|
522
|
+
@parser.on("--[no-]watermark [TEXT]",
|
523
|
+
"Writes a text as a watermark at the back ",
|
524
|
+
"of the plot") do |c|
|
525
|
+
current_plot_style.watermark_text = c
|
526
|
+
end
|
527
|
+
|
528
|
+
@parser.on("--watermark-color [COLOR]",
|
529
|
+
"Chooses the color of the watermark") do |c|
|
530
|
+
c = CTioga.get_tioga_color(c) if c
|
531
|
+
current_plot_style.watermark_color = c
|
532
|
+
end
|
533
|
+
|
534
|
+
@parser.on("--watermark-scale SCALE",
|
535
|
+
"Chooses the color of the watermark") do |c|
|
536
|
+
current_plot_style.watermark_scale = Float(c)
|
537
|
+
end
|
538
|
+
|
539
|
+
@parser.on("--aspect-ratio [RATIO]",
|
540
|
+
"Sets the aspect ratio (defaults to 1)") do |a|
|
541
|
+
a = 1.0 unless a
|
542
|
+
a = a.to_f
|
543
|
+
if a <= 0.0
|
544
|
+
warn "Aspect ratio #{a} not valid, ignored"
|
545
|
+
else
|
546
|
+
add_elem_funcall(:set_aspect_ratio, a)
|
547
|
+
end
|
548
|
+
end
|
549
|
+
@parser.on("--golden-ratio",
|
550
|
+
"Sets the aspect ratio to the golden number") do |a|
|
551
|
+
add_elem_funcall(:set_aspect_ratio, 1.61803398874989)
|
552
|
+
end
|
553
|
+
@parser.on("--xrange RANGE",
|
554
|
+
"X plotting range") do |a|
|
555
|
+
set_range('left','right', a)
|
556
|
+
end
|
557
|
+
@parser.on("--yrange RANGE",
|
558
|
+
"y plotting range") do |a|
|
559
|
+
set_range('bottom','top', a)
|
560
|
+
end
|
561
|
+
@parser.on("--margin MARGIN",
|
562
|
+
"Sets the margin around data", "(left,right,top,bottom",
|
563
|
+
"in fractions of the corresponding size)") do |v|
|
564
|
+
current_object.plot_margins = margins_from_text(v)
|
565
|
+
end
|
566
|
+
|
567
|
+
@parser.on("--rescale FACTOR",
|
568
|
+
"Scales everything by a given factor",
|
569
|
+
"(useful for subplots)") do |fact|
|
570
|
+
current_object.rescale = safe_float(fact)
|
571
|
+
end
|
572
|
+
|
573
|
+
|
574
|
+
# TODO: this should probably move to PlotStyle
|
575
|
+
@parser.on("--padding PADDING",
|
576
|
+
"Changes the padding for the current object",
|
577
|
+
"and subsequent ones") do |pad|
|
578
|
+
ary = expand_sides(pad)
|
579
|
+
@default_padding = ary.map {|dim| Dimension.new(dim)}
|
580
|
+
use_default_padding
|
581
|
+
end
|
582
|
+
|
583
|
+
axes_options(@parser)
|
584
|
+
|
585
|
+
@parser.separator "\nSubfigures, subplots, regions..."
|
586
|
+
|
587
|
+
@parser.on("--y2",
|
588
|
+
"Switch to an alternative Y axis") do
|
589
|
+
begin
|
590
|
+
current_object.disable_axis(:y)
|
591
|
+
rescue
|
592
|
+
# Purely ignore errors
|
593
|
+
warn "Something should have happened here "+
|
594
|
+
"that did not happen properly"
|
595
|
+
end
|
596
|
+
plot = SharedAxisPlot.new(:y, current_object)
|
597
|
+
# The label will be on the right
|
598
|
+
plot.plot_style.ylabel.side = RIGHT
|
599
|
+
plot.plot_style.edges.yaxis.loc = RIGHT
|
600
|
+
plot.plot_style.edges.set_edges(AXIS_HIDDEN, :left,:top,:bottom)
|
601
|
+
current_plot_style.edges.set_edges(AXIS_LINE_ONLY, :right)
|
602
|
+
# A simple layout will control the plot
|
603
|
+
layout = FixedLayout.new(plot)
|
604
|
+
# And this layout should report its info to
|
605
|
+
# the current one.
|
606
|
+
#
|
607
|
+
# Note that we add the child's *layout* to the
|
608
|
+
# objects layout, not the child itself.
|
609
|
+
#
|
610
|
+
# Won't fail if the current plot does not have a layout
|
611
|
+
if current_object.layout
|
612
|
+
current_object.layout.add_child(layout)
|
613
|
+
end
|
614
|
+
enter_child_object(plot)
|
615
|
+
end
|
616
|
+
|
617
|
+
@parser.on("--x2",
|
618
|
+
"Switch to an alternative X axis") do
|
619
|
+
begin
|
620
|
+
current_object.disable_axis(:y)
|
621
|
+
rescue
|
622
|
+
# Purely ignore errors
|
623
|
+
warn "Something should have happened here " +
|
624
|
+
"that did not happen properly"
|
625
|
+
end
|
626
|
+
plot = SharedAxisPlot.new(:x, current_object)
|
627
|
+
# The label will be on the right
|
628
|
+
plot.xlabel.side = TOP
|
629
|
+
plot.edges.xaxis.loc = TOP
|
630
|
+
plot.edges.set_edges(AXIS_HIDDEN, :left,:right,:bottom)
|
631
|
+
current_object.edges.set_edges(AXIS_LINE_ONLY, :top)
|
632
|
+
layout = FixedLayout.new(plot)
|
633
|
+
# And this layout should report its info to
|
634
|
+
# the current one.
|
635
|
+
#
|
636
|
+
# Note that we add the child's *layout* to the
|
637
|
+
# objects layout, not the child itself.
|
638
|
+
current_object.layout.add_child(layout)
|
639
|
+
enter_child_object(plot)
|
640
|
+
end
|
641
|
+
|
642
|
+
@parser.on("--inset SPEC",
|
643
|
+
"Creates an inset with the given",
|
644
|
+
"specifications (x,y:w[xh] or x1,y1;x2,y2)" ) do |a|
|
645
|
+
plot = SubPlot.new(:subplot, current_object)
|
646
|
+
plot.show_legend = true
|
647
|
+
enter_child_object(plot)
|
648
|
+
# TODO - high priority !
|
649
|
+
# implement a way to specify insets with *real* tex dimensions
|
650
|
+
# !!!! (such as 3cm,2cm:5cm) !
|
651
|
+
#
|
652
|
+
# This would *really* make way for real-size graphs
|
653
|
+
margins = Utils::inset_margins(a)
|
654
|
+
debug "inset margins: #{margins.inspect}"
|
655
|
+
current_object.convert_layout(FixedLayout)
|
656
|
+
current_object.layout.position = margins
|
657
|
+
# By default, we redirect all legends for the inset
|
658
|
+
# to the parent.
|
659
|
+
current_object.accept_legend = false
|
660
|
+
end
|
661
|
+
|
662
|
+
@parser.on("--zoom-inset SPEC",
|
663
|
+
"Creates an inset as with --inset " +
|
664
|
+
"and copies all the ",
|
665
|
+
"elements in the current plot there") do |a|
|
666
|
+
plot = SubPlot.new(:subplot, current_object)
|
667
|
+
# We redirect all legends for the inset to the parent.
|
668
|
+
plot.accept_legend = false
|
669
|
+
# Temporarily disable legends.
|
670
|
+
plot.disable_legend = true
|
671
|
+
for obj in @current_object.elements
|
672
|
+
plot.add_elem(obj.dup)
|
673
|
+
end
|
674
|
+
for obj in @current_object.funcalls
|
675
|
+
plot.add_funcall(obj.dup)
|
676
|
+
end
|
677
|
+
plot.disable_legend = false
|
678
|
+
# Better add it afterwards...
|
679
|
+
enter_child_object(plot)
|
680
|
+
margins = Utils::inset_margins(a)
|
681
|
+
debug "zoom margins: #{margins.inspect}"
|
682
|
+
current_object.convert_layout(FixedLayout)
|
683
|
+
current_object.layout.position = margins
|
684
|
+
end
|
685
|
+
|
686
|
+
@parser.on("--next-inset SPEC",
|
687
|
+
"Equivalent to --end --inset SPEC, except that",
|
688
|
+
"various style information are carried from",
|
689
|
+
"the previous inset") do |a|
|
690
|
+
plot = SubPlot.new(:subplot, current_object)
|
691
|
+
plot.show_legend = true
|
692
|
+
old = leave_child_object
|
693
|
+
enter_child_object(plot)
|
694
|
+
|
695
|
+
# Force copy of the style:
|
696
|
+
plot.plot_style = old.plot_style.deep_copy(plot)
|
697
|
+
|
698
|
+
margins = Utils::inset_margins(a)
|
699
|
+
debug "inset margins: #{margins.inspect}"
|
700
|
+
current_object.convert_layout(FixedLayout)
|
701
|
+
current_object.layout.position = margins
|
702
|
+
# We redirect all legends for the inset to the parent.
|
703
|
+
current_object.accept_legend = false
|
704
|
+
end
|
705
|
+
|
706
|
+
@parser.on("--subplot SPEC",
|
707
|
+
"Creates a subplot with the given specifications",
|
708
|
+
"(x,y:w[xh] or x1,y1;x2,y2)" ) do |a|
|
709
|
+
plot = SubPlot.new(:subplot, current_object)
|
710
|
+
enter_child_object(plot)
|
711
|
+
margins = Utils::inset_margins(a)
|
712
|
+
|
713
|
+
debug "subplot margins: #{margins.inspect}"
|
714
|
+
# In contrast with --inset, we use a layout:
|
715
|
+
current_object.layout = SimpleLayout.new(current_object)
|
716
|
+
current_object.layout.bounding_box = margins
|
717
|
+
|
718
|
+
# We do not redirect legends for a subplot
|
719
|
+
current_object.accept_legend = true
|
720
|
+
|
721
|
+
end
|
722
|
+
|
723
|
+
@parser.on("--next-subplot SPEC",
|
724
|
+
"Creates a subplot with the given specifications",
|
725
|
+
"(x,y:w[xh] or x1,y1;x2,y2)" ) do |a|
|
726
|
+
plot = SubPlot.new(:subplot, current_object)
|
727
|
+
old = leave_child_object
|
728
|
+
enter_child_object(plot)
|
729
|
+
margins = Utils::inset_margins(a)
|
730
|
+
|
731
|
+
# Force copy of the style:
|
732
|
+
plot.plot_style = old.plot_style.deep_copy(plot)
|
733
|
+
|
734
|
+
debug "subplot margins: #{margins.inspect}"
|
735
|
+
# In contrast with --inset, we use a layout:
|
736
|
+
current_object.layout = SimpleLayout.new(current_object)
|
737
|
+
current_object.layout.bounding_box = margins
|
738
|
+
|
739
|
+
# We do not redirect legends for a subplot
|
740
|
+
current_object.accept_legend = true
|
741
|
+
|
742
|
+
end
|
743
|
+
|
744
|
+
|
745
|
+
|
746
|
+
@parser.on("--disable-legends",
|
747
|
+
"Disable the display of legends for the current " +
|
748
|
+
"subplot/subfigure") do
|
749
|
+
current_object.disable_legend = true
|
750
|
+
end
|
751
|
+
|
752
|
+
@parser.on("--enable-legends",
|
753
|
+
"Reverts --disable-legends") do
|
754
|
+
current_object.disable_legend = false
|
755
|
+
end
|
756
|
+
|
757
|
+
@parser.on("--[no-]forward-legends",
|
758
|
+
"Forwards the legends to the parent object") do |v|
|
759
|
+
current_object.accept_legend = ! v
|
760
|
+
end
|
761
|
+
|
762
|
+
# Black magic is starting here !
|
763
|
+
@parser.on("--grid SPEC",
|
764
|
+
"Creates a grid. SPEC is column|row=nb") do |s|
|
765
|
+
# get the spec:
|
766
|
+
s =~ /(\w+)=(\d+)/
|
767
|
+
which = "#{$1}s=".to_sym
|
768
|
+
number = $2.to_i
|
769
|
+
if which == :columns= or which == :rows=
|
770
|
+
current_object.layout = current_object.layout.
|
771
|
+
convert_layout(GridLayout)
|
772
|
+
|
773
|
+
current_plot_style.hide_axis_and_edges(:x, :y)
|
774
|
+
|
775
|
+
# We rescale the padding by a factor of 1/number, so it looks
|
776
|
+
# reasonable in the end.
|
777
|
+
@default_padding.each do |dim|
|
778
|
+
dim.scale!(1.0/number)
|
779
|
+
end
|
780
|
+
|
781
|
+
# We use the default padding for the chidren
|
782
|
+
use_default_padding
|
783
|
+
|
784
|
+
current_plot_style.set_xy_labels(nil, nil)
|
785
|
+
|
786
|
+
current_object.layout.send(which, number)
|
787
|
+
plot = SubPlot.new(:subplot, current_object)
|
788
|
+
plot.accept_legend = false
|
789
|
+
@current_object.layout.add_child(GridItemLayout.new(plot))
|
790
|
+
@current_object.add_elem(plot)
|
791
|
+
self.current_object = plot
|
792
|
+
@next_spec = [SubPlot, :subplot]
|
793
|
+
@next_block = proc do |old, new|
|
794
|
+
new.accept_legend = false # By default, forward to the parent.
|
795
|
+
use_default_padding(new)
|
796
|
+
end
|
797
|
+
else
|
798
|
+
warn "Unrecognized spec #$1"
|
799
|
+
end
|
800
|
+
end
|
801
|
+
|
802
|
+
# Black magic is starting here !
|
803
|
+
@parser.on("--col",
|
804
|
+
"Starts a column of graphes with shared X axis") do
|
805
|
+
# We convert the current object to a GridLayout. If
|
806
|
+
# there are already plots, that really won't look good.
|
807
|
+
# You've been warned !!!
|
808
|
+
current_object.layout = current_object.layout.
|
809
|
+
convert_layout(GridLayout)
|
810
|
+
# current_object.edges.disable
|
811
|
+
|
812
|
+
current_plot_style.hide_axis_and_edges(:x, :y)
|
813
|
+
|
814
|
+
current_object.layout_preferences.padding[2] = Dimension.new(0.0)
|
815
|
+
current_object.layout_preferences.padding[3] = Dimension.new(0.0)
|
816
|
+
|
817
|
+
current_object.layout.columns = 1
|
818
|
+
plot = SharedAxisPlot.new(:y, current_object)
|
819
|
+
@next_spec = [SharedAxisPlot, :y]
|
820
|
+
@current_object.layout.add_child(GridItemLayout.new(plot))
|
821
|
+
@current_object.add_elem(plot)
|
822
|
+
|
823
|
+
# We copy the X label and the title
|
824
|
+
plot.plot_style.xlabel.label = current_plot_style.xlabel.label
|
825
|
+
current_plot_style.xlabel.label = nil
|
826
|
+
plot.plot_style.title.label = current_plot_style.title.label
|
827
|
+
current_plot_style.title.label = nil
|
828
|
+
self.current_object = plot
|
829
|
+
|
830
|
+
# We cancel vertical padding by default:
|
831
|
+
@default_padding[2] = Dimension.new(0.0)
|
832
|
+
@default_padding[3] = Dimension.new(0.0)
|
833
|
+
|
834
|
+
use_default_padding(plot)
|
835
|
+
# Forwards the x label to the child, and cancels it here.
|
836
|
+
# The code to be run on the old and new tings
|
837
|
+
@next_block = proc do |old, new|
|
838
|
+
old.plot_style.edges.
|
839
|
+
set_axis_and_edges_style(:x, AXIS_WITH_TICKS_ONLY)
|
840
|
+
old.plot_style.xlabel.label = nil
|
841
|
+
|
842
|
+
# Disable title by default
|
843
|
+
new.plot_style.title.label = nil
|
844
|
+
use_default_padding(new)
|
845
|
+
end
|
846
|
+
end
|
847
|
+
|
848
|
+
@parser.on("--next",
|
849
|
+
"Start the next object") do
|
850
|
+
next_object
|
851
|
+
end
|
852
|
+
|
853
|
+
@parser.on("--[no-]auto-next",
|
854
|
+
"Automatically start a --next graph",
|
855
|
+
"for every element on the command-line") do |v|
|
856
|
+
if v
|
857
|
+
@auto_next = :element
|
858
|
+
else
|
859
|
+
@auto_next = false
|
860
|
+
end
|
861
|
+
end
|
862
|
+
|
863
|
+
@parser.on("--auto-next-expanded",
|
864
|
+
"Automatically start a --next graph",
|
865
|
+
"for every element on the command-line",
|
866
|
+
"after data set expansion !") do
|
867
|
+
@auto_next = :dataset
|
868
|
+
end
|
869
|
+
|
870
|
+
|
871
|
+
# @parser.on("--subframes FRAMES",
|
872
|
+
# "Setup the frames relative to the parent",
|
873
|
+
# "for the current " +
|
874
|
+
# "subplot",
|
875
|
+
# "(distance from left,right,top,bottom)") do |v|
|
876
|
+
# current_object.frame_margins = margins_from_text(v)
|
877
|
+
# end
|
878
|
+
|
879
|
+
@parser.on("--region",
|
880
|
+
"Start a colored region") do
|
881
|
+
plot = Region.new(current_object)
|
882
|
+
@current_object.add_elem(plot)
|
883
|
+
self.current_object = plot
|
884
|
+
end
|
885
|
+
|
886
|
+
@parser.on("--region-color COLOR",
|
887
|
+
"The color of the region") do |v|
|
888
|
+
c = CTioga.get_tioga_color(v)
|
889
|
+
begin
|
890
|
+
current_object.region_color = c
|
891
|
+
rescue
|
892
|
+
error "The current container isn't a --region !"
|
893
|
+
end
|
894
|
+
end
|
895
|
+
|
896
|
+
@parser.on("--region-transparency TRANS",
|
897
|
+
"The transparency of the region") do |v|
|
898
|
+
c = v.to_f
|
899
|
+
begin
|
900
|
+
current_object.region_transparency = c
|
901
|
+
rescue
|
902
|
+
error "The current container isn't a --region !"
|
903
|
+
end
|
904
|
+
end
|
905
|
+
|
906
|
+
@parser.on("--region-dont-display",
|
907
|
+
"No curve until the next --end will actually be ",
|
908
|
+
"displayed: they are just used to delimit the ",
|
909
|
+
"region used for the fills") do
|
910
|
+
begin
|
911
|
+
current_object.dont_display = true
|
912
|
+
rescue
|
913
|
+
error "The current container isn't a --region !"
|
914
|
+
end
|
915
|
+
end
|
916
|
+
|
917
|
+
@parser.on("--region-debug",
|
918
|
+
"Setup the fills for the curves inside the",
|
919
|
+
"region to help understanding what happens") do
|
920
|
+
begin
|
921
|
+
current_object.region_debug = true
|
922
|
+
rescue
|
923
|
+
error "The current container isn't a --region !"
|
924
|
+
end
|
925
|
+
end
|
926
|
+
|
927
|
+
@parser.on("--region-invert-rule",
|
928
|
+
"Inverts the rule for choosing the filled region") do
|
929
|
+
begin
|
930
|
+
current_object.invert_rule = true
|
931
|
+
rescue
|
932
|
+
error "The current container isn't a --region !"
|
933
|
+
end
|
934
|
+
end
|
935
|
+
|
936
|
+
@parser.on("--region-fill-twice",
|
937
|
+
"Fills the region twice, choosing opposite",
|
938
|
+
"rules for the boundaries.") do
|
939
|
+
begin
|
940
|
+
current_object.fill_twice = true
|
941
|
+
rescue
|
942
|
+
error "The current container isn't a --region !"
|
943
|
+
end
|
944
|
+
end
|
945
|
+
|
946
|
+
@parser.on("--end",
|
947
|
+
"Ends the last subplot or region") do
|
948
|
+
leave_child_object
|
949
|
+
end
|
950
|
+
|
951
|
+
|
952
|
+
@parser.separator "\nOptions for real size output:"
|
953
|
+
# Real size
|
954
|
+
@parser.on("-r", "--[no-]real-size [SIZE]",
|
955
|
+
"Tries to produce a PDF file suitable",
|
956
|
+
"for inclusion " +
|
957
|
+
"with \\includegraphics") do |spec|
|
958
|
+
@real_size = spec
|
959
|
+
end
|
960
|
+
@parser.on("--frame-margins VAL",
|
961
|
+
"The proportion of space to leave on",
|
962
|
+
"each side for the text display" ) do |val|
|
963
|
+
set_frame_margins(margins_from_text(val))
|
964
|
+
end
|
965
|
+
|
966
|
+
Legends::fill_option_parser(@parser, self)
|
967
|
+
|
968
|
+
@parser.separator "\nTexts:"
|
969
|
+
|
970
|
+
## Default TeX fontsize
|
971
|
+
@parser.on("--fontsize NB",
|
972
|
+
"Default TeX fontsize in points") do |size|
|
973
|
+
@tex_fontsize = size
|
974
|
+
end
|
975
|
+
|
976
|
+
@parser.on("--tick-label-scale SCALE",
|
977
|
+
"Sets the scale of the text for tick labels") do |l|
|
978
|
+
add_elem_funcall(:xaxis_numeric_label_scale=,Float(l))
|
979
|
+
add_elem_funcall(:yaxis_numeric_label_scale=,Float(l))
|
980
|
+
end
|
981
|
+
|
982
|
+
|
983
|
+
@parser.on("--[no-]separate-legends",
|
984
|
+
"If set, PDF files are produced that contain",
|
985
|
+
"what the legend pictogram would look like") do |l|
|
986
|
+
@separate_legends = l
|
987
|
+
if l
|
988
|
+
# Switches off autolegends by default
|
989
|
+
@autolegends = false
|
990
|
+
end
|
991
|
+
end
|
992
|
+
|
993
|
+
@parser.separator "\nGraphic primitives:"
|
994
|
+
@parser.on("-d","--draw SPEC",
|
995
|
+
"Draw a graphic primitive") do |spec|
|
996
|
+
add_elems_to_current(*TiogaPrimitiveMaker.parse_spec(spec, self))
|
997
|
+
end
|
998
|
+
@parser.on("--draw-help",
|
999
|
+
"Display what is known about graphic",
|
1000
|
+
"primitives and exit") do
|
1001
|
+
puts
|
1002
|
+
puts "Currently known graphics primitives are"
|
1003
|
+
puts
|
1004
|
+
puts TiogaPrimitiveMaker.introspect
|
1005
|
+
exit
|
1006
|
+
end
|
1007
|
+
|
1008
|
+
prepare_backend_options(@parser)
|
1009
|
+
|
1010
|
+
@parser.separator "\nHousekeeping and miscellaneous options:"
|
1011
|
+
@parser.on("--xpdf",
|
1012
|
+
"Runs xpdf to show the plot obtained") do
|
1013
|
+
@viewer = "xpdf -z page" # With the zoom set to full page
|
1014
|
+
end
|
1015
|
+
|
1016
|
+
@parser.on("--open",
|
1017
|
+
"Runs open to show the plot obtained") do
|
1018
|
+
@viewer = "open"
|
1019
|
+
end
|
1020
|
+
|
1021
|
+
@parser.on("--[no-]viewer [VIEWER]",
|
1022
|
+
"Runs VIEWER to show the plot obtained --",
|
1023
|
+
"or doesn't show up the viewer at all") do |x|
|
1024
|
+
@viewer = x
|
1025
|
+
end
|
1026
|
+
|
1027
|
+
@parser.on("--[no-]cleanup",
|
1028
|
+
"Removes all the accessory files produced") do |x|
|
1029
|
+
@cleanup = x
|
1030
|
+
end
|
1031
|
+
|
1032
|
+
@parser.on("--tex-cleanup",
|
1033
|
+
"Removes all files produced except those",
|
1034
|
+
"necessary " +
|
1035
|
+
"for inclusion in a TeX document") do
|
1036
|
+
@tex_cleanup = true
|
1037
|
+
end
|
1038
|
+
|
1039
|
+
@parser.on("--clean-all",
|
1040
|
+
"Removes all files produced -- better use",
|
1041
|
+
"along with " +
|
1042
|
+
"--xpdf or --open") do
|
1043
|
+
@clean_all = true
|
1044
|
+
end
|
1045
|
+
|
1046
|
+
@parser.on("--save-dir DIR",
|
1047
|
+
"Sets the directory for output") do |x|
|
1048
|
+
add_init_funcall(:save_dir=, x)
|
1049
|
+
end
|
1050
|
+
|
1051
|
+
|
1052
|
+
@parser.on("--include FILE",
|
1053
|
+
"Imports the file's functions into ctioga's",
|
1054
|
+
"namespace so that they can be used",
|
1055
|
+
"by backends") do |file|
|
1056
|
+
read_config_file(file)
|
1057
|
+
end
|
1058
|
+
|
1059
|
+
@parser.on("-n", "--name NAME",
|
1060
|
+
"Base name") do |name|
|
1061
|
+
@fig_name = name
|
1062
|
+
end
|
1063
|
+
|
1064
|
+
@parser.on("-o", "--output NAME",
|
1065
|
+
"Produces a graph named NAME with the current output") do |name|
|
1066
|
+
output_figure(name)
|
1067
|
+
end
|
1068
|
+
|
1069
|
+
@parser.on("-O", "--output-dir DIR",
|
1070
|
+
"Sets the output directory") do |dir|
|
1071
|
+
@output_directory = dir
|
1072
|
+
end
|
1073
|
+
|
1074
|
+
@parser.on("--echo", "Prints command-line to standard output") do
|
1075
|
+
puts "Command-line used:"
|
1076
|
+
puts @command_line
|
1077
|
+
end
|
1078
|
+
|
1079
|
+
@parser.on("--display-commandline",
|
1080
|
+
"Adds the command line used to make",
|
1081
|
+
"to graph on the bottom of the graph") do
|
1082
|
+
add_elem_funcall(:show_marker,
|
1083
|
+
{
|
1084
|
+
"x" => 0.5,
|
1085
|
+
"y" => -0.2,
|
1086
|
+
"text" => @command_line,
|
1087
|
+
"scale" => 0.6,
|
1088
|
+
"font" => Tioga::MarkerConstants::Courier,
|
1089
|
+
}
|
1090
|
+
)
|
1091
|
+
end
|
1092
|
+
|
1093
|
+
@parser.on("--[no-]mark",
|
1094
|
+
"Fills the 'creator' field of the " ,
|
1095
|
+
"produced PDF file with the command-line",
|
1096
|
+
"when on. ") do |v|
|
1097
|
+
@mark = v
|
1098
|
+
end
|
1099
|
+
|
1100
|
+
logger_options(@parser)
|
1101
|
+
@parser.on("--debug-patterns",
|
1102
|
+
"Produces an alignement-checking PDF file") do |v|
|
1103
|
+
@debug_patterns = v
|
1104
|
+
end
|
1105
|
+
|
1106
|
+
@parser.on("--eps",
|
1107
|
+
"Attempt to produce an EPS file,",
|
1108
|
+
"using pdftops, LaTeX and dvips.",
|
1109
|
+
"The PDF output is kept in any case") do |v|
|
1110
|
+
@eps = v
|
1111
|
+
end
|
1112
|
+
|
1113
|
+
@parser.on("--png SIZE",
|
1114
|
+
"Runs ctioga as usual, and runs convert afterwards",
|
1115
|
+
"to produce a nice PNG file") do |s|
|
1116
|
+
@png = true
|
1117
|
+
s =~ /(\d+)x(\d+)/
|
1118
|
+
@png_size = [$1,$2]
|
1119
|
+
# We setup the real size so that it matches the number of
|
1120
|
+
# postscript points:
|
1121
|
+
@real_size = "#{$1}bpx#{$2}bp"
|
1122
|
+
end
|
1123
|
+
|
1124
|
+
@parser.on("--png-oversampling NB",
|
1125
|
+
"How many more points to produce before downscaling") do |s|
|
1126
|
+
@png_oversampling = s.to_f
|
1127
|
+
end
|
1128
|
+
|
1129
|
+
@parser.on("--svg",
|
1130
|
+
"Runs ctioga as usual, and runs pdf2svg afterwards",
|
1131
|
+
"to produce a nice SVG file") do |s|
|
1132
|
+
@svg = true
|
1133
|
+
end
|
1134
|
+
|
1135
|
+
|
1136
|
+
@parser.on("--args FILE",
|
1137
|
+
"Reads argument from FILE, *one per",
|
1138
|
+
"line* and then resumes normal processing") do |v|
|
1139
|
+
File.open(v) do |f|
|
1140
|
+
unshift_cmdline_args(*f.readlines.collect {|s| s.chomp})
|
1141
|
+
end
|
1142
|
+
end
|
1143
|
+
|
1144
|
+
@parser.on_tail("-h", "--help", "Show this message") do
|
1145
|
+
puts banner
|
1146
|
+
puts
|
1147
|
+
puts @parser
|
1148
|
+
puts
|
1149
|
+
puts "Please note that all the options can be abbreviated"
|
1150
|
+
puts "as long as they are still unique"
|
1151
|
+
exit
|
1152
|
+
end
|
1153
|
+
|
1154
|
+
@parser.on_tail("--version", "Show version number") do
|
1155
|
+
puts "This is ctioga version " + Version::version
|
1156
|
+
exit
|
1157
|
+
end
|
1158
|
+
end
|
1159
|
+
|
1160
|
+
|
1161
|
+
# Start the next object in a complex plot (--grid, --col...)
|
1162
|
+
def next_object
|
1163
|
+
# We swicth back to the parent object
|
1164
|
+
old = leave_child_object
|
1165
|
+
a = @next_spec.dup # We need to dup, else shift would make the
|
1166
|
+
# next curve not that happy...
|
1167
|
+
cls = a.shift
|
1168
|
+
# We push the parent object as the last element for new.
|
1169
|
+
a << current_object
|
1170
|
+
plot = cls.new(*a)
|
1171
|
+
@current_object.layout.add_child(GridItemLayout.new(plot))
|
1172
|
+
enter_child_object(plot)
|
1173
|
+
|
1174
|
+
# We copy the style from the old plot to the new one:
|
1175
|
+
plot.plot_style = old.plot_style.deep_copy(plot)
|
1176
|
+
|
1177
|
+
@next_block.call(old, plot) if @next_block
|
1178
|
+
end
|
1179
|
+
|
1180
|
+
|
1181
|
+
def unshift_cmdline_args(*args)
|
1182
|
+
@args.unshift(*args)
|
1183
|
+
end
|
1184
|
+
|
1185
|
+
# the functions for plot structure...
|
1186
|
+
def subplot
|
1187
|
+
new_object = SubPlot.new(:subplot,@current_object)
|
1188
|
+
@current_object.add_elem(new_object)
|
1189
|
+
@current_object = new_object
|
1190
|
+
end
|
1191
|
+
|
1192
|
+
def subfigure
|
1193
|
+
new_object = SubPlot.new(:subfigure,@current_object)
|
1194
|
+
@current_object.add_elem(new_object)
|
1195
|
+
@current_object = new_object
|
1196
|
+
end
|
1197
|
+
|
1198
|
+
def end
|
1199
|
+
@current_object = @current_object.parent if @current_object.parent
|
1200
|
+
end
|
1201
|
+
|
1202
|
+
def add_elems_to_current(*elems)
|
1203
|
+
for el in elems
|
1204
|
+
@current_object.add_elem(el)
|
1205
|
+
end
|
1206
|
+
end
|
1207
|
+
|
1208
|
+
# This function is called whenever PlotMaker needs to process one
|
1209
|
+
# data set. It's name is given in parameter here.
|
1210
|
+
def process_data_set(set)
|
1211
|
+
# First, we get the dataset:
|
1212
|
+
begin
|
1213
|
+
data = xy_data_set(set)
|
1214
|
+
rescue Exception => ex
|
1215
|
+
error "Problem when processing set #{set} " +
|
1216
|
+
"(backend #{backend.class}): #{ex.message}"
|
1217
|
+
debug "Error backtrace: #{ex.backtrace.join "\n"}"
|
1218
|
+
warn "Simply ignoring set #{set}"
|
1219
|
+
return # Simply return.
|
1220
|
+
end
|
1221
|
+
|
1222
|
+
# Scale plots if necessary:
|
1223
|
+
data.mul!(:x,x_factor) if x_factor
|
1224
|
+
data.mul!(:y,y_factor) if y_factor
|
1225
|
+
|
1226
|
+
# Add an offset
|
1227
|
+
data.add!(:x,x_offset) if x_offset
|
1228
|
+
data.add!(:y,y_offset) if y_offset
|
1229
|
+
|
1230
|
+
# Implement a logarithmic scale
|
1231
|
+
data.safe_log10!(:x) if x_log
|
1232
|
+
data.safe_log10!(:y) if y_log
|
1233
|
+
|
1234
|
+
data.strip_nan
|
1235
|
+
|
1236
|
+
# Then we create a curve object and give it style
|
1237
|
+
if histogram
|
1238
|
+
curve = Histogram2D.new(get_current_style(set))
|
1239
|
+
else
|
1240
|
+
curve = Curve2D.new(get_current_style(set))
|
1241
|
+
end
|
1242
|
+
if @separate_legends
|
1243
|
+
@separate_legend_styles << curve.style
|
1244
|
+
end
|
1245
|
+
curve.set_data(data)
|
1246
|
+
|
1247
|
+
# Then we add ot to the current object:
|
1248
|
+
add_elems_to_current(curve)
|
1249
|
+
@last_curve = curve # And we update the last_curve pointer.
|
1250
|
+
end
|
1251
|
+
|
1252
|
+
# Provides the name of an output file, given its suffix and
|
1253
|
+
# optionnaly it base name (defaults to @figure_name).
|
1254
|
+
def output_filename(suffix, base = @fig_name)
|
1255
|
+
if @figmaker.save_dir
|
1256
|
+
File.join(@figmaker.save_dir,"#{base}#{suffix}")
|
1257
|
+
else
|
1258
|
+
"#{base}#{suffix}"
|
1259
|
+
end
|
1260
|
+
end
|
1261
|
+
|
1262
|
+
# Parses _args_ as command-line arguments.
|
1263
|
+
def parse_command_line_arguments(args)
|
1264
|
+
|
1265
|
+
# A little trick to allow in-place modification of the
|
1266
|
+
# command-line from an option.
|
1267
|
+
@args += args
|
1268
|
+
|
1269
|
+
# Runs the command to set command-line defaults. Ignored silently
|
1270
|
+
# in the files given on the command-line.
|
1271
|
+
|
1272
|
+
quoted_args = args.collect do |s|
|
1273
|
+
CTioga.shell_quote_string(s)
|
1274
|
+
end.join ' '
|
1275
|
+
|
1276
|
+
@command_line = "#{File.basename($0)} #{quoted_args}"
|
1277
|
+
|
1278
|
+
# Read the CTIOGA environment variable
|
1279
|
+
if ENV.key? "CTIOGA"
|
1280
|
+
ctioga_env = Shellwords.shellwords(ENV["CTIOGA"])
|
1281
|
+
@parser.parse(ctioga_env)
|
1282
|
+
end
|
1283
|
+
|
1284
|
+
if respond_to? :ctioga_defaults
|
1285
|
+
send :ctioga_defaults
|
1286
|
+
end
|
1287
|
+
|
1288
|
+
first = true
|
1289
|
+
done = false
|
1290
|
+
while ! done
|
1291
|
+
begin
|
1292
|
+
@parser.order!(@args) do |spec|
|
1293
|
+
# We use Backend#expand to leave room for the backend to
|
1294
|
+
# interpret the text as many different sets.
|
1295
|
+
sets = expand_spec(spec)
|
1296
|
+
info "Expanding #{spec} to #{sets.join(', ')}"
|
1297
|
+
# Auto --next:
|
1298
|
+
next_object if (@auto_next == :element and !first)
|
1299
|
+
for set in sets
|
1300
|
+
next_object if (@auto_next == :dataset and !first)
|
1301
|
+
process_data_set(set)
|
1302
|
+
first = false
|
1303
|
+
end
|
1304
|
+
end
|
1305
|
+
done = true
|
1306
|
+
rescue OptionParser::InvalidOption => o
|
1307
|
+
# Here, we should try to be clever about options:
|
1308
|
+
new_arg = nil
|
1309
|
+
option_name = o.args.first.gsub(/^--?/,'')
|
1310
|
+
# First, we check whether it corresponds to a shortcut:
|
1311
|
+
if Shortcut.has? option_name
|
1312
|
+
new_arg = ["--short", option_name]
|
1313
|
+
# Or an option of the current backend:
|
1314
|
+
elsif backend.description.param_hash.key? option_name
|
1315
|
+
new_arg = ["--#{backend.description.name}-#{option_name}"]
|
1316
|
+
end
|
1317
|
+
|
1318
|
+
if new_arg
|
1319
|
+
info "Option #{o.args.first} was reinterpreted " +
|
1320
|
+
"as #{new_arg.join ' '}"
|
1321
|
+
@args.unshift(*new_arg)
|
1322
|
+
else
|
1323
|
+
debug "Invalid option: #{o.inspect}"
|
1324
|
+
debug "Remaining args: #{@args.inspect}"
|
1325
|
+
error "Invalid option: #{o.args.first}. Please run " +
|
1326
|
+
"ctioga --help to have a list of valid ones"
|
1327
|
+
exit 1
|
1328
|
+
end
|
1329
|
+
rescue Exception => ex
|
1330
|
+
# Do not catch normal exit !
|
1331
|
+
if ex.is_a? SystemExit
|
1332
|
+
raise
|
1333
|
+
end
|
1334
|
+
fatal "ctioga encountered the following problem: #{ex.to_s}"
|
1335
|
+
debug "Error backtrace: #{ex.backtrace.join "\n"}"
|
1336
|
+
fatal "Aborting"
|
1337
|
+
exit 1
|
1338
|
+
end
|
1339
|
+
end
|
1340
|
+
end
|
1341
|
+
|
1342
|
+
# Setups up various figuremaker variables
|
1343
|
+
def setup_figure_maker(t)
|
1344
|
+
# Setting the TeX fontsize if default is not adequate
|
1345
|
+
t.tex_fontsize = @tex_fontsize if @tex_fontsize
|
1346
|
+
|
1347
|
+
# add hyperref preamble to have nice PDF comments:
|
1348
|
+
if @mark
|
1349
|
+
# We should use the \pdfinfo pdftex primitive, as
|
1350
|
+
# this will allow to have less surprises...
|
1351
|
+
t.tex_preview_preamble +=
|
1352
|
+
"\n\\pdfinfo {\n /Title (" +
|
1353
|
+
CTioga.pdftex_quote_string(@command_line) +
|
1354
|
+
")\n" + "/Creator(" +
|
1355
|
+
CTioga.pdftex_quote_string("ctioga #{Version::version}") +
|
1356
|
+
")\n}\n"
|
1357
|
+
end
|
1358
|
+
|
1359
|
+
if @debug_patterns
|
1360
|
+
debug_patterns(t)
|
1361
|
+
end
|
1362
|
+
|
1363
|
+
# We use Vincent's algorithm for major ticks when available ;-)...
|
1364
|
+
begin
|
1365
|
+
t.vincent_or_bill = true
|
1366
|
+
info "Using Vincent's algorithm for major ticks"
|
1367
|
+
rescue
|
1368
|
+
info "Using Bill's algorithm for major ticks"
|
1369
|
+
end
|
1370
|
+
|
1371
|
+
|
1372
|
+
return unless @root_object.number > 0
|
1373
|
+
|
1374
|
+
# Asking the theme to do something.
|
1375
|
+
theme_bod_hook(t)
|
1376
|
+
|
1377
|
+
# We put the command line used in the .tex file:
|
1378
|
+
t.tex_preview_preamble +=
|
1379
|
+
"% Produced by ctioga, with the command-line\n" +
|
1380
|
+
"% #{@command_line.gsub(/\n/,"\n% ")}\n"
|
1381
|
+
|
1382
|
+
if decimal_separator
|
1383
|
+
t.tex_preamble += <<'EOD'.gsub(',',decimal_separator) # Neat !
|
1384
|
+
\def\frenchsep#1{\frenchsepma#1\endoffrenchsep}
|
1385
|
+
\def\fseat#1{\frenchsepma}
|
1386
|
+
\def\frenchsepma{\futurelet\next\frenchsepmw}
|
1387
|
+
\def\frenchsepmw{\ifx\next\endoffrenchsep\let\endoffrenchsep=\relax%
|
1388
|
+
\else\if\next.\ifmmode\mathord,\else,\fi%
|
1389
|
+
\else\next\fi\expandafter
|
1390
|
+
\fseat\fi}
|
1391
|
+
EOD
|
1392
|
+
t.yaxis_numeric_label_tex = t.
|
1393
|
+
yaxis_numeric_label_tex.gsub('#1','\frenchsep{#1}')
|
1394
|
+
t.xaxis_numeric_label_tex = t.
|
1395
|
+
xaxis_numeric_label_tex.gsub('#1','\frenchsep{#1}')
|
1396
|
+
end
|
1397
|
+
|
1398
|
+
# Add custom preamble:
|
1399
|
+
t.tex_preview_preamble += @preamble
|
1400
|
+
|
1401
|
+
# If the initialization function was found, we call it
|
1402
|
+
if respond_to? :ctioga_init
|
1403
|
+
send :ctioga_init, t
|
1404
|
+
end
|
1405
|
+
|
1406
|
+
for funcall in @init_funcalls
|
1407
|
+
funcall.do(t)
|
1408
|
+
end
|
1409
|
+
|
1410
|
+
end
|
1411
|
+
|
1412
|
+
# Runs the code to make the actual figure.
|
1413
|
+
def do_figure(t)
|
1414
|
+
@root_object.do(t)
|
1415
|
+
end
|
1416
|
+
|
1417
|
+
# Ctioga's banner
|
1418
|
+
def banner
|
1419
|
+
<<"EOBANNER"
|
1420
|
+
This is ctioga version #{Version::version}
|
1421
|
+
ctioga is copyright (C) 2006, 2007, 2008 by Vincent Fourmond
|
1422
|
+
|
1423
|
+
ctioga comes with absolutely NO WARRANTY. This is
|
1424
|
+
free software, you are welcome to redistribute it under certain
|
1425
|
+
conditions. See the COPYING file in the original tarball for
|
1426
|
+
details. You are also welcome to contribute if you like it, see in
|
1427
|
+
the manual page where to ask.
|
1428
|
+
EOBANNER
|
1429
|
+
end
|
1430
|
+
|
1431
|
+
# Output a figure with the given name
|
1432
|
+
def output_figure(fig_name)
|
1433
|
+
# now, we plot the stuffs... if there is actually
|
1434
|
+
# something to be plotted ;-) !
|
1435
|
+
|
1436
|
+
@figmaker = t = FigureMaker.new
|
1437
|
+
# Cleanup code off by default, as this could cause problems for
|
1438
|
+
# legend pictures generation/EPS generation.
|
1439
|
+
# The files will get cleaned up anyway.
|
1440
|
+
t.autocleanup = false
|
1441
|
+
|
1442
|
+
|
1443
|
+
# Now, we must use real_size stuff !! (default 12cmx12cm)
|
1444
|
+
# This is not part of setup_figure_maker, as it can strongly interfere
|
1445
|
+
# when ctioga is used to produce a figure.
|
1446
|
+
info "Producing PDF file of real size #{@real_size}"
|
1447
|
+
setup_real_size(t)
|
1448
|
+
|
1449
|
+
# And, after that, the generic setup of the FigureMaker object.
|
1450
|
+
setup_figure_maker(t)
|
1451
|
+
|
1452
|
+
info "Generating file '#{fig_name}.pdf'"
|
1453
|
+
|
1454
|
+
# If fig_name is clearly a path, we split that bit out
|
1455
|
+
if File::basename(fig_name) != fig_name
|
1456
|
+
dir = File::dirname(fig_name)
|
1457
|
+
# If path is relative and output_directory is specified, we make
|
1458
|
+
# the path relative to output_dir
|
1459
|
+
if @output_directory && dir =~ /^[^\/~]/
|
1460
|
+
dir = File::join(@output_directory, dir)
|
1461
|
+
end
|
1462
|
+
t.save_dir = dir
|
1463
|
+
fig_name = File::basename(fig_name)
|
1464
|
+
elsif @output_directory
|
1465
|
+
t.save_dir = @output_directory
|
1466
|
+
end
|
1467
|
+
|
1468
|
+
t.def_figure(fig_name) {
|
1469
|
+
do_figure(t)
|
1470
|
+
}
|
1471
|
+
|
1472
|
+
debug "Contents of the main FigureMaker object: " +
|
1473
|
+
figmaker_options(t)
|
1474
|
+
|
1475
|
+
|
1476
|
+
if @fast
|
1477
|
+
# It isn't that fast, actually.
|
1478
|
+
info "Producing a fast version"
|
1479
|
+
# We output only the temporary PDF file
|
1480
|
+
class << t
|
1481
|
+
undef show_text
|
1482
|
+
# We replace show_text by show_marker, to at least get text
|
1483
|
+
# Doesn't work for axes and ticks... Pity !
|
1484
|
+
def show_text(dict)
|
1485
|
+
for key in %w{side shift position pos justification}
|
1486
|
+
dict.delete key
|
1487
|
+
end
|
1488
|
+
show_marker(dict)
|
1489
|
+
end
|
1490
|
+
end
|
1491
|
+
t.create_figure_temp_files(t.figure_index(fig_name))
|
1492
|
+
base_name = if t.save_dir
|
1493
|
+
File.join(t.save_dir,fig_name)
|
1494
|
+
else
|
1495
|
+
fig_name
|
1496
|
+
end
|
1497
|
+
# Remove the _figure.txt files
|
1498
|
+
File.rename(base_name + "_figure.pdf", base_name + ".pdf")
|
1499
|
+
else
|
1500
|
+
t.make_preview_pdf(t.figure_index(fig_name))
|
1501
|
+
end
|
1502
|
+
|
1503
|
+
if @eps
|
1504
|
+
info "Attempting to create an EPS file, watch out error messages"
|
1505
|
+
# First, we convert the _figure.pdf into eps with pdftops
|
1506
|
+
cmd = "pdftops -eps #{output_filename('_figure.pdf', fig_name)}"
|
1507
|
+
debug "Running #{cmd}"
|
1508
|
+
system cmd
|
1509
|
+
f = output_filename('.tex', fig_name)
|
1510
|
+
new = output_filename('.new.tex', fig_name)
|
1511
|
+
debug "Modifying #{f}"
|
1512
|
+
orig = open(f)
|
1513
|
+
out = open(new, "w")
|
1514
|
+
for l in orig
|
1515
|
+
l.gsub!('pdftex,','') # \usepackage[pdftex,...]...
|
1516
|
+
l.gsub!('_figure.pdf', '_figure.eps')
|
1517
|
+
out.print l
|
1518
|
+
end
|
1519
|
+
orig.close
|
1520
|
+
out.close
|
1521
|
+
|
1522
|
+
spawn "latex #{new}"
|
1523
|
+
spawn "dvips -o #{output_filename('.eps', fig_name)} -t unknown -T " +
|
1524
|
+
"#{@real_size.gsub(/x/,',')} #{output_filename('.new.dvi', fig_name)}"
|
1525
|
+
end
|
1526
|
+
|
1527
|
+
if @png
|
1528
|
+
# We create the PNG file:
|
1529
|
+
spawn "convert -density #{(@png_oversampling * 72).to_i} "+
|
1530
|
+
"#{output_filename('.pdf', fig_name)} -resize " +
|
1531
|
+
"#{@png_size.join('x')} #{output_filename('.png', fig_name)}"
|
1532
|
+
end
|
1533
|
+
|
1534
|
+
if @svg
|
1535
|
+
# We create the SVG file
|
1536
|
+
spawn "pdf2svg #{output_filename('.pdf', fig_name)} " +
|
1537
|
+
"#{output_filename('.svg', fig_name)}"
|
1538
|
+
end
|
1539
|
+
|
1540
|
+
|
1541
|
+
# Generating the separate legends if requested
|
1542
|
+
if not @separate_legend_styles.empty?
|
1543
|
+
index = 0
|
1544
|
+
info "Creating separate lengends"
|
1545
|
+
for style in @separate_legend_styles
|
1546
|
+
name = sprintf "%s_legend_%02d", fig_name, index
|
1547
|
+
index += 1
|
1548
|
+
t.def_figure(name) {
|
1549
|
+
t.set_device_pagesize(@separate_legend_width * 10,
|
1550
|
+
@separate_legend_height * 10)
|
1551
|
+
t.set_frame_sides(0,1,1,0)
|
1552
|
+
t.update_bbox(0,0)
|
1553
|
+
t.update_bbox(1,1)
|
1554
|
+
style.output_legend_pictogram(t)
|
1555
|
+
}
|
1556
|
+
# create_figure takes a number. It is part of Tioga's internals.
|
1557
|
+
t.create_figure_temp_files(t.figure_index(name))
|
1558
|
+
|
1559
|
+
|
1560
|
+
# Some post_processing to rename the files appropriately
|
1561
|
+
# and delete the unwanted _figure.txt file
|
1562
|
+
base_name = if t.save_dir
|
1563
|
+
File.join(t.save_dir,name)
|
1564
|
+
else
|
1565
|
+
name
|
1566
|
+
end
|
1567
|
+
# Remove the _figure.txt files
|
1568
|
+
begin
|
1569
|
+
File.rename(base_name + "_figure.pdf", base_name + ".pdf")
|
1570
|
+
if @eps # Create also EPS files:
|
1571
|
+
spawn "pdftops -eps #{base_name}.pdf"
|
1572
|
+
end
|
1573
|
+
File.delete(base_name + ".tex")
|
1574
|
+
File.delete(base_name + "_figure.txt")
|
1575
|
+
rescue
|
1576
|
+
warn "Files should have been deleted, but were not found"
|
1577
|
+
end
|
1578
|
+
end
|
1579
|
+
end
|
1580
|
+
|
1581
|
+
if @cleanup
|
1582
|
+
files_to_remove = %w(.tex .out .aux .log _figure.pdf _figure.txt) +
|
1583
|
+
%w(.new.tex .new.aux .new.dvi .new.log _figure.eps) # EPS-related files
|
1584
|
+
elsif @tex_cleanup
|
1585
|
+
files_to_remove = %w(.tex .out .aux .log .pdf)
|
1586
|
+
elsif @clean_all
|
1587
|
+
files_to_remove = %w(.tex .out .aux .log .pdf _figure.pdf _figure.txt) +
|
1588
|
+
%w(.new.tex .new.aux .new.dvi .new.log .eps _figure.eps) # EPS-related files
|
1589
|
+
|
1590
|
+
end
|
1591
|
+
if @viewer
|
1592
|
+
# we display the output file:
|
1593
|
+
info "Starting the viewer #{@viewer} as requested"
|
1594
|
+
pdf_file = if t.save_dir
|
1595
|
+
File.join(t.save_dir,fig_name+".pdf")
|
1596
|
+
else
|
1597
|
+
fig_name+".pdf"
|
1598
|
+
end
|
1599
|
+
system("#{@viewer} #{pdf_file}")
|
1600
|
+
end
|
1601
|
+
|
1602
|
+
if files_to_remove
|
1603
|
+
files_to_remove.collect! {|s|
|
1604
|
+
output_filename(s, fig_name)
|
1605
|
+
}
|
1606
|
+
files_to_remove << "pdflatex.log"
|
1607
|
+
info "Cleaning up temporary files"
|
1608
|
+
debug "Cleaning #{files_to_remove.join ' '}"
|
1609
|
+
files_to_remove.each do |f|
|
1610
|
+
begin
|
1611
|
+
File.delete(f)
|
1612
|
+
rescue
|
1613
|
+
debug "File #{f} absent, not deleted"
|
1614
|
+
end
|
1615
|
+
end
|
1616
|
+
end
|
1617
|
+
|
1618
|
+
end
|
1619
|
+
|
1620
|
+
# Runs the plots, using the arguments given in the command-line.
|
1621
|
+
# Now delegates to some other small functions.
|
1622
|
+
def run(args)
|
1623
|
+
|
1624
|
+
# First, we parse command-line arguments:
|
1625
|
+
parse_command_line_arguments(args)
|
1626
|
+
|
1627
|
+
# Then, we spit the output:
|
1628
|
+
output_figure(@fig_name)
|
1629
|
+
|
1630
|
+
end
|
1631
|
+
|
1632
|
+
end
|
1633
|
+
|
1634
|
+
# Takes a string a returns a quoted version that should be able
|
1635
|
+
# to go through shell expansion. For now, it won't work with
|
1636
|
+
# strings containing ', but that's already a good help.
|
1637
|
+
def CTioga.shell_quote_string(str)
|
1638
|
+
if str =~ /[\s"*$()\[\]{}';]/
|
1639
|
+
if str =~ /'/
|
1640
|
+
a = str.gsub(/(["$\\])/) { "\\#$1" }
|
1641
|
+
return "\"#{a}\""
|
1642
|
+
else
|
1643
|
+
return "'#{str}'"
|
1644
|
+
end
|
1645
|
+
else
|
1646
|
+
return str
|
1647
|
+
end
|
1648
|
+
end
|
1649
|
+
|
1650
|
+
# This function provides a 'command-line' interface from within Tioga !
|
1651
|
+
def CTioga.define_tioga_figure(t, name, *args)
|
1652
|
+
t.def_figure(name) {
|
1653
|
+
CTioga.show_tioga_plot(t, *args)
|
1654
|
+
}
|
1655
|
+
end
|
1656
|
+
|
1657
|
+
# This function provides a 'command-line' interface from within Tioga !
|
1658
|
+
def CTioga.show_tioga_plot(t, *args)
|
1659
|
+
plot = PlotMaker.new
|
1660
|
+
plot.parse_command_line_arguments(args)
|
1661
|
+
plot.setup_figure_maker(t)
|
1662
|
+
|
1663
|
+
width = 72 * t.
|
1664
|
+
convert_output_to_inches(t.
|
1665
|
+
convert_figure_to_output_dx(t.convert_frame_to_figure_dx(1)))
|
1666
|
+
height = 72 * t.
|
1667
|
+
convert_output_to_inches(t.
|
1668
|
+
convert_figure_to_output_dy(t.convert_frame_to_figure_dy(1)))
|
1669
|
+
|
1670
|
+
plot.set_root_frame(0, width, height, 0)
|
1671
|
+
|
1672
|
+
plot.do_figure(t)
|
1673
|
+
end
|
1674
|
+
|
1675
|
+
|
1676
|
+
end
|
1677
|
+
|