Scatter.html 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  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; }
  10. </style>
  11. <script type="text/javascript">
  12. function highlight() {
  13. document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
  14. }
  15. </script>
  16. </head>
  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
  21. *
  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:
  26. *
  27. * @example
  28. * var store = Ext.create('Ext.data.JsonStore', {
  29. * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
  30. * data: [
  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 }
  36. * ]
  37. * });
  38. *
  39. * Ext.create('Ext.chart.Chart', {
  40. * renderTo: Ext.getBody(),
  41. * width: 500,
  42. * height: 300,
  43. * animate: true,
  44. * theme:'Category2',
  45. * store: store,
  46. * axes: [{
  47. * type: 'Numeric',
  48. * position: 'left',
  49. * fields: ['data2', 'data3'],
  50. * title: 'Sample Values',
  51. * grid: true,
  52. * minimum: 0
  53. * }, {
  54. * type: 'Category',
  55. * position: 'bottom',
  56. * fields: ['name'],
  57. * title: 'Sample Metrics'
  58. * }],
  59. * series: [{
  60. * type: 'scatter',
  61. * markerConfig: {
  62. * radius: 5,
  63. * size: 5
  64. * },
  65. * axis: 'left',
  66. * xField: 'name',
  67. * yField: 'data2'
  68. * }, {
  69. * type: 'scatter',
  70. * markerConfig: {
  71. * radius: 5,
  72. * size: 5
  73. * },
  74. * axis: 'left',
  75. * xField: 'name',
  76. * yField: 'data3'
  77. * }]
  78. * });
  79. *
  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.
  84. *
  85. * @xtype scatter
  86. */
  87. Ext.define('Ext.chart.series.Scatter', {
  88. /* Begin Definitions */
  89. extend: 'Ext.chart.series.Cartesian',
  90. requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.fx.Anim'],
  91. /* End Definitions */
  92. type: 'scatter',
  93. alias: 'series.scatter',
  94. <span id='Ext-chart-series-Scatter-cfg-markerConfig'> /**
  95. </span> * @cfg {Object} markerConfig
  96. * The display style for the scatter series markers.
  97. */
  98. <span id='Ext-chart-series-Scatter-cfg-style'> /**
  99. </span> * @cfg {Object} style
  100. * Append styling properties to this object for it to override theme properties.
  101. */
  102. <span id='Ext-chart-series-Scatter-cfg-axis'> /**
  103. </span> * @cfg {String/Array} axis
  104. * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
  105. * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
  106. * relative scale will be used. If multiple axes are being used, they should both be specified in in the configuration.
  107. */
  108. constructor: function(config) {
  109. this.callParent(arguments);
  110. var me = this,
  111. shadow = me.chart.shadow,
  112. surface = me.chart.surface, i, l;
  113. Ext.apply(me, config, {
  114. style: {},
  115. markerConfig: {},
  116. shadowAttributes: [{
  117. &quot;stroke-width&quot;: 6,
  118. &quot;stroke-opacity&quot;: 0.05,
  119. stroke: 'rgb(0, 0, 0)'
  120. }, {
  121. &quot;stroke-width&quot;: 4,
  122. &quot;stroke-opacity&quot;: 0.1,
  123. stroke: 'rgb(0, 0, 0)'
  124. }, {
  125. &quot;stroke-width&quot;: 2,
  126. &quot;stroke-opacity&quot;: 0.15,
  127. stroke: 'rgb(0, 0, 0)'
  128. }]
  129. });
  130. me.group = surface.getGroup(me.seriesId);
  131. if (shadow) {
  132. for (i = 0, l = me.shadowAttributes.length; i &lt; l; i++) {
  133. me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
  134. }
  135. }
  136. },
  137. // @private Get chart and data boundaries
  138. getBounds: function() {
  139. var me = this,
  140. chart = me.chart,
  141. store = chart.getChartStore(),
  142. chartAxes = chart.axes,
  143. boundAxes = me.getAxesForXAndYFields(),
  144. boundXAxis = boundAxes.xAxis,
  145. boundYAxis = boundAxes.yAxis,
  146. bbox, xScale, yScale, ln, minX, minY, maxX, maxY, i, axis, ends;
  147. me.setBBox();
  148. bbox = me.bbox;
  149. if (axis = chartAxes.get(boundXAxis)) {
  150. ends = axis.applyData();
  151. minX = ends.from;
  152. maxX = ends.to;
  153. }
  154. if (axis = chartAxes.get(boundYAxis)) {
  155. ends = axis.applyData();
  156. minY = ends.from;
  157. maxY = ends.to;
  158. }
  159. // If a field was specified without a corresponding axis, create one to get bounds
  160. if (me.xField &amp;&amp; !Ext.isNumber(minX)) {
  161. axis = me.getMinMaxXValues();
  162. minX = axis[0];
  163. maxX = axis[1];
  164. }
  165. if (me.yField &amp;&amp; !Ext.isNumber(minY)) {
  166. axis = me.getMinMaxYValues();
  167. minY = axis[0];
  168. maxY = axis[1];
  169. }
  170. if (isNaN(minX)) {
  171. minX = 0;
  172. maxX = store.getCount() - 1;
  173. xScale = bbox.width / (store.getCount() - 1);
  174. }
  175. else {
  176. xScale = bbox.width / (maxX - minX);
  177. }
  178. if (isNaN(minY)) {
  179. minY = 0;
  180. maxY = store.getCount() - 1;
  181. yScale = bbox.height / (store.getCount() - 1);
  182. }
  183. else {
  184. yScale = bbox.height / (maxY - minY);
  185. }
  186. return {
  187. bbox: bbox,
  188. minX: minX,
  189. minY: minY,
  190. xScale: xScale,
  191. yScale: yScale
  192. };
  193. },
  194. // @private Build an array of paths for the chart
  195. getPaths: function() {
  196. var me = this,
  197. chart = me.chart,
  198. enableShadows = chart.shadow,
  199. store = chart.getChartStore(),
  200. data = store.data.items,
  201. i, ln, record,
  202. group = me.group,
  203. bounds = me.bounds = me.getBounds(),
  204. bbox = me.bbox,
  205. xScale = bounds.xScale,
  206. yScale = bounds.yScale,
  207. minX = bounds.minX,
  208. minY = bounds.minY,
  209. boxX = bbox.x,
  210. boxY = bbox.y,
  211. boxHeight = bbox.height,
  212. items = me.items = [],
  213. attrs = [],
  214. x, y, xValue, yValue, sprite;
  215. for (i = 0, ln = data.length; i &lt; ln; i++) {
  216. record = data[i];
  217. xValue = record.get(me.xField);
  218. yValue = record.get(me.yField);
  219. //skip undefined or null values
  220. if (typeof yValue == 'undefined' || (typeof yValue == 'string' &amp;&amp; !yValue)
  221. || xValue == null || yValue == null) {
  222. //&lt;debug warn&gt;
  223. if (Ext.isDefined(Ext.global.console)) {
  224. Ext.global.console.warn(&quot;[Ext.chart.series.Scatter] Skipping a store element with a value which is either undefined or null at &quot;, record, xValue, yValue);
  225. }
  226. //&lt;/debug&gt;
  227. continue;
  228. }
  229. // Ensure a value
  230. if (typeof xValue == 'string' || typeof xValue == 'object' &amp;&amp; !Ext.isDate(xValue)) {
  231. xValue = i;
  232. }
  233. if (typeof yValue == 'string' || typeof yValue == 'object' &amp;&amp; !Ext.isDate(yValue)) {
  234. yValue = i;
  235. }
  236. x = boxX + (xValue - minX) * xScale;
  237. y = boxY + boxHeight - (yValue - minY) * yScale;
  238. attrs.push({
  239. x: x,
  240. y: y
  241. });
  242. me.items.push({
  243. series: me,
  244. value: [xValue, yValue],
  245. point: [x, y],
  246. storeItem: record
  247. });
  248. // When resizing, reset before animating
  249. if (chart.animate &amp;&amp; chart.resizing) {
  250. sprite = group.getAt(i);
  251. if (sprite) {
  252. me.resetPoint(sprite);
  253. if (enableShadows) {
  254. me.resetShadow(sprite);
  255. }
  256. }
  257. }
  258. }
  259. return attrs;
  260. },
  261. // @private translate point to the center
  262. resetPoint: function(sprite) {
  263. var bbox = this.bbox;
  264. sprite.setAttributes({
  265. translate: {
  266. x: (bbox.x + bbox.width) / 2,
  267. y: (bbox.y + bbox.height) / 2
  268. }
  269. }, true);
  270. },
  271. // @private translate shadows of a sprite to the center
  272. resetShadow: function(sprite) {
  273. var me = this,
  274. shadows = sprite.shadows,
  275. shadowAttributes = me.shadowAttributes,
  276. ln = me.shadowGroups.length,
  277. bbox = me.bbox,
  278. i, attr;
  279. for (i = 0; i &lt; ln; i++) {
  280. attr = Ext.apply({}, shadowAttributes[i]);
  281. // TODO: fix this with setAttributes
  282. if (attr.translate) {
  283. attr.translate.x += (bbox.x + bbox.width) / 2;
  284. attr.translate.y += (bbox.y + bbox.height) / 2;
  285. }
  286. else {
  287. attr.translate = {
  288. x: (bbox.x + bbox.width) / 2,
  289. y: (bbox.y + bbox.height) / 2
  290. };
  291. }
  292. shadows[i].setAttributes(attr, true);
  293. }
  294. },
  295. // @private create a new point
  296. createPoint: function(attr, type) {
  297. var me = this,
  298. chart = me.chart,
  299. group = me.group,
  300. bbox = me.bbox;
  301. return Ext.chart.Shape[type](chart.surface, Ext.apply({}, {
  302. x: 0,
  303. y: 0,
  304. group: group,
  305. translate: {
  306. x: (bbox.x + bbox.width) / 2,
  307. y: (bbox.y + bbox.height) / 2
  308. }
  309. }, attr));
  310. },
  311. // @private create a new set of shadows for a sprite
  312. createShadow: function(sprite, endMarkerStyle, type) {
  313. var me = this,
  314. chart = me.chart,
  315. shadowGroups = me.shadowGroups,
  316. shadowAttributes = me.shadowAttributes,
  317. lnsh = shadowGroups.length,
  318. bbox = me.bbox,
  319. i, shadow, shadows, attr;
  320. sprite.shadows = shadows = [];
  321. for (i = 0; i &lt; lnsh; i++) {
  322. attr = Ext.apply({}, shadowAttributes[i]);
  323. if (attr.translate) {
  324. attr.translate.x += (bbox.x + bbox.width) / 2;
  325. attr.translate.y += (bbox.y + bbox.height) / 2;
  326. }
  327. else {
  328. Ext.apply(attr, {
  329. translate: {
  330. x: (bbox.x + bbox.width) / 2,
  331. y: (bbox.y + bbox.height) / 2
  332. }
  333. });
  334. }
  335. Ext.apply(attr, endMarkerStyle);
  336. shadow = Ext.chart.Shape[type](chart.surface, Ext.apply({}, {
  337. x: 0,
  338. y: 0,
  339. group: shadowGroups[i]
  340. }, attr));
  341. shadows.push(shadow);
  342. }
  343. },
  344. <span id='Ext-chart-series-Scatter-method-drawSeries'> /**
  345. </span> * Draws the series for the current chart.
  346. */
  347. drawSeries: function() {
  348. var me = this,
  349. chart = me.chart,
  350. store = chart.getChartStore(),
  351. group = me.group,
  352. enableShadows = chart.shadow,
  353. shadowGroups = me.shadowGroups,
  354. shadowAttributes = me.shadowAttributes,
  355. lnsh = shadowGroups.length,
  356. sprite, attrs, attr, ln, i, endMarkerStyle, shindex, type, shadows,
  357. rendererAttributes, shadowAttribute;
  358. endMarkerStyle = Ext.apply(me.markerStyle, me.markerConfig);
  359. type = endMarkerStyle.type;
  360. delete endMarkerStyle.type;
  361. //if the store is empty then there's nothing to be rendered
  362. if (!store || !store.getCount()) {
  363. me.hide();
  364. me.items = [];
  365. return;
  366. }
  367. me.unHighlightItem();
  368. me.cleanHighlights();
  369. attrs = me.getPaths();
  370. ln = attrs.length;
  371. for (i = 0; i &lt; ln; i++) {
  372. attr = attrs[i];
  373. sprite = group.getAt(i);
  374. Ext.apply(attr, endMarkerStyle);
  375. // Create a new sprite if needed (no height)
  376. if (!sprite) {
  377. sprite = me.createPoint(attr, type);
  378. if (enableShadows) {
  379. me.createShadow(sprite, endMarkerStyle, type);
  380. }
  381. }
  382. shadows = sprite.shadows;
  383. if (chart.animate) {
  384. rendererAttributes = me.renderer(sprite, store.getAt(i), { translate: attr }, i, store);
  385. sprite._to = rendererAttributes;
  386. me.onAnimate(sprite, {
  387. to: rendererAttributes
  388. });
  389. //animate shadows
  390. for (shindex = 0; shindex &lt; lnsh; shindex++) {
  391. shadowAttribute = Ext.apply({}, shadowAttributes[shindex]);
  392. rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({}, {
  393. hidden: false,
  394. translate: {
  395. x: attr.x + (shadowAttribute.translate? shadowAttribute.translate.x : 0),
  396. y: attr.y + (shadowAttribute.translate? shadowAttribute.translate.y : 0)
  397. }
  398. }, shadowAttribute), i, store);
  399. me.onAnimate(shadows[shindex], { to: rendererAttributes });
  400. }
  401. }
  402. else {
  403. rendererAttributes = me.renderer(sprite, store.getAt(i), { translate: attr }, i, store);
  404. sprite._to = rendererAttributes;
  405. sprite.setAttributes(rendererAttributes, true);
  406. //animate shadows
  407. for (shindex = 0; shindex &lt; lnsh; shindex++) {
  408. shadowAttribute = Ext.apply({}, shadowAttributes[shindex]);
  409. rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({}, {
  410. hidden: false,
  411. translate: {
  412. x: attr.x + (shadowAttribute.translate? shadowAttribute.translate.x : 0),
  413. y: attr.y + (shadowAttribute.translate? shadowAttribute.translate.y : 0)
  414. }
  415. }, shadowAttribute), i, store);
  416. shadows[shindex].setAttributes(rendererAttributes, true);
  417. }
  418. }
  419. me.items[i].sprite = sprite;
  420. }
  421. // Hide unused sprites
  422. ln = group.getCount();
  423. for (i = attrs.length; i &lt; ln; i++) {
  424. group.getAt(i).hide(true);
  425. }
  426. me.renderLabels();
  427. me.renderCallouts();
  428. },
  429. // @private callback for when creating a label sprite.
  430. onCreateLabel: function(storeItem, item, i, display) {
  431. var me = this,
  432. group = me.labelsGroup,
  433. config = me.label,
  434. endLabelStyle = Ext.apply({}, config, me.seriesLabelStyle),
  435. bbox = me.bbox;
  436. return me.chart.surface.add(Ext.apply({
  437. type: 'text',
  438. group: group,
  439. x: item.point[0],
  440. y: bbox.y + bbox.height / 2
  441. }, endLabelStyle));
  442. },
  443. // @private callback for when placing a label sprite.
  444. onPlaceLabel: function(label, storeItem, item, i, display, animate) {
  445. var me = this,
  446. chart = me.chart,
  447. resizing = chart.resizing,
  448. config = me.label,
  449. format = config.renderer,
  450. field = config.field,
  451. bbox = me.bbox,
  452. x = item.point[0],
  453. y = item.point[1],
  454. radius = item.sprite.attr.radius,
  455. bb, width, height, anim;
  456. label.setAttributes({
  457. text: format(storeItem.get(field)),
  458. hidden: true
  459. }, true);
  460. if (display == 'rotate') {
  461. label.setAttributes({
  462. 'text-anchor': 'start',
  463. 'rotation': {
  464. x: x,
  465. y: y,
  466. degrees: -45
  467. }
  468. }, true);
  469. //correct label position to fit into the box
  470. bb = label.getBBox();
  471. width = bb.width;
  472. height = bb.height;
  473. x = x &lt; bbox.x? bbox.x : x;
  474. x = (x + width &gt; bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
  475. y = (y - height &lt; bbox.y)? bbox.y + height : y;
  476. } else if (display == 'under' || display == 'over') {
  477. //TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
  478. bb = item.sprite.getBBox();
  479. bb.width = bb.width || (radius * 2);
  480. bb.height = bb.height || (radius * 2);
  481. y = y + (display == 'over'? -bb.height : bb.height);
  482. //correct label position to fit into the box
  483. bb = label.getBBox();
  484. width = bb.width/2;
  485. height = bb.height/2;
  486. x = x - width &lt; bbox.x ? bbox.x + width : x;
  487. x = (x + width &gt; bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
  488. y = y - height &lt; bbox.y? bbox.y + height : y;
  489. y = (y + height &gt; bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
  490. }
  491. if (!chart.animate) {
  492. label.setAttributes({
  493. x: x,
  494. y: y
  495. }, true);
  496. label.show(true);
  497. }
  498. else {
  499. if (resizing) {
  500. anim = item.sprite.getActiveAnimation();
  501. if (anim) {
  502. anim.on('afteranimate', function() {
  503. label.setAttributes({
  504. x: x,
  505. y: y
  506. }, true);
  507. label.show(true);
  508. });
  509. }
  510. else {
  511. label.show(true);
  512. }
  513. }
  514. else {
  515. me.onAnimate(label, {
  516. to: {
  517. x: x,
  518. y: y
  519. }
  520. });
  521. }
  522. }
  523. },
  524. // @private callback for when placing a callout sprite.
  525. onPlaceCallout: function(callout, storeItem, item, i, display, animate, index) {
  526. var me = this,
  527. chart = me.chart,
  528. surface = chart.surface,
  529. resizing = chart.resizing,
  530. config = me.callouts,
  531. items = me.items,
  532. cur = item.point,
  533. normal,
  534. bbox = callout.label.getBBox(),
  535. offsetFromViz = 30,
  536. offsetToSide = 10,
  537. offsetBox = 3,
  538. boxx, boxy, boxw, boxh,
  539. p, clipRect = me.bbox,
  540. x, y;
  541. //position
  542. normal = [Math.cos(Math.PI /4), -Math.sin(Math.PI /4)];
  543. x = cur[0] + normal[0] * offsetFromViz;
  544. y = cur[1] + normal[1] * offsetFromViz;
  545. //box position and dimensions
  546. boxx = x + (normal[0] &gt; 0? 0 : -(bbox.width + 2 * offsetBox));
  547. boxy = y - bbox.height /2 - offsetBox;
  548. boxw = bbox.width + 2 * offsetBox;
  549. boxh = bbox.height + 2 * offsetBox;
  550. //now check if we're out of bounds and invert the normal vector correspondingly
  551. //this may add new overlaps between labels (but labels won't be out of bounds).
  552. if (boxx &lt; clipRect[0] || (boxx + boxw) &gt; (clipRect[0] + clipRect[2])) {
  553. normal[0] *= -1;
  554. }
  555. if (boxy &lt; clipRect[1] || (boxy + boxh) &gt; (clipRect[1] + clipRect[3])) {
  556. normal[1] *= -1;
  557. }
  558. //update positions
  559. x = cur[0] + normal[0] * offsetFromViz;
  560. y = cur[1] + normal[1] * offsetFromViz;
  561. //update box position and dimensions
  562. boxx = x + (normal[0] &gt; 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;
  566. if (chart.animate) {
  567. //set the line from the middle of the pie to the box.
  568. me.onAnimate(callout.lines, {
  569. to: {
  570. path: [&quot;M&quot;, cur[0], cur[1], &quot;L&quot;, x, y, &quot;Z&quot;]
  571. }
  572. }, true);
  573. //set box position
  574. me.onAnimate(callout.box, {
  575. to: {
  576. x: boxx,
  577. y: boxy,
  578. width: boxw,
  579. height: boxh
  580. }
  581. }, true);
  582. //set text position
  583. me.onAnimate(callout.label, {
  584. to: {
  585. x: x + (normal[0] &gt; 0? offsetBox : -(bbox.width + offsetBox)),
  586. y: y
  587. }
  588. }, true);
  589. } else {
  590. //set the line from the middle of the pie to the box.
  591. callout.lines.setAttributes({
  592. path: [&quot;M&quot;, cur[0], cur[1], &quot;L&quot;, x, y, &quot;Z&quot;]
  593. }, true);
  594. //set box position
  595. callout.box.setAttributes({
  596. x: boxx,
  597. y: boxy,
  598. width: boxw,
  599. height: boxh
  600. }, true);
  601. //set text position
  602. callout.label.setAttributes({
  603. x: x + (normal[0] &gt; 0? offsetBox : -(bbox.width + offsetBox)),
  604. y: y
  605. }, true);
  606. }
  607. for (p in callout) {
  608. callout[p].show(true);
  609. }
  610. },
  611. // @private handles sprite animation for the series.
  612. onAnimate: function(sprite, attr) {
  613. sprite.show();
  614. return this.callParent(arguments);
  615. },
  616. isItemInPoint: function(x, y, item) {
  617. var point,
  618. tolerance = 10,
  619. abs = Math.abs;
  620. function dist(point) {
  621. var dx = abs(point[0] - x),
  622. dy = abs(point[1] - y);
  623. return Math.sqrt(dx * dx + dy * dy);
  624. }
  625. point = item.point;
  626. return (point[0] - tolerance &lt;= x &amp;&amp; point[0] + tolerance &gt;= x &amp;&amp;
  627. point[1] - tolerance &lt;= y &amp;&amp; point[1] + tolerance &gt;= y);
  628. }
  629. });
  630. </pre>
  631. </body>
  632. </html>