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.

315 lines
7.3 KiB

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