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.

274 lines
9.0 KiB

2 years ago
  1. /**
  2. * @license
  3. * Highcharts funnel module, Beta
  4. *
  5. * (c) 2010-2012 Torstein Hønsi
  6. *
  7. * License: www.highcharts.com/license
  8. */
  9. /*global Highcharts */
  10. (function (Highcharts) {
  11. 'use strict';
  12. // create shortcuts
  13. var defaultOptions = Highcharts.getOptions(),
  14. defaultPlotOptions = defaultOptions.plotOptions,
  15. seriesTypes = Highcharts.seriesTypes,
  16. merge = Highcharts.merge,
  17. noop = function () { },
  18. each = Highcharts.each;
  19. // set default options
  20. defaultPlotOptions.funnel = merge(defaultPlotOptions.pie, {
  21. center: ['50%', '50%'],
  22. width: '90%',
  23. neckWidth: '30%',
  24. height: '100%',
  25. neckHeight: '25%',
  26. dataLabels: {
  27. //position: 'right',
  28. connectorWidth: 1,
  29. connectorColor: '#606060'
  30. },
  31. size: true, // to avoid adapting to data label size in Pie.drawDataLabels
  32. states: {
  33. select: {
  34. color: '#C0C0C0',
  35. borderColor: '#000000',
  36. shadow: false
  37. }
  38. }
  39. });
  40. seriesTypes.funnel = Highcharts.extendClass(seriesTypes.pie, {
  41. type: 'funnel',
  42. animate: noop,
  43. /**
  44. * Overrides the pie translate method
  45. */
  46. translate: function () {
  47. var
  48. // Get positions - either an integer or a percentage string must be given
  49. getLength = function (length, relativeTo) {
  50. return (/%$/).test(length) ?
  51. relativeTo * parseInt(length, 10) / 100 :
  52. parseInt(length, 10);
  53. },
  54. sum = 0,
  55. series = this,
  56. chart = series.chart,
  57. plotWidth = chart.plotWidth,
  58. plotHeight = chart.plotHeight,
  59. cumulative = 0, // start at top
  60. options = series.options,
  61. center = options.center,
  62. centerX = getLength(center[0], plotWidth),
  63. centerY = getLength(center[0], plotHeight),
  64. width = getLength(options.width, plotWidth),
  65. tempWidth,
  66. getWidthAt,
  67. height = getLength(options.height, plotHeight),
  68. neckWidth = getLength(options.neckWidth, plotWidth),
  69. neckHeight = getLength(options.neckHeight, plotHeight),
  70. neckY = height - neckHeight,
  71. data = series.data,
  72. path,
  73. fraction,
  74. half = options.dataLabels.position === 'left' ? 1 : 0,
  75. x1,
  76. y1,
  77. x2,
  78. x3,
  79. y3,
  80. x4,
  81. y5;
  82. // Return the width at a specific y coordinate
  83. series.getWidthAt = getWidthAt = function (y) {
  84. return y > height - neckHeight || height === neckHeight ?
  85. neckWidth :
  86. neckWidth + (width - neckWidth) * ((height - neckHeight - y) / (height - neckHeight));
  87. };
  88. series.getX = function (y, half) {
  89. return centerX + (half ? -1 : 1) * ((getWidthAt(y) / 2) + options.dataLabels.distance);
  90. };
  91. // Expose
  92. series.center = [centerX, centerY, height];
  93. series.centerX = centerX;
  94. /*
  95. * Individual point coordinate naming:
  96. *
  97. * x1,y1 _________________ x2,y1
  98. * \ /
  99. * \ /
  100. * \ /
  101. * \ /
  102. * \ /
  103. * x3,y3 _________ x4,y3
  104. *
  105. * Additional for the base of the neck:
  106. *
  107. * | |
  108. * | |
  109. * | |
  110. * x3,y5 _________ x4,y5
  111. */
  112. // get the total sum
  113. each(data, function (point) {
  114. sum += point.y;
  115. });
  116. each(data, function (point) {
  117. // set start and end positions
  118. y5 = null;
  119. fraction = sum ? point.y / sum : 0;
  120. y1 = centerY - height / 2 + cumulative * height;
  121. y3 = y1 + fraction * height;
  122. //tempWidth = neckWidth + (width - neckWidth) * ((height - neckHeight - y1) / (height - neckHeight));
  123. tempWidth = getWidthAt(y1);
  124. x1 = centerX - tempWidth / 2;
  125. x2 = x1 + tempWidth;
  126. tempWidth = getWidthAt(y3);
  127. x3 = centerX - tempWidth / 2;
  128. x4 = x3 + tempWidth;
  129. // the entire point is within the neck
  130. if (y1 > neckY) {
  131. x1 = x3 = centerX - neckWidth / 2;
  132. x2 = x4 = centerX + neckWidth / 2;
  133. // the base of the neck
  134. } else if (y3 > neckY) {
  135. y5 = y3;
  136. tempWidth = getWidthAt(neckY);
  137. x3 = centerX - tempWidth / 2;
  138. x4 = x3 + tempWidth;
  139. y3 = neckY;
  140. }
  141. // save the path
  142. path = [
  143. 'M',
  144. x1, y1,
  145. 'L',
  146. x2, y1,
  147. x4, y3
  148. ];
  149. if (y5) {
  150. path.push(x4, y5, x3, y5);
  151. }
  152. path.push(x3, y3, 'Z');
  153. // prepare for using shared dr
  154. point.shapeType = 'path';
  155. point.shapeArgs = { d: path };
  156. // for tooltips and data labels
  157. point.percentage = fraction * 100;
  158. point.plotX = centerX;
  159. point.plotY = (y1 + (y5 || y3)) / 2;
  160. // Placement of tooltips and data labels
  161. point.tooltipPos = [
  162. centerX,
  163. point.plotY
  164. ];
  165. // Slice is a noop on funnel points
  166. point.slice = noop;
  167. // Mimicking pie data label placement logic
  168. point.half = half;
  169. cumulative += fraction;
  170. });
  171. series.setTooltipPoints();
  172. },
  173. /**
  174. * Draw a single point (wedge)
  175. * @param {Object} point The point object
  176. * @param {Object} color The color of the point
  177. * @param {Number} brightness The brightness relative to the color
  178. */
  179. drawPoints: function () {
  180. var series = this,
  181. options = series.options,
  182. chart = series.chart,
  183. renderer = chart.renderer;
  184. each(series.data, function (point) {
  185. var graphic = point.graphic,
  186. shapeArgs = point.shapeArgs;
  187. if (!graphic) { // Create the shapes
  188. point.graphic = renderer.path(shapeArgs).
  189. attr({
  190. fill: point.color,
  191. stroke: options.borderColor,
  192. 'stroke-width': options.borderWidth
  193. }).
  194. add(series.group);
  195. } else { // Update the shapes
  196. graphic.animate(shapeArgs);
  197. }
  198. });
  199. },
  200. /**
  201. * Funnel items don't have angles (#2289)
  202. */
  203. sortByAngle: noop,
  204. /**
  205. * Extend the pie data label method
  206. */
  207. drawDataLabels: function () {
  208. var data = this.data,
  209. labelDistance = this.options.dataLabels.distance,
  210. leftSide,
  211. sign,
  212. point,
  213. i = data.length,
  214. x,
  215. y;
  216. // In the original pie label anticollision logic, the slots are distributed
  217. // from one labelDistance above to one labelDistance below the pie. In funnels
  218. // we don't want this.
  219. this.center[2] -= 2 * labelDistance;
  220. // Set the label position array for each point.
  221. while (i--) {
  222. point = data[i];
  223. leftSide = point.half;
  224. sign = leftSide ? 1 : -1;
  225. y = point.plotY;
  226. x = this.getX(y, leftSide);
  227. // set the anchor point for data labels
  228. point.labelPos = [
  229. 0, // first break of connector
  230. y, // a/a
  231. x + (labelDistance - 5) * sign, // second break, right outside point shape
  232. y, // a/a
  233. x + labelDistance * sign, // landing point for connector
  234. y, // a/a
  235. leftSide ? 'right' : 'left', // alignment
  236. 0 // center angle
  237. ];
  238. }
  239. seriesTypes.pie.prototype.drawDataLabels.call(this);
  240. }
  241. });
  242. }(Highcharts));