lazarus/components/tachart/tatools.pas

2437 lines
65 KiB
ObjectPascal

{
*****************************************************************************
See the file COPYING.modifiedLGPL.txt, included in this distribution,
for details about the license.
*****************************************************************************
Authors: Alexander Klenin
}
unit TATools;
{$WARN 6058 off : Call to subroutine "$1" marked as inline is not inlined}
interface
{$H+}
uses
Classes, SysUtils, Types, Math, FPCanvas,
// LCL
Controls, CustomTimer, {GraphMath,} Forms, LCLPlatformDef, InterfaceBase,
LCLType, LCLIntf,
// TAChart
TAChartUtils, TADrawUtils, TAChartAxis, TALegend, TAGraph,
TATypes, TATextElements;
type
TChartToolset = class;
TChartTool = class;
TChartToolEvent = procedure (ATool: TChartTool; APoint: TPoint) of object;
TChartToolDrawingMode = (tdmDefault, tdmNormal, tdmXor);
TChartToolEffectiveDrawingMode = tdmNormal .. tdmXor;
{ TChartTool }
TChartTool = class(TBasicChartTool)
strict private
FActiveCursor: TCursor;
FDrawingMode: TChartToolDrawingMode;
FEnabled: Boolean;
FEscapeCancels: Boolean;
FEventsAfter: array [TChartToolEventId] of TChartToolEvent;
FEventsBefore: array [TChartToolEventId] of TChartToolEvent;
FOldCursor: TCursor;
FShift: TShiftState;
FToolset: TChartToolset;
FTransparency: TChartTransparency;
function GetAfterEvent(AIndex: Integer): TChartToolEvent;
function GetBeforeEvent(AIndex: Integer): TChartToolEvent;
procedure SetActiveCursor(AValue: TCursor);
procedure SetAfterEvent(AIndex: Integer; AValue: TChartToolEvent);
procedure SetBeforeEvent(AIndex: Integer; AValue: TChartToolEvent);
procedure SetDrawingMode(AValue: TChartToolDrawingMode);
procedure SetToolset(AValue: TChartToolset);
protected
procedure ReadState(Reader: TReader); override;
procedure SetParentComponent(AParent: TComponent); override;
property DrawingMode: TChartToolDrawingMode
read FDrawingMode write SetDrawingMode default tdmDefault;
strict protected
FCurrentDrawer: IChartDrawer;
FIgnoreClipRect: Boolean;
procedure Activate; override;
procedure Cancel; virtual;
procedure Deactivate; override;
function EffectiveDrawingMode: TChartToolEffectiveDrawingMode;
function GetCurrentDrawer: IChartDrawer; inline;
function GetIndex: Integer; override;
function IsActive: Boolean;
procedure KeyDown(APoint: TPoint); virtual;
procedure KeyUp(APoint: TPoint); virtual;
procedure MouseDown(APoint: TPoint); virtual;
procedure MouseMove(APoint: TPoint); virtual;
procedure MouseUp(APoint: TPoint); virtual;
procedure MouseWheelDown(APoint: TPoint); virtual;
procedure MouseWheelUp(APoint: TPoint); virtual;
function PopupMenuConflict: Boolean; override;
procedure PrepareDrawingModePen(ADrawer: IChartDrawer; APen: TFPCustomPen);
procedure RestoreCursor;
procedure SetCursor;
procedure SetIndex(AValue: Integer); override;
procedure StartTransparency(ADrawer: IChartDrawer);
property EscapeCancels: Boolean
read FEscapeCancels write FEscapeCancels default false;
property Transparency: TChartTransparency
read FTransparency write FTransparency default 0;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
public
function GetParentComponent: TComponent; override;
function HasParent: Boolean; override;
public
procedure AfterDraw(AChart: TChart; ADrawer: IChartDrawer); virtual;
procedure Assign(Source: TPersistent); override;
procedure Dispatch(
AChart: TChart; AEventId: TChartToolEventId; APoint: TPoint); overload;
procedure Draw(AChart: TChart; ADrawer: IChartDrawer); virtual;
procedure Handled;
property ActiveCursor: TCursor
read FActiveCursor write SetActiveCursor default crDefault;
property Toolset: TChartToolset read FToolset write SetToolset;
published
property Enabled: Boolean read FEnabled write FEnabled default true;
property Shift: TShiftState read FShift write FShift default [];
published
property OnAfterKeyDown: TChartToolEvent
index 0 read GetAfterEvent write SetAfterEvent;
property OnAfterKeyUp: TChartToolEvent
index 1 read GetAfterEvent write SetAfterEvent;
property OnAfterMouseDown: TChartToolEvent
index 2 read GetAfterEvent write SetAfterEvent;
property OnAfterMouseMove: TChartToolEvent
index 3 read GetAfterEvent write SetAfterEvent;
property OnAfterMouseUp: TChartToolEvent
index 4 read GetAfterEvent write SetAfterEvent;
property OnAfterMouseWheelDown: TChartToolEvent
index 5 read GetAfterEvent write SetAfterEvent;
property OnAfterMouseWheelUp: TChartToolEvent
index 6 read GetAfterEvent write SetAfterEvent;
property OnBeforeKeyDown: TChartToolEvent
index 0 read GetBeforeEvent write SetBeforeEvent;
property OnBeforeKeyUp: TChartToolEvent
index 1 read GetBeforeEvent write SetBeforeEvent;
property OnBeforeMouseDown: TChartToolEvent
index 2 read GetBeforeEvent write SetBeforeEvent;
property OnBeforeMouseMove: TChartToolEvent
index 3 read GetBeforeEvent write SetBeforeEvent;
property OnBeforeMouseUp: TChartToolEvent
index 4 read GetBeforeEvent write SetBeforeEvent;
property OnBeforeMouseWheelDown: TChartToolEvent
index 5 read GetBeforeEvent write SetBeforeEvent;
property OnBeforeMouseWheelUp: TChartToolEvent
index 6 read GetBeforeEvent write SetBeforeEvent;
end;
{$IFNDEF fpdoc} // Workaround for issue #18549.
TChartToolsEnumerator = specialize TTypedFPListEnumerator<TChartTool>;
{$ENDIF}
TChartToolClass = class of TChartTool;
TChartTools = class(TIndexedComponentList)
public
function GetEnumerator: TChartToolsEnumerator;
end;
{ TChartToolset }
TChartToolset = class(TBasicChartToolset)
strict private
FDispatchedShiftState: TShiftState;
FTools: TChartTools;
function GetItem(AIndex: Integer): TChartTool;
private
FIsHandled: Boolean;
protected
procedure SetName(const AValue: TComponentName); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
public
procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
procedure SetChildOrder(Child: TComponent; Order: Integer); override;
public
function Dispatch(
AChart: TChart; AEventId: TChartToolEventId;
AShift: TShiftState; APoint: TPoint): Boolean; override;
procedure Draw(AChart: TChart; ADrawer: IChartDrawer); override;
property DispatchedShiftState: TShiftState read FDispatchedShiftState;
property Item[AIndex: Integer]: TChartTool read GetItem; default;
published
property Tools: TChartTools read FTools;
end;
TUserDefinedTool = class(TChartTool)
end;
TZoomDirection = (zdLeft, zdUp, zdRight, zdDown);
TZoomDirectionSet = set of TZoomDirection;
{ TBasicZoomTool }
TBasicZoomTool = class(TChartTool)
strict private
FAnimationInterval: Cardinal;
FAnimationSteps: Cardinal;
FCurrentStep: Cardinal;
FExtDst: TDoubleRect;
FExtSrc: TDoubleRect;
FFullZoom: Boolean;
FLimitToExtent: TZoomDirectionSet;
FTimer: TCustomTimer;
procedure OnTimer(ASender: TObject);
protected
procedure DoZoom(ANewExtent: TDoubleRect; AFull: Boolean);
function IsAnimating: Boolean; inline;
function IsProportional: Boolean; virtual;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
public
procedure Deactivate; override;
published
property AnimationInterval: Cardinal
read FAnimationInterval write FAnimationInterval default 0;
property AnimationSteps: Cardinal
read FAnimationSteps write FAnimationSteps default 0;
property LimitToExtent: TZoomDirectionSet
read FLimitToExtent write FLimitToExtent default [];
end;
TZoomRatioLimit = (zrlNone, zrlProportional, zrlFixedX, zrlFixedY);
TZoomDragTool = class(TBasicZoomTool)
published
type
TRestoreExtentOn = (
zreDragTopLeft, zreDragTopRight, zreDragBottomLeft, zreDragBottomRight,
zreClick, zreDifferentDrag);
TRestoreExtentOnSet = set of TRestoreExtentOn;
TZoomDragBrush = TClearBrush;
strict private
FAdjustSelection: Boolean;
FBrush: TZoomDragBrush;
FFrame: TChartPen;
FPrevDragDir: TRestoreExtentOn;
FRatioLimit: TZoomRatioLimit;
FRestoreExtentOn: TRestoreExtentOnSet;
FSelectionRect: TRect;
function CalculateNewExtent: TDoubleRect;
function CalculateDrawRect: TRect;
procedure SetBrush(AValue: TZoomDragBrush);
procedure SetFrame(AValue: TChartPen);
procedure SetSelectionRect(AValue: TRect);
strict protected
procedure Cancel; override;
protected
function IsProportional: Boolean; override;
public
procedure MouseDown(APoint: TPoint); override;
procedure MouseMove(APoint: TPoint); override;
procedure MouseUp(APoint: TPoint); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Draw(AChart: TChart; ADrawer: IChartDrawer); override;
property SelectionRect: TRect read FSelectionRect write SetSelectionRect;
published
property AdjustSelection: Boolean
read FAdjustSelection write FAdjustSelection default true;
property Brush: TZoomDragBrush read FBrush write SetBrush;
property DrawingMode;
property EscapeCancels;
property Frame: TChartPen read FFrame write SetFrame;
property RatioLimit: TZoomRatioLimit
read FRatioLimit write FRatioLimit default zrlNone;
property RestoreExtentOn: TRestoreExtentOnSet
read FRestoreExtentOn write FRestoreExtentOn
default [zreDragTopLeft, zreDragTopRight, zreDragBottomLeft, zreClick];
property Transparency;
end;
TBasicZoomStepTool = class(TBasicZoomTool)
strict private
FFixedPoint: Boolean;
FZoomFactor: Double;
FZoomRatio: Double;
function ZoomFactorIsStored: boolean;
function ZoomRatioIsStored: boolean;
strict protected
procedure DoZoomStep(const APoint: TPoint; const AFactor: TDoublePoint);
protected
function IsProportional: Boolean; override;
public
constructor Create(AOwner: TComponent); override;
published
property FixedPoint: Boolean read FFixedPoint write FFixedPoint default true;
property ZoomFactor: Double
read FZoomFactor write FZoomFactor stored ZoomFactorIsStored;
property ZoomRatio: Double
read FZoomRatio write FZoomRatio stored ZoomRatioIsStored;
end;
TZoomClickTool = class(TBasicZoomStepTool)
public
procedure MouseDown(APoint: TPoint); override;
end;
TZoomMouseWheelTool = class(TBasicZoomStepTool)
public
procedure MouseWheelDown(APoint: TPoint); override;
procedure MouseWheelUp(APoint: TPoint); override;
end;
TPanDirection = (pdLeft, pdUp, pdRight, pdDown);
TPanDirectionSet = set of TPanDirection;
const
PAN_DIRECTIONS_ALL = [Low(TPanDirection) .. High(TPanDirection)];
type
{ TBasicPanTool }
TBasicPanTool = class(TChartTool)
strict private
FLimitToExtent: TPanDirectionSet;
strict protected
procedure PanBy(AOffset: TPoint);
public
constructor Create(AOwner: TComponent); override;
published
property LimitToExtent: TPanDirectionSet
read FLimitToExtent write FLimitToExtent default [];
end;
{ TPanDragTool }
TPanDragTool = class(TBasicPanTool)
strict private
FDirections: TPanDirectionSet;
FMinDragRadius: Cardinal;
FOrigin: TPoint;
FPrev: TPoint;
strict protected
procedure Activate; override;
procedure Cancel; override;
procedure Deactivate; override;
public
constructor Create(AOwner: TComponent); override;
procedure MouseDown(APoint: TPoint); override;
procedure MouseMove(APoint: TPoint); override;
procedure MouseUp(APoint: TPoint); override;
published
property ActiveCursor default crSizeAll;
property Directions: TPanDirectionSet
read FDirections write FDirections default PAN_DIRECTIONS_ALL;
property EscapeCancels;
property MinDragRadius: Cardinal
read FMinDragRadius write FMinDragRadius default 0;
end;
{ TPanClickTool }
TPanClickTool = class(TBasicPanTool)
strict private
FInterval: Cardinal;
FMargins: TChartMargins;
FOffset: TPoint;
FTimer: TCustomTimer;
function GetOffset(APoint: TPoint): TPoint;
procedure OnTimer(ASender: TObject);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
public
procedure Deactivate; override;
procedure MouseDown(APoint: TPoint); override;
procedure MouseMove(APoint: TPoint); override;
procedure MouseUp(APoint: TPoint); override;
published
property ActiveCursor default crSizeAll;
property Interval: Cardinal read FInterval write FInterval default 0;
property Margins: TChartMargins read FMargins write FMargins;
end;
{ TPanMouseWheelTool }
TPanMouseWheelTool = class(TBasicPanTool)
strict private
FStep: Cardinal;
FWheelUpDirection: TPanDirection;
procedure DoPan(AStep: Integer);
public
constructor Create(AOwner: TComponent); override;
procedure MouseWheelDown(APoint: TPoint); override;
procedure MouseWheelUp(APoint: TPoint); override;
published
property Step: Cardinal read FStep write FStep default 10;
property WheelUpDirection: TPanDirection
read FWheelUpDirection write FWheelUpDirection default pdUp;
end;
TChartDistanceMode = (cdmXY, cdmOnlyX, cdmOnlyY);
TDataPointTool = class(TChartTool)
public
type
TPointRef = class
private
FGraphPos: TDoublePoint;
FIndex: Integer;
FSeries: TBasicChartSeries;
procedure SetGraphPos(const ANewPos: TDoublePoint);
public
procedure Assign(ASource: TPointRef);
function AxisPos(ADefaultSeries: TBasicChartSeries = nil): TDoublePoint;
property GraphPos: TDoublePoint read FGraphPos;
property Index: Integer read FIndex;
property Series: TBasicChartSeries read FSeries;
end;
strict private
FAffectedSeries: TPublishedIntegerSet;
FDistanceMode: TChartDistanceMode;
FGrabRadius: Integer;
FMouseInsideOnly: Boolean;
FTargets: TNearestPointTargets;
function GetAffectedSeries: String; inline;
function GetIsSeriesAffected(AIndex: Integer): Boolean; inline;
procedure SetAffectedSeries(AValue: String); inline;
procedure SetIsSeriesAffected(AIndex: Integer; AValue: Boolean); inline;
strict protected
FNearestGraphPoint: TDoublePoint;
FPointIndex: Integer;
FXIndex: Integer;
FYIndex: Integer;
FSeries: TBasicChartSeries;
procedure FindNearestPoint(APoint: TPoint);
property MouseInsideOnly: Boolean
read FMouseInsideOnly write FMouseInsideOnly default false;
property Targets: TNearestPointTargets
read FTargets write FTargets default [nptPoint, nptXList, nptYList, nptCustom];
public
constructor Create(AOwner: TComponent); override;
public
property IsSeriesAffected[AIndex: Integer]: Boolean
read GetIsSeriesAffected write SetIsSeriesAffected;
property NearestGraphPoint: TDoublePoint read FNearestGraphPoint;
property PointIndex: Integer read FPointIndex;
property Series: TBasicChartSeries read FSeries;
property XIndex: Integer read FXIndex;
property YIndex: Integer read FYIndex;
published
property AffectedSeries: String
read GetAffectedSeries write SetAffectedSeries;
property DistanceMode: TChartDistanceMode
read FDistanceMode write FDistanceMode default cdmXY;
property GrabRadius: Integer read FGrabRadius write FGrabRadius default 4;
end;
TDataPointDragTool = class;
TDataPointDragEvent = procedure (
ASender: TDataPointDragTool; var AGraphPoint: TDoublePoint) of object;
{ TDataPointDragTool }
TDataPointDragTool = class(TDataPointTool)
strict private
FOnDrag: TDataPointDragEvent;
FOnDragStart: TDataPointDragEvent;
FOrigin: TDoublePoint;
FKeepDistance: Boolean;
FDistance: TDoublePoint;
strict protected
procedure Cancel; override;
public
constructor Create(AOwner: TComponent); override;
procedure MouseDown(APoint: TPoint); override;
procedure MouseMove(APoint: TPoint); override;
procedure MouseUp(APoint: TPoint); override;
property Origin: TDoublePoint read FOrigin;
published
property ActiveCursor default crSizeAll;
property EscapeCancels default true;
property KeepDistance: Boolean read FKeepDistance write FKeepDistance default false;
property Targets;
property OnDrag: TDataPointDragEvent read FOnDrag write FOnDrag;
property OnDragStart: TDataPointDragEvent
read FOnDragStart write FOnDragStart;
end;
{ TDataPointClickTool }
TDataPointClickTool = class(TDataPointTool)
strict private
FMouseDownPoint: TPoint;
FOnPointClick: TChartToolEvent;
public
procedure MouseDown(APoint: TPoint); override;
procedure MouseUp(APoint: TPoint); override;
published
property ActiveCursor;
property Targets;
property OnPointClick: TChartToolEvent
read FOnPointClick write FOnPointClick;
end;
TDataPointHintTool = class;
TChartToolHintEvent = procedure (
ATool: TDataPointHintTool; const APoint: TPoint; var AHint: String) of object;
TChartToolHintPositionEvent = procedure (
ATool: TDataPointHintTool; var APoint: TPoint) of object;
TChartToolHintLocationEvent = procedure (
ATool: TDataPointHintTool; AHintSize: TSize; var APoint: TPoint) of object;
{ TDataPointHintTool }
TDataPointHintTool = class(TDataPointTool)
strict private
FHintWindow: THintWindow;
FOnHint: TChartToolHintEvent;
FOnHintPosition: TChartToolHintPositionEvent;
FOnHintLocation: TChartToolHintLocationEvent;
FPrevPointIndex: Integer;
FPrevSeries: TBasicChartSeries;
FPrevYIndex: Integer;
FUseApplicationHint: Boolean;
FUseDefaultHintText: Boolean;
procedure HideHint;
procedure SetUseApplicationHint(AValue: Boolean);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure KeyDown(APoint: TPoint); override;
procedure KeyUp(APoint: TPoint); override;
procedure MouseDown(APoint: TPoint); override;
procedure MouseMove(APoint: TPoint); override;
procedure MouseUp(APoint: TPoint); override;
published
property ActiveCursor;
property Targets;
property OnHint: TChartToolHintEvent read FOnHint write FOnHint;
property OnHintLocation: TChartToolHintLocationEvent
read FOnHintLocation write FOnHintLocation;
property OnHintPosition: TChartToolHintPositionEvent
read FOnHintPosition write FOnHintPosition;
property UseApplicationHint: Boolean
read FUseApplicationHint write SetUseApplicationHint default false;
property UseDefaultHintText: Boolean
read FUseDefaultHintText write FUseDefaultHintText default true;
property MouseInsideOnly;
end;
{ TDataPointDrawTool }
TDataPointDrawTool = class;
TChartDataPointCustomDrawEvent = procedure (
ASender: TDataPointDrawTool; ADrawer: IChartDrawer) of object;
TChartDataPointDrawEvent = procedure (
ASender: TDataPointDrawTool) of object;
TDataPointDrawTool = class(TDataPointTool)
strict private
FOnCustomDraw: TChartDataPointCustomDrawEvent;
FOnDraw: TChartDataPointDrawEvent;
strict protected
FPen: TChartPen;
procedure DoDraw(ADrawer: IChartDrawer); virtual;
procedure DoHide(ADrawer: IChartDrawer); virtual;
procedure SetPen(AValue: TChartPen);
// deprecated
procedure DoDraw; virtual; deprecated;
procedure DoHide; virtual; deprecated;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Draw(AChart: TChart; ADrawer: IChartDrawer); override;
procedure Hide; virtual;
published
property DrawingMode;
property GrabRadius default 20;
property OnCustomDraw: TChartDataPointCustomDrawEvent
read FOnCustomDraw write FOnCustomDraw;
property OnDraw: TChartDataPointDrawEvent
read FOnDraw write FOnDraw; deprecated 'Use OnCustomDraw';
property MouseInsideOnly;
end;
TChartCrosshairShape = (ccsNone, ccsVertical, ccsHorizontal, ccsCross);
{ TDataPointCrossHairTool }
TDataPointCrosshairTool = class(TDataPointDrawTool)
strict private
FPosition: TDoublePoint;
FShape: TChartCrosshairShape;
FSize: Integer;
strict protected
procedure DoDraw(ADrawer: IChartDrawer); override;
procedure DoHide(ADrawer: IChartDrawer); override;
public
constructor Create(AOwner: TComponent); override;
procedure Draw(AChart: TChart; ADrawer: IChartDrawer); override;
procedure KeyDown(APoint: TPoint); override;
procedure MouseDown(APoint: TPoint); override;
procedure MouseMove(APoint: TPoint); override;
property Position: TDoublePoint read FPosition;
published
property CrosshairPen: TChartPen read FPen write SetPen;
property Shape: TChartCrosshairShape
read FShape write FShape default ccsCross;
property Size: Integer read FSize write FSize default -1;
property Targets;
end;
TAxisClickEvent = procedure (ASender: TChartTool; Axis: TChartAxis;
AHitInfo: TChartAxisHitTests) of object;
TAxisClickTool = class(TChartTool)
private
FAxis: TChartAxis;
FGrabRadius: Integer;
FHitTest: TChartAxisHitTests;
FOnClick: TAxisClickEvent;
protected
function GetHitTestInfo(APoint: TPoint): boolean;
public
constructor Create(AOwner: TComponent); override;
procedure MouseDown(APoint: TPoint); override;
procedure MouseUp(APoint: TPoint); override;
published
property GrabRadius: Integer read FGrabRadius write FGrabRadius default 4;
property OnClick: TAxisClickEvent read FOnClick write FOnClick;
end;
TTitleFootClickEvent = procedure (ASender: TChartTool;
ATitle: TChartTitle) of object;
TTitleFootClickTool = class(TChartTool)
private
FOnClick: TTitleFootClickEvent;
FTitle: TChartTitle;
protected
function GetHit(APoint: TPoint): Boolean;
public
constructor Create(AOwner: TComponent); override;
procedure MouseDown(APoint: TPoint); override;
procedure MouseUp(APoint: TPoint); override;
published
property OnClick: TTitleFootClickEvent read FOnClick write FOnClick;
end;
TLegendClickEvent = procedure (ASender: TChartTool;
ALegend: TChartLegend) of object;
TLegendSeriesClickEvent = procedure (ASender: TChartTool;
ALegend: TChartLegend; ASeries: TBasicChartSeries) of object;
TLegendClickTool = class(TChartTool)
private
FOnClick: TLegendClickEvent;
FOnSeriesClick: TLegendSeriesClickEvent;
FLegend: TChartLegend;
public
constructor Create(AOwner: TComponent); override;
procedure MouseDown(APoint: TPoint); override;
procedure MouseUp(APoint: TPoint); override;
published
property OnClick: TLegendClickEvent read FOnClick write FOnClick;
property OnSeriesClick: TLegendSeriesClickEvent read FOnSeriesClick write FOnSeriesClick;
end;
procedure Register;
procedure RegisterChartToolClass(AToolClass: TChartToolClass;
const ACaption: String); overload;
procedure RegisterChartToolClass(AToolClass: TChartToolClass;
ACaptionPtr: PStr); overload;
var
ToolsClassRegistry: TClassRegistry = nil;
implementation
uses
LResources,
TAChartStrConsts, TACustomSeries, TAEnumerators, TAGeometry, TAMath;
function InitBuiltinTools(AChart: TChart): TBasicChartToolset;
var
ts: TChartToolset;
begin
ts := TChartToolset.Create(AChart);
Result := ts;
with TZoomDragTool.Create(AChart) do begin
Shift := [ssLeft];
Toolset := ts;
end;
with TPanDragTool.Create(AChart) do begin
Shift := [ssRight];
Toolset := ts;
end;
end;
procedure Register;
var
i: Integer;
begin
for i := 0 to ToolsClassRegistry.Count - 1 do
RegisterNoIcon([TChartToolClass(ToolsClassRegistry.GetClass(i))]);
RegisterComponents(CHART_COMPONENT_IDE_PAGE, [TChartToolset]);
end;
procedure RegisterChartToolClass(AToolClass: TChartToolClass;
const ACaption: String);
begin
RegisterClass(AToolClass);
if ToolsClassRegistry.IndexOfClass(AToolClass) < 0 then
ToolsClassRegistry.Add(TClassRegistryItem.Create(AToolClass, ACaption));
end;
procedure RegisterChartToolClass(AToolClass: TChartToolClass; ACaptionPtr: PStr);
begin
RegisterClass(AToolClass);
if ToolsClassRegistry.IndexOfClass(AToolClass) < 0 then
ToolsClassRegistry.Add(TClassRegistryItem.CreateRes(AToolClass, ACaptionPtr));
end;
{ TDataPointTool.TPointRef }
procedure TDataPointTool.TPointRef.Assign(ASource: TPointRef);
begin
with ASource do begin
Self.FGraphPos := FGraphPos;
Self.FIndex := FIndex;
Self.FSeries := FSeries;
end;
end;
function TDataPointTool.TPointRef.AxisPos(
ADefaultSeries: TBasicChartSeries): TDoublePoint;
var
s: TBasicChartSeries;
begin
s := Series;
if s = nil then
s := ADefaultSeries;
if s = nil then
Result := GraphPos
else
Result := DoublePoint(s.GraphToAxisX(GraphPos.X), s.GraphToAxisY(GraphPos.Y));
end;
procedure TDataPointTool.TPointRef.SetGraphPos(const ANewPos: TDoublePoint);
begin
FGraphPos := ANewPos;
FIndex := -1;
FSeries := nil;
end;
{ TChartTool }
procedure TChartTool.Activate;
var
i: Integer;
begin
i := FChart.ActiveToolIndex;
if (i <> Index) and InRange(i, 0, Toolset.Tools.Count) then
Toolset[i].Deactivate;
FCurrentDrawer := nil;
inherited;
SetCursor;
end;
procedure TChartTool.AfterDraw(AChart: TChart; ADrawer: IChartDrawer);
begin
Unused(AChart, ADrawer);
FCurrentDrawer := AChart.Drawer;
if not IsActive then
FChart := nil;
end;
procedure TChartTool.Assign(Source: TPersistent);
begin
if Source is TChartTool then
with TChartTool(Source) do begin
Self.FEnabled := Enabled;
Self.FShift := Shift;
end
else
inherited Assign(Source);
end;
procedure TChartTool.Cancel;
begin
// Empty.
end;
constructor TChartTool.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FEnabled := true;
FActiveCursor := crDefault;
end;
procedure TChartTool.Deactivate;
begin
RestoreCursor;
inherited;
end;
destructor TChartTool.Destroy;
begin
Toolset := nil;
inherited;
end;
procedure TChartTool.Dispatch(
AChart: TChart; AEventId: TChartToolEventId; APoint: TPoint);
var
ev: TChartToolEvent;
begin
if not Enabled or (FChart <> nil) and (FChart <> AChart) then exit;
FChart := AChart;
ev := FEventsBefore[AEventId];
if Assigned(ev) then begin
ev(Self, APoint);
if Toolset.FIsHandled then exit;
end;
case AEventId of
evidKeyDown : KeyDown (APoint);
evidKeyUp : KeyUp (APoint);
evidMouseWheelDown: MouseWheelDown(APoint);
evidMouseWheelUp : MouseWheelUp (APoint);
evidMouseMove : MouseMove (APoint);
evidMouseUp : MouseUp (APoint);
evidMouseDown : if FIgnoreClipRect or PtInRect(FChart.ClipRect, APoint) then
MouseDown(APoint);
end;
ev := FEventsAfter[AEventId];
if Assigned(ev) then
ev(Self, APoint);
if not IsActive then
FChart := nil;
end;
procedure TChartTool.Draw(AChart: TChart; ADrawer: IChartDrawer);
begin
Unused(ADrawer);
FChart := AChart;
FCurrentDrawer := ADrawer;
end;
function TChartTool.EffectiveDrawingMode: TChartToolEffectiveDrawingMode;
begin
if DrawingMode <> tdmDefault then
Result := DrawingMode
else if WidgetSet.LCLPlatform in [lpGtk, lpGtk2, lpWin32] then
Result := tdmXor
else
Result := tdmNormal;
end;
function TChartTool.GetAfterEvent(AIndex: Integer): TChartToolEvent;
begin
Result := FEventsAfter[TChartToolEventId(AIndex)];
end;
function TChartTool.GetBeforeEvent(AIndex: Integer): TChartToolEvent;
begin
Result := FEventsBefore[TChartToolEventId(AIndex)];
end;
function TChartTool.GetIndex: Integer;
begin
if Toolset = nil then
Result := -1
else
Result := Toolset.Tools.IndexOf(Self);
end;
function TChartTool.GetCurrentDrawer: IChartDrawer;
begin
if FCurrentDrawer <> nil then
Result := FCurrentDrawer
else
if Assigned(FChart) then
Result := FChart.Drawer
else
Result := nil;
end;
function TChartTool.GetParentComponent: TComponent;
begin
Result := FToolset;
end;
procedure TChartTool.Handled;
begin
Toolset.FIsHandled := true;
end;
function TChartTool.HasParent: Boolean;
begin
Result := true;
end;
function TChartTool.IsActive: Boolean;
begin
Result := (FChart <> nil) and (FChart.ActiveToolIndex = Index);
end;
procedure TChartTool.KeyDown(APoint: TPoint);
begin
Unused(APoint);
if EscapeCancels and ((GetKeyState(VK_ESCAPE) and $8000) <> 0) then
Cancel;
end;
procedure TChartTool.KeyUp(APoint: TPoint);
begin
Unused(APoint);
end;
procedure TChartTool.MouseDown(APoint: TPoint);
begin
Unused(APoint);
end;
procedure TChartTool.MouseMove(APoint: TPoint);
begin
Unused(APoint);
end;
procedure TChartTool.MouseUp(APoint: TPoint);
begin
Unused(APoint);
end;
procedure TChartTool.MouseWheelDown(APoint: TPoint);
begin
Unused(APoint);
end;
procedure TChartTool.MouseWheelUp(APoint: TPoint);
begin
Unused(APoint);
end;
function TChartTool.PopupMenuConflict: Boolean;
var
P: TPoint;
begin
Result := false;
if Shift = [ssRight] then begin
P := Mouse.CursorPos;
if (P.X = FStartMousePos.X) then
exit;
if (P.Y = FStartMousePos.Y) then
exit;
Result := true;
end;
end;
procedure TChartTool.PrepareDrawingModePen(
ADrawer: IChartDrawer; APen: TFPCustomPen);
begin
ADrawer.SetXor(EffectiveDrawingMode = tdmXor);
ADrawer.Pen := APen;
if (APen is TChartPen) then
if not TChartPen(APen).EffVisible then
ADrawer.SetPenParams(psClear, TChartPen(APen).Color);
end;
procedure TChartTool.ReadState(Reader: TReader);
begin
inherited ReadState(Reader);
if Reader.Parent is TChartToolset then
Toolset := TChartToolset(Reader.Parent);
end;
procedure TChartTool.RestoreCursor;
begin
if ActiveCursor = crDefault then exit;
FChart.Cursor := FOldCursor;
end;
procedure TChartTool.SetActiveCursor(AValue: TCursor);
begin
if FActiveCursor = AValue then exit;
if IsActive then
RestoreCursor;
FActiveCursor := AValue;
if IsActive then
SetCursor;
end;
procedure TChartTool.SetAfterEvent(AIndex: Integer; AValue: TChartToolEvent);
begin
FEventsAfter[TChartToolEventId(AIndex)] := AValue;
end;
procedure TChartTool.SetBeforeEvent(AIndex: Integer; AValue: TChartToolEvent);
begin
FEventsBefore[TChartToolEventId(AIndex)] := AValue;
end;
procedure TChartTool.SetCursor;
begin
if (ActiveCursor = crDefault) or (ActiveCursor = FChart.Cursor) then exit;
FOldCursor := FChart.Cursor;
FChart.Cursor := ActiveCursor;
end;
procedure TChartTool.SetDrawingMode(AValue: TChartToolDrawingMode);
begin
if FDrawingMode = AValue then exit;
FDrawingMode := AValue;
end;
procedure TChartTool.SetIndex(AValue: Integer);
begin
Toolset.Tools.Move(Index, EnsureRange(AValue, 0, Toolset.Tools.Count - 1));
end;
procedure TChartTool.SetParentComponent(AParent: TComponent);
begin
if not (csLoading in ComponentState) then
Toolset := AParent as TChartToolset;
end;
procedure TChartTool.SetToolset(AValue: TChartToolset);
begin
if FToolset = AValue then exit;
if FToolset <> nil then
FToolset.Tools.Remove(Self);
FToolset := AValue;
if FToolset <> nil then
FToolset.Tools.Add(Self);
end;
procedure TChartTool.StartTransparency(ADrawer: IChartDrawer);
begin
if EffectiveDrawingMode = tdmNormal then
ADrawer.SetTransparency(Transparency);
end;
{ TChartTools }
function TChartTools.GetEnumerator: TChartToolsEnumerator;
begin
Result := TChartToolsEnumerator.Create(Self);
end;
{ TChartToolset }
constructor TChartToolset.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FTools := TChartTools.Create;
end;
destructor TChartToolset.Destroy;
begin
while Tools.Count > 0 do
Item[Tools.Count - 1].Free;
FreeAndNil(FTools);
inherited;
end;
function TChartToolset.Dispatch(
AChart: TChart; AEventId: TChartToolEventId;
AShift: TShiftState; APoint: TPoint): Boolean;
var
candidates: array of TChartTool = nil;
candidateCount: Integer;
procedure AddCandidate(AIndex: Integer);
begin
candidates[candidateCount] := Item[AIndex];
candidateCount += 1;
end;
var
i, ai: Integer;
begin
if (Tools.Count = 0) or (not AChart.ScalingValid) then exit(false);
SetLength(candidates, Tools.Count);
candidateCount := 0;
ai := AChart.ActiveToolIndex;
if InRange(ai, 0, Tools.Count - 1) then
AddCandidate(ai);
for i := 0 to Tools.Count - 1 do
if (i <> ai) and (Item[i].Shift = AShift) then
AddCandidate(i);
FDispatchedShiftState := AShift;
FIsHandled := false;
for i := 0 to candidateCount - 1 do begin
candidates[i].Dispatch(AChart, AEventId, APoint);
if FIsHandled then exit(true);
end;
Result := false;
end;
procedure TChartToolset.Draw(AChart: TChart; ADrawer: IChartDrawer);
var
t: TChartTool;
begin
for t in Tools do begin
t.Draw(AChart, ADrawer);
t.AfterDraw(AChart, ADrawer);
end;
end;
procedure TChartToolset.GetChildren(Proc: TGetChildProc; Root: TComponent);
var
t: TChartTool;
begin
for t in Tools do
if t.Owner = Root then
Proc(t);
end;
function TChartToolset.GetItem(AIndex: Integer): TChartTool;
begin
Result := TChartTool(Tools.Items[AIndex]);
end;
procedure TChartToolset.SetChildOrder(Child: TComponent; Order: Integer);
var
i: Integer;
begin
i := Tools.IndexOf(Child);
if i >= 0 then
Tools.Move(i, Order);
end;
procedure TChartToolset.SetName(const AValue: TComponentName);
var
oldName: String;
begin
if Name = AValue then exit;
oldName := Name;
inherited SetName(AValue);
if csDesigning in ComponentState then
Tools.ChangeNamePrefix(oldName, AValue);
end;
{ TBasicZoomTool }
constructor TBasicZoomTool.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FTimer := TCustomTimer.Create(nil);
FTimer.Enabled := false;
FTimer.OnTimer := @OnTimer;
end;
procedure TBasicZoomTool.Deactivate;
begin
FTimer.Enabled := false;
inherited;
end;
destructor TBasicZoomTool.Destroy;
begin
FreeAndNil(FTimer);
inherited Destroy;
end;
procedure TBasicZoomTool.DoZoom(ANewExtent: TDoubleRect; AFull: Boolean);
procedure ValidateNewSize(LimitLo, LimitHi: TZoomDirection;
const PrevSize, NewSize, MaxSize, ImageMaxSize: Double; out Scale: Double;
out AllowProportionalAdjustment: Boolean);
begin
// if new size is only a bit different than previous size, this may be due to
// limited precision of floating-point calculations, so - if change in size
// is smaller than half of the pixel - set Scale to 0, disable proportional
// adjustments and exit; in this case, change in size will be reverted for
// the current dimension, and adjusting the other dimension will be performed
// independently
if (NewSize > PrevSize * (1 - 0.5 / abs(ImageMaxSize))) and
(NewSize < PrevSize * (1 + 0.5 / abs(ImageMaxSize))) then begin
Scale := 0;
AllowProportionalAdjustment := false;
exit;
end;
Scale := 1;
AllowProportionalAdjustment := true;
// if there is no both-sides extent limitation - allow change
if not (LimitLo in LimitToExtent) or not (LimitHi in LimitToExtent) then exit;
// if new size is within the limit - allow change
if NewSize <= MaxSize then exit;
// if size is not growing - allow change
if NewSize <= PrevSize then exit;
if PrevSize >= MaxSize then begin
// if previous size already reaches or exceeds the limit - set Scale to 0,
// disable proportional adjustments and exit; in this case, change in size
// will be reverted for the current dimension, and adjusting the other
// dimension will be performed independently
Scale := 0;
AllowProportionalAdjustment := false;
end else
// if previous size is within the limit - allow change, but make the new
// size smaller than requested
Scale := (MaxSize - PrevSize) / (NewSize - PrevSize);
end;
procedure AdjustNewSizeAndPosition(LimitLo, LimitHi: TZoomDirection;
var NewSizeLo, NewSizeHi: Double; const MaxSizeLo, MaxSizeHi: Double);
var
Diff: Double;
begin
if (LimitLo in LimitToExtent) and (LimitHi in LimitToExtent) then begin
Diff := NewSizeHi - NewSizeLo - (MaxSizeHi - MaxSizeLo);
if Diff > 0 then begin
NewSizeLo := MaxSizeLo - 0.5 * Diff;
NewSizeHi := MaxSizeHi + 0.5 * Diff;
end else
if NewSizeLo < MaxSizeLo then begin
NewSizeLo := MaxSizeLo;
NewSizeHi := MaxSizeHi + Diff;
end else
if NewSizeHi > MaxSizeHi then begin
NewSizeLo := MaxSizeLo - Diff;
NewSizeHi := MaxSizeHi;
end;
end else
if LimitLo in LimitToExtent then begin
if NewSizeLo < MaxSizeLo then begin
NewSizeHi := MaxSizeLo + (NewSizeHi - NewSizeLo);
NewSizeLo := MaxSizeLo;
end;
end else
if LimitHi in LimitToExtent then begin
if NewSizeHi > MaxSizeHi then begin
NewSizeLo := MaxSizeHi - (NewSizeHi - NewSizeLo);
NewSizeHi := MaxSizeHi;
end;
end;
end;
var
FullExt: TDoubleRect;
ScaleX, ScaleY: Double;
AllowProportionalAdjustmentX, AllowProportionalAdjustmentY: Boolean;
begin
if not AFull then
// perform the actions below even when LimitToExtent is empty - this will
// correct sub-pixel changes in viewport size (occuring due to limited
// precision of floating-point calculations), which will result in a more
// smooth visual behavior
with ANewExtent do begin
FullExt := FChart.GetFullExtent;
ValidateNewSize(zdLeft, zdRight, FChart.LogicalExtent.b.X - FChart.LogicalExtent.a.X,
b.X - a.X, FullExt.b.X - FullExt.a.X,
FChart.XGraphToImage(FullExt.b.X) - FChart.XGraphToImage(FullExt.a.X),
ScaleX, AllowProportionalAdjustmentX);
ValidateNewSize(zdDown, zdUp, FChart.LogicalExtent.b.Y - FChart.LogicalExtent.a.Y,
b.Y - a.Y, FullExt.b.Y - FullExt.a.Y,
FChart.YGraphToImage(FullExt.b.Y) - FChart.YGraphToImage(FullExt.a.Y),
ScaleY, AllowProportionalAdjustmentY);
if AllowProportionalAdjustmentX and AllowProportionalAdjustmentY and
IsProportional then begin
ScaleX := Min(ScaleX, ScaleY);
ScaleY := ScaleX;
end;
a.X := WeightedAverage(FChart.LogicalExtent.a.X, a.X, ScaleX);
b.X := WeightedAverage(FChart.LogicalExtent.b.X, b.X, ScaleX);
a.Y := WeightedAverage(FChart.LogicalExtent.a.Y, a.Y, ScaleY);
b.Y := WeightedAverage(FChart.LogicalExtent.b.Y, b.Y, ScaleY);
AdjustNewSizeAndPosition(zdLeft, zdRight, a.X, b.X, FullExt.a.X, FullExt.b.X);
AdjustNewSizeAndPosition(zdDown, zdUp, a.Y, b.Y, FullExt.a.Y, FullExt.b.Y);
end;
if (AnimationInterval = 0) or (AnimationSteps = 0) then begin
if AFull then
FChart.ZoomFull
else
FChart.LogicalExtent := ANewExtent;
if IsActive then
Deactivate;
exit;
end;
if not IsActive then
Activate;
FExtSrc := FChart.LogicalExtent;
FExtDst := ANewExtent;
FFullZoom := AFull;
FCurrentStep := 0;
FTimer.Interval := AnimationInterval;
FTimer.Enabled := true;
end;
function TBasicZoomTool.IsAnimating: Boolean;
begin
Result := FTimer.Enabled;
end;
function TBasicZoomTool.IsProportional: Boolean;
begin
Result := false;
end;
procedure TBasicZoomTool.OnTimer(ASender: TObject);
var
ext: TDoubleRect;
t: Double;
i: Integer;
begin
Unused(ASender);
FCurrentStep += 1;
FTimer.Enabled := FCurrentStep < AnimationSteps;
if FFullZoom and not IsAnimating then
FChart.ZoomFull
else begin
t := FCurrentStep / AnimationSteps;
for i := Low(ext.coords) to High(ext.coords) do
ext.coords[i] := WeightedAverage(FExtSrc.coords[i], FExtDst.coords[i], t);
NormalizeRect(ext);
FChart.LogicalExtent := ext;
end;
if not IsAnimating then
Deactivate;
end;
{ TZoomDragTool }
function TZoomDragTool.CalculateDrawRect: TRect;
begin
if not AdjustSelection or (RatioLimit = zrlNone) then exit(FSelectionRect);
with CalculateNewExtent do begin
Result.TopLeft := Chart.GraphToImage(a);
Result.BottomRight := Chart.GraphToImage(b);
end;
end;
function TZoomDragTool.CalculateNewExtent: TDoubleRect;
procedure CheckProportions;
var
newSize, oldSize: TDoublePoint;
coeff: Double;
begin
case RatioLimit of
zrlNone: exit;
zrlProportional: begin
newSize := Result.b - Result.a;
oldSize := FChart.LogicalExtent.b - FChart.LogicalExtent.a;
coeff := newSize.Y * oldSize.X;
if coeff = 0 then exit;
coeff := newSize.X * oldSize.Y / coeff;
if coeff = 0 then exit;
if coeff > 1 then
ExpandRange(Result.a.Y, Result.b.Y, (coeff - 1) / 2)
else
ExpandRange(Result.a.X, Result.b.X, (1 / coeff - 1) / 2);
end;
zrlFixedX:
with FChart.GetFullExtent do begin
Result.a.X := a.X;
Result.b.X := b.X;
end;
zrlFixedY:
with FChart.GetFullExtent do begin
Result.a.Y := a.Y;
Result.b.Y := b.Y;
end;
end;
end;
begin
with FSelectionRect do begin
Result.a := Chart.ImageToGraph(TopLeft);
Result.b := Chart.ImageToGraph(BottomRight);
end;
NormalizeRect(Result);
CheckProportions;
end;
procedure TZoomDragTool.Cancel;
begin
if not IsActive then exit;
if EffectiveDrawingMode = tdmXor then
Draw(FChart, GetCurrentDrawer)
else
FChart.StyleChanged(Self);
Deactivate;
Handled;
end;
constructor TZoomDragTool.Create(AOwner: TComponent);
begin
inherited;
SetPropDefaults(Self, ['RestoreExtentOn']);
FAdjustSelection := true;
FBrush := TZoomDragBrush.Create;
FBrush.Style := bsClear;
FFrame := TChartPen.Create;
FPrevDragDir := zreDifferentDrag;
end;
destructor TZoomDragTool.Destroy;
begin
FreeAndNil(FBrush);
FreeAndNil(FFrame);
inherited;
end;
procedure TZoomDragTool.Draw(AChart: TChart; ADrawer: IChartDrawer);
begin
if not IsActive or IsAnimating then exit;
inherited;
StartTransparency(ADrawer);
PrepareDrawingModePen(ADrawer, Frame);
ADrawer.SetBrush(Brush);
ADrawer.Rectangle(CalculateDrawRect);
ADrawer.SetXor(false);
ADrawer.SetTransparency(0);
end;
function TZoomDragTool.IsProportional: Boolean;
begin
Result := AdjustSelection and (RatioLimit = zrlProportional);
end;
procedure TZoomDragTool.MouseDown(APoint: TPoint);
begin
if FChart.UsesBuiltinToolset and (not FChart.AllowZoom) then exit;
Activate;
with APoint do
FSelectionRect := Rect(X, Y, X, Y);
Handled;
end;
procedure TZoomDragTool.MouseMove(APoint: TPoint);
begin
if not IsActive then exit;
SelectionRect := Rect(SelectionRect.Left, SelectionRect.Top, APoint.X, APoint.Y);
Handled;
end;
procedure TZoomDragTool.MouseUp(APoint: TPoint);
const
DRAG_DIR: array [-1..1, -1..1] of TRestoreExtentOn = (
(zreDragTopLeft, zreClick, zreDragBottomLeft),
(zreClick, zreClick, zreClick),
(zreDragTopRight, zreClick, zreDragBottomRight));
var
dragDir: TRestoreExtentOn;
begin
Unused(APoint);
if not IsActive then exit;
if EffectiveDrawingMode = tdmXor then
Draw(FChart, GetCurrentDrawer);
with FSelectionRect do
dragDir := DRAG_DIR[Sign(Right - Left), Sign(Bottom - Top)];
if
(dragDir in RestoreExtentOn) or
(zreDifferentDrag in RestoreExtentOn) and
(dragDir <> zreClick) and not (FPrevDragDir in [dragDir, zreDifferentDrag])
then begin
FPrevDragDir := zreDifferentDrag;
if not Chart.IsZoomed and (EffectiveDrawingMode = tdmNormal) then
// ZoomFull will not cause redraw, force it to erase the tool.
Chart.StyleChanged(Self);
DoZoom(FChart.GetFullExtent, true);
Handled;
exit;
end;
// If empty rectangle does not cause un-zooming, ignore it to prevent SIGFPE.
if dragDir = zreClick then begin
Deactivate;
exit;
end;
FPrevDragDir := dragDir;
DoZoom(CalculateNewExtent, false);
Handled;
end;
procedure TZoomDragTool.SetBrush(AValue: TZoomDragBrush);
begin
if FBrush = AValue then exit;
FBrush.Assign(AValue);
end;
procedure TZoomDragTool.SetFrame(AValue: TChartPen);
begin
FFrame.Assign(AValue);
end;
procedure TZoomDragTool.SetSelectionRect(AValue: TRect);
var
rOld, rNew: TRect;
begin
if (FSelectionRect = AValue) or not IsActive or IsAnimating then exit;
case EffectiveDrawingMode of
tdmXor:
with GetCurrentDrawer do begin
rOld := CalculateDrawRect;
FSelectionRect := AValue;
rNew := CalculateDrawRect;
if rOld = rNew then // avoid unnecessary flicker when xor-painting the same rect
exit;
SetXor(true);
Pen := Frame;
Brush := Self.Brush;
Rectangle(rOld);
Rectangle(rNew);
SetXor(false);
end;
tdmNormal:
begin
FSelectionRect := AValue;
FChart.StyleChanged(Self);
end;
end;
end;
{ TBasicZoomStepTool }
constructor TBasicZoomStepTool.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FFixedPoint := true;
FZoomFactor := 1.0;
FZoomRatio := 1.0;
end;
procedure TBasicZoomStepTool.DoZoomStep(
const APoint: TPoint; const AFactor: TDoublePoint);
var
sz, center, ratio: TDoublePoint;
ext: TDoubleRect;
begin
ext := FChart.LogicalExtent;
sz := ext.b - ext.a;
if FixedPoint and (sz.X <> 0) and (sz.Y <> 0) then begin
center := FChart.ImageToGraph(APoint);
ratio := (center - ext.a) / sz;
end else begin
center := DoublePoint((ext.a.x + ext.b.X) / 2, (ext.a.y + ext.b.y) / 2);
ratio := DoublePoint(0.5, 0.5);
end;
ext.a := center - sz * ratio / AFactor;
ext.b := center + sz * (DoublePoint(1, 1) - ratio) / AFactor;
DoZoom(ext, false);
Handled;
end;
function TBasicZoomStepTool.IsProportional: Boolean;
begin
Result := true;
end;
function TBasicZoomStepTool.ZoomFactorIsStored: boolean;
begin
Result := FZoomFactor <> 1.0;
end;
function TBasicZoomStepTool.ZoomRatioIsStored: boolean;
begin
Result := FZoomRatio <> 1.0;
end;
{ TZoomClickTool }
procedure TZoomClickTool.MouseDown(APoint: TPoint);
begin
if (ZoomFactor <= 0) or (ZoomRatio <= 0) then exit;
DoZoomStep(APoint, DoublePoint(ZoomFactor, ZoomFactor * ZoomRatio));
end;
{ TZoomMouseWheelTool }
procedure TZoomMouseWheelTool.MouseWheelDown(APoint: TPoint);
begin
if (ZoomFactor <= 0) or (ZoomRatio <= 0) then exit;
DoZoomStep(APoint, DoublePoint(ZoomFactor, ZoomFactor * ZoomRatio));
end;
procedure TZoomMouseWheelTool.MouseWheelUp(APoint: TPoint);
begin
if (ZoomFactor <= 0) or (ZoomRatio <= 0) then exit;
DoZoomStep(APoint, DoublePoint(1 / ZoomFactor, 1 / ZoomFactor / ZoomRatio));
end;
{ TBasicPanTool }
constructor TBasicPanTool.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
ActiveCursor := crSizeAll;
end;
procedure TBasicPanTool.PanBy(AOffset: TPoint);
var
dd: TDoublePoint;
ext, fullExt: TDoubleRect;
begin
dd := FChart.ImageToGraph(AOffset) - FChart.ImageToGraph(Point(0, 0));
ext := FChart.LogicalExtent;
if LimitToExtent <> [] then begin
fullExt := FChart.GetFullExtent;
if (pdRight in LimitToExtent) and (ext.a.X + dd.X < fullExt.a.X) then
dd.X := fullExt.a.X - ext.a.X;
if (pdUp in LimitToExtent) and (ext.a.Y + dd.Y < fullExt.a.Y) then
dd.Y := fullExt.a.Y - ext.a.Y;
if (pdLeft in LimitToExtent) and (ext.b.X + dd.X > fullExt.b.X) then
dd.X := fullExt.b.X - ext.b.X;
if (pdDown in LimitToExtent) and (ext.b.Y + dd.Y > fullExt.b.Y) then
dd.Y := fullExt.b.Y - ext.b.Y;
end;
ext.a += dd;
ext.b += dd;
FChart.LogicalExtent := ext;
end;
{ TPanDragTool }
procedure TPanDragTool.Activate;
begin
inherited;
FChart.LockClipRect;
end;
procedure TPanDragTool.Cancel;
begin
if not IsActive then exit;
MouseMove(FOrigin);
Deactivate;
end;
constructor TPanDragTool.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FDirections := PAN_DIRECTIONS_ALL;
end;
procedure TPanDragtool.Deactivate;
begin
inherited;
FChart.UnlockClipRect;
end;
procedure TPanDragTool.MouseDown(APoint: TPoint);
begin
if FChart.UsesBuiltinToolset and (not FChart.AllowPanning) then
exit;
FOrigin := APoint;
FPrev := APoint;
if MinDragRadius = 0 then begin
Activate;
Handled;
end;
end;
procedure TPanDragTool.MouseMove(APoint: TPoint);
var
d: TPoint;
begin
if FChart.UsesBuiltinToolset and (not FChart.AllowPanning) then
exit;
if not IsActive then begin
if PointDist(FOrigin, APoint) < Sqr(MinDragRadius) then
exit;
Activate;
end;
d := FPrev - APoint;
FPrev := APoint;
if not (pdLeft in Directions) then d.X := Max(d.X, 0);
if not (pdRight in Directions) then d.X := Min(d.X, 0);
if not (pdUp in Directions) then d.Y := Max(d.Y, 0);
if not (pdDown in Directions) then d.Y := Min(d.Y, 0);
PanBy(d);
Handled;
end;
procedure TPanDragTool.MouseUp(APoint: TPoint);
begin
if not IsActive then exit;
Unused(APoint);
Deactivate;
Handled;
end;
{ TPanClickTool }
constructor TPanClickTool.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FMargins := TChartMargins.Create(nil);
FTimer := TCustomTimer.Create(nil);
FTimer.Enabled := false;
FTimer.OnTimer := @OnTimer;
end;
procedure TPanClickTool.Deactivate;
begin
FTimer.Enabled := false;
inherited;
end;
destructor TPanClickTool.Destroy;
begin
FreeAndNil(FMargins);
FreeAndNil(FTimer);
inherited Destroy;
end;
function TPanClickTool.GetOffset(APoint: TPoint): TPoint;
var
r: TRect;
begin
Result := Point(0, 0);
r := FChart.ClipRect;
if not PtInRect(r, APoint) then exit;
with Size(r) do
if
(Margins.Left + Margins.Right >= cx) or
(Margins.Top + Margins.Bottom >= cy)
then
exit;
Result.X := Min(APoint.X - r.Left - Margins.Left, 0);
if Result.X = 0 then
Result.X := Max(Margins.Right - r.Right + APoint.X, 0);
Result.Y := Min(APoint.Y - r.Top - Margins.Top, 0);
if Result.Y = 0 then
Result.Y := Max(Margins.Bottom - r.Bottom + APoint.Y, 0);
end;
procedure TPanClickTool.MouseDown(APoint: TPoint);
begin
FOffset := GetOffset(APoint);
if FOffset = Point(0, 0) then exit;
PanBy(FOffset);
if Interval > 0 then begin
Activate;
FTimer.Interval := Interval;
FTimer.Enabled := true;
end;
Handled;
end;
procedure TPanClickTool.MouseMove(APoint: TPoint);
begin
if not IsActive then exit;
FOffset := GetOffset(APoint);
FTimer.Enabled := FOffset <> Point(0, 0);
end;
procedure TPanClickTool.MouseUp(APoint: TPoint);
begin
Unused(APoint);
Deactivate;
Handled;
end;
procedure TPanClickTool.OnTimer(ASender: TObject);
begin
Unused(ASender);
if FOffset <> Point(0, 0) then
PanBy(FOffset);
end;
{ TPanMouseWheelTool }
constructor TPanMouseWheelTool.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
SetPropDefaults(Self, ['Step', 'WheelUpDirection']);
end;
procedure TPanMouseWheelTool.DoPan(AStep: Integer);
const
DIR_TO_OFFSET: array [TPanDirection] of TPoint =
// pdLeft, pdUp, pdRight, pdDown
((X: -1; Y: 0), (X: 0; Y: -1), (X: 1; Y: 0), (X: 0; Y: 1));
begin
PanBy(DIR_TO_OFFSET[WheelUpDirection] * AStep);
end;
procedure TPanMouseWheelTool.MouseWheelDown(APoint: TPoint);
begin
Unused(APoint);
DoPan(-Step);
end;
procedure TPanMouseWheelTool.MouseWheelUp(APoint: TPoint);
begin
Unused(APoint);
DoPan(Step);
end;
{ TDataPointTool }
constructor TDataPointTool.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FAffectedSeries.Init;
SetPropDefaults(Self, ['GrabRadius']);
FPointIndex := -1;
FYIndex := 0;
FTargets := [nptPoint, nptXList, nptYList, nptCustom]; // check all targets
end;
procedure TDataPointTool.FindNearestPoint(APoint: TPoint);
function InBoundaryBox(ASeries: TCustomChartSeries): Boolean;
var
r, gr: TDoubleRect;
p: TDoublePoint;
ext: TDoubleRect;
begin
if ASeries.SpecialPointPos then
exit(true);
r := ASeries.GetGraphBounds;
ext := FChart.CurrentExtent;
if not RectIntersectsRect(r, ext) then
exit(false);
if FMouseInsideOnly then begin
p := FChart.ImageToGraph(APoint);
if not (SafeInRange(p.x, ext.a.x, ext.b.x) and SafeInRange(p.y, ext.a.y, ext.b.y)) then
exit(false);
end;
case DistanceMode of
cdmOnlyX: begin
gr.a := DoublePoint(FChart.XImageToGraph(APoint.X - GrabRadius), NegInfinity);
gr.b := DoublePoint(FChart.XImageToGraph(APoint.X + GrabRadius), SafeInfinity);
end;
cdmOnlyY: begin
gr.a := DoublePoint(NegInfinity, FChart.YImageToGraph(APoint.Y - GrabRadius));
gr.b := DoublePoint(SafeInfinity, FChart.YImageToGraph(APoint.Y + GrabRadius));
end;
cdmXY: begin
gr.a := FChart.ImageToGraph(APoint - Point(GrabRadius, GrabRadius));
gr.b := FChart.ImageToGraph(APoint + Point(GrabRadius, GrabRadius));
end;
end;
Result := RectIntersectsRect(r, gr);
end;
const
DIST_FUNCS: array [TChartDistanceMode] of TPointDistFunc = (
@PointDist, @PointDistX, @PointDistY);
var
s, bestS: TCustomChartSeries;
p: TNearestPointParams;
cur, best: TNearestPointResults;
begin
if not FChart.ScalingValid then
exit;
p.FDistFunc := DIST_FUNCS[DistanceMode];
p.FPoint := APoint;
p.FRadius := GrabRadius;
p.FOptimizeX := DistanceMode <> cdmOnlyY;
p.FTargets := Targets;
best.FDist := MaxInt;
for s in CustomSeries(FChart, FAffectedSeries.AsBooleans(FChart.SeriesCount)) do
if
InBoundaryBox(s) and s.Active and s.GetNearestPoint(p, cur) and
PtInRect(FChart.ClipRect, cur.FImg) and (cur.FDist < best.FDist)
then begin
bestS := s;
best := cur;
end;
if best.FDist = MaxInt then exit;
FSeries := bestS;
FPointIndex := best.FIndex;
FXIndex := best.FXIndex;
FYIndex := best.FYIndex;
FNearestGraphPoint := FChart.ImageToGraph(best.FImg);
end;
function TDataPointTool.GetAffectedSeries: String;
begin
Result := FAffectedSeries.AsString;
end;
function TDataPointTool.GetIsSeriesAffected(AIndex: Integer): Boolean;
begin
Result := FAffectedSeries.IsSet[AIndex];
end;
procedure TDataPointTool.SetAffectedSeries(AValue: String);
begin
FAffectedSeries.AsString := AValue;
end;
procedure TDataPointTool.SetIsSeriesAffected(AIndex: Integer; AValue: Boolean);
begin
FAffectedSeries.IsSet[AIndex] := AValue;
end;
{ TDataPointDragTool }
procedure TDataPointDragTool.Cancel;
begin
if FSeries <> nil then
FSeries.MovePoint(FPointIndex, Origin);
if IsActive then
Deactivate;
Handled;
end;
constructor TDataPointDragTool.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
ActiveCursor := crSizeAll;
EscapeCancels := true;
end;
procedure TDataPointDragTool.MouseDown(APoint: TPoint);
var
p: TDoublePoint;
begin
FindNearestPoint(APoint);
if FSeries = nil then exit;
FOrigin := NearestGraphPoint;
FSeries.DragOrigin := APoint;
p := FChart.ImageToGraph(APoint);
FDistance := p - FOrigin;
if Assigned(OnDragStart) then begin
OnDragStart(Self, p);
if Toolset.FIsHandled then exit;
end;
Activate;
Handled;
end;
procedure TDataPointDragTool.MouseMove(APoint: TPoint);
var
p: TDoublePoint;
begin
if not IsActive or (FSeries = nil) then exit;
p := FChart.ImageToGraph(APoint);
if Assigned(OnDrag) then begin
OnDrag(Self, p);
if Toolset.FIsHandled then exit;
end;
if FKeepDistance then p := p - FDistance;
// FSeries.MovePoint(FPointIndex, p);
FSeries.MovePointEx(FPointIndex, FXIndex, FYIndex, p);
Handled;
end;
procedure TDataPointDragTool.MouseUp(APoint: TPoint);
begin
Unused(APoint);
FSeries := nil;
Deactivate;
Handled;
end;
{ TDataPointClickTool }
procedure TDataPointClickTool.MouseDown(APoint: TPoint);
begin
FindNearestPoint(APoint);
if FSeries = nil then exit;
FMouseDownPoint := APoint;
Activate;
Handled;
end;
procedure TDataPointClickTool.MouseUp(APoint: TPoint);
begin
if
Assigned(OnPointClick) and (FSeries <> nil) and
(FSeries.SpecialPointPos or (PointDist(APoint, FMouseDownPoint) <= Sqr(GrabRadius)))
then
OnPointClick(Self, FMouseDownPoint);
FSeries := nil;
Deactivate;
Handled;
end;
{ TDataPointHintTool }
constructor TDataPointHintTool.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FUseDefaultHintText := true;
end;
destructor TDataPointHintTool.Destroy;
begin
FreeAndNil(FHintWindow);
inherited;
end;
procedure TDataPointHintTool.HideHint;
begin
if UseApplicationHint then begin
FChart.ShowHint := false;
Application.CancelHint;
end
else
FreeAndNil(FHintWindow);
RestoreCursor;
FPrevSeries := nil;
end;
procedure TDataPointHintTool.KeyDown(APoint: TPoint);
begin
MouseMove(APoint);
end;
procedure TDataPointHintTool.KeyUp(APoint: TPoint);
begin
Unused(APoint);
HideHint;
end;
procedure TDataPointHintTool.MouseDown(APoint: TPoint);
begin
MouseMove(APoint);
end;
procedure TDataPointHintTool.MouseMove(APoint: TPoint);
function GetHintText: String;
begin
if UseDefaultHintText and (PointIndex > -1) then begin
if Series is TChartSeries then
Result := TChartSeries(Series).FormattedMark(PointIndex)
else
Result := Format(
'%s: %d', [(Series as TCustomChartSeries).Title, PointIndex]);
end;
if Assigned(OnHint) then
OnHint(Self, APoint, Result);
end;
var
r: TRect;
h: String;
sz: TSize;
begin
FSeries := nil;
FindNearestPoint(APoint);
if Series = nil then begin
HideHint;
exit;
end;
if (FPrevSeries = Series) and (FPrevPointIndex = PointIndex) and
(FPrevYIndex = YIndex)
then
exit;
if FPrevSeries = nil then
SetCursor;
FPrevSeries := Series;
FPrevPointIndex := PointIndex;
FPrevYIndex := YIndex;
h := GetHintText;
APoint := FChart.ClientToScreen(APoint);
if Assigned(OnHintPosition) then
OnHintPosition(Self, APoint);
if UseApplicationHint then begin
FChart.Hint := h;
FChart.ShowHint := FChart.Hint <> '';
if not FChart.ShowHint then exit;
Application.HintPause := 0;
Application.ActivateHint(APoint);
end
else begin
if FHintWindow = nil then
FHintWindow := THintWindow.Create(nil);
if h = '' then exit;
r := FHintWindow.CalcHintRect(FChart.Width, h, Nil);
if Assigned(OnHintLocation) then begin
sz.CX := r.Right - r.Left;
sz.CY := r.Bottom - r.Top;
OnHintLocation(Self, sz, APoint);
end;
OffsetRect(r, APoint.X, APoint.Y);
FHintWindow.ActivateHint(r, h);
end;
end;
procedure TDataPointHintTool.MouseUp(APoint: TPoint);
begin
Unused(APoint);
HideHint;
end;
procedure TDataPointHintTool.SetUseApplicationHint(AValue: Boolean);
begin
if FUseApplicationHint = AValue then exit;
FUseApplicationHint := AValue;
end;
{ TDataPointDrawTool }
constructor TDataPointDrawTool.Create(AOwner: TComponent);
begin
inherited;
GrabRadius := 20;
FPen := TChartPen.Create;
end;
destructor TDataPointDrawTool.Destroy;
begin
FreeAndNil(FPen);
inherited;
end;
procedure TDataPointDrawTool.DoDraw;
begin
DoDraw(GetCurrentDrawer);
end;
procedure TDataPointDrawTool.DoDraw(ADrawer: IChartDrawer);
begin
if Assigned(OnCustomDraw) then
OnCustomDraw(Self, ADrawer);
if Assigned(OnDraw) then
OnDraw(Self);
end;
procedure TDataPointDrawTool.DoHide;
begin
DoHide(GetCurrentDrawer);
end;
procedure TDataPointDrawTool.DoHide(ADrawer: IChartDrawer);
begin
if ADrawer = nil then exit;
case EffectiveDrawingMode of
tdmXor: begin
ADrawer.SetXor(true);
DoDraw(ADrawer);
ADrawer.SetXor(false);
end;
tdmNormal:
FChart.StyleChanged(Self);
end;
end;
procedure TDataPointDrawTool.Draw(AChart: TChart; ADrawer: IChartDrawer);
begin
inherited;
PrepareDrawingModePen(ADrawer, FPen);
DoDraw(ADrawer);
ADrawer.SetXor(false);
end;
procedure TDataPointDrawTool.Hide;
begin
DoHide(GetCurrentDrawer);
Deactivate;
end;
procedure TDataPointDrawTool.SetPen(AValue: TChartPen);
begin
FPen.Assign(AValue);
end;
{ TDataPointCrosshairTool }
constructor TDataPointCrosshairTool.Create(AOwner: TComponent);
begin
inherited;
SetPropDefaults(Self, ['Shape', 'Size']);
end;
procedure TDataPointCrosshairTool.DoDraw(ADrawer: IChartDrawer);
var
p: TPoint;
ps: TFPPenStyle;
begin
if not CrosshairPen.Visible then
ps := CrosshairPen.Style;
PrepareDrawingModePen(ADrawer, CrosshairPen);
p := FChart.GraphToImage(Position);
if Shape in [ccsVertical, ccsCross] then
if Size < 0 then
FChart.DrawLineVert(ADrawer, p.X)
else
ADrawer.Line(p - Point(0, Size), p + Point(0, Size));
if Shape in [ccsHorizontal, ccsCross] then
if Size < 0 then
FChart.DrawLineHoriz(ADrawer, p.Y)
else
ADrawer.Line(p - Point(Size, 0), p + Point(Size, 0));
if not CrosshairPen.Visible then
ADrawer.SetPenParams(ps, CrosshairPen.Color);
inherited;
end;
procedure TDataPointCrosshairTool.DoHide(ADrawer: IChartDrawer);
begin
if FSeries = nil then exit;
FSeries := nil;
inherited DoHide(ADrawer);
end;
procedure TDataPointCrosshairTool.Draw(AChart: TChart; ADrawer: IChartDrawer);
begin
if FSeries = nil then exit;
inherited;
end;
procedure TDataPointCrosshairTool.KeyDown(APoint: TPoint);
begin
MouseMove(APoint);
end;
procedure TDataPointCrosshairTool.MouseDown(APoint: TPoint);
begin
FCurrentDrawer := nil;
MouseMove(APoint);
end;
procedure TDataPointCrosshairTool.MouseMove(APoint: TPoint);
var
id: IChartDrawer;
lastSeries, currentSeries: TBasicChartSeries;
lastIndex: Integer;
xorMode: Boolean;
begin
id := GetCurrentDrawer;
lastSeries := FSeries;
lastIndex := FPointIndex;
xorMode := EffectiveDrawingMode = tdmXOR;
FSeries := nil;
FindNearestPoint(APoint);
if xorMode and (FSeries = lastSeries) and (FPointIndex = lastIndex) and (FPointIndex > -1) then exit;
currentSeries := FSeries;
FSeries := lastSeries;
if Assigned(id) then DoHide(id);
FSeries := currentSeries;
if FSeries = nil then exit;
FPosition := FNearestGraphPoint;
if xorMode and Assigned(id) then begin
id.SetXor(true);
DoDraw(id);
id.SetXor(false);
end;
end;
{ TAxisClickTool }
constructor TAxisClickTool.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
SetPropDefaults(Self, ['GrabRadius']);
FIgnoreClipRect := true; // Allow mousedown outside cliprect
end;
function TAxisClickTool.GetHitTestInfo(APoint: TPoint): Boolean;
var
ax: TChartAxis;
begin
for ax in FChart.AxisList do begin
FHitTest := ax.GetHitTestInfoAt(APoint, FGrabRadius);
if FHitTest <> [] then begin
FAxis := ax;
Result := true;
exit;
end;
end;
Result := false;
FAxis := nil;
FHitTest := [];
end;
procedure TAxisClickTool.MouseDown(APoint: TPoint);
begin
if GetHitTestInfo(APoint) then begin
Activate;
Handled;
end;
end;
procedure TAxisClickTool.MouseUp(APoint: TPoint);
begin
if FHitTest <> [] then begin
GetHitTestInfo(APoint);
if Assigned(FOnClick) and (FAxis <> nil) then FOnClick(Self, FAxis, FHitTest);
end;
Deactivate;
Handled;
end;
{ TTitleFootClickTool }
constructor TTitleFootClickTool.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FIgnoreClipRect := true; // Allow mousedown outside cliprect
end;
function TTitleFootClickTool.GetHit(APoint: TPoint): Boolean;
begin
FTitle := nil;
if FChart.Title.IsPointInBounds(APoint) then
FTitle := FChart.Title
else if FChart.Foot.IsPointInBounds(APoint) then
FTitle := FChart.Foot;
Result := FTitle <> nil;
end;
procedure TTitleFootClickTool.MouseDown(APoint: TPoint);
begin
if GetHit(APoint) then begin
Activate;
Handled;
end;
end;
procedure TTitleFootClickTool.MouseUp(APoint: TPoint);
begin
if IsActive then begin
GetHit(APoint);
if Assigned(FOnClick) and (FTitle <> nil) then FOnClick(Self, FTitle);
end;
end;
{ TLegendClickTool }
constructor TLegendClickTool.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FIgnoreClipRect := true; // Allow mousedown outside cliprect
end;
procedure TLegendClickTool.MouseDown(APoint: TPoint);
begin
if Assigned(FChart.Legend) and FChart.Legend.IsPointInBounds(APoint) then begin
Activate;
Handled;
end;
end;
procedure TLegendClickTool.MouseUp(APoint: TPoint);
var
idx: Integer;
ser: TBasicChartSeries = nil;
items: TChartLegendItems = nil;
begin
if not (IsActive and Assigned(FChart.Legend)) then
begin
FLegend := nil;
exit;
end;
FLegend := FChart.Legend;
if Assigned(FOnSeriesClick) then
begin
try
items := FChart.GetLegendItems;
idx := FLegend.ItemClicked(FChart.Drawer, APoint, items);
if idx <> -1 then
ser := TBasicChartSeries(items[idx].Owner);
FOnSeriesClick(Self, FLegend, ser);
finally
items.Free;
end;
end else
if Assigned(FOnClick) and FLegend.IsPointInBounds(APoint) then
FOnClick(Self, FLegend);
Deactivate;
Handled;
end;
{ -------- }
procedure SkipObsoleteProperties;
const
PROPORTIONAL_NOTE = 'Obsolete, use TZoomDragTool.RatioLimit=zlrProportional instead';
begin
RegisterPropertyToSkip(TZoomDragTool, 'Proportional', PROPORTIONAL_NOTE, '');
end;
initialization
ToolsClassRegistry := TClassRegistry.Create;
OnInitBuiltinTools := @InitBuiltinTools;
RegisterChartToolClass(TZoomDragTool, @rsZoomByDrag);
RegisterChartToolClass(TZoomClickTool, @rsZoomByClick);
RegisterChartToolClass(TZoomMouseWheelTool, @rsZoomByMouseWheel);
RegisterChartToolClass(TPanDragTool, @rsPanningByDrag);
RegisterChartToolClass(TPanClickTool, @rsPanningbyClick);
RegisterChartToolClass(TPanMouseWheelTool, @rsPanningByMouseWheel);
RegisterChartToolClass(TDataPointClickTool, @rsDataPointClick);
RegisterChartToolClass(TDataPointDragTool, @rsDataPointDrag);
RegisterChartToolClass(TDataPointHintTool, @rsDataPointHint);
RegisterChartToolClass(TDataPointCrosshairTool, @rsDataPointCrosshair);
RegisterChartToolClass(TAxisClickTool, @rsAxisClickTool);
RegisterChartToolClass(TTitleFootClickTool, @rsHeaderFooterClickTool);
RegisterChartToolClass(TLegendClickTool, @rsLegendClickTool);
RegisterChartToolClass(TUserDefinedTool, @rsUserDefinedTool);
SkipObsoleteProperties;
finalization
FreeAndNil(ToolsClassRegistry);
end.