I have a chart that displays the latest 1000 candles(Binance API limit). I get the chart data using:
BinanceClient client = new BinanceClient();
var candles = client.Spot.Market.GetKlines(symbol: "BTCUSDT", interval: (Binance.Net.Enums.KlineInterval)inter, startTime: start, endTime: end, limit: 1000).Data.ToList();
This is the data source.
I then build the chart using:
for (int i = 0; i < candles.Count; i++)
{
// adding date and high
chart1.Series["Price15"].Points.AddXY(candles[i].OpenTime, candles[i].High);
// adding low
chart1.Series["Price15"].Points[i].YValues[1] = (double)candles[i].Low;
//adding open
chart1.Series["Price15"].Points[i].YValues[2] = (double)candles[i].Open;
// adding close
chart1.Series["Price15"].Points[i].YValues[3] = (double)candles[i].Close;
}
This all works, but I have created 2 Functions that I believe have the same problem source.
The first function is to allow zooming into the chart area, But this does not work properly(?)
as the X axis does not display the dates correctly so it only really zooms from left to right(i.e does not zoom if the cursor is in the middle of the chart. This is done like this:
private void chart1_MouseWheel(object sender, MouseEventArgs e)
{
try
{
Axis xAxis = chart1.ChartAreas[0].AxisX;
double xMin = xAxis.ScaleView.ViewMinimum;
double xMax = xAxis.ScaleView.ViewMaximum;
double xPixelPos = xAxis.PixelPositionToValue(e.Location.X);
if (e.Delta < 0)
{
var posXStart = xAxis.PixelPositionToValue(e.Location.X) - (xMax - xMin) / 0.75;
var posXFinish = xAxis.PixelPositionToValue(e.Location.X) + (xMax - xMin) / 1;
xAxis.ScaleView.Zoom(posXStart, posXFinish);
}
else if (e.Delta > 0)
{
var posXStart = xAxis.PixelPositionToValue(e.Location.X) - (xMax - xMin) * 0.75;
var posXFinish = xAxis.PixelPositionToValue(e.Location.X) + (xMax - xMin) * 1;
xAxis.ScaleView.Zoom(posXStart, posXFinish);
FZoomLevel++;
}
}
catch { }
}
Second function is a Hittest to try and find my coordinate values. This worked for the Y axis, but still no values for the X axis. This is done like this:
private void chart1_GetToolTipText(object sender, ToolTipEventArgs e)
{
System.Windows.Forms.DataVisualization.Charting.HitTestResult htr = chart1.HitTest(e.X, e.Y);
if (htr.ChartElementType != ChartElementType.PlottingArea && htr.ChartElementType != ChartElementType.DataPoint && htr.ChartElementType != ChartElementType.Gridlines&& htr.ChartElementType!= ChartElementType.Axis)
return;
ChartArea ca = chart1.ChartAreas[0];
double yCoord = ca.AxisY2.PixelPositionToValue(e.Y);
string s = e.X.ToString("s");
double xCord = ca.AxisX.PixelPositionToValue(e.X);
e.Text = "\nY = " + Math.Round(yCoord, 2).ToString();
e.Text = "\nX = " + xCord.ToString();
}
My question is how can I format my X axis Like TradingView(e.g)? right now it automatically can calculate the range of dates. i.e. if I want 15min candle data from this date it will start from 2021-05-18 to 2021-05-28, but it wont display hours and x values.
background: I am trying to create an algotrading environment
chart1.Series[0].XValueType = ChartValueType.DateTime;
chart1.ChartAreas[0].AxisX.IntervalType = (DateTimeIntervalType)DateRangeType.DayOfMonth;
chart1.ChartAreas[0].AxisX.LabelStyle.Format = "mm-hh-dd";
You can edit the format of the x axis by first setting the XValueType to either Time or DateTime
Not perfect but allows for more detail when zooming in.
Related
I have a working mouse click event on my windows form graph and now I'd like to add data points on each click to make it visible where on the graph it was clicked. Upon the 3rd click, the previous 2 will clear and the 3rd and 4th click will have their own new data points and so on and so on (2 data points at a time to show start and stop locations and the difference/delta is calculated between those to positions).
My current code looks like:
private void chart1_MouseClick(object sender, MouseEventArgs e)
{
HitTestResult result = chart1.HitTest(e.X, e.Y);
if (result.PointIndex >= 0)
{
if (diffCounter == 0)
{
xOne = result.Series.Points[result.PointIndex].YValues[0];
diffCounter++;
//Console.WriteLine("VALY " + xOne);
}
else if (diffCounter == 1)
{
xTwo = result.Series.Points[result.PointIndex].YValues[0];
diffCounter = 0;
//Console.WriteLine("Delta = " + Math.Round(Math.Abs(xTwo - xOne)), 2);
pointDifferenceTextBox.Text = Math.Round((Math.Abs(xTwo - xOne)), 2).ToString();
}
}
}
I cannot find anything anywhere about adding a data point based on where a hit test was performed on a line chart (or any chart for that matter).
Difference Counter is just an int to determine whether its the first or second click.
xOne is to get the first click y-value, xTwo is to get the second click y-value.
EDIT: I'd like to had a circle data point based on where the hit test is performed on.
Since the post was changed a new answer seems warranted.
Here is how one can create two points to be drawn in a Paint event.
First we need to store them:
PointF p1 = PointNull;
PointF p2 = PointNull;
To flag state we also use a static value:
static PointF PointNull = new PointF(-123f, -123f);
You could use some other flag as well in order to control switching between the 1st and 2nd point.
Next we need to store values in the click :
private void chart1_MouseClick(object sender, MouseEventArgs e)
{
Axis ax = chart1.ChartAreas[0].AxisX;
Axis ay = chart1.ChartAreas[0].AxisY;
double x = ax.PixelPositionToValue(e.X);
double y = ay.PixelPositionToValue(e.Y);
y = GetMedianYValue(chart1.Series[0], x);
if (p1 == PointNull ||(p1 != PointNull && p2 != PointNull))
{
p1 = new PointF((float)x, (float)y);
p2 = PointNull;
}
else
{
p2 = new PointF((float)x, (float)y);
}
// values have changed, trigger drawing them!
chart1.Invalidate();
}
Note that I first use the axis functions to get the axis values of the clicked position. Then I overwrite the y-value by a function that calculates the point on the line..:
double GetMedianYValue(Series s, double xval )
{
// Findclosest datapoints:
DataPoint dp1 = s.Points.Where(x => x.XValue <= xval).LastOrDefault();
DataPoint dp2 = s.Points.Where(x => x.XValue >= xval).FirstOrDefault();
// optional
dp1.MarkerStyle = MarkerStyle.Circle;
dp1.MarkerColor = Color.Purple;
dp2.MarkerStyle = MarkerStyle.Circle;
dp2.MarkerColor = Color.Violet;
double dx = dp2.XValue - dp1.XValue;
double dy = dp2.YValues[0] - dp1.YValues[0];
// same point
if (dx == 0) return dp1.YValues[0];
// calculate median
double d = dp1.YValues[0] + dy / dx * ( xval - dp1.XValue) ;
return d;
}
Note that this function marks the neighbouring datapoints for testing only!
Finally we need to draw the two points:
private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
{
Axis ax = chart1.ChartAreas[0].AxisX;
Axis ay = chart1.ChartAreas[0].AxisY;
int x1 = (int)ax.ValueToPixelPosition(p1.X);
int y1 = (int)ay.ValueToPixelPosition(p1.Y);
int x2 = (int)ax.ValueToPixelPosition(p2.X);
int y2 = (int)ay.ValueToPixelPosition(p2.Y);
if (x1 >= 0 && x1 < chart1.Width) // sanity check
if (p1 != PointNull)
e.ChartGraphics.Graphics.DrawEllipse(Pens.LightSeaGreen, x1 - 3, y1 - 3, 6, 6);
if (x2 >= 0 && x2 < chart1.Width) // sanity check
if (p2 != PointNull)
e.ChartGraphics.Graphics.DrawEllipse(Pens.Red, x2 - 3, y2 - 3, 6, 6);
}
Here is the result:
The original post asked for adding a DataPoint at the clicked location. For this HitTest is not useful.
Instead you need one of the the axis functions; PixelPositionToValue will convert the pixels position to an axis value..:
Axis ax = chart1.ChartAreas[0].AxisX;
Axis ay = chart1.ChartAreas[0].AxisY;
double x = ax.PixelPositionToValue(e.X);
double y = ay.PixelPositionToValue(e.Y);
DataPoint dp = new DataPoint(x, y);
dp.Color = Color.Red;
chart1.Series[0].Points.Add(dp);
Note that these function are only valid in either one of the paint or one of the mouse events!
I have a database table of dates and stock prices. I bind it to a WinForm Chart control according to the article. https://msdn.microsoft.com/en-us/library/dd489231(v=vs.110).aspx
I want to mark the highest data point as this article. https://msdn.microsoft.com/en-us/library/dd456612(v=vs.110).aspx
// Get relative coordinates of the data point
System.Drawing.PointF pos = System.Drawing.PointF.Empty;
pos.X = (float)cg.GetPositionFromAxis("ChartArea1", AxisName.X, i);
pos.Y = (float)cg.GetPositionFromAxis("ChartArea1", AxisName.Y, max);
// Convert relative coordinates to absolute coordinates.
os = cg.GetAbsolutePoint(pos);
Since the X-Axis is datetime value, I got pos.X = -99845. I think I should use OADate as the article. https://msdn.microsoft.com/en-us/library/dd456614(v=vs.110).aspx But I haven't figured out the way for the binding source.
I find I should use the point's X value instead of its index i like in that tutorial example.
private void chart_PostPaint(object sender, ChartPaintEventArgs e)
{
if (e.ChartElement is Series && ((Series)e.ChartElement).Name == "Series3")
{
Series s = e.Chart.Series[0];
ChartGraphics cg = e.ChartGraphics;
double max = s.Points.FindMaxByValue().YValues[0];
for (int i = 0; i < s.Points.Count; i++)
{
if (s.Points[i].YValues[0] == max)
{
PointF pos = PointF.Empty;
pos.X = (float)cg.GetPositionFromAxis("ChartArea1", AxisName.X, s.Points[i].XValue);
pos.Y = (float)cg.GetPositionFromAxis("ChartArea1", AxisName.Y, max);
pos = cg.GetAbsolutePoint(pos);
for (int r = 10; r < 40; r+=10)
{
cg.Graphics.DrawEllipse(
Pens.Red,
pos.X - r / 2,
pos.Y - r / 2,
r, r);
}
}
}
}
}
I have a polar chart I created by chart control in my WinForms app. X axis type is Distance, Y axis type is Azimuth.
My problem is that I cannot set X,Y initial points i.e (0,0) at center, it always appears at different place. Just as shown in figure 1
As you see in the above figures, Azimuth value is Zero at 270 and 360 at 90, and Distance is 0 at 180 and 100 at 0.
Here is the code I used to create polar chart.
public PolarChart()
{
this.WindowState = FormWindowState.Maximized;
InitializeComponent();
Series s0 = new Series("PolarChart");
s0.Color = Color.Gray;
s0.ChartType = SeriesChartType.Polar;
s0.Points.AddXY(0, 0);
chart1.Series.Add(s0);
chart1.Series[0]["PolarDrawingStyle"] = "line";
chart1.ChartAreas[0].AxisX.Minimum = 0;
chart1.ChartAreas[0].AxisX.Maximum = 360;
chart1.ChartAreas[0].AxisX.Interval = 30;
chart1.ChartAreas[0].AxisY.Minimum = 0;
chart1.ChartAreas[0].AxisY.Maximum = 100;
chart1.ChartAreas[0].AxisY.Interval = 10;
}
private void btnDisplay_Click(object sender, EventArgs e)
{
TextFieldParser reader = new TextFieldParser("LFPoints.txt");
reader.Delimiters = new string[] { "," };
reader.HasFieldsEnclosedInQuotes = true;
string[] read = reader.ReadFields();
try
{
Series S2 = new Series("Plot");
S2.ChartType = SeriesChartType.Polar;
S2.BorderColor = Color.Red;
if (read != null)
if (read.Length != 0)
{
foreach (string other in read)
{
ComputeDistanceAngle(other, Base);
S2.Points.AddXY(Azimuth, Distance);
}
chart1.Series.Add(S2);
chart1.Series["LFPlot"]["PolarDrawingStyle"] = "marker";
}
else
{
MessageBox.Show("No Graph to Plot");
}
else
MessageBox.Show("No Graph to Plot");
}
catch (Exception)
{
}
}
private void chart1_MouseMove(object sender, MouseEventArgs e)
{
var pos = e.Location;
if (prevPosition.HasValue && pos == prevPosition.Value)
return;
tooltip.RemoveAll();
prevPosition = pos;
var results = chart1.HitTest(pos.X, pos.Y, false,
ChartElementType.PlottingArea);
foreach (var result in results)
{
if (result.ChartElementType == ChartElementType.PlottingArea)
{
var xVal = result.ChartArea.AxisX.PixelPositionToValue(pos.X);
var yVal = result.ChartArea.AxisY.PixelPositionToValue(pos.Y);
tooltip.Show("Azimuth = " + xVal + "\n"+" Distance = " + yVal, this.chart1,
pos.X, pos.Y - 15);
}
}
}
After I tried a lot I came to know about "Crossing" property.
chart1.ChartAreas[0].AxisX.Crossing = 90;
chart1.ChartAreas[0].AxisY.Crossing = 0;
But I didn't get what i need. So please help me to plot the points in polar chart with X and Y points starting from center. Here is the link which exactly how I need my polar chart. enter link description here
I am using Visual Studio 2010.
I have a winforms application which contains a chart control called
comparisonChart
I have implemented Zoom capability in the chart control by subscribing to the mousewheel event and doing the following.
private void comparisonChartMouseWheel(object sender, MouseEventArgs e)
{
if (e.Delta < 0)
{
this.comparisonChart.ChartAreas[0].AxisX.ScaleView.ZoomReset(0);
this.comparisonChart.ChartAreas[0].AxisY.ScaleView.ZoomReset(0);
}
else if (e.Delta > 0)
{
double xMin = this.comparisonChart.ChartAreas[0].AxisX.ScaleView.ViewMinimum;
double xMax = this.comparisonChart.ChartAreas[0].AxisX.ScaleView.ViewMaximum;
double yMin = this.comparisonChart.ChartAreas[0].AxisY.ScaleView.ViewMinimum;
double yMax = this.comparisonChart.ChartAreas[0].AxisY.ScaleView.ViewMaximum;
double posXStart = this.comparisonChart.ChartAreas[0].AxisX.PixelPositionToValue(e.Location.X) - (xMax - xMin) / 4;
double posXFinish = this.comparisonChart.ChartAreas[0].AxisX.PixelPositionToValue(e.Location.X) + (xMax - xMin) / 4;
double posYStart = this.comparisonChart.ChartAreas[0].AxisY.PixelPositionToValue(e.Location.Y) - (yMax - yMin) / 4;
double posYFinish = this.comparisonChart.ChartAreas[0].AxisY.PixelPositionToValue(e.Location.Y) + (yMax - yMin) / 4;
this.comparisonChart.ChartAreas[0].AxisX.ScaleView.Zoom(posXStart, posXFinish);
this.comparisonChart.ChartAreas[0].AxisY.ScaleView.Zoom(posXStart, posXFinish);
}
}
When I zoom in on the chart, the X axis values show decimal values.
To remove the decimal values I have also done the following.
comparisonChart.Series[0].XValueType = ChartValueType.Int32;
But again it is showing decimal values when I zoom in.
The chart control as such doesn't have this logic.
Check the solution of this question.
Hope this helps.
Edit: In the meanwhile, I tried the below code snippet in a sample program with your zoom code. Now the axis values are displayed without decimals.
chart1.ChartAreas[0].AxisX.LabelStyle.Format = "0";
chart1.ChartAreas[0].AxisY.LabelStyle.Format = "0";
I've been searching the net for some time now yet still haven't found any good solution to my problem. I want to make MS Chart to automatically rescale Y axis on scrolling to make sure that all data points are visible. The twist here is that I need to have the ability to exclude certain series from being used for auto scale. So far I only found solutions that offer to iterate through the entire point collection on AxisViewChanged event, which doesn't work well when you have large collections of points and a few series to iterate through. I was wondering if there was any way to narrow the search by obtaining data points that are between currently visible min and max X values. Any help would be appreciated.
Edit Heres the image. As you can see the candlesticks in the middle aren't entirely visible.
you can try this code
DateTime date = DateTime.Now;
chart1.ChartAreas[0].AxisX.Minimum = 0;
chart1.ChartAreas[0].AxisX.Maximum = 20;
Random r = new Random((int)date.Ticks);
chart1.Series[0].ChartType = SeriesChartType.Candlestick;
chart1.Series[0].Color = Color.Green;
chart1.Series[0].XValueType = ChartValueType.Time;
chart1.Series[0].IsXValueIndexed = true;
chart1.Series[0].YValuesPerPoint = 4;
chart1.Series[0].CustomProperties = "MaxPixelPointWidth=10";
for (int i = 0; i < 100; i++ )
{
DataPoint point = new DataPoint(date.AddHours(i).ToOADate(), new double[] { r.Next(10, 20), r.Next(30, 40), r.Next(20, 30), r.Next(20, 30) });
chart1.Series[0].Points.Add(point);
}
int min = (int)chart1.ChartAreas[0].AxisX.Minimum;
int max = (int)chart1.ChartAreas[0].AxisX.Maximum;
if (max > chart1.Series[0].Points.Count)
max = chart1.Series[0].Points.Count;
var points = chart1.Series[0].Points.Skip(min).Take(max - min);
var minValue = points.Min(x => x.YValues[0]);
var maxValue = points.Max(x => x.YValues[1]);
chart1.ChartAreas[0].AxisY.Minimum = minValue;
chart1.ChartAreas[0].AxisY.Maximum = maxValue;
Use a query to find out which series you want to use for finding ymin and ymax in the code.
private void chart1_AxisViewChanged(object sender, ViewEventArgs e)
{
if (e.Axis.AxisName == AxisName.X)
{
int start = (int)e.Axis.ScaleView.ViewMinimum;
int end = (int)e.Axis.ScaleView.ViewMaximum;
// Series ss = chart1.Series.FindByName("SeriesName");
// use ss instead of chart1.Series[0]
double[] temp = chart1.Series[0].Points.Where((x, i) => i >= start && i <= end).Select(x => x.YValues[0]).ToArray();
double ymin = temp.Min();
double ymax = temp.Max();
chart1.ChartAreas[0].AxisY.ScaleView.Position = ymin;
chart1.ChartAreas[0].AxisY.ScaleView.Size = ymax - ymin;
}
}
This is a minor improvement on the excellent submission from Shivaram K R, to prevent open, close and low dropping off the bottom for the lowest points on financial charts with four Y values: high, low, open close.
// The following line goes in your form constructor
this.chart1.AxisViewChanged += new EventHandler<ViewEventArgs> (this.chart1_AxisViewChanged);
private void chart1_AxisViewChanged(object sender, ViewEventArgs e)
{
if (e.Axis.AxisName == AxisName.X)
{
int start = (int)e.Axis.ScaleView.ViewMinimum;
int end = (int)e.Axis.ScaleView.ViewMaximum;
// Use two separate arrays, one for highs (same as temp was in Shavram's original)
// and a new one for lows which is used to set the Y axis min.
double[] tempHighs = chart1.Series[0].Points.Where((x, i) => i >= start && i <= end).Select(x => x.YValues[0]).ToArray();
double[] tempLows = chart1.Series[0].Points.Where((x, i) => i >= start && i <= end).Select(x => x.YValues[1]).ToArray();
double ymin = tempLows.Min();
double ymax = tempHighs.Max();
chart1.ChartAreas[0].AxisY.ScaleView.Position = ymin;
chart1.ChartAreas[0].AxisY.ScaleView.Size = ymax - ymin;
}
}
Based on previous answers
private void chart1_AxisViewChanged(object sender, ViewEventArgs e)
{
if(e.Axis.AxisName == AxisName.X)
{
int start = (int)e.Axis.ScaleView.ViewMinimum;
int end = (int)e.Axis.ScaleView.ViewMaximum;
List<double> allNumbers = new List<double>();
foreach(Series item in chart1.Series)
allNumbers.AddRange(item.Points.Where((x, i) => i >= start && i <= end).Select(x => x.YValues[0]).ToList());
double ymin = allNumbers.Min();
double ymax = allNumbers.Max();
chart1.ChartAreas[0].AxisY.ScaleView.Position = ymin;
chart1.ChartAreas[0].AxisY.ScaleView.Size = ymax - ymin;
}
}
It could be you have more series in the chartarea. In this case you pick the high and low of all series in the area instead of just one.
regards,
Matthijs
Above answers were very helpful for me. However, I have a chart with multiple charting areas. I have adapted the code to scale up to all chart areas:
foreach (ChartArea area in chart1.ChartAreas)
{
List<double> allNumbers = new List<double>();
foreach (Series item in chart1.Series)
if (item.ChartArea == area.Name)
allNumbers.AddRange(item.Points.Where((x, i) => i >= start && i <= end).Select(x => x.YValues[0]).ToList());
double ymin = allNumbers.Min();
double ymax = allNumbers.Max();
if (ymax > ymin)
{
double offset = 0.02 * (ymax - ymin);
area.AxisY.Maximum = ymax + offset;
area.AxisY.Minimum = ymin - offset;
}
}