Convert seconds to hhh:mm:ss in a chart - c#

I have a MsSql database which calculates the timespan between two dates in seconds. That works fine. I use this column afterwards in C# and write them in an array.
This array is the input for a chart later on.
So far this works well, but I cannot find a way to display the seconds in a format like hhh:mm:ss as the timespan can be greater than 24h.
I tried ChartArea.AxisY.LabelStyle.Format = "hhmmss"; but it does not work at all.
Does anybody has an idea how I could do that?
EDIT:
I add the data this way:
chart2.Series.Clear();
chart2.ChartAreas.Clear();
Series BoxPlotSeries = new Series();
ChartArea ChartArea2 = new ChartArea();
ChartArea ChartArea3 = new ChartArea();
chart2.ChartAreas.Add(ChartArea2);
chart2.ChartAreas.Add(ChartArea3);
ChartArea2.Name = "Data Chart Area";
ChartArea3.Name = "BoxPlotArea";
BoxPlotSeries.Name = "BoxPlotSeries";
BoxPlotSeries.ChartType = SeriesChartType.BoxPlot;
BoxPlotSeries.ChartArea = "BoxPlotArea";
chart2.Series.Add(BoxPlotSeries);
Series Input1 = new Series();
Input1.Name = "Input1";
Input1.ChartType = SeriesChartType.Point;
Input1.ChartArea = "Data Chart Area";
chart2.Series.Add(Input1);
chart2.Series["Input1"].Points.DataBindY(InputArray);
chart2.ChartAreas["BoxPlotArea"].AxisX.CustomLabels.Add(2, 0.0, "BoxPlot1");
chart2.Series["BoxPlotSeries"]["BoxPlotSeries"] = "Input1";
chart2.Series["BoxPlotSeries"]["BoxPlotShowMedian"] = "true";
chart2.Series["BoxPlotSeries"]["BoxPlotShowUnusualValues"] = "false";
chart2.Series["BoxPlotSeries"]["PointWidth"] = "0.5";
chart2.Series["BoxPlotSeries"].IsValueShownAsLabel = false;
ChartArea2.Visible = false;
ChartArea3.BackColor = Color.FromArgb(224,224,224);
//I tried to format it this way but it didn't work
//ChartArea3.AxisY.LabelStyle.Format = "{0:HHHmmss}";
chart2.ChartAreas["BoxPlotArea"].AxisX.LabelStyle.Angle = -90;
EDIT2:
And here's how I populate the input array
int[] InputArray = new int[1000000];
int c = 0;
con.Open();
dr = cmd.ExecuteReader();
if (dr.HasRows)
{
while (dr.Read())
{
int n;
if (int.TryParse(dr[0].ToString(),out n) == true)
{
InputArray[c] = Convert.ToInt32(dr[0].ToString());
c++;
}
}
}
if (c == 0) { c = 1; }
Array.Resize(ref InputArray, c - 1);
EDIT 3:
The Boxplot should look like this in the end:
In Excel the format to display hours greater than 24 is called "[h]:mm:ss;#"
EDIT4:
Thanks to #TAW I nearly managed to solve my problem. I made some adjustments to his solution and came up with this:
In the chart code block:
The Value "max" is set before.
ChartArea3.AxisY.MajorTickMark.Interval = addCustomLabels(ChartArea3, BoxPlotSeries, 60 * 60, max);
int addCustomLabels(ChartArea ca, Series series, int interval, int max)
{
int tickNo = 0;
ca.AxisY.CustomLabels.Clear();
if(max / interval > 10)
{
interval = (max / 10) - (max / 10) % (60*30);
tickNo = (max / 10) - (max / 10) % (60*30);
}
if (max / interval <= 2 )
{
interval = (max / 4) - (max / 4) % (60 * 15);
tickNo = (max / 4) - (max / 4) % (60 * 15);
}
for (int i = 0; i < max; i += interval)
{
CustomLabel cl = new CustomLabel();
cl.FromPosition = i - interval / 2;
cl.ToPosition = i + interval / 2;
cl.Text = hhh_mm_ss(i);
ca.AxisY.CustomLabels.Add(cl);
}
return tickNo;
}
My problem is now, that sometimes no axis lable (apart from 0:00) is shown even when the code runs through it without any problems.
Has anybody and idea what could be wrong?

Your task involves two parts:
displaying seconds in the hhh:mm:ss format
putting them as labels on the y-axis
There is no suitable date-time formatting string for this in c#, so we can't make use of the built-in automatic labels and their formatting.
There also no way to use expressions that call a function on the automatic labels, unfortunately.
So we can't use those.
Instead we will have to add CustomLabels. This is not very hard but does take a few steps..
But let's start with a function that converts an int to the hhh:mm:ss string we want; this should do the job:
string hhh_mm_ss(int seconds)
{
int sec = seconds % 60;
int min = ((seconds - sec)/60) % 60;
int hhh = (seconds - sec - 60 * min) / 3600;
return hhh > 0 ? string.Format("{2}:{1:00}:{0:00}", sec, min, hhh)
: min + ":" + sec.ToString("00");
}
Maybe it can be optimized, but for our purpose it'll do.
Next we need to create the CustomLabels. They will replace the normal axis labels and we need to add them in a separate loop over the data after each binding.
One special thing about them is their positioning. Which is smack between two values we need to give them: the FromPosition and ToPosition, both in the unit of the axis-values.
Another difference to normal, automatic Labels is that it is up to us to create as many or few of them as we need..
This function tries to create a number that will go up to the maximum y-value and space the CustomLabels at a given interval:
void addCustomLabels(ChartArea ca, Series series, int interval)
{
// we get the maximum form the 1st y-value
int max = (int)series.Points.Select(x => x.YValues[0]).Max();
// we delete any CLs we have
ca.AxisY.CustomLabels.Clear();
// now we add new custom labels
for (int i = 0; i < max; i += interval)
{
CustomLabel cl = new CustomLabel();
cl.FromPosition = i - interval / 2;
cl.ToPosition = i + interval / 2;
cl.Text = hhh_mm_ss(i);
ca.AxisY.CustomLabels.Add(cl);
}
}
The first parameters to call this are obvious; the last one however is tricky:
You need to decide to interval you want your labels to have. It will depend on various details of your chart:
the range of values
the size of the chart area
the size of the font of the axis
I didn't set any special Font in the function; CustomLabels use the same Font as normal axis labels, i.e. AxisY.LabelStyle.Font.
For my screenshot I prepared the chart like this:
ca.AxisX.Minimum = 0;
ca.AxisY.MajorTickMark.Interval = 60 * 60; // one tick per hour
addCustomLabels(ca, s, 60 * 30); // one label every 30 minutes
I have also added DataPoint Labels for testing to show the values..:
series.Points[p].Label = hhh_mm_ss((int)y) + "\n" + y;
Here is the result:

UPDATE: This answer may be quite useful for other readers, but it pretty much misses the OP's issues. I'll leave it as it stands, but it will not help in creating specially formatted y-axis labels..
Most Chart problems stem from invalid or useless x-values. The following discussion tries to help avoiding or getting around them..
A number is a number and you can't simply display it as a DateTime, or for that matter a TimeSpan.
So you need to add the X-Values as either DateTime or as double that contain values that can be converted to DateTime. The fomer is what I prefer..
So instead of adding the seconds directly add them as offsets from a given DateTime:
Change something like this
series.Points.AddXY(sec, yValues);
To this:
var dt = new DateTime(0).AddSeconds(sec);
series.Points.AddXY(dt, yValues);
Now you can use the date and time formatting strings as needed..:
chartArea.AxisX.LabelStyle.Format = "{mm:ss}";
You could also add them as doubles that actually are calculated from DateTimes via the ToOADate:
series.Points.AddXY(dt.ToOADate(), yValues);
But now you will have to set the ChartValueType.DateTime and probably also AxisX.IntervalType and AxisX.Interval to make sure the chart gets the formatting right..:
s.XValueType = ChartValueType.DateTime;
ca.AxisX.Interval = 5;
ca.AxisX.IntervalType = DateTimeIntervalType.Seconds;
ca.AxisX.LabelStyle.Format = "{mm:ss}";
Pick values that suit your data!
Note that the problem with your original code is that the X-Values internally always are doubles, but the seconds are not integer values in them but fractional parts; so you need some kind of calculation. That's what ToOADate does. Here is a short test that shows what one second actually does amount to as a OADate double :
Best add the X-Values as DateTimes so all further processing can rely on the type..
Update I just saw that you have finally added the real code to your question and that is uses Points.DataBindY. This will not create meaningful X-Values, I'm afraid. Try to switch to Points.DataBindXY! Of course the X-Values you bind to also need to follow the rules I have explained above..!
You can do a loop over your array and convert the numbers like I shown above; here is a simple example:
int[] seconds = new int[5] { 1, 3, 88, 123, 3333 };
double[] oaSeconds = seconds.Select(x => new DateTime(0).AddSeconds(x).ToOADate())
.ToArray();

If you are trying to show more than 2 digits of hour I think this should work for you
//yourTimeSpan is the TimeSpan that you already have
var hoursDouble = Math.Floor(yourTimeSpan.TotalHours);
string hours;
string minutes;
string seconds;
//check hours
if(hoursDouble < 10)
{
hours = string.Format("0{0}", hoursDouble);
}
else
{
hours = hoursDouble.ToString();
}
//check minutes
if (yourTimeSpan.Minutes < 10)
{
minutes = string.Format("0{0}", yourTimeSpan.Minutes);
}
else
{
minutes = yourTimeSpan.Minutes.ToString();
}
//check seconds
if (yourTimeSpan.Seconds < 10)
{
seconds = string.Format("0{0}", yourTimeSpan.Seconds);
}
else
{
seconds = yourTimeSpan.Seconds.ToString();
}
string formattedSpan = String.Format("{0}:{1}:{2}", hours, minutes, seconds);
Update: I think this should solve the problem you were seeing with single digit numbers

Related

C# CCS PIC - Enhance equations processing

I'm using a PIC 18F2550 programmed with CCS to do several things. The root of my problem is that one of my interruptions handles an equation that I need to process often, and that controls several variables. This equation process involves typecasting, it has int16's as input variables and it's result is an int8 variable. This process takes too long and it slows down my other interruptions, causing several problems.
How can I speed up this function as much as possible but without hindering it's accuracy as much?
void dynamicControl(){
refDelta = phRef - procSignal[0]; //all 3 are int16 variables.
int16 prOffset = procSignal[1] - 500; //Their values will change on every iteration
int16 _fixed(2) calcVar; //Tried using float and double here but with no success.
if (refDelta < 0) {
risePr = TRUE; dropPr = FALSE;
calcVar = ((int16 _fixed(2))prOffset / 65.00) * (int16 _fixed(2))(4 - refDelta) + 1;
nValPuls = (int8)calcVar;
} else if(refDelta > 0){
risePr = FALSE; dropPr = TRUE;
nValPuls = (refDelta - 1);
} else {
risePr = dropPr = FALSE;
nValPuls = 0x00;
}
if (refDelta < 10){
calcVar = ((int16 _fixed(2))prOffset / 100.00) * (int16 _fixed(2))(5 - refDelta) * 0.70 + 18.00;
pumpDutyLock = (int8)calcVar;
if(pumpDutyLock > 30) pumpDutyLock = 30;
} else { pumpDutyLock = 0x00; }
}
The 4 lines of code that slow down my code are the following. If I comment them all, the function runs at a good speed.
calcVar = ((int16 _fixed(2))prOffset / 65.00) * (int16 _fixed(2))(4 - refDelta) + 1;
nValPuls = (int8)calcVar;
calcVar = ((int16 _fixed(2))prOffset / 100.00) * (int16 _fixed(2))(5 - refDelta) * 0.70 + 18.00;
pumpDutyLock = (int8)calcVar;
I realize that typecasting takes a long time here, I've tried using float or double for calcVar, instead of int16 _fixed(2), with no progress.
EDIT: After some further research I've found that, as stated by the comments, most of these operations take at least 50 us to complete, which means my problem cannot be solved by modifying the equation. I've resorted to transfering most of the calculations over to the PC side of the system.

Average all PingReply RoundTripTime in array C# WPF

I'm working on a new program in C# WPF, and part of it includes averaging ten PingReply.RoundTripTime and dividing it by two to find the one-way trip time. I put all the PingReply instances in an array, arrayReply, and now want to perform the above operations. I've tried a bunch of things, but this is what I have right now:
//New integer replyCount for counting ping replies
int replyCount = 0;
long Atime = 0;
long time = 0;
//for each PingReply instance in arrayReply
foreach (PingReply r in arrayReply)
{
//set long integer (64-bit) Atime (time of ping) to the previous value of Atime
//plus half of the next RoundTripTime divided by 2 (for approx one-way distance)
long oneWayTime = r.RoundtripTime / 2;
long x = Atime + oneWayTime;
//add one to replyCount
replyCount++;
//Divide Atime by number of replies
time = x / replyCount;
}
pingAverage.Text = time.ToString();
However, this results in 0 every time. How can I fix this?
Use LINQ to average:
var time = arrayReply.Average(pr => pr.RoundtripTime) / 2;

Different label types on one axis in chart

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;
}

Line Chart Graphical Alert

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})" ;

ASP.Net Chart - Label With Extra Data Not Used In Chart?

I have an asp:Chart control and it is working great. I simply am passing it times (in millitary format) and then values that are average length in time of requests. The following code does what I need - almost (feel free to mark it up if I am going overboard for I am new to the chart control).
My data is in table for like the following:
Date by Hours 9:00 10:00 11:00 12:00 13:00 14:00 15:00 16:00 17:00 18:00
12/03/2010 8 43 53 55 33 46 51 60 50 9
Friday 1.773 1.337 1.242 1.239 1.340 1.191 1.479 1.223 1.178 1.516
Gives me a nice chart. My question is below this code:
List<double> yValues = new List<double>();
List<string> xValues = new List<string>();
// First and Last columns do not contain chartable data
for (int i = 1; i < dtResults.Columns.Count - 1; i++) {
double d;
if (double.TryParse(dtResults.Rows[1][i].ToString(), out d))
yValues.Add(d == 0 ? double.NaN : d);
else
yValues.Add(double.NaN);
} // foreach of the Average Time Values the chart
// foreach of the column names
for (int i = 1; i < dtResults.Columns.Count - 1; i++)
xValues.Add(dtResults.Columns[i].ColumnName);
this.Chart.Titles["Title1"].Text = string.Format(
"Average Request Time In Seconds On {0:MM/dd/yyyy} Between {1:HH} and {2:HH} In {3}",
this.DateRange.BeginDate.Value,
this.ucsTimePicker.BeginTime,
this.ucsTimePicker.EndTime,
this.SelectedSourceEnvironmentName
);
this.Chart.Series["Series1"].Points.DataBindXY(xValues, yValues);
this.Chart.Series["Series1"].ChartType = SeriesChartType.Line;
this.Chart.Series["Series1"].IsValueShownAsLabel = true;
this.Chart.Series["Series1"]["ShowMarkerLines"] = "true";
this.Chart.Series["Series1"].Label = "#VALY{0.000}"; // Make sure they have only 3 decimal places
this.Chart.ChartAreas["ChartArea1"].AxisX.IsMarginVisible = true;
this.Chart.ChartAreas["ChartArea1"].AxisX.Title = "Hours of the Day";
this.Chart.ChartAreas["ChartArea1"].AxisY.Title = "Time in Seconds";
// Handle styling when there is a Zero or missing value
this.Chart.Series["Series1"].EmptyPointStyle.Color = Color.Red;
this.Chart.Series["Series1"].EmptyPointStyle.BorderWidth = 3;
this.Chart.Series["Series1"].EmptyPointStyle.BorderDashStyle = ChartDashStyle.Dash;
this.Chart.Series["Series1"].EmptyPointStyle.MarkerStyle = MarkerStyle.Diamond;
this.Chart.Series["Series1"].EmptyPointStyle.MarkerColor = Color.Red;
this.Chart.Series["Series1"].EmptyPointStyle.MarkerSize = 8;
this.Chart.Series["Series1"].EmptyPointStyle.MarkerBorderColor = Color.Black;
this.Chart.Series["Series1"]["EmptyPointValue"] = "Zero";
There are labels showing (the decimal numbers in the table above) but what I want to do is have the label Also show the total number of requests which is the 2nd row of data in the table above. I was able to add the values to the chart with the code below:
for (int i = 1; i < dtResults.Columns.Count - 1; i++) {
int n;
if (int.TryParse(dtResults.Rows[0][i].ToString(), out n))
this.Chart.Series["Series1"].Points.AddY(n);
else
this.Chart.Series["Series1"].Points.AddY(0);
} // foreach of the Count of Request within the Hour values
That seemed to not throw any fits, but I couldn't access the values with the following adjustment:
this.Chart.Series["Series1"].Label = "#VALY{0.000}\n#VALY2{0}";
All I get is the original value (1.773) showing up twice.
So is there a way to add data to a Chart that is only for labeling purposes and then access it?
Okay, after no help here (which actually shocks me) I was able to figure it out with some help outside of this site. Essentially, I don't have to Add the "extra" data but I do have to modify each Label as opposed to just having the generic label like the following:
this.Chart.Series["Series1"].Label = "#VALY{0.000}"; // Make sure they have only 3 decimal places
I also had to take out the following line:
this.Chart.Series["Series1"].IsValueShownAsLabel = true;
So just for brevity, here is the entire code giving me the two line label where the first line shows the average (which is the actual chart data) and the second line is the count which is not in the data at all.
List<double> yValues = new List<double>();
List<string> xValues = new List<string>();
List<int> zValues = new List<int>();
// First and Last columns do not contain chartable data
for (int i = 1; i < dtResults.Columns.Count - 1; i++) {
double d;
if (double.TryParse(dtResults.Rows[1][i].ToString(), out d))
yValues.Add(d == 0 ? double.NaN : d);
else
yValues.Add(double.NaN);
} // foreach of the Average Time Values the chart
// foreach of the column names
for (int i = 1; i < dtResults.Columns.Count - 1; i++)
xValues.Add(dtResults.Columns[i].ColumnName);
this.Chart.Titles["Title1"].Text = string.Format(
"Average Request Time In Seconds On {0:MM/dd/yyyy} Between {1:HH} and {2:HH} In {3}",
this.DateRange.BeginDate.Value,
this.ucsTimePicker.BeginTime,
this.ucsTimePicker.EndTime,
this.SelectedSourceEnvironmentName
);
this.Chart.Series["Series1"].Points.DataBindXY(xValues, yValues);
/// This loop will setup the point labels in a two line format where the first line displays
/// the Average that the point is actually showing. The second line is data taken from the
/// results table and is not a part of the chart at all but is useful information and is the
/// Count of records in that time frame.
/// In order for this to work, the Series property IsValueShownAsLabel needs to be NOT True.
for (int i = 0; i < this.Chart.Series["Series1"].Points.Count; i++) {
int n = 0;
int.TryParse(dtResults.Rows[0][i + 1].ToString(), out n);
this.Chart.Series["Series1"].Points[i].Label = string.Format("Avg: #VALY{{0.000}}\nCount: {0}", n);
} // foreach of the Count of Request within the Hour values
this.Chart.Series["Series1"].ChartType = SeriesChartType.Line;
this.Chart.Series["Series1"]["ShowMarkerLines"] = "true";
this.Chart.ChartAreas["ChartArea1"].AxisX.IsMarginVisible = true;
this.Chart.ChartAreas["ChartArea1"].AxisX.Title = "Hours of the Day";
this.Chart.ChartAreas["ChartArea1"].AxisY.Title = "Time in Seconds";
// Handle styling when there is a Zero or missing value
this.Chart.Series["Series1"].EmptyPointStyle.Color = Color.Red;
this.Chart.Series["Series1"].EmptyPointStyle.BorderWidth = 3;
this.Chart.Series["Series1"].EmptyPointStyle.BorderDashStyle = ChartDashStyle.Dash;
this.Chart.Series["Series1"].EmptyPointStyle.MarkerStyle = MarkerStyle.Diamond;
this.Chart.Series["Series1"].EmptyPointStyle.MarkerColor = Color.Red;
this.Chart.Series["Series1"].EmptyPointStyle.MarkerSize = 8;
this.Chart.Series["Series1"].EmptyPointStyle.MarkerBorderColor = Color.Black;
this.Chart.Series["Series1"]["EmptyPointValue"] = "Zero";

Categories

Resources