X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/0494b8d9b9bb03ab6c22b34dae81261e3cd7e3e6..7a654f8d43fdb43d78b63d90528bed6e86b608cc:/src/chart/series/Line.js diff --git a/src/chart/series/Line.js b/src/chart/series/Line.js new file mode 100644 index 00000000..5369cdd3 --- /dev/null +++ b/src/chart/series/Line.js @@ -0,0 +1,994 @@ +/** + * @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: +
+{@img Ext.chart.series.Line/Ext.chart.series.Line.png Ext.chart.series.Line chart series} +
+ 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':27, 'data2':38, '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: 'bottom',
+ fields: ['data1'],
+ label: {
+ renderer: Ext.util.Format.numberRenderer('0,0')
+ },
+ title: 'Sample Values',
+ grid: true,
+ minimum: 0
+ }, {
+ type: 'Category',
+ position: 'left',
+ fields: ['name'],
+ title: 'Sample Metrics'
+ }],
+ 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 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. +
+ */ + +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'], + + /* End Definitions */ + + type: 'line', + + alias: 'series.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 data points along the line. If true, + * then the {@link #markerConfig} config item will determine the markers' styling. + */ + showMarkers: true, + + /** + * @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: + * +
+ markerConfig: {
+ type: 'circle',
+ radius: 4,
+ 'fill': '#f00'
+ }
+
+
+ */
+ markerConfig: {},
+
+ /**
+ * @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 {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: {
+ 'stroke-width': 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,
+ shadowAttributes = this.shadowAttributes,
+ lnsh = shadowGroups.length,
+ dummyPath = ["M"],
+ path = ["M"],
+ markerIndex = chart.markerIndex,
+ axes = [].concat(me.axis),
+ shadowGroup,
+ shadowBarAttr,
+ xValues = [],
+ yValues = [],
+ onbreak = false,
+ markerStyle = me.markerStyle,
+ seriesStyle = me.seriesStyle,
+ seriesLabelStyle = me.seriesLabelStyle,
+ colorArrayStyle = me.colorArrayStyle,
+ colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
+ seriesIdx = me.seriesIdx, shadows, shadow, shindex, fromPath, fill, fillPath, rendererAttributes,
+ x, y, prevX, prevY, firstY, markerCount, i, j, ln, axis, ends, marker, markerAux, item, xValue,
+ yValue, coords, xScale, yScale, minX, maxX, minY, maxY, line, animation, endMarkerStyle,
+ endLineStyle, type, props, firstMarker;
+
+ //if store is empty then there's nothing to draw.
+ if (!store || !store.getCount()) {
+ return;
+ }
+
+ //prepare style objects for line and markers
+ endMarkerStyle = Ext.apply(markerStyle, me.markerConfig);
+ 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
+ }
+ }, true);
+ }
+ }
+
+ me.unHighlightItem();
+ me.cleanHighlights();
+
+ me.setBBox();
+ bbox = me.bbox;
+
+ me.clipRect = [bbox.x, bbox.y, bbox.width, bbox.height];
+
+ for (i = 0, ln = axes.length; i < ln; i++) {
+ axis = chart.axes.get(axes[i]);
+ if (axis) {
+ ends = axis.calcEnds();
+ if (axis.position == 'top' || axis.position == 'bottom') {
+ minX = ends.from;
+ maxX = ends.to;
+ }
+ else {
+ minY = ends.from;
+ maxY = ends.to;
+ }
+ }
+ }
+ // If a field was specified without a corresponding axis, create one to get bounds
+ //only do this for the axis where real values are bound (that's why we check for
+ //me.axis)
+ if (me.xField && !Ext.isNumber(minX)
+ && (me.axis == 'bottom' || me.axis == 'top')) {
+ axis = Ext.create('Ext.chart.axis.Axis', {
+ chart: chart,
+ fields: [].concat(me.xField)
+ }).calcEnds();
+ minX = axis.from;
+ maxX = axis.to;
+ }
+ if (me.yField && !Ext.isNumber(minY)
+ && (me.axis == 'right' || me.axis == 'left')) {
+ axis = Ext.create('Ext.chart.axis.Axis', {
+ chart: chart,
+ fields: [].concat(me.yField)
+ }).calcEnds();
+ minY = axis.from;
+ maxY = axis.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);
+ //skip undefined values
+ if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)) {
+ //