4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5 <title>The source code</title>
6 <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
7 <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
8 <style type="text/css">
9 .highlight { display: block; background-color: #ddd; }
11 <script type="text/javascript">
12 function highlight() {
13 document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
17 <body onload="prettyPrint(); highlight();">
18 <pre class="prettyprint lang-js"><span id='Ext-chart-series-Scatter'>/**
19 </span> * @class Ext.chart.series.Scatter
20 * @extends Ext.chart.series.Cartesian
22 * Creates a Scatter Chart. The scatter plot is useful when trying to display more than two variables in the same visualization.
23 * These variables can be mapped into x, y coordinates and also to an element's radius/size, color, etc.
24 * As with all other series, the Scatter Series must be appended in the *series* Chart array configuration. See the Chart
25 * documentation for more information on creating charts. A typical configuration object for the scatter could be:
28 * var store = Ext.create('Ext.data.JsonStore', {
29 * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
31 * { 'name': 'metric one', 'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8, 'data5': 13 },
32 * { 'name': 'metric two', 'data1': 7, 'data2': 8, 'data3': 16, 'data4': 10, 'data5': 3 },
33 * { 'name': 'metric three', 'data1': 5, 'data2': 2, 'data3': 14, 'data4': 12, 'data5': 7 },
34 * { 'name': 'metric four', 'data1': 2, 'data2': 14, 'data3': 6, 'data4': 1, 'data5': 23 },
35 * { 'name': 'metric five', 'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
39 * Ext.create('Ext.chart.Chart', {
40 * renderTo: Ext.getBody(),
49 * fields: ['data2', 'data3'],
50 * title: 'Sample Values',
57 * title: 'Sample Metrics'
80 * 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,
81 * `data1`, `data2` and `data3` respectively. All x-fields for the series must be the same field, in this case `name`.
82 * Each scatter series has a different styling configuration for markers, specified by the `markerConfig` object. Finally we set the left axis as
83 * axis to show the current values of the elements.
87 Ext.define('Ext.chart.series.Scatter', {
89 /* Begin Definitions */
91 extend: 'Ext.chart.series.Cartesian',
93 requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.fx.Anim'],
98 alias: 'series.scatter',
100 <span id='Ext-chart-series-Scatter-cfg-markerConfig'> /**
101 </span> * @cfg {Object} markerConfig
102 * The display style for the scatter series markers.
105 <span id='Ext-chart-series-Scatter-cfg-style'> /**
106 </span> * @cfg {Object} style
107 * Append styling properties to this object for it to override theme properties.
110 <span id='Ext-chart-series-Scatter-cfg-axis'> /**
111 </span> * @cfg {String/Array} axis
112 * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
113 * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
114 * relative scale will be used. If multiple axes are being used, they should both be specified in in the configuration.
117 constructor: function(config) {
118 this.callParent(arguments);
120 shadow = me.chart.shadow,
121 surface = me.chart.surface, i, l;
122 Ext.apply(me, config, {
126 "stroke-width": 6,
127 "stroke-opacity": 0.05,
128 stroke: 'rgb(0, 0, 0)'
130 "stroke-width": 4,
131 "stroke-opacity": 0.1,
132 stroke: 'rgb(0, 0, 0)'
134 "stroke-width": 2,
135 "stroke-opacity": 0.15,
136 stroke: 'rgb(0, 0, 0)'
139 me.group = surface.getGroup(me.seriesId);
141 for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
142 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
147 // @private Get chart and data boundaries
148 getBounds: function() {
151 store = chart.getChartStore(),
152 axes = [].concat(me.axis),
153 bbox, xScale, yScale, ln, minX, minY, maxX, maxY, i, axis, ends;
158 for (i = 0, ln = axes.length; i < ln; i++) {
159 axis = chart.axes.get(axes[i]);
161 ends = axis.calcEnds();
162 if (axis.position == 'top' || axis.position == 'bottom') {
172 // If a field was specified without a corresponding axis, create one to get bounds
173 if (me.xField && !Ext.isNumber(minX)) {
174 axis = Ext.create('Ext.chart.axis.Axis', {
176 fields: [].concat(me.xField)
181 if (me.yField && !Ext.isNumber(minY)) {
182 axis = Ext.create('Ext.chart.axis.Axis', {
184 fields: [].concat(me.yField)
192 maxX = store.getCount() - 1;
193 xScale = bbox.width / (store.getCount() - 1);
196 xScale = bbox.width / (maxX - minX);
201 maxY = store.getCount() - 1;
202 yScale = bbox.height / (store.getCount() - 1);
205 yScale = bbox.height / (maxY - minY);
217 // @private Build an array of paths for the chart
218 getPaths: function() {
221 enableShadows = chart.shadow,
222 store = chart.getChartStore(),
224 bounds = me.bounds = me.getBounds(),
226 xScale = bounds.xScale,
227 yScale = bounds.yScale,
232 boxHeight = bbox.height,
233 items = me.items = [],
235 x, y, xValue, yValue, sprite;
237 store.each(function(record, i) {
238 xValue = record.get(me.xField);
239 yValue = record.get(me.yField);
240 //skip undefined values
241 if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)) {
243 if (Ext.isDefined(Ext.global.console)) {
244 Ext.global.console.warn("[Ext.chart.series.Scatter] Skipping a store element with an undefined value at ", record, xValue, yValue);
250 if (typeof xValue == 'string' || typeof xValue == 'object' && !Ext.isDate(xValue)) {
253 if (typeof yValue == 'string' || typeof yValue == 'object' && !Ext.isDate(yValue)) {
256 x = boxX + (xValue - minX) * xScale;
257 y = boxY + boxHeight - (yValue - minY) * yScale;
265 value: [xValue, yValue],
270 // When resizing, reset before animating
271 if (chart.animate && chart.resizing) {
272 sprite = group.getAt(i);
274 me.resetPoint(sprite);
276 me.resetShadow(sprite);
284 // @private translate point to the center
285 resetPoint: function(sprite) {
286 var bbox = this.bbox;
287 sprite.setAttributes({
289 x: (bbox.x + bbox.width) / 2,
290 y: (bbox.y + bbox.height) / 2
295 // @private translate shadows of a sprite to the center
296 resetShadow: function(sprite) {
298 shadows = sprite.shadows,
299 shadowAttributes = me.shadowAttributes,
300 ln = me.shadowGroups.length,
303 for (i = 0; i < ln; i++) {
304 attr = Ext.apply({}, shadowAttributes[i]);
305 if (attr.translate) {
306 attr.translate.x += (bbox.x + bbox.width) / 2;
307 attr.translate.y += (bbox.y + bbox.height) / 2;
311 x: (bbox.x + bbox.width) / 2,
312 y: (bbox.y + bbox.height) / 2
315 shadows[i].setAttributes(attr, true);
319 // @private create a new point
320 createPoint: function(attr, type) {
326 return Ext.chart.Shape[type](chart.surface, Ext.apply({}, {
331 x: (bbox.x + bbox.width) / 2,
332 y: (bbox.y + bbox.height) / 2
337 // @private create a new set of shadows for a sprite
338 createShadow: function(sprite, endMarkerStyle, type) {
341 shadowGroups = me.shadowGroups,
342 shadowAttributes = me.shadowAttributes,
343 lnsh = shadowGroups.length,
345 i, shadow, shadows, attr;
347 sprite.shadows = shadows = [];
349 for (i = 0; i < lnsh; i++) {
350 attr = Ext.apply({}, shadowAttributes[i]);
351 if (attr.translate) {
352 attr.translate.x += (bbox.x + bbox.width) / 2;
353 attr.translate.y += (bbox.y + bbox.height) / 2;
358 x: (bbox.x + bbox.width) / 2,
359 y: (bbox.y + bbox.height) / 2
363 Ext.apply(attr, endMarkerStyle);
364 shadow = Ext.chart.Shape[type](chart.surface, Ext.apply({}, {
367 group: shadowGroups[i]
369 shadows.push(shadow);
373 <span id='Ext-chart-series-Scatter-method-drawSeries'> /**
374 </span> * Draws the series for the current chart.
376 drawSeries: function() {
379 store = chart.getChartStore(),
381 enableShadows = chart.shadow,
382 shadowGroups = me.shadowGroups,
383 shadowAttributes = me.shadowAttributes,
384 lnsh = shadowGroups.length,
385 sprite, attrs, attr, ln, i, endMarkerStyle, shindex, type, shadows,
386 rendererAttributes, shadowAttribute;
388 endMarkerStyle = Ext.apply(me.markerStyle, me.markerConfig);
389 type = endMarkerStyle.type;
390 delete endMarkerStyle.type;
392 //if the store is empty then there's nothing to be rendered
393 if (!store || !store.getCount()) {
397 me.unHighlightItem();
398 me.cleanHighlights();
400 attrs = me.getPaths();
402 for (i = 0; i < ln; i++) {
404 sprite = group.getAt(i);
405 Ext.apply(attr, endMarkerStyle);
407 // Create a new sprite if needed (no height)
409 sprite = me.createPoint(attr, type);
411 me.createShadow(sprite, endMarkerStyle, type);
415 shadows = sprite.shadows;
417 rendererAttributes = me.renderer(sprite, store.getAt(i), { translate: attr }, i, store);
418 sprite._to = rendererAttributes;
419 me.onAnimate(sprite, {
420 to: rendererAttributes
423 for (shindex = 0; shindex < lnsh; shindex++) {
424 shadowAttribute = Ext.apply({}, shadowAttributes[shindex]);
425 rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({}, {
428 x: attr.x + (shadowAttribute.translate? shadowAttribute.translate.x : 0),
429 y: attr.y + (shadowAttribute.translate? shadowAttribute.translate.y : 0)
431 }, shadowAttribute), i, store);
432 me.onAnimate(shadows[shindex], { to: rendererAttributes });
436 rendererAttributes = me.renderer(sprite, store.getAt(i), { translate: attr }, i, store);
437 sprite._to = rendererAttributes;
438 sprite.setAttributes(rendererAttributes, true);
440 for (shindex = 0; shindex < lnsh; shindex++) {
441 shadowAttribute = Ext.apply({}, shadowAttributes[shindex]);
442 rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({}, {
445 x: attr.x + (shadowAttribute.translate? shadowAttribute.translate.x : 0),
446 y: attr.y + (shadowAttribute.translate? shadowAttribute.translate.y : 0)
448 }, shadowAttribute), i, store);
449 shadows[shindex].setAttributes(rendererAttributes, true);
452 me.items[i].sprite = sprite;
455 // Hide unused sprites
456 ln = group.getCount();
457 for (i = attrs.length; i < ln; i++) {
458 group.getAt(i).hide(true);
464 // @private callback for when creating a label sprite.
465 onCreateLabel: function(storeItem, item, i, display) {
467 group = me.labelsGroup,
469 endLabelStyle = Ext.apply({}, config, me.seriesLabelStyle),
472 return me.chart.surface.add(Ext.apply({
476 y: bbox.y + bbox.height / 2
480 // @private callback for when placing a label sprite.
481 onPlaceLabel: function(label, storeItem, item, i, display, animate) {
484 resizing = chart.resizing,
486 format = config.renderer,
487 field = config.field,
491 radius = item.sprite.attr.radius,
492 bb, width, height, anim;
494 label.setAttributes({
495 text: format(storeItem.get(field)),
499 if (display == 'rotate') {
500 label.setAttributes({
501 'text-anchor': 'start',
508 //correct label position to fit into the box
509 bb = label.getBBox();
512 x = x < bbox.x? bbox.x : x;
513 x = (x + width > bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
514 y = (y - height < bbox.y)? bbox.y + height : y;
516 } else if (display == 'under' || display == 'over') {
517 //TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
518 bb = item.sprite.getBBox();
519 bb.width = bb.width || (radius * 2);
520 bb.height = bb.height || (radius * 2);
521 y = y + (display == 'over'? -bb.height : bb.height);
522 //correct label position to fit into the box
523 bb = label.getBBox();
525 height = bb.height/2;
526 x = x - width < bbox.x ? bbox.x + width : x;
527 x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
528 y = y - height < bbox.y? bbox.y + height : y;
529 y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
532 if (!chart.animate) {
533 label.setAttributes({
541 anim = item.sprite.getActiveAnimation();
543 anim.on('afteranimate', function() {
544 label.setAttributes({
556 me.onAnimate(label, {
566 // @private callback for when placing a callout sprite.
567 onPlaceCallout: function(callout, storeItem, item, i, display, animate, index) {
570 surface = chart.surface,
571 resizing = chart.resizing,
572 config = me.callouts,
576 bbox = callout.label.getBBox(),
580 boxx, boxy, boxw, boxh,
581 p, clipRect = me.bbox,
585 normal = [Math.cos(Math.PI /4), -Math.sin(Math.PI /4)];
586 x = cur[0] + normal[0] * offsetFromViz;
587 y = cur[1] + normal[1] * offsetFromViz;
589 //box position and dimensions
590 boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
591 boxy = y - bbox.height /2 - offsetBox;
592 boxw = bbox.width + 2 * offsetBox;
593 boxh = bbox.height + 2 * offsetBox;
595 //now check if we're out of bounds and invert the normal vector correspondingly
596 //this may add new overlaps between labels (but labels won't be out of bounds).
597 if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
600 if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
605 x = cur[0] + normal[0] * offsetFromViz;
606 y = cur[1] + normal[1] * offsetFromViz;
608 //update box position and dimensions
609 boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
610 boxy = y - bbox.height /2 - offsetBox;
611 boxw = bbox.width + 2 * offsetBox;
612 boxh = bbox.height + 2 * offsetBox;
615 //set the line from the middle of the pie to the box.
616 me.onAnimate(callout.lines, {
618 path: ["M", cur[0], cur[1], "L", x, y, "Z"]
622 me.onAnimate(callout.box, {
631 me.onAnimate(callout.label, {
633 x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
638 //set the line from the middle of the pie to the box.
639 callout.lines.setAttributes({
640 path: ["M", cur[0], cur[1], "L", x, y, "Z"]
643 callout.box.setAttributes({
650 callout.label.setAttributes({
651 x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
656 callout[p].show(true);
660 // @private handles sprite animation for the series.
661 onAnimate: function(sprite, attr) {
663 return this.callParent(arguments);
666 isItemInPoint: function(x, y, item) {
671 function dist(point) {
672 var dx = abs(point[0] - x),
673 dy = abs(point[1] - y);
674 return Math.sqrt(dx * dx + dy * dy);
677 return (point[0] - tolerance <= x && point[0] + tolerance >= x &&
678 point[1] - tolerance <= y && point[1] + tolerance >= y);