1
# factory.rb: an object in charge of generating the style for Curves
2
# copyright (c) 2009 by Vincent Fourmond
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.
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).
14
require 'ctioga2/utils'
17
# This module contains all the classes used by ctioga
20
Version::register_svn_info('$Revision: 217 $', '$Date: 2010-12-31 16:18:20 +0100 (Fri, 31 Dec 2010) $')
26
# This object is in charge of the generation of the CurveStyle
27
# object for the next curve to be drawn.
28
class CurveStyleFactory
32
# A private class that defines a parameter for the Factory
33
class CurveStyleFactoryParameter
35
# The code-like name of the parameter
38
# The Commands::CommandType of the parameter
41
# The pre-defined sets available to use with that
42
# parameter. It is a hash.
45
# The description of the parameter.
46
attr_accessor :description
48
# The short option for setting the parameter directly from
50
attr_accessor :short_option
52
# The MetaBuilder::Type object that can convert a String to
53
# an Array suitable for use with CircularArray.
54
attr_accessor :sets_type
56
# If this attribute is on, then CurveStyleFactory will not
57
# generate commands for this parameter, only the option.
58
attr_accessor :disable_commands
61
# Creates a new CurveStyleFactoryParameter object.
62
def initialize(name, type, sets, description,
63
short_option = nil, disable_cmds = false)
65
@type = Commands::CommandType.get_type(type)
67
@description = description
68
@short_option = short_option
69
@disable_commands = disable_cmds
71
## \todo it is not very satisfying to mix CommandTypes and
72
# MetaBuilder::Type on the same level.
75
MetaBuilder::Type.get_type({
77
:subtype => @type.type,
83
# Returns a suitable default set for the given object.
85
return nil unless @sets
86
if @sets.key? 'default'
87
return @sets['default']
97
# Switch some parameter back to automatic
100
# Sets some parameter to _false_.
101
DisableRE = /no(ne)?|off/i
103
# If that matches, we use the value as a link to other values.
104
LinkRE = /(?:=|->)(\S+)/
107
# Creates a new parameter for the style factory.
109
# \todo add a way to add some more text to the description;
110
# possibly a self.describe_parameter function ?
111
def self.define_parameter(target, name, type, sets, description,
112
short_option = nil, disable_cmds = false)
113
# We define two new types:
114
# - first, the color-or-auto type:
115
base_type = Commands::CommandType.get_type(type)
117
if ! Commands::Interpreter.type("#{base_type.name}-or-auto")
118
mb_type = base_type.type.dup
119
mb_type.re_shortcuts = (mb_type.re_shortcuts ?
120
mb_type.re_shortcuts.dup : {})
122
mb_type.re_shortcuts[AutoRE] = 'auto'
123
mb_type.re_shortcuts[DisableRE] = false
125
# Add passthrough for expressions such as =color...
126
mb_type.passthrough = LinkRE
128
# Now, register a type for the type or automatic.
129
CmdType.new("#{base_type.name}-or-auto", mb_type,
130
"Same thing as {type:#{base_type.name}}, or @auto@ to let the style factory handle automatically.")
134
if sets and ! Commands::Interpreter.type("#{base_type.name}-set")
135
# Now, register a type for the type or automatic.
136
CmdType.new("#{base_type.name}-set",{
138
:subtype => base_type.type,
141
"Sets of {type: #{base_type.name}}")
144
CurveStyleFactoryParameter.new(name, type, sets,
145
description, short_option,
148
@parameters[target] = param
150
@name_to_target ||= {}
151
@name_to_target[name] = target
154
# Returns the Hash containing the class parameters.
156
return @parameters || {}
160
# Returns the Hash containing the class parameters.
161
def self.name_to_target
162
return @name_to_target
165
# The CmdGroup for stylistic information about
168
CmdGroup.new('curve-style', "Curves styles",
169
"Set stylistic details of curves or other object drawn from data", 1)
172
# Creates two commands for each parameter of the object:
173
# * a command to set the override
174
# * a command to choose the sets.
175
def self.create_commands
176
parameters.each do |target, param|
177
next if param.disable_commands
179
Cmd.new("#{param.name}",
183
CmdArg.new("#{param.type.name}-or-auto")
185
"Sets the #{param.description} for subsequent curves",
186
"Sets the #{param.description} for subsequent curves, until cancelled with @auto@ as argument.", CurveStyleGroup) do |plotmaker, value|
187
plotmaker.curve_generator.style_factory.
188
set_parameter_override(target, value)
192
next if param.disable_commands
194
Cmd.new("#{param.name}-set",
196
"--#{param.name}-set",
198
CmdArg.new("#{param.type.name}-set")
200
"Chooses a set for the #{param.description} of subsequent curves",
201
"Chooses a set for the #{param.description} of subsequent curves. Also sets {command: #{param.name}} to @auto@, so that the set takes effect immediately",
202
CurveStyleGroup) do |plotmaker, value|
203
plotmaker.curve_generator.style_factory.
204
set_parameter_set(target, value)
205
plotmaker.curve_generator.style_factory.
206
set_parameter_override(target, 'auto')
212
# This function returns a hash suitable for use with the plot
213
# command as optional arguments, that will end up as the
214
# _one_time_ hash in #next.
215
def self.plot_optional_arguments
217
for option_name, param in @parameters
219
CmdArg.new(param.type)
222
# Here, we add the support for a /legend= option
223
args['legend'] = CmdArg.new('text')
224
@name_to_target['legend'] = 'legend'
230
# A hash containing values that override default ones derived
231
# from the CircularArray objects.
232
attr_accessor :override_parameters
234
# A hash of CircularArray objects.
235
attr_accessor :parameter_carrays
238
# Creates a new CurveStyleFactory.
240
# Overrides as in the first ctioga
241
@override_parameters = {
242
'line_style' => LineStyles::Solid,
243
'marker_marker' => false,
244
'marker_scale' => 0.5
246
@parameters_carrays = {}
247
for target, param in self.class.parameters
248
set = param.default_set
250
@parameters_carrays[target] = CircularArray.new(set)
255
# Gets the style for the next curve. The _one_time_ hash
256
# contains values 'parameter name' (name, and not target) =>
257
# value that are used for this time only.
258
def next(one_time = {})
260
for target, array in @parameters_carrays
261
base[target] = array.next
263
base.merge!(@override_parameters)
264
base.merge!(hash_name_to_target(one_time))
265
return CurveStyle.from_hash(resolve_links(base))
270
# Sets the override for the given parameter. This corresponds
271
# to fixing manually the corresponding element until the
272
# override is removed, by a call with a _value_ that matches
275
# The _value_ should ideally be a String that is further
276
# converted to the appropriate type. Non-string objects will
278
def set_parameter_override(target, value)
279
param = get_parameter(target)
280
# Perform automatic type conversion only on strings.
281
if value.is_a? String
283
@override_parameters.delete(target)
285
elsif value =~ LinkRE
287
convert = self.class.name_to_target
289
value = "=#{convert[t]}".to_sym
291
warn { "No known key: #{t}, treating as auto" }
292
@override_parameters.delete(target)
296
elsif value =~ DisableRE
299
value = param.type.string_to_type(value)
303
@override_parameters[target] = value
306
# Sets the CircularArray set corresponding to the named
307
def set_parameter_set(target, value)
308
param = get_parameter(target)
309
# Perform automatic type conversion only on strings.
310
if value.is_a? String
311
value = param.sets_type.string_to_type(value)
313
@parameters_carrays[target].set = value
316
# Now, the parameters:
319
define_parameter 'line_color', 'color', 'color',
320
Sets::ColorSets, "color", "-c"
322
define_parameter 'line_width', 'line-width', 'float',
323
Sets::LineWidthSets, "line width", nil
325
define_parameter 'line_style', 'line-style', 'line-style',
326
Sets::LineStyleSets, "line style", nil
329
define_parameter 'marker_marker', 'marker', 'marker',
330
Sets::MarkerSets, "marker", '-m'
332
define_parameter 'marker_color', 'marker-color', 'color',
333
Sets::ColorSets, "marker color", nil
335
define_parameter 'marker_scale', 'marker-scale', 'float',
336
Sets::LineWidthSets, "marker scale", nil
339
define_parameter 'error_bar_color', 'error-bar-color', 'color',
340
Sets::ColorSets, "error bar color", nil
343
define_parameter 'location_xaxis', 'xaxis', 'axis',
344
nil, "X axis", nil, true
346
define_parameter 'location_yaxis', 'yaxis', 'axis',
347
nil, "Y axis", nil, true
350
define_parameter 'fill_y0', 'fill', 'fill-until',
351
{}, "Fill until", nil
353
define_parameter 'fill_color', 'fill-color', 'color',
354
Sets::ColorSets, "fill color", nil
356
define_parameter 'fill_transparency', 'fill-transparency', 'float',
357
{}, "Fill transparency", nil
360
define_parameter 'region_position', 'region-side', 'region-side',
361
{"default" => [:above, :below]}, "region side", nil
364
define_parameter 'style', 'style', 'text',
365
{}, "Path style", nil
367
# Only for xyz-maps or xy-parametric
368
define_parameter 'color_map', 'color-map', 'colormap',
369
nil, "Color map", nil
372
define_parameter 'zaxis', 'zaxis', 'text',
373
nil, "Name for the Z axis", nil
375
## @todo For xy-parametric, there should be a way to specify
376
## to which z value the maps apply (ie lines = y2, marker =
377
## y3...). Although for readability, it is probably better
379
define_parameter 'marker_color_map', 'marker-color-map', 'colormap',
380
nil, "Marker color map", nil
382
define_parameter 'split_on_nan', 'split-on-nan', 'boolean',
383
nil, "Split on NaN", nil
386
# And finally, we register all necessary commands...
389
# A constant suitable for use as the optional arguments of the
391
PlotCommandOptions = plot_optional_arguments
395
# Returns the CurveFactoryParameterType object corresponding
396
# to the named parameter.
397
def get_parameter(target)
398
if ! parameters.key? target
399
raise "Unkown parameter: #{target}"
401
return parameters[target]
405
# Returns the class parameters hash
407
return self.class.parameters
410
# Converts the one-time parameters, which is a hash whose keys
411
# are the names of the parameters to targets.
412
def hash_name_to_target(h)
414
convert = self.class.name_to_target
417
retval[convert[k]] = v
419
warn { "Unkown key for hash_name_to_target: #{k}" }
425
# Resolve potential links in the form of :=stuff within the
426
# given hash, and returns a new version of the hash.
428
# \warning the _h_ parameter is completely destroyed in the
433
# First, copy plain values
435
if v.is_a?(Symbol) && v.to_s =~ /^(=|->)/
443
# Now, we will iterate over the remaining things; we will
444
# stop with an error if the number of remaining keys does
445
# not decrease after one step
449
v.to_s =~ /^(?:=|->)(\S+)/
456
if h.size >= pre_size
457
raise "Error: infinite recursion loop while gathering styles"