controller.line.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. 'use strict';
  2. module.exports = function(Chart) {
  3. var helpers = Chart.helpers;
  4. Chart.defaults.line = {
  5. showLines: true,
  6. spanGaps: false,
  7. hover: {
  8. mode: 'label'
  9. },
  10. scales: {
  11. xAxes: [{
  12. type: 'category',
  13. id: 'x-axis-0'
  14. }],
  15. yAxes: [{
  16. type: 'linear',
  17. id: 'y-axis-0'
  18. }]
  19. }
  20. };
  21. function lineEnabled(dataset, options) {
  22. return helpers.getValueOrDefault(dataset.showLine, options.showLines);
  23. }
  24. Chart.controllers.line = Chart.DatasetController.extend({
  25. datasetElementType: Chart.elements.Line,
  26. dataElementType: Chart.elements.Point,
  27. update: function(reset) {
  28. var me = this;
  29. var meta = me.getMeta();
  30. var line = meta.dataset;
  31. var points = meta.data || [];
  32. var options = me.chart.options;
  33. var lineElementOptions = options.elements.line;
  34. var scale = me.getScaleForId(meta.yAxisID);
  35. var i, ilen, custom;
  36. var dataset = me.getDataset();
  37. var showLine = lineEnabled(dataset, options);
  38. // Update Line
  39. if (showLine) {
  40. custom = line.custom || {};
  41. // Compatibility: If the properties are defined with only the old name, use those values
  42. if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
  43. dataset.lineTension = dataset.tension;
  44. }
  45. // Utility
  46. line._scale = scale;
  47. line._datasetIndex = me.index;
  48. // Data
  49. line._children = points;
  50. // Model
  51. line._model = {
  52. // Appearance
  53. // The default behavior of lines is to break at null values, according
  54. // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158
  55. // This option gives lines the ability to span gaps
  56. spanGaps: dataset.spanGaps ? dataset.spanGaps : options.spanGaps,
  57. tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.lineTension, lineElementOptions.tension),
  58. backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
  59. borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
  60. borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
  61. borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
  62. borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
  63. borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
  64. borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
  65. fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
  66. steppedLine: custom.steppedLine ? custom.steppedLine : helpers.getValueOrDefault(dataset.steppedLine, lineElementOptions.stepped),
  67. cubicInterpolationMode: custom.cubicInterpolationMode ? custom.cubicInterpolationMode : helpers.getValueOrDefault(dataset.cubicInterpolationMode, lineElementOptions.cubicInterpolationMode),
  68. // Scale
  69. scaleTop: scale.top,
  70. scaleBottom: scale.bottom,
  71. scaleZero: scale.getBasePixel()
  72. };
  73. line.pivot();
  74. }
  75. // Update Points
  76. for (i=0, ilen=points.length; i<ilen; ++i) {
  77. me.updateElement(points[i], i, reset);
  78. }
  79. if (showLine && line._model.tension !== 0) {
  80. me.updateBezierControlPoints();
  81. }
  82. // Now pivot the point for animation
  83. for (i=0, ilen=points.length; i<ilen; ++i) {
  84. points[i].pivot();
  85. }
  86. },
  87. getPointBackgroundColor: function(point, index) {
  88. var backgroundColor = this.chart.options.elements.point.backgroundColor;
  89. var dataset = this.getDataset();
  90. var custom = point.custom || {};
  91. if (custom.backgroundColor) {
  92. backgroundColor = custom.backgroundColor;
  93. } else if (dataset.pointBackgroundColor) {
  94. backgroundColor = helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor);
  95. } else if (dataset.backgroundColor) {
  96. backgroundColor = dataset.backgroundColor;
  97. }
  98. return backgroundColor;
  99. },
  100. getPointBorderColor: function(point, index) {
  101. var borderColor = this.chart.options.elements.point.borderColor;
  102. var dataset = this.getDataset();
  103. var custom = point.custom || {};
  104. if (custom.borderColor) {
  105. borderColor = custom.borderColor;
  106. } else if (dataset.pointBorderColor) {
  107. borderColor = helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, borderColor);
  108. } else if (dataset.borderColor) {
  109. borderColor = dataset.borderColor;
  110. }
  111. return borderColor;
  112. },
  113. getPointBorderWidth: function(point, index) {
  114. var borderWidth = this.chart.options.elements.point.borderWidth;
  115. var dataset = this.getDataset();
  116. var custom = point.custom || {};
  117. if (!isNaN(custom.borderWidth)) {
  118. borderWidth = custom.borderWidth;
  119. } else if (!isNaN(dataset.pointBorderWidth)) {
  120. borderWidth = helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth);
  121. } else if (!isNaN(dataset.borderWidth)) {
  122. borderWidth = dataset.borderWidth;
  123. }
  124. return borderWidth;
  125. },
  126. updateElement: function(point, index, reset) {
  127. var me = this;
  128. var meta = me.getMeta();
  129. var custom = point.custom || {};
  130. var dataset = me.getDataset();
  131. var datasetIndex = me.index;
  132. var value = dataset.data[index];
  133. var yScale = me.getScaleForId(meta.yAxisID);
  134. var xScale = me.getScaleForId(meta.xAxisID);
  135. var pointOptions = me.chart.options.elements.point;
  136. var x, y;
  137. var labels = me.chart.data.labels || [];
  138. var includeOffset = (labels.length === 1 || dataset.data.length === 1) || me.chart.isCombo;
  139. // Compatibility: If the properties are defined with only the old name, use those values
  140. if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
  141. dataset.pointRadius = dataset.radius;
  142. }
  143. if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) {
  144. dataset.pointHitRadius = dataset.hitRadius;
  145. }
  146. x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex, includeOffset);
  147. y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex);
  148. // Utility
  149. point._xScale = xScale;
  150. point._yScale = yScale;
  151. point._datasetIndex = datasetIndex;
  152. point._index = index;
  153. // Desired view properties
  154. point._model = {
  155. x: x,
  156. y: y,
  157. skip: custom.skip || isNaN(x) || isNaN(y),
  158. // Appearance
  159. radius: custom.radius || helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, pointOptions.radius),
  160. pointStyle: custom.pointStyle || helpers.getValueAtIndexOrDefault(dataset.pointStyle, index, pointOptions.pointStyle),
  161. backgroundColor: me.getPointBackgroundColor(point, index),
  162. borderColor: me.getPointBorderColor(point, index),
  163. borderWidth: me.getPointBorderWidth(point, index),
  164. tension: meta.dataset._model ? meta.dataset._model.tension : 0,
  165. steppedLine: meta.dataset._model ? meta.dataset._model.steppedLine : false,
  166. // Tooltip
  167. hitRadius: custom.hitRadius || helpers.getValueAtIndexOrDefault(dataset.pointHitRadius, index, pointOptions.hitRadius)
  168. };
  169. },
  170. calculatePointY: function(value, index, datasetIndex) {
  171. var me = this;
  172. var chart = me.chart;
  173. var meta = me.getMeta();
  174. var yScale = me.getScaleForId(meta.yAxisID);
  175. var sumPos = 0;
  176. var sumNeg = 0;
  177. var i, ds, dsMeta;
  178. if (yScale.options.stacked) {
  179. for (i = 0; i < datasetIndex; i++) {
  180. ds = chart.data.datasets[i];
  181. dsMeta = chart.getDatasetMeta(i);
  182. if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) {
  183. var stackedRightValue = Number(yScale.getRightValue(ds.data[index]));
  184. if (stackedRightValue < 0) {
  185. sumNeg += stackedRightValue || 0;
  186. } else {
  187. sumPos += stackedRightValue || 0;
  188. }
  189. }
  190. }
  191. var rightValue = Number(yScale.getRightValue(value));
  192. if (rightValue < 0) {
  193. return yScale.getPixelForValue(sumNeg + rightValue);
  194. }
  195. return yScale.getPixelForValue(sumPos + rightValue);
  196. }
  197. return yScale.getPixelForValue(value);
  198. },
  199. updateBezierControlPoints: function() {
  200. var me = this;
  201. var meta = me.getMeta();
  202. var area = me.chart.chartArea;
  203. var points = (meta.data || []);
  204. var i, ilen, point, model, controlPoints;
  205. // Only consider points that are drawn in case the spanGaps option is used
  206. if (meta.dataset._model.spanGaps) {
  207. points = points.filter(function(pt) {
  208. return !pt._model.skip;
  209. });
  210. }
  211. function capControlPoint(pt, min, max) {
  212. return Math.max(Math.min(pt, max), min);
  213. }
  214. if (meta.dataset._model.cubicInterpolationMode === 'monotone') {
  215. helpers.splineCurveMonotone(points);
  216. } else {
  217. for (i = 0, ilen = points.length; i < ilen; ++i) {
  218. point = points[i];
  219. model = point._model;
  220. controlPoints = helpers.splineCurve(
  221. helpers.previousItem(points, i)._model,
  222. model,
  223. helpers.nextItem(points, i)._model,
  224. meta.dataset._model.tension
  225. );
  226. model.controlPointPreviousX = controlPoints.previous.x;
  227. model.controlPointPreviousY = controlPoints.previous.y;
  228. model.controlPointNextX = controlPoints.next.x;
  229. model.controlPointNextY = controlPoints.next.y;
  230. }
  231. }
  232. if (me.chart.options.elements.line.capBezierPoints) {
  233. for (i = 0, ilen = points.length; i < ilen; ++i) {
  234. model = points[i]._model;
  235. model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right);
  236. model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom);
  237. model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right);
  238. model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom);
  239. }
  240. }
  241. },
  242. draw: function(ease) {
  243. var me = this;
  244. var meta = me.getMeta();
  245. var points = meta.data || [];
  246. var easingDecimal = ease || 1;
  247. var i, ilen;
  248. // Transition Point Locations
  249. for (i=0, ilen=points.length; i<ilen; ++i) {
  250. points[i].transition(easingDecimal);
  251. }
  252. // Transition and Draw the line
  253. if (lineEnabled(me.getDataset(), me.chart.options)) {
  254. meta.dataset.transition(easingDecimal).draw();
  255. }
  256. // Draw the points
  257. for (i=0, ilen=points.length; i<ilen; ++i) {
  258. points[i].draw();
  259. }
  260. },
  261. setHoverStyle: function(point) {
  262. // Point
  263. var dataset = this.chart.data.datasets[point._datasetIndex];
  264. var index = point._index;
  265. var custom = point.custom || {};
  266. var model = point._model;
  267. model.radius = custom.hoverRadius || helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
  268. model.backgroundColor = custom.hoverBackgroundColor || helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
  269. model.borderColor = custom.hoverBorderColor || helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));
  270. model.borderWidth = custom.hoverBorderWidth || helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);
  271. },
  272. removeHoverStyle: function(point) {
  273. var me = this;
  274. var dataset = me.chart.data.datasets[point._datasetIndex];
  275. var index = point._index;
  276. var custom = point.custom || {};
  277. var model = point._model;
  278. // Compatibility: If the properties are defined with only the old name, use those values
  279. if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
  280. dataset.pointRadius = dataset.radius;
  281. }
  282. model.radius = custom.radius || helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, me.chart.options.elements.point.radius);
  283. model.backgroundColor = me.getPointBackgroundColor(point, index);
  284. model.borderColor = me.getPointBorderColor(point, index);
  285. model.borderWidth = me.getPointBorderWidth(point, index);
  286. }
  287. });
  288. };