{ ***************************************************************************** * * * 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, laBottomLeft, laTopRight, 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; 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(ARect.Right + SYMBOL_TEXT_SPACING, ARect.Top, FText); 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); begin if ADrawer.HasCanvas and Assigned(FOnDraw) then FOnDraw(ADrawer.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.Canvas, 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 try // Draw the background and the border. ADrawer.Font := Font; ADrawer.Brush := BackgroundBrush; ADrawer.Pen := Frame; ADrawer.Rectangle(ABounds); r := ABounds; r.Right -= 1; ADrawer.Canvas.ClipRect := r; ADrawer.Canvas.Clipping := true; 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.Canvas.Clipping := false; end; end; function TChartLegend.Prepare( ADrawer: IChartDrawer; AItems: TObjectList; var AClipRect: TRect): TRect; var w, x, y, i, textHeight, legendWidth, legendHeight: Integer; begin ADrawer.Font := Font; // Measure the legend. legendWidth := 0; textHeight := 0; for i := 0 to AItems.Count - 1 do with ADrawer.TextExtent((AItems[i] as TLegendItem).FText) do begin legendWidth := Max(X, legendWidth); textHeight := Max(Y, textHeight); end; legendWidth += 2 * Spacing + SYMBOL_TEXT_SPACING + SymbolWidth; w := 2 * MarginX; with AClipRect do legendWidth := EnsureRange(legendWidth, 0, Right - Left - w); w += legendWidth; legendHeight := Spacing + AItems.Count * (textHeight + Spacing); // Determine position according to the alignment. if Alignment in [laTopLeft, laBottomLeft] then begin x := AClipRect.Left + MarginX; if UseSidebar then AClipRect.Left += w; end else begin x := AClipRect.Right - legendWidth - MarginX; if UseSidebar then AClipRect.Right -= w; end; if Alignment in [laTopLeft, laTopRight] then y := AClipRect.Top + MarginY else y := AClipRect.Bottom - MarginY - legendHeight; Result := Bounds(x, y, legendWidth, legendHeight); 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 FOnDraw = 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.