How do we assign a Series to a ChartArea? - c#

I have an array of ChartArea area[] and an array of Series series[]
What I need to do is use a different ChartArea for each Series.
This is what is usually done :
Chart Chart0 = new Chart();
ChartArea ChartArea0 = new ChartArea("name");
Chart0.ChartAreas.Add(ChartArea0);
Series Series0 = new Series();
Chart0.Series.Add(Series0);
// link series to area here
Series0.ChartArea = "name";
But in my case, I cannot have a string name for each. It needs to be an array. How can I work around this?

I asked a few clarifying questions in my comment, but if I go off my assumptions of the situation you expressed, here's what I would do:
var sourceStringArray = new string[] { "ChartArea1Name" };
Chart Chart0 = new Chart();
ChartArea ChartArea0 = new ChartArea(sourceStringArray[0]);
Chart0.ChartAreas.Add(ChartArea0);
Series Series0 = new Series();
Chart0.Series.Add(Series0);
// link series to area here
Series0.ChartArea = sourceStringArray[0];
I will edit my answer based on the responses to my comment.
Edit:
Based on your comment:
I need it to be an array as the number of ChartAreas I need is dependant on a variable number of parameters
You would probably want to make a method that does the creation of your ChartArea objects. Something like:
private List<ChartArea> CreateChartAreas(string[] chartAreaNames)
{
var chartAreaInstances = new List<ChartArea>();
foreach (var chartAreaName in chartAreaNames)
{
var tempChart = new Chart();
var tempChartArea = new ChartArea(chartAreaName);
tempChart.ChartAreas.Add(tempChartArea);
var tempSeries = new Series();
tempChart.Series.Add(tempSeries);
tempSeries.ChartArea = chartAreaName;
}
return chartAreaInstances;
}
Then in the main code, do something like:
var sourceStringArray = new string[] { "ChartArea1Name", "ChartArea2Name" };
var chartAreas = CreateChartAreas(sourceStringArray);
foreach (var chartArea in chartAreas)
{
// Do something with your chart area instances.
}
Disclaimer: I haven't tested this code as I don't know what your specific use case is for this.

Related

How to remove a single DataLabel from a Chart made with EPPlus and C#

Is it possible in any way to remove a single DataLabel from a Chart made with EPPlus and C#?
I just want to remove a single one, not the setting for the hole Chart...
How can the collection of DataLabels be accessed (dLbls > dLbl)?
var chart = (ExcelBarChart)worksheet.Drawings.AddChart("Chart", eChartType.ColumnStacked);
Also made use of coloring a individual point of Chart with this:
Legend color is incorrect in excel chart created using EPPlus
(The red point at the upper right)
Now I want to remove a specific label of a point, with generally applied DataLabels...
chart.DataLabel.ShowValue = true;
Any hint appreciated.
I figured out a solution. This Extension method is doing the trick...
public static void RemoveDataLabel(this ExcelBarChart chart, int serieNumber, int objectNumber)
{
var chartXml = chart.ChartXml;
var nsa = chart.WorkSheet.Drawings.NameSpaceManager.LookupNamespace("a");
var nsuri = chartXml.DocumentElement.NamespaceURI;
var nsm = new XmlNamespaceManager(chartXml.NameTable);
nsm.AddNamespace("a", nsa);
nsm.AddNamespace("c", nsuri);
var dLbls = chart.ChartXml.SelectSingleNode(#"c:chartSpace/c:chart/c:plotArea/c:barChart/c:ser[c:idx[#val='" + serieNumber + "']]/c:dLbls", nsm);
var dLbl = chartXml.CreateNode(XmlNodeType.Element, "dLbl", nsuri);
var idx = chartXml.CreateNode(XmlNodeType.Element, "idx", nsuri);
var valueIdx = chartXml.CreateAttribute("val", nsuri);
valueIdx.Value = objectNumber.ToString();
idx.Attributes.Append(valueIdx);
dLbl.AppendChild(idx);
var delete = chartXml.CreateNode(XmlNodeType.Element, "delete", nsuri);
var valueDelete = chartXml.CreateAttribute("val", nsuri);
valueDelete.Value = "1";
delete.Attributes.Append(valueDelete);
dLbl.AppendChild(delete);
dLbls.AppendChild(dLbl);
}

C# Chart - multiple curves but color gets overwritten

So i'm displaying multiple curves in my chart. However, when i generate a random color, all the other curves will get this color too.
int fileIndex=0;
Random r = new Random();
foreach (var i in graphContainer)
{
fileIndex++;
var series = new Series
{
Name = legendNames[fileIndex],
Color = Color.FromArgb(r.Next(0, 256), r.Next(0, 256), r.Next(0, 256)),
IsVisibleInLegend = true,
IsXValueIndexed = false,
ChartType = SeriesChartType.Line
};
foreach (var j in i)
{
series.Points.AddXY (j.Item2, j.Item1);
}
chart.Invalidate ();
chart.Series.Add (series);
}
Note: all the curves except one has the same values, but you get the idea.
Why is my curves getting the same colors?
Entire function:
private void generateWaveformsFromFileBtn_Click(object sender, EventArgs e)
{
int fileIndex = -1;
string folder = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName) + #"\WaveForms\";
string filter = "*.csv";
string[] filePath = Directory.GetFiles(folder, filter);
List<string> legendNames = new List<string>();
List<Tuple<double, double>> graph = new List<Tuple<double, double>>();
List<List<Tuple<double, double>>> graphContainer = new List<List<Tuple<double, double>>>();
chart.Series.Clear();
foreach(var fileName in filePath) {
legendNames.Add(Path.GetFileName(fileName));
using (TextFieldParser csvParser = new TextFieldParser(fileName))
{
csvParser.SetDelimiters (new string[] { ";" });
csvParser.ReadLine ();
while (!csvParser.EndOfData)
{
string[] fields = csvParser.ReadFields();
double current = Double.Parse(fields[0]);
double inductance = Double.Parse(fields[1]);
graph.Add (new Tuple<double,double>(current, inductance));
}
graphContainer.Add(graph);
}
}
Random r = new Random();
foreach (var i in graphContainer)
{
fileIndex++;
var series = new Series
{
Name = legendNames[fileIndex],
Color = Color.FromArgb(r.Next(0, 256), r.Next(0, 256), r.Next(0, 256)),
IsVisibleInLegend = true,
IsXValueIndexed = false,
ChartType = SeriesChartType.Line
};
foreach (var j in i)
{
series.Points.AddXY (j.Item2, j.Item1);
}
chart.Invalidate ();
chart.Series.Add (series);
}
}
Suggestion: (?)
You have already posted one solution:
Before adding new data in a loop, make sure the old data are deleted. You do it by creating a new instance:
graph = new List<Tuple<double, double>>();
This is a valid approach.
But for completeness' sake let's look at the more direct aproach as well:
graph.Clear();
This clears the data from the list object. But you are adding the graph to the graphContainer list like this:
graphContainer.Add(graph);
which means that the very object gets put into the list which, before the next loop, will be cleared. This is a common mistake. The solution is simple: Don't add the graph object itself into the graphContainer list but a copy of it :
graphContainer.Add(graph.ToList());
The ToList() call is a simple way to create a copy of a List<T>.
As for the post you linked to: The old post is strictly about runtime performance. And not about creating copies of the lists.
But even there, the recommended way to do it is depending on whether you can predict the number of elements you will put in each list.
If you can predict it and if it is somewhat largish, let's say more than a few hundred of large or a many thousands of smallish elements you should tell the list which capacity it should reserve.
This avoids multiple growing pains. After a Clear() the Capacity is retained, avoiding new allocations.
Your elements (Tuples) are small, so you probably don't need to worry about the list growing performance..
As suggested by user #Taw, i forgot to create a new instance of the graph or empty it, between runs.
graph = new List<Tuple<double, double>>(); // <-- solved it
while (!csvParser.EndOfData)
{
string[] fields = csvParser.ReadFields();
...
}
EDIT:
Do not use yourList.Clear() see here why: https://stackoverflow.com/a/35848659/2902996

C# simply chart binding

The idea is to simply to plot arrD[i] in a chart called chart5 SeriesA. The issue is that nothing is plotted in the windows form. Maybe someone could help. Many thanks.
chart5 = new Chart();
Series SeriesA = new Series();
Dictionary<int, double> value5 = new Dictionary<int, double>();
for (int i = 0; i < monthCount; i++)
{
value5.Add(i, arrD[i]);
}
SeriesA.XValueMember = "Location";
SeriesA.YValueMembers = "Value";
chart5.DataSource = value5;
chart5.Series.Add("SeriesA");
You do not add the series you created to your chart.
Try this code :
Series SeriesA = new Series();
SeriesA.Points.DataBind(arrD, "Location", "Value", "");
chart5.Series.Add(SeriesA);
Note that we add SeriesA and not "SeriesA"
Ok, I simplified the binding (which works well now) as well as the loop for hiding the zero values. But how to print now the modified chart without zero values .. many thanks.
chart7.Series["Series3"].ChartType = SeriesChartType.Line;
chart7.Series["Series3"].Points.DataBindXY(xVal, arrDouble3);
foreach (Series series in chart7.Series)
{
foreach (DataPoint arrP in series.Points)
{
if (arrP.YValues.Length > 0 && (double)arrP.YValues.GetValue(0) == 0)
{
arrP.IsValueShownAsLabel = false;
}
}
}
chart7.Series["Series3"].Points.DataBindXY(xVal, arrP); ????

Adding series to Excel chart exception

I have an excel file filled with some data. I am trying to open the second sheet and create a chart. The problem is that the Series are giving me either a System.Runtime.InteropServices.COMException was caught or if I un-comment the commented lines a No overload for method 'SeriesCollection' takes '0' arguments. Here is the code that I have:
Microsoft.Office.Interop.Excel.ChartObjects chartObjs = (Microsoft.Office.Interop.Excel.ChartObjects)ws.ChartObjects(Type.Missing);
Microsoft.Office.Interop.Excel.ChartObject chartObj = chartObjs.Add(100, 20, 300, 300);
Microsoft.Office.Interop.Excel.Chart xlChart = chartObj.Chart;
Range rg1 = ws.get_Range("A1", "D" + rowcount);
rg1.VerticalAlignment = Microsoft.Office.Interop.Excel.XlVAlign.xlVAlignCenter;
xlChart.SetSourceData(rg1, Microsoft.Office.Interop.Excel.XlRowCol.xlColumns);
xlChart.ChartType = XlChartType.xlLine;
xlChart.Legend.Position = XlLegendPosition.xlLegendPositionBottom;
Axis axis = (Axis)xlChart.Axes(Microsoft.Office.Interop.Excel.XlAxisType.xlValue, Microsoft.Office.Interop.Excel.XlAxisGroup.xlPrimary);
axis.MaximumScaleIsAuto = false;
axis.MaximumScale = 3;
Axis Xaxis = (Axis)xlChart.Axes(Microsoft.Office.Interop.Excel.XlAxisType.xlCategory, Microsoft.Office.Interop.Excel.XlAxisGroup.xlPrimary);
Xaxis.TickLabels.Orientation = XlTickLabelOrientation.xlTickLabelOrientationDownward;
//SeriesCollection seriesCollection = (SeriesCollection)xlChart.SeriesCollection();
Series s1 = (Series)xlChart.SeriesCollection(1);
s1.Name = "Serie1";
s1.MarkerStyle = XlMarkerStyle.xlMarkerStyleCircle;
//seriesCollection.NewSeries();
Series s2 = (Series)xlChart.SeriesCollection(2);
s2.Name = "Serie2";
s2.MarkerStyle = XlMarkerStyle.xlMarkerStyleNone;
//seriesCollection.NewSeries();
Series s3 = (Series)xlChart.SeriesCollection(3);
s3.Name = "Serie3";
s3.MarkerStyle = XlMarkerStyle.xlMarkerStyleNone;
If I keep the comments, the error says invalid parameter and is shown on that line:
Series s2 = (Series)xlChart.SeriesCollection(2);
If I remove the comments, I get the second exception on that line:
SeriesCollection seriesCollection = (SeriesCollection)xlChart.SeriesCollection();
If I add 1 as a parameter, then the chart is not displayed properly. Do you have any suggestions how to make it work?
Argh that stuff still gives me nightmares. There was some weirdness around SeriesCollection - but I cannot remember exactly what it was.
Try to re-include that line
//SeriesCollection seriesCollection = (SeriesCollection)xlChart.SeriesCollection();
and refernece the seriesCollection object everywhere.
Alos it could be, that the index for SeriesCollection is zero - based, can you try that?
By Default when you create a new chart it doesn't have any series so you can't select it using chartPage.SeriesCollection(1). You need to create a series first.
In order to add a new series you need to use something like:
SeriesCollection seriesCollection = (SeriesCollection)xlChart.SeriesCollection();
Series s1 = seriesCollection.NewSeries();
s1.Name = "Serie1";
s1.MarkerStyle = XlMarkerStyle.xlMarkerStyleCircle;
Series s2 = seriesCollection.NewSeries();
s2.Name = "Serie2";
s2.MarkerStyle = XlMarkerStyle.xlMarkerStyleNone;
You may also need to add the values to the series rather than to the chart, eg:
Series ser = sc.NewSeries();
ser.XValues = _excelWorksheet.Range[YourRange];
ser.Values = _excelWorksheet.Range[YourRange];

Converting data from discrete properties of objects in an IEnumerable into an array/series for charting?

I have an IEnumerable (List<>) of a particular Type. The Type has a set of properties which I would like to chart as individual series (MVC3/Razor).
Is there a way I can transpose the data in the list of objects into a list of the properties?
e.g.
Given the code below, the end result I am trying to acheive is a set of Series so I have
Bucket1: IEnumerable<Date, Value>
Bucket2: IEnumerable<Date, Value>
etc
So that I can then chart each series to get a line chart with Date on x and value on y, for (in this example) 3 series/lines. This code works perfectly, but just feels wrong somehow?
var buckets = new List<Bucket>(){
new Bucket{
Date=DateTime.Today.AddDays(-3),
Bucket1=1000,
Bucket2=2000,
Bucket3=3000},
new Bucket{
Date=DateTime.Today.AddDays(-2),
Bucket1=1000,
Bucket2=2020,
Bucket3=3300},
new Bucket{
Date=DateTime.Today.AddDays(-1),
Bucket1=1000,
Bucket2=2040,
Bucket3=3600}
};
var chart = new Chart(){ Height = 400, Width = 600 };
var area = new ChartArea();
chart.ChartAreas.Add(area);
var series1 = chart.Series.Add("Bucket 1");
var series2 = chart.Series.Add("Bucket 2");
var series3 = chart.Series.Add("Bucket 3");
foreach (var series in chart.Series)
{
series.ChartType = SeriesChartType.Line;
}
foreach (var item in buckets)
{
series1.Points.AddXY(item.Date, item.Bucket1);
series2.Points.AddXY(item.Date, item.Bucket2);
series3.Points.AddXY(item.Date, item.Bucket3);
}
What about just using a Lambda expression for each serie (code below was not tested - it's just a prototype):
var series1 = buckets.Select(b => new { b.Date, b.Bucket1 });
var series2 = buckets.Select(b => new { b.Date, b.Bucket2 });
Thanks to the comments posted I kept my original code and don't think there's an easier way to do this.

Categories