Page 1 of 1

Painting ChartRect to a custom canvas

Posted: Sat Apr 21, 2018 2:20 pm
by 16582633
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 28293 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 28292 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.

Re: Painting ChartRect to a custom canvas

Posted: Mon Apr 23, 2018 11:47 am
by yeray
Hello,

You could use TeeChart Integration for QuickReport sources as reference. Concretely take a look at the QrTee.pas unit.

Re: Painting ChartRect to a custom canvas

Posted: Wed Apr 25, 2018 7:49 am
by 16582633
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!

Re: Painting ChartRect to a custom canvas

Posted: Fri Apr 27, 2018 3:24 pm
by yeray
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?

Re: Painting ChartRect to a custom canvas

Posted: Mon Jul 16, 2018 11:37 am
by 16582633
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

Re: Painting ChartRect to a custom canvas

Posted: Mon Jul 16, 2018 2:07 pm
by 16582633
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 28068 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

Re: Painting ChartRect to a custom canvas

Posted: Fri Jul 20, 2018 8:47 am
by yeray
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;

Re: Painting ChartRect to a custom canvas

Posted: Fri Jul 20, 2018 11:30 am
by 16582633
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

Re: Painting ChartRect to a custom canvas

Posted: Fri Jul 27, 2018 1:56 pm
by yeray
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 978 times

Re: Painting ChartRect to a custom canvas

Posted: Sun Aug 26, 2018 11:25 am
by 16582633
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

Re: Painting ChartRect to a custom canvas

Posted: Mon Sep 03, 2018 11:02 am
by yeray
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 1096 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);

Re: Painting ChartRect to a custom canvas

Posted: Mon Sep 03, 2018 5:20 pm
by 16582633
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

Re: Painting ChartRect to a custom canvas

Posted: Wed Sep 05, 2018 1:21 pm
by yeray
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;

Re: Painting ChartRect to a custom canvas

Posted: Fri Sep 07, 2018 8:59 am
by 16582633
Hi Yeray,

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

Regards,
Mark