TAChart: Refinements for long series labels (respect device scaling, introduce chart property MinDataSpace). Issue #34896.

git-svn-id: trunk@60151 -
This commit is contained in:
wp 2019-01-22 23:07:37 +00:00
parent 33691d63bf
commit e4ec9da618
3 changed files with 62 additions and 32 deletions

View File

@ -251,8 +251,8 @@ type
function CalcOffset(AScale: Double): Double;
function CalcScale(ASign: Integer): Double;
constructor Init(AAxis: TChartAxis; AImageLo, AImageHi: Integer;
AMarginLo, AMarginHi, ARequiredMarginLo, ARequiredMarginHi: Integer;
AMin, AMax: PDouble);
AMarginLo, AMarginHi, ARequiredMarginLo, ARequiredMarginHi, AMinDataSpace: Integer;
HasMarksInMargin: Boolean; AMin, AMax: PDouble);
procedure UpdateMinMax(AConv: TAxisConvFunc);
end;
@ -1245,11 +1245,11 @@ begin
end;
constructor TAxisCoeffHelper.Init(AAxis: TChartAxis; AImageLo, AImageHi: Integer;
AMarginLo, AMarginHi, ARequiredMarginLo, ARequiredMarginHi: Integer;
AMin, AMax: PDouble);
const
GUARANTEED_SPACE = 10;
AMarginLo, AMarginHi, ARequiredMarginLo, ARequiredMarginHi, AMinDataSpace: Integer;
HasMarksInMargin: Boolean; AMin, AMax: PDouble);
begin
Assert(AAxis <> nil);
FAxis := AAxis;
FImageLo := AImageLo;
FImageHi := AImageHi;
@ -1258,14 +1258,16 @@ begin
FLo := FImageLo + AMarginLo;
FHi := FImageHi + AMarginHi;
if Assigned(AAxis) and AAxis.IsVertical then begin
if (FHi + GUARANTEED_SPACE >= FLo) then
EnsureGuaranteedSpace(FHi, FLo, FImageHi, FImageLo,
ARequiredMarginHi, ARequiredMarginLo, GUARANTEED_SPACE);
end else begin
if (FLo + GUARANTEED_SPACE >= FHi) then
EnsureGuaranteedSpace(FLo, FHi, FImageLo, FImageHi,
ARequiredMarginLo, ARequiredMarginHi, GUARANTEED_SPACE);
if HasMarksInMargin then begin
if FAxis.IsVertical then begin
if (FHi + AMinDataSpace >= FLo) then
EnsureGuaranteedSpace(FHi, FLo, FImageHi, FImageLo,
ARequiredMarginHi, ARequiredMarginLo, AMinDataSpace);
end else begin
if (FLo + AMinDataSpace >= FHi) then
EnsureGuaranteedSpace(FLo, FHi, FImageLo, FImageHi,
ARequiredMarginLo, ARequiredMarginHi, AMinDataSpace);
end;
end;
end;
@ -1275,20 +1277,20 @@ begin
Result := ASign
else
Result := (FHi - FLo) / (FMax^ - FMin^);
if (FAxis <> nil) and FAxis.IsFlipped then
if FAxis.IsFlipped then
Result := -Result;
end;
function TAxisCoeffHelper.CalcOffset(AScale: Double): Double;
begin
Result := (FLo + FHi) / 2 - AScale * (FMin^ + FMax^) / 2;
Result := ((FLo + FHi) - AScale * (FMin^ + FMax^)) * 0.5;
end;
procedure TAxisCoeffHelper.UpdateMinMax(AConv: TAxisConvFunc);
begin
FMin^ := AConv(FImageLo);
FMax^ := AConv(FImageHi);
if (FAxis <> nil) and FAxis.IsFlipped then
if FAxis.IsFlipped then
Exchange(FMin^, FMax^);
end;

View File

@ -204,6 +204,7 @@ type
FLogicalExtent: TDoubleRect;
FMargins: TChartMargins;
FMarginsExternal: TChartMargins;
FMinDataSpace: Integer;
FOnAfterCustomDrawBackground: TChartAfterCustomDrawEvent;
FOnAfterCustomDrawBackWall: TChartAfterCustomDrawEvent;
FOnAfterDraw: TChartDrawEvent;
@ -244,7 +245,8 @@ type
FSavedClipRect: TRect;
FClipRectLock: Integer;
procedure CalculateTransformationCoeffs(const AMargin: TRect);
procedure CalculateTransformationCoeffs(const AMargin, AChartMargins: TRect;
const AMinDataSpace: Integer);
procedure DrawReticule(ADrawer: IChartDrawer); deprecated 'Use DatapointCrosshairTool instead';
procedure FindComponentClass(
AReader: TReader; const AClassName: String; var AClass: TComponentClass);
@ -271,6 +273,7 @@ type
procedure SetLogicalExtent(const AValue: TDoubleRect);
procedure SetMargins(AValue: TChartMargins);
procedure SetMarginsExternal(AValue: TChartMargins);
procedure SetMinDataSpace(const AValue: Integer);
procedure SetOnAfterCustomDrawBackground(AValue: TChartAfterCustomDrawEvent);
procedure SetOnAfterCustomDrawBackWall(AValue: TChartAfterCustomDrawEvent);
procedure SetOnAfterDraw(AValue: TChartDrawEvent);
@ -384,6 +387,8 @@ type
property ExtentBroadcaster: TBroadcaster read FExtentBroadcaster;
property IsZoomed: Boolean read FIsZoomed;
property LogicalExtent: TDoubleRect read FLogicalExtent write SetLogicalExtent;
property MinDataSpace: Integer
read FMinDataSpace write SetMinDataSpace; // default DEF_MIN_DATA_SPACE;
property OnChartPaint: TChartPaintEvent
read FOnChartPaint write SetOnChartPaint; experimental;
property PrevLogicalExtent: TDoubleRect read FPrevLogicalExtent;
@ -577,16 +582,22 @@ begin
StyleChanged(ASeries);
end;
procedure TChart.CalculateTransformationCoeffs(const AMargin: TRect);
procedure TChart.CalculateTransformationCoeffs(const AMargin, AChartMargins: TRect;
const AMinDataSpace: Integer);
var
rX, rY: TAxisCoeffHelper;
begin
rX.Init(
BottomAxis, FClipRect.Left, FClipRect.Right, AMargin.Left, -AMargin.Right,
FMargins.Left, FMargins.Right, @FCurrentExtent.a.X, @FCurrentExtent.b.X);
AChartMargins.Left, AChartMargins.Right, AMinDataSpace,
(AMargin.Left <> AChartMargins.Left) or (AMargin.Right <> AChartMargins.Right),
@FCurrentExtent.a.X, @FCurrentExtent.b.X);
rY.Init(
LeftAxis, FClipRect.Bottom, FClipRect.Top, -AMargin.Bottom, AMargin.Top,
FMargins.Bottom, FMargins.Top, @FCurrentExtent.a.Y, @FCurrentExtent.b.Y);
AChartMargins.Bottom, AChartMargins.Top, AMinDataSpace,
(AMargin.Top <> AChartMargins.Top) or (AMargin.Bottom <> AChartMargins.Bottom),
@FCurrentExtent.a.Y, @FCurrentExtent.b.Y);
FScale.X := rX.CalcScale(1);
FScale.Y := rY.CalcScale(-1);
if Proportional then begin
@ -730,6 +741,7 @@ begin
FExtentSizeLimit := TChartExtent.Create(Self);
FMargins := TChartMargins.Create(Self);
FMarginsExternal := TChartMargins.Create(Self);
FMinDataSpace := DEF_MIN_DATA_SPACE;
if OnInitBuiltinTools <> nil then
FBuiltinToolset := OnInitBuiltinTools(Self);
@ -1435,34 +1447,42 @@ var
tries: Integer;
prevExt: TDoubleRect;
axis: TChartAxis;
scaled_depth: Integer;
scDepth: Integer;
scChartMargins: TRect;
scMinDataSpace: Integer;
begin
scaled_depth := ADrawer.Scale(Depth);
scDepth := ADrawer.Scale(Depth);
scChartMargins.Left := ADrawer.Scale(Margins.Left);
scChartMargins.Right := ADrawer.Scale(Margins.Right);
scChartMargins.Top := ADrawer.Scale(Margins.Top);
scChartMargins.Bottom := ADrawer.Scale(Margins.Bottom);
scMinDataSpace := ADrawer.Scale(FMinDataSpace);
if not AxisVisible then begin
FClipRect.Left += scaled_depth;
FClipRect.Bottom -= scaled_depth;
CalculateTransformationCoeffs(GetMargins(ADrawer));
FClipRect.Left += scDepth;
FClipRect.Bottom -= scDepth;
CalculateTransformationCoeffs(GetMargins(ADrawer), scChartMargins, scMinDataSpace);
exit;
end;
AxisList.PrepareGroups;
for axis in AxisList do
axis.PrepareHelper(ADrawer, Self, @FClipRect, scaled_depth);
axis.PrepareHelper(ADrawer, Self, @FClipRect, scDepth);
// There is a cyclic dependency: extent -> visible marks -> margins.
// We recalculate them iteratively hoping that the process converges.
CalculateTransformationCoeffs(ZeroRect);
CalculateTransformationCoeffs(ZeroRect, scChartMargins, scMinDataSpace);
cr := FClipRect;
for tries := 1 to 10 do begin
axisMargin := AxisList.Measure(CurrentExtent, scaled_depth);
axisMargin[calLeft] := Max(axisMargin[calLeft], scaled_depth);
axisMargin[calBottom] := Max(axisMargin[calBottom], scaled_depth);
axisMargin := AxisList.Measure(CurrentExtent, scDepth);
axisMargin[calLeft] := Max(axisMargin[calLeft], scDepth);
axisMargin[calBottom] := Max(axisMargin[calBottom], scDepth);
FClipRect := cr;
for aa := Low(aa) to High(aa) do
SideByAlignment(FClipRect, aa, -axisMargin[aa]);
prevExt := FCurrentExtent;
FCurrentExtent := FLogicalExtent;
CalculateTransformationCoeffs(GetMargins(ADrawer));
CalculateTransformationCoeffs(GetMargins(ADrawer), scChartMargins, scMinDataSpace);
if prevExt = FCurrentExtent then break;
prevExt := FCurrentExtent;
end;
@ -1681,6 +1701,13 @@ begin
StyleChanged(Self);
end;
procedure TChart.SetMinDataSpace(const AValue: Integer);
begin
if FMinDataSpace = abs(AValue) then exit;
FMinDataSpace := abs(AValue);
StyleChanged(Self);
end;
procedure TChart.SetName(const AValue: TComponentName);
var
oldName: String;

View File

@ -27,6 +27,7 @@ uses
const
DEF_MARGIN = 4;
DEF_MIN_DATA_SPACE = 10;
DEF_MARKS_DISTANCE = 20;
DEF_POINTER_SIZE = 4;
MARKS_YINDEX_ALL = -1;