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).
50 Ext.define('Ext.chart.Chart', {
52 /* Begin Definitions */
54 alias: 'widget.chart',
56 extend: 'Ext.draw.Component',
59 themeManager: 'Ext.chart.theme.Theme',
60 mask: 'Ext.chart.Mask',
61 navigation: 'Ext.chart.Navigation'
65 'Ext.util.MixedCollection',
66 'Ext.data.StoreManager',
68 'Ext.util.DelayedTask'
76 <span id='Ext-chart-Chart-cfg-theme'> /**
77 </span> * @cfg {String} theme (optional) The name of the theme to be used. A theme defines the colors and
78 * other visual displays of tick marks on axis, text, title text, line colors, marker colors and styles, etc.
79 * Possible theme values are 'Base', 'Green', 'Sky', 'Red', 'Purple', 'Blue', 'Yellow' and also six category themes
80 * 'Category1' to 'Category6'. Default value is 'Base'.
83 <span id='Ext-chart-Chart-cfg-animate'> /**
84 </span> * @cfg {Boolean/Object} animate (optional) true for the default animation (easing: 'ease' and duration: 500)
85 * or a standard animation config object to be used for default chart animations. Defaults to false.
89 <span id='Ext-chart-Chart-cfg-legend'> /**
90 </span> * @cfg {Boolean/Object} legend (optional) true for the default legend display or a legend config object. Defaults to false.
94 <span id='Ext-chart-Chart-cfg-insetPadding'> /**
95 </span> * @cfg {integer} insetPadding (optional) Set the amount of inset padding in pixels for the chart. Defaults to 10.
99 <span id='Ext-chart-Chart-cfg-enginePriority'> /**
100 </span> * @cfg {Array} enginePriority
101 * Defines the priority order for which Surface implementation to use. The first
102 * one supported by the current environment will be used.
104 enginePriority: ['Svg', 'Vml'],
106 <span id='Ext-chart-Chart-cfg-background'> /**
107 </span> * @cfg {Object|Boolean} background (optional) Set the chart background. This can be a gradient object, image, or color.
108 * Defaults to false for no background.
110 * For example, if `background` were to be a color we could set the object as
112 <pre><code>
117 </code></pre>
119 You can specify an image by using:
121 <pre><code>
123 image: 'http://path.to.image/'
125 </code></pre>
127 Also you can specify a gradient by using the gradient object syntax:
129 <pre><code>
144 </code></pre>
148 <span id='Ext-chart-Chart-cfg-gradients'> /**
149 </span> * @cfg {Array} gradients (optional) Define a set of gradients that can be used as `fill` property in sprites.
150 * The gradients array is an array of objects with the following properties:
153 * <li><strong>id</strong> - string - The unique name of the gradient.</li>
154 * <li><strong>angle</strong> - number, optional - The angle of the gradient in degrees.</li>
155 * <li><strong>stops</strong> - object - An object with numbers as keys (from 0 to 100) and style objects
156 * as values</li>
162 <pre><code>
189 </code></pre>
191 Then the sprites can use `gradientId` and `gradientId2` by setting the fill attributes to those ids, for example:
193 <pre><code>
194 sprite.setAttributes({
195 fill: 'url(#gradientId)'
197 </code></pre>
202 constructor: function(config) {
205 me.initTheme(config.theme || me.theme);
207 Ext.apply(config, { gradients: me.gradients });
210 Ext.apply(config, { background: me.background });
212 if (config.animate) {
217 if (Ext.isObject(config.animate)) {
218 config.animate = Ext.applyIf(config.animate, defaultAnim);
221 config.animate = defaultAnim;
224 me.mixins.mask.constructor.call(me, config);
225 me.mixins.navigation.constructor.call(me, config);
226 me.callParent([config]);
229 initComponent: function() {
244 <span id='Ext-chart-Chart-event-beforerefresh'> /**
245 </span> * @event beforerefresh
246 * Fires before a refresh to the chart data is called. If the beforerefresh handler returns
247 * <tt>false</tt> the {@link #refresh} action will be cancelled.
248 * @param {Chart} this
251 <span id='Ext-chart-Chart-event-refresh'> /**
252 </span> * @event refresh
253 * Fires after the chart data has been refreshed.
254 * @param {Chart} this
266 me.maxGutter = [0, 0];
267 me.store = Ext.data.StoreManager.lookup(me.store);
269 me.axes = Ext.create('Ext.util.MixedCollection', false, function(a) { return a.position; });
271 me.axes.addAll(axes);
274 me.series = Ext.create('Ext.util.MixedCollection', false, function(a) { return a.seriesId || (a.seriesId = Ext.id(null, 'ext-chart-series-')); });
276 me.series.addAll(series);
278 if (me.legend !== false) {
279 me.legend = Ext.create('Ext.chart.Legend', Ext.applyIf({chart:me}, me.legend));
283 mousemove: me.onMouseMove,
284 mouseleave: me.onMouseLeave,
285 mousedown: me.onMouseDown,
286 mouseup: me.onMouseUp,
291 // @private overrides the component method to set the correct dimensions to the chart.
292 afterComponentLayout: function(width, height) {
294 if (Ext.isNumber(width) && Ext.isNumber(height)) {
296 me.curHeight = height;
299 this.callParent(arguments);
302 <span id='Ext-chart-Chart-cfg-resize'> /**
303 </span> * Redraw the chart. If animations are set this will animate the chart too.
304 * @cfg {boolean} resize Optional flag which changes the default origin points of the chart for animations.
306 redraw: function(resize) {
308 chartBBox = me.chartBBox = {
311 height: me.curHeight,
315 me.surface.setSize(chartBBox.width, chartBBox.height);
316 // Instantiate Series and Axes
317 me.series.each(me.initializeSeries, me);
318 me.axes.each(me.initializeAxis, me);
319 //process all views (aggregated data etc) on stores
321 me.axes.each(function(axis) {
324 me.axes.each(function(axis) {
328 // Create legend if not already created
329 if (legend !== false) {
333 // Place axes properly, including influence from each other
336 // Reposition legend based on new axis alignment
337 if (me.legend !== false) {
338 legend.updatePosition();
341 // Find the max gutter
344 // Draw axes and series
345 me.resizing = !!resize;
347 me.axes.each(me.drawAxis, me);
348 me.series.each(me.drawCharts, me);
352 // @private set the store after rendering the chart.
353 afterRender: function() {
358 if (me.categoryNames) {
359 me.setCategoryNames(me.categoryNames);
362 if (me.tipRenderer) {
363 ref = me.getFunctionRef(me.tipRenderer);
364 me.setTipRenderer(ref.fn, ref.scope);
366 me.bindStore(me.store, true);
370 // @private get x and y position of the mouse cursor.
371 getEventXY: function(e) {
373 box = this.surface.getRegion(),
375 x = pageXY[0] - box.left,
376 y = pageXY[1] - box.top;
380 // @private wrap the mouse down position to delegate the event to the series.
381 onClick: function(e) {
383 position = me.getEventXY(e),
386 // Ask each series if it has an item corresponding to (not necessarily exactly
387 // on top of) the current mouse coords. Fire itemclick event.
388 me.series.each(function(series) {
389 if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
390 if (series.getItemForPoint) {
391 item = series.getItemForPoint(position[0], position[1]);
393 series.fireEvent('itemclick', item);
400 // @private wrap the mouse down position to delegate the event to the series.
401 onMouseDown: function(e) {
403 position = me.getEventXY(e),
407 me.mixins.mask.onMouseDown.call(me, e);
409 // Ask each series if it has an item corresponding to (not necessarily exactly
410 // on top of) the current mouse coords. Fire mousedown event.
411 me.series.each(function(series) {
412 if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
413 if (series.getItemForPoint) {
414 item = series.getItemForPoint(position[0], position[1]);
416 series.fireEvent('itemmousedown', item);
423 // @private wrap the mouse up event to delegate it to the series.
424 onMouseUp: function(e) {
426 position = me.getEventXY(e),
430 me.mixins.mask.onMouseUp.call(me, e);
432 // Ask each series if it has an item corresponding to (not necessarily exactly
433 // on top of) the current mouse coords. Fire mousedown event.
434 me.series.each(function(series) {
435 if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
436 if (series.getItemForPoint) {
437 item = series.getItemForPoint(position[0], position[1]);
439 series.fireEvent('itemmouseup', item);
446 // @private wrap the mouse move event so it can be delegated to the series.
447 onMouseMove: function(e) {
449 position = me.getEventXY(e),
450 item, last, storeItem, storeField;
453 me.mixins.mask.onMouseMove.call(me, e);
455 // Ask each series if it has an item corresponding to (not necessarily exactly
456 // on top of) the current mouse coords. Fire itemmouseover/out events.
457 me.series.each(function(series) {
458 if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
459 if (series.getItemForPoint) {
460 item = series.getItemForPoint(position[0], position[1]);
461 last = series._lastItemForPoint;
462 storeItem = series._lastStoreItem;
463 storeField = series._lastStoreField;
466 if (item !== last || item && (item.storeItem != storeItem || item.storeField != storeField)) {
468 series.fireEvent('itemmouseout', last);
469 delete series._lastItemForPoint;
470 delete series._lastStoreField;
471 delete series._lastStoreItem;
474 series.fireEvent('itemmouseover', item);
475 series._lastItemForPoint = item;
476 series._lastStoreItem = item.storeItem;
477 series._lastStoreField = item.storeField;
482 last = series._lastItemForPoint;
484 series.fireEvent('itemmouseout', last);
485 delete series._lastItemForPoint;
486 delete series._lastStoreField;
487 delete series._lastStoreItem;
493 // @private handle mouse leave event.
494 onMouseLeave: function(e) {
497 me.mixins.mask.onMouseLeave.call(me, e);
499 me.series.each(function(series) {
500 delete series._lastItemForPoint;
504 // @private buffered refresh for when we update the store
505 delayRefresh: function() {
507 if (!me.refreshTask) {
508 me.refreshTask = Ext.create('Ext.util.DelayedTask', me.refresh, me);
510 me.refreshTask.delay(me.refreshBuffer);
514 refresh: function() {
516 if (me.rendered && me.curWidth != undefined && me.curHeight != undefined) {
517 if (me.fireEvent('beforerefresh', me) !== false) {
519 me.fireEvent('refresh', me);
524 <span id='Ext-chart-Chart-method-bindStore'> /**
525 </span> * Changes the data store bound to this chart and refreshes it.
526 * @param {Store} store The store to bind to this chart
528 bindStore: function(store, initial) {
530 if (!initial && me.store) {
531 if (store !== me.store && me.store.autoDestroy) {
535 me.store.un('datachanged', me.refresh, me);
536 me.store.un('add', me.delayRefresh, me);
537 me.store.un('remove', me.delayRefresh, me);
538 me.store.un('update', me.delayRefresh, me);
539 me.store.un('clear', me.refresh, me);
543 store = Ext.data.StoreManager.lookup(store);
546 datachanged: me.refresh,
547 add: me.delayRefresh,
548 remove: me.delayRefresh,
549 update: me.delayRefresh,
554 if (store && !initial) {
559 // @private Create Axis
560 initializeAxis: function(axis) {
562 chartBBox = me.chartBBox,
564 h = chartBBox.height,
567 themeAttrs = me.themeAttrs,
572 config.axisStyle = Ext.apply({}, themeAttrs.axis);
573 config.axisLabelLeftStyle = Ext.apply({}, themeAttrs.axisLabelLeft);
574 config.axisLabelRightStyle = Ext.apply({}, themeAttrs.axisLabelRight);
575 config.axisLabelTopStyle = Ext.apply({}, themeAttrs.axisLabelTop);
576 config.axisLabelBottomStyle = Ext.apply({}, themeAttrs.axisLabelBottom);
577 config.axisTitleLeftStyle = Ext.apply({}, themeAttrs.axisTitleLeft);
578 config.axisTitleRightStyle = Ext.apply({}, themeAttrs.axisTitleRight);
579 config.axisTitleTopStyle = Ext.apply({}, themeAttrs.axisTitleTop);
580 config.axisTitleBottomStyle = Ext.apply({}, themeAttrs.axisTitleBottom);
582 switch (axis.position) {
617 Ext.apply(config, axis);
618 axis = me.axes.replace(Ext.createByAlias('axis.' + axis.type.toLowerCase(), config));
621 Ext.apply(axis, config);
626 <span id='Ext-chart-Chart-method-alignAxes'> /**
627 </span> * @private Adjust the dimensions and positions of each axis and the chart body area after accounting
628 * for the space taken up on each side by the axes and legend.
630 alignAxes: function() {
634 edges = ['top', 'right', 'bottom', 'left'],
636 insetPadding = me.insetPadding,
640 bottom: insetPadding,
644 function getAxis(edge) {
645 var i = axes.findIndex('position', edge);
646 return (i < 0) ? null : axes.getAt(i);
649 // Find the space needed by axes and legend as a positive inset from each edge
650 Ext.each(edges, function(edge) {
651 var isVertical = (edge === 'left' || edge === 'right'),
652 axis = getAxis(edge),
655 // Add legend size if it's on this edge
656 if (legend !== false) {
657 if (legend.position === edge) {
658 bbox = legend.getBBox();
659 insets[edge] += (isVertical ? bbox.width : bbox.height) + insets[edge];
663 // Add axis size if there's one on this edge only if it has been
665 if (axis && axis.bbox) {
667 insets[edge] += (isVertical ? bbox.width : bbox.height);
670 // Build the chart bbox based on the collected inset values
674 width: me.curWidth - insets.left - insets.right,
675 height: me.curHeight - insets.top - insets.bottom
677 me.chartBBox = chartBBox;
679 // Go back through each axis and set its length and position based on the
680 // corresponding edge of the chartBBox
681 axes.each(function(axis) {
682 var pos = axis.position,
683 isVertical = (pos === 'left' || pos === 'right');
685 axis.x = (pos === 'right' ? chartBBox.x + chartBBox.width : chartBBox.x);
686 axis.y = (pos === 'top' ? chartBBox.y : chartBBox.y + chartBBox.height);
687 axis.width = (isVertical ? chartBBox.width : chartBBox.height);
688 axis.length = (isVertical ? chartBBox.height : chartBBox.width);
692 // @private initialize the series.
693 initializeSeries: function(series, idx) {
695 themeAttrs = me.themeAttrs,
696 seriesObj, markerObj, seriesThemes, st,
697 markerThemes, colorArrayStyle = [],
701 seriesId: series.seriesId
704 seriesThemes = themeAttrs.seriesThemes;
705 markerThemes = themeAttrs.markerThemes;
706 seriesObj = Ext.apply({}, themeAttrs.series);
707 markerObj = Ext.apply({}, themeAttrs.marker);
708 config.seriesStyle = Ext.apply(seriesObj, seriesThemes[idx % seriesThemes.length]);
709 config.seriesLabelStyle = Ext.apply({}, themeAttrs.seriesLabel);
710 config.markerStyle = Ext.apply(markerObj, markerThemes[idx % markerThemes.length]);
711 if (themeAttrs.colors) {
712 config.colorArrayStyle = themeAttrs.colors;
714 colorArrayStyle = [];
715 for (l = seriesThemes.length; i < l; i++) {
716 st = seriesThemes[i];
717 if (st.fill || st.stroke) {
718 colorArrayStyle.push(st.fill || st.stroke);
721 if (colorArrayStyle.length) {
722 config.colorArrayStyle = colorArrayStyle;
725 config.seriesIdx = idx;
727 if (series instanceof Ext.chart.series.Series) {
728 Ext.apply(series, config);
730 Ext.applyIf(config, series);
731 series = me.series.replace(Ext.createByAlias('series.' + series.type.toLowerCase(), config));
733 if (series.initialize) {
739 getMaxGutter: function() {
742 me.series.each(function(s) {
743 var gutter = s.getGutters && s.getGutters() || [0, 0];
744 maxGutter[0] = Math.max(maxGutter[0], gutter[0]);
745 maxGutter[1] = Math.max(maxGutter[1], gutter[1]);
747 me.maxGutter = maxGutter;
750 // @private draw axis.
751 drawAxis: function(axis) {
755 // @private draw series.
756 drawCharts: function(series) {
757 series.triggerafterrender = false;
760 series.fireEvent('afterrender');
764 // @private remove gently.
765 destroy: function() {
766 this.surface.destroy();
767 this.bindStore(null);
768 this.callParent(arguments);