You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

270 lines
8.8KB

  1. "use strict";
  2. module.exports = function(Chart) {
  3. var helpers = Chart.helpers,
  4. defaults = Chart.defaults;
  5. defaults.doughnut = {
  6. animation: {
  7. //Boolean - Whether we animate the rotation of the Doughnut
  8. animateRotate: true,
  9. //Boolean - Whether we animate scaling the Doughnut from the centre
  10. animateScale: false
  11. },
  12. aspectRatio: 1,
  13. hover: {
  14. mode: 'single'
  15. },
  16. legendCallback: function(chart) {
  17. var text = [];
  18. text.push('<ul class="' + chart.id + '-legend">');
  19. var data = chart.data;
  20. var datasets = data.datasets;
  21. var labels = data.labels;
  22. if (datasets.length) {
  23. for (var i = 0; i < datasets[0].data.length; ++i) {
  24. text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>');
  25. if (labels[i]) {
  26. text.push(labels[i]);
  27. }
  28. text.push('</li>');
  29. }
  30. }
  31. text.push('</ul>');
  32. return text.join("");
  33. },
  34. legend: {
  35. labels: {
  36. generateLabels: function(chart) {
  37. var data = chart.data;
  38. if (data.labels.length && data.datasets.length) {
  39. return data.labels.map(function(label, i) {
  40. var meta = chart.getDatasetMeta(0);
  41. var ds = data.datasets[0];
  42. var arc = meta.data[i];
  43. var custom = arc.custom || {};
  44. var getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault;
  45. var arcOpts = chart.options.elements.arc;
  46. var fill = custom.backgroundColor ? custom.backgroundColor : getValueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
  47. var stroke = custom.borderColor ? custom.borderColor : getValueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
  48. var bw = custom.borderWidth ? custom.borderWidth : getValueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
  49. return {
  50. text: label,
  51. fillStyle: fill,
  52. strokeStyle: stroke,
  53. lineWidth: bw,
  54. hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
  55. // Extra data used for toggling the correct item
  56. index: i
  57. };
  58. });
  59. } else {
  60. return [];
  61. }
  62. }
  63. },
  64. onClick: function(e, legendItem) {
  65. var index = legendItem.index;
  66. var chart = this.chart;
  67. var i, ilen, meta;
  68. for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
  69. meta = chart.getDatasetMeta(i);
  70. meta.data[index].hidden = !meta.data[index].hidden;
  71. }
  72. chart.update();
  73. }
  74. },
  75. //The percentage of the chart that we cut out of the middle.
  76. cutoutPercentage: 50,
  77. //The rotation of the chart, where the first data arc begins.
  78. rotation: Math.PI * -0.5,
  79. //The total circumference of the chart.
  80. circumference: Math.PI * 2.0,
  81. // Need to override these to give a nice default
  82. tooltips: {
  83. callbacks: {
  84. title: function() {
  85. return '';
  86. },
  87. label: function(tooltipItem, data) {
  88. return data.labels[tooltipItem.index] + ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
  89. }
  90. }
  91. }
  92. };
  93. defaults.pie = helpers.clone(defaults.doughnut);
  94. helpers.extend(defaults.pie, {
  95. cutoutPercentage: 0
  96. });
  97. Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({
  98. dataElementType: Chart.elements.Arc,
  99. linkScales: helpers.noop,
  100. // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly
  101. getRingIndex: function getRingIndex(datasetIndex) {
  102. var ringIndex = 0;
  103. for (var j = 0; j < datasetIndex; ++j) {
  104. if (this.chart.isDatasetVisible(j)) {
  105. ++ringIndex;
  106. }
  107. }
  108. return ringIndex;
  109. },
  110. update: function update(reset) {
  111. var me = this;
  112. var chart = me.chart,
  113. chartArea = chart.chartArea,
  114. opts = chart.options,
  115. arcOpts = opts.elements.arc,
  116. availableWidth = chartArea.right - chartArea.left - arcOpts.borderWidth,
  117. availableHeight = chartArea.bottom - chartArea.top - arcOpts.borderWidth,
  118. minSize = Math.min(availableWidth, availableHeight),
  119. offset = {
  120. x: 0,
  121. y: 0
  122. },
  123. meta = me.getMeta(),
  124. cutoutPercentage = opts.cutoutPercentage,
  125. circumference = opts.circumference;
  126. // If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc
  127. if (circumference < Math.PI * 2.0) {
  128. var startAngle = opts.rotation % (Math.PI * 2.0);
  129. startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0);
  130. var endAngle = startAngle + circumference;
  131. var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)};
  132. var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)};
  133. var contains0 = (startAngle <= 0 && 0 <= endAngle) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle);
  134. var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle);
  135. var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle);
  136. var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle);
  137. var cutout = cutoutPercentage / 100.0;
  138. var min = {x: contains180 ? -1 : Math.min(start.x * (start.x < 0 ? 1 : cutout), end.x * (end.x < 0 ? 1 : cutout)), y: contains270 ? -1 : Math.min(start.y * (start.y < 0 ? 1 : cutout), end.y * (end.y < 0 ? 1 : cutout))};
  139. var max = {x: contains0 ? 1 : Math.max(start.x * (start.x > 0 ? 1 : cutout), end.x * (end.x > 0 ? 1 : cutout)), y: contains90 ? 1 : Math.max(start.y * (start.y > 0 ? 1 : cutout), end.y * (end.y > 0 ? 1 : cutout))};
  140. var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5};
  141. minSize = Math.min(availableWidth / size.width, availableHeight / size.height);
  142. offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5};
  143. }
  144. chart.outerRadius = Math.max(minSize / 2, 0);
  145. chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 1, 0);
  146. chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
  147. chart.offsetX = offset.x * chart.outerRadius;
  148. chart.offsetY = offset.y * chart.outerRadius;
  149. meta.total = me.calculateTotal();
  150. me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index));
  151. me.innerRadius = me.outerRadius - chart.radiusLength;
  152. helpers.each(meta.data, function(arc, index) {
  153. me.updateElement(arc, index, reset);
  154. });
  155. },
  156. updateElement: function(arc, index, reset) {
  157. var me = this;
  158. var chart = me.chart,
  159. chartArea = chart.chartArea,
  160. opts = chart.options,
  161. animationOpts = opts.animation,
  162. arcOpts = opts.elements.arc,
  163. centerX = (chartArea.left + chartArea.right) / 2,
  164. centerY = (chartArea.top + chartArea.bottom) / 2,
  165. startAngle = opts.rotation, // non reset case handled later
  166. endAngle = opts.rotation, // non reset case handled later
  167. dataset = me.getDataset(),
  168. circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / (2.0 * Math.PI)),
  169. innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius,
  170. outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius,
  171. custom = arc.custom || {},
  172. valueAtIndexOrDefault = helpers.getValueAtIndexOrDefault;
  173. helpers.extend(arc, {
  174. // Utility
  175. _datasetIndex: me.index,
  176. _index: index,
  177. // Desired view properties
  178. _model: {
  179. x: centerX + chart.offsetX,
  180. y: centerY + chart.offsetY,
  181. startAngle: startAngle,
  182. endAngle: endAngle,
  183. circumference: circumference,
  184. outerRadius: outerRadius,
  185. innerRadius: innerRadius,
  186. label: valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index])
  187. }
  188. });
  189. var model = arc._model;
  190. // Resets the visual styles
  191. this.removeHoverStyle(arc);
  192. // Set correct angles if not resetting
  193. if (!reset || !animationOpts.animateRotate) {
  194. if (index === 0) {
  195. model.startAngle = opts.rotation;
  196. } else {
  197. model.startAngle = me.getMeta().data[index - 1]._model.endAngle;
  198. }
  199. model.endAngle = model.startAngle + model.circumference;
  200. }
  201. arc.pivot();
  202. },
  203. removeHoverStyle: function(arc) {
  204. Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc);
  205. },
  206. calculateTotal: function() {
  207. var dataset = this.getDataset();
  208. var meta = this.getMeta();
  209. var total = 0;
  210. var value;
  211. helpers.each(meta.data, function(element, index) {
  212. value = dataset.data[index];
  213. if (!isNaN(value) && !element.hidden) {
  214. total += Math.abs(value);
  215. }
  216. });
  217. return total;
  218. },
  219. calculateCircumference: function(value) {
  220. var total = this.getMeta().total;
  221. if (total > 0 && !isNaN(value)) {
  222. return (Math.PI * 2.0) * (value / total);
  223. } else {
  224. return 0;
  225. }
  226. }
  227. });
  228. };