{ Axises for TAChart series. ***************************************************************************** * * * See the file COPYING.modifiedLGPL.txt, included in this distribution, * * for details about the copyright. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * * ***************************************************************************** Authors: Alexander Klenin } unit TAChartAxis; {$H+} interface uses Classes, SysUtils, Types, TAChartAxisUtils, TAChartUtils, TACustomSource, TADrawUtils, TATransformations, TATypes; const DEF_TICK_LENGTH = 4; DEF_INTERVALS_COUNT = 5; type { TChartMinorAxis } TChartMinorAxis = class(TChartBasicAxis) strict private FIntervalsCount: Cardinal; function GetMarks: TChartMinorAxisMarks; inline; procedure SetIntervalsCount(AValue: Cardinal); procedure SetMarks(AValue: TChartMinorAxisMarks); protected function GetDisplayName: String; override; strict protected function GetAlignment: TChartAxisAlignment; override; procedure SetAlignment(AValue: TChartAxisAlignment); override; procedure StyleChanged(ASender: TObject); override; public constructor Create(ACollection: TCollection); override; function GetMarkValues(AMin, AMax: Double): TChartValueTextArray; published property IntervalsCount: Cardinal read FIntervalsCount write SetIntervalsCount default DEF_INTERVALS_COUNT; property Marks: TChartMinorAxisMarks read GetMarks write SetMarks; property TickLength default DEF_TICK_LENGTH div 2; end; TChartAxis = class; { TChartMinorAxisList } TChartMinorAxisList = class(TCollection) strict private FAxis: TChartAxis; function GetAxes(AIndex: Integer): TChartMinorAxis; protected function GetOwner: TPersistent; override; procedure Update(AItem: TCollectionItem); override; public constructor Create(AOwner: TChartAxis); public function Add: TChartMinorAxis; function GetChart: TCustomChart; inline; property Axes[AIndex: Integer]: TChartMinorAxis read GetAxes; default; end; TChartAxisGroup = record FCount: Integer; FFirstMark: Integer; FLastMark: Integer; FSize: Integer; FTitleSize: Integer; end; TChartAxisPen = class(TChartPen) published property Visible default false; end; { TChartAxis } TChartAxis = class(TChartBasicAxis) strict private FListener: TListener; FMarkValues: TChartValueTextArray; procedure GetMarkValues(AMin, AMax: Double); procedure VisitSource(ASource: TCustomChartSource; var AData); private FAxisRect: TRect; FGroupIndex: Integer; FTitleRect: TRect; strict private FAlignment: TChartAxisAlignment; FAxisPen: TChartAxisPen; FGroup: Integer; FHelper: TAxisDrawHelper; FInverted: Boolean; FMinors: TChartMinorAxisList; FOnMarkToText: TChartAxisMarkToTextEvent; FTitle: TChartAxisTitle; FTransformations: TChartAxisTransformations; FZPosition: TChartDistance; function GetMarks: TChartAxisMarks; inline; function GetTransform: TChartAxisTransformations; procedure SetAxisPen(AValue: TChartAxisPen); procedure SetGroup(AValue: Integer); procedure SetInverted(AValue: Boolean); procedure SetMarks(AValue: TChartAxisMarks); procedure SetMinors(AValue: TChartMinorAxisList); procedure SetOnMarkToText(AValue: TChartAxisMarkToTextEvent); procedure SetTitle(AValue: TChartAxisTitle); procedure SetTransformations(AValue: TChartAxisTransformations); procedure SetZPosition(AValue: TChartDistance); protected function GetDisplayName: String; override; procedure StyleChanged(ASender: TObject); override; strict protected function GetAlignment: TChartAxisAlignment; override; procedure SetAlignment(AValue: TChartAxisAlignment); override; public constructor Create(ACollection: TCollection); override; destructor Destroy; override; public procedure Assign(ASource: TPersistent); override; procedure Draw; procedure DrawTitle(const ACenter: TPoint; ASize: Integer); function GetChart: TCustomChart; inline; function IsVertical: Boolean; inline; procedure Measure( const AExtent: TDoubleRect; var AMeasureData: TChartAxisGroup); procedure PrepareHelper( ADrawer: IChartDrawer; const ATransf: ICoordTransformer; AClipRect: PRect; AMaxZPosition: Integer); published property Alignment default calLeft; property Arrow; property AxisPen: TChartAxisPen read FAxisPen write SetAxisPen; property Group: Integer read FGroup write SetGroup default 0; // Inverts the axis scale from increasing to decreasing. property Inverted: boolean read FInverted write SetInverted default false; property Marks: TChartAxisMarks read GetMarks write SetMarks; property Minors: TChartMinorAxisList read FMinors write SetMinors; property TickLength default DEF_TICK_LENGTH; property Title: TChartAxisTitle read FTitle write SetTitle; property Transformations: TChartAxisTransformations read FTransformations write SetTransformations; property ZPosition: TChartDistance read FZPosition write SetZPosition default 0; published property OnMarkToText: TChartAxisMarkToTextEvent read FOnMarkToText write SetOnMarkToText; end; TChartOnSourceVisitor = procedure (ASource: TCustomChartSource; var AData) of object; TChartOnVisitSources = procedure ( AVisitor: TChartOnSourceVisitor; AAxis: TChartAxis; var AData) of object; TChartAxisEnumerator = class(TCollectionEnumerator) public function GetCurrent: TChartAxis; property Current: TChartAxis read GetCurrent; end; { TChartAxisList } TChartAxisList = class(TCollection) strict private FChart: TCustomChart; FOnVisitSources: TChartOnVisitSources; function GetAxes(AIndex: Integer): TChartAxis; strict private FCenterPoint: TPoint; FGroupOrder: TFPList; FGroups: array of TChartAxisGroup; FZOrder: TFPList; procedure InitAndSort(AList: TFPList; ACompare: TListSortCompare); protected function GetOwner: TPersistent; override; procedure Update(AItem: TCollectionItem); override; public constructor Create(AOwner: TCustomChart); destructor Destroy; override; public function Add: TChartAxis; inline; procedure Draw(ACurrentZ: Integer; var AIndex: Integer); function GetAxis(AIndex: Integer): TChartAxis; function GetEnumerator: TChartAxisEnumerator; function Measure(const AExtent: TDoubleRect): TChartAxisMargins; procedure Prepare(ARect: TRect); procedure PrepareGroups; 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; property OnVisitSources: TChartOnVisitSources read FOnVisitSources write FOnVisitSources; end; TAxisConvFunc = function (AX: Integer): Double of object; { TAxisCoeffHelper } TAxisCoeffHelper = object FAxis: TChartAxis; FImageLo, FImageHi: Integer; FLo, FHi: Integer; FMin, FMax: PDouble; function CalcOffset(AScale: Double): Double; function CalcScale(ASign: Integer): Double; constructor Init( AAxis: TChartAxis; AImageLo, AImageHi, AMarginLo, AMarginHi: Integer; AMin, AMax: PDouble); procedure UpdateMinMax(AConv: TAxisConvFunc); end; procedure SideByAlignment( var ARect: TRect; AAlignment: TChartAxisAlignment; ADelta: Integer); function TransformByAxis( AAxisList: TChartAxisList; AIndex: Integer): TChartAxisTransformations; implementation uses LResources, Math, PropEdits, TAGeometry; type TAxisDataExtent = record FMin, FMax: Double; end; var VIdentityTransform: TChartAxisTransformations; function AxisGroupCompare(Item1, Item2: Pointer): Integer; begin Result := TChartAxis(Item1).Group - TChartAxis(Item2).Group; end; function AxisZCompare(Item1, Item2: Pointer): Integer; begin Result := TChartAxis(Item1).ZPosition - TChartAxis(Item2).ZPosition; end; procedure SideByAlignment( var ARect: TRect; AAlignment: TChartAxisAlignment; ADelta: Integer); var a: TChartAxisMargins absolute ARect; begin if AAlignment in [calLeft, calTop] then ADelta := -ADelta; a[AAlignment] += ADelta; end; function TransformByAxis( AAxisList: TChartAxisList; AIndex: Integer): TChartAxisTransformations; begin Result := nil; if InRange(AIndex, 0, AAxisList.Count - 1) then Result := AAxisList[AIndex].Transformations; if Result = nil then Result := VIdentityTransform; end; { TChartAxisEnumerator } function TChartAxisEnumerator.GetCurrent: TChartAxis; begin Result := TChartAxis(inherited GetCurrent); end; { TChartMinorAxis } constructor TChartMinorAxis.Create(ACollection: TCollection); begin inherited Create(ACollection, (ACollection as TChartMinorAxisList).GetChart); FIntervalsCount := DEF_INTERVALS_COUNT; FMarks := TChartMinorAxisMarks.Create( (ACollection as TChartMinorAxisList).GetChart); TickLength := DEF_TICK_LENGTH div 2; end; function TChartMinorAxis.GetAlignment: TChartAxisAlignment; begin Result := (Collection.Owner as TChartAxis).GetAlignment; end; function TChartMinorAxis.GetDisplayName: String; begin Result := 'M'; end; function TChartMinorAxis.GetMarks: TChartMinorAxisMarks; begin Result := TChartMinorAxisMarks(inherited Marks); end; function TChartMinorAxis.GetMarkValues(AMin, AMax: Double): TChartValueTextArray; var c, ic: Integer; begin if not Visible then exit(nil); ic := IntervalsCount; SetLength(Result, ic - 1); for c := 1 to ic - 1 do with Result[c - 1] do begin FValue := WeightedAverage(AMin, AMax, c / ic); if Marks.Visible then FText := Format(Marks.Format, [FValue, 0.0, '', 0.0, 0.0]); end; end; procedure TChartMinorAxis.SetAlignment(AValue: TChartAxisAlignment); begin Unused(AValue); raise EChartError.Create('TChartMinorAxis.SetAlignment'); end; procedure TChartMinorAxis.SetIntervalsCount(AValue: Cardinal); begin if FIntervalsCount = AValue then exit; FIntervalsCount := AValue; StyleChanged(Self); end; procedure TChartMinorAxis.SetMarks(AValue: TChartMinorAxisMarks); begin inherited Marks := AValue; end; procedure TChartMinorAxis.StyleChanged(ASender: TObject); begin (Collection.Owner as TChartAxis).StyleChanged(ASender); end; { TChartMinorAxisList } function TChartMinorAxisList.Add: TChartMinorAxis; begin Result := TChartMinorAxis(inherited Add); end; constructor TChartMinorAxisList.Create(AOwner: TChartAxis); begin inherited Create(TChartMinorAxis); FAxis := AOwner; end; function TChartMinorAxisList.GetAxes(AIndex: Integer): TChartMinorAxis; begin Result := TChartMinorAxis(Items[AIndex]); end; function TChartMinorAxisList.GetChart: TCustomChart; begin Result := FAxis.GetChart; end; function TChartMinorAxisList.GetOwner: TPersistent; begin Result := FAxis; end; procedure TChartMinorAxisList.Update(AItem: TCollectionItem); begin FAxis.StyleChanged(AItem); end; { TChartAxis } procedure TChartAxis.Assign(ASource: TPersistent); begin if ASource is TChartAxis then with TChartAxis(ASource) do begin Self.FGroup := Group; Self.FInverted := Inverted; Self.FTitle.Assign(Title); Self.FTransformations := Transformations; Self.FZPosition := ZPosition; Self.FOnMarkToText := OnMarkToText; end; inherited Assign(ASource); end; constructor TChartAxis.Create(ACollection: TCollection); begin inherited Create(ACollection, ACollection.Owner as TCustomChart); FAxisPen := TChartAxisPen.Create; FAxisPen.OnChange := @StyleChanged; FAxisPen.Visible := false; FListener := TListener.Create(@FTransformations, @StyleChanged); FMarks := TChartAxisMarks.Create(ACollection.Owner as TCustomChart); FMinors := TChartMinorAxisList.Create(Self); TickLength := DEF_TICK_LENGTH; FTitle := TChartAxisTitle.Create(ACollection.Owner as TCustomChart); end; destructor TChartAxis.Destroy; begin FreeAndNil(FTitle); FreeAndNil(FMinors); FreeAndNil(FListener); FreeAndNil(FHelper); FreeAndNil(FAxisPen); inherited; end; procedure TChartAxis.Draw; procedure DrawMinors(AFixedCoord: Integer; AMin, AMax: Double); var j: Integer; minorMarks: TChartValueTextArray; m: TChartValueText; begin if IsNan(AMin) or (AMin = AMax) then exit; for j := 0 to Minors.Count - 1 do begin minorMarks := Minors[j].GetMarkValues(AMin, AMax); if minorMarks = nil then continue; with FHelper.Clone do begin FAxis := Minors[j]; try BeginDrawing; for m in minorMarks do DrawMark(AFixedCoord, m.FValue, m.FText); EndDrawing; finally Free; end; end; end; end; var fixedCoord: Integer; pv, v: Double; t: TChartValueText; begin if not Visible then exit; if Marks.Visible then FHelper.FDrawer.Font := Marks.LabelFont; fixedCoord := TChartAxisMargins(FAxisRect)[Alignment]; pv := NaN; FHelper.BeginDrawing; FHelper.DrawAxisLine(AxisPen, fixedCoord); for t in FMarkValues do begin v := FHelper.FAxisTransf(t.FValue); FHelper.DrawMark(fixedCoord, v, t.FText); DrawMinors(fixedCoord, pv, v); pv := v; end; FHelper.EndDrawing; end; procedure TChartAxis.DrawTitle(const ACenter: TPoint; ASize: Integer); var p: TPoint; dummy: TPointArray = nil; d: Integer; begin if not Visible or (ASize = 0) then exit; p := ACenter; d := (ASize + Title.Distance) div 2; case Alignment of calLeft: p.X := FTitleRect.Left - d; calTop: p.Y := FTitleRect.Top - d; calRight: p.X := FTitleRect.Right + d; calBottom: p.Y := FTitleRect.Bottom + d; end; p += FHelper.FZOffset; Title.DrawLabel(FHelper.FDrawer, p, p, Title.Caption, dummy); end; function TChartAxis.GetAlignment: TChartAxisAlignment; begin Result := FAlignment; end; function TChartAxis.GetChart: TCustomChart; begin Result := Collection.Owner as TCustomChart; end; function TChartAxis.GetDisplayName: string; const SIDE_NAME: array [TChartAxisAlignment] of String = ('Left', 'Top', 'Right', 'Bottom'); VISIBLE_NAME: array [Boolean] of String = (' Hidden', ''); INVERTED_NAME: array [Boolean] of String = ('', ' Inverted'); CAPTION_FMT = ' (%s)'; begin Result := SIDE_NAME[Alignment] + VISIBLE_NAME[Visible] + INVERTED_NAME[Inverted]; if Title.Caption <> '' then Result += Format(CAPTION_FMT, [Title.Caption]); end; function TChartAxis.GetMarks: TChartAxisMarks; begin Result := TChartAxisMarks(inherited Marks); end; procedure TChartAxis.GetMarkValues(AMin, AMax: Double); var i: Integer; d: TAxisDataExtent; vis: TChartOnVisitSources; t: TChartValueText; begin AMin := GetTransform.GraphToAxis(AMin); AMax := GetTransform.GraphToAxis(AMax); EnsureOrder(AMin, AMax); SetLength(FMarkValues, 0); vis := TChartAxisList(Collection).OnVisitSources; if Marks.AtDataOnly and Assigned(vis) then begin d.FMin := AMin; d.FMax := AMax; vis(@VisitSource, Self, d); end else Marks.SourceDef.ValuesInRange( AMin, AMax, Marks.Format, IsVertical, FMarkValues); if Inverted then for i := 0 to High(FMarkValues) div 2 do begin t := FMarkValues[i]; FMarkValues[i] := FMarkValues[High(FMarkValues) - i]; FMarkValues[High(FMarkValues) - i] := t; end; if Assigned(FOnMarkToText) then for i := 0 to High(FMarkValues) do FOnMarkToText(FMarkValues[i].FText, FMarkValues[i].FValue); end; function TChartAxis.GetTransform: TChartAxisTransformations; begin Result := Transformations; if Result = nil then Result := VIdentityTransform; end; function TChartAxis.IsVertical: Boolean; inline; begin Result := Alignment in [calLeft, calRight]; end; procedure TChartAxis.Measure( const AExtent: TDoubleRect; var AMeasureData: TChartAxisGroup); var v: Boolean; d: IChartDrawer; function TitleSize: Integer; begin if not Title.Visible or (Title.Caption = '') then exit(0); Result := TPointBoolArr(Title.MeasureLabel(d, Title.Caption))[not v]; if Title.DistanceToCenter then Result := Result div 2; Result += d.Scale(Title.Distance); end; procedure UpdateFirstLast(ACoord, AIndex, ARMin, ARMax: Integer); var sz, fm, lm: Integer; begin sz := TPointBoolArr( Marks.MeasureLabel(d, FMarkValues[AIndex].FText))[v] div 2; fm := sz - ACoord + ARMin; lm := sz - ARMax + ACoord; if v then Exchange(fm, lm); with AMeasureData do begin FFirstMark := Max(fm, FFirstMark); FLastMark := Max(lm, FLastMark); end; end; var sz, rmin, rmax, c, i, j, minc, maxc, mini, maxi: Integer; minorValues: TChartValueTextArray; pv: Double = NaN; cv: Double; begin if not Visible then exit; v := IsVertical; d := FHelper.FDrawer; GetMarkValues( TDoublePointBoolArr(AExtent.a)[v], TDoublePointBoolArr(AExtent.b)[v]); sz := Marks.Measure(d, not v, TickLength, FMarkValues); FHelper.GetClipRange(rmin, rmax); minc := MaxInt; maxc := -MaxInt; for i := 0 to High(FMarkValues) do begin cv := FMarkValues[i].FValue; if not IsNan(pv) then begin for j := 0 to Minors.Count - 1 do with Minors[j] do begin minorValues := GetMarkValues(pv, cv); sz := Max(Marks.Measure(d, not v, TickLength, minorValues), sz); end; end; pv := cv; // Optimization: only measure edge labels to calculate longitudinal margins. c := FHelper.GraphToImage(FHelper.FAxisTransf(cv)); if not InRange(c, rmin, rmax) then continue; if c < minc then begin minc := c; mini := i; end; if c > maxc then begin maxc := c; maxi := i; end; end; with AMeasureData do begin FSize := Max(sz, FSize); FTitleSize := Max(TitleSize, FTitleSize); end; if minc < MaxInt then begin UpdateFirstLast(minc, mini, rmin, rmax); UpdateFirstLast(maxc, maxi, rmin, rmax); end; if Arrow.Visible then with AMeasureData do begin FSize := Max(d.Scale(Arrow.Width), FSize); FLastMark := Max(d.Scale(Arrow.Length), FLastMark); end; end; procedure TChartAxis.PrepareHelper( ADrawer: IChartDrawer; const ATransf: ICoordTransformer; AClipRect: PRect; AMaxZPosition: Integer); begin FreeAndNil(FHelper); if IsVertical then FHelper := TAxisDrawHelperY.Create else FHelper := TAxisDrawHelperX.Create; FHelper.FAxis := Self; FHelper.FAxisTransf := @GetTransform.AxisToGraph; FHelper.FClipRect := AClipRect; FHelper.FDrawer := ADrawer; FHelper.FTransf := ATransf; FHelper.FZOffset.Y := Min(ZPosition, AMaxZPosition); FHelper.FZOffset.X := -FHelper.FZOffset.Y; end; procedure TChartAxis.SetAlignment(AValue: TChartAxisAlignment); begin if FAlignment = AValue then exit; FAlignment := AValue; StyleChanged(Self); end; procedure TChartAxis.SetAxisPen(AValue: TChartAxisPen); begin FAxisPen.Assign(AValue); StyleChanged(Self); end; procedure TChartAxis.SetGroup(AValue: Integer); begin if FGroup = AValue then exit; FGroup := AValue; StyleChanged(Self); end; procedure TChartAxis.SetInverted(AValue: Boolean); begin if FInverted = AValue then exit; FInverted := AValue; StyleChanged(Self); end; procedure TChartAxis.SetMarks(AValue: TChartAxisMarks); begin inherited Marks := AValue; end; procedure TChartAxis.SetMinors(AValue: TChartMinorAxisList); begin FMinors.Assign(AValue); StyleChanged(Self); end; procedure TChartAxis.SetOnMarkToText(AValue: TChartAxisMarkToTextEvent); begin if TMethod(FOnMarkToText) = TMethod(AValue) then exit; FOnMarkToText := AValue; StyleChanged(Self); end; procedure TChartAxis.SetTitle(AValue: TChartAxisTitle); begin FTitle.Assign(AValue); StyleChanged(Self); end; procedure TChartAxis.SetTransformations(AValue: TChartAxisTransformations); begin if FTransformations = AValue then exit; if FListener.IsListening then Transformations.Broadcaster.Unsubscribe(FListener); FTransformations := AValue; if FTransformations <> nil then Transformations.Broadcaster.Subscribe(FListener); StyleChanged(Self); end; procedure TChartAxis.SetZPosition(AValue: TChartDistance); begin if FZPosition = AValue then exit; FZPosition := AValue; StyleChanged(Self); end; procedure TChartAxis.StyleChanged(ASender: TObject); begin with GetChart do begin // Transformation change could have invalidated the current extent, // so revert to full extent for now. if ASender is TAxisTransform then ZoomFull; Invalidate; end; end; procedure TChartAxis.VisitSource(ASource: TCustomChartSource; var AData); var ext: TDoubleRect; begin ext := ASource.Extent; with TAxisDataExtent(AData) do Marks.SourceDef.ValuesInRange( Max(TDoublePointBoolArr(ext.a)[IsVertical], FMin), Min(TDoublePointBoolArr(ext.b)[IsVertical], FMax), Marks.Format, IsVertical, FMarkValues); 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; FGroupOrder := TFPList.Create; FZOrder := TFPList.Create; end; destructor TChartAxisList.Destroy; begin FreeAndNil(FGroupOrder); FreeAndNil(FZOrder); inherited Destroy; end; procedure TChartAxisList.Draw(ACurrentZ: Integer; var AIndex: Integer); begin while AIndex < FZOrder.Count do with TChartAxis(FZOrder[AIndex]) do begin if ACurrentZ < ZPosition then break; Draw; DrawTitle(FCenterPoint, FGroups[FGroupIndex].FTitleSize); AIndex += 1; end; end; function TChartAxisList.GetAxes(AIndex: Integer): TChartAxis; begin Result := TChartAxis(Items[AIndex]); end; function TChartAxisList.GetAxis(AIndex: Integer): TChartAxis; begin for Result in Self do if Result.Alignment = AXIS_INDEX[AIndex] then exit; Result := nil; end; function TChartAxisList.GetEnumerator: TChartAxisEnumerator; begin Result := TChartAxisEnumerator.Create(Self); end; function TChartAxisList.GetOwner: TPersistent; begin Result := FChart; end; procedure TChartAxisList.InitAndSort( AList: TFPList; ACompare: TListSortCompare); var a: TChartAxis; begin AList.Clear; for a in Self do AList.Add(Pointer(a)); AList.Sort(ACompare); end; function TChartAxisList.Measure(const AExtent: TDoubleRect): TChartAxisMargins; var g: ^TChartAxisGroup; procedure UpdateMarginsForMarks(AFirst, ALast: TChartAxisAlignment); begin Result[AFirst] := Max(Result[AFirst], g^.FFirstMark); Result[ALast] := Max(Result[ALast], g^.FLastMark); end; var i, j, ai: Integer; axis: TChartAxis; begin FillChar(Result, SizeOf(Result), 0); ai := 0; for i := 0 to High(FGroups) do begin g := @FGroups[i]; g^.FFirstMark := 0; g^.FLastMark := 0; g^.FSize := 0; g^.FTitleSize := 0; for j := 0 to g^.FCount - 1 do begin axis := TChartAxis(FGroupOrder[ai]); axis.Measure(AExtent, g^); ai += 1; end; // Axises of the same group should have the same Alignment and ZPosition. Result[axis.Alignment] += g^.FSize + g^.FTitleSize; end; ai := 0; for i := 0 to High(FGroups) do begin g := @FGroups[i]; if TChartAxis(FGroupOrder[ai]).IsVertical then UpdateMarginsForMarks(calBottom, calTop) else UpdateMarginsForMarks(calLeft, calRight); ai += g^.FCount; end; end; procedure TChartAxisList.Prepare(ARect: TRect); var i, j, ai: Integer; axis: TChartAxis; g: ^TChartAxisGroup; begin FCenterPoint := CenterPoint(ARect); ai := 0; for i := 0 to High(FGroups) do begin g := @FGroups[i]; for j := 0 to g^.FCount - 1 do begin axis := TChartAxis(FGroupOrder[ai + j]); axis.FAxisRect := ARect; end; SideByAlignment(ARect, axis.Alignment, g^.FSize); for j := 0 to g^.FCount - 1 do begin axis := TChartAxis(FGroupOrder[ai]); axis.FTitleRect := ARect; ai += 1; end; SideByAlignment(ARect, axis.Alignment, g^.FTitleSize); end; InitAndSort(FZOrder, @AxisZCompare); end; procedure TChartAxisList.PrepareGroups; var i, prevGroup, groupCount: Integer; begin InitAndSort(FGroupOrder, @AxisGroupCompare); SetLength(FGroups, Count); groupCount := 0; prevGroup := 0; for i := 0 to FGroupOrder.Count - 1 do with TChartAxis(FGroupOrder[i]) do begin if (Group = 0) or (Group <> prevGroup) then begin FGroups[groupCount].FCount := 1; groupCount += 1; prevGroup := Group; end else FGroups[groupCount - 1].FCount += 1; FGroupIndex := groupCount - 1; end; SetLength(FGroups, groupCount); 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.Alignment := AXIS_INDEX[AIndex]; end; procedure TChartAxisList.Update(AItem: TCollectionItem); begin Unused(AItem); FChart.Invalidate; end; { TAxisCoeffHelper } constructor TAxisCoeffHelper.Init( AAxis: TChartAxis; AImageLo, AImageHi, AMarginLo, AMarginHi: Integer; AMin, AMax: PDouble); begin FAxis := AAxis; FImageLo := AImageLo; FImageHi := AImageHi; FMin := AMin; FMax := AMax; FLo := FImageLo + AMarginLo; FHi := FImageHi + AMarginHi; end; function TAxisCoeffHelper.CalcScale(ASign: Integer): Double; begin if (FMax^ = FMin^) or (Sign(FHi - FLo) <> ASign) then exit(1.0); if (FAxis <> nil) and FAxis.Inverted then Exchange(FLo, FHi); Result := (FHi - FLo) / (FMax^ - FMin^); end; function TAxisCoeffHelper.CalcOffset(AScale: Double): Double; begin Result := (FLo + FHi) / 2 - AScale * (FMin^ + FMax^) / 2; end; procedure TAxisCoeffHelper.UpdateMinMax(AConv: TAxisConvFunc); begin FMin^ := AConv(FImageLo); FMax^ := AConv(FImageHi); if (FAxis <> nil) and FAxis.Inverted then Exchange(FMin^, FMax^); end; procedure SkipObsoleteAxisProperties; const TRANSFORM_NOTE = 'Obsolete, use Transformations instead'; begin RegisterPropertyToSkip(TChartAxis, 'Offset', TRANSFORM_NOTE, ''); RegisterPropertyToSkip(TChartAxis, 'Scale', TRANSFORM_NOTE, ''); RegisterPropertyToSkip(TChartAxis, 'Transformation', TRANSFORM_NOTE, ''); end; initialization VIdentityTransform := TChartAxisTransformations.Create(nil); SkipObsoleteAxisProperties; finalization FreeAndNil(VIdentityTransform); end.