Line.html 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124
  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-Line'>/**
  19. </span> * @class Ext.chart.series.Line
  20. * @extends Ext.chart.series.Cartesian
  21. *
  22. * Creates a Line Chart. A Line Chart is a useful visualization technique to display quantitative information for different
  23. * categories or other real values (as opposed to the bar chart), that can show some progression (or regression) in the dataset.
  24. * As with all other series, the Line Series must be appended in the *series* Chart array configuration. See the Chart
  25. * documentation for more information. A typical configuration object for the line series 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': 4, 'data2': 4, '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. * store: store,
  45. * axes: [
  46. * {
  47. * type: 'Numeric',
  48. * position: 'left',
  49. * fields: ['data1', 'data2'],
  50. * label: {
  51. * renderer: Ext.util.Format.numberRenderer('0,0')
  52. * },
  53. * title: 'Sample Values',
  54. * grid: true,
  55. * minimum: 0
  56. * },
  57. * {
  58. * type: 'Category',
  59. * position: 'bottom',
  60. * fields: ['name'],
  61. * title: 'Sample Metrics'
  62. * }
  63. * ],
  64. * series: [
  65. * {
  66. * type: 'line',
  67. * highlight: {
  68. * size: 7,
  69. * radius: 7
  70. * },
  71. * axis: 'left',
  72. * xField: 'name',
  73. * yField: 'data1',
  74. * markerConfig: {
  75. * type: 'cross',
  76. * size: 4,
  77. * radius: 4,
  78. * 'stroke-width': 0
  79. * }
  80. * },
  81. * {
  82. * type: 'line',
  83. * highlight: {
  84. * size: 7,
  85. * radius: 7
  86. * },
  87. * axis: 'left',
  88. * fill: true,
  89. * xField: 'name',
  90. * yField: 'data2',
  91. * markerConfig: {
  92. * type: 'circle',
  93. * size: 4,
  94. * radius: 4,
  95. * 'stroke-width': 0
  96. * }
  97. * }
  98. * ]
  99. * });
  100. *
  101. * In this configuration we're adding two series (or lines), one bound to the `data1`
  102. * property of the store and the other to `data3`. The type for both configurations is
  103. * `line`. The `xField` for both series is the same, the name propert of the store.
  104. * Both line series share the same axis, the left axis. You can set particular marker
  105. * configuration by adding properties onto the markerConfig object. Both series have
  106. * an object as highlight so that markers animate smoothly to the properties in highlight
  107. * when hovered. The second series has `fill=true` which means that the line will also
  108. * have an area below it of the same color.
  109. *
  110. * **Note:** In the series definition remember to explicitly set the axis to bind the
  111. * values of the line series to. This can be done by using the `axis` configuration property.
  112. */
  113. Ext.define('Ext.chart.series.Line', {
  114. /* Begin Definitions */
  115. extend: 'Ext.chart.series.Cartesian',
  116. alternateClassName: ['Ext.chart.LineSeries', 'Ext.chart.LineChart'],
  117. requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.draw.Draw', 'Ext.fx.Anim'],
  118. /* End Definitions */
  119. type: 'line',
  120. alias: 'series.line',
  121. <span id='Ext-chart-series-Line-cfg-axis'> /**
  122. </span> * @cfg {String} axis
  123. * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
  124. * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
  125. * relative scale will be used.
  126. */
  127. <span id='Ext-chart-series-Line-cfg-selectionTolerance'> /**
  128. </span> * @cfg {Number} selectionTolerance
  129. * The offset distance from the cursor position to the line series to trigger events (then used for highlighting series, etc).
  130. */
  131. selectionTolerance: 20,
  132. <span id='Ext-chart-series-Line-cfg-showMarkers'> /**
  133. </span> * @cfg {Boolean} showMarkers
  134. * Whether markers should be displayed at the data points along the line. If true,
  135. * then the {@link #markerConfig} config item will determine the markers' styling.
  136. */
  137. showMarkers: true,
  138. <span id='Ext-chart-series-Line-cfg-markerConfig'> /**
  139. </span> * @cfg {Object} markerConfig
  140. * The display style for the markers. Only used if {@link #showMarkers} is true.
  141. * The markerConfig is a configuration object containing the same set of properties defined in
  142. * the Sprite class. For example, if we were to set red circles as markers to the line series we could
  143. * pass the object:
  144. *
  145. &lt;pre&gt;&lt;code&gt;
  146. markerConfig: {
  147. type: 'circle',
  148. radius: 4,
  149. 'fill': '#f00'
  150. }
  151. &lt;/code&gt;&lt;/pre&gt;
  152. */
  153. markerConfig: {},
  154. <span id='Ext-chart-series-Line-cfg-style'> /**
  155. </span> * @cfg {Object} style
  156. * An object containing style properties for the visualization lines and fill.
  157. * These styles will override the theme styles. The following are valid style properties:
  158. *
  159. * - `stroke` - an rgb or hex color string for the background color of the line
  160. * - `stroke-width` - the width of the stroke (integer)
  161. * - `fill` - the background fill color string (hex or rgb), only works if {@link #fill} is `true`
  162. * - `opacity` - the opacity of the line and the fill color (decimal)
  163. *
  164. * Example usage:
  165. *
  166. * style: {
  167. * stroke: '#00ff00',
  168. * 'stroke-width': 10,
  169. * fill: '#80A080',
  170. * opacity: 0.2
  171. * }
  172. */
  173. style: {},
  174. <span id='Ext-chart-series-Line-cfg-smooth'> /**
  175. </span> * @cfg {Boolean/Number} smooth
  176. * If set to `true` or a non-zero number, the line will be smoothed/rounded around its points; otherwise
  177. * straight line segments will be drawn.
  178. *
  179. * A numeric value is interpreted as a divisor of the horizontal distance between consecutive points in
  180. * the line; larger numbers result in sharper curves while smaller numbers result in smoother curves.
  181. *
  182. * If set to `true` then a default numeric value of 3 will be used. Defaults to `false`.
  183. */
  184. smooth: false,
  185. <span id='Ext-chart-series-Line-property-defaultSmoothness'> /**
  186. </span> * @private Default numeric smoothing value to be used when {@link #smooth} = true.
  187. */
  188. defaultSmoothness: 3,
  189. <span id='Ext-chart-series-Line-cfg-fill'> /**
  190. </span> * @cfg {Boolean} fill
  191. * If true, the area below the line will be filled in using the {@link #style eefill} and
  192. * {@link #style opacity} config properties. Defaults to false.
  193. */
  194. fill: false,
  195. constructor: function(config) {
  196. this.callParent(arguments);
  197. var me = this,
  198. surface = me.chart.surface,
  199. shadow = me.chart.shadow,
  200. i, l;
  201. config.highlightCfg = Ext.Object.merge({ 'stroke-width': 3 }, config.highlightCfg);
  202. Ext.apply(me, config, {
  203. shadowAttributes: [{
  204. &quot;stroke-width&quot;: 6,
  205. &quot;stroke-opacity&quot;: 0.05,
  206. stroke: 'rgb(0, 0, 0)',
  207. translate: {
  208. x: 1,
  209. y: 1
  210. }
  211. }, {
  212. &quot;stroke-width&quot;: 4,
  213. &quot;stroke-opacity&quot;: 0.1,
  214. stroke: 'rgb(0, 0, 0)',
  215. translate: {
  216. x: 1,
  217. y: 1
  218. }
  219. }, {
  220. &quot;stroke-width&quot;: 2,
  221. &quot;stroke-opacity&quot;: 0.15,
  222. stroke: 'rgb(0, 0, 0)',
  223. translate: {
  224. x: 1,
  225. y: 1
  226. }
  227. }]
  228. });
  229. me.group = surface.getGroup(me.seriesId);
  230. if (me.showMarkers) {
  231. me.markerGroup = surface.getGroup(me.seriesId + '-markers');
  232. }
  233. if (shadow) {
  234. for (i = 0, l = me.shadowAttributes.length; i &lt; l; i++) {
  235. me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
  236. }
  237. }
  238. },
  239. // @private makes an average of points when there are more data points than pixels to be rendered.
  240. shrink: function(xValues, yValues, size) {
  241. // Start at the 2nd point...
  242. var len = xValues.length,
  243. ratio = Math.floor(len / size),
  244. i = 1,
  245. xSum = 0,
  246. ySum = 0,
  247. xRes = [+xValues[0]],
  248. yRes = [+yValues[0]];
  249. for (; i &lt; len; ++i) {
  250. xSum += +xValues[i] || 0;
  251. ySum += +yValues[i] || 0;
  252. if (i % ratio == 0) {
  253. xRes.push(xSum/ratio);
  254. yRes.push(ySum/ratio);
  255. xSum = 0;
  256. ySum = 0;
  257. }
  258. }
  259. return {
  260. x: xRes,
  261. y: yRes
  262. };
  263. },
  264. <span id='Ext-chart-series-Line-method-drawSeries'> /**
  265. </span> * Draws the series for the current chart.
  266. */
  267. drawSeries: function() {
  268. var me = this,
  269. chart = me.chart,
  270. chartAxes = chart.axes,
  271. store = chart.getChartStore(),
  272. data = store.data.items,
  273. record,
  274. storeCount = store.getCount(),
  275. surface = me.chart.surface,
  276. bbox = {},
  277. group = me.group,
  278. showMarkers = me.showMarkers,
  279. markerGroup = me.markerGroup,
  280. enableShadows = chart.shadow,
  281. shadowGroups = me.shadowGroups,
  282. shadowAttributes = me.shadowAttributes,
  283. smooth = me.smooth,
  284. lnsh = shadowGroups.length,
  285. dummyPath = [&quot;M&quot;],
  286. path = [&quot;M&quot;],
  287. renderPath = [&quot;M&quot;],
  288. smoothPath = [&quot;M&quot;],
  289. markerIndex = chart.markerIndex,
  290. axes = [].concat(me.axis),
  291. shadowBarAttr,
  292. xValues = [],
  293. xValueMap = {},
  294. yValues = [],
  295. yValueMap = {},
  296. onbreak = false,
  297. storeIndices = [],
  298. markerStyle = me.markerStyle,
  299. seriesStyle = me.seriesStyle,
  300. colorArrayStyle = me.colorArrayStyle,
  301. colorArrayLength = colorArrayStyle &amp;&amp; colorArrayStyle.length || 0,
  302. isNumber = Ext.isNumber,
  303. seriesIdx = me.seriesIdx,
  304. boundAxes = me.getAxesForXAndYFields(),
  305. boundXAxis = boundAxes.xAxis,
  306. boundYAxis = boundAxes.yAxis,
  307. shadows, shadow, shindex, fromPath, fill, fillPath, rendererAttributes,
  308. x, y, prevX, prevY, firstX, firstY, markerCount, i, j, ln, axis, ends, marker, markerAux, item, xValue,
  309. yValue, coords, xScale, yScale, minX, maxX, minY, maxY, line, animation, endMarkerStyle,
  310. endLineStyle, type, count, opacity, lineOpacity, fillOpacity, fillDefaultValue;
  311. if (me.fireEvent('beforedraw', me) === false) {
  312. return;
  313. }
  314. //if store is empty or the series is excluded in the legend then there's nothing to draw.
  315. if (!storeCount || me.seriesIsHidden) {
  316. me.hide();
  317. me.items = [];
  318. if (me.line) {
  319. me.line.hide(true);
  320. if (me.line.shadows) {
  321. shadows = me.line.shadows;
  322. for (j = 0, lnsh = shadows.length; j &lt; lnsh; j++) {
  323. shadow = shadows[j];
  324. shadow.hide(true);
  325. }
  326. }
  327. if (me.fillPath) {
  328. me.fillPath.hide(true);
  329. }
  330. }
  331. me.line = null;
  332. me.fillPath = null;
  333. return;
  334. }
  335. //prepare style objects for line and markers
  336. endMarkerStyle = Ext.apply(markerStyle || {}, me.markerConfig, {
  337. fill: me.seriesStyle.fill || colorArrayStyle[seriesIdx % colorArrayStyle.length]
  338. });
  339. type = endMarkerStyle.type;
  340. delete endMarkerStyle.type;
  341. endLineStyle = seriesStyle;
  342. //if no stroke with is specified force it to 0.5 because this is
  343. //about making *lines*
  344. if (!endLineStyle['stroke-width']) {
  345. endLineStyle['stroke-width'] = 0.5;
  346. }
  347. //set opacity values
  348. opacity = 'opacity' in endLineStyle ? endLineStyle.opacity : 1;
  349. fillDefaultValue = 'opacity' in endLineStyle ? endLineStyle.opacity : 0.3;
  350. lineOpacity = 'lineOpacity' in endLineStyle ? endLineStyle.lineOpacity : opacity;
  351. fillOpacity = 'fillOpacity' in endLineStyle ? endLineStyle.fillOpacity : fillDefaultValue;
  352. //If we're using a time axis and we need to translate the points,
  353. //then reuse the first markers as the last markers.
  354. if (markerIndex &amp;&amp; markerGroup &amp;&amp; markerGroup.getCount()) {
  355. for (i = 0; i &lt; markerIndex; i++) {
  356. marker = markerGroup.getAt(i);
  357. markerGroup.remove(marker);
  358. markerGroup.add(marker);
  359. markerAux = markerGroup.getAt(markerGroup.getCount() - 2);
  360. marker.setAttributes({
  361. x: 0,
  362. y: 0,
  363. translate: {
  364. x: markerAux.attr.translation.x,
  365. y: markerAux.attr.translation.y
  366. }
  367. }, true);
  368. }
  369. }
  370. me.unHighlightItem();
  371. me.cleanHighlights();
  372. me.setBBox();
  373. bbox = me.bbox;
  374. me.clipRect = [bbox.x, bbox.y, bbox.width, bbox.height];
  375. if (axis = chartAxes.get(boundXAxis)) {
  376. ends = axis.applyData();
  377. minX = ends.from;
  378. maxX = ends.to;
  379. }
  380. if (axis = chartAxes.get(boundYAxis)) {
  381. ends = axis.applyData();
  382. minY = ends.from;
  383. maxY = ends.to;
  384. }
  385. // If a field was specified without a corresponding axis, create one to get bounds
  386. if (me.xField &amp;&amp; !Ext.isNumber(minX)) {
  387. axis = me.getMinMaxXValues();
  388. minX = axis[0];
  389. maxX = axis[1];
  390. }
  391. if (me.yField &amp;&amp; !Ext.isNumber(minY)) {
  392. axis = me.getMinMaxYValues();
  393. minY = axis[0];
  394. maxY = axis[1];
  395. }
  396. if (isNaN(minX)) {
  397. minX = 0;
  398. xScale = bbox.width / ((storeCount - 1) || 1);
  399. }
  400. else {
  401. xScale = bbox.width / ((maxX - minX) || (storeCount -1) || 1);
  402. }
  403. if (isNaN(minY)) {
  404. minY = 0;
  405. yScale = bbox.height / ((storeCount - 1) || 1);
  406. }
  407. else {
  408. yScale = bbox.height / ((maxY - minY) || (storeCount - 1) || 1);
  409. }
  410. // Extract all x and y values from the store
  411. for (i = 0, ln = data.length; i &lt; ln; i++) {
  412. record = data[i];
  413. xValue = record.get(me.xField);
  414. // Ensure a value
  415. if (typeof xValue == 'string' || typeof xValue == 'object' &amp;&amp; !Ext.isDate(xValue)
  416. //set as uniform distribution if the axis is a category axis.
  417. || boundXAxis &amp;&amp; chartAxes.get(boundXAxis) &amp;&amp; chartAxes.get(boundXAxis).type == 'Category') {
  418. if (xValue in xValueMap) {
  419. xValue = xValueMap[xValue];
  420. } else {
  421. xValue = xValueMap[xValue] = i;
  422. }
  423. }
  424. // Filter out values that don't fit within the pan/zoom buffer area
  425. yValue = record.get(me.yField);
  426. //skip undefined values
  427. if (typeof yValue == 'undefined' || (typeof yValue == 'string' &amp;&amp; !yValue)) {
  428. //&lt;debug warn&gt;
  429. if (Ext.isDefined(Ext.global.console)) {
  430. Ext.global.console.warn(&quot;[Ext.chart.series.Line] Skipping a store element with an undefined value at &quot;, record, xValue, yValue);
  431. }
  432. //&lt;/debug&gt;
  433. continue;
  434. }
  435. // Ensure a value
  436. if (typeof yValue == 'string' || typeof yValue == 'object' &amp;&amp; !Ext.isDate(yValue)
  437. //set as uniform distribution if the axis is a category axis.
  438. || boundYAxis &amp;&amp; chartAxes.get(boundYAxis) &amp;&amp; chartAxes.get(boundYAxis).type == 'Category') {
  439. yValue = i;
  440. }
  441. storeIndices.push(i);
  442. xValues.push(xValue);
  443. yValues.push(yValue);
  444. }
  445. ln = xValues.length;
  446. if (ln &gt; bbox.width) {
  447. coords = me.shrink(xValues, yValues, bbox.width);
  448. xValues = coords.x;
  449. yValues = coords.y;
  450. }
  451. me.items = [];
  452. count = 0;
  453. ln = xValues.length;
  454. for (i = 0; i &lt; ln; i++) {
  455. xValue = xValues[i];
  456. yValue = yValues[i];
  457. if (yValue === false) {
  458. if (path.length == 1) {
  459. path = [];
  460. }
  461. onbreak = true;
  462. me.items.push(false);
  463. continue;
  464. } else {
  465. x = (bbox.x + (xValue - minX) * xScale).toFixed(2);
  466. y = ((bbox.y + bbox.height) - (yValue - minY) * yScale).toFixed(2);
  467. if (onbreak) {
  468. onbreak = false;
  469. path.push('M');
  470. }
  471. path = path.concat([x, y]);
  472. }
  473. if ((typeof firstY == 'undefined') &amp;&amp; (typeof y != 'undefined')) {
  474. firstY = y;
  475. firstX = x;
  476. }
  477. // If this is the first line, create a dummypath to animate in from.
  478. if (!me.line || chart.resizing) {
  479. dummyPath = dummyPath.concat([x, bbox.y + bbox.height / 2]);
  480. }
  481. // When resizing, reset before animating
  482. if (chart.animate &amp;&amp; chart.resizing &amp;&amp; me.line) {
  483. me.line.setAttributes({
  484. path: dummyPath,
  485. opacity: lineOpacity
  486. }, true);
  487. if (me.fillPath) {
  488. me.fillPath.setAttributes({
  489. path: dummyPath,
  490. opacity: fillOpacity
  491. }, true);
  492. }
  493. if (me.line.shadows) {
  494. shadows = me.line.shadows;
  495. for (j = 0, lnsh = shadows.length; j &lt; lnsh; j++) {
  496. shadow = shadows[j];
  497. shadow.setAttributes({
  498. path: dummyPath
  499. }, true);
  500. }
  501. }
  502. }
  503. if (showMarkers) {
  504. marker = markerGroup.getAt(count++);
  505. if (!marker) {
  506. marker = Ext.chart.Shape[type](surface, Ext.apply({
  507. group: [group, markerGroup],
  508. x: 0, y: 0,
  509. translate: {
  510. x: +(prevX || x),
  511. y: prevY || (bbox.y + bbox.height / 2)
  512. },
  513. value: '&quot;' + xValue + ', ' + yValue + '&quot;',
  514. zIndex: 4000
  515. }, endMarkerStyle));
  516. marker._to = {
  517. translate: {
  518. x: +x,
  519. y: +y
  520. }
  521. };
  522. } else {
  523. marker.setAttributes({
  524. value: '&quot;' + xValue + ', ' + yValue + '&quot;',
  525. x: 0, y: 0,
  526. hidden: false
  527. }, true);
  528. marker._to = {
  529. translate: {
  530. x: +x,
  531. y: +y
  532. }
  533. };
  534. }
  535. }
  536. me.items.push({
  537. series: me,
  538. value: [xValue, yValue],
  539. point: [x, y],
  540. sprite: marker,
  541. storeItem: store.getAt(storeIndices[i])
  542. });
  543. prevX = x;
  544. prevY = y;
  545. }
  546. if (path.length &lt;= 1) {
  547. //nothing to be rendered
  548. return;
  549. }
  550. if (me.smooth) {
  551. smoothPath = Ext.draw.Draw.smooth(path, isNumber(smooth) ? smooth : me.defaultSmoothness);
  552. }
  553. renderPath = smooth ? smoothPath : path;
  554. //Correct path if we're animating timeAxis intervals
  555. if (chart.markerIndex &amp;&amp; me.previousPath) {
  556. fromPath = me.previousPath;
  557. if (!smooth) {
  558. Ext.Array.erase(fromPath, 1, 2);
  559. }
  560. } else {
  561. fromPath = path;
  562. }
  563. // Only create a line if one doesn't exist.
  564. if (!me.line) {
  565. me.line = surface.add(Ext.apply({
  566. type: 'path',
  567. group: group,
  568. path: dummyPath,
  569. stroke: endLineStyle.stroke || endLineStyle.fill
  570. }, endLineStyle || {}));
  571. //set configuration opacity
  572. me.line.setAttributes({
  573. opacity: lineOpacity
  574. }, true);
  575. if (enableShadows) {
  576. me.line.setAttributes(Ext.apply({}, me.shadowOptions), true);
  577. }
  578. //unset fill here (there's always a default fill withing the themes).
  579. me.line.setAttributes({
  580. fill: 'none',
  581. zIndex: 3000
  582. });
  583. if (!endLineStyle.stroke &amp;&amp; colorArrayLength) {
  584. me.line.setAttributes({
  585. stroke: colorArrayStyle[seriesIdx % colorArrayLength]
  586. }, true);
  587. }
  588. if (enableShadows) {
  589. //create shadows
  590. shadows = me.line.shadows = [];
  591. for (shindex = 0; shindex &lt; lnsh; shindex++) {
  592. shadowBarAttr = shadowAttributes[shindex];
  593. shadowBarAttr = Ext.apply({}, shadowBarAttr, { path: dummyPath });
  594. shadow = surface.add(Ext.apply({}, {
  595. type: 'path',
  596. group: shadowGroups[shindex]
  597. }, shadowBarAttr));
  598. shadows.push(shadow);
  599. }
  600. }
  601. }
  602. if (me.fill) {
  603. fillPath = renderPath.concat([
  604. [&quot;L&quot;, x, bbox.y + bbox.height],
  605. [&quot;L&quot;, firstX, bbox.y + bbox.height],
  606. [&quot;L&quot;, firstX, firstY]
  607. ]);
  608. if (!me.fillPath) {
  609. me.fillPath = surface.add({
  610. group: group,
  611. type: 'path',
  612. fill: endLineStyle.fill || colorArrayStyle[seriesIdx % colorArrayLength],
  613. path: dummyPath
  614. });
  615. }
  616. }
  617. markerCount = showMarkers &amp;&amp; markerGroup.getCount();
  618. if (chart.animate) {
  619. fill = me.fill;
  620. line = me.line;
  621. //Add renderer to line. There is not unique record associated with this.
  622. rendererAttributes = me.renderer(line, false, { path: renderPath }, i, store);
  623. Ext.apply(rendererAttributes, endLineStyle || {}, {
  624. stroke: endLineStyle.stroke || endLineStyle.fill
  625. });
  626. //fill should not be used here but when drawing the special fill path object
  627. delete rendererAttributes.fill;
  628. line.show(true);
  629. if (chart.markerIndex &amp;&amp; me.previousPath) {
  630. me.animation = animation = me.onAnimate(line, {
  631. to: rendererAttributes,
  632. from: {
  633. path: fromPath
  634. }
  635. });
  636. } else {
  637. me.animation = animation = me.onAnimate(line, {
  638. to: rendererAttributes
  639. });
  640. }
  641. //animate shadows
  642. if (enableShadows) {
  643. shadows = line.shadows;
  644. for(j = 0; j &lt; lnsh; j++) {
  645. shadows[j].show(true);
  646. if (chart.markerIndex &amp;&amp; me.previousPath) {
  647. me.onAnimate(shadows[j], {
  648. to: { path: renderPath },
  649. from: { path: fromPath }
  650. });
  651. } else {
  652. me.onAnimate(shadows[j], {
  653. to: { path: renderPath }
  654. });
  655. }
  656. }
  657. }
  658. //animate fill path
  659. if (fill) {
  660. me.fillPath.show(true);
  661. me.onAnimate(me.fillPath, {
  662. to: Ext.apply({}, {
  663. path: fillPath,
  664. fill: endLineStyle.fill || colorArrayStyle[seriesIdx % colorArrayLength],
  665. 'stroke-width': 0,
  666. opacity: fillOpacity
  667. }, endLineStyle || {})
  668. });
  669. }
  670. //animate markers
  671. if (showMarkers) {
  672. count = 0;
  673. for(i = 0; i &lt; ln; i++) {
  674. if (me.items[i]) {
  675. item = markerGroup.getAt(count++);
  676. if (item) {
  677. rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
  678. me.onAnimate(item, {
  679. to: Ext.apply(rendererAttributes, endMarkerStyle || {})
  680. });
  681. item.show(true);
  682. }
  683. }
  684. }
  685. for(; count &lt; markerCount; count++) {
  686. item = markerGroup.getAt(count);
  687. item.hide(true);
  688. }
  689. // for(i = 0; i &lt; (chart.markerIndex || 0)-1; i++) {
  690. // item = markerGroup.getAt(i);
  691. // item.hide(true);
  692. // }
  693. }
  694. } else {
  695. rendererAttributes = me.renderer(me.line, false, { path: renderPath, hidden: false }, i, store);
  696. Ext.apply(rendererAttributes, endLineStyle || {}, {
  697. stroke: endLineStyle.stroke || endLineStyle.fill
  698. });
  699. //fill should not be used here but when drawing the special fill path object
  700. delete rendererAttributes.fill;
  701. me.line.setAttributes(rendererAttributes, true);
  702. me.line.setAttributes({
  703. opacity: lineOpacity
  704. }, true);
  705. //set path for shadows
  706. if (enableShadows) {
  707. shadows = me.line.shadows;
  708. for(j = 0; j &lt; lnsh; j++) {
  709. shadows[j].setAttributes({
  710. path: renderPath,
  711. hidden: false
  712. }, true);
  713. }
  714. }
  715. if (me.fill) {
  716. me.fillPath.setAttributes({
  717. path: fillPath,
  718. hidden: false,
  719. opacity: fillOpacity
  720. }, true);
  721. }
  722. if (showMarkers) {
  723. count = 0;
  724. for(i = 0; i &lt; ln; i++) {
  725. if (me.items[i]) {
  726. item = markerGroup.getAt(count++);
  727. if (item) {
  728. rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
  729. item.setAttributes(Ext.apply(endMarkerStyle || {}, rendererAttributes || {}), true);
  730. if (!item.attr.hidden) {
  731. item.show(true);
  732. }
  733. }
  734. }
  735. }
  736. for(; count &lt; markerCount; count++) {
  737. item = markerGroup.getAt(count);
  738. item.hide(true);
  739. }
  740. }
  741. }
  742. if (chart.markerIndex) {
  743. if (me.smooth) {
  744. Ext.Array.erase(path, 1, 2);
  745. } else {
  746. Ext.Array.splice(path, 1, 0, path[1], path[2]);
  747. }
  748. me.previousPath = path;
  749. }
  750. me.renderLabels();
  751. me.renderCallouts();
  752. me.fireEvent('draw', me);
  753. },
  754. // @private called when a label is to be created.
  755. onCreateLabel: function(storeItem, item, i, display) {
  756. var me = this,
  757. group = me.labelsGroup,
  758. config = me.label,
  759. bbox = me.bbox,
  760. endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
  761. return me.chart.surface.add(Ext.apply({
  762. 'type': 'text',
  763. 'text-anchor': 'middle',
  764. 'group': group,
  765. 'x': item.point[0],
  766. 'y': bbox.y + bbox.height / 2
  767. }, endLabelStyle || {}));
  768. },
  769. // @private called when a label is to be created.
  770. onPlaceLabel: function(label, storeItem, item, i, display, animate) {
  771. var me = this,
  772. chart = me.chart,
  773. resizing = chart.resizing,
  774. config = me.label,
  775. format = config.renderer,
  776. field = config.field,
  777. bbox = me.bbox,
  778. x = item.point[0],
  779. y = item.point[1],
  780. radius = item.sprite.attr.radius,
  781. bb, width, height;
  782. label.setAttributes({
  783. text: format(storeItem.get(field)),
  784. hidden: true
  785. }, true);
  786. if (display == 'rotate') {
  787. label.setAttributes({
  788. 'text-anchor': 'start',
  789. 'rotation': {
  790. x: x,
  791. y: y,
  792. degrees: -45
  793. }
  794. }, true);
  795. //correct label position to fit into the box
  796. bb = label.getBBox();
  797. width = bb.width;
  798. height = bb.height;
  799. x = x &lt; bbox.x? bbox.x : x;
  800. x = (x + width &gt; bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
  801. y = (y - height &lt; bbox.y)? bbox.y + height : y;
  802. } else if (display == 'under' || display == 'over') {
  803. //TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
  804. bb = item.sprite.getBBox();
  805. bb.width = bb.width || (radius * 2);
  806. bb.height = bb.height || (radius * 2);
  807. y = y + (display == 'over'? -bb.height : bb.height);
  808. //correct label position to fit into the box
  809. bb = label.getBBox();
  810. width = bb.width/2;
  811. height = bb.height/2;
  812. x = x - width &lt; bbox.x? bbox.x + width : x;
  813. x = (x + width &gt; bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
  814. y = y - height &lt; bbox.y? bbox.y + height : y;
  815. y = (y + height &gt; bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
  816. }
  817. if (me.chart.animate &amp;&amp; !me.chart.resizing) {
  818. label.show(true);
  819. me.onAnimate(label, {
  820. to: {
  821. x: x,
  822. y: y
  823. }
  824. });
  825. } else {
  826. label.setAttributes({
  827. x: x,
  828. y: y
  829. }, true);
  830. if (resizing &amp;&amp; me.animation) {
  831. me.animation.on('afteranimate', function() {
  832. label.show(true);
  833. });
  834. } else {
  835. label.show(true);
  836. }
  837. }
  838. },
  839. // @private Overriding highlights.js highlightItem method.
  840. highlightItem: function() {
  841. var me = this;
  842. me.callParent(arguments);
  843. if (me.line &amp;&amp; !me.highlighted) {
  844. if (!('__strokeWidth' in me.line)) {
  845. me.line.__strokeWidth = parseFloat(me.line.attr['stroke-width']) || 0;
  846. }
  847. if (me.line.__anim) {
  848. me.line.__anim.paused = true;
  849. }
  850. me.line.__anim = Ext.create('Ext.fx.Anim', {
  851. target: me.line,
  852. to: {
  853. 'stroke-width': me.line.__strokeWidth + 3
  854. }
  855. });
  856. me.highlighted = true;
  857. }
  858. },
  859. // @private Overriding highlights.js unHighlightItem method.
  860. unHighlightItem: function() {
  861. var me = this;
  862. me.callParent(arguments);
  863. if (me.line &amp;&amp; me.highlighted) {
  864. me.line.__anim = Ext.create('Ext.fx.Anim', {
  865. target: me.line,
  866. to: {
  867. 'stroke-width': me.line.__strokeWidth
  868. }
  869. });
  870. me.highlighted = false;
  871. }
  872. },
  873. // @private called when a callout needs to be placed.
  874. onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
  875. if (!display) {
  876. return;
  877. }
  878. var me = this,
  879. chart = me.chart,
  880. surface = chart.surface,
  881. resizing = chart.resizing,
  882. config = me.callouts,
  883. items = me.items,
  884. prev = i == 0? false : items[i -1].point,
  885. next = (i == items.length -1)? false : items[i +1].point,
  886. cur = [+item.point[0], +item.point[1]],
  887. dir, norm, normal, a, aprev, anext,
  888. offsetFromViz = config.offsetFromViz || 30,
  889. offsetToSide = config.offsetToSide || 10,
  890. offsetBox = config.offsetBox || 3,
  891. boxx, boxy, boxw, boxh,
  892. p, clipRect = me.clipRect,
  893. bbox = {
  894. width: config.styles.width || 10,
  895. height: config.styles.height || 10
  896. },
  897. x, y;
  898. //get the right two points
  899. if (!prev) {
  900. prev = cur;
  901. }
  902. if (!next) {
  903. next = cur;
  904. }
  905. a = (next[1] - prev[1]) / (next[0] - prev[0]);
  906. aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
  907. anext = (next[1] - cur[1]) / (next[0] - cur[0]);
  908. norm = Math.sqrt(1 + a * a);
  909. dir = [1 / norm, a / norm];
  910. normal = [-dir[1], dir[0]];
  911. //keep the label always on the outer part of the &quot;elbow&quot;
  912. if (aprev &gt; 0 &amp;&amp; anext &lt; 0 &amp;&amp; normal[1] &lt; 0
  913. || aprev &lt; 0 &amp;&amp; anext &gt; 0 &amp;&amp; normal[1] &gt; 0) {
  914. normal[0] *= -1;
  915. normal[1] *= -1;
  916. } else if (Math.abs(aprev) &lt; Math.abs(anext) &amp;&amp; normal[0] &lt; 0
  917. || Math.abs(aprev) &gt; Math.abs(anext) &amp;&amp; normal[0] &gt; 0) {
  918. normal[0] *= -1;
  919. normal[1] *= -1;
  920. }
  921. //position
  922. x = cur[0] + normal[0] * offsetFromViz;
  923. y = cur[1] + normal[1] * offsetFromViz;
  924. //box position and dimensions
  925. boxx = x + (normal[0] &gt; 0? 0 : -(bbox.width + 2 * offsetBox));
  926. boxy = y - bbox.height /2 - offsetBox;
  927. boxw = bbox.width + 2 * offsetBox;
  928. boxh = bbox.height + 2 * offsetBox;
  929. //now check if we're out of bounds and invert the normal vector correspondingly
  930. //this may add new overlaps between labels (but labels won't be out of bounds).
  931. if (boxx &lt; clipRect[0] || (boxx + boxw) &gt; (clipRect[0] + clipRect[2])) {
  932. normal[0] *= -1;
  933. }
  934. if (boxy &lt; clipRect[1] || (boxy + boxh) &gt; (clipRect[1] + clipRect[3])) {
  935. normal[1] *= -1;
  936. }
  937. //update positions
  938. x = cur[0] + normal[0] * offsetFromViz;
  939. y = cur[1] + normal[1] * offsetFromViz;
  940. //update box position and dimensions
  941. boxx = x + (normal[0] &gt; 0? 0 : -(bbox.width + 2 * offsetBox));
  942. boxy = y - bbox.height /2 - offsetBox;
  943. boxw = bbox.width + 2 * offsetBox;
  944. boxh = bbox.height + 2 * offsetBox;
  945. if (chart.animate) {
  946. //set the line from the middle of the pie to the box.
  947. me.onAnimate(callout.lines, {
  948. to: {
  949. path: [&quot;M&quot;, cur[0], cur[1], &quot;L&quot;, x, y, &quot;Z&quot;]
  950. }
  951. });
  952. //set component position
  953. if (callout.panel) {
  954. callout.panel.setPosition(boxx, boxy, true);
  955. }
  956. }
  957. else {
  958. //set the line from the middle of the pie to the box.
  959. callout.lines.setAttributes({
  960. path: [&quot;M&quot;, cur[0], cur[1], &quot;L&quot;, x, y, &quot;Z&quot;]
  961. }, true);
  962. //set component position
  963. if (callout.panel) {
  964. callout.panel.setPosition(boxx, boxy);
  965. }
  966. }
  967. for (p in callout) {
  968. callout[p].show(true);
  969. }
  970. },
  971. isItemInPoint: function(x, y, item, i) {
  972. var me = this,
  973. items = me.items,
  974. tolerance = me.selectionTolerance,
  975. result = null,
  976. prevItem,
  977. nextItem,
  978. prevPoint,
  979. nextPoint,
  980. ln,
  981. x1,
  982. y1,
  983. x2,
  984. y2,
  985. xIntersect,
  986. yIntersect,
  987. dist1, dist2, dist, midx, midy,
  988. sqrt = Math.sqrt, abs = Math.abs;
  989. nextItem = items[i];
  990. prevItem = i &amp;&amp; items[i - 1];
  991. if (i &gt;= ln) {
  992. prevItem = items[ln - 1];
  993. }
  994. prevPoint = prevItem &amp;&amp; prevItem.point;
  995. nextPoint = nextItem &amp;&amp; nextItem.point;
  996. x1 = prevItem ? prevPoint[0] : nextPoint[0] - tolerance;
  997. y1 = prevItem ? prevPoint[1] : nextPoint[1];
  998. x2 = nextItem ? nextPoint[0] : prevPoint[0] + tolerance;
  999. y2 = nextItem ? nextPoint[1] : prevPoint[1];
  1000. dist1 = sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
  1001. dist2 = sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
  1002. dist = Math.min(dist1, dist2);
  1003. if (dist &lt;= tolerance) {
  1004. return dist == dist1? prevItem : nextItem;
  1005. }
  1006. return false;
  1007. },
  1008. // @private toggle visibility of all series elements (markers, sprites).
  1009. toggleAll: function(show) {
  1010. var me = this,
  1011. i, ln, shadow, shadows;
  1012. if (!show) {
  1013. Ext.chart.series.Cartesian.prototype.hideAll.call(me);
  1014. }
  1015. else {
  1016. Ext.chart.series.Cartesian.prototype.showAll.call(me);
  1017. }
  1018. if (me.line) {
  1019. me.line.setAttributes({
  1020. hidden: !show
  1021. }, true);
  1022. //hide shadows too
  1023. if (me.line.shadows) {
  1024. for (i = 0, shadows = me.line.shadows, ln = shadows.length; i &lt; ln; i++) {
  1025. shadow = shadows[i];
  1026. shadow.setAttributes({
  1027. hidden: !show
  1028. }, true);
  1029. }
  1030. }
  1031. }
  1032. if (me.fillPath) {
  1033. me.fillPath.setAttributes({
  1034. hidden: !show
  1035. }, true);
  1036. }
  1037. },
  1038. // @private hide all series elements (markers, sprites).
  1039. hideAll: function() {
  1040. this.toggleAll(false);
  1041. },
  1042. // @private hide all series elements (markers, sprites).
  1043. showAll: function() {
  1044. this.toggleAll(true);
  1045. }
  1046. });
  1047. </pre>
  1048. </body>
  1049. </html>