TAChart: Add new property InnerRadiusPercent to TPieSeries for achieving a "donut" series

git-svn-id: trunk@60666 -
This commit is contained in:
wp 2019-03-14 11:13:12 +00:00
parent b2b0533619
commit a8812d76d2
4 changed files with 302 additions and 94 deletions

View File

@ -1,32 +1,32 @@
object Form1: TForm1
Left = 459
Height = 433
Height = 482
Top = 346
Width = 566
Width = 580
Caption = 'Form1'
ClientHeight = 433
ClientWidth = 566
ClientHeight = 482
ClientWidth = 580
OnCreate = FormCreate
Position = poScreenCenter
LCLVersion = '2.1.0.0'
object PageControl1: TPageControl
Left = 0
Height = 433
Height = 482
Top = 0
Width = 566
Width = 580
ActivePage = tsPie
Align = alClient
TabIndex = 0
TabOrder = 0
object tsPie: TTabSheet
Caption = 'Pie'
ClientHeight = 405
ClientWidth = 558
ClientHeight = 454
ClientWidth = 572
object ChartPie: TChart
Left = 0
Height = 337
Top = 68
Width = 558
Height = 336
Top = 118
Width = 572
AxisList = <
item
Visible = False
@ -54,6 +54,7 @@ object Form1: TForm1
Color = clDefault
OnMouseDown = ChartPieMouseDown
object ChartPiePieSeries1: TPieSeries
DepthBrightnessDelta = -32
Exploded = True
Marks.Distance = 40
Marks.Format = '%2:s'
@ -64,27 +65,26 @@ object Form1: TForm1
object Panel1: TPanel
AnchorSideTop.Side = asrCenter
Left = 0
Height = 68
Height = 118
Top = 0
Width = 558
Width = 572
Align = alTop
Alignment = taLeftJustify
AutoSize = True
Caption = ' Click on a slice to explode/unexplode it'
ClientHeight = 68
ClientWidth = 558
ClientHeight = 118
ClientWidth = 572
TabOrder = 1
object seWords: TSpinEdit
AnchorSideTop.Control = Panel1
AnchorSideRight.Control = Panel1
AnchorSideRight.Side = asrBottom
Left = 491
AnchorSideRight.Control = lblInnerRadius
Left = 326
Height = 23
Top = 9
Width = 58
Top = 7
Width = 72
Anchors = [akTop, akRight]
BorderSpacing.Top = 8
BorderSpacing.Right = 8
BorderSpacing.Top = 6
BorderSpacing.Right = 16
MaxValue = 10
OnChange = seWordsChange
TabOrder = 0
@ -94,9 +94,9 @@ object Form1: TForm1
AnchorSideTop.Control = seWords
AnchorSideTop.Side = asrCenter
AnchorSideRight.Control = seWords
Left = 420
Left = 255
Height = 15
Top = 13
Top = 11
Width = 63
Anchors = [akTop, akRight]
BorderSpacing.Right = 8
@ -104,14 +104,17 @@ object Form1: TForm1
ParentColor = False
end
object seLabelAngle: TSpinEdit
AnchorSideLeft.Control = seWords
AnchorSideTop.Control = seWords
AnchorSideRight.Control = lblWords
Left = 354
AnchorSideTop.Side = asrBottom
AnchorSideRight.Control = seWords
AnchorSideRight.Side = asrBottom
Left = 326
Height = 23
Top = 9
Width = 50
Anchors = [akTop, akRight]
BorderSpacing.Right = 16
Top = 34
Width = 72
Anchors = [akTop, akLeft, akRight]
BorderSpacing.Top = 4
Increment = 5
MaxValue = 360
MinValue = -360
@ -121,43 +124,43 @@ object Form1: TForm1
object lblLabelAngle: TLabel
AnchorSideTop.Control = seLabelAngle
AnchorSideTop.Side = asrCenter
Left = 291
AnchorSideRight.Control = seLabelAngle
Left = 258
Height = 15
Top = 13
Top = 38
Width = 60
Anchors = [akTop, akRight]
BorderSpacing.Right = 8
Caption = 'Label angle'
ParentColor = False
end
object cbRotate: TCheckBox
AnchorSideLeft.Control = Cb3D
AnchorSideLeft.Side = asrBottom
AnchorSideLeft.Control = cbMarkPositions
AnchorSideTop.Control = cbMarkPositions
AnchorSideTop.Side = asrCenter
AnchorSideTop.Side = asrBottom
AnchorSideRight.Side = asrBottom
Left = 462
Left = 255
Height = 19
Top = 38
Top = 90
Width = 87
BorderSpacing.Left = 8
BorderSpacing.Top = 4
BorderSpacing.Bottom = 8
Caption = 'Rotate labels'
OnChange = cbRotateChange
TabOrder = 2
end
object cbMarkPositions: TComboBox
AnchorSideLeft.Control = lblWords
AnchorSideTop.Control = lblLabelAngle
AnchorSideTop.Side = asrBottom
AnchorSideRight.Control = seLabelAngle
AnchorSideRight.Side = asrBottom
Left = 291
Left = 255
Height = 23
Top = 36
Width = 113
Anchors = [akTop, akRight]
Top = 61
Width = 143
Anchors = [akTop, akLeft, akRight]
BorderSpacing.Top = 8
BorderSpacing.Bottom = 8
BorderSpacing.Bottom = 6
ItemHeight = 15
ItemIndex = 0
Items.Strings = (
@ -171,30 +174,126 @@ object Form1: TForm1
Text = 'Around'
end
object Cb3D: TCheckBox
AnchorSideLeft.Control = cbMarkPositions
AnchorSideLeft.Side = asrBottom
AnchorSideTop.Control = cbMarkPositions
AnchorSideLeft.Control = lblInnerRadius
AnchorSideTop.Control = seLabelAngle
AnchorSideTop.Side = asrCenter
Left = 420
Left = 414
Height = 19
Top = 38
Top = 36
Width = 34
BorderSpacing.Left = 16
Caption = '3D'
OnChange = Cb3DChange
TabOrder = 4
end
object seInnerRadius: TSpinEdit
AnchorSideTop.Control = seWords
AnchorSideRight.Control = Panel1
AnchorSideRight.Side = asrBottom
Left = 497
Height = 23
Top = 7
Width = 62
Anchors = [akTop, akRight]
BorderSpacing.Right = 12
OnChange = seInnerRadiusChange
TabOrder = 5
end
object lblInnerRadius: TLabel
AnchorSideLeft.Side = asrBottom
AnchorSideTop.Control = lblWords
AnchorSideRight.Control = seInnerRadius
Left = 414
Height = 15
Top = 11
Width = 75
Anchors = [akTop, akRight]
BorderSpacing.Right = 8
Caption = 'Inner radius %'
ParentColor = False
end
object lblDepth: TLabel
AnchorSideLeft.Control = Cb3D
AnchorSideLeft.Side = asrBottom
AnchorSideTop.Control = Cb3D
AnchorSideTop.Side = asrCenter
AnchorSideRight.Control = seDepth
Left = 457
Height = 15
Top = 38
Width = 32
Anchors = [akTop, akRight]
BorderSpacing.Left = 12
BorderSpacing.Right = 8
Caption = 'Depth'
Enabled = False
ParentColor = False
end
object seDepth: TSpinEdit
AnchorSideLeft.Control = seInnerRadius
AnchorSideTop.Control = seLabelAngle
AnchorSideTop.Side = asrCenter
AnchorSideRight.Control = Panel1
AnchorSideRight.Side = asrBottom
Left = 497
Height = 23
Top = 34
Width = 62
Anchors = [akTop, akRight]
BorderSpacing.Right = 12
Enabled = False
OnChange = seDepthChange
TabOrder = 6
Value = 20
end
object seDepthBrightnessDelta: TSpinEdit
AnchorSideLeft.Control = seInnerRadius
AnchorSideTop.Control = seDepth
AnchorSideTop.Side = asrBottom
AnchorSideRight.Control = Panel1
AnchorSideRight.Side = asrBottom
Left = 497
Height = 23
Top = 61
Width = 62
Anchors = [akTop, akRight]
BorderSpacing.Top = 4
BorderSpacing.Right = 12
Enabled = False
MaxValue = 255
MinValue = -255
OnChange = seDepthBrightnessDeltaChange
TabOrder = 7
Value = -32
end
object lblDepthBrightnessDelta: TLabel
AnchorSideLeft.Control = cbMarkPositions
AnchorSideLeft.Side = asrBottom
AnchorSideTop.Control = seDepthBrightnessDelta
AnchorSideTop.Side = asrCenter
AnchorSideRight.Control = seDepthBrightnessDelta
Left = 398
Height = 15
Top = 65
Width = 91
Alignment = taRightJustify
Anchors = [akTop, akLeft, akRight]
BorderSpacing.Right = 8
Caption = 'Brightness delta'
Enabled = False
ParentColor = False
WordWrap = True
end
end
end
object tsPolar: TTabSheet
Caption = 'Polar'
ClientHeight = 405
ClientWidth = 558
ClientHeight = 454
ClientWidth = 572
object ChartPolar: TChart
Left = 0
Height = 405
Height = 454
Top = 0
Width = 420
Width = 457
AxisList = <
item
Marks.LabelBrush.Style = bsClear
@ -237,14 +336,14 @@ object Form1: TForm1
end
end
object pnlPolar: TPanel
Left = 420
Height = 405
Left = 457
Height = 454
Top = 0
Width = 138
Width = 115
Align = alRight
AutoSize = True
ClientHeight = 405
ClientWidth = 138
ClientHeight = 454
ClientWidth = 115
TabOrder = 1
object cbCloseCircle: TCheckBox
AnchorSideLeft.Control = pnlPolar
@ -296,7 +395,7 @@ object Form1: TForm1
Left = 9
Height = 17
Top = 117
Width = 120
Width = 97
BorderSpacing.Left = 8
BorderSpacing.Top = 4
BorderSpacing.Right = 8
@ -324,23 +423,18 @@ object Form1: TForm1
end
object ListChartSource1: TListChartSource
DataPoints.Strings = (
'0|1|?|'
'0|2|?|'
'0|3|?|'
'0|1|?|'
'0|2|?|'
'0|3|?|'
'0|1|?|'
'0|2|?|'
'0|3|?|'
'0|5|?|'
'0|3|?|'
'0|1|?|'
'0.20000000000000001|0.20000000000000001|?|'
'0|1|?|'
)
left = 488
top = 296
left = 64
top = 256
end
object ChartToolset1: TChartToolset
left = 488
top = 240
left = 64
top = 200
end
object RandomChartSource1: TRandomChartSource
PointsNumber = 15
@ -350,7 +444,7 @@ object Form1: TForm1
YCount = 2
YMax = 10
YMin = 2
left = 488
top = 356
left = 64
top = 316
end
end

View File

@ -26,6 +26,11 @@ type
cbShowPoints: TCheckBox;
cbFilled: TCheckBox;
Cb3D: TCheckBox;
seDepth: TSpinEdit;
seDepthBrightnessDelta: TSpinEdit;
lblInnerRadius: TLabel;
lblDepth: TLabel;
lblDepthBrightnessDelta: TLabel;
lblTransparency: TLabel;
lblWords: TLabel;
lblLabelAngle: TLabel;
@ -37,6 +42,7 @@ type
sbTransparency: TScrollBar;
seWords: TSpinEdit;
seLabelAngle: TSpinEdit;
seInnerRadius: TSpinEdit;
tsPolar: TTabSheet;
tsPie: TTabSheet;
procedure cbCloseCircleChange(Sender: TObject);
@ -47,6 +53,9 @@ type
Shift: TShiftState; X, Y: Integer);
procedure cbShowPointsChange(Sender: TObject);
procedure Cb3DChange(Sender: TObject);
procedure seDepthBrightnessDeltaChange(Sender: TObject);
procedure seDepthChange(Sender: TObject);
procedure seInnerRadiusChange(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure sbTransparencyChange(Sender: TObject);
procedure seWordsChange(Sender: TObject);
@ -95,10 +104,30 @@ begin
end;
procedure TForm1.Cb3DChange(Sender: TObject);
var
DEPTH: array[boolean] of Integer = (0, 10);
begin
ChartPiePieSeries1.Depth := DEPTH[Cb3D.Checked];
if cb3D.Checked then
ChartPiePieSeries1.Depth := seDepth.Value
else
ChartPiePieSeries1.Depth := 0;
seDepth.Enabled := cb3D.Checked;
lblDepth.Enabled := cb3D.Checked;
seDepthBrightnessDelta.Enabled := cb3D.Checked;
lblDepthBrightnessDelta.Enabled := cb3D.Checked;
end;
procedure TForm1.seDepthBrightnessDeltaChange(Sender: TObject);
begin
ChartPiePieSeries1.DepthBrightnessDelta := seDepthBrightnessDelta.Value;
end;
procedure TForm1.seDepthChange(Sender: TObject);
begin
ChartPiePieSeries1.Depth := seDepth.Value;
end;
procedure TForm1.seInnerRadiusChange(Sender: TObject);
begin
ChartPiePieSeries1.InnerRadiusPercent := seInnerRadius.Value;
end;
procedure TForm1.FormCreate(Sender: TObject);

View File

@ -65,6 +65,7 @@ type
FMarkDistancePercent: Boolean;
FMarkPositions: TPieMarkPositions;
FRadius: Integer;
FInnerRadiusPercent: Integer;
FSlices: array of TPieSlice;
private
FEdgePen: TPen;
@ -75,14 +76,18 @@ type
procedure SetEdgePen(AValue: TPen);
procedure SetExploded(AValue: Boolean);
procedure SetFixedRadius(AValue: TChartDistance);
procedure SetInnerRadiusPercent(AValue: Integer);
procedure SetMarkDistancePercent(AValue: Boolean);
procedure SetMarkPositions(AValue: TPieMarkPositions);
procedure SetRotateLabels(AValue: Boolean);
function SliceColor(AIndex: Integer): TColor;
function TryRadius(ADrawer: IChartDrawer): TRect;
protected
function CalcInnerRadius: Integer; inline;
procedure GetLegendItems(AItems: TChartLegendItems); override;
property Radius: Integer read FRadius;
property InnerRadiusPercent: Integer
read FInnerRadiusPercent write SetInnerRadiusPercent default 0;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
@ -243,11 +248,17 @@ begin
with TCustomPieSeries(ASource) do begin
Self.FExploded := FExploded;
Self.FFixedRadius := FFixedRadius;
Self.FInnerRadiusPercent := FInnerRadiusPercent;
Self.FRotateLabels := FRotateLabels;
end;
inherited Assign(ASource);
end;
function TCustomPieSeries.CalcInnerRadius: Integer;
begin
Result := Round(0.01 * FRadius * FInnerRadiusPercent);
end;
constructor TCustomPieSeries.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
@ -275,6 +286,7 @@ const
var
ps: TPieSlice;
scaled_depth: Integer;
innerRadius: Integer;
function PrevSlice(ASlice: TPieSlice): TPieSlice;
begin
@ -309,23 +321,42 @@ var
(SliceExploded(ASlice) or SliceExploded(NextSlice(ASlice)));
end;
procedure DrawArc3D(ASlice: TPieSlice);
procedure DrawArc3D(ASlice: TPieSlice; AInside: Boolean);
var
i, numSteps: Integer;
a: Double;
p: Array of TPoint;
angle1, angle2: Double;
clr: TColor;
r: Integer;
isVisible: Boolean;
outsideVisible: Boolean;
insideVisible: Boolean;
begin
if InRange(ASlice.FPrevAngle, PI_3_4, PI_7_4) and InRange(ASlice.FNextAngle, PI_3_4, PI_7_4) then
if AInside and (FInnerRadiusPercent = 0) then
exit;
angle1 := IfThen(InRange(ASlice.FPrevAngle, PI_3_4, PI_7_4), PI_7_4, ASlice.FPrevAngle);
angle2 := IfThen(InRange(ASlice.FNextAngle, PI_3_4, PI_7_4), PI_3_4, ASlice.FNextAngle);
numSteps := Max(Round(TWO_PI * (angle2 - angle1) * FRadius / STEP), 2);
if AInside then
isVisible := (ASlice.FPrevAngle < PI_7_4) and (ASlice.FNextAngle > PI_3_4)
else
isVisible := not (InRange(ASlice.FPrevAngle, PI_3_4, PI_7_4) and
InRange(ASlice.FNextAngle, PI_3_4, PI_7_4) );
if not isVisible then
exit;
if AInside then begin
r := innerRadius;
angle1 := IfThen(InRange(ASlice.FPrevAngle, PI_3_4, PI_7_4), ASlice.FPrevAngle, PI_3_4);
angle2 := IfThen(InRange(ASlice.FNextAngle, PI_3_4, PI_7_4), ASlice.FNextAngle, PI_7_4);
end else begin
r := FRadius;
angle1 := IfThen(InRange(ASlice.FPrevAngle, PI_3_4, PI_7_4), PI_7_4, ASlice.FPrevAngle);
angle2 := IfThen(InRange(ASlice.FNextAngle, PI_3_4, PI_7_4), PI_3_4, ASlice.FNextAngle);
end;
numSteps := Max(Round(TWO_PI * (angle2 - angle1) * r / STEP), 2);
SetLength(p, 2 * numSteps + 1);
for i := 0 to numSteps - 1 do begin
a := WeightedAverage(angle1, angle2, i / (numSteps - 1));
p[i] := ASlice.FBase + RotatePointX(FRadius, -a);
p[i] := ASlice.FBase + RotatePointX(r, -a);
p[High(p) - i - 1] := p[i] + Point(scaled_depth, -scaled_depth);
end;
p[High(p)] := p[0];
@ -337,12 +368,46 @@ var
ADrawer.PolyLine(p, numSteps-1, numSteps+2);
end;
procedure DrawVisibleArc3D(ASlice: TPieSlice);
begin
if ASlice.FVisible then begin
DrawArc3D(ASlice, false);
DrawArc3D(ASlice, true);
end;
end;
procedure DrawPieRing(ASlice: TPieSlice);
var
i: Integer;
a, angle1, angle2: Double;
ni, no: Integer;
p: Array of TPoint;
begin
angle1 := ASlice.FPrevAngle;
angle2 := ASlice.FNextAngle;
ni := Max(Round(TWO_PI * (angle2 - angle1) * innerRadius / STEP), 2);
no := Max(Round(TWO_PI * (angle2 - angle1) * FRadius / STEP), 2);
SetLength(p, ni + no);
for i := 0 to no - 1 do begin
a := WeightedAverage(angle1, angle2, i / (no - 1));
p[i] := ASlice.FBase + RotatePointX(FRadius, -a);
end;
for i := 0 to ni - 1 do begin
a := WeightedAverage(angle1, angle2, i / (ni - 1));
p[no + ni - 1 - i] := ASlice.FBase + RotatePointX(innerRadius, -a);
end;
ADrawer.Polygon(p, 0, Length(p));
end;
procedure DrawStartEdge3D(ASlice: TPieSlice);
begin
ADrawer.SetBrushParams(
bsSolid, GetDepthColor(SliceColor(ASlice.FOrigIndex)));
ADrawer.DrawLineDepth(
ASlice.FBase, ASlice.FBase + RotatePointX(FRadius, -ASlice.FPrevAngle), scaled_depth);
ASlice.FBase + RotatePointX(innerRadius, -ASlice.FPrevAngle),
ASlice.FBase + RotatePointX(FRadius, -ASlice.FPrevAngle),
scaled_depth
);
end;
procedure DrawEndEdge3D(ASlice: TPieSlice);
@ -350,7 +415,10 @@ var
ADrawer.SetBrushParams(
bsSolid, GetDepthColor(SliceColor(ASlice.FOrigIndex)));
ADrawer.DrawLineDepth(
ASlice.FBase, ASlice.FBase + RotatePointX(FRadius, -ASlice.FNextAngle), scaled_depth);
ASlice.FBase + RotatePointX(innerRadius, -ASlice.FNextAngle),
ASlice.FBase + RotatePointX(FRadius, -ASlice.FNextAngle),
scaled_depth
);
end;
procedure FindLeftMostIndex(out AIndex: Integer);
@ -373,6 +441,7 @@ begin
Marks.SetAdditionalAngle(0);
Measure(ADrawer);
innerRadius := CalcInnerRadius;
ADrawer.SetPen(EdgePen);
if Depth > 0 then begin
@ -384,7 +453,8 @@ begin
DrawStartEdge3D(FSlices[iL]);
if EndEdgeVisible(FSlices[iL]) then
DrawEndEdge3D(FSlices[iL]);
DrawArc3D(FSlices[iL]);
DrawArc3D(FSlices[iL], false);
DrawArc3D(FSlices[iL], true);
end;
for i:=iL+1 to High(FSlices) do
@ -393,7 +463,8 @@ begin
DrawStartEdge3D(FSlices[i]);
if EndEdgeVisible(FSlices[i]) then
DrawEndEdge3D(FSlices[i]);
DrawArc3D(FSlices[i]);
DrawArc3D(FSlices[i], false);
DrawArc3d(FSlices[i], true);
end;
if iL <> 0 then
@ -407,17 +478,20 @@ begin
// Draw arcs
for i:= iL-1 downto 0 do
if FSlices[i].FVisible then DrawArc3D(FSlices[i]);
DrawVisibleArc3D(FSlices[i]);
end;
ADrawer.SetPen(EdgePen);
for ps in FSlices do begin
if not ps.FVisible then continue;
ADrawer.SetBrushParams(bsSolid, SliceColor(ps.FOrigIndex));
ADrawer.RadialPie(
ps.FBase.X - FRadius, ps.FBase.Y - FRadius,
ps.FBase.X + FRadius, ps.FBase.Y + FRadius,
RadToDeg16(ps.FPrevAngle), RadToDeg16(ps.Angle));
if FInnerRadiusPercent = 0 then
ADrawer.RadialPie(
ps.FBase.X - FRadius, ps.FBase.Y - FRadius,
ps.FBase.X + FRadius, ps.FBase.Y + FRadius,
RadToDeg16(ps.FPrevAngle), RadToDeg16(ps.Angle))
else
DrawPieRing(ps);
end;
if not Marks.IsMarkLabelsVisible then exit;
@ -437,16 +511,18 @@ var
c: TPoint;
pointAngle: Double;
ps: TPieSlice;
innerRadius: Integer;
begin
for ps in FSlices do begin
if not ps.FVisible then continue;
c := APoint - ps.FBase;
pointAngle := ArcTan2(-c.Y, c.X);
innerRadius := CalcInnerRadius;
if pointAngle < 0 then
pointAngle += 2 * Pi;
if
InRange(pointAngle, ps.FPrevAngle, ps.FNextAngle) and
(Sqr(c.X) + Sqr(c.Y) <= Sqr(FRadius))
InRange(Sqr(c.X) + Sqr(c.Y), Sqr(innerRadius), Sqr(FRadius))
then
exit(ps.FOrigIndex);
end;
@ -569,6 +645,14 @@ begin
UpdateParentChart;
end;
procedure TCustomPieSeries.SetInnerRadiusPercent(AValue: Integer);
begin
AValue := EnsureRange(AValue, 0, 100);
if FInnerRadiusPercent = AValue then exit;
FInnerRadiusPercent := AValue;
UpdateParentChart;
end;
procedure TCustomPieSeries.SetMarkPositions(AValue: TPieMarkPositions);
begin
if FMarkPositions = AValue then exit;

View File

@ -128,6 +128,7 @@ type
property DepthBrightnessDelta;
property Exploded;
property FixedRadius;
property InnerRadiusPercent;
property MarkDistancePercent;
property MarkPositions;
property Marks;