964a4314839d59bc6c3218105ac211782a4b80f8.svn-base 7.3 KB

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