I was hoping to display data for 4 groups. Each group has 1 stacked column, and 1 non-stacked. Here's a mockup of what I'm after:
Getting four groups is easy, this gives me what I want:
Series series = chart.Series.Add("Budget");
series.ChartType = SeriesChartType.Column;
series.Name = "Budget";
series.Points.Add(55);
series.Points.Add(10);
series.Points.Add(50);
series.Points.Add(50);
series = chart.Series.Add("Actual");
series.ChartType = SeriesChartType.Column;
series.Name = "Actual";
series.Points.Add(80);
series.Points.Add(90);
series.Points.Add(10);
series.Points.Add(10);
Now I want to make the yellow bars stacked bars. I've experimened with adding 3 series; ChartType = Column, StackedColumn, StackedColumn. But the stacked columns appear infront of the non-stacked one.
Is what I'm attempting possible?
Under the Series Invoke the CustomPropertiesMethod and assign the groups different StackedGroupNames for example if you want Series 1 and 2 to stack but want series 3 to be beside
Series1.CustomProperties = "StackedGroupName=Group1";
Series2.CustomProperties = "StackedGroupName=Group1";
Series3.CustomProperties = "StackedGroupName=Group2";
I may be wrong but the series are shown in the same order as they're added.
The way I approached it was to phony up the X-axis data so it was offset on either side of the 'real' points, then use custom labels. I don't have easily pasted. tested code, but I think it goes something like this:
double pointWidth = 0.375;
double pointOffset = pointWidth * 0.5;
// Add first series - column
_Chart.Add("Series1");
_Chart.Series["Series1"].ChartType = SeriesChartType.Column;
_Chart.Series["Series1"]["PointWidth"] = pointWidth.ToString();
for (int ii = 0; ii < 10; ii++)
{
_Chart.Series["Series1"].Points.AddXY(ii - pointOffset, YourYValueHere);
}
// Add second series - stacked column
_Chart.Add("Series2");
_Chart.Series["Series2"].ChartType = SeriesChartType.Column;
_Chart.Series["Series2"]["PointWidth"] = pointWidth.ToString();
for (int ii = 0; ii < 10; ii++)
{
_Chart.Series["Series2"].Points.AddXY(ii + pointOffset, YourYValueHere);
}
// Add thrid series - stacked column
_Chart.Add("Series3");
_Chart.Series["Series3"].ChartType = SeriesChartType.Column;
_Chart.Series["Series3"]["PointWidth"] = pointWidth.ToString();
for (int ii = 0; ii < 10; ii++)
{
_Chart.Series["Series3"].Points.AddXY(ii + pointOffset, YourYValueHere);
_Chart.ChartAreas["Area1"].AxisX.CustomLabels.Add(
ii - 0.5, ii + 0.5, ii);
}
_Chart.ChartAreas["Area1"].AxisX.Minimum = -0.5;
_Chart.ChartAreas["Area1"].AxisX.Maximum =
_Chart.Series["Series3"].Points.Count - 0.5;
_Chart.ChartAreas["Area1"].AxisX.LabelStyle.IsEndLabelVisible = true;
_Chart.ChartAreas["Area1"].AxisX.IsMarginVisible = false;
If that StackedGroupName thing works, it would be much simpler, though. I'm going to try it.
Related
My code is C# windows form
I have a file with data:
David One And Two/Three
Alex One Two Four And Five/Six
Amanda Two Seven/Ten
Micheal Seven/Nine
and trying to have them in array like
string[] Names = File.ReadAllLines("C:\Students\Name.txt", Encoding.Default);
and have them in Group of Radio Buttons
RadioButton[] n = new RadioButton[Names.Length];
for (int i = 0; i < Names.Length; i++)
{
n[i] = new RadioButton();
n[i].Text = Names[i];
n[i].Location = new Point(10, 10 + i * 20);
groupBox1.Controls.Add(n[i]);
}
But it shows as my attached image
I tried without Encoding.Default and Encoding.UTF8 but the same problem.
What is I'am doing wrong? Please see my image and help me. Thank you in advance!
I'm guessing your file contains empty lines at the end. You can try to remove them before creating a radio button:
for (int i = 0; i < Names.Length; i++)
{
var name = Names[i];
if(name.Trim() == string.Empty) continue;
n[i] = new RadioButton();
n[i].Text = Names[i];
n[i].Location = new Point(10, 10 + i * 20);
groupBox1.Controls.Add(n[i]);
}
If your text is trimmed you can try to increase the width of the control:
n[i].Width = 300; // Put a value which will show the entire text
I have a chart with two series that share the same X axis values. I want to subtract the Y values and create a new series.
There are two ways I've tried so far:
Convert the points on the chart and put them into an array and subtract them:
double[] arrayX = new double[chart2.Series[0].Points.Count()];
double[] arrayY = new double[chart2.Series[0].Points.Count()];
double[] arrayResult = { };
for (int i = 0; i < chart2.Series[0].Points.Count(); i++)
{
arrayX[i] = chart2.Series[0].Points[i].XValue;
arrayY[i] = chart2.Series[0].Points[i].YValues[0];
}
The issue with this is that this only gets the X Values from the first series and leaves out the second series X Values.
When I add the points to the chart I used this:
chart2.Series[SplitListBox.Items[0].ToString()].Points.AddXY(e, firstval);
chart2.Series[SplitListBox.Items[1].ToString()].Points.AddXY(e, firstval);
Both of which are in separate loops. I was going to use 2 array to catch the points (e,firstval) for each loop but I don't know how to subtract the two from each other and still keep the values that exist in one series but not in the other.
After creating the new Series SDelta to your liking you can call a simple function to fill it like this:
void PlotDelta(Chart chart, Series S1, Series S2, Series SDelta)
{
for (int i = 0; i < S1.Points.Count; i++)
{
if ( i < S2.Points.Count)
{
DataPoint dp1 = S1.Points[i];
DataPoint dp2 = S2.Points[i];
if (!dp1.IsEmpty && !dp2.IsEmpty)
SDelta.Points.AddXY(dp1.XValue, dp2.YValues[0] - dp1.YValues[0]);
}
}
You may want to improve the error handling for cases of different point counts or emtpy points..
Here is a testbed:
private void button1_Click(object sender, EventArgs e)
{
chart1.Series.Clear();
chart1.Series.Add(new Series { Name = "Cos", ChartType = SeriesChartType.Line });
chart1.Series.Add(new Series { Name = "Sin", ChartType = SeriesChartType.Line });
chart1.Series.Add(new Series { Name = "Delta", ChartType = SeriesChartType.Line });
for (int i = 0; i < 100; i++ )
{
chart1.Series["Cos"].Points.AddXY(i, Math.Cos(i / Math.PI));
chart1.Series["Sin"].Points.AddXY(i, Math.Sin(i / Math.PI));
}
PlotDelta(chart1, chart1.Series["Cos"], chart1.Series["Sin"],
chart1.Series["Delta"]);
}
Of course you could integrate the creation of the delta series in the function; but you would have to pass in any properties you may want to vary like Color, ChartType etc..
I am trying to show x axis value in the form of date(MMM-yy) but it is always begin with jan-01. So please provide solution to display other then jan-01, I mean instead of Jan-01 show oct-01.
Please find the simulated function :
private static void drawGraph()
{
List<GraphPoints> listGP = new List<GraphPoints>();
listGP.Add(new GraphPoints()
{
RecordDate = "01/10/1984",
benchmark = "10000.00"
});
listGP.Add(new GraphPoints()
{
RecordDate = "29/06/1987",
benchmark = "30396.00"
});
listGP.Add(new GraphPoints()
{
RecordDate = "31/05/1989",
benchmark = "10000.00"
});
listGP.Add(new GraphPoints()
{
RecordDate = "30/09/1993",
benchmark = "310137.88"
});
listGP.Add(new GraphPoints()
{
RecordDate = "31/12/2015",
benchmark = "440037.28"
});
Graph.Chart chart;
chart = new Graph.Chart();
chart.Location = new System.Drawing.Point(10, 10);
chart.Size = new System.Drawing.Size(800, 300);
chart.ChartAreas.Add("draw");
chart.ChartAreas["draw"].AxisX.IntervalType = Graph.DateTimeIntervalType.Years;
chart.ChartAreas["draw"].AxisX.LabelStyle.Format = "MMM-yyyy";
chart.ChartAreas["draw"].AxisX.MajorGrid.LineColor = Color.Black;
chart.ChartAreas["draw"].AxisX.MajorGrid.LineDashStyle = Graph.ChartDashStyle.NotSet;
chart.ChartAreas["draw"].AxisY.IntervalAutoMode = Graph.IntervalAutoMode.VariableCount;
chart.ChartAreas["draw"].AxisY.MajorGrid.LineColor = Color.Black;
chart.ChartAreas["draw"].AxisY.MajorGrid.LineDashStyle = Graph.ChartDashStyle.NotSet;
chart.ChartAreas["draw"].BackColor = Color.White;
chart.Series.Add("Bench-Mark");
chart.Series["Bench-Mark"].XValueType = Graph.ChartValueType.Date;
chart.Series["Bench-Mark"].ChartType = Graph.SeriesChartType.Line;
chart.Series["Bench-Mark"].Color = Color.Red;
chart.Series["Bench-Mark"].BorderWidth = 1;
foreach (var item in listGP)
{
chart.Series["Bench-Mark"].Points.AddXY(Convert.ToDateTime(item.RecordDate).ToOADate(), item.benchmark);
}
chart.SaveImage("MyImage.jpg", Graph.ChartImageFormat.Jpeg);
}
See Graph axis points are showing from jan 1988.
There is a nice page on MSDN on various types of labels.
It explains three types of labelling:
Using the Label of the AxisX.LabelStyle.Format and Axis.Interval property to create a regular grid of labels not really connected to the data points
Using the DataPoint.AxisLabel property to label each DataPoint individually
Using CustomLabels to set labels at arbitrary points on an axis.
You are using the first option but this will put a Label at the beginning of one unit of the AxisX.IntervalType which in your case will be years even if you switch to Months because there simply are too few points.
This really should be simple to correct; since you do not want the IntervalType units labelled but the individual DataPoints, you should add AxisLabels to each DataPoint and all ought to be well:
Series S1 = chart.Series["Bench-Mark"];
foreach (var item in listGP)
{
DateTime dt = Convert.ToDateTime(item);
int p = S1.Points.AddXY(dt, listGP[item]);
string l = dt.ToString("MMM-yyyy");
S1.Points[p].AxisLabel = l;
}
Unfortunately with your data, the Chart control's built-in 'intelligence' makes this a bit harder; the issue is that no matter which combination of Interval and IntervalType you choose it just never will show each AxisLabel. In fact I only managed to display those that actually hit the Interval, i.e. DataPoints the fall the 1st of a month. But your data are randomly spread out over several years.
But you can use CustomLabels as a workaround. After adding the AxisLabels in the code above call this little helper function to add CustomLabels; we need to tell the Chart for which range each is to be displayed, and with a large enough range they all show up where they should (according to my data):
void addCustomLabels(Chart chart)
{
Series S1 = chart.Series[0];
ChartArea CA = chart.ChartAreas[0];
CA.AxisX.CustomLabels.Clear();
for (int i = 0; i < S1.Points.Count; i++)
{
CustomLabel cl = new CustomLabel();
cl.FromPosition = S1.Points[i].XValue - 10; // 10 day back and ahead..
cl.ToPosition = S1.Points[i].XValue + 10; //..will be enough space for one label
cl.Text = S1.Points[i].AxisLabel;
CA.AxisX.CustomLabels.Add(cl);
}
}
Note that such a solution will only work well if your data points are as sparse as in the example; for many points the labels would clutter the axis.
I'm using System.Windows.Forms.DataVisualization.Charting in my app and I want to add different label types on my X axis which is representing a timeline. For example, I want to use "HH:mm" format for labels but when it's 00:00 I'd like to show "dd.MM" format instead. I tried to add cutom labels but it has no effect at all.
var area = new ChartArea();
area.AxisX.LabelStyle.Format = "HH:mm";
area.AxisX.Interval = 1 / 24.0;
area.AxisX.CustomLabels.Add(1.0, DateTimeIntervalType.Days, "dd.MM");
Adding CustomLabels will help; however if you want them to show an individual format you will have to add them individually to each DataPoint!
Doing so is not quite as simple as one could wish for; there are several overloads but none is really easy to use. The simplest way, imo, is to use one with a FromPosition and a ToPosition; these should then to be set in a way that they hit right between the DataPoints; this way they will be centered nicely..
Note that as the X-Values are really DateTimes, but as always in a chart, converted to doubles we need to convert them back to DateTime for correct formatting and also use their values to calculate the middle or rather half an interval..
Here is an example:
// prepare the test chart..
chart1.ChartAreas.Clear();
ChartArea CA = chart1.ChartAreas.Add("CA1");
Random R = new Random(123);
chart1.Series.Clear();
CA.AxisX.MajorTickMark.Interval = 1;
Series S = chart1.Series.Add("S1");
S.Points.Clear();
S.ChartType = SeriesChartType.Column;
S.SetCustomProperty("PixelPointWidth", "10");
// some random data:
DateTime dt0 = new DateTime(2015, 05, 01);
for (int i = 0; i< 40; i++)
{
int p = S.Points.AddXY(dt0.AddHours(i), R.Next(100));
}
// each custom label will be placed centered in a range
// so we need an amount of half an interval..
// this assumes equal spacing..
double ih = (S.Points[0].XValue - S.Points[1].XValue) / 2d;
// now we add a custom label to each data point
for (int i = 0; i < S.Points.Count; i++)
{
DataPoint pt = S.Points[i];
string s = (DateTime.FromOADate(pt.XValue)).ToString("HH:mm");
bool midnite = s == "00:00";
if (midnite) s = DateTime.FromOADate(pt.XValue).ToString("dd.MM.yyyy");
CustomLabel CL = CA.AxisX.CustomLabels.Add(pt.XValue - ih, pt.XValue + ih, s);
CL.ForeColor = midnite ? Color.Blue : Color.Black;
}
I have an asp:Chart control and it is working great. I simply am passing it times (in millitary format) and then values that are average length in time of requests. The following code does what I need - almost (feel free to mark it up if I am going overboard for I am new to the chart control).
My data is in table for like the following:
Date by Hours 9:00 10:00 11:00 12:00 13:00 14:00 15:00 16:00 17:00 18:00
12/03/2010 8 43 53 55 33 46 51 60 50 9
Friday 1.773 1.337 1.242 1.239 1.340 1.191 1.479 1.223 1.178 1.516
Gives me a nice chart. My question is below this code:
List<double> yValues = new List<double>();
List<string> xValues = new List<string>();
// First and Last columns do not contain chartable data
for (int i = 1; i < dtResults.Columns.Count - 1; i++) {
double d;
if (double.TryParse(dtResults.Rows[1][i].ToString(), out d))
yValues.Add(d == 0 ? double.NaN : d);
else
yValues.Add(double.NaN);
} // foreach of the Average Time Values the chart
// foreach of the column names
for (int i = 1; i < dtResults.Columns.Count - 1; i++)
xValues.Add(dtResults.Columns[i].ColumnName);
this.Chart.Titles["Title1"].Text = string.Format(
"Average Request Time In Seconds On {0:MM/dd/yyyy} Between {1:HH} and {2:HH} In {3}",
this.DateRange.BeginDate.Value,
this.ucsTimePicker.BeginTime,
this.ucsTimePicker.EndTime,
this.SelectedSourceEnvironmentName
);
this.Chart.Series["Series1"].Points.DataBindXY(xValues, yValues);
this.Chart.Series["Series1"].ChartType = SeriesChartType.Line;
this.Chart.Series["Series1"].IsValueShownAsLabel = true;
this.Chart.Series["Series1"]["ShowMarkerLines"] = "true";
this.Chart.Series["Series1"].Label = "#VALY{0.000}"; // Make sure they have only 3 decimal places
this.Chart.ChartAreas["ChartArea1"].AxisX.IsMarginVisible = true;
this.Chart.ChartAreas["ChartArea1"].AxisX.Title = "Hours of the Day";
this.Chart.ChartAreas["ChartArea1"].AxisY.Title = "Time in Seconds";
// Handle styling when there is a Zero or missing value
this.Chart.Series["Series1"].EmptyPointStyle.Color = Color.Red;
this.Chart.Series["Series1"].EmptyPointStyle.BorderWidth = 3;
this.Chart.Series["Series1"].EmptyPointStyle.BorderDashStyle = ChartDashStyle.Dash;
this.Chart.Series["Series1"].EmptyPointStyle.MarkerStyle = MarkerStyle.Diamond;
this.Chart.Series["Series1"].EmptyPointStyle.MarkerColor = Color.Red;
this.Chart.Series["Series1"].EmptyPointStyle.MarkerSize = 8;
this.Chart.Series["Series1"].EmptyPointStyle.MarkerBorderColor = Color.Black;
this.Chart.Series["Series1"]["EmptyPointValue"] = "Zero";
There are labels showing (the decimal numbers in the table above) but what I want to do is have the label Also show the total number of requests which is the 2nd row of data in the table above. I was able to add the values to the chart with the code below:
for (int i = 1; i < dtResults.Columns.Count - 1; i++) {
int n;
if (int.TryParse(dtResults.Rows[0][i].ToString(), out n))
this.Chart.Series["Series1"].Points.AddY(n);
else
this.Chart.Series["Series1"].Points.AddY(0);
} // foreach of the Count of Request within the Hour values
That seemed to not throw any fits, but I couldn't access the values with the following adjustment:
this.Chart.Series["Series1"].Label = "#VALY{0.000}\n#VALY2{0}";
All I get is the original value (1.773) showing up twice.
So is there a way to add data to a Chart that is only for labeling purposes and then access it?
Okay, after no help here (which actually shocks me) I was able to figure it out with some help outside of this site. Essentially, I don't have to Add the "extra" data but I do have to modify each Label as opposed to just having the generic label like the following:
this.Chart.Series["Series1"].Label = "#VALY{0.000}"; // Make sure they have only 3 decimal places
I also had to take out the following line:
this.Chart.Series["Series1"].IsValueShownAsLabel = true;
So just for brevity, here is the entire code giving me the two line label where the first line shows the average (which is the actual chart data) and the second line is the count which is not in the data at all.
List<double> yValues = new List<double>();
List<string> xValues = new List<string>();
List<int> zValues = new List<int>();
// First and Last columns do not contain chartable data
for (int i = 1; i < dtResults.Columns.Count - 1; i++) {
double d;
if (double.TryParse(dtResults.Rows[1][i].ToString(), out d))
yValues.Add(d == 0 ? double.NaN : d);
else
yValues.Add(double.NaN);
} // foreach of the Average Time Values the chart
// foreach of the column names
for (int i = 1; i < dtResults.Columns.Count - 1; i++)
xValues.Add(dtResults.Columns[i].ColumnName);
this.Chart.Titles["Title1"].Text = string.Format(
"Average Request Time In Seconds On {0:MM/dd/yyyy} Between {1:HH} and {2:HH} In {3}",
this.DateRange.BeginDate.Value,
this.ucsTimePicker.BeginTime,
this.ucsTimePicker.EndTime,
this.SelectedSourceEnvironmentName
);
this.Chart.Series["Series1"].Points.DataBindXY(xValues, yValues);
/// This loop will setup the point labels in a two line format where the first line displays
/// the Average that the point is actually showing. The second line is data taken from the
/// results table and is not a part of the chart at all but is useful information and is the
/// Count of records in that time frame.
/// In order for this to work, the Series property IsValueShownAsLabel needs to be NOT True.
for (int i = 0; i < this.Chart.Series["Series1"].Points.Count; i++) {
int n = 0;
int.TryParse(dtResults.Rows[0][i + 1].ToString(), out n);
this.Chart.Series["Series1"].Points[i].Label = string.Format("Avg: #VALY{{0.000}}\nCount: {0}", n);
} // foreach of the Count of Request within the Hour values
this.Chart.Series["Series1"].ChartType = SeriesChartType.Line;
this.Chart.Series["Series1"]["ShowMarkerLines"] = "true";
this.Chart.ChartAreas["ChartArea1"].AxisX.IsMarginVisible = true;
this.Chart.ChartAreas["ChartArea1"].AxisX.Title = "Hours of the Day";
this.Chart.ChartAreas["ChartArea1"].AxisY.Title = "Time in Seconds";
// Handle styling when there is a Zero or missing value
this.Chart.Series["Series1"].EmptyPointStyle.Color = Color.Red;
this.Chart.Series["Series1"].EmptyPointStyle.BorderWidth = 3;
this.Chart.Series["Series1"].EmptyPointStyle.BorderDashStyle = ChartDashStyle.Dash;
this.Chart.Series["Series1"].EmptyPointStyle.MarkerStyle = MarkerStyle.Diamond;
this.Chart.Series["Series1"].EmptyPointStyle.MarkerColor = Color.Red;
this.Chart.Series["Series1"].EmptyPointStyle.MarkerSize = 8;
this.Chart.Series["Series1"].EmptyPointStyle.MarkerBorderColor = Color.Black;
this.Chart.Series["Series1"]["EmptyPointValue"] = "Zero";