1 /** 2 * @license 3 * Part of dygraphs, see top-level LICENSE.txt file 4 * MIT-licenced: https://opensource.org/licenses/MIT 5 */ 6 (function() { 7 "use strict"; 8 9 var Dygraph; 10 if (window.Dygraph) { 11 Dygraph = window.Dygraph; 12 } else if (typeof(module) !== 'undefined') { 13 Dygraph = require('../dygraph'); 14 } 15 16 /** 17 * Given three sequential points, p0, p1 and p2, find the left and right 18 * control points for p1. 19 * 20 * The three points are expected to have x and y properties. 21 * 22 * The alpha parameter controls the amount of smoothing. 23 * If α=0, then both control points will be the same as p1 (i.e. no smoothing). 24 * 25 * Returns [l1x, l1y, r1x, r1y] 26 * 27 * It's guaranteed that the line from (l1x, l1y)-(r1x, r1y) passes through p1. 28 * Unless allowFalseExtrema is set, then it's also guaranteed that: 29 * l1y ∈ [p0.y, p1.y] 30 * r1y ∈ [p1.y, p2.y] 31 * 32 * The basic algorithm is: 33 * 1. Put the control points l1 and r1 α of the way down (p0, p1) and (p1, p2). 34 * 2. Shift l1 and r2 so that the line l1–r1 passes through p1 35 * 3. Adjust to prevent false extrema while keeping p1 on the l1–r1 line. 36 * 37 * This is loosely based on the HighCharts algorithm. 38 */ 39 function getControlPoints(p0, p1, p2, opt_alpha, opt_allowFalseExtrema) { 40 var alpha = (opt_alpha !== undefined) ? opt_alpha : 1/3; // 0=no smoothing, 1=crazy smoothing 41 var allowFalseExtrema = opt_allowFalseExtrema || false; 42 43 if (!p2) { 44 return [p1.x, p1.y, null, null]; 45 } 46 47 // Step 1: Position the control points along each line segment. 48 var l1x = (1 - alpha) * p1.x + alpha * p0.x, 49 l1y = (1 - alpha) * p1.y + alpha * p0.y, 50 r1x = (1 - alpha) * p1.x + alpha * p2.x, 51 r1y = (1 - alpha) * p1.y + alpha * p2.y; 52 53 // Step 2: shift the points up so that p1 is on the l1–r1 line. 54 if (l1x != r1x) { 55 // This can be derived w/ some basic algebra. 56 var deltaY = p1.y - r1y - (p1.x - r1x) * (l1y - r1y) / (l1x - r1x); 57 l1y += deltaY; 58 r1y += deltaY; 59 } 60 61 // Step 3: correct to avoid false extrema. 62 if (!allowFalseExtrema) { 63 if (l1y > p0.y && l1y > p1.y) { 64 l1y = Math.max(p0.y, p1.y); 65 r1y = 2 * p1.y - l1y; 66 } else if (l1y < p0.y && l1y < p1.y) { 67 l1y = Math.min(p0.y, p1.y); 68 r1y = 2 * p1.y - l1y; 69 } 70 71 if (r1y > p1.y && r1y > p2.y) { 72 r1y = Math.max(p1.y, p2.y); 73 l1y = 2 * p1.y - r1y; 74 } else if (r1y < p1.y && r1y < p2.y) { 75 r1y = Math.min(p1.y, p2.y); 76 l1y = 2 * p1.y - r1y; 77 } 78 } 79 80 return [l1x, l1y, r1x, r1y]; 81 } 82 83 // i.e. is none of (null, undefined, NaN) 84 function isOK(x) { 85 return !!x && !isNaN(x); 86 }; 87 88 // A plotter which uses splines to create a smooth curve. 89 // See tests/plotters.html for a demo. 90 // Can be controlled via smoothPlotter.smoothing 91 function smoothPlotter(e) { 92 var ctx = e.drawingContext, 93 points = e.points; 94 95 ctx.beginPath(); 96 ctx.moveTo(points[0].canvasx, points[0].canvasy); 97 98 // right control point for previous point 99 var lastRightX = points[0].canvasx, lastRightY = points[0].canvasy; 100 101 for (var i = 1; i < points.length; i++) { 102 var p0 = points[i - 1], 103 p1 = points[i], 104 p2 = points[i + 1]; 105 p0 = p0 && isOK(p0.canvasy) ? p0 : null; 106 p1 = p1 && isOK(p1.canvasy) ? p1 : null; 107 p2 = p2 && isOK(p2.canvasy) ? p2 : null; 108 if (p0 && p1) { 109 var controls = getControlPoints({x: p0.canvasx, y: p0.canvasy}, 110 {x: p1.canvasx, y: p1.canvasy}, 111 p2 && {x: p2.canvasx, y: p2.canvasy}, 112 smoothPlotter.smoothing); 113 // Uncomment to show the control points: 114 // ctx.lineTo(lastRightX, lastRightY); 115 // ctx.lineTo(controls[0], controls[1]); 116 // ctx.lineTo(p1.canvasx, p1.canvasy); 117 lastRightX = (lastRightX !== null) ? lastRightX : p0.canvasx; 118 lastRightY = (lastRightY !== null) ? lastRightY : p0.canvasy; 119 ctx.bezierCurveTo(lastRightX, lastRightY, 120 controls[0], controls[1], 121 p1.canvasx, p1.canvasy); 122 lastRightX = controls[2]; 123 lastRightY = controls[3]; 124 } else if (p1) { 125 // We're starting again after a missing point. 126 ctx.moveTo(p1.canvasx, p1.canvasy); 127 lastRightX = p1.canvasx; 128 lastRightY = p1.canvasy; 129 } else { 130 lastRightX = lastRightY = null; 131 } 132 } 133 134 ctx.stroke(); 135 } 136 smoothPlotter.smoothing = 1/3; 137 smoothPlotter._getControlPoints = getControlPoints; // for testing 138 139 // older versions exported a global. 140 // This will be removed in the future. 141 // The preferred way to access smoothPlotter is via Dygraph.smoothPlotter. 142 window.smoothPlotter = smoothPlotter; 143 Dygraph.smoothPlotter = smoothPlotter; 144 145 })(); 146