+ }
+ 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]];
+ }
+ path.push('L', bbox.x, path[2], 'Z');
+ }
+ prevAreaIndex = areaIndex;
+ }
+ return {
+ paths: paths,
+ areasLen: bounds.areasLen
+ };
+ },
+
+ /**
+ * 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;