| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818 | <!DOCTYPE html><html><head>  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />  <title>The source code</title>  <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>  <script type="text/javascript">    function highlight() {      document.getElementById(location.hash.replace(/#/, "")).className = "highlight";    }  </script></head><body onload="prettyPrint(); highlight();">  <pre class="prettyprint lang-js"><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'],    /* 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;        config.highlightCfg = Ext.Object.merge({}, {            lineWidth: 3,            stroke: '#55c',            opacity: 0.8,            color: '#f00'        }, config.highlightCfg);        Ext.apply(me, config, {            __excludes: []        });        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(),            data = store.data.items,            i, l, record,            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,            boundAxis = me.getAxesForXAndYFields(),            boundXAxis = boundAxis.xAxis,            boundYAxis = boundAxis.yAxis,            ends, allowDate,            bbox, xScale, yScale, xValue, yValue, areaIndex, acumY, ln, sumValues, clipBox, areaElem, axis, out;        me.setBBox();        bbox = me.bbox;        if (axis = chart.axes.get(boundXAxis)) {            if (axis.type === 'Time') {                allowDate = true;            }            ends = axis.applyData();            minX = ends.from;            maxX = ends.to;        }        if (axis = chart.axes.get(boundYAxis)) {            ends = axis.applyData();            minY = ends.from;            maxY = ends.to;        }        // If a field was specified without a corresponding axis, create one to get bounds        if (me.xField && !Ext.isNumber(minX)) {            axis = me.getMinMaxXValues();            allowDate = true;            minX = axis[0];            maxX = axis[1];        }        if (me.yField && !Ext.isNumber(minY)) {            axis = me.getMinMaxYValues();            minY = axis[0];            maxY = axis[1];        }        if (!Ext.isNumber(minY)) {            minY = 0;        }        if (!Ext.isNumber(maxY)) {            maxY = 0;        }        for (i = 0, l = data.length; i < l; i++) {            record = data[i];            xValue = record.get(me.xField);            yValue = [];            if (typeof xValue != 'number') {                if (allowDate) {                    xValue = +xValue;                } else {                    xValue = i;                }            }            xValues.push(xValue);            acumY = 0;            for (areaIndex = 0; areaIndex < areasLen; areaIndex++) {                // Excluded series                if (me.__excludes[areaIndex]) {                    continue;                }                areaElem = record.get(areas[areaIndex]);                if (typeof areaElem == 'number') {                    yValue.push(areaElem);                }            }            yValues.push(yValue);        }        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,            count = 0,            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;            count = 0;            for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {                // Excluded series                if (me.__excludes[areaIndex]) {                    continue;                }                if (!componentPaths[areaIndex]) {                    componentPaths[areaIndex] = [];                }                areaElem = yValue[count];                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]);                count++;            }        }        // 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        };    },<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() || me.seriesIsHidden) {            me.hide();            me.items = [];            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,            distChanged = false,            last = false,            dist = Infinity, p, pln, point;        for (p = 0, pln = pointsUp.length; p < pln; p++) {            point = [pointsUp[p][0], pointsUp[p][1]];                        distChanged = false;            last = p == pln -1;            if (dist > abs(x - point[0])) {                dist = abs(x - point[0]);                distChanged = true;                if (last) {                    ++p;                }            }                        if (!distChanged || (distChanged && last)) {                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;                }            }        }        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 = new 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 = new 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(index) {        var me = this;        index = (isNaN(me._index) ? index : me._index) || 0;        me.__excludes[index] = true;        me.areas[index].hide(true);        me.redraw();    },    // @private    showAll: function(index) {        var me = this;        index = (isNaN(me._index) ? index : me._index) || 0;        me.__excludes[index] = false;        me.areas[index].show(true);        me.redraw();    },    redraw: function() {        //store previous configuration for the legend        //and set it to false so we don't        //re-build label elements if not necessary.        var me = this,            prevLegendConfig;        prevLegendConfig = me.chart.legend.rebuild;        me.chart.legend.rebuild = false;        me.chart.redraw();        me.chart.legend.rebuild = prevLegendConfig;    },        hide: function() {        if (this.areas) {            var me = this,                areas = me.areas,                i, j, l, ln, shadows;                        if (areas && areas.length) {                for (i = 0, ln = areas.length; i < ln; ++i) {                    if (areas[i]) {                        areas[i].hide(true);                    }                }                me.hideLabels();            }        }    },<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 {Object} item Info about the item; same format as returned by #getItemForPoint     */    getLegendColor: function(index) {        var me = this;        return me.colorArrayStyle[index % me.colorArrayStyle.length];    }});</pre></body></html>
 |