4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5 <title>The source code</title>
6 <link href="../prettify/prettify.css" type="text/css" rel="stylesheet" />
7 <script type="text/javascript" src="../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-Line'>/**
19 </span> * @class Ext.chart.series.Line
20 * @extends Ext.chart.series.Cartesian
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:
27 * {@img Ext.chart.series.Line/Ext.chart.series.Line.png Ext.chart.series.Line chart series}
29 * var store = Ext.create('Ext.data.JsonStore', {
30 * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
32 * {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
33 * {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
34 * {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
35 * {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
36 * {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
40 * Ext.create('Ext.chart.Chart', {
41 * renderTo: Ext.getBody(),
51 * renderer: Ext.util.Format.numberRenderer('0,0')
53 * title: 'Sample Values',
60 * title: 'Sample Metrics'
96 * In this configuration we're adding two series (or lines), one bound to the `data1`
97 * property of the store and the other to `data3`. The type for both configurations is
98 * `line`. The `xField` for both series is the same, the name propert of the store.
99 * Both line series share the same axis, the left axis. You can set particular marker
100 * configuration by adding properties onto the markerConfig object. Both series have
101 * an object as highlight so that markers animate smoothly to the properties in highlight
102 * when hovered. The second series has `fill=true` which means that the line will also
103 * have an area below it of the same color.
105 * **Note:** In the series definition remember to explicitly set the axis to bind the
106 * values of the line series to. This can be done by using the `axis` configuration property.
108 Ext.define('Ext.chart.series.Line', {
110 /* Begin Definitions */
112 extend: 'Ext.chart.series.Cartesian',
114 alternateClassName: ['Ext.chart.LineSeries', 'Ext.chart.LineChart'],
116 requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.draw.Draw', 'Ext.fx.Anim'],
118 /* End Definitions */
122 alias: 'series.line',
124 <span id='Ext-chart-series-Line-cfg-axis'> /**
125 </span> * @cfg {String} axis
126 * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
127 * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
128 * relative scale will be used.
131 <span id='Ext-chart-series-Line-cfg-selectionTolerance'> /**
132 </span> * @cfg {Number} selectionTolerance
133 * The offset distance from the cursor position to the line series to trigger events (then used for highlighting series, etc).
135 selectionTolerance: 20,
137 <span id='Ext-chart-series-Line-cfg-showMarkers'> /**
138 </span> * @cfg {Boolean} showMarkers
139 * Whether markers should be displayed at the data points along the line. If true,
140 * then the {@link #markerConfig} config item will determine the markers' styling.
144 <span id='Ext-chart-series-Line-cfg-markerConfig'> /**
145 </span> * @cfg {Object} markerConfig
146 * The display style for the markers. Only used if {@link #showMarkers} is true.
147 * The markerConfig is a configuration object containing the same set of properties defined in
148 * the Sprite class. For example, if we were to set red circles as markers to the line series we could
151 <pre><code>
157 </code></pre>
162 <span id='Ext-chart-series-Line-cfg-style'> /**
163 </span> * @cfg {Object} style
164 * An object containing styles for the visualization lines. These styles will override the theme styles.
165 * Some options contained within the style object will are described next.
169 <span id='Ext-chart-series-Line-cfg-smooth'> /**
170 </span> * @cfg {Boolean/Number} smooth
171 * If set to `true` or a non-zero number, the line will be smoothed/rounded around its points; otherwise
172 * straight line segments will be drawn.
174 * A numeric value is interpreted as a divisor of the horizontal distance between consecutive points in
175 * the line; larger numbers result in sharper curves while smaller numbers result in smoother curves.
177 * If set to `true` then a default numeric value of 3 will be used. Defaults to `false`.
181 <span id='Ext-chart-series-Line-property-defaultSmoothness'> /**
182 </span> * @private Default numeric smoothing value to be used when {@link #smooth} = true.
184 defaultSmoothness: 3,
186 <span id='Ext-chart-series-Line-cfg-fill'> /**
187 </span> * @cfg {Boolean} fill
188 * If true, the area below the line will be filled in using the {@link #style.eefill} and
189 * {@link #style.opacity} config properties. Defaults to false.
193 constructor: function(config) {
194 this.callParent(arguments);
196 surface = me.chart.surface,
197 shadow = me.chart.shadow,
199 Ext.apply(me, config, {
204 "stroke-width": 6,
205 "stroke-opacity": 0.05,
206 stroke: 'rgb(0, 0, 0)',
212 "stroke-width": 4,
213 "stroke-opacity": 0.1,
214 stroke: 'rgb(0, 0, 0)',
220 "stroke-width": 2,
221 "stroke-opacity": 0.15,
222 stroke: 'rgb(0, 0, 0)',
229 me.group = surface.getGroup(me.seriesId);
230 if (me.showMarkers) {
231 me.markerGroup = surface.getGroup(me.seriesId + '-markers');
234 for (i = 0, l = this.shadowAttributes.length; i < l; i++) {
235 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
240 // @private makes an average of points when there are more data points than pixels to be rendered.
241 shrink: function(xValues, yValues, size) {
242 // Start at the 2nd point...
243 var len = xValues.length,
244 ratio = Math.floor(len / size),
251 for (; i < len; ++i) {
252 xSum += xValues[i] || 0;
253 ySum += yValues[i] || 0;
254 if (i % ratio == 0) {
255 xRes.push(xSum/ratio);
256 yRes.push(ySum/ratio);
267 <span id='Ext-chart-series-Line-method-drawSeries'> /**
268 </span> * Draws the series for the current chart.
270 drawSeries: function() {
273 store = chart.substore || chart.store,
274 surface = chart.surface,
275 chartBBox = chart.chartBBox,
278 gutterX = chart.maxGutter[0],
279 gutterY = chart.maxGutter[1],
280 showMarkers = me.showMarkers,
281 markerGroup = me.markerGroup,
282 enableShadows = chart.shadow,
283 shadowGroups = me.shadowGroups,
284 shadowAttributes = me.shadowAttributes,
286 lnsh = shadowGroups.length,
287 dummyPath = ["M"],
288 path = ["M"],
289 markerIndex = chart.markerIndex,
290 axes = [].concat(me.axis),
299 markerStyle = me.markerStyle,
300 seriesStyle = me.seriesStyle,
301 seriesLabelStyle = me.seriesLabelStyle,
302 colorArrayStyle = me.colorArrayStyle,
303 colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
310 isNumber = Ext.isNumber,
311 seriesIdx = me.seriesIdx, shadows, shadow, shindex, fromPath, fill, fillPath, rendererAttributes,
312 x, y, prevX, prevY, firstY, markerCount, i, j, ln, axis, ends, marker, markerAux, item, xValue,
313 yValue, coords, xScale, yScale, minX, maxX, minY, maxY, line, animation, endMarkerStyle,
314 endLineStyle, type, props, firstMarker, count, smoothPath, renderPath;
316 //if store is empty then there's nothing to draw.
317 if (!store || !store.getCount()) {
321 //prepare style objects for line and markers
322 endMarkerStyle = Ext.apply(markerStyle, me.markerConfig);
323 type = endMarkerStyle.type;
324 delete endMarkerStyle.type;
325 endLineStyle = Ext.apply(seriesStyle, me.style);
326 //if no stroke with is specified force it to 0.5 because this is
327 //about making *lines*
328 if (!endLineStyle['stroke-width']) {
329 endLineStyle['stroke-width'] = 0.5;
331 //If we're using a time axis and we need to translate the points,
332 //then reuse the first markers as the last markers.
333 if (markerIndex && markerGroup && markerGroup.getCount()) {
334 for (i = 0; i < markerIndex; i++) {
335 marker = markerGroup.getAt(i);
336 markerGroup.remove(marker);
337 markerGroup.add(marker);
338 markerAux = markerGroup.getAt(markerGroup.getCount() - 2);
339 marker.setAttributes({
343 x: markerAux.attr.translation.x,
344 y: markerAux.attr.translation.y
350 me.unHighlightItem();
351 me.cleanHighlights();
356 me.clipRect = [bbox.x, bbox.y, bbox.width, bbox.height];
358 chart.axes.each(function(axis) {
359 //only apply position calculations to axes that affect this series
360 //this means the axis in the position referred by this series and also
361 //the axis in the other coordinate for this series. For example: (left, top|bottom),
362 //or (top, left|right), etc.
363 if (axis.position == me.axis || axis.position != posHash[me.axis]) {
365 if (axis.type != 'Numeric') {
369 numericAxis = (numericAxis && axis.type == 'Numeric');
371 ends = axis.calcEnds();
372 if (axis.position == 'top' || axis.position == 'bottom') {
384 //If there's only one axis specified for a series, then we set the default type of the other
385 //axis to a category axis. So in this case numericAxis, which would be true if both axes affecting
386 //the series are numeric should be false.
387 if (numericAxis && axisCount == 1) {
391 // If a field was specified without a corresponding axis, create one to get bounds
392 //only do this for the axis where real values are bound (that's why we check for
394 if (me.xField && !isNumber(minX)) {
395 if (me.axis == 'bottom' || me.axis == 'top') {
396 axis = Ext.create('Ext.chart.axis.Axis', {
398 fields: [].concat(me.xField)
402 } else if (numericAxis) {
403 axis = Ext.create('Ext.chart.axis.Axis', {
405 fields: [].concat(me.xField),
413 if (me.yField && !isNumber(minY)) {
414 if (me.axis == 'right' || me.axis == 'left') {
415 axis = Ext.create('Ext.chart.axis.Axis', {
417 fields: [].concat(me.yField)
421 } else if (numericAxis) {
422 axis = Ext.create('Ext.chart.axis.Axis', {
424 fields: [].concat(me.yField),
434 xScale = bbox.width / (store.getCount() - 1);
437 //In case some person decides to set an axis' minimum and maximum
438 //configuration properties to the same value, then fallback the
439 //denominator to a > 0 value.
440 xScale = bbox.width / ((maxX - minX) || (store.getCount() - 1));
445 yScale = bbox.height / (store.getCount() - 1);
448 //In case some person decides to set an axis' minimum and maximum
449 //configuration properties to the same value, then fallback the
450 //denominator to a > 0 value.
451 yScale = bbox.height / ((maxY - minY) || (store.getCount() - 1));
454 store.each(function(record, i) {
455 xValue = record.get(me.xField);
456 yValue = record.get(me.yField);
457 //skip undefined values
458 if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)) {
460 if (Ext.isDefined(Ext.global.console)) {
461 Ext.global.console.warn("[Ext.chart.series.Line] Skipping a store element with an undefined value at ", record, xValue, yValue);
467 if (typeof xValue == 'string' || typeof xValue == 'object'
468 //set as uniform distribution if the axis is a category axis.
469 || (me.axis != 'top' && me.axis != 'bottom' && !numericAxis)) {
472 if (typeof yValue == 'string' || typeof yValue == 'object'
473 //set as uniform distribution if the axis is a category axis.
474 || (me.axis != 'left' && me.axis != 'right' && !numericAxis)) {
477 storeIndices.push(i);
478 xValues.push(xValue);
479 yValues.push(yValue);
483 if (ln > bbox.width) {
484 coords = me.shrink(xValues, yValues, bbox.width);
493 for (i = 0; i < ln; i++) {
496 if (yValue === false) {
497 if (path.length == 1) {
501 me.items.push(false);
504 x = (bbox.x + (xValue - minX) * xScale).toFixed(2);
505 y = ((bbox.y + bbox.height) - (yValue - minY) * yScale).toFixed(2);
510 path = path.concat([x, y]);
512 if ((typeof firstY == 'undefined') && (typeof y != 'undefined')) {
515 // If this is the first line, create a dummypath to animate in from.
516 if (!me.line || chart.resizing) {
517 dummyPath = dummyPath.concat([x, bbox.y + bbox.height / 2]);
520 // When resizing, reset before animating
521 if (chart.animate && chart.resizing && me.line) {
522 me.line.setAttributes({
526 me.fillPath.setAttributes({
531 if (me.line.shadows) {
532 shadows = me.line.shadows;
533 for (j = 0, lnsh = shadows.length; j < lnsh; j++) {
535 shadow.setAttributes({
542 marker = markerGroup.getAt(count++);
544 marker = Ext.chart.Shape[type](surface, Ext.apply({
545 group: [group, markerGroup],
549 y: prevY || (bbox.y + bbox.height / 2)
551 value: '"' + xValue + ', ' + yValue + '"'
560 marker.setAttributes({
561 value: '"' + xValue + ', ' + yValue + '"',
575 value: [xValue, yValue],
578 storeItem: store.getAt(storeIndices[i])
584 if (path.length <= 1) {
585 //nothing to be rendered
590 smoothPath = Ext.draw.Draw.smooth(path, isNumber(smooth) ? smooth : me.defaultSmoothness);
593 renderPath = smooth ? smoothPath : path;
595 //Correct path if we're animating timeAxis intervals
596 if (chart.markerIndex && me.previousPath) {
597 fromPath = me.previousPath;
599 Ext.Array.erase(fromPath, 1, 2);
605 // Only create a line if one doesn't exist.
607 me.line = surface.add(Ext.apply({
611 stroke: endLineStyle.stroke || endLineStyle.fill
612 }, endLineStyle || {}));
613 //unset fill here (there's always a default fill withing the themes).
614 me.line.setAttributes({
617 if (!endLineStyle.stroke && colorArrayLength) {
618 me.line.setAttributes({
619 stroke: colorArrayStyle[seriesIdx % colorArrayLength]
624 shadows = me.line.shadows = [];
625 for (shindex = 0; shindex < lnsh; shindex++) {
626 shadowBarAttr = shadowAttributes[shindex];
627 shadowBarAttr = Ext.apply({}, shadowBarAttr, { path: dummyPath });
628 shadow = chart.surface.add(Ext.apply({}, {
630 group: shadowGroups[shindex]
632 shadows.push(shadow);
637 fillPath = renderPath.concat([
638 ["L", x, bbox.y + bbox.height],
639 ["L", bbox.x, bbox.y + bbox.height],
640 ["L", bbox.x, firstY]
643 me.fillPath = surface.add({
646 opacity: endLineStyle.opacity || 0.3,
647 fill: endLineStyle.fill || colorArrayStyle[seriesIdx % colorArrayLength],
652 markerCount = showMarkers && markerGroup.getCount();
656 //Add renderer to line. There is not unique record associated with this.
657 rendererAttributes = me.renderer(line, false, { path: renderPath }, i, store);
658 Ext.apply(rendererAttributes, endLineStyle || {}, {
659 stroke: endLineStyle.stroke || endLineStyle.fill
661 //fill should not be used here but when drawing the special fill path object
662 delete rendererAttributes.fill;
663 if (chart.markerIndex && me.previousPath) {
664 me.animation = animation = me.onAnimate(line, {
665 to: rendererAttributes,
671 me.animation = animation = me.onAnimate(line, {
672 to: rendererAttributes
677 shadows = line.shadows;
678 for(j = 0; j < lnsh; j++) {
679 if (chart.markerIndex && me.previousPath) {
680 me.onAnimate(shadows[j], {
681 to: { path: renderPath },
682 from: { path: fromPath }
685 me.onAnimate(shadows[j], {
686 to: { path: renderPath }
693 me.onAnimate(me.fillPath, {
696 fill: endLineStyle.fill || colorArrayStyle[seriesIdx % colorArrayLength]
697 }, endLineStyle || {})
703 for(i = 0; i < ln; i++) {
705 item = markerGroup.getAt(count++);
707 rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
709 to: Ext.apply(rendererAttributes, endMarkerStyle || {})
714 for(; count < markerCount; count++) {
715 item = markerGroup.getAt(count);
720 rendererAttributes = me.renderer(me.line, false, { path: renderPath, hidden: false }, i, store);
721 Ext.apply(rendererAttributes, endLineStyle || {}, {
722 stroke: endLineStyle.stroke || endLineStyle.fill
724 //fill should not be used here but when drawing the special fill path object
725 delete rendererAttributes.fill;
726 me.line.setAttributes(rendererAttributes, true);
727 //set path for shadows
729 shadows = me.line.shadows;
730 for(j = 0; j < lnsh; j++) {
731 shadows[j].setAttributes({
737 me.fillPath.setAttributes({
743 for(i = 0; i < ln; i++) {
745 item = markerGroup.getAt(count++);
747 rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
748 item.setAttributes(Ext.apply(endMarkerStyle || {}, rendererAttributes || {}), true);
752 for(; count < markerCount; count++) {
753 item = markerGroup.getAt(count);
759 if (chart.markerIndex) {
761 Ext.Array.erase(path, 1, 2);
763 Ext.Array.splice(path, 1, 0, path[1], path[2]);
765 me.previousPath = path;
771 // @private called when a label is to be created.
772 onCreateLabel: function(storeItem, item, i, display) {
774 group = me.labelsGroup,
777 endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
779 return me.chart.surface.add(Ext.apply({
781 'text-anchor': 'middle',
784 'y': bbox.y + bbox.height / 2
785 }, endLabelStyle || {}));
788 // @private called when a label is to be created.
789 onPlaceLabel: function(label, storeItem, item, i, display, animate) {
792 resizing = chart.resizing,
794 format = config.renderer,
795 field = config.field,
799 radius = item.sprite.attr.radius,
802 label.setAttributes({
803 text: format(storeItem.get(field)),
807 if (display == 'rotate') {
808 label.setAttributes({
809 'text-anchor': 'start',
816 //correct label position to fit into the box
817 bb = label.getBBox();
820 x = x < bbox.x? bbox.x : x;
821 x = (x + width > bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
822 y = (y - height < bbox.y)? bbox.y + height : y;
824 } else if (display == 'under' || display == 'over') {
825 //TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
826 bb = item.sprite.getBBox();
827 bb.width = bb.width || (radius * 2);
828 bb.height = bb.height || (radius * 2);
829 y = y + (display == 'over'? -bb.height : bb.height);
830 //correct label position to fit into the box
831 bb = label.getBBox();
833 height = bb.height/2;
834 x = x - width < bbox.x? bbox.x + width : x;
835 x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
836 y = y - height < bbox.y? bbox.y + height : y;
837 y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
840 if (me.chart.animate && !me.chart.resizing) {
842 me.onAnimate(label, {
849 label.setAttributes({
854 me.animation.on('afteranimate', function() {
863 //@private Overriding highlights.js highlightItem method.
864 highlightItem: function() {
866 me.callParent(arguments);
867 if (this.line && !this.highlighted) {
868 if (!('__strokeWidth' in this.line)) {
869 this.line.__strokeWidth = this.line.attr['stroke-width'] || 0;
871 if (this.line.__anim) {
872 this.line.__anim.paused = true;
874 this.line.__anim = Ext.create('Ext.fx.Anim', {
877 'stroke-width': this.line.__strokeWidth + 3
880 this.highlighted = true;
884 //@private Overriding highlights.js unHighlightItem method.
885 unHighlightItem: function() {
887 me.callParent(arguments);
888 if (this.line && this.highlighted) {
889 this.line.__anim = Ext.create('Ext.fx.Anim', {
892 'stroke-width': this.line.__strokeWidth
895 this.highlighted = false;
899 //@private called when a callout needs to be placed.
900 onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
907 surface = chart.surface,
908 resizing = chart.resizing,
909 config = me.callouts,
911 prev = i == 0? false : items[i -1].point,
912 next = (i == items.length -1)? false : items[i +1].point,
913 cur = [+item.point[0], +item.point[1]],
914 dir, norm, normal, a, aprev, anext,
915 offsetFromViz = config.offsetFromViz || 30,
916 offsetToSide = config.offsetToSide || 10,
917 offsetBox = config.offsetBox || 3,
918 boxx, boxy, boxw, boxh,
919 p, clipRect = me.clipRect,
921 width: config.styles.width || 10,
922 height: config.styles.height || 10
926 //get the right two points
933 a = (next[1] - prev[1]) / (next[0] - prev[0]);
934 aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
935 anext = (next[1] - cur[1]) / (next[0] - cur[0]);
937 norm = Math.sqrt(1 + a * a);
938 dir = [1 / norm, a / norm];
939 normal = [-dir[1], dir[0]];
941 //keep the label always on the outer part of the "elbow"
942 if (aprev > 0 && anext < 0 && normal[1] < 0
943 || aprev < 0 && anext > 0 && normal[1] > 0) {
946 } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0
947 || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
952 x = cur[0] + normal[0] * offsetFromViz;
953 y = cur[1] + normal[1] * offsetFromViz;
955 //box position and dimensions
956 boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
957 boxy = y - bbox.height /2 - offsetBox;
958 boxw = bbox.width + 2 * offsetBox;
959 boxh = bbox.height + 2 * offsetBox;
961 //now check if we're out of bounds and invert the normal vector correspondingly
962 //this may add new overlaps between labels (but labels won't be out of bounds).
963 if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
966 if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
971 x = cur[0] + normal[0] * offsetFromViz;
972 y = cur[1] + normal[1] * offsetFromViz;
974 //update box position and dimensions
975 boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
976 boxy = y - bbox.height /2 - offsetBox;
977 boxw = bbox.width + 2 * offsetBox;
978 boxh = bbox.height + 2 * offsetBox;
981 //set the line from the middle of the pie to the box.
982 me.onAnimate(callout.lines, {
984 path: ["M", cur[0], cur[1], "L", x, y, "Z"]
987 //set component position
989 callout.panel.setPosition(boxx, boxy, true);
993 //set the line from the middle of the pie to the box.
994 callout.lines.setAttributes({
995 path: ["M", cur[0], cur[1], "L", x, y, "Z"]
997 //set component position
999 callout.panel.setPosition(boxx, boxy);
1002 for (p in callout) {
1003 callout[p].show(true);
1007 isItemInPoint: function(x, y, item, i) {
1010 tolerance = me.selectionTolerance,
1023 dist1, dist2, dist, midx, midy,
1024 sqrt = Math.sqrt, abs = Math.abs;
1026 nextItem = items[i];
1027 prevItem = i && items[i - 1];
1030 prevItem = items[ln - 1];
1032 prevPoint = prevItem && prevItem.point;
1033 nextPoint = nextItem && nextItem.point;
1034 x1 = prevItem ? prevPoint[0] : nextPoint[0] - tolerance;
1035 y1 = prevItem ? prevPoint[1] : nextPoint[1];
1036 x2 = nextItem ? nextPoint[0] : prevPoint[0] + tolerance;
1037 y2 = nextItem ? nextPoint[1] : prevPoint[1];
1038 dist1 = sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
1039 dist2 = sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
1040 dist = Math.min(dist1, dist2);
1042 if (dist <= tolerance) {
1043 return dist == dist1? prevItem : nextItem;
1048 // @private toggle visibility of all series elements (markers, sprites).
1049 toggleAll: function(show) {
1051 i, ln, shadow, shadows;
1053 Ext.chart.series.Line.superclass.hideAll.call(me);
1056 Ext.chart.series.Line.superclass.showAll.call(me);
1059 me.line.setAttributes({
1063 if (me.line.shadows) {
1064 for (i = 0, shadows = me.line.shadows, ln = shadows.length; i < ln; i++) {
1065 shadow = shadows[i];
1066 shadow.setAttributes({
1073 me.fillPath.setAttributes({
1079 // @private hide all series elements (markers, sprites).
1080 hideAll: function() {
1081 this.toggleAll(false);
1084 // @private hide all series elements (markers, sprites).
1085 showAll: function() {
1086 this.toggleAll(true);