PDA

View Full Version : [OPEN] [#216] Filtering chart store



bayoglu
Apr 23, 2013, 2:23 PM
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>

Daniil
Apr 24, 2013, 7:54 AM
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);
}
});

Daniil
Apr 24, 2013, 4:27 PM
Sencha opened a bug.

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