2

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: enter image description here

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?

FriendlyGuy
  • 331
  • 5
  • 14
  • What kind of chart are you using? – Jason Watkins Mar 29 '16 at 20:03
  • What is the UI framework and what is the full name of the type you are using? – Jason Watkins Mar 29 '16 at 20:04
  • So in C# you have a TimeSpan object? You can call [ToString()](https://msdn.microsoft.com/en-us/library/dd992632(v=vs.110).aspx) on that object to 1) get a string out and 2) have that string be in whatever format you want, e.g., `_theTimeSpanObject.ToString("hh:mm:ss")`, then feed that string into your label. Or you can create TimeSpan or DateTime object inputting the seconds to it, then calling ToString("hh:mm:ss") on the new object. – Quantic Mar 29 '16 at 20:04
  • Do you want hhh or hh? You have both listed in your question. – Tim Freese Mar 29 '16 at 20:09
  • I use the basic chart from the toolbox .Datavisualization.Charting.Chart In C# I have a double value from the database which represents the timespan in seconds. And as the boxplot chart calculates it's required values by the input, they cannot be strings. – FriendlyGuy Mar 29 '16 at 20:12
  • 1
    Czech this out: http://stackoverflow.com/questions/463642/what-is-the-best-way-to-convert-seconds-into-hourminutessecondsmilliseconds – B. Clay Shannon-B. Crow Raven Mar 29 '16 at 20:13
  • I want to show e.g. 90000 seconds as 25:00:00 – FriendlyGuy Mar 29 '16 at 20:21
  • You need to tell ie show us how you add the datapoints. The question is: __Are your x-values actually datetimes__ or at least numbers or just strings? – TaW Mar 29 '16 at 21:49
  • I have an array with double values (seconds) and add the complete array as y datapoints to the series – FriendlyGuy Mar 29 '16 at 21:57
  • Good to see the real code, finally. You will have to expand on the InputArray a little, though.. – TaW Mar 30 '16 at 08:56
  • I have seriously misread your problems; I am sorry. I may delete my original answer although all of it is quite true and potentially useful for others. But it completely misses your issue. I have added another answer, that offers what I consider the best option.. – TaW Mar 31 '16 at 09:46

3 Answers3

2

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:

enter image description here

TaW
  • 53,122
  • 8
  • 69
  • 111
  • Thank you very much, that helped a lot. And sorry for the late reply, I had a rough weekend with lots of work for my university... In most of the cases it works really great but as the times fluctuate a lot, I had to do some adjustments as the times sometimes vary between 30 Minutes and 400 Hours. In the main article I am going to write what I did now, maybe you see where I made a mistake :) – FriendlyGuy Apr 06 '16 at 10:30
1

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 :

enter image description here

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();
TaW
  • 53,122
  • 8
  • 69
  • 111
  • Hello @TaW, thank you very much for your input. I also thought about converting it to Datetime but this way I don't know how I can display time spans which are greater than 24 hours. Do you maybe also know how I could do that? Thanks in advance – FriendlyGuy Mar 30 '16 at 21:09
  • Do you mean the hours going over 23 instead of carrying over to the day? No, I don't think there is a time format for that. This imo would amount to a custom string and I think you would have to put those strings into CustomLabels. Those are somewhat tricky to place.. Or you could try to set the `AxisLabel` ot each DataPoint, calling a function you write. You will have to experiment with the Interval to show a suitable number of those labels.. – TaW Mar 30 '16 at 21:43
  • To be honest I am not a professional programmer, I just do that in my freetime and use it in university to deal with data. As the boxplot chart is calculating it's required values on it's own, I do not think that I can set the AxisLabels manually therefore I would probably need a custom string. Could you please give me a hint how I could do that? It would help me a lot as I don't have a clue how I can customize a string :) – FriendlyGuy Mar 31 '16 at 06:26
  • I also added a picture in the main question to show you how it should look like. In Excel I can achieve that with the following format "[h]:mm:ss;@" but unfortunately I cannot figure out how to do that in C# – FriendlyGuy Mar 31 '16 at 06:35
0

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

Tim Freese
  • 435
  • 3
  • 14
  • Well if I use string formattedSpan = String.Format("{0}:{1:00}:{2:00}", Convert.ToInt32(yourTimeSpan.TotalHours), Convert.ToInt32(yourTimeSpan.Minutes) % 60 , Convert.ToInt32(yourTimeSpan.Seconds) % 60); It shows me the hours in three digits but I don't know how I can do that in the chart. Can you please give me a hint how I could do that? – FriendlyGuy Mar 29 '16 at 20:35
  • Thanks Tim, my edited solution also does the same. I guess my major problem is the formatting of the axis label as I cannot use timespan there or is there a way? – FriendlyGuy Mar 29 '16 at 20:58