Upgrade to ExtJS 4.0.0 - Released 04/26/2011
[extjs.git] / docs / source / Scatter.html
diff --git a/docs/source/Scatter.html b/docs/source/Scatter.html
new file mode 100644 (file)
index 0000000..b0e9939
--- /dev/null
@@ -0,0 +1,654 @@
+<!DOCTYPE html><html><head><title>Sencha Documentation Project</title><link rel="stylesheet" href="../reset.css" type="text/css"><link rel="stylesheet" href="../prettify.css" type="text/css"><link rel="stylesheet" href="../prettify_sa.css" type="text/css"><script type="text/javascript" src="../prettify.js"></script></head><body onload="prettyPrint()"><pre class="prettyprint"><pre><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:
+ *
+ * {@img Ext.chart.series.Scatter/Ext.chart.series.Scatter.png Ext.chart.series.Scatter chart series}  
+ *
+ *     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: 'bottom',
+ *             fields: ['data1', 'data2', 'data3'],
+ *             title: 'Sample Values',
+ *             grid: true,
+ *             minimum: 0
+ *         }, {
+ *             type: 'Category',
+ *             position: 'left',
+ *             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.
+     */
+
+    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: [{
+                &quot;stroke-width&quot;: 6,
+                &quot;stroke-opacity&quot;: 0.05,
+                stroke: 'rgb(0, 0, 0)'
+            }, {
+                &quot;stroke-width&quot;: 4,
+                &quot;stroke-opacity&quot;: 0.1,
+                stroke: 'rgb(0, 0, 0)'
+            }, {
+                &quot;stroke-width&quot;: 2,
+                &quot;stroke-opacity&quot;: 0.15,
+                stroke: 'rgb(0, 0, 0)'
+            }]
+        });
+        me.group = surface.getGroup(me.seriesId);
+        if (shadow) {
+            for (i = 0, l = me.shadowAttributes.length; i &lt; 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.substore || chart.store,
+            axes = [].concat(me.axis),
+            bbox, xScale, yScale, ln, minX, minY, maxX, maxY, i, axis, ends;
+
+        me.setBBox();
+        bbox = me.bbox;
+
+        for (i = 0, ln = axes.length; i &lt; ln; i++) { 
+            axis = chart.axes.get(axes[i]);
+            if (axis) {
+                ends = axis.calcEnds();
+                if (axis.position == 'top' || axis.position == 'bottom') {
+                    minX = ends.from;
+                    maxX = ends.to;
+                }
+                else {
+                    minY = ends.from;
+                    maxY = ends.to;
+                }
+            }
+        }
+        // If a field was specified without a corresponding axis, create one to get bounds
+        if (me.xField &amp;&amp; !Ext.isNumber(minX)) {
+            axis = Ext.create('Ext.chart.axis.Axis', {
+                chart: chart,
+                fields: [].concat(me.xField)
+            }).calcEnds();
+            minX = axis.from;
+            maxX = axis.to;
+        }
+        if (me.yField &amp;&amp; !Ext.isNumber(minY)) {
+            axis = Ext.create('Ext.chart.axis.Axis', {
+                chart: chart,
+                fields: [].concat(me.yField)
+            }).calcEnds();
+            minY = axis.from;
+            maxY = axis.to;
+        }
+
+        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.substore || chart.store,
+            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;
+
+        store.each(function(record, i) {
+            xValue = record.get(me.xField);
+            yValue = record.get(me.yField);
+            //skip undefined values
+            if (typeof yValue == 'undefined' || (typeof yValue == 'string' &amp;&amp; !yValue)) {
+                //&lt;debug warn&gt;
+                if (Ext.isDefined(Ext.global.console)) {
+                    Ext.global.console.warn(&quot;[Ext.chart.series.Scatter]  Skipping a store element with an undefined value at &quot;, record, xValue, yValue);
+                }
+                //&lt;/debug&gt;
+                return;
+            }
+            // Ensure a value
+            if (typeof xValue == 'string' || typeof xValue == 'object') {
+                xValue = i;
+            }
+            if (typeof yValue == 'string' || typeof yValue == 'object') {
+                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 &amp;&amp; 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 &lt; ln; 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 {
+                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 &lt; 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.substore || chart.store,
+            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()) {
+            return;
+        }
+
+        me.unHighlightItem();
+        me.cleanHighlights();
+
+        attrs = me.getPaths();
+        ln = attrs.length;
+        for (i = 0; i &lt; 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 &lt; lnsh; shindex++) {
+                    shadowAttribute = Ext.apply({}, shadowAttributes[shindex]);
+                    rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({}, { 
+                        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), Ext.apply({ translate: attr }, { hidden: false }), i, store);
+                sprite.setAttributes(rendererAttributes, true);
+                //update shadows
+                for (shindex = 0; shindex &lt; lnsh; shindex++) {
+                    shadowAttribute = shadowAttributes[shindex];
+                    rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({ 
+                        x: attr.x,
+                        y: attr.y
+                    }, shadowAttribute), i, store);
+                    shadows[shindex].setAttributes(rendererAttributes, true);
+                }
+            }
+            me.items[i].sprite = sprite;
+        }
+
+        // Hide unused sprites
+        ln = group.getCount();
+        for (i = attrs.length; i &lt; 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 &lt; bbox.x? bbox.x : x;
+            x = (x + width &gt; bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
+            y = (y - height &lt; 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 &lt; bbox.x ? bbox.x + width : x;
+            x = (x + width &gt; bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
+            y = y - height &lt; bbox.y? bbox.y + height : y;
+            y = (y + height &gt; 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] &gt; 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 &lt; clipRect[0] || (boxx + boxw) &gt; (clipRect[0] + clipRect[2])) {
+            normal[0] *= -1;
+        }
+        if (boxy &lt; clipRect[1] || (boxy + boxh) &gt; (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] &gt; 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: [&quot;M&quot;, cur[0], cur[1], &quot;L&quot;, x, y, &quot;Z&quot;]
+                }
+            }, 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] &gt; 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: [&quot;M&quot;, cur[0], cur[1], &quot;L&quot;, x, y, &quot;Z&quot;]
+            }, 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] &gt; 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 &lt;= x &amp;&amp; point[0] + tolerance &gt;= x &amp;&amp;
+            point[1] - tolerance &lt;= y &amp;&amp; point[1] + tolerance &gt;= y);
+    }
+});
+
+</pre></pre></body></html>
\ No newline at end of file