Visual artefacts when drawing 2D Function contour plot (using FPlot) - c#

So I use an open source .NET library for plotting: FPlot
Here's the function that I'm trying to draw:
f(x,y) = x^2+3*y^2+2*x*y
Here's what I want it to look like:
Clarification:
I don't need the exact same appearence as in the image, I just need the plot to be mathematically correct
There are only 10 conours in the picture, I need as much as can be fit on the screen
Here's how I tried to do this:
var graphFunction = new Function2D();
graphFunction.source = "return (pow(x,2)+3*pow(y,2)+2*x*y)/10;";
/* I'm dividing by 10 because otherwise the whole plot is solid color */
graphFunction.Compile(true);
That's how the FPlot generated plot looks up close:
This is exactly what I want, but when I zoom out here's what happens:
Theese extra ellipses are not supposed to be there, in fact they are not there, this is just a graphical artefact, because when you zoom into one of theese 'fake' ellipses this is what you see:
The problem can be in this line:
graphFunction.source = "return (pow(x,2)+3*pow(y,2)+2*x*y)/10;";
...or in the FPlot source code. Any Ideas?
UPDATE:
So, z value in graph seems to be the problem. When value of a function, z = f(x,y) in a graph exceeds the z1 (max z) value it resets to z = z%z1 (same happens when z is lower then z0), which causes these "lines" - they are not countour lines, like I thought.
So that means the solution is: set z0 to min f(x,y) on screen, and set z1 to max f(x,y) on screen.

Make the displaying borders of your FPlotLibrary.GraphControl have the same value in all 3 dimensions and the problem goes away:
graphControl1.x0 = -40;
graphControl1.x1 = 40;
graphControl1.y0 = -40;
graphControl1.y1 = 40;
graphControl1.z0 = -40;
graphControl1.z1 = 40;
Btw, the "problem" reproduces, for instance, if you do
graphControl1.x0 = -40;
graphControl1.x1 = 40;
graphControl1.y0 = -40;
graphControl1.y1 = 40;
graphControl1.z0 = -1;
graphControl1.z1 = 1;

Related

Combine BarChart and PointChart

i got a Little "Problem", i want to create a Chart looking like this:
So basically
Series 1 = Normal bar Chart. Color green if it Ends before the "time max" (series2) Series 2 = just a DataPoint / Marker on top of series 1 items.
I am struggling with this though...
my Code:
chart_TimeChart.Series.Clear();
string series_timeneeded = "Time Needed";
chart_TimeChart.Series.Add(series_timeneeded);
chart_TimeChart.Series[series_timeneeded]["PixelPointWidth"] = "5";
chart_TimeChart.ChartAreas[0].AxisY.ScrollBar.Size = 10;
chart_TimeChart.ChartAreas[0].AxisY.ScrollBar.ButtonStyle = ScrollBarButtonStyles.SmallScroll;
chart_TimeChart.ChartAreas[0].AxisY.ScrollBar.IsPositionedInside = true;
chart_TimeChart.ChartAreas[0].AxisY.ScrollBar.Enabled = true;
chart_TimeChart.Series[series_timeneeded].BorderWidth = 2;
chart_TimeChart.Series[series_timeneeded].ChartType = SeriesChartType.StackedBar;
chart_TimeChart.Series[series_timeneeded].YValueType = ChartValueType.Time;
chart_TimeChart.ChartAreas[0].AxisY.LabelStyle.Format = "HH:mm:ss";
chart_TimeChart.Series[series_timeneeded].XValueType = ChartValueType.String;
for (int i = 0; i < MaxNumber; i++)
{
chart_TimeChart.Series[series_timeneeded].Points.AddXY("item"+ " " + (i + 1).ToString(), DateTime.Now.Add(Timespans[i]));
}
chart_TimeChart.Series.Add(series_FinishTime);
chart_TimeChart.Series[series_FinishTime].ChartType = SeriesChartType.StackedBar;
chart_TimeChart.Series[series_FinishTime].BorderWidth = 0;
chart_TimeChart.Series[series_FinishTime].MarkerSize = 15;
chart_TimeChart.Series[series_FinishTime].MarkerStyle = MarkerStyle.Square;
chart_TimeChart.Series[series_FinishTime].MarkerColor = Color.Black;
chart_TimeChart.Series[series_FinishTime].YValueType = ChartValueType.DateTime;
chart_TimeChart.Series[series_FinishTime].XValueType = ChartValueType.String;
for (int i = 0; i < MaxNumber; i++)
{
DateTime YPosition = GetFinishTime(i);
chart_TimeChart.Series[series_FinishTime].Points.AddXY("item"+ " " +(i+1).ToString(), YPosition);
}
but this only Displays the 2nd series on top of the first one but the first one isnt visible anymore. The Maker of series 2 isnt shown but instead the bar is (eventhough i made borderwidth to 0). In my opinion/thinking i just have to make the "bar" of series 2 invisible and just Show the marker Points for series 2.
Any ideas?
Update:
string seriesname = Name+ i.ToString();
chart_TimeChart.Series.Add(seriesname);
chart_TimeChart.Series[seriesname].SetCustomProperty("DrawSideBySide", "false");
chart_TimeChart.Series[seriesname].SetCustomProperty("StackedGroupName", seriesname);
chart_TimeChart.Series[seriesname].ChartType = SeriesChartType.StackedBar; //Y and X are exchanged
chart_TimeChart.Series[seriesname].YValueType = ChartValueType.Time;
chart_TimeChart.ChartAreas[0].AxisY.LabelStyle.Format = "HH:mm:ss";
chart_TimeChart.Series[seriesname].XValueType = ChartValueType.String;
DateTime TimeNeeded = DateTime.Now.Add(List_AllLiniengroupsTimespans[k][i]);
DateTime TimeMax = GetFinishTime(k, i);
TimeSpan TimeDifference = TimeNeeded - TimeMax;
if (TimeNeeded > TimeMax) //All good
{
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded); //Time till finish
chart_TimeChart.Series[seriesname].Points[0].Color = Color.Blue;
chart_TimeChart.Series[seriesname].Points[0].SetCustomProperty("StackedGroupName", seriesname);
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded.Add(TimeDifference)); //time left
chart_TimeChart.Series[seriesname].Points[1].Color = Color.Red;
chart_TimeChart.Series[seriesname].Points[1].SetCustomProperty("StackedGroupName", seriesname);
}
else if (TimeMax > TimeNeeded) //wont make it in time
{
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded); //time till still okay
chart_TimeChart.Series[seriesname].Points[0].Color = Color.Blue;
chart_TimeChart.Series[seriesname].Points[0].SetCustomProperty("StackedGroupName", seriesname);
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded.Add(TimeDifference)); //Time that is too much
chart_TimeChart.Series[seriesname].Points[1].Color = Color.Green;
chart_TimeChart.Series[seriesname].Points[1].SetCustomProperty("StackedGroupName", seriesname);
}
else if (TimeMax == TimeNeeded) //fits exactly
{
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded);
chart_TimeChart.Series[seriesname].Points[0].Color = Color.DarkOrange;
chart_TimeChart.Series[seriesname].Points[0].SetCustomProperty("StackedGroupName", seriesname);
}
the Code will be displayed as:
but i want it to look like this:
!! See the update below !!
If you really want to create a StackedBar chart, your chart has two issues:
If you want to stack datapoints they need to have meaningful x-values; without them how can it know what to stack on each other?
You add strings, which look fine but simply don't work. That is because the DataPoint.XValue field is double and when you add string into it it is set to 0 !! Your string is copied to the Label but otherwise lost.
So you need to come up with a suitable numeric value you use for the x-values..
And you also need to group the series you want to stack. For this there is a special property called StackedGroupName which serves to group those series that shall be stacked.
Here is how you can use it:
yourSeries1.SetCustomProperty("StackedGroupName", "Group1");
For a full example see this post !
It also shows one way of setting the Labels with string values of your choice..
This is the way to go for real StackedBar charts. Your workaround may or may not work. You could try to make the colors transparent or equal to the chart's backcolor; but it won't be more than a hack, imo.
Update
I guess I have misread the question. From what I see you do not really want to create a stacked chart.
Instead you struggle with these issues:
displaying bars at the same y-spot
making some bars invisible
displaying a vertical line as a marker
Let's tackle each:
Some column types including all Bars, Columns and then some have a little known special property called DrawSideBySide.
By default is is set to Auto, which will work like True. This is usually fine as we don't want bars to sit upon each other, effectively hiding all or part of the overlaid points.
But here we do want them to share the same y-position, so we need to set the property to false for at least one Series; the others (on Auto) will follow..:
You can do it either like this:
aSeries["DrawSideBySide"] = "false";
or like this:
aSeries.SetCustomProperty("DrawSideBySide", "false");
Next we hide the overlaid Series; this is simple:
aSeries.Color = Color.Transparent;
The last issue is displaying a line marker. There is no such MarkerStyle, so we need to use a custom style. For this we need to create a suitable bitmap and add it as a NamedImage to the chart's Images collection.
This sounds more complicated than it is; however the MarkerImage will not be scaled, so we need to created suitable sizes whenever we resize the Chart or add/remove points. I will ignore this complication for now..
int pointCount = 10;
Bitmap bmp = new Bitmap(2, chart.ClientSize.Height / pointCount - 5);
using (Graphics g = Graphics.FromImage(bmp)) g.Clear(Color.Black);
NamedImage marker = new NamedImage("marker", bmp);
chart.Images.Clear(); // quick & dirty
chart.Images.Add(marker);
Here is the result:
A few notes:
I would recommend to use variables for all chart elements you refer to repeatedly instead of using indexed references all the time. Less code, easier to read, a lot easier to maintain, and probably better performance.
Since your code called for the visible datapoints to be either red or green the Legend will not show a good representation. See here for an example of drawing a multi-colored legend item..
I used the chart height; this is not really recommended as there may be Titles or Legends or even more ChartAreas; instead you should use the height of the ChartArea, or even more precise, the height of the InnerPlotPosition. You would need to convert those from percentages to pixels. Not too hard, see below or see here
or here for more examples!
The markers should be adapted from the Resize and probably from the AxisViewChanged events. Putting it in a nice function to call (e.g. void setMarkerImage(Chart chart, Series s, string name, int width, Color c)) is always a good idea.
If you need to adapt the size of the marker image repeatedly, you may want to write better code for clearing the old one; this should include disposing of the Bitmap that was used before..
Here is an example:
var oldni = chart.Images.FindByName("marker");
if (oldni != null)
{
oldni.Image.Dispose();
chart.Images.Remove(oldni);
oldni.Dispose();
}
In some situations one needs to nudge the Chart to update some of its properties; RecalculateAxesScale is one such nudge.
Example for calculating a suitable marker height:
ChartArea ca = chart.ChartAreas[0];
ca.RecalculateAxesScale();
float cah = ca.Position.Height;
float iph = ca.InnerPlotPosition.Height;
float h = chart3.ClientSize.Height * cah / 100f * iph / 100f;
int mh = (int)(h / s.Points.Count);
Final note: The original answer stressed the importance of using meaningful x-values. Strings are useless! This was important for stacking bars; but it is equally important now, when we want bars to sit at the same vertical positions! Adding the x-values as strings is again resulting in nonsense..
(Since we have Bars the x-values go along the vertical axis and vice versa..)

Custom chart elements

I want to make a chart in C# with custom elements. What I have:
What I want:
Elements marked by red circles need to be replaced by the image. My program code is very short, just some values for the chart. All settings for chart were set by the "Collection" in Chart section (as shown on first image).
This is a BoxPlot chart and it takes 6 y-values.
You can add them or let the chart calculate them.
Looks like you want to add several images to the various y-values..
Here is an example of how to do that by owner-drawing the Chart. (No, not the whole chart, just a little extra custom-drawing ;-)
It adds an image to each of the y-values; it should be easy to adapt to only those values you really want. And if you only want one you may even do away with the ImageList and pick the image from the resources; (although using an ImageList is a nice way, as long as you can live with the limitations of 256x256 maximum size and all images having the same size and color depth..)
You seem to want one of these only:
4 Average and mean
5 Median
private void chart_PostPaint(object sender, ChartPaintEventArgs e)
{
Series s1 = chart.Series[0];
ChartArea ca = chart.ChartAreas[0];
Axis ax = ca.AxisX;
Axis ay = ca.AxisY;
Graphics g = e.ChartGraphics.Graphics;
int iw = imageList1.ImageSize.Width / 2;
int ih = imageList1.ImageSize.Height / 2;
foreach (DataPoint dp in s1.Points)
{
int x = (int) ax.ValueToPixelPosition(dp.XValue);
for (int i = 0; i < 6; i++)
{
int y = (int) ay.ValueToPixelPosition(dp.YValues[i]);
g.DrawImage(imageList1.Images[i], x - iw, y - ih);
}
}
}
I suggest to use png files with transparency and an odd width so they look nice and centered. (I used randomly 16x16, which is not quite that nice ;-) - For this you need to set the ImageSize and the ColorDepth of the ImageList.
To further style the chart you may use these special properties
Custom attributes
BoxPlotPercentile, BoxPlotSeries, BoxPlotShowAverage,
BoxPlotShowMedian, BoxPlotShowUnusualValues, BoxPlotWhiskerPercentile,
DrawSideBySide, MaxPixelPointWidth, MinPixelPointWidth,
PixelPointDepth, PixelPointGapDepth, PixelPointWidth, PointWidth
Note that you need to set them all as strings, maybe like this:
s1.SetCustomProperty("someAttribute", "someValue");

Accessing the Y value of a line chart based on cursor position

I'm new to charts and have a line chart that looks as thus.
The vertical line is where the cursor is and updates via mousemove rather than mouseclick.
As the title suggests what I want to do is access the Y value at the point at which the vertical line and the 'data line' intersect.
I've tried this - Get Y value of series from the X Cursor position where a ChartArea is clicked on but unless I'm missing something it just doesn't work, it either returns the first or the last value in the series depending on which datapoint you use.
I've tried hittestresult, that only seems to work if you're 'touching' the data line itself.
Any ideas?
Since you didn't show us any code and didn't answer my questions I can only assume that your chart doesn't have valid, i.e. numeric x-values.
This means the the x-values are all 0 and can't be used for anything: neither for setting a zoom range, not for formatting axis or other labels and also not for finding the DataPoints at an x-position.
This can be called 'implicitly indexed'. The result is similar to explicitly indexed charts, which results from setting the IsXValueIndexed of a Series to true: The DataPoints are lined up in a row and all are displayed at the same distance.
This is usually not what one wants and I really suggest you fix it by adding the DataPoints not like this:
for (int i = 0; i < count; i++) chart1.Series[0].Points.AddY(someYValue);
but maybe like this:
for (int i = 0; i < count; i++) chart1.Series[0].Points.AddXY(i, someYValue);
Then the linked answer will work just fine.
But just to show how you could workaround here is how to find the two closest points in an indexed chart.
Note that it uses a function, (actually two) that calculates the pixel rectangle of the inner plot postion. You can find them here or here..:
private void chart1_MouseMove(object sender, MouseEventArgs e)
{
ChartArea ca = chart1.ChartAreas[0];
Series S = chart1.Series[0];
RectangleF rf = InnerPlotPositionClientRectangle(chart1, ca);
float px = (float)( (e.X - rf.X) * S.Points.Count / rf.Width );
int p0 = (int)px; // previous point
int p1 = p0 + 1; // next point
if (p0 >= 0 && p0 < S.Points.Count)
Console.WriteLine( "DataPoint # " + p0 + " has a y-value of " +
S.Points[p0].YValues[0].ToString("0.00"));
//..
}
It will work but you really should correct the way you add the data points!

C# Windows form Chart fixed axis?

I am very new in all this of programming and I am beginning with C#
now I have an application which should show some points in a chart, but i would like to fix the x and y axis in a way that the graph area always has as minimum 50 and maximum 50, but the graphic area doesn't auto-fit the graph, I don't know if I explain it well, I mean I want the points for x and y axis always display from 0 to 50. does some one know how to set this?
you can easily set limits like this:
chart1.ChartAreas[0].AxisX.Maximum = 50;
chart1.ChartAreas[0].AxisX.Minimum = 0;
chart1.ChartAreas[0].AxisY.Maximum = 50;
chart1.ChartAreas[0].AxisY.Minimum = 0;

Formatting main chart's axis in C#

I'm trying to make chart in C# which should be readable easily. I managed to draw sin() graph, but it's not really readable, as X and Y axis don't stand out, I feel like it's the middle of nowhere. I tried to set line width using following code:
area.AxisX.LineWidth = 3;
But it only made line in the bottom of chart fatter, not main axis (Y = 0) like I need.
Anyone know how I would accomplish that? Is Y axis (X = 0) same? If no, could you please specify how to bold it too?
I think what you are looking for is the MajorGridLines:
area.AxisY.MajorGrid.LineWidth = 3
Or maybe this:
area.AxisX.LineWidth = 3;
area.AxisX.Crossing = 0;
area.AxisY.LineWidth = 3;
area.AxisY.Crossing = 0;
Crossing will put the axis at the value you set it to.

Categories