From 3456985cf71acd7cc212027720f0494918585d8f Mon Sep 17 00:00:00 2001 From: wp Date: Thu, 23 Feb 2017 22:03:25 +0000 Subject: [PATCH] TAChart: Add new property RotationCenter to TChartTextElement and publish it for axis and series marks. git-svn-id: trunk@54258 - --- components/tachart/tachartaxis.pas | 12 +++- components/tachart/tachartaxisutils.pas | 16 +++-- components/tachart/tacustomseries.pas | 6 +- components/tachart/tatextelements.pas | 85 +++++++++++++++++++++++-- 4 files changed, 109 insertions(+), 10 deletions(-) diff --git a/components/tachart/tachartaxis.pas b/components/tachart/tachartaxis.pas index 446b10fa98..e94ec1706b 100644 --- a/components/tachart/tachartaxis.pas +++ b/components/tachart/tachartaxis.pas @@ -427,7 +427,6 @@ begin Self.FTransformations := Transformations; Self.FZPosition := ZPosition; Self.FMarginsForMarks := MarginsForMarks; - Self.FOnMarkToText := OnMarkToText; end; inherited Assign(ASource); @@ -446,6 +445,7 @@ begin TickLength := DEF_TICK_LENGTH; FTitle := TChartAxisTitle.Create(ACollection.Owner as TCustomChart); FMarginsForMarks := true; + FMarks.SetInsideDir(1, 0); end; destructor TChartAxis.Destroy; @@ -604,6 +604,7 @@ begin FMaxForMarks := Max(FMaxForMarks, GetTransform.AxisToGraph(d.FMax)); EnsureOrder(FValueMin, FValueMax); EnsureOrder(FMinForMarks, FMaxForMarks); + FRotationCenter := Marks.RotationCenter; end; if Assigned(FOnMarkToText) then @@ -826,12 +827,21 @@ begin FHelper.FAtDataOnly := AtDataOnly; FHelper.FMaxForMarks := NegInfinity; FHelper.FMinForMarks := SafeInfinity; + FHelper.FRotationCenter := Marks.RotationCenter; end; procedure TChartAxis.SetAlignment(AValue: TChartAxisAlignment); begin if FAlignment = AValue then exit; FAlignment := AValue; + // Define the "inside" direction of an axis such that rotated labels with + // rotation center at the text start or end never reach into the chart. + case FAlignment of + calBottom: FMarks.SetInsideDir(0, +1); + calTop : FMarks.SetInsideDir(0, -1); + calLeft : FMarks.SetInsideDir(+1, 0); + calRight : FMarks.SetInsideDir(-1, 0); + end; StyleChanged(Self); end; diff --git a/components/tachart/tachartaxisutils.pas b/components/tachart/tachartaxisutils.pas index 9430a023e0..e056468878 100644 --- a/components/tachart/tachartaxisutils.pas +++ b/components/tachart/tachartaxisutils.pas @@ -134,6 +134,7 @@ type property LabelBrush; property OverlapPolicy; property Range: TChartRange read FRange write SetRange; + property RotationCenter; property Source: TCustomChartSource read FSource write SetSource; property Stripes; property Style default smsValue; @@ -223,6 +224,7 @@ type FValueMin: Double; FMaxForMarks: Double; FMinForMarks: Double; + FRotationCenter: TChartTextRotationCenter; FZOffset: TPoint; procedure BeginDrawing; virtual; @@ -310,6 +312,7 @@ begin Result.FValueMin := FValueMin; Result.FMinForMarks := FMinForMarks; Result.FMaxForMarks := FMaxForMarks; + Result.FRotationCenter := FRotationCenter; Result.FZOffset := FZOffset; end; @@ -321,8 +324,7 @@ end; procedure TAxisDrawHelper.DrawLabel(ALabelCenter: TPoint; const AText: String); begin ALabelCenter += FZOffset; - FAxis.Marks.DrawLabel( - FDrawer, ALabelCenter, ALabelCenter, AText, FPrevLabelPoly); + FAxis.Marks.DrawLabel(FDrawer, ALabelCenter, ALabelCenter, AText, FPrevLabelPoly); end; procedure TAxisDrawHelper.DrawMark( @@ -426,7 +428,10 @@ procedure TAxisDrawHelperX.DrawLabelAndTick( var d, up, down: Integer; begin - d := FScaledTickLength + FAxis.Marks.CenterOffset(FDrawer, AText).cy; + if FRotationCenter = rcCenter then + d := FScaledTickLength + FAxis.Marks.CenterOffset(FDrawer, AText).cy + else + d := FScaledTickLength + FAxis.Marks.CenterHeightOffset(FDrawer, AText).cy; up := FScaledTickInnerLength; down := FScaledTickLength; if FAxis.Alignment = calTop then begin @@ -492,7 +497,10 @@ procedure TAxisDrawHelperY.DrawLabelAndTick( var d, left, right: Integer; begin - d := FScaledTickLength + FAxis.Marks.CenterOffset(FDrawer, AText).cx; + if FRotationCenter = rcCenter then + d := FScaledTickLength + FAxis.Marks.CenterOffset(FDrawer, AText).cx + else + d := FScaledTickLength + FAxis.Marks.CenterHeightOffset(FDrawer, AText).cx; left := FScaledTickInnerLength; right := FScaledTickLength; if FAxis.Alignment = calLeft then begin diff --git a/components/tachart/tacustomseries.pas b/components/tachart/tacustomseries.pas index 518e93c33f..cba9d45e48 100644 --- a/components/tachart/tacustomseries.pas +++ b/components/tachart/tacustomseries.pas @@ -1120,7 +1120,11 @@ var center: TPoint; begin if AText = '' then exit; - center := ADataPoint + OFFSETS[ADir] * Marks.CenterOffset(ADrawer, AText); + + if Marks.RotationCenter = rcCenter then + center := ADataPoint + OFFSETS[ADir] * Marks.CenterOffset(ADrawer, AText) + else + center := ADataPoint + OFFSETS[ADir] * Marks.CenterHeightOffset(ADrawer, AText); Marks.DrawLabel(ADrawer, ADataPoint, center, AText, prevLabelPoly); end; diff --git a/components/tachart/tatextelements.pas b/components/tachart/tatextelements.pas index 47cfd6f1a2..b479201b6f 100644 --- a/components/tachart/tatextelements.pas +++ b/components/tachart/tatextelements.pas @@ -40,6 +40,8 @@ type TChartLabelShape = ( clsRectangle, clsEllipse, clsRoundRect, clsRoundSide, clsUserDefined); + TChartTextRotationCenter = (rcCenter, rcEdge, rcLeft, rcRight); + TChartTextElement = class; TChartGetShapeEvent = procedure ( @@ -60,14 +62,18 @@ type procedure SetMargins(AValue: TChartLabelMargins); procedure SetOnGetShape(AValue: TChartGetShapeEvent); procedure SetOverlapPolicy(AValue: TChartMarksOverlapPolicy); + procedure SetRotationCenter(AValue: TChartTextRotationCenter); procedure SetShape(AValue: TChartLabelShape); strict protected FAlignment: TAlignment; + FInsideDir: TDoublePoint; + FRotationCenter: TChartTextRotationCenter; procedure ApplyLabelFont(ADrawer: IChartDrawer); virtual; procedure DrawLink( ADrawer: IChartDrawer; ADataPoint, ALabelCenter: TPoint); virtual; function GetBoundingBox( ADrawer: IChartDrawer; const ATextSize: TPoint): TRect; + function GetTextShiftNeeded: Boolean; function IsMarginRequired: Boolean; strict protected function GetFrame: TChartPen; virtual; abstract; @@ -75,6 +81,8 @@ type function GetLabelBrush: TBrush; virtual; abstract; function GetLabelFont: TFont; virtual; abstract; function GetLinkPen: TChartPen; virtual; + property RotationCenter: TChartTextRotationCenter + read FRotationCenter write SetRotationCenter default rcCenter; public constructor Create(AOwner: TCustomChart); destructor Destroy; override; @@ -86,6 +94,8 @@ type function GetLabelPolygon( ADrawer: IChartDrawer; ASize: TPoint): TPointArray; function MeasureLabel(ADrawer: IChartDrawer; const AText: String): TSize; + function MeasureLabelHeight(ADrawer: IChartDrawer; const AText: String): TSize; + procedure SetInsideDir(dx, dy: Double); public property CalloutAngle: Cardinal read FCalloutAngle write SetCalloutAngle default 0; @@ -203,6 +213,7 @@ type destructor Destroy; override; public procedure Assign(ASource: TPersistent); override; + function CenterHeightOffset(ADrawer: IChartDrawer; const AText: String): TSize; function CenterOffset(ADrawer: IChartDrawer; const AText: String): TSize; function IsMarkLabelsVisible: Boolean; procedure SetAdditionalAngle(AAngle: Double); @@ -265,6 +276,7 @@ type property LinkDistance; property LinkPen; property OverlapPolicy; + property RotationCenter; property Style default smsNone; property YIndex; end; @@ -288,6 +300,7 @@ begin Self.FMargins.Assign(FMargins); Self.FOverlapPolicy := FOverlapPolicy; Self.FShape := FShape; + Self.FInsideDir := FInsideDir; end; inherited Assign(ASource); end; @@ -311,7 +324,7 @@ procedure TChartTextElement.DrawLabel( const AText: String; var APrevLabelPoly: TPointArray); var labelPoly: TPointArray; - ptText: TPoint; + ptText, P: TPoint; i, w: Integer; begin ApplyLabelFont(ADrawer); @@ -324,8 +337,7 @@ begin labelPoly := MakeCallout( labelPoly, ALabelCenter, ADataPoint, OrientToRad(CalloutAngle)); - if - (OverlapPolicy = opHideNeighbour) and + if (OverlapPolicy = opHideNeighbour) and IsPolygonIntersectsPolygon(APrevLabelPoly, labelPoly) then exit; @@ -344,7 +356,18 @@ begin ADrawer.Polygon(labelPoly, 0, Length(labelPoly)); end; - ptText := RotatePoint(-ptText div 2, GetLabelAngle) + ALabelCenter; + case FRotationCenter of + rcCenter: P := -ptText div 2; + rcEdge, + rcLeft : begin + P := Point(0, -ptText.y div 2); + if (FRotationCenter = rcEdge) and GetTextShiftNeeded then + P.x := -ptText.x; + end; + rcRight : P := Point(-ptText.x, -ptText.y div 2); + end; + ptText := RotatePoint(P, GetLabelAngle) + ALabelCenter; + ADrawer.TextOut.Pos(ptText).Alignment(Alignment).Width(w).Text(AText).Done; if not Clipped then ADrawer.ClippingStart; @@ -368,6 +391,18 @@ function TChartTextElement.GetBoundingBox( begin Result := ZeroRect; InflateRect(Result, ATextSize.X div 2, ATextSize.Y div 2); + + case FRotationCenter of + rcCenter : ; + rcLeft, + rcEdge : begin + OffsetRect(Result, ATextSize.x div 2, 0); + if (FRotationCenter = rcEdge) and GetTextShiftNeeded then + OffsetRect(Result, -ATextSize.x, 0); + end; + rcRight : OffsetRect(Result, -ATextSize.x div 2, 0); + end; + if IsMarginRequired then Margins.ExpandRectScaled(ADrawer, Result); end; @@ -413,6 +448,14 @@ begin Result := nil; end; +function TChartTextElement.GetTextShiftNeeded: Boolean; +var + textdir: TDoublePoint; +begin + SinCos(-GetLabelAngle, textdir.y, textdir.x); + Result := DotProduct(textdir, FInsideDir) > 0; +end; + function TChartTextElement.IsMarginRequired: Boolean; begin Result := (GetLabelBrush.Style <> bsClear) or GetFrame.EffVisible; @@ -426,6 +469,19 @@ begin Result := MeasureRotatedRect(Point(Right - Left, Bottom - Top), GetLabelAngle); end; +function TChartTextElement.MeasureLabelHeight( + ADrawer: IChartDrawer; const AText: String): TSize; +var + R: TRect; +begin + ApplyLabelFont(ADrawer); + R := Rect(0, 0, 0, ADrawer.TextExtent(AText).y); + OffsetRect(R, 0, -(R.Bottom - R.Top) div 2); + if IsMarginRequired then + Margins.ExpandRectScaled(ADrawer, R); + Result := MeasureRotatedRect(Point(R.Right - R.Left, R.Bottom - R.Top), GetLabelAngle); +end; + procedure TChartTextElement.SetAlignment(AValue: TAlignment); begin if FAlignment = AValue then exit; @@ -454,6 +510,11 @@ begin StyleChanged(Self); end; +procedure TChartTextElement.SetInsideDir(dx, dy: Double); +begin + FInsideDir := DoublePoint(dx, dy); +end; + procedure TChartTextElement.SetOnGetShape(AValue: TChartGetShapeEvent); begin if TMethod(FOnGetShape) = TMethod(AValue) then exit; @@ -468,6 +529,13 @@ begin StyleChanged(Self); end; +procedure TChartTextElement.SetRotationCenter(AValue: TChartTextRotationCenter); +begin + if FRotationCenter = AValue then exit; + FRotationCenter := AValue; + StyleChanged(Self); +end; + procedure TChartTextElement.SetShape(AValue: TChartLabelShape); begin if FShape = AValue then exit; @@ -624,6 +692,15 @@ begin inherited Assign(ASource); end; +function TGenericChartMarks.CenterHeightOffset( + ADrawer: IChartDrawer; const AText: String): TSize; +var + d: Integer; +begin + d := ADrawer.Scale(Distance); + Result := Size(d, d) + MeasureLabelHeight(ADrawer, AText) div 2; +end; + function TGenericChartMarks.CenterOffset( ADrawer: IChartDrawer; const AText: String): TSize; var