rubyvis 0.1.7 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- @tick_format= Rubyvis.Format.number.fraction_digits([0, -(Rubyvis.log(step, 10) + 0.01).floor].max)
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
- return e if (fill.opacity==0 and stroke.opacity==0)
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.SvgScene.curve_basis(pointsT);
68
- pathB = Rubyvis.SvgScene.curve_basis(pointsB);
65
+ pathT = Rubyvis::SvgScene.curve_basis(pointsT);
66
+ pathB = Rubyvis::SvgScene.curve_basis(pointsB);
69
67
  elsif (s.interpolate == "cardinal")
70
- pathT = Rubyvis.SvgScene.curve_cardinal(pointsT, s.tension);
71
- pathB = Rubyvis.SvgScene.curve_cardinal(pointsB, s.tension);
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.SvgScene.curve_monotone(pointsT);
74
- pathB = Rubyvis.SvgScene.curve_monotone(pointsB);
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
- #/* points */
81
- d = []
82
- si=nil
83
- sj=nil
84
- i=0
85
- while(i<scenes.size)
86
- si = scenes[i]
87
- continue if (si.width==0 and si.height==0)
88
- j=0
89
- (i+1).upto(scenes.size-1) {|jj|
90
- j=jj; sj = scenes[jj];
91
- break if (si.width==0 and si.height==0)
92
- }
93
-
94
- i=i-1 if (i!=0 and (s.interpolate != "step-after"))
95
- j=j+1 if ((j < scenes.size) and (s.interpolate != "step-before"))
96
- d.push(((j - i > 2 and (s.interpolate == "basis" or s.interpolate == "cardinal" or s.interpolate == "monotone")) ? path_curve : path).call(i, j - 1))
97
- i = j - 1
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
- e = self.expect(e, "path", {
104
- "shape-rendering"=> s.antialias ? nil : "crispEdges",
105
- "pointer-events"=> s.events,
106
- "cursor"=> s.cursor,
107
- "d"=> "M" + d.join("ZM") + "Z",
108
- "fill"=> fill.color,
109
- "fill-opacity"=> fill.opacity==0 ? nil : fill.opacity,
110
- "stroke"=> stroke.color,
111
- "stroke-opacity"=> stroke.opacity==0 ? nil : stroke.opacity,
112
- "stroke-width"=> stroke.opacity!=0 ? s.line_width / self.scale : nil
113
- });
114
- return self.append(e, scenes, 0);
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
- e = scenes._g.elements[1]
120
- s = scenes[0]
121
- pathsT=[]
122
- pathsB=[]
123
-
124
- if (s.interpolate == "basis" or s.interpolate == "cardinal" or s.interpolate == "monotone")
125
- pointsT = []
126
- pointsB = []
127
- n=scenes.size
128
- n.times {|i|
129
- sj = scenes[n - i - 1]
130
- pointsT.push(scenes[i])
131
- pointsB.push(OpenStruct.new({:left=> sj.left + sj.width, :top=> sj.top + sj.height}));
132
- }
133
-
134
- if (s.interpolate == "basis")
135
- pathT = Rubyvis.SvgScene.curve_basis_segments(pointsT);
136
- pathB = Rubyvis.SvgScene.curve_basis_segments(pointsB);
137
- elsif (s.interpolate == "cardinal")
138
- pathT = Rubyvis.SvgScene.curve_cardinal_segments(pointsT, s.tension);
139
- pathB = Rubyvis.SvgScene.curve_cardinal_segments(pointsB, s.tension);
140
- elsif # monotone
141
- pathT = Rubyvis.SvgScene.curve_monotone_segments(pointsT);
142
- pathB = Rubyvis.SvgScene.curve_monotone_segments(pointsB);
143
- end
144
- end
145
- n=scenes.size-1
146
- n.times {|i|
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
- #/* path */
177
- 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"
178
- end
154
+ s1 = scenes[i]
155
+ s2 = scenes[i + 1]
156
+
157
+ # /* visible */
158
+ next if (!s1.visible or !s2.visible)
179
159
 
180
- e = self.expect(e, "path", {
181
- "shape-rendering"=> s1.antialias ? null : "crispEdges",
182
- "pointer-events"=> s1.events,
183
- "cursor"=> s1.cursor,
184
- "d"=> d,
185
- "fill"=> fill.color,
186
- "fill-opacity"=> fill.opacity==0 ? nil : fill.opacity,
187
- "stroke"=> stroke.color,
188
- "stroke-opacity"=> stroke.opacity==0 ? nil : stroke.opacity,
189
- "stroke-width"=> stroke.opacity!=0 ? s1.line_width / self.scale : nil
190
- });
191
- e = self.append(e, scenes, i);
192
- }
193
- return e;
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