1 pv.SvgScene.line = function(scenes) { 2 var e = scenes.$g.firstChild; 3 if (scenes.length < 2) return e; 4 var s = scenes[0]; 5 6 /* segmented */ 7 if (s.segmented) return this.lineSegment(scenes); 8 9 /* visible */ 10 if (!s.visible) return e; 11 var fill = s.fillStyle, stroke = s.strokeStyle; 12 if (!fill.opacity && !stroke.opacity) return e; 13 14 /* points */ 15 var d = "M" + s.left + "," + s.top; 16 17 if (scenes.length > 2 && (s.interpolate == "basis" || s.interpolate == "cardinal" || s.interpolate == "monotone")) { 18 switch (s.interpolate) { 19 case "basis": d += this.curveBasis(scenes); break; 20 case "cardinal": d += this.curveCardinal(scenes, s.tension); break; 21 case "monotone": d += this.curveMonotone(scenes); break; 22 } 23 } else { 24 for (var i = 1; i < scenes.length; i++) { 25 d += this.pathSegment(scenes[i - 1], scenes[i]); 26 } 27 } 28 29 e = this.expect(e, "path", { 30 "shape-rendering": s.antialias ? null : "crispEdges", 31 "pointer-events": s.events, 32 "cursor": s.cursor, 33 "d": d, 34 "fill": fill.color, 35 "fill-opacity": fill.opacity || null, 36 "stroke": stroke.color, 37 "stroke-opacity": stroke.opacity || null, 38 "stroke-width": stroke.opacity ? s.lineWidth / this.scale : null, 39 "stroke-linejoin": s.lineJoin 40 }); 41 return this.append(e, scenes, 0); 42 }; 43 44 pv.SvgScene.lineSegment = function(scenes) { 45 var e = scenes.$g.firstChild; 46 47 var s = scenes[0]; 48 var paths; 49 switch (s.interpolate) { 50 case "basis": paths = this.curveBasisSegments(scenes); break; 51 case "cardinal": paths = this.curveCardinalSegments(scenes, s.tension); break; 52 case "monotone": paths = this.curveMonotoneSegments(scenes); break; 53 } 54 55 for (var i = 0, n = scenes.length - 1; i < n; i++) { 56 var s1 = scenes[i], s2 = scenes[i + 1]; 57 58 /* visible */ 59 if (!s1.visible || !s2.visible) continue; 60 var stroke = s1.strokeStyle, fill = pv.Color.transparent; 61 if (!stroke.opacity) continue; 62 63 /* interpolate */ 64 var d; 65 if ((s1.interpolate == "linear") && (s1.lineJoin == "miter")) { 66 fill = stroke; 67 stroke = pv.Color.transparent; 68 d = this.pathJoin(scenes[i - 1], s1, s2, scenes[i + 2]); 69 } else if(paths) { 70 d = paths[i]; 71 } else { 72 d = "M" + s1.left + "," + s1.top + this.pathSegment(s1, s2); 73 } 74 75 e = this.expect(e, "path", { 76 "shape-rendering": s1.antialias ? null : "crispEdges", 77 "pointer-events": s1.events, 78 "cursor": s1.cursor, 79 "d": d, 80 "fill": fill.color, 81 "fill-opacity": fill.opacity || null, 82 "stroke": stroke.color, 83 "stroke-opacity": stroke.opacity || null, 84 "stroke-width": stroke.opacity ? s1.lineWidth / this.scale : null, 85 "stroke-linejoin": s1.lineJoin 86 }); 87 e = this.append(e, scenes, i); 88 } 89 return e; 90 }; 91 92 /** @private Returns the path segment for the specified points. */ 93 pv.SvgScene.pathSegment = function(s1, s2) { 94 var l = 1; // sweep-flag 95 switch (s1.interpolate) { 96 case "polar-reverse": 97 l = 0; 98 case "polar": { 99 var dx = s2.left - s1.left, 100 dy = s2.top - s1.top, 101 e = 1 - s1.eccentricity, 102 r = Math.sqrt(dx * dx + dy * dy) / (2 * e); 103 if ((e <= 0) || (e > 1)) break; // draw a straight line 104 return "A" + r + "," + r + " 0 0," + l + " " + s2.left + "," + s2.top; 105 } 106 case "step-before": return "V" + s2.top + "H" + s2.left; 107 case "step-after": return "H" + s2.left + "V" + s2.top; 108 } 109 return "L" + s2.left + "," + s2.top; 110 }; 111 112 /** @private Line-line intersection, per Akenine-Moller 16.16.1. */ 113 pv.SvgScene.lineIntersect = function(o1, d1, o2, d2) { 114 return o1.plus(d1.times(o2.minus(o1).dot(d2.perp()) / d1.dot(d2.perp()))); 115 } 116 117 /** @private Returns the miter join path for the specified points. */ 118 pv.SvgScene.pathJoin = function(s0, s1, s2, s3) { 119 /* 120 * P1-P2 is the current line segment. V is a vector that is perpendicular to 121 * the line segment, and has length lineWidth / 2. ABCD forms the initial 122 * bounding box of the line segment (i.e., the line segment if we were to do 123 * no joins). 124 */ 125 var p1 = pv.vector(s1.left, s1.top), 126 p2 = pv.vector(s2.left, s2.top), 127 p = p2.minus(p1), 128 v = p.perp().norm(), 129 w = v.times(s1.lineWidth / (2 * this.scale)), 130 a = p1.plus(w), 131 b = p2.plus(w), 132 c = p2.minus(w), 133 d = p1.minus(w); 134 135 /* 136 * Start join. P0 is the previous line segment's start point. We define the 137 * cutting plane as the average of the vector perpendicular to P0-P1, and 138 * the vector perpendicular to P1-P2. This insures that the cross-section of 139 * the line on the cutting plane is equal if the line-width is unchanged. 140 * Note that we don't implement miter limits, so these can get wild. 141 */ 142 if (s0 && s0.visible) { 143 var v1 = p1.minus(s0.left, s0.top).perp().norm().plus(v); 144 d = this.lineIntersect(p1, v1, d, p); 145 a = this.lineIntersect(p1, v1, a, p); 146 } 147 148 /* Similarly, for end join. */ 149 if (s3 && s3.visible) { 150 var v2 = pv.vector(s3.left, s3.top).minus(p2).perp().norm().plus(v); 151 c = this.lineIntersect(p2, v2, c, p); 152 b = this.lineIntersect(p2, v2, b, p); 153 } 154 155 return "M" + a.x + "," + a.y 156 + "L" + b.x + "," + b.y 157 + " " + c.x + "," + c.y 158 + " " + d.x + "," + d.y; 159 }; 160