[OPEN] [#216] Filtering chart store

  1. #1

    [OPEN] [#216] Filtering chart store

    Hello,

    Since there are known problems regarding zooming a chart, I am locally filtering chart store (for a time range) to get the desired view. After filtering the chart store, if a series has no items (points), earlier markers and line stays on the chart where they should not appear. I tried .redraw(), .refresh() etc. with no change.

    Please investigate the code below. When you click the "Test" button, the series starting @Today+2 and ending @Today+3 should go away since the store is filtered for (Date>=Today) and (Date<=Today+1). In fact store is properly filtered and the points which should disappear are no longer in store's data items collection after filtering. However chart is not refreshed.

    Thanks.

    <%@ Page Language="C#" %>
     
    <%@ Register Assembly="Ext.Net" Namespace="Ext.Net" TagPrefix="ext" %>
    <script runat="server">
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!X.IsAjaxRequest)
            {
                this.Store1.DataSource = this.TestData();
                this.Store1.DataBind();
            }
        }
        private List<object> TestData()
        {
            List<object> data = new List<object>
                {
                    new {
                            ID = 0,
                            Date = DateTime.Today,
                            Data1 = 1,
                            Data2=""
                        },
                    new {
                            ID = 1,
                            Date = DateTime.Today.AddDays(1),
                            Data1 = 1,
                            Data2 = ""
                        },
                    new {
                            ID = 2,
                            Date = DateTime.Today.AddDays(2),
                            Data1 = "",
                            Data2 = 2
                        },
                    new {
                            ID = 3,
                            Date = DateTime.Today.AddDays(3),
                            Data1 = "",
                            Data2 = 2
                        }                    
                };
            return data;
        }
    </script>
    <script type="text/javascript">
        function myFilterFunc() {
            var store = App.Store1;
            var chart = App.Chart1;
            var today = new Date();
            today.setHours(0, 0, 0, 0);
            todayTime = today.getTime();
            var chartFromDate = new Date(todayTime);
            var chartToDate = new Date(todayTime + 24 * 60 * 60 * 1000);
            store.clearFilter();
            var filterFn = function (item) {
                return (item.data.Date >= chartFromDate && item.data.Date <= chartToDate);
            };
            store.filterBy(filterFn);
    
            if (chart.series.items[1].items.length == 0) {
                alert("Test series has no items and should not appear on the chart.");
            }
    
            //tried those:
            chart.redraw();
            chart.refresh();
            chart.forceRefresh();
        }
    </script>
    
    <!DOCTYPE html>
    <html>
    <head id="Head1" runat="server">
        <title>Filtering Chart Store</title>
    </head>
    <body>
        <form id="Form1" runat="server">
        <ext:ResourceManager ID="ResourceManager1" runat="server" SourceFormatting="True" />    
        <ext:Button ID="Button1" runat="server" Text="Test">
            <Listeners>
                <Click Handler="myFilterFunc()"></Click>
            </Listeners>
        </ext:Button>
        <ext:Panel ID="Panel1" runat="server" Width="800" Height="500" Layout="FitLayout">        
            <Items>            
                <ext:Chart ID="Chart1" runat="server" StyleSpec="background:#fff;" Animate="false" Flex="1">
                    <Store>
                        <ext:Store ID="Store1" runat="server">
                            <Model>
                                <ext:Model ID="Model2" runat="server" IDProperty="ID">
                                    <Fields>
                                        <ext:ModelField Name="ID" Type="Int" />
                                        <ext:ModelField Name="Date" Type="Date" />
                                        <ext:ModelField Name="Data1" Type="Int">
                                            <Convert Handler="if(value==''){value=undefined;} return value;"></Convert>
                                        </ext:ModelField>
                                        <ext:ModelField Name="Data2" Type="Int">
                                            <Convert Handler="if(value==''){value=undefined;} return value;"></Convert>                                    
                                        </ext:ModelField>
                                    </Fields>
                                </ext:Model>
                            </Model>
                        </ext:Store>
                    </Store>
                    <Axes>
                        <ext:TimeAxis Fields="Date" Position="Bottom" Title="Time" DateFormat="Y, MMM dd">
                        </ext:TimeAxis>
                        <ext:NumericAxis Title="Value" Fields="Data1,Data2" Position="Left" MajorTickSteps="1" MinorTickSteps="0">      
                            <Label>
                                <Renderer Handler="return value" />
                            </Label>                                                                                            
                        </ext:NumericAxis>
                    </Axes>
                    <Series>
                        <ext:LineSeries Axis="Left" XField="Date" YField="Data1" ShowInLegend="false">
                            <Tips ID="MyTip" runat="server" Width="250">
                                <Renderer Handler="this.setTitle(storeItem.data.Date.toString());"></Renderer>
                            </Tips>
                        </ext:LineSeries>
                        <ext:LineSeries Axis="Left" XField="Date" YField="Data2" ShowInLegend="false">
                            <Tips ID="Tips1" runat="server" Width="250">
                                <Renderer Handler="this.setTitle(storeItem.data.Date.toString());"></Renderer>
                            </Tips>
                        </ext:LineSeries>
                    </Series>
                </ext:Chart>
            </Items>
        </ext:Panel>
        </form>
    </body>
    </html>
    Last edited by Daniil; Apr 24, 2013 at 4:27 PM. Reason: [OPEN] [#216]
  2. #2
    Hi @bayoglu,

    Thank you for the report. I think it is a bug and reported to Sencha.
    http://www.sencha.com/forum/showthread.php?262150

    I think there is a logical mistake within a LineSeries' drawSeries method and here is a possible fix.

    It gets your sample to be working as expected.

    Fix
    Ext.chart.series.Line.override({
        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;
            }
    
            //FIX: moved this part here to determine at earlier stage a need to hide a line
            // 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);
            }
    
            //if store is empty or the series is excluded in the legend then there's nothing to draw.
            if (!storeCount || me.seriesIsHidden || yValues.length === 0) { //FIX: added the "|| yValues.length === 0" condition
                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);
            }            
    
            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);
                    }
                }
            } 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);
        }    
    });
  3. #3
    Sencha opened a bug.

    We created an Issue to track it.
    https://github.com/extnet/Ext.NET/issues/216

Similar Threads

  1. [CLOSED] IE 8 Store filtering issue.
    By Leonid_Veriga in forum 2.x Legacy Premium Help
    Replies: 1
    Last Post: Oct 29, 2012, 3:53 PM
  2. [CLOSED] Filtering a store with nested data
    By jchau in forum 1.x Legacy Premium Help
    Replies: 3
    Last Post: Dec 13, 2011, 1:27 PM
  3. Grid (Store) filtering question
    By quasimidi in forum 1.x Help
    Replies: 2
    Last Post: Jun 15, 2010, 9:10 AM
  4. Filtering Can't work in store
    By syed2uk in forum 1.x Help
    Replies: 1
    Last Post: Jan 19, 2010, 5:02 AM
  5. Replies: 1
    Last Post: May 18, 2009, 1:41 PM

Posting Permissions