1 <!DOCTYPE html><html><head><title>Sencha Documentation Project</title><link rel="stylesheet" href="../reset.css" type="text/css"><link rel="stylesheet" href="../prettify.css" type="text/css"><link rel="stylesheet" href="../prettify_sa.css" type="text/css"><script type="text/javascript" src="../prettify.js"></script></head><body onload="prettyPrint()"><pre class="prettyprint"><pre><span id='Ext-chart.series.Area'>/**
2 </span> * @class Ext.chart.series.Area
3 * @extends Ext.chart.series.Cartesian
6 Creates a Stacked Area Chart. The stacked area chart is useful when displaying multiple aggregated layers of information.
7 As with all other series, the Area Series must be appended in the *series* Chart array configuration. See the Chart
8 documentation for more information. A typical configuration object for the area series could be:
10 {@img Ext.chart.series.Area/Ext.chart.series.Area.png Ext.chart.series.Area chart series}
11 <pre><code>
12 var store = Ext.create('Ext.data.JsonStore', {
13 fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
15 {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
16 {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
17 {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
18 {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
19 {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
23 Ext.create('Ext.chart.Chart', {
24 renderTo: Ext.getBody(),
32 fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
33 title: 'Sample Values',
43 adjustMinimumByMajorUnit: 0
48 title: 'Sample Metrics',
61 yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
67 </code></pre>
71 In this configuration we set `area` as the type for the series, set highlighting options to true for highlighting elements on hover,
72 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,
73 and as yFields (aggregated layers) seven data fields from the same store. Then we override some theming styles by adding some opacity
80 Ext.define('Ext.chart.series.Area', {
82 /* Begin Definitions */
84 extend: 'Ext.chart.series.Cartesian',
88 requires: ['Ext.chart.axis.Axis', 'Ext.draw.Color', 'Ext.fx.Anim'],
94 // @private Area charts are alyways stacked
97 <span id='Ext-chart.series.Area-cfg-style'> /**
98 </span> * @cfg {Object} style
99 * Append styling properties to this object for it to override theme properties.
103 constructor: function(config) {
104 this.callParent(arguments);
106 surface = me.chart.surface,
108 Ext.apply(me, config, {
118 me.highlightSprite = surface.add({
128 me.group = surface.getGroup(me.seriesId);
131 // @private Shrinks dataSets down to a smaller size
132 shrink: function(xValues, yValues, size) {
133 var len = xValues.length,
134 ratio = Math.floor(len / size),
137 yCompLen = this.areas.length,
142 for (j = 0; j < yCompLen; ++j) {
145 for (i = 0; i < len; ++i) {
147 for (j = 0; j < yCompLen; ++j) {
148 ySum[j] += yValues[i][j];
150 if (i % ratio == 0) {
152 xRes.push(xSum/ratio);
153 for (j = 0; j < yCompLen; ++j) {
157 //reset sum accumulators
159 for (j = 0, ySum = []; j < yCompLen; ++j) {
170 // @private Get chart and data boundaries
171 getBounds: function() {
174 store = chart.substore || chart.store,
175 areas = [].concat(me.yField),
176 areasLen = areas.length,
187 bbox, xScale, yScale, xValue, yValue, areaIndex, acumY, ln, sumValues, clipBox, areaElem;
192 // Run through the axis
194 axis = chart.axes.get(me.axis);
196 out = axis.calcEnds();
197 minY = out.from || axis.prevMin;
198 maxY = mmax(out.to || axis.prevMax, 0);
202 if (me.yField && !Ext.isNumber(minY)) {
203 axis = Ext.create('Ext.chart.axis.Axis', {
205 fields: [].concat(me.yField)
207 out = axis.calcEnds();
208 minY = out.from || axis.prevMin;
209 maxY = mmax(out.to || axis.prevMax, 0);
212 if (!Ext.isNumber(minY)) {
215 if (!Ext.isNumber(maxY)) {
219 store.each(function(record, i) {
220 xValue = record.get(me.xField);
222 if (typeof xValue != 'number') {
225 xValues.push(xValue);
227 for (areaIndex = 0; areaIndex < areasLen; areaIndex++) {
228 areaElem = record.get(areas[areaIndex]);
229 if (typeof areaElem == 'number') {
230 minY = mmin(minY, areaElem);
231 yValue.push(areaElem);
235 minX = mmin(minX, xValue);
236 maxX = mmax(maxX, xValue);
237 maxY = mmax(maxY, acumY);
238 yValues.push(yValue);
241 xScale = bbox.width / (maxX - minX);
242 yScale = bbox.height / (maxY - minY);
245 if ((ln > bbox.width) && me.areas) {
246 sumValues = me.shrink(xValues, yValues, bbox.width);
247 xValues = sumValues.x;
248 yValues = sumValues.y;
263 // @private Build an array of paths for the chart
264 getPaths: function() {
267 store = chart.substore || chart.store,
269 bounds = me.getBounds(),
271 items = me.items = [],
275 i, ln, x, y, xValue, yValue, acumY, areaIndex, prevAreaIndex, areaElem, path;
277 ln = bounds.xValues.length;
279 for (i = 0; i < ln; i++) {
280 xValue = bounds.xValues[i];
281 yValue = bounds.yValues[i];
282 x = bbox.x + (xValue - bounds.minX) * bounds.xScale;
284 for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
286 if (me.__excludes[areaIndex]) {
289 if (!componentPaths[areaIndex]) {
290 componentPaths[areaIndex] = [];
292 areaElem = yValue[areaIndex];
294 y = bbox.y + bbox.height - (acumY - bounds.minY) * bounds.yScale;
295 if (!paths[areaIndex]) {
296 paths[areaIndex] = ['M', x, y];
297 componentPaths[areaIndex].push(['L', x, y]);
299 paths[areaIndex].push('L', x, y);
300 componentPaths[areaIndex].push(['L', x, y]);
302 if (!items[areaIndex]) {
309 items[areaIndex].pointsUp.push([x, y]);
314 for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
316 if (me.__excludes[areaIndex]) {
319 path = paths[areaIndex];
320 // Close bottom path to the axis
321 if (areaIndex == 0 || first) {
323 path.push('L', x, bbox.y + bbox.height,
324 'L', bbox.x, bbox.y + bbox.height,
327 // Close other paths to the one before them
329 componentPath = componentPaths[prevAreaIndex];
330 componentPath.reverse();
331 path.push('L', x, componentPath[0][2]);
332 for (i = 0; i < ln; i++) {
333 path.push(componentPath[i][0],
335 componentPath[i][2]);
336 items[areaIndex].pointsDown[ln -i -1] = [componentPath[i][1], componentPath[i][2]];
338 path.push('L', bbox.x, path[2], 'Z');
340 prevAreaIndex = areaIndex;
344 areasLen: bounds.areasLen
348 <span id='Ext-chart.series.Area-method-drawSeries'> /**
349 </span> * Draws the series for the current chart.
351 drawSeries: function() {
354 store = chart.substore || chart.store,
355 surface = chart.surface,
356 animate = chart.animate,
358 endLineStyle = Ext.apply(me.seriesStyle, me.style),
359 colorArrayStyle = me.colorArrayStyle,
360 colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
361 areaIndex, areaElem, paths, path, rendererAttributes;
363 me.unHighlightItem();
364 me.cleanHighlights();
366 if (!store || !store.getCount()) {
370 paths = me.getPaths();
376 for (areaIndex = 0; areaIndex < paths.areasLen; areaIndex++) {
378 if (me.__excludes[areaIndex]) {
381 if (!me.areas[areaIndex]) {
382 me.items[areaIndex].sprite = me.areas[areaIndex] = surface.add(Ext.apply({}, {
385 // 'clip-rect': me.clipBox,
386 path: paths.paths[areaIndex],
387 stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength],
388 fill: colorArrayStyle[areaIndex % colorArrayLength]
389 }, endLineStyle || {}));
391 areaElem = me.areas[areaIndex];
392 path = paths.paths[areaIndex];
394 //Add renderer to line. There is not a unique record associated with this.
395 rendererAttributes = me.renderer(areaElem, false, {
397 // 'clip-rect': me.clipBox,
398 fill: colorArrayStyle[areaIndex % colorArrayLength],
399 stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
400 }, areaIndex, store);
401 //fill should not be used here but when drawing the special fill path object
402 me.animation = me.onAnimate(areaElem, {
403 to: rendererAttributes
406 rendererAttributes = me.renderer(areaElem, false, {
408 // 'clip-rect': me.clipBox,
410 fill: colorArrayStyle[areaIndex % colorArrayLength],
411 stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
412 }, areaIndex, store);
413 me.areas[areaIndex].setAttributes(rendererAttributes, true);
421 onAnimate: function(sprite, attr) {
423 return this.callParent(arguments);
427 onCreateLabel: function(storeItem, item, i, display) {
429 group = me.labelsGroup,
432 endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
434 return me.chart.surface.add(Ext.apply({
436 'text-anchor': 'middle',
439 'y': bbox.y + bbox.height / 2
440 }, endLabelStyle || {}));
444 onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
447 resizing = chart.resizing,
449 format = config.renderer,
450 field = config.field,
456 label.setAttributes({
457 text: format(storeItem.get(field[index])),
461 bb = label.getBBox();
462 width = bb.width / 2;
463 height = bb.height / 2;
465 x = x - width < bbox.x? bbox.x + width : x;
466 x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
467 y = y - height < bbox.y? bbox.y + height : y;
468 y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
470 if (me.chart.animate && !me.chart.resizing) {
472 me.onAnimate(label, {
479 label.setAttributes({
484 me.animation.on('afteranimate', function() {
494 onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
497 surface = chart.surface,
498 resizing = chart.resizing,
499 config = me.callouts,
501 prev = (i == 0) ? false : items[i -1].point,
502 next = (i == items.length -1) ? false : items[i +1].point,
504 dir, norm, normal, a, aprev, anext,
505 bbox = callout.label.getBBox(),
509 boxx, boxy, boxw, boxh,
510 p, clipRect = me.clipRect,
513 //get the right two points
520 a = (next[1] - prev[1]) / (next[0] - prev[0]);
521 aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
522 anext = (next[1] - cur[1]) / (next[0] - cur[0]);
524 norm = Math.sqrt(1 + a * a);
525 dir = [1 / norm, a / norm];
526 normal = [-dir[1], dir[0]];
528 //keep the label always on the outer part of the "elbow"
529 if (aprev > 0 && anext < 0 && normal[1] < 0 || aprev < 0 && anext > 0 && normal[1] > 0) {
532 } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0 || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
538 x = cur[0] + normal[0] * offsetFromViz;
539 y = cur[1] + normal[1] * offsetFromViz;
541 //box position and dimensions
542 boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
543 boxy = y - bbox.height /2 - offsetBox;
544 boxw = bbox.width + 2 * offsetBox;
545 boxh = bbox.height + 2 * offsetBox;
547 //now check if we're out of bounds and invert the normal vector correspondingly
548 //this may add new overlaps between labels (but labels won't be out of bounds).
549 if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
552 if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
557 x = cur[0] + normal[0] * offsetFromViz;
558 y = cur[1] + normal[1] * offsetFromViz;
560 //update box position and dimensions
561 boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
562 boxy = y - bbox.height /2 - offsetBox;
563 boxw = bbox.width + 2 * offsetBox;
564 boxh = bbox.height + 2 * offsetBox;
566 //set the line from the middle of the pie to the box.
567 callout.lines.setAttributes({
568 path: ["M", cur[0], cur[1], "L", x, y, "Z"]
571 callout.box.setAttributes({
578 callout.label.setAttributes({
579 x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
583 callout[p].show(true);
587 isItemInPoint: function(x, y, item, i) {
589 pointsUp = item.pointsUp,
590 pointsDown = item.pointsDown,
592 dist = Infinity, p, pln, point;
594 for (p = 0, pln = pointsUp.length; p < pln; p++) {
595 point = [pointsUp[p][0], pointsUp[p][1]];
596 if (dist > abs(x - point[0])) {
597 dist = abs(x - point[0]);
599 point = pointsUp[p -1];
600 if (y >= point[1] && (!pointsDown.length || y <= (pointsDown[p -1][1]))) {
601 item.storeIndex = p -1;
602 item.storeField = me.yField[i];
603 item.storeItem = me.chart.store.getAt(p -1);
604 item._points = pointsDown.length? [point, pointsDown[p -1]] : [point];
614 <span id='Ext-chart.series.Area-method-highlightSeries'> /**
615 </span> * Highlight this entire series.
616 * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
618 highlightSeries: function() {
619 var area, to, fillColor;
620 if (this._index !== undefined) {
621 area = this.areas[this._index];
622 if (area.__highlightAnim) {
623 area.__highlightAnim.paused = true;
625 area.__highlighted = true;
626 area.__prevOpacity = area.__prevOpacity || area.attr.opacity || 1;
627 area.__prevFill = area.__prevFill || area.attr.fill;
628 area.__prevLineWidth = area.__prevLineWidth || area.attr.lineWidth;
629 fillColor = Ext.draw.Color.fromString(area.__prevFill);
631 lineWidth: (area.__prevLineWidth || 0) + 2
634 to.fill = fillColor.getLighter(0.2).toString();
637 to.opacity = Math.max(area.__prevOpacity - 0.3, 0);
639 if (this.chart.animate) {
640 area.__highlightAnim = Ext.create('Ext.fx.Anim', Ext.apply({
643 }, this.chart.animate));
646 area.setAttributes(to, true);
651 <span id='Ext-chart.series.Area-method-unHighlightSeries'> /**
652 </span> * UnHighlight this entire series.
653 * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
655 unHighlightSeries: function() {
657 if (this._index !== undefined) {
658 area = this.areas[this._index];
659 if (area.__highlightAnim) {
660 area.__highlightAnim.paused = true;
662 if (area.__highlighted) {
663 area.__highlighted = false;
664 area.__highlightAnim = Ext.create('Ext.fx.Anim', {
667 fill: area.__prevFill,
668 opacity: area.__prevOpacity,
669 lineWidth: area.__prevLineWidth
676 <span id='Ext-chart.series.Area-method-highlightItem'> /**
677 </span> * Highlight the specified item. If no item is provided the whole series will be highlighted.
678 * @param item {Object} Info about the item; same format as returned by #getItemForPoint
680 highlightItem: function(item) {
684 this.highlightSeries();
687 points = item._points;
688 path = points.length == 2? ['M', points[0][0], points[0][1], 'L', points[1][0], points[1][1]]
689 : ['M', points[0][0], points[0][1], 'L', points[0][0], me.bbox.y + me.bbox.height];
690 me.highlightSprite.setAttributes({
696 <span id='Ext-chart.series.Area-method-unHighlightItem'> /**
697 </span> * un-highlights the specified item. If no item is provided it will un-highlight the entire series.
698 * @param item {Object} Info about the item; same format as returned by #getItemForPoint
700 unHighlightItem: function(item) {
702 this.unHighlightSeries();
705 if (this.highlightSprite) {
706 this.highlightSprite.hide(true);
711 hideAll: function() {
712 if (!isNaN(this._index)) {
713 this.__excludes[this._index] = true;
714 this.areas[this._index].hide(true);
720 showAll: function() {
721 if (!isNaN(this._index)) {
722 this.__excludes[this._index] = false;
723 this.areas[this._index].show(true);
728 <span id='Ext-chart.series.Area-method-getLegendColor'> /**
729 </span> * Returns the color of the series (to be displayed as color for the series legend item).
730 * @param item {Object} Info about the item; same format as returned by #getItemForPoint
732 getLegendColor: function(index) {
734 return me.colorArrayStyle[index % me.colorArrayStyle.length];
737 </pre></pre></body></html>