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-series-Area'>/**
19 </span> * @class Ext.chart.series.Area
20 * @extends Ext.chart.series.Cartesian
22 * Creates a Stacked Area Chart. The stacked area chart is useful when displaying multiple aggregated layers of information.
23 * As with all other series, the Area Series must be appended in the *series* Chart array configuration. See the Chart
24 * documentation for more information. A typical configuration object for the area series could be:
27 * var store = Ext.create('Ext.data.JsonStore', {
28 * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
30 * { 'name': 'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13 },
31 * { 'name': 'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3 },
32 * { 'name': 'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7 },
33 * { 'name': 'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23 },
34 * { 'name': 'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
38 * Ext.create('Ext.chart.Chart', {
39 * renderTo: Ext.getBody(),
48 * fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
49 * title: 'Sample Values',
59 * adjustMinimumByMajorUnit: 0
65 * title: 'Sample Metrics',
79 * yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
86 * In this configuration we set `area` as the type for the series, set highlighting options to true for highlighting elements on hover,
87 * take the left axis to measure the data in the area series, set as xField (x values) the name field of each element in the store,
88 * and as yFields (aggregated layers) seven data fields from the same store. Then we override some theming styles by adding some opacity
89 * to the style object.
93 Ext.define('Ext.chart.series.Area', {
95 /* Begin Definitions */
97 extend: 'Ext.chart.series.Cartesian',
101 requires: ['Ext.chart.axis.Axis', 'Ext.draw.Color', 'Ext.fx.Anim'],
103 /* End Definitions */
107 // @private Area charts are alyways stacked
110 <span id='Ext-chart-series-Area-cfg-style'> /**
111 </span> * @cfg {Object} style
112 * Append styling properties to this object for it to override theme properties.
116 constructor: function(config) {
117 this.callParent(arguments);
119 surface = me.chart.surface,
121 Ext.apply(me, config, {
131 me.highlightSprite = surface.add({
141 me.group = surface.getGroup(me.seriesId);
144 // @private Shrinks dataSets down to a smaller size
145 shrink: function(xValues, yValues, size) {
146 var len = xValues.length,
147 ratio = Math.floor(len / size),
150 yCompLen = this.areas.length,
155 for (j = 0; j < yCompLen; ++j) {
158 for (i = 0; i < len; ++i) {
160 for (j = 0; j < yCompLen; ++j) {
161 ySum[j] += yValues[i][j];
163 if (i % ratio == 0) {
165 xRes.push(xSum/ratio);
166 for (j = 0; j < yCompLen; ++j) {
170 //reset sum accumulators
172 for (j = 0, ySum = []; j < yCompLen; ++j) {
183 // @private Get chart and data boundaries
184 getBounds: function() {
187 store = chart.getChartStore(),
188 areas = [].concat(me.yField),
189 areasLen = areas.length,
200 bbox, xScale, yScale, xValue, yValue, areaIndex, acumY, ln, sumValues, clipBox, areaElem;
205 // Run through the axis
207 axis = chart.axes.get(me.axis);
209 out = axis.calcEnds();
210 minY = out.from || axis.prevMin;
211 maxY = mmax(out.to || axis.prevMax, 0);
215 if (me.yField && !Ext.isNumber(minY)) {
216 axis = Ext.create('Ext.chart.axis.Axis', {
218 fields: [].concat(me.yField)
220 out = axis.calcEnds();
221 minY = out.from || axis.prevMin;
222 maxY = mmax(out.to || axis.prevMax, 0);
225 if (!Ext.isNumber(minY)) {
228 if (!Ext.isNumber(maxY)) {
232 store.each(function(record, i) {
233 xValue = record.get(me.xField);
235 if (typeof xValue != 'number') {
238 xValues.push(xValue);
240 for (areaIndex = 0; areaIndex < areasLen; areaIndex++) {
241 areaElem = record.get(areas[areaIndex]);
242 if (typeof areaElem == 'number') {
243 minY = mmin(minY, areaElem);
244 yValue.push(areaElem);
248 minX = mmin(minX, xValue);
249 maxX = mmax(maxX, xValue);
250 maxY = mmax(maxY, acumY);
251 yValues.push(yValue);
254 xScale = bbox.width / ((maxX - minX) || 1);
255 yScale = bbox.height / ((maxY - minY) || 1);
258 if ((ln > bbox.width) && me.areas) {
259 sumValues = me.shrink(xValues, yValues, bbox.width);
260 xValues = sumValues.x;
261 yValues = sumValues.y;
276 // @private Build an array of paths for the chart
277 getPaths: function() {
280 store = chart.getChartStore(),
282 bounds = me.getBounds(),
284 items = me.items = [],
288 i, ln, x, y, xValue, yValue, acumY, areaIndex, prevAreaIndex, areaElem, path;
290 ln = bounds.xValues.length;
292 for (i = 0; i < ln; i++) {
293 xValue = bounds.xValues[i];
294 yValue = bounds.yValues[i];
295 x = bbox.x + (xValue - bounds.minX) * bounds.xScale;
297 for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
299 if (me.__excludes[areaIndex]) {
302 if (!componentPaths[areaIndex]) {
303 componentPaths[areaIndex] = [];
305 areaElem = yValue[areaIndex];
307 y = bbox.y + bbox.height - (acumY - bounds.minY) * bounds.yScale;
308 if (!paths[areaIndex]) {
309 paths[areaIndex] = ['M', x, y];
310 componentPaths[areaIndex].push(['L', x, y]);
312 paths[areaIndex].push('L', x, y);
313 componentPaths[areaIndex].push(['L', x, y]);
315 if (!items[areaIndex]) {
322 items[areaIndex].pointsUp.push([x, y]);
327 for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
329 if (me.__excludes[areaIndex]) {
332 path = paths[areaIndex];
333 // Close bottom path to the axis
334 if (areaIndex == 0 || first) {
336 path.push('L', x, bbox.y + bbox.height,
337 'L', bbox.x, bbox.y + bbox.height,
340 // Close other paths to the one before them
342 componentPath = componentPaths[prevAreaIndex];
343 componentPath.reverse();
344 path.push('L', x, componentPath[0][2]);
345 for (i = 0; i < ln; i++) {
346 path.push(componentPath[i][0],
348 componentPath[i][2]);
349 items[areaIndex].pointsDown[ln -i -1] = [componentPath[i][1], componentPath[i][2]];
351 path.push('L', bbox.x, path[2], 'Z');
353 prevAreaIndex = areaIndex;
357 areasLen: bounds.areasLen
361 <span id='Ext-chart-series-Area-method-drawSeries'> /**
362 </span> * Draws the series for the current chart.
364 drawSeries: function() {
367 store = chart.getChartStore(),
368 surface = chart.surface,
369 animate = chart.animate,
371 endLineStyle = Ext.apply(me.seriesStyle, me.style),
372 colorArrayStyle = me.colorArrayStyle,
373 colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
374 areaIndex, areaElem, paths, path, rendererAttributes;
376 me.unHighlightItem();
377 me.cleanHighlights();
379 if (!store || !store.getCount()) {
383 paths = me.getPaths();
389 for (areaIndex = 0; areaIndex < paths.areasLen; areaIndex++) {
391 if (me.__excludes[areaIndex]) {
394 if (!me.areas[areaIndex]) {
395 me.items[areaIndex].sprite = me.areas[areaIndex] = surface.add(Ext.apply({}, {
398 // 'clip-rect': me.clipBox,
399 path: paths.paths[areaIndex],
400 stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength],
401 fill: colorArrayStyle[areaIndex % colorArrayLength]
402 }, endLineStyle || {}));
404 areaElem = me.areas[areaIndex];
405 path = paths.paths[areaIndex];
407 //Add renderer to line. There is not a unique record associated with this.
408 rendererAttributes = me.renderer(areaElem, false, {
410 // 'clip-rect': me.clipBox,
411 fill: colorArrayStyle[areaIndex % colorArrayLength],
412 stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
413 }, areaIndex, store);
414 //fill should not be used here but when drawing the special fill path object
415 me.animation = me.onAnimate(areaElem, {
416 to: rendererAttributes
419 rendererAttributes = me.renderer(areaElem, false, {
421 // 'clip-rect': me.clipBox,
423 fill: colorArrayStyle[areaIndex % colorArrayLength],
424 stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
425 }, areaIndex, store);
426 me.areas[areaIndex].setAttributes(rendererAttributes, true);
434 onAnimate: function(sprite, attr) {
436 return this.callParent(arguments);
440 onCreateLabel: function(storeItem, item, i, display) {
442 group = me.labelsGroup,
445 endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
447 return me.chart.surface.add(Ext.apply({
449 'text-anchor': 'middle',
452 'y': bbox.y + bbox.height / 2
453 }, endLabelStyle || {}));
457 onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
460 resizing = chart.resizing,
462 format = config.renderer,
463 field = config.field,
469 label.setAttributes({
470 text: format(storeItem.get(field[index])),
474 bb = label.getBBox();
475 width = bb.width / 2;
476 height = bb.height / 2;
478 x = x - width < bbox.x? bbox.x + width : x;
479 x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
480 y = y - height < bbox.y? bbox.y + height : y;
481 y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
483 if (me.chart.animate && !me.chart.resizing) {
485 me.onAnimate(label, {
492 label.setAttributes({
497 me.animation.on('afteranimate', function() {
507 onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
510 surface = chart.surface,
511 resizing = chart.resizing,
512 config = me.callouts,
514 prev = (i == 0) ? false : items[i -1].point,
515 next = (i == items.length -1) ? false : items[i +1].point,
517 dir, norm, normal, a, aprev, anext,
518 bbox = callout.label.getBBox(),
522 boxx, boxy, boxw, boxh,
523 p, clipRect = me.clipRect,
526 //get the right two points
533 a = (next[1] - prev[1]) / (next[0] - prev[0]);
534 aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
535 anext = (next[1] - cur[1]) / (next[0] - cur[0]);
537 norm = Math.sqrt(1 + a * a);
538 dir = [1 / norm, a / norm];
539 normal = [-dir[1], dir[0]];
541 //keep the label always on the outer part of the "elbow"
542 if (aprev > 0 && anext < 0 && normal[1] < 0 || aprev < 0 && anext > 0 && normal[1] > 0) {
545 } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0 || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
551 x = cur[0] + normal[0] * offsetFromViz;
552 y = cur[1] + normal[1] * offsetFromViz;
554 //box position and dimensions
555 boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
556 boxy = y - bbox.height /2 - offsetBox;
557 boxw = bbox.width + 2 * offsetBox;
558 boxh = bbox.height + 2 * offsetBox;
560 //now check if we're out of bounds and invert the normal vector correspondingly
561 //this may add new overlaps between labels (but labels won't be out of bounds).
562 if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
565 if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
570 x = cur[0] + normal[0] * offsetFromViz;
571 y = cur[1] + normal[1] * offsetFromViz;
573 //update box position and dimensions
574 boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
575 boxy = y - bbox.height /2 - offsetBox;
576 boxw = bbox.width + 2 * offsetBox;
577 boxh = bbox.height + 2 * offsetBox;
579 //set the line from the middle of the pie to the box.
580 callout.lines.setAttributes({
581 path: ["M", cur[0], cur[1], "L", x, y, "Z"]
584 callout.box.setAttributes({
591 callout.label.setAttributes({
592 x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
596 callout[p].show(true);
600 isItemInPoint: function(x, y, item, i) {
602 pointsUp = item.pointsUp,
603 pointsDown = item.pointsDown,
605 dist = Infinity, p, pln, point;
607 for (p = 0, pln = pointsUp.length; p < pln; p++) {
608 point = [pointsUp[p][0], pointsUp[p][1]];
609 if (dist > abs(x - point[0])) {
610 dist = abs(x - point[0]);
612 point = pointsUp[p -1];
613 if (y >= point[1] && (!pointsDown.length || y <= (pointsDown[p -1][1]))) {
614 item.storeIndex = p -1;
615 item.storeField = me.yField[i];
616 item.storeItem = me.chart.store.getAt(p -1);
617 item._points = pointsDown.length? [point, pointsDown[p -1]] : [point];
627 <span id='Ext-chart-series-Area-method-highlightSeries'> /**
628 </span> * Highlight this entire series.
629 * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
631 highlightSeries: function() {
632 var area, to, fillColor;
633 if (this._index !== undefined) {
634 area = this.areas[this._index];
635 if (area.__highlightAnim) {
636 area.__highlightAnim.paused = true;
638 area.__highlighted = true;
639 area.__prevOpacity = area.__prevOpacity || area.attr.opacity || 1;
640 area.__prevFill = area.__prevFill || area.attr.fill;
641 area.__prevLineWidth = area.__prevLineWidth || area.attr.lineWidth;
642 fillColor = Ext.draw.Color.fromString(area.__prevFill);
644 lineWidth: (area.__prevLineWidth || 0) + 2
647 to.fill = fillColor.getLighter(0.2).toString();
650 to.opacity = Math.max(area.__prevOpacity - 0.3, 0);
652 if (this.chart.animate) {
653 area.__highlightAnim = Ext.create('Ext.fx.Anim', Ext.apply({
656 }, this.chart.animate));
659 area.setAttributes(to, true);
664 <span id='Ext-chart-series-Area-method-unHighlightSeries'> /**
665 </span> * UnHighlight this entire series.
666 * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
668 unHighlightSeries: function() {
670 if (this._index !== undefined) {
671 area = this.areas[this._index];
672 if (area.__highlightAnim) {
673 area.__highlightAnim.paused = true;
675 if (area.__highlighted) {
676 area.__highlighted = false;
677 area.__highlightAnim = Ext.create('Ext.fx.Anim', {
680 fill: area.__prevFill,
681 opacity: area.__prevOpacity,
682 lineWidth: area.__prevLineWidth
689 <span id='Ext-chart-series-Area-method-highlightItem'> /**
690 </span> * Highlight the specified item. If no item is provided the whole series will be highlighted.
691 * @param item {Object} Info about the item; same format as returned by #getItemForPoint
693 highlightItem: function(item) {
697 this.highlightSeries();
700 points = item._points;
701 path = points.length == 2? ['M', points[0][0], points[0][1], 'L', points[1][0], points[1][1]]
702 : ['M', points[0][0], points[0][1], 'L', points[0][0], me.bbox.y + me.bbox.height];
703 me.highlightSprite.setAttributes({
709 <span id='Ext-chart-series-Area-method-unHighlightItem'> /**
710 </span> * Un-highlights the specified item. If no item is provided it will un-highlight the entire series.
711 * @param {Object} item Info about the item; same format as returned by #getItemForPoint
713 unHighlightItem: function(item) {
715 this.unHighlightSeries();
718 if (this.highlightSprite) {
719 this.highlightSprite.hide(true);
724 hideAll: function() {
725 if (!isNaN(this._index)) {
726 this.__excludes[this._index] = true;
727 this.areas[this._index].hide(true);
733 showAll: function() {
734 if (!isNaN(this._index)) {
735 this.__excludes[this._index] = false;
736 this.areas[this._index].show(true);
741 <span id='Ext-chart-series-Area-method-getLegendColor'> /**
742 </span> * Returns the color of the series (to be displayed as color for the series legend item).
743 * @param item {Object} Info about the item; same format as returned by #getItemForPoint
745 getLegendColor: function(index) {
747 return me.colorArrayStyle[index % me.colorArrayStyle.length];
750 <span id='Ext-chart-series-Area'>/**
751 </span> * @class Ext.chart.series.Area
752 * @extends Ext.chart.series.Cartesian
754 * Creates a Stacked Area Chart. The stacked area chart is useful when displaying multiple aggregated layers of information.
755 * As with all other series, the Area Series must be appended in the *series* Chart array configuration. See the Chart
756 * documentation for more information. A typical configuration object for the area series could be:
759 * var store = Ext.create('Ext.data.JsonStore', {
760 * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
762 * { 'name': 'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13 },
763 * { 'name': 'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3 },
764 * { 'name': 'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7 },
765 * { 'name': 'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23 },
766 * { 'name': 'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
770 * Ext.create('Ext.chart.Chart', {
771 * renderTo: Ext.getBody(),
780 * fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
781 * title: 'Sample Values',
791 * adjustMinimumByMajorUnit: 0
795 * position: 'bottom',
797 * title: 'Sample Metrics',
811 * yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
818 * In this configuration we set `area` as the type for the series, set highlighting options to true for highlighting elements on hover,
819 * take the left axis to measure the data in the area series, set as xField (x values) the name field of each element in the store,
820 * and as yFields (aggregated layers) seven data fields from the same store. Then we override some theming styles by adding some opacity
821 * to the style object.
825 Ext.define('Ext.chart.series.Area', {
827 /* Begin Definitions */
829 extend: 'Ext.chart.series.Cartesian',
831 alias: 'series.area',
833 requires: ['Ext.chart.axis.Axis', 'Ext.draw.Color', 'Ext.fx.Anim'],
835 /* End Definitions */
839 // @private Area charts are alyways stacked
842 <span id='Ext-chart-series-Area-cfg-style'> /**
843 </span> * @cfg {Object} style
844 * Append styling properties to this object for it to override theme properties.
848 constructor: function(config) {
849 this.callParent(arguments);
851 surface = me.chart.surface,
853 Ext.apply(me, config, {
863 me.highlightSprite = surface.add({
873 me.group = surface.getGroup(me.seriesId);
876 // @private Shrinks dataSets down to a smaller size
877 shrink: function(xValues, yValues, size) {
878 var len = xValues.length,
879 ratio = Math.floor(len / size),
882 yCompLen = this.areas.length,
887 for (j = 0; j < yCompLen; ++j) {
890 for (i = 0; i < len; ++i) {
892 for (j = 0; j < yCompLen; ++j) {
893 ySum[j] += yValues[i][j];
895 if (i % ratio == 0) {
897 xRes.push(xSum/ratio);
898 for (j = 0; j < yCompLen; ++j) {
902 //reset sum accumulators
904 for (j = 0, ySum = []; j < yCompLen; ++j) {
915 // @private Get chart and data boundaries
916 getBounds: function() {
919 store = chart.getChartStore(),
920 areas = [].concat(me.yField),
921 areasLen = areas.length,
932 bbox, xScale, yScale, xValue, yValue, areaIndex, acumY, ln, sumValues, clipBox, areaElem;
937 // Run through the axis
939 axis = chart.axes.get(me.axis);
941 out = axis.calcEnds();
942 minY = out.from || axis.prevMin;
943 maxY = mmax(out.to || axis.prevMax, 0);
947 if (me.yField && !Ext.isNumber(minY)) {
948 axis = Ext.create('Ext.chart.axis.Axis', {
950 fields: [].concat(me.yField)
952 out = axis.calcEnds();
953 minY = out.from || axis.prevMin;
954 maxY = mmax(out.to || axis.prevMax, 0);
957 if (!Ext.isNumber(minY)) {
960 if (!Ext.isNumber(maxY)) {
964 store.each(function(record, i) {
965 xValue = record.get(me.xField);
967 if (typeof xValue != 'number') {
970 xValues.push(xValue);
972 for (areaIndex = 0; areaIndex < areasLen; areaIndex++) {
973 areaElem = record.get(areas[areaIndex]);
974 if (typeof areaElem == 'number') {
975 minY = mmin(minY, areaElem);
976 yValue.push(areaElem);
980 minX = mmin(minX, xValue);
981 maxX = mmax(maxX, xValue);
982 maxY = mmax(maxY, acumY);
983 yValues.push(yValue);
986 xScale = bbox.width / ((maxX - minX) || 1);
987 yScale = bbox.height / ((maxY - minY) || 1);
990 if ((ln > bbox.width) && me.areas) {
991 sumValues = me.shrink(xValues, yValues, bbox.width);
992 xValues = sumValues.x;
993 yValues = sumValues.y;
1008 // @private Build an array of paths for the chart
1009 getPaths: function() {
1012 store = chart.getChartStore(),
1014 bounds = me.getBounds(),
1016 items = me.items = [],
1017 componentPaths = [],
1020 i, ln, x, y, xValue, yValue, acumY, areaIndex, prevAreaIndex, areaElem, path;
1022 ln = bounds.xValues.length;
1024 for (i = 0; i < ln; i++) {
1025 xValue = bounds.xValues[i];
1026 yValue = bounds.yValues[i];
1027 x = bbox.x + (xValue - bounds.minX) * bounds.xScale;
1029 for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
1031 if (me.__excludes[areaIndex]) {
1034 if (!componentPaths[areaIndex]) {
1035 componentPaths[areaIndex] = [];
1037 areaElem = yValue[areaIndex];
1039 y = bbox.y + bbox.height - (acumY - bounds.minY) * bounds.yScale;
1040 if (!paths[areaIndex]) {
1041 paths[areaIndex] = ['M', x, y];
1042 componentPaths[areaIndex].push(['L', x, y]);
1044 paths[areaIndex].push('L', x, y);
1045 componentPaths[areaIndex].push(['L', x, y]);
1047 if (!items[areaIndex]) {
1048 items[areaIndex] = {
1054 items[areaIndex].pointsUp.push([x, y]);
1059 for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
1061 if (me.__excludes[areaIndex]) {
1064 path = paths[areaIndex];
1065 // Close bottom path to the axis
1066 if (areaIndex == 0 || first) {
1068 path.push('L', x, bbox.y + bbox.height,
1069 'L', bbox.x, bbox.y + bbox.height,
1072 // Close other paths to the one before them
1074 componentPath = componentPaths[prevAreaIndex];
1075 componentPath.reverse();
1076 path.push('L', x, componentPath[0][2]);
1077 for (i = 0; i < ln; i++) {
1078 path.push(componentPath[i][0],
1079 componentPath[i][1],
1080 componentPath[i][2]);
1081 items[areaIndex].pointsDown[ln -i -1] = [componentPath[i][1], componentPath[i][2]];
1083 path.push('L', bbox.x, path[2], 'Z');
1085 prevAreaIndex = areaIndex;
1089 areasLen: bounds.areasLen
1093 <span id='Ext-chart-series-Area-method-drawSeries'> /**
1094 </span> * Draws the series for the current chart.
1096 drawSeries: function() {
1099 store = chart.getChartStore(),
1100 surface = chart.surface,
1101 animate = chart.animate,
1103 endLineStyle = Ext.apply(me.seriesStyle, me.style),
1104 colorArrayStyle = me.colorArrayStyle,
1105 colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
1106 areaIndex, areaElem, paths, path, rendererAttributes;
1108 me.unHighlightItem();
1109 me.cleanHighlights();
1111 if (!store || !store.getCount()) {
1115 paths = me.getPaths();
1121 for (areaIndex = 0; areaIndex < paths.areasLen; areaIndex++) {
1123 if (me.__excludes[areaIndex]) {
1126 if (!me.areas[areaIndex]) {
1127 me.items[areaIndex].sprite = me.areas[areaIndex] = surface.add(Ext.apply({}, {
1130 // 'clip-rect': me.clipBox,
1131 path: paths.paths[areaIndex],
1132 stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength],
1133 fill: colorArrayStyle[areaIndex % colorArrayLength]
1134 }, endLineStyle || {}));
1136 areaElem = me.areas[areaIndex];
1137 path = paths.paths[areaIndex];
1139 //Add renderer to line. There is not a unique record associated with this.
1140 rendererAttributes = me.renderer(areaElem, false, {
1142 // 'clip-rect': me.clipBox,
1143 fill: colorArrayStyle[areaIndex % colorArrayLength],
1144 stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
1145 }, areaIndex, store);
1146 //fill should not be used here but when drawing the special fill path object
1147 me.animation = me.onAnimate(areaElem, {
1148 to: rendererAttributes
1151 rendererAttributes = me.renderer(areaElem, false, {
1153 // 'clip-rect': me.clipBox,
1155 fill: colorArrayStyle[areaIndex % colorArrayLength],
1156 stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
1157 }, areaIndex, store);
1158 me.areas[areaIndex].setAttributes(rendererAttributes, true);
1162 me.renderCallouts();
1166 onAnimate: function(sprite, attr) {
1168 return this.callParent(arguments);
1172 onCreateLabel: function(storeItem, item, i, display) {
1174 group = me.labelsGroup,
1177 endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
1179 return me.chart.surface.add(Ext.apply({
1181 'text-anchor': 'middle',
1184 'y': bbox.y + bbox.height / 2
1185 }, endLabelStyle || {}));
1189 onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
1192 resizing = chart.resizing,
1194 format = config.renderer,
1195 field = config.field,
1201 label.setAttributes({
1202 text: format(storeItem.get(field[index])),
1206 bb = label.getBBox();
1207 width = bb.width / 2;
1208 height = bb.height / 2;
1210 x = x - width < bbox.x? bbox.x + width : x;
1211 x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
1212 y = y - height < bbox.y? bbox.y + height : y;
1213 y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
1215 if (me.chart.animate && !me.chart.resizing) {
1217 me.onAnimate(label, {
1224 label.setAttributes({
1229 me.animation.on('afteranimate', function() {
1239 onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
1242 surface = chart.surface,
1243 resizing = chart.resizing,
1244 config = me.callouts,
1246 prev = (i == 0) ? false : items[i -1].point,
1247 next = (i == items.length -1) ? false : items[i +1].point,
1249 dir, norm, normal, a, aprev, anext,
1250 bbox = callout.label.getBBox(),
1254 boxx, boxy, boxw, boxh,
1255 p, clipRect = me.clipRect,
1258 //get the right two points
1265 a = (next[1] - prev[1]) / (next[0] - prev[0]);
1266 aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
1267 anext = (next[1] - cur[1]) / (next[0] - cur[0]);
1269 norm = Math.sqrt(1 + a * a);
1270 dir = [1 / norm, a / norm];
1271 normal = [-dir[1], dir[0]];
1273 //keep the label always on the outer part of the "elbow"
1274 if (aprev > 0 && anext < 0 && normal[1] < 0 || aprev < 0 && anext > 0 && normal[1] > 0) {
1277 } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0 || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
1283 x = cur[0] + normal[0] * offsetFromViz;
1284 y = cur[1] + normal[1] * offsetFromViz;
1286 //box position and dimensions
1287 boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
1288 boxy = y - bbox.height /2 - offsetBox;
1289 boxw = bbox.width + 2 * offsetBox;
1290 boxh = bbox.height + 2 * offsetBox;
1292 //now check if we're out of bounds and invert the normal vector correspondingly
1293 //this may add new overlaps between labels (but labels won't be out of bounds).
1294 if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
1297 if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
1302 x = cur[0] + normal[0] * offsetFromViz;
1303 y = cur[1] + normal[1] * offsetFromViz;
1305 //update box position and dimensions
1306 boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
1307 boxy = y - bbox.height /2 - offsetBox;
1308 boxw = bbox.width + 2 * offsetBox;
1309 boxh = bbox.height + 2 * offsetBox;
1311 //set the line from the middle of the pie to the box.
1312 callout.lines.setAttributes({
1313 path: ["M", cur[0], cur[1], "L", x, y, "Z"]
1316 callout.box.setAttributes({
1323 callout.label.setAttributes({
1324 x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
1327 for (p in callout) {
1328 callout[p].show(true);
1332 isItemInPoint: function(x, y, item, i) {
1334 pointsUp = item.pointsUp,
1335 pointsDown = item.pointsDown,
1337 dist = Infinity, p, pln, point;
1339 for (p = 0, pln = pointsUp.length; p < pln; p++) {
1340 point = [pointsUp[p][0], pointsUp[p][1]];
1341 if (dist > abs(x - point[0])) {
1342 dist = abs(x - point[0]);
1344 point = pointsUp[p -1];
1345 if (y >= point[1] && (!pointsDown.length || y <= (pointsDown[p -1][1]))) {
1346 item.storeIndex = p -1;
1347 item.storeField = me.yField[i];
1348 item.storeItem = me.chart.store.getAt(p -1);
1349 item._points = pointsDown.length? [point, pointsDown[p -1]] : [point];
1359 <span id='Ext-chart-series-Area-method-highlightSeries'> /**
1360 </span> * Highlight this entire series.
1361 * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
1363 highlightSeries: function() {
1364 var area, to, fillColor;
1365 if (this._index !== undefined) {
1366 area = this.areas[this._index];
1367 if (area.__highlightAnim) {
1368 area.__highlightAnim.paused = true;
1370 area.__highlighted = true;
1371 area.__prevOpacity = area.__prevOpacity || area.attr.opacity || 1;
1372 area.__prevFill = area.__prevFill || area.attr.fill;
1373 area.__prevLineWidth = area.__prevLineWidth || area.attr.lineWidth;
1374 fillColor = Ext.draw.Color.fromString(area.__prevFill);
1376 lineWidth: (area.__prevLineWidth || 0) + 2
1379 to.fill = fillColor.getLighter(0.2).toString();
1382 to.opacity = Math.max(area.__prevOpacity - 0.3, 0);
1384 if (this.chart.animate) {
1385 area.__highlightAnim = Ext.create('Ext.fx.Anim', Ext.apply({
1388 }, this.chart.animate));
1391 area.setAttributes(to, true);
1396 <span id='Ext-chart-series-Area-method-unHighlightSeries'> /**
1397 </span> * UnHighlight this entire series.
1398 * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
1400 unHighlightSeries: function() {
1402 if (this._index !== undefined) {
1403 area = this.areas[this._index];
1404 if (area.__highlightAnim) {
1405 area.__highlightAnim.paused = true;
1407 if (area.__highlighted) {
1408 area.__highlighted = false;
1409 area.__highlightAnim = Ext.create('Ext.fx.Anim', {
1412 fill: area.__prevFill,
1413 opacity: area.__prevOpacity,
1414 lineWidth: area.__prevLineWidth
1421 <span id='Ext-chart-series-Area-method-highlightItem'> /**
1422 </span> * Highlight the specified item. If no item is provided the whole series will be highlighted.
1423 * @param item {Object} Info about the item; same format as returned by #getItemForPoint
1425 highlightItem: function(item) {
1429 this.highlightSeries();
1432 points = item._points;
1433 path = points.length == 2? ['M', points[0][0], points[0][1], 'L', points[1][0], points[1][1]]
1434 : ['M', points[0][0], points[0][1], 'L', points[0][0], me.bbox.y + me.bbox.height];
1435 me.highlightSprite.setAttributes({
1441 <span id='Ext-chart-series-Area-method-unHighlightItem'> /**
1442 </span> * un-highlights the specified item. If no item is provided it will un-highlight the entire series.
1443 * @param item {Object} Info about the item; same format as returned by #getItemForPoint
1445 unHighlightItem: function(item) {
1447 this.unHighlightSeries();
1450 if (this.highlightSprite) {
1451 this.highlightSprite.hide(true);
1456 hideAll: function() {
1457 if (!isNaN(this._index)) {
1458 this.__excludes[this._index] = true;
1459 this.areas[this._index].hide(true);
1465 showAll: function() {
1466 if (!isNaN(this._index)) {
1467 this.__excludes[this._index] = false;
1468 this.areas[this._index].show(true);
1473 <span id='Ext-chart-series-Area-method-getLegendColor'> /**
1474 </span> * Returns the color of the series (to be displayed as color for the series legend item).
1475 * @param item {Object} Info about the item; same format as returned by #getItemForPoint
1477 getLegendColor: function(index) {
1479 return me.colorArrayStyle[index % me.colorArrayStyle.length];