/**
* @class Ext.chart.series.Line
* @extends Ext.chart.series.Cartesian
*
* Creates a Line Chart. A Line Chart is a useful visualization technique to display quantitative information for different
* categories or other real values (as opposed to the bar chart), that can show some progression (or regression) in the dataset.
* As with all other series, the Line Series must be appended in the *series* Chart array configuration. See the Chart
* documentation for more information. A typical configuration object for the line series could be:
*
series: [{
type: 'line',
highlight: {
size: 7,
radius: 7
},
axis: 'left',
xField: 'name',
yField: 'data1',
markerCfg: {
type: 'cross',
size: 4,
radius: 4,
'stroke-width': 0
}
}, {
type: 'line',
highlight: {
size: 7,
radius: 7
},
axis: 'left',
fill: true,
xField: 'name',
yField: 'data3',
markerCfg: {
type: 'circle',
size: 4,
radius: 4,
'stroke-width': 0
}
}]
*
* In this configuration we're adding two series (or lines), one bound to the `data1` property of the store and the other to `data3`. The type for both configurations is
* `line`. The `xField` for both series is the same, the name propert of the store. Both line series share the same axis, the left axis. You can set particular marker
* configuration by adding properties onto the markerCfg object. Both series have an object as highlight so that markers animate smoothly to the properties in highlight
* when hovered. The second series has `fill=true` which means that the line will also have an area below it of the same color.
*
*/
Ext.define('Ext.chart.series.Line', {
/* Begin Definitions */
extend: 'Ext.chart.series.Cartesian',
requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shapes', 'Ext.draw.Draw', 'Ext.fx.Anim'],
/* End Definitions */
type: 'line',
/**
* @cfg {Number} selectionTolerance
* The offset distance from the cursor position to the line series to trigger events (then used for highlighting series, etc).
*/
selectionTolerance: 20,
/**
* @cfg {Boolean} showMarkers
* Whether markers should be displayed at the date points along the line. If true,
* then the {@link #markerCfg} config item will determine the markers' styling.
*/
showMarkers: true,
/**
* @cfg {Object} markerCfg
* The display style for the markers. Only used if {@link #showMarkers} is true.
*/
markerCfg: {},
/**
* @cfg {Object} style
* An object containing styles for the visualization lines. These styles will override the theme styles.
* Some options contained within the style object will are described next.
*/
style: {},
/**
* @cfg {String} dash
* Optional dash array for the line.
*/
dash: '',
/**
* @cfg {Boolean} smooth
* If true, the line will be smoothed/rounded around its points, otherwise straight line
* segments will be drawn. Defaults to false.
*/
//smooth: false,
/**
* @cfg {Boolean} fill
* If true, the area below the line will be filled in using the {@link #style.eefill} and
* {@link #style.opacity} config properties. Defaults to false.
*/
fill: false,
constructor: function(config) {
this.callParent(arguments);
var me = this,
surface = me.chart.surface,
shadow = me.chart.shadow,
i, l;
Ext.apply(me, config, {
highlightCfg: {
lineWidth: 3
},
shadowAttributes: [{
"stroke-width": 6,
"stroke-opacity": 0.05,
stroke: 'rgb(0, 0, 0)',
translate: {
x: 1,
y: 1
}
}, {
"stroke-width": 4,
"stroke-opacity": 0.1,
stroke: 'rgb(0, 0, 0)',
translate: {
x: 1,
y: 1
}
}, {
"stroke-width": 2,
"stroke-opacity": 0.15,
stroke: 'rgb(0, 0, 0)',
translate: {
x: 1,
y: 1
}
}]
});
me.group = surface.getGroup(me.seriesId);
if (me.showMarkers) {
me.markerGroup = surface.getGroup(me.seriesId + '-markers');
}
if (shadow) {
for (i = 0, l = this.shadowAttributes.length; i < l; i++) {
me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
}
}
},
// @private makes an average of points when there are more data points than pixels to be rendered.
shrink: function(xValues, yValues, size) {
// Start at the 2nd point...
var len = xValues.length,
ratio = Math.floor(len / size),
i = 1,
xSum = 0,
ySum = 0,
xRes = [xValues[0]],
yRes = [yValues[0]];
for (; i < len; ++i) {
xSum += xValues[i] || 0;
ySum += yValues[i] || 0;
if (i % ratio == 0) {
xRes.push(xSum/ratio);
yRes.push(ySum/ratio);
xSum = 0;
ySum = 0;
}
}
return {
x: xRes,
y: yRes
};
},
/**
* Draws the series for the current chart.
*/
drawSeries: function() {
var me = this,
chart = me.chart,
store = chart.substore || chart.store,
surface = chart.surface,
chartBBox = chart.chartBBox,
bbox = {},
group = me.group,
gutterX = chart.maxGutter[0],
gutterY = chart.maxGutter[1],
showMarkers = me.showMarkers,
markerGroup = me.markerGroup,
enableShadows = chart.shadow,
shadowGroups = me.shadowGroups,
shadowGroup,
shadowAttributes = this.shadowAttributes,
shadowBarAttr,
shadows,
shadow,
shindex,
lnsh = shadowGroups.length,
dummyPath = ["M"],
path = ["M"],
fromPath,
fill,
fillPath,
rendererAttributes,
x, y,
prevX, prevY,
markerIndex = chart.markerIndex,
firstY,
markerCount,
i, j,
ln,
axis,
ends,
marker,
markerAux,
item,
xValue,
yValue,
coords,
xValues = [],
yValues = [],
xScale,
yScale,
minX,
maxX,
minY,
maxY,
line,
onbreak = false,
animation,
markerStyle = me.markerStyle,
seriesStyle = me.seriesStyle,
seriesLabelStyle = me.seriesLabelStyle,
colorArrayStyle = me.colorArrayStyle,
colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
seriesIdx = me.seriesIdx,
endMarkerStyle, endLineStyle, type,
props, firstMarker;
//prepare style objects for line and markers
endMarkerStyle = Ext.apply(markerStyle, me.markerCfg);
type = endMarkerStyle.type;
delete endMarkerStyle.type;
endLineStyle = Ext.apply(seriesStyle, me.style);
//if no stroke with is specified force it to 0.5 because this is
//about making *lines*
if (!endLineStyle['stroke-width']) {
endLineStyle['stroke-width'] = 0.5;
}
//If we're using a time axis and we need to translate the points,
//then reuse the first markers as the last markers.
if (markerIndex && markerGroup && markerGroup.getCount()) {
for (i = 0; i < markerIndex; i++) {
marker = markerGroup.getAt(i);
markerGroup.remove(marker);
markerGroup.add(marker);
markerAux = markerGroup.getAt(markerGroup.getCount() - 2);
marker.setAttributes({
x: 0,
y: 0,
translate: {
x: markerAux.attr.translation.x,
y: markerAux.attr.translation.y
}
});
}
}
me.unHighlightItem();
me.cleanHighlights();
//get box dimensions
me.bbox = bbox = {
x: chartBBox.x + gutterX,
y: chartBBox.y + gutterY,
width: chartBBox.width - (gutterX * 2),
height: chartBBox.height - (gutterY * 2)
};
me.clipRect = [bbox.x, bbox.y, bbox.width, bbox.height];
axis = chart.axes.get(me.axis);
if (axis) {
if (axis.position == 'top' || axis.position == 'bottom') {
minX = axis.from;
maxX = axis.to;
}
else {
minY = axis.from;
maxY = axis.to;
}
}
else {
if (me.xField) {
ends = new Ext.chart.axis.Axis({
chart: chart,
fields: [me.xField]
}).calcEnds();
minX = ends.from;
maxX = ends.to;
}
if (me.yField) {
ends = new Ext.chart.axis.Axis({
chart: chart,
fields: [me.yField]
}).calcEnds();
minY = ends.from;
maxY = ends.to;
}
}
if (isNaN(minX)) {
minX = 0;
xScale = bbox.width / (store.getCount() - 1);
}
else {
xScale = bbox.width / (maxX - minX);
}
if (isNaN(minY)) {
minY = 0;
yScale = bbox.height / (store.getCount() - 1);
}
else {
yScale = bbox.height / (maxY - minY);
}
store.each(function(record, i) {
xValue = record.get(me.xField);
yValue = record.get(me.yField);
// Ensure a value
if (typeof xValue == 'string' || typeof xValue == 'object') {
xValue = i;
}
if (typeof yValue == 'string') {
yValue = i;
}
xValues.push(xValue);
yValues.push(yValue);
}, me);
ln = xValues.length;
if (ln > bbox.width || ln > bbox.height) {
coords = me.shrink(xValues, yValues, Math.min(bbox.width, bbox.height));
xValues = coords.x;
yValues = coords.y;
}
me.items = [];
ln = xValues.length;
for (i = 0; i < ln; i++) {
xValue = xValues[i];
yValue = yValues[i];
if (yValue === false) {
if (path.length == 1) {
path = [];
}
onbreak = true;
continue;
} else {
x = (bbox.x + (xValue - minX) * xScale).toFixed(2);
y = ((bbox.y + bbox.height) - (yValue - minY) * yScale).toFixed(2);
if (onbreak) {
onbreak = false;
path.push('M');
}
path = path.concat([x, y]);
}
if ((typeof firstY == 'undefined') && (typeof y != 'undefined')) {
firstY = y;
}
// If this is the first line, create a dummypath to animate in from.
if (!me.line || chart.resizing) {
dummyPath = dummyPath.concat([x, bbox.y + bbox.height / 2]);
}
// When resizing, reset before animating
if (chart.animate && chart.resizing && me.line) {
me.line.setAttributes({
path: dummyPath
}, true);
if (me.fillPath) {
me.fillPath.setAttributes({
path: dummyPath,
opacity: 0.2
}, true);
}
if (me.line.shadows) {
shadows = me.line.shadows;
for (j = 0, lnsh = shadows.length; j < lnsh; j++) {
shadow = shadows[j];
shadow.setAttributes({
path: dummyPath
}, true);
}
}
}
if (showMarkers) {
marker = markerGroup.getAt(i);
if (!marker) {
marker = Ext.chart.Shapes[type](surface, Ext.apply({
group: [group, markerGroup],
x: 0, y: 0,
translate: {
x: prevX || x,
y: prevY || (bbox.y + bbox.height / 2)
},
value: '"' + xValue + ', ' + yValue + '"'
}, endMarkerStyle));
marker._to = {
translate: {
x: x,
y: y
}
};
} else {
marker.setAttributes({
value: '"' + xValue + ', ' + yValue + '"',
x: 0, y: 0,
hidden: false
}, true);
marker._to = {
translate: {
x: x, y: y
}
};
}
}
me.items.push({
series: me,
value: [xValue, yValue],
point: [x, y],
sprite: marker,
storeItem: store.getAt(i)
});
prevX = x;
prevY = y;
}
if (me.smooth) {
path = Ext.draw.Draw.smooth(path, 6);
}
//Correct path if we're animating timeAxis intervals
if (chart.markerIndex && me.previousPath) {
fromPath = me.previousPath;
fromPath.splice(1, 2);
} else {
fromPath = path;
}
// Only create a line if one doesn't exist.
if (!me.line) {
me.line = surface.add(Ext.apply({
type: 'path',
group: group,
path: dummyPath,
stroke: endLineStyle.stroke || endLineStyle.fill
}, endLineStyle || {}));
//unset fill here (there's always a default fill withing the themes).
me.line.setAttributes({
fill: 'none'
});
if (!endLineStyle.stroke && colorArrayLength) {
me.line.setAttributes({
stroke: colorArrayStyle[seriesIdx % colorArrayLength]
}, true);
}
if (enableShadows) {
//create shadows
shadows = me.line.shadows = [];
for (shindex = 0; shindex < lnsh; shindex++) {
shadowBarAttr = shadowAttributes[shindex];
shadowBarAttr = Ext.apply({}, shadowBarAttr, { path: dummyPath });
shadow = chart.surface.add(Ext.apply({}, {
type: 'path',
group: shadowGroups[shindex]
}, shadowBarAttr));
shadows.push(shadow);
}
}
}
if (me.fill) {
fillPath = path.concat([
["L", x, bbox.y + bbox.height],
["L", bbox.x, bbox.y + bbox.height],
["L", bbox.x, firstY]
]);
if (!me.fillPath) {
me.fillPath = surface.add({
group: group,
type: 'path',
opacity: endLineStyle.opacity || 0.3,
fill: colorArrayStyle[seriesIdx % colorArrayLength] || endLineStyle.fill,
path: dummyPath
});
}
}
markerCount = showMarkers && markerGroup.getCount();
if (chart.animate) {
fill = me.fill;
line = me.line;
//Add renderer to line. There is not unique record associated with this.
rendererAttributes = me.renderer(line, false, { path: path }, i, store);
Ext.apply(rendererAttributes, endLineStyle || {}, {
stroke: endLineStyle.stroke || endLineStyle.fill
});
//fill should not be used here but when drawing the special fill path object
delete rendererAttributes.fill;
if (chart.markerIndex && me.previousPath) {
me.animation = animation = me.onAnimate(line, {
to: rendererAttributes,
from: {
path: fromPath
}
});
} else {
me.animation = animation = me.onAnimate(line, {
to: rendererAttributes
});
}
//animate shadows
if (enableShadows) {
shadows = line.shadows;
for(j = 0; j < lnsh; j++) {
if (chart.markerIndex && me.previousPath) {
me.onAnimate(shadows[j], {
to: { path: path },
from: { path: fromPath }
});
} else {
me.onAnimate(shadows[j], {
to: { path: path }
});
}
}
}
//animate fill path
if (fill) {
me.onAnimate(me.fillPath, {
to: Ext.apply({}, {
path: fillPath,
fill: colorArrayStyle[seriesIdx % colorArrayLength] || endLineStyle.fill
}, endLineStyle || {})
});
}
//animate markers
if (showMarkers) {
for(i = 0; i < ln; i++) {
item = markerGroup.getAt(i);
if (item) {
rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
me.onAnimate(item, {
to: Ext.apply(rendererAttributes, endMarkerStyle || {})
});
}
}
for(; i < markerCount; i++) {
item = markerGroup.getAt(i);
item.hide(true);
}
// for(i = 0; i < (chart.markerIndex || 0)-1; i++) {
// item = markerGroup.getAt(i);
// item.hide(true);
// }
}
} else {
rendererAttributes = me.renderer(me.line, false, { path: path, hidden: false }, i, store);
Ext.apply(rendererAttributes, endLineStyle || {}, {
stroke: endLineStyle.stroke || endLineStyle.fill
});
//fill should not be used here but when drawing the special fill path object
delete rendererAttributes.fill;
me.line.setAttributes(rendererAttributes, true);
//set path for shadows
if (enableShadows) {
shadows = me.line.shadows;
for(j = 0; j < lnsh; j++) {
shadows[j].setAttributes({
path: path
});
}
}
if (me.fill) {
me.fillPath.setAttributes({
path: fillPath
}, true);
}
if (showMarkers) {
for(i = 0; i < ln; i++) {
item = markerGroup.getAt(i);
if (item) {
rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
item.setAttributes(Ext.apply(endMarkerStyle || {}, rendererAttributes || {}), true);
// me.onAnimate(item, {
// to: Ext.apply(rendererAttributes, endMarkerStyle || {})
// });
}
}
for(; i < markerCount; i++) {
item = markerGroup.getAt(i);
item.hide(true);
}
// for(i = 0; i < (chart.markerIndex || 0); i++) {
// item = markerGroup.getAt(i);
// item.hide(true);
// }
}
}
if (chart.markerIndex) {
path.splice(1, 0, path[1], path[2]);
me.previousPath = path;
}
me.renderLabels();
me.renderCallouts();
},
// @private called when a label is to be created.
onCreateLabel: function(storeItem, item, i, display) {
var me = this,
group = me.labelsGroup,
config = me.label,
bbox = me.bbox,
endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
return me.chart.surface.add(Ext.apply({
'type': 'text',
'text-anchor': 'middle',
'group': group,
'x': item.point[0],
'y': bbox.y + bbox.height / 2
}, endLabelStyle || {}));
},
// @private called when a label is to be created.
onPlaceLabel: function(label, storeItem, item, i, display, animate) {
var me = this,
chart = me.chart,
resizing = chart.resizing,
config = me.label,
format = config.renderer,
field = config.field,
bbox = me.bbox,
x = item.point[0],
y = item.point[1],
radius = item.sprite.attr.radius,
bb, width, height;
label.setAttributes({
text: format(storeItem.get(field)),
hidden: true
}, true);
if (display == 'rotate') {
label.setAttributes({
'text-anchor': 'start',
'rotation': {
x: x,
y: y,
degrees: -45
}
}, true);
//correct label position to fit into the box
bb = label.getBBox();
width = bb.width;
height = bb.height;
x = x < bbox.x? bbox.x : x;
x = (x + width > bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
y = (y - height < bbox.y)? bbox.y + height : y;
} else if (display == 'under' || display == 'over') {
//TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
bb = item.sprite.getBBox();
bb.width = bb.width || (radius * 2);
bb.height = bb.height || (radius * 2);
y = y + (display == 'over'? -bb.height : bb.height);
//correct label position to fit into the box
bb = label.getBBox();
width = bb.width/2;
height = bb.height/2;
x = x - width < bbox.x? bbox.x + width : x;
x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
y = y - height < bbox.y? bbox.y + height : y;
y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
}
if (me.chart.animate && !me.chart.resizing) {
label.show(true);
me.onAnimate(label, {
to: {
x: x,
y: y
}
});
} else {
label.setAttributes({
x: x,
y: y
}, true);
if (resizing) {
me.animation.on('afteranimate', function() {
label.show(true);
});
} else {
label.show(true);
}
}
},
//@private Overriding highlights.js highlightItem method.
highlightItem: function() {
var me = this;
me.callParent(arguments);
if (this.line && !this.highlighted) {
if (!('__strokeWidth' in this.line)) {
this.line.__strokeWidth = this.line.attr['stroke-width'] || 0;
}
if (this.line.__anim) {
this.line.__anim.paused = true;
}
this.line.__anim = new Ext.fx.Anim({
target: this.line,
to: {
'stroke-width': this.line.__strokeWidth + 3
}
});
this.highlighted = true;
}
},
//@private Overriding highlights.js unHighlightItem method.
unHighlightItem: function() {
var me = this;
me.callParent(arguments);
if (this.line && this.highlighted) {
this.line.__anim = new Ext.fx.Anim({
target: this.line,
to: {
'stroke-width': this.line.__strokeWidth
}
});
this.highlighted = false;
}
},
//@private called when a callout needs to be placed.
onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
var me = this,
chart = me.chart,
surface = chart.surface,
resizing = chart.resizing,
config = me.callouts,
items = me.items,
prev = i == 0? false : items[i -1].point,
next = (i == items.length -1)? false : items[i +1].point,
cur = item.point,
dir, norm, normal, a, aprev, anext,
bbox = callout.label.getBBox(),
offsetFromViz = 30,
offsetToSide = 10,
offsetBox = 3,
boxx, boxy, boxw, boxh,
p, clipRect = me.clipRect,
x, y;
//get the right two points
if (!prev) {
prev = cur;
}
if (!next) {
next = cur;
}
a = (next[1] - prev[1]) / (next[0] - prev[0]);
aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
anext = (next[1] - cur[1]) / (next[0] - cur[0]);
norm = Math.sqrt(1 + a * a);
dir = [1 / norm, a / norm];
normal = [-dir[1], dir[0]];
//keep the label always on the outer part of the "elbow"
if (aprev > 0 && anext < 0 && normal[1] < 0 || aprev < 0 && anext > 0 && normal[1] > 0) {
normal[0] *= -1;
normal[1] *= -1;
} else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0 || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
normal[0] *= -1;
normal[1] *= -1;
}
//position
x = cur[0] + normal[0] * offsetFromViz;
y = cur[1] + normal[1] * offsetFromViz;
//box position and dimensions
boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
boxy = y - bbox.height /2 - offsetBox;
boxw = bbox.width + 2 * offsetBox;
boxh = bbox.height + 2 * offsetBox;
//now check if we're out of bounds and invert the normal vector correspondingly
//this may add new overlaps between labels (but labels won't be out of bounds).
if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
normal[0] *= -1;
}
if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
normal[1] *= -1;
}
//update positions
x = cur[0] + normal[0] * offsetFromViz;
y = cur[1] + normal[1] * offsetFromViz;
//update box position and dimensions
boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
boxy = y - bbox.height /2 - offsetBox;
boxw = bbox.width + 2 * offsetBox;
boxh = bbox.height + 2 * offsetBox;
if (chart.animate) {
//set the line from the middle of the pie to the box.
me.onAnimate(callout.lines, {
to: {
path: ["M", cur[0], cur[1], "L", x, y, "Z"]
}
});
//set box position
me.onAnimate(callout.box, {
to: {
x: boxx,
y: boxy,
width: boxw,
height: boxh
}
});
//set text position
me.onAnimate(callout.label, {
to: {
x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
y: y
}
});
}
else {
//set the line from the middle of the pie to the box.
callout.lines.setAttributes({
path: ["M", cur[0], cur[1], "L", x, y, "Z"]
}, true);
//set box position
callout.box.setAttributes({
x: boxx,
y: boxy,
width: boxw,
height: boxh
}, true);
//set text position
callout.label.setAttributes({
x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
y: y
}, true);
}
for (p in callout) {
callout[p].show(true);
}
},
/**
* For a given x/y point relative to the Surface, find a corresponding item from this
* series, if any.
*
* For Line series, this snaps to the nearest vertex if the target point is within a
* certain vertical distance from the line.
*
* @param {Number} x
* @param {Number} y
* @return {Object}
*/
getItemForPoint: function(x, y) {
var me = this,
items = me.items,
tolerance = me.selectionTolerance,
result = null,
prevItem,
nextItem,
prevPoint,
nextPoint,
i,
ln,
x1,
y1,
x2,
y2,
xIntersect,
yIntersect,
dist1, dist2, dist, midx, midy,
sqrt = Math.sqrt, abs = Math.abs;
if (items && items.length) {
// Find coordinates for the vertices before and after the target point
for (i = 0, ln = items.length; i < ln; i++) {
if (items[i].point[0] >= x) {
nextItem = items[i];
prevItem = i && items[i - 1];
break;
}
}
if (i >= ln) {
prevItem = items[ln - 1];
}
prevPoint = prevItem && prevItem.point;
nextPoint = nextItem && nextItem.point;
x1 = prevItem ? prevPoint[0] : nextPoint[0] - tolerance;
y1 = prevItem ? prevPoint[1] : nextPoint[1];
x2 = nextItem ? nextPoint[0] : prevPoint[0] + tolerance;
y2 = nextItem ? nextPoint[1] : prevPoint[1];
dist1 = sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
dist2 = sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
dist = Math.min(dist1, dist2);
if (dist <= tolerance) {
return dist == dist1? prevItem : nextItem;
}
/*Doesn't work properly. Fix for later.
// Determine whether the point is within the vertical tolerance distance from a straight
// line between the two vertices (TODO make this accurate for curved lines)
if (x >= x1 && x <= x2) {
yIntersect = (y2 - y1) / (x2 - x1) * (x - x1) + y1;
console.log('x', x, 'y', y, 'yIntersect', yIntersect, 'y1', y1, 'y2', y2, 'x1', x1, 'x2', x2);
if (Math.abs(yIntersect - y) <= tolerance) {
result = (x2 - x < x - x1) ? nextItem : prevItem;
}
}
*/
}
return result;
},
// @private toggle visibility of all series elements (markers, sprites).
toggleAll: function(show) {
var me = this,
i, ln, shadow, shadows;
if (!show) {
Ext.chart.series.Line.superclass.hideAll.call(me);
}
else {
Ext.chart.series.Line.superclass.showAll.call(me);
}
if (me.line) {
me.line.setAttributes({
hidden: !show
}, true);
//hide shadows too
if (me.line.shadows) {
for (i = 0, shadows = me.line.shadows, ln = shadows.length; i < ln; i++) {
shadow = shadows[i];
shadow.setAttributes({
hidden: !show
}, true);
}
}
}
if (me.fillPath) {
me.fillPath.setAttributes({
hidden: !show
}, true);
}
},
// @private hide all series elements (markers, sprites).
hideAll: function() {
this.toggleAll(false);
},
// @private hide all series elements (markers, sprites).
showAll: function() {
this.toggleAll(true);
}
});