TColorLineTool glitch

TeeChart VCL for Borland/CodeGear/Embarcadero RAD Studio, Delphi and C++ Builder.
Post Reply
fabnav
Newbie
Newbie
Posts: 17
Joined: Thu Sep 30, 2004 4:00 am

TColorLineTool glitch

Post by fabnav » Tue May 08, 2007 3:28 am

Hi,

I'm trying to use the TColorLineTool to draw horizontal lines at the bottom and top of the left axes.

It works, except in the case of a bar chart with visible marks. Bar charts with marks automatically rescale in order to fit the marks on the chart. I don't know why only bar charts do this. Other chart series types don't, though it might be nice if they did.

In any case, when the marks are visible, the color line with a style of clMaximum, isn't positioned at the top of the axis, but at the top of the bar.

I have included a simple unit that shows this. I would appreciate help in getting the line to always be positioned at the top of the axis, regardless of the series type, or marks visibility. Maybe there is some other way to do it.

Regards,

Bill


unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, TeEngine, Series, ExtCtrls, TeeProcs, Chart, StdCtrls;

type
TForm1 = class(TForm)
Chart1: TChart;
Series1: TBarSeries;
CheckBox1: TCheckBox;
procedure FormCreate(Sender: TObject);
procedure CheckBox1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

uses
TeeTools;

procedure TForm1.CheckBox1Click(Sender: TObject);
begin
Series1.Marks.Visible := CheckBox1.Checked;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
I: Integer;
ColorLineTool: TColorLineTool;
begin
for I := 0 to 10 do
Series1.Add(I, IntToStr(I));
Series1.Marks.Visible := CheckBox1.Checked;
{ Maximum line }
ColorLineTool := Chart1.Tools.Add(TColorLineTool.Create(Self)) as TColorLineTool;
ColorLineTool.Axis := Chart1.LeftAxis;
ColorLineTool.Style := clMaximum;
end;

end.



object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 407
ClientWidth = 617
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
PixelsPerInch = 96
TextHeight = 13
object Chart1: TChart
Left = 49
Top = 43
Width = 400
Height = 250
Title.Text.Strings = (
'TChart')
TabOrder = 0
object Series1: TBarSeries
Marks.Callout.Brush.Color = clBlack
Marks.Visible = True
Gradient.Direction = gdTopBottom
XValues.Name = 'X'
XValues.Order = loAscending
YValues.Name = 'Bar'
YValues.Order = loNone
end
end
object CheckBox1: TCheckBox
Left = 23
Top = 10
Width = 97
Height = 17
Caption = 'CheckBox1'
TabOrder = 1
OnClick = CheckBox1Click
end
end

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

Post by Yeray » Tue May 08, 2007 10:30 am

Hi Bill,

The problem is that when you have your axis maximum set to auto (Chart1.Axes.Left.AutomaticMaximum is true) the ChartRect is recalculated to fit the marks (if they're visible) but Chart1.Axes.Left.Maximum is not changed. So to draw your line at the top of the ChartRect you should replace this line:

Code: Select all

ColorLineTool.Style := clMaximum;
for this anotherone:

Code: Select all

ColorLineTool.Value := Chart1.Axes.Left.CalcPosPoint(Chart1.Axes.Left.IStartPos);
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

fabnav
Newbie
Newbie
Posts: 17
Joined: Thu Sep 30, 2004 4:00 am

Post by fabnav » Tue May 08, 2007 12:32 pm

I tried what you suggested but got the same result. I included my modified code below. Note: I moved the creation of the lines into the AfterDraw event. I also gave the top and bottom lines different colors to make it easier to see what is happening. With this code, the lines aren't right when I zoom either.

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, TeEngine, Series, ExtCtrls, TeeProcs, Chart, StdCtrls;

type
TForm1 = class(TForm)
Chart1: TChart;
Series1: TBarSeries;
CheckBox1: TCheckBox;
Series2: TPointSeries;
procedure FormCreate(Sender: TObject);
procedure CheckBox1Click(Sender: TObject);
procedure Chart1AfterDraw(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

uses
TeeTools;

procedure TForm1.Chart1AfterDraw(Sender: TObject);
var
AAxes: Integer;
ColorLineTool: TColorLineTool;
begin
Chart1.Tools.Clear;
for AAxes := 0 to Pred(Chart1.AxesList.Count) do
if (Chart1.AxesList.Items[AAxes].Title.Caption = 'Left 1') or
(Chart1.AxesList.Items[AAxes].Title.Caption = 'Custom 1') then
begin
{ Minimum line }
ColorLineTool := Chart1.Tools.Add(TColorLineTool.Create(Self)) as TColorLineTool;
ColorLineTool.Axis := Chart1.AxesList.Items[AAxes];
ColorLineTool.Style := clMinimum;
ColorLineTool.Pen.Color := clGreen;
ColorLineTool.Pen.Width := 2;
{ Maximum line }
ColorLineTool := Chart1.Tools.Add(TColorLineTool.Create(Self)) as TColorLineTool;
ColorLineTool.Axis := Chart1.AxesList.Items[AAxes];
ColorLineTool.Value := ColorLineTool.Axis.CalcPosPoint(ColorLineTool.Axis.IStartPos);
ColorLineTool.Pen.Color := clRed;
ColorLineTool.Pen.Width := 2;
end;
end;

procedure TForm1.CheckBox1Click(Sender: TObject);
begin
Series1.Marks.Visible := CheckBox1.Checked;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
I: Integer;
begin
for I := 0 to 10 do
begin
Series1.Add(I, IntToStr(I));
Series2.Add(I, IntToStr(I));
end;
Series1.Marks.Visible := CheckBox1.Checked;
Chart1.Draw;
end;

end.

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

Post by Yeray » Wed May 09, 2007 8:12 am

Hi Bill,

I'm sorry but I'm not sure to understand what exactly you need. Why have you moved the Lines creation to OnAfterDraw event? You want to have always a line at the top of the chart and anotherone at the bottom?
I've modified your new OnAfterDraw event to achieve it and it remains:

Code: Select all

procedure TForm1.Chart1AfterDraw(Sender: TObject);
var
AAxes: Integer;
ColorLineTool: TColorLineTool;
begin
  Chart1.Tools.Clear;
  for AAxes := 0 to Pred(Chart1.AxesList.Count) do
    if (Chart1.AxesList.Items[AAxes].Title.Caption = 'Custom 1') then
    begin
      { Minimum line }
      ColorLineTool := Chart1.Tools.Add(TColorLineTool.Create(Self)) as TColorLineTool;
      ColorLineTool.Axis := Chart1.AxesList.Items[AAxes];
      ColorLineTool.Style := clMinimum;
      ColorLineTool.Pen.Color := clGreen;
      ColorLineTool.Pen.Width := 2;
    end
    else if (Chart1.AxesList.Items[AAxes].Title.Caption = 'Left 1') then
    begin
      { Maximum line }
      ColorLineTool := Chart1.Tools.Add(TColorLineTool.Create(Self)) as TColorLineTool;
      ColorLineTool.Axis := Chart1.AxesList.Items[AAxes];
      ColorLineTool.Value := Chart1.AxesList.Items[AAxes].IEndPos;
      ColorLineTool.Pen.Color := clRed;
      ColorLineTool.Pen.Width := 2;
    end;
end;
And the form:

Code: Select all

object Form1: TForm1
  Left = 189
  Top = 86
  Width = 641
  Height = 434
  Caption = 'Form1'
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  OnCreate = FormCreate
  PixelsPerInch = 96
  TextHeight = 13
  object Chart1: TChart
    Left = 49
    Top = 43
    Width = 512
    Height = 310
    Title.Text.Strings = (
      'TChart')
    BottomAxis.Axis.Width = 1
    CustomAxes = <
      item
        Horizontal = False
        OtherSide = True
        Title.Caption = 'Custom 1'
      end>
    LeftAxis.Axis.Width = 1
    LeftAxis.Title.Caption = 'Left 1'
    View3D = False
    OnAfterDraw = Chart1AfterDraw
    TabOrder = 0
    object Series1: TBarSeries
      Marks.Arrow.Visible = True
      Marks.Callout.Brush.Color = clBlack
      Marks.Callout.Arrow.Visible = True
      Marks.Visible = True
      VertAxis = aCustomVertAxis
      Gradient.Direction = gdTopBottom
      XValues.Name = 'X'
      XValues.Order = loAscending
      YValues.Name = 'Bar'
      YValues.Order = loNone
      CustomVertAxis = 0
    end
    object Series2: TPointSeries
      Marks.Arrow.Visible = True
      Marks.Callout.Brush.Color = clBlack
      Marks.Callout.Arrow.Visible = True
      Marks.Visible = False
      ClickableLine = False
      Pointer.InflateMargins = True
      Pointer.Style = psRectangle
      Pointer.Visible = True
      XValues.Name = 'X'
      XValues.Order = loAscending
      YValues.Name = 'Y'
      YValues.Order = loNone
    end
  end
  object CheckBox1: TCheckBox
    Left = 23
    Top = 10
    Width = 97
    Height = 17
    Caption = 'CheckBox1'
    TabOrder = 1
    OnClick = CheckBox1Click
  end
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

fabnav
Newbie
Newbie
Posts: 17
Joined: Thu Sep 30, 2004 4:00 am

Post by fabnav » Wed May 09, 2007 12:27 pm

When you have multiple axes, one above the other, and in my application I allow up to four, it looks better when there is something to clearly mark where one axis ends and the next starts. I am trying to draw a thick line at the bottom and the top of each axis to achieve this effect. It should look as if I have multiple charts stacked one above the other.

It would look even better if I could place a shape (filled rectangle) between the axes, but I'm not sure how to do that.

Bill

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

Post by Yeray » Fri May 11, 2007 8:42 am

Hi Bill,

I achieved what you requested working with:
-StartPosition and EndPosition percents from Custom axes.
-Chart1.CustomAxes.Items.CalcPosPoint(Chart1.CustomAxes.Items.IEndPos) and IStartPos pixels from Custom Axes.
-Value pixels from Color Line Tools.
-Left, Width, Top, Height pixels from Rectangle Tools.

The following code shows you a complete example. I only added a chart, a combobox and a checkbox in designtime to show everything by code:

Code: Select all

procedure TForm1.FormCreate(Sender: TObject);
const nSeries = 4;
var i: Integer;
begin
  Chart1.View3D := false;
  Chart1.Title.Visible := false;
  Chart1.Legend.Visible := false;
  Chart1.MarginLeft := 5;
  Chart1.Axes.Bottom.Axis.Width := 1;

  for i:=0 to nSeries-1 do
  begin
    Chart1.AddSeries(TLineSeries);
    ComboBox1.Items.Add('Series' + inttostr(i+1));
    With Chart1.CustomAxes do
    begin
      Add;
      Chart1.Tools.Add(TColorLineTool);
      (Chart1.Tools.Items[i*2] as TColorLineTool).Axis := Items[i];
      Chart1.Tools.Add(TColorLineTool);
      (Chart1.Tools.Items[(i*2)+1] as TColorLineTool).Axis := Items[i];
      Chart1[i].CustomVertAxis := Items[i];
      Items[i].PositionUnits := muPixels;
      Items[i].Axis.Width := 1;
      Items[i].Axis.Color := Chart1[i].Color;
    end;
    Chart1[i].FillSampleValues();
  end;

  for i:=0 to Chart1.SeriesCount-2 do
  begin
    Chart1.Tools.Add(TRectangleTool);
    (Chart1.Tools.Items[i+Chart1.SeriesCount*2] as TRectangleTool).PositionUnits := muPixels;
  end;

  ComboBox1.ItemIndex := 0;
  Chart1.Draw;
  CalcPositions;

  for i:=0 to (Chart1.SeriesCount*2)-1 do
  begin
    ((Chart1.Tools.Items[i]) as TColorLineTool).AllowDrag := false;
  end;
end;

procedure TForm1.ComboBox1Change(Sender: TObject);
begin
  CheckBox1.Checked := Chart1[ComboBox1.ItemIndex].Marks.Visible;
end;

procedure TForm1.CheckBox1Click(Sender: TObject);
begin
  Chart1[ComboBox1.ItemIndex].Marks.Visible := CheckBox1.Checked;
  Chart1.Draw;
  CalcPositions;
end;

procedure TForm1.CalcPositions;
var percent, i: Integer;
begin
  percent := 100 div Chart1.SeriesCount;

  for i := 0 to Chart1.CustomAxes.Count-1 do
  begin
    With Chart1.CustomAxes do
    begin
      Items[i].StartPosition := (percent * i) + percent/5;
      Items[i].EndPosition := (percent * (i+1)) - percent/5;
      ((Chart1.Tools.Items[(i*2)+1]) as TColorLineTool).Value := Items[i].CalcPosPoint(Items[i].IEndPos);
      ((Chart1.Tools.Items[i*2]) as TColorLineTool).Value := Items[i].CalcPosPoint(Items[i].IStartPos);
    end;
  end;

  Chart1.Draw;

  for i := (Chart1.SeriesCount*2) to (Chart1.SeriesCount*2 + Chart1.SeriesCount-2) do
  begin
    With (Chart1.Tools.Items[i] as TRectangleTool), Chart1.CustomAxes do
    begin
      Left := Chart1.ChartRect.Left;
      Width := Chart1.ChartRect.Right-Chart1.ChartRect.Left+2;
      Top := Items[i-(Chart1.SeriesCount*2)].IEndPos;
      Height := Items[i-(Chart1.SeriesCount*2)+1].IStartPos - Items[i-(Chart1.SeriesCount*2)].IEndPos + 2;
      AllowDrag := false;
      AllowResize := false;
    end;
  end;
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

fabnav
Newbie
Newbie
Posts: 17
Joined: Thu Sep 30, 2004 4:00 am

Post by fabnav » Mon May 14, 2007 1:30 pm

I went with drawing rectangles between the axes in the AfterDraw event handler. What I needed to know, was where to place them, and I got that from looking at your code (IStartPos and IEndPos).

Thanks,

Bill

Post Reply