I'd like to get the x axis value of the maximum point from the chart bellow.
You can see that the value I get is around 67, even though it should be around 30. I thought it happened because of I also limit the chart maximum X axis into a certain value with the chart3.ChartAreas[0].AxisX.Maximum code, then I tried the following code to get the maximum value var dataPoint = chart3.Series[0].Points.FindMaxByValue();. So maybe this code is searching the maximum value from the whole series, not only from the visible chart area.
My question is, is there any idea to get the maximum value from the visible chart area only?? Or maybe it's better to erase the unwanted chart series, but I don't know how to do that.
Thanks
You could try checking out this stackoverflow answer.
The below code will search all DataPoint in the Series.
var xAxisValue = chart3.Series[0].Points.FindMaxByValue();
So, from the link above you can get the value you require by doing something like this:
var scaleView = chart3.ChartAreas[0].AxisX.ScaleView;
var min = scaleView.ViewMinimum;
var max = scaleView.ViewMaximum;
var yourAnswerMaybe = chart3.Series[0].Points
.Where(p => p.XValue > min && p.XValue < max)
.OrderBy(p => p.XValue)
.Last()
.XValue;
I have a candlestick chart which automatically updates with real time prices from a cryptocurrency exchange in the .NET forms. The goal is to make the bot preform actions when the price on chart passes one of the lines drawn by the user. So far I've come to the point of enabling line-drawing for users thanks to this article.
Could anyone please point me towards a method of detecting collision between the chart candles and the drawn lines? I feel like there must be an easier way than what I'm thinking of currently, just can't seem to figure out the way to it.
Using the exact solution for the line drawing as in the article, also posted code for the line-drawing below:
int index1 = 1;
int index2 = 4;
DataPoint left = chart.Series[0].Points[index1];
DataPoint right = chart.Series[0].Points[index2];
//Init the annotation
LineAnnotation line = new LineAnnotation();
line.AxisX = chart.ChartAreas[0].AxisX;
line.AxisY = chart.ChartAreas[0].AxisY;
line.IsSizeAlwaysRelative = false;
//Each point in a candlestick series has several y values, 0=high, 1=low, 2=open, 3=close
line.Y = left.YValues[1]; //low
line.X = left.XValue;
//If your data is indexed (your x values are Strings or you've set Series.IsXValueIndexed to true), use the data point index(+1) as the line X coordinate.
//line.X = index1 + 1;
//Use the width and height properties to determine the end position of the annotation.
line.Height = right.YValues[1] - left.YValues[1];
line.Width = right.XValue - left.XValue;
//Again, use the index if necessary
//line.Width = index2 - index1;
chart.Annotations.Add(line);
Just looking for a point in the direction of an easier solution, not the solution itself :) Thanks in advance!
So it sounds like you are asking is if a Point (Geometry) is above or below a line.
Here are the assumption (which you can change later to fit your needs):
an external resource is giving you a specific value (Y) at a specific point in time (X), which will call the Integral point XY.
The user has drawn a line which gives you a starting point (x1, y1) and an end point (x2, y2).
The graphs X component is in minutes, with each tick horizontally is 1 minute.
The graphs Y component is in dollars, with each tick is $25.
The user has drawn a line from (1:00pm, $50) to (1:05pm, $75).
We get an Integral Point XY at 1:10pm of $125.
What is the value of the line at 1:10pm so you can compare it to the Integral Point XY.
Based on my comments of Trigonometry..
We know the adjacent length is: 1:05 - 1:00 = 5
We know the opposite length is: 75 - 25 = 50
Using the formula: atan(opposite / adjacent) = angle
We calculate that the angle is: atan(50 / 5) = 1.47112767rad (radians)
Now we simply reverse our math:
We know the adjacent length is: 1:10 - 1:00 = 10
We know our Angle in Radians: 1.47112767
Using the formula: adjacent * tan(angle) = opposite
We calculate that the opposite is: 10 * tan(1.47112767) = ~$99.999999 or $100
$125 is above $100, do what you want.
For example I have chart with 2 points - 0,0 and 10,10 and chart type is FastLine.
I want to know what Y value will be in chart in choosen X value.
For example when X is 5 I want to know that Y is 5.
My charts more complicated and have tons of points, I need to get Y value through X.
How can I do this?
The problem boils down to two tasks:
Finding the neighbouring points for an x-value
Interpolating their y-values for the given x-value.
If the x-values are indeed steadily increasing this should solve both:
double interpolatedY(Series s, double xval)
{
DataPoint pPrev = s.Points.Last(x => x.XValue <= xval);
DataPoint pNext = s.Points.First(x => x.XValue >= xval);
if (pPrev == pNext) return pPrev.YValues[0];
return pPrev.YValues[0] + (pNext.YValues[0] - pPrev.YValues[0])
* (xval - pPrev.XValue)/ (pNext.XValue - pPrev.XValue);
}
It uses Linq to find the previous and next datapoint and then uses simple math to find the interpolated value.
Note that most checks are omitted!
Here I have added an identical point series and a third one to add the interpolated values:
To convert between chart pixels and values there are Axis functions ValueToPixelPosition and PixelPositionToValue, btw.
is there a way to get the rectangles of the stackcolumn chart bar?
this code snippet is how it can be works but it's very ugly:
var points = new List<Point>();
for (int x = 0; x < chart.Size.Width; x++)
{
for (int y = 0; y < chart.Size.Height; y++)
{
var hp = chart.HitTest(x, y, false, ChartElementType.DataPoint);
var result = hp.Where(h => h.Series?.Name == "Cats");
if (result.Count() > 0)
{
points.Add(new Point(x, y));
}
}
}
var bottomright = points.First();
var topleft = points.Last();
I will try to describe my purpose:
I would like to create a chart from various testresults and make this available as a HTML file. This generated Chart is inserted as an image file in the HTML document. Now, I would like to link each part of a Bar area from the Chart to an external document. Since the graphics is static, I have only the possibility to use the "MAP Area" element to make any area as a link from HTML. The "map" element requires a "rectangle", or these coordinates. That's the reason why I need the coordinator of each part of a Bar.
I have to mention that I am not really familiar with the Chart control yet.
The graphics is generated testweise.
[SOLVED]
i got the solution:
var stackedColumns = new List<Tuple<string,string,Rectangle>>();
for (int p = 0; p < chart.Series.Select(sm => sm.Points.Count).Max(); p++)
{
var totalPoints = 0;
foreach (var series in chart.Series)
{
var width = int.Parse(series.GetCustomProperty("PixelPointWidth"));
var x = (int)area.AxisX.ValueToPixelPosition(p + 1) - (width / 2);
int y = (int)area.AxisY.ValueToPixelPosition(totalPoints);
totalPoints += series.Points.Count > p ? (int)series.Points[p].YValues[0] : 0;
int y_total = (int)area.AxisY.ValueToPixelPosition(totalPoints);
var rect = new Rectangle(x, y_total, width, Math.Abs(y - y_total));
stackedColumns.Add(new Tuple<string, string, Rectangle>(series.Name, series.Points.ElementAtOrDefault(p)?.AxisLabel, rect));
}
}
this workaround works for stackedcolumn and points starts at x-axis=0.
just the PixelPointWidth property has to be set manualy to get the right width. i have not yet found a way to get the default bar width..
This is extremely tricky and I really wish I knew how to get the bounds from some chart functionionality!
You code snippet is actulally a good start for a workaround. I agree though that it has issues:
It is ugly
It doesn't always work
It has terrible performance
Let's tackle these issues one by one:
Yes it is ugly, but then that's the way of workarounds. My solution is even uglier ;-)
There are two things I found don't work:
You can't call a HitTest during a Pre/PostPaint event or terrible things will happen, like some Series go missing, SO exceptions or other crashes..
The result for the widths of the last Series are off by 1-2 pixels.
The performance of testing each pixel in the chart will be terrible even for small charts, but gets worse and worse when you enlarge the chart. This is relatively easy to prevent, though..:
What we are searching are bounding rectangles for each DataPoint of each Series.
A rectangle is defined by left and right or width plus top and bottom or height.
We can get precise values for top and bottom by using the axis function ValueToPixelPosition feeding in the y-value and 0 for each point. This is simple and cheap.
With that out of the way we still need to find the left and right edges of the points. To do so all we need to do it test along the zero-line. (All points will either start or end there!)
This greatly reduces the number of tests.
I have decided to do the testing for each series separately, restaring at 0 each time. For even better performance one could do it all in one go.
Here is a function that returns a List<Rectangle> for a given Series:
List<Rectangle> GetColumnSeriesRectangles(Series s, Chart chart, ChartArea ca)
{
ca.RecalculateAxesScale();
List<Rectangle> rex = new List<Rectangle>();
int loff = s == chart.Series.Last() ? 2 : 0; ;
int y0 = (int)ca.AxisY.ValueToPixelPosition(0);
int left = -1;
int right = -1;
foreach (var dp in s.Points)
{
left = -1;
int delta = 0;
int off = dp.YValues[0] > 0 ? delta : -delta;
for (int x = 0; x < chart.Width; x++)
{
var hitt = chart.HitTest(x, y0 +off );
if (hitt.ChartElementType == ChartElementType.DataPoint &&
((DataPoint)hitt.Object) == dp)
{
if (left < 0) left = x;
right = x;
}
else if (left > 0 && right > left) break;
}
int y = (int)ca.AxisY.ValueToPixelPosition(dp.YValues[0]);
rex.Add(new Rectangle(left, Math.Min(y0, y),
right - left + 1 - loff, Math.Abs(y - y0)));
left = -1;
}
return rex;
}
A few notes:
I start by doing a RecalculateAxesScale because we can't Hittest before the current layout has been calculated.
I use a helper variable loff to hold the offset for the width in the last Series.
I start searching at the last x coordinate as the points should all lie in sequence. If they don't because you have used funny x-values or inserted points you may need to start at 0 instead..
I use y0 as the baseline of the zero values for both the hittesting y and also the points' base.
I use a little Math to get the bounds right for both positive and negative y-values.
Here is a structure to hold those rectangles for all Series and code to collect them:
Dictionary<string, List<Rectangle>> ChartColumnRectangles = null;
Dictionary<string, List<Rectangle>> GetChartColumnRectangles(Chart chart, ChartArea ca)
{
Dictionary<string, List<Rectangle>> allrex = new Dictionary<string, List<Rectangle>>();
foreach (var s in chart.Series)
{
allrex.Add(s.Name, GetColumnSeriesRectangles(s, chart, ca));
}
return allrex;
}
We need to re-calculate the rectangles whenever we add points or resize the chart; also whenever the axis view changes. The common code for AxisViewChanged, ClientSizeChanged, Resize and any spot you add or remove points could look like this:
Chart chart= sender as Chart;
GetChartColumnRectangles(chart, chart.ChartAreas[0]);
Let's test the result with a Paint event:
private void chart1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
chart1.ApplyPaletteColors();
foreach (var kv in ChartColumnRectangles)
{
{
foreach (var r in kv.Value)
g.DrawRectangle(Pens.Black, r);
}
}
}
Here it is in action:
Well, I've been down this path and the BIG issue for me is that the custom property of 'PixelPointWidth' is just that - it is custom. You cannot retrieve it unless you've set it. I needed the width of the item - had to scwag/calculate it myself. Keep in mind that many charts can be panned/zoomed, so once you go down this path, then you need to recalculate it and set it for the chart prepaint events.
Here is a crude little function I made (is more verbose than needed - for educational purposes and has no error handling :)):
private int CalculateChartPixelPointWidth(Chart chart, ChartArea chartArea, Series series)
{
// Get right side - takes some goofy stuff - as the pixel location isn't available
var areaRightX = Math.Round(GetChartAreaRightPositionX(chart, chartArea));
var xValue = series.Points[0].XValue;
var xPixelValue = chartArea.AxisX.ValueToPixelPosition(xValue);
var seriesLeftX = chart.Location.X + xPixelValue;
var viewPointWidth = Math.Round((areaRightX - seriesLeftX - (series.Points.Count * 2)) / series.Points.Count, 0);
return Convert.ToInt32(viewPointWidth);
}
And this as well:
private double GetChartAreaRightPositionX(Chart chart, ChartArea area)
{
var xLoc = chart.Location.X;
return xLoc + (area.Position.Width + area.Position.X) / 100 * chart.Size.Width;
}
The reason I'm calculating this is because I need to draw some graphical overlays on top of the normal chart item objects (my own rendering for my own purposes).
In the 'prepaint' event for the chart, I need to calculate the 'PixelPointWidth' that matches the current chart view (might be panned/zoomed). I then use that value to SET the chart custom property to match . . . such that the normal chart entities and MINE are correctly aligned/scaled (ensures we're in exactly the right 'x' axis position):
In my prepaint event, I do the following - just prior to drawing my graphical entities:
// Pretty close scwag . . .
var viewPointWidth = CalculateChartPixelPointWidth(e.Chart, e.Chart.ChartAreas[0], e.Chart.Series[0]);
// Set the custom property and use the same point width for my own entities . .
chart1.Series[0].SetCustomProperty("PixelPointWidth", viewPointWidth.ToString("D"));
// . . . now draw my entities below . . .
How can I search for X-value in knowing a certain point, I know the value of Y
I want the function to automatically search for the X value when given the value of Y on chart C#!
i use this chart1.Series["E.P"].Points.FindMinByValue().YValues.ToString()); to know the Minvalue of Y axis i want to know the X value n this case (Xvalue, YminValue).
how i can know x value when y min value in chart c#
You get the DataPoint already by using FindMinByValue so if you assign it first:
var dataPoint = chart1.Series["E.P"].Points.FindMinByValue();
You can then access the relevant X and Y values like so:
var x = dataPoint.XValue;
var yVals = dataPoint.YValues;