I am using Dundas Charts in Visual Studio using C#.
I have a chart with one series - the chart is displaying the series on columns. I am currently using the following code to add the series:
public void AddSeries(string name, SeriesChartType type, IEnumerable xValues, IEnumerable yValues, bool showLabels = true)
{
Series s = new Series();
s.Points.DataBindXY(xValues, yValues);
s.LegendText = name;
s.Type = type;
if (type == SeriesChartType.Line) s.Color = Color.FromArgb(139,0,0);
else s.Color = _palette[_chart.Series.Count < _palette.Length ? _chart.Series.Count : 0];
s.ShowLabelAsValue = showLabels;
s.FontAngle = -90;
s.LabelFormat = string.IsNullOrWhiteSpace(_chart.ChartAreas["Default"].AxisY.LabelStyle.Format) ? "P0" : _chart.ChartAreas["Default"].AxisY.LabelStyle.Format;
s.Font = new Font("Arial", 5);
_chart.Series.Add(s);
}
I would like to change the colour of each column depending on the value for that column - this will be based on a int target value.
For example, if the column is less than our target value, it should display red, else it should display green.
How would you recommend doing this?
I have found a suitable answer for this, the only issue I have with this solution is that the columns come out in varying widths depending on the number of points on the x axis.
void GetColourCodedSeries(ChartHelper chartHelper, DataTable table, string groupingColumn, string valueColumn)
{
List<Color> _palette = new List<Color>();
var x = table.AsEnumerable().Select(f => f.Field<string>(groupingColumn)).ToArray();
foreach (DataRow row in table.Rows)
{
if (Convert.ToDecimal(row[valueColumn].ToString()) >= _availabilityTarget) _palette.Add(Color.FromArgb(0, 176, 80));
else _palette.Add(Color.FromArgb(139, 0, 0));
List<decimal?> y = new List<decimal?>();
foreach(var value in x)
{
if (value == row[groupingColumn].ToString()) y.Add(Convert.ToDecimal(row[valueColumn].ToString()));
else y.Add(null);
}
chartHelper.AddSeries(SeriesChartType.Column, x, y, _palette.ToArray());
}
}
This code creates a dynamic colour palette for the columns based on the values. I then add one series per row in my table, when adding the series I am including all of the possible x values and 1 y value per each of these - however, only one of the y axis values has an actual value, all of the others are marked as null values (i did try using 0 values but this made the chart look very messy).
Related
I'm trying to retrieve the pixel position on the chart of a DataPoint using its X Value (DateTime).
I'm using this code after the Chart has been painted but I get a very large number:
DateTime date = ... ; // DateTime of the DataPoint, I verified the date is correct and the chart contains it.
var pixelsPosition = ChartAreas[0].AxisX.ValueToPixelPosition(date.ToOADate());
// Here pixelsPosition is a very large number, above 600 000.
These are the Chart settings:
Series[0].XValueType = ChartValueType.DateTime;
Series[0].IsXValueIndexed = true;
The Chart has around 2000 Points.
I'm using the same code to retrieve a pixel position of a DataPoint using the Y Value and it works. I'm sure it's a stupid problem but I can't figure it out.
Working code for the Y-Axis
double yValue = 100;
double pixPositionY = ChartAreas[0].AxisY.ValueToPixelPosition(yValue); // THIS WORKS
What am I doing wrong?
EDIT:
I read that the method ValueToPixelPosition works only on Paint Events, I've tried to execute it on the Paint and PrePaint events but I get the same error.
I have found the solution to my problem:
DateTime date;
int pixelPositionX = 0;
var point = Series[0].Points.Where(X => X.XValue == date.ToOADate()).FirstOrDefault();
var index = (point != null) ? Series[0].Points.IndexOf(point) : -1;
if (index > -1)
pixelPositionX = (int)ChartAreas[0].AxisX.ValueToPixelPosition(index + 1);
That gives me the correct position of the DataPoint on the screen. ValueToPixelPosition works if I pass the dataPoint index as parameter rather than the datetime value converted to OADate. I'm not sure if this is the right solution but it's working for me.
I have the same problem, but your solution is surely wrong. ValueToPixelPosition() requires a double value and not an index. Apart from that your workaround is very slow because it must search through all DateTime values on X axis.
To find out how Microsoft generates these strange X values I used ILSpy to disassemble System.Windows.Forms.DataVisualization.ni.dll which can be found in C:\Windows\assembly\....
Here is the Microsoft code which converts DateTime into double:
// System.Windows.Forms.DataVisualization.Charting.DataPoint
public void SetValueXY(object xValue, params object[] yValue)
{
.......
else if (type == typeof(DateTime))
{
_xValue = ((DateTime)xValue).ToOADate();
}
........
if (series != null && xValue is DateTime)
{
if (series.XValueType == ChartValueType.Date)
{
DateTime dateTime = new DateTime(((DateTime)xValue).Year, ((DateTime)xValue).Month, ((DateTime)xValue).Day, 0, 0, 0, 0);
_xValue = dateTime.ToOADate();
}
else if (series.XValueType == ChartValueType.Time)
{
DateTime dateTime2 = new DateTime(1899, 12, 30, ((DateTime)xValue).Hour, ((DateTime)xValue).Minute, ((DateTime)xValue).Second, ((DateTime)xValue).Millisecond);
_xValue = dateTime2.ToOADate();
}
}
.....
If you use ChartValueType.Time the double value on X axis is between 0.0 (for 00:00:00.000) and 1.0 (for 23:59:59.999)
I don't like the Microsoft code using a date from 1899.
This can be done easier:
int s32_Millis = (((k_Time.Hour*60) +k_Time.Minute)*60 +k_Time.Second)*1000 +k_Time.Millisecond;
double d_OaTime = (double)s32_Millis / (24*60*60*1000);
double d_PixelPosX = ChartAreas[0].AxisX.ValueToPixelPosition(d_OaTime);
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 currently have a line graph in my C# program, and I have a min and max variable. If any the graph ever exceeds the max, or goes below the min, is there any built in way of displaying on the graph (such as a dot at the point) that the limit was passed, and display the x/y values for that point?
int max = 2000;
int min = 2000;
for (int i = 0; i < dgvLoadedValues.RowCount - 1; i++)
{
DateTime x = Convert.ToDateTime(dgvLoadedValues.Rows[i].Cells[0].Value.ToString());
try
{
float y = float.Parse(dgvLoadedValues.Rows[i].Cells[e.ColumnIndex].Value.ToString());
chart1.Series["Series1"].Points.AddXY(x, y);
}
catch
{
Console.WriteLine("Unable to plot point");
}
}
Code above simply shows values taken from a datagridview and displaying it into a line graph
Thank you
Unfortunately there seems to be no way to define such an automatic alert.
But as you know just when the DataPoints are added or bound you can set a Marker where necessary.
Here is a loop that does it after the fact in one go, but of course you can just as well set the markers as you add the points..:
foreach (DataPoint dp in chart1.Series[0].Points)
{
if (dp.YValues[0] < max && dp.YValues[0] > min ) continue;
dp.MarkerStyle = MarkerStyle.Circle;
dp.MarkerColor = Color.Red;
}
Or in your case:
try
{
float y = float.Parse(dgvLoadedValues.Rows[i].Cells[e.ColumnIndex].Value.ToString());
int i = chart1.Series["Series1"].Points.AddXY(x, y);
if (y < min || y > max)
{
chart1.Series["Series1"].Points[i].MarkerStyle = MarkerStyle.Circle;
chart1.Series["Series1"].Points[i].MarkerColor = Color.Red;
}
}
To clear a marker you can set its MarkerStyle = MarkerStyle.None.
Of course you could easily give the min and max points different colors..
Here is an example with the simple circle style, but there are others including images..:
To add the values in a label use a format like this:
dp.Label = "(#VALX{0.0} / #VAL{0.0})" ;
I am creating a extremely basic image editor, and trying to set a value for a color (R,G,B) numerically. For example, here is an fragment of my code:
for (int row = 0; row < thePicture.Width; row = row + 1)
{
for (int col = 0; col < thePicture.Height; col = col + 1)
{
Color pixel = thePicture.GetPixel(row, col);
pixel = Color.FromArgb(5 + pixel.R, 5 + pixel.G, 5 + pixel.B);
//+5 is making the image darker... I think
if (pixel.R > 255)//This is used to prevent the program from crashing
{
pixel.R = //is this possible? or another way? I am intending
} //Make this 255
thePicture.SetPixel(row, col, pixel);
}
}
Take mind that it's in windows forum.
Nothhing too advanced please, very basic understanding of C#. Thanks
From the MSDN article for System.Drawing.Color's .R property.
The property R is readonly (only the getter is defined).
Thus, a new color must be created.
Try pixel = Color.FromArgb(pixel.A, Math.Min(255, pixel.R + 5), pixel.G, pixel.B);
Explaination:
We're creating a new color using the previous color's A (Alpha), R (Red), G (Green), and B (Blue) property values.
However, in the case for R, instead of passing in the previous R value, we pass the adjusted R value. You can use Math.Min(x,y) to ensure the "brightened" R value doesn't exceed the maximum 255 value