funnel.src.js 8.9 KB

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