The control shows all the points of the graph even if some points fall on the same pixel on the screen. This make graphs with large amount of points (~100k) to be repainted painfully slow.
It would be great for the next version to discard these points (is it possible).
Just taking every Nth point of the graph cannot be done since the curve should show all the anomalities. This is the reason I cannot use average or other function do decrease number of points.
I am trying to dilute the graph by manually removing all the points that are duplicated on the screen from list of points:
/* get the axis rectangle */RectangleFrectf=pane.CalcChartRect(zgc.CreateGraphics());/* returned point pair list */PointPairListppl=newPointPairList();bVals=newbyte[(int)rectf.Width + 1, (int)rectf.Height + 1];for(inti=0;i<plist.Count;i++){PointFpf=pane.GeneralTransform(newPointF((float)plist[i].X,(float)plist[i].Y),CoordType.AxisXYScale);if(pf.X>=0&&pf.Y>=0&&((int)pf.X)<bVals.GetLength(0)&&((int)pf.Y)<bVals.GetLength(1)&&bVals[(int)pf.X, (int)pf.Y]==0){ppl.Add(newPointPair(plist[i]));bVals[(int)pf.X, (int)pf.Y]=1;}}returnppl;
}
This simple function makes second buffer with size equal to the size of drawn pane area and goes over the list of points of the curve. Each point coordinates are transformed into pixel coordinates and if this point is already on the screen, it is thrown from the list.
The only problem, GeneralTransform always returns me (0, 0).
What am I doing wrong with transformation so it does not work?
I am interested in hearing other suggestions for the solution (maybe, seeing dilute feature in v5 or ZedGraph, ah, dreams!).
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Hi,
I got to thinking about this...a number of people have been looking at filtering methods to reduce the point count for very large datasets. Your method of looking at the actual pixel location for overlaps seems like a good approach. So, to this end I went ahead and setup an IPointList based on "nearest pixel" logic. You can filter points that overlap exactly, or points that lie within a specified number of pixels of an existing point.
This is called NoDupePointList. To use it, you would do something like this:
NoDupePointList list = new NoDupePointList();
Loop to add 100000 points
{
list.Add( x, y );
}
// this is the number of pixels that would be
// considered close enough to overlap.
// 0 means they must overlap exactly
// 1 means within 1 pixel, etc.
list.FilterMode = 0;
// This does the actual filtering. You should
// call this method anytime your data change,
// the graph is resized, etc. in order to
// redo the filtering based on the current
// chart configuration.
list.FilterData( myPane, myPane.YAxis );
You can grab the NoDupePointList.cs file from the cvs under the V5_BRANCH.
John
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
I am trying to use this (from v4.9.8) in a master pane layout with little success :-(
I can add the first curve and filter it, but when I try to add more curves to the same pane, the pixel scaling does not work. The y value is always larger than the height so all points are filtered out.
Before I call FilterData() I call AxisChange() to auto-scale the Y axis based on the full data set. Do I need to call anything else to make sure the scaling will work?
Also, I added a check for points that contain NaN as I need to make sure these are not filtered out (so that discontinuous lines can be plotted).
FWIW I also tried the FilteredPointList but I cannot get that to work - no points are shown at all unless I disable filtering with -1 in SetBounds. I've seen someone else with this problem in the forum.
Cheers,
Jonathan
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Hi,
You probably just need to call AxisChange() first, so that the scale ranges will be setup.
Another way to set this up would be to create an IPointList type that handles it automatically. In this manner, you could have the IPointList adjust itself if the graph is resized. (see the FilteredPointList and BasicArrayPointList for examples).
John
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Anonymous
-
2006-05-22
The only thing I can say:
WOW! Thanks!
Going to test it now!
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Anonymous
-
2006-05-23
Is it possible to put V5_BRANCH somewhere for download via HTTP standard ports? I have no access to cvs ports of SourceForge.
Tonight I ended up with 740MB downloaded from CVS's web interface using "wget -r" and nothing that is usable... :(
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Hi,
You can download version 4.9.7 from the developer releases on sourceforge. I think the CVS browser is actually whacked, since it has no recent files.
John
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
namespace ZedGraph
{
/// <summary>
/// A simple storage struct to maintain an individual sampling of data. This only
/// contains two data values in order to reduce to memory load for large datasets.
/// (e.g., no Tag or Z property)
/// </summary>
public struct DataPoint
{
public double X;
public double Y;
}
/// <summary>
/// A collection class to maintain a set of samples.
/// </summary>
/// <remarks>This type, intended for very
/// large datasets, will reduce the number of points displayed by eliminating
/// individual points that overlay (at the same pixel location) on the graph.
/// Note that this type probably does not make sense for line plots, but is intended
/// primarily for scatter plots.
/// </remarks>
///
/// <author> John Champion </author>
/// <version> $Revision: 1.1.2.1 $ $Date: 2006/05/19 05:11:32 $ </version> [Serializable]
public class NoDupePointList : List<DataPoint>, IPointList, IPointListEdit
{
protected bool _isFiltered;
protected int _filteredCount;
protected int[] _visibleIndicies;
protectedint_filterMode;/// <summary>/// Gets or sets a value that determines how close a point must be to a prior/// neighbor in order to be filtered out./// </summary>/// <remarks>/// A value of 0 indicates that subsequent/// points must coincide exactly at the same pixel location. A value of 1 or more/// indicates that number of pixels distance from a prior point that will cause/// a new point to be filtered out. For example, a value of 2 means that, once/// a particular pixel location is taken, any subsequent point that lies within 2/// pixels of that location will be filtered out./// </remarks>publicintFilterMode{get{return_filterMode;}set{_filterMode=value;}}/// <summary>/// Gets a value indicated whether or not the data have been filtered. If the data/// have not been filtered, then <see cref="Count" /> will be equal to/// <see cref="TotalCount" />./// </summary>publicboolIsFiltered{get{return_isFiltered;}}/// <summary>/// Indexer: get the DataPoint instance at the specified ordinal position in the list/// </summary>/// <remarks>/// This method will throw an exception if the index is out of range. This can happen/// if the index is less than the number of filtered values, or if data points are/// removed from a filtered dataset with updating the filter (by calling/// <see cref="FilterData" />)./// </remarks>/// <param name="index">The ordinal position in the list of points</param>/// <returns>Returns a <see cref="PointPair" /> instance. The <see cref="PointPair.Z" />/// and <see cref="PointPair.Tag" /> properties will be defaulted to/// <see cref="PointPair.Missing" /> and null, respectively./// </returns>publicnewPointPairthis[intindex]{get{intj=index;if(_isFiltered)j=_visibleIndicies[index];DataPointdp=base[j];PointPairpt=newPointPair(dp.X,dp.Y);returnpt;}set{intj=index;if(_isFiltered)j=_visibleIndicies[index];DataPointdp;dp.X=value.X;dp.Y=value.Y;base[j]=dp;}}/// <summary>/// Gets the number of active samples in the collection. This is the number of/// samples that are non-duplicates. See the <see cref="TotalCount" /> property/// to get the total number of samples in the list./// </summary>publicnewintCount{get{if(!_isFiltered)returnbase.Count;elsereturn_filteredCount;}}/// <summary>/// Gets the total number of samples in the collection. See the <see cref="Count" />/// property to get the number of active (non-duplicate) samples in the list./// </summary>publicintTotalCount{get{returnbase.Count;}}/// <summary>/// Append a data point to the collection/// </summary>/// <param name="sample">The DataPoint to append</param>publicvoidAdd(PointPairpt){DataPointdp=newDataPoint();dp.X=pt.X;dp.Y=pt.Y;Add(dp);}/// <summary>/// Append a point to the collection/// </summary>/// <param name="x">The x value of the point to append</param>/// <param name="y">The y value of the point to append</param>publicvoidAdd(doublex,doubley){DataPointdp=newDataPoint();dp.X=x;dp.Y=y;Add(dp);}// generic Clone: just call the typesafe versionobjectICloneable.Clone(){returnthis.Clone();}/// <summary>/// typesafe clone method/// </summary>/// <returns>A new cloned NoDupePointList. This returns a copy of the structure,/// but it does not duplicate the data (it just keeps a reference to the original)/// </returns>publicNoDupePointListClone(){returnnewNoDupePointList(this);}/// <summary>/// default constructor/// </summary>publicNoDupePointList(){_isFiltered=false;_filteredCount=0;_visibleIndicies=null;_filterMode=0;}/// <summary>/// copy constructor -- this returns a copy of the structure,/// but it does not duplicate the data (it just keeps a reference to the original)/// </summary>/// <param name="rhs">The NoDupePointList to be copied</param>publicNoDupePointList(NoDupePointListrhs){intcount=rhs.TotalCount;for(inti=0;i<count;i++)Add(rhs.GetDataPointAt(i));_filteredCount=rhs._filteredCount;_isFiltered=rhs._isFiltered;_filterMode=rhs._filterMode;if(rhs._visibleIndicies!=null)_visibleIndicies=(int[])rhs._visibleIndicies.Clone();else_visibleIndicies=null;}/// <summary>/// Protected method to access the internal DataPoint collection, without any/// translation to a PointPair./// </summary>/// <param name="index">The ordinal position of the DataPoint of interest</param>protectedDataPointGetDataPointAt(intindex){returnbase[index];}/// <summary>/// Clears any filtering previously done by a call to <see cref="FilterData" />./// After calling this method, all data points will be visible, and/// <see cref="Count" /> will be equal to <see cref="TotalCount" />./// </summary>publicvoidClearFilter(){_isFiltered=false;_filteredCount=0;}/// <summary>/// Go through the collection, and hide (filter out) any points that fall on the/// same pixel location as a previously included point./// </summary>/// <remarks>/// This method does not delete any points, it just temporarily hides them until/// the next call to <see cref="FilterData" /> or <see cref="ClearFilter" />./// You should call <see cref="FilterData" /> once your collection of points has/// been constructed. You may need to call <see cref="FilterData" /> again if/// you add points, or if the chart rect changes size (by resizing, printing,/// image save, etc.), or if the scale range changes./// You must call <see cref="AxisChange" /> before calling/// this method so that the <see cref="Chart.Rect">GraphPane.Chart.Rect</see>/// and the scale ranges are valid. This method is not valid for/// ordinal axes (but ordinal axes don't make sense for very large datasets/// anyway)./// </remarks>/// <param name="pane">The <see cref="GraphPane" /> into which the data/// will be plotted. </param>/// <param name="yAxis">The <see cref="Axis" /> class to be used in the Y direction/// for plotting these data. This can be a <see cref="YAxis" /> or a /// <see cref="Y2Axis" />, and can be a primary or secondary axis (if multiple Y or Y2/// axes are being used)./// </param>publicvoidFilterData(GraphPanepane,AxisyAxis){if(_visibleIndicies==null||_visibleIndicies.Length<base.Count)_visibleIndicies=newint[base.Count];_filteredCount=0;_isFiltered=true;intwidth=(int)pane.Chart.Rect.Width;intheight=(int)pane.Chart.Rect.Height;if(width<=0||height<=0)thrownewIndexOutOfRangeException("ErrorinFilterData:Chartrectnotvalid");bool[,]usedArray=newbool[width,height];for(inti=0;i<width;i++)for(intj=0;j<height;j++)usedArray[i,j]=false;AxisxAxis=pane.XAxis;xAxis.Scale.SetupScaleData(pane,pane.XAxis);yAxis.Scale.SetupScaleData(pane,yAxis);intn=_filterMode<0?0:_filterMode;for(inti=0;i<base.Count;i++){DataPointdp=base[i];intx=(int)(xAxis.Scale.Transform(dp.X)+0.5);inty=(int)(yAxis.Scale.Transform(dp.Y)+0.5);if(x>=0&&x<width&&y>=0&&y<height){boolused=false;if(n<=0)used=usedArray[x,y];else{for(intix=x-n;ix<=x+n;ix++)for(intiy=y-n;iy<=y+n;iy++)used|=(ix>=0&&ix<width&&iy>=0&&iy<height&&usedArray[ix,iy]);}if(!used){usedArray[x,y]=true;_visibleIndicies[_filteredCount]=i;_filteredCount++;}}}}
}
}
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Yes they are all independant lists. The problem is the scaling or transforming.
pane.Chart.Rect.Height returns e.g. 89
then
yAxis.Scale.Transform always returns y > height.
I am assuming there is an error in SetupScaleData() or Transform() or that I need to call something other than just AxisChange() to get the axes set correctly. I haven't been able to make sense of the transform code yet to see if I can work out what is wrong.
My sequence is actually different to yours because I don't know when each curve will be added...
AddCurve()
{
NoDupePointList ndp1 = new NoDupePointList();
I created a test file to demonstrate the problem. It seems to be related to the use of MasterPane, so I guess the pixel position code in NoDupePointList does not take this into account correctly.
NB I add the ZedGraph control as the only control on the form and set to DockStyle.Fill.
Jonathan
using System;
using System.Drawing;
using System.Windows.Forms;
using ZedGraph;
namespace NoDupePointListTest
{
public partial class Form1 : Form
{
const int NUMPOINTS = 10000;
public Form1()
{
InitializeComponent();
Jonathon,
I think I found it. It's a bust in the logic for the NoDupePointList. The Filter() method is expecting pixel values that go from 0 to width and 0 to height, but the actual pixel values have a non-zero origin. The code below is a quick fix. Can you give this a try? It's just a matter of adding the left & top values to the two Transform() calculations.
John
int left = (int)pane.Chart.Rect.Left;
int top = (int)pane.Chart.Rect.Top;
for ( int i=0; i<base.Count; i++ )
{
DataPoint dp = base[i];
int x = (int)( xAxis.Scale.Transform( dp.X ) + 0.5 ) - left;
int y = (int)( yAxis.Scale.Transform( dp.Y ) + 0.5 ) - top;
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
The control shows all the points of the graph even if some points fall on the same pixel on the screen. This make graphs with large amount of points (~100k) to be repainted painfully slow.
It would be great for the next version to discard these points (is it possible).
Just taking every Nth point of the graph cannot be done since the curve should show all the anomalities. This is the reason I cannot use average or other function do decrease number of points.
I am trying to dilute the graph by manually removing all the points that are duplicated on the screen from list of points:
private IPointList DiVect(IPointList plist, GraphPane pane, ZedGraphControl zgc)
{
byte[,] bVals = null;
}
This simple function makes second buffer with size equal to the size of drawn pane area and goes over the list of points of the curve. Each point coordinates are transformed into pixel coordinates and if this point is already on the screen, it is thrown from the list.
The only problem, GeneralTransform always returns me (0, 0).
What am I doing wrong with transformation so it does not work?
I am interested in hearing other suggestions for the solution (maybe, seeing dilute feature in v5 or ZedGraph, ah, dreams!).
Hi,
I got to thinking about this...a number of people have been looking at filtering methods to reduce the point count for very large datasets. Your method of looking at the actual pixel location for overlaps seems like a good approach. So, to this end I went ahead and setup an IPointList based on "nearest pixel" logic. You can filter points that overlap exactly, or points that lie within a specified number of pixels of an existing point.
This is called NoDupePointList. To use it, you would do something like this:
NoDupePointList list = new NoDupePointList();
Loop to add 100000 points
{
list.Add( x, y );
}
LineItem curve = myPane.AddCurve( "title", list, ...);
curve.Line.IsVisible = false;
myPane.AxisChange();
// this is the number of pixels that would be
// considered close enough to overlap.
// 0 means they must overlap exactly
// 1 means within 1 pixel, etc.
list.FilterMode = 0;
// This does the actual filtering. You should
// call this method anytime your data change,
// the graph is resized, etc. in order to
// redo the filtering based on the current
// chart configuration.
list.FilterData( myPane, myPane.YAxis );
You can grab the NoDupePointList.cs file from the cvs under the V5_BRANCH.
John
Hi,
I am trying to use this (from v4.9.8) in a master pane layout with little success :-(
I can add the first curve and filter it, but when I try to add more curves to the same pane, the pixel scaling does not work. The y value is always larger than the height so all points are filtered out.
Before I call FilterData() I call AxisChange() to auto-scale the Y axis based on the full data set. Do I need to call anything else to make sure the scaling will work?
Also, I added a check for points that contain NaN as I need to make sure these are not filtered out (so that discontinuous lines can be plotted).
FWIW I also tried the FilteredPointList but I cannot get that to work - no points are shown at all unless I disable filtering with -1 in SetBounds. I've seen someone else with this problem in the forum.
Cheers,
Jonathan
Hi,
You probably just need to call AxisChange() first, so that the scale ranges will be setup.
Another way to set this up would be to create an IPointList type that handles it automatically. In this manner, you could have the IPointList adjust itself if the graph is resized. (see the FilteredPointList and BasicArrayPointList for examples).
John
The only thing I can say:
WOW! Thanks!
Going to test it now!
Is it possible to put V5_BRANCH somewhere for download via HTTP standard ports? I have no access to cvs ports of SourceForge.
Tonight I ended up with 740MB downloaded from CVS's web interface using "wget -r" and nothing that is usable... :(
Hi,
You can download version 4.9.7 from the developer releases on sourceforge. I think the CVS browser is actually whacked, since it has no recent files.
John
4.9.7 from devloper releases has no NoDupePointList class. :(
Or am I looking at wrong position?
http://sourceforge.net/project/showfiles.php?group_id=114675&package_id=162846&release_id=417180
Hi,
Sorry about that...I've just included the file here:
John
//============================================================================
//ZedGraph Class Library - A Flexible Line Graph/Bar Graph Library in C#
//Copyright © 2006 John Champion
//
//This library is free software; you can redistribute it and/or
//modify it under the terms of the GNU Lesser General Public
//License as published by the Free Software Foundation; either
//version 2.1 of the License, or (at your option) any later version.
//
//This library is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
//Lesser General Public License for more details.
//
//You should have received a copy of the GNU Lesser General Public
//License along with this library; if not, write to the Free Software
//Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//=============================================================================
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
namespace ZedGraph
{
/// <summary>
/// A simple storage struct to maintain an individual sampling of data. This only
/// contains two data values in order to reduce to memory load for large datasets.
/// (e.g., no Tag or Z property)
/// </summary>
public struct DataPoint
{
public double X;
public double Y;
}
/// <summary>
/// A collection class to maintain a set of samples.
/// </summary>
/// <remarks>This type, intended for very
/// large datasets, will reduce the number of points displayed by eliminating
/// individual points that overlay (at the same pixel location) on the graph.
/// Note that this type probably does not make sense for line plots, but is intended
/// primarily for scatter plots.
/// </remarks>
///
/// <author> John Champion </author>
/// <version> $Revision: 1.1.2.1 $ $Date: 2006/05/19 05:11:32 $ </version>
[Serializable]
public class NoDupePointList : List<DataPoint>, IPointList, IPointListEdit
{
protected bool _isFiltered;
protected int _filteredCount;
protected int[] _visibleIndicies;
}
}
Jonathon,
Are you sure that there is an independent instance of a NoDupePointList for each curve? That is,
NoDupePointList ndp1 = new NoDupePointList();
NoDupePointList ndp2 = new NoDupePointList();
NoDupePointList ndp3 = new NoDupePointList();
// Fill in ndp lists here
LineItem curve1 = myPane.AddCurve( "a", ndp1, ... );
LineItem curve2 = myPane.AddCurve( "b", ndp2, ... );
LineItem curve3 = myPane.AddCurve( "c", ndp3, ... );
AxisChange();
ndp1.FilterData(...);
ndp2.FilterData(...);
ndp3.FilterData(...);
John
Hi,
Yes they are all independant lists. The problem is the scaling or transforming.
pane.Chart.Rect.Height returns e.g. 89
then
yAxis.Scale.Transform always returns y > height.
I am assuming there is an error in SetupScaleData() or Transform() or that I need to call something other than just AxisChange() to get the axes set correctly. I haven't been able to make sense of the transform code yet to see if I can work out what is wrong.
My sequence is actually different to yours because I don't know when each curve will be added...
AddCurve()
{
NoDupePointList ndp1 = new NoDupePointList();
// Fill in ndp lists here
LineItem curve1 = myPane.AddCurve( "a", ndp1, ... );
//I've tried calling ClearFilter() here
AxisChange();
//I've tried calling control.Refresh() here
for(i = 0; i < myPane.CurveList.Count; i++)
{
myPane.CurveList[i].FilterData(...);
}
control.Refresh();
}
Regards,
Jonathan
I created a test file to demonstrate the problem. It seems to be related to the use of MasterPane, so I guess the pixel position code in NoDupePointList does not take this into account correctly.
NB I add the ZedGraph control as the only control on the form and set to DockStyle.Fill.
Jonathan
using System;
using System.Drawing;
using System.Windows.Forms;
using ZedGraph;
namespace NoDupePointListTest
{
public partial class Form1 : Form
{
const int NUMPOINTS = 10000;
public Form1()
{
InitializeComponent();
}
Jonathon,
I think I found it. It's a bust in the logic for the NoDupePointList. The Filter() method is expecting pixel values that go from 0 to width and 0 to height, but the actual pixel values have a non-zero origin. The code below is a quick fix. Can you give this a try? It's just a matter of adding the left & top values to the two Transform() calculations.
John
int left = (int)pane.Chart.Rect.Left;
int top = (int)pane.Chart.Rect.Top;
for ( int i=0; i<base.Count; i++ )
{
DataPoint dp = base[i];
int x = (int)( xAxis.Scale.Transform( dp.X ) + 0.5 ) - left;
int y = (int)( yAxis.Scale.Transform( dp.Y ) + 0.5 ) - top;
Hi,
Thanks, I tried it in v5.0.0 and it now works.
:-)
Jonathan