funnel.src.js 7.0 KB

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