市长热线演示版

drilldown.src.js 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  1. /**
  2. * Highcharts Drilldown plugin
  3. *
  4. * Author: Torstein Honsi
  5. * License: MIT License
  6. *
  7. * Demo: http://jsfiddle.net/highcharts/Vf3yT/
  8. */
  9. /*global HighchartsAdapter*/
  10. (function (H) {
  11. "use strict";
  12. var noop = function () {},
  13. defaultOptions = H.getOptions(),
  14. each = H.each,
  15. extend = H.extend,
  16. format = H.format,
  17. wrap = H.wrap,
  18. Chart = H.Chart,
  19. seriesTypes = H.seriesTypes,
  20. PieSeries = seriesTypes.pie,
  21. ColumnSeries = seriesTypes.column,
  22. fireEvent = HighchartsAdapter.fireEvent,
  23. inArray = HighchartsAdapter.inArray;
  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: '#0d233a',
  42. fontWeight: 'bold',
  43. textDecoration: 'underline'
  44. },
  45. activeDataLabelStyle: {
  46. cursor: 'pointer',
  47. color: '#0d233a',
  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 (animation) {
  68. this
  69. .attr({
  70. opacity: 0.1,
  71. visibility: 'inherit'
  72. })
  73. .animate({
  74. opacity: 1
  75. }, animation || {
  76. duration: 250
  77. });
  78. };
  79. Chart.prototype.addSeriesAsDrilldown = function (point, ddOptions) {
  80. this.addSingleSeriesAsDrilldown(point, ddOptions);
  81. this.applyDrilldown();
  82. };
  83. Chart.prototype.addSingleSeriesAsDrilldown = function (point, ddOptions) {
  84. var oldSeries = point.series,
  85. xAxis = oldSeries.xAxis,
  86. yAxis = oldSeries.yAxis,
  87. newSeries,
  88. color = point.color || oldSeries.color,
  89. pointIndex,
  90. levelSeries = [],
  91. levelSeriesOptions = [],
  92. level,
  93. levelNumber;
  94. levelNumber = oldSeries.levelNumber || 0;
  95. ddOptions = extend({
  96. color: color
  97. }, ddOptions);
  98. pointIndex = inArray(point, oldSeries.points);
  99. // Record options for all current series
  100. each(oldSeries.chart.series, function (series) {
  101. if (series.xAxis === xAxis) {
  102. levelSeries.push(series);
  103. levelSeriesOptions.push(series.userOptions);
  104. series.levelNumber = series.levelNumber || 0;
  105. }
  106. });
  107. // Add a record of properties for each drilldown level
  108. level = {
  109. levelNumber: levelNumber,
  110. seriesOptions: oldSeries.userOptions,
  111. levelSeriesOptions: levelSeriesOptions,
  112. levelSeries: levelSeries,
  113. shapeArgs: point.shapeArgs,
  114. bBox: point.graphic.getBBox(),
  115. color: color,
  116. lowerSeriesOptions: ddOptions,
  117. pointOptions: oldSeries.options.data[pointIndex],
  118. pointIndex: pointIndex,
  119. oldExtremes: {
  120. xMin: xAxis && xAxis.userMin,
  121. xMax: xAxis && xAxis.userMax,
  122. yMin: yAxis && yAxis.userMin,
  123. yMax: yAxis && yAxis.userMax
  124. }
  125. };
  126. // Generate and push it to a lookup array
  127. if (!this.drilldownLevels) {
  128. this.drilldownLevels = [];
  129. }
  130. this.drilldownLevels.push(level);
  131. newSeries = level.lowerSeries = this.addSeries(ddOptions, false);
  132. newSeries.levelNumber = levelNumber + 1;
  133. if (xAxis) {
  134. xAxis.oldPos = xAxis.pos;
  135. xAxis.userMin = xAxis.userMax = null;
  136. yAxis.userMin = yAxis.userMax = null;
  137. }
  138. // Run fancy cross-animation on supported and equal types
  139. if (oldSeries.type === newSeries.type) {
  140. newSeries.animate = newSeries.animateDrilldown || noop;
  141. newSeries.options.animation = true;
  142. }
  143. };
  144. Chart.prototype.applyDrilldown = function () {
  145. var drilldownLevels = this.drilldownLevels,
  146. levelToRemove = drilldownLevels[drilldownLevels.length - 1].levelNumber;
  147. each(this.drilldownLevels, function (level) {
  148. if (level.levelNumber === levelToRemove) {
  149. each(level.levelSeries, function (series) {
  150. if (series.levelNumber === levelToRemove) { // Not removed, not added as part of a multi-series drilldown
  151. series.remove(false);
  152. }
  153. });
  154. }
  155. });
  156. this.redraw();
  157. this.showDrillUpButton();
  158. };
  159. Chart.prototype.getDrilldownBackText = function () {
  160. var lastLevel = this.drilldownLevels[this.drilldownLevels.length - 1];
  161. lastLevel.series = lastLevel.seriesOptions;
  162. return format(this.options.lang.drillUpText, lastLevel);
  163. };
  164. Chart.prototype.showDrillUpButton = function () {
  165. var chart = this,
  166. backText = this.getDrilldownBackText(),
  167. buttonOptions = chart.options.drilldown.drillUpButton,
  168. attr,
  169. states;
  170. if (!this.drillUpButton) {
  171. attr = buttonOptions.theme;
  172. states = attr && attr.states;
  173. this.drillUpButton = this.renderer.button(
  174. backText,
  175. null,
  176. null,
  177. function () {
  178. chart.drillUp();
  179. },
  180. attr,
  181. states && states.hover,
  182. states && states.select
  183. )
  184. .attr({
  185. align: buttonOptions.position.align,
  186. zIndex: 9
  187. })
  188. .add()
  189. .align(buttonOptions.position, false, buttonOptions.relativeTo || 'plotBox');
  190. } else {
  191. this.drillUpButton.attr({
  192. text: backText
  193. })
  194. .align();
  195. }
  196. };
  197. Chart.prototype.drillUp = function () {
  198. var chart = this,
  199. drilldownLevels = chart.drilldownLevels,
  200. levelNumber = drilldownLevels[drilldownLevels.length - 1].levelNumber,
  201. i = drilldownLevels.length,
  202. chartSeries = chart.series,
  203. seriesI = chartSeries.length,
  204. level,
  205. oldSeries,
  206. newSeries,
  207. oldExtremes,
  208. addSeries = function (seriesOptions) {
  209. var addedSeries;
  210. each(chartSeries, function (series) {
  211. if (series.userOptions === seriesOptions) {
  212. addedSeries = series;
  213. }
  214. });
  215. addedSeries = addedSeries || chart.addSeries(seriesOptions, false);
  216. if (addedSeries.type === oldSeries.type && addedSeries.animateDrillupTo) {
  217. addedSeries.animate = addedSeries.animateDrillupTo;
  218. }
  219. if (seriesOptions === level.seriesOptions) {
  220. newSeries = addedSeries;
  221. }
  222. };
  223. while (i--) {
  224. level = drilldownLevels[i];
  225. if (level.levelNumber === levelNumber) {
  226. drilldownLevels.pop();
  227. // Get the lower series by reference or id
  228. oldSeries = level.lowerSeries;
  229. if (!oldSeries.chart) { // #2786
  230. while (seriesI--) {
  231. if (chartSeries[seriesI].options.id === level.lowerSeriesOptions.id) {
  232. oldSeries = chartSeries[seriesI];
  233. break;
  234. }
  235. }
  236. }
  237. oldSeries.xData = []; // Overcome problems with minRange (#2898)
  238. each(level.levelSeriesOptions, addSeries);
  239. fireEvent(chart, 'drillup', { seriesOptions: level.seriesOptions });
  240. if (newSeries.type === oldSeries.type) {
  241. newSeries.drilldownLevel = level;
  242. newSeries.options.animation = chart.options.drilldown.animation;
  243. if (oldSeries.animateDrillupFrom) {
  244. oldSeries.animateDrillupFrom(level);
  245. }
  246. }
  247. newSeries.levelNumber = levelNumber;
  248. oldSeries.remove(false);
  249. // Reset the zoom level of the upper series
  250. if (newSeries.xAxis) {
  251. oldExtremes = level.oldExtremes;
  252. newSeries.xAxis.setExtremes(oldExtremes.xMin, oldExtremes.xMax, false);
  253. newSeries.yAxis.setExtremes(oldExtremes.yMin, oldExtremes.yMax, false);
  254. }
  255. }
  256. }
  257. this.redraw();
  258. if (this.drilldownLevels.length === 0) {
  259. this.drillUpButton = this.drillUpButton.destroy();
  260. } else {
  261. this.drillUpButton.attr({
  262. text: this.getDrilldownBackText()
  263. })
  264. .align();
  265. }
  266. };
  267. ColumnSeries.prototype.supportsDrilldown = true;
  268. /**
  269. * When drilling up, keep the upper series invisible until the lower series has
  270. * moved into place
  271. */
  272. ColumnSeries.prototype.animateDrillupTo = function (init) {
  273. if (!init) {
  274. var newSeries = this,
  275. level = newSeries.drilldownLevel;
  276. each(this.points, function (point) {
  277. point.graphic.hide();
  278. if (point.dataLabel) {
  279. point.dataLabel.hide();
  280. }
  281. if (point.connector) {
  282. point.connector.hide();
  283. }
  284. });
  285. // Do dummy animation on first point to get to complete
  286. setTimeout(function () {
  287. each(newSeries.points, function (point, i) {
  288. // Fade in other points
  289. var verb = i === (level && level.pointIndex) ? 'show' : 'fadeIn',
  290. inherit = verb === 'show' ? true : undefined;
  291. point.graphic[verb](inherit);
  292. if (point.dataLabel) {
  293. point.dataLabel[verb](inherit);
  294. }
  295. if (point.connector) {
  296. point.connector[verb](inherit);
  297. }
  298. });
  299. }, Math.max(this.chart.options.drilldown.animation.duration - 50, 0));
  300. // Reset
  301. this.animate = noop;
  302. }
  303. };
  304. ColumnSeries.prototype.animateDrilldown = function (init) {
  305. var series = this,
  306. drilldownLevels = this.chart.drilldownLevels,
  307. animateFrom = this.chart.drilldownLevels[this.chart.drilldownLevels.length - 1].shapeArgs,
  308. animationOptions = this.chart.options.drilldown.animation;
  309. if (!init) {
  310. each(drilldownLevels, function (level) {
  311. if (series.userOptions === level.lowerSeriesOptions) {
  312. animateFrom = level.shapeArgs;
  313. }
  314. });
  315. animateFrom.x += (this.xAxis.oldPos - this.xAxis.pos);
  316. each(this.points, function (point) {
  317. if (point.graphic) {
  318. point.graphic
  319. .attr(animateFrom)
  320. .animate(point.shapeArgs, animationOptions);
  321. }
  322. if (point.dataLabel) {
  323. point.dataLabel.fadeIn(animationOptions);
  324. }
  325. });
  326. this.animate = null;
  327. }
  328. };
  329. /**
  330. * When drilling up, pull out the individual point graphics from the lower series
  331. * and animate them into the origin point in the upper series.
  332. */
  333. ColumnSeries.prototype.animateDrillupFrom = function (level) {
  334. var animationOptions = this.chart.options.drilldown.animation,
  335. group = this.group,
  336. series = this;
  337. // Cancel mouse events on the series group (#2787)
  338. each(series.trackerGroups, function (key) {
  339. if (series[key]) { // we don't always have dataLabelsGroup
  340. series[key].on('mouseover');
  341. }
  342. });
  343. delete this.group;
  344. each(this.points, function (point) {
  345. var graphic = point.graphic,
  346. startColor = H.Color(point.color).rgba,
  347. endColor = H.Color(level.color).rgba,
  348. complete = function () {
  349. graphic.destroy();
  350. if (group) {
  351. group = group.destroy();
  352. }
  353. };
  354. if (graphic) {
  355. delete point.graphic;
  356. if (animationOptions) {
  357. /*jslint unparam: true*/
  358. graphic.animate(level.shapeArgs, H.merge(animationOptions, {
  359. step: function (val, fx) {
  360. if (fx.prop === 'start' && startColor.length === 4 && endColor.length === 4) {
  361. this.attr({
  362. fill: tweenColors(startColor, endColor, fx.pos)
  363. });
  364. }
  365. },
  366. complete: complete
  367. }));
  368. /*jslint unparam: false*/
  369. } else {
  370. graphic.attr(level.shapeArgs);
  371. complete();
  372. }
  373. }
  374. });
  375. };
  376. if (PieSeries) {
  377. extend(PieSeries.prototype, {
  378. supportsDrilldown: true,
  379. animateDrillupTo: ColumnSeries.prototype.animateDrillupTo,
  380. animateDrillupFrom: ColumnSeries.prototype.animateDrillupFrom,
  381. animateDrilldown: function (init) {
  382. var level = this.chart.drilldownLevels[this.chart.drilldownLevels.length - 1],
  383. animationOptions = this.chart.options.drilldown.animation,
  384. animateFrom = level.shapeArgs,
  385. start = animateFrom.start,
  386. angle = animateFrom.end - start,
  387. startAngle = angle / this.points.length,
  388. startColor = H.Color(level.color).rgba;
  389. if (!init) {
  390. each(this.points, function (point, i) {
  391. var endColor = H.Color(point.color).rgba;
  392. /*jslint unparam: true*/
  393. point.graphic
  394. .attr(H.merge(animateFrom, {
  395. start: start + i * startAngle,
  396. end: start + (i + 1) * startAngle
  397. }))[animationOptions ? 'animate' : 'attr'](point.shapeArgs, H.merge(animationOptions, {
  398. step: function (val, fx) {
  399. if (fx.prop === 'start' && startColor.length === 4 && endColor.length === 4) {
  400. this.attr({
  401. fill: tweenColors(startColor, endColor, fx.pos)
  402. });
  403. }
  404. }
  405. }));
  406. /*jslint unparam: false*/
  407. });
  408. this.animate = null;
  409. }
  410. }
  411. });
  412. }
  413. H.Point.prototype.doDrilldown = function (_holdRedraw) {
  414. var series = this.series,
  415. chart = series.chart,
  416. drilldown = chart.options.drilldown,
  417. i = (drilldown.series || []).length,
  418. seriesOptions;
  419. while (i-- && !seriesOptions) {
  420. if (drilldown.series[i].id === this.drilldown) {
  421. seriesOptions = drilldown.series[i];
  422. }
  423. }
  424. // Fire the event. If seriesOptions is undefined, the implementer can check for
  425. // seriesOptions, and call addSeriesAsDrilldown async if necessary.
  426. fireEvent(chart, 'drilldown', {
  427. point: this,
  428. seriesOptions: seriesOptions
  429. });
  430. if (seriesOptions) {
  431. if (_holdRedraw) {
  432. chart.addSingleSeriesAsDrilldown(this, seriesOptions);
  433. } else {
  434. chart.addSeriesAsDrilldown(this, seriesOptions);
  435. }
  436. }
  437. };
  438. wrap(H.Point.prototype, 'init', function (proceed, series, options, x) {
  439. var point = proceed.call(this, series, options, x),
  440. chart = series.chart,
  441. tick = series.xAxis && series.xAxis.ticks[x],
  442. tickLabel = tick && tick.label;
  443. if (point.drilldown) {
  444. // Add the click event to the point label
  445. H.addEvent(point, 'click', function () {
  446. point.doDrilldown();
  447. });
  448. // Make axis labels clickable
  449. if (tickLabel) {
  450. if (!tickLabel._basicStyle) {
  451. tickLabel._basicStyle = tickLabel.element.getAttribute('style');
  452. }
  453. tickLabel
  454. .addClass('highcharts-drilldown-axis-label')
  455. .css(chart.options.drilldown.activeAxisLabelStyle)
  456. .on('click', function () {
  457. each(tickLabel.ddPoints, function (point) {
  458. if (point.doDrilldown) {
  459. point.doDrilldown(true);
  460. }
  461. });
  462. chart.applyDrilldown();
  463. });
  464. if (!tickLabel.ddPoints) {
  465. tickLabel.ddPoints = [];
  466. }
  467. tickLabel.ddPoints.push(point);
  468. }
  469. } else if (tickLabel && tickLabel._basicStyle) {
  470. tickLabel.element.setAttribute('style', tickLabel._basicStyle);
  471. }
  472. return point;
  473. });
  474. wrap(H.Series.prototype, 'drawDataLabels', function (proceed) {
  475. var css = this.chart.options.drilldown.activeDataLabelStyle;
  476. proceed.call(this);
  477. each(this.points, function (point) {
  478. if (point.drilldown && point.dataLabel) {
  479. point.dataLabel
  480. .attr({
  481. 'class': 'highcharts-drilldown-data-label'
  482. })
  483. .css(css)
  484. .on('click', function () {
  485. point.doDrilldown();
  486. });
  487. }
  488. });
  489. });
  490. // Mark the trackers with a pointer
  491. var type,
  492. drawTrackerWrapper = function (proceed) {
  493. proceed.call(this);
  494. each(this.points, function (point) {
  495. if (point.drilldown && point.graphic) {
  496. point.graphic
  497. .attr({
  498. 'class': 'highcharts-drilldown-point'
  499. })
  500. .css({ cursor: 'pointer' });
  501. }
  502. });
  503. };
  504. for (type in seriesTypes) {
  505. if (seriesTypes[type].prototype.supportsDrilldown) {
  506. wrap(seriesTypes[type].prototype, 'drawTracker', drawTrackerWrapper);
  507. }
  508. }
  509. }(Highcharts));