controller.bar.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. 'use strict';
  2. module.exports = function(Chart) {
  3. var helpers = Chart.helpers;
  4. Chart.defaults.bar = {
  5. hover: {
  6. mode: 'label'
  7. },
  8. scales: {
  9. xAxes: [{
  10. type: 'category',
  11. // Specific to Bar Controller
  12. categoryPercentage: 0.8,
  13. barPercentage: 0.9,
  14. // grid line settings
  15. gridLines: {
  16. offsetGridLines: true
  17. }
  18. }],
  19. yAxes: [{
  20. type: 'linear'
  21. }]
  22. }
  23. };
  24. Chart.controllers.bar = Chart.DatasetController.extend({
  25. dataElementType: Chart.elements.Rectangle,
  26. initialize: function(chart, datasetIndex) {
  27. Chart.DatasetController.prototype.initialize.call(this, chart, datasetIndex);
  28. // Use this to indicate that this is a bar dataset.
  29. this.getMeta().bar = true;
  30. },
  31. // Get the number of datasets that display bars. We use this to correctly calculate the bar width
  32. getBarCount: function() {
  33. var me = this;
  34. var barCount = 0;
  35. helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) {
  36. var meta = me.chart.getDatasetMeta(datasetIndex);
  37. if (meta.bar && me.chart.isDatasetVisible(datasetIndex)) {
  38. ++barCount;
  39. }
  40. }, me);
  41. return barCount;
  42. },
  43. update: function(reset) {
  44. var me = this;
  45. helpers.each(me.getMeta().data, function(rectangle, index) {
  46. me.updateElement(rectangle, index, reset);
  47. }, me);
  48. },
  49. updateElement: function(rectangle, index, reset) {
  50. var me = this;
  51. var meta = me.getMeta();
  52. var xScale = me.getScaleForId(meta.xAxisID);
  53. var yScale = me.getScaleForId(meta.yAxisID);
  54. var scaleBase = yScale.getBasePixel();
  55. var rectangleElementOptions = me.chart.options.elements.rectangle;
  56. var custom = rectangle.custom || {};
  57. var dataset = me.getDataset();
  58. rectangle._xScale = xScale;
  59. rectangle._yScale = yScale;
  60. rectangle._datasetIndex = me.index;
  61. rectangle._index = index;
  62. var ruler = me.getRuler(index);
  63. rectangle._model = {
  64. x: me.calculateBarX(index, me.index, ruler),
  65. y: reset ? scaleBase : me.calculateBarY(index, me.index),
  66. // Tooltip
  67. label: me.chart.data.labels[index],
  68. datasetLabel: dataset.label,
  69. // Appearance
  70. base: reset ? scaleBase : me.calculateBarBase(me.index, index),
  71. width: me.calculateBarWidth(ruler),
  72. backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor),
  73. borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleElementOptions.borderSkipped,
  74. borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor),
  75. borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth)
  76. };
  77. rectangle.pivot();
  78. },
  79. calculateBarBase: function(datasetIndex, index) {
  80. var me = this;
  81. var meta = me.getMeta();
  82. var yScale = me.getScaleForId(meta.yAxisID);
  83. var base = 0;
  84. if (yScale.options.stacked) {
  85. var chart = me.chart;
  86. var datasets = chart.data.datasets;
  87. var value = Number(datasets[datasetIndex].data[index]);
  88. for (var i = 0; i < datasetIndex; i++) {
  89. var currentDs = datasets[i];
  90. var currentDsMeta = chart.getDatasetMeta(i);
  91. if (currentDsMeta.bar && currentDsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) {
  92. var currentVal = Number(currentDs.data[index]);
  93. base += value < 0 ? Math.min(currentVal, 0) : Math.max(currentVal, 0);
  94. }
  95. }
  96. return yScale.getPixelForValue(base);
  97. }
  98. return yScale.getBasePixel();
  99. },
  100. getRuler: function(index) {
  101. var me = this;
  102. var meta = me.getMeta();
  103. var xScale = me.getScaleForId(meta.xAxisID);
  104. var datasetCount = me.getBarCount();
  105. var tickWidth;
  106. if (xScale.options.type === 'category') {
  107. tickWidth = xScale.getPixelForTick(index + 1) - xScale.getPixelForTick(index);
  108. } else {
  109. // Average width
  110. tickWidth = xScale.width / xScale.ticks.length;
  111. }
  112. var categoryWidth = tickWidth * xScale.options.categoryPercentage;
  113. var categorySpacing = (tickWidth - (tickWidth * xScale.options.categoryPercentage)) / 2;
  114. var fullBarWidth = categoryWidth / datasetCount;
  115. if (xScale.ticks.length !== me.chart.data.labels.length) {
  116. var perc = xScale.ticks.length / me.chart.data.labels.length;
  117. fullBarWidth = fullBarWidth * perc;
  118. }
  119. var barWidth = fullBarWidth * xScale.options.barPercentage;
  120. var barSpacing = fullBarWidth - (fullBarWidth * xScale.options.barPercentage);
  121. return {
  122. datasetCount: datasetCount,
  123. tickWidth: tickWidth,
  124. categoryWidth: categoryWidth,
  125. categorySpacing: categorySpacing,
  126. fullBarWidth: fullBarWidth,
  127. barWidth: barWidth,
  128. barSpacing: barSpacing
  129. };
  130. },
  131. calculateBarWidth: function(ruler) {
  132. var xScale = this.getScaleForId(this.getMeta().xAxisID);
  133. if (xScale.options.barThickness) {
  134. return xScale.options.barThickness;
  135. }
  136. return xScale.options.stacked ? ruler.categoryWidth : ruler.barWidth;
  137. },
  138. // Get bar index from the given dataset index accounting for the fact that not all bars are visible
  139. getBarIndex: function(datasetIndex) {
  140. var barIndex = 0;
  141. var meta, j;
  142. for (j = 0; j < datasetIndex; ++j) {
  143. meta = this.chart.getDatasetMeta(j);
  144. if (meta.bar && this.chart.isDatasetVisible(j)) {
  145. ++barIndex;
  146. }
  147. }
  148. return barIndex;
  149. },
  150. calculateBarX: function(index, datasetIndex, ruler) {
  151. var me = this;
  152. var meta = me.getMeta();
  153. var xScale = me.getScaleForId(meta.xAxisID);
  154. var barIndex = me.getBarIndex(datasetIndex);
  155. var leftTick = xScale.getPixelForValue(null, index, datasetIndex, me.chart.isCombo);
  156. leftTick -= me.chart.isCombo ? (ruler.tickWidth / 2) : 0;
  157. if (xScale.options.stacked) {
  158. return leftTick + (ruler.categoryWidth / 2) + ruler.categorySpacing;
  159. }
  160. return leftTick +
  161. (ruler.barWidth / 2) +
  162. ruler.categorySpacing +
  163. (ruler.barWidth * barIndex) +
  164. (ruler.barSpacing / 2) +
  165. (ruler.barSpacing * barIndex);
  166. },
  167. calculateBarY: function(index, datasetIndex) {
  168. var me = this;
  169. var meta = me.getMeta();
  170. var yScale = me.getScaleForId(meta.yAxisID);
  171. var value = Number(me.getDataset().data[index]);
  172. if (yScale.options.stacked) {
  173. var sumPos = 0,
  174. sumNeg = 0;
  175. for (var i = 0; i < datasetIndex; i++) {
  176. var ds = me.chart.data.datasets[i];
  177. var dsMeta = me.chart.getDatasetMeta(i);
  178. if (dsMeta.bar && dsMeta.yAxisID === yScale.id && me.chart.isDatasetVisible(i)) {
  179. var stackedVal = Number(ds.data[index]);
  180. if (stackedVal < 0) {
  181. sumNeg += stackedVal || 0;
  182. } else {
  183. sumPos += stackedVal || 0;
  184. }
  185. }
  186. }
  187. if (value < 0) {
  188. return yScale.getPixelForValue(sumNeg + value);
  189. }
  190. return yScale.getPixelForValue(sumPos + value);
  191. }
  192. return yScale.getPixelForValue(value);
  193. },
  194. draw: function(ease) {
  195. var me = this;
  196. var easingDecimal = ease || 1;
  197. var metaData = me.getMeta().data;
  198. var dataset = me.getDataset();
  199. var i, len;
  200. for (i = 0, len = metaData.length; i < len; ++i) {
  201. var d = dataset.data[i];
  202. if (d !== null && d !== undefined && !isNaN(d)) {
  203. metaData[i].transition(easingDecimal).draw();
  204. }
  205. }
  206. },
  207. setHoverStyle: function(rectangle) {
  208. var dataset = this.chart.data.datasets[rectangle._datasetIndex];
  209. var index = rectangle._index;
  210. var custom = rectangle.custom || {};
  211. var model = rectangle._model;
  212. model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
  213. model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.getHoverColor(model.borderColor));
  214. model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
  215. },
  216. removeHoverStyle: function(rectangle) {
  217. var dataset = this.chart.data.datasets[rectangle._datasetIndex];
  218. var index = rectangle._index;
  219. var custom = rectangle.custom || {};
  220. var model = rectangle._model;
  221. var rectangleElementOptions = this.chart.options.elements.rectangle;
  222. model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor);
  223. model.borderColor = custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor);
  224. model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth);
  225. }
  226. });
  227. // including horizontalBar in the bar file, instead of a file of its own
  228. // it extends bar (like pie extends doughnut)
  229. Chart.defaults.horizontalBar = {
  230. hover: {
  231. mode: 'label'
  232. },
  233. scales: {
  234. xAxes: [{
  235. type: 'linear',
  236. position: 'bottom'
  237. }],
  238. yAxes: [{
  239. position: 'left',
  240. type: 'category',
  241. // Specific to Horizontal Bar Controller
  242. categoryPercentage: 0.8,
  243. barPercentage: 0.9,
  244. // grid line settings
  245. gridLines: {
  246. offsetGridLines: true
  247. }
  248. }]
  249. },
  250. elements: {
  251. rectangle: {
  252. borderSkipped: 'left'
  253. }
  254. },
  255. tooltips: {
  256. callbacks: {
  257. title: function(tooltipItems, data) {
  258. // Pick first xLabel for now
  259. var title = '';
  260. if (tooltipItems.length > 0) {
  261. if (tooltipItems[0].yLabel) {
  262. title = tooltipItems[0].yLabel;
  263. } else if (data.labels.length > 0 && tooltipItems[0].index < data.labels.length) {
  264. title = data.labels[tooltipItems[0].index];
  265. }
  266. }
  267. return title;
  268. },
  269. label: function(tooltipItem, data) {
  270. var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || '';
  271. return datasetLabel + ': ' + tooltipItem.xLabel;
  272. }
  273. }
  274. }
  275. };
  276. Chart.controllers.horizontalBar = Chart.controllers.bar.extend({
  277. updateElement: function(rectangle, index, reset) {
  278. var me = this;
  279. var meta = me.getMeta();
  280. var xScale = me.getScaleForId(meta.xAxisID);
  281. var yScale = me.getScaleForId(meta.yAxisID);
  282. var scaleBase = xScale.getBasePixel();
  283. var custom = rectangle.custom || {};
  284. var dataset = me.getDataset();
  285. var rectangleElementOptions = me.chart.options.elements.rectangle;
  286. rectangle._xScale = xScale;
  287. rectangle._yScale = yScale;
  288. rectangle._datasetIndex = me.index;
  289. rectangle._index = index;
  290. var ruler = me.getRuler(index);
  291. rectangle._model = {
  292. x: reset ? scaleBase : me.calculateBarX(index, me.index),
  293. y: me.calculateBarY(index, me.index, ruler),
  294. // Tooltip
  295. label: me.chart.data.labels[index],
  296. datasetLabel: dataset.label,
  297. // Appearance
  298. base: reset ? scaleBase : me.calculateBarBase(me.index, index),
  299. height: me.calculateBarHeight(ruler),
  300. backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor),
  301. borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleElementOptions.borderSkipped,
  302. borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor),
  303. borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth)
  304. };
  305. rectangle.draw = function() {
  306. var ctx = this._chart.ctx;
  307. var vm = this._view;
  308. var halfHeight = vm.height / 2,
  309. topY = vm.y - halfHeight,
  310. bottomY = vm.y + halfHeight,
  311. right = vm.base - (vm.base - vm.x),
  312. halfStroke = vm.borderWidth / 2;
  313. // Canvas doesn't allow us to stroke inside the width so we can
  314. // adjust the sizes to fit if we're setting a stroke on the line
  315. if (vm.borderWidth) {
  316. topY += halfStroke;
  317. bottomY -= halfStroke;
  318. right += halfStroke;
  319. }
  320. ctx.beginPath();
  321. ctx.fillStyle = vm.backgroundColor;
  322. ctx.strokeStyle = vm.borderColor;
  323. ctx.lineWidth = vm.borderWidth;
  324. // Corner points, from bottom-left to bottom-right clockwise
  325. // | 1 2 |
  326. // | 0 3 |
  327. var corners = [
  328. [vm.base, bottomY],
  329. [vm.base, topY],
  330. [right, topY],
  331. [right, bottomY]
  332. ];
  333. // Find first (starting) corner with fallback to 'bottom'
  334. var borders = ['bottom', 'left', 'top', 'right'];
  335. var startCorner = borders.indexOf(vm.borderSkipped, 0);
  336. if (startCorner === -1) {
  337. startCorner = 0;
  338. }
  339. function cornerAt(cornerIndex) {
  340. return corners[(startCorner + cornerIndex) % 4];
  341. }
  342. // Draw rectangle from 'startCorner'
  343. ctx.moveTo.apply(ctx, cornerAt(0));
  344. for (var i = 1; i < 4; i++) {
  345. ctx.lineTo.apply(ctx, cornerAt(i));
  346. }
  347. ctx.fill();
  348. if (vm.borderWidth) {
  349. ctx.stroke();
  350. }
  351. };
  352. rectangle.pivot();
  353. },
  354. calculateBarBase: function(datasetIndex, index) {
  355. var me = this;
  356. var meta = me.getMeta();
  357. var xScale = me.getScaleForId(meta.xAxisID);
  358. var base = 0;
  359. if (xScale.options.stacked) {
  360. var chart = me.chart;
  361. var datasets = chart.data.datasets;
  362. var value = Number(datasets[datasetIndex].data[index]);
  363. for (var i = 0; i < datasetIndex; i++) {
  364. var currentDs = datasets[i];
  365. var currentDsMeta = chart.getDatasetMeta(i);
  366. if (currentDsMeta.bar && currentDsMeta.xAxisID === xScale.id && chart.isDatasetVisible(i)) {
  367. var currentVal = Number(currentDs.data[index]);
  368. base += value < 0 ? Math.min(currentVal, 0) : Math.max(currentVal, 0);
  369. }
  370. }
  371. return xScale.getPixelForValue(base);
  372. }
  373. return xScale.getBasePixel();
  374. },
  375. getRuler: function(index) {
  376. var me = this;
  377. var meta = me.getMeta();
  378. var yScale = me.getScaleForId(meta.yAxisID);
  379. var datasetCount = me.getBarCount();
  380. var tickHeight;
  381. if (yScale.options.type === 'category') {
  382. tickHeight = yScale.getPixelForTick(index + 1) - yScale.getPixelForTick(index);
  383. } else {
  384. // Average width
  385. tickHeight = yScale.width / yScale.ticks.length;
  386. }
  387. var categoryHeight = tickHeight * yScale.options.categoryPercentage;
  388. var categorySpacing = (tickHeight - (tickHeight * yScale.options.categoryPercentage)) / 2;
  389. var fullBarHeight = categoryHeight / datasetCount;
  390. if (yScale.ticks.length !== me.chart.data.labels.length) {
  391. var perc = yScale.ticks.length / me.chart.data.labels.length;
  392. fullBarHeight = fullBarHeight * perc;
  393. }
  394. var barHeight = fullBarHeight * yScale.options.barPercentage;
  395. var barSpacing = fullBarHeight - (fullBarHeight * yScale.options.barPercentage);
  396. return {
  397. datasetCount: datasetCount,
  398. tickHeight: tickHeight,
  399. categoryHeight: categoryHeight,
  400. categorySpacing: categorySpacing,
  401. fullBarHeight: fullBarHeight,
  402. barHeight: barHeight,
  403. barSpacing: barSpacing
  404. };
  405. },
  406. calculateBarHeight: function(ruler) {
  407. var me = this;
  408. var yScale = me.getScaleForId(me.getMeta().yAxisID);
  409. if (yScale.options.barThickness) {
  410. return yScale.options.barThickness;
  411. }
  412. return yScale.options.stacked ? ruler.categoryHeight : ruler.barHeight;
  413. },
  414. calculateBarX: function(index, datasetIndex) {
  415. var me = this;
  416. var meta = me.getMeta();
  417. var xScale = me.getScaleForId(meta.xAxisID);
  418. var value = Number(me.getDataset().data[index]);
  419. if (xScale.options.stacked) {
  420. var sumPos = 0,
  421. sumNeg = 0;
  422. for (var i = 0; i < datasetIndex; i++) {
  423. var ds = me.chart.data.datasets[i];
  424. var dsMeta = me.chart.getDatasetMeta(i);
  425. if (dsMeta.bar && dsMeta.xAxisID === xScale.id && me.chart.isDatasetVisible(i)) {
  426. var stackedVal = Number(ds.data[index]);
  427. if (stackedVal < 0) {
  428. sumNeg += stackedVal || 0;
  429. } else {
  430. sumPos += stackedVal || 0;
  431. }
  432. }
  433. }
  434. if (value < 0) {
  435. return xScale.getPixelForValue(sumNeg + value);
  436. }
  437. return xScale.getPixelForValue(sumPos + value);
  438. }
  439. return xScale.getPixelForValue(value);
  440. },
  441. calculateBarY: function(index, datasetIndex, ruler) {
  442. var me = this;
  443. var meta = me.getMeta();
  444. var yScale = me.getScaleForId(meta.yAxisID);
  445. var barIndex = me.getBarIndex(datasetIndex);
  446. var topTick = yScale.getPixelForValue(null, index, datasetIndex, me.chart.isCombo);
  447. topTick -= me.chart.isCombo ? (ruler.tickHeight / 2) : 0;
  448. if (yScale.options.stacked) {
  449. return topTick + (ruler.categoryHeight / 2) + ruler.categorySpacing;
  450. }
  451. return topTick +
  452. (ruler.barHeight / 2) +
  453. ruler.categorySpacing +
  454. (ruler.barHeight * barIndex) +
  455. (ruler.barSpacing / 2) +
  456. (ruler.barSpacing * barIndex);
  457. }
  458. });
  459. };