TChart: Introduce TChartAxisAlignment and TChartAxisList

* First step for adding support for multiple axises
* Does not yet change any user-visible behaviour

git-svn-id: trunk@20312 -
This commit is contained in:
ask 2009-05-30 15:17:35 +00:00
parent d99a3fc221
commit b991035ee7
2 changed files with 175 additions and 80 deletions

View File

@ -107,19 +107,15 @@ type
TChartSeriesList = class(TPersistent)
private
FChart: TChart;
FList: TFPList;
function GetItem(AIndex: Integer): TBasicChartSeries;
procedure SetItem(AIndex: Integer; const AValue: TBasicChartSeries);
public
function Count: Integer;
constructor Create(AOwner: TChart);
constructor Create;
destructor Destroy; override;
public
property Chart: TChart read FChart;
property Items[AIndex: Integer]: TBasicChartSeries
read GetItem write SetItem; default;
property Items[AIndex: Integer]: TBasicChartSeries read GetItem; default;
end;
{ TChart }
@ -130,14 +126,12 @@ type
FAxisColor: TColor;
FAxisVisible: Boolean;
FBackColor: TColor;
FBottomAxis: TChartAxis;
FDepth: TChartZPosition;
FExpandPercentage: Integer;
FExtent: TChartExtent;
FFoot: TChartTitle;
FFrame: TChartPen;
FGraphBrush: TBrush;
FLeftAxis: TChartAxis;
FLegend: TChartLegend;
FMargins: TChartMargins;
FOnDrawReticule: TDrawReticuleEvent;
@ -145,6 +139,7 @@ type
FTitle: TChartTitle;
private
FAxisList: TChartAxisList;
FClipRect: TRect;
FCurrentExtent: TDoubleRect;
FIsMouseDown: Boolean;
@ -157,6 +152,7 @@ type
procedure CalculateTransformationCoeffs(const AMargin: TRect);
procedure DrawReticule(ACanvas: TCanvas);
function GetAxis(AIndex: integer): TChartAxis; inline;
function GetChartHeight: Integer;
function GetChartWidth: Integer;
function GetLegendWidth(ACanvas: TCanvas): Integer;
@ -165,17 +161,16 @@ type
function GetSeriesInZOrder: TFPList;
procedure PrepareXorPen;
procedure SetAxis(AIndex: Integer; AValue: TChartAxis);
procedure SetAxisColor(const AValue: TColor);
procedure SetAxisVisible(Value: Boolean);
procedure SetBackColor(const AValue: TColor);
procedure SetBottomAxis(Value: TChartAxis);
procedure SetDepth(AValue: TChartZPosition);
procedure SetExpandPercentage(AValue: Integer);
procedure SetExtent(const AValue: TChartExtent);
procedure SetFoot(Value: TChartTitle);
procedure SetFrame(Value: TChartPen);
procedure SetGraphBrush(Value: TBrush);
procedure SetLeftAxis(Value: TChartAxis);
procedure SetLegend(Value: TChartLegend);
procedure SetMargins(AValue: TChartMargins);
procedure SetReticuleMode(const AValue: TReticuleMode);
@ -203,7 +198,7 @@ type
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
destructor Destroy; override;
procedure EraseBackground(DC: HDC); override;
procedure GetChildren(AProc: TGetChildProc; ARoot: TComponent); override;
procedure Paint; override;
@ -252,7 +247,7 @@ type
property AxisColor: TColor read FAxisColor write SetAxisColor default clBlack;
property AxisVisible: Boolean read FAxisVisible write SetAxisVisible default true;
property BackColor: TColor read FBackColor write SetBackColor default clBtnFace;
property BottomAxis: TChartAxis read FBottomAxis write SetBottomAxis;
property BottomAxis: TChartAxis index 1 read GetAxis write SetAxis;
property Depth: TChartZPosition read FDepth write SetDepth default 0;
property ExpandPercentage: Integer
read FExpandPercentage write SetExpandPercentage default 0;
@ -260,7 +255,7 @@ type
property Foot: TChartTitle read FFoot write SetFoot;
property Frame: TChartPen read FFrame write SetFrame;
property GraphBrush: TBrush read FGraphBrush write SetGraphBrush;
property LeftAxis: TChartAxis read FLeftAxis write SetLeftAxis;
property LeftAxis: TChartAxis index 2 read GetAxis write SetAxis;
property Legend: TChartLegend read FLegend write SetLegend;
property Margins: TChartMargins read FMargins write SetMargins;
property ReticuleMode: TReticuleMode
@ -349,7 +344,7 @@ begin
FReticulePos := Point(-1, -1);
FReticuleMode := rmNone;
FSeries := TChartSeriesList.Create(Self);
FSeries := TChartSeriesList.Create;
Color := clBtnFace;
FBackColor := clBtnFace;
@ -368,12 +363,17 @@ begin
FTitle.Text.Add('TAChart');
FFoot := TChartTitle.Create(Self);
FLeftAxis := TChartAxis.Create(Self);
FLeftAxis.Title.Angle := 90;
FLeftAxis.Grid.Style := psDot;
FBottomAxis := TChartAxis.Create(Self);
FBottomAxis.Title.Angle := 0;
FBottomAxis.Grid.Style := psDot;
FAxisList := TChartAxisList.Create(Self);
with TChartAxis.Create(FAxisList) do begin
Alignment := calLeft;
Title.Angle := 90;
Grid.Style := psDot;
end;
with TChartAxis.Create(FAxisList) do begin
Alignment := calBottom;
Title.Angle := 0;
Grid.Style := psDot;
end;
FFrame := TChartPen.Create;
FFrame.OnChange := @StyleChanged;
@ -390,8 +390,7 @@ begin
FLegend.Free;
FTitle.Free;
FFoot.Free;
FLeftAxis.Free;
FBottomAxis.Free;
FAxisList.Free;
FFrame.Free;
FExtent.Free;
FMargins.Free;
@ -414,6 +413,11 @@ begin
Unused(DC);
end;
function TChart.GetAxis(AIndex: integer): TChartAxis;
begin
Result := FAxisList.GetAxis(AIndex);
end;
procedure TChart.StyleChanged(Sender: TObject);
begin
Invalidate;
@ -462,7 +466,7 @@ type
TConvFunc = function (AX: Integer): Double of object;
procedure CalcOneCoord(
AInverted: boolean; AConv: TConvFunc; var AGraphMin, AGraphMax: Double;
AAxis: TChartAxis; AConv: TConvFunc; var AGraphMin, AGraphMax: Double;
AImageLo, AImageHi, AMarginLo, AMarginHi, ASign: Integer;
out AScale, AOffset: Double);
var
@ -477,24 +481,24 @@ type
exit;
end;
if AInverted then
if (AAxis <> nil) and AAxis.Inverted then
Exchange(lo, hi);
AScale := (hi - lo) / (AGraphMax - AGraphMin);
AOffset := hi - AScale * AGraphMax;
AGraphMin := AConv(AImageLo);
AGraphMax := AConv(AImageHi);;
if AInverted then
if (AAxis <> nil) and AAxis.Inverted then
Exchange(AGraphMin, AGraphMax);
end;
begin
CalcOneCoord(
BottomAxis.Inverted, @XImageToGraph, FCurrentExtent.a.X, FCurrentExtent.b.X,
BottomAxis, @XImageToGraph, FCurrentExtent.a.X, FCurrentExtent.b.X,
FClipRect.Left, FClipRect.Right, AMargin.Left, -AMargin.Right, 1,
FScale.X, FOffset.X);
CalcOneCoord(
LeftAxis.Inverted, @YImageToGraph, FCurrentExtent.a.Y, FCurrentExtent.b.Y,
LeftAxis, @YImageToGraph, FCurrentExtent.a.Y, FCurrentExtent.b.Y,
FClipRect.Bottom, FClipRect.Top, -AMargin.Bottom, AMargin.Top, -1,
FScale.Y, FOffset.Y);
end;
@ -586,21 +590,26 @@ var
begin
// FIXME: Angle assumed to be around 0 for bottom and 90 for left axis.
c := CenterPoint(FClipRect);
s := FLeftAxis.Title.Caption;
if FLeftAxis.Visible and (s <> '') then begin
w := ACanvas.TextHeight(FLeftAxis.Title.Caption);
x := FClipRect.Left;
leftOffset := w + 4;
ACanvas.Font.Orientation := FLeftAxis.Title.Angle * DEGREES_TO_ORIENT;
ACanvas.TextOut(x, c.Y - w div 2, s);
if LeftAxis <> nil then begin
s := LeftAxis.Title.Caption;
if LeftAxis.Visible and (s <> '') then begin
w := ACanvas.TextHeight(LeftAxis.Title.Caption);
x := FClipRect.Left;
leftOffset := w + 4;
ACanvas.Font.Orientation := LeftAxis.Title.Angle * DEGREES_TO_ORIENT;
ACanvas.TextOut(x, c.Y - w div 2, s);
end;
end;
s := FBottomAxis.Title.Caption;
if FBottomAxis.Visible and (s <> '') then begin
sz := ACanvas.TextExtent(s);
ACanvas.Font.Orientation := FBottomAxis.Title.Angle * DEGREES_TO_ORIENT;
ACanvas.TextOut(c.X - sz.cx div 2, FClipRect.Bottom - sz.cy, s);
bottomOffset := sz.cy + 4;
if BottomAxis <> nil then begin
s := BottomAxis.Title.Caption;
if BottomAxis.Visible and (s <> '') then begin
sz := ACanvas.TextExtent(s);
ACanvas.Font.Orientation := BottomAxis.Title.Angle * DEGREES_TO_ORIENT;
ACanvas.TextOut(c.X - sz.cx div 2, FClipRect.Bottom - sz.cy, s);
bottomOffset := sz.cy + 4;
end;
end;
ACanvas.Font.Orientation := 0;
end;
@ -612,8 +621,8 @@ var
begin
x := XGraphToImage(AMark);
if FBottomAxis.Grid.Visible then begin
ACanvas.Pen.Assign(FBottomAxis.Grid);
if BottomAxis.Grid.Visible then begin
ACanvas.Pen.Assign(BottomAxis.Grid);
ACanvas.Brush.Style := bsClear;
DrawLineVert(ACanvas, x);
end;
@ -639,8 +648,8 @@ var
begin
y := YGraphToImage(AMark);
if FLeftAxis.Grid.Visible then begin
ACanvas.Pen.Assign(FLeftAxis.Grid);
if LeftAxis.Grid.Visible then begin
ACanvas.Pen.Assign(LeftAxis.Grid);
ACanvas.Brush.Style := bsClear;
DrawLineHoriz(ACanvas, y);
end;
@ -675,11 +684,11 @@ begin
DrawAxisTitles;
// Check AxisScale for both axes
leftAxisScale := INV_TO_SCALE[LeftAxis.Inverted];
bottomAxisScale := INV_TO_SCALE[BottomAxis.Inverted];
leftAxisScale := INV_TO_SCALE[(LeftAxis <> nil) and LeftAxis.Inverted];
bottomAxisScale := INV_TO_SCALE[(BottomAxis <> nil) and BottomAxis.Inverted];
leftAxisWidth := 0;
if FLeftAxis.Visible then begin
if (LeftAxis <> nil) and LeftAxis.Visible then begin
// Find max mark width
maxWidth := 0;
if YGraphMin <> YGraphMax then begin
@ -710,7 +719,7 @@ begin
leftOffset += leftAxisWidth;
end;
if FBottomAxis.Visible then
if (BottomAxis <> nil) and BottomAxis.Visible then
bottomOffset += ACanvas.TextHeight('0') + 5;
FClipRect.Left += Max(leftOffset, Depth);
@ -730,7 +739,7 @@ begin
end;
// X graduations
if FBottomAxis.Visible and (XGraphMin <> XGraphMax) then begin
if (BottomAxis <> nil) and BottomAxis.Visible and (XGraphMin <> XGraphMax) then begin
CalculateIntervals(XGraphMin, XGraphMax, bottomAxisScale, mark, step);
case bottomAxisScale of
asIncreasing:
@ -749,7 +758,7 @@ begin
end;
// Y graduations
if FLeftAxis.Visible and (YGraphMin <> YGraphMax) then begin
if (LeftAxis <> nil) and LeftAxis.Visible and (YGraphMin <> YGraphMax) then begin
CalculateIntervals(YGraphMin, YGraphMax, leftAxisScale, mark, step);
case leftAxisScale of
asIncreasing:
@ -1032,6 +1041,12 @@ begin
end;
end;
procedure TChart.SetAxis(AIndex: Integer; AValue: TChartAxis);
begin
FAxisList.SetAxis(AIndex, AValue);
Invalidate;
end;
procedure TChart.SetAxisColor(const AValue: TColor);
begin
if FAxisColor = AValue then exit;
@ -1220,18 +1235,6 @@ begin
Invalidate;
end;
procedure TChart.SetLeftAxis(Value: TChartAxis);
begin
FLeftAxis.Assign(Value);
Invalidate;
end;
procedure TChart.SetBottomAxis(Value: TChartAxis);
begin
FBottomAxis.Assign(Value);
Invalidate;
end;
procedure TChart.SetMargins(AValue: TChartMargins);
begin
FMargins.Assign(AValue);
@ -1454,9 +1457,8 @@ begin
Result := FList.Count;
end;
constructor TChartSeriesList.Create(AOwner: TChart);
constructor TChartSeriesList.Create;
begin
FChart := AOwner;
FList := TFPList.Create;
end;
@ -1477,12 +1479,6 @@ begin
Result := TBasicChartSeries(FList.Items[AIndex]);
end;
procedure TChartSeriesList.SetItem(
AIndex: Integer; const AValue: TBasicChartSeries);
begin
GetItem(AIndex).Assign(AValue);
end;
procedure SkipObsoleteChartProperties;
const
MIRRORX_NOTE = 'Obsolete, use BottomAxis.Invert instead';

View File

@ -64,9 +64,9 @@ type
procedure SetVisible(const AValue: Boolean);
protected
FOwner: TCustomChart;
procedure StyleChanged(Sender: TObject);
procedure InitHelper(
var AResult: TFPCanvasHelper; AClass: TFPCanvasHelperClass);
procedure StyleChanged(Sender: TObject);
public
constructor Create(AOwner: TCustomChart);
procedure Assign(Source: TPersistent); override;
@ -147,26 +147,57 @@ type
property Visible default false;
end;
TChartAxis = class(TChartElement)
TChartAxisAlignment = (calLeft, calTop, calRight, calBottom);
{ TChartAxis }
TChartAxis = class(TCollectionItem)
private
FAlignment: TChartAxisAlignment;
FGrid: TChartPen;
FInverted: Boolean;
FTitle: TChartAxisTitle;
FVisible: Boolean;
procedure SetAlignment(AValue: TChartAxisAlignment);
procedure SetGrid(AValue: TChartPen);
procedure SetInverted(AValue: Boolean);
procedure SetTitle(AValue: TChartAxisTitle);
procedure SetVisible(const AValue: Boolean);
procedure StyleChanged(ASender: TObject);
public
constructor Create(AOwner: TCustomChart);
constructor Create(ACollection: TCollection); override;
destructor Destroy; override;
procedure Assign(Source: TPersistent); override;
published
property Alignment: TChartAxisAlignment read FAlignment write SetAlignment;
property Grid: TChartPen read FGrid write SetGrid;
// Inverts the axis scale from increasing to decreasing.
property Inverted: boolean read FInverted write SetInverted default false;
property Title: TChartAxisTitle read FTitle write SetTitle;
property Visible default true;
property Visible: Boolean read FVisible write SetVisible default true;
end;
{ TChartAxisList }
TChartAxisList = class(TCollection)
private
FChart: TCustomChart;
function GetAxes(AIndex: Integer): TChartAxis;
protected
function GetOwner: TPersistent; override;
public
constructor Create(AOwner: TCustomChart);
public
function Add: TChartAxis; inline;
function GetAxis(AIndex: Integer): TChartAxis;
procedure SetAxis(AIndex: Integer; AValue: TChartAxis);
property Axes[AIndex: Integer]: TChartAxis read GetAxes; default;
property BottomAxis: TChartAxis index 1 read GetAxis write SetAxis;
property LeftAxis: TChartAxis index 2 read GetAxis write SetAxis;
end;
TChartLinkPen = class(TChartPen)
@ -333,7 +364,7 @@ end;
procedure TChartElement.Assign(Source: TPersistent);
begin
inherited Assign(Source);
//inherited Assign(Source);
if Source is TChartElement then
Self.FVisible := TChartElement(Source).FVisible;
end;
@ -542,14 +573,15 @@ begin
FInverted := Inverted;
FTitle.Assign(Title);
end;
inherited Assign(Source);
//inherited Assign(Source);
end;
constructor TChartAxis.Create(AOwner: TCustomChart);
constructor TChartAxis.Create(ACollection: TCollection);
begin
inherited Create(AOwner);
FTitle := TChartAxisTitle.Create(AOwner);
InitHelper(FGrid, TChartPen);
inherited Create(ACollection);
FTitle := TChartAxisTitle.Create(ACollection.Owner as TCustomChart);
FGrid := TChartPen.Create;
FGrid.OnChange := @StyleChanged;
FVisible := true;
end;
@ -560,6 +592,13 @@ begin
inherited;
end;
procedure TChartAxis.SetAlignment(AValue: TChartAxisAlignment);
begin
if FAlignment = AValue then exit;
FAlignment := AValue;
StyleChanged(Self);
end;
procedure TChartAxis.SetGrid(AValue: TChartPen);
begin
FGrid.Assign(AValue);
@ -578,6 +617,19 @@ begin
StyleChanged(Self);
end;
procedure TChartAxis.SetVisible(const AValue: Boolean);
begin
if FVisible = AValue then exit;
FVisible := AValue;
StyleChanged(Self);
end;
procedure TChartAxis.StyleChanged(ASender: TObject);
begin
Unused(ASender);
(Collection.Owner as TCustomChart).Invalidate;
end;
{ TChartMarks }
procedure TChartMarks.Assign(Source: TPersistent);
@ -882,5 +934,52 @@ begin
StyleChanged(Self);
end;
const
AXIS_INDEX: array [1..2] of TChartAxisAlignment = (calBottom, calLeft);
{ TChartAxisList }
function TChartAxisList.Add: TChartAxis; inline;
begin
Result := TChartAxis(inherited Add);
end;
constructor TChartAxisList.Create(AOwner: TCustomChart);
begin
inherited Create(TChartAxis);
FChart := AOwner;
end;
function TChartAxisList.GetAxes(AIndex: Integer): TChartAxis;
begin
Result := TChartAxis(Items[AIndex]);
end;
function TChartAxisList.GetAxis(AIndex: Integer): TChartAxis;
var
i: Integer;
begin
for i := 0 to Count - 1 do
if Axes[i].Alignment = AXIS_INDEX[AIndex] then
exit(Axes[i]);
Result := nil;
end;
function TChartAxisList.GetOwner: TPersistent;
begin
Result := FChart;
end;
procedure TChartAxisList.SetAxis(AIndex: Integer; AValue: TChartAxis);
var
a: TChartAxis;
begin
a := GetAxis(AIndex);
if a = nil then
a := Add;
a.Assign(AValue);
a.FAlignment := AXIS_INDEX[AIndex];
end;
end.