Class: SVGPlot::SVGTag

Inherits:
Object
  • Object
show all
Defined in:
lib/svgplot/plot.rb

Direct Known Subclasses

SVGTagWithParent

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tag, attributes = {}, &block) ⇒ SVGTag

Returns a new instance of SVGTag.



6
7
8
9
10
11
12
13
14
# File 'lib/svgplot/plot.rb', line 6

def initialize(tag, attributes={}, &block)
  @tag = validate_tag(tag)
  @attributes = validate_attributes(attributes)
  @children = []

  if block
    instance_exec &block
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(meth, *args, &block) ⇒ Object



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/svgplot/plot.rb', line 233

def method_missing(meth, *args, &block)
  #if method is a setter or a getter, check valid attributes:
  check = /^(?<name>.*)(?<op>=|\?)$/.match(meth)
  if check
    raise "Passing a code block to setter or getter is not permited!" if block
    name = validate_attribute(check[:name].to_sym)
    if check[:op] == '?'
      @attributes[name]
    elsif check[:op] == '='
      raise "Setting an attribute with multiple values is not permited!" if args.size > 1
      @attributes[name] = args[0]
    end
  elsif child = validate_child_name(meth)
    spawn_child(child, *args, &block)
  else
    super
  end
end

Instance Attribute Details

#attributesObject (readonly)

Returns the value of attribute attributes.



4
5
6
# File 'lib/svgplot/plot.rb', line 4

def attributes
  @attributes
end

#childrenObject (readonly)

Returns the value of attribute children.



4
5
6
# File 'lib/svgplot/plot.rb', line 4

def children
  @children
end

#tagObject (readonly)

Returns the value of attribute tag.



4
5
6
# File 'lib/svgplot/plot.rb', line 4

def tag
  @tag
end

Instance Method Details

#append_child(child) ⇒ Object



185
186
187
188
189
# File 'lib/svgplot/plot.rb', line 185

def append_child(child)
  @children.push(child)
  child.push_defaults(merge_defaults()) if @defaults
  child
end

#linearGradient(id, attributes = {}, if_exists = :skip, &block) ⇒ Object

special case for linearGradient



133
134
135
136
# File 'lib/svgplot/plot.rb', line 133

def linearGradient(id, attributes={}, if_exists = :skip, &block)
  raise "image reference isn't set, cannot use 'defs' (and thus linearGradient) !" if @img.nil?
  @img.add_def(id, SVGPlot::SVGLinearGradient.new(@img, attributes), if_exists, &block)
end

#matrix(a, b, c, d, e, f) ⇒ Object



45
46
47
48
# File 'lib/svgplot/plot.rb', line 45

def matrix(a, b, c, d, e, f)
  add_transform(:matrix, "#{a}, #{b}, #{c}, #{d}, #{e}, #{f}")
  self
end

#merge_defaultsObject



192
193
194
195
196
197
# File 'lib/svgplot/plot.rb', line 192

def merge_defaults()
  result = {}
  return result if @defaults.empty?
  @defaults.each { |d| result.merge!(d) }
  result
end

#path(attributes = {}, &block) ⇒ Object

special case for path block



121
122
123
# File 'lib/svgplot/plot.rb', line 121

def path(attributes = {}, &block)
  append_child SVGPlot::SVGPath.new(@img, attributes, &block)
end

#pop_defaultsObject



206
207
208
# File 'lib/svgplot/plot.rb', line 206

def pop_defaults()
  @defaults.pop()
end

#push_defaults(defaults) ⇒ Object



200
201
202
203
# File 'lib/svgplot/plot.rb', line 200

def push_defaults(defaults)
  @defaults = [] unless @defaults
  @defaults.push(defaults)
end

#radialGradient(id, attributes = {}, if_exists = :skip, &block) ⇒ Object

special case for radialGradient



140
141
142
143
# File 'lib/svgplot/plot.rb', line 140

def radialGradient(id, attributes={}, if_exists = :skip, &block)
  raise "image reference isn't set, cannot use 'defs' (and thus radialGradient) !" if @img.nil?
  @img.add_def(id, SVGPlot::SVGRadialGradient.new(@img, attributes), if_exists, &block)
end

#raw(data) ⇒ Object

special case for raw blocks.



116
117
118
# File 'lib/svgplot/plot.rb', line 116

def raw(data)
  append_child SVGPlot::SVGRaw.new(@img, data)
end

#rotate(angle, cx = nil, cy = nil) ⇒ Object



30
31
32
33
# File 'lib/svgplot/plot.rb', line 30

def rotate(angle, cx = nil, cy = nil)
  add_transform(:rotate, "#{angle}#{(cx.nil? or cy.nil?) ? "" : ", #{cx}, #{cy}"}")
  self
end

#scale(sx, sy = 1) ⇒ Object



25
26
27
28
# File 'lib/svgplot/plot.rb', line 25

def scale(sx, sy = 1)
  add_transform(:scale, "#{sx}, #{sy}")
  self
end

#skewX(angle) ⇒ Object



35
36
37
38
# File 'lib/svgplot/plot.rb', line 35

def skewX(angle)
  add_transform(:skewX, "#{angle}")
  self
end

#skewY(angle) ⇒ Object



40
41
42
43
# File 'lib/svgplot/plot.rb', line 40

def skewY(angle)
  add_transform(:skewY, "#{angle}")
  self
end

#spawn_child(tag, *args, &block) ⇒ Object



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/svgplot/plot.rb', line 148

def spawn_child(tag, *args, &block)
  #expected args: nil, [hash], [...]
  parameters = {} if args.size == 0

  unless parameters #are empty
    parameters = args[0] if args[0].is_a? Hash
  end

  unless parameters #are set
    #try to find args expansion rule
    expansion = SVGPlot::SVG_EXPANSION[tag.to_sym]
    raise "Unnamed parameters for #{tag} are not allowed!" unless expansion
  
    if expansion.is_a? Array
      raise "Bad unnamed parameter count for #{tag}, expecting #{expansion.size} got #{if args.last.is_a? Hash then args.size-1 else args.size end}" unless (args.size == expansion.size and not args.last.is_a? Hash) or (args.size - 1 == expansion.size and args.last.is_a? Hash)
      parameters = Hash[expansion.zip(args)]
      if args.last.is_a? Hash
        parameters.merge! args.last
      end
    elsif expansion.is_a? Proc
      hash = args.pop if args.last.is_a? Hash
      parameters = expansion.call(args)
      parameters.merge! hash if hash
    else
      raise "Unexpected expansion mechanism: #{expansion.class}"
    end
  end

  # add default parameters if they are not overwritten
  merge_defaults().each do |key, value|
    parameters[key] = value unless parameters[key]
  end if @defaults

  append_child(SVGPlot::SVGTagWithParent.new(@img, tag, parameters, &block))
end

#to_sObject



278
279
280
281
282
# File 'lib/svgplot/plot.rb', line 278

def to_s
  str = ""
  write(str)
  return str
end

#translate(tx, ty = 0) ⇒ Object

Provide methods for SVG transformations



20
21
22
23
# File 'lib/svgplot/plot.rb', line 20

def translate(tx, ty = 0)
  add_transform(:translate, "#{tx}, #{ty}")
  self
end

#use(id, attributes = {}) ⇒ Object

special case for use block



126
127
128
129
# File 'lib/svgplot/plot.rb', line 126

def use(id, attributes = {})
  id = id.attributes[:id] if id.is_a? SVGPlot::SVGTag
  append_child SVGPlot::SVGTagWithParent.new(@img, "use", attributes.merge("xlink:href" => "##{id}"))
end

#validate_attribute(attribute) ⇒ Object



87
88
89
90
# File 'lib/svgplot/plot.rb', line 87

def validate_attribute(attribute)
  raise "#{@tag} does not support attribute #{attribute}" unless SVGPlot::SVG_STRUCTURE[@tag.to_sym][:attributes].include?(attribute.to_sym)
  attribute.to_sym
end

#validate_attributes(attributes) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/svgplot/plot.rb', line 55

def validate_attributes(attributes)
  clean_attributes = {}
  transforms = {}
  styles = {}

  attributes.each do |attribute, value|
    if SVGPlot::SVG_TRANSFORM.include? attribute
      transforms[attribute] = value
    elsif SVGPlot::CSS_STYLE.include? attribute
      styles[attribute] = value
    elsif attribute.match(/^data-[a-z]+$/)
      clean_attributes[attribute] = value
    else
      clean_attributes[validate_attribute(attribute)] = value
    end
  end

  #always prefer more verbose definition.
  unless transforms.empty?
    transforms.merge!(clean_attributes[:transform]) if clean_attributes[:transform]
    str = ""
    write_transforms(transforms, str)
    clean_attributes[validate_attribute(:transform)] = str
  end

  unless styles.empty?
    styles.merge!(clean_attributes[:style]) if clean_attributes[:style]
    clean_attributes[validate_attribute(:style)] = styles
  end
  clean_attributes
end

#validate_child_name(name) ⇒ Object



220
221
222
223
224
225
226
227
228
229
230
# File 'lib/svgplot/plot.rb', line 220

def validate_child_name(name)
  #aliases the name (like, group instead of g)
  name = SVGPlot::SVG_ALIAS[name.to_sym] if SVGPlot::SVG_ALIAS[name.to_sym]

  #raises only if given name is an actual svg tag. In other case -- assumes user just mistyped.
  if SVGPlot::SVG_STRUCTURE[@tag.to_sym][:elements].include?(name.to_sym)
    name.to_sym
  elsif SVGPlot::SVG_ELEMENTS.include?(name.to_sym)
    raise "#{@tag} should not contain child #{name}" 
  end
end

#validate_tag(tag) ⇒ Object



50
51
52
53
# File 'lib/svgplot/plot.rb', line 50

def validate_tag(tag)
  raise "#{tag} is not a valid tag" unless SVGPlot::SVG_ELEMENTS.include?(tag.to_sym)
  tag.to_sym
end

#with_style(style = {}, &proc) ⇒ Object



211
212
213
214
215
216
217
# File 'lib/svgplot/plot.rb', line 211

def with_style(style={}, &proc)
  push_defaults(style)
  # Call the block
  self.instance_exec(&proc)
  # Pop style again to revert changes
  pop_defaults()
end

#write(output) ⇒ Object



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/svgplot/plot.rb', line 253

def write(output)
  raise "Can not write to given output!" unless output.respond_to?(:<<)
  output << "<#{@tag.to_s}"
  @attributes.each do
    |attribute, value|
    output << " #{attribute.to_s}=\""
    if attribute == :style
      write_styles(value, output)
    elsif attribute == :points
      write_points(value, output)
    else
      output << "#{value.to_s}"
    end
    output << "\""
  end

  if @children.empty?
    output << "/>"
  else
    output << ">"
    @children.each { |c| c.write(output) }
    output << "</#{@tag.to_s}>"
  end  
end

#write_points(points, output) ⇒ Object



107
108
109
110
111
112
113
# File 'lib/svgplot/plot.rb', line 107

def write_points(points, output)
  points.each_with_index do |value, index|
    output << value.to_s
    output << ',' if index.even?
    output << ' ' if (index.odd? and (index != points.size-1))
  end
end

#write_styles(styles, output) ⇒ Object



92
93
94
95
96
97
98
# File 'lib/svgplot/plot.rb', line 92

def write_styles(styles, output)
  styles.each do |attribute, value|
    attribute = attribute.to_s
    attribute.gsub!('_','-')
    output << "#{attribute}:#{value};"
  end
end

#write_transforms(transforms, output) ⇒ Object



100
101
102
103
104
105
# File 'lib/svgplot/plot.rb', line 100

def write_transforms(transforms, output)
  transforms.each do |attribute, value|
    value = [value] unless value.is_a?(Array)
    output << "#{attribute.to_s}(#{value.join(',')}) "
  end
end