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.
@@ -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