I am building a "Stacked Column" chart via MS-Chart using C# in MVC. My chart has 3 series. I am trying to get the data value labels of each series to display underneath the X-axis instead of on each column.
I have been searching the net in hoping to find some pointer to this similar lay-out but have found none for 2 days.
Can someone please give me some pointer on how to accomplish this same position of data value labels arrangement?
Here is the simplest solution. It isn't nicely styled but takes only a few lines:
var months = new[] { "Jan", "Feb", "Mar", "Apr", "May",
"Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
for (int i = 0; i < pointCount; i++)
{
double sum = 0;
string label = "";
for (int j = seriesCount - 1; j >= 0; j--)
{
sum += chart.Series[j].Points[i].YValues[0];
label += "\n" + +chart.Series[j].Points[i].YValues[0] ;
}
chart.Series[0].Points[i].AxisLabel = months[i] + "\n" + sum + label;
}
This adds a label string to each DataPoint of the 1st Series. Note that only one such labels can be shown per point; labels from later Series will be ignored.
Use suitable counters and whatever you want for the month part of the label.
For nicer looks, like bold sums or colored backgrounds you will need to do a lot more work.
Note that the numbers in the labels and the series are reversely stacked so the inner loop goes backward.
This will work for any number of series.
Here is a variation using the PostPaint event:
private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
{
if (chart1.Series[0].Points.Count <= 0) return;
Graphics g = e.ChartGraphics.Graphics;
ChartArea ca = chart1.ChartAreas[0];
Rectangle rip = Rectangle.Round(InnerPlotPositionClientRectangle(chart1, ca));
StringFormat fmt = new StringFormat()
{ Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
int cols = chart1.Series[0].Points.Count;
int sCount = chart1.Series.Count;
int w = rip.Width / (cols + 1); // there is ca. 1/2 column gap to the sides
for (int i = 0; i < cols; i++)
{
List<string> s = (List<string>)(chart1.Series[0].Points[i].Tag);
for (int j = 0; j < s.Count; j++)
{
// change magic numbers with your font!
Rectangle r = new Rectangle(rip.Left + i * w + w / 2,
rip.Bottom + 5 + 25 * j, w, 25);
// 1st row: header, 2nd row sum, rest moved up by and reversed
using (SolidBrush brush = new SolidBrush(j == 0 ? Color.Transparent
: j == 1 ? Color.Gray : chart1.Series[sCount + 1 - j].Color))
g.FillRectangle(brush, r);
g.DrawRectangle(Pens.White, r);
g.DrawString(s[j], ca.AxisX.LabelStyle.Font, Brushes.White, r, fmt);
}
}
}
It uses the same routine to collect the label strings, but instead of setting the AxisLabels it adds them to the Tags of the DataPoints:
string l = months[i] + "\n" + sum + label;
chart.Series[0].Points[i].Tag = l.Split('\n').ToList();
The Chart styling almost takes the largest part of the code:
chart.BackColor = Color.DarkSlateGray;
ChartArea ca = chart.ChartAreas[0];
chart.Legends[0].Alignment = StringAlignment.Center;
chart.Legends[0].Docking = Docking.Top;
chart.Legends[0].BackColor = chart.BackColor;
chart.Legends[0].ForeColor = Color.White;
Legend L = chart.Legends[0];
L.CustomItems.Add(Color.Silver, "Sum");
ca.BackColor = Color.LightSteelBlue;
ca.Position = new ElementPosition(2, 8, 93, 70); // make room
ca.Area3DStyle.Enable3D = true;
ca.Area3DStyle.PointDepth = 25;
ca.Area3DStyle.WallWidth = 0;
ca.AxisX.MajorGrid.Enabled = false;
ca.AxisY.MajorGrid.LineColor = Color.White;
ca.AxisY.LineColor = Color.White;
ca.AxisY.LabelStyle.ForeColor = Color.White;
ca.AxisY.MajorTickMark.LineColor = Color.White;
ca.AxisX.LabelStyle.Enabled = false;
ca.AxisX.LineColor = Color.White;
ca.AxisX.MajorTickMark.Enabled = false;
After creating the Series with theit Colors you need to apply them, so they can be accessed in code:
chart1.ApplyPaletteColors();
The nice rounded columns of a Series s are created by a CustomProperty
s.SetCustomProperty("DrawingStyle", "Cylinder");
Some more details:
chart.Series[1].Color = Color.Crimson;
chart.Series[0].LegendText = "Hobbits";
..
Update: You need to include two functions InnerPlotPositionClientRectangle and ChartAreaClientRectangle from some of my other posts, like here or here!
To make this work in ASP you hook up the event in the PageLoad:
chart1.PostPaint += new EventHandler<ChartPaintEventArgs>(chart1_PostPaint);
Related
I've tried without luck do make such graph in WinForms with Chart control.Graph with uneven intervals on y axis My first idea was to use pointed Chart with images for empty and not empty marker. The lines are displayed, but unfortunatelly there is a big space between values 4 - 19 and 20 - 33. Is there a way to avoid the space when using standard WinForms chart Control?
My second idea was to use a DatagridView. I could add not more that something aboit 700 columns. I would need almost 2000 columns. Thus the DataGridView isn't best choice for me.
Thank for any hint how to achieve it.
Edit 1:
As sugested by TnTinMn I've tried ScaleBreakStyle. This seems to remove some spaces from the graph. At the moment my graph looks like this:
This was achieved by the Code:
var chartArea = chart1.ChartAreas[series.ChartArea];
chartArea.AxisX.LabelStyle.IsStaggered = false;
chartArea.AxisX.Minimum = 0;
chartArea.AxisX.Maximum = (int)numericUpDown1.Value;
chartArea.AxisX.IsReversed = true;
chartArea.AxisX.Interval = 1;
chartArea.AxisX.ScaleView.SizeType = DateTimeIntervalType.Number;
chartArea.AxisY.ScaleBreakStyle.Enabled = true;
chartArea.AxisY.ScaleBreakStyle.BreakLineStyle = BreakLineStyle.None;
chartArea.AxisY.ScaleBreakStyle.Spacing = 0;
chartArea.AxisY.ScaleBreakStyle.CollapsibleSpaceThreshold = 50;
chartArea.AxisY.Interval = 1;
If I try to put the y labels on the left side, it doesn't work
chartArea.AxisY.Enabled = AxisEnabled.False;
chartArea.AxisY2.Enabled = AxisEnabled.True;
Adding folowing code doesn't solve the problem:
chartArea.AxisY2.ScaleBreakStyle.Enabled = true;
chartArea.AxisY2.ScaleBreakStyle.BreakLineStyle = BreakLineStyle.None;
chartArea.AxisY2.ScaleBreakStyle.Spacing = 0;
chartArea.AxisY2.ScaleBreakStyle.CollapsibleSpaceThreshold = 50;
chartArea.AxisY2.Interval = 1;
How can I remove 26 and 28, and leave only 1 and 27 on the chart? How can I put 1 and 27 on the bottom and leave a "big" space above them?
Edit 2:
After some tryies I've achieved what I wanted(I still have to adjust the marker images, but it looks very promising).
This is what I have at the moment:
As TnTiMn said, it is a little bit tricky. I had to map 1, 2, 3... to my real values and then to assign custom labels. I didn't used ScaleBreakStyle. Thanks anyway for your help.
My code:
chart1.ChartAreas[0].AxisX.IsReversed = true;
chart1.ChartAreas[0].AxisX.MajorGrid.Enabled = false;
chart1.ChartAreas[0].AxisY.MajorGrid.Enabled = false;
chart1.ChartAreas[0].AxisY2.MajorGrid.Enabled = false;
chart1.ChartAreas[0].AxisY.Enabled = AxisEnabled.False;
chart1.ChartAreas[0].AxisY2.Enabled = AxisEnabled.True;
chart1.ChartAreas[0].AxisY2.ScaleView.SizeType = DateTimeIntervalType.Number;
chart1.ChartAreas[0].AxisY2.MajorTickMark.Enabled = false;
chart1.Series[0].MarkerImage ="NotEmptyPoint.png";
chart1.Series[0].EmptyPointStyle.MarkerImage = "EmptyPoint.png";
chart1.Series[1].MarkerImage = "NotEmptyPoint.png";
chart1.Series[1].EmptyPointStyle.MarkerImage = "EmptyPoint.png";
chart1.Series[2].MarkerImage = "NotEmptyPoint.png";
chart1.Series[2].EmptyPointStyle.MarkerImage = "EmptyPoint.png";
chart1.ChartAreas[0].AxisY.Interval = 1;
chart1.ChartAreas[0].AxisY2.Interval = 1;
chart1.ChartAreas[0].AxisY2.Minimum = 0;
chart1.ChartAreas[0].AxisY2.Maximum = 10;
chart1.ChartAreas[0].AxisY.Minimum = 0;
chart1.ChartAreas[0].AxisY.Maximum = 10;
int number = 0;
string mappedNumber;
for (int i = 1; i < 15; i++)
{
number = 1;
mappedNumber = "1";
chart1.Series["Series1"].Points.AddXY(i, number);
chart1.ChartAreas[0].AxisY2.CustomLabels.Add(number - 0.5, number + 0.5, mappedNumber);
if(i % 3 == 0)
{
chart1.Series["Series1"].Points[chart1.Series["Series1"].Points.Count - 1].IsEmpty = true;
}
}
for (int i = 1; i < 15; i++)
{
number = 2;
mappedNumber = "7";
chart1.Series["Series2"].Points.AddXY(i, number);
chart1.ChartAreas[0].AxisY2.CustomLabels.Add(number - 0.5, number + 0.5, mappedNumber);
if (i % 4 == 0)
{
chart1.Series["Series2"].Points[chart1.Series["Series2"].Points.Count - 1].IsEmpty = true;
}
}
for (int i = 1; i < 15; i++)
{
number = 3;
mappedNumber = "20";
chart1.Series["Series3"].Points.AddXY(i, number);
chart1.ChartAreas[0].AxisY2.CustomLabels.Add(number - 0.5, number + 0.5, mappedNumber);
if (i % 5 == 0)
{
chart1.Series["Series3"].Points[chart1.Series["Series3"].Points.Count - 1].IsEmpty = true;
}
}
Here's the code I use to draw the discrete graph:
private void DrawHartleyGraph(double [] values, ZedGraphControl zed)
{
GraphPane pane = zed.GraphPane;
pane.CurveList.Clear();
pane.XAxis.Title.Text = "ν";
pane.YAxis.Title.Text = "H(ν)";
pane.Title.Text = "ДПХ";
PointPairList list = new PointPairList();
for (int i = 0; i < values.Length ; i++)
{
list.Add((double)i, values[i]);
}
LineItem myCurve = pane.AddCurve("H(ν) - ДПХ", list, Color.Blue, SymbolType.Circle);
myCurve.Line.IsVisible = false;
myCurve.Symbol.Fill.Color = Color.Blue;
myCurve.Symbol.Fill.Type = FillType.Solid;
myCurve.Symbol.Size = 4;
pane.XAxis.Scale.Min = 0;
pane.XAxis.Scale.Max = numOfCountsN;
pane.YAxis.Scale.Min = values.Min();
pane.YAxis.Scale.Max = values.Max();
pane.IsBoundedRanges = true;
zed.AxisChange();
zed.Invalidate();
}
I've got the picture like this:
How can I draw vertical lines beginning on the X-Axis and ending in the point, representing the value of function?
Solved
Here is the found decision. You need to modify the for-cycle this way:
for (int i = 0; i < values.Length ; i++)
{
list.Add((double)i, values[i]);
LineObj vertLine = new LineObj(Color.Red, i, 0,
i, values[i]);
pane.GraphObjList.Add(vertLine);
}
Here is the code I'm using
It's supposed to create labels for each module name entered by a user and all the assessment names for that module under it. Should work with any number of modules and assessments. The problem is that it only shows assessments for the last module displayed.
dat.Modules and dat.Assessments are arraylists, each of them holds 4 elements with info about a module or an assessments, that is why i divide the count by 4.
private void testingButton_Click(object sender, EventArgs e)
{
int pos1 = 50;
int pos2 = 150;
int modLength = dat.Modules.Count;
modLength = modLength / 4;
int assessLength = 0;
int arrayData = 0;
int displayCount = 0;
for (int i = 0; i < modLength; i++)
{
this.moduleLabels.Add(new Label());
System.Drawing.Point pLabel1 = new System.Drawing.Point(50, pos1);
(moduleLabels[i] as Label).Location = pLabel1;
(moduleLabels[i] as Label).Size = new System.Drawing.Size(100, 13);
(moduleLabels[i] as Label).Text = dat.Modules[arrayData].ToString();
tabPage5.Controls.Add((moduleLabels[i] as Label));
String asd = dat.Modules[arrayData + 2].ToString();
Console.WriteLine(asd);
assessLength = int.Parse(asd);
pos2 = pos1 + 25;
for (int y = 0; y < assessLength; y++)
{
this.assessLabels.Add(new Label());
System.Drawing.Point pLabel2 = new System.Drawing.Point(70, pos2);
(assessLabels[y] as Label).Location = pLabel2;
(assessLabels[y] as Label).Size = new System.Drawing.Size(250, 13);
(assessLabels[y] as Label).Text = dat.Assessments[displayCount + 1].ToString() + " weights " + dat.Assessments[displayCount+2].ToString() +"%, Enter your mark:";
textboxComputer.Add(new TextBox());
System.Drawing.Point pText1 = new System.Drawing.Point(400, pos2);
(textboxComputer[y] as TextBox).Location = pText1;
(textboxComputer[y] as TextBox).Size = new System.Drawing.Size(20, 20);
tabPage5.Controls.Add(assessLabels[y] as Label);
tabPage5.Controls.Add(textboxComputer[y] as TextBox);
pos2 = pos2 + 25;
displayCount = displayCount + 4;
}
pos1 = pos2+25;
arrayData = arrayData + 4;
}
}
this is an example of what it displays
http://dc540.4shared.com/download/YI8IENYI/tsid20120501-211723-cbc785f9/asd.jpg
The first two modules should have their assessments listed. The first one doesn't display any. For Java it only displays the last one, out of 3 total for that module. And for the last Module "Another Module" it displays all assessments.
For each increment of i, y starts at 0. You then add new labels to assessLabels, but attempt to access the one you added by using assessLabels[y] which would usually yield the labels created for the previous value of i. This causes labels created for the first module to be reused by the next, and so forth.
A quick solution is not to use assessLabels[y] but assessLabels[assessLabels.Count - 1].
A better solution is to create a local variable for the labels, set their properties, and then add them to the list:
for (int y = 0; y < assessLength; y++)
{
Label assessLabel = new Label();
assessLabel.Location = ...;
// etc.
tabPage5.Controls.Add(assessLabel);
assessLabels.Add(assessLabel);
}
This would also remove the need to continuously cast the ArrayList members and unneeded access to the list.
PS. If assessLabels only contains objects of type Labels, consider using a List<Label> instead.
Somewhere in internet I found this code
private void PopulateChart()
{
int elements = 500;
Random r = new Random();
List<double> xValues = new List<double>();
double currentX = 0;
for (int i = 0; i < elements; i++)
{
xValues.Add(currentX);
currentX = currentX + r.Next(1, 2000);
}
List<double> yValues = new List<double>();
for (int i = 0; i < elements; i++)
{
yValues.Add(r.Next(0, 50));
}
// remove all previous series
chart1.Series.Clear();
var series = chart1.Series.Add("MySeries");
series.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Stock;
//series.XValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.Auto;
DateTime baseDate = DateTime.Today;
for (int i = 0; i < xValues.Count; i++)
{
var xDate = baseDate.AddSeconds(xValues[i]);
var yValue = yValues[i];
series.Points.AddXY(xDate, yValue);
}
// show an X label every itme interval (values in minute 60 = 1 hour)
chart1.ChartAreas[series.ChartArea].AxisX.Interval = 100.0;
chart1.ChartAreas[0].AxisX.IntervalType = System.Windows.Forms.DataVisualization.Charting.DateTimeIntervalType.Minutes;
// label format
chart1.ChartAreas[0].AxisX.LabelStyle.Format = "HH:mm:ss";
}
This displays random data in chart with grouping of data with some time interval.
Now I want to put a horizontal scrollbar (x-axis). I tried using code used in this post
Adding a scroll bar to MS Chart control C#
but I couldnot apply it with full functionality.
Can anyone help me in this problem?
You have enable the X axis for zooming.
chart1.ChartAreas["ChartArea1"].CursorX.IsUserEnabled = true;
chart1.ChartAreas["ChartArea1"].CursorX.IsUserSelectionEnabled = true;
chart1.ChartAreas["ChartArea1"].AxisX.ScaleView.Zoomable = true;
chart1.ChartAreas["ChartArea1"].AxisX.ScrollBar.IsPositionedInside = true;
I am having trouble displaying data. My problem is that the first series i establish starts from "0" on y, but the second series starts at the y value from the series before it. What do i need to adjust in my code to allow all subsequent series after the first to start at y = 0?
Code.
private void BuildGraph(machine_data[] array)
{
int series_cnt = 1;
chart1.Series.Clear();
chart2.Series.Clear();
for (int x = 0; x < array.Count(); x++)
{
chart1.Series.Add(array[x].name + array[x].Digital_Location);
chart2.Series.Add(array[x].name + array[x].Digital_Location);
int numpoints = array[x].Multi_Datapoints.Count();
for (Int32 i = 0; i < numpoints; i++)
{
/***************************/
chart1.Series[array[x].name + array[x].Digital_Location].ChartType = SeriesChartType.Area;
chart1.Series[array[x].name + array[x].Digital_Location].Points.AddXY(array[x].Multi_Datapoints[i].dt, array[x].Multi_Datapoints[i].state);
/***************************/
chart2.Series[array[x].name + array[x].Digital_Location].ChartType = SeriesChartType.StackedColumn;
// Set up the charting location of the Series
if (array[x].name == "Preci_4")
chart2.Series[array[x].name + array[x].Digital_Location].Points.AddXY(0, array[x].count_1s);
else
chart2.Series[array[x].name + array[x].Digital_Location].Points.AddXY(1, array[x].count_1s);
// Set up the color of the series' according to which DIO they correspond to.
if (array[x].Digital_Location == "DIO0")
chart2.Series[array[x].name + array[x].Digital_Location].Color = Color.Green;
else
chart2.Series[array[x].name + array[x].Digital_Location].Color = Color.Red;
/*chart2.AlignDataPointsByAxisLabel();
return chart2;*/
}
series_cnt++;
}
I've figured it out - I was making 4 separate series. I need only 2 series and to vary the x position. When I make 2 series I am then able to start from 0.