mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-02 09:23:46 +02:00
563 lines
15 KiB
ObjectPascal
563 lines
15 KiB
ObjectPascal
{
|
|
*****************************************************************************
|
|
* *
|
|
* See the file COPYING.modifiedLGPL.txt, included in this distribution, *
|
|
* for details about the copyright. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
|
|
* *
|
|
*****************************************************************************
|
|
|
|
Authors: Alexander Klenin
|
|
|
|
}
|
|
unit TALegend;
|
|
|
|
{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, Contnrs, FPCanvas, Graphics, SysUtils,
|
|
TAChartUtils, TADrawUtils, TATypes;
|
|
|
|
const
|
|
DEF_LEGEND_SPACING = 4;
|
|
DEF_LEGEND_MARGIN = 4;
|
|
DEF_LEGEND_SYMBOL_WIDTH = 20;
|
|
|
|
type
|
|
{ TLegendItem }
|
|
|
|
TLegendItem = class
|
|
private
|
|
FColor: TColor;
|
|
FText: String;
|
|
public
|
|
constructor Create(const AText: String; AColor: TColor = clTAColor);
|
|
procedure Draw(ADrawer: IChartDrawer; const ARect: TRect); virtual;
|
|
public
|
|
property Color: TColor read FColor write FColor;
|
|
end;
|
|
|
|
TLegendItemDrawEvent = procedure (
|
|
ACanvas: TCanvas; const ARect: TRect; AIndex: Integer; var AText: String
|
|
) of object;
|
|
|
|
{ TLegendItemUserDrawn }
|
|
|
|
TLegendItemUserDrawn = class(TLegendItem)
|
|
private
|
|
FIndex: Integer;
|
|
FOnDraw: TLegendItemDrawEvent;
|
|
public
|
|
constructor Create(
|
|
AIndex: Integer; AOnDraw: TLegendItemDrawEvent; const AText: String);
|
|
procedure Draw(ADrawer: IChartDrawer; const ARect: TRect); override;
|
|
property OnDraw: TLegendItemDrawEvent read FOnDraw;
|
|
end;
|
|
|
|
{ TLegendItemLine }
|
|
|
|
TLegendItemLine = class(TLegendItem)
|
|
private
|
|
FPen: TFPCustomPen;
|
|
public
|
|
constructor Create(APen: TFPCustomPen; const AText: String);
|
|
procedure Draw(ADrawer: IChartDrawer; const ARect: TRect); override;
|
|
end;
|
|
|
|
{ TLegendItemLinePointer }
|
|
|
|
TLegendItemLinePointer = class(TLegendItemLine)
|
|
protected
|
|
FPointer: TSeriesPointer;
|
|
public
|
|
constructor Create(
|
|
APen: TPen; APointer: TSeriesPointer; const AText: String);
|
|
procedure Draw(ADrawer: IChartDrawer; const ARect: TRect); override;
|
|
end;
|
|
|
|
{ TLegendItemBrushRect }
|
|
|
|
TLegendItemBrushRect = class(TLegendItem)
|
|
private
|
|
FBrush: TFPCustomBrush;
|
|
public
|
|
constructor Create(ABrush: TFPCustomBrush; const AText: String);
|
|
procedure Draw(ADrawer: IChartDrawer; const ARect: TRect); override;
|
|
end;
|
|
|
|
{ TLegendItemPieSlice }
|
|
|
|
TLegendItemPieSlice = class(TLegendItem)
|
|
public
|
|
procedure Draw(ADrawer: IChartDrawer; const ARect: TRect); override;
|
|
end;
|
|
|
|
TChartLegendItems = TObjectList;
|
|
|
|
TChartLegendBrush = class(TBrush)
|
|
published
|
|
property Color default clWhite;
|
|
end;
|
|
|
|
TLegendAlignment = (
|
|
laTopLeft, laCenterLeft, laBottomLeft,
|
|
laTopCenter, laBottomCenter, // laCenterCenter makes no sense.
|
|
laTopRight, laCenterRight, laBottomRight);
|
|
|
|
{ TChartLegend }
|
|
|
|
TChartLegend = class(TChartElement)
|
|
private
|
|
FAlignment: TLegendAlignment;
|
|
FBackgroundBrush: TChartLegendBrush;
|
|
FFont: TFont;
|
|
FFrame: TChartPen;
|
|
FMarginX: TChartDistance;
|
|
FMarginY: TChartDistance;
|
|
FSpacing: TChartDistance;
|
|
FSymbolWidth: TChartDistance;
|
|
FUseSidebar: Boolean;
|
|
|
|
procedure SetAlignment(AValue: TLegendAlignment);
|
|
procedure SetBackgroundBrush(AValue: TChartLegendBrush);
|
|
procedure SetFont(AValue: TFont);
|
|
procedure SetFrame(AValue: TChartPen);
|
|
procedure SetMargin(AValue: TChartDistance);
|
|
procedure SetMarginX(AValue: TChartDistance);
|
|
procedure SetMarginY(AValue: TChartDistance);
|
|
procedure SetSpacing(AValue: TChartDistance);
|
|
procedure SetSymbolWidth(AValue: TChartDistance);
|
|
procedure SetUseSidebar(AValue: Boolean);
|
|
public
|
|
constructor Create(AOwner: TCustomChart);
|
|
destructor Destroy; override;
|
|
|
|
public
|
|
procedure Assign(Source: TPersistent); override;
|
|
procedure Draw(
|
|
ADrawer: IChartDrawer; AItems: TObjectList; const ABounds: TRect);
|
|
function Prepare(
|
|
ADrawer: IChartDrawer; AItems: TObjectList; var AClipRect: TRect): TRect;
|
|
published
|
|
property Alignment: TLegendAlignment
|
|
read FAlignment write SetAlignment default laTopRight;
|
|
property BackgroundBrush: TChartLegendBrush
|
|
read FBackgroundBrush write SetBackgroundBrush;
|
|
property Font: TFont read FFont write SetFont;
|
|
property Frame: TChartPen read FFrame write SetFrame;
|
|
property Margin: TChartDistance
|
|
read FMarginX write SetMargin stored false; deprecated;
|
|
property MarginX: TChartDistance
|
|
read FMarginX write SetMarginX default DEF_LEGEND_MARGIN;
|
|
property MarginY: TChartDistance
|
|
read FMarginY write SetMarginY default DEF_LEGEND_MARGIN;
|
|
property Spacing: TChartDistance
|
|
read FSpacing write SetSpacing default DEF_LEGEND_SPACING;
|
|
property SymbolWidth: TChartDistance
|
|
read FSymbolWidth write SetSymbolWidth default DEF_LEGEND_SYMBOL_WIDTH;
|
|
property UseSidebar: Boolean read FUseSidebar write SetUseSidebar default true;
|
|
property Visible default false;
|
|
end;
|
|
|
|
TLegendMultiplicity = (lmSingle, lmPoint);
|
|
|
|
{ TChartSeriesLegend }
|
|
|
|
TChartSeriesLegend = class(TChartElement)
|
|
private
|
|
FMultiplicity: TLegendMultiplicity;
|
|
FOnDraw: TLegendItemDrawEvent;
|
|
FUserItemsCount: Integer;
|
|
procedure SetMultiplicity(AValue: TLegendMultiplicity);
|
|
procedure SetOnDraw(AValue: TLegendItemDrawEvent);
|
|
procedure SetUserItemsCount(AValue: Integer);
|
|
public
|
|
constructor Create(AOwner: TCustomChart);
|
|
public
|
|
procedure Assign(Source: TPersistent); override;
|
|
published
|
|
property Multiplicity: TLegendMultiplicity
|
|
read FMultiplicity write SetMultiplicity default lmSingle;
|
|
property OnDraw: TLegendItemDrawEvent read FOnDraw write SetOnDraw;
|
|
property UserItemsCount: Integer
|
|
read FUserItemsCount write SetUserItemsCount default 1;
|
|
property Visible default true;
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
Math, PropEdits, Types, TADrawerCanvas;
|
|
|
|
const
|
|
SYMBOL_TEXT_SPACING = 4;
|
|
|
|
{ TLegendItem }
|
|
|
|
constructor TLegendItem.Create(const AText: String; AColor: TColor);
|
|
begin
|
|
FColor := AColor;
|
|
FText := AText;
|
|
end;
|
|
|
|
procedure TLegendItem.Draw(ADrawer: IChartDrawer; const ARect: TRect);
|
|
begin
|
|
ADrawer.TextOut.
|
|
Pos(ARect.Right + SYMBOL_TEXT_SPACING, ARect.Top).Text(FText).Done;
|
|
end;
|
|
|
|
{ TLegendItemUserDrawn }
|
|
|
|
constructor TLegendItemUserDrawn.Create(
|
|
AIndex: Integer; AOnDraw: TLegendItemDrawEvent; const AText: String);
|
|
begin
|
|
inherited Create(AText);
|
|
FIndex := AIndex;
|
|
FOnDraw := AOnDraw;
|
|
end;
|
|
|
|
procedure TLegendItemUserDrawn.Draw(ADrawer: IChartDrawer; const ARect: TRect);
|
|
var
|
|
ic: IChartTCanvasDrawer;
|
|
begin
|
|
if Supports(ADrawer, IChartTCanvasDrawer, ic) and Assigned(FOnDraw) then
|
|
FOnDraw(ic.Canvas, ARect, FIndex, FText);
|
|
inherited Draw(ADrawer, ARect);
|
|
end;
|
|
|
|
{ TLegendItemLine }
|
|
|
|
constructor TLegendItemLine.Create(APen: TFPCustomPen; const AText: String);
|
|
begin
|
|
inherited Create(AText);
|
|
FPen := APen;
|
|
end;
|
|
|
|
procedure TLegendItemLine.Draw(ADrawer: IChartDrawer; const ARect: TRect);
|
|
var
|
|
y: Integer;
|
|
begin
|
|
inherited Draw(ADrawer, ARect);
|
|
if FPen = nil then exit;
|
|
ADrawer.Pen := FPen;
|
|
y := (ARect.Top + ARect.Bottom) div 2;
|
|
ADrawer.Line(ARect.Left, y, ARect.Right, y);
|
|
end;
|
|
|
|
{ TLegendItemLinePointer }
|
|
|
|
constructor TLegendItemLinePointer.Create(
|
|
APen: TPen; APointer: TSeriesPointer; const AText: String);
|
|
begin
|
|
inherited Create(APen, AText);
|
|
FPointer := APointer;
|
|
end;
|
|
|
|
procedure TLegendItemLinePointer.Draw(
|
|
ADrawer: IChartDrawer; const ARect: TRect);
|
|
var
|
|
c, sz: TPoint;
|
|
begin
|
|
inherited Draw(ADrawer, ARect);
|
|
if FPointer = nil then exit;
|
|
c := CenterPoint(ARect);
|
|
// Max width slightly narrower then ARect to leave place for the line.
|
|
sz.X := Min(FPointer.HorizSize, (ARect.Right - ARect.Left) div 3);
|
|
sz.Y := Min(FPointer.VertSize, (ARect.Bottom - ARect.Top) div 2);
|
|
FPointer.DrawSize(ADrawer, c, sz, Color);
|
|
end;
|
|
|
|
{ TLegendItemBrushRect }
|
|
|
|
constructor TLegendItemBrushRect.Create(
|
|
ABrush: TFPCustomBrush; const AText: String);
|
|
begin
|
|
inherited Create(AText);
|
|
FBrush := ABrush;
|
|
end;
|
|
|
|
procedure TLegendItemBrushRect.Draw(ADrawer: IChartDrawer; const ARect: TRect);
|
|
begin
|
|
inherited Draw(ADrawer, ARect);
|
|
if FBrush = nil then
|
|
ADrawer.SetBrushParams(bsSolid, IfThen(Color = clTAColor, clRed, Color))
|
|
else begin
|
|
ADrawer.Brush := FBrush;
|
|
if Color <> clTAColor then
|
|
ADrawer.SetBrushParams(FBrush.Style, Color);
|
|
end;
|
|
ADrawer.Rectangle(ARect);
|
|
end;
|
|
|
|
{ TLegendItemPieSlice }
|
|
|
|
procedure TLegendItemPieSlice.Draw(ADrawer: IChartDrawer; const ARect: TRect);
|
|
const
|
|
ANGLE = 30 * 16;
|
|
var
|
|
bc: TChartColor = clRed;
|
|
begin
|
|
inherited Draw(ADrawer, ARect);
|
|
if Color <> clTAColor then
|
|
bc := Color;
|
|
ADrawer.SetBrushParams(bsSolid, bc);
|
|
ADrawer.RadialPie(
|
|
2 * ARect.Left - ARect.Right, ARect.Top, ARect.Right, ARect.Bottom,
|
|
-ANGLE, 2 * ANGLE);
|
|
end;
|
|
|
|
{ TChartLegend }
|
|
|
|
procedure TChartLegend.Assign(Source: TPersistent);
|
|
begin
|
|
if Source is TChartLegend then
|
|
with TChartLegend(Source) do
|
|
Self.FAlignment := FAlignment;
|
|
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
constructor TChartLegend.Create(AOwner: TCustomChart);
|
|
begin
|
|
inherited Create(AOwner);
|
|
FAlignment := laTopRight;
|
|
FMarginX := DEF_LEGEND_MARGIN;
|
|
FMarginY := DEF_LEGEND_MARGIN;
|
|
FSpacing := DEF_LEGEND_SPACING;
|
|
FSymbolWidth := DEF_LEGEND_SYMBOL_WIDTH;
|
|
FUseSidebar := true;
|
|
Visible := false;
|
|
|
|
InitHelper(FBackgroundBrush, TChartLegendBrush);
|
|
InitHelper(FFont, TFont);
|
|
InitHelper(FFrame, TChartPen);
|
|
end;
|
|
|
|
destructor TChartLegend.Destroy;
|
|
begin
|
|
FreeAndNil(FBackgroundBrush);
|
|
FreeAndNil(FFont);
|
|
FreeAndNil(FFrame);
|
|
|
|
inherited;
|
|
end;
|
|
|
|
procedure TChartLegend.Draw(
|
|
ADrawer: IChartDrawer; AItems: TObjectList; const ABounds: TRect);
|
|
var
|
|
i, itemHeight: Integer;
|
|
r: TRect;
|
|
begin
|
|
// Draw the background and the border.
|
|
ADrawer.Font := Font;
|
|
ADrawer.Brush := BackgroundBrush;
|
|
ADrawer.Pen := Frame;
|
|
ADrawer.Rectangle(ABounds);
|
|
if AItems.Count = 0 then exit;
|
|
|
|
try
|
|
r := ABounds;
|
|
r.Right -= 1;
|
|
ADrawer.ClippingStart(r);
|
|
|
|
itemHeight :=
|
|
(ABounds.Bottom - ABounds.Top - Spacing) div AItems.Count - Spacing;
|
|
r := Bounds(
|
|
ABounds.Left + Spacing, ABounds.Top + Spacing, SymbolWidth, itemHeight);
|
|
for i := 0 to AItems.Count - 1 do begin
|
|
ADrawer.Font := Font;
|
|
ADrawer.Brush := BackgroundBrush;
|
|
ADrawer.Pen := Frame;
|
|
(AItems[i] as TLegendItem).Draw(ADrawer, r);
|
|
OffsetRect(r, 0, itemHeight + Spacing);
|
|
end;
|
|
finally
|
|
ADrawer.ClippingStop;
|
|
end;
|
|
end;
|
|
|
|
function TChartLegend.Prepare(
|
|
ADrawer: IChartDrawer; AItems: TObjectList; var AClipRect: TRect): TRect;
|
|
var
|
|
x, y, i, textHeight: Integer;
|
|
sidebar, legendSize: TPoint;
|
|
begin
|
|
ADrawer.Font := Font;
|
|
|
|
// Measure the legend.
|
|
legendSize.X := 0;
|
|
textHeight := 0;
|
|
for i := 0 to AItems.Count - 1 do
|
|
with ADrawer.TextExtent((AItems[i] as TLegendItem).FText) do begin
|
|
legendSize.X := Max(X, legendSize.X);
|
|
textHeight := Max(Y, textHeight);
|
|
end;
|
|
|
|
legendSize.X += 2 * Spacing + SYMBOL_TEXT_SPACING + SymbolWidth;
|
|
sidebar.X := 2 * MarginX;
|
|
with AClipRect do
|
|
legendSize.X := EnsureRange(legendSize.X, 0, Right - Left - sidebar.X);
|
|
sidebar.X += legendSize.X;
|
|
|
|
legendSize.Y := Spacing + AItems.Count * (textHeight + Spacing);
|
|
sidebar.Y := 2 * MarginX;
|
|
with AClipRect do
|
|
legendSize.Y := EnsureRange(legendSize.Y, 0, Bottom - Top - sidebar.Y);
|
|
sidebar.Y += legendSize.Y;
|
|
|
|
// Determine position according to the alignment.
|
|
case Alignment of
|
|
laTopLeft, laCenterLeft, laBottomLeft:
|
|
x := AClipRect.Left + MarginX;
|
|
laTopRight, laCenterRight, laBottomRight:
|
|
x := AClipRect.Right - legendSize.X - MarginX;
|
|
laTopCenter, laBottomCenter:
|
|
x := (AClipRect.Right + AClipRect.Left - legendSize.X) div 2;
|
|
end;
|
|
case Alignment of
|
|
laTopLeft, laTopCenter, laTopRight:
|
|
y := AClipRect.Top + MarginY;
|
|
laBottomLeft, laBottomCenter, laBottomRight:
|
|
y := AClipRect.Bottom - MarginY - legendSize.Y;
|
|
laCenterLeft, laCenterRight:
|
|
y := (AClipRect.Top + AClipRect.Bottom - legendSize.Y) div 2;
|
|
end;
|
|
if UseSidebar then
|
|
case Alignment of
|
|
laTopLeft, laCenterLeft, laBottomLeft:
|
|
AClipRect.Left += sidebar.X;
|
|
laTopRight, laCenterRight, laBottomRight:
|
|
AClipRect.Right -= sidebar.X;
|
|
laTopCenter:
|
|
AClipRect.Top += legendSize.Y + 2 * MarginY;
|
|
laBottomCenter:
|
|
AClipRect.Bottom -= legendSize.Y + 2 * MarginY;
|
|
end;
|
|
Result := Bounds(x, y, legendSize.X, legendSize.Y);
|
|
end;
|
|
|
|
procedure TChartLegend.SetAlignment(AValue: TLegendAlignment);
|
|
begin
|
|
if FAlignment = AValue then exit;
|
|
FAlignment := AValue;
|
|
StyleChanged(Self);
|
|
end;
|
|
|
|
procedure TChartLegend.SetBackgroundBrush(AValue: TChartLegendBrush);
|
|
begin
|
|
FBackgroundBrush.Assign(AValue);
|
|
StyleChanged(Self);
|
|
end;
|
|
|
|
procedure TChartLegend.SetFont(AValue: TFont);
|
|
begin
|
|
FFont.Assign(AValue);
|
|
StyleChanged(Self);
|
|
end;
|
|
|
|
procedure TChartLegend.SetFrame(AValue: TChartPen);
|
|
begin
|
|
FFrame.Assign(AValue);
|
|
StyleChanged(Self);
|
|
end;
|
|
|
|
procedure TChartLegend.SetMargin(AValue: TChartDistance);
|
|
begin
|
|
SetMarginX(AValue);
|
|
SetMarginY(AValue);
|
|
end;
|
|
|
|
procedure TChartLegend.SetMarginX(AValue: TChartDistance);
|
|
begin
|
|
if FMarginX = AValue then exit;
|
|
FMarginX := AValue;
|
|
StyleChanged(Self);
|
|
end;
|
|
|
|
procedure TChartLegend.SetMarginY(AValue: TChartDistance);
|
|
begin
|
|
if FMarginY = AValue then exit;
|
|
FMarginY := AValue;
|
|
StyleChanged(Self);
|
|
end;
|
|
|
|
procedure TChartLegend.SetSpacing(AValue: TChartDistance);
|
|
begin
|
|
if FSpacing = AValue then exit;
|
|
FSpacing := AValue;
|
|
StyleChanged(Self);
|
|
end;
|
|
|
|
procedure TChartLegend.SetSymbolWidth(AValue: TChartDistance);
|
|
begin
|
|
if FSymbolWidth = AValue then exit;
|
|
FSymbolWidth := AValue;
|
|
StyleChanged(Self);
|
|
end;
|
|
|
|
procedure TChartLegend.SetUseSidebar(AValue: Boolean);
|
|
begin
|
|
if FUseSidebar = AValue then exit;
|
|
FUseSidebar := AValue;
|
|
StyleChanged(Self);
|
|
end;
|
|
|
|
{ TChartSeriesLegend }
|
|
|
|
procedure TChartSeriesLegend.Assign(Source: TPersistent);
|
|
begin
|
|
if Source is TChartSeriesLegend then
|
|
with TChartSeriesLegend(Source) do begin
|
|
Self.FMultiplicity := FMultiplicity;
|
|
Self.FOnDraw := FOnDraw;
|
|
Self.FUserItemsCount := FUserItemsCount;
|
|
end;
|
|
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
constructor TChartSeriesLegend.Create(AOwner: TCustomChart);
|
|
begin
|
|
inherited Create(AOwner);
|
|
FVisible := true;
|
|
FUserItemsCount := 1;
|
|
end;
|
|
|
|
procedure TChartSeriesLegend.SetMultiplicity(AValue: TLegendMultiplicity);
|
|
begin
|
|
if FMultiplicity = AValue then exit;
|
|
FMultiplicity := AValue;
|
|
StyleChanged(Self);
|
|
end;
|
|
|
|
procedure TChartSeriesLegend.SetOnDraw(AValue: TLegendItemDrawEvent);
|
|
begin
|
|
if TMethod(FOnDraw) = TMethod(AValue) then exit;
|
|
FOnDraw := AValue;
|
|
StyleChanged(Self);
|
|
end;
|
|
|
|
procedure TChartSeriesLegend.SetUserItemsCount(AValue: Integer);
|
|
begin
|
|
if FUserItemsCount = AValue then exit;
|
|
FUserItemsCount := AValue;
|
|
StyleChanged(Self);
|
|
end;
|
|
|
|
procedure SkipObsoleteProperties;
|
|
begin
|
|
RegisterPropertyEditor(
|
|
TypeInfo(TChartDistance), TChartLegend, 'Margin', THiddenPropertyEditor);
|
|
end;
|
|
|
|
initialization
|
|
SkipObsoleteProperties;
|
|
|
|
end.
|
|
|