TAChart: Define bubble size in TBubbleseries as percentage - new.

This commit is contained in:
wp_xyz 2023-12-01 00:00:59 +01:00
parent dd3322cfc0
commit 120d66d06e

View File

@ -41,9 +41,10 @@ type
TBubbleOverrideColor = (bocBrush, bocPen); TBubbleOverrideColor = (bocBrush, bocPen);
TBubbleOverrideColors = set of TBubbleOverrideColor; TBubbleOverrideColors = set of TBubbleOverrideColor;
TBubbleRadiusUnits = ( TBubbleRadiusUnits = (
bruX, // Circle with radius given in x axis units bruX, // Circle with radius given in x axis units
bruY, // Circle with radius given in y axis units bruY, // Circle with radius given in y axis units
bruXY // Ellipse bruXY, // Ellipse
bruPercentage // Percentage of the smallest dimension of plot area
); );
{ TBubbleSeries } { TBubbleSeries }
@ -53,13 +54,17 @@ type
FBubbleBrush: TBrush; FBubbleBrush: TBrush;
FBubblePen: TPen; FBubblePen: TPen;
FOverrideColor: TBubbleOverrideColors; FOverrideColor: TBubbleOverrideColors;
FBubbleRadiusPercentage: Integer;
FBubbleRadiusUnits: TBubbleRadiusUnits; FBubbleRadiusUnits: TBubbleRadiusUnits;
FBubbleScalingFactor: Double;
procedure SetBubbleBrush(AValue: TBrush); procedure SetBubbleBrush(AValue: TBrush);
procedure SetBubblePen(AValue: TPen); procedure SetBubblePen(AValue: TPen);
procedure SetBubbleRadiusPercentage(AValue: Integer);
procedure SetBubbleRadiusUnits(AValue: TBubbleRadiusUnits); procedure SetBubbleRadiusUnits(AValue: TBubbleRadiusUnits);
procedure SetOverrideColor(AValue: TBubbleOverrideColors); procedure SetOverrideColor(AValue: TBubbleOverrideColors);
protected protected
function GetBubbleRect(AItem: PChartDataItem; out ARect: TRect): Boolean; function CalcBubbleScalingFactor(const ARect: TRect): Double;
function GetBubbleRect(AItem: PChartDataItem; AFactor: Double; out ARect: TRect): Boolean;
function GetLabelDataPoint(AIndex, AYIndex: Integer): TDoublePoint; override; function GetLabelDataPoint(AIndex, AYIndex: Integer): TDoublePoint; override;
procedure GetLegendItems(AItems: TChartLegendItems); override; procedure GetLegendItems(AItems: TChartLegendItems); override;
function GetSeriesColor: TColor; override; function GetSeriesColor: TColor; override;
@ -86,6 +91,8 @@ type
property AxisIndexY; property AxisIndexY;
property BubbleBrush: TBrush read FBubbleBrush write SetBubbleBrush; property BubbleBrush: TBrush read FBubbleBrush write SetBubbleBrush;
property BubblePen: TPen read FBubblePen write SetBubblePen; property BubblePen: TPen read FBubblePen write SetBubblePen;
property BubbleRadiusPercentage: Integer read FBubbleRadiusPercentage
write SetBubbleRadiusPercentage default 20;
property BubbleRadiusUnits: TBubbleRadiusUnits read FBubbleRadiusUnits property BubbleRadiusUnits: TBubbleRadiusUnits read FBubbleRadiusUnits
write SetBubbleRadiusUnits default bruXY; write SetBubbleRadiusUnits default bruXY;
property MarkPositions; property MarkPositions;
@ -532,10 +539,25 @@ begin
with TBubbleSeries(ASource) do begin with TBubbleSeries(ASource) do begin
Self.BubbleBrush := FBubbleBrush; Self.BubbleBrush := FBubbleBrush;
Self.BubblePen := FBubblePen; Self.BubblePen := FBubblePen;
Self.BubbleRadiusUnits := FBubbleRadiusUnits;
Self.BubbleRadiusPercentage := FBubbleRadiusPercentage;
Self.OverrideColor := FOverrideColor;
end; end;
inherited Assign(ASource); inherited Assign(ASource);
end; end;
function TBubbleSeries.CalcBubbleScalingFactor(const ARect: TRect): Double;
var
rMin, rMax: Double;
begin
if FBubbleRadiusUnits = bruPercentage then
begin
Source.YRange(1, rMin, rMax);
Result := Min(ARect.Width, ARect.Height) * FBubbleRadiusPercentage * PERCENT / abs(rMax);
end else
Result := 1.0;
end;
constructor TBubbleSeries.Create(AOwner: TComponent); constructor TBubbleSeries.Create(AOwner: TComponent);
begin begin
inherited Create(AOwner); inherited Create(AOwner);
@ -544,7 +566,9 @@ begin
FBubblePen.OnChange := @StyleChanged; FBubblePen.OnChange := @StyleChanged;
FBubbleBrush := TBrush.Create; FBubbleBrush := TBrush.Create;
FBubbleBrush.OnChange := @StyleChanged; FBubbleBrush.OnChange := @StyleChanged;
FBubbleRadiusPercentage := 20;
FBubbleRadiusUnits := bruXY; FBubbleRadiusUnits := bruXY;
FBubbleScalingFactor := 1.0;
end; end;
destructor TBubbleSeries.Destroy; destructor TBubbleSeries.Destroy;
@ -584,9 +608,11 @@ begin
NormalizeRect(clipR); NormalizeRect(clipR);
ADrawer.ClippingStart(clipR); ADrawer.ClippingStart(clipR);
FBubbleScalingFactor := CalcBubbleScalingFactor(clipR);
for i := 0 to Count - 1 do begin for i := 0 to Count - 1 do begin
item := Source[i]; item := Source[i];
if not GetBubbleRect(item, irect) then if not GetBubbleRect(item, FBubbleScalingFactor, irect) then
continue; continue;
if not IntersectRect(dummyR, clipR, irect) then if not IntersectRect(dummyR, clipR, irect) then
continue; continue;
@ -594,19 +620,24 @@ begin
ADrawer.SetPenParams(BubblePen.Style, ColorDef(item^.Color, BubblePen.Color)); ADrawer.SetPenParams(BubblePen.Style, ColorDef(item^.Color, BubblePen.Color));
if bocBrush in OverrideColor then if bocBrush in OverrideColor then
ADrawer.SetBrushColor(ColorDef(item^.Color, BubbleBrush.Color)); ADrawer.SetBrushColor(ColorDef(item^.Color, BubbleBrush.Color));
ADrawer.Ellipse(irect.Left, irect.Top, irect.Right, irect.Bottom); ADrawer.Ellipse(irect.Left, irect.Top, irect.Right, irect.Bottom);
end; end;
GetXYCountNeeded(nx, ny); GetXYCountNeeded(nx, ny);
if Source.YCount > ny then if Source.YCount >= ny then
for i := 0 to ny - 1 do DrawLabels(ADrawer, i) for i := 0 to ny - 1 do DrawLabels(ADrawer, i)
else else
DrawLabels(ADrawer); DrawLabels(ADrawer);
ADrawer.ClippingStop; ADrawer.ClippingStop;
end; end;
{ Calculates the extent of the series such that bubbles are not clipped.
But note that this method is correct only for BubbleRadiusUnits bruXY, it
would crash for bruX and bruY. Adjust Chart.Margins or Chart.ExpandPercentage
in these cases. }
function TBubbleSeries.Extent: TDoubleRect; function TBubbleSeries.Extent: TDoubleRect;
// to do: this method is correct only for BubbleRadiusMode bruXY.
// The radius calculation in case of bruX or bruY causes a crash.,,
var var
i: Integer; i: Integer;
r: Double; r: Double;
@ -617,27 +648,32 @@ begin
if IsEmpty then exit; if IsEmpty then exit;
if not RequestValidChartScaling then exit; if not RequestValidChartScaling then exit;
for i := 0 to Count - 1 do begin if FBubbleRadiusUnits = bruXY then
item := Source[i]; begin
sp := item^.Point; for i := 0 to Count - 1 do begin
if TAChartUtils.IsNaN(sp) then item := Source[i];
continue; sp := item^.Point;
r := item^.YList[0]; if TAChartUtils.IsNaN(sp) then
if Math.IsNaN(r) then continue;
continue; r := item^.YList[0];
rp := DoublePoint(r, r); if Math.IsNaN(r) then
gp := AxisToGraph(sp); continue;
gq := AxisToGraph(sp + rp); rp := DoublePoint(r, r);
rp := gq - gp; gp := AxisToGraph(sp);
gq := AxisToGraph(sp + rp);
rp := gq - gp;
Result.a.X := Min(Result.a.X, sp.x - rp.x); Result.a.X := Min(Result.a.X, sp.x - rp.x);
Result.b.X := Max(Result.b.X, sp.x + rp.x); Result.b.X := Max(Result.b.X, sp.x + rp.x);
Result.a.Y := Min(Result.a.Y, sp.y - rp.y); Result.a.Y := Min(Result.a.Y, sp.y - rp.y);
Result.b.Y := Max(Result.b.Y, sp.y + rp.y); Result.b.Y := Max(Result.b.Y, sp.y + rp.y);
end; end;
end else
Result := Source.BasicExtent;
end; end;
function TBubbleSeries.GetBubbleRect(AItem: PChartDataItem; out ARect: TRect): Boolean; function TBubbleSeries.GetBubbleRect(AItem: PChartDataItem;
AFactor: Double; out ARect: TRect): Boolean;
var var
sp: TDoublePoint; // source point in axis units sp: TDoublePoint; // source point in axis units
p: TPoint; // bubble center in image units p: TPoint; // bubble center in image units
@ -673,6 +709,12 @@ begin
ARect.TopLeft := ParentChart.GraphToImage(AxisToGraph(DoublePoint(sp.x - r, sp.y - r))); ARect.TopLeft := ParentChart.GraphToImage(AxisToGraph(DoublePoint(sp.x - r, sp.y - r)));
ARect.BottomRight := ParentChart.GraphToImage(AxisToGraph(DoublePoint(sp.x + r, sp.y + r))); ARect.BottomRight := ParentChart.GraphToImage(AxisToGraph(DoublePoint(sp.x + r, sp.y + r)));
end; end;
bruPercentage:
begin
p := ParentChart.GraphToImage(AxisToGraph(sp));
ri := round(r * AFactor);
ARect := Rect(p.x - ri, p.y - ri, p.x + ri, p.y + ri);
end;
end; end;
NormalizeRect(ARect); NormalizeRect(ARect);
Result := true; Result := true;
@ -691,7 +733,7 @@ var
isneg: Boolean; isneg: Boolean;
dir: TLabelDirection; dir: TLabelDirection;
begin begin
if (AYIndex = 1) and GetBubbleRect(Source.Item[AIndex + FLoBound], R) then begin if (AYIndex = 1) and GetBubbleRect(Source.Item[AIndex + FLoBound], FBubbleScalingFactor, R) then begin
isNeg := IS_NEGATIVE[MarkPositions]; isNeg := IS_NEGATIVE[MarkPositions];
if Assigned(GetAxisY) then if Assigned(GetAxisY) then
if (IsRotated and ParentChart.IsRightToLeft) xor GetAxisY.Inverted then if (IsRotated and ParentChart.IsRightToLeft) xor GetAxisY.Inverted then
@ -732,7 +774,7 @@ begin
if Result and (nptYList in AParams.FTargets) and (nptYList in ToolTargets) then if Result and (nptYList in AParams.FTargets) and (nptYList in ToolTargets) then
if (AResults.FYIndex = 1) then begin if (AResults.FYIndex = 1) then begin
item := Source[AResults.FIndex]; item := Source[AResults.FIndex];
GetBubbleRect(item, iRect); GetBubbleRect(item, FBubbleScalingFactor, iRect);
rx := (iRect.Right - iRect.Left) div 2; rx := (iRect.Right - iRect.Left) div 2;
ry := (iRect.Bottom - iRect.Top) div 2; ry := (iRect.Bottom - iRect.Top) div 2;
p := ParentChart.GraphToImage(AxisToGraph(item^.Point)); p := ParentChart.GraphToImage(AxisToGraph(item^.Point));
@ -746,7 +788,7 @@ begin
dist := MaxInt; dist := MaxInt;
for i := 0 to Count - 1 do begin for i := 0 to Count - 1 do begin
item := Source[i]; item := Source[i];
if not GetBubbleRect(item, irect) then if not GetBubbleRect(item, FBubbleScalingFactor, irect) then
continue; continue;
rx := (iRect.Right - iRect.Left) div 2; rx := (iRect.Right - iRect.Left) div 2;
ry := (iRect.Bottom - iRect.Top) div 2; ry := (iRect.Bottom - iRect.Top) div 2;
@ -862,6 +904,13 @@ begin
UpdateParentChart; UpdateParentChart;
end; end;
procedure TBubbleSeries.SetBubbleRadiusPercentage(AValue: Integer);
begin
if FBubbleRadiusPercentage = AValue then exit;
FBubbleRadiusPercentage := AValue;
UpdateParentChart;
end;
procedure TBubbleSeries.SetBubbleRadiusUnits(AValue: TBubbleRadiusUnits); procedure TBubbleSeries.SetBubbleRadiusUnits(AValue: TBubbleRadiusUnits);
begin begin
if FBubbleRadiusUnits = AValue then exit; if FBubbleRadiusUnits = AValue then exit;
@ -892,7 +941,7 @@ begin
end; end;
item := Source[APointIdx]; item := Source[APointIdx];
GetBubbleRect(item, iRect); GetBubbleRect(item, FBubbleScalingFactor, iRect);
rx := (iRect.Right - iRect.Left) div 2; rx := (iRect.Right - iRect.Left) div 2;
ry := (iRect.Bottom - iRect.Top) div 2; ry := (iRect.Bottom - iRect.Top) div 2;
p := ParentChart.GraphToImage(AxisToGraph(item^.Point)); p := ParentChart.GraphToImage(AxisToGraph(item^.Point));
@ -940,6 +989,7 @@ begin
center := AxisToGraphY((a.y + b.y) * 0.5); center := AxisToGraphY((a.y + b.y) * 0.5);
UpdateLabelDirectionReferenceLevel(0, 0, center); UpdateLabelDirectionReferenceLevel(0, 0, center);
scMarksDistance := ADrawer.Scale(Marks.Distance); scMarksDistance := ADrawer.Scale(Marks.Distance);
for i := FLoBound to FUpBound do begin for i := FLoBound to FUpBound do begin
for j := 0 to Min(1, Source.YCount-1) do begin for j := 0 to Min(1, Source.YCount-1) do begin
gp := GetLabelDataPoint(i, j); gp := GetLabelDataPoint(i, j);