rubyvis 0.1.7 → 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.
- data.tar.gz.sig +0 -0
- data/History.txt +12 -0
- data/Manifest.txt +9 -2
- data/README.txt +30 -59
- data/examples/area_interpolation.rb +56 -0
- data/examples/{first.rb → first_protovis_api.rb} +1 -1
- data/examples/first_rbp_api.rb +21 -0
- data/examples/image.rb +0 -1
- data/examples/line.rb +0 -1
- data/examples/line_interpolation.rb +55 -0
- data/lib/rubyvis.rb +3 -3
- data/lib/rubyvis/color/color.rb +177 -20
- data/lib/rubyvis/color/colors.rb +98 -0
- data/lib/rubyvis/layout/stack.rb +271 -196
- data/lib/rubyvis/mark.rb +26 -9
- data/lib/rubyvis/mark/anchor.rb +2 -1
- data/lib/rubyvis/mark/area.rb +99 -1
- data/lib/rubyvis/mark/bar.rb +2 -2
- data/lib/rubyvis/mark/dot.rb +2 -2
- data/lib/rubyvis/mark/line.rb +116 -4
- data/lib/rubyvis/mark/panel.rb +2 -1
- data/lib/rubyvis/mark/shorcut_methods.rb +58 -0
- data/lib/rubyvis/scale/quantitative.rb +5 -3
- data/lib/rubyvis/scene/svg_area.rb +127 -121
- data/lib/rubyvis/scene/svg_curve.rb +328 -0
- data/lib/rubyvis/scene/svg_line.rb +20 -18
- data/lib/rubyvis/scene/svg_scene.rb +5 -4
- data/lib/rubyvis/sceneelement.rb +4 -1
- data/spec/area_spec.rb +48 -0
- data/spec/bar_spec.rb +1 -1
- data/spec/line_spec.rb +63 -0
- data/spec/panel_spec.rb +2 -4
- data/spec/ruby_api_spec.rb +47 -0
- data/spec/scale_linear_spec.rb +14 -1
- data/spec/spec_helper.rb +35 -0
- data/web/Rakefile +1 -1
- data/web/build_site.rb +17 -1
- data/web/examples.haml +2 -2
- data/web/index.haml +13 -7
- metadata +16 -6
- metadata.gz.sig +0 -0
- data/lib/rubyvis/color/ramp.rb +0 -1
@@ -67,7 +67,7 @@ module Rubyvis
|
|
67
67
|
@g=Rubyvis.identity
|
68
68
|
@tick_format=lambda {|x|
|
69
69
|
if x.is_a? Numeric
|
70
|
-
(x.to_f-x.to_i==0) ? x.to_i : x.to_f
|
70
|
+
((x.to_f-x.to_i==0) ? x.to_i : x.to_f).to_s
|
71
71
|
else
|
72
72
|
""
|
73
73
|
end
|
@@ -369,7 +369,7 @@ module Rubyvis
|
|
369
369
|
date.setFullYear((date.getFullYear().quo(step)).floor * step);
|
370
370
|
end
|
371
371
|
end
|
372
|
-
|
372
|
+
# END FIX
|
373
373
|
while (true)
|
374
374
|
date=increment.call(date)
|
375
375
|
break if (date.to_f > max.to_f)
|
@@ -392,7 +392,9 @@ module Rubyvis
|
|
392
392
|
end
|
393
393
|
start = (min.quo(step)).ceil * step
|
394
394
|
_end = (max.quo(step)).floor * step
|
395
|
-
|
395
|
+
|
396
|
+
@tick_format= Rubyvis.Format.number.fraction_digits([0, -(Rubyvis.log(step, 10) + 0.01).floor].max).to_proc
|
397
|
+
|
396
398
|
ticks = Rubyvis.range(start, _end + step, step);
|
397
399
|
return reverse ? ticks.reverse() : ticks;
|
398
400
|
end
|
@@ -4,17 +4,15 @@ module Rubyvis
|
|
4
4
|
e = scenes._g.elements[1]
|
5
5
|
return e if scenes.size==0
|
6
6
|
s=scenes[0]
|
7
|
-
# segmented
|
7
|
+
# segmented
|
8
8
|
return self.area_segment(scenes) if (s.segmented)
|
9
9
|
# visible
|
10
|
-
#
|
11
10
|
return e if (!s.visible)
|
12
|
-
|
13
11
|
fill = s.fill_style
|
14
12
|
stroke = s.stroke_style
|
13
|
+
return e if (fill.opacity==0 and stroke.opacity==0)
|
15
14
|
|
16
|
-
|
17
|
-
# /** @private Computes the straight path for the range [i, j]. */
|
15
|
+
# Computes the straight path for the range [i, j]
|
18
16
|
path=lambda {|ii,j|
|
19
17
|
p1 = []
|
20
18
|
p2 = [];
|
@@ -62,135 +60,143 @@ module Rubyvis
|
|
62
60
|
pointsB.push(OpenStruct.new({:left=> sj.left + sj.width, :top=> sj.top + sj.height}))
|
63
61
|
j=j-1
|
64
62
|
}
|
65
|
-
|
63
|
+
|
66
64
|
if (s.interpolate == "basis")
|
67
|
-
pathT = Rubyvis
|
68
|
-
pathB = Rubyvis
|
65
|
+
pathT = Rubyvis::SvgScene.curve_basis(pointsT);
|
66
|
+
pathB = Rubyvis::SvgScene.curve_basis(pointsB);
|
69
67
|
elsif (s.interpolate == "cardinal")
|
70
|
-
pathT = Rubyvis
|
71
|
-
pathB = Rubyvis
|
68
|
+
pathT = Rubyvis::SvgScene.curve_cardinal(pointsT, s.tension);
|
69
|
+
pathB = Rubyvis::SvgScene.curve_cardinal(pointsB, s.tension);
|
72
70
|
elsif # monotone
|
73
|
-
pathT = Rubyvis
|
74
|
-
pathB = Rubyvis
|
71
|
+
pathT = Rubyvis::SvgScene.curve_monotone(pointsT);
|
72
|
+
pathB = Rubyvis::SvgScene.curve_monotone(pointsB);
|
75
73
|
end
|
76
|
-
|
74
|
+
|
77
75
|
"#{pointsT[0].left },#{ pointsT[0].top }#{ pathT }L#{ pointsB[0].left},#{pointsB[0].top}#{pathB}"
|
78
|
-
|
76
|
+
}
|
79
77
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
i+=1
|
99
|
-
end
|
100
|
-
|
101
|
-
return e if d.size==0
|
78
|
+
#/* points */
|
79
|
+
d = []
|
80
|
+
si=nil
|
81
|
+
sj=nil
|
82
|
+
i=0
|
83
|
+
# puts "Scenes:#{scenes.size}, interpolation:#{scenes[0].interpolate}"
|
84
|
+
|
85
|
+
while(i<scenes.size)
|
86
|
+
|
87
|
+
si = scenes[i]
|
88
|
+
next if (si.width==0 and si.height==0)
|
89
|
+
j=0
|
90
|
+
|
91
|
+
j=(i+1).upto(scenes.size-1).find {|jj|
|
92
|
+
sj=scenes[jj]
|
93
|
+
si.width==0 and si.height==0
|
94
|
+
}
|
95
|
+
j||=scenes.size
|
102
96
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
97
|
+
i=i-1 if (i!=0 and (s.interpolate != "step-after"))
|
98
|
+
|
99
|
+
j=j+1 if ((j < scenes.size) and (s.interpolate != "step-before"))
|
100
|
+
|
101
|
+
d.push(((j - i > 2 and (s.interpolate == "basis" or s.interpolate == "cardinal" or s.interpolate == "monotone")) ? path_curve : path).call(i, j - 1))
|
102
|
+
|
103
|
+
i = j - 1
|
104
|
+
i+=1
|
105
|
+
|
106
|
+
end
|
107
|
+
return e if d.size==0
|
108
|
+
|
109
|
+
e = self.expect(e, "path", {
|
110
|
+
"shape-rendering"=> s.antialias ? nil : "crispEdges",
|
111
|
+
"pointer-events"=> s.events,
|
112
|
+
"cursor"=> s.cursor,
|
113
|
+
"d"=> "M" + d.join("ZM") + "Z",
|
114
|
+
"fill"=> fill.color,
|
115
|
+
"fill-opacity"=> fill.opacity==0 ? nil : fill.opacity,
|
116
|
+
"stroke"=> stroke.color,
|
117
|
+
"stroke-opacity"=> stroke.opacity==0 ? nil : stroke.opacity,
|
118
|
+
"stroke-width"=> stroke.opacity!=0 ? s.line_width / self.scale : nil
|
119
|
+
})
|
120
|
+
self.append(e, scenes, 0);
|
115
121
|
end
|
116
122
|
|
117
123
|
def self.area_segment(scenes)
|
118
124
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
s1 = scenes[i]
|
149
|
-
s2 = scenes[i + 1]
|
150
|
-
|
151
|
-
# /* visible */
|
152
|
-
next if (!s1.visible or !s2.visible)
|
153
|
-
|
154
|
-
fill = s.fill_style
|
155
|
-
stroke = s.stroke_style
|
156
|
-
next e if (fill.opacity==0 and stroke.opacity==0)
|
157
|
-
d=nil
|
158
|
-
if (pathsT)
|
159
|
-
pathT = pathsT[i]
|
160
|
-
pb=pathsB[n - i - 1]
|
161
|
-
pathB = "L" + pb[1,pb.size-1]
|
162
|
-
d = pathT + pathB + "Z";
|
163
|
-
else
|
164
|
-
#/* interpolate */
|
165
|
-
si = s1
|
166
|
-
sj = s2
|
167
|
-
|
168
|
-
case (s1.interpolate)
|
169
|
-
when "step-before"
|
170
|
-
si = s2
|
171
|
-
when "step-after"
|
172
|
-
sj = s1
|
173
|
-
end
|
174
|
-
|
125
|
+
e = scenes._g.elements[1]
|
126
|
+
s = scenes[0]
|
127
|
+
pathsT=[]
|
128
|
+
pathsB=[]
|
129
|
+
|
130
|
+
if (s.interpolate == "basis" or s.interpolate == "cardinal" or s.interpolate == "monotone")
|
131
|
+
pointsT = []
|
132
|
+
pointsB = []
|
133
|
+
n=scenes.size
|
134
|
+
n.times {|i|
|
135
|
+
sj = scenes[n - i - 1]
|
136
|
+
pointsT.push(scenes[i])
|
137
|
+
pointsB.push(OpenStruct.new({:left=> sj.left + sj.width, :top=> sj.top + sj.height}));
|
138
|
+
}
|
139
|
+
|
140
|
+
if (s.interpolate == "basis")
|
141
|
+
pathT = Rubyvis.SvgScene.curve_basis_segments(pointsT);
|
142
|
+
pathB = Rubyvis.SvgScene.curve_basis_segments(pointsB);
|
143
|
+
elsif (s.interpolate == "cardinal")
|
144
|
+
pathT = Rubyvis.SvgScene.curve_cardinal_segments(pointsT, s.tension);
|
145
|
+
pathB = Rubyvis.SvgScene.curve_cardinal_segments(pointsB, s.tension);
|
146
|
+
elsif # monotone
|
147
|
+
pathT = Rubyvis.SvgScene.curve_monotone_segments(pointsT);
|
148
|
+
pathB = Rubyvis.SvgScene.curve_monotone_segments(pointsB);
|
149
|
+
end
|
150
|
+
end
|
151
|
+
n=scenes.size-1
|
152
|
+
n.times {|i|
|
175
153
|
|
176
|
-
|
177
|
-
|
178
|
-
|
154
|
+
s1 = scenes[i]
|
155
|
+
s2 = scenes[i + 1]
|
156
|
+
|
157
|
+
# /* visible */
|
158
|
+
next if (!s1.visible or !s2.visible)
|
179
159
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
160
|
+
fill = s.fill_style
|
161
|
+
stroke = s.stroke_style
|
162
|
+
next e if (fill.opacity==0 and stroke.opacity==0)
|
163
|
+
d=nil
|
164
|
+
if (pathsT)
|
165
|
+
pathT = pathsT[i]
|
166
|
+
pb=pathsB[n - i - 1]
|
167
|
+
pathB = "L" + pb[1,pb.size-1]
|
168
|
+
d = pathT + pathB + "Z";
|
169
|
+
else
|
170
|
+
#/* interpolate */
|
171
|
+
si = s1
|
172
|
+
sj = s2
|
173
|
+
|
174
|
+
case (s1.interpolate)
|
175
|
+
when "step-before"
|
176
|
+
si = s2
|
177
|
+
when "step-after"
|
178
|
+
sj = s1
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
#/* path */
|
183
|
+
d = "M#{s1.left},#{si.top}L#{s2.left},#{sj.top }L#{s2.left + s2.width},#{sj.top + sj.height}L#{s1.left + s1.width},#{si.top + si.height}Z"
|
184
|
+
end
|
185
|
+
|
186
|
+
e = self.expect(e, "path", {
|
187
|
+
"shape-rendering"=> s1.antialias ? nil : "crispEdges",
|
188
|
+
"pointer-events"=> s1.events,
|
189
|
+
"cursor"=> s1.cursor,
|
190
|
+
"d"=> d,
|
191
|
+
"fill"=> fill.color,
|
192
|
+
"fill-opacity"=> fill.opacity==0 ? nil : fill.opacity,
|
193
|
+
"stroke"=> stroke.color,
|
194
|
+
"stroke-opacity"=> stroke.opacity==0 ? nil : stroke.opacity,
|
195
|
+
"stroke-width"=> stroke.opacity!=0 ? s1.line_width / self.scale : nil
|
196
|
+
});
|
197
|
+
e = self.append(e, scenes, i);
|
198
|
+
}
|
199
|
+
return e
|
194
200
|
end
|
195
201
|
end
|
196
202
|
end
|
@@ -0,0 +1,328 @@
|
|
1
|
+
module Rubyvis::SvgScene
|
2
|
+
class PathBasis #:nodoc:
|
3
|
+
def initialize(p0,p1,p2,p3)
|
4
|
+
@p0=p0
|
5
|
+
@p1=p1
|
6
|
+
@p2=p2
|
7
|
+
@p3=p3
|
8
|
+
end
|
9
|
+
attr_accessor :p0,:p1,:p2,:p3
|
10
|
+
|
11
|
+
#
|
12
|
+
# Matrix to transform basis (b-spline) control points to bezier control
|
13
|
+
# points. Derived from FvD 11.2.8.
|
14
|
+
#
|
15
|
+
|
16
|
+
def basis
|
17
|
+
[
|
18
|
+
[ 1/6.0, 2/3.0, 1/6.0, 0 ],
|
19
|
+
[ 0, 2/3.0, 1/3.0, 0 ],
|
20
|
+
[ 0, 1/3.0, 2/3.0, 0 ],
|
21
|
+
[ 0, 1/6.0, 2/3.0, 1/6.0 ]
|
22
|
+
]
|
23
|
+
end
|
24
|
+
# Returns the point that is the weighted sum of the specified control points,
|
25
|
+
# using the specified weights. This method requires that there are four
|
26
|
+
# weights and four control points.
|
27
|
+
def weight(w)
|
28
|
+
OpenStruct.new({
|
29
|
+
:x=> w[0] * p0.left + w[1] * p1.left + w[2] * p2.left + w[3] * p3.left,
|
30
|
+
:y=> w[0] * p0.top + w[1] * p1.top + w[2] * p2.top + w[3] * p3.top
|
31
|
+
})
|
32
|
+
end
|
33
|
+
def convert
|
34
|
+
b1 = weight(basis[1])
|
35
|
+
b2 = weight(basis[2])
|
36
|
+
b3 = weight(basis[3])
|
37
|
+
"C#{b1.x},#{b1.y},#{b2.x},#{b2.y },#{b3.x},#{b3.y}"
|
38
|
+
end
|
39
|
+
def to_s
|
40
|
+
convert
|
41
|
+
end
|
42
|
+
def segment
|
43
|
+
b0 = weight(basis[0])
|
44
|
+
b1 = weight(basis[1])
|
45
|
+
b2 = weight(basis[2])
|
46
|
+
b3 = weight(basis[3])
|
47
|
+
"M#{b0.x},#{b0.y}C#{b1.x},#{b1.y},#{b2.x},#{b2.y},#{b3.x},#{b3.y}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
# Converts the specified b-spline curve segment to a bezier curve
|
51
|
+
# compatible with SVG "C".
|
52
|
+
# * @param p0 the first control point.
|
53
|
+
# * @param p1 the second control point.
|
54
|
+
# * @param p2 the third control point.
|
55
|
+
# * @param p3 the fourth control point.
|
56
|
+
def self.path_basis(p0,p1,p2,p3)
|
57
|
+
PathBasis.new(p0,p1,p2,p3)
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
# Interpolates the given points using the basis spline interpolation.
|
62
|
+
# Returns an SVG path without the leading M instruction to allow path
|
63
|
+
# appending.
|
64
|
+
|
65
|
+
def self.curve_basis(points)
|
66
|
+
return "" if (points.size <= 2)
|
67
|
+
path = ""
|
68
|
+
p0 = points[0]
|
69
|
+
p1 = p0
|
70
|
+
p2 = p0
|
71
|
+
p3 = points[1]
|
72
|
+
|
73
|
+
path += self.path_basis(p0, p1, p2, p3).to_s
|
74
|
+
2.upto(points.size-1) {|i|
|
75
|
+
p0 = p1
|
76
|
+
p1 = p2
|
77
|
+
p2 = p3
|
78
|
+
p3 = points[i]
|
79
|
+
path += self.path_basis(p0, p1, p2, p3).to_s
|
80
|
+
}
|
81
|
+
# Cycle through to get the last point.
|
82
|
+
path += self.path_basis(p1, p2, p3, p3).to_s
|
83
|
+
path += self.path_basis(p2, p3, p3, p3).to_s
|
84
|
+
path;
|
85
|
+
end
|
86
|
+
|
87
|
+
# Interpolates the given points using the basis spline interpolation.
|
88
|
+
# If points.length == tangents.length then a regular Hermite interpolation is
|
89
|
+
# performed, if points.length == tangents.length + 2 then the first and last
|
90
|
+
# segments are filled in with cubic bazier segments. Returns an array of path
|
91
|
+
# strings.
|
92
|
+
|
93
|
+
def self.curve_basis_segments(points)
|
94
|
+
return "" if (points.size <= 2)
|
95
|
+
paths = []
|
96
|
+
p0 = points[0]
|
97
|
+
p1 = p0
|
98
|
+
p2 = p0
|
99
|
+
p3 = points[1]
|
100
|
+
firstPath = self.path_basis(p0, p1, p2, p3).segment
|
101
|
+
p0 = p1;
|
102
|
+
p1 = p2;
|
103
|
+
p2 = p3;
|
104
|
+
p3 = points[2];
|
105
|
+
paths.push(firstPath + self.path_basis(p0, p1, p2, p3)); # merge first & second path
|
106
|
+
3.upto(points.size-1) {|i|
|
107
|
+
p0 = p1;
|
108
|
+
p1 = p2;
|
109
|
+
p2 = p3;
|
110
|
+
p3 = points[i];
|
111
|
+
paths.push(path_basis(p0, p1, p2, p3).segment);
|
112
|
+
}
|
113
|
+
|
114
|
+
# merge last & second-to-last path
|
115
|
+
paths.push(path_basis(p1, p2, p3, p3).segment + path_basis(p2, p3, p3, p3))
|
116
|
+
paths
|
117
|
+
end
|
118
|
+
|
119
|
+
# Interpolates the given points with respective tangents using the cubic
|
120
|
+
# Hermite spline interpolation. If points.length == tangents.length then a regular
|
121
|
+
# Hermite interpolation is performed, if points.length == tangents.length + 2 then
|
122
|
+
# the first and last segments are filled in with cubic bazier segments.
|
123
|
+
# Returns an SVG path without the leading M instruction to allow path appending.
|
124
|
+
#
|
125
|
+
# * @param points the array of points.
|
126
|
+
# * @param tangents the array of tangent vectors.
|
127
|
+
#/
|
128
|
+
|
129
|
+
def self.curve_hermite(points, tangents)
|
130
|
+
return "" if (tangents.size < 1 or (points.size != tangents.size and points.size != tangents.size + 2))
|
131
|
+
quad = points.size != tangents.size
|
132
|
+
path = ""
|
133
|
+
p0 = points[0]
|
134
|
+
p = points[1]
|
135
|
+
t0 = tangents[0]
|
136
|
+
t = t0
|
137
|
+
pi = 1
|
138
|
+
|
139
|
+
if (quad)
|
140
|
+
path += "Q#{(p.left - t0.x * 2 / 3)},#{(p.top - t0.y * 2 / 3)},#{p.left},#{p.top}"
|
141
|
+
p0 = points[1];
|
142
|
+
pi = 2;
|
143
|
+
end
|
144
|
+
|
145
|
+
if (tangents.length > 1)
|
146
|
+
t = tangents[1]
|
147
|
+
p = points[pi]
|
148
|
+
pi+=1
|
149
|
+
path += "C#{(p0.left + t0.x)},#{(p0.top + t0.y) },#{(p.left - t.x) },#{(p.top - t.y)},#{p.left},#{p.top}"
|
150
|
+
|
151
|
+
2.upto(tangents.size-1) {|i|
|
152
|
+
p = points[pi];
|
153
|
+
t = tangents[i];
|
154
|
+
path += "S#{(p.left - t.x)},#{(p.top - t.y)},#{p.left},#{p.top}"
|
155
|
+
pi+=1
|
156
|
+
}
|
157
|
+
end
|
158
|
+
|
159
|
+
if (quad)
|
160
|
+
lp = points[pi];
|
161
|
+
path += "Q#{(p.left + t.x * 2 / 3)},#{(p.top + t.y * 2 / 3)},#{lp.left},#{lp.top}"
|
162
|
+
end
|
163
|
+
|
164
|
+
path;
|
165
|
+
end
|
166
|
+
# Interpolates the given points with respective tangents using the
|
167
|
+
# cubic Hermite spline interpolation. Returns an array of path strings.
|
168
|
+
#
|
169
|
+
# * @param points the array of points.
|
170
|
+
# * @param tangents the array of tangent vectors.
|
171
|
+
def self.curve_hermite_segments(points, tangents)
|
172
|
+
return [] if (tangents.size < 1 or (points.size != tangents.size and points.size != tangents.size + 2))
|
173
|
+
quad = points.size != tangents.size
|
174
|
+
paths = []
|
175
|
+
p0 = points[0]
|
176
|
+
p = p0
|
177
|
+
t0 = tangents[0]
|
178
|
+
t = t0
|
179
|
+
pi = 1
|
180
|
+
|
181
|
+
if (quad)
|
182
|
+
p = points[1]
|
183
|
+
paths.push("M#{p0.left},#{p0.top }Q#{(p.left - t.x * 2 / 3.0 )},#{(p.top - t.y * 2 / 3)},#{p.left},#{p.top}")
|
184
|
+
pi = 2
|
185
|
+
end
|
186
|
+
|
187
|
+
1.upto(tangents.size-1) {|i|
|
188
|
+
p0 = p;
|
189
|
+
t0 = t;
|
190
|
+
p = points[pi]
|
191
|
+
t = tangents[i]
|
192
|
+
paths.push("M#{p0.left },#{p0.top
|
193
|
+
}C#{(p0.left + t0.x) },#{(p0.top + t0.y)
|
194
|
+
},#{(p.left - t.x) },#{(p.top - t.y)
|
195
|
+
},#{p.left },#{p.top}")
|
196
|
+
pi+=1
|
197
|
+
}
|
198
|
+
|
199
|
+
if (quad)
|
200
|
+
lp = points[pi];
|
201
|
+
paths.push("M#{p.left },#{p.top
|
202
|
+
}Q#{(p.left + t.x * 2 / 3) },#{(p.top + t.y * 2 / 3) },#{lp.left },#{lp.top}")
|
203
|
+
end
|
204
|
+
|
205
|
+
paths
|
206
|
+
end
|
207
|
+
|
208
|
+
# Computes the tangents for the given points needed for cardinal
|
209
|
+
# spline interpolation. Returns an array of tangent vectors. Note: that for n
|
210
|
+
# points only the n-2 well defined tangents are returned.
|
211
|
+
#
|
212
|
+
# * @param points the array of points.
|
213
|
+
# * @param tension the tension of hte cardinal spline.
|
214
|
+
def self.cardinal_tangents(points, tension)
|
215
|
+
tangents = []
|
216
|
+
a = (1 - tension) / 2.0
|
217
|
+
p0 = points[0]
|
218
|
+
p1 = points[1]
|
219
|
+
p2 = points[2]
|
220
|
+
3.upto(points.size-1) {|i|
|
221
|
+
tangents.push(OpenStruct.new({:x=> a * (p2.left - p0.left), :y=> a * (p2.top - p0.top)}))
|
222
|
+
p0 = p1;
|
223
|
+
p1 = p2;
|
224
|
+
p2 = points[i];
|
225
|
+
}
|
226
|
+
|
227
|
+
tangents.push(OpenStruct.new({:x=> a * (p2.left - p0.left), :y=> a * (p2.top - p0.top)}))
|
228
|
+
return tangents;
|
229
|
+
end
|
230
|
+
|
231
|
+
|
232
|
+
# Interpolates the given points using cardinal spline interpolation.
|
233
|
+
# Returns an SVG path without the leading M instruction to allow path
|
234
|
+
# appending.
|
235
|
+
#
|
236
|
+
# * @param points the array of points.
|
237
|
+
# * @param tension the tension of hte cardinal spline.
|
238
|
+
def self.curve_cardinal(points, tension)
|
239
|
+
return "" if (points.size <= 2)
|
240
|
+
self.curve_hermite(points, self.cardinal_tangents(points, tension))
|
241
|
+
end
|
242
|
+
# Interpolates the given points using cardinal spline interpolation.
|
243
|
+
# Returns an array of path strings.
|
244
|
+
#
|
245
|
+
# @param points the array of points.
|
246
|
+
# @param tension the tension of hte cardinal spline.
|
247
|
+
def self.curve_cardinal_segments(points, tension)
|
248
|
+
return "" if (points.size <= 2)
|
249
|
+
self.curve_hermite_segments(points, self.cardinal_tangents(points, tension))
|
250
|
+
end
|
251
|
+
|
252
|
+
# Interpolates the given points using Fritsch-Carlson Monotone cubic
|
253
|
+
# Hermite interpolation. Returns an array of tangent vectors.
|
254
|
+
#
|
255
|
+
# *@param points the array of points.
|
256
|
+
def self.monotone_tangents(points)
|
257
|
+
tangents = []
|
258
|
+
d = []
|
259
|
+
m = []
|
260
|
+
dx = []
|
261
|
+
k = 0
|
262
|
+
|
263
|
+
#/* Compute the slopes of the secant lines between successive points. */
|
264
|
+
(points.size-1).times {|k|
|
265
|
+
d[k] = (points[k+1].top - points[k].top) / (points[k+1].left - points[k].left).to_f
|
266
|
+
}
|
267
|
+
|
268
|
+
#/* Initialize the tangents at every point as the average of the secants. */
|
269
|
+
m[0] = d[0]
|
270
|
+
dx[0] = points[1].left - points[0].left
|
271
|
+
1.upto(points.size-2) {|k|
|
272
|
+
m[k] = (d[k-1]+d[k]) / 2.0
|
273
|
+
dx[k] = (points[k+1].left - points[k-1].left) / 2.0
|
274
|
+
}
|
275
|
+
m[k] = d[k-1];
|
276
|
+
dx[k] = (points[k].left - points[k-1].left);
|
277
|
+
|
278
|
+
# /* Step 3. Very important, step 3. Yep. Wouldn't miss it. */
|
279
|
+
(points.size-1).times {|k|
|
280
|
+
if d[k] == 0
|
281
|
+
m[ k ] = 0;
|
282
|
+
m[k+1] = 0;
|
283
|
+
end
|
284
|
+
}
|
285
|
+
|
286
|
+
# /* Step 4 + 5. Out of 5 or more steps. */
|
287
|
+
(points.size-1).times {|k|
|
288
|
+
next if ((m[k].abs < 1e-5) or (m[k+1].abs < 1e-5))
|
289
|
+
ak = m[k] / d[k].to_f
|
290
|
+
bk = m[k + 1] / d[k].to_f
|
291
|
+
s = ak * ak + bk * bk; # monotone constant (?)
|
292
|
+
if (s > 9)
|
293
|
+
tk = 3.0 / Math.sqrt(s)
|
294
|
+
m[k] = tk * ak * d[k]
|
295
|
+
m[k + 1] = tk * bk * d[k]
|
296
|
+
end
|
297
|
+
}
|
298
|
+
len=nil;
|
299
|
+
points.size.times {|i|
|
300
|
+
len = 1 + m[i] * m[i]; #// pv.vector(1, m[i]).norm().times(dx[i]/3)
|
301
|
+
tangents.push(OpenStruct.new({:x=> dx[i] / 3.0 / len, :y=> m[i] * dx[i] / 3.0 / len}))
|
302
|
+
}
|
303
|
+
|
304
|
+
tangents;
|
305
|
+
end
|
306
|
+
|
307
|
+
# Interpolates the given points using Fritsch-Carlson Monotone cubic
|
308
|
+
# Hermite interpolation. Returns an SVG path without the leading M instruction
|
309
|
+
# to allow path appending.
|
310
|
+
#
|
311
|
+
# * @param points the array of points.
|
312
|
+
def self.curve_monotone(points)
|
313
|
+
return "" if (points.length <= 2)
|
314
|
+
return self.curve_hermite(points, self.monotone_tangents(points))
|
315
|
+
end
|
316
|
+
|
317
|
+
# Interpolates the given points using Fritsch-Carlson Monotone cubic
|
318
|
+
# Hermite interpolation.
|
319
|
+
# Returns an array of path strings.
|
320
|
+
#
|
321
|
+
# * @param points the array of points.
|
322
|
+
#/
|
323
|
+
def self.curve_monotone_segments(points)
|
324
|
+
return "" if (points.length <= 2)
|
325
|
+
self.curve_hermite_segments(points, self.monotone_tangents(points))
|
326
|
+
end
|
327
|
+
|
328
|
+
end
|