X Axis as Time

Hi, after a couple of days of trying to get this I resort to asking. I have searched the forum and read the developer guide ad infinitum.

I am trying to plot numbers vs time. At the moment the Date/Time is column 0 of my AdvStringGrid and I am choosing column 3 as my values. Time format in col 0 is 16/02/2016 21:05:02 etc. Values in column 31 vary between and 24.
Question 1: The panes->Range->RangeFrom/RangeTo. I would have assumed these were TDateTime variables and indeed when I use the start and end times for my data the X axis shows those correct times. However it shows no data values. If I change RangeFrom/RangeTo to be integers, ie my sample numbers (1 through 1600) then I get the chart shown but the X Axis values are wrong. They start at 12/09/00 and go through 15/03/04. It seems I get one or the other.
Question 2: The function s->AddSinglePoint(latest,timeSample); should timeSample be a string or a TDateTime variable? Again it does not seem to affect the outcome above, the manual says TDateTime but some answers Bruno gives on the forum say it is just a string.

The code I am using is as follows. I don't think the casting I have done really does anything, but I tried everything I could.
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
Extended value;
double max,min,latest;
String St="12.5";
String strCell;
int cnt;
TDateTime rangeStart,rangeEnd,timeSample;
cnt = weatherChart->Panes->Count;
weatherChart->BeginUpdate();
weatherChart->Panes->Clear();
weatherChart->Panes->Add()->Series->Add();
int i;
TChartPane *p = weatherChart->Panes->Items[0];
TChartSerie *s = p->Series->Items[0];
s->LegendText = "Hello";
TextToFloat(St.c_str(),&value,fvExtended);
rangeStart = chartGrid->Cells[0][1];
for(cnt=1;cnt<chartGrid->RowCount-1;cnt++){
strCell = chartGrid->Cells[3][cnt];
TextToFloat(strCell.c_str(),&value,fvExtended);
latest = (double)value;
timeSample = (TDateTime)(chartGrid->Cells[0][cnt]);
s->AddSinglePoint(latest,timeSample);
if(cnt==1)rangeStart = timeSample;
rangeEnd = timeSample;
}
p->Range->RangeFrom = 1;//rangeStart;
p->Range->RangeTo = cnt-1;//rangeEnd;
p->XAxis->UnitType = utDay;
p->YAxis->AutoSize = arEnabled;
p->YAxis->AutoUnits = true;
s->ChartType = ctLine;
s->AutoRange = arEnabled;
s->LegendText = "Humidity";
s->XAxis->MajorUnitTimeFormat = "DD/MM/YY";
//max = s->SerieMax();
//min = s->SerieMin();
//s->SerieType = stBoth;
weatherChart->EndUpdate();
//p->CrossHair->CrossHairType = chtFullSizeCrossHairAtCursor;
//p->CrossHair->CrossHairYValues->ShowSerieValues;
//p->CrossHair->Visible = true;

}
//---------------------------------------------------------------------------




Hi, 


There are 2 demo's included in the distribution on how to add a datetime x-axis as well as a tip at the following page:

http://tmssoftware.com/site/advchart.asp?s=faq&show=293

You are correct that there are demos, which I looked at. However I don't think they really address the question. The thing that I missed was the Range.StartDate which in my case is 16/02/2016 21:00:02 so I assume that if I use the RangeFrom as 0 it sets this point as this Date/Time. Then depending on the value of XAxis->UnitType it adds a day(utDay) or a minute (uTMinute) to that DateTime to get the date/time for sample 1 etc. Your example is very simplistic in that it adds one day at a time and you have a complete data set. In my case the samples can occur at asynchronous times, but all registered by the correct DateTime stamp. Also some samples may be missing. Using the theory that I have arrived at above, AdvCharts cannot cope with my data. Is this so? Otherwise it has to associate each record, in your example 0 to 30 with the real TDateTime and it does not seem to do that in my case. Sorry if i am missing something but I can't see it

The Range.StartDate in combination with the UnitType, and the Range.RangeFrom / Range.RangeTo create an X-Axis with date/time values equally distributed. if Range.RangeTo is 60 and the unit type is utMinute it will create an X-Axis with 60 minutes starting from the Range.StartDate. You can add points by passing a timestamp within the Range.StartDate to Range.StartDate + 60 minutes to the AddSingleXYPoint overload, but then you will need to switch to ctXYLine instead.

Thanks Peter, I have been trying the AddXY points, was trying to make some sense of the format for it from the demo of XY time.  The developer manual just states that it is overloaded, all sorts of combinations seem to compile and give weird results. The Delphi code in the demo is

      begin
        d := EncodeTime(10, 0, 0, 0) + (1 / 24 / 60 * c);
        c := c + (Random(5) + 1);
        AddSingleXYPoint(0, Random(100), EncodeTime(10, I, 0, 0), d);
      end;
The developer manual has AddSingleXYPoint(SingleXPoint, SingleYPoint: double); so the X value is first, so for the demo
So do I assume that the first parameter is always 0? What is it? The second Random(100) must be the Y value which seems at odds with the manual, the third parameter is the X value as TDateTime, why a fourth parameter which is also TDateTime.  Please give me some help here, the function may be overloaded but so is my brain. Thanks

The AddSingleXYPoint can also be used for gantt charts, therefore 2 timestamp parameters can be used. For a ctXYLine, you can use the following overload, only using the Y-value and the first timestamp parameter:


AddSingleXYPoint(0, Random(100), d, 0);

Peter, taking all that into account, could you please explain why my code will not work. I am setting the UnitType to uTDay and the RangeTo as 3, so that should give me a range of 3 days from my start date? I am using the ChartType ctXYLine.

I then put in my values using s->AddSingleXYPoint(0,latest,timeSample,0); latest is type double, timeSample is type TDateTime. I don't see any values displayed on the chart, X axis is as I would expect, start date + the three days
TDateTime rangeStart,rangeEnd,timeSample;

cnt = weatherChart->Panes->Count;

weatherChart->BeginUpdate();
weatherChart->Panes->Clear();
weatherChart->Panes->Add()->Series->Add();

int i;
TChartPane *p = weatherChart->Panes->Items[0];
TChartSerie *s = p->Series->Items[0];
s->LegendText = "Hello";

TextToFloat(St.c_str(),&value,fvExtended);
rangeStart = chartGrid->Cells[0][1];
p->Range->StartDate = rangeStart;
p->Range->RangeFrom = 0;//rangeStart;
p->Range->RangeTo = 3;//cnt-1;//rangeEnd;
p->XAxis->UnitType = utDay;
p->YAxis->AutoSize = arEnabled;
p->YAxis->AutoUnits = true;
s->ChartType = ctXYLine;
s->AutoRange = arEnabled;
s->LegendText = "Humidity";
s->XAxis->MajorUnitTimeFormat = "dd/mm/yyyy";
for(cnt=1;cnt<chartGrid->RowCount-1;cnt++){
strCell = chartGrid->Cells[3][cnt];
TextToFloat(strCell.c_str(),&value,fvExtended);
if((double)value > max)max = value;
if((double)value < min)min = value;
latest = (double)value;
timeSample = (TDateTime)(chartGrid->Cells[0][cnt]);
// s->AddSinglePoint(latest,timeSample);
s->AddSingleXYPoint(0,latest,timeSample,0);
if(cnt==1)rangeStart = timeSample;
  // rangeEnd = timeSample;
}
//max = s->SerieMax();
//min = s->SerieMin();
//s->SerieType = stBoth;
weatherChart->EndUpdate();
 

Hi, 


The overload accepts additional parameters. It seems in C++ that the overload needs a specific type casting to TDateTime. Additionally, since the RangeTo is set to 3, it will only try to display 3 points, which means that you need to force the chart to use the full range of points. Included is a working sample on a default chart in C++

int cnt;
cnt = weatherChart->Panes->Count;

weatherChart->BeginUpdate();
weatherChart->Panes->Clear();
weatherChart->Panes->Add()->Series->Add();

int i;
TChartPane *p = weatherChart->Panes->Items[0];
TChartSerie *s = p->Series->Items[0];
s->LegendText = "Hello";

TDateTime rangeStart = EncodeDate(2016, 2, 26);
p->Range->StartDate = rangeStart;
p->Range->RangeFrom = 0;
p->Range->RangeTo = 3;
p->XAxis->UnitType = utDay;
p->YAxis->AutoSize = arEnabled;
p->YAxis->AutoUnits = true;
s->ChartType = ctXYLine;
s->AutoRange = arEnabled;
s->UseFullRange = True;
s->LegendText = "Humidity";
s->XAxis->MajorUnitTimeFormat = "dd/mm/yyyy";
for(cnt=0;cnt<25;cnt++){
TDateTime timeSample = IncHour(rangeStart, cnt * 3);
s->AddSingleXYPoint(0,Random(100),timeSample,TDateTime(0));
}
weatherChart->EndUpdate();

Thanks Peter, it works but I really can't see any difference to mine except the casting on the final parameter in the AddSingleXYPoint function. But I will chase it through and see where my error is.

Thanks for your help, I will report back

Peter, I am going to give up for the night now, getting late. Your example works, If I change your for(cnt=0 loop to pick up my data as follows;

for(cnt=1;cnt<chartGrid->RowCount-1;cnt++){
strCell = chartGrid->Cells[3][cnt];
TextToFloat(strCell.c_str(),&value,fvExtended);
latest = (double)value;
TDateTime timeSample = (TDateTime)chartGrid->Cells[0][cnt];
//TDateTime timeSample = IncHour(rangeStart, cnt * 3);
s->AddSingleXYPoint(0,latest,timeSample,TDateTime(0));
}
When I use your line with the IncHours, ie setting a regular increment, I get a chart displayed. If I substitute my line picking up the time from the grid, which is already in TDateTime format but I cast it again for good measure, I get X axis as expected, showing the dates for the 3 days and I get the Y axis scaled according to my data. But no trace. The only difference I can see is that you are using exact, regular, no missing sample data, I am using asynchronous, sample missing data. It is very late here so I am going to turn off the lights and go home but if you can see any difference I would certainly appreciate it.
Thanks

Hi, 


To skip data, you need to add an additional Defined parameter added to the AddSingleXYPoint overload, this in case there is no data for that specific time. 

My timeSample has resolution down to seconds as well, where you are only using to hours, but I would not have thought that was an issue. Thanks

Hi, 


You can add time in seconds, the data was only a quick sample, but when the datetime is 0, the data is always added, therefore you need to manually add an "undefined" point.

So can I clear this up. If I have to put in any missing points, which is a hassle since I would have to really go through my whole data set looking for missing points. What if the points are not falling completely synchronously. Ie, what if my time values are 10:15:00, 10:20:00, 10:22:00, 10:30:00 etc, is it going to cope with that, because I really will be using the AddXYPoint data with that irregularity. I suspect that is where the issue is? When I started out here I was expecting that the X axis was composed of TDateTime values but it really is a value representing the sample number and I think that is where my understanding is conflicting with TMS reality?


If that is the case, what if I use TDBAdvCharts? I am assuming that I can set my data base there to have a column which is the time value which could be asynchronous. Does it pick up and use the asynchronous, missing data or do I have to tidy it all up before calling the chart?

The values for the ctXYLine chart type can be added with different intervals. You should be able to add the values you mention without any issue, yet when having a point that doesn't not have a valid value or datetime, adding that point will result in a line drawn, therefore in your loop, you only need to make sure you are adding valid points, and add an undefined point for the invalid points. The DB chart currently doesn't support XY charts. This behavior would need to be manually implemented by using a first.. next statement and fetching the data from the database.

An undefined point is a point that is added through the AddSingleXYPoint overload, but with a True flag passed as a parameter to one of the overloads. Included is a sample that demonstrates. When running the sample you will notice a hole that is formed by undefined points. The undefined points are now simulated, but the check that is now if (cnt > 10 && cnt < 20) { should be replaced with a check if the points coming from the grid are valid, for example, if the timestamp in the grid is not an empty string. Additionally, you can see the timestamp that is being passed is randomly increased with a range from 1 to 10 hours. You only need to make sure that the next point that is added does not have a timestamp in the past (before the previous point).


int cnt;
cnt = weatherChart->Panes->Count;

weatherChart->BeginUpdate();
weatherChart->Panes->Clear();
weatherChart->Panes->Add()->Series->Add();

int i;
TChartPane *p = weatherChart->Panes->Items[0];
TChartSerie *s = p->Series->Items[0];
s->LegendText = "Hello";

TDateTime rangeStart = EncodeDate(2016, 2, 26);
p->Range->StartDate = rangeStart;
p->Range->RangeFrom = 0;
p->Range->RangeTo = 5;
p->XAxis->UnitType = utDay;
p->YAxis->AutoSize = arEnabled;
p->YAxis->AutoUnits = true;
s->ChartType = ctXYLine;
s->AutoRange = arEnabled;
s->UseFullRange = True;
s->LegendText = "Humidity";
s->XAxis->MajorUnitTimeFormat = "dd/mm/yyyy";
TDateTime timeSample = rangeStart;
for(cnt=0;cnt<30;cnt++){
timeSample = IncHour(timeSample, RandomRange(1, 10));
if (cnt > 10 && cnt < 20) {
s->AddSingleXYPoint(0,Random(100),timeSample,TDateTime(0), False);
}
else
{
s->AddSingleXYPoint(0,Random(100),timeSample,TDateTime(0));
}
}
weatherChart->EndUpdate();

Pieter Scheldeman2016-02-26 12:21:26

Thanks Peter, I will go through your example tomorrow. Late here. I have my code working. Problem was you had your start date as 26/2/2016 which I did not notice, the start dates in my table are 16//2/2016 which explains why the values were not showing as they were off the range.

I replaced my casting of my time cell with StrToDateTime(chartGrid->Cells[0][cnt]) and that is creating a proper point.
Home now, eyballs hanging on desk but thanks

Peter, when I add a cross hair to the project you provided at 11:03 am yesterday on this issue, using the code

s->ShowValueInTracker = true;
p->CrossHair->CrossHairType = chtSmallCrossHair;
p->CrossHair->CrossHairYValues->ShowSerieValues = true;
p->CrossHair->CrossHairYValues->Position = p->CrossHair->CrossHairYValues->Position << chValueTracker;
p->CrossHair->Visible = true;
The values at the cross hair do not correspond to the position on the chart, they represent values to the right of the crosshair by a substantial amount, about half a day. I saw this in my own code, suspected I had an error and then went back and ran your code again and see the same problem, can you see it too or is it something at this end. In my real data it corresponds to about 12 hours difference on the x axis. Is there a chance the crosshair is not converting between am and pm?
Thanks
robyn

I have found the answer to the last question. It involves the XScaleOffset factor. There is no mention of what this is in the developers manual. There is a brief mention that it was added as a new feature in the history. What does it do? By implementing the line p->XScaleOffset = false; i correct the problem but what am I actually doing. Is the offset settable somewhere?

The offset is not settable, the XScale starts at point 0 which is already offset by the widh of the series rectangle divided by the amount of visible points. When XScaleOffset is set to False, the first point starts at 0 pixels, instead of the XScale in pixels.