Painting ChartRect to a custom canvas

TeeChart VCL for Borland/CodeGear/Embarcadero RAD Studio, Delphi and C++ Builder.
Post Reply
Softdrill NL
Newbie
Newbie
Posts: 12
Joined: Thu Dec 28, 2017 12:00 am

Painting ChartRect to a custom canvas

Post by Softdrill NL » Sat Apr 21, 2018 2:20 pm

I have been trying to achieve the following, but I can't get close (unfortunately the Help is correctly installed and/or helpful either):

I wish to develop a custom graphic object/component that embeds a TChart (just the Chart Rectangle that is). In its most simple form, it would look like show below.
Note I have shrunk the "graph" slightly in order to show all borders. Other (derived) objects will be more complex, obviously.
Example 1.png
Example 1.png (58.03 KiB) Viewed 28490 times
The idea is that the ChartRect (re border area) of an "off-screen" TChart (memory) is either copied or drawn to a different canvas (controlled by the custom object/component).
So far I have tried to simply copy the ChartRect to a custom bitmap (TImage), but I have not been successful.
Example 2.png
Example 2.png (63.17 KiB) Viewed 28489 times
The code I have used for this (in many variations) is attached to the button:

Code: Select all

procedure TForm1.Button1Click(Sender: TObject);
var
  aRect: TRect;
  aBmp: TBitmap;
begin
  Image1.Width := Chart1.Width;
  Image1.Height := Chart1.Height;
  Chart1.Canvas.UseBuffer := false;
  aRect := Chart1.ChartRect;
  aBmp := Chart1.TeeCreateBitmap(clWhite, aRect);
  try
    Image1.Picture.Bitmap.Assign(aBmp);
  finally
    aBmp.Free;
  end;
end;
What would be the best approach for this? Ideally, I'd like to use the component for screen and printing (hence the canvas controlled by the custom object), so it should take into account the possibility of different resolutions.
The first mistake I just made was to assume that the aRect in

Code: Select all

aBmp := Chart1.TeeCreateBitmap(clWhite, aRect)
was the rectangle area clipped from the chart, where it turns out to be the size of the "destination".
Any steer in the right direction would be very welcome! I hope I described the issue clear enough, but if more info is required I will gladly supply it!

Thanks.

Yeray
Site Admin
Site Admin
Posts: 9614
Joined: Tue Dec 05, 2006 12:00 am
Location: Girona, Catalonia
Contact:

Re: Painting ChartRect to a custom canvas

Post by Yeray » Mon Apr 23, 2018 11:47 am

Hello,

You could use TeeChart Integration for QuickReport sources as reference. Concretely take a look at the QrTee.pas unit.
Best Regards,
ImageYeray Alonso
Development & Support
Steema Software
Av. Montilivi 33, 17003 Girona, Catalonia (SP)
Image Image Image Image Image Image Please read our Bug Fixing Policy

Softdrill NL
Newbie
Newbie
Posts: 12
Joined: Thu Dec 28, 2017 12:00 am

Re: Painting ChartRect to a custom canvas

Post by Softdrill NL » Wed Apr 25, 2018 7:49 am

Having reviewed QrTee.pas and using some code (BitBlt / StretchBlt) I have sort of managed to achieve the desired result.
I now create the bitmap (TeeCreateBitmap) from a TChart on the form and, using BitBlt/StretchBlt, I copy to the TImage, cropping the bitmap to the ChartRect.
This works fine, however I'm left with two issues to make this work for the project I have in mind;
  • 1. I wish to create the TChart in memory only and copy to a canvas as required. However, the TChart doesn't seem to "work" without a Parent window? Can this be done somehow?
  • 2. For efficiency, can the TChart be resized in a single command so that the ChartRectangle matches in size (WxH) with the rectangle area on the canvas I wish to copy to? I have seen a post on this forum discussing this, but unfortunately it was inconclusive (i.e. no clear example/instruction)
Alternatively, and probably easier, I could embed (paint on canvas as in QrTee.pas) an entire TChart in my graphic component I suppose.
However, is there any way to get rid of the "space" around the ChartRect (i.e. ChartRect = TChart.ClientRect)? I have tried several settings but there always remains some "margin"...

Hopefully you can give me some ideas / guidance on how I can achieve the desired result. Thanks!

Yeray
Site Admin
Site Admin
Posts: 9614
Joined: Tue Dec 05, 2006 12:00 am
Location: Girona, Catalonia
Contact:

Re: Painting ChartRect to a custom canvas

Post by Yeray » Fri Apr 27, 2018 3:24 pm

Hello,

I've done a simple test and it seems to work for me here. In a new application, just drop a TImage in the form and use this code:

Code: Select all

uses Chart, Series;

procedure TForm1.FormCreate(Sender: TObject);
var Chart1: TChart;
begin
  Chart1:=TChart.Create(nil);
  Chart1.AddSeries(TBarSeries).FillSampleValues;

  Image1.Picture.Bitmap:=Chart1.TeeCreateBitmap(clWhite, Rect(0,0,Image1.BoundsRect.Right-Image1.BoundsRect.Left,Image1.BoundsRect.Bottom-Image1.BoundsRect.Top));
end;
Isn't it doing what you are trying to achieve?
Best Regards,
ImageYeray Alonso
Development & Support
Steema Software
Av. Montilivi 33, 17003 Girona, Catalonia (SP)
Image Image Image Image Image Image Please read our Bug Fixing Policy

Softdrill NL
Newbie
Newbie
Posts: 12
Joined: Thu Dec 28, 2017 12:00 am

Re: Painting ChartRect to a custom canvas

Post by Softdrill NL » Mon Jul 16, 2018 11:37 am

Hello Yeray,

First of all: apologies for the late reply, but this is an experimental project I'm working on and more urgent projects required my attention first.
I have experimented a little further and I have now found that (creating a "memory only" TChart) the problem lies in setting/getting the ChartRect.

I create the TChart as follows (imgChart is a DevExpress TcxImage control):

Code: Select all

procedure TForm1.FormCreate(Sender: TObject);
begin
  FChart := TChart.Create(nil);
  FChart.Width := FChart.Width + (imgChart.ClientRect.Width - FChart.ChartRect.Width);
  FChart.Height := FChart.Height + (imgChart.ClientRect.Height - FChart.ChartRect.Height);

  // Set some chart properties, create series, fill random values, etc.

end;
I am trying to achieve that the ChartRect is the same dimension as the Image component (its Bitmap that is).

I then have a button to copy the chart to the TcxImage's bitmap:

Code: Select all

  imgChart.Picture.Bitmap := FChart.TeeCreateBitmap(clWhite, FChart.ChartRect);
This code "fails" because FChart.ChartRect returns (0, 0, 0, 0), so nothing appears in the TcxImage. Changing to FChart.TeeCreateBitmap(clWhite, FChart.BoundsRect) makes it work but it copies the entire chart where I only wish to copy the ChartRect.

How to obtain the bounds rectangle for the chart area of the off-screen TChart? Any suggestions?

Regards,
Mark

Softdrill NL
Newbie
Newbie
Posts: 12
Joined: Thu Dec 28, 2017 12:00 am

Re: Painting ChartRect to a custom canvas

Post by Softdrill NL » Mon Jul 16, 2018 2:07 pm

Hello Yeray,

After some further experimenting, I am almost there. The trick was to use FChart.CustomChartRect := true; etc...
There is one small detail left to fix though. For the axes I am showing (PositionPercent = 100), there still seems to be a label margin included in the ChartRect. I have created the Chart with a red background, so this margin is visible as shown below;
Screenshot.png
Screenshot.png (69.63 KiB) Viewed 28265 times
Surely there must be a property (axis?) by which I can resize the ChartRect to get rid of this margin?
For your reference, I have attached the small sample program I have created (changed all to standard VCL controls).


Regards,
Mark
Attachments
ChartCopy.zip
(1.79 KiB) Downloaded 1058 times

Yeray
Site Admin
Site Admin
Posts: 9614
Joined: Tue Dec 05, 2006 12:00 am
Location: Girona, Catalonia
Contact:

Re: Painting ChartRect to a custom canvas

Post by Yeray » Fri Jul 20, 2018 8:47 am

Hello,
Softdrill NL wrote:there still seems to be a label margin included in the ChartRect
Yes, the margin for the labels is still reserved so you could calculate and apply it to your custom ChartRect
I've added the margin for the labels and ticks (and some hardcoded extra space to separate a bit the labels from the ticks). I've also had to do an intermediate repaint with TeeCreateBitmap to have a better result at the second repaint:
This seems to work fine for me:

Code: Select all

procedure TForm1.Button1Click(Sender: TObject);
begin
  FChart.Axes.Right.LabelsFormat.Visible := cbLabelsRight.Checked;
  FChart.Axes.Bottom.LabelsFormat.Visible := cbLabelsBottom.Checked;
  FChart.CustomChartRect := true;
  FChart.ChartRect := imgChart.ClientRect;
  FChart.ChartRect.Right:=FChart.ChartRect.Right+FChart.Axes.Right.MaxLabelsWidth+FChart.Axes.Right.TickLength+2;
  FChart.ChartRect.Bottom:=FChart.ChartRect.Bottom+Abs(FChart.Axes.Bottom.LabelsFont.Height)+FChart.Axes.Bottom.TickLength+5;
  FChart.TeeCreateBitmap(clWhite, FChart.ChartRect);
  imgChart.Picture.Bitmap := FChart.TeeCreateBitmap(clWhite, FChart.ChartRect);
end;
Best Regards,
ImageYeray Alonso
Development & Support
Steema Software
Av. Montilivi 33, 17003 Girona, Catalonia (SP)
Image Image Image Image Image Image Please read our Bug Fixing Policy

Softdrill NL
Newbie
Newbie
Posts: 12
Joined: Thu Dec 28, 2017 12:00 am

Re: Painting ChartRect to a custom canvas

Post by Softdrill NL » Fri Jul 20, 2018 11:30 am

Hello Yeray,

It's still not functioning the it should. I have slightly modified your proposed approach and added a design-time (visual) TChart.
This results in in two notable issues;

1. If you click the button ('Chart to Image') twice (without changing form size), you'll see a distinct difference.
2. If you click the other button ('Rectangle'), you'll see a red rectangle, sized to the ChartRect of the visual TChart - exactly the area I want to copy (as bitmap) from the off-screen TChart.

It appears there's a difference between on-screen and off-screen, which is rather unpredictable...

Regards,
Mark
Attachments
ChartCopy_Rev1.zip
(6.61 KiB) Downloaded 1019 times

Yeray
Site Admin
Site Admin
Posts: 9614
Joined: Tue Dec 05, 2006 12:00 am
Location: Girona, Catalonia
Contact:

Re: Painting ChartRect to a custom canvas

Post by Yeray » Fri Jul 27, 2018 1:56 pm

I've removed the chart from the right and added the main chart into a page in a new TPageControl.
Adjusting the hardcoded offsets seems to work fine for me here.
ChartCopy_Rev1.zip
(2.19 KiB) Downloaded 988 times
Best Regards,
ImageYeray Alonso
Development & Support
Steema Software
Av. Montilivi 33, 17003 Girona, Catalonia (SP)
Image Image Image Image Image Image Please read our Bug Fixing Policy

Softdrill NL
Newbie
Newbie
Posts: 12
Joined: Thu Dec 28, 2017 12:00 am

Re: Painting ChartRect to a custom canvas

Post by Softdrill NL » Sun Aug 26, 2018 11:25 am

Hi Yeray,

Again apologies for the late reply. Single-handed programmer working on / maintaining different applications, hence priorities change continuously :wink:

This is still not working for me the way I would like to see it. Let me therefore try and explain in text what I am trying to achieve:

I want to create a custom object that handles drawing to a canvas (could be screen, could be printer). As part of this object I want to define a rectangle area (ChartRect) in which I want to copy (paint) the chart area of an off-screen (memory) chart. In the sample application, it would be the area in the on-screen chart indicated by the red rectangle show when the [Rectangle] button is clicked.

In order to achieve this, I need to do two things which I haven't succeeded in yet:
  • Create a TChart in memory and size it so that the ChartRect is the correct size (defined by the custom object)
  • Copy (paint) the ChartRect to the canvas in the rectangle defined by the custom object without the extra margin
Hope this better explains my "problem" and that you can give me some guidance how to achieve this. With an on-screen chart it works, but with an off-screen chart I keep getting the extra margin (i.e. distorting the end result).

Thanks in advance,
Mark

Yeray
Site Admin
Site Admin
Posts: 9614
Joined: Tue Dec 05, 2006 12:00 am
Location: Girona, Catalonia
Contact:

Re: Painting ChartRect to a custom canvas

Post by Yeray » Mon Sep 03, 2018 11:02 am

Hello,

From the example above, now I'm creating a new chart exactly like the first one but without a parent. This new chart (FChartMem) generates the same image than the chart with a parent (FChart):

ChartCopy_Rev2.zip
(2.36 KiB) Downloaded 1105 times
Even more, you can comment the creation of the chart with parent (FChart) and the memory chart still produces the same image.

Code: Select all

  //FChart:=CreateChart(TabSheet1);
Best Regards,
ImageYeray Alonso
Development & Support
Steema Software
Av. Montilivi 33, 17003 Girona, Catalonia (SP)
Image Image Image Image Image Image Please read our Bug Fixing Policy

Softdrill NL
Newbie
Newbie
Posts: 12
Joined: Thu Dec 28, 2017 12:00 am

Re: Painting ChartRect to a custom canvas

Post by Softdrill NL » Mon Sep 03, 2018 5:20 pm

Hello Yeray,

Thanks for the modified project. Unfortunately, it still didn't work as required but I have now changed it myself and I'm almost there.
Attached my Rev 3 of the project as zip file. Please refer to the included PDF with screenshots). There are two small issues remaining:

The first issue concerns the rectangle copied. If you set a break at the InflateRect command, you'll see that FChartMem.Axes.Right.MaxLabelsWidth returns different values the first and subsequent times the routine is called (i.e. button clicked). As a result the right side of the chart is blank the first time. I have extracted ChartSetup in order to call it after resizing ChartRect. I suspect it has to do with the chart not being fully drawn the first time, but how to force this (calling ChartSetup once more doesn't seem to have effect)?

The second issue (although less important) is the -50 label in both charts (circled). This appears, even though ChartSetup sets the axis to Automatic = false and Min & Max to 0 & 1000 respectively.

Hopefully you can help me "iron out" these small issues. Thanks in advance for your time and patience!

Regards,
Mark
Attachments
ChartCopy_Rev3.zip
(120.22 KiB) Downloaded 1027 times

Yeray
Site Admin
Site Admin
Posts: 9614
Joined: Tue Dec 05, 2006 12:00 am
Location: Girona, Catalonia
Contact:

Re: Painting ChartRect to a custom canvas

Post by Yeray » Wed Sep 05, 2018 1:21 pm

Hello Mark,
Softdrill NL wrote:
Mon Sep 03, 2018 5:20 pm
The first issue concerns the rectangle copied. If you set a break at the InflateRect command, you'll see that FChartMem.Axes.Right.MaxLabelsWidth returns different values the first and subsequent times the routine is called (i.e. button clicked). As a result the right side of the chart is blank the first time. I have extracted ChartSetup in order to call it after resizing ChartRect. I suspect it has to do with the chart not being fully drawn the first time, but how to force this (calling ChartSetup once more doesn't seem to have effect)?
Indeed, forcing a chart repaint before retrieving that MaxLabelsWidth property solves the issue for me here:

Code: Select all

  FChartMem.TeeCreateBitmap;
Softdrill NL wrote:
Mon Sep 03, 2018 5:20 pm
The second issue (although less important) is the -50 label in both charts (circled). This appears, even though ChartSetup sets the axis to Automatic = false and Min & Max to 0 & 1000 respectively.
I see you've set a MinimumOffset for the Right axis. Remove that and the "-50" label will disappear.

Code: Select all

  //FChartMem.Axes.Right.MinimumOffset := 50;
Best Regards,
ImageYeray Alonso
Development & Support
Steema Software
Av. Montilivi 33, 17003 Girona, Catalonia (SP)
Image Image Image Image Image Image Please read our Bug Fixing Policy

Softdrill NL
Newbie
Newbie
Posts: 12
Joined: Thu Dec 28, 2017 12:00 am

Re: Painting ChartRect to a custom canvas

Post by Softdrill NL » Fri Sep 07, 2018 8:59 am

Hi Yeray,

Those two things did the trick; everything is working as I need it now!
Thanks for the excellent support.

Regards,
Mark

Post Reply