There is an input of points with size of n like below:
S = {x1,y1,x2,y2,...,xn,yn}
I want to display scatter graph of S sequence in a picture box. So for transforming them into picture box dimensions, I have normalized them and multiplied them by width and height of picture box with respecting picture box left and top:
waveData= wave.GetWaveData();
normalizedData = GetSignedNormalized();
n = normalizedData.Count;
picW = pictureBox1.Width;
picH = pictureBox1.Height;
picL = pictureBox1.Left;
picT = pictureBox1.Top;
normalizedInPictureBox = new List<float>();
for (int i=0;i< n; i +=2)
{
float px = normalizedData[i];
float py = normalizedData[i+1];
px = px * (picW - picL);
py = py * (picH - picT) ;
normalizedInPictureBox.Add(px);
normalizedInPictureBox.Add(py);
}
Normalize Method is also:
public List<float> GetSignedNormalized()
{
List<float> data = new List<float>();
short max = waveData.Max();
int m = waveData.Count;
for(int i=0;i< m; i++)
{
data.Add((float)waveData[i] / (float)max);
}
return data;
}
Now I am thinking normalizedInPictureBox List contains vertices in the range of picture box, and here is the code for drawing them on picture box:
In the paint method of picture box:
Graphics gr = e.Graphics;
gr.Clear(Color.Black);
for(int i=0;i< n; i +=2)
{
float x = normalizedInPictureBox[i] ;
float y = normalizedInPictureBox[i+1];
gr.FillEllipse(Brushes.Green, new RectangleF(x, y, 2.25f, 2.25f));
}
But the result is shown below:
I don't Know whats going wrong here , but I think the graph should be horizontal not diagonal ,the desire result is something like this:
I know that I can transform it to center of picture box after this. but How can change my own result to the desire one?
Thanks in advance.
I don't really know why your code doesn't work correctly without having a look at the actual data and playing around with it, but having done chart drawing before, I suggest you go the full way and clearly define your axis ranges and do proper interpolating. It get's much clearer from there.
Here is what I came up with
static Bitmap DrawChart(float[] Values, int Width, int Height)
{
var n = Values.Count();
if (n % 2 == 1) throw new Exception("Invalid data");
//Split the data into lists for easy access
var x = new List<float>();
var y = new List<float>();
for (int i = 0; i < n - 1; i += 2)
{
x.Add(Values[i]);
y.Add(Values[i + 1]);
}
//Chart axis limits, change here to get custom ranges like -1,+1
var minx = x.Min();
var miny = y.Min();
var maxx = x.Max();
var maxy = y.Max();
var dxOld = maxx - minx;
var dyOld = maxy - miny;
//Rescale the y-Range to add a border at the top and bottom
miny -= dyOld * 0.2f;
maxy += dyOld * 0.2f;
var dxNew = (float)Width;
var dyNew = (float)Height;
//Draw the data
Bitmap res = new Bitmap(Width, Height);
using (var g = Graphics.FromImage(res))
{
g.Clear(Color.Black);
for (int i = 0; i < x.Count; i++)
{
//Calculate the coordinates
var px = Interpolate(x[i], minx, maxx, 0, dxNew);
var py = Interpolate(y[i], miny, maxy, 0, dyNew);
//Draw, put the ellipse center around the point
g.FillEllipse(Brushes.ForestGreen, px - 1.0f, py - 1.0f, 2.0f, 2.0f);
}
}
return res;
}
static float Interpolate(float Value, float OldMin, float OldMax, float NewMin, float NewMax)
{
//Linear interpolation
return ((NewMax - NewMin) / (OldMax - OldMin)) * (Value - OldMin) + NewMin;
}
It should be relatively self explanatory. You may consider drawing lines instead of single points, that depends on the look and feel you want to achive. Draw other chart elements to your liking.
Important: The y-Axis is actually inversed in the code above, so positive values go down, negative go up, it is scaled like the screen coordinates. You'll figure out how to fix that :-)
Example with 5000 random-y points (x is indexed):
Related
I am trying to generate a grid across my map and add nodes depending on the perlin noise value. Depending on the value obtained from the perlin noise at a location, I will add a new Node which will be of a certain type e.g. Mountain, Water etc to represent terrian. Here I am trying to make it so that if the value is > 0.5, this mean it's only mountains and so a black coloured cubes should surround the mountain areas, However, my black cubes do not match the mountain areas from the perlin noise and I cannot seem to figure out why I am going wrong. Would appreciate any insight into how I could go about achieving this.
private void LocateWalkableCells()
{
for(int z = 0; z < Height; z++)
{
for(int x = 0; x < Width; x++)
{
noise = GetNoiseValue(x, z);
if(noise > 0.5) {
grid[x,z] = new Node(new Vector3(x, 0, z), TerrainType.Mountain, 1);
}
else {
grid[x,z] = new Node(new Vector3(x, 0, z), TerrainType.Grass, 1);
}
}
}
}
private float GetNoiseValue(int x, int z)
{
int pos = (x * Width) + z;
return Mathf.Round(terrainGenerator.noiseArray[pos] * 10) / 10;
}
// Draw gizmos to visualize colour
void OnDrawGizmos()
{
Gizmos.DrawWireCube(transform.position, new Vector3(Width, 1, Height));
if(grid != null)
{
foreach(Node n in grid)
{
if(n.TerrainType == TerrainType.Grass)
{
Gizmos.color = Color.green;
}
else if(n.TerrainType == TerrainType.Mountain)
{
Gizmos.color = Color.black;
}
Gizmos.DrawCube(n.Position, Vector3.one * (nodeDiameter - .1f));
}
}
}
noiseArray is used for the vertices of the terrain in the following code:
vertices = new Vector3[(Width + 1) * (Depth + 1)];
noiseArray = PerlinNoise();
int i = 0;
for(int z = 0; z <= Depth; z++)
{
for(int x = 0; x <= Width; x++)
{
var currentHeight = noiseArray[i];
if(currentHeight > HeightThreshold)
{
currentHeight *= HeightMultiplier;
}
vertices[i] = new Vector3(x, currentHeight, z);
i++;
}
}
Output
Result from suggested answer
Still seems to miss some mountain areas, colouring green instead of black.
It think the issue is in
var pos = (x * Width) + z;
since x is you index on the width of the grid you would probably rather want
var pos = z * Width + x;
in other words you want to
skip z rows
each row has Width elements
then from there take the xth element
assuming your terrain is laid out row-wise.
Or if it is laid out column-wise (which is rather unusual but possible)
var pos = x * Height + z;
or in other words
skip x columns
each column has Height elements
then from there take the zth element
See also Converting index of one dimensional array into two dimensional array i. e. row and column
Update
Now that you have showed the terrain generation code it needs to be
var pos = z * (Width + 1) + x;
since the terrain array has actually Width + 1 elements per row.
I'm attempting to write a matrix transform to convert chart points to device pixels in SkiaSharp. I have it functional as long as I use 0,0 as my minimum chart coordinates but if I need to to step up from a negative number, it causes the drawing to shift left and down. That is to say that the X Axis is shifted to the left off the window and the Y Axis is shift down off the window.
This is intended to be a typical line chart (minimum chart point at the lower left while minimum device point at the upper left). I have accounted for that already in the transform.
While stepping through code I can see that the coordinates returned from the Matrix are not what I expect them to be, so I believe the issue to be with my transform but I haven't been able to pinpoint it.
UPDATE: After further examination, I believe I was mistaken, it is not shifted, it's just not scaling properly to the max end of the screen. There is a bigger margin at the top and right side of the chart than there should be, but the bottom and left side are fine. I've been undable to determine why the scaling doesn't fill the canvas.
Below are my matrix methods:
private SKMatrix ChartToDeviceMatrix, DeviceToChartMatrix;
private void ConfigureTransforms(SKPoint ChartMin,
SKPoint ChartMax, SKPoint DeviceMin, SKPoint DeviceMax)
{
this.ChartToDeviceMatrix = SKMatrix.MakeIdentity();
float xScale = (DeviceMax.X - DeviceMin.X) / (ChartMax.X - ChartMin.X);
float yScale = (DeviceMin.Y - DeviceMax.Y) / (ChartMax.Y - ChartMin.Y);
this.ChartToDeviceMatrix.SetScaleTranslate(xScale, yScale, DeviceMin.X, DeviceMax.Y);
this.ChartToDeviceMatrix.TryInvert(out this.DeviceToChartMatrix);
}
// Transform a point from chart to device coordinates.
private SKPoint ChartToDevice(SKPoint point)
{
return this.ChartToDeviceMatrix.MapPoint(point);
}
The code invoking this is:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
float strokeWidth = 1;
float margin = 10;
// SKPaint definitions omitted for brevity.
var ChartMin = new SKPoint(-10, -1); // Works fine if I change this to 0,0
var ChartMax = new SKPoint(110, 11);
var DeviceMin = new SKPoint(margin, margin);
var DeviceMax = new SKPoint(info.Width - margin, info.Height - margin);
const float stepX = 10;
const float stepY = 1;
const float tickX = 0.5;
const float tickY = 0.075F;
// Prepare the transformation matrices.
this.ConfigureTransforms(ChartMin, ChartMax, DeviceMin, DeviceMax);
// Draw the X axis.
var lineStart = new SKPoint(ChartMin.X, 0);
var lineEnd = new SKPoint(ChartMax.X, 0);
canvas.DrawLine(this.ChartToDevice(lineStart), this.ChartToDevice(lineEnd), axisPaint);
// X Axis Tick Marks
for (float x = stepX; x <= ChartMax.X - stepX; x += stepX)
{
var tickMin = new SKPoint(x, -tickY);
var tickMax = new SKPoint(x, tickY);
canvas.DrawLine(this.ChartToDevice(tickMin), this.ChartToDevice(tickMax), axisPaint);
}
// Draw the Y axis.
// The inversion of above, basically the same.
I was able to discover my own problem with enough time. I wasn't calculating the offset correct.
this.ChartToDeviceMatrix.SetScaleTranslate(xScale, yScale, DeviceMin.X, DeviceMax.X);
Should have been:
this.ChartToDeviceMatrix.SetScaleTranslate(xScale, yScale, -ChartMin.X * xScale + DeviceMin.Y, -ChartMin.Y * yScale + DeviceMax.Y);
Final Matrix method was:
private SKMatrix ChartToDeviceMatrix, DeviceToChartMatrix;
private void ConfigureTransforms(SKPoint ChartMin, SKPoint ChartMax, SKPoint DeviceMin, SKPoint DeviceMax)
{
this.ChartToDeviceMatrix = SKMatrix.MakeIdentity();
float xScale = (DeviceMax.X - DeviceMin.X) / (ChartMax.X - ChartMin.X);
float yScale = (DeviceMin.Y - DeviceMax.Y) / (ChartMax.Y - ChartMin.Y);
float xOffset = -ChartMin.X * xScale + DeviceMin.X;
float yOffset = -ChartMin.Y * yScale + DeviceMax.Y;
this.ChartToDeviceMatrix.SetScaleTranslate(xScale, yScale, xOffset, yOffset);
this.ChartToDeviceMatrix.TryInvert(out this.DeviceToChartMatrix);
}
I am creating One Cross Platform Application in Xamarin Forms and try to draw lines from 10 to -10 using below code. But the problem is lines are drawn from 10 to 0 only. Why this is happening I don't have any Idea.
int margin = 20;
int steps = 20;
float start = margin;
float end = width - margin;
float dHeigth = heigth - (margin * 4);
float hStep = dHeigth / Convert.ToSingle(steps);
float textMargin = 30;
// draw the line
for (int i = 10; i >= -10; i--)
{
float xpoint = i * hStep + margin;
if (i.IsOdd())
{
canvas.DrawLine(start + textMargin, xpoint, end, xpoint, LineWhitePaint);
}
else
{
decimal dText = 0;
canvas.DrawLine(start + textMargin, xpoint, end, xpoint, LineGreyPaint);
if (i < 0)
dText = i;
else
dText = (10 - i);
string txt = dText.ToString();
canvas.DrawText(txt, start + margin, xpoint + 15, TextStyleFillPaintX);
}
}
I am attaching screen shot of that
For the positive lines, you are drawing 10 - i, which yields 0 for the first iteration, 2 for the third and so on. Regarding this, you can see, that you are beginning to draw the lines from the middle of the canvas. The tenth iteration will draw the topmost line (the one with the 10). Further lines are drawn, but not on the screen.
You can see this, too, when you are writing xPoint to the debug output. As i gets negative, xPoint will, too. To fix this, you'll have to offset xPoint to always draw on screen
float xpoint = i * hStep + margin + steps / 2 * hStep;
Alternatively, you could loop from 20 to 0 and change how the text is generated.
for (int i = 20; i >= 0; i--)
{
var xPoint = i * hStep + margin;
// ...
var displayedText = GetDisplayedText(i, steps);
// ...
}
string GetDisplayedText(int i, int steps)
{
var displayedValue = i > steps / 2
? steps - i
: -i - steps / 2; // I think this should be the formula
return displayedValue.ToString();
}
Remarks: It would even better to encapsulate the concept of the lines, to separate their calculation from draawing them. You could create a factory that generates the correct line based on the index and the number of steps and then only iterate over the Line objects, and draw them by passing the canvas. This would make your code way cleaner and neater.
UPDATE
Since we have been able to clarify the requirements, I will give another shot.
First of all, I'd define methods to transform graph coordinates to canvas coordinates
private SKPoint ToCanvasCoordinates(SKPoint graphCoordinates)
{
var x = Margin + TextMargin + (_canvas.Width - 2*Margin - TextMargin)*graphCoordinates.X;
var y = (MaxY - graphCoordinates.Y)*(_canvas.Height - 2 * Margin)/(MaxY - MinY) + Margin;
return new SKPoint(x,y);
}
private SKPoint GetLegendCoordinates(int i)
{
var x = Margin;
var y = (MaxY - graphCoordinates.Y)*(_canvas.Height - 2 * Margin)/(MaxY - MinY) + Margin + 15;
return new SKPoint(x,y);
}
_canvas is a private member field in this case, Margin, MaxY and MinY are properties. I've assumed the min of x being 0 and the max bein 1.
Now you can draw your lines like
for(int i = -1; i <= 10; i++)
{
var lineStart = ToCanvasCoordinates(new SKPoint(0, i));
var lineEnd = ToCanvasCoordinates(new SKPoint(1, i));
canvas.DrawLine(lineStart, lineEnd, LineGreyPaint);
var textPosition = GetLegendCoordinates(i);
canvas.DrawText(i.ToString(), textPosition, TextStyleFillPaintX);
}
Furthermore, if you'd like to draw a line between two of the grid lines, you can use the following methods
private void DrawDataLine(SKPoint start, SKPoint end, SKPaint paint)
{
var startTransformed = ToCanvasCoordinates(start);
var endTransformed = ToCanvasCoordinates(end);
_canvas.DrawLine(startTransformed, endTransformed, paint);
}
private void DrawData(SKPaint paint)
{
for(int i=1; i<_data.Length; i++)
{
DrawDataLine(new SKPoint(data[i-1].X, data[i-1].Y), new SKPoint(data[i].X, data[i].Y)); // given that the objects in _data have the properties X and Y
}
}
What i want is to set borders between two series in StackedBar Like this image The bold black line between blue and green
I can not figure out any idea to specify the border, i tried to set the borders to the series throuh this code
chart.Series["series0"].BorderWidth = 2;
chart.Series["series0"].BorderColor = Color.Black;
chart.Series["series0"].BorderDashStyle = ChartDashStyle.Solid;
but this the result i got
Here's my code
double l = Convert.ToDouble(query1[i - 1][0]) - 10;
string n = query1[i - 1][1];
int count = 0;
for (double t = l; t < l + 10; t++)
{
//Next line Calc. the occurence of character in a text file
count = n.Split('C').Length - 1;
//Multiple the occurence by 10 so it become percent
chart.Series["series0"].Points.AddXY(t, count * 10);
chart.Series["series0"]["PointWidth"] = "1";
chart.Series["series0"].BorderWidth = 2;
chart.Series["series0"].BorderColor = Color.Black;
chart.Series["series0"].BorderDashStyle = ChartDashStyle.Solid;
count = n.Split('o').Length - 1;
chart.Series["series1"].Points.AddXY(t, count * 10);
chart.Series["series1"]["PointWidth"] = "1";
}
How to achieve the first pic effect using StackedBar ? , if i can not using StackedBar, what chart type you suggest to use ??
There are no built-in chart elements that could easily be made into a borderline between those two Series. (Creating LineAnnotations to achieve this would be a nightmare..)
So the way to add the lines is to draw them onto the surface of the Chart. This is most naturally done in the PostPaint event, provided just for such adornments.
Here the Axes have handy functions to convert between the data values and the pixel positions. We need the ValueToPixelPosition method.
I will take you through variations of Chart drawing that gradually get a little more complicated as we approach the final version..:
Let's start with a simple example: Let's build and adorn a StackedArea chart; here is the drawing code:
private void chart2_PostPaint(object sender, ChartPaintEventArgs e)
{
Series s = chart1.Series[0];
ChartArea ca = chart1.ChartAreas[0];
var pp = s.Points.Select(x=>
new PointF( (float)ca.AxisX.ValueToPixelPosition(x.XValue),
(float)ca.AxisY.ValueToPixelPosition(x.YValues[0]) ) );
if (s.Points.Count > 1)
using (Pen pen = new Pen(Color.DarkOliveGreen, 4f))
e.ChartGraphics.Graphics.DrawLines(pen, pp.ToArray());
}
The Points.Select is really just a shorthand for a loop; so after creating the pixel point list we simply draw it.
Now, as you can see, as StackedArea chart is pointy and doesn't look like a StackedBar or StackedColumn chart. So let's cheat and 'rectify' the area chart by adding a few extra points:
void rectifyArea(Series s)
{
for (int i = s.Points.Count - 1; i > 0; i--)
s.Points.InsertXY(i, i - 1, s.Points[i].YValues[0]);
}
Results:
Now that was not so hard; unfortunately you just can't turn a StackedArea to go from left to right instead of bottom-up. So we need to change the chart type to a Bar type eventually..
Here the challenge is to find the right upper and lower corners of those bars. We do have the DataPoint values, but these are in the middle of the bars. So we need to add/subtract half of the Bars' width to get the corners. For this we need the width.
While you have set it with the PointWidth property to 1, what we really need is the pixel width. We best get it by subtracting the pixel coordinates of two neighbouring points.
This makes the PostPaint event a little longer, but still not overly complicated; we will start with a StackedColumn chart, adding two corner points for each data point:
private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
{
Series s = chart1.Series[0];
ChartArea ca = chart1.ChartAreas[0];
if (s.Points.Count <= 0) return;
// calculate width of a column:
int pp1 = (int)ca.AxisX.ValueToPixelPosition(s.Points[0].XValue);
int pp2 = (int)ca.AxisX.ValueToPixelPosition(s.Points[1].XValue);
float w2 = Math.Abs(pp2 - pp1) / 2f;
List<PointF> points = new List<PointF>();
for (int i = 0; i < s.Points.Count; i++)
{
DataPoint dp = s.Points[i];
points.Add(new PointF( (int)ca.AxisX.ValueToPixelPosition(dp.XValue) - w2,
(int)ca.AxisY.ValueToPixelPosition(dp.YValues[0]) ));
points.Add(new PointF( (int)ca.AxisX.ValueToPixelPosition(dp.XValue) + w2,
(int)ca.AxisY.ValueToPixelPosition(dp.YValues[0]) ));
}
if (points.Count > 1)
using (Pen pen = new Pen(Color.DarkOliveGreen, 4f))
e.ChartGraphics.Graphics.DrawLines(pen, points.ToArray());
}
Now this looks pretty much identical to our fake version of the 'rectified area chart'. What will we need to change to apply this to a StackedBar chart? Almost nothing! The only two things we need to take care of are
the direction of the y-axis. Since the points move upward but the pixel coordinates of GDI+ graphhics move downwards we need to create the two cornerpoints in the reverse order.
And we need to reverse the x- and y coodinates, as the axes are reversed for all types of Bar charts.
Here are the two stacked charts with a border:
This is the loop for the StackBar chart:
for (int i = 0; i < s.Points.Count; i++)
{
points.Add(new PointF( (float)ca.AxisY.ValueToPixelPosition(s.Points[i].YValues[0]),
(float)ca.AxisX.ValueToPixelPosition(s.Points[i].XValue) + w2));
points.Add(new PointF( (float)ca.AxisY.ValueToPixelPosition(s.Points[i].YValues[0]),
(float)ca.AxisX.ValueToPixelPosition(s.Points[i].XValue) - w2));
}
Note that I am drawing with a fixed pen width of 4 pixels. To make it scale with the Chart you may want to calculate the pen width dynamically..
Update
To draw borders on top of several series you can put the code into a loop like this:
private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
{
Chart chart = chart1;
Series s0 = chart.Series[0];
ChartArea ca = chart.ChartAreas[0];
// calculate width of a bar:
int pp1 = (int)ca.AxisX.ValueToPixelPosition(s0.Points[0].XValue);
int pp2 = (int)ca.AxisX.ValueToPixelPosition(s0.Points[1].XValue);
float delta = Math.Abs(pp2 - pp1) / 2f;
for (int s = 0; s < chart.Series.Count; s++)
{
List<PointF> points = new List<PointF>();
for (int p = 0; p < chart.Series[s].Points.Count; p++)
{
DataPoint dp = chart.Series[s].Points[p];
double v = GetStackTopValue(chart, s, p);
points.Add(new PointF((float)ca.AxisY.ValueToPixelPosition(v),
(float)ca.AxisX.ValueToPixelPosition(dp.XValue) + delta));
points.Add(new PointF((float)ca.AxisY.ValueToPixelPosition(v),
(float)ca.AxisX.ValueToPixelPosition(dp.XValue) - delta));
}
using (Pen pen = new Pen(Color.DarkOliveGreen, 3f))
e.ChartGraphics.Graphics.DrawLines(pen, points.ToArray());
}
}
double GetStackTopValue(Chart chart, int series, int point)
{
double v = 0;
for (int i = 0; i < series + 1; i++)
v += chart.Series[i].Points[point].YValues[0];
return v;
}
I've got a simple line graph and I'd like to highlight some parts of this graph by drawing a rectangle around the line (ideally a filled rectangle with transparency...). I haven't any idea if this is possible with the MS chart control ?
I recommend you download the code samples from MS and checkout the section on annotations. In there you will find all the documentation you require to achieve what you described:
private void AddRectangleAnnotation()
{
RectangleAnnotation annotation = new RectangleAnnotation();
annotation.AnchorDataPoint = Chart1.Series[0].Points[2];
annotation.Text = "I am a\nRectangleAnnotation";
annotation.ForeColor = Color.Black;
annotation.Font = new Font("Arial", 12);;
annotation.LineWidth = 2;
annotation.BackColor = Color.PaleYellow;
annotation.LineDashStyle = ChartDashStyle.Dash;
Chart1.Annotations.Add(annotation);
}
Do you mean:
using (Graphics g = Graphics.FromImage(pictureBox1.Image))
{
using(Brush brush = new SolidBrush(your_color))
{
g.FillRectangle(brush , x, y, width, height);
}
}
or you can use
Brush brush = new SolidBrush(Color.FromArgb(alpha, red, green, blue))
where alpha goes from 0 to 255, so a value of 128 for your alpha will give you 50% opactity.
When you'd like to draw on a chart you can take add a LineAnnotation or RectangleAnnotation. if however you'd like more control you can use the chart's PrePaint and PostPaint events. And if you can paint, well then you can paint anything. Also using this will make the chart "printing" and "exporting" look the same as you painted it. Paining over it will look funny when the chart's location is changed on the screen, so always paint in it.
Say you have a trading chart and you need to draw a line as to where you become profitable or as square as to state where you're "To Something" tzhen just add the coordinates from where to where you'd like to be and of you go...
MS Chart sample project shows how to do this with the following code (vb.net also available):
using System.Windows.Forms.DataVisualization.Charting;
...
private void chart1_PostPaint(object sender, System.Windows.Forms.DataVisualization.Charting.ChartPaintEventArgs e)
{
if(sender is ChartArea)
{
ChartArea area = (ChartArea)sender;
if(area.Name == "Default")
{
// If Connection line is not checked return
if( !ConnectionLine.Checked )
return;
double max;
double min;
double xMax;
double xMin;
// Find Minimum and Maximum values
FindMaxMin( out max, out min, out xMax, out xMin );
// Get Graphics object from chart
Graphics graph = e.ChartGraphics.Graphics;
// Convert X and Y values to screen position
float pixelYMax = (float)e.ChartGraphics.GetPositionFromAxis("Default",AxisName.Y,max);
float pixelXMax = (float)e.ChartGraphics.GetPositionFromAxis("Default",AxisName.X,xMax);
float pixelYMin = (float)e.ChartGraphics.GetPositionFromAxis("Default",AxisName.Y,min);
float pixelXMin = (float)e.ChartGraphics.GetPositionFromAxis("Default",AxisName.X,xMin);
PointF point1 = PointF.Empty;
PointF point2 = PointF.Empty;
// Set Maximum and minimum points
point1.X = pixelXMax;
point1.Y = pixelYMax;
point2.X = pixelXMin;
point2.Y = pixelYMin;
// Convert relative coordinates to absolute coordinates.
point1 = e.ChartGraphics.GetAbsolutePoint(point1);
point2 = e.ChartGraphics.GetAbsolutePoint(point2);
// Draw connection line
graph.DrawLine(new Pen(Color.Yellow,3), point1,point2);
}
}
}
private void chart1_PrePaint(object sender, System.Windows.Forms.DataVisualization.Charting.ChartPaintEventArgs e)
{
if(sender is ChartArea){
ChartArea area = (ChartArea)sender;
if(area.Name == "Default")
{
double max;
double min;
double xMax;
double xMin;
// Find Minimum and Maximum values
FindMaxMin( out max, out min, out xMax, out xMin );
// Get Graphics object from chart
Graphics graph = e.ChartGraphics.Graphics;
// Convert X and Y values to screen position
float pixelYMax = (float)e.ChartGraphics.GetPositionFromAxis("Default",AxisName.Y,max);
float pixelXMax = (float)e.ChartGraphics.GetPositionFromAxis("Default",AxisName.X,xMax);
float pixelYMin = (float)e.ChartGraphics.GetPositionFromAxis("Default",AxisName.Y,min);
float pixelXMin = (float)e.ChartGraphics.GetPositionFromAxis("Default",AxisName.X,xMin);
// Specify width of triangle
float width = 3;
// Set Maximum points
PointF [] points = new PointF[3];
points[0].X = pixelXMax - width;
points[0].Y = pixelYMax - width - 2;
points[1].X = pixelXMax + width;
points[1].Y = pixelYMax - width - 2;
points[2].X = pixelXMax;
points[2].Y = pixelYMax - 1;
// Convert relative coordinates to absolute coordinates.
points[0] = e.ChartGraphics.GetAbsolutePoint(points[0]);
points[1] = e.ChartGraphics.GetAbsolutePoint(points[1]);
points[2] = e.ChartGraphics.GetAbsolutePoint(points[2]);
// Draw Maximum trangle
graph.FillPolygon(new SolidBrush(Color.Red), points);
// Set Minimum points
points = new PointF[3];
points[0].X = pixelXMin - width;
points[0].Y = pixelYMin + width + 2;
points[1].X = pixelXMin + width;
points[1].Y = pixelYMin + width + 2;
points[2].X = pixelXMin;
points[2].Y = pixelYMin + 1;
// Convert relative coordinates to absolute coordinates.
points[0] = e.ChartGraphics.GetAbsolutePoint(points[0]);
points[1] = e.ChartGraphics.GetAbsolutePoint(points[1]);
points[2] = e.ChartGraphics.GetAbsolutePoint(points[2]);
// Draw Minimum triangle
graph.FillPolygon(new SolidBrush(Color.Blue), points);
}
}
}