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-series-Area'>/**
19 </span> * @class Ext.chart.series.Area
20 * @extends Ext.chart.series.Cartesian
23 Creates a Stacked Area Chart. The stacked area chart is useful when displaying multiple aggregated layers of information.
24 As with all other series, the Area Series must be appended in the *series* Chart array configuration. See the Chart
25 documentation for more information. A typical configuration object for the area series could be:
27 {@img Ext.chart.series.Area/Ext.chart.series.Area.png Ext.chart.series.Area chart series}
28 <pre><code>
29 var store = Ext.create('Ext.data.JsonStore', {
30 fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
32 {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
33 {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
34 {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
35 {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
36 {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
40 Ext.create('Ext.chart.Chart', {
41 renderTo: Ext.getBody(),
49 fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
50 title: 'Sample Values',
60 adjustMinimumByMajorUnit: 0
65 title: 'Sample Metrics',
78 yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
84 </code></pre>
88 In this configuration we set `area` as the type for the series, set highlighting options to true for highlighting elements on hover,
89 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,
90 and as yFields (aggregated layers) seven data fields from the same store. Then we override some theming styles by adding some opacity
97 Ext.define('Ext.chart.series.Area', {
99 /* Begin Definitions */
101 extend: 'Ext.chart.series.Cartesian',
103 alias: 'series.area',
105 requires: ['Ext.chart.axis.Axis', 'Ext.draw.Color', 'Ext.fx.Anim'],
107 /* End Definitions */
111 // @private Area charts are alyways stacked
114 <span id='Ext-chart-series-Area-cfg-style'> /**
115 </span> * @cfg {Object} style
116 * Append styling properties to this object for it to override theme properties.
120 constructor: function(config) {
121 this.callParent(arguments);
123 surface = me.chart.surface,
125 Ext.apply(me, config, {
135 me.highlightSprite = surface.add({
145 me.group = surface.getGroup(me.seriesId);
148 // @private Shrinks dataSets down to a smaller size
149 shrink: function(xValues, yValues, size) {
150 var len = xValues.length,
151 ratio = Math.floor(len / size),
154 yCompLen = this.areas.length,
159 for (j = 0; j < yCompLen; ++j) {
162 for (i = 0; i < len; ++i) {
164 for (j = 0; j < yCompLen; ++j) {
165 ySum[j] += yValues[i][j];
167 if (i % ratio == 0) {
169 xRes.push(xSum/ratio);
170 for (j = 0; j < yCompLen; ++j) {
174 //reset sum accumulators
176 for (j = 0, ySum = []; j < yCompLen; ++j) {
187 // @private Get chart and data boundaries
188 getBounds: function() {
191 store = chart.substore || chart.store,
192 areas = [].concat(me.yField),
193 areasLen = areas.length,
204 bbox, xScale, yScale, xValue, yValue, areaIndex, acumY, ln, sumValues, clipBox, areaElem;
209 // Run through the axis
211 axis = chart.axes.get(me.axis);
213 out = axis.calcEnds();
214 minY = out.from || axis.prevMin;
215 maxY = mmax(out.to || axis.prevMax, 0);
219 if (me.yField && !Ext.isNumber(minY)) {
220 axis = Ext.create('Ext.chart.axis.Axis', {
222 fields: [].concat(me.yField)
224 out = axis.calcEnds();
225 minY = out.from || axis.prevMin;
226 maxY = mmax(out.to || axis.prevMax, 0);
229 if (!Ext.isNumber(minY)) {
232 if (!Ext.isNumber(maxY)) {
236 store.each(function(record, i) {
237 xValue = record.get(me.xField);
239 if (typeof xValue != 'number') {
242 xValues.push(xValue);
244 for (areaIndex = 0; areaIndex < areasLen; areaIndex++) {
245 areaElem = record.get(areas[areaIndex]);
246 if (typeof areaElem == 'number') {
247 minY = mmin(minY, areaElem);
248 yValue.push(areaElem);
252 minX = mmin(minX, xValue);
253 maxX = mmax(maxX, xValue);
254 maxY = mmax(maxY, acumY);
255 yValues.push(yValue);
258 xScale = bbox.width / (maxX - minX);
259 yScale = bbox.height / (maxY - minY);
262 if ((ln > bbox.width) && me.areas) {
263 sumValues = me.shrink(xValues, yValues, bbox.width);
264 xValues = sumValues.x;
265 yValues = sumValues.y;
280 // @private Build an array of paths for the chart
281 getPaths: function() {
284 store = chart.substore || chart.store,
286 bounds = me.getBounds(),
288 items = me.items = [],
292 i, ln, x, y, xValue, yValue, acumY, areaIndex, prevAreaIndex, areaElem, path;
294 ln = bounds.xValues.length;
296 for (i = 0; i < ln; i++) {
297 xValue = bounds.xValues[i];
298 yValue = bounds.yValues[i];
299 x = bbox.x + (xValue - bounds.minX) * bounds.xScale;
301 for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
303 if (me.__excludes[areaIndex]) {
306 if (!componentPaths[areaIndex]) {
307 componentPaths[areaIndex] = [];
309 areaElem = yValue[areaIndex];
311 y = bbox.y + bbox.height - (acumY - bounds.minY) * bounds.yScale;
312 if (!paths[areaIndex]) {
313 paths[areaIndex] = ['M', x, y];
314 componentPaths[areaIndex].push(['L', x, y]);
316 paths[areaIndex].push('L', x, y);
317 componentPaths[areaIndex].push(['L', x, y]);
319 if (!items[areaIndex]) {
326 items[areaIndex].pointsUp.push([x, y]);
331 for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
333 if (me.__excludes[areaIndex]) {
336 path = paths[areaIndex];
337 // Close bottom path to the axis
338 if (areaIndex == 0 || first) {
340 path.push('L', x, bbox.y + bbox.height,
341 'L', bbox.x, bbox.y + bbox.height,
344 // Close other paths to the one before them
346 componentPath = componentPaths[prevAreaIndex];
347 componentPath.reverse();
348 path.push('L', x, componentPath[0][2]);
349 for (i = 0; i < ln; i++) {
350 path.push(componentPath[i][0],
352 componentPath[i][2]);
353 items[areaIndex].pointsDown[ln -i -1] = [componentPath[i][1], componentPath[i][2]];
355 path.push('L', bbox.x, path[2], 'Z');
357 prevAreaIndex = areaIndex;
361 areasLen: bounds.areasLen
365 <span id='Ext-chart-series-Area-method-drawSeries'> /**
366 </span> * Draws the series for the current chart.
368 drawSeries: function() {
371 store = chart.substore || chart.store,
372 surface = chart.surface,
373 animate = chart.animate,
375 endLineStyle = Ext.apply(me.seriesStyle, me.style),
376 colorArrayStyle = me.colorArrayStyle,
377 colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
378 areaIndex, areaElem, paths, path, rendererAttributes;
380 me.unHighlightItem();
381 me.cleanHighlights();
383 if (!store || !store.getCount()) {
387 paths = me.getPaths();
393 for (areaIndex = 0; areaIndex < paths.areasLen; areaIndex++) {
395 if (me.__excludes[areaIndex]) {
398 if (!me.areas[areaIndex]) {
399 me.items[areaIndex].sprite = me.areas[areaIndex] = surface.add(Ext.apply({}, {
402 // 'clip-rect': me.clipBox,
403 path: paths.paths[areaIndex],
404 stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength],
405 fill: colorArrayStyle[areaIndex % colorArrayLength]
406 }, endLineStyle || {}));
408 areaElem = me.areas[areaIndex];
409 path = paths.paths[areaIndex];
411 //Add renderer to line. There is not a unique record associated with this.
412 rendererAttributes = me.renderer(areaElem, false, {
414 // 'clip-rect': me.clipBox,
415 fill: colorArrayStyle[areaIndex % colorArrayLength],
416 stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
417 }, areaIndex, store);
418 //fill should not be used here but when drawing the special fill path object
419 me.animation = me.onAnimate(areaElem, {
420 to: rendererAttributes
423 rendererAttributes = me.renderer(areaElem, false, {
425 // 'clip-rect': me.clipBox,
427 fill: colorArrayStyle[areaIndex % colorArrayLength],
428 stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
429 }, areaIndex, store);
430 me.areas[areaIndex].setAttributes(rendererAttributes, true);
438 onAnimate: function(sprite, attr) {
440 return this.callParent(arguments);
444 onCreateLabel: function(storeItem, item, i, display) {
446 group = me.labelsGroup,
449 endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
451 return me.chart.surface.add(Ext.apply({
453 'text-anchor': 'middle',
456 'y': bbox.y + bbox.height / 2
457 }, endLabelStyle || {}));
461 onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
464 resizing = chart.resizing,
466 format = config.renderer,
467 field = config.field,
473 label.setAttributes({
474 text: format(storeItem.get(field[index])),
478 bb = label.getBBox();
479 width = bb.width / 2;
480 height = bb.height / 2;
482 x = x - width < bbox.x? bbox.x + width : x;
483 x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
484 y = y - height < bbox.y? bbox.y + height : y;
485 y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
487 if (me.chart.animate && !me.chart.resizing) {
489 me.onAnimate(label, {
496 label.setAttributes({
501 me.animation.on('afteranimate', function() {
511 onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
514 surface = chart.surface,
515 resizing = chart.resizing,
516 config = me.callouts,
518 prev = (i == 0) ? false : items[i -1].point,
519 next = (i == items.length -1) ? false : items[i +1].point,
521 dir, norm, normal, a, aprev, anext,
522 bbox = callout.label.getBBox(),
526 boxx, boxy, boxw, boxh,
527 p, clipRect = me.clipRect,
530 //get the right two points
537 a = (next[1] - prev[1]) / (next[0] - prev[0]);
538 aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
539 anext = (next[1] - cur[1]) / (next[0] - cur[0]);
541 norm = Math.sqrt(1 + a * a);
542 dir = [1 / norm, a / norm];
543 normal = [-dir[1], dir[0]];
545 //keep the label always on the outer part of the "elbow"
546 if (aprev > 0 && anext < 0 && normal[1] < 0 || aprev < 0 && anext > 0 && normal[1] > 0) {
549 } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0 || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
555 x = cur[0] + normal[0] * offsetFromViz;
556 y = cur[1] + normal[1] * offsetFromViz;
558 //box position and dimensions
559 boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
560 boxy = y - bbox.height /2 - offsetBox;
561 boxw = bbox.width + 2 * offsetBox;
562 boxh = bbox.height + 2 * offsetBox;
564 //now check if we're out of bounds and invert the normal vector correspondingly
565 //this may add new overlaps between labels (but labels won't be out of bounds).
566 if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
569 if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
574 x = cur[0] + normal[0] * offsetFromViz;
575 y = cur[1] + normal[1] * offsetFromViz;
577 //update box position and dimensions
578 boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
579 boxy = y - bbox.height /2 - offsetBox;
580 boxw = bbox.width + 2 * offsetBox;
581 boxh = bbox.height + 2 * offsetBox;
583 //set the line from the middle of the pie to the box.
584 callout.lines.setAttributes({
585 path: ["M", cur[0], cur[1], "L", x, y, "Z"]
588 callout.box.setAttributes({
595 callout.label.setAttributes({
596 x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
600 callout[p].show(true);
604 isItemInPoint: function(x, y, item, i) {
606 pointsUp = item.pointsUp,
607 pointsDown = item.pointsDown,
609 dist = Infinity, p, pln, point;
611 for (p = 0, pln = pointsUp.length; p < pln; p++) {
612 point = [pointsUp[p][0], pointsUp[p][1]];
613 if (dist > abs(x - point[0])) {
614 dist = abs(x - point[0]);
616 point = pointsUp[p -1];
617 if (y >= point[1] && (!pointsDown.length || y <= (pointsDown[p -1][1]))) {
618 item.storeIndex = p -1;
619 item.storeField = me.yField[i];
620 item.storeItem = me.chart.store.getAt(p -1);
621 item._points = pointsDown.length? [point, pointsDown[p -1]] : [point];
631 <span id='Ext-chart-series-Area-method-highlightSeries'> /**
632 </span> * Highlight this entire series.
633 * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
635 highlightSeries: function() {
636 var area, to, fillColor;
637 if (this._index !== undefined) {
638 area = this.areas[this._index];
639 if (area.__highlightAnim) {
640 area.__highlightAnim.paused = true;
642 area.__highlighted = true;
643 area.__prevOpacity = area.__prevOpacity || area.attr.opacity || 1;
644 area.__prevFill = area.__prevFill || area.attr.fill;
645 area.__prevLineWidth = area.__prevLineWidth || area.attr.lineWidth;
646 fillColor = Ext.draw.Color.fromString(area.__prevFill);
648 lineWidth: (area.__prevLineWidth || 0) + 2
651 to.fill = fillColor.getLighter(0.2).toString();
654 to.opacity = Math.max(area.__prevOpacity - 0.3, 0);
656 if (this.chart.animate) {
657 area.__highlightAnim = Ext.create('Ext.fx.Anim', Ext.apply({
660 }, this.chart.animate));
663 area.setAttributes(to, true);
668 <span id='Ext-chart-series-Area-method-unHighlightSeries'> /**
669 </span> * UnHighlight this entire series.
670 * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
672 unHighlightSeries: function() {
674 if (this._index !== undefined) {
675 area = this.areas[this._index];
676 if (area.__highlightAnim) {
677 area.__highlightAnim.paused = true;
679 if (area.__highlighted) {
680 area.__highlighted = false;
681 area.__highlightAnim = Ext.create('Ext.fx.Anim', {
684 fill: area.__prevFill,
685 opacity: area.__prevOpacity,
686 lineWidth: area.__prevLineWidth
693 <span id='Ext-chart-series-Area-method-highlightItem'> /**
694 </span> * Highlight the specified item. If no item is provided the whole series will be highlighted.
695 * @param item {Object} Info about the item; same format as returned by #getItemForPoint
697 highlightItem: function(item) {
701 this.highlightSeries();
704 points = item._points;
705 path = points.length == 2? ['M', points[0][0], points[0][1], 'L', points[1][0], points[1][1]]
706 : ['M', points[0][0], points[0][1], 'L', points[0][0], me.bbox.y + me.bbox.height];
707 me.highlightSprite.setAttributes({
713 <span id='Ext-chart-series-Area-method-unHighlightItem'> /**
714 </span> * un-highlights the specified item. If no item is provided it will un-highlight the entire series.
715 * @param item {Object} Info about the item; same format as returned by #getItemForPoint
717 unHighlightItem: function(item) {
719 this.unHighlightSeries();
722 if (this.highlightSprite) {
723 this.highlightSprite.hide(true);
728 hideAll: function() {
729 if (!isNaN(this._index)) {
730 this.__excludes[this._index] = true;
731 this.areas[this._index].hide(true);
737 showAll: function() {
738 if (!isNaN(this._index)) {
739 this.__excludes[this._index] = false;
740 this.areas[this._index].show(true);
745 <span id='Ext-chart-series-Area-method-getLegendColor'> /**
746 </span> * Returns the color of the series (to be displayed as color for the series legend item).
747 * @param item {Object} Info about the item; same format as returned by #getItemForPoint
749 getLegendColor: function(index) {
751 return me.colorArrayStyle[index % me.colorArrayStyle.length];