<!DOCTYPE html>
|
<html>
|
<head>
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<title>The source code</title>
|
<link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
|
<script type="text/javascript" src="../resources/prettify/prettify.js"></script>
|
<style type="text/css">
|
.highlight { display: block; background-color: #ddd; }
|
</style>
|
<script type="text/javascript">
|
function highlight() {
|
document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
|
}
|
</script>
|
</head>
|
<body onload="prettyPrint(); highlight();">
|
<pre class="prettyprint lang-js"><span id='Ext-chart-series-Line'>/**
|
</span> * @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:
|
*
|
* @example
|
* var store = Ext.create('Ext.data.JsonStore', {
|
* fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
|
* data: [
|
* { 'name': 'metric one', 'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8, 'data5': 13 },
|
* { 'name': 'metric two', 'data1': 7, 'data2': 8, 'data3': 16, 'data4': 10, 'data5': 3 },
|
* { 'name': 'metric three', 'data1': 5, 'data2': 2, 'data3': 14, 'data4': 12, 'data5': 7 },
|
* { 'name': 'metric four', 'data1': 2, 'data2': 14, 'data3': 6, 'data4': 1, 'data5': 23 },
|
* { 'name': 'metric five', 'data1': 4, 'data2': 4, 'data3': 36, 'data4': 13, 'data5': 33 }
|
* ]
|
* });
|
*
|
* Ext.create('Ext.chart.Chart', {
|
* renderTo: Ext.getBody(),
|
* width: 500,
|
* height: 300,
|
* animate: true,
|
* store: store,
|
* axes: [
|
* {
|
* type: 'Numeric',
|
* position: 'left',
|
* fields: ['data1', 'data2'],
|
* label: {
|
* renderer: Ext.util.Format.numberRenderer('0,0')
|
* },
|
* title: 'Sample Values',
|
* grid: true,
|
* minimum: 0
|
* },
|
* {
|
* type: 'Category',
|
* position: 'bottom',
|
* fields: ['name'],
|
* title: 'Sample Metrics'
|
* }
|
* ],
|
* series: [
|
* {
|
* type: 'line',
|
* highlight: {
|
* size: 7,
|
* radius: 7
|
* },
|
* axis: 'left',
|
* xField: 'name',
|
* yField: 'data1',
|
* markerConfig: {
|
* type: 'cross',
|
* size: 4,
|
* radius: 4,
|
* 'stroke-width': 0
|
* }
|
* },
|
* {
|
* type: 'line',
|
* highlight: {
|
* size: 7,
|
* radius: 7
|
* },
|
* axis: 'left',
|
* fill: true,
|
* xField: 'name',
|
* yField: 'data2',
|
* markerConfig: {
|
* 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 markerConfig 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.
|
*
|
* **Note:** In the series definition remember to explicitly set the axis to bind the
|
* values of the line series to. This can be done by using the `axis` configuration property.
|
*/
|
Ext.define('Ext.chart.series.Line', {
|
|
/* Begin Definitions */
|
|
extend: 'Ext.chart.series.Cartesian',
|
|
alternateClassName: ['Ext.chart.LineSeries', 'Ext.chart.LineChart'],
|
|
requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.draw.Draw', 'Ext.fx.Anim'],
|
|
<span id='Ext-chart-series-Line-cfg-type'> /* End Definitions */
|
</span>
|
type: 'line',
|
|
alias: 'series.line',
|
|
<span id='Ext-chart-series-Line-cfg-selectionTolerance'> /**
|
</span> * @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,
|
|
<span id='Ext-chart-series-Line-cfg-showMarkers'> /**
|
</span> * @cfg {Boolean} showMarkers
|
* Whether markers should be displayed at the data points along the line. If true,
|
* then the {@link #markerConfig} config item will determine the markers' styling.
|
*/
|
showMarkers: true,
|
|
<span id='Ext-chart-series-Line-cfg-markerConfig'> /**
|
</span> * @cfg {Object} markerConfig
|
* The display style for the markers. Only used if {@link #showMarkers} is true.
|
* The markerConfig is a configuration object containing the same set of properties defined in
|
* the Sprite class. For example, if we were to set red circles as markers to the line series we could
|
* pass the object:
|
*
|
<pre><code>
|
markerConfig: {
|
type: 'circle',
|
radius: 4,
|
'fill': '#f00'
|
}
|
</code></pre>
|
|
*/
|
markerConfig: {},
|
|
<span id='Ext-chart-series-Line-cfg-style'> /**
|
</span> * @cfg {Object} style
|
* An object containing style properties for the visualization lines and fill.
|
* These styles will override the theme styles. The following are valid style properties:
|
*
|
* - `stroke` - an rgb or hex color string for the background color of the line
|
* - `stroke-width` - the width of the stroke (integer)
|
* - `fill` - the background fill color string (hex or rgb), only works if {@link #fill} is `true`
|
* - `opacity` - the opacity of the line and the fill color (decimal)
|
*
|
* Example usage:
|
*
|
* style: {
|
* stroke: '#00ff00',
|
* 'stroke-width': 10,
|
* fill: '#80A080',
|
* opacity: 0.2
|
* }
|
*/
|
style: {},
|
|
<span id='Ext-chart-series-Line-cfg-smooth'> /**
|
</span> * @cfg {Boolean/Number} smooth
|
* If set to `true` or a non-zero number, the line will be smoothed/rounded around its points; otherwise
|
* straight line segments will be drawn.
|
*
|
* A numeric value is interpreted as a divisor of the horizontal distance between consecutive points in
|
* the line; larger numbers result in sharper curves while smaller numbers result in smoother curves.
|
*
|
* If set to `true` then a default numeric value of 3 will be used. Defaults to `false`.
|
*/
|
smooth: false,
|
|
<span id='Ext-chart-series-Line-property-defaultSmoothness'> /**
|
</span> * @private Default numeric smoothing value to be used when {@link #smooth} = true.
|
*/
|
defaultSmoothness: 3,
|
|
<span id='Ext-chart-series-Line-cfg-fill'> /**
|
</span> * @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,
|
|
<span id='Ext-chart-series-Line-method-constructor'> constructor: function(config) {
|
</span> this.callParent(arguments);
|
var me = this,
|
surface = me.chart.surface,
|
shadow = me.chart.shadow,
|
i, l;
|
config.highlightCfg = Ext.Object.merge({ 'stroke-width': 3 }, config.highlightCfg);
|
Ext.apply(me, config, {
|
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 = me.shadowAttributes.length; i < l; i++) {
|
me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
|
}
|
}
|
},
|
|
<span id='Ext-chart-series-Line-method-shrink'> // @private makes an average of points when there are more data points than pixels to be rendered.
|
</span> 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
|
};
|
},
|
|
<span id='Ext-chart-series-Line-method-drawSeries'> /**
|
</span> * Draws the series for the current chart.
|
*/
|
drawSeries: function() {
|
var me = this,
|
chart = me.chart,
|
chartAxes = chart.axes,
|
store = chart.getChartStore(),
|
data = store.data.items,
|
record,
|
storeCount = store.getCount(),
|
surface = me.chart.surface,
|
bbox = {},
|
group = me.group,
|
showMarkers = me.showMarkers,
|
markerGroup = me.markerGroup,
|
enableShadows = chart.shadow,
|
shadowGroups = me.shadowGroups,
|
shadowAttributes = me.shadowAttributes,
|
smooth = me.smooth,
|
lnsh = shadowGroups.length,
|
dummyPath = ["M"],
|
path = ["M"],
|
renderPath = ["M"],
|
smoothPath = ["M"],
|
markerIndex = chart.markerIndex,
|
axes = [].concat(me.axis),
|
shadowBarAttr,
|
xValues = [],
|
xValueMap = {},
|
yValues = [],
|
yValueMap = {},
|
onbreak = false,
|
storeIndices = [],
|
markerStyle = Ext.apply({}, me.markerStyle),
|
seriesStyle = me.seriesStyle,
|
colorArrayStyle = me.colorArrayStyle,
|
colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
|
isNumber = Ext.isNumber,
|
seriesIdx = me.seriesIdx,
|
boundAxes = me.getAxesForXAndYFields(),
|
boundXAxis = boundAxes.xAxis,
|
boundYAxis = boundAxes.yAxis,
|
xAxisType = boundXAxis ? chartAxes.get(boundXAxis).type : '',
|
yAxisType = boundYAxis ? chartAxes.get(boundYAxis).type : '',
|
shadows, shadow, shindex, fromPath, fill, fillPath, rendererAttributes,
|
x, y, prevX, prevY, firstX, firstY, markerCount, i, j, ln, axis, ends, marker, markerAux, item, xValue,
|
yValue, coords, xScale, yScale, minX, maxX, minY, maxY, line, animation, endMarkerStyle,
|
endLineStyle, type, count, opacity, lineOpacity, fillOpacity, fillDefaultValue;
|
|
if (me.fireEvent('beforedraw', me) === false) {
|
return;
|
}
|
|
//if store is empty or the series is excluded in the legend then there's nothing to draw.
|
if (!storeCount || me.seriesIsHidden) {
|
me.hide();
|
me.items = [];
|
if (me.line) {
|
me.line.hide(true);
|
if (me.line.shadows) {
|
shadows = me.line.shadows;
|
for (j = 0, lnsh = shadows.length; j < lnsh; j++) {
|
shadow = shadows[j];
|
shadow.hide(true);
|
}
|
}
|
if (me.fillPath) {
|
me.fillPath.hide(true);
|
}
|
}
|
me.line = null;
|
me.fillPath = null;
|
return;
|
}
|
|
//prepare style objects for line and markers
|
endMarkerStyle = Ext.apply(markerStyle || {}, me.markerConfig, {
|
fill: me.seriesStyle.fill || colorArrayStyle[me.themeIdx % colorArrayStyle.length]
|
});
|
type = endMarkerStyle.type;
|
delete endMarkerStyle.type;
|
endLineStyle = seriesStyle;
|
//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;
|
}
|
|
//set opacity values
|
opacity = 'opacity' in endLineStyle ? endLineStyle.opacity : 1;
|
fillDefaultValue = 'opacity' in endLineStyle ? endLineStyle.opacity : 0.3;
|
lineOpacity = 'lineOpacity' in endLineStyle ? endLineStyle.lineOpacity : opacity;
|
fillOpacity = 'fillOpacity' in endLineStyle ? endLineStyle.fillOpacity : fillDefaultValue;
|
|
//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
|
}
|
}, true);
|
}
|
}
|
|
me.unHighlightItem();
|
me.cleanHighlights();
|
|
me.setBBox();
|
bbox = me.bbox;
|
me.clipRect = [bbox.x, bbox.y, bbox.width, bbox.height];
|
|
if (axis = chartAxes.get(boundXAxis)) {
|
ends = axis.applyData();
|
minX = ends.from;
|
maxX = ends.to;
|
}
|
|
if (axis = chartAxes.get(boundYAxis)) {
|
ends = axis.applyData();
|
minY = ends.from;
|
maxY = ends.to;
|
}
|
|
// If a field was specified without a corresponding axis, create one to get bounds
|
if (me.xField && !Ext.isNumber(minX)) {
|
axis = me.getMinMaxXValues();
|
minX = axis[0];
|
maxX = axis[1];
|
}
|
|
if (me.yField && !Ext.isNumber(minY)) {
|
axis = me.getMinMaxYValues();
|
minY = axis[0];
|
maxY = axis[1];
|
}
|
|
if (isNaN(minX)) {
|
minX = 0;
|
xScale = bbox.width / ((storeCount - 1) || 1);
|
}
|
else {
|
xScale = bbox.width / ((maxX - minX) || (storeCount -1) || 1);
|
}
|
|
if (isNaN(minY)) {
|
minY = 0;
|
yScale = bbox.height / ((storeCount - 1) || 1);
|
}
|
else {
|
yScale = bbox.height / ((maxY - minY) || (storeCount - 1) || 1);
|
}
|
// Extract all x and y values from the store
|
for (i = 0, ln = data.length; i < ln; i++) {
|
record = data[i];
|
xValue = record.get(me.xField);
|
if (xAxisType == 'Time' && typeof xValue == "string") {
|
xValue = Date.parse(xValue);
|
}
|
// Ensure a value
|
if (typeof xValue == 'string' || typeof xValue == 'object' && !Ext.isDate(xValue)
|
//set as uniform distribution if the axis is a category axis.
|
|| boundXAxis && chartAxes.get(boundXAxis) && chartAxes.get(boundXAxis).type == 'Category') {
|
if (xValue in xValueMap) {
|
xValue = xValueMap[xValue];
|
} else {
|
xValue = xValueMap[xValue] = i;
|
}
|
}
|
|
// Filter out values that don't fit within the pan/zoom buffer area
|
yValue = record.get(me.yField);
|
if (yAxisType == 'Time' && typeof yValue == "string") {
|
yValue = Date.parse(yValue);
|
}
|
//skip undefined values
|
if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)) {
|
//<debug warn>
|
if (Ext.isDefined(Ext.global.console)) {
|
Ext.global.console.warn("[Ext.chart.series.Line] Skipping a store element with an undefined value at ", record, xValue, yValue);
|
}
|
//</debug>
|
continue;
|
}
|
// Ensure a value
|
if (typeof yValue == 'string' || typeof yValue == 'object' && !Ext.isDate(yValue)
|
//set as uniform distribution if the axis is a category axis.
|
|| boundYAxis && chartAxes.get(boundYAxis) && chartAxes.get(boundYAxis).type == 'Category') {
|
yValue = i;
|
}
|
storeIndices.push(i);
|
xValues.push(xValue);
|
yValues.push(yValue);
|
}
|
|
ln = xValues.length;
|
if (ln > bbox.width) {
|
coords = me.shrink(xValues, yValues, bbox.width);
|
xValues = coords.x;
|
yValues = coords.y;
|
}
|
|
me.items = [];
|
|
count = 0;
|
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;
|
me.items.push(false);
|
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;
|
firstX = x;
|
}
|
// 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,
|
opacity: lineOpacity
|
}, true);
|
if (me.fillPath) {
|
me.fillPath.setAttributes({
|
path: dummyPath,
|
opacity: fillOpacity
|
}, 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(count++);
|
if (!marker) {
|
marker = Ext.chart.Shape[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 + '"',
|
zIndex: 4000
|
}, 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(storeIndices[i])
|
});
|
prevX = x;
|
prevY = y;
|
}
|
|
if (path.length <= 1) {
|
//nothing to be rendered
|
return;
|
}
|
|
if (me.smooth) {
|
smoothPath = Ext.draw.Draw.smooth(path, isNumber(smooth) ? smooth : me.defaultSmoothness);
|
}
|
|
renderPath = smooth ? smoothPath : path;
|
|
//Correct path if we're animating timeAxis intervals
|
if (chart.markerIndex && me.previousPath) {
|
fromPath = me.previousPath;
|
if (!smooth) {
|
Ext.Array.erase(fromPath, 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 || {}));
|
me
|
|
//set configuration opacity
|
me.line.setAttributes({
|
opacity: lineOpacity
|
}, true);
|
|
if (enableShadows) {
|
me.line.setAttributes(Ext.apply({}, me.shadowOptions), true);
|
}
|
|
//unset fill here (there's always a default fill withing the themes).
|
me.line.setAttributes({
|
fill: 'none',
|
zIndex: 3000
|
});
|
if (!endLineStyle.stroke && colorArrayLength) {
|
me.line.setAttributes({
|
stroke: colorArrayStyle[me.themeIdx % 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 = surface.add(Ext.apply({}, {
|
type: 'path',
|
group: shadowGroups[shindex]
|
}, shadowBarAttr));
|
shadows.push(shadow);
|
}
|
}
|
}
|
if (me.fill) {
|
fillPath = renderPath.concat([
|
["L", x, bbox.y + bbox.height],
|
["L", firstX, bbox.y + bbox.height],
|
["L", firstX, firstY]
|
]);
|
if (!me.fillPath) {
|
me.fillPath = surface.add({
|
group: group,
|
type: 'path',
|
fill: endLineStyle.fill || colorArrayStyle[me.themeIdx % colorArrayLength],
|
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: renderPath }, 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;
|
line.show(true);
|
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++) {
|
shadows[j].show(true);
|
if (chart.markerIndex && me.previousPath) {
|
me.onAnimate(shadows[j], {
|
to: { path: renderPath },
|
from: { path: fromPath }
|
});
|
} else {
|
me.onAnimate(shadows[j], {
|
to: { path: renderPath }
|
});
|
}
|
}
|
}
|
//animate fill path
|
if (fill) {
|
me.fillPath.show(true);
|
me.onAnimate(me.fillPath, {
|
to: Ext.apply({}, {
|
path: fillPath,
|
fill: endLineStyle.fill || colorArrayStyle[me.themeIdx % colorArrayLength],
|
'stroke-width': 0,
|
opacity: fillOpacity
|
}, endLineStyle || {})
|
});
|
}
|
//animate markers
|
if (showMarkers) {
|
count = 0;
|
for(i = 0; i < ln; i++) {
|
if (me.items[i]) {
|
item = markerGroup.getAt(count++);
|
if (item) {
|
rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
|
me.onAnimate(item, {
|
to: Ext.applyIf(rendererAttributes, endMarkerStyle || {})
|
});
|
item.show(true);
|
}
|
}
|
}
|
for(; count < markerCount; count++) {
|
item = markerGroup.getAt(count);
|
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: renderPath, 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);
|
me.line.setAttributes({
|
opacity: lineOpacity
|
}, true);
|
//set path for shadows
|
if (enableShadows) {
|
shadows = me.line.shadows;
|
for(j = 0; j < lnsh; j++) {
|
shadows[j].setAttributes({
|
path: renderPath,
|
hidden: false
|
}, true);
|
}
|
}
|
if (me.fill) {
|
me.fillPath.setAttributes({
|
path: fillPath,
|
hidden: false,
|
opacity: fillOpacity
|
}, true);
|
}
|
if (showMarkers) {
|
count = 0;
|
for(i = 0; i < ln; i++) {
|
if (me.items[i]) {
|
item = markerGroup.getAt(count++);
|
if (item) {
|
rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
|
item.setAttributes(Ext.apply(endMarkerStyle || {}, rendererAttributes || {}), true);
|
if (!item.attr.hidden) {
|
item.show(true);
|
}
|
}
|
}
|
}
|
for(; count < markerCount; count++) {
|
item = markerGroup.getAt(count);
|
item.hide(true);
|
}
|
}
|
}
|
|
if (chart.markerIndex) {
|
if (me.smooth) {
|
Ext.Array.erase(path, 1, 2);
|
} else {
|
Ext.Array.splice(path, 1, 0, path[1], path[2]);
|
}
|
me.previousPath = path;
|
}
|
me.renderLabels();
|
me.renderCallouts();
|
|
me.fireEvent('draw', me);
|
},
|
|
<span id='Ext-chart-series-Line-method-onCreateLabel'> // @private called when a label is to be created.
|
</span> 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': Number(item.point[0]),
|
'y': bbox.y + bbox.height / 2
|
}, endLabelStyle || {}));
|
},
|
|
<span id='Ext-chart-series-Line-method-onPlaceLabel'> // @private called when a label is to be positioned.
|
</span> onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
|
var me = this,
|
chart = me.chart,
|
resizing = chart.resizing,
|
config = me.label,
|
format = config.renderer,
|
field = config.field,
|
bbox = me.bbox,
|
x = Number(item.point[0]),
|
y = Number(item.point[1]),
|
radius = item.sprite.attr.radius,
|
labelBox, markerBox, width, height, xOffset, yOffset;
|
|
label.setAttributes({
|
text: format(storeItem.get(field), label, storeItem, item, i, display, animate, index),
|
hidden: true
|
}, true);
|
|
//TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
|
markerBox = item.sprite.getBBox();
|
markerBox.width = markerBox.width || (radius * 2);
|
markerBox.height = markerBox.height || (radius * 2);
|
|
labelBox = label.getBBox();
|
width = labelBox.width/2;
|
height = labelBox.height/2;
|
|
if (display == 'rotate') {
|
//correct label position to fit into the box
|
xOffset = markerBox.width/2 + width + height/2;
|
if (x + xOffset + width > bbox.x + bbox.width) {
|
x -= xOffset;
|
} else {
|
x += xOffset;
|
}
|
label.setAttributes({
|
'rotation': {
|
x: x,
|
y: y,
|
degrees: -45
|
}
|
}, true);
|
} else if (display == 'under' || display == 'over') {
|
label.setAttributes({
|
'rotation': {
|
degrees: 0
|
}
|
}, true);
|
|
//correct label position to fit into the box
|
if (x < bbox.x + width) {
|
x = bbox.x + width;
|
} else if (x + width > bbox.x + bbox.width) {
|
x = bbox.x + bbox.width - width;
|
}
|
|
yOffset = markerBox.height/2 + height;
|
y = y + (display == 'over' ? -yOffset : yOffset);
|
if (y < bbox.y + height) {
|
y += 2 * yOffset;
|
} else if (y + height > bbox.y + bbox.height) {
|
y -= 2 * yOffset;
|
}
|
}
|
|
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) {
|
me.animation.on('afteranimate', function() {
|
label.show(true);
|
});
|
} else {
|
label.show(true);
|
}
|
}
|
},
|
|
<span id='Ext-chart-series-Line-method-highlightItem'> // @private Overriding highlights.js highlightItem method.
|
</span> highlightItem: function() {
|
var me = this,
|
line = me.line;
|
|
me.callParent(arguments);
|
if (line && !me.highlighted) {
|
if (!('__strokeWidth' in line)) {
|
line.__strokeWidth = parseFloat(line.attr['stroke-width']) || 0;
|
}
|
if (line.__anim) {
|
line.__anim.paused = true;
|
}
|
|
line.__anim = new Ext.fx.Anim({
|
target: line,
|
to: {
|
'stroke-width': line.__strokeWidth + 3
|
}
|
});
|
me.highlighted = true;
|
}
|
},
|
|
<span id='Ext-chart-series-Line-method-unHighlightItem'> // @private Overriding highlights.js unHighlightItem method.
|
</span> unHighlightItem: function() {
|
var me = this,
|
line = me.line,
|
width;
|
|
me.callParent(arguments);
|
if (line && me.highlighted) {
|
width = line.__strokeWidth || parseFloat(line.attr['stroke-width']) || 0;
|
line.__anim = new Ext.fx.Anim({
|
target: line,
|
to: {
|
'stroke-width': width
|
}
|
});
|
me.highlighted = false;
|
}
|
},
|
|
<span id='Ext-chart-series-Line-method-onPlaceCallout'> // @private called when a callout needs to be placed.
|
</span> onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
|
if (!display) {
|
return;
|
}
|
|
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[0], +item.point[1]],
|
dir, norm, normal, a, aprev, anext,
|
offsetFromViz = config.offsetFromViz || 30,
|
offsetToSide = config.offsetToSide || 10,
|
offsetBox = config.offsetBox || 3,
|
boxx, boxy, boxw, boxh,
|
p, clipRect = me.clipRect,
|
bbox = {
|
width: config.styles.width || 10,
|
height: config.styles.height || 10
|
},
|
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 component position
|
if (callout.panel) {
|
callout.panel.setPosition(boxx, boxy, true);
|
}
|
}
|
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 component position
|
if (callout.panel) {
|
callout.panel.setPosition(boxx, boxy);
|
}
|
}
|
for (p in callout) {
|
callout[p].show(true);
|
}
|
},
|
|
<span id='Ext-chart-series-Line-method-isItemInPoint'> isItemInPoint: function(x, y, item, i) {
|
</span> var me = this,
|
items = me.items,
|
tolerance = me.selectionTolerance,
|
result = null,
|
prevItem,
|
nextItem,
|
prevPoint,
|
nextPoint,
|
ln,
|
x1,
|
y1,
|
x2,
|
y2,
|
xIntersect,
|
yIntersect,
|
dist1, dist2, dist, midx, midy,
|
sqrt = Math.sqrt, abs = Math.abs;
|
|
nextItem = items[i];
|
prevItem = i && items[i - 1];
|
|
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;
|
}
|
return false;
|
},
|
|
<span id='Ext-chart-series-Line-method-toggleAll'> // @private toggle visibility of all series elements (markers, sprites).
|
</span> toggleAll: function(show) {
|
var me = this,
|
i, ln, shadow, shadows;
|
if (!show) {
|
Ext.chart.series.Cartesian.prototype.hideAll.call(me);
|
}
|
else {
|
Ext.chart.series.Cartesian.prototype.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);
|
}
|
},
|
|
<span id='Ext-chart-series-Line-method-hideAll'> // @private hide all series elements (markers, sprites).
|
</span> hideAll: function() {
|
this.toggleAll(false);
|
},
|
|
<span id='Ext-chart-series-Line-method-showAll'> // @private hide all series elements (markers, sprites).
|
</span> showAll: function() {
|
this.toggleAll(true);
|
}
|
});
|
</pre>
|
</body>
|
</html>
|