drilldown.src.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. /**
  2. * Highcharts Drilldown plugin
  3. *
  4. * Author: Torstein Honsi
  5. * Last revision: 2013-02-18
  6. * License: MIT License
  7. *
  8. * Demo: http://jsfiddle.net/highcharts/Vf3yT/
  9. */
  10. /*global HighchartsAdapter*/
  11. (function (H) {
  12. "use strict";
  13. var noop = function () {
  14. },
  15. defaultOptions = H.getOptions(),
  16. each = H.each,
  17. extend = H.extend,
  18. wrap = H.wrap,
  19. Chart = H.Chart,
  20. seriesTypes = H.seriesTypes,
  21. PieSeries = seriesTypes.pie,
  22. ColumnSeries = seriesTypes.column,
  23. fireEvent = HighchartsAdapter.fireEvent;
  24. // Utilities
  25. function tweenColors(startColor, endColor, pos) {
  26. var rgba = [
  27. Math.round(startColor[0] + (endColor[0] - startColor[0]) * pos),
  28. Math.round(startColor[1] + (endColor[1] - startColor[1]) * pos),
  29. Math.round(startColor[2] + (endColor[2] - startColor[2]) * pos),
  30. startColor[3] + (endColor[3] - startColor[3]) * pos
  31. ];
  32. return 'rgba(' + rgba.join(',') + ')';
  33. }
  34. // Add language
  35. extend(defaultOptions.lang, {
  36. drillUpText: '◁ Back to {series.name}'
  37. });
  38. defaultOptions.drilldown = {
  39. activeAxisLabelStyle: {
  40. cursor: 'pointer',
  41. color: '#039',
  42. fontWeight: 'bold',
  43. textDecoration: 'underline'
  44. },
  45. activeDataLabelStyle: {
  46. cursor: 'pointer',
  47. color: '#039',
  48. fontWeight: 'bold',
  49. textDecoration: 'underline'
  50. },
  51. animation: {
  52. duration: 500
  53. },
  54. drillUpButton: {
  55. position: {
  56. align: 'right',
  57. x: -10,
  58. y: 10
  59. }
  60. // relativeTo: 'plotBox'
  61. // theme
  62. }
  63. };
  64. /**
  65. * A general fadeIn method
  66. */
  67. H.SVGRenderer.prototype.Element.prototype.fadeIn = function () {
  68. this
  69. .attr({
  70. opacity: 0.1,
  71. visibility: 'visible'
  72. })
  73. .animate({
  74. opacity: 1
  75. }, {
  76. duration: 250
  77. });
  78. };
  79. // Extend the Chart prototype
  80. Chart.prototype.drilldownLevels = [];
  81. Chart.prototype.addSeriesAsDrilldown = function (point, ddOptions) {
  82. var oldSeries = point.series,
  83. xAxis = oldSeries.xAxis,
  84. yAxis = oldSeries.yAxis,
  85. newSeries,
  86. color = point.color || oldSeries.color,
  87. pointIndex,
  88. level;
  89. ddOptions = extend({
  90. color: color
  91. }, ddOptions);
  92. pointIndex = HighchartsAdapter.inArray(this, oldSeries.points);
  93. level = {
  94. seriesOptions: oldSeries.userOptions,
  95. shapeArgs: point.shapeArgs,
  96. bBox: point.graphic.getBBox(),
  97. color: color,
  98. newSeries: ddOptions,
  99. pointOptions: oldSeries.options.data[pointIndex],
  100. pointIndex: pointIndex,
  101. oldExtremes: {
  102. xMin: xAxis && xAxis.userMin,
  103. xMax: xAxis && xAxis.userMax,
  104. yMin: yAxis && yAxis.userMin,
  105. yMax: yAxis && yAxis.userMax
  106. }
  107. };
  108. this.drilldownLevels.push(level);
  109. newSeries = this.addSeries(ddOptions, false);
  110. if (xAxis) {
  111. xAxis.oldPos = xAxis.pos;
  112. xAxis.userMin = xAxis.userMax = null;
  113. yAxis.userMin = yAxis.userMax = null;
  114. }
  115. // Run fancy cross-animation on supported and equal types
  116. if (oldSeries.type === newSeries.type) {
  117. newSeries.animate = newSeries.animateDrilldown || noop;
  118. newSeries.options.animation = true;
  119. }
  120. oldSeries.remove(false);
  121. this.redraw();
  122. this.showDrillUpButton();
  123. };
  124. Chart.prototype.getDrilldownBackText = function () {
  125. var lastLevel = this.drilldownLevels[this.drilldownLevels.length - 1];
  126. return this.options.lang.drillUpText.replace('{series.name}', lastLevel.seriesOptions.name);
  127. };
  128. Chart.prototype.showDrillUpButton = function () {
  129. var chart = this,
  130. backText = this.getDrilldownBackText(),
  131. buttonOptions = chart.options.drilldown.drillUpButton;
  132. if (!this.drillUpButton) {
  133. this.drillUpButton = this.renderer.button(
  134. backText,
  135. null,
  136. null,
  137. function () {
  138. chart.drillUp();
  139. }
  140. )
  141. .attr(extend({
  142. align: buttonOptions.position.align,
  143. zIndex: 9
  144. }, buttonOptions.theme))
  145. .add()
  146. .align(buttonOptions.position, false, buttonOptions.relativeTo || 'plotBox');
  147. } else {
  148. this.drillUpButton.attr({
  149. text: backText
  150. })
  151. .align();
  152. }
  153. };
  154. Chart.prototype.drillUp = function () {
  155. var chart = this,
  156. level = chart.drilldownLevels.pop(),
  157. oldSeries = chart.series[0],
  158. oldExtremes = level.oldExtremes,
  159. newSeries = chart.addSeries(level.seriesOptions, false);
  160. fireEvent(chart, 'drillup', {seriesOptions: level.seriesOptions});
  161. if (newSeries.type === oldSeries.type) {
  162. newSeries.drilldownLevel = level;
  163. newSeries.animate = newSeries.animateDrillupTo || noop;
  164. newSeries.options.animation = true;
  165. if (oldSeries.animateDrillupFrom) {
  166. oldSeries.animateDrillupFrom(level);
  167. }
  168. }
  169. oldSeries.remove(false);
  170. // Reset the zoom level of the upper series
  171. if (newSeries.xAxis) {
  172. newSeries.xAxis.setExtremes(oldExtremes.xMin, oldExtremes.xMax, false);
  173. newSeries.yAxis.setExtremes(oldExtremes.yMin, oldExtremes.yMax, false);
  174. }
  175. this.redraw();
  176. if (this.drilldownLevels.length === 0) {
  177. this.drillUpButton = this.drillUpButton.destroy();
  178. } else {
  179. this.drillUpButton.attr({
  180. text: this.getDrilldownBackText()
  181. })
  182. .align();
  183. }
  184. };
  185. PieSeries.prototype.animateDrilldown = function (init) {
  186. var level = this.chart.drilldownLevels[this.chart.drilldownLevels.length - 1],
  187. animationOptions = this.chart.options.drilldown.animation,
  188. animateFrom = level.shapeArgs,
  189. start = animateFrom.start,
  190. angle = animateFrom.end - start,
  191. startAngle = angle / this.points.length,
  192. startColor = H.Color(level.color).rgba;
  193. if (!init) {
  194. each(this.points, function (point, i) {
  195. var endColor = H.Color(point.color).rgba;
  196. /*jslint unparam: true*/
  197. point.graphic
  198. .attr(H.merge(animateFrom, {
  199. start: start + i * startAngle,
  200. end: start + (i + 1) * startAngle
  201. }))
  202. .animate(point.shapeArgs, H.merge(animationOptions, {
  203. step: function (val, fx) {
  204. if (fx.prop === 'start') {
  205. this.attr({
  206. fill: tweenColors(startColor, endColor, fx.pos)
  207. });
  208. }
  209. }
  210. }));
  211. /*jslint unparam: false*/
  212. });
  213. }
  214. };
  215. /**
  216. * When drilling up, keep the upper series invisible until the lower series has
  217. * moved into place
  218. */
  219. PieSeries.prototype.animateDrillupTo =
  220. ColumnSeries.prototype.animateDrillupTo = function (init) {
  221. if (!init) {
  222. var newSeries = this,
  223. level = newSeries.drilldownLevel;
  224. each(this.points, function (point) {
  225. point.graphic.hide();
  226. if (point.dataLabel) {
  227. point.dataLabel.hide();
  228. }
  229. if (point.connector) {
  230. point.connector.hide();
  231. }
  232. });
  233. // Do dummy animation on first point to get to complete
  234. setTimeout(function () {
  235. each(newSeries.points, function (point, i) {
  236. // Fade in other points
  237. var verb = i === level.pointIndex ? 'show' : 'fadeIn';
  238. point.graphic[verb]();
  239. if (point.dataLabel) {
  240. point.dataLabel[verb]();
  241. }
  242. if (point.connector) {
  243. point.connector[verb]();
  244. }
  245. });
  246. }, Math.max(this.chart.options.drilldown.animation.duration - 50, 0));
  247. // Reset
  248. this.animate = noop;
  249. }
  250. };
  251. ColumnSeries.prototype.animateDrilldown = function (init) {
  252. var animateFrom = this.chart.drilldownLevels[this.chart.drilldownLevels.length - 1].shapeArgs,
  253. animationOptions = this.chart.options.drilldown.animation;
  254. if (!init) {
  255. animateFrom.x += (this.xAxis.oldPos - this.xAxis.pos);
  256. each(this.points, function (point) {
  257. point.graphic
  258. .attr(animateFrom)
  259. .animate(point.shapeArgs, animationOptions);
  260. });
  261. }
  262. };
  263. /**
  264. * When drilling up, pull out the individual point graphics from the lower series
  265. * and animate them into the origin point in the upper series.
  266. */
  267. ColumnSeries.prototype.animateDrillupFrom =
  268. PieSeries.prototype.animateDrillupFrom =
  269. function (level) {
  270. var animationOptions = this.chart.options.drilldown.animation,
  271. group = this.group;
  272. delete this.group;
  273. each(this.points, function (point) {
  274. var graphic = point.graphic,
  275. startColor = H.Color(point.color).rgba;
  276. delete point.graphic;
  277. /*jslint unparam: true*/
  278. graphic.animate(level.shapeArgs, H.merge(animationOptions, {
  279. step: function (val, fx) {
  280. if (fx.prop === 'start') {
  281. this.attr({
  282. fill: tweenColors(startColor, H.Color(level.color).rgba, fx.pos)
  283. });
  284. }
  285. },
  286. complete: function () {
  287. graphic.destroy();
  288. if (group) {
  289. group = group.destroy();
  290. }
  291. }
  292. }));
  293. /*jslint unparam: false*/
  294. });
  295. };
  296. H.Point.prototype.doDrilldown = function () {
  297. var series = this.series,
  298. chart = series.chart,
  299. drilldown = chart.options.drilldown,
  300. i = drilldown.series.length,
  301. seriesOptions;
  302. while (i-- && !seriesOptions) {
  303. if (drilldown.series[i].id === this.drilldown) {
  304. seriesOptions = drilldown.series[i];
  305. }
  306. }
  307. // Fire the event. If seriesOptions is undefined, the implementer can check for
  308. // seriesOptions, and call addSeriesAsDrilldown async if necessary.
  309. fireEvent(chart, 'drilldown', {
  310. point: this,
  311. seriesOptions: seriesOptions
  312. });
  313. if (seriesOptions) {
  314. chart.addSeriesAsDrilldown(this, seriesOptions);
  315. }
  316. };
  317. wrap(H.Point.prototype, 'init', function (proceed, series, options, x) {
  318. var point = proceed.call(this, series, options, x),
  319. chart = series.chart,
  320. tick = series.xAxis && series.xAxis.ticks[x],
  321. tickLabel = tick && tick.label;
  322. if (point.drilldown) {
  323. // Add the click event to the point label
  324. H.addEvent(point, 'click', function () {
  325. point.doDrilldown();
  326. });
  327. // Make axis labels clickable
  328. if (tickLabel) {
  329. if (!tickLabel._basicStyle) {
  330. tickLabel._basicStyle = tickLabel.element.getAttribute('style');
  331. }
  332. tickLabel
  333. .addClass('highcharts-drilldown-axis-label')
  334. .css(chart.options.drilldown.activeAxisLabelStyle)
  335. .on('click', function () {
  336. if (point.doDrilldown) {
  337. point.doDrilldown();
  338. }
  339. });
  340. }
  341. } else if (tickLabel && tickLabel._basicStyle) {
  342. tickLabel.element.setAttribute('style', tickLabel._basicStyle);
  343. }
  344. return point;
  345. });
  346. wrap(H.Series.prototype, 'drawDataLabels', function (proceed) {
  347. var css = this.chart.options.drilldown.activeDataLabelStyle;
  348. proceed.call(this);
  349. each(this.points, function (point) {
  350. if (point.drilldown && point.dataLabel) {
  351. point.dataLabel
  352. .attr({
  353. 'class': 'highcharts-drilldown-data-label'
  354. })
  355. .css(css)
  356. .on('click', function () {
  357. point.doDrilldown();
  358. });
  359. }
  360. });
  361. });
  362. // Mark the trackers with a pointer
  363. ColumnSeries.prototype.supportsDrilldown = true;
  364. PieSeries.prototype.supportsDrilldown = true;
  365. var type,
  366. drawTrackerWrapper = function (proceed) {
  367. proceed.call(this);
  368. each(this.points, function (point) {
  369. if (point.drilldown && point.graphic) {
  370. point.graphic
  371. .attr({
  372. 'class': 'highcharts-drilldown-point'
  373. })
  374. .css({cursor: 'pointer'});
  375. }
  376. });
  377. };
  378. for (type in seriesTypes) {
  379. if (seriesTypes[type].prototype.supportsDrilldown) {
  380. wrap(seriesTypes[type].prototype, 'drawTracker', drawTrackerWrapper);
  381. }
  382. }
  383. }(Highcharts));