Percent of Value graph in DevExpress Grid Cell - c#

I need to represent a percent of value as a graph in DevExpress grid cell. I am able to paint using DrawLine but my problem is as soon as the percent value is equal to greater than 1 it is treated as 100% in this code. Please find the code below, As shown in the screenshot, 3.59 should be shown less than 8.35! Please help.
private void CustomDrawCell(object sender, RowCellCustomDrawEventArgs args)
{
args.Appearance.DrawBackground(args.Graphics, args.Cache, args.Bounds);
if (column != null)
{
int penSize = args.Bounds.Height * 2 / 3;
double value = GetValue(); // This is the value against which I have to display the graph, its in %.
int left = args.Bounds.Left;
int middle = args.Bounds.Height / 2 + args.Bounds.Top;
int width = args.Bounds.Width;
int right = (int)(left + width * value);
args.Graphics.DrawLine(new Pen(Color.Green, penSize), left, middle, right, middle);
}
args.Handled = true;
}

int right = (int)(left + width * value);
This code calculates the bar length correctly only if the value is between 0 and 1. If values are between 0 and 100 or between 0 and 10, you need to divide the result of multiplying by 100 or 10 correspondingly.
int right = (int)(left + width * value / 100);
By the way, it is not necessary to puzzle over custom drawing, because you are using XtraGrid. There is the RepositoryItemProgressBar component, which can be embedded into XtraGrid cell. It displays the line according to the cell value and allows you to define the maximum and minimum value, so that the line is most exactly visualize the cell value. Read this article to learn how to assign editors to columns: Assigning Editors to Columns and Card Fields

Related

How to add a percentage to top of Column chart in C#

So, here's the problem.
I have a chart that displays two columns, Completed and Uncompleted, across a number of work types, using the following loop:
foreach (var workType in model.WorkTypes)
{
decimal completed = 0;
decimal uncompleted = 0;
decimal workSubmitted = 0;
decimal completionRate= 0;
foreach (var rec in model.JobList.Where(x => x.jobType== workType.Id))
{
uncompleted += model.JobList.SingleOrDefault(x => x.recID== rec.recID && x.jobType == workType.Id).Uncompleted;
completed += model.JobList.SingleOrDefault(x => x.recID == rec.recID && x.jobType == workType.Id).Completed;
}
workSubmitted = uncompleted + completed;
if (uncompleted != 0)
{
completionRate= (completed/ workSubmitted) * 100;
}
myChart.Series["Uncompleted"].Points.AddXY(workType.TypeName, uncompleted );
myChart.Series["Completed"].Points.AddXY(workType.TypeName, completed);
}
What I am trying to do is have it display a label above the two columns that displays the completionRate value as a percentage for each workType.
Any help or advice would be appreciated.
This is the current look of the chart:
By default the Labels show the y-value but you can set an arbitrary Label for each DataPoint e.g. when you add the point like this:
int p = myChart.Series["Uncompleted"].Points.AddXY(workType.TypeName, rejections);
myChart.Series["Uncompleted"].Points[p].Label = sometext;
And of course you can calculate the text for the label as needed, e.g.:
string sometext = (workSubmitted / rejections * 100).ToString("0.00") + "%";
Note that you must update the Label after changing the values in your calculation. No automatic expressions are supported!
Update
As I wrote, placing a Label centered at the x-value the columns share, is hard or even impossible; that is because a Label belongs to an individual data point. This is a unique problem with column (and bar) type charts, since here the points of the series are displayed in clusters around the common x-value. (We could workaround if and only if we had an odd number of series by adding the labels to the mid points)
So we need to use Annotations. Here is a function that will place a TextAnnotation centered at the x-value and at the height of the larger y-value of two data points..:
void setCenterAnnotation(Chart chart, ChartArea ca,
DataPoint dp1, DataPoint dp2, string lbl)
{
TextAnnotation ta = new TextAnnotation();
ta.Alignment = ContentAlignment.BottomCenter;
ta.AnchorAlignment = ContentAlignment.TopCenter;
DataPoint dp = dp1.YValues[0] > dp2.YValues[0] ? dp1 : dp2;
ta.Height = 0.36f;
ta.AxisX = ca.AxisX;
ta.AxisY = ca.AxisY;
ta.AnchorDataPoint = dp;
ta.AnchorX = dp1.XValue;
ta.Text = lbl;
chart.Annotations.Add(ta);
}
If you have more than two Series you would best determine the anchorpoint, i.e. the one with the larger value before, and pass it instead of the two points I pass here..
Placing/anchoring annotations is not really obvious, so here are a few notes:
I anchor to a DataPoint to make it show at the height of its y-value.
To use (axis-)values for anchoring one has to assign one or both axes to it.
I then (order matters!) set the AnchorX property so that it is not centered over a point but over the common x-value.
I also set some Height or else the text won't move up on top of the column; not quite sure what the rationale is here..
Here is the result:
I had added the anotations while adding the points:
int ix = s1.Points.AddXY(i, rnd.Next(i+7));
s2.Points.AddXY(i, rnd.Next(i+4)+3);
double vmax = Math.Max(s1.Points[ix].YValues[0], s2.Points[ix].YValues[0]);
string lbl = (vmax / 123f).ToString("0.0") + "%";
setCenterAnnotation(chart12, ca, s1.Points[ix], s2.Points[ix], lbl );

Chart : displaying boolean flags in a time plot as filled rectangles (instead of as lines)

I use the C# Chart in WinForms to plot a variety of variables in real time using the "line" chart type. That works well for analog values, but it's less than ideal for on/off flags.
I'd like to plot multiple flags as horizontal bars that are filled when the value is '1" and clear when the value is '0'.
Before I start coding a solution from scratch, do you have any suggestion on how I could take advantage of any features of the "chart" object to implement this more effectively?
EDIT: I am playing with the Area type, and it seems to be promising.
EDIT 2: That didn't work, because the area in the Area type always starts at the bottom of the chart, hiding the other rows. I am now trying the Range Column type
There are several ways to tackle this.: StackedBars, AreaChart, Annotations but I think by far the simplest is using a LineChartType.
The first issue is: How to create the gaps? The simplest way is to draw them as lines but with Color.Transparent. So instead of using the flag value as our y-value we use it to set the color..
So we could use a function like this:
void AddFlagLine(Chart chart, int series, int flag, int x)
{
Series s = chart.Series[series];
int px = s.Points.AddXY(x, series);
s.Points[px].Color = s.Color;
if (px > 0) s.Points[px - 1].Color = flag == 1 ? s.Color : Color.Transparent;
}
It takes the index of your Series and uses the flag to determine the color; note that the color of a line segment is controlled by the color of the end point.
So if you want to have the line going out from the new point to have its flag color, you need to set it when adding the next one..
This is simple enough and for lines as thick as 1-10 it works fine. But if you want larger widths things get a bit ugly..:
The rounded caps start to get bigger and bigger until they actually touch, flling the gaps more or less.
Unfortunately there seems to be no way to controls the caps-style of the lines. There are many CustomAttributes including DashStyles but not this one. So we have to resort to owner-drawing. This is rather simple for line charts. Here is an example:
The xxxPaint event looks like this:
private void chart_PostPaint(object sender, ChartPaintEventArgs e)
{
Graphics g = e.ChartGraphics.Graphics;
Axis ax = chart.ChartAreas[0].AxisX;
Axis ay = chart.ChartAreas[0].AxisY;
for (int si = 0; si < chart.Series.Count; si++ )
{
Series s = chart.Series[si];
for (int pi = 1; pi < s.Points.Count - 1; pi++)
{
DataPoint dp = s.Points[pi];
int y = (int) ay.ValueToPixelPosition(dp.YValues[0]+1); ///*1*
int x0 = (int)ax.ValueToPixelPosition(ax.Minimum);
int x1 = (int)ax.ValueToPixelPosition(s.Points[pi-1].XValue); ///*2*
int x2 = (int)ax.ValueToPixelPosition(dp.XValue);
x1 = Math.Max(x1, x0);
x2 = Math.Max(x2, x0);
using (Pen pen = new Pen(dp.Color, 40) ///*3*
{ StartCap = System.Drawing.Drawing2D.LineCap.Flat,
EndCap = System.Drawing.Drawing2D.LineCap.Flat })
{
g.DrawLine(pen, x1, y, x2, y);
}
}
}
A few notes:
1 : I have decided to move the the series up by one; this is up to you just as using or turning off the y-axis labels or replacing them by custom labels..
2 : Here we use the previous point's x-position!
3 : Note that instead of hard coding a width of 40 pixels you really should decide on a calculated width. This is an example that almost fills up the area:
int width = (int)( ( ay.ValueToPixelPosition(ay.Minimum) -
ay.ValueToPixelPosition(ay.Maximum)) / (chart7.Series.Count + 2));
You can twist is to fill more or less by adding less or more than 2.
I have turned all BorderWidths to 0 so only the drawn lines show.
I got it:
It turned out to actually be pretty easy; I used the Range Column type.
A) Set-up (done once):
plotChart.Series[chanNo].ChartType = SeriesChartType.RangeColumn;
plotChart.Series[chanNo].CustomProperties = "PointWidth=" + noOfFlags;
PointWidth is required to set the relative width of each rectangle so that it fills the entire width of one data point (if too small, there are gaps in the horizontal bar; if too large, there is overlap). noOfFlags is the number of flags shown (in the example shown above, noOfFlags = 4). (By the way the MSDN documentation is wrong: PointWidth is not limited to 2.)
B) Plotting (done for each new data point):
baseLine--;
int barHeight = flagHigh ? 1 : 0;
plotChart.Series[chanNo].Points.AddXY(pointX, baseLine, baseLine + barHeight);
flagHigh is a bool that is equal to the flag being monitored.
baseLine is decremented for each trace. In the example above, baseLine starts at 4, and is decremented down to 0.
Note that for each data point, RangeColumn requires 2 "Y" values: one for the bottom of the rectangle, one for the top; in the code, I set the bottom Y to the bottom of the row that I use for that particular flag, and the top to 1 above the bottom, to give me a height of 1.

How can I set an exact size (in centimeters) for an Excel cell programatically (C#)?

I intend to write some information to Excel from C#. The code I am using is:
var oXL = new Microsoft.Office.Interop.Excel.Application();
oXL.Visible = false;
var oWB = (Microsoft.Office.Interop.Excel._Workbook)(oXL.Workbooks.Add(""));
var oSheet = (Microsoft.Office.Interop.Excel._Worksheet)oWB.ActiveSheet;
oSheet.Columns.ColumnWidth = 5;
oSheet.Rows.RowHeight = 5;
oSheet.Cells[1, 1] = "Smith";
...
The problem is that I need to set an exact size in centimeters (5) for all the cells. The ColumnWidth property receives the number of points and not centimeters. In various articles I found that 1 point = 0.035cm, meaning that 1cm = 28.57p, but when I pass 5*28.57 for ColumnWidth and for RowHeight, Excel's columns are set to 28.58cm and rows are set to 5.04cm.
How can I solve this case?
Thank you,
The column width in Excel is specified in "Characters", not "Points". The default column width is 8.37 "Characters", 1 character being the width of the 1 character of the default font used to display normal text in Excel.
In order to specify the column width in cm, you need to first convert cm to points using the following function:
double x = xl.Application.CentimetersToPoints(5);
Then use one loop to repeatedly decrease the column width by 0.1 points until it matches the number of points (x). (For columns whose width is greater than 'x' points)
while(ws.Columns.ColumnWidth - 0.1 > points)
{
ws.Columns.ColumnWidth = ws.Columns.ColumnWidth - 0.1;
}
Then use another loop to repeatedly increase the column width by 0.1 points until it matches the number of points (x). (For columns whose width is lesser than 'x' points)
while(ws.Columns.ColumnWidth + 0.1 < points)
{
ws.Columns.ColumnWidth = ws.Columns.ColumnWidth + 0.1;
}
The above method takes a few seconds to execute because of the loops. However, it achieves the solution (setting column width in cm).
Note: In the above code,
xl : Excel application object
ws : Worksheet object

How do I set the charts Y axis to be dynamic with maximum value set at 125?

I have a charts populated in asp.net C#. I have multiple charts and each can range from 0 - 125.
which look like this if I do not set the maximum which one of it, the chart maximum will go to 140 if my series range max is 125.:
Thus I would like to set the maximum of Y axis to 125 however not staticly setting it for all charts. which mean I would like to make it dynamic still like the second chart image with the maximum set as 125.
Please advice thanks.
// Maximum value in Series, you may have to change YValues[0] to the appropriate
// index for your type of chart (so that it measures the top of your markers)
double maxValue = chart.Series[0].Points.Max(x => x.YValues[0])
// yAxisMax calculated as max value in series + 10 then rounded up to nearest 10
// but capped at 125 below. You could change this to perhaps
// maxValue * 1.1 to always get a 10 % extra above, anything goes
double yAxisMax = Math.Ceiling((maxValue + 10) / 10) * 10;
if (yAxisMax > 125)
yAxisMax = 125;
chart.ChartAreas[0].AxisY.Minimum = 0;
chart.ChartAreas[0].AxisY.Maximum = yAxisMax;
chart.ChartAreas[0].AxisY.Interval = 5;

MS Chart draw barchart

I need to draw a histogram, and columns with minimum and maximum values ​​should have other colors that are different from other columns and have different widths!
Now I have done this in two sets, but it's the wrong outcome.
Below is my code:
chart1.Series[0].Color = Color.Blue;
int y;
for (y = 0; y < lbls.Length; y++)
{
chart1.Series[0].Points.AddY(costs[y]);
chart1.ChartAreas[0].AxisX.CustomLabels.Add(new CustomLabel(y, y + 2, lbls[y], 0, LabelMarkStyle.LineSideMark));
}
chart1.Series[1].Color = Color.Red;
chart1.Series[1].Points.AddY(1500);
chart1.ChartAreas[1].AxisX.CustomLabels.Add(new CustomLabel(y+4, y + 6, "lol", 0, LabelMarkStyle.LineSideMark));
its give me:
Not too sure how to change specific column width but and to change a specific i points color do the below.
'Loop through series to find min and max and then color label
Chart1.Series[0].Points[i].Color = Color.Red
P.S.
I'm not sure if you can change just one columns width. I sometimes use Chart1.Series[0]("PixelPointWidth") = some integer to change the width of column series but looking at the msdn page it seems it can only be applied to the series and not individual datapoints.
PixelPointWidth # MSDN
You could just create a new series and add it to the chartarea with just the max and min values and then do Chart1.Series[1]("PixelPointWidth") = some integer to make them wider than the first series

Categories

Resources