I created a Line Chart control in Windows Forms.
I divided the ChartArea, AxisX into four intervals but I want to apply back color (unique color) to each interval.
Can someone help me on this?
You could paint those areas, but this would always show above all chart elements including grid and data points.
So, as NLindborn suggests, the best way are StripLines.
They are under all elements and will scale nicely.
Note that their properties are in data values, so you need to know the values, or rather the x-axis range, in your chart.
Here is complete code example using StripLines:
// set up the chart:
ChartArea ca = chart.ChartAreas[0];
chart.Series.Clear();
for (int i = 0; i < 5; i++)
{
Series s = chart.Series.Add("Series" + (i+1));
s.ChartType = SeriesChartType.Line;
s.BorderWidth = 2;
}
// add a few test data
for (int i = 0; i <= 360; i++)
{
chart.Series[0].Points.AddXY(i, Math.Sin(i * Math.PI / 180f));
chart.Series[1].Points.AddXY(i, Math.Cos(i * Math.PI / 180f));
chart.Series[2].Points.AddXY(i, Math.Sin(i * Math.PI / 90f));
chart.Series[3].Points.AddXY(i, Math.Cos(i * Math.PI / 90f));
chart.Series[4].Points.AddXY(i, Math.Sin(i * Math.PI / 30f));
}
// set up the chart area:
Axis ax = ca.AxisX;
ax.Minimum = 0;
ax.Maximum = 360;
ax.Interval = 30;
// a few semi-transparent colors
List<Color> colors = new List<Color>() { Color.FromArgb(64, Color.LightBlue),
Color.FromArgb(64, Color.LightSalmon), Color.FromArgb(64, Color.LightSeaGreen),
Color.FromArgb(64, Color.LightGoldenrodYellow)};
Now we are ready to create the StripLines:
// this is the width of the chart in values:
double hrange = ax.Maximum - ax.Minimum;
// now we create and add four striplines:
for (int i = 0; i < 4; i++)
{
StripLine sl = new StripLine();
sl.Interval = hrange; // no less than the range, so it won't repeat
sl.StripWidth = hrange / 4f; // width
sl.IntervalOffset = sl.StripWidth * i; // x-position
sl.BackColor = colors[i];
ax.StripLines.Add(sl);
}
Note that you will need to adapt the stripline data whenever you change the axis range!
Also note the StripLine use axis values.
Update:
One common issue is to move the striplines when zooming. Without a little help they will stick to the original positions. Codeing the AxisViewChanged will help, maybe like so:
For each of your striplines calculate an IntervalOffset; in the simplest case of the 1st one this should work:
chart1.ChartAreas[0].AxisX.StripLines[0].IntervalOffset =
chart1.Series[0].Points[0].XValue - e.NewPosition;
For the others add the correct multiple of the width as above!
AxisX into four intervals but I want to apply back color (unique color)
These intervals are created with colored StripLine(s). Either via code:
var stripLine = new System.Windows.Forms.DataVisualization.Charting.StripLine()
{
BackColor = Color.Blue,
IntervalOffset = 4, // This is where the stripline starts
StripWidth = 2 // And this is how long the interval is
};
chart1.ChartAreas[0].AxisX.StripLines.Add(stripLine);
You need to add data points for the interval to show.
Or, StripLines can also be added from VS design mode from (Properties) -> ChartAreas -> Select a ChartArea -> Axes -> Select the Axis you want it to show on -> StripLines, then Add StripLine. You have to set a BackColor, IntervalOffset and StripWidth for it to show. If you set StripLine.Interval it will repeat by that interval.
Related
I need your advice. I want to add many ChartAreas to my Chart and align them vertically in one column which would be 100% width of its parent and which would become scrollable when more chat areas are added.
For now, I use this code to add more chart areas:
private void AddChartArea(int index)
{
string chartAreaName = index.ToString();
chart.ChartAreas.Add(chartAreaName);
chart.Series.Add(new Series());
chart.Series[index].ChartType = SeriesChartType.Line;
chart.Series[index].MarkerStyle = MarkerStyle.Diamond;
chart.Series[index].ChartArea = chartAreaName;
/* Trying to align chart areas vertically */
chart.ChartAreas[index].AlignWithChartArea = chart.ChartAreas[index - 1].Name;
chart.ChartAreas[index].AlignmentStyle = AreaAlignmentStyles.AxesView;
chart.ChartAreas[index].AlignmentOrientation = AreaAlignmentOrientations.Vertical;
}
But my chart areas are still going this way when chart areas number is > 3:
While I want it to be like this but with a vertical scrollbar on the right:
So, as TaW has pointed out, my question is similar to this one. But I've made some improvements to automatically adjust overall Chart height.
So my Chart is placed into Panel, which has AutoScroll true.
Then, every time I create a new ChartArea a call this method:
private void DrawNewChartArea(int index)
{
int chartAreaMinHeight = 200;
chart.Dock = DockStyle.Top;
float width = 99;
float height = 100 / chart.ChartAreas.Count;
float x = 0;
float y = height * index;
if (chartAreaMinHeight * (index + 1) > chart.Height)
{
chart.Height = chartAreaMinHeight * (index + 1);
// realign height of all the chart areas if we are changing chart height
for (int i = 0; i < chart.ChartAreas.Count; i++)
{
float caY = height * i;
chart.ChartAreas[i].Position.Height = height;
chart.ChartAreas[i].Position.Y = caY;
}
}
chart.ChartAreas[index].Position = new ElementPosition(x, y, width, height);
}
The resulting graphs must be represented in such a way that 1mm (1 unit) horizontal is exactly 1mm (1 unit) vertical (or any other measurement, basically a square in equal units MUST be square, not rectangular in any way).
I am using System.Windows.Forms.DataVisualization.Charting library and working on a Windows Forms application.
chart1.Width= 500;
chart1.Height = 500;
chart1.Legends.Clear();
var area = chart1.ChartAreas[0];
area.AxisX.Minimum = 0;
area.AxisX.Maximum = 10;
area.AxisX.Interval = 1;
area.AxisY.Minimum = 0;
area.AxisY.Maximum = 15;
area.AxisY.Interval = 1;
var lineSeries = chart1.Series[0];
lineSeries.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Line;
//lineSeries.MarkerSize = 3;
lineSeries.BorderWidth = 3;
lineSeries.Points.AddXY(0, 0);
lineSeries.Points.AddXY(2, 2);
lineSeries.Points.AddXY(4, 6);
lineSeries.Points.AddXY(6, 10);
lineSeries.Points.AddXY(10, 10);
And here the output shows height and width ratio of the graph is not proper (width should be 2/3 of the height).
And this output shows height and width of graph is identical and square. If i make both axes equal. e.g.
area.AxisX.Minimum = 0;
area.AxisX.Maximum = 15;
area.AxisX.Interval = 1;
area.AxisY.Minimum = 0;
area.AxisY.Maximum = 15;
area.AxisY.Interval = 1;
I finally got the answer by #Taw's mentioned post and comments.
Given that your monitor is able to show square pixels you would have
to make sure the the InnerPlotPosition of the ChartArea is a square.
Constrain aspect ratio in WindowsForms DataVisualization Chart
I have a chart with a point (those quadcopter images) and I want to draw a circle on that point with x radius. I cant figure out how to do it.
I want this
Code:
for (int i = 0; i < drone.Length; i++)
{
var pos_atual = drone[i].posicao_atual;
var pos_desej = drone[i].posicao_desejada;
chart.Series[i].Points.Clear();
chart.Series[i].Points.AddXY(drone[i].pos_atual().X, drone[i].pos_atual().Y);
}
}
You need to code one of the xxxPaint events, maybe like this:
private void chart_PostPaint(object sender, ChartPaintEventArgs e)
{
Series s = chart.Series[yourSeriesIndex];
int yourPointIndex = 4;
if (s.Points.Count < yourPointIndex) return;
DataPoint dp = s.Points[yourPointIndex];
ChartArea ca = chart.ChartAreas[0];
int x = (int) ca.AxisX.ValueToPixelPosition(dp.XValue);
int y = (int) ca.AxisY.ValueToPixelPosition(dp.YValues[0]);
// a circle with 20 pixels diameter
e.ChartGraphics.Graphics.DrawEllipse(Pens.Red, x-10, y-10, 20, 20);
}
As an alternative you can add a Marker with MarkerStyle Circle, Color Transparent and MarkerBorderColor Red to the DataPoint..:
Chart chart = TestChart;
Series s = chart.Series[0];
DataPoint dp = s.Points[5];
dp.MarkerStyle = MarkerStyle.Circle;
dp.MarkerSize = 20; // diameter in pixels
dp.MarkerColor = Color.Transparent;
dp.MarkerBorderColor = Color.Orange;
dp.MarkerBorderWidth = 2;
Update
From you comment I understand that you want is to set the size of the circle not in pixels but in data point values.
This is also possible and not really hard; however it does take some understanding of the Chart rules.
Here we go:
First we define the width and height we want. Note how my numbers are quite different from yours and how they are also not the same for width and height!!
double vx = 1d;
double vy = 20d;
Next we calculate the sizes; they are the difference (!) between zero and our values. This seems complicated, but look at my chart: The y-axis starts in the negative. So simply getting the pixels from one value will be quite offset..
int wx = (int) ( ca.AxisX.ValueToPixelPosition(vx) -
ca.AxisX.ValueToPixelPosition(0)) ;
int wy = (int) ( ca.AxisY.ValueToPixelPosition(vy) -
ca.AxisY.ValueToPixelPosition(0)) ;
With these numbers we can draw the circle:
e.ChartGraphics.Graphics.DrawEllipse(Pens.Red, x - wx / 2, y - wy / 2, wx, wy);
Note how we start the bounding rectangle of the circle offset left and top by half the size!
Now the size will follow the chart size:
I have following issue:
I need to show multiple columns in a chart control (about seven series in one chart area). Now when I have a chart of type "Column" all seven columns get shown side by side. What I want to do is to overlap them. Is this possible?
The following two solutions didn't help me:
Plotting overlapping column or bar chart
Chart control two data set bars overlapping
Thank you.
There is no built-in way to do that.
One workaround is to turn on 3-d, but that will completely change the look of the chart..
The other is to owner-draw the chart.
This is not exactly easy for column and bar types, since the sizeof the columns is not exposed.
Also note that overlapping columns do get somewhat harder to read, esp. when you also have Labels.
Here is an example of a owner-drawing column chart. It has several simplifications:
All Series have the same number of points and are aligned, all y-values are positive and there are no other adornments. They may all be overcome, but probably with some extra efforts..
private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
{
if (!checkBox2.Checked) return;
int sMax = chart1.Series.Count;
ChartArea ca = chart1.ChartAreas[0];
Axis ax = ca.AxisX;
Axis ay = ca.AxisY;
float py0 = (float)ay.ValueToPixelPosition(ay.Minimum);
Rectangle ipr = Rectangle.Round(InnerPlotPositionClientRectangle(chart1, ca));
int pMax = chart1.Series[0].Points.Count;
float shift = (overlap * sMax) / 2f;
float deltaX = 1f * ipr.Width / (pMax+1);
float colWidth = 1f * deltaX / sMax;
for (int j = 0; j < chart1.Series.Count; j++)
for (int i = 0; i < chart1.Series[j].Points.Count; i++)
{
DataPoint dp = chart1.Series[j].Points[i];
float px = (float)ax.ValueToPixelPosition(dp.XValue);
float py = (float)ay.ValueToPixelPosition(dp.YValues[0]);
using (SolidBrush brush = new SolidBrush(chart1.Series[j].Color))
e.ChartGraphics.Graphics.FillRectangle(brush,
px + j * colWidth - deltaX / 2 - overlap * j + shift, py,
colWidth, py0 - py );
}
}
It makes use of a function InnerPlotPositionClientRectangle which you can find here
Here is the result:
Note that to access the Series Colors you need to apply them to the Chart:
chart1.ApplyPaletteColors();
The Column width is set like this:
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
for (int j = 0; j < chart1.Series.Count; j++)
chart1.Series[j]["PointWidth"] = numericUpDown1.Value.ToString();
}
At "0" the columns disappear.
Column Series has a CustomProperties named DrawSideBySide, set it to False will result columns drawing overlap.
series1.CustomProperties = "DrawSideBySide=False";
It can also be set in IDE, by going to Properties window, Series Collection Editor, then find CustomProperties, DrawSideBySide.
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;
}