I have a fast-line chart series where on X I have DateTime and on Y double values - series is added to the chart with such method:
public virtual bool AddOrUpdateSeries(int caIndex, Series newSeries, bool visibleInLegend)
{
var chartArea = GetChartArea(caIndex);
if (chartArea == null) return false;
var existingSeries = _chart.Series.FirstOrDefault(s => s.Name == newSeries.Name);
if (existingSeries != null)
{
existingSeries.Points.Clear();
AddPoints(newSeries.Points, existingSeries);
}
else
{
newSeries.ChartArea = chartArea.Name;
newSeries.Legend = chartArea.Name;
newSeries.IsVisibleInLegend = visibleInLegend;
newSeries.BorderWidth = 2;
newSeries.EmptyPointStyle = new DataPointCustomProperties { Color = Color.Red };
_chart.Series.Add(newSeries);
}
return true;
}
As you can see, I am setting the style for empty point to be shown in red.
The first points that are added to the series are as follows:
So as you can see, first two points have the same Y value, but in addition -
first one has IsEmpty flag set.
Empty point is added to the series with such piece of code:
series.Points.Add(new DataPoint
{
XValue = _beginOADate,
YValues = new[] { firstDbPoint.Y },
IsEmpty = true
});
where _beginOADate is double OADate value = 42563 = 12/07/2016 00:00 as DateTime.
The second point's DateTime is 15/08/2016 22:20
When chart is displayed with the beginning of the X axis, everything looks ok as on the picture below - empty datapoint starts at 12/07/2016 and lasts until 15/08/2016.
But, when I scroll one position on X, the empty datapoint's red line is not being displayed - instead, whole visible part of empty datapoint's line is displayed as it is non-empty:
Anybody knows how to fix this behaviour so that the whole line starting from Empty datapoint until first non-empty datapoint would always be shown in red?
Of course the dummy solution would be to add one more extra empty datapoint very close to the first non-empty point, but I don't like that solution.
The ChartType.FastLine is much faster the the simple Line chart but to be so fast it makes several simplifications in rendering, which means that not all chart features are supported:
The FastLine chart type is a variation of the Line chart that
significantly reduces the drawing time of a series that contains a
very large number of data points. Use this chart in situations where
very large data sets are used and rendering speed is critical.
Some charting features are omitted from the FastLine chart to improve
performance. The features omitted include control of point level
visual attributes, markers, data point labels, and shadows.
Unfortunately EmptyPointStyle is such a 'point level visual attribute'.
So you will need to decide which is more important: The raw speed or the direct and plausible treatment of empty DataPoints.
(I have a hunch that you'll go for your 'dummy solution', which imo is a 1st class workaround ;-)
Related
I have a horizontally stacked bar chart in my app, and I'm trying to figure out how to get the X axis value when the user clicks on a bar. The problem is when I look at the values at runtime, the Y values are fine but the X values are all 0.
Screen capture
In the image above, light blue bars are from Series[0] and represent the MTD sales and the darker ones from Series[1] represent last year's sales for the same month. My goal is that when the user double-clicks on a bar, he is taken to the detailed sales report for that salesperson.
I haven't tried switching my chart to a regular bar chart because this is what I will need to look like in the end. But I'm starting to wonder if the reason I'm getting all 0's in the XValue field is because of this or because I am using strings as the type of value.
Has anyone ever encountered this or have any clues as to how to fix it?
Screen capture of points at runtime
You use one of the Bar chart types.
They have their x-axis and y-axis switched compared to most normal types.
Therefore in order to get the values along the horizontal axis you actually want to grab the y-values.
To get at the y-value of the double-clicked datapoint you can do a HitTest like in this code:
private void chart1_MouseDoubleClick(object sender, MouseEventArgs e)
{
var hit = chart1.HitTest(e.X, e.Y, ChartElementType.DataPoint);
if (hit.PointIndex >= 0)
{
DataPoint dp = hit.Series.Points[hit.PointIndex];
Console.WriteLine(dp.YValues[0]);
}
}
Note however that in a stacked bar the values look stacked but each point will still only have its own value.
If you wanted to get at the stacked/summed up values you would have to add up all points below and including the one that was hit. 'Below' here means points at the same x-slot but in lower series!
You will not be able to use the x-values if you have added them as strings since in that case they will all be 0, as you can see in your screenshot.
But since all stacked points in your case will have the same e.PointIndex we can use this to access all points in the series below..:
..
int si = 0;
double vsum = 0;
Series s = null;
do
{
s = chart4.Series[si++];
vsum += s.Points[hit.PointIndex].YValues[0];
} while (hit.Series != s);
Console.WriteLine(vsum);
If you actually want to access the x-values you have two options:
You can explicitly add the strings to the AxisLabel of each DataPoint. While the x-values will still be all 0 the AxisLabels now can be accessed.
Or you can add them as numbers, maybe using some scheme to map the strings to numbers and back and, again set the AxisLabels.
OK so I finally managed to put custom labels on the chart using the Chart.Customize event.
Here is the data I am using for this chart:
Vendeur | Total | idDepartement | idEmploye | TotalLastYear
Ghislain 5256.30 1 56 0.00
Kim 12568.42 1 1 74719.18
Philippe 17565.27 1 44 38454.85
I changed X axis XValueType to Double, and the XValueMember to idEmploye.
Result
As you can see, it's not very user friendly to have employee id's on a chart. This is where the Customize event gets useful.
Here is my event:
private void chart1_Customize(object sender, EventArgs e)
{
// Set X axis interval to 1, label will be centered (between 0.5 and 1.5)
chart1.ChartAreas[0].AxisX.Interval = 1;
double startOffset = 0.5;
double endOffset = 1.5;
chart1.ChartAreas[0].AxisX.CustomLabels.Clear();
// Cycle through chart Datapoints in first serie
foreach (System.Windows.Forms.DataVisualization.Charting.DataPoint pt in chart1.Series[0].Points)
{
// First get the dataset used for the chart (from its bindingsource)
DataSet dsSales = (DataSet)bsViewVentesParUtilisateurSignsMoisCourant.DataSource;
// Second get the datatable from that dataset based on the datamember
// property of the bindingsource
DataTable dtSales = (DataTable)dsSales.Tables[bsViewVentesParUtilisateurSignsMoisCourant.DataMember];
// Get a dataview and filter its result based on the current point's XValue
DataView dv = new DataView(dtSales);
dv.RowFilter = "idEmploye=" + pt.XValue.ToString();
// Get the "Salesperson" (or "Vendeur") column value from the first result
// (other rows will have the same value but row 0 is safe)
string firstname = dv.ToTable().Rows[0].Field<string>("Vendeur").ToString();
// Create new customlabel and add it to the X axis
CustomLabel salespersonLabel = new CustomLabel(startOffset, endOffset, firstname, 0, LabelMarkStyle.None);
chart1.ChartAreas[0].AxisX.CustomLabels.Add(salespersonLabel);
startOffset += 1;
endOffset += 1;
}
}
Final result
I am very happy with the result. And now when I double-click on a bar in the chart, I can get the employee id from the X value and generate the code to get the detailed sales report for that person for the given month.
i got a Little "Problem", i want to create a Chart looking like this:
So basically
Series 1 = Normal bar Chart. Color green if it Ends before the "time max" (series2) Series 2 = just a DataPoint / Marker on top of series 1 items.
I am struggling with this though...
my Code:
chart_TimeChart.Series.Clear();
string series_timeneeded = "Time Needed";
chart_TimeChart.Series.Add(series_timeneeded);
chart_TimeChart.Series[series_timeneeded]["PixelPointWidth"] = "5";
chart_TimeChart.ChartAreas[0].AxisY.ScrollBar.Size = 10;
chart_TimeChart.ChartAreas[0].AxisY.ScrollBar.ButtonStyle = ScrollBarButtonStyles.SmallScroll;
chart_TimeChart.ChartAreas[0].AxisY.ScrollBar.IsPositionedInside = true;
chart_TimeChart.ChartAreas[0].AxisY.ScrollBar.Enabled = true;
chart_TimeChart.Series[series_timeneeded].BorderWidth = 2;
chart_TimeChart.Series[series_timeneeded].ChartType = SeriesChartType.StackedBar;
chart_TimeChart.Series[series_timeneeded].YValueType = ChartValueType.Time;
chart_TimeChart.ChartAreas[0].AxisY.LabelStyle.Format = "HH:mm:ss";
chart_TimeChart.Series[series_timeneeded].XValueType = ChartValueType.String;
for (int i = 0; i < MaxNumber; i++)
{
chart_TimeChart.Series[series_timeneeded].Points.AddXY("item"+ " " + (i + 1).ToString(), DateTime.Now.Add(Timespans[i]));
}
chart_TimeChart.Series.Add(series_FinishTime);
chart_TimeChart.Series[series_FinishTime].ChartType = SeriesChartType.StackedBar;
chart_TimeChart.Series[series_FinishTime].BorderWidth = 0;
chart_TimeChart.Series[series_FinishTime].MarkerSize = 15;
chart_TimeChart.Series[series_FinishTime].MarkerStyle = MarkerStyle.Square;
chart_TimeChart.Series[series_FinishTime].MarkerColor = Color.Black;
chart_TimeChart.Series[series_FinishTime].YValueType = ChartValueType.DateTime;
chart_TimeChart.Series[series_FinishTime].XValueType = ChartValueType.String;
for (int i = 0; i < MaxNumber; i++)
{
DateTime YPosition = GetFinishTime(i);
chart_TimeChart.Series[series_FinishTime].Points.AddXY("item"+ " " +(i+1).ToString(), YPosition);
}
but this only Displays the 2nd series on top of the first one but the first one isnt visible anymore. The Maker of series 2 isnt shown but instead the bar is (eventhough i made borderwidth to 0). In my opinion/thinking i just have to make the "bar" of series 2 invisible and just Show the marker Points for series 2.
Any ideas?
Update:
string seriesname = Name+ i.ToString();
chart_TimeChart.Series.Add(seriesname);
chart_TimeChart.Series[seriesname].SetCustomProperty("DrawSideBySide", "false");
chart_TimeChart.Series[seriesname].SetCustomProperty("StackedGroupName", seriesname);
chart_TimeChart.Series[seriesname].ChartType = SeriesChartType.StackedBar; //Y and X are exchanged
chart_TimeChart.Series[seriesname].YValueType = ChartValueType.Time;
chart_TimeChart.ChartAreas[0].AxisY.LabelStyle.Format = "HH:mm:ss";
chart_TimeChart.Series[seriesname].XValueType = ChartValueType.String;
DateTime TimeNeeded = DateTime.Now.Add(List_AllLiniengroupsTimespans[k][i]);
DateTime TimeMax = GetFinishTime(k, i);
TimeSpan TimeDifference = TimeNeeded - TimeMax;
if (TimeNeeded > TimeMax) //All good
{
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded); //Time till finish
chart_TimeChart.Series[seriesname].Points[0].Color = Color.Blue;
chart_TimeChart.Series[seriesname].Points[0].SetCustomProperty("StackedGroupName", seriesname);
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded.Add(TimeDifference)); //time left
chart_TimeChart.Series[seriesname].Points[1].Color = Color.Red;
chart_TimeChart.Series[seriesname].Points[1].SetCustomProperty("StackedGroupName", seriesname);
}
else if (TimeMax > TimeNeeded) //wont make it in time
{
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded); //time till still okay
chart_TimeChart.Series[seriesname].Points[0].Color = Color.Blue;
chart_TimeChart.Series[seriesname].Points[0].SetCustomProperty("StackedGroupName", seriesname);
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded.Add(TimeDifference)); //Time that is too much
chart_TimeChart.Series[seriesname].Points[1].Color = Color.Green;
chart_TimeChart.Series[seriesname].Points[1].SetCustomProperty("StackedGroupName", seriesname);
}
else if (TimeMax == TimeNeeded) //fits exactly
{
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded);
chart_TimeChart.Series[seriesname].Points[0].Color = Color.DarkOrange;
chart_TimeChart.Series[seriesname].Points[0].SetCustomProperty("StackedGroupName", seriesname);
}
the Code will be displayed as:
but i want it to look like this:
!! See the update below !!
If you really want to create a StackedBar chart, your chart has two issues:
If you want to stack datapoints they need to have meaningful x-values; without them how can it know what to stack on each other?
You add strings, which look fine but simply don't work. That is because the DataPoint.XValue field is double and when you add string into it it is set to 0 !! Your string is copied to the Label but otherwise lost.
So you need to come up with a suitable numeric value you use for the x-values..
And you also need to group the series you want to stack. For this there is a special property called StackedGroupName which serves to group those series that shall be stacked.
Here is how you can use it:
yourSeries1.SetCustomProperty("StackedGroupName", "Group1");
For a full example see this post !
It also shows one way of setting the Labels with string values of your choice..
This is the way to go for real StackedBar charts. Your workaround may or may not work. You could try to make the colors transparent or equal to the chart's backcolor; but it won't be more than a hack, imo.
Update
I guess I have misread the question. From what I see you do not really want to create a stacked chart.
Instead you struggle with these issues:
displaying bars at the same y-spot
making some bars invisible
displaying a vertical line as a marker
Let's tackle each:
Some column types including all Bars, Columns and then some have a little known special property called DrawSideBySide.
By default is is set to Auto, which will work like True. This is usually fine as we don't want bars to sit upon each other, effectively hiding all or part of the overlaid points.
But here we do want them to share the same y-position, so we need to set the property to false for at least one Series; the others (on Auto) will follow..:
You can do it either like this:
aSeries["DrawSideBySide"] = "false";
or like this:
aSeries.SetCustomProperty("DrawSideBySide", "false");
Next we hide the overlaid Series; this is simple:
aSeries.Color = Color.Transparent;
The last issue is displaying a line marker. There is no such MarkerStyle, so we need to use a custom style. For this we need to create a suitable bitmap and add it as a NamedImage to the chart's Images collection.
This sounds more complicated than it is; however the MarkerImage will not be scaled, so we need to created suitable sizes whenever we resize the Chart or add/remove points. I will ignore this complication for now..
int pointCount = 10;
Bitmap bmp = new Bitmap(2, chart.ClientSize.Height / pointCount - 5);
using (Graphics g = Graphics.FromImage(bmp)) g.Clear(Color.Black);
NamedImage marker = new NamedImage("marker", bmp);
chart.Images.Clear(); // quick & dirty
chart.Images.Add(marker);
Here is the result:
A few notes:
I would recommend to use variables for all chart elements you refer to repeatedly instead of using indexed references all the time. Less code, easier to read, a lot easier to maintain, and probably better performance.
Since your code called for the visible datapoints to be either red or green the Legend will not show a good representation. See here for an example of drawing a multi-colored legend item..
I used the chart height; this is not really recommended as there may be Titles or Legends or even more ChartAreas; instead you should use the height of the ChartArea, or even more precise, the height of the InnerPlotPosition. You would need to convert those from percentages to pixels. Not too hard, see below or see here
or here for more examples!
The markers should be adapted from the Resize and probably from the AxisViewChanged events. Putting it in a nice function to call (e.g. void setMarkerImage(Chart chart, Series s, string name, int width, Color c)) is always a good idea.
If you need to adapt the size of the marker image repeatedly, you may want to write better code for clearing the old one; this should include disposing of the Bitmap that was used before..
Here is an example:
var oldni = chart.Images.FindByName("marker");
if (oldni != null)
{
oldni.Image.Dispose();
chart.Images.Remove(oldni);
oldni.Dispose();
}
In some situations one needs to nudge the Chart to update some of its properties; RecalculateAxesScale is one such nudge.
Example for calculating a suitable marker height:
ChartArea ca = chart.ChartAreas[0];
ca.RecalculateAxesScale();
float cah = ca.Position.Height;
float iph = ca.InnerPlotPosition.Height;
float h = chart3.ClientSize.Height * cah / 100f * iph / 100f;
int mh = (int)(h / s.Points.Count);
Final note: The original answer stressed the importance of using meaningful x-values. Strings are useless! This was important for stacking bars; but it is equally important now, when we want bars to sit at the same vertical positions! Adding the x-values as strings is again resulting in nonsense..
(Since we have Bars the x-values go along the vertical axis and vice versa..)
I'm looking to change my chart series data points if there is an error values. I want to set rule to highlight those data points like below. Please help to get below code working.
// Find first point with a Y2 value of equal or less than 10.
var dataPoint = Chart1.Series[1].Points.Where(x => x.YValues <= 10);
foreach (DataPoint dt in dataPoint)
{
dt.BorderDashStyle = ChartDashStyle.Dot;
dt.Color = Color.Red;
}
DataPoint.YValues is an array.
The YValues property is used to set the Y-values of data points.
Only one Y-value per point is required for all chart types except
bubble, candlestick and stock charts. These chart types require more
than one Y-value because one data point consists of multiple values.
For example, to plot one stock chart column, four values are required:
high, low, open and close values.
The YValues property returns an array of double values when used to
retrieve the Y-values.
Important The YValuesPerPoint property determines the maximum number
of Y-values that all data points in a Series can have. If you specify
more than the allowable number of Y-values, an exception will be
raised.
Unless you are using one of the above special ChartTypes you always will want to use the first element. So simply write:
var dataPoint = Chart1.Series[1].Points.Where(x => x.YValues[0] <= 10);
If you do use one of the three multiple Y-Values chart types you could, depending on the situation for example write this:
var dataPoint = Chart1.Series[1].Points.Where(x => x.YValues.Max() <= 10);
or this:
var dataPoint = Chart1.Series[1].Points.Where(x => x.YValues.Min() <= 10);
Here is my code:
public Form1()
{
InitializeComponent();
// Data arrays.
string[] seriesArray = { "Cats", "Dogs" };
int[] pointsArray = { 1, 2 };
// Set palette.
this.chart1.Palette = ChartColorPalette.SeaGreen;
// Set title.
this.chart1.Titles.Add("Pets");
// Add series.
for (int i = 0; i < seriesArray.Length; i++)
{
// Add series.
Series series = this.chart1.Series.Add(seriesArray[i]);
// Add point.
series.Points.Add(pointsArray[i]);
}
}
private void button1_Click(object sender, EventArgs e)
{
chart1.Titles[0].Position.Y = chart1.Titles[0].Position.Y + 1;
}
The problem i face is when i change the Location of the Title Pets is that the Chart redraws itself in a different way. I want to understand why does this happen and is there a way to work around this - since it brings the text on top of the chart and this happens only at the first time.
Here is what i mean:
How does adding 1 make such a huge difference in the chart?
Try with DockingOffset instead of Position.Y
private void button1_Click(object sender, EventArgs e)
{
chart1.Titles[0].DockingOffset = chart1.Titles[0].DockingOffset + 1;
}
The problem with 'Position.Y' is that it starts with "auto" position, so the first time you add 1 to it, the compiler assign a value, redraws and then add 1.
How does adding 1 make such a huge difference in the chart?
First that's because many or most ChartElements are positioned by percentages.
Some are by Values, given in doubles, other in pixels, but most are in percentages of their respective containers.
Or, initially set to auto and recalculated whenever necessary.
So if your ChartArea has a height of 80% and the Chart's Clientsize is 500pixels the ChartArea's height calculates to 400 pixels.
I don't know to what percentage the Title.Position.Height has been calculated in your chart, but if it is, say 4% then adding 1 makes it 5%, bringing it down from 20px to y=25px.
But wait, the difference you observe is a lot more than that! In fact the by far larger difference is the Height & Top of the ChartArea! Why is that?
Secondly, by messing with the Chart's Title's Position you have taken it out of the elements to consider when auto-calculating the available spaces, and with the Title 'gone' from that calculation, there suddenly is a lot more vertical space for the ChartArea. So is grows and even happens to overlap the Title.
How can you prevent that? Well you can prevent the changes from the calculation by taking the automatic out, maybe like this:
RectangleF r = chart1.ChartAreas[0].Position.ToRectangleF();
chart1.ChartAreas[0].Position = new ElementPosition(r.X, r.Y, r.Width, r.Height);
Note that the other elements may be affected as well. So the Legend will also use the freed space at the top and move up.. (You can see that in your screenshot.) And since the spaces are no longer calculated automatically you will have to take care of some more issues, when some elements grow and need more space, like the axis labels or a Legend..
By the way, to restore a ChartElement.Position to Auto you need to set the values to double.NaN.
I want to use MSDN charts to represent realtime data i'm getting from a telnet application. For testing purpose i have added a button to alter the chart manually. I manually made the chart and it has 0 to 5 points on the X axis with values different values on the X. The series is named by it's default "Series1".
I tried the following:
chart1.Series["Series1"].Points.ElementAt(0).SetValueY(40); //Nothing happens
chart1.Series["Series1"].Points.ElementAt(1).SetValueXY(1, 20); //Nothing happens
chart1.Series["Series1"].Points[0].SetValueY(40); //Nothing happens
chart1.Series["Series1"].Points.ElementAt(1).YValues.SetValue(10, 0); //Nothing happens
chart1.Series["Series1"].Points.Clear(); //Removes all points like it should.
So how do i change datapoint entries on runtime?
-EDIT-
If i modify a point using chart1.Series["Series1"].Points.ElementAt(0).SetValueY(40); and add a point after this with chart1.Series["Series1"].Points.AddXY(1, 40); the modified point does snap into it's modified place. The conclusion is that modifying does change the points Y value but the graph does not get refreshed. The function AddXY() seems to autorefresh. I cannot seem to find a way to call Refresh() manually.
Call chart1.Refresh() after changing the value; it will force a redraw of the chart, picking up the new values.
I've just found out that SetValueY() does not update the maximum interval in the Y axis. Therefore, if your current maximum is 0, it will not show anything higher than 0.
I do this:
public static void Refresh(this Chart chart) // update changed data
{
chart.Series[0].Points.AddXY(1, 1);
chart.Update();
chart.Series[0].Points.RemoveAt(chart.Series[0].Points.Count-1);
}
chart1.Refresh();
DataTable dtChartDataSource = Input from your side.
foreach (DataColumn dc in dtChartDataSource.Columns)
{
//a series to the chart
if (chart.Series.FindByName(dc.ColumnName) == null)
{
series = dc.ColumnName;
chart.Series.Add(series);
chart.Series[series].ChartType = SeriesChartType.Column;
foreach (DataRow dr in dtChartDataSource.Rows)
{
double dataPoint = 0;
double.TryParse(dr[dc.ColumnName].ToString(), out dataPoint);
Yourchart.Series[seriesName].Points.AddXY("customStringsOnAxis", dataPoints);
}
}
}
It will add the x axis data and Y axis values to the Column chart.
Hope its helps