Results 1 to 5 of 5

Thread: Consider supporting a 'stepline' chart - 9 lines code change on existing line chart

    You found a bug! We've classified it as EXTJS-5030 . We encourage you to continue the discussion and to find an acceptable workaround while we work on a permanent fix.
  1. #1
    Sencha User
    Join Date
    Nov 2007
    Posts
    17

    Default Consider supporting a 'stepline' chart - 9 lines code change on existing line chart

    For discontinuous data, please consider supporting a step line chart. It is a very small increment over the line chart and useful in many situations. It only requires a few small changes in the Ext.chart.series.Line class 'drawSeries' method.

    I chose to use the 'smooth' parameter to pass the configuration but it could just as easily be another configuration parameter

    Jason

    The changes are below:

    Code:
        drawSeries: function()
        {
    	    var me = this,
    	        chart = me.chart,
    	        chartAxes = chart.axes,
    	        store = chart.getChartStore(),
    	        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,
    	        stepped = (me.smooth == 'stepped'),
    	        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 = 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,
    	        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;
    	
    	    if (me.fireEvent('beforedraw', me) === false) {
    	        return;
    	    }
    	
    	    
    	    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;
    	    }
    	
    	    
    	    endMarkerStyle = Ext.apply(markerStyle || {}, me.markerConfig, {
    	        fill: me.seriesStyle.fill || colorArrayStyle[seriesIdx % colorArrayStyle.length]
    	    });
    	    type = endMarkerStyle.type;
    	    delete endMarkerStyle.type;
    	    endLineStyle = seriesStyle;
    	    
    	    
    	    if (!endLineStyle['stroke-width']) {
    	        endLineStyle['stroke-width'] = 0.5;
    	    }
    	    
    	    
    	    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 = chartAxes.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 (me.xField && !isNumber(minX) &&
    	        (boundXAxis == 'bottom' || boundXAxis == 'top') && 
    	        !chartAxes.get(boundXAxis)) {
    	        axis = Ext.create('Ext.chart.axis.Axis', {
    	            chart: chart,
    	            fields: [].concat(me.xField)
    	        }).calcEnds();
    	        minX = axis.from;
    	        maxX = axis.to;
    	    }
    	    if (me.yField && !isNumber(minY) &&
    	        (boundYAxis == 'right' || boundYAxis == 'left') &&
    	        !chartAxes.get(boundYAxis)) {
    	        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 / ((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);
    	    }
    	
    	    
    	    me.eachRecord(function(record, i) {
    	        xValue = record.get(me.xField);
    	
    	        
    	        if (typeof xValue == 'string' || typeof xValue == 'object' && !Ext.isDate(xValue)
    	            
    	            || boundXAxis && chartAxes.get(boundXAxis) && chartAxes.get(boundXAxis).type == 'Category') {
    	                if (xValue in xValueMap) {
    	                    xValue = xValueMap[xValue];
    	                } else {
    	                    xValue = xValueMap[xValue] = i;
    	                }
    	        }
    	
    	        
    	        yValue = record.get(me.yField);
    	        
    	        if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)) {
    	            return;
    	        }
    	        
    	        if (typeof yValue == 'string' || typeof yValue == 'object' && !Ext.isDate(yValue)
    	            
    	            || 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 ((stepped) && (prevX !== undefined))
    	            {
    		            path = path.concat([x, prevY]);
    	            }
    	            if (onbreak) {
    	                onbreak = false;
    	                path.push('M');
    	            }
    	            path = path.concat([x, y]);
    	        }
    	        if ((typeof firstY == 'undefined') && (typeof y != 'undefined')) {
    	            firstY = y;
    	            firstX = x;
    	        }
    	        
    	        if (!me.line || chart.resizing) {
    	            dummyPath = dummyPath.concat([x, bbox.y + bbox.height / 2]);
    	        }
    	
    	        
    	        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(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) {
    	        
    	        return;
    	    }
    	
    	    if ((me.smooth) && (!stepped)) {
    	        smoothPath = Ext.draw.Draw.smooth(path, isNumber(smooth) ? smooth : me.defaultSmoothness);
    	    }
    	
    	    renderPath = (smooth && !stepped) ? smoothPath : path;
    	
    	    
    	    if (chart.markerIndex && me.previousPath) {
    	        fromPath = me.previousPath;
    	        if (!smooth || stepped) {
    	            Ext.Array.erase(fromPath, 1, 2);
    	        }
    	    } else {
    	        fromPath = path;
    	    }
    	
    	    
    	    if (!me.line) {
    	        me.line = surface.add(Ext.apply({
    	            type: 'path',
    	            group: group,
    	            path: dummyPath,
    	            stroke: endLineStyle.stroke || endLineStyle.fill
    	        }, endLineStyle || {}));
    	
    	        if (enableShadows) {
    	            me.line.setAttributes(Ext.apply({}, me.shadowOptions), true);
    	        }
    	
    	        
    	        me.line.setAttributes({
    	            fill: 'none',
    	            zIndex: 3000
    	        });
    	        if (!endLineStyle.stroke && colorArrayLength) {
    	            me.line.setAttributes({
    	                stroke: colorArrayStyle[seriesIdx % colorArrayLength]
    	            }, true);
    	        }
    	        if (enableShadows) {
    	            
    	            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',
    	                opacity: endLineStyle.opacity || 0.3,
    	                fill: endLineStyle.fill || colorArrayStyle[seriesIdx % colorArrayLength],
    	                path: dummyPath
    	            });
    	        }
    	    }
    	    markerCount = showMarkers && markerGroup.getCount();
    	    if (chart.animate) {
    	        fill = me.fill;
    	        line = me.line;
    	        
    	        rendererAttributes = me.renderer(line, false, { path: renderPath }, i, store);
    	        Ext.apply(rendererAttributes, endLineStyle || {}, {
    	            stroke: endLineStyle.stroke || endLineStyle.fill
    	        });
    	        
    	        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
    	            });
    	        }
    	        
    	        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 }
    	                    });
    	                }
    	            }
    	        }
    	        
    	        if (fill) {
    	            me.fillPath.show(true);
    	            me.onAnimate(me.fillPath, {
    	                to: Ext.apply({}, {
    	                    path: fillPath,
    	                    fill: endLineStyle.fill || colorArrayStyle[seriesIdx % colorArrayLength],
    	                    'stroke-width': 0
    	                }, endLineStyle || {})
    	            });
    	        }
    	        
    	        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.apply(rendererAttributes, endMarkerStyle || {})
    	                        });
    	                        item.show(true);
    	                    }
    	                }
    	            }
    	            for(; count < markerCount; count++) {
    	                item = markerGroup.getAt(count);
    	                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
    	        });
    	        
    	        delete rendererAttributes.fill;
    	        me.line.setAttributes(rendererAttributes, true);
    	        
    	        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
    	            }, 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);
    	                        item.show(true);
    	                    }
    	                }
    	            }
    	            for(; count < markerCount; count++) {
    	                item = markerGroup.getAt(count);
    	                item.hide(true);
    	            }
    	        }
    	    }
    	
    	    if (chart.markerIndex) {
    	        if ((me.smooth) && (!stepped)) {
    	            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);
        }

  2. #2
    Sencha Premium User mitchellsimoens's Avatar
    Join Date
    Mar 2007
    Location
    Gainesville, FL
    Posts
    40,379

    Default

    Thank you for the report.
    Mitchell Simoens @LikelyMitch

    Check out my GitHub:
    https://github.com/mitchellsimoens

    Posts are my own, not any current, past or future employer's.

  3. #3
    Sencha User
    Join Date
    Nov 2007
    Posts
    17

    Default What is needed to get it into the product?

    Hello,

    Is there any way that I could help to get this into the product code? Let me know if I can be of assistance.

    Jason

  4. #4
    Sencha Premium Member
    Join Date
    Dec 2012
    Posts
    12

    Default A "stepped" area chart also, please

    I would like to see a similar modification to the area chart. For example, I made the following changes to yield a "stepped" version of the area chart. I have likely missed something to make this a robust solution, but it appeared to work for my chart. Changes in red below.


    Code:
    Ext.define('Ext.chart.series.Area', {
    
    
        /* Begin Definitions */
    
    
        extend: 'Ext.chart.series.Cartesian',
    
    
        alias: 'series.area',
    
    
        requires: ['Ext.chart.axis.Axis', 'Ext.draw.Color', 'Ext.fx.Anim'],
    
    
        /* End Definitions */
    
    
        type: 'area',
    
    
        // @private Area charts are alyways stacked
        stacked: true,
    
    
        // set to 'stepped' for a stepped area chart
        smooth: false,
    
    ...
    
        // @private Build an array of paths for the chart
        getPaths: function() {
            var me = this,
                chart = me.chart,
                store = chart.getChartStore(),
                first = true,
                bounds = me.getBounds(),
                bbox = bounds.bbox,
                items = me.items = [],
                componentPaths = [],
                componentPath,
                count = 0,
                paths = [],
                i, ln, x, y, xValue, yValue, acumY, areaIndex, prevAreaIndex, areaElem, path;
    
    
    
    
            //added to support steps
            var prevY = [],
            stepped = (me.smooth == 'stepped');
    
    
            ln = bounds.xValues.length;
            // Start the path
            for (i = 0; i < ln; i++) {
                xValue = bounds.xValues[i];
                yValue = bounds.yValues[i];
                x = bbox.x + (xValue - bounds.minX) * bounds.xScale;
                acumY = 0;
                count = 0;
                for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
                    // Excluded series
                    if (me.__excludes[areaIndex]) {
                        continue;
                    }
                    if (!componentPaths[areaIndex]) {
                        componentPaths[areaIndex] = [];
                    }
                    areaElem = yValue[count];
                    acumY += areaElem;
                    y = bbox.y + bbox.height - (acumY - bounds.minY) * bounds.yScale;
                    
                    if (!paths[areaIndex]) {
                        paths[areaIndex] = ['M', x, y];
                        componentPaths[areaIndex].push(['L', x, y]);
                    } else {
                        // Steps
                        if (stepped)
                        {
                            paths[areaIndex].push('L', x, prevY[count]);
                            componentPaths[areaIndex].push(['L', x, prevY[count]]);
                        }
    
    
                        paths[areaIndex].push('L', x, y);
                        componentPaths[areaIndex].push(['L', x, y]);
                    }
                    if (!items[areaIndex]) {
                        items[areaIndex] = {
                            pointsUp: [],
                            pointsDown: [],
                            series: me
                        };
                    }
    
    
                    //Steps
                    if (stepped)
                    {
                        items[areaIndex].pointsUp.push([x, prevY[count]]);
                    }
    
    
                    items[areaIndex].pointsUp.push([x, y]);
    
    
                    prevY[count] = y;
    
    
                    count++;
                }
            }
    
    
            // Close the paths
            for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
                // Excluded series
                if (me.__excludes[areaIndex]) {
                    continue;
                }
                path = paths[areaIndex];
                // Close bottom path to the axis
                if (areaIndex == 0 || first) {
                    first = false;
                    path.push('L', x, bbox.y + bbox.height,
                              'L', bbox.x, bbox.y + bbox.height,
                              'Z');
                }
                // Close other paths to the one before them
                else {
                    componentPath = componentPaths[prevAreaIndex];
                    componentPath.reverse();
                    path.push('L', x, componentPath[0][2]);
                    for (i = 0; i < ln; i++) {
                        path.push(componentPath[i][0],
                                  componentPath[i][1],
                                  componentPath[i][2]);
                        items[areaIndex].pointsDown[ln -i -1] = [componentPath[i][1], componentPath[i][2]];
                    }
                    path.push('L', bbox.x, path[2], 'Z');
                }
                prevAreaIndex = areaIndex;
            }
            return {
                paths: paths,
                areasLen: bounds.areasLen
            };
        }

  5. #5
    Sencha Premium Member
    Join Date
    Oct 2013
    Location
    Slovakia
    Posts
    80

    Default Ext.ux.chart.series.StepLine

    Based on @lorenzzz's code and updated to version Ext JS 4.2.2:

    Code:
    Ext.define('Ext.ux.chart.series.StepLine', {
        
        extend: 'Ext.chart.series.Line',
        
        type: 'stepline',
        
        alias: 'series.stepline',
        
        smooth: 'stepped',
        
        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,
                stepped = (me.smooth == 'stepped'),
                lnsh = shadowGroups.length,
                dummyPath = ["M"],
                path = ["M"],
                renderPath = ["M"],
                smoothPath = ["M"],
                markerIndex = chart.markerIndex,
                axes = [].concat(me.axis),
                shadowBarAttr,
                xValues = [],
                yValues = [],
                onbreak = false,
                reverse = me.reverse,
                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,
                xAxis = chartAxes && chartAxes.get(boundXAxis),
                yAxis = chartAxes && chartAxes.get(boundYAxis),
                xAxisType = boundXAxis ? xAxis && xAxis.type : '',
                yAxisType = boundYAxis ? yAxis && yAxis.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 (xAxis) {
                ends = xAxis.applyData();
                        minX = ends.from;
                        maxX = ends.to;
                    }
    
            if (yAxis) {
                ends = yAxis.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.
                    || xAxisType === 'Category') {
                        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.
                    || yAxisType === '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 {
                    if (reverse) {
                        x = bbox.x + bbox.width - ((xValue - minX) * xScale);
                    } else {
                        x = (bbox.x + (xValue - minX) * xScale);
                    }
                    x = Ext.Number.toFixed(x, 2);
                    y = Ext.Number.toFixed((bbox.y + bbox.height) - (yValue - minY) * yScale, 2);
                    if ((stepped) && (prevX !== undefined))
                    {
                        path = path.concat([x, prevY]);
                    }
                    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) && (!stepped)) {
                smoothPath = Ext.draw.Draw.smooth(path, isNumber(smooth) ? smooth : me.defaultSmoothness);
            }
        
            renderPath = (smooth && !stepped) ? smoothPath : path;
        
            
            //Correct path if we're animating timeAxis intervals
            if (chart.markerIndex && me.previousPath) {
                fromPath = me.previousPath;
                if (!smooth || stepped) {
                    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) && (!stepped)) {
                    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);
        }
        
    });
    It would be nice to have also 'step' config with values: 'left', 'right' or 'center' - like in Highcharts.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •