dygraph-smooth-plotter-c91c859.js 4.5 KB

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