ctioga2 0.10.1 → 0.11
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.
- checksums.yaml +7 -0
- data/Changelog +18 -0
- data/bin/ctioga2 +28 -0
- data/lib/ctioga2/commands/commands.rb +1 -0
- data/lib/ctioga2/commands/doc/documentation-commands.rb +13 -0
- data/lib/ctioga2/commands/doc/help.rb +3 -2
- data/lib/ctioga2/commands/doc/html.rb +48 -0
- data/lib/ctioga2/commands/doc/introspection.rb +2 -2
- data/lib/ctioga2/commands/general-types.rb +10 -0
- data/lib/ctioga2/commands/parsers/file.rb +23 -2
- data/lib/ctioga2/data/backends/backends.rb +1 -2
- data/lib/ctioga2/data/backends/backends/smath.rb +129 -0
- data/lib/ctioga2/data/backends/backends/text.rb +1 -0
- data/lib/ctioga2/data/dataset.rb +1 -1
- data/lib/ctioga2/data/stack.rb +13 -3
- data/lib/ctioga2/graphics/elements.rb +2 -0
- data/lib/ctioga2/graphics/elements/containers.rb +3 -1
- data/lib/ctioga2/graphics/elements/element.rb +194 -5
- data/lib/ctioga2/graphics/elements/gradient-region.rb +5 -2
- data/lib/ctioga2/graphics/elements/histogram.rb +7 -2
- data/lib/ctioga2/graphics/elements/plot-elements.rb +88 -0
- data/lib/ctioga2/graphics/elements/primitive.rb +28 -12
- data/lib/ctioga2/graphics/elements/region.rb +6 -1
- data/lib/ctioga2/graphics/elements/style-lists.rb +2 -2
- data/lib/ctioga2/graphics/elements/subplot.rb +3 -3
- data/lib/ctioga2/graphics/elements/tangent.rb +5 -8
- data/lib/ctioga2/graphics/generator.rb +10 -0
- data/lib/ctioga2/graphics/geometry.rb +96 -0
- data/lib/ctioga2/graphics/legends.rb +4 -2
- data/lib/ctioga2/graphics/legends/area.rb +12 -4
- data/lib/ctioga2/graphics/root.rb +16 -14
- data/lib/ctioga2/graphics/styles.rb +5 -2
- data/lib/ctioga2/graphics/styles/arrows.rb +5 -0
- data/lib/ctioga2/graphics/styles/axes.rb +1 -1
- data/lib/ctioga2/graphics/styles/base.rb +95 -14
- data/lib/ctioga2/graphics/styles/curve.rb +8 -0
- data/lib/ctioga2/graphics/styles/drawable.rb +35 -48
- data/lib/ctioga2/graphics/styles/factory.rb +23 -23
- data/lib/ctioga2/graphics/styles/fill.rb +268 -0
- data/lib/ctioga2/graphics/styles/plot.rb +90 -46
- data/lib/ctioga2/graphics/styles/sets.rb +3 -0
- data/lib/ctioga2/graphics/styles/{sheet.rb → styles.rb} +70 -160
- data/lib/ctioga2/graphics/styles/stylesheet.rb +355 -0
- data/lib/ctioga2/graphics/styles/texts.rb +4 -2
- data/lib/ctioga2/graphics/styles/ticks.rb +44 -4
- data/lib/ctioga2/graphics/subplot-commands.rb +84 -9
- data/lib/ctioga2/graphics/types.rb +1 -1
- data/lib/ctioga2/graphics/types/dimensions.rb +40 -0
- data/lib/ctioga2/graphics/types/grid.rb +21 -5
- data/lib/ctioga2/graphics/types/point.rb +2 -1
- data/lib/ctioga2/log.rb +5 -1
- data/lib/ctioga2/metabuilder/types/styles.rb +11 -7
- data/lib/ctioga2/plotmaker.rb +2 -0
- data/lib/ctioga2/utils.rb +21 -6
- data/lib/ctioga2/version.rb +2 -2
- metadata +105 -108
@@ -0,0 +1,355 @@
|
|
1
|
+
# stylesheet.rb: handling of style sheets
|
2
|
+
# copyright (c) 2014 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 'ctioga2/utils'
|
15
|
+
require 'ctioga2/log'
|
16
|
+
|
17
|
+
require 'ctioga2/graphics/coordinates'
|
18
|
+
|
19
|
+
# This module contains all the classes used by ctioga
|
20
|
+
module CTioga2
|
21
|
+
|
22
|
+
module Graphics
|
23
|
+
|
24
|
+
module Styles
|
25
|
+
|
26
|
+
# A StyleSheet is a simple implementation of CSS-like facilities
|
27
|
+
# in ctioga2. As in CSS, it recognizes id and classes, and type.
|
28
|
+
class StyleSheet
|
29
|
+
|
30
|
+
# An element in a XPath
|
31
|
+
class XPathElement
|
32
|
+
|
33
|
+
# The type -- or nil if not selecting on type
|
34
|
+
attr_accessor :obj_type
|
35
|
+
|
36
|
+
# The class -- or nil if not selecting on class
|
37
|
+
attr_accessor :obj_class
|
38
|
+
|
39
|
+
# The ID, or nil if not selecting on id
|
40
|
+
attr_accessor :obj_id
|
41
|
+
|
42
|
+
# If this flag is on, the object has to be the direct parent
|
43
|
+
# of the child below.
|
44
|
+
attr_accessor :direct_parent
|
45
|
+
|
46
|
+
# A XPathElement is a series of elements (\w and - allowed),
|
47
|
+
# optionnally followed by a > sign. No space allowed,
|
48
|
+
# excepted before the > sign
|
49
|
+
def parse_string(txt)
|
50
|
+
|
51
|
+
|
52
|
+
rest = txt.gsub(/([.#]?)([\w-]+)/) do |x|
|
53
|
+
if $1 == "."
|
54
|
+
@obj_class = $2
|
55
|
+
elsif $1 == "#"
|
56
|
+
@obj_id = $2
|
57
|
+
else
|
58
|
+
@obj_type = $2
|
59
|
+
end
|
60
|
+
""
|
61
|
+
end
|
62
|
+
|
63
|
+
if rest =~ /^\s*\*?\s*(>)?$/
|
64
|
+
if $1 == ">"
|
65
|
+
@direct_parent = true
|
66
|
+
end
|
67
|
+
else
|
68
|
+
raise "Incorrect XPath element: #{txt}"
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.from_text(txt)
|
74
|
+
a = XPathElement.new
|
75
|
+
a.parse_string(txt)
|
76
|
+
return a
|
77
|
+
end
|
78
|
+
|
79
|
+
def matches?(obj)
|
80
|
+
if @obj_type && (obj.style_name != @obj_type)
|
81
|
+
return false
|
82
|
+
end
|
83
|
+
if @obj_class && !obj.object_classes.include?(@obj_class)
|
84
|
+
return false
|
85
|
+
end
|
86
|
+
if @obj_id && (obj.object_id != @obj_id)
|
87
|
+
return false
|
88
|
+
end
|
89
|
+
return true
|
90
|
+
end
|
91
|
+
|
92
|
+
def to_s
|
93
|
+
a = @obj_type || ""
|
94
|
+
if @obj_id
|
95
|
+
a += "##{@obj_id}"
|
96
|
+
end
|
97
|
+
if @obj_class
|
98
|
+
a += ".#{@obj_class}"
|
99
|
+
end
|
100
|
+
if a.size == 0
|
101
|
+
a = "*"
|
102
|
+
end
|
103
|
+
if @direct_parent
|
104
|
+
a += " >"
|
105
|
+
end
|
106
|
+
return a
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
# p self.from_text("*").to_s
|
111
|
+
# p self.from_text("bidule").to_s
|
112
|
+
# p self.from_text(".bidule").to_s
|
113
|
+
# p self.from_text("#bidule").to_s
|
114
|
+
# p self.from_text("b#a.cls").to_s
|
115
|
+
# p self.from_text(".cs").to_s
|
116
|
+
# p self.from_text(".cs#truc").to_s
|
117
|
+
# p self.from_text("b#a.cls >").to_s
|
118
|
+
|
119
|
+
# p self.from_text("$sdf")
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
# An XPath, ie a series of XPathElement from outermost to
|
124
|
+
# innermost.
|
125
|
+
class XPath
|
126
|
+
|
127
|
+
# From the innermost to outermost
|
128
|
+
attr_accessor :elements
|
129
|
+
|
130
|
+
def parse_string(txt)
|
131
|
+
@elements = txt.gsub(/\s*>/, '>').split(/\s+/).reverse.map do |x|
|
132
|
+
XPathElement.from_text(x)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.from_text(txt)
|
137
|
+
a = XPath.new
|
138
|
+
a.parse_string(txt)
|
139
|
+
return a
|
140
|
+
end
|
141
|
+
|
142
|
+
# Returns true if the innermost element has a type
|
143
|
+
def typed?
|
144
|
+
if @elements.first.obj_type
|
145
|
+
return true
|
146
|
+
else
|
147
|
+
return false
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def matches?(obj)
|
152
|
+
return match_chain(obj, @elements)
|
153
|
+
end
|
154
|
+
|
155
|
+
# Returns a normalized version of the XPATH, that can be
|
156
|
+
# used as a hash key.
|
157
|
+
def to_s
|
158
|
+
return @elements.reverse.map { |x| x.to_s }.join(" ")
|
159
|
+
end
|
160
|
+
|
161
|
+
protected
|
162
|
+
|
163
|
+
def match_chain(obj, elems)
|
164
|
+
if ! elems.first.matches?(obj)
|
165
|
+
return false
|
166
|
+
end
|
167
|
+
|
168
|
+
if elems.size <= 1
|
169
|
+
return true
|
170
|
+
end
|
171
|
+
|
172
|
+
np = obj.object_parent
|
173
|
+
if ! np
|
174
|
+
return false
|
175
|
+
end
|
176
|
+
if elems[1].direct_parent
|
177
|
+
return match_chain(np, elems[1..-1])
|
178
|
+
else
|
179
|
+
while np
|
180
|
+
if match_chain(np, elems[1..-1])
|
181
|
+
return true
|
182
|
+
else
|
183
|
+
np = np.object_parent
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
return false
|
188
|
+
end
|
189
|
+
|
190
|
+
# p self.from_text("b#a.cls #stuff").to_s
|
191
|
+
# p self.from_text("b.cls#a > foo").to_s
|
192
|
+
|
193
|
+
end
|
194
|
+
|
195
|
+
# A style bucket, a hash 'key' => 'value' associated with a
|
196
|
+
# unique xpath
|
197
|
+
class Bucket
|
198
|
+
|
199
|
+
# The style information (a string->string hash).
|
200
|
+
#
|
201
|
+
# Not that it can actually be a string->typed stuff, since
|
202
|
+
# most types accept that !
|
203
|
+
attr_accessor :style
|
204
|
+
|
205
|
+
# All the XPath associated with this style information
|
206
|
+
attr_accessor :xpath
|
207
|
+
|
208
|
+
# The xpath text initially used
|
209
|
+
attr_accessor :xname
|
210
|
+
|
211
|
+
def initialize(xp)
|
212
|
+
@xname = xp
|
213
|
+
@xpath = XPath.from_text(xp)
|
214
|
+
@style = {}
|
215
|
+
end
|
216
|
+
|
217
|
+
def matches?(obj)
|
218
|
+
if @xpath.matches?(obj)
|
219
|
+
return true
|
220
|
+
else
|
221
|
+
return false
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# Returns the style, but with all the options normalized to
|
226
|
+
# lowercase and without
|
227
|
+
def normalized_style
|
228
|
+
stl = {}
|
229
|
+
for k,v in @style
|
230
|
+
stl[k.gsub(/-/,"_").downcase] = v
|
231
|
+
end
|
232
|
+
return stl
|
233
|
+
end
|
234
|
+
|
235
|
+
# # Returns the style for the given object. DOES NOT CHECK
|
236
|
+
# # that the object belongs to this Bucket.
|
237
|
+
# def style_for(obj)
|
238
|
+
# @cache ||= {}
|
239
|
+
# if ! @cache.key?(obj.style_name)
|
240
|
+
# @cache[obj.style_name] = obj.style_class.from_hash(@style)
|
241
|
+
# end
|
242
|
+
# return @cache[obj.style_name]
|
243
|
+
# end
|
244
|
+
end
|
245
|
+
|
246
|
+
# OK, so now we begin the StyleSheet class per se.
|
247
|
+
#
|
248
|
+
# The stylesheet class is but an ordered list of buckets.
|
249
|
+
|
250
|
+
# The list of buckets
|
251
|
+
attr_accessor :buckets
|
252
|
+
|
253
|
+
# A hash "full xpath" -> bucket name, so that one can update a
|
254
|
+
# bucket instead of just adding to it.
|
255
|
+
attr_accessor :buckets_by_xpath
|
256
|
+
|
257
|
+
def initialize()
|
258
|
+
@buckets = []
|
259
|
+
@buckets_by_xpath = {}
|
260
|
+
end
|
261
|
+
|
262
|
+
def set_style(xpath, style)
|
263
|
+
for f in xpath.split(/\s*,\s*/)
|
264
|
+
bkt = get_bucket(f)
|
265
|
+
bkt.style = style
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
# @todo Maybe update and set should just add a new bucket at
|
270
|
+
# the end, so that it overrides the previous ones anyway ?
|
271
|
+
def update_style(xpath, style, default_type = nil)
|
272
|
+
for f in xpath.split(/\s*,\s*/)
|
273
|
+
xp = XPath.from_text(f)
|
274
|
+
if default_type
|
275
|
+
xp.elements.first.obj_type ||= default_type
|
276
|
+
end
|
277
|
+
bkt = get_bucket(xp)
|
278
|
+
bkt.style.merge!(style)
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def style_hash_for(obj)
|
283
|
+
stl = {}
|
284
|
+
# p [:cls, obj.class, obj.object_id, obj.object_classes]
|
285
|
+
for bkt in @buckets
|
286
|
+
# p [bkt.xpath, bkt.matches?(obj), bkt.style]
|
287
|
+
if bkt.matches?(obj)
|
288
|
+
stl.merge!(bkt.normalized_style)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
# p [:s, stl]
|
293
|
+
cls = obj.style_class
|
294
|
+
# p cls.options_hash.keys
|
295
|
+
rv = cls.convert_string_hash(stl)
|
296
|
+
# p [:t, rv]
|
297
|
+
return rv
|
298
|
+
end
|
299
|
+
|
300
|
+
def style_for(obj)
|
301
|
+
return obj.style_class.from_hash(style_hash_for(obj))
|
302
|
+
end
|
303
|
+
|
304
|
+
def self.style_sheet
|
305
|
+
@style_sheet ||= StyleSheet.new
|
306
|
+
@style_sheet
|
307
|
+
end
|
308
|
+
|
309
|
+
def self.style_hash_for(obj)
|
310
|
+
return self.style_sheet.style_hash_for(obj)
|
311
|
+
end
|
312
|
+
|
313
|
+
def self.style_for(obj)
|
314
|
+
return self.style_sheet.style_for(obj)
|
315
|
+
end
|
316
|
+
|
317
|
+
def update_from_file(file)
|
318
|
+
end
|
319
|
+
|
320
|
+
def update_from_string(str)
|
321
|
+
# First, strip all comments from the string
|
322
|
+
str = str.gsub(/\/\*.*?\*\//m, '')
|
323
|
+
|
324
|
+
str.gsub(/^\s*((?:[.#]?[,\w-]+\s*>?\s*)+)\s*\{([^}]+)\}/m) do |x|
|
325
|
+
xpath = $1
|
326
|
+
smts = $2.split(/\s*;\s*/)
|
327
|
+
|
328
|
+
stl = {}
|
329
|
+
for s in smts
|
330
|
+
if s =~ /\s*([\w-]+)\s*:\s*(.*)/m
|
331
|
+
stl[$1] = $2
|
332
|
+
else
|
333
|
+
error { "Style not understood: #{s}" }
|
334
|
+
end
|
335
|
+
end
|
336
|
+
update_style(xpath, stl)
|
337
|
+
end
|
338
|
+
# p self
|
339
|
+
end
|
340
|
+
|
341
|
+
protected
|
342
|
+
|
343
|
+
def get_bucket(xp)
|
344
|
+
xpath = xp.to_s
|
345
|
+
if ! @buckets_by_xpath.key? xpath
|
346
|
+
@buckets << Bucket.new(xpath)
|
347
|
+
@buckets_by_xpath[xpath] = @buckets.last
|
348
|
+
end
|
349
|
+
return @buckets_by_xpath[xpath]
|
350
|
+
end
|
351
|
+
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
@@ -44,12 +44,12 @@ module CTioga2
|
|
44
44
|
# The vertical alignment
|
45
45
|
typed_attribute :alignment, 'alignment'
|
46
46
|
|
47
|
-
alias_for :valign, :alignment
|
47
|
+
alias_for :valign, :alignment, true
|
48
48
|
|
49
49
|
# The horizontal alignment
|
50
50
|
typed_attribute :justification, 'justification'
|
51
51
|
|
52
|
-
alias_for :halign, :justification
|
52
|
+
alias_for :halign, :justification, true
|
53
53
|
|
54
54
|
# A text width
|
55
55
|
typed_attribute :text_width, "dimension-or-no"
|
@@ -110,6 +110,8 @@ module CTioga2
|
|
110
110
|
# Prepares the dictionnary for use with show_text
|
111
111
|
def prepare_show_text_dict(t, text, x_or_loc, y = nil, measure = nil)
|
112
112
|
dict = self.hash_for_tioga(t)
|
113
|
+
# get rid of uncommented stuff problems
|
114
|
+
text = "#{text}%\n"
|
113
115
|
|
114
116
|
loc = nil
|
115
117
|
|
@@ -26,6 +26,9 @@ module CTioga2
|
|
26
26
|
|
27
27
|
# This class describes where to place ticks on the target axis
|
28
28
|
# and how to label them.
|
29
|
+
#
|
30
|
+
# I really should drop the call to Tioga altogether. It makes
|
31
|
+
# everything too complicated.
|
29
32
|
class AxisTicks < BasicStyle
|
30
33
|
|
31
34
|
include Log
|
@@ -51,6 +54,11 @@ module CTioga2
|
|
51
54
|
# Separation between major ticks. Overriden by the major list
|
52
55
|
typed_attribute :major_delta, "float"
|
53
56
|
|
57
|
+
# Or physical separation between ticks
|
58
|
+
#
|
59
|
+
# @todo Make a major_min_sep ?
|
60
|
+
typed_attribute :major_sep, 'dimension'
|
61
|
+
|
54
62
|
# Approximate number of major ticks
|
55
63
|
typed_attribute :major_number, "integer"
|
56
64
|
|
@@ -63,6 +71,11 @@ module CTioga2
|
|
63
71
|
# Number of minor ticks between major ticks
|
64
72
|
typed_attribute :minor_number, "integer"
|
65
73
|
|
74
|
+
# This needs a little more careful thinking. Or just a min
|
75
|
+
# separation ?
|
76
|
+
# Physical separation between the minor ticks
|
77
|
+
typed_attribute :minor_sep_min, 'dimension'
|
78
|
+
|
66
79
|
|
67
80
|
|
68
81
|
# The list of labels
|
@@ -96,13 +109,22 @@ module CTioga2
|
|
96
109
|
xl, xr = xr, xl
|
97
110
|
end
|
98
111
|
|
112
|
+
mn = if @major_number
|
113
|
+
@major_number
|
114
|
+
elsif @major_sep
|
115
|
+
dx = @major_sep.to_figure(t, info['vertical'] ? :y : :x)
|
116
|
+
mn = (xr - xl)/dx
|
117
|
+
else
|
118
|
+
nil
|
119
|
+
end
|
120
|
+
|
99
121
|
if @major
|
100
122
|
ret['minor_ticks'] = Dobjects::Dvector.new
|
101
123
|
ret['major_ticks'] = Dobjects::Dvector.new(@major)
|
102
124
|
|
103
125
|
fmt ||= "$%g$"
|
104
|
-
elsif @major_delta ||
|
105
|
-
delta = @major_delta || Utils::closest_subdivision(( (xr - xl)
|
126
|
+
elsif @major_delta || mn
|
127
|
+
delta = @major_delta || Utils::closest_subdivision(( (xr - xl)/mn))
|
106
128
|
ret['major_ticks'] = Utils::integer_subdivisions(xl, xr,
|
107
129
|
delta)
|
108
130
|
fmt ||= "$%g$"
|
@@ -110,8 +132,26 @@ module CTioga2
|
|
110
132
|
|
111
133
|
if @minor
|
112
134
|
ret['minor_ticks'] = Dobjects::Dvector.new(@minor)
|
113
|
-
elsif @minor_delta || delta
|
114
|
-
|
135
|
+
elsif @minor_delta || @minor_sep || delta
|
136
|
+
|
137
|
+
dt = if @minor_delta
|
138
|
+
@minor_delta
|
139
|
+
else
|
140
|
+
nb = if @minor_number
|
141
|
+
@minor_number
|
142
|
+
elsif @minor_sep_min
|
143
|
+
dx = @minor_sep_min.to_figure(t, info['vertical'] ? :y : :x)
|
144
|
+
mx = ((delta/dx).round - 1)
|
145
|
+
if mx > 3
|
146
|
+
3
|
147
|
+
else
|
148
|
+
mx
|
149
|
+
end
|
150
|
+
else
|
151
|
+
3
|
152
|
+
end
|
153
|
+
delta/(nb+1)
|
154
|
+
end
|
115
155
|
ret['minor_ticks'] = Utils::integer_subdivisions(xl, xr,
|
116
156
|
dt)
|
117
157
|
end
|