Page 1 of 2
Issue with drawing of series mark
Posted: Wed Mar 09, 2016 3:55 pm
by 16576844
Hi,
I have a TFastLineSeries that, for every 50th value entered, should display a single character at the bottom of the chart (but above the x-axis). I've got everything working as I intended, except for a small drawing issue. I'm not sure if it is "as designed" or an issue I've overlooked.
The way I've implemented is as follows:
From a timer event I call this:
Code: Select all
procedure TForm1.RealTimeAdd(Series: TChartSeries);
var
XValue,YValue : Double;
aIndex: integer;
begin
if Series.Count = 0 then begin // First random point
YValue := Random(50);
XValue := now;
end else begin
// Next point
YValue := Series.YValues.Last + Random(10) - 4.5;
YValue := Max(Min(YValue, 100), 0);
XValue := now;
end;
// Add new point
aIndex := Series.AddXY(XValue, YValue);
//For every 50th value add a mark
if Series.Count mod 50 = 0 then begin
Series.Marks.Item[aIndex].Text.Text := 'A'; //Just show an 'A' for now
end;
end;
And then I've implemented the Chart's OnAfterDraw event like this:
Code: Select all
procedure TForm1.chMainAfterDraw(Sender: TObject);
var
APosition: TSeriesMarkPosition;
i: Integer;
begin
inherited;
APosition := TSeriesMarkPosition.Create;
try
begin
for i := 0 to Series1.Count - 1 do begin
APosition.Custom := true;
APosition.LeftTop.x := Series1.CalcXPos(i);
APosition.LeftTop.y := Series1.CalcYPosValue(8); //Static
Series1.Marks.Positions[i] := APosition;
end;
end;
finally
APosition.Free;
end;
end;
Screenshot of output:
Please see the small dot right above the 'A'. Why is that there, and what to do to get rid of it?
[img]
- SupportSteema001.PNG (20.65 KiB) Viewed 29203 times
[/img]
Thanks in advance.
Regards,
Leo
Re: Issue with drawing of series mark
Posted: Wed Mar 09, 2016 4:19 pm
by yeray
Hello Leo,
I've added a TChart and a TTimer to a new form and the following code (basically your code plus series and timer initialization):
Code: Select all
uses Series, Math, TeeConst;
procedure TForm1.Chart1AfterDraw(Sender: TObject);
var
APosition: TSeriesMarkPosition;
i: Integer;
begin
inherited;
APosition := TSeriesMarkPosition.Create;
try
begin
with Chart1[0] do
begin
for i := 0 to Chart1[0].Count - 1 do begin
APosition.Custom := true;
APosition.LeftTop.x := CalcXPos(i);
APosition.LeftTop.y := CalcYPosValue(8); //Static
Marks.Positions[i] := APosition;
end;
end;
end;
finally
APosition.Free;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Chart1.Title.Text.Text:=TeeMsg_Version;
Chart1.View3D:=false;
Chart1.Legend.Visible:=false;
Chart1.Axes.Bottom.DateTimeFormat:='hh:mm:ss';
with Chart1.AddSeries(TFastLineSeries)do
begin
XValues.DateTime:=true;
Marks.Visible:=true;
Marks.Style:=smsLabel;
end;
Timer1.Interval:=100;
Timer1.Enabled:=true;
end;
procedure TForm1.RealTimeAdd(Series: TChartSeries);
var
XValue,YValue : Double;
aIndex: integer;
begin
if Series.Count = 0 then begin // First random point
YValue := Random(50);
XValue := now;
end else begin
// Next point
YValue := Series.YValues.Last + Random(10) - 4.5;
YValue := Max(Min(YValue, 100), 0);
XValue := now;
end;
// Add new point
aIndex := Series.AddXY(XValue, YValue);
//For every 50th value add a mark
if Series.Count mod 50 = 0 then begin
Series.Marks.Item[aIndex].Text.Text := 'A'; //Just show an 'A' for now
end;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
RealTimeAdd(Chart1[0]);
end;
And this is what I'm getting:
- test.png (27.5 KiB) Viewed 29189 times
I can't see any dot above the "A"s.
Am I missing any setting? Are you using the latest version available?
Re: Issue with drawing of series mark
Posted: Wed Mar 09, 2016 4:23 pm
by yeray
Hello again,
I now see it is reproducible in FMX, not in VCL.
We'll be back to you asap.
Re: Issue with drawing of series mark
Posted: Thu Mar 10, 2016 3:25 pm
by 16576844
Hi,
Thanks for checking out the issue.
I just noticed that the issue disappears when app is run on the actual device. But the issue is still there for the FMX on Windows version.
Regards,
Leo
Re: Issue with drawing of series mark
Posted: Fri Mar 11, 2016 9:59 am
by yeray
Hi Leo,
I found what happens.
Since you are assigning new Positions to the marks, they are being used. However, the positions (TSeriesMarkPosition) include Width and Height and you aren't initializing them so they are 0.
Then, depending on the framework a rectangle with width=0 and height=0 is drawn (as a dot) or not.
To fix that, you can calculate the Widht and Height:
Code: Select all
APosition.Width:=Chart1.Canvas.TextWidth(Labels[i])+7;
APosition.Height:=Chart1.Canvas.TextHeight(Labels[i])+5;
Or you can just hide the mark rectangles after creating the series:
Code: Select all
Chart1[0].Marks.Transparent:=true;
Re: Issue with drawing of series mark
Posted: Tue Dec 13, 2016 2:37 pm
by 16579731
Thank you for your help, I just have a small followup question to the case above:
- Is there a way to set the position of the Mark(s), without having to recalc position in ChartAfterDraw?
(Seems a bit odd to have to run through all Series.Count and to find the Marks in use?)
Regards,
Leo
Re: Issue with drawing of series mark
Posted: Tue Dec 13, 2016 3:24 pm
by yeray
Hello Leo,
You are right, the Series' OnGetMarkText event could be a better place. Following the example above:
Code: Select all
Procedure TForm1.SeriesGetMarkText(Sender:TChartSeries; ValueIndex:Integer; var MarkText:String);
var
APosition: TSeriesMarkPosition;
begin
inherited;
APosition := TSeriesMarkPosition.Create;
try
begin
with Chart1[0] do
begin
APosition.Custom := true;
APosition.LeftTop.x := CalcXPos(ValueIndex);
APosition.LeftTop.y := CalcYPosValue(8); //Static
Marks.Positions[ValueIndex] := APosition;
end;
end;
finally
APosition.Free;
end;
end;
Re: Issue with drawing of series mark
Posted: Mon Feb 13, 2017 2:10 pm
by 16579731
Hi,
Unsure if I should create a new support entry, or continue posting to this thread. My request is related to the current topic so I'll post here, forgive if that's not according to the rules.
Anyway,
Case: Delphi 10.1 Upd 2 + TChart Pro (v2016.19.161025) FMX on Android. Simple TFastLineSeries simulating a running graph. A Timer is used to add a chart value every 45ms. For every 50th point I draw a mark (1-one single charachter) close to the x-axis.
Issue 1: Please have a look at the attached movie to see the odd (incorrect drawing and flickering) changing the device from landscape to portrait. I have 3 different devices available (2 phones and a Pad) and the issue is reproducible on both phones. For the Pad I have not be able to reproduce so far ??
Issue 2: In my simple code example (also attached) the Marks are drawn for every 50th sample coming in. Why is the horisontal space between my marks not equally separated? Am I overlooking something? Maybe using a TTimer is not best approach....
Issue 3: Do you have any comments on the performance (lack of) of the chart drawing, as it does not seems to be anywhere as smooth as I would have hoped? Once again; Am I overlooking something or is this as expected?
How to run the code ex:
1. Start App and let it run for 60 seconds ++. This to come to the point in the app where it deletes "Past values" to simulate a running graph.
2. Now move device back and forth from landscape to portrait to see drawing issue
Regards,
Leif Eirik
Re: Issue with drawing of series mark
Posted: Tue Feb 14, 2017 9:37 am
by yeray
Hello,
I don't see any timer in your application and the RealTimeAdd and DoScrollPoints methods seem not to be called anywhere.
Is that the correct version of the test application?
Re: Issue with drawing of series mark
Posted: Tue Feb 14, 2017 10:59 am
by 16579731
Ahh, sorry about the incorrect demo project. Had to strip it down due to size limit on attachments and I must have zipped the wrong project.
Anyway, new project attached.
Re: Issue with drawing of series mark
Posted: Wed Feb 15, 2017 8:15 am
by yeray
Hello,
It seems to work fine for me here in a Nexus 4 running Android 7.1.1. See the video
here.
The Android version may be relevant in this issue.
Re: Issue with drawing of series mark
Posted: Wed Feb 15, 2017 11:37 am
by 16579731
Hi,
Well, that does unfortunately not help me much. Android 7.1.1 is not even available for our Samsung devices yet.
I've tested with Android 4.4.4, 5.0.1 and 6.0.1 For both version #4 and #6 I can see the issue.
Maybe I can ask if you have any suggestions as to think differently to solve the issue...maybe there is a different way to display a running graph with these characters (ID codes) displayed where they are? Maybe use a separate chart for the ID codes, maybe a separate series, use the feature split axis ?
May I be so bold as to ask for your recommendation? (performance is of importance)
Best regards,
Leif Eirik
Re: Issue with drawing of series mark
Posted: Wed Feb 15, 2017 4:04 pm
by yeray
Hello,
Leo_ wrote:I've tested with Android 4.4.4, 5.0.1 and 6.0.1 For both version #4 and #6 I can see the issue.
I've just tested your application with an Android 5.1.1 and I could reproduce the problem with the labels drawn at different distances (Issue 2, I'll investigate what's wrong there) but not the flickering problem (Issue 1).
Leo_ wrote:Maybe I can ask if you have any suggestions as to think differently to solve the issue...maybe there is a different way to display a running graph with these characters (ID codes) displayed where they are? Maybe use a separate chart for the ID codes, maybe a separate series, use the feature split axis ?
An alternative could be to use OnAfterDraw event, loop your series points in it and draw the texts manually instead of looping your series points inside OnGetMarkText event.
Ie:
Code: Select all
procedure TfrmMain.chMainAfterDraw(Sender: TObject);
var i: Integer;
tmpX, tmpY: Integer;
begin
for i := 0 to aSeries.Count - 1 do begin
if chMain[0].Marks.Item[i].Text.Text <> '' then
begin
tmpX:=chMain[0].CalcXPos(i);
tmpY:=chMain[0].CalcYPosValue(8);
chMain.Canvas.TextOut(tmpX, tmpY,chMain[0].Marks.Item[i].Text.Text,False);
end;
end;
end;
Leo_ wrote:May I be so bold as to ask for your recommendation? (performance is of importance)
Find
here some tips to improve the performance when drawing many points.
However, I don't think you can win much speed than the one you are getting. Why do you think that's slow?
Re: Issue with drawing of series mark
Posted: Thu Feb 16, 2017 10:34 am
by yeray
Hello,
Regarding the "Issue 1", the problem is you are adding a label each 50 points, and that's regular. But, you are adding values with an XValue that depends on "Now", called from a Timer. And this isn't so regular.
Here the test I did:
Code: Select all
procedure TfrmMain.chMainAfterDraw(Sender: TObject);
var i: Integer;
tmpX, tmpY: Integer;
tmpDiff: TDateTime;
begin
for i := 0 to aSeries.Count - 1 do begin
if chMain[0].Marks.Item[i].Text.Text <> '' then
begin
tmpX:=chMain[0].CalcXPos(i);
tmpY:=chMain[0].CalcYPosValue(8);
chMain.Canvas.TextOut(tmpX, tmpY, 'A', False);
chMain.Canvas.VertLine3D(tmpX, chMain.ChartRect.Top, chMain.ChartRect.Bottom, 0);
Dec(tmpY, 30);
chMain.Canvas.TextOut(tmpX, tmpY, FormatDateTime(chMain.Axes.Bottom.DateTimeFormat, chMain[0].XValue[i]), False);
if i>=50 then
begin
tmpDiff:=chMain[0].XValue[i] - chMain[0].XValue[i-50];
Inc(tmpY, 15);
chMain.Canvas.TextOut(tmpX, tmpY, '+'+FormatDateTime('s', tmpDiff)+'s', False);
end;
end;
end;
end;
When you let the app run, it add points at a constant time difference. However, if the CPU demand varies, the differences also vary. You can check it by rotating the device or switching to another app for a moment.
I'm not sure if it will satisfy your needs, but another possibility would be to add the labels when the 9 seconds have passed instead of each 50 points. Ie:
Code: Select all
var lastLabelIndex: Integer;
procedure TfrmMain.FormCreate(Sender: TObject);
begin
FNumOfEntries := 0;
lastLabelIndex:=0;
//..
procedure TfrmMain.RealTimeAdd(Series: TChartSeries);
var
XValue,YValue : Double;
CurrentIndex: integer;
begin
if Series.Count = 0 then begin // First random point
YValue := Random(50);
XValue := now;
end else begin
// Next point
YValue := Series.YValues.Last + Random(10) - 4.5;
YValue := Max(Min(YValue, 100), 0);
XValue := now;
end;
// Add new point
CurrentIndex := Series.AddXY(XValue,YValue);
if Series.XValue[CurrentIndex]-Series.XValue[lastLabelIndex] >= DateTimeStep[dtOneSecond]*9 then
begin
Series.Marks.Item[CurrentIndex].Text.Text:='A';
lastLabelIndex:=CurrentIndex;
end;
end;
procedure TfrmMain.DoScrollPoints(Series: TChartSeries);
begin
// Delete 'past' points.
if Series.XValues.Count > 0 then begin
while Series.XValues.First < IncSecond(now, -NUM_OF_SEC_T0_VEIW) do begin
Series.Delete(0);
Dec(lastLabelIndex);
end;
end;
// Adjust 'view'
AdjustView(Series);
end;
However, if you want to draw a mark each 9 regular seconds, it would be easier if you stored only a reference point and draw all the labels from there.
Re: Issue with drawing of series mark
Posted: Fri Feb 17, 2017 9:52 am
by 16579731
Thank you very much for your feedback, much appreciated.
1. Regarding the drawing issue of the characters (ID Codes): I just did a test where I create a second separate series for the ID Codes. It's a TPointSeries with Style set to "Nothing" to not show the point itself. Now I can add (Assign) a Mark to the point and use the default positioning and thus do not have to implement OnAfterDraw or Series.OnGetMarkText to reposition the Marks when the main graph is scrolling. It seems to work great, so I'll go for this solution.
2. Inconsistent spacing of marks: I agree, the combination of a TTimer and function the Now from the main Thread does not seem to be a good thing in this case. My incoming datastream for the real app (not the demo attached here) does not have a TimeStamp for the incoming datasamples as they are considered live data. Thus I have to use the "Now" function I think, but I'll wrap this part of my app in a separate thread. I would not be surprised if this solves the issue
3. Speed: I've done some more testing and on my three available devices it is actually the 10" pad that seems to suffer the most. This device is now running on Android 5.0.1. What I'll do:
- Improve my own code by using threads
- Follow the hints you provided in the link
- Upgrade the device to maybe Android 6
Once again thank you for the support.
regards,
Leo