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>
|