svgplot 0.1.1 → 0.2.0
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/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
|