3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
16 * @class Ext.chart.series.Line
17 * @extends Ext.chart.series.Cartesian
19 * Creates a Line Chart. A Line Chart is a useful visualization technique to display quantitative information for different
20 * categories or other real values (as opposed to the bar chart), that can show some progression (or regression) in the dataset.
21 * As with all other series, the Line Series must be appended in the *series* Chart array configuration. See the Chart
22 * documentation for more information. A typical configuration object for the line series could be:
25 * var store = Ext.create('Ext.data.JsonStore', {
26 * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
28 * { 'name': 'metric one', 'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8, 'data5': 13 },
29 * { 'name': 'metric two', 'data1': 7, 'data2': 8, 'data3': 16, 'data4': 10, 'data5': 3 },
30 * { 'name': 'metric three', 'data1': 5, 'data2': 2, 'data3': 14, 'data4': 12, 'data5': 7 },
31 * { 'name': 'metric four', 'data1': 2, 'data2': 14, 'data3': 6, 'data4': 1, 'data5': 23 },
32 * { 'name': 'metric five', 'data1': 4, 'data2': 4, 'data3': 36, 'data4': 13, 'data5': 33 }
36 * Ext.create('Ext.chart.Chart', {
37 * renderTo: Ext.getBody(),
46 * fields: ['data1', 'data2'],
48 * renderer: Ext.util.Format.numberRenderer('0,0')
50 * title: 'Sample Values',
58 * title: 'Sample Metrics'
98 * In this configuration we're adding two series (or lines), one bound to the `data1`
99 * property of the store and the other to `data3`. The type for both configurations is
100 * `line`. The `xField` for both series is the same, the name propert of the store.
101 * Both line series share the same axis, the left axis. You can set particular marker
102 * configuration by adding properties onto the markerConfig object. Both series have
103 * an object as highlight so that markers animate smoothly to the properties in highlight
104 * when hovered. The second series has `fill=true` which means that the line will also
105 * have an area below it of the same color.
107 * **Note:** In the series definition remember to explicitly set the axis to bind the
108 * values of the line series to. This can be done by using the `axis` configuration property.
110 Ext.define('Ext.chart.series.Line', {
112 /* Begin Definitions */
114 extend: 'Ext.chart.series.Cartesian',
116 alternateClassName: ['Ext.chart.LineSeries', 'Ext.chart.LineChart'],
118 requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.draw.Draw', 'Ext.fx.Anim'],
120 /* End Definitions */
124 alias: 'series.line',
128 * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
129 * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
130 * relative scale will be used.
134 * @cfg {Number} selectionTolerance
135 * The offset distance from the cursor position to the line series to trigger events (then used for highlighting series, etc).
137 selectionTolerance: 20,
140 * @cfg {Boolean} showMarkers
141 * Whether markers should be displayed at the data points along the line. If true,
142 * then the {@link #markerConfig} config item will determine the markers' styling.
147 * @cfg {Object} markerConfig
148 * The display style for the markers. Only used if {@link #showMarkers} is true.
149 * The markerConfig is a configuration object containing the same set of properties defined in
150 * the Sprite class. For example, if we were to set red circles as markers to the line series we could
165 * @cfg {Object} style
166 * An object containing style properties for the visualization lines and fill.
167 * These styles will override the theme styles. The following are valid style properties:
169 * - `stroke` - an rgb or hex color string for the background color of the line
170 * - `stroke-width` - the width of the stroke (integer)
171 * - `fill` - the background fill color string (hex or rgb), only works if {@link #fill} is `true`
172 * - `opacity` - the opacity of the line and the fill color (decimal)
178 * 'stroke-width': 10,
186 * @cfg {Boolean/Number} smooth
187 * If set to `true` or a non-zero number, the line will be smoothed/rounded around its points; otherwise
188 * straight line segments will be drawn.
190 * A numeric value is interpreted as a divisor of the horizontal distance between consecutive points in
191 * the line; larger numbers result in sharper curves while smaller numbers result in smoother curves.
193 * If set to `true` then a default numeric value of 3 will be used. Defaults to `false`.
198 * @private Default numeric smoothing value to be used when {@link #smooth} = true.
200 defaultSmoothness: 3,
203 * @cfg {Boolean} fill
204 * If true, the area below the line will be filled in using the {@link #style eefill} and
205 * {@link #style opacity} config properties. Defaults to false.
209 constructor: function(config) {
210 this.callParent(arguments);
212 surface = me.chart.surface,
213 shadow = me.chart.shadow,
215 Ext.apply(me, config, {
221 "stroke-opacity": 0.05,
222 stroke: 'rgb(0, 0, 0)',
229 "stroke-opacity": 0.1,
230 stroke: 'rgb(0, 0, 0)',
237 "stroke-opacity": 0.15,
238 stroke: 'rgb(0, 0, 0)',
245 me.group = surface.getGroup(me.seriesId);
246 if (me.showMarkers) {
247 me.markerGroup = surface.getGroup(me.seriesId + '-markers');
250 for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
251 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
256 // @private makes an average of points when there are more data points than pixels to be rendered.
257 shrink: function(xValues, yValues, size) {
258 // Start at the 2nd point...
259 var len = xValues.length,
260 ratio = Math.floor(len / size),
267 for (; i < len; ++i) {
268 xSum += xValues[i] || 0;
269 ySum += yValues[i] || 0;
270 if (i % ratio == 0) {
271 xRes.push(xSum/ratio);
272 yRes.push(ySum/ratio);
284 * Draws the series for the current chart.
286 drawSeries: function() {
289 chartAxes = chart.axes,
290 store = chart.getChartStore(),
291 storeCount = store.getCount(),
292 surface = me.chart.surface,
295 showMarkers = me.showMarkers,
296 markerGroup = me.markerGroup,
297 enableShadows = chart.shadow,
298 shadowGroups = me.shadowGroups,
299 shadowAttributes = me.shadowAttributes,
301 lnsh = shadowGroups.length,
306 markerIndex = chart.markerIndex,
307 axes = [].concat(me.axis),
315 markerStyle = me.markerStyle,
316 seriesStyle = me.style,
317 colorArrayStyle = me.colorArrayStyle,
318 colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
319 isNumber = Ext.isNumber,
320 seriesIdx = me.seriesIdx,
321 boundAxes = me.getAxesForXAndYFields(),
322 boundXAxis = boundAxes.xAxis,
323 boundYAxis = boundAxes.yAxis,
324 shadows, shadow, shindex, fromPath, fill, fillPath, rendererAttributes,
325 x, y, prevX, prevY, firstX, firstY, markerCount, i, j, ln, axis, ends, marker, markerAux, item, xValue,
326 yValue, coords, xScale, yScale, minX, maxX, minY, maxY, line, animation, endMarkerStyle,
327 endLineStyle, type, count, items;
329 if (me.fireEvent('beforedraw', me) === false) {
333 //if store is empty or the series is excluded in the legend then there's nothing to draw.
334 if (!storeCount || me.seriesIsHidden) {
337 for (i = 0, ln = items.length; i < ln; ++i) {
338 if (items[i].sprite) {
339 items[i].sprite.hide(true);
346 //prepare style objects for line and markers
347 endMarkerStyle = Ext.apply(markerStyle || {}, me.markerConfig);
348 type = endMarkerStyle.type;
349 delete endMarkerStyle.type;
350 endLineStyle = seriesStyle;
351 //if no stroke with is specified force it to 0.5 because this is
352 //about making *lines*
353 if (!endLineStyle['stroke-width']) {
354 endLineStyle['stroke-width'] = 0.5;
356 //If we're using a time axis and we need to translate the points,
357 //then reuse the first markers as the last markers.
358 if (markerIndex && markerGroup && markerGroup.getCount()) {
359 for (i = 0; i < markerIndex; i++) {
360 marker = markerGroup.getAt(i);
361 markerGroup.remove(marker);
362 markerGroup.add(marker);
363 markerAux = markerGroup.getAt(markerGroup.getCount() - 2);
364 marker.setAttributes({
368 x: markerAux.attr.translation.x,
369 y: markerAux.attr.translation.y
375 me.unHighlightItem();
376 me.cleanHighlights();
380 me.clipRect = [bbox.x, bbox.y, bbox.width, bbox.height];
381 for (i = 0, ln = axes.length; i < ln; i++) {
382 axis = chartAxes.get(axes[i]);
384 ends = axis.calcEnds();
385 if (axis.position == 'top' || axis.position == 'bottom') {
395 // If a field was specified without a corresponding axis, create one to get bounds
396 //only do this for the axis where real values are bound (that's why we check for
398 if (me.xField && !isNumber(minX) &&
399 (boundXAxis == 'bottom' || boundXAxis == 'top') &&
400 !chartAxes.get(boundXAxis)) {
401 axis = Ext.create('Ext.chart.axis.Axis', {
403 fields: [].concat(me.xField)
408 if (me.yField && !isNumber(minY) &&
409 (boundYAxis == 'right' || boundYAxis == 'left') &&
410 !chartAxes.get(boundYAxis)) {
411 axis = Ext.create('Ext.chart.axis.Axis', {
413 fields: [].concat(me.yField)
420 xScale = bbox.width / ((storeCount - 1) || 1);
423 xScale = bbox.width / ((maxX - minX) || (storeCount -1) || 1);
428 yScale = bbox.height / ((storeCount - 1) || 1);
431 yScale = bbox.height / ((maxY - minY) || (storeCount - 1) || 1);
434 // Extract all x and y values from the store
435 me.eachRecord(function(record, i) {
436 xValue = record.get(me.xField);
439 if (typeof xValue == 'string' || typeof xValue == 'object' && !Ext.isDate(xValue)
440 //set as uniform distribution if the axis is a category axis.
441 || boundXAxis && chartAxes.get(boundXAxis) && chartAxes.get(boundXAxis).type == 'Category') {
442 if (xValue in xValueMap) {
443 xValue = xValueMap[xValue];
445 xValue = xValueMap[xValue] = i;
449 // Filter out values that don't fit within the pan/zoom buffer area
450 yValue = record.get(me.yField);
451 //skip undefined values
452 if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)) {
454 if (Ext.isDefined(Ext.global.console)) {
455 Ext.global.console.warn("[Ext.chart.series.Line] Skipping a store element with an undefined value at ", record, xValue, yValue);
461 if (typeof yValue == 'string' || typeof yValue == 'object' && !Ext.isDate(yValue)
462 //set as uniform distribution if the axis is a category axis.
463 || boundYAxis && chartAxes.get(boundYAxis) && chartAxes.get(boundYAxis).type == 'Category') {
466 storeIndices.push(i);
467 xValues.push(xValue);
468 yValues.push(yValue);
472 if (ln > bbox.width) {
473 coords = me.shrink(xValues, yValues, bbox.width);
482 for (i = 0; i < ln; i++) {
485 if (yValue === false) {
486 if (path.length == 1) {
490 me.items.push(false);
493 x = (bbox.x + (xValue - minX) * xScale).toFixed(2);
494 y = ((bbox.y + bbox.height) - (yValue - minY) * yScale).toFixed(2);
499 path = path.concat([x, y]);
501 if ((typeof firstY == 'undefined') && (typeof y != 'undefined')) {
505 // If this is the first line, create a dummypath to animate in from.
506 if (!me.line || chart.resizing) {
507 dummyPath = dummyPath.concat([x, bbox.y + bbox.height / 2]);
510 // When resizing, reset before animating
511 if (chart.animate && chart.resizing && me.line) {
512 me.line.setAttributes({
516 me.fillPath.setAttributes({
521 if (me.line.shadows) {
522 shadows = me.line.shadows;
523 for (j = 0, lnsh = shadows.length; j < lnsh; j++) {
525 shadow.setAttributes({
532 marker = markerGroup.getAt(count++);
534 marker = Ext.chart.Shape[type](surface, Ext.apply({
535 group: [group, markerGroup],
539 y: prevY || (bbox.y + bbox.height / 2)
541 value: '"' + xValue + ', ' + yValue + '"',
551 marker.setAttributes({
552 value: '"' + xValue + ', ' + yValue + '"',
566 value: [xValue, yValue],
569 storeItem: store.getAt(storeIndices[i])
575 if (path.length <= 1) {
576 //nothing to be rendered
581 smoothPath = Ext.draw.Draw.smooth(path, isNumber(smooth) ? smooth : me.defaultSmoothness);
584 renderPath = smooth ? smoothPath : path;
586 //Correct path if we're animating timeAxis intervals
587 if (chart.markerIndex && me.previousPath) {
588 fromPath = me.previousPath;
590 Ext.Array.erase(fromPath, 1, 2);
596 // Only create a line if one doesn't exist.
598 me.line = surface.add(Ext.apply({
602 stroke: endLineStyle.stroke || endLineStyle.fill
603 }, endLineStyle || {}));
606 me.line.setAttributes(Ext.apply({}, me.shadowOptions), true);
609 //unset fill here (there's always a default fill withing the themes).
610 me.line.setAttributes({
614 if (!endLineStyle.stroke && colorArrayLength) {
615 me.line.setAttributes({
616 stroke: colorArrayStyle[seriesIdx % colorArrayLength]
621 shadows = me.line.shadows = [];
622 for (shindex = 0; shindex < lnsh; shindex++) {
623 shadowBarAttr = shadowAttributes[shindex];
624 shadowBarAttr = Ext.apply({}, shadowBarAttr, { path: dummyPath });
625 shadow = surface.add(Ext.apply({}, {
627 group: shadowGroups[shindex]
629 shadows.push(shadow);
634 fillPath = renderPath.concat([
635 ["L", x, bbox.y + bbox.height],
636 ["L", firstX, bbox.y + bbox.height],
637 ["L", firstX, firstY]
640 me.fillPath = surface.add({
643 opacity: endLineStyle.opacity || 0.3,
644 fill: endLineStyle.fill || colorArrayStyle[seriesIdx % colorArrayLength],
649 markerCount = showMarkers && markerGroup.getCount();
653 //Add renderer to line. There is not unique record associated with this.
654 rendererAttributes = me.renderer(line, false, { path: renderPath }, i, store);
655 Ext.apply(rendererAttributes, endLineStyle || {}, {
656 stroke: endLineStyle.stroke || endLineStyle.fill
658 //fill should not be used here but when drawing the special fill path object
659 delete rendererAttributes.fill;
661 if (chart.markerIndex && me.previousPath) {
662 me.animation = animation = me.onAnimate(line, {
663 to: rendererAttributes,
669 me.animation = animation = me.onAnimate(line, {
670 to: rendererAttributes
675 shadows = line.shadows;
676 for(j = 0; j < lnsh; j++) {
677 shadows[j].show(true);
678 if (chart.markerIndex && me.previousPath) {
679 me.onAnimate(shadows[j], {
680 to: { path: renderPath },
681 from: { path: fromPath }
684 me.onAnimate(shadows[j], {
685 to: { path: renderPath }
692 me.fillPath.show(true);
693 me.onAnimate(me.fillPath, {
696 fill: endLineStyle.fill || colorArrayStyle[seriesIdx % colorArrayLength],
698 }, endLineStyle || {})
704 for(i = 0; i < ln; i++) {
706 item = markerGroup.getAt(count++);
708 rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
710 to: Ext.apply(rendererAttributes, endMarkerStyle || {})
716 for(; count < markerCount; count++) {
717 item = markerGroup.getAt(count);
720 // for(i = 0; i < (chart.markerIndex || 0)-1; i++) {
721 // item = markerGroup.getAt(i);
726 rendererAttributes = me.renderer(me.line, false, { path: renderPath, hidden: false }, i, store);
727 Ext.apply(rendererAttributes, endLineStyle || {}, {
728 stroke: endLineStyle.stroke || endLineStyle.fill
730 //fill should not be used here but when drawing the special fill path object
731 delete rendererAttributes.fill;
732 me.line.setAttributes(rendererAttributes, true);
733 //set path for shadows
735 shadows = me.line.shadows;
736 for(j = 0; j < lnsh; j++) {
737 shadows[j].setAttributes({
744 me.fillPath.setAttributes({
751 for(i = 0; i < ln; i++) {
753 item = markerGroup.getAt(count++);
755 rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
756 item.setAttributes(Ext.apply(endMarkerStyle || {}, rendererAttributes || {}), true);
761 for(; count < markerCount; count++) {
762 item = markerGroup.getAt(count);
768 if (chart.markerIndex) {
770 Ext.Array.erase(path, 1, 2);
772 Ext.Array.splice(path, 1, 0, path[1], path[2]);
774 me.previousPath = path;
779 me.fireEvent('draw', me);
782 // @private called when a label is to be created.
783 onCreateLabel: function(storeItem, item, i, display) {
785 group = me.labelsGroup,
788 endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
790 return me.chart.surface.add(Ext.apply({
792 'text-anchor': 'middle',
795 'y': bbox.y + bbox.height / 2
796 }, endLabelStyle || {}));
799 // @private called when a label is to be created.
800 onPlaceLabel: function(label, storeItem, item, i, display, animate) {
803 resizing = chart.resizing,
805 format = config.renderer,
806 field = config.field,
810 radius = item.sprite.attr.radius,
813 label.setAttributes({
814 text: format(storeItem.get(field)),
818 if (display == 'rotate') {
819 label.setAttributes({
820 'text-anchor': 'start',
827 //correct label position to fit into the box
828 bb = label.getBBox();
831 x = x < bbox.x? bbox.x : x;
832 x = (x + width > bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
833 y = (y - height < bbox.y)? bbox.y + height : y;
835 } else if (display == 'under' || display == 'over') {
836 //TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
837 bb = item.sprite.getBBox();
838 bb.width = bb.width || (radius * 2);
839 bb.height = bb.height || (radius * 2);
840 y = y + (display == 'over'? -bb.height : bb.height);
841 //correct label position to fit into the box
842 bb = label.getBBox();
844 height = bb.height/2;
845 x = x - width < bbox.x? bbox.x + width : x;
846 x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
847 y = y - height < bbox.y? bbox.y + height : y;
848 y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
851 if (me.chart.animate && !me.chart.resizing) {
853 me.onAnimate(label, {
860 label.setAttributes({
864 if (resizing && me.animation) {
865 me.animation.on('afteranimate', function() {
874 //@private Overriding highlights.js highlightItem method.
875 highlightItem: function() {
877 me.callParent(arguments);
878 if (me.line && !me.highlighted) {
879 if (!('__strokeWidth' in me.line)) {
880 me.line.__strokeWidth = me.line.attr['stroke-width'] || 0;
882 if (me.line.__anim) {
883 me.line.__anim.paused = true;
885 me.line.__anim = Ext.create('Ext.fx.Anim', {
888 'stroke-width': me.line.__strokeWidth + 3
891 me.highlighted = true;
895 //@private Overriding highlights.js unHighlightItem method.
896 unHighlightItem: function() {
898 me.callParent(arguments);
899 if (me.line && me.highlighted) {
900 me.line.__anim = Ext.create('Ext.fx.Anim', {
903 'stroke-width': me.line.__strokeWidth
906 me.highlighted = false;
910 //@private called when a callout needs to be placed.
911 onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
918 surface = chart.surface,
919 resizing = chart.resizing,
920 config = me.callouts,
922 prev = i == 0? false : items[i -1].point,
923 next = (i == items.length -1)? false : items[i +1].point,
924 cur = [+item.point[0], +item.point[1]],
925 dir, norm, normal, a, aprev, anext,
926 offsetFromViz = config.offsetFromViz || 30,
927 offsetToSide = config.offsetToSide || 10,
928 offsetBox = config.offsetBox || 3,
929 boxx, boxy, boxw, boxh,
930 p, clipRect = me.clipRect,
932 width: config.styles.width || 10,
933 height: config.styles.height || 10
937 //get the right two points
944 a = (next[1] - prev[1]) / (next[0] - prev[0]);
945 aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
946 anext = (next[1] - cur[1]) / (next[0] - cur[0]);
948 norm = Math.sqrt(1 + a * a);
949 dir = [1 / norm, a / norm];
950 normal = [-dir[1], dir[0]];
952 //keep the label always on the outer part of the "elbow"
953 if (aprev > 0 && anext < 0 && normal[1] < 0
954 || aprev < 0 && anext > 0 && normal[1] > 0) {
957 } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0
958 || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
963 x = cur[0] + normal[0] * offsetFromViz;
964 y = cur[1] + normal[1] * offsetFromViz;
966 //box position and dimensions
967 boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
968 boxy = y - bbox.height /2 - offsetBox;
969 boxw = bbox.width + 2 * offsetBox;
970 boxh = bbox.height + 2 * offsetBox;
972 //now check if we're out of bounds and invert the normal vector correspondingly
973 //this may add new overlaps between labels (but labels won't be out of bounds).
974 if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
977 if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
982 x = cur[0] + normal[0] * offsetFromViz;
983 y = cur[1] + normal[1] * offsetFromViz;
985 //update box position and dimensions
986 boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
987 boxy = y - bbox.height /2 - offsetBox;
988 boxw = bbox.width + 2 * offsetBox;
989 boxh = bbox.height + 2 * offsetBox;
992 //set the line from the middle of the pie to the box.
993 me.onAnimate(callout.lines, {
995 path: ["M", cur[0], cur[1], "L", x, y, "Z"]
998 //set component position
1000 callout.panel.setPosition(boxx, boxy, true);
1004 //set the line from the middle of the pie to the box.
1005 callout.lines.setAttributes({
1006 path: ["M", cur[0], cur[1], "L", x, y, "Z"]
1008 //set component position
1009 if (callout.panel) {
1010 callout.panel.setPosition(boxx, boxy);
1013 for (p in callout) {
1014 callout[p].show(true);
1018 isItemInPoint: function(x, y, item, i) {
1021 tolerance = me.selectionTolerance,
1034 dist1, dist2, dist, midx, midy,
1035 sqrt = Math.sqrt, abs = Math.abs;
1037 nextItem = items[i];
1038 prevItem = i && items[i - 1];
1041 prevItem = items[ln - 1];
1043 prevPoint = prevItem && prevItem.point;
1044 nextPoint = nextItem && nextItem.point;
1045 x1 = prevItem ? prevPoint[0] : nextPoint[0] - tolerance;
1046 y1 = prevItem ? prevPoint[1] : nextPoint[1];
1047 x2 = nextItem ? nextPoint[0] : prevPoint[0] + tolerance;
1048 y2 = nextItem ? nextPoint[1] : prevPoint[1];
1049 dist1 = sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
1050 dist2 = sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
1051 dist = Math.min(dist1, dist2);
1053 if (dist <= tolerance) {
1054 return dist == dist1? prevItem : nextItem;
1059 // @private toggle visibility of all series elements (markers, sprites).
1060 toggleAll: function(show) {
1062 i, ln, shadow, shadows;
1064 Ext.chart.series.Cartesian.prototype.hideAll.call(me);
1067 Ext.chart.series.Cartesian.prototype.showAll.call(me);
1070 me.line.setAttributes({
1074 if (me.line.shadows) {
1075 for (i = 0, shadows = me.line.shadows, ln = shadows.length; i < ln; i++) {
1076 shadow = shadows[i];
1077 shadow.setAttributes({
1084 me.fillPath.setAttributes({
1090 // @private hide all series elements (markers, sprites).
1091 hideAll: function() {
1092 this.toggleAll(false);
1095 // @private hide all series elements (markers, sprites).
1096 showAll: function() {
1097 this.toggleAll(true);