svgplot 0.0.1 → 0.0.2
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 +4 -4
- data/.travis.yml +2 -0
- data/Rakefile +2 -2
- data/bin/svgplot +5 -0
- data/dev/update_spec.rb +1 -1
- data/lib/svgplot.rb +6 -9
- data/lib/svgplot/application.rb +32 -0
- data/lib/svgplot/meta.rb +5 -11
- data/lib/svgplot/plot.rb +636 -0
- data/spec/svgplot_spec.rb +1 -1
- data/svgplot.gemspec +8 -6
- metadata +48 -30
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e18768c73ada9428946371059e71d7d74b3748bd
|
4
|
+
data.tar.gz: d035ac8e2786d63be01c412004e72c280ea51d14
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 871812ba7324ffcd641f1ff6acfe90c876c63ee12b5c33f19b499ad3cd5334897acbdc4459258ad3f9f63dc6db51a04c4901fe4eaa565c4eb12283d5799b84f9
|
7
|
+
data.tar.gz: a993173c4902c88ac7a0b821a72c68198b7857b5bf34b209875bd43b5263a643d694337483f806d22a235081f6c4ee6b38a5bef3f43a2cf650b4d7b0de8416cf
|
data/.travis.yml
CHANGED
data/Rakefile
CHANGED
@@ -6,8 +6,8 @@ desc 'Run tests'
|
|
6
6
|
RSpec::Core::RakeTask.new(:spec)
|
7
7
|
|
8
8
|
desc 'Run Rubocop on the gem'
|
9
|
-
|
10
|
-
task.patterns = ['lib/**/*.rb', 'spec/**/*.rb', 'dev/**/*.rb']
|
9
|
+
RuboCop::RakeTask.new(:rubocop) do |task|
|
10
|
+
task.patterns = ['lib/**/*.rb', 'spec/**/*.rb', 'dev/**/*.rb', 'bin/*']
|
11
11
|
task.fail_on_error = true
|
12
12
|
end
|
13
13
|
|
data/bin/svgplot
ADDED
data/dev/update_spec.rb
CHANGED
@@ -35,7 +35,7 @@ Nokogiri::HTML(open(version_url + 'eltindex.html')).css('li').each do |li|
|
|
35
35
|
end
|
36
36
|
puts
|
37
37
|
|
38
|
-
xmlns = %w(svg cc dc rdf inkscape xlink
|
38
|
+
xmlns = %w(svg cc dc rdf inkscape xlink).map! { |e| ('xmlns:' + e).to_sym }
|
39
39
|
structure[:svg][:attributes].push(:xmlns, *xmlns)
|
40
40
|
|
41
41
|
skipped_cops = %w(SpaceAroundOperators SpaceInsideHashLiteralBraces HashSyntax)
|
data/lib/svgplot.rb
CHANGED
@@ -2,19 +2,16 @@
|
|
2
2
|
# SVG generation module
|
3
3
|
module SVGPlot
|
4
4
|
class << self
|
5
|
-
|
6
|
-
|
5
|
+
##
|
6
|
+
# Add .new() helper for creating a new Plot object
|
7
7
|
|
8
|
-
def new(*args)
|
9
|
-
self::Plot.new(*args)
|
8
|
+
def new(*args, &block)
|
9
|
+
self::Plot.new(*args, &block)
|
10
10
|
end
|
11
11
|
end
|
12
|
-
|
13
|
-
##
|
14
|
-
# Plot object (represents an SVG image)
|
15
|
-
class Plot
|
16
|
-
end
|
17
12
|
end
|
18
13
|
|
19
14
|
require 'svgplot/spec'
|
15
|
+
require 'svgplot/application'
|
20
16
|
require 'svgplot/meta'
|
17
|
+
require 'svgplot/plot'
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module SVGPlot
|
2
|
+
##
|
3
|
+
# Application object for running .svgplot files
|
4
|
+
class Application
|
5
|
+
def initialize(*args)
|
6
|
+
@files = args.empty? ? Dir.glob(File.expand_path('*.svgplot')) : args
|
7
|
+
fail('No input files') if @files.empty?
|
8
|
+
end
|
9
|
+
|
10
|
+
def run!
|
11
|
+
@files.each do |file|
|
12
|
+
output = file.sub(/\.svgplot/, '') + '.svg'
|
13
|
+
File.open(output, 'w') do |fh|
|
14
|
+
build file, fh
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def build(file, fh)
|
22
|
+
SVGPlot::Plot.new({ width: '100%', height: '100%' }, fh) do
|
23
|
+
begin
|
24
|
+
instance_eval File.read(file), file
|
25
|
+
rescue StandardError => e
|
26
|
+
backtrace = e.backtrace.grep(Regexp.new(File.expand_path(file)))
|
27
|
+
raise e.class, e.message, backtrace
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/svgplot/meta.rb
CHANGED
@@ -41,16 +41,6 @@ SVGPlot::SVG_EXPANSION = {
|
|
41
41
|
end
|
42
42
|
}
|
43
43
|
|
44
|
-
SVGPlot::SVG_DEFAULTS = {
|
45
|
-
text: { fill: 'black' },
|
46
|
-
line: { stroke: 'black' },
|
47
|
-
rect: { stroke: 'black' },
|
48
|
-
circle: { stroke: 'black' },
|
49
|
-
ellipse: { stroke: 'black' },
|
50
|
-
polygon: { stroke: 'black' },
|
51
|
-
polyline: { stroke: 'black' }
|
52
|
-
}
|
53
|
-
|
54
44
|
# TODO: move to documentation?
|
55
45
|
SVGPlot::SVG_TRANSFORM = [
|
56
46
|
:matrix, # expects an array of 6 elements
|
@@ -67,5 +57,9 @@ SVGPlot::CSS_STYLE = [
|
|
67
57
|
:stroke,
|
68
58
|
:fill_opacity,
|
69
59
|
:stroke_opacity,
|
70
|
-
:opacity
|
60
|
+
:opacity,
|
61
|
+
:'text-align',
|
62
|
+
:font,
|
63
|
+
:'white-space',
|
64
|
+
:display
|
71
65
|
]
|
data/lib/svgplot/plot.rb
ADDED
@@ -0,0 +1,636 @@
|
|
1
|
+
# rubocop:disable all
|
2
|
+
module SVGPlot
|
3
|
+
class SVGTag
|
4
|
+
attr_reader :tag, :attributes, :children
|
5
|
+
|
6
|
+
def initialize(tag, attributes={}, &block)
|
7
|
+
@tag = validate_tag(tag)
|
8
|
+
@attributes = validate_attributes(attributes)
|
9
|
+
@children = []
|
10
|
+
|
11
|
+
if block
|
12
|
+
instance_exec &block
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# Provide methods for SVG transformations
|
18
|
+
##
|
19
|
+
|
20
|
+
def translate(tx, ty = 0)
|
21
|
+
add_transform(:translate, "#{tx}, #{ty}")
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def scale(sx, sy = 1)
|
26
|
+
add_transform(:scale, "#{sx}, #{sy}")
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def rotate(angle, cx = nil, cy = nil)
|
31
|
+
add_transform(:rotate, "#{angle}#{(cx.nil? or cy.nil?) ? "" : ", #{cx}, #{cy}"}")
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def skewX(angle)
|
36
|
+
add_transform(:skewX, "#{angle}")
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
def skewY(angle)
|
41
|
+
add_transform(:skewY, "#{angle}")
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
def matrix(a, b, c, d, e, f)
|
46
|
+
add_transform(:matrix, "#{a}, #{b}, #{c}, #{d}, #{e}, #{f}")
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def validate_tag(tag)
|
51
|
+
raise "#{tag} is not a valid tag" unless SVGPlot::SVG_ELEMENTS.include?(tag.to_sym)
|
52
|
+
tag.to_sym
|
53
|
+
end
|
54
|
+
|
55
|
+
def validate_attributes(attributes)
|
56
|
+
clean_attributes = {}
|
57
|
+
transforms = {}
|
58
|
+
styles = {}
|
59
|
+
|
60
|
+
attributes.each do |attribute, value|
|
61
|
+
if SVGPlot::SVG_TRANSFORM.include? attribute
|
62
|
+
transforms[attribute] = value
|
63
|
+
elsif SVGPlot::CSS_STYLE.include? attribute
|
64
|
+
styles[attribute] = value
|
65
|
+
else
|
66
|
+
clean_attributes[validate_attribute(attribute)] = value
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
#always prefer more verbose definition.
|
71
|
+
unless transforms.empty?
|
72
|
+
transforms.merge!(clean_attributes[:transform]) if clean_attributes[:transform]
|
73
|
+
str = ""
|
74
|
+
write_transforms(transforms, str)
|
75
|
+
clean_attributes[validate_attribute(:transform)] = str
|
76
|
+
end
|
77
|
+
|
78
|
+
unless styles.empty?
|
79
|
+
styles.merge!(clean_attributes[:style]) if clean_attributes[:style]
|
80
|
+
clean_attributes[validate_attribute(:style)] = styles
|
81
|
+
end
|
82
|
+
clean_attributes
|
83
|
+
end
|
84
|
+
|
85
|
+
def validate_attribute(attribute)
|
86
|
+
raise "#{@tag} does not support attribute #{attribute}" unless SVGPlot::SVG_STRUCTURE[@tag.to_sym][:attributes].include?(attribute.to_sym)
|
87
|
+
attribute.to_sym
|
88
|
+
end
|
89
|
+
|
90
|
+
def write_styles(styles, output)
|
91
|
+
styles.each do |attribute, value|
|
92
|
+
attribute = attribute.to_s
|
93
|
+
attribute.gsub!('_','-')
|
94
|
+
output << "#{attribute}:#{value};"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def write_transforms(transforms, output)
|
99
|
+
transforms.each do |attribute, value|
|
100
|
+
value = [value] unless value.is_a?(Array)
|
101
|
+
output << "#{attribute.to_s}(#{value.join(',')}) "
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def write_points(points, output)
|
106
|
+
points.each_with_index do |value, index|
|
107
|
+
output << value.to_s
|
108
|
+
output << ',' if index.even?
|
109
|
+
output << ' ' if (index.odd? and (index != points.size-1))
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
#special case for raw blocks.
|
114
|
+
def raw(data)
|
115
|
+
append_child SVGPlot::SVGRaw.new(@img, data)
|
116
|
+
end
|
117
|
+
|
118
|
+
#special case for path block
|
119
|
+
def path(attributes = {}, &block)
|
120
|
+
append_child SVGPlot::SVGPath.new(@img, attributes, &block)
|
121
|
+
end
|
122
|
+
|
123
|
+
#special case for use block
|
124
|
+
def use(id, attributes = {})
|
125
|
+
id = id.attributes[:id] if id.is_a? SVGPlot::SVGTag
|
126
|
+
append_child SVGPlot::SVGTagWithParent.new(@img, "use", attributes.merge("xlink:href" => "##{id}"))
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
#special case for linearGradient
|
131
|
+
def linearGradient(id, attributes={}, if_exists = :skip, &block)
|
132
|
+
raise "image reference isn't set, cannot use 'defs' (and thus linearGradient) !" if @img.nil?
|
133
|
+
@img.add_def(id, SVGPlot::SVGLinearGradient.new(@img, attributes), if_exists, &block)
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
#special case for radialGradient
|
138
|
+
def radialGradient(id, attributes={}, if_exists = :skip, &block)
|
139
|
+
raise "image reference isn't set, cannot use 'defs' (and thus radialGradient) !" if @img.nil?
|
140
|
+
@img.add_def(id, SVGPlot::SVGRadialGradient.new(@img, attributes), if_exists, &block)
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
|
145
|
+
|
146
|
+
def spawn_child(tag, *args, &block)
|
147
|
+
#expected args: nil, [hash], [...]
|
148
|
+
parameters = {} if args.size == 0
|
149
|
+
|
150
|
+
unless parameters #are empty
|
151
|
+
parameters = args[0] if args[0].is_a? Hash
|
152
|
+
end
|
153
|
+
|
154
|
+
unless parameters #are set
|
155
|
+
#try to find args expansion rule
|
156
|
+
expansion = SVGPlot::SVG_EXPANSION[tag.to_sym]
|
157
|
+
raise "Unnamed parameters for #{tag} are not allowed!" unless expansion
|
158
|
+
|
159
|
+
if expansion.is_a? Array
|
160
|
+
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)
|
161
|
+
parameters = Hash[expansion.zip(args)]
|
162
|
+
if args.last.is_a? Hash
|
163
|
+
parameters.merge! args.last
|
164
|
+
end
|
165
|
+
elsif expansion.is_a? Proc
|
166
|
+
hash = args.pop if args.last.is_a? Hash
|
167
|
+
parameters = expansion.call(args)
|
168
|
+
parameters.merge! hash if hash
|
169
|
+
else
|
170
|
+
raise "Unexpected expansion mechanism: #{expansion.class}"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# add default parameters if they are not overwritten
|
175
|
+
merge_defaults().each do |key, value|
|
176
|
+
parameters[key] = value unless parameters[key]
|
177
|
+
end if @defaults
|
178
|
+
|
179
|
+
append_child(SVGPlot::SVGTagWithParent.new(@img, tag, parameters, &block))
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
def append_child(child)
|
184
|
+
@children.push(child)
|
185
|
+
child.push_defaults(merge_defaults()) if @defaults
|
186
|
+
child
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
def merge_defaults()
|
191
|
+
result = {}
|
192
|
+
return result if @defaults.empty?
|
193
|
+
@defaults.each { |d| result.merge!(d) }
|
194
|
+
result
|
195
|
+
end
|
196
|
+
|
197
|
+
|
198
|
+
def push_defaults(defaults)
|
199
|
+
@defaults = [] unless @defaults
|
200
|
+
@defaults.push(defaults)
|
201
|
+
end
|
202
|
+
|
203
|
+
|
204
|
+
def pop_defaults()
|
205
|
+
@defaults.pop()
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
def with_style(style={}, &proc)
|
210
|
+
push_defaults(style)
|
211
|
+
# Call the block
|
212
|
+
self.instance_exec(&proc)
|
213
|
+
# Pop style again to revert changes
|
214
|
+
pop_defaults()
|
215
|
+
end
|
216
|
+
|
217
|
+
|
218
|
+
def validate_child_name(name)
|
219
|
+
#aliases the name (like, group instead of g)
|
220
|
+
name = SVGPlot::SVG_ALIAS[name.to_sym] if SVGPlot::SVG_ALIAS[name.to_sym]
|
221
|
+
|
222
|
+
#raises only if given name is an actual svg tag. In other case -- assumes user just mistyped.
|
223
|
+
if SVGPlot::SVG_STRUCTURE[@tag.to_sym][:elements].include?(name.to_sym)
|
224
|
+
name.to_sym
|
225
|
+
elsif SVGPlot::SVG_ELEMENTS.include?(name.to_sym)
|
226
|
+
raise "#{@tag} should not contain child #{name}"
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
|
231
|
+
def method_missing(meth, *args, &block)
|
232
|
+
#if method is a setter or a getter, check valid attributes:
|
233
|
+
check = /^(?<name>.*)(?<op>=|\?)$/.match(meth)
|
234
|
+
if check
|
235
|
+
raise "Passing a code block to setter or getter is not permited!" if block
|
236
|
+
name = validate_attribute(check[:name].to_sym)
|
237
|
+
if check[:op] == '?'
|
238
|
+
@attributes[name]
|
239
|
+
elsif check[:op] == '='
|
240
|
+
raise "Setting an attribute with multiple values is not permited!" if args.size > 1
|
241
|
+
@attributes[name] = args[0]
|
242
|
+
end
|
243
|
+
elsif child = validate_child_name(meth)
|
244
|
+
spawn_child(child, *args, &block)
|
245
|
+
else
|
246
|
+
super
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
|
251
|
+
def write(output)
|
252
|
+
raise "Can not write to given output!" unless output.respond_to?(:<<)
|
253
|
+
output << "<#{@tag.to_s}"
|
254
|
+
@attributes.each do
|
255
|
+
|attribute, value|
|
256
|
+
output << " #{attribute.to_s}=\""
|
257
|
+
if attribute == :style
|
258
|
+
write_styles(value, output)
|
259
|
+
elsif attribute == :points
|
260
|
+
write_points(value, output)
|
261
|
+
else
|
262
|
+
output << "#{value.to_s}"
|
263
|
+
end
|
264
|
+
output << "\""
|
265
|
+
end
|
266
|
+
|
267
|
+
if @children.empty?
|
268
|
+
output << "/>"
|
269
|
+
else
|
270
|
+
output << ">"
|
271
|
+
@children.each { |c| c.write(output) }
|
272
|
+
output << "</#{@tag.to_s}>"
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
def to_s
|
277
|
+
str = ""
|
278
|
+
write(str)
|
279
|
+
return str
|
280
|
+
end
|
281
|
+
|
282
|
+
|
283
|
+
private
|
284
|
+
|
285
|
+
def add_transform(type, params)
|
286
|
+
attr_name = validate_attribute(:transform)
|
287
|
+
@attributes[attr_name] = "" if @attributes[attr_name].nil?
|
288
|
+
@attributes[attr_name] = @attributes[attr_name] + "#{type}(#{params})"
|
289
|
+
end
|
290
|
+
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
|
295
|
+
#Extension of SVGTag to provide a reference to the parent img (used for defs)
|
296
|
+
module SVGPlot
|
297
|
+
class SVGTagWithParent < SVGPlot::SVGTag
|
298
|
+
attr_reader :img
|
299
|
+
|
300
|
+
def initialize(img, tag, params = {}, output=nil, &block)
|
301
|
+
@img = img
|
302
|
+
super(tag, params, &block)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
|
308
|
+
|
309
|
+
#inherit from tag for basic functionality, control raw data using the write method
|
310
|
+
module SVGPlot
|
311
|
+
class SVGRaw < SVGPlot::SVGTagWithParent
|
312
|
+
|
313
|
+
def initialize(img, data)
|
314
|
+
@img = img
|
315
|
+
@data = data
|
316
|
+
end
|
317
|
+
|
318
|
+
def write(output)
|
319
|
+
output << @data.to_s
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
|
325
|
+
|
326
|
+
module SVGPlot
|
327
|
+
class Plot < SVGPlot::SVGTagWithParent
|
328
|
+
def initialize(params = {}, output=nil, &block)
|
329
|
+
@defs = nil
|
330
|
+
@defs_ids = {}
|
331
|
+
|
332
|
+
params[:"version"] = "1.1" unless params[:"version"]
|
333
|
+
params[:"xmlns"] = "http://www.w3.org/2000/svg" unless params[:"xmlns"]
|
334
|
+
params[:"xmlns:xlink"] = "http://www.w3.org/1999/xlink" unless params[:"xmlns:xlink"]
|
335
|
+
super(self, "svg", params, &block)
|
336
|
+
|
337
|
+
@output = (output or "")
|
338
|
+
validate_output(@output) if output
|
339
|
+
|
340
|
+
if block
|
341
|
+
write(@output)
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
def add_def(id, child, if_exists = :skip, &block)
|
346
|
+
#init on the fly if needed
|
347
|
+
@defs = SVGPlot::SVGTagWithParent.new(@img, "defs") if @defs.nil?
|
348
|
+
|
349
|
+
#raise an error if id is already present and if_exists is :fail
|
350
|
+
raise "Definition '#{id}' already exists" if @defs_ids.has_key? id and if_exists == :fail
|
351
|
+
|
352
|
+
#return the existing element if id is already present and if_exists is :skip
|
353
|
+
return @defs_ids[id] if if_exists == :skip and @defs_ids.has_key? id
|
354
|
+
|
355
|
+
#search for the existing element
|
356
|
+
if @defs_ids[id]
|
357
|
+
old_idx = nil
|
358
|
+
@defs.children.each_with_index { |c,i| if c.attributes[:id] == id then old_idx = i ; break end }
|
359
|
+
end
|
360
|
+
|
361
|
+
#force the id, append the child to definitions and call the given block to fill the group
|
362
|
+
child.attributes[:id] = id
|
363
|
+
@defs.append_child child
|
364
|
+
@defs_ids[id] = child
|
365
|
+
child.instance_exec block
|
366
|
+
|
367
|
+
#remove the old element if present
|
368
|
+
@defs.children.delete_at old_idx if old_idx
|
369
|
+
|
370
|
+
return child
|
371
|
+
end
|
372
|
+
|
373
|
+
def def_group(id, if_exists = :skip, &block)
|
374
|
+
g = SVGPlot::SVGTagWithParent.new(@img, "g", :id => id)
|
375
|
+
return add_def(id, g, if_exists, &block)
|
376
|
+
end
|
377
|
+
|
378
|
+
#def text(x, y, text, style=DefaultStyles[:text])
|
379
|
+
# @output << %Q{<text x="#{x}" y="#{y}"}
|
380
|
+
# style = fix_style(default_style.merge(style))
|
381
|
+
# @output << %Q{ font-family="#{style.delete "font-family"}"} if style["font-family"]
|
382
|
+
# @output << %Q{ font-size="#{style.delete "font-size"}"} if style["font-size"]
|
383
|
+
# write_style style
|
384
|
+
# @output << ">"
|
385
|
+
# dy = 0 # First line should not be shifted
|
386
|
+
# text.each_line do |line|
|
387
|
+
# @output << %Q{<tspan x="#{x}" dy="#{dy}em">}
|
388
|
+
# dy = 1 # Next lines should be shifted
|
389
|
+
# @output << line.rstrip
|
390
|
+
# @output << "</tspan>"
|
391
|
+
# end
|
392
|
+
# @output << "</text>"
|
393
|
+
#end
|
394
|
+
|
395
|
+
|
396
|
+
def write(output)
|
397
|
+
validate_output(output)
|
398
|
+
write_header(output)
|
399
|
+
|
400
|
+
@children.unshift @defs if @defs
|
401
|
+
super(output)
|
402
|
+
@children.shift if @defs
|
403
|
+
end
|
404
|
+
|
405
|
+
|
406
|
+
# how to define output << image ?
|
407
|
+
#def <<(output)
|
408
|
+
# write(output)
|
409
|
+
#end
|
410
|
+
|
411
|
+
private
|
412
|
+
|
413
|
+
def validate_output(output)
|
414
|
+
raise "Illegal output object: #{output.inspect}" unless output.respond_to?(:<<)
|
415
|
+
end
|
416
|
+
|
417
|
+
# Writes file header
|
418
|
+
def write_header(output)
|
419
|
+
output << <<-HEADER
|
420
|
+
<?xml version="1.0" standalone="no"?>
|
421
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
422
|
+
HEADER
|
423
|
+
end
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
#SVG Path element, with ruby methods to describe the path
|
428
|
+
class SVGPlot::SVGPath < SVGPlot::SVGTagWithParent
|
429
|
+
def initialize(img = nil, attributes={}, &block)
|
430
|
+
attributes.merge!(:d => "") unless attributes.has_key? :d
|
431
|
+
super(img, "path", attributes)
|
432
|
+
end
|
433
|
+
|
434
|
+
##
|
435
|
+
# moveTo commands
|
436
|
+
#
|
437
|
+
# Moves pen to specified point x,y without drawing.
|
438
|
+
#
|
439
|
+
##
|
440
|
+
def moveTo(x, y)
|
441
|
+
add_d("m#{x},#{y}")
|
442
|
+
end
|
443
|
+
|
444
|
+
|
445
|
+
def moveToA(x, y)
|
446
|
+
add_d("M#{x},#{y}")
|
447
|
+
end
|
448
|
+
|
449
|
+
|
450
|
+
##
|
451
|
+
# lineTo commands
|
452
|
+
#
|
453
|
+
# Draws a line from current pen location to specified point x,y.
|
454
|
+
#
|
455
|
+
##
|
456
|
+
def lineTo(x, y)
|
457
|
+
add_d("l#{x},#{y}")
|
458
|
+
end
|
459
|
+
|
460
|
+
|
461
|
+
def lineToA(x, y)
|
462
|
+
add_d("L#{x},#{y}")
|
463
|
+
end
|
464
|
+
|
465
|
+
|
466
|
+
##
|
467
|
+
# horizontal lineTo commands
|
468
|
+
#
|
469
|
+
# Draws a horizontal line to the point defined by x.
|
470
|
+
#
|
471
|
+
##
|
472
|
+
def hlineTo(x)
|
473
|
+
add_d("h#{x}")
|
474
|
+
end
|
475
|
+
|
476
|
+
|
477
|
+
def hlineToA(x)
|
478
|
+
add_d("H#{x}")
|
479
|
+
end
|
480
|
+
|
481
|
+
|
482
|
+
##
|
483
|
+
# vertical lineTo commands
|
484
|
+
#
|
485
|
+
# Draws a vertical line to the point defined by y.
|
486
|
+
#
|
487
|
+
##
|
488
|
+
def vlineTo(y)
|
489
|
+
add_d("v#{y}")
|
490
|
+
end
|
491
|
+
|
492
|
+
|
493
|
+
def vlineToA(y)
|
494
|
+
add_d("V#{y}")
|
495
|
+
end
|
496
|
+
|
497
|
+
|
498
|
+
##
|
499
|
+
# curveTo commands
|
500
|
+
#
|
501
|
+
# Draws a cubic Bezier curve from current pen point to dx,dy. x1,y1 and x2,y2
|
502
|
+
# are start and end control points of the curve, controlling how it bends.
|
503
|
+
#
|
504
|
+
##
|
505
|
+
def curveTo(dx, dy, x1, y1, x2, y2)
|
506
|
+
add_d("c#{x1},#{y1} #{x2},#{y2} #{dx},#{dy}")
|
507
|
+
end
|
508
|
+
|
509
|
+
|
510
|
+
def curveToA(dx, dy, x1, y1, x2, y2)
|
511
|
+
add_d("C#{x1},#{y1} #{x2},#{y2} #{dx},#{dy}")
|
512
|
+
end
|
513
|
+
|
514
|
+
|
515
|
+
##
|
516
|
+
# smooth curveTo commands
|
517
|
+
#
|
518
|
+
# Draws a cubic Bezier curve from current pen point to dx,dy. x2,y2 is the
|
519
|
+
# end control point. The start control point is is assumed to be the same
|
520
|
+
# as the end control point of the previous curve.
|
521
|
+
#
|
522
|
+
##
|
523
|
+
def scurveTo(dx, dy, x2, y2)
|
524
|
+
add_d("s#{x2},#{y2} #{dx},#{dy}")
|
525
|
+
end
|
526
|
+
|
527
|
+
|
528
|
+
def scurveToA(dx, dy, x2, y2)
|
529
|
+
add_d("S#{x2},#{y2} #{dx},#{dy}")
|
530
|
+
end
|
531
|
+
|
532
|
+
|
533
|
+
##
|
534
|
+
# quadratic Bezier curveTo commands
|
535
|
+
#
|
536
|
+
# Draws a quadratic Bezier curve from current pen point to dx,dy. x1,y1 is the
|
537
|
+
# control point controlling how the curve bends.
|
538
|
+
#
|
539
|
+
##
|
540
|
+
def qcurveTo(dx, dy, x1, y1)
|
541
|
+
add_d("q#{x1},#{y1} #{dx},#{dy}")
|
542
|
+
end
|
543
|
+
|
544
|
+
|
545
|
+
def qcurveToA(dx, dy, x1, y1)
|
546
|
+
add_d("Q#{x1},#{y1} #{dx},#{dy}")
|
547
|
+
end
|
548
|
+
|
549
|
+
|
550
|
+
##
|
551
|
+
# smooth quadratic Bezier curveTo commands
|
552
|
+
#
|
553
|
+
# Draws a quadratic Bezier curve from current pen point to dx,dy. The control
|
554
|
+
# point is assumed to be the same as the last control point used.
|
555
|
+
#
|
556
|
+
##
|
557
|
+
def sqcurveTo(dx, dy)
|
558
|
+
add_d("t#{dx},#{dy}")
|
559
|
+
end
|
560
|
+
|
561
|
+
|
562
|
+
def sqcurveToA(dx, dy)
|
563
|
+
add_d("T#{dx},#{dy}")
|
564
|
+
end
|
565
|
+
|
566
|
+
|
567
|
+
##
|
568
|
+
# elliptical arc commands
|
569
|
+
#
|
570
|
+
# Draws an elliptical arc from the current point to the point x,y. rx and ry
|
571
|
+
# are the elliptical radius in x and y direction.
|
572
|
+
# The x-rotation determines how much the arc is to be rotated around the
|
573
|
+
# x-axis. It only seems to have an effect when rx and ry have different values.
|
574
|
+
# The large-arc-flag doesn't seem to be used (can be either 0 or 1). Neither
|
575
|
+
# value (0 or 1) changes the arc.
|
576
|
+
# The sweep-flag determines the direction to draw the arc in.
|
577
|
+
#
|
578
|
+
##
|
579
|
+
def arcTo(dx, dy, rx, ry, axis_rotation, large_arc_flag, sweep_flag)
|
580
|
+
add_d("a#{rx},#{ry} #{axis_rotation} #{large_arc_flag},#{sweep_flag} #{dx},#{dy}")
|
581
|
+
end
|
582
|
+
|
583
|
+
|
584
|
+
def arcToA(dx, dy, rx, ry, axis_rotation, large_arc_flag, sweep_flag)
|
585
|
+
add_d("A#{rx},#{ry} #{axis_rotation} #{large_arc_flag},#{sweep_flag} #{dx},#{dy}")
|
586
|
+
end
|
587
|
+
|
588
|
+
|
589
|
+
##
|
590
|
+
# close path command
|
591
|
+
#
|
592
|
+
# Closes the path by drawing a line from current point to first point.
|
593
|
+
#
|
594
|
+
##
|
595
|
+
def close
|
596
|
+
add_d("Z")
|
597
|
+
end
|
598
|
+
|
599
|
+
|
600
|
+
private
|
601
|
+
def add_d(op)
|
602
|
+
@attributes[:d] = @attributes[:d] + " " + op
|
603
|
+
end
|
604
|
+
end
|
605
|
+
|
606
|
+
#SVG base gradient element, with ruby methods to describe the gradient
|
607
|
+
class SVGPlot::SVGGradient < SVGPlot::SVGTagWithParent
|
608
|
+
|
609
|
+
def fill
|
610
|
+
"url(##{@attributes[:id]})"
|
611
|
+
end
|
612
|
+
|
613
|
+
|
614
|
+
def stop(offset, color, opacity)
|
615
|
+
append_child(SVGPlot::SVGTag.new("stop", "offset" => offset, "stop-color" => color, "stop-opacity" => opacity))
|
616
|
+
end
|
617
|
+
|
618
|
+
end
|
619
|
+
|
620
|
+
#SVG linear gradient element
|
621
|
+
class SVGPlot::SVGLinearGradient < SVGPlot::SVGGradient
|
622
|
+
|
623
|
+
def initialize(img, attributes={}, &block)
|
624
|
+
super(img, "linearGradient", attributes, &block)
|
625
|
+
end
|
626
|
+
|
627
|
+
end
|
628
|
+
|
629
|
+
#SVG radial gradient element
|
630
|
+
class SVGPlot::SVGRadialGradient < SVGPlot::SVGGradient
|
631
|
+
|
632
|
+
def initialize(img, attributes={}, &block)
|
633
|
+
super(img, "radialGradient", attributes, &block)
|
634
|
+
end
|
635
|
+
|
636
|
+
end
|
data/spec/svgplot_spec.rb
CHANGED
data/svgplot.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'svgplot'
|
3
|
-
s.version = '0.0.
|
3
|
+
s.version = '0.0.2'
|
4
4
|
s.date = Time.now.strftime("%Y-%m-%d")
|
5
5
|
|
6
6
|
s.summary = 'SVG Generation Library'
|
@@ -17,10 +17,12 @@ Gem::Specification.new do |s|
|
|
17
17
|
|
18
18
|
s.files = `git ls-files`.split
|
19
19
|
s.test_files = `git ls-files spec/*`.split
|
20
|
+
s.executables = ['svgplot']
|
20
21
|
|
21
|
-
s.add_development_dependency 'rubocop', '~> 0.
|
22
|
-
s.add_development_dependency 'rake', '~> 10.
|
23
|
-
s.add_development_dependency 'coveralls', '~> 0.7.
|
24
|
-
s.add_development_dependency 'rspec', '~>
|
25
|
-
s.add_development_dependency 'fuubar', '~>
|
22
|
+
s.add_development_dependency 'rubocop', '~> 0.28.0'
|
23
|
+
s.add_development_dependency 'rake', '~> 10.4.0'
|
24
|
+
s.add_development_dependency 'coveralls', '~> 0.7.1'
|
25
|
+
s.add_development_dependency 'rspec', '~> 3.1.0'
|
26
|
+
s.add_development_dependency 'fuubar', '~> 2.0.0'
|
27
|
+
s.add_development_dependency 'nokogiri', '~> 1.6.5'
|
26
28
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: svgplot
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Les Aker
|
@@ -11,95 +11,113 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date:
|
14
|
+
date: 2015-01-04 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: rubocop
|
18
18
|
requirement: !ruby/object:Gem::Requirement
|
19
19
|
requirements:
|
20
|
-
- - ~>
|
20
|
+
- - "~>"
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: 0.
|
22
|
+
version: 0.28.0
|
23
23
|
type: :development
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
26
26
|
requirements:
|
27
|
-
- - ~>
|
27
|
+
- - "~>"
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: 0.
|
29
|
+
version: 0.28.0
|
30
30
|
- !ruby/object:Gem::Dependency
|
31
31
|
name: rake
|
32
32
|
requirement: !ruby/object:Gem::Requirement
|
33
33
|
requirements:
|
34
|
-
- - ~>
|
34
|
+
- - "~>"
|
35
35
|
- !ruby/object:Gem::Version
|
36
|
-
version: 10.
|
36
|
+
version: 10.4.0
|
37
37
|
type: :development
|
38
38
|
prerelease: false
|
39
39
|
version_requirements: !ruby/object:Gem::Requirement
|
40
40
|
requirements:
|
41
|
-
- - ~>
|
41
|
+
- - "~>"
|
42
42
|
- !ruby/object:Gem::Version
|
43
|
-
version: 10.
|
43
|
+
version: 10.4.0
|
44
44
|
- !ruby/object:Gem::Dependency
|
45
45
|
name: coveralls
|
46
46
|
requirement: !ruby/object:Gem::Requirement
|
47
47
|
requirements:
|
48
|
-
- - ~>
|
48
|
+
- - "~>"
|
49
49
|
- !ruby/object:Gem::Version
|
50
|
-
version: 0.7.
|
50
|
+
version: 0.7.1
|
51
51
|
type: :development
|
52
52
|
prerelease: false
|
53
53
|
version_requirements: !ruby/object:Gem::Requirement
|
54
54
|
requirements:
|
55
|
-
- - ~>
|
55
|
+
- - "~>"
|
56
56
|
- !ruby/object:Gem::Version
|
57
|
-
version: 0.7.
|
57
|
+
version: 0.7.1
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: rspec
|
60
60
|
requirement: !ruby/object:Gem::Requirement
|
61
61
|
requirements:
|
62
|
-
- - ~>
|
62
|
+
- - "~>"
|
63
63
|
- !ruby/object:Gem::Version
|
64
|
-
version:
|
64
|
+
version: 3.1.0
|
65
65
|
type: :development
|
66
66
|
prerelease: false
|
67
67
|
version_requirements: !ruby/object:Gem::Requirement
|
68
68
|
requirements:
|
69
|
-
- - ~>
|
69
|
+
- - "~>"
|
70
70
|
- !ruby/object:Gem::Version
|
71
|
-
version:
|
71
|
+
version: 3.1.0
|
72
72
|
- !ruby/object:Gem::Dependency
|
73
73
|
name: fuubar
|
74
74
|
requirement: !ruby/object:Gem::Requirement
|
75
75
|
requirements:
|
76
|
-
- - ~>
|
76
|
+
- - "~>"
|
77
77
|
- !ruby/object:Gem::Version
|
78
|
-
version:
|
78
|
+
version: 2.0.0
|
79
79
|
type: :development
|
80
80
|
prerelease: false
|
81
81
|
version_requirements: !ruby/object:Gem::Requirement
|
82
82
|
requirements:
|
83
|
-
- - ~>
|
83
|
+
- - "~>"
|
84
84
|
- !ruby/object:Gem::Version
|
85
|
-
version:
|
85
|
+
version: 2.0.0
|
86
|
+
- !ruby/object:Gem::Dependency
|
87
|
+
name: nokogiri
|
88
|
+
requirement: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - "~>"
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: 1.6.5
|
93
|
+
type: :development
|
94
|
+
prerelease: false
|
95
|
+
version_requirements: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - "~>"
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: 1.6.5
|
86
100
|
description: Ruby interface for creating SVG images
|
87
101
|
email: EMAIL_ADDRESS
|
88
|
-
executables:
|
102
|
+
executables:
|
103
|
+
- svgplot
|
89
104
|
extensions: []
|
90
105
|
extra_rdoc_files: []
|
91
106
|
files:
|
92
|
-
- .gitignore
|
93
|
-
- .rspec
|
94
|
-
- .rubocop.yml
|
95
|
-
- .travis.yml
|
107
|
+
- ".gitignore"
|
108
|
+
- ".rspec"
|
109
|
+
- ".rubocop.yml"
|
110
|
+
- ".travis.yml"
|
96
111
|
- Gemfile
|
97
112
|
- LICENSE
|
98
113
|
- README.md
|
99
114
|
- Rakefile
|
115
|
+
- bin/svgplot
|
100
116
|
- dev/update_spec.rb
|
101
117
|
- lib/svgplot.rb
|
118
|
+
- lib/svgplot/application.rb
|
102
119
|
- lib/svgplot/meta.rb
|
120
|
+
- lib/svgplot/plot.rb
|
103
121
|
- lib/svgplot/spec.rb
|
104
122
|
- spec/spec_helper.rb
|
105
123
|
- spec/svgplot_spec.rb
|
@@ -114,17 +132,17 @@ require_paths:
|
|
114
132
|
- lib
|
115
133
|
required_ruby_version: !ruby/object:Gem::Requirement
|
116
134
|
requirements:
|
117
|
-
- -
|
135
|
+
- - ">="
|
118
136
|
- !ruby/object:Gem::Version
|
119
137
|
version: '0'
|
120
138
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
139
|
requirements:
|
122
|
-
- -
|
140
|
+
- - ">="
|
123
141
|
- !ruby/object:Gem::Version
|
124
142
|
version: '0'
|
125
143
|
requirements: []
|
126
144
|
rubyforge_project:
|
127
|
-
rubygems_version: 2.
|
145
|
+
rubygems_version: 2.2.2
|
128
146
|
signing_key:
|
129
147
|
specification_version: 4
|
130
148
|
summary: SVG Generation Library
|