rubyvis 0.1.7 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|