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