I'm currently using the charting within .NET using System.Windows.Forms.DataVisualization.Charting.Chart. Thus far it seems very powerful, and works great. However, there is a huge problem in terms of how it is auto-calculating intervals. I use a lot of double values, and in libraries like ZedGraph, it handles this perfectly. It selects min/max/interval just fine. However, in MS Chart, it may select 206.3334539832 as a minimum, and intervals of a similar decimal precision. Obviously this looks quite ugly.
So, I tried simply making the axis format {0.00} and it works great when it loads the chart. Except when you zoom in, you need greater precision, maybe at 4 decimal places instead of 2. It seems I'm either stuck with 9 decimal places all the time, or else a constant fixed number that may break when someone requires greater precision. I'd rather it pick up the precision based on the level of zoom currently applied. Libraries like ZedGraph and Dundas (which I believe MS is even using!) tend to pick good values that change as you zoom in and out.
Is there any way to have the intervals change precision as the zoom frame changes? It's probably some simple property I have set wrong, but it's hard to tell with the millions of properties this thing has (especially when there's about 14 places that represent the concept of Interval).
I had the exact same problem when zooming. I added code to format the axis labels and call it from the Paint handler. The Axis View objects have an IsZoomed property and have functions to get the current axis limits (GetViewMinimum/Maximum). I set the Axis LabelStyle.Format to "N" for all cases unless the Max-Min=range is less than 1. Then I set the format to "F#" where # is calculated based on the axis range.
# = Convert.ToInt32(Math.Abs(Math.Log10(range) - .5)) + 1;
Having played around with the chart control I haven't been able to find a simple solution to your problem. However the following may help:
Have you considered setting the maximum and minimum values for the axes yourself? If you round the actual maximum and minimum values to the nearest sensible "round" number (5, 10, 0.5, 0.01) this should make the calculated intervals a bit more friendly.
I understand this is not an ideal solution but by carefully choosing the maximum and/or minimum values you can ensure the intervals are "nicer" numbers. If the range of your axes is say divisible by 2, 5 & 10 it should result in fairly nice intervals.
Why not modify number format string.
Create format string
string formatString = "{0.00";
Identify zoom level, say zoomLevel = 2;
formatString = formatString.PadRight(5+zoomLevel, '0');
formatString += "}";
Now use this format on axis legend. Use string builder or some better way to modify the format string.
To provide the result with minimal cost you can use exponential scientific format
You can attach to customize event.
From there, you can modify the labels on x-axis:
var xAxisLabels = chart1.ChartAreas[0].AxisX.CustomLabels;
...
xAxisLabels[0].Text = ...
set min. and max. values:
chart1.ChartAreas[0].AxisX.Maximum = ...;
etc.
you can dynamically update the max and min based on your data set. each time user zooms in, you do a FOREACH on every point and get the stats and based on that set your max and min
It helps to set the IntervalOffset of the axis, here an example:
Private Sub chMap_AxisViewChanged(sender As System.Object, e As System.Windows.Forms.DataVisualization.Charting.ViewEventArgs) Handles chMap.AxisViewChanged
'the grid ticks are rounded values
e.Axis.IntervalOffset = -e.Axis.ScaleView.ViewMinimum
End Sub
Related
I have a simple chart with 12 data points. The problem is that it shows a little white area before starting the trend lines.
Here is the code
for (int i = 0; i < 12; i++)
{
chart1.Series[0].Points.AddXY(DataTable.Rows[i].ItemArray[0], plotDataTable.Rows[i].ItemArray[1]);
chart1.Series[1].Points.AddXY(DataTable.Rows[i].ItemArray[0], plotDataTable.Rows[i].ItemArray[2]);
chart1.Series[2].Points.AddXY(DataTable.Rows[i].ItemArray[0], DataTable.Rows[i].ItemArray[3]);
chart1.Series[3].Points.AddXY(DataTable.Rows[i].ItemArray[0], DataTable.Rows[i].ItemArray[4]);
}
First column of DataTable is string and the other four are floats.
You need to set the AxisX.Minimum to a suitable value.
Usually this would be 0 or the x-value of the first DataPoint.
But the way you add the values this will not work.
You are adding the DataPoints in a rather unfortunte way, which sometimes is ok, but more often than not it will create all sorts of problems.
The recommended way is to add the x-values as numbers, or DateTimes, which internally will be converted to doubles.
But you add strings. This looks ok but the x-values contain neither those strings not anything else but 0s. Threfore you can't use them to set the range or tooltips or zoom ranges or to calculate stuff..
But if you want to you can still get the result you want by setting the minimum to 1:
ChartArea ca = yourChart.ChartAreas[0];
ca.AxisX.Minimum = 1;
I have added my x-values as string, too, but they look like numbers.
But the recommended way would be to convert your values to numbers so you can use them for all sorts of things..
A few notes:
This conversion is done by the chart, if at all possible for the y-values but not for the x-values! Maybe because a chart without numeric y-values makes no sense at all while sometimes x-values simply do not contain meaningful numeric data, like names, IDs, zip codes etc..
Don't let the visuals fool you: The strings are only copied into the axis labels; otherwise they are lost! (You should check this with the debugger!!)
You may notice that the number of Label changes in the screenshot. The number is calculated from the Interval of the x-axis. By default it is calculated automatically (Interval=double.NaN)to fit in a reasonable number. You can set it to any distance you like. Normaly it refers to the axis unit but in this case to the number of points. Set it to 2 to get one Label for every 2nd point; set it to 0.5 to get 2 Labels per DataPoint..
With real numbers (or DataTimes) as x-values you can also set a type for the interval like seconds or days..
By default, the ChartArea's IsStartedFromZero property is set true. This setting will force the chart to always start from zero. Try setting it to false:
chart1.ChartAreas[0].AxisX.IsStartedFromZero = false;
chart1.ChartAreas[0].AxisY.IsStartedFromZero = false;
I'm making a bar chart using .NETs Chart controls with a pretty wide range of values. The top of the chart (AxisY.Maximum) is 4930 and some of the Y values are:
573, 392, 151, 182, 4675, 103, 3100, 432, 326, 53, 3415, 1125, 115...
A pretty wide range of values, so I'm setting the chart to use a logarithmic scale with
chart.ChartAreas[0].AxisY.IsLogarithmic = true;
and
chart.ChartAreas[0].AxisY.Interval = .1; //Gives a nice number of lines.
That's fine, it works quite well. What isn't working is I'm trying to figure out the scale of each tick mark on the graph.
Side of the graph looks like this:
Looking at the .NET object, it says the log base scale is 10, but that doesn't seem to fit at all. Looks like each tick mark is going up by 25.
Here's why I'm trying to do, each of the bars in the chart have text on them, I'm looking at each one individually and trimming the text if it gets too long.
So something with a Y value of around 125 should only have a few characters of text, but something with a value of 1000 should be allowed about 50 characters (Total length of the screen in characters is about 110).
The formula I've tried using with a log base of 10, works OK, but has a tendency to put in too many characters on low scores. This is what it looks like:
My ultimate question is, if I'm looking an individual Y value of a bar (Say 150) how can I tell where on the chart this will fall (e.g. it's 150, so it's above the 126 tick mark and below the 158 tick mark.).
So, if I understand your question correctly you want to know how far along the chart a number is. The answer could be pixels or inches or whatever...we'll call that multiplier m.
10 should appear at 1*m (log (10) == 1)
100 should appear at 2*m (log (100) == 2)
To find where any arbitrary value will appear (I'm using 150 from your example):
Math.Log10(150) * m
Which equals 2.18 * m.
All that's left is for you to figure out what your m is. I don't have enough info to help you with that.
It may help to use values from the chart to understand why they are there:
Math.Log10(126) = 2.1
Math.Log10(158) = 2.2
Math.Log10(200) = 2.3
Math.Log10(251) = 2.4
You'll note that they increment along the chart by 1/10 each time.
In my WPF application the user is inputting a GPS coordinates in the format of Degrees : Minutes : Seconds as decimal fraction of minutes.
So 60° 30' 45" would be entered as 60° 30.750' .
I then store it as a pure decimal number so the above example would be stored as 60.5125.
The idea was that so the users wouldn't mess up the input it would be set in 3 different textboxes. One for Degrees, other for Minutes and one for the fractionalSeconds. It's somewhat bad that one of the numbers is seperated into two textboxes but if they have to type the whole number in the are afraid of all the point or comma confusion and that they could mess up.
So one thing I thought might work was a IMultiValueConverter but it seems to only work with MultiBindings which is something I'm not doing here.
Currently my solution is to bind to different properties and do all the calculations in code behind but I'm not really happy about the fractional bit. I assume 3 fractional letters but if they enter only 7 and assume 0.700 but get 0.007 so I thought I would have to do a string format bit.
Is there a better solution out there. Can I use MultiValueConverter for something like this?
You could try using a MaskedTextBox, such as the one from the Extended WPF Toolkit.
You could use a Masked TextBox. This implementation uses MaskedTextProvider which is a .net class.
I have chart that can contain a lot of points (10000 +)
When I scale the chart in order to see all points in screen, it takes some time to draw them
Can You advice me some optimization, in order not to draw all points
I'm not an expert with listed technologies, but I would solve this by 'bucketing' your data points.
Your X axis is time, so determine the resolution point for the current chart size. IE, if you are seeing the entire chart you will only need a data point per day for example. If you are zoomed in a long way, you might want a point per hour.
Now you have determined resolution, go through your chart, and find all the data that exists between the resolution points, IE, all data that is > 20th April 2011 at 4pm and < 20th April 2011 at 5pm if you are on an hourly resolution.
Depending on the type of data you are using, will determine if you want to average all the data point you have collected, or find the median (or some other method, such as a candle stick chart to show the max/min values). Either way, pick the most relevant method, repeat for all points and render the result with your new data.
Hope that's what you meant.
Seems like you should use some sort of level of detail (LoD) algorithm.
For example:
Always use a maximum given set of points to represent all your actual points. By calculating local minima and maxima you can create a proper representation of the given point set for a certain 'detail', depending on how far you are zoomed in.
Calculating these extrema can still prove to be slow, so you might need to cache them. You can calculate and cache this on the fly as new data arrives.
In addition to the other good suggestions, I would
Do some random-pausing on it, to see if it's spending much time doing something else that could be avoided, such as maybe allocating new point structures all the time.
Rather than paint directly to the window, paint to a bitmap, and copy that to the window. It always looks faster, and sometimes it even is faster. (Be sure to stub out the method that clears the window background.)
I had experienced a severe performance problem with thousands of Series added to the chart rather than thousands of Points. The solution that worked for me was a flavor of the Flyweight pattern:
Instead of adding 1000-s of series, add just a single one.
At the end of a virtual series, i.e., when all points of the series have been added and it's time to move on to the next one, insert an empty point:
series.Points.Add(new DataPoint(0, 0) { IsEmpty = true });
Hope that helps somebody.
I'm implementing a scatter plot using the MS Chart Control .NET 3.5, WinForms, C#. My x-axis data is DateTime and noticed I couldn't zoom in smaller than a resolution of 1 day, despite setting the ScaleView as follows:
chart1.ChartAreas["MyChart"].AxisX.ScaleView.MinSize = 4;
chart1.ChartAreas["MyChart"].AxisX.ScaleView.MinSizeType = DateTimeIntervalType.Hours;
Has anyone else had this issue? Any ideas?
Figured this out... perhaps I didn't RTFM close enough, but it wasn't obvious from the interactive demo.
Set
chart1.ChartAreas["MyChart"].CursorX.Interval = 0;
and then it allowed me to zoom along the x-axis just fine.
Works Great !
Very handy and mandatory if you want to have smooth Zooming behavior.
Didn't stumble upon it, though I did RTFM :-)
However, if you handle doubles or floats instead of integer based types (such as hours or days), setting the interval to Zero may be a little bit extreme : While zooming, you will end up having overly precise labels such as 2,907343253253235
A good combination is to use these two properties :
chartArea1.AxisY.ScaleView.MinSize = 0;
chartArea1.CursorY.Interval = 0.001;
this way you can zoom as much as you want, while still controlling precision at a reasonable level