TAChart: Add new properties Orientation and ViewAngle to TPieSeries. Update demo.

git-svn-id: trunk@60728 -
This commit is contained in:
wp 2019-03-19 22:11:22 +00:00
parent 4259c682c9
commit 50b7e5b2a4
4 changed files with 366 additions and 81 deletions

View File

@ -2,10 +2,10 @@ object Form1: TForm1
Left = 443 Left = 443
Height = 559 Height = 559
Top = 340 Top = 340
Width = 580 Width = 659
Caption = 'Form1' Caption = 'Form1'
ClientHeight = 559 ClientHeight = 559
ClientWidth = 580 ClientWidth = 659
OnCreate = FormCreate OnCreate = FormCreate
Position = poScreenCenter Position = poScreenCenter
LCLVersion = '2.1.0.0' LCLVersion = '2.1.0.0'
@ -13,7 +13,7 @@ object Form1: TForm1
Left = 0 Left = 0
Height = 559 Height = 559
Top = 0 Top = 0
Width = 580 Width = 659
ActivePage = tsPie ActivePage = tsPie
Align = alClient Align = alClient
TabIndex = 0 TabIndex = 0
@ -21,12 +21,12 @@ object Form1: TForm1
object tsPie: TTabSheet object tsPie: TTabSheet
Caption = 'Pie' Caption = 'Pie'
ClientHeight = 531 ClientHeight = 531
ClientWidth = 572 ClientWidth = 651
object ChartPie: TChart object ChartPie: TChart
Left = 0 Left = 0
Height = 407 Height = 407
Top = 124 Top = 124
Width = 572 Width = 651
AxisList = < AxisList = <
item item
Visible = False Visible = False
@ -71,14 +71,14 @@ object Form1: TForm1
Left = 0 Left = 0
Height = 124 Height = 124
Top = 0 Top = 0
Width = 572 Width = 651
Align = alTop Align = alTop
Alignment = taLeftJustify Alignment = taLeftJustify
Anchors = [akTop, akRight] Anchors = [akTop, akRight]
AutoSize = True AutoSize = True
BevelOuter = bvNone BevelOuter = bvNone
ClientHeight = 124 ClientHeight = 124
ClientWidth = 572 ClientWidth = 651
TabOrder = 1 TabOrder = 1
object seWords: TSpinEdit object seWords: TSpinEdit
AnchorSideLeft.Control = lblWords AnchorSideLeft.Control = lblWords
@ -400,6 +400,64 @@ object Form1: TForm1
OnChange = seStartAngleChange OnChange = seStartAngleChange
TabOrder = 12 TabOrder = 12
end end
object seViewAngle: TSpinEdit
AnchorSideLeft.Control = lblViewAngle
AnchorSideLeft.Side = asrBottom
AnchorSideTop.Control = seDistance
AnchorSideTop.Side = asrCenter
AnchorSideRight.Side = asrBottom
Left = 549
Height = 23
Top = 64
Width = 62
BorderSpacing.Right = 6
Enabled = False
MaxValue = 89
OnChange = seViewAngleChange
TabOrder = 13
Value = 60
end
object lblViewAngle: TLabel
AnchorSideLeft.Control = seDepth
AnchorSideLeft.Side = asrBottom
AnchorSideTop.Control = seViewAngle
AnchorSideTop.Side = asrCenter
Left = 484
Height = 15
Top = 68
Width = 57
BorderSpacing.Left = 12
BorderSpacing.Right = 8
Caption = 'View angle'
Enabled = False
ParentColor = False
end
object cmbOrientation: TComboBox
AnchorSideLeft.Control = lblViewAngle
AnchorSideTop.Control = cbMarkAttachment
AnchorSideTop.Side = asrBottom
AnchorSideRight.Control = seViewAngle
AnchorSideRight.Side = asrBottom
Left = 484
Height = 23
Top = 93
Width = 127
Anchors = [akTop, akLeft, akRight]
AutoSize = False
BorderSpacing.Bottom = 8
Enabled = False
ItemHeight = 15
ItemIndex = 0
Items.Strings = (
'normal'
'horizontal'
'vertical'
)
OnChange = cmbOrientationChange
Style = csDropDownList
TabOrder = 14
Text = 'normal'
end
end end
end end
object tsPolar: TTabSheet object tsPolar: TTabSheet

View File

@ -15,6 +15,7 @@ type
TForm1 = class(TForm) TForm1 = class(TForm)
cbMarkAttachment: TComboBox; cbMarkAttachment: TComboBox;
cmbOrientation: TComboBox;
ChartPolar: TChart; ChartPolar: TChart;
ChartPolarSeries1: TPolarSeries; ChartPolarSeries1: TPolarSeries;
ChartPolarSeries2: TPolarSeries; ChartPolarSeries2: TPolarSeries;
@ -29,9 +30,11 @@ type
Cb3D: TCheckBox; Cb3D: TCheckBox;
cbShowLabels: TCheckBox; cbShowLabels: TCheckBox;
cbMarkPositionsCentered: TCheckBox; cbMarkPositionsCentered: TCheckBox;
lblViewAngle: TLabel;
lblDistance: TLabel; lblDistance: TLabel;
lblStartAngle: TLabel; lblStartAngle: TLabel;
seDepth: TSpinEdit; seDepth: TSpinEdit;
seViewAngle: TSpinEdit;
seDepthBrightnessDelta: TSpinEdit; seDepthBrightnessDelta: TSpinEdit;
lblInnerRadius: TLabel; lblInnerRadius: TLabel;
lblDepth: TLabel; lblDepth: TLabel;
@ -63,6 +66,7 @@ type
Shift: TShiftState; X, Y: Integer); Shift: TShiftState; X, Y: Integer);
procedure cbShowPointsChange(Sender: TObject); procedure cbShowPointsChange(Sender: TObject);
procedure Cb3DChange(Sender: TObject); procedure Cb3DChange(Sender: TObject);
procedure cmbOrientationChange(Sender: TObject);
procedure seDepthBrightnessDeltaChange(Sender: TObject); procedure seDepthBrightnessDeltaChange(Sender: TObject);
procedure seDepthChange(Sender: TObject); procedure seDepthChange(Sender: TObject);
procedure seDistanceChange(Sender: TObject); procedure seDistanceChange(Sender: TObject);
@ -70,6 +74,7 @@ type
procedure FormCreate(Sender: TObject); procedure FormCreate(Sender: TObject);
procedure sbTransparencyChange(Sender: TObject); procedure sbTransparencyChange(Sender: TObject);
procedure seStartAngleChange(Sender: TObject); procedure seStartAngleChange(Sender: TObject);
procedure seViewAngleChange(Sender: TObject);
procedure seWordsChange(Sender: TObject); procedure seWordsChange(Sender: TObject);
procedure seLabelAngleChange(Sender: TObject); procedure seLabelAngleChange(Sender: TObject);
end; end;
@ -152,6 +157,14 @@ begin
lblDepth.Enabled := cb3D.Checked; lblDepth.Enabled := cb3D.Checked;
seDepthBrightnessDelta.Enabled := cb3D.Checked; seDepthBrightnessDelta.Enabled := cb3D.Checked;
lblDepthBrightnessDelta.Enabled := cb3D.Checked; lblDepthBrightnessDelta.Enabled := cb3D.Checked;
lblViewAngle.Enabled := cb3D.Checked;
seViewAngle.Enabled := cb3D.Checked;
cmbOrientation.Enabled := cb3D.Checked;
end;
procedure TForm1.cmbOrientationChange(Sender: TObject);
begin
ChartPiePieSeries1.Orientation := TPieOrientation(cmbOrientation.ItemIndex);
end; end;
procedure TForm1.seDepthBrightnessDeltaChange(Sender: TObject); procedure TForm1.seDepthBrightnessDeltaChange(Sender: TObject);
@ -202,6 +215,11 @@ begin
ChartPiePieSeries1.StartAngle := seStartAngle.Value; ChartPiePieSeries1.StartAngle := seStartAngle.Value;
end; end;
procedure TForm1.seViewAngleChange(Sender: TObject);
begin
ChartPiePieSeries1.ViewAngle := seViewAngle.Value;
end;
procedure TForm1.seLabelAngleChange(Sender: TObject); procedure TForm1.seLabelAngleChange(Sender: TObject);
begin begin
ChartPiePieSeries1.Marks.LabelFont.Orientation := seLabelAngle.Value * 10; ChartPiePieSeries1.Marks.LabelFont.Orientation := seLabelAngle.Value * 10;

View File

@ -62,13 +62,16 @@ type
{ TCustomPieSeries } { TCustomPieSeries }
TSliceArray = array of TPieSlice; TSliceArray = array of TPieSlice;
TPieOrientation = (poNormal, poHorizontal, poVertical);
TCustomPieSeries = class(TChartSeries) TCustomPieSeries = class(TChartSeries)
private private
FAspectRatio: Double;
FCenter: TPoint; FCenter: TPoint;
FMarkDistancePercent: Boolean; FMarkDistancePercent: Boolean;
FMarkPositionCentered: Boolean; FMarkPositionCentered: Boolean;
FMarkPositions: TPieMarkPositions; FMarkPositions: TPieMarkPositions;
FOrientation: TPieOrientation;
FRadius: Integer; FRadius: Integer;
FInnerRadiusPercent: Integer; FInnerRadiusPercent: Integer;
FSlices: array of TPieSlice; FSlices: array of TPieSlice;
@ -78,6 +81,8 @@ type
FExploded: Boolean; FExploded: Boolean;
FFixedRadius: TChartDistance; FFixedRadius: TChartDistance;
FRotateLabels: Boolean; FRotateLabels: Boolean;
function FixAspectRatio(P: TPoint): TPoint;
function GetViewAngle: Integer;
procedure Measure(ADrawer: IChartDrawer); procedure Measure(ADrawer: IChartDrawer);
procedure SetEdgePen(AValue: TPen); procedure SetEdgePen(AValue: TPen);
procedure SetExploded(AValue: Boolean); procedure SetExploded(AValue: Boolean);
@ -86,9 +91,12 @@ type
procedure SetMarkDistancePercent(AValue: Boolean); procedure SetMarkDistancePercent(AValue: Boolean);
procedure SetMarkPositionCentered(AValue: Boolean); procedure SetMarkPositionCentered(AValue: Boolean);
procedure SetMarkPositions(AValue: TPieMarkPositions); procedure SetMarkPositions(AValue: TPieMarkPositions);
procedure SetOrientation(AValue: TPieOrientation);
procedure SetRotateLabels(AValue: Boolean); procedure SetRotateLabels(AValue: Boolean);
procedure SetStartAngle(AValue: Integer); procedure SetStartAngle(AValue: Integer);
procedure SetViewAngle(AValue: Integer);
function SliceColor(AIndex: Integer): TColor; function SliceColor(AIndex: Integer): TColor;
function SliceExploded(ASlice: TPieSlice): Boolean; inline;
function TryRadius(ADrawer: IChartDrawer): TRect; function TryRadius(ADrawer: IChartDrawer): TRect;
protected protected
function CalcInnerRadius: Integer; inline; function CalcInnerRadius: Integer; inline;
@ -98,8 +106,13 @@ type
read FInnerRadiusPercent write SetInnerRadiusPercent default 0; read FInnerRadiusPercent write SetInnerRadiusPercent default 0;
property MarkPositionCentered: Boolean property MarkPositionCentered: Boolean
read FMarkPositionCentered write SetMarkPositionCentered default false; read FMarkPositionCentered write SetMarkPositionCentered default false;
property Orientation: TPieOrientation
read FOrientation write SetOrientation default poNormal;
property Radius: Integer read FRadius; property Radius: Integer read FRadius;
property StartAngle: Integer read FStartAngle write SetStartAngle default 0; property StartAngle: Integer
read FStartAngle write SetStartAngle default 0;
property ViewAngle: Integer
read GetViewAngle write SetViewAngle default 60;
public public
constructor Create(AOwner: TComponent); override; constructor Create(AOwner: TComponent); override;
destructor Destroy; override; destructor Destroy; override;
@ -283,11 +296,13 @@ procedure TCustomPieSeries.Assign(ASource: TPersistent);
begin begin
if ASource is TCustomPieSeries then if ASource is TCustomPieSeries then
with TCustomPieSeries(ASource) do begin with TCustomPieSeries(ASource) do begin
Self.FAspectRatio := FAspectRatio;
Self.FExploded := FExploded; Self.FExploded := FExploded;
Self.FFixedRadius := FFixedRadius; Self.FFixedRadius := FFixedRadius;
Self.FInnerRadiusPercent := FInnerRadiusPercent; Self.FInnerRadiusPercent := FInnerRadiusPercent;
Self.FMarkDistancePercent := FMarkDistancePercent; Self.FMarkDistancePercent := FMarkDistancePercent;
Self.FMarkPositionCentered := FMarkPositionCentered; Self.FMarkPositionCentered := FMarkPositionCentered;
Self.FOrientation := FOrientation;
Self.FRotateLabels := FRotateLabels; Self.FRotateLabels := FRotateLabels;
Self.FStartAngle := FStartAngle; Self.FStartAngle := FStartAngle;
end; end;
@ -302,6 +317,7 @@ end;
constructor TCustomPieSeries.Create(AOwner: TComponent); constructor TCustomPieSeries.Create(AOwner: TComponent);
begin begin
inherited Create(AOwner); inherited Create(AOwner);
ViewAngle := 60;
FEdgePen := TPen.Create; FEdgePen := TPen.Create;
FEdgePen.OnChange := @StyleChanged; FEdgePen.OnChange := @StyleChanged;
@ -345,16 +361,18 @@ var
end; end;
end; end;
function SliceExploded(ASlice: TPieSlice): Boolean;
begin
Result := ASlice.FBase <> FCenter;
end;
function StartEdgeVisible(ASlice: TPieSlice): Boolean; function StartEdgeVisible(ASlice: TPieSlice): Boolean;
var var
prev: TPieSlice; prev: TPieSlice;
begin begin
Result := InRange(ASlice.FPrevAngle, PI_1_4, PI_5_4); case FOrientation of
poNormal:
Result := InRange(ASlice.FPrevAngle, PI_1_4, PI_5_4);
poHorizontal:
Result := not InRange(ASlice.FPrevAngle, PI_1_2, PI_3_2);
poVertical:
Result := InRange(ASlice.FPrevAngle, 0, pi);
end;
if Result then begin if Result then begin
prev := PrevSlice(ASlice); prev := PrevSlice(ASlice);
Result := SliceExploded(ASlice) or SliceExploded(prev) or not prev.FVisible; Result := SliceExploded(ASlice) or SliceExploded(prev) or not prev.FVisible;
@ -365,7 +383,14 @@ var
var var
next: TPieSlice; next: TPieSlice;
begin begin
Result := not InRange(ASlice.FNextAngle, PI_1_4, PI_5_4); case FOrientation of
poNormal:
Result := not InRange(ASlice.FNextAngle, PI_1_4, PI_5_4);
poHorizontal:
Result := InRange(ASlice.FNextAngle, PI_1_2, PI_3_2);
poVertical:
Result := InRange(ASlice.FNextAngle, pi, TWO_PI);
end;
if Result then begin if Result then begin
next := NextSlice(ASlice); next := NextSlice(ASlice);
Result := SliceExploded(ASlice) or SliceExploded(next) or not next.FVisible; Result := SliceExploded(ASlice) or SliceExploded(next) or not next.FVisible;
@ -381,6 +406,7 @@ var
clr: TColor; clr: TColor;
r: Integer; r: Integer;
isVisible: Boolean; isVisible: Boolean;
ofs: TPoint;
begin begin
if AInside and (FInnerRadiusPercent = 0) then if AInside and (FInnerRadiusPercent = 0) then
exit; exit;
@ -389,29 +415,84 @@ var
isVisible := true isVisible := true
else else
if AInside then if AInside then
isVisible := InRange(ASlice.FPrevAngle, PI_3_4, PI_7_4) or InRange(ASlice.FNextAngle, PI_3_4, PI_7_4) case FOrientation of
poNormal:
isVisible := InRange(ASlice.FPrevAngle, PI_3_4, PI_7_4) or
InRange(ASlice.FNextAngle, PI_3_4, PI_7_4);
poHorizontal:
isVisible := InRange(ASlice.FPrevAngle, 0, pi) or
InRange(ASlice.FNextAngle, 0, pi);
poVertical:
isVisible := InRange(ASlice.FPrevAngle, PI_1_2, PI_3_2) or
InRange(ASlice.FNextAngle, PI_1_2, PI_3_2);
end
else else
isVisible := (ASlice.FPrevAngle >= PI_7_4) or (ASlice.FPrevAngle <= PI_3_4) or case FOrientation of
(ASlice.FNextAngle >= PI_7_4) or (ASlice.FNextAngle <= PI_3_4); poNormal:
isVisible := (ASlice.FPrevAngle >= PI_7_4) or (ASlice.FPrevAngle <= PI_3_4) or
(ASlice.FNextAngle >= PI_7_4) or (ASlice.FNextAngle <= PI_3_4);
poHorizontal:
isVisible := InRange(ASlice.FPrevAngle, pi, TWO_PI) or
InRange(ASlice.FNextAngle, pi, TWO_PI);
poVertical:
isVisible := (ASlice.FPrevAngle >= PI_3_2) or (ASlice.FPrevAngle <= PI_1_2) or
(ASlice.FNextAngle >= PI_3_2) or (ASlice.FNextAngle <= PI_1_2);
end;
if not isVisible then if not isVisible then
exit; exit;
if AInside then begin if AInside then begin
r := innerRadius; r := innerRadius;
angle1 := IfThen(InRange(ASlice.FPrevAngle, PI_3_4, PI_7_4), ASlice.FPrevAngle, PI_3_4); case FOrientation of
angle2 := IfThen(InRange(ASlice.FNextAngle, PI_3_4, PI_7_4), ASlice.FNextAngle, PI_7_4); poNormal:
begin
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;
poHorizontal:
begin
angle1 := IfThen(InRange(ASlice.FPrevAngle, 0, pi), ASlice.FPrevAngle, 0);
angle2 := IfThen(InRange(ASlice.FNextAngle, 0, pi), ASlice.FNextAngle, pi);
end;
poVertical:
begin
angle1 := IfThen(InRange(ASlice.FPrevAngle, PI_1_2, PI_3_2), ASlice.FPrevAngle, PI_1_2);
angle2 := IfThen(InRange(ASlice.FNextAngle, PI_1_2, PI_3_2), ASlice.FNextAngle, PI_3_2);
end;
end;
end else begin end else begin
r := FRadius; r := FRadius;
angle1 := IfThen(InRange(ASlice.FPrevAngle, PI_3_4, PI_7_4), PI_7_4, ASlice.FPrevAngle); case FOrientation of
angle2 := IfThen(InRange(ASlice.FNextAngle, PI_3_4, PI_7_4), PI_3_4, ASlice.FNextAngle); poNormal:
begin
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;
poHorizontal:
begin
angle1 := IfThen(InRange(ASlice.FPrevAngle, 0, pi), pi, ASlice.FPrevAngle);
angle2 := Ifthen(InRange(ASlice.FNextAngle, 0, pi), TWO_PI, ASlice.FNextAngle);
end;
poVertical:
begin
angle1 := IfThen(InRange(ASlice.FPrevAngle, PI_1_2, PI_3_2), PI_3_2, ASlice.FPrevAngle);
angle2 := IfThen(InRange(ASlice.FNextAngle, PI_1_2, PI_3_2), PI_1_2, ASlice.FNextAngle);
end;
end;
end; end;
if angle2 < angle1 then angle2 += TWO_PI; if angle2 < angle1 then angle2 += TWO_PI;
numSteps := Max(Round(TWO_PI * (angle2 - angle1) * r / STEP), 2); numSteps := Max(Round(TWO_PI * (angle2 - angle1) * r / STEP), 2);
SetLength(p, 2 * numSteps + 1); SetLength(p, 2 * numSteps + 1);
case FOrientation of
poNormal: ofs := Point(scaled_depth, -scaled_depth);
poHorizontal: ofs := Point(0, scaled_depth);
poVertical: ofs := Point(scaled_depth, 0);
end;
for i := 0 to numSteps - 1 do begin for i := 0 to numSteps - 1 do begin
a := WeightedAverage(angle1, angle2, i / (numSteps - 1)); a := WeightedAverage(angle1, angle2, i / (numSteps - 1));
p[i] := ASlice.FBase + RotatePointX(r, -a); p[i] := ASlice.FBase + FixAspectRatio(RotatePointX(r, -a));
p[High(p) - i - 1] := p[i] + Point(scaled_depth, -scaled_depth); p[High(p) - i - 1] := p[i] + ofs;
end; end;
p[High(p)] := p[0]; p[High(p)] := p[0];
clr := GetDepthColor(SliceColor(ASlice.FOrigIndex)); clr := GetDepthColor(SliceColor(ASlice.FOrigIndex));
@ -438,59 +519,101 @@ var
p: Array of TPoint; p: Array of TPoint;
begin begin
angle1 := ASlice.FPrevAngle; angle1 := ASlice.FPrevAngle;
if ASlice.FNextAngle < ASlice.FPrevAngle then angle2 := ASlice.FixedNextAngle;
angle2 := TWO_PI + ASlice.FNextAngle
else
angle2 := ASlice.FNextAngle;
ni := Max(Round(TWO_PI * (angle2 - angle1) * innerRadius / STEP), 2); ni := Max(Round(TWO_PI * (angle2 - angle1) * innerRadius / STEP), 2);
no := Max(Round(TWO_PI * (angle2 - angle1) * FRadius / STEP), 2); no := Max(Round(TWO_PI * (angle2 - angle1) * FRadius / STEP), 2);
SetLength(p, ni + no); SetLength(p, ni + no);
for i := 0 to no - 1 do begin for i := 0 to no - 1 do begin
a := WeightedAverage(angle1, angle2, i / (no - 1)); a := WeightedAverage(angle1, angle2, i / (no - 1));
p[i] := ASlice.FBase + RotatePointX(FRadius, -a); p[i] := ASlice.FBase + FixAspectRatio(RotatePointX(FRadius, -a));
end; end;
for i := 0 to ni - 1 do begin for i := 0 to ni - 1 do begin
a := WeightedAverage(angle1, angle2, i / (ni - 1)); a := WeightedAverage(angle1, angle2, i / (ni - 1));
p[no + ni - 1 - i] := ASlice.FBase + RotatePointX(innerRadius, -a); p[no + ni - 1 - i] := ASlice.FBase + FixAspectRatio(RotatePointX(innerRadius , -a));
end; end;
ADrawer.Polygon(p, 0, Length(p)); ADrawer.Polygon(p, 0, Length(p));
end; end;
procedure DrawEdge3D(ASlice: TPieSlice; Angle: Double);
var
P1, P2, d: TPoint;
begin
ADrawer.SetBrushParams(
bsSolid, GetDepthColor(SliceColor(ASlice.FOrigIndex)));
P1 := ASlice.FBase + FixAspectRatio(RotatePointX(innerRadius, -Angle));
P2 := ASlice.FBase + FixAspectRatio(RotatePointX(FRadius, -Angle));
case FOrientation of
poNormal:
ADrawer.DrawLineDepth(P1, P2, scaled_depth);
poHorizontal:
begin
d := Point(0, scaled_depth);
ADrawer.Polygon([P1, P1 + d, P2 + d, P2], 0, 4);
end;
poVertical:
begin
d := Point(scaled_depth, 0);
ADrawer.Polygon([P1, P1 + d, P2 + d, P2], 0, 4);
end;
end;
end;
procedure DrawStartEdge3D(ASlice: TPieSlice); procedure DrawStartEdge3D(ASlice: TPieSlice);
begin begin
if StartEdgeVisible(ASlice) then begin if StartEdgeVisible(ASlice) then
ADrawer.SetBrushParams( DrawEdge3D(ASlice, ASlice.FPrevAngle);
bsSolid, GetDepthColor(SliceColor(ASlice.FOrigIndex)));
ADrawer.DrawLineDepth(
ASlice.FBase + RotatePointX(innerRadius, -ASlice.FPrevAngle),
ASlice.FBase + RotatePointX(FRadius, -ASlice.FPrevAngle),
scaled_depth
);
end;
end; end;
procedure DrawEndEdge3D(ASlice: TPieSlice); procedure DrawEndEdge3D(ASlice: TPieSlice);
begin begin
if EndEdgeVisible(ASlice) then begin if EndEdgeVisible(ASlice) then
ADrawer.SetBrushParams( DrawEdge3D(ASlice, ASlice.FNextAngle);
bsSolid, GetDepthColor(SliceColor(ASlice.FOrigIndex))); end;
ADrawer.DrawLineDepth(
ASlice.FBase + RotatePointX(innerRadius, -ASlice.FNextAngle), procedure FixJoin(ASlice: TPieSlice);
ASlice.FBase + RotatePointX(FRadius, -ASlice.FNextAngle), var
scaled_depth P1, P2: TPoint;
); angle: Double;
r: Integer;
d: TPoint;
begin
if not ASlice.FVisible then
exit;
Angle := (ASlice.FPrevAngle + ASlice.FixedNextAngle) * 0.5;
case FOrientation of
poNormal:
begin
if not InRange(angle, PI_3_4, PI_7_4) then r := FRadius else r := CalcInnerRadius;
d := Point(scaled_depth, -scaled_depth);
end;
poHorizontal:
begin
if angle > pi then r := FRadius else r := CalcInnerRadius;
d := Point(0, scaled_depth);
end;
poVertical:
begin
if not InRange(angle, PI_1_2, PI_3_2) then r := FRadius else r := CalcInnerRadius;
d := Point(scaled_depth, 0);
end;
end; end;
P1 := ASlice.FBase + FixAspectRatio(RotatePointX(r, -Angle));
P2 := P1 + d;
ADrawer.SetPenParams(psSolid, GetDepthColor(SliceColor(ASlice.FOrigIndex)));
ADrawer.Line(P1, P2);
end; end;
var var
prevLabelPoly: TPointArray = nil; prevLabelPoly: TPointArray = nil;
ps: TPieSlice; ps: TPieSlice;
r: TPoint;
begin begin
if IsEmpty then exit; if IsEmpty then exit;
Marks.SetAdditionalAngle(0); Marks.SetAdditionalAngle(0);
Measure(ADrawer); Measure(ADrawer);
innerRadius := CalcInnerRadius; innerRadius := CalcInnerRadius;
r := FixAspectRatio(Point(FRadius, FRadius));
ADrawer.SetPen(EdgePen); ADrawer.SetPen(EdgePen);
if Depth > 0 then begin if Depth > 0 then begin
@ -503,9 +626,9 @@ begin
end; end;
// Fix edge of ulta-long slice // Fix edge of ulta-long slice
for ps in FSlices do for ps in FSlices do
if ps.Angle >= pi then begin; if ps.Angle >= pi then begin
DrawVisibleArc3D(ps); //DrawVisibleArc3D(ps);
break; FixJoin(ps);
end; end;
end; end;
@ -515,8 +638,8 @@ begin
ADrawer.SetBrushParams(bsSolid, SliceColor(ps.FOrigIndex)); ADrawer.SetBrushParams(bsSolid, SliceColor(ps.FOrigIndex));
if FInnerRadiusPercent = 0 then if FInnerRadiusPercent = 0 then
ADrawer.RadialPie( ADrawer.RadialPie(
ps.FBase.X - FRadius, ps.FBase.Y - FRadius, ps.FBase.X - r.x, ps.FBase.Y - r.y,
ps.FBase.X + FRadius, ps.FBase.Y + FRadius, ps.FBase.X + r.x, ps.FBase.Y + r.y,
RadToDeg16(ps.FPrevAngle), RadToDeg16(ps.Angle)) RadToDeg16(ps.FPrevAngle), RadToDeg16(ps.Angle))
else else
DrawRing(ps); DrawRing(ps);
@ -545,6 +668,12 @@ begin
for ps in FSlices do begin for ps in FSlices do begin
if not ps.FVisible then continue; if not ps.FVisible then continue;
c := APoint - ps.FBase; c := APoint - ps.FBase;
if (FDepth > 0) then
case FOrientation of
poNormal: ;
poHorizontal: c.Y := round(c.Y / FAspectRatio);
poVertical: c.X := round(c.X / FAspectRatio);
end;
if not InRange(sqr(c.X) + sqr(c.Y), sqr(innerRadius), sqr(FRadius)) then if not InRange(sqr(c.X) + sqr(c.Y), sqr(innerRadius), sqr(FRadius)) then
continue; continue;
pointAngle := NormalizeAngle(ArcTan2(-c.Y, c.X)); pointAngle := NormalizeAngle(ArcTan2(-c.Y, c.X));
@ -561,6 +690,17 @@ begin
Result := -1; Result := -1;
end; end;
function TCustomPieSeries.FixAspectRatio(P: TPoint): TPoint;
begin
Result := P;
if FDepth > 0 then
case FOrientation of
poNormal: ;
poVertical: Result.X := round(P.X * FAspectRatio);
poHorizontal: Result.Y := round(P.Y * FAspectRatio);
end;
end;
procedure TCustomPieSeries.GetLegendItems(AItems: TChartLegendItems); procedure TCustomPieSeries.GetLegendItems(AItems: TChartLegendItems);
var var
i: Integer; i: Integer;
@ -603,6 +743,11 @@ begin
end; end;
end; end;
function TCustomPieSeries.GetViewAngle: Integer;
begin
Result := round(arccos(FAspectRatio) / pi * 180);
end;
procedure TCustomPieSeries.Measure(ADrawer: IChartDrawer); procedure TCustomPieSeries.Measure(ADrawer: IChartDrawer);
const const
MIN_RADIUS = 5; MIN_RADIUS = 5;
@ -706,6 +851,13 @@ begin
UpdateParentChart; UpdateParentChart;
end; end;
procedure TCustomPieSeries.SetOrientation(AValue: TPieOrientation);
begin
if FOrientation = AValue then exit;
FOrientation := AValue;
UpdateParentChart;
end;
procedure TCustomPieSeries.SetRotateLabels(AValue: Boolean); procedure TCustomPieSeries.SetRotateLabels(AValue: Boolean);
begin begin
if FRotateLabels = AValue then exit; if FRotateLabels = AValue then exit;
@ -720,6 +872,13 @@ begin
UpdateParentChart; UpdateParentChart;
end; end;
procedure TCustomPieSeries.SetViewAngle(AValue: Integer);
begin
if GetViewAngle = AValue then exit;
FAspectRatio := cos(pi * EnsureRange(AValue, 0, 89) / 180);
UpdateParentChart;
end;
function TCustomPieSeries.SliceColor(AIndex: Integer): TColor; function TCustomPieSeries.SliceColor(AIndex: Integer): TColor;
const const
SLICE_COLORS: array [0..14] of TColor = ( SLICE_COLORS: array [0..14] of TColor = (
@ -733,37 +892,79 @@ begin
Result := ColorDef(Result, SLICE_COLORS[AIndex mod Length(SLICE_COLORS)]); Result := ColorDef(Result, SLICE_COLORS[AIndex mod Length(SLICE_COLORS)]);
end; end;
function TCustomPieSeries.SliceExploded(ASlice: TPieSlice): Boolean;
begin
Result := ASlice.FBase <> FCenter;
end;
type
TAngleFunc = function (ASlice: TPieSlice): Double;
function GetAngleForSortingNormal(ASlice: TPieSlice): Double;
var
next_angle: Double;
begin
next_angle := ASlice.FixedNextAngle;
if ((ASlice.FPrevAngle >= PI_5_4) or (ASlice.FPrevAngle <= PI_1_4)) and
InRange(ASlice.FNextAngle, PI_1_4, PI_5_4)
then
// Slice crossing the 45° point --> must be last slice to draw
Result := 0
else
if InRange(ASlice.FPrevAngle, PI_1_4, PI_5_4) and InRange(next_angle, PI_5_4, TWO_PI + PI_1_4)
then
// Slice crossing the 225° point --> must be first slice to draw
Result := pi
else
Result := IfThen(InRange(ASlice.FPrevAngle, PI_1_4, PI_5_4), ASlice.FPrevAngle, next_angle) - PI_1_4;
end;
function GetAngleForSortingHoriz(ASlice: TPieSlice): Double;
begin
if (InRange(ASlice.FPrevAngle, 0, PI_1_2) or InRange(ASlice.FPrevAngle, PI_3_2, TWO_pi)) and
InRange(ASlice.FNextAngle, PI_1_2, PI_3_2)
then
// Most backward slice --> must be first to draw
Result := pi
else
if InRange(ASlice.FPrevAngle, PI_1_2, PI_3_2) and
(InRange(ASlice.FNextAngle, PI_3_2, TWO_PI) or InRange(ASlice.FNextAngle, 0, PI_1_2))
then
// Most foremost slice --> must be the last to draw
Result := 0
else
Result := IfThen(InRange(ASlice.FPrevAngle, PI_3_2, TWO_PI) or InRange(ASlice.FPrevAngle, 0, PI_1_2),
ASlice.FPrevAngle, ASlice.FNextAngle) - PI_3_2
end;
function GetAngleForSortingVert(ASlice: TPieSlice): Double;
var
next_angle: Double;
begin
next_angle := ASlice.FixedNextAngle;
if (ASlice.FPrevAngle <= pi) and (ASlice.FNextAngle > pi) then
// Slice crossing the 180° point, most backward slice --> first to draw
Result := pi
else
if (ASlice.FPrevAngle >= pi) and (ASlice.FNextAngle < pi) then
// Slice crossing the 0° point, most foreward slice --> last to draw
Result := 0
else
Result :=IfThen(ASlice.FPrevAngle > pi, ASlice.FPrevAngle, next_angle);
end;
procedure TCustomPieSeries.SortSlices(out ASlices: TSliceArray); procedure TCustomPieSeries.SortSlices(out ASlices: TSliceArray);
function GetAngleForSorting(ASlice: TPieSlice): Double; function CompareSlices(ASlice1, ASlice2: TPieSlice; AngleFunc: TAngleFunc): Integer;
var
next_angle: double;
begin
next_angle := ASlice.FixedNextAngle;
if ((ASlice.FPrevAngle >= PI_5_4) or (ASlice.FPrevAngle <= PI_1_4)) and
InRange(ASlice.FNextAngle, PI_1_4, PI_5_4)
then
// Slice crossing the 45° point --> must be last slice to draw
Result := PI_1_4
else
if InRange(ASlice.FPrevAngle, PI_1_4, PI_5_4) and InRange(next_angle, PI_5_4, TWO_PI + PI_1_4)
then
// Slice crossing the 225° point --> must be first slice to draw
Result := PI_5_4
else
Result := IfThen(InRange(ASlice.FPrevAngle, PI_1_4, PI_5_4), ASlice.FPrevAngle, next_angle);
end;
function CompareSlices(ASlice1, ASlice2: TPieSlice): Integer;
var var
angle1, angle2: Double; angle1, angle2: Double;
begin begin
angle1 := GetAngleForSorting(ASlice1) - PI_1_4; angle1 := AngleFunc(ASlice1);
angle2 := GetAngleForSorting(ASlice2) - PI_1_4; angle2 := AngleFunc(ASlice2);
Result := CompareValue(cos(angle1), cos(angle2)); Result := CompareValue(cos(angle1), cos(angle2));
end; end;
procedure QuickSort(const L, R: Integer); procedure QuickSort(const L, R: Integer; AngleFunc: TAngleFunc);
var var
i, j, m: Integer; i, j, m: Integer;
ps: TPieSlice; ps: TPieSlice;
@ -772,8 +973,8 @@ procedure TCustomPieSeries.SortSlices(out ASlices: TSliceArray);
j := R; j := R;
m := (L + R) div 2; m := (L + R) div 2;
while (i <= j) do begin while (i <= j) do begin
while CompareSlices(ASlices[i], ASlices[m]) < 0 do inc(i); while CompareSlices(ASlices[i], ASlices[m], AngleFunc) < 0 do inc(i);
while CompareSlices(ASlices[j], ASlices[m]) > 0 do dec(j); while CompareSlices(ASlices[j], ASlices[m], AngleFunc) > 0 do dec(j);
if i <= j then begin if i <= j then begin
ps := ASlices[i]; ps := ASlices[i];
ASlices[i] := ASlices[j]; ASlices[i] := ASlices[j];
@ -781,13 +982,14 @@ procedure TCustomPieSeries.SortSlices(out ASlices: TSliceArray);
inc(i); inc(i);
dec(j); dec(j);
end; end;
if L < j then QuickSort(L, j); if L < j then QuickSort(L, j, AngleFunc);
if i < R then QuickSort(i, R); if i < R then QuickSort(i, R, AngleFunc);
end; end;
end; end;
var var
i, j: Integer; i, j: Integer;
f: TAngleFunc;
begin begin
SetLength(ASlices, Length(FSlices) + 1); SetLength(ASlices, Length(FSlices) + 1);
j := 0; j := 0;
@ -803,15 +1005,20 @@ begin
inc(j); inc(j);
end; end;
end; end;
case FOrientation of
poNormal: f := @GetAngleForSortingNormal;
poHorizontal: f := @GetAngleForSortingHoriz;
poVertical: f := @GetAngleForSortingVert;
end;
SetLength(ASlices, j); SetLength(ASlices, j);
QuickSort(0, High(ASlices)); QuickSort(0, High(ASlices), f);
end; end;
function TCustomPieSeries.TryRadius(ADrawer: IChartDrawer): TRect; function TCustomPieSeries.TryRadius(ADrawer: IChartDrawer): TRect;
function EndPoint(AAngle, ARadius: Double): TPoint; function EndPoint(AAngle, ARadius: Double): TPoint;
begin begin
Result := RotatePointX(ARadius, -AAngle); Result := FixAspectRatio(RotatePointX(ARadius, -AAngle));
end; end;
function LabelExtraDist(APoly: TPointArray; AAngle: Double): Double; function LabelExtraDist(APoly: TPointArray; AAngle: Double): Double;

View File

@ -133,9 +133,11 @@ type
property MarkPositionCentered; property MarkPositionCentered;
property MarkPositions; property MarkPositions;
property Marks; property Marks;
property Orientation;
property RotateLabels; property RotateLabels;
property StartAngle; property StartAngle;
property Source; property Source;
property ViewAngle;
end; end;
TConnectType = (ctLine, ctStepXY, ctStepYX); TConnectType = (ctLine, ctStepXY, ctStepYX);