| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688 | <!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-Scatter'>/**</span> * @class Ext.chart.series.Scatter * @extends Ext.chart.series.Cartesian * * Creates a Scatter Chart. The scatter plot is useful when trying to display more than two variables in the same visualization. * These variables can be mapped into x, y coordinates and also to an element's radius/size, color, etc. * As with all other series, the Scatter Series must be appended in the *series* Chart array configuration. See the Chart * documentation for more information on creating charts. A typical configuration object for the scatter 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, *         animate: true, *         theme:'Category2', *         store: store, *         axes: [{ *             type: 'Numeric', *             position: 'left', *             fields: ['data2', 'data3'], *             title: 'Sample Values', *             grid: true, *             minimum: 0 *         }, { *             type: 'Category', *             position: 'bottom', *             fields: ['name'], *             title: 'Sample Metrics' *         }], *         series: [{ *             type: 'scatter', *             markerConfig: { *                 radius: 5, *                 size: 5 *             }, *             axis: 'left', *             xField: 'name', *             yField: 'data2' *         }, { *             type: 'scatter', *             markerConfig: { *                 radius: 5, *                 size: 5 *             }, *             axis: 'left', *             xField: 'name', *             yField: 'data3' *         }] *     }); * * In this configuration we add three different categories of scatter series. Each of them is bound to a different field of the same data store, * `data1`, `data2` and `data3` respectively. All x-fields for the series must be the same field, in this case `name`. * Each scatter series has a different styling configuration for markers, specified by the `markerConfig` object. Finally we set the left axis as * axis to show the current values of the elements. * * @xtype scatter */Ext.define('Ext.chart.series.Scatter', {    /* Begin Definitions */    extend: 'Ext.chart.series.Cartesian',    requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.fx.Anim'],    /* End Definitions */    type: 'scatter',    alias: 'series.scatter',<span id='Ext-chart-series-Scatter-cfg-markerConfig'>    /**</span>     * @cfg {Object} markerConfig     * The display style for the scatter series markers.     */<span id='Ext-chart-series-Scatter-cfg-style'>    /**</span>     * @cfg {Object} style     * Append styling properties to this object for it to override theme properties.     */    <span id='Ext-chart-series-Scatter-cfg-axis'>    /**</span>     * @cfg {String/Array} axis     * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.     * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a     * relative scale will be used. If multiple axes are being used, they should both be specified in in the configuration.     */    constructor: function(config) {        this.callParent(arguments);        var me = this,            shadow = me.chart.shadow,            surface = me.chart.surface, i, l;        Ext.apply(me, config, {            style: {},            markerConfig: {},            shadowAttributes: [{                "stroke-width": 6,                "stroke-opacity": 0.05,                stroke: 'rgb(0, 0, 0)'            }, {                "stroke-width": 4,                "stroke-opacity": 0.1,                stroke: 'rgb(0, 0, 0)'            }, {                "stroke-width": 2,                "stroke-opacity": 0.15,                stroke: 'rgb(0, 0, 0)'            }]        });        me.group = surface.getGroup(me.seriesId);        if (shadow) {            for (i = 0, l = me.shadowAttributes.length; i < l; i++) {                me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));            }        }    },    // @private Get chart and data boundaries    getBounds: function() {        var me = this,            chart = me.chart,            store = chart.getChartStore(),            chartAxes = chart.axes,            boundAxes = me.getAxesForXAndYFields(),            boundXAxis = boundAxes.xAxis,            boundYAxis = boundAxes.yAxis,            bbox, xScale, yScale, ln, minX, minY, maxX, maxY, i, axis, ends;        me.setBBox();        bbox = me.bbox;        if (axis = chartAxes.get(boundXAxis)) {            ends = axis.applyData();            minX = ends.from;            maxX = ends.to;        }        if (axis = chartAxes.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();            minX = axis[0];            maxX = axis[1];        }        if (me.yField && !Ext.isNumber(minY)) {            axis = me.getMinMaxYValues();            minY = axis[0];            maxY = axis[1];        }        if (isNaN(minX)) {            minX = 0;            maxX = store.getCount() - 1;            xScale = bbox.width / (store.getCount() - 1);        }        else {            xScale = bbox.width / (maxX - minX);        }        if (isNaN(minY)) {            minY = 0;            maxY = store.getCount() - 1;            yScale = bbox.height / (store.getCount() - 1);        }        else {            yScale = bbox.height / (maxY - minY);        }        return {            bbox: bbox,            minX: minX,            minY: minY,            xScale: xScale,            yScale: yScale        };    },    // @private Build an array of paths for the chart    getPaths: function() {        var me = this,            chart = me.chart,            enableShadows = chart.shadow,            store = chart.getChartStore(),            data = store.data.items,            i, ln, record,            group = me.group,            bounds = me.bounds = me.getBounds(),            bbox = me.bbox,            xScale = bounds.xScale,            yScale = bounds.yScale,            minX = bounds.minX,            minY = bounds.minY,            boxX = bbox.x,            boxY = bbox.y,            boxHeight = bbox.height,            items = me.items = [],            attrs = [],            x, y, xValue, yValue, sprite;        for (i = 0, ln = data.length; i < ln; i++) {            record = data[i];            xValue = record.get(me.xField);            yValue = record.get(me.yField);            //skip undefined or null values            if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)                || xValue == null || yValue == null) {                //<debug warn>                if (Ext.isDefined(Ext.global.console)) {                    Ext.global.console.warn("[Ext.chart.series.Scatter]  Skipping a store element with a value which is either undefined or null  at ", record, xValue, yValue);                }                //</debug>                continue;            }            // Ensure a value            if (typeof xValue == 'string' || typeof xValue == 'object' && !Ext.isDate(xValue)) {                xValue = i;            }            if (typeof yValue == 'string' || typeof yValue == 'object' && !Ext.isDate(yValue)) {                yValue = i;            }            x = boxX + (xValue - minX) * xScale;            y = boxY + boxHeight - (yValue - minY) * yScale;            attrs.push({                x: x,                y: y            });            me.items.push({                series: me,                value: [xValue, yValue],                point: [x, y],                storeItem: record            });            // When resizing, reset before animating            if (chart.animate && chart.resizing) {                sprite = group.getAt(i);                if (sprite) {                    me.resetPoint(sprite);                    if (enableShadows) {                        me.resetShadow(sprite);                    }                }            }        }        return attrs;    },    // @private translate point to the center    resetPoint: function(sprite) {        var bbox = this.bbox;        sprite.setAttributes({            translate: {                x: (bbox.x + bbox.width) / 2,                y: (bbox.y + bbox.height) / 2            }        }, true);    },    // @private translate shadows of a sprite to the center    resetShadow: function(sprite) {        var me = this,            shadows = sprite.shadows,            shadowAttributes = me.shadowAttributes,            ln = me.shadowGroups.length,            bbox = me.bbox,            i, attr;        for (i = 0; i < ln; i++) {            attr = Ext.apply({}, shadowAttributes[i]);            // TODO: fix this with setAttributes            if (attr.translate) {                attr.translate.x += (bbox.x + bbox.width) / 2;                attr.translate.y += (bbox.y + bbox.height) / 2;            }            else {                attr.translate = {                    x: (bbox.x + bbox.width) / 2,                    y: (bbox.y + bbox.height) / 2                };            }            shadows[i].setAttributes(attr, true);        }    },    // @private create a new point    createPoint: function(attr, type) {        var me = this,            chart = me.chart,            group = me.group,            bbox = me.bbox;        return Ext.chart.Shape[type](chart.surface, Ext.apply({}, {            x: 0,            y: 0,            group: group,            translate: {                x: (bbox.x + bbox.width) / 2,                y: (bbox.y + bbox.height) / 2            }        }, attr));    },    // @private create a new set of shadows for a sprite    createShadow: function(sprite, endMarkerStyle, type) {        var me = this,            chart = me.chart,            shadowGroups = me.shadowGroups,            shadowAttributes = me.shadowAttributes,            lnsh = shadowGroups.length,            bbox = me.bbox,            i, shadow, shadows, attr;        sprite.shadows = shadows = [];        for (i = 0; i < lnsh; i++) {            attr = Ext.apply({}, shadowAttributes[i]);            if (attr.translate) {                attr.translate.x += (bbox.x + bbox.width) / 2;                attr.translate.y += (bbox.y + bbox.height) / 2;            }            else {                Ext.apply(attr, {                    translate: {                        x: (bbox.x + bbox.width) / 2,                        y: (bbox.y + bbox.height) / 2                    }                });            }            Ext.apply(attr, endMarkerStyle);            shadow = Ext.chart.Shape[type](chart.surface, Ext.apply({}, {                x: 0,                y: 0,                group: shadowGroups[i]            }, attr));            shadows.push(shadow);        }    },<span id='Ext-chart-series-Scatter-method-drawSeries'>    /**</span>     * Draws the series for the current chart.     */    drawSeries: function() {        var me = this,            chart = me.chart,            store = chart.getChartStore(),            group = me.group,            enableShadows = chart.shadow,            shadowGroups = me.shadowGroups,            shadowAttributes = me.shadowAttributes,            lnsh = shadowGroups.length,            sprite, attrs, attr, ln, i, endMarkerStyle, shindex, type, shadows,            rendererAttributes, shadowAttribute;        endMarkerStyle = Ext.apply(me.markerStyle, me.markerConfig);        type = endMarkerStyle.type;        delete endMarkerStyle.type;        //if the store is empty then there's nothing to be rendered        if (!store || !store.getCount()) {            me.hide();            me.items = [];            return;        }        me.unHighlightItem();        me.cleanHighlights();        attrs = me.getPaths();        ln = attrs.length;        for (i = 0; i < ln; i++) {            attr = attrs[i];            sprite = group.getAt(i);            Ext.apply(attr, endMarkerStyle);            // Create a new sprite if needed (no height)            if (!sprite) {                sprite = me.createPoint(attr, type);                if (enableShadows) {                    me.createShadow(sprite, endMarkerStyle, type);                }            }            shadows = sprite.shadows;            if (chart.animate) {                rendererAttributes = me.renderer(sprite, store.getAt(i), { translate: attr }, i, store);                sprite._to = rendererAttributes;                me.onAnimate(sprite, {                    to: rendererAttributes                });                //animate shadows                for (shindex = 0; shindex < lnsh; shindex++) {                    shadowAttribute = Ext.apply({}, shadowAttributes[shindex]);                    rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({}, {                         hidden: false,                        translate: {                            x: attr.x + (shadowAttribute.translate? shadowAttribute.translate.x : 0),                            y: attr.y + (shadowAttribute.translate? shadowAttribute.translate.y : 0)                        }                    }, shadowAttribute), i, store);                    me.onAnimate(shadows[shindex], { to: rendererAttributes });                }            }            else {                rendererAttributes = me.renderer(sprite, store.getAt(i), { translate: attr }, i, store);                sprite._to = rendererAttributes;                sprite.setAttributes(rendererAttributes, true);                //animate shadows                for (shindex = 0; shindex < lnsh; shindex++) {                    shadowAttribute = Ext.apply({}, shadowAttributes[shindex]);                    rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({}, {                         hidden: false,                        translate: {                            x: attr.x + (shadowAttribute.translate? shadowAttribute.translate.x : 0),                            y: attr.y + (shadowAttribute.translate? shadowAttribute.translate.y : 0)                        }                     }, shadowAttribute), i, store);                    shadows[shindex].setAttributes(rendererAttributes, true);                }            }            me.items[i].sprite = sprite;        }        // Hide unused sprites        ln = group.getCount();        for (i = attrs.length; i < ln; i++) {            group.getAt(i).hide(true);        }        me.renderLabels();        me.renderCallouts();    },    // @private callback for when creating a label sprite.    onCreateLabel: function(storeItem, item, i, display) {        var me = this,            group = me.labelsGroup,            config = me.label,            endLabelStyle = Ext.apply({}, config, me.seriesLabelStyle),            bbox = me.bbox;        return me.chart.surface.add(Ext.apply({            type: 'text',            group: group,            x: item.point[0],            y: bbox.y + bbox.height / 2        }, endLabelStyle));    },    // @private callback for when placing a label sprite.    onPlaceLabel: function(label, storeItem, item, i, display, animate) {        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],            radius = item.sprite.attr.radius,            bb, width, height, anim;        label.setAttributes({            text: format(storeItem.get(field)),            hidden: true        }, true);        if (display == 'rotate') {            label.setAttributes({                'text-anchor': 'start',                'rotation': {                    x: x,                    y: y,                    degrees: -45                }            }, true);            //correct label position to fit into the box            bb = label.getBBox();            width = bb.width;            height = bb.height;            x = x < bbox.x? bbox.x : 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;        } else if (display == 'under' || display == 'over') {            //TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.            bb = item.sprite.getBBox();            bb.width = bb.width || (radius * 2);            bb.height = bb.height || (radius * 2);            y = y + (display == 'over'? -bb.height : bb.height);            //correct label position to fit into the box            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 (!chart.animate) {            label.setAttributes({                x: x,                y: y            }, true);            label.show(true);        }        else {            if (resizing) {                anim = item.sprite.getActiveAnimation();                if (anim) {                    anim.on('afteranimate', function() {                        label.setAttributes({                            x: x,                            y: y                        }, true);                        label.show(true);                    });                }                else {                    label.show(true);                }            }            else {                me.onAnimate(label, {                    to: {                        x: x,                        y: y                    }                });            }        }    },    // @private callback for when placing a callout sprite.    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,            cur = item.point,            normal,            bbox = callout.label.getBBox(),            offsetFromViz = 30,            offsetToSide = 10,            offsetBox = 3,            boxx, boxy, boxw, boxh,            p, clipRect = me.bbox,            x, y;        //position        normal = [Math.cos(Math.PI /4), -Math.sin(Math.PI /4)];        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;        if (chart.animate) {            //set the line from the middle of the pie to the box.            me.onAnimate(callout.lines, {                to: {                    path: ["M", cur[0], cur[1], "L", x, y, "Z"]                }            }, true);            //set box position            me.onAnimate(callout.box, {                to: {                    x: boxx,                    y: boxy,                    width: boxw,                    height: boxh                }            }, true);            //set text position            me.onAnimate(callout.label, {                to: {                    x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),                    y: y                }            }, true);        } else {            //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);        }    },    // @private handles sprite animation for the series.    onAnimate: function(sprite, attr) {        sprite.show();        return this.callParent(arguments);    },    isItemInPoint: function(x, y, item) {        var point,            tolerance = 10,            abs = Math.abs;        function dist(point) {            var dx = abs(point[0] - x),                dy = abs(point[1] - y);            return Math.sqrt(dx * dx + dy * dy);        }        point = item.point;        return (point[0] - tolerance <= x && point[0] + tolerance >= x &&            point[1] - tolerance <= y && point[1] + tolerance >= y);    }});</pre></body></html>
 |