svgplot 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/dev/update_spec.rb +0 -1
- data/lib/svgplot.rb +6 -0
- data/lib/svgplot/application.rb +4 -5
- data/lib/svgplot/defaults.rb +17 -0
- data/lib/svgplot/gradient.rb +35 -0
- data/lib/svgplot/meta.rb +53 -46
- data/lib/svgplot/parsers.rb +80 -0
- data/lib/svgplot/path.rb +132 -0
- data/lib/svgplot/plot.rb +38 -608
- data/lib/svgplot/tag.rb +117 -0
- data/lib/svgplot/transform.rb +45 -0
- data/svgplot.gemspec +1 -1
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c34bf4dae7c76286d0c33dcd17be373d8bd786fe
|
4
|
+
data.tar.gz: dc246aa266b423d779bea78b735995cfc0c21cc8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6164dafb06d5e99a8c8bfd93dc7ce0f2891f9a65375ec466e8fec25011e8581318b5571d77b28199a3f5157067498cb245a08d68511c85827310cbfc15f5c44c
|
7
|
+
data.tar.gz: 05b792cd73ad32864c3a97dd417d7f9b612cf1908189abdc9d557d4099311fbe68fbf3b5373e11a8684746ba12b1ad93841813c28df86de6578e8cb29bb52c18
|
data/dev/update_spec.rb
CHANGED
data/lib/svgplot.rb
CHANGED
@@ -14,4 +14,10 @@ end
|
|
14
14
|
require 'svgplot/spec'
|
15
15
|
require 'svgplot/application'
|
16
16
|
require 'svgplot/meta'
|
17
|
+
require 'svgplot/parsers'
|
18
|
+
require 'svgplot/transform'
|
19
|
+
require 'svgplot/defaults'
|
20
|
+
require 'svgplot/tag'
|
21
|
+
require 'svgplot/path'
|
22
|
+
require 'svgplot/gradient'
|
17
23
|
require 'svgplot/plot'
|
data/lib/svgplot/application.rb
CHANGED
@@ -10,20 +10,19 @@ module SVGPlot
|
|
10
10
|
def run!
|
11
11
|
@files.each do |file|
|
12
12
|
output = file.sub(/\.svgplot/, '') + '.svg'
|
13
|
-
File.open(output, 'w')
|
14
|
-
build file, fh
|
15
|
-
end
|
13
|
+
File.open(output, 'w') { |fh| build file, fh }
|
16
14
|
end
|
17
15
|
end
|
18
16
|
|
19
17
|
private
|
20
18
|
|
21
19
|
def build(file, fh)
|
22
|
-
SVGPlot
|
20
|
+
SVGPlot.new({ width: '100%', height: '100%' }, fh) do
|
23
21
|
begin
|
24
22
|
instance_eval File.read(file), file
|
25
23
|
rescue StandardError => e
|
26
|
-
|
24
|
+
regexp = Regexp.new(File.expand_path(file))
|
25
|
+
backtrace = e.backtrace.grep regexp
|
27
26
|
raise e.class, e.message, backtrace
|
28
27
|
end
|
29
28
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module SVGPlot
|
2
|
+
##
|
3
|
+
# Adds default attributes helpers for Tags
|
4
|
+
module Defaults
|
5
|
+
attr_writer :defaults
|
6
|
+
|
7
|
+
def defaults
|
8
|
+
@defaults ||= {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def with(defaults, &block)
|
12
|
+
@defaults = defaults
|
13
|
+
instance_exec(&block)
|
14
|
+
@defaults = {}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module SVGPlot
|
2
|
+
##
|
3
|
+
# SVG base gradient element, with ruby methods to describe the gradient
|
4
|
+
class Gradient < ChildTag
|
5
|
+
def fill
|
6
|
+
"url(##{@attributes[:id]})"
|
7
|
+
end
|
8
|
+
|
9
|
+
def stop(offset, color, opacity)
|
10
|
+
append_child(ChildTag.new(
|
11
|
+
@img,
|
12
|
+
'stop',
|
13
|
+
'offset' => offset,
|
14
|
+
'stop-color' => color,
|
15
|
+
'stop-opacity' => opacity
|
16
|
+
))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
##
|
21
|
+
# linear gradient element
|
22
|
+
class LinearGradient < Gradient
|
23
|
+
def initialize(img, attributes = {}, &block)
|
24
|
+
super(img, 'linearGradient', attributes, &block)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# radial gradient element
|
30
|
+
class RadialGradient < Gradient
|
31
|
+
def initialize(img, attributes = {}, &block)
|
32
|
+
super(img, 'radialGradient', attributes, &block)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/svgplot/meta.rb
CHANGED
@@ -1,52 +1,59 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
##
|
2
|
+
# Define additional spec pieces outside the generated data
|
3
|
+
module SVGPlot
|
4
|
+
##
|
5
|
+
# Aliases for common tags
|
6
|
+
SVG_ALIAS = {
|
7
|
+
group: :g,
|
8
|
+
rectangle: :rect
|
9
|
+
}
|
5
10
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
image: [:x, :y, :width, :height, :'xlink:href'],
|
10
|
-
ellipse: [:cx, :cy, :rx, :ry],
|
11
|
-
text: [:x, :y],
|
12
|
-
rect: lambda do |args|
|
13
|
-
unless [4, 5, 6].include? args.size
|
14
|
-
fail ArgumentError 'Wrong unnamed argument count'
|
15
|
-
end
|
16
|
-
result = {
|
17
|
-
x: args[0],
|
18
|
-
y: args[1],
|
19
|
-
width: args[2],
|
20
|
-
height: args[3]
|
21
|
-
}
|
22
|
-
if args.size > 4
|
23
|
-
result[:rx] = args[4]
|
24
|
-
result[:ry] = (args[5] || args[4])
|
25
|
-
end
|
26
|
-
result
|
27
|
-
end,
|
28
|
-
polygon: lambda do |args|
|
29
|
-
args.flatten!
|
30
|
-
if args.length.odd?
|
31
|
-
fail ArgumentError 'Illegal number of coordinates (should be even)'
|
32
|
-
end
|
33
|
-
{ points: args }
|
34
|
-
end,
|
35
|
-
polyline: lambda do |args|
|
36
|
-
args.flatten!
|
11
|
+
##
|
12
|
+
# Generic lambda for adding points, used below
|
13
|
+
POINT_LAMBDA = lambda do |args|
|
37
14
|
if args.length.odd?
|
38
15
|
fail ArgumentError 'Illegal number of coordinates (should be even)'
|
39
16
|
end
|
40
|
-
{ points: args }
|
17
|
+
{ points: args.each_slice(2).map { |x| x.join(',') }.join(' ') }
|
41
18
|
end
|
42
|
-
}
|
43
19
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
20
|
+
##
|
21
|
+
# Expansion definitions for unnamed args
|
22
|
+
SVG_EXPANSION = {
|
23
|
+
line: [:x1, :y1, :x2, :y2],
|
24
|
+
circle: [:cx, :cy, :r],
|
25
|
+
image: [:x, :y, :width, :height, :'xlink:href'],
|
26
|
+
ellipse: [:cx, :cy, :rx, :ry],
|
27
|
+
text: [:x, :y],
|
28
|
+
rect: lambda do |args|
|
29
|
+
unless [4, 5, 6].include? args.size
|
30
|
+
fail ArgumentError 'Wrong unnamed argument count'
|
31
|
+
end
|
32
|
+
result = Hash[[:x, :y, :width, :height].zip(args)]
|
33
|
+
if args.size > 4
|
34
|
+
result[:rx] = args[4]
|
35
|
+
result[:ry] = args[5] || args[4]
|
36
|
+
end
|
37
|
+
result
|
38
|
+
end,
|
39
|
+
polygon: POINT_LAMBDA,
|
40
|
+
polyline: POINT_LAMBDA
|
41
|
+
}
|
42
|
+
|
43
|
+
##
|
44
|
+
# Define processing for Expansion constants
|
45
|
+
module Expansion
|
46
|
+
def expand(tag, args)
|
47
|
+
expansion = SVG_EXPANSION[tag.to_sym]
|
48
|
+
fail("Unnamed parameters for #{tag} are not allowed!") unless expansion
|
49
|
+
|
50
|
+
if expansion.is_a? Array
|
51
|
+
parse_args(tag, expansion, args)
|
52
|
+
elsif expansion.is_a? Proc
|
53
|
+
expansion.call(args)
|
54
|
+
else
|
55
|
+
fail "Unexpected expansion mechanism: #{expansion.class}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module SVGPlot
|
2
|
+
module Parsers
|
3
|
+
##
|
4
|
+
# Add parsing methods for various attributes
|
5
|
+
module Tag
|
6
|
+
private
|
7
|
+
|
8
|
+
def parse_tag(tag)
|
9
|
+
return tag.to_sym if SVGPlot::SVG_ELEMENTS.include?(tag.to_sym)
|
10
|
+
fail "#{tag} is not a valid tag"
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse_args(tag, keys, values)
|
14
|
+
if keys.size != values.size
|
15
|
+
fail("Arg mismatch for #{tag}: got #{values.size}, not #{keys.size}")
|
16
|
+
end
|
17
|
+
Hash[keys.zip(values)]
|
18
|
+
end
|
19
|
+
|
20
|
+
def valid_attribute?(attribute)
|
21
|
+
allowed = SVGPlot::SVG_STRUCTURE[@tag.to_sym][:attributes]
|
22
|
+
return true if allowed.include?(attribute.to_sym)
|
23
|
+
false
|
24
|
+
end
|
25
|
+
|
26
|
+
def validate_attribute(attribute)
|
27
|
+
return attribute.to_sym if valid_attribute? attribute
|
28
|
+
fail "#{@tag} does not support attribute #{attribute}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def parse_attributes(raw)
|
32
|
+
clean = {
|
33
|
+
transform: parse_transforms(raw.delete(:transform)),
|
34
|
+
style: parse_styles(raw.delete(:style))
|
35
|
+
}
|
36
|
+
raw.delete(:data) { Hash.new }.each do |key, value|
|
37
|
+
clean["data-#{key}".to_sym] = value
|
38
|
+
end
|
39
|
+
raw.each_key { |k| validate_attribute k }
|
40
|
+
clean.reject { |_, v| v.nil? }.merge raw
|
41
|
+
end
|
42
|
+
|
43
|
+
def parse_transforms(transforms)
|
44
|
+
return nil unless transforms && valid_attribute?(:transform)
|
45
|
+
transforms.each_with_object('') do |(attr, value), str|
|
46
|
+
str << "#{attr}(#{value.is_a?(Array) ? value.join(',') : value }) "
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def parse_styles(styles)
|
51
|
+
return nil unless styles && valid_attribute?(:style)
|
52
|
+
styles.each_with_object('') { |(k, v), str| str << "#{k}:#{v};" }
|
53
|
+
end
|
54
|
+
|
55
|
+
def parse_method_name(method)
|
56
|
+
check = /^(?<name>.*)(?<op>=|\?)$/.match(method)
|
57
|
+
return check if check && valid_attribute?(check[:name])
|
58
|
+
end
|
59
|
+
|
60
|
+
def parse_method_op(op, attr, args, &block)
|
61
|
+
fail('Invalid attribute name') unless valid_attribute? attr
|
62
|
+
fail('Passing a block to setter or getter is not permitted') if block
|
63
|
+
return @attributes[attr] if op == '?'
|
64
|
+
return @attributes[attr] = args.first if args.size == 1
|
65
|
+
fail('Setting an attribute with multiple values is not permitted!')
|
66
|
+
end
|
67
|
+
|
68
|
+
def parse_child_name(name)
|
69
|
+
name = SVG_ALIAS[name.to_sym] if SVG_ALIAS[name.to_sym]
|
70
|
+
|
71
|
+
if SVG_STRUCTURE[@tag.to_sym][:elements].include?(name.to_sym)
|
72
|
+
return name.to_sym
|
73
|
+
elsif SVG_ELEMENTS.include?(name.to_sym)
|
74
|
+
fail "#{@tag} should not contain child #{name}"
|
75
|
+
end
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/svgplot/path.rb
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
# rubocop:disable Metrics/ParameterLists
|
2
|
+
module SVGPlot
|
3
|
+
# SVG Path element, with ruby methods to describe the path
|
4
|
+
class Path < ChildTag
|
5
|
+
def initialize(img, attributes = {})
|
6
|
+
attributes[:d] ||= ''
|
7
|
+
super(img, 'path', attributes)
|
8
|
+
end
|
9
|
+
|
10
|
+
##
|
11
|
+
# Moves pen to specified point x,y without drawing.
|
12
|
+
def move_to(x, y)
|
13
|
+
add_d("m#{x},#{y}")
|
14
|
+
end
|
15
|
+
|
16
|
+
def move_to_a(x, y)
|
17
|
+
add_d("M#{x},#{y}")
|
18
|
+
end
|
19
|
+
|
20
|
+
##
|
21
|
+
# Draws a line from current pen location to specified point x,y.
|
22
|
+
def line_to(x, y)
|
23
|
+
add_d("l#{x},#{y}")
|
24
|
+
end
|
25
|
+
|
26
|
+
def line_to_a(x, y)
|
27
|
+
add_d("L#{x},#{y}")
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Draws a horizontal line to the point defined by x.
|
32
|
+
def hline_to(x)
|
33
|
+
add_d("h#{x}")
|
34
|
+
end
|
35
|
+
|
36
|
+
def hline_to_a(x)
|
37
|
+
add_d("H#{x}")
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# Draws a vertical line to the point defined by y.
|
42
|
+
def vline_to(y)
|
43
|
+
add_d("v#{y}")
|
44
|
+
end
|
45
|
+
|
46
|
+
def vline_to_a(y)
|
47
|
+
add_d("V#{y}")
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# Draws a cubic Bezier curve from current pen point to dx,dy.
|
52
|
+
# x1,y1 and x2,y2 are start and end control points of the curve,
|
53
|
+
# controlling how it bends.
|
54
|
+
def curve_to(dx, dy, x1, y1, x2, y2)
|
55
|
+
add_d("c#{x1},#{y1} #{x2},#{y2} #{dx},#{dy}")
|
56
|
+
end
|
57
|
+
|
58
|
+
def curve_to_a(dx, dy, x1, y1, x2, y2)
|
59
|
+
add_d("C#{x1},#{y1} #{x2},#{y2} #{dx},#{dy}")
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# Draws a cubic Bezier curve from current pen point to dx,dy. x2,y2 is the
|
64
|
+
# end control point. The start control point is is assumed to be the same
|
65
|
+
# as the end control point of the previous curve.
|
66
|
+
def scurve_to(dx, dy, x2, y2)
|
67
|
+
add_d("s#{x2},#{y2} #{dx},#{dy}")
|
68
|
+
end
|
69
|
+
|
70
|
+
def scurve_to_a(dx, dy, x2, y2)
|
71
|
+
add_d("S#{x2},#{y2} #{dx},#{dy}")
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# Draws a quadratic Bezier curve from current pen point to dx,dy. x1,y1 is
|
76
|
+
# the control point controlling how the curve bends.
|
77
|
+
def qcurve_to(dx, dy, x1, y1)
|
78
|
+
add_d("q#{x1},#{y1} #{dx},#{dy}")
|
79
|
+
end
|
80
|
+
|
81
|
+
def qcurve_to_a(dx, dy, x1, y1)
|
82
|
+
add_d("Q#{x1},#{y1} #{dx},#{dy}")
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# Draws a quadratic Bezier curve from current pen point to dx,dy. The
|
87
|
+
# control point is assumed to be the same as the last control point used.
|
88
|
+
def sqcurve_to(dx, dy)
|
89
|
+
add_d("t#{dx},#{dy}")
|
90
|
+
end
|
91
|
+
|
92
|
+
def sqcurve_to_a(dx, dy)
|
93
|
+
add_d("T#{dx},#{dy}")
|
94
|
+
end
|
95
|
+
|
96
|
+
##
|
97
|
+
# Draws an elliptical arc from the current point to the point x,y. rx and ry
|
98
|
+
# are the elliptical radius in x and y direction.
|
99
|
+
# The x-rotation determines how much the arc is to be rotated around the
|
100
|
+
# x-axis. It only has an effect when rx and ry have different values.
|
101
|
+
# The large-arc-flag doesn't seem to be used (can be either 0 or 1). Neither
|
102
|
+
# value (0 or 1) changes the arc.
|
103
|
+
# The sweep-flag determines the direction to draw the arc in.
|
104
|
+
def arc_to(dx, dy, rx, ry, axis_rot, large_arc_flag, sweep_flag)
|
105
|
+
add_d(
|
106
|
+
"a#{rx},#{ry} #{axis_rot} #{large_arc_flag},#{sweep_flag} #{dx},#{dy}"
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
110
|
+
def arc_to_a(dx, dy, rx, ry, axis_rot, large_arc_flag, sweep_flag)
|
111
|
+
add_d(
|
112
|
+
"A#{rx},#{ry} #{axis_rot} #{large_arc_flag},#{sweep_flag} #{dx},#{dy}"
|
113
|
+
)
|
114
|
+
end
|
115
|
+
|
116
|
+
##
|
117
|
+
# close path command
|
118
|
+
#
|
119
|
+
# Closes the path by drawing a line from current point to first point.
|
120
|
+
#
|
121
|
+
##
|
122
|
+
def close
|
123
|
+
add_d('Z')
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def add_d(op)
|
129
|
+
@attributes[:d] << " #{op}"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
data/lib/svgplot/plot.rb
CHANGED
@@ -1,636 +1,66 @@
|
|
1
|
-
# rubocop:disable all
|
2
1
|
module SVGPlot
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
58
|
-
transforms = {}
|
59
|
-
attributes.delete(:transform) { Hash.new }.each do |key, value|
|
60
|
-
transforms[key] = value
|
61
|
-
end
|
62
|
-
unless transforms.empty?
|
63
|
-
str = ""
|
64
|
-
write_transforms(transforms, str)
|
65
|
-
clean_attributes[validate_attribute(:transform)] = str
|
66
|
-
end
|
67
|
-
|
68
|
-
styles = {}
|
69
|
-
attributes.delete(:style) { Hash.new }.each { |k, v| styles[k] = v }
|
70
|
-
unless styles.empty?
|
71
|
-
clean_attributes[validate_attribute(:style)] = styles
|
72
|
-
end
|
73
|
-
|
74
|
-
attributes.delete(:data) { Hash.new }.each do |key, value|
|
75
|
-
clean_attributes["data-#{key.to_s}".to_sym] = value
|
76
|
-
end
|
77
|
-
|
78
|
-
attributes.each do |key, value|
|
79
|
-
clean_attributes[validate_attribute(key)] = value
|
80
|
-
end
|
81
|
-
|
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
|
2
|
+
##
|
3
|
+
# Main Plot object
|
4
|
+
class Plot < ChildTag
|
5
|
+
DEFAULTS = {
|
6
|
+
version: 1.1,
|
7
|
+
xmlns: 'http://www.w3.org/2000/svg',
|
8
|
+
:'xmlns:xlink' => 'http://www.w3.org/1999/xlink'
|
9
|
+
}
|
339
10
|
|
340
|
-
|
341
|
-
|
342
|
-
|
11
|
+
def initialize(params = {}, output = nil, &block)
|
12
|
+
params = DEFAULTS.dup.merge params
|
13
|
+
super(self, 'svg', params, &block)
|
14
|
+
@output = output || ''
|
15
|
+
write(@output) if block
|
343
16
|
end
|
344
17
|
|
345
18
|
def add_def(id, child, if_exists = :skip, &block)
|
346
|
-
|
347
|
-
|
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
|
19
|
+
@defs ||= ChildTag.new(@img, 'defs')
|
20
|
+
old_id = check_conflicts(id, if_exists) if @defs_ids.key? id
|
21
|
+
return old_id if old_id
|
351
22
|
|
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
23
|
child.attributes[:id] = id
|
363
24
|
@defs.append_child child
|
364
25
|
@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
|
26
|
+
child.instance_exec block if block
|
369
27
|
|
370
|
-
|
28
|
+
child
|
371
29
|
end
|
372
30
|
|
373
31
|
def def_group(id, if_exists = :skip, &block)
|
374
|
-
g = SVGPlot::SVGTagWithParent.new(@img,
|
375
|
-
|
32
|
+
g = SVGPlot::SVGTagWithParent.new(@img, 'g', id: id)
|
33
|
+
add_def(id, g, if_exists, &block)
|
376
34
|
end
|
377
35
|
|
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
36
|
def write(output)
|
397
|
-
|
398
|
-
|
399
|
-
|
37
|
+
fail("Illegal output: #{@output.inspect}") unless @output.respond_to? :<<
|
38
|
+
output << header
|
400
39
|
@children.unshift @defs if @defs
|
401
40
|
super(output)
|
402
41
|
@children.shift if @defs
|
403
42
|
end
|
404
43
|
|
405
|
-
|
406
|
-
# how to define output << image ?
|
407
|
-
#def <<(output)
|
408
|
-
# write(output)
|
409
|
-
#end
|
410
|
-
|
411
44
|
private
|
412
45
|
|
413
|
-
def
|
414
|
-
|
46
|
+
def check_conflicts(id, if_exists)
|
47
|
+
case if_exists
|
48
|
+
when :fail
|
49
|
+
fail("Definition '#{id}' already exists")
|
50
|
+
when :skip
|
51
|
+
return @defs_ids[id]
|
52
|
+
else
|
53
|
+
@defs.children.reject! { |x| x.attributes[:id] == id }
|
54
|
+
nil
|
55
|
+
end
|
415
56
|
end
|
416
57
|
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
58
|
+
def header
|
59
|
+
[
|
60
|
+
%(<?xml version="1.0" standalone="no"?>\n),
|
61
|
+
%(<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" ),
|
62
|
+
%("http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">)
|
63
|
+
].join
|
423
64
|
end
|
424
65
|
end
|
425
66
|
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/lib/svgplot/tag.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
module SVGPlot
|
2
|
+
##
|
3
|
+
# Tag object, represents a single SVG tag
|
4
|
+
class Tag
|
5
|
+
include Parsers::Tag
|
6
|
+
include Transform
|
7
|
+
include Defaults
|
8
|
+
include Expansion
|
9
|
+
|
10
|
+
attr_reader :tag, :attributes, :children
|
11
|
+
|
12
|
+
def initialize(tag, attributes = {}, &block)
|
13
|
+
@tag = parse_tag tag
|
14
|
+
@attributes = parse_attributes attributes
|
15
|
+
@children = []
|
16
|
+
|
17
|
+
instance_exec(&block) if block
|
18
|
+
end
|
19
|
+
|
20
|
+
def raw(data)
|
21
|
+
append_child Raw.new(@img, data)
|
22
|
+
end
|
23
|
+
|
24
|
+
def path(attributes = {}, &block)
|
25
|
+
append_child Path.new(@img, attributes, &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
def use(id, attributes = {})
|
29
|
+
id = id.attributes[:id] if id.is_a? Tag
|
30
|
+
attributes.merge!('xlink:href' => "##{id}")
|
31
|
+
append_child ChildTag.new(@img, 'use', attributes)
|
32
|
+
end
|
33
|
+
|
34
|
+
def append_child(child)
|
35
|
+
@children.push(child)
|
36
|
+
child.defaults = defaults
|
37
|
+
child
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_s
|
41
|
+
str = ''
|
42
|
+
write(str)
|
43
|
+
str
|
44
|
+
end
|
45
|
+
|
46
|
+
def write(output)
|
47
|
+
output << "<#{@tag}"
|
48
|
+
@attributes.each { |attr, value| output << %( #{attr}="#{value}") }
|
49
|
+
if @children.empty?
|
50
|
+
output << '/>'
|
51
|
+
else
|
52
|
+
output << '>'
|
53
|
+
@children.each { |c| c.write(output) }
|
54
|
+
output << "</#{@tag}>"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def spawn_child(tag, *args, &block)
|
59
|
+
parameters = args.first.is_a?(Hash) ? args.unshift : {}
|
60
|
+
if parameters.empty?
|
61
|
+
parameters = args.last.is_a?(Hash) ? args.pop : {}
|
62
|
+
parameters.merge! expand(tag, args)
|
63
|
+
end
|
64
|
+
parameters = defaults.dup.merge! parameters
|
65
|
+
|
66
|
+
append_child ChildTag.new(@img, tag, parameters, &block)
|
67
|
+
end
|
68
|
+
|
69
|
+
def respond_to?(method)
|
70
|
+
return true if parse_method_name(method) || parse_child_name(meth)
|
71
|
+
super
|
72
|
+
end
|
73
|
+
|
74
|
+
def method_missing(method, *args, &block)
|
75
|
+
check = parse_method_name(method)
|
76
|
+
if check
|
77
|
+
return parse_method_op(check[:op], check[:name], args, &block)
|
78
|
+
else
|
79
|
+
child_name = parse_child_name(method)
|
80
|
+
return spawn_child(child_name, *args, &block) if child_name
|
81
|
+
end
|
82
|
+
super
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
##
|
87
|
+
# Child tag, used for tags within another tag
|
88
|
+
class ChildTag < Tag
|
89
|
+
attr_reader :img
|
90
|
+
|
91
|
+
def initialize(img, tag, params = {}, &block)
|
92
|
+
@img = img
|
93
|
+
super(tag, params, &block)
|
94
|
+
end
|
95
|
+
|
96
|
+
def linear_gradient(id, attributes = {}, if_exists = :skip, &block)
|
97
|
+
@img.add_def(id, LinearGradient.new(@img, attributes), if_exists, &block)
|
98
|
+
end
|
99
|
+
|
100
|
+
def radial_gradient(id, attributes = {}, if_exists = :skip, &block)
|
101
|
+
@img.add_def(id, RadialGradient.new(@img, attributes), if_exists, &block)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
##
|
106
|
+
# Raw child, just prints data provided
|
107
|
+
class Raw < ChildTag
|
108
|
+
def initialize(img, data)
|
109
|
+
@img = img
|
110
|
+
@data = data
|
111
|
+
end
|
112
|
+
|
113
|
+
def write(output)
|
114
|
+
output << @data.to_s
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module SVGPlot
|
2
|
+
##
|
3
|
+
# Provide methods for SVG transformations
|
4
|
+
module Transform
|
5
|
+
def translate(tx, ty = 0)
|
6
|
+
add_transform(:translate, "#{tx}, #{ty}")
|
7
|
+
self
|
8
|
+
end
|
9
|
+
|
10
|
+
def scale(sx, sy = 1)
|
11
|
+
add_transform(:scale, "#{sx}, #{sy}")
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def rotate(angle, cx = nil, cy = nil)
|
16
|
+
string = [cx, cy].any?(&:nil?) ? "#{angle}" : "#{angle}, #{cx}, #{cy}"
|
17
|
+
add_transform(:rotate, string)
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def skew_x(angle)
|
22
|
+
add_transform(:skewX, "#{angle}")
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def skew_y(angle)
|
27
|
+
add_transform(:skewY, "#{angle}")
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def matrix(*args)
|
32
|
+
fail('matrix takes 6 args') unless args.size == 6
|
33
|
+
add_transform(:matrix, args.join(', '))
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def add_transform(type, params)
|
40
|
+
validate_attribute(:transform)
|
41
|
+
@attributes[:transform] ||= ''
|
42
|
+
@attributes[:transform] << "#{type}(#{params})"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/svgplot.gemspec
CHANGED
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.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Les Aker
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2015-01-
|
14
|
+
date: 2015-01-10 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: rubocop
|
@@ -116,9 +116,15 @@ files:
|
|
116
116
|
- dev/update_spec.rb
|
117
117
|
- lib/svgplot.rb
|
118
118
|
- lib/svgplot/application.rb
|
119
|
+
- lib/svgplot/defaults.rb
|
120
|
+
- lib/svgplot/gradient.rb
|
119
121
|
- lib/svgplot/meta.rb
|
122
|
+
- lib/svgplot/parsers.rb
|
123
|
+
- lib/svgplot/path.rb
|
120
124
|
- lib/svgplot/plot.rb
|
121
125
|
- lib/svgplot/spec.rb
|
126
|
+
- lib/svgplot/tag.rb
|
127
|
+
- lib/svgplot/transform.rb
|
122
128
|
- spec/spec_helper.rb
|
123
129
|
- spec/svgplot_spec.rb
|
124
130
|
- svgplot.gemspec
|