4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5 <title>The source code</title>
6 <link href="../prettify/prettify.css" type="text/css" rel="stylesheet" />
7 <script type="text/javascript" src="../prettify/prettify.js"></script>
8 <style type="text/css">
9 .highlight { display: block; background-color: #ddd; }
11 <script type="text/javascript">
12 function highlight() {
13 document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
17 <body onload="prettyPrint(); highlight();">
18 <pre class="prettyprint lang-js"><span id='Ext-chart-Chart'>/**
19 </span> * @class Ext.chart.Chart
20 * @extends Ext.draw.Component
22 * The Ext.chart package provides the capability to visualize data.
23 * Each chart binds directly to an Ext.data.Store enabling automatic updates of the chart.
24 * A chart configuration object has some overall styling options as well as an array of axes
25 * and series. A chart instance example could look like:
27 <pre><code>
28 Ext.create('Ext.chart.Chart', {
29 renderTo: Ext.getBody(),
39 axes: [ ...some axes options... ],
40 series: [ ...some series options... ]
42 </code></pre>
44 * In this example we set the `width` and `height` of the chart, we decide whether our series are
45 * animated or not and we select a store to be bound to the chart. We also turn on shadows for all series,
46 * select a color theme `Category1` for coloring the series, set the legend to the right part of the chart and
47 * then tell the chart to render itself in the body element of the document. For more information about the axes and
48 * series configurations please check the documentation of each series (Line, Bar, Pie, etc).
53 Ext.define('Ext.chart.Chart', {
55 /* Begin Definitions */
57 alias: 'widget.chart',
59 extend: 'Ext.draw.Component',
62 themeManager: 'Ext.chart.theme.Theme',
63 mask: 'Ext.chart.Mask',
64 navigation: 'Ext.chart.Navigation'
68 'Ext.util.MixedCollection',
69 'Ext.data.StoreManager',
71 'Ext.util.DelayedTask'
79 <span id='Ext-chart-Chart-cfg-theme'> /**
80 </span> * @cfg {String} theme (optional) The name of the theme to be used. A theme defines the colors and
81 * other visual displays of tick marks on axis, text, title text, line colors, marker colors and styles, etc.
82 * Possible theme values are 'Base', 'Green', 'Sky', 'Red', 'Purple', 'Blue', 'Yellow' and also six category themes
83 * 'Category1' to 'Category6'. Default value is 'Base'.
86 <span id='Ext-chart-Chart-cfg-animate'> /**
87 </span> * @cfg {Boolean/Object} animate (optional) true for the default animation (easing: 'ease' and duration: 500)
88 * or a standard animation config object to be used for default chart animations. Defaults to false.
92 <span id='Ext-chart-Chart-cfg-legend'> /**
93 </span> * @cfg {Boolean/Object} legend (optional) true for the default legend display or a legend config object. Defaults to false.
97 <span id='Ext-chart-Chart-cfg-insetPadding'> /**
98 </span> * @cfg {integer} insetPadding (optional) Set the amount of inset padding in pixels for the chart. Defaults to 10.
102 <span id='Ext-chart-Chart-cfg-enginePriority'> /**
103 </span> * @cfg {Array} enginePriority
104 * Defines the priority order for which Surface implementation to use. The first
105 * one supported by the current environment will be used.
107 enginePriority: ['Svg', 'Vml'],
109 <span id='Ext-chart-Chart-cfg-background'> /**
110 </span> * @cfg {Object|Boolean} background (optional) Set the chart background. This can be a gradient object, image, or color.
111 * Defaults to false for no background.
113 * For example, if `background` were to be a color we could set the object as
115 <pre><code>
120 </code></pre>
122 You can specify an image by using:
124 <pre><code>
126 image: 'http://path.to.image/'
128 </code></pre>
130 Also you can specify a gradient by using the gradient object syntax:
132 <pre><code>
147 </code></pre>
151 <span id='Ext-chart-Chart-cfg-gradients'> /**
152 </span> * @cfg {Array} gradients (optional) Define a set of gradients that can be used as `fill` property in sprites.
153 * The gradients array is an array of objects with the following properties:
156 * <li><strong>id</strong> - string - The unique name of the gradient.</li>
157 * <li><strong>angle</strong> - number, optional - The angle of the gradient in degrees.</li>
158 * <li><strong>stops</strong> - object - An object with numbers as keys (from 0 to 100) and style objects
159 * as values</li>
165 <pre><code>
192 </code></pre>
194 Then the sprites can use `gradientId` and `gradientId2` by setting the fill attributes to those ids, for example:
196 <pre><code>
197 sprite.setAttributes({
198 fill: 'url(#gradientId)'
200 </code></pre>
205 constructor: function(config) {
208 me.initTheme(config.theme || me.theme);
210 Ext.apply(config, { gradients: me.gradients });
213 Ext.apply(config, { background: me.background });
215 if (config.animate) {
220 if (Ext.isObject(config.animate)) {
221 config.animate = Ext.applyIf(config.animate, defaultAnim);
224 config.animate = defaultAnim;
227 me.mixins.mask.constructor.call(me, config);
228 me.mixins.navigation.constructor.call(me, config);
229 me.callParent([config]);
232 initComponent: function() {
247 <span id='Ext-chart-Chart-event-beforerefresh'> /**
248 </span> * @event beforerefresh
249 * Fires before a refresh to the chart data is called. If the beforerefresh handler returns
250 * <tt>false</tt> the {@link #refresh} action will be cancelled.
251 * @param {Chart} this
254 <span id='Ext-chart-Chart-event-refresh'> /**
255 </span> * @event refresh
256 * Fires after the chart data has been refreshed.
257 * @param {Chart} this
269 me.maxGutter = [0, 0];
270 me.store = Ext.data.StoreManager.lookup(me.store);
272 me.axes = Ext.create('Ext.util.MixedCollection', false, function(a) { return a.position; });
274 me.axes.addAll(axes);
277 me.series = Ext.create('Ext.util.MixedCollection', false, function(a) { return a.seriesId || (a.seriesId = Ext.id(null, 'ext-chart-series-')); });
279 me.series.addAll(series);
281 if (me.legend !== false) {
282 me.legend = Ext.create('Ext.chart.Legend', Ext.applyIf({chart:me}, me.legend));
286 mousemove: me.onMouseMove,
287 mouseleave: me.onMouseLeave,
288 mousedown: me.onMouseDown,
289 mouseup: me.onMouseUp,
294 // @private overrides the component method to set the correct dimensions to the chart.
295 afterComponentLayout: function(width, height) {
297 if (Ext.isNumber(width) && Ext.isNumber(height)) {
299 me.curHeight = height;
302 this.callParent(arguments);
305 <span id='Ext-chart-Chart-cfg-resize'> /**
306 </span> * Redraw the chart. If animations are set this will animate the chart too.
307 * @cfg {boolean} resize Optional flag which changes the default origin points of the chart for animations.
309 redraw: function(resize) {
311 chartBBox = me.chartBBox = {
314 height: me.curHeight,
318 me.surface.setSize(chartBBox.width, chartBBox.height);
319 // Instantiate Series and Axes
320 me.series.each(me.initializeSeries, me);
321 me.axes.each(me.initializeAxis, me);
322 //process all views (aggregated data etc) on stores
324 me.axes.each(function(axis) {
327 me.axes.each(function(axis) {
331 // Create legend if not already created
332 if (legend !== false) {
336 // Place axes properly, including influence from each other
339 // Reposition legend based on new axis alignment
340 if (me.legend !== false) {
341 legend.updatePosition();
344 // Find the max gutter
347 // Draw axes and series
348 me.resizing = !!resize;
350 me.axes.each(me.drawAxis, me);
351 me.series.each(me.drawCharts, me);
355 // @private set the store after rendering the chart.
356 afterRender: function() {
361 if (me.categoryNames) {
362 me.setCategoryNames(me.categoryNames);
365 if (me.tipRenderer) {
366 ref = me.getFunctionRef(me.tipRenderer);
367 me.setTipRenderer(ref.fn, ref.scope);
369 me.bindStore(me.store, true);
373 // @private get x and y position of the mouse cursor.
374 getEventXY: function(e) {
376 box = this.surface.getRegion(),
378 x = pageXY[0] - box.left,
379 y = pageXY[1] - box.top;
383 // @private wrap the mouse down position to delegate the event to the series.
384 onClick: function(e) {
386 position = me.getEventXY(e),
389 // Ask each series if it has an item corresponding to (not necessarily exactly
390 // on top of) the current mouse coords. Fire itemclick event.
391 me.series.each(function(series) {
392 if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
393 if (series.getItemForPoint) {
394 item = series.getItemForPoint(position[0], position[1]);
396 series.fireEvent('itemclick', item);
403 // @private wrap the mouse down position to delegate the event to the series.
404 onMouseDown: function(e) {
406 position = me.getEventXY(e),
410 me.mixins.mask.onMouseDown.call(me, e);
412 // Ask each series if it has an item corresponding to (not necessarily exactly
413 // on top of) the current mouse coords. Fire mousedown event.
414 me.series.each(function(series) {
415 if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
416 if (series.getItemForPoint) {
417 item = series.getItemForPoint(position[0], position[1]);
419 series.fireEvent('itemmousedown', item);
426 // @private wrap the mouse up event to delegate it to the series.
427 onMouseUp: function(e) {
429 position = me.getEventXY(e),
433 me.mixins.mask.onMouseUp.call(me, e);
435 // Ask each series if it has an item corresponding to (not necessarily exactly
436 // on top of) the current mouse coords. Fire mousedown event.
437 me.series.each(function(series) {
438 if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
439 if (series.getItemForPoint) {
440 item = series.getItemForPoint(position[0], position[1]);
442 series.fireEvent('itemmouseup', item);
449 // @private wrap the mouse move event so it can be delegated to the series.
450 onMouseMove: function(e) {
452 position = me.getEventXY(e),
453 item, last, storeItem, storeField;
456 me.mixins.mask.onMouseMove.call(me, e);
458 // Ask each series if it has an item corresponding to (not necessarily exactly
459 // on top of) the current mouse coords. Fire itemmouseover/out events.
460 me.series.each(function(series) {
461 if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
462 if (series.getItemForPoint) {
463 item = series.getItemForPoint(position[0], position[1]);
464 last = series._lastItemForPoint;
465 storeItem = series._lastStoreItem;
466 storeField = series._lastStoreField;
469 if (item !== last || item && (item.storeItem != storeItem || item.storeField != storeField)) {
471 series.fireEvent('itemmouseout', last);
472 delete series._lastItemForPoint;
473 delete series._lastStoreField;
474 delete series._lastStoreItem;
477 series.fireEvent('itemmouseover', item);
478 series._lastItemForPoint = item;
479 series._lastStoreItem = item.storeItem;
480 series._lastStoreField = item.storeField;
485 last = series._lastItemForPoint;
487 series.fireEvent('itemmouseout', last);
488 delete series._lastItemForPoint;
489 delete series._lastStoreField;
490 delete series._lastStoreItem;
496 // @private handle mouse leave event.
497 onMouseLeave: function(e) {
500 me.mixins.mask.onMouseLeave.call(me, e);
502 me.series.each(function(series) {
503 delete series._lastItemForPoint;
507 // @private buffered refresh for when we update the store
508 delayRefresh: function() {
510 if (!me.refreshTask) {
511 me.refreshTask = Ext.create('Ext.util.DelayedTask', me.refresh, me);
513 me.refreshTask.delay(me.refreshBuffer);
517 refresh: function() {
519 if (me.rendered && me.curWidth != undefined && me.curHeight != undefined) {
520 if (me.fireEvent('beforerefresh', me) !== false) {
522 me.fireEvent('refresh', me);
527 <span id='Ext-chart-Chart-method-bindStore'> /**
528 </span> * Changes the data store bound to this chart and refreshes it.
529 * @param {Store} store The store to bind to this chart
531 bindStore: function(store, initial) {
533 if (!initial && me.store) {
534 if (store !== me.store && me.store.autoDestroy) {
538 me.store.un('datachanged', me.refresh, me);
539 me.store.un('add', me.delayRefresh, me);
540 me.store.un('remove', me.delayRefresh, me);
541 me.store.un('update', me.delayRefresh, me);
542 me.store.un('clear', me.refresh, me);
546 store = Ext.data.StoreManager.lookup(store);
549 datachanged: me.refresh,
550 add: me.delayRefresh,
551 remove: me.delayRefresh,
552 update: me.delayRefresh,
557 if (store && !initial) {
562 // @private Create Axis
563 initializeAxis: function(axis) {
565 chartBBox = me.chartBBox,
567 h = chartBBox.height,
570 themeAttrs = me.themeAttrs,
575 config.axisStyle = Ext.apply({}, themeAttrs.axis);
576 config.axisLabelLeftStyle = Ext.apply({}, themeAttrs.axisLabelLeft);
577 config.axisLabelRightStyle = Ext.apply({}, themeAttrs.axisLabelRight);
578 config.axisLabelTopStyle = Ext.apply({}, themeAttrs.axisLabelTop);
579 config.axisLabelBottomStyle = Ext.apply({}, themeAttrs.axisLabelBottom);
580 config.axisTitleLeftStyle = Ext.apply({}, themeAttrs.axisTitleLeft);
581 config.axisTitleRightStyle = Ext.apply({}, themeAttrs.axisTitleRight);
582 config.axisTitleTopStyle = Ext.apply({}, themeAttrs.axisTitleTop);
583 config.axisTitleBottomStyle = Ext.apply({}, themeAttrs.axisTitleBottom);
585 switch (axis.position) {
620 Ext.apply(config, axis);
621 axis = me.axes.replace(Ext.createByAlias('axis.' + axis.type.toLowerCase(), config));
624 Ext.apply(axis, config);
629 <span id='Ext-chart-Chart-method-alignAxes'> /**
630 </span> * @private Adjust the dimensions and positions of each axis and the chart body area after accounting
631 * for the space taken up on each side by the axes and legend.
633 alignAxes: function() {
637 edges = ['top', 'right', 'bottom', 'left'],
639 insetPadding = me.insetPadding,
643 bottom: insetPadding,
647 function getAxis(edge) {
648 var i = axes.findIndex('position', edge);
649 return (i < 0) ? null : axes.getAt(i);
652 // Find the space needed by axes and legend as a positive inset from each edge
653 Ext.each(edges, function(edge) {
654 var isVertical = (edge === 'left' || edge === 'right'),
655 axis = getAxis(edge),
658 // Add legend size if it's on this edge
659 if (legend !== false) {
660 if (legend.position === edge) {
661 bbox = legend.getBBox();
662 insets[edge] += (isVertical ? bbox.width : bbox.height) + insets[edge];
666 // Add axis size if there's one on this edge only if it has been
668 if (axis && axis.bbox) {
670 insets[edge] += (isVertical ? bbox.width : bbox.height);
673 // Build the chart bbox based on the collected inset values
677 width: me.curWidth - insets.left - insets.right,
678 height: me.curHeight - insets.top - insets.bottom
680 me.chartBBox = chartBBox;
682 // Go back through each axis and set its length and position based on the
683 // corresponding edge of the chartBBox
684 axes.each(function(axis) {
685 var pos = axis.position,
686 isVertical = (pos === 'left' || pos === 'right');
688 axis.x = (pos === 'right' ? chartBBox.x + chartBBox.width : chartBBox.x);
689 axis.y = (pos === 'top' ? chartBBox.y : chartBBox.y + chartBBox.height);
690 axis.width = (isVertical ? chartBBox.width : chartBBox.height);
691 axis.length = (isVertical ? chartBBox.height : chartBBox.width);
695 // @private initialize the series.
696 initializeSeries: function(series, idx) {
698 themeAttrs = me.themeAttrs,
699 seriesObj, markerObj, seriesThemes, st,
700 markerThemes, colorArrayStyle = [],
704 seriesId: series.seriesId
707 seriesThemes = themeAttrs.seriesThemes;
708 markerThemes = themeAttrs.markerThemes;
709 seriesObj = Ext.apply({}, themeAttrs.series);
710 markerObj = Ext.apply({}, themeAttrs.marker);
711 config.seriesStyle = Ext.apply(seriesObj, seriesThemes[idx % seriesThemes.length]);
712 config.seriesLabelStyle = Ext.apply({}, themeAttrs.seriesLabel);
713 config.markerStyle = Ext.apply(markerObj, markerThemes[idx % markerThemes.length]);
714 if (themeAttrs.colors) {
715 config.colorArrayStyle = themeAttrs.colors;
717 colorArrayStyle = [];
718 for (l = seriesThemes.length; i < l; i++) {
719 st = seriesThemes[i];
720 if (st.fill || st.stroke) {
721 colorArrayStyle.push(st.fill || st.stroke);
724 if (colorArrayStyle.length) {
725 config.colorArrayStyle = colorArrayStyle;
728 config.seriesIdx = idx;
730 if (series instanceof Ext.chart.series.Series) {
731 Ext.apply(series, config);
733 Ext.applyIf(config, series);
734 series = me.series.replace(Ext.createByAlias('series.' + series.type.toLowerCase(), config));
736 if (series.initialize) {
742 getMaxGutter: function() {
745 me.series.each(function(s) {
746 var gutter = s.getGutters && s.getGutters() || [0, 0];
747 maxGutter[0] = Math.max(maxGutter[0], gutter[0]);
748 maxGutter[1] = Math.max(maxGutter[1], gutter[1]);
750 me.maxGutter = maxGutter;
753 // @private draw axis.
754 drawAxis: function(axis) {
758 // @private draw series.
759 drawCharts: function(series) {
760 series.triggerafterrender = false;
763 series.fireEvent('afterrender');
767 // @private remove gently.
768 destroy: function() {
769 this.surface.destroy();
770 this.bindStore(null);
771 this.callParent(arguments);