1 <!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'>/**
2 </span> * @class Ext.chart.series.Scatter
3 * @extends Ext.chart.series.Cartesian
5 * Creates a Scatter Chart. The scatter plot is useful when trying to display more than two variables in the same visualization.
6 * These variables can be mapped into x, y coordinates and also to an element's radius/size, color, etc.
7 * As with all other series, the Scatter Series must be appended in the *series* Chart array configuration. See the Chart
8 * documentation for more information on creating charts. A typical configuration object for the scatter could be:
10 * {@img Ext.chart.series.Scatter/Ext.chart.series.Scatter.png Ext.chart.series.Scatter chart series}
12 * var store = Ext.create('Ext.data.JsonStore', {
13 * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
15 * {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
16 * {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
17 * {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
18 * {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
19 * {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
23 * Ext.create('Ext.chart.Chart', {
24 * renderTo: Ext.getBody(),
33 * fields: ['data1', 'data2', 'data3'],
34 * title: 'Sample Values',
41 * title: 'Sample Metrics'
64 * 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,
65 * `data1`, `data2` and `data3` respectively. All x-fields for the series must be the same field, in this case `name`.
66 * Each scatter series has a different styling configuration for markers, specified by the `markerConfig` object. Finally we set the left axis as
67 * axis to show the current values of the elements.
71 Ext.define('Ext.chart.series.Scatter', {
73 /* Begin Definitions */
75 extend: 'Ext.chart.series.Cartesian',
77 requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.fx.Anim'],
82 alias: 'series.scatter',
84 <span id='Ext-chart.series.Scatter-cfg-markerConfig'> /**
85 </span> * @cfg {Object} markerConfig
86 * The display style for the scatter series markers.
89 <span id='Ext-chart.series.Scatter-cfg-style'> /**
90 </span> * @cfg {Object} style
91 * Append styling properties to this object for it to override theme properties.
94 constructor: function(config) {
95 this.callParent(arguments);
97 shadow = me.chart.shadow,
98 surface = me.chart.surface, i, l;
99 Ext.apply(me, config, {
103 "stroke-width": 6,
104 "stroke-opacity": 0.05,
105 stroke: 'rgb(0, 0, 0)'
107 "stroke-width": 4,
108 "stroke-opacity": 0.1,
109 stroke: 'rgb(0, 0, 0)'
111 "stroke-width": 2,
112 "stroke-opacity": 0.15,
113 stroke: 'rgb(0, 0, 0)'
116 me.group = surface.getGroup(me.seriesId);
118 for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
119 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
124 // @private Get chart and data boundaries
125 getBounds: function() {
128 store = chart.substore || chart.store,
129 axes = [].concat(me.axis),
130 bbox, xScale, yScale, ln, minX, minY, maxX, maxY, i, axis, ends;
135 for (i = 0, ln = axes.length; i < ln; i++) {
136 axis = chart.axes.get(axes[i]);
138 ends = axis.calcEnds();
139 if (axis.position == 'top' || axis.position == 'bottom') {
149 // If a field was specified without a corresponding axis, create one to get bounds
150 if (me.xField && !Ext.isNumber(minX)) {
151 axis = Ext.create('Ext.chart.axis.Axis', {
153 fields: [].concat(me.xField)
158 if (me.yField && !Ext.isNumber(minY)) {
159 axis = Ext.create('Ext.chart.axis.Axis', {
161 fields: [].concat(me.yField)
169 maxX = store.getCount() - 1;
170 xScale = bbox.width / (store.getCount() - 1);
173 xScale = bbox.width / (maxX - minX);
178 maxY = store.getCount() - 1;
179 yScale = bbox.height / (store.getCount() - 1);
182 yScale = bbox.height / (maxY - minY);
194 // @private Build an array of paths for the chart
195 getPaths: function() {
198 enableShadows = chart.shadow,
199 store = chart.substore || chart.store,
201 bounds = me.bounds = me.getBounds(),
203 xScale = bounds.xScale,
204 yScale = bounds.yScale,
209 boxHeight = bbox.height,
210 items = me.items = [],
212 x, y, xValue, yValue, sprite;
214 store.each(function(record, i) {
215 xValue = record.get(me.xField);
216 yValue = record.get(me.yField);
217 //skip undefined values
218 if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)) {
220 if (Ext.isDefined(Ext.global.console)) {
221 Ext.global.console.warn("[Ext.chart.series.Scatter] Skipping a store element with an undefined value at ", record, xValue, yValue);
227 if (typeof xValue == 'string' || typeof xValue == 'object') {
230 if (typeof yValue == 'string' || typeof yValue == 'object') {
233 x = boxX + (xValue - minX) * xScale;
234 y = boxY + boxHeight - (yValue - minY) * yScale;
242 value: [xValue, yValue],
247 // When resizing, reset before animating
248 if (chart.animate && chart.resizing) {
249 sprite = group.getAt(i);
251 me.resetPoint(sprite);
253 me.resetShadow(sprite);
261 // @private translate point to the center
262 resetPoint: function(sprite) {
263 var bbox = this.bbox;
264 sprite.setAttributes({
266 x: (bbox.x + bbox.width) / 2,
267 y: (bbox.y + bbox.height) / 2
272 // @private translate shadows of a sprite to the center
273 resetShadow: function(sprite) {
275 shadows = sprite.shadows,
276 shadowAttributes = me.shadowAttributes,
277 ln = me.shadowGroups.length,
280 for (i = 0; i < ln; i++) {
281 attr = Ext.apply({}, shadowAttributes[i]);
282 if (attr.translate) {
283 attr.translate.x += (bbox.x + bbox.width) / 2;
284 attr.translate.y += (bbox.y + bbox.height) / 2;
288 x: (bbox.x + bbox.width) / 2,
289 y: (bbox.y + bbox.height) / 2
292 shadows[i].setAttributes(attr, true);
296 // @private create a new point
297 createPoint: function(attr, type) {
303 return Ext.chart.Shape[type](chart.surface, Ext.apply({}, {
308 x: (bbox.x + bbox.width) / 2,
309 y: (bbox.y + bbox.height) / 2
314 // @private create a new set of shadows for a sprite
315 createShadow: function(sprite, endMarkerStyle, type) {
318 shadowGroups = me.shadowGroups,
319 shadowAttributes = me.shadowAttributes,
320 lnsh = shadowGroups.length,
322 i, shadow, shadows, attr;
324 sprite.shadows = shadows = [];
326 for (i = 0; i < lnsh; i++) {
327 attr = Ext.apply({}, shadowAttributes[i]);
328 if (attr.translate) {
329 attr.translate.x += (bbox.x + bbox.width) / 2;
330 attr.translate.y += (bbox.y + bbox.height) / 2;
335 x: (bbox.x + bbox.width) / 2,
336 y: (bbox.y + bbox.height) / 2
340 Ext.apply(attr, endMarkerStyle);
341 shadow = Ext.chart.Shape[type](chart.surface, Ext.apply({}, {
344 group: shadowGroups[i]
346 shadows.push(shadow);
350 <span id='Ext-chart.series.Scatter-method-drawSeries'> /**
351 </span> * Draws the series for the current chart.
353 drawSeries: function() {
356 store = chart.substore || chart.store,
358 enableShadows = chart.shadow,
359 shadowGroups = me.shadowGroups,
360 shadowAttributes = me.shadowAttributes,
361 lnsh = shadowGroups.length,
362 sprite, attrs, attr, ln, i, endMarkerStyle, shindex, type, shadows,
363 rendererAttributes, shadowAttribute;
365 endMarkerStyle = Ext.apply(me.markerStyle, me.markerConfig);
366 type = endMarkerStyle.type;
367 delete endMarkerStyle.type;
369 //if the store is empty then there's nothing to be rendered
370 if (!store || !store.getCount()) {
374 me.unHighlightItem();
375 me.cleanHighlights();
377 attrs = me.getPaths();
379 for (i = 0; i < ln; i++) {
381 sprite = group.getAt(i);
382 Ext.apply(attr, endMarkerStyle);
384 // Create a new sprite if needed (no height)
386 sprite = me.createPoint(attr, type);
388 me.createShadow(sprite, endMarkerStyle, type);
392 shadows = sprite.shadows;
394 rendererAttributes = me.renderer(sprite, store.getAt(i), { translate: attr }, i, store);
395 sprite._to = rendererAttributes;
396 me.onAnimate(sprite, {
397 to: rendererAttributes
400 for (shindex = 0; shindex < lnsh; shindex++) {
401 shadowAttribute = Ext.apply({}, shadowAttributes[shindex]);
402 rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({}, {
404 x: attr.x + (shadowAttribute.translate? shadowAttribute.translate.x : 0),
405 y: attr.y + (shadowAttribute.translate? shadowAttribute.translate.y : 0)
407 }, shadowAttribute), i, store);
408 me.onAnimate(shadows[shindex], { to: rendererAttributes });
412 rendererAttributes = me.renderer(sprite, store.getAt(i), Ext.apply({ translate: attr }, { hidden: false }), i, store);
413 sprite.setAttributes(rendererAttributes, true);
415 for (shindex = 0; shindex < lnsh; shindex++) {
416 shadowAttribute = shadowAttributes[shindex];
417 rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({
420 }, shadowAttribute), i, store);
421 shadows[shindex].setAttributes(rendererAttributes, true);
424 me.items[i].sprite = sprite;
427 // Hide unused sprites
428 ln = group.getCount();
429 for (i = attrs.length; i < ln; i++) {
430 group.getAt(i).hide(true);
436 // @private callback for when creating a label sprite.
437 onCreateLabel: function(storeItem, item, i, display) {
439 group = me.labelsGroup,
441 endLabelStyle = Ext.apply({}, config, me.seriesLabelStyle),
444 return me.chart.surface.add(Ext.apply({
448 y: bbox.y + bbox.height / 2
452 // @private callback for when placing a label sprite.
453 onPlaceLabel: function(label, storeItem, item, i, display, animate) {
456 resizing = chart.resizing,
458 format = config.renderer,
459 field = config.field,
463 radius = item.sprite.attr.radius,
464 bb, width, height, anim;
466 label.setAttributes({
467 text: format(storeItem.get(field)),
471 if (display == 'rotate') {
472 label.setAttributes({
473 'text-anchor': 'start',
480 //correct label position to fit into the box
481 bb = label.getBBox();
484 x = x < bbox.x? bbox.x : x;
485 x = (x + width > bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
486 y = (y - height < bbox.y)? bbox.y + height : y;
488 } else if (display == 'under' || display == 'over') {
489 //TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
490 bb = item.sprite.getBBox();
491 bb.width = bb.width || (radius * 2);
492 bb.height = bb.height || (radius * 2);
493 y = y + (display == 'over'? -bb.height : bb.height);
494 //correct label position to fit into the box
495 bb = label.getBBox();
497 height = bb.height/2;
498 x = x - width < bbox.x ? bbox.x + width : x;
499 x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
500 y = y - height < bbox.y? bbox.y + height : y;
501 y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
504 if (!chart.animate) {
505 label.setAttributes({
513 anim = item.sprite.getActiveAnimation();
515 anim.on('afteranimate', function() {
516 label.setAttributes({
528 me.onAnimate(label, {
538 // @private callback for when placing a callout sprite.
539 onPlaceCallout: function(callout, storeItem, item, i, display, animate, index) {
542 surface = chart.surface,
543 resizing = chart.resizing,
544 config = me.callouts,
548 bbox = callout.label.getBBox(),
552 boxx, boxy, boxw, boxh,
553 p, clipRect = me.bbox,
557 normal = [Math.cos(Math.PI /4), -Math.sin(Math.PI /4)];
558 x = cur[0] + normal[0] * offsetFromViz;
559 y = cur[1] + normal[1] * offsetFromViz;
561 //box position and dimensions
562 boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
563 boxy = y - bbox.height /2 - offsetBox;
564 boxw = bbox.width + 2 * offsetBox;
565 boxh = bbox.height + 2 * offsetBox;
567 //now check if we're out of bounds and invert the normal vector correspondingly
568 //this may add new overlaps between labels (but labels won't be out of bounds).
569 if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
572 if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
577 x = cur[0] + normal[0] * offsetFromViz;
578 y = cur[1] + normal[1] * offsetFromViz;
580 //update box position and dimensions
581 boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
582 boxy = y - bbox.height /2 - offsetBox;
583 boxw = bbox.width + 2 * offsetBox;
584 boxh = bbox.height + 2 * offsetBox;
587 //set the line from the middle of the pie to the box.
588 me.onAnimate(callout.lines, {
590 path: ["M", cur[0], cur[1], "L", x, y, "Z"]
594 me.onAnimate(callout.box, {
603 me.onAnimate(callout.label, {
605 x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
610 //set the line from the middle of the pie to the box.
611 callout.lines.setAttributes({
612 path: ["M", cur[0], cur[1], "L", x, y, "Z"]
615 callout.box.setAttributes({
622 callout.label.setAttributes({
623 x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
628 callout[p].show(true);
632 // @private handles sprite animation for the series.
633 onAnimate: function(sprite, attr) {
635 return this.callParent(arguments);
638 isItemInPoint: function(x, y, item) {
643 function dist(point) {
644 var dx = abs(point[0] - x),
645 dy = abs(point[1] - y);
646 return Math.sqrt(dx * dx + dy * dy);
649 return (point[0] - tolerance <= x && point[0] + tolerance >= x &&
650 point[1] - tolerance <= y && point[1] + tolerance >= y);
654 </pre></pre></body></html>