      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