<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>The source code</title>
- <link href="../prettify/prettify.css" type="text/css" rel="stylesheet" />
- <script type="text/javascript" src="../prettify/prettify.js"></script>
+ <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
+ <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
<style type="text/css">
.highlight { display: block; background-color: #ddd; }
</style>
<pre class="prettyprint lang-js"><span id='Ext-chart-series-Area'>/**
</span> * @class Ext.chart.series.Area
* @extends Ext.chart.series.Cartesian
- *
- <p>
- Creates a Stacked Area Chart. The stacked area chart is useful when displaying multiple aggregated layers of information.
- As with all other series, the Area Series must be appended in the *series* Chart array configuration. See the Chart
- documentation for more information. A typical configuration object for the area series could be:
- </p>
-{@img Ext.chart.series.Area/Ext.chart.series.Area.png Ext.chart.series.Area chart series}
- <pre><code>
- var store = Ext.create('Ext.data.JsonStore', {
- fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
- data: [
- {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
- {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
- {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
- {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
- {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
- ]
- });
-
- Ext.create('Ext.chart.Chart', {
- renderTo: Ext.getBody(),
- width: 500,
- height: 300,
- store: store,
- axes: [{
- type: 'Numeric',
- grid: true,
- position: 'left',
- fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
- title: 'Sample Values',
- grid: {
- odd: {
- opacity: 1,
- fill: '#ddd',
- stroke: '#bbb',
- 'stroke-width': 1
+ *
+ * Creates a Stacked Area Chart. The stacked area chart is useful when displaying multiple aggregated layers of information.
+ * As with all other series, the Area Series must be appended in the *series* Chart array configuration. See the Chart
+ * documentation for more information. A typical configuration object for the area series could be:
+ *
+ * @example
+ * var store = Ext.create('Ext.data.JsonStore', {
+ * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
+ * data: [
+ * { 'name': 'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13 },
+ * { 'name': 'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3 },
+ * { 'name': 'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7 },
+ * { 'name': 'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23 },
+ * { 'name': 'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
+ * ]
+ * });
+ *
+ * Ext.create('Ext.chart.Chart', {
+ * renderTo: Ext.getBody(),
+ * width: 500,
+ * height: 300,
+ * store: store,
+ * axes: [
+ * {
+ * type: 'Numeric',
+ * grid: true,
+ * position: 'left',
+ * fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
+ * title: 'Sample Values',
+ * grid: {
+ * odd: {
+ * opacity: 1,
+ * fill: '#ddd',
+ * stroke: '#bbb',
+ * 'stroke-width': 1
+ * }
+ * },
+ * minimum: 0,
+ * adjustMinimumByMajorUnit: 0
+ * },
+ * {
+ * type: 'Category',
+ * position: 'bottom',
+ * fields: ['name'],
+ * title: 'Sample Metrics',
+ * grid: true,
+ * label: {
+ * rotate: {
+ * degrees: 315
+ * }
+ * }
+ * }
+ * ],
+ * series: [{
+ * type: 'area',
+ * highlight: false,
+ * axis: 'left',
+ * xField: 'name',
+ * yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
+ * style: {
+ * opacity: 0.93
+ * }
+ * }]
+ * });
+ *
+ * In this configuration we set `area` as the type for the series, set highlighting options to true for highlighting elements on hover,
+ * 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,
+ * and as yFields (aggregated layers) seven data fields from the same store. Then we override some theming styles by adding some opacity
+ * to the style object.
+ *
+ * @xtype area
+ */
+Ext.define('Ext.chart.series.Area', {
+
+ /* Begin Definitions */
+
+ extend: 'Ext.chart.series.Cartesian',
+
+ alias: 'series.area',
+
+ requires: ['Ext.chart.axis.Axis', 'Ext.draw.Color', 'Ext.fx.Anim'],
+
+ /* End Definitions */
+
+ type: 'area',
+
+ // @private Area charts are alyways stacked
+ stacked: true,
+
+<span id='Ext-chart-series-Area-cfg-style'> /**
+</span> * @cfg {Object} style
+ * Append styling properties to this object for it to override theme properties.
+ */
+ style: {},
+
+ constructor: function(config) {
+ this.callParent(arguments);
+ var me = this,
+ surface = me.chart.surface,
+ i, l;
+ Ext.apply(me, config, {
+ __excludes: [],
+ highlightCfg: {
+ lineWidth: 3,
+ stroke: '#55c',
+ opacity: 0.8,
+ color: '#f00'
+ }
+ });
+ if (me.highlight) {
+ me.highlightSprite = surface.add({
+ type: 'path',
+ path: ['M', 0, 0],
+ zIndex: 1000,
+ opacity: 0.3,
+ lineWidth: 5,
+ hidden: true,
+ stroke: '#444'
+ });
+ }
+ me.group = surface.getGroup(me.seriesId);
+ },
+
+ // @private Shrinks dataSets down to a smaller size
+ shrink: function(xValues, yValues, size) {
+ var len = xValues.length,
+ ratio = Math.floor(len / size),
+ i, j,
+ xSum = 0,
+ yCompLen = this.areas.length,
+ ySum = [],
+ xRes = [],
+ yRes = [];
+ //initialize array
+ for (j = 0; j < yCompLen; ++j) {
+ ySum[j] = 0;
+ }
+ for (i = 0; i < len; ++i) {
+ xSum += xValues[i];
+ for (j = 0; j < yCompLen; ++j) {
+ ySum[j] += yValues[i][j];
+ }
+ if (i % ratio == 0) {
+ //push averages
+ xRes.push(xSum/ratio);
+ for (j = 0; j < yCompLen; ++j) {
+ ySum[j] /= ratio;
+ }
+ yRes.push(ySum);
+ //reset sum accumulators
+ xSum = 0;
+ for (j = 0, ySum = []; j < yCompLen; ++j) {
+ ySum[j] = 0;
+ }
+ }
+ }
+ return {
+ x: xRes,
+ y: yRes
+ };
+ },
+
+ // @private Get chart and data boundaries
+ getBounds: function() {
+ var me = this,
+ chart = me.chart,
+ store = chart.getChartStore(),
+ areas = [].concat(me.yField),
+ areasLen = areas.length,
+ xValues = [],
+ yValues = [],
+ infinity = Infinity,
+ minX = infinity,
+ minY = infinity,
+ maxX = -infinity,
+ maxY = -infinity,
+ math = Math,
+ mmin = math.min,
+ mmax = math.max,
+ bbox, xScale, yScale, xValue, yValue, areaIndex, acumY, ln, sumValues, clipBox, areaElem;
+
+ me.setBBox();
+ bbox = me.bbox;
+
+ // Run through the axis
+ if (me.axis) {
+ axis = chart.axes.get(me.axis);
+ if (axis) {
+ out = axis.calcEnds();
+ minY = out.from || axis.prevMin;
+ maxY = mmax(out.to || axis.prevMax, 0);
+ }
+ }
+
+ if (me.yField && !Ext.isNumber(minY)) {
+ axis = Ext.create('Ext.chart.axis.Axis', {
+ chart: chart,
+ fields: [].concat(me.yField)
+ });
+ out = axis.calcEnds();
+ minY = out.from || axis.prevMin;
+ maxY = mmax(out.to || axis.prevMax, 0);
+ }
+
+ if (!Ext.isNumber(minY)) {
+ minY = 0;
+ }
+ if (!Ext.isNumber(maxY)) {
+ maxY = 0;
+ }
+
+ store.each(function(record, i) {
+ xValue = record.get(me.xField);
+ yValue = [];
+ if (typeof xValue != 'number') {
+ xValue = i;
+ }
+ xValues.push(xValue);
+ acumY = 0;
+ for (areaIndex = 0; areaIndex < areasLen; areaIndex++) {
+ areaElem = record.get(areas[areaIndex]);
+ if (typeof areaElem == 'number') {
+ minY = mmin(minY, areaElem);
+ yValue.push(areaElem);
+ acumY += areaElem;
+ }
+ }
+ minX = mmin(minX, xValue);
+ maxX = mmax(maxX, xValue);
+ maxY = mmax(maxY, acumY);
+ yValues.push(yValue);
+ }, me);
+
+ xScale = bbox.width / ((maxX - minX) || 1);
+ yScale = bbox.height / ((maxY - minY) || 1);
+
+ ln = xValues.length;
+ if ((ln > bbox.width) && me.areas) {
+ sumValues = me.shrink(xValues, yValues, bbox.width);
+ xValues = sumValues.x;
+ yValues = sumValues.y;
+ }
+
+ return {
+ bbox: bbox,
+ minX: minX,
+ minY: minY,
+ xValues: xValues,
+ yValues: yValues,
+ xScale: xScale,
+ yScale: yScale,
+ areasLen: areasLen
+ };
+ },
+
+ // @private Build an array of paths for the chart
+ getPaths: function() {
+ var me = this,
+ chart = me.chart,
+ store = chart.getChartStore(),
+ first = true,
+ bounds = me.getBounds(),
+ bbox = bounds.bbox,
+ items = me.items = [],
+ componentPaths = [],
+ componentPath,
+ paths = [],
+ i, ln, x, y, xValue, yValue, acumY, areaIndex, prevAreaIndex, areaElem, path;
+
+ ln = bounds.xValues.length;
+ // Start the path
+ for (i = 0; i < ln; i++) {
+ xValue = bounds.xValues[i];
+ yValue = bounds.yValues[i];
+ x = bbox.x + (xValue - bounds.minX) * bounds.xScale;
+ acumY = 0;
+ for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
+ // Excluded series
+ if (me.__excludes[areaIndex]) {
+ continue;
+ }
+ if (!componentPaths[areaIndex]) {
+ componentPaths[areaIndex] = [];
+ }
+ areaElem = yValue[areaIndex];
+ acumY += areaElem;
+ y = bbox.y + bbox.height - (acumY - bounds.minY) * bounds.yScale;
+ if (!paths[areaIndex]) {
+ paths[areaIndex] = ['M', x, y];
+ componentPaths[areaIndex].push(['L', x, y]);
+ } else {
+ paths[areaIndex].push('L', x, y);
+ componentPaths[areaIndex].push(['L', x, y]);
+ }
+ if (!items[areaIndex]) {
+ items[areaIndex] = {
+ pointsUp: [],
+ pointsDown: [],
+ series: me
+ };
+ }
+ items[areaIndex].pointsUp.push([x, y]);
+ }
+ }
+
+ // Close the paths
+ for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
+ // Excluded series
+ if (me.__excludes[areaIndex]) {
+ continue;
+ }
+ path = paths[areaIndex];
+ // Close bottom path to the axis
+ if (areaIndex == 0 || first) {
+ first = false;
+ path.push('L', x, bbox.y + bbox.height,
+ 'L', bbox.x, bbox.y + bbox.height,
+ 'Z');
+ }
+ // Close other paths to the one before them
+ else {
+ componentPath = componentPaths[prevAreaIndex];
+ componentPath.reverse();
+ path.push('L', x, componentPath[0][2]);
+ for (i = 0; i < ln; i++) {
+ path.push(componentPath[i][0],
+ componentPath[i][1],
+ componentPath[i][2]);
+ items[areaIndex].pointsDown[ln -i -1] = [componentPath[i][1], componentPath[i][2]];
}
- },
- minimum: 0,
- adjustMinimumByMajorUnit: 0
- }, {
- type: 'Category',
- position: 'bottom',
- fields: ['name'],
- title: 'Sample Metrics',
- grid: true,
- label: {
- rotate: {
- degrees: 315
+ path.push('L', bbox.x, path[2], 'Z');
+ }
+ prevAreaIndex = areaIndex;
+ }
+ return {
+ paths: paths,
+ areasLen: bounds.areasLen
+ };
+ },
+
+<span id='Ext-chart-series-Area-method-drawSeries'> /**
+</span> * Draws the series for the current chart.
+ */
+ drawSeries: function() {
+ var me = this,
+ chart = me.chart,
+ store = chart.getChartStore(),
+ surface = chart.surface,
+ animate = chart.animate,
+ group = me.group,
+ endLineStyle = Ext.apply(me.seriesStyle, me.style),
+ colorArrayStyle = me.colorArrayStyle,
+ colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
+ areaIndex, areaElem, paths, path, rendererAttributes;
+
+ me.unHighlightItem();
+ me.cleanHighlights();
+
+ if (!store || !store.getCount()) {
+ return;
+ }
+
+ paths = me.getPaths();
+
+ if (!me.areas) {
+ me.areas = [];
+ }
+
+ for (areaIndex = 0; areaIndex < paths.areasLen; areaIndex++) {
+ // Excluded series
+ if (me.__excludes[areaIndex]) {
+ continue;
+ }
+ if (!me.areas[areaIndex]) {
+ me.items[areaIndex].sprite = me.areas[areaIndex] = surface.add(Ext.apply({}, {
+ type: 'path',
+ group: group,
+ // 'clip-rect': me.clipBox,
+ path: paths.paths[areaIndex],
+ stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength],
+ fill: colorArrayStyle[areaIndex % colorArrayLength]
+ }, endLineStyle || {}));
+ }
+ areaElem = me.areas[areaIndex];
+ path = paths.paths[areaIndex];
+ if (animate) {
+ //Add renderer to line. There is not a unique record associated with this.
+ rendererAttributes = me.renderer(areaElem, false, {
+ path: path,
+ // 'clip-rect': me.clipBox,
+ fill: colorArrayStyle[areaIndex % colorArrayLength],
+ stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
+ }, areaIndex, store);
+ //fill should not be used here but when drawing the special fill path object
+ me.animation = me.onAnimate(areaElem, {
+ to: rendererAttributes
+ });
+ } else {
+ rendererAttributes = me.renderer(areaElem, false, {
+ path: path,
+ // 'clip-rect': me.clipBox,
+ hidden: false,
+ fill: colorArrayStyle[areaIndex % colorArrayLength],
+ stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
+ }, areaIndex, store);
+ me.areas[areaIndex].setAttributes(rendererAttributes, true);
+ }
+ }
+ me.renderLabels();
+ me.renderCallouts();
+ },
+
+ // @private
+ onAnimate: function(sprite, attr) {
+ sprite.show();
+ return this.callParent(arguments);
+ },
+
+ // @private
+ onCreateLabel: function(storeItem, item, i, display) {
+ var me = this,
+ group = me.labelsGroup,
+ config = me.label,
+ bbox = me.bbox,
+ endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
+
+ return me.chart.surface.add(Ext.apply({
+ 'type': 'text',
+ 'text-anchor': 'middle',
+ 'group': group,
+ 'x': item.point[0],
+ 'y': bbox.y + bbox.height / 2
+ }, endLabelStyle || {}));
+ },
+
+ // @private
+ onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
+ var me = this,
+ chart = me.chart,
+ resizing = chart.resizing,
+ config = me.label,
+ format = config.renderer,
+ field = config.field,
+ bbox = me.bbox,
+ x = item.point[0],
+ y = item.point[1],
+ bb, width, height;
+
+ label.setAttributes({
+ text: format(storeItem.get(field[index])),
+ hidden: true
+ }, true);
+
+ bb = label.getBBox();
+ width = bb.width / 2;
+ height = bb.height / 2;
+
+ x = x - width < bbox.x? bbox.x + width : x;
+ x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
+ y = y - height < bbox.y? bbox.y + height : y;
+ y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
+
+ if (me.chart.animate && !me.chart.resizing) {
+ label.show(true);
+ me.onAnimate(label, {
+ to: {
+ x: x,
+ y: y
+ }
+ });
+ } else {
+ label.setAttributes({
+ x: x,
+ y: y
+ }, true);
+ if (resizing) {
+ me.animation.on('afteranimate', function() {
+ label.show(true);
+ });
+ } else {
+ label.show(true);
+ }
+ }
+ },
+
+ // @private
+ onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
+ var me = this,
+ chart = me.chart,
+ surface = chart.surface,
+ resizing = chart.resizing,
+ config = me.callouts,
+ items = me.items,
+ prev = (i == 0) ? false : items[i -1].point,
+ next = (i == items.length -1) ? false : items[i +1].point,
+ cur = item.point,
+ dir, norm, normal, a, aprev, anext,
+ bbox = callout.label.getBBox(),
+ offsetFromViz = 30,
+ offsetToSide = 10,
+ offsetBox = 3,
+ boxx, boxy, boxw, boxh,
+ p, clipRect = me.clipRect,
+ x, y;
+
+ //get the right two points
+ if (!prev) {
+ prev = cur;
+ }
+ if (!next) {
+ next = cur;
+ }
+ a = (next[1] - prev[1]) / (next[0] - prev[0]);
+ aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
+ anext = (next[1] - cur[1]) / (next[0] - cur[0]);
+
+ norm = Math.sqrt(1 + a * a);
+ dir = [1 / norm, a / norm];
+ normal = [-dir[1], dir[0]];
+
+ //keep the label always on the outer part of the "elbow"
+ if (aprev > 0 && anext < 0 && normal[1] < 0 || aprev < 0 && anext > 0 && normal[1] > 0) {
+ normal[0] *= -1;
+ normal[1] *= -1;
+ } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0 || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
+ normal[0] *= -1;
+ normal[1] *= -1;
+ }
+
+ //position
+ x = cur[0] + normal[0] * offsetFromViz;
+ y = cur[1] + normal[1] * offsetFromViz;
+
+ //box position and dimensions
+ boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
+ boxy = y - bbox.height /2 - offsetBox;
+ boxw = bbox.width + 2 * offsetBox;
+ boxh = bbox.height + 2 * offsetBox;
+
+ //now check if we're out of bounds and invert the normal vector correspondingly
+ //this may add new overlaps between labels (but labels won't be out of bounds).
+ if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
+ normal[0] *= -1;
+ }
+ if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
+ normal[1] *= -1;
+ }
+
+ //update positions
+ x = cur[0] + normal[0] * offsetFromViz;
+ y = cur[1] + normal[1] * offsetFromViz;
+
+ //update box position and dimensions
+ boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
+ boxy = y - bbox.height /2 - offsetBox;
+ boxw = bbox.width + 2 * offsetBox;
+ boxh = bbox.height + 2 * offsetBox;
+
+ //set the line from the middle of the pie to the box.
+ callout.lines.setAttributes({
+ path: ["M", cur[0], cur[1], "L", x, y, "Z"]
+ }, true);
+ //set box position
+ callout.box.setAttributes({
+ x: boxx,
+ y: boxy,
+ width: boxw,
+ height: boxh
+ }, true);
+ //set text position
+ callout.label.setAttributes({
+ x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
+ y: y
+ }, true);
+ for (p in callout) {
+ callout[p].show(true);
+ }
+ },
+
+ isItemInPoint: function(x, y, item, i) {
+ var me = this,
+ pointsUp = item.pointsUp,
+ pointsDown = item.pointsDown,
+ abs = Math.abs,
+ dist = Infinity, p, pln, point;
+
+ for (p = 0, pln = pointsUp.length; p < pln; p++) {
+ point = [pointsUp[p][0], pointsUp[p][1]];
+ if (dist > abs(x - point[0])) {
+ dist = abs(x - point[0]);
+ } else {
+ point = pointsUp[p -1];
+ if (y >= point[1] && (!pointsDown.length || y <= (pointsDown[p -1][1]))) {
+ item.storeIndex = p -1;
+ item.storeField = me.yField[i];
+ item.storeItem = me.chart.store.getAt(p -1);
+ item._points = pointsDown.length? [point, pointsDown[p -1]] : [point];
+ return true;
+ } else {
+ break;
}
}
- }],
- series: [{
- type: 'area',
- highlight: false,
- axis: 'left',
- xField: 'name',
- yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
- style: {
- opacity: 0.93
- }
- }]
- });
- </code></pre>
-
-
- <p>
- In this configuration we set `area` as the type for the series, set highlighting options to true for highlighting elements on hover,
- 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,
- and as yFields (aggregated layers) seven data fields from the same store. Then we override some theming styles by adding some opacity
- to the style object.
- </p>
-
+ }
+ return false;
+ },
+
+<span id='Ext-chart-series-Area-method-highlightSeries'> /**
+</span> * Highlight this entire series.
+ * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
+ */
+ highlightSeries: function() {
+ var area, to, fillColor;
+ if (this._index !== undefined) {
+ area = this.areas[this._index];
+ if (area.__highlightAnim) {
+ area.__highlightAnim.paused = true;
+ }
+ area.__highlighted = true;
+ area.__prevOpacity = area.__prevOpacity || area.attr.opacity || 1;
+ area.__prevFill = area.__prevFill || area.attr.fill;
+ area.__prevLineWidth = area.__prevLineWidth || area.attr.lineWidth;
+ fillColor = Ext.draw.Color.fromString(area.__prevFill);
+ to = {
+ lineWidth: (area.__prevLineWidth || 0) + 2
+ };
+ if (fillColor) {
+ to.fill = fillColor.getLighter(0.2).toString();
+ }
+ else {
+ to.opacity = Math.max(area.__prevOpacity - 0.3, 0);
+ }
+ if (this.chart.animate) {
+ area.__highlightAnim = Ext.create('Ext.fx.Anim', Ext.apply({
+ target: area,
+ to: to
+ }, this.chart.animate));
+ }
+ else {
+ area.setAttributes(to, true);
+ }
+ }
+ },
+
+<span id='Ext-chart-series-Area-method-unHighlightSeries'> /**
+</span> * UnHighlight this entire series.
+ * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
+ */
+ unHighlightSeries: function() {
+ var area;
+ if (this._index !== undefined) {
+ area = this.areas[this._index];
+ if (area.__highlightAnim) {
+ area.__highlightAnim.paused = true;
+ }
+ if (area.__highlighted) {
+ area.__highlighted = false;
+ area.__highlightAnim = Ext.create('Ext.fx.Anim', {
+ target: area,
+ to: {
+ fill: area.__prevFill,
+ opacity: area.__prevOpacity,
+ lineWidth: area.__prevLineWidth
+ }
+ });
+ }
+ }
+ },
+
+<span id='Ext-chart-series-Area-method-highlightItem'> /**
+</span> * Highlight the specified item. If no item is provided the whole series will be highlighted.
+ * @param item {Object} Info about the item; same format as returned by #getItemForPoint
+ */
+ highlightItem: function(item) {
+ var me = this,
+ points, path;
+ if (!item) {
+ this.highlightSeries();
+ return;
+ }
+ points = item._points;
+ path = points.length == 2? ['M', points[0][0], points[0][1], 'L', points[1][0], points[1][1]]
+ : ['M', points[0][0], points[0][1], 'L', points[0][0], me.bbox.y + me.bbox.height];
+ me.highlightSprite.setAttributes({
+ path: path,
+ hidden: false
+ }, true);
+ },
+
+<span id='Ext-chart-series-Area-method-unHighlightItem'> /**
+</span> * Un-highlights the specified item. If no item is provided it will un-highlight the entire series.
+ * @param {Object} item Info about the item; same format as returned by #getItemForPoint
+ */
+ unHighlightItem: function(item) {
+ if (!item) {
+ this.unHighlightSeries();
+ }
+
+ if (this.highlightSprite) {
+ this.highlightSprite.hide(true);
+ }
+ },
+
+ // @private
+ hideAll: function() {
+ if (!isNaN(this._index)) {
+ this.__excludes[this._index] = true;
+ this.areas[this._index].hide(true);
+ this.drawSeries();
+ }
+ },
+
+ // @private
+ showAll: function() {
+ if (!isNaN(this._index)) {
+ this.__excludes[this._index] = false;
+ this.areas[this._index].show(true);
+ this.drawSeries();
+ }
+ },
+
+<span id='Ext-chart-series-Area-method-getLegendColor'> /**
+</span> * Returns the color of the series (to be displayed as color for the series legend item).
+ * @param item {Object} Info about the item; same format as returned by #getItemForPoint
+ */
+ getLegendColor: function(index) {
+ var me = this;
+ return me.colorArrayStyle[index % me.colorArrayStyle.length];
+ }
+});
+<span id='Ext-chart-series-Area'>/**
+</span> * @class Ext.chart.series.Area
+ * @extends Ext.chart.series.Cartesian
+ *
+ * Creates a Stacked Area Chart. The stacked area chart is useful when displaying multiple aggregated layers of information.
+ * As with all other series, the Area Series must be appended in the *series* Chart array configuration. See the Chart
+ * documentation for more information. A typical configuration object for the area series could be:
+ *
+ * @example
+ * var store = Ext.create('Ext.data.JsonStore', {
+ * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
+ * data: [
+ * { 'name': 'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13 },
+ * { 'name': 'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3 },
+ * { 'name': 'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7 },
+ * { 'name': 'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23 },
+ * { 'name': 'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
+ * ]
+ * });
+ *
+ * Ext.create('Ext.chart.Chart', {
+ * renderTo: Ext.getBody(),
+ * width: 500,
+ * height: 300,
+ * store: store,
+ * axes: [
+ * {
+ * type: 'Numeric',
+ * grid: true,
+ * position: 'left',
+ * fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
+ * title: 'Sample Values',
+ * grid: {
+ * odd: {
+ * opacity: 1,
+ * fill: '#ddd',
+ * stroke: '#bbb',
+ * 'stroke-width': 1
+ * }
+ * },
+ * minimum: 0,
+ * adjustMinimumByMajorUnit: 0
+ * },
+ * {
+ * type: 'Category',
+ * position: 'bottom',
+ * fields: ['name'],
+ * title: 'Sample Metrics',
+ * grid: true,
+ * label: {
+ * rotate: {
+ * degrees: 315
+ * }
+ * }
+ * }
+ * ],
+ * series: [{
+ * type: 'area',
+ * highlight: false,
+ * axis: 'left',
+ * xField: 'name',
+ * yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
+ * style: {
+ * opacity: 0.93
+ * }
+ * }]
+ * });
+ *
+ * In this configuration we set `area` as the type for the series, set highlighting options to true for highlighting elements on hover,
+ * 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,
+ * and as yFields (aggregated layers) seven data fields from the same store. Then we override some theming styles by adding some opacity
+ * to the style object.
+ *
* @xtype area
- *
*/
Ext.define('Ext.chart.series.Area', {
/* Begin Definitions */
extend: 'Ext.chart.series.Cartesian',
-
+
alias: 'series.area',
requires: ['Ext.chart.axis.Axis', 'Ext.draw.Color', 'Ext.fx.Anim'],
stacked: true,
<span id='Ext-chart-series-Area-cfg-style'> /**
-</span> * @cfg {Object} style
+</span> * @cfg {Object} style
* Append styling properties to this object for it to override theme properties.
*/
style: {},
getBounds: function() {
var me = this,
chart = me.chart,
- store = chart.substore || chart.store,
+ store = chart.getChartStore(),
areas = [].concat(me.yField),
areasLen = areas.length,
xValues = [],
yValues.push(yValue);
}, me);
- xScale = bbox.width / (maxX - minX);
- yScale = bbox.height / (maxY - minY);
+ xScale = bbox.width / ((maxX - minX) || 1);
+ yScale = bbox.height / ((maxY - minY) || 1);
ln = xValues.length;
if ((ln > bbox.width) && me.areas) {
getPaths: function() {
var me = this,
chart = me.chart,
- store = chart.substore || chart.store,
+ store = chart.getChartStore(),
first = true,
bounds = me.getBounds(),
bbox = bounds.bbox,
items[areaIndex].pointsUp.push([x, y]);
}
}
-
+
// Close the paths
for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
// Excluded series
drawSeries: function() {
var me = this,
chart = me.chart,
- store = chart.substore || chart.store,
+ store = chart.getChartStore(),
surface = chart.surface,
animate = chart.animate,
group = me.group,
if (!store || !store.getCount()) {
return;
}
-
+
paths = me.getPaths();
if (!me.areas) {
path = paths.paths[areaIndex];
if (animate) {
//Add renderer to line. There is not a unique record associated with this.
- rendererAttributes = me.renderer(areaElem, false, {
+ rendererAttributes = me.renderer(areaElem, false, {
path: path,
// 'clip-rect': me.clipBox,
fill: colorArrayStyle[areaIndex % colorArrayLength],
to: rendererAttributes
});
} else {
- rendererAttributes = me.renderer(areaElem, false, {
+ rendererAttributes = me.renderer(areaElem, false, {
path: path,
// 'clip-rect': me.clipBox,
hidden: false,
x = item.point[0],
y = item.point[1],
bb, width, height;
-
+
label.setAttributes({
text: format(storeItem.get(field[index])),
hidden: true
}, true);
-
+
bb = label.getBBox();
width = bb.width / 2;
height = bb.height / 2;
-
+
x = x - width < bbox.x? bbox.x + width : x;
x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
y = y - height < bbox.y? bbox.y + height : y;
a = (next[1] - prev[1]) / (next[0] - prev[0]);
aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
anext = (next[1] - cur[1]) / (next[0] - cur[0]);
-
+
norm = Math.sqrt(1 + a * a);
dir = [1 / norm, a / norm];
normal = [-dir[1], dir[0]];
-
+
//keep the label always on the outer part of the "elbow"
if (aprev > 0 && anext < 0 && normal[1] < 0 || aprev < 0 && anext > 0 && normal[1] > 0) {
normal[0] *= -1;
//position
x = cur[0] + normal[0] * offsetFromViz;
y = cur[1] + normal[1] * offsetFromViz;
-
+
//box position and dimensions
boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
boxy = y - bbox.height /2 - offsetBox;
boxw = bbox.width + 2 * offsetBox;
boxh = bbox.height + 2 * offsetBox;
-
+
//now check if we're out of bounds and invert the normal vector correspondingly
//this may add new overlaps between labels (but labels won't be out of bounds).
if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
//update positions
x = cur[0] + normal[0] * offsetFromViz;
y = cur[1] + normal[1] * offsetFromViz;
-
+
//update box position and dimensions
boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
boxy = y - bbox.height /2 - offsetBox;
boxw = bbox.width + 2 * offsetBox;
boxh = bbox.height + 2 * offsetBox;
-
+
//set the line from the middle of the pie to the box.
callout.lines.setAttributes({
path: ["M", cur[0], cur[1], "L", x, y, "Z"]
callout[p].show(true);
}
},
-
+
isItemInPoint: function(x, y, item, i) {
var me = this,
pointsUp = item.pointsUp,
pointsDown = item.pointsDown,
abs = Math.abs,
dist = Infinity, p, pln, point;
-
+
for (p = 0, pln = pointsUp.length; p < pln; p++) {
point = [pointsUp[p][0], pointsUp[p][1]];
if (dist > abs(x - point[0])) {