4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5 <title>The source code</title>
6 <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
7 <script type="text/javascript" src="../resources/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> * Charts provide a flexible way to achieve a wide range of data visualization capablitities.
20 * Each Chart gets its data directly from a {@link Ext.data.Store Store}, and automatically
21 * updates its display whenever data in the Store changes. In addition, the look and feel
22 * of a Chart can be customized using {@link Ext.chart.theme.Theme Theme}s.
24 * ## Creating a Simple Chart
26 * Every Chart has three key parts - a {@link Ext.data.Store Store} that contains the data,
27 * an array of {@link Ext.chart.axis.Axis Axes} which define the boundaries of the Chart,
28 * and one or more {@link Ext.chart.series.Series Series} to handle the visual rendering of the data points.
30 * ### 1. Creating a Store
32 * The first step is to create a {@link Ext.data.Model Model} that represents the type of
33 * data that will be displayed in the Chart. For example the data for a chart that displays
34 * a weather forecast could be represented as a series of "WeatherPoint" data points with
35 * two fields - "temperature", and "date":
37 * Ext.define('WeatherPoint', {
38 * extend: 'Ext.data.Model',
39 * fields: ['temperature', 'date']
42 * Next a {@link Ext.data.Store Store} must be created. The store contains a collection of "WeatherPoint" Model instances.
43 * The data could be loaded dynamically, but for sake of ease this example uses inline data:
45 * var store = Ext.create('Ext.data.Store', {
46 * model: 'WeatherPoint',
48 * { temperature: 58, date: new Date(2011, 1, 1, 8) },
49 * { temperature: 63, date: new Date(2011, 1, 1, 9) },
50 * { temperature: 73, date: new Date(2011, 1, 1, 10) },
51 * { temperature: 78, date: new Date(2011, 1, 1, 11) },
52 * { temperature: 81, date: new Date(2011, 1, 1, 12) }
56 * For additional information on Models and Stores please refer to the [Data Guide](#/guide/data).
58 * ### 2. Creating the Chart object
60 * Now that a Store has been created it can be used in a Chart:
62 * Ext.create('Ext.chart.Chart', {
63 * renderTo: Ext.getBody(),
69 * That's all it takes to create a Chart instance that is backed by a Store.
70 * However, if the above code is run in a browser, a blank screen will be displayed.
71 * This is because the two pieces that are responsible for the visual display,
72 * the Chart's {@link #cfg-axes axes} and {@link #cfg-series series}, have not yet been defined.
74 * ### 3. Configuring the Axes
76 * {@link Ext.chart.axis.Axis Axes} are the lines that define the boundaries of the data points that a Chart can display.
77 * This example uses one of the most common Axes configurations - a horizontal "x" axis, and a vertical "y" axis:
79 * Ext.create('Ext.chart.Chart', {
83 * title: 'Temperature',
86 * fields: ['temperature'],
100 * The "Temperature" axis is a vertical {@link Ext.chart.axis.Numeric Numeric Axis} and is positioned on the left edge of the Chart.
101 * It represents the bounds of the data contained in the "WeatherPoint" Model's "temperature" field that was
102 * defined above. The minimum value for this axis is "0", and the maximum is "100".
104 * The horizontal axis is a {@link Ext.chart.axis.Time Time Axis} and is positioned on the bottom edge of the Chart.
105 * It represents the bounds of the data contained in the "WeatherPoint" Model's "date" field.
106 * The {@link Ext.chart.axis.Time#cfg-dateFormat dateFormat}
107 * configuration tells the Time Axis how to format it's labels.
109 * Here's what the Chart looks like now that it has its Axes configured:
111 * {@img Ext.chart.Chart/Ext.chart.Chart1.png Chart Axes}
113 * ### 4. Configuring the Series
115 * The final step in creating a simple Chart is to configure one or more {@link Ext.chart.series.Series Series}.
116 * Series are responsible for the visual representation of the data points contained in the Store.
117 * This example only has one Series:
119 * Ext.create('Ext.chart.Chart', {
128 * yField: 'temperature'
133 * This Series is a {@link Ext.chart.series.Line Line Series}, and it uses the "date" and "temperature" fields
134 * from the "WeatherPoint" Models in the Store to plot its data points:
136 * {@img Ext.chart.Chart/Ext.chart.Chart2.png Line Series}
138 * See the [Simple Chart Example](doc-resources/Ext.chart.Chart/examples/simple_chart/index.html) for a live demo.
142 * The color scheme for a Chart can be easily changed using the {@link #cfg-theme theme} configuration option:
144 * Ext.create('Ext.chart.Chart', {
150 * {@img Ext.chart.Chart/Ext.chart.Chart3.png Green Theme}
152 * For more information on Charts please refer to the [Drawing and Charting Guide](#/guide/drawing_and_charting).
155 Ext.define('Ext.chart.Chart', {
157 /* Begin Definitions */
159 alias: 'widget.chart',
161 extend: 'Ext.draw.Component',
164 themeManager: 'Ext.chart.theme.Theme',
165 mask: 'Ext.chart.Mask',
166 navigation: 'Ext.chart.Navigation'
170 'Ext.util.MixedCollection',
171 'Ext.data.StoreManager',
173 'Ext.util.DelayedTask'
176 /* End Definitions */
181 <span id='Ext-chart-Chart-cfg-theme'> /**
182 </span> * @cfg {String} theme
183 * The name of the theme to be used. A theme defines the colors and other visual displays of tick marks
184 * on axis, text, title text, line colors, marker colors and styles, etc. Possible theme values are 'Base', 'Green',
185 * 'Sky', 'Red', 'Purple', 'Blue', 'Yellow' and also six category themes 'Category1' to 'Category6'. Default value
189 <span id='Ext-chart-Chart-cfg-animate'> /**
190 </span> * @cfg {Boolean/Object} animate
191 * True for the default animation (easing: 'ease' and duration: 500) or a standard animation config
192 * object to be used for default chart animations. Defaults to false.
196 <span id='Ext-chart-Chart-cfg-legend'> /**
197 </span> * @cfg {Boolean/Object} legend
198 * True for the default legend display or a legend config object. Defaults to false.
202 <span id='Ext-chart-Chart-cfg-insetPadding'> /**
203 </span> * @cfg {Number} insetPadding
204 * The amount of inset padding in pixels for the chart. Defaults to 10.
208 <span id='Ext-chart-Chart-cfg-enginePriority'> /**
209 </span> * @cfg {String[]} enginePriority
210 * Defines the priority order for which Surface implementation to use. The first one supported by the current
211 * environment will be used. Defaults to `['Svg', 'Vml']`.
213 enginePriority: ['Svg', 'Vml'],
215 <span id='Ext-chart-Chart-cfg-background'> /**
216 </span> * @cfg {Object/Boolean} background
217 * The chart background. This can be a gradient object, image, or color. Defaults to false for no
218 * background. For example, if `background` were to be a color we could set the object as
225 * You can specify an image by using:
228 * image: 'http://path.to.image/'
231 * Also you can specify a gradient by using the gradient object syntax:
250 <span id='Ext-chart-Chart-cfg-gradients'> /**
251 </span> * @cfg {Object[]} gradients
252 * Define a set of gradients that can be used as `fill` property in sprites. The gradients array is an
253 * array of objects with the following properties:
255 * - **id** - string - The unique name of the gradient.
256 * - **angle** - number, optional - The angle of the gradient in degrees.
257 * - **stops** - object - An object with numbers as keys (from 0 to 100) and style objects as values
288 * Then the sprites can use `gradientId` and `gradientId2` by setting the fill attributes to those ids, for example:
290 * sprite.setAttributes({
291 * fill: 'url(#gradientId)'
295 <span id='Ext-chart-Chart-cfg-store'> /**
296 </span> * @cfg {Ext.data.Store} store
297 * The store that supplies data to this chart.
300 <span id='Ext-chart-Chart-cfg-series'> /**
301 </span> * @cfg {Ext.chart.series.Series[]} series
302 * Array of {@link Ext.chart.series.Series Series} instances or config objects. For example:
308 * 'afterrender': function() {
309 * console('afterrender');
312 * xField: 'category',
317 <span id='Ext-chart-Chart-cfg-axes'> /**
318 </span> * @cfg {Ext.chart.axis.Axis[]} axes
319 * Array of {@link Ext.chart.axis.Axis Axis} instances or config objects. For example:
325 * title: 'Number of Hits',
327 * //one minor tick between two major ticks
331 * position: 'bottom',
333 * title: 'Month of the Year'
337 constructor: function(config) {
341 config = Ext.apply({}, config);
342 me.initTheme(config.theme || me.theme);
344 Ext.apply(config, { gradients: me.gradients });
347 Ext.apply(config, { background: me.background });
349 if (config.animate) {
354 if (Ext.isObject(config.animate)) {
355 config.animate = Ext.applyIf(config.animate, defaultAnim);
358 config.animate = defaultAnim;
361 me.mixins.mask.constructor.call(me, config);
362 me.mixins.navigation.constructor.call(me, config);
363 me.callParent([config]);
366 getChartStore: function(){
367 return this.substore || this.store;
370 initComponent: function() {
385 <span id='Ext-chart-Chart-event-beforerefresh'> /**
386 </span> * @event beforerefresh
387 * Fires before a refresh to the chart data is called. If the beforerefresh handler returns false the
388 * {@link #refresh} action will be cancelled.
389 * @param {Ext.chart.Chart} this
392 <span id='Ext-chart-Chart-event-refresh'> /**
393 </span> * @event refresh
394 * Fires after the chart data has been refreshed.
395 * @param {Ext.chart.Chart} this
407 me.maxGutter = [0, 0];
408 me.store = Ext.data.StoreManager.lookup(me.store);
410 me.axes = Ext.create('Ext.util.MixedCollection', false, function(a) { return a.position; });
412 me.axes.addAll(axes);
415 me.series = Ext.create('Ext.util.MixedCollection', false, function(a) { return a.seriesId || (a.seriesId = Ext.id(null, 'ext-chart-series-')); });
417 me.series.addAll(series);
419 if (me.legend !== false) {
420 me.legend = Ext.create('Ext.chart.Legend', Ext.applyIf({chart:me}, me.legend));
424 mousemove: me.onMouseMove,
425 mouseleave: me.onMouseLeave,
426 mousedown: me.onMouseDown,
427 mouseup: me.onMouseUp,
432 // @private overrides the component method to set the correct dimensions to the chart.
433 afterComponentLayout: function(width, height) {
435 if (Ext.isNumber(width) && Ext.isNumber(height)) {
437 me.curHeight = height;
440 this.callParent(arguments);
443 <span id='Ext-chart-Chart-method-redraw'> /**
444 </span> * Redraws the chart. If animations are set this will animate the chart too.
445 * @param {Boolean} resize (optional) flag which changes the default origin points of the chart for animations.
447 redraw: function(resize) {
449 chartBBox = me.chartBBox = {
452 height: me.curHeight,
456 me.surface.setSize(chartBBox.width, chartBBox.height);
457 // Instantiate Series and Axes
458 me.series.each(me.initializeSeries, me);
459 me.axes.each(me.initializeAxis, me);
460 //process all views (aggregated data etc) on stores
462 me.axes.each(function(axis) {
465 me.axes.each(function(axis) {
469 // Create legend if not already created
470 if (legend !== false) {
474 // Place axes properly, including influence from each other
477 // Reposition legend based on new axis alignment
478 if (me.legend !== false) {
479 legend.updatePosition();
482 // Find the max gutter
485 // Draw axes and series
486 me.resizing = !!resize;
488 me.axes.each(me.drawAxis, me);
489 me.series.each(me.drawCharts, me);
493 // @private set the store after rendering the chart.
494 afterRender: function() {
499 if (me.categoryNames) {
500 me.setCategoryNames(me.categoryNames);
503 if (me.tipRenderer) {
504 ref = me.getFunctionRef(me.tipRenderer);
505 me.setTipRenderer(ref.fn, ref.scope);
507 me.bindStore(me.store, true);
511 // @private get x and y position of the mouse cursor.
512 getEventXY: function(e) {
514 box = this.surface.getRegion(),
516 x = pageXY[0] - box.left,
517 y = pageXY[1] - box.top;
521 // @private wrap the mouse down position to delegate the event to the series.
522 onClick: function(e) {
524 position = me.getEventXY(e),
527 // Ask each series if it has an item corresponding to (not necessarily exactly
528 // on top of) the current mouse coords. Fire itemclick event.
529 me.series.each(function(series) {
530 if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
531 if (series.getItemForPoint) {
532 item = series.getItemForPoint(position[0], position[1]);
534 series.fireEvent('itemclick', item);
541 // @private wrap the mouse down position to delegate the event to the series.
542 onMouseDown: function(e) {
544 position = me.getEventXY(e),
548 me.mixins.mask.onMouseDown.call(me, e);
550 // Ask each series if it has an item corresponding to (not necessarily exactly
551 // on top of) the current mouse coords. Fire mousedown event.
552 me.series.each(function(series) {
553 if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
554 if (series.getItemForPoint) {
555 item = series.getItemForPoint(position[0], position[1]);
557 series.fireEvent('itemmousedown', item);
564 // @private wrap the mouse up event to delegate it to the series.
565 onMouseUp: function(e) {
567 position = me.getEventXY(e),
571 me.mixins.mask.onMouseUp.call(me, e);
573 // Ask each series if it has an item corresponding to (not necessarily exactly
574 // on top of) the current mouse coords. Fire mousedown event.
575 me.series.each(function(series) {
576 if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
577 if (series.getItemForPoint) {
578 item = series.getItemForPoint(position[0], position[1]);
580 series.fireEvent('itemmouseup', item);
587 // @private wrap the mouse move event so it can be delegated to the series.
588 onMouseMove: function(e) {
590 position = me.getEventXY(e),
591 item, last, storeItem, storeField;
594 me.mixins.mask.onMouseMove.call(me, e);
596 // Ask each series if it has an item corresponding to (not necessarily exactly
597 // on top of) the current mouse coords. Fire itemmouseover/out events.
598 me.series.each(function(series) {
599 if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
600 if (series.getItemForPoint) {
601 item = series.getItemForPoint(position[0], position[1]);
602 last = series._lastItemForPoint;
603 storeItem = series._lastStoreItem;
604 storeField = series._lastStoreField;
607 if (item !== last || item && (item.storeItem != storeItem || item.storeField != storeField)) {
609 series.fireEvent('itemmouseout', last);
610 delete series._lastItemForPoint;
611 delete series._lastStoreField;
612 delete series._lastStoreItem;
615 series.fireEvent('itemmouseover', item);
616 series._lastItemForPoint = item;
617 series._lastStoreItem = item.storeItem;
618 series._lastStoreField = item.storeField;
623 last = series._lastItemForPoint;
625 series.fireEvent('itemmouseout', last);
626 delete series._lastItemForPoint;
627 delete series._lastStoreField;
628 delete series._lastStoreItem;
634 // @private handle mouse leave event.
635 onMouseLeave: function(e) {
638 me.mixins.mask.onMouseLeave.call(me, e);
640 me.series.each(function(series) {
641 delete series._lastItemForPoint;
645 // @private buffered refresh for when we update the store
646 delayRefresh: function() {
648 if (!me.refreshTask) {
649 me.refreshTask = Ext.create('Ext.util.DelayedTask', me.refresh, me);
651 me.refreshTask.delay(me.refreshBuffer);
655 refresh: function() {
657 if (me.rendered && me.curWidth !== undefined && me.curHeight !== undefined) {
658 if (me.fireEvent('beforerefresh', me) !== false) {
660 me.fireEvent('refresh', me);
665 <span id='Ext-chart-Chart-method-bindStore'> /**
666 </span> * Changes the data store bound to this chart and refreshes it.
667 * @param {Ext.data.Store} store The store to bind to this chart
669 bindStore: function(store, initial) {
671 if (!initial && me.store) {
672 if (store !== me.store && me.store.autoDestroy) {
673 me.store.destroyStore();
676 me.store.un('datachanged', me.refresh, me);
677 me.store.un('add', me.delayRefresh, me);
678 me.store.un('remove', me.delayRefresh, me);
679 me.store.un('update', me.delayRefresh, me);
680 me.store.un('clear', me.refresh, me);
684 store = Ext.data.StoreManager.lookup(store);
687 datachanged: me.refresh,
688 add: me.delayRefresh,
689 remove: me.delayRefresh,
690 update: me.delayRefresh,
695 if (store && !initial) {
700 // @private Create Axis
701 initializeAxis: function(axis) {
703 chartBBox = me.chartBBox,
705 h = chartBBox.height,
708 themeAttrs = me.themeAttrs,
713 config.axisStyle = Ext.apply({}, themeAttrs.axis);
714 config.axisLabelLeftStyle = Ext.apply({}, themeAttrs.axisLabelLeft);
715 config.axisLabelRightStyle = Ext.apply({}, themeAttrs.axisLabelRight);
716 config.axisLabelTopStyle = Ext.apply({}, themeAttrs.axisLabelTop);
717 config.axisLabelBottomStyle = Ext.apply({}, themeAttrs.axisLabelBottom);
718 config.axisTitleLeftStyle = Ext.apply({}, themeAttrs.axisTitleLeft);
719 config.axisTitleRightStyle = Ext.apply({}, themeAttrs.axisTitleRight);
720 config.axisTitleTopStyle = Ext.apply({}, themeAttrs.axisTitleTop);
721 config.axisTitleBottomStyle = Ext.apply({}, themeAttrs.axisTitleBottom);
723 switch (axis.position) {
758 Ext.apply(config, axis);
759 axis = me.axes.replace(Ext.createByAlias('axis.' + axis.type.toLowerCase(), config));
762 Ext.apply(axis, config);
767 <span id='Ext-chart-Chart-method-alignAxes'> /**
768 </span> * @private Adjust the dimensions and positions of each axis and the chart body area after accounting
769 * for the space taken up on each side by the axes and legend.
771 alignAxes: function() {
775 edges = ['top', 'right', 'bottom', 'left'],
777 insetPadding = me.insetPadding,
781 bottom: insetPadding,
785 function getAxis(edge) {
786 var i = axes.findIndex('position', edge);
787 return (i < 0) ? null : axes.getAt(i);
790 // Find the space needed by axes and legend as a positive inset from each edge
791 Ext.each(edges, function(edge) {
792 var isVertical = (edge === 'left' || edge === 'right'),
793 axis = getAxis(edge),
796 // Add legend size if it's on this edge
797 if (legend !== false) {
798 if (legend.position === edge) {
799 bbox = legend.getBBox();
800 insets[edge] += (isVertical ? bbox.width : bbox.height) + insets[edge];
804 // Add axis size if there's one on this edge only if it has been
806 if (axis && axis.bbox) {
808 insets[edge] += (isVertical ? bbox.width : bbox.height);
811 // Build the chart bbox based on the collected inset values
815 width: me.curWidth - insets.left - insets.right,
816 height: me.curHeight - insets.top - insets.bottom
818 me.chartBBox = chartBBox;
820 // Go back through each axis and set its length and position based on the
821 // corresponding edge of the chartBBox
822 axes.each(function(axis) {
823 var pos = axis.position,
824 isVertical = (pos === 'left' || pos === 'right');
826 axis.x = (pos === 'right' ? chartBBox.x + chartBBox.width : chartBBox.x);
827 axis.y = (pos === 'top' ? chartBBox.y : chartBBox.y + chartBBox.height);
828 axis.width = (isVertical ? chartBBox.width : chartBBox.height);
829 axis.length = (isVertical ? chartBBox.height : chartBBox.width);
833 // @private initialize the series.
834 initializeSeries: function(series, idx) {
836 themeAttrs = me.themeAttrs,
837 seriesObj, markerObj, seriesThemes, st,
838 markerThemes, colorArrayStyle = [],
842 seriesId: series.seriesId
845 seriesThemes = themeAttrs.seriesThemes;
846 markerThemes = themeAttrs.markerThemes;
847 seriesObj = Ext.apply({}, themeAttrs.series);
848 markerObj = Ext.apply({}, themeAttrs.marker);
849 config.seriesStyle = Ext.apply(seriesObj, seriesThemes[idx % seriesThemes.length]);
850 config.seriesLabelStyle = Ext.apply({}, themeAttrs.seriesLabel);
851 config.markerStyle = Ext.apply(markerObj, markerThemes[idx % markerThemes.length]);
852 if (themeAttrs.colors) {
853 config.colorArrayStyle = themeAttrs.colors;
855 colorArrayStyle = [];
856 for (l = seriesThemes.length; i < l; i++) {
857 st = seriesThemes[i];
858 if (st.fill || st.stroke) {
859 colorArrayStyle.push(st.fill || st.stroke);
862 if (colorArrayStyle.length) {
863 config.colorArrayStyle = colorArrayStyle;
866 config.seriesIdx = idx;
868 if (series instanceof Ext.chart.series.Series) {
869 Ext.apply(series, config);
871 Ext.applyIf(config, series);
872 series = me.series.replace(Ext.createByAlias('series.' + series.type.toLowerCase(), config));
874 if (series.initialize) {
880 getMaxGutter: function() {
883 me.series.each(function(s) {
884 var gutter = s.getGutters && s.getGutters() || [0, 0];
885 maxGutter[0] = Math.max(maxGutter[0], gutter[0]);
886 maxGutter[1] = Math.max(maxGutter[1], gutter[1]);
888 me.maxGutter = maxGutter;
891 // @private draw axis.
892 drawAxis: function(axis) {
896 // @private draw series.
897 drawCharts: function(series) {
898 series.triggerafterrender = false;
901 series.fireEvent('afterrender');
905 // @private remove gently.
906 destroy: function() {
907 Ext.destroy(this.surface);
908 this.bindStore(null);
909 this.callParent(arguments);