mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-03 07:23:38 +02:00
2318 lines
67 KiB
ObjectPascal
2318 lines
67 KiB
ObjectPascal
{
|
|
/***************************************************************************
|
|
TAGraph.pp
|
|
----------
|
|
Component Library Standard Graph
|
|
|
|
|
|
***************************************************************************/
|
|
|
|
*****************************************************************************
|
|
* *
|
|
* See the file COPYING.modifiedLGPL, 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: Luís Rodrigues and Philippe Martinole
|
|
|
|
}
|
|
unit TAGraph;
|
|
|
|
{$IFDEF fpc}
|
|
{$MODE DELPHI}{$H+}
|
|
{$ENDIF}
|
|
|
|
interface
|
|
|
|
uses
|
|
{$IFDEF fpc}
|
|
LCLIntF, LCLType, LResources,
|
|
{$ELSE}
|
|
Windows,
|
|
{$ENDIF}
|
|
SysUtils, Classes, Controls, Graphics, Dialogs, StdCtrls, TAEngine, Clipbrd;
|
|
|
|
const
|
|
MinDouble=-1.7e308;
|
|
MaxDouble=1.7e308;
|
|
MaxArray=2;
|
|
MaxColor=15;
|
|
Colors:array[1..MaxColor] of TColor=
|
|
( clRed,
|
|
clGreen,
|
|
clyellow,
|
|
clBlue,
|
|
clWhite,
|
|
clGray,
|
|
clFuchsia,
|
|
clTeal,
|
|
clNavy,
|
|
clMaroon,
|
|
clLime,
|
|
clOlive,
|
|
clPurple,
|
|
clSilver,
|
|
clAqua
|
|
);
|
|
|
|
type
|
|
|
|
TDrawVertReticule=procedure(Sender:TComponent;IndexSerie,Index,Xi,Yi:Integer;
|
|
Xg,Yg:Double) of object;
|
|
TDrawReticule=procedure(Sender:TComponent;IndexSerie,Index,Xi,Yi:Integer;
|
|
Xg,Yg:Double) of object;
|
|
|
|
|
|
TCustomChart = class(TGraphicControl);
|
|
|
|
TChartPen = class(TPen)
|
|
private
|
|
FVisible: boolean;
|
|
procedure SetVisible(value: boolean);
|
|
protected
|
|
procedure Assign(Source:TPersistent); override;
|
|
published
|
|
property Visible: boolean read FVisible write SetVisible;
|
|
end;
|
|
|
|
TLegendAlignment=(laLeft,laRight,laTop,laBottom);
|
|
TChartLegend = class(TPersistent)
|
|
private
|
|
FVisible: boolean;
|
|
FAlignment: TLegendAlignment;
|
|
FOwner: TCustomChart;
|
|
FFont: TFont;
|
|
FFrame: TChartPen;
|
|
|
|
procedure SetVisible(value: boolean);
|
|
procedure SetAlignment(value: TLegendAlignment);
|
|
procedure SetFont(value: TFont);
|
|
procedure SetFrame(value: TChartPen);
|
|
procedure StyleChanged(Sender: TObject);
|
|
protected
|
|
procedure Assign(Source:TPersistent); override;
|
|
public
|
|
Constructor Create(AOwner: TCustomChart);
|
|
Destructor Destroy; override;
|
|
published
|
|
property Visible: boolean read FVisible write SetVisible;
|
|
property Alignment: TLegendAlignment read FAlignment write SetAlignment;
|
|
property Font: TFont read FFont write SetFont;
|
|
property Frame: TChartPen read FFrame write SetFrame;
|
|
end;
|
|
|
|
TChartTitle = class(TPersistent)
|
|
private
|
|
FVisible: boolean;
|
|
FOwner: TCustomChart;
|
|
FFont: TFont;
|
|
FFrame: TChartPen;
|
|
FBrush: TBrush;
|
|
FText: TStrings;
|
|
FAlignment: TAlignment;
|
|
|
|
procedure SetVisible(value: boolean);
|
|
procedure SetFont(value: TFont);
|
|
procedure SetFrame(value: TChartPen);
|
|
procedure SetBrush(value: TBrush);
|
|
procedure SetText(value: TStrings);
|
|
procedure SetAlignment(value: TAlignment);
|
|
procedure StyleChanged(Sender: TObject);
|
|
protected
|
|
procedure Assign(Source:TPersistent); override;
|
|
public
|
|
Constructor Create(AOwner: TCustomChart);
|
|
Destructor Destroy; override;
|
|
published
|
|
property Visible: boolean read FVisible write SetVisible;
|
|
property Brush: TBrush read FBrush write SetBrush;
|
|
property Font: TFont read FFont write SetFont;
|
|
property Frame: TChartPen read FFrame write SetFrame;
|
|
property Alignment: TAlignment read FAlignment write SetAlignment;
|
|
property Text: TStrings read FText write SetText;
|
|
end;
|
|
|
|
TChartAxisTitle = class(TPersistent)
|
|
private
|
|
FVisible: boolean;
|
|
FOwner: TCustomChart;
|
|
FAngle: Integer;
|
|
FCaption: String;
|
|
FFont: TFont;
|
|
|
|
procedure SetCaption(value: String);
|
|
procedure SetAngle(value: Integer);
|
|
procedure SetFont(value: TFont);
|
|
procedure StyleChanged(Sender: TObject);
|
|
protected
|
|
procedure Assign(Source:TPersistent); override;
|
|
public
|
|
Constructor Create(AOwner: TCustomChart);
|
|
Destructor Destroy; override;
|
|
published
|
|
property Caption: String read FCaption write SetCaption;
|
|
property Angle: Integer read FAngle write SetAngle;
|
|
property Font: TFont read FFont write SetFont;
|
|
end;
|
|
|
|
TAxisScale=(asIncreasing,asDecreasing,asLogIncreasing,asLogDecreasing);
|
|
TChartAxis = class(TPersistent)
|
|
private
|
|
FVisible: boolean;
|
|
FOwner: TCustomChart;
|
|
FTitle: TChartAxisTitle;
|
|
FGrid: TChartPen;
|
|
FInverted: boolean;
|
|
|
|
procedure SetVisible(value: boolean);
|
|
procedure SetTitle(value: TChartAxisTitle);
|
|
procedure SetGrid(value: TChartPen);
|
|
procedure SetInverted(value: boolean);
|
|
procedure StyleChanged(Sender: TObject);
|
|
protected
|
|
procedure Assign(Source:TPersistent); override;
|
|
public
|
|
Constructor Create(AOwner: TCustomChart);
|
|
Destructor Destroy; override;
|
|
published
|
|
property Visible: boolean read FVisible write SetVisible;
|
|
property Inverted: boolean read FInverted write SetInverted;
|
|
property Title: TChartAxisTitle read FTitle write SetTitle;
|
|
property Grid: TChartPen read FGrid write SetGrid;
|
|
|
|
end;
|
|
|
|
TChart = class(TCustomChart)
|
|
private
|
|
{ Déclarations privées }
|
|
TmpBrush: TBrush;
|
|
TmpPen: TPen;
|
|
TmpFont: TFont;
|
|
FSeries:TSeriesList; // List of series
|
|
FMirrorX:Boolean; // From right to left ?
|
|
YMarkWidth:Integer; // Depend on Y marks
|
|
FXGraphMin,FYGraphMin:Double; // Graph coordinates of limits
|
|
FXGraphMax,FYGraphMax:Double;
|
|
FAutoUpdateXMin:Boolean; // Automatic calculation of XMin limit of graph ?
|
|
FAutoUpdateXMax:Boolean; // Automatic calculation of XMax limit of graph ?
|
|
FAutoUpdateYMin:Boolean; // Automatic calculation of YMin limit of graph ?
|
|
FAutoUpdateYMax:Boolean; // Automatic calculation of YMax limit of graph ?
|
|
|
|
FLegend: TChartLegend; //legend configuration
|
|
FTitle: TChartTitle; //legend configuration
|
|
FFoot: TChartTitle; //legend configuration
|
|
FLeftAxis: TChartAxis;
|
|
FBottomAxis: TChartAxis;
|
|
|
|
FAllowZoom: Boolean;
|
|
|
|
FGraphBrush:TBrush;
|
|
AxisColor:TColor; // Axis color
|
|
ax,bx,ay,by:Double; // Image<->Graphe conversion coefs
|
|
|
|
Down:Boolean;
|
|
Zoom:Boolean;
|
|
Fixed:Boolean;
|
|
XDown,YDown,XOld,YOld:Integer;
|
|
XVMarkOld, XMarkOld,YMarkOld:Integer;
|
|
ZoomRect:TRect;
|
|
|
|
FShowReticule:Boolean;
|
|
FShowVerticalReticule:Boolean;
|
|
|
|
FDrawVertReticule:TDrawVertReticule;
|
|
FDrawReticule:TDrawReticule;
|
|
|
|
XReticule,YReticule:Integer;
|
|
|
|
FFrame: TChartPen;
|
|
|
|
FBackColor: TColor;
|
|
|
|
FAxisVisible: boolean;
|
|
|
|
FNumBarSeries: integer;
|
|
|
|
procedure SetAutoUpdateXMin(Value:Boolean);
|
|
procedure SetAutoUpdateXMax(Value:Boolean);
|
|
procedure SetAutoUpdateYMin(Value:Boolean);
|
|
procedure SetAutoUpdateYMax(Value:Boolean);
|
|
procedure SetXGraphMin(Value:Double);
|
|
procedure SetYGraphMin(Value:Double);
|
|
procedure SetXGraphMax(Value:Double);
|
|
procedure SetYGraphMax(Value:Double);
|
|
procedure SetMirrorX(Value:Boolean);
|
|
procedure SetGraphBrush(Value:TBrush);
|
|
procedure SetTitle(Value:TChartTitle);
|
|
procedure SetFoot(Value:TChartTitle);
|
|
function GetLegendWidth(ACanvas : TCanvas):Integer;
|
|
procedure GetPointNextTo(X,Y:Integer;var SerieNumberOut,PointNumberOut,XOut,YOut:Integer);
|
|
procedure GetXPointNextTo(X,Y:Integer;var SerieNumberOut,PointNumberOut,XOut,YOut:Integer);
|
|
procedure GetYPointNextTo(X,Y:Integer;var SerieNumberOut,PointNumberOut,XOut,YOut:Integer);
|
|
procedure DrawReticule(ACanvas : TCanvas; X,Y:Integer);
|
|
procedure DrawVerticalReticule(ACanvas : TCanvas; X:Integer);
|
|
procedure SetShowVerticalReticule(Value:Boolean);
|
|
procedure SetShowReticule(Value:Boolean);
|
|
|
|
procedure SetLegend(Value:TChartLegend);
|
|
procedure SetLeftAxis(Value:TChartAxis);
|
|
procedure SetBottomAxis(Value:TChartAxis);
|
|
|
|
procedure SetFrame(Value: TChartPen);
|
|
|
|
procedure SetBackColor(Value: TColor);
|
|
procedure SetAxisVisible(Value: boolean);
|
|
|
|
function GetChartHeight: integer;
|
|
function GetChartWidth: integer;
|
|
|
|
function GetSeriesCount: Integer;
|
|
|
|
function only_pie: boolean;
|
|
function get_pie: pointer;
|
|
function SeriesInLegendCount: integer;
|
|
protected
|
|
{ Déclarations protégées }
|
|
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
|
|
procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
|
|
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
|
|
procedure DoDrawVertReticule(IndexSerie,Index,Xi,Yi:Integer;Xg,Yg:Double); virtual;
|
|
procedure DoDrawReticule(IndexSerie,Index,Xi,Yi:Integer;Xg,Yg:Double); virtual;
|
|
public
|
|
XImageMin,YImageMin:Integer; // Image coordinates of limits
|
|
XImageMax,YImageMax:Integer;
|
|
|
|
{ Déclarations publiques }
|
|
constructor Create(AOwner:TComponent); override;
|
|
destructor Destroy; override;
|
|
procedure Paint; override;
|
|
procedure PaintOnCanvas(ACanvas : TCanvas; ARect : TRect);
|
|
procedure Refresh(ACanvas : TCanvas; ARect : TRect);
|
|
procedure Clean(ACanvas : TCanvas; ARect : TRect);
|
|
procedure DrawTitleFoot(ACanvas : TCanvas; ARect : TRect);
|
|
procedure DrawAxis(ACanvas : TCanvas; ARect : TRect);
|
|
procedure DrawLegend(ACanvas : TCanvas; ARect : TRect);
|
|
|
|
procedure AddSerie(Serie:TComponent);
|
|
procedure DeleteSerie(Serie:TComponent);
|
|
function GetSerie(i:Integer):TComponent;
|
|
procedure SetAutoXMin(Auto:Boolean);
|
|
procedure SetAutoXMax(Auto:Boolean);
|
|
procedure SetAutoYMin(Auto:Boolean);
|
|
procedure SetAutoYMax(Auto:Boolean);
|
|
|
|
procedure XGraphToImage(Xin:Double;var XOut:Integer);
|
|
procedure YGraphToImage(Yin:Double;var YOut:Integer);
|
|
procedure GraphToImage(Xin,Yin:Double;var XOut,YOut:Integer);
|
|
procedure XImageToGraph(XIn:Integer;var XOut:Double);
|
|
procedure YImageToGraph(YIn:Integer;var YOut:Double);
|
|
procedure ImageToGraph(XIn,YIn:Integer;var XOut,YOut:Double);
|
|
procedure DisplaySeries(ACanvas : TCanvas);
|
|
procedure ZoomFull;
|
|
|
|
procedure SaveToBitmapFile(Const FileName:String);
|
|
procedure CopyToClipboardBitmap;
|
|
procedure DrawOnCanvas(Rect : TRect; ACanvas : TCanvas);
|
|
|
|
property SeriesCount:Integer read GetSeriesCount;
|
|
|
|
property NumBarSeries: integer read FNumBarSeries;
|
|
|
|
function GetNewColor:TColor;
|
|
|
|
function GetRectangle:TRect;
|
|
|
|
property Canvas;
|
|
|
|
property ChartHeight: Integer read GetChartHeight;
|
|
property ChartWidth: Integer read GetChartWidth;
|
|
|
|
property Series: TSeriesList read FSeries write FSeries;
|
|
published
|
|
{ Déclarations publiées }
|
|
procedure StyleChanged(Sender: TObject);
|
|
property AutoUpdateXMin:Boolean read FAutoUpdateXMin write SetAutoUpdateXMin;
|
|
property AutoUpdateXMax:Boolean read FAutoUpdateXMax write SetAutoUpdateXMax;
|
|
property AutoUpdateYMin:Boolean read FAutoUpdateYMin write SetAutoUpdateYMin;
|
|
property AutoUpdateYMax:Boolean read FAutoUpdateYMax write SetAutoUpdateYMax;
|
|
property XGraphMin:Double read FXGraphMin write SetXGraphMin;
|
|
property YGraphMin:Double read FYGraphMin write SetYGraphMin;
|
|
property XGraphMax:Double read FXGraphMax write SetXGraphMax;
|
|
property YGraphMax:Double read FYGraphMax write SetYGraphMax;
|
|
property MirrorX:Boolean read FMirrorX write SetMirrorX;
|
|
property GraphBrush:TBrush read FGraphBrush write SetGraphBrush;
|
|
property ShowVerticalReticule:Boolean read FShowVerticalReticule write SetShowVerticalReticule;
|
|
property ShowReticule:Boolean read FShowReticule write SetShowReticule;
|
|
|
|
property OnDrawVertReticule:TDrawVertReticule read FDrawVertReticule write FDrawVertReticule;
|
|
property OnDrawReticule:TDrawReticule read FDrawReticule write FDrawReticule;
|
|
|
|
property Legend: TChartLegend read FLegend write SetLegend;
|
|
property Title: TChartTitle read FTitle write SetTitle;
|
|
property Foot: TChartTitle read FFoot write SetFoot;
|
|
|
|
property AllowZoom: Boolean read FAllowZoom write FAllowZoom;
|
|
|
|
property LeftAxis: TChartAxis read FLeftAxis write SetLeftAxis;
|
|
property BottomAxis: TChartAxis read FBottomAxis write SetBottomAxis;
|
|
property Frame: TChartPen read FFrame write setFrame;
|
|
|
|
property BackColor: TColor read FBackColor write SetBackColor;
|
|
|
|
property AxisVisible: boolean read FAxisVisible write SetAxisVisible;
|
|
|
|
property Align;
|
|
property Color;
|
|
property DragCursor;
|
|
property DragMode;
|
|
property Enabled;
|
|
property ParentColor;
|
|
property ParentShowHint;
|
|
property PopupMenu;
|
|
property ShowHint;
|
|
property Visible;
|
|
|
|
property OnClick;
|
|
property OnDblClick;
|
|
property OnDragDrop;
|
|
property OnDragOver;
|
|
property OnEndDrag;
|
|
property OnStartDrag;
|
|
property OnMouseDown;
|
|
property OnMouseMove;
|
|
property OnMouseUp;
|
|
end;
|
|
|
|
|
|
|
|
procedure Register;
|
|
|
|
implementation
|
|
|
|
uses taseries;
|
|
|
|
procedure RotateLabel(Canvas: TCanvas; x,y:Integer; Const St:String; RotDegree:Integer);
|
|
var OldFont,
|
|
NewFont: HFONT;
|
|
LogRec: TLOGFONT;
|
|
DC : HDC;
|
|
begin
|
|
with Canvas do begin
|
|
Brush.Style := bsClear;
|
|
GetObject(Font.Handle, SizeOf(LogRec), @LogRec);
|
|
LogRec.lfEscapement := RotDegree*10;
|
|
LogRec.lfOrientation := 0;
|
|
LogRec.lfOutPrecision := OUT_TT_ONLY_PRECIS;
|
|
NewFont := CreateFontIndirect(LogRec);
|
|
DC:=Handle;
|
|
end;
|
|
OldFont := SelectObject(DC,NewFont);
|
|
TextOut(DC,X, Y, @St[1],Length(St));
|
|
DeleteObject(SelectObject(DC,OldFont));
|
|
end;
|
|
|
|
procedure TChartPen.SetVisible(Value: Boolean);
|
|
begin
|
|
FVisible := Value;
|
|
if assigned( OnChange ) then OnChange(Self);
|
|
end;
|
|
|
|
procedure TChartPen.Assign(Source: TPersistent);
|
|
begin
|
|
if Source is TChartPen then
|
|
with TChartPen( Source ) do begin
|
|
FVisible := Visible;
|
|
end;
|
|
inherited Assign( Source );
|
|
end;
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
Constructor TChartAxis.Create(AOwner: TCustomChart);
|
|
begin
|
|
inherited Create;
|
|
FOwner := AOwner;
|
|
FTitle := TChartAxisTitle.Create(AOwner);
|
|
FGrid := TChartPen.Create;
|
|
FGrid.OnChange := StyleChanged;
|
|
end;
|
|
|
|
Destructor TChartAxis.Destroy;
|
|
begin
|
|
FTitle.Free;
|
|
FGrid.Free;
|
|
inherited;
|
|
end;
|
|
|
|
procedure TChartAxis.SetVisible(value: boolean);
|
|
begin
|
|
FVisible := value;
|
|
StyleChanged(Self);
|
|
end;
|
|
|
|
procedure TChartAxis.SetTitle(value: TChartAxisTitle);
|
|
begin
|
|
FTitle.Assign(Value);
|
|
StyleChanged(Self);
|
|
end;
|
|
|
|
procedure TChartAxis.SetGrid(value: TChartPen);
|
|
begin
|
|
FGrid.Assign(Value);
|
|
StyleChanged(Self);
|
|
end;
|
|
|
|
procedure TChartAxis.SetInverted(value: boolean);
|
|
//Inverts the axis scale from increasing to decreasing
|
|
begin
|
|
FInverted := value;
|
|
StyleChanged(Self);
|
|
end;
|
|
|
|
procedure TChartAxis.Assign(Source:TPersistent);
|
|
begin
|
|
if Source is TChartAxis then
|
|
with TChartAxis(Source) do begin
|
|
FTitle.Assign( Title );
|
|
FVisible := Visible;
|
|
end;
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
procedure TChartAxis.StyleChanged(Sender: TObject);
|
|
begin
|
|
FOwner.invalidate;
|
|
end;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
Constructor TChartAxisTitle.Create(AOwner: TCustomChart);
|
|
begin
|
|
inherited Create;
|
|
FOwner := AOwner;
|
|
FFont := TFont.Create;
|
|
FFont.OnChange := StyleChanged;
|
|
end;
|
|
|
|
Destructor TChartAxisTitle.Destroy;
|
|
begin
|
|
FFont.Destroy;
|
|
inherited;
|
|
end;
|
|
|
|
procedure TChartAxisTitle.SetCaption(value: String);
|
|
begin
|
|
FCaption := Value;
|
|
StyleChanged(Self);
|
|
end;
|
|
|
|
procedure TChartAxisTitle.SetAngle(value: Integer);
|
|
begin
|
|
FAngle := Value;
|
|
StyleChanged(Self);
|
|
end;
|
|
|
|
procedure TChartAxisTitle.SetFont(value: TFont);
|
|
begin
|
|
FFont.Assign( Value );
|
|
StyleChanged(Self);
|
|
end;
|
|
|
|
procedure TChartAxisTitle.StyleChanged(Sender: TObject);
|
|
begin
|
|
FOwner.Invalidate;
|
|
end;
|
|
|
|
procedure TChartAxisTitle.Assign(Source:TPersistent);
|
|
begin
|
|
if Source is TChartAxisTitle then
|
|
with TChartAxisTitle(Source) do begin
|
|
FCaption := Caption;
|
|
FAngle := Angle;
|
|
FFont.Assign(Font);
|
|
end;
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
Constructor TChartLegend.Create(AOwner: TCustomChart);
|
|
begin
|
|
inherited create;
|
|
FOwner := AOwner;
|
|
FVisible := false;
|
|
FAlignment := laRight;
|
|
|
|
FFont := TFont.Create;
|
|
FFont.OnChange := StyleChanged;
|
|
FFrame := TChartPen.Create;
|
|
FFrame.OnChange := StyleChanged;
|
|
end;
|
|
|
|
Destructor TChartLegend.Destroy;
|
|
begin
|
|
FFont.Destroy;
|
|
FFrame.Destroy;
|
|
|
|
inherited Destroy;
|
|
end;
|
|
|
|
Procedure TChartLegend.Assign(Source:TPersistent);
|
|
begin
|
|
if Source is TChartLegend then
|
|
With TChartLegend(Source) do
|
|
Begin
|
|
Self.FVisible := FVisible;
|
|
Self.FAlignment := FAlignment;
|
|
end;
|
|
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
procedure TChartLegend.SetVisible(value: boolean);
|
|
begin
|
|
FVisible := value;
|
|
StyleChanged(Self);
|
|
end;
|
|
|
|
procedure TChartLegend.SetAlignment(value: TLegendAlignment);
|
|
begin
|
|
FAlignment := value;
|
|
StyleChanged(Self);
|
|
end;
|
|
|
|
procedure TChartLegend.SetFont(value: TFont);
|
|
begin
|
|
FFont.Assign( value );
|
|
StyleChanged(Self);
|
|
end;
|
|
|
|
procedure TChartLegend.SetFrame(value: TChartPen);
|
|
begin
|
|
FFrame.Assign( value );
|
|
StyleChanged(Self);
|
|
end;
|
|
|
|
procedure TChartLegend.StyleChanged(Sender: TObject);
|
|
begin
|
|
FOwner.Invalidate;
|
|
end;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
Constructor TChartTitle.Create(AOwner: TCustomChart);
|
|
begin
|
|
inherited create;
|
|
FOwner := AOwner;
|
|
|
|
FFont := TFont.Create;
|
|
FFont.Color := clBlue;
|
|
FFont.OnChange := StyleChanged;
|
|
FFrame := TChartPen.Create;
|
|
FFrame.OnChange := StyleChanged;
|
|
FBrush := TBrush.Create;
|
|
FBrush.Color := FOwner.Color;
|
|
FBrush.OnChange := StyleChanged;
|
|
FText := TStringList.Create;
|
|
end;
|
|
|
|
Destructor TChartTitle.Destroy;
|
|
begin
|
|
FFont.Destroy;
|
|
FFrame.Destroy;
|
|
FBrush.Destroy;
|
|
FText.Destroy;
|
|
|
|
inherited Destroy;
|
|
end;
|
|
|
|
Procedure TChartTitle.Assign(Source:TPersistent);
|
|
begin
|
|
if Source is TChartTitle then
|
|
With TChartLegend(Source) do Begin
|
|
Self.FVisible := FVisible;
|
|
Self.FFont.Assign( Font );
|
|
Self.FBrush.Assign( Brush );
|
|
Self.FFrame.Assign( Frame );
|
|
Self.FText.Assign( Text );
|
|
end;
|
|
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
procedure TChartTitle.SetVisible(value: boolean);
|
|
begin
|
|
FVisible := value;
|
|
StyleChanged( Self );
|
|
end;
|
|
|
|
procedure TChartTitle.SetFont(value: TFont);
|
|
begin
|
|
FFont.Assign( value );
|
|
StyleChanged( Self );
|
|
end;
|
|
|
|
procedure TChartTitle.SetFrame(value: TChartPen);
|
|
begin
|
|
FFrame.Assign( value );
|
|
StyleChanged( Self );
|
|
end;
|
|
|
|
|
|
procedure TChartTitle.SetBrush(value: TBrush);
|
|
begin
|
|
FBrush.Assign( value );
|
|
StyleChanged( Self );
|
|
end;
|
|
|
|
procedure TChartTitle.SetText(value: TStrings);
|
|
begin
|
|
FText.Assign( value );
|
|
StyleChanged( Self );
|
|
end;
|
|
|
|
procedure TChartTitle.SetAlignment(value: TAlignment);
|
|
begin
|
|
FAlignment := Value;
|
|
StyleChanged( Self );
|
|
end;
|
|
|
|
|
|
procedure TChartTitle.StyleChanged(Sender: TObject);
|
|
begin
|
|
FOwner.Invalidate;
|
|
end;
|
|
|
|
|
|
procedure CalculateIntervals(Mini,Maxi:Double; AxisScale: TAxisScale;
|
|
var Debut,Pas:Double);
|
|
var
|
|
Etendue,EtendueTmp:Double;
|
|
NbPas,Mult:array[1..3] of Double;
|
|
|
|
Index:array[1..3] of Byte;
|
|
Trouve:Boolean;
|
|
DTmp:Double;
|
|
BTmp:Byte;
|
|
i,j:Integer;
|
|
begin
|
|
if Maxi>59 then Sleep(1);
|
|
Etendue:=Maxi-Mini;
|
|
if Etendue<=0 then begin Debut:=Mini; Pas:=1; Exit; end;
|
|
Mult[1]:=1;
|
|
EtendueTmp:=Etendue;
|
|
NbPas[1]:=EtendueTmp;
|
|
if NbPas[1]>=10 then
|
|
begin
|
|
while NbPas[1]>10 do
|
|
begin
|
|
EtendueTmp:=EtendueTmp/10;
|
|
Mult[1]:=Mult[1]/10;
|
|
NbPas[1]:=EtendueTmp;
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
while EtendueTmp*10<=10 do
|
|
begin
|
|
EtendueTmp:=EtendueTmp*10;
|
|
Mult[1]:=Mult[1]*10;
|
|
NbPas[1]:=EtendueTmp;
|
|
end;
|
|
end;
|
|
Mult[2]:=1;
|
|
EtendueTmp:=Etendue;
|
|
NbPas[2]:=EtendueTmp/0.5;
|
|
if NbPas[2]>=10 then
|
|
begin
|
|
while NbPas[2]>10 do
|
|
begin
|
|
EtendueTmp:=EtendueTmp/10;
|
|
Mult[2]:=Mult[2]/10;
|
|
NbPas[2]:=EtendueTmp/0.5;
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
while EtendueTmp*10/0.5<=10 do
|
|
begin
|
|
EtendueTmp:=EtendueTmp*10;
|
|
Mult[2]:=Mult[2]*10;
|
|
NbPas[2]:=EtendueTmp/0.5;
|
|
end;
|
|
end;
|
|
Mult[3]:=1;
|
|
EtendueTmp:=Etendue;
|
|
NbPas[3]:=EtendueTmp/0.2;
|
|
if NbPas[3]>=10 then
|
|
begin
|
|
while NbPas[3]>10 do
|
|
begin
|
|
EtendueTmp:=EtendueTmp/10;
|
|
Mult[3]:=Mult[3]/10;
|
|
NbPas[3]:=EtendueTmp/0.2;
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
while EtendueTmp*10/0.2<=10 do
|
|
begin
|
|
EtendueTmp:=EtendueTmp*10;
|
|
Mult[3]:=Mult[3]*10;
|
|
NbPas[3]:=EtendueTmp/0.2;
|
|
end;
|
|
end;
|
|
for i:=1 to 3 do Index[i]:=i;
|
|
Trouve:=True;
|
|
while Trouve do
|
|
begin
|
|
Trouve:=False;
|
|
for i:=1 to 2 do
|
|
if NbPas[i]>NbPas[i+1] then
|
|
begin
|
|
Trouve:=True;
|
|
DTmp:=NbPas[i];
|
|
NbPas[i]:=NbPas[i+1];
|
|
NbPas[i+1]:=DTmp;
|
|
BTmp:=Index[i];
|
|
Index[i]:=Index[i+1];
|
|
Index[i+1]:=BTmp;
|
|
end;
|
|
end;
|
|
if NbPas[3]<=10 then j:=3
|
|
else if NbPas[2]<=10 then j:=2
|
|
else if NbPas[1]<=10 then j:=1
|
|
else
|
|
begin
|
|
// ShowMessage(lang('Erreur'));
|
|
Exit;
|
|
end;
|
|
if Index[j]=1 then Pas:=1;
|
|
if Index[j]=2 then Pas:=0.5;
|
|
if Index[j]=3 then Pas:=0.2;
|
|
Pas:=Pas/Mult[Index[j]];
|
|
case AxisScale of
|
|
asIncreasing:
|
|
begin
|
|
// Sets 0 as a mark, in case it is in the interval
|
|
if (Mini<0) and (Maxi>0) then
|
|
begin
|
|
Debut:=0;
|
|
while (Debut > Mini) do Debut := Debut-Pas;
|
|
end
|
|
else
|
|
begin
|
|
// Don''t work if mini is negative and > 1
|
|
// if Abs(Mini)<1 then
|
|
Debut:=Round((Mini-Pas)*Mult[Index[j]])/Mult[Index[j]]
|
|
// else
|
|
// Debut:=System.Int(Mini)-Pas; //null
|
|
end;
|
|
end;
|
|
asDecreasing:
|
|
begin
|
|
// Sets 0 as a mark, in case it is in the interval
|
|
if (Mini<0) and (Maxi>0) then
|
|
begin
|
|
Debut:=0;
|
|
while (Debut < Maxi) do Debut := Debut+Pas;
|
|
end
|
|
else
|
|
begin
|
|
// Don''t work if mini is negative and > 1
|
|
// if Abs(Mini)<1 then
|
|
Debut:=Round((Maxi+Pas)*Mult[Index[j]])/Mult[Index[j]]
|
|
// else
|
|
// Debut:=System.Int(Mini)-Pas; //null
|
|
end;
|
|
end;
|
|
asLogIncreasing:
|
|
begin
|
|
// FIXME: asLogIncreasing is still not implemented. The following is the
|
|
// same code for asIncreasing;
|
|
// Sets 0 as a mark, in case it is in the interval
|
|
if (Mini<0) and (Maxi>0) then
|
|
begin
|
|
Debut:=0;
|
|
while (Debut > Mini) do Debut := Debut-Pas;
|
|
end
|
|
else
|
|
begin
|
|
// Don''t work if mini is negative and > 1
|
|
// if Abs(Mini)<1 then
|
|
Debut:=Round((Mini-Pas)*Mult[Index[j]])/Mult[Index[j]]
|
|
// else
|
|
// Debut:=System.Int(Mini)-Pas; //null
|
|
end;
|
|
end;
|
|
asLogDecreasing:
|
|
begin
|
|
// FIXME: asLogDecreasing is still not implemented. The following is the
|
|
// same code for asIncreasing;
|
|
// Sets 0 as a mark, in case it is in the interval
|
|
if (Mini<0) and (Maxi>0) then
|
|
begin
|
|
Debut:=0;
|
|
while (Debut > Mini) do Debut := Debut-Pas;
|
|
end
|
|
else
|
|
begin
|
|
// Don''t work if mini is negative and > 1
|
|
// if Abs(Mini)<1 then
|
|
Debut:=Round((Mini-Pas)*Mult[Index[j]])/Mult[Index[j]]
|
|
// else
|
|
// Debut:=System.Int(Mini)-Pas; //null
|
|
end;
|
|
end;
|
|
end; {case AxisScale}
|
|
end;
|
|
|
|
constructor TChart.Create(AOwner:TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
TmpBrush := TBrush.Create;
|
|
TmpPen := TPen.Create;
|
|
TmpFont := TFont.Create;
|
|
|
|
FAllowZoom := True;
|
|
FAxisVisible := true;
|
|
|
|
Width := 400;
|
|
Height := 300;
|
|
|
|
XVMarkOld:=-1;
|
|
XMarkOld:=-1;
|
|
YMarkOld:=-1;
|
|
|
|
Series:=TSeriesList.Create;
|
|
|
|
YMarkWidth:=10;
|
|
|
|
FAutoUpdateXMin:=True;
|
|
FAutoUpdateXMax:=True;
|
|
FAutoUpdateYMin:=True;
|
|
FAutoUpdateYMax:=True;
|
|
|
|
Color:=clBtnFace;
|
|
AxisColor:=clBlack;
|
|
|
|
FXGraphMax:=0;
|
|
FXGraphMin:=0;
|
|
FYGraphMax:=0;
|
|
FYGraphMin:=0;
|
|
|
|
MirrorX:=False;
|
|
Fixed:=False;
|
|
Zoom:=False;
|
|
FShowReticule:=False;
|
|
FShowVerticalReticule:=False;
|
|
FBackColor := Color;
|
|
|
|
FGraphBrush:=TBrush.Create;
|
|
FGraphBrush.OnChange:=StyleChanged;
|
|
|
|
FLegend := TChartLegend.Create(Self);
|
|
FTitle := TChartTitle.Create(Self);
|
|
FTitle.Alignment := taCenter;
|
|
FTitle.Text.Add('TAChart');
|
|
FFoot := TChartTitle.Create(Self);
|
|
|
|
FLeftAxis := TChartAxis.Create(Self);
|
|
FLeftAxis.Title.Angle := 90;
|
|
FLeftAxis.Inverted := false;
|
|
FLeftAxis.Grid.Visible := True;
|
|
FLeftAxis.Grid.Style := psDot;
|
|
FBottomAxis := TChartAxis.Create(Self);
|
|
FBottomAxis.Title.Angle := 0;
|
|
FBottomAxis.Inverted := false;
|
|
FBottomAxis.Grid.Visible := True;
|
|
FBottomAxis.Grid.Style := psDot;
|
|
|
|
FFrame := TChartPen.Create;
|
|
FFrame.Visible := true;
|
|
FFrame.OnChange := StyleChanged;
|
|
|
|
FNumBarSeries := 0;
|
|
end;
|
|
|
|
destructor TChart.Destroy;
|
|
var
|
|
MySerie:TChartSeries;
|
|
i,c: integer;
|
|
begin
|
|
if FSeries.Count > 0 then begin
|
|
c := FSeries.Count - 1;
|
|
for i := 0 to c do begin
|
|
TChartSeries(FSeries.Items[0]).Free;
|
|
FSeries.Delete( 0 );
|
|
end;
|
|
end;
|
|
|
|
FSeries.Free;
|
|
FGraphBrush.Free;
|
|
|
|
TmpBrush.Destroy;
|
|
TmpPen.Destroy;
|
|
TmpFont.Destroy;
|
|
|
|
FLegend.Destroy;
|
|
FTitle.Destroy;
|
|
LeftAxis.Destroy;
|
|
BottomAxis.Destroy;
|
|
FFrame.Destroy;
|
|
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TChart.StyleChanged(Sender: TObject);
|
|
begin
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TChart.Paint;
|
|
Begin
|
|
PaintOnCanvas(Canvas,Rect(0,0,Width,Height));
|
|
End;
|
|
|
|
procedure TChart.PaintOnCanvas(ACanvas : TCanvas; ARect : TRect);
|
|
var i: integer;
|
|
begin
|
|
YImageMin:=ARect.Bottom-5;
|
|
YImageMax:=ARect.Top+5;
|
|
|
|
if FTitle.Visible then begin
|
|
TmpFont.Assign( ACanvas.Font );
|
|
ACanvas.Font.Assign( FTitle.Font );
|
|
for i := 0 to FTitle.Text.Count -1 do begin
|
|
YImageMax:=YImageMax+5+ACanvas.TextHeight(FTitle.Text[i]);
|
|
end;
|
|
ACanvas.Font.Assign(TmpFont);
|
|
end;
|
|
|
|
if FFoot.Visible then begin
|
|
TmpFont.Assign( ACanvas.Font );
|
|
ACanvas.Font.Assign( FFoot.Font );
|
|
for i := 0 to FFoot.Text.Count -1 do begin
|
|
YImageMin:=YImageMin-5-ACanvas.TextHeight(FFoot.Text[i]);
|
|
end;
|
|
ACanvas.Font.Assign(TmpFont);
|
|
end;
|
|
|
|
if FBottomAxis.Visible and FAxisVisible then begin
|
|
//FIXME: fix to rotate other than 0/90/180 degres
|
|
YImageMin:=YImageMin-ACanvas.TextHeight(FBottomAxis.Title.Caption)
|
|
-ACanvas.TextHeight('1');
|
|
end;
|
|
if FMirrorX then begin
|
|
XImageMin:=ARect.Right-YMarkWidth-GetLegendWidth(ACanvas);
|
|
XImageMax:=ARect.Left;
|
|
end else begin
|
|
if FLeftAxis.Visible and FAxisVisible then
|
|
XImageMin:=YMarkWidth+ACanvas.TextHeight(FLeftAxis.Title.Caption)+ARect.Left
|
|
else
|
|
XImageMin:=YMarkWidth+ARect.Left;
|
|
XImageMax:=ARect.Right-10-GetLegendWidth(ACanvas);
|
|
end;
|
|
Refresh(ACanvas, ARect);
|
|
end;
|
|
|
|
procedure TChart.Clean;
|
|
begin
|
|
ACanvas.Pen.Mode:=pmCopy;
|
|
ACanvas.Pen.Style:=psSolid;
|
|
ACanvas.Pen.Color:=Color;
|
|
ACanvas.Brush.Color:=Color;
|
|
ACanvas.Brush.Style:=bsSolid;
|
|
ACanvas.Rectangle(ARect.Left,ARect.Top,ARect.Right,ARect.Bottom);
|
|
end;
|
|
|
|
procedure TChart.DrawTitleFoot(ACanvas : TCanvas; ARect : TRect);
|
|
var i: Integer;
|
|
t, xpos: Integer;
|
|
begin
|
|
if FTitle.Visible and (FTitle.Text.Count > 0) then begin
|
|
TmpBrush.Assign( ACanvas.Brush );
|
|
TmpFont.Assign( ACanvas.Font );
|
|
|
|
ACanvas.Brush.Assign( FTitle.Brush );
|
|
ACanvas.Font.Assign( FTitle.Font );
|
|
t := 5+ARect.Top;
|
|
for i := 0 to FTitle.Text.Count -1 do begin
|
|
case FTitle.Alignment of
|
|
taLeftJustify: xpos := XImageMin;
|
|
taCenter: xpos := (ARect.Left+ARect.Right-ACanvas.TextWidth(FTitle.Text[i])) div 2;
|
|
taRightJustify: xpos := XImageMax - ACanvas.TextWidth(FTitle.Text[i]);
|
|
end;
|
|
ACanvas.TextOut( xpos ,t,FTitle.Text[i]);
|
|
t := t + ACanvas.TextHeight(FTitle.Text[i]);
|
|
end;
|
|
ACanvas.Brush.Assign( TmpBrush );
|
|
ACanvas.Font.Assign( TmpFont );
|
|
end;
|
|
if FFoot.Visible and (FFoot.Text.Count > 0) then begin
|
|
TmpBrush.Assign( ACanvas.Brush );
|
|
TmpFont.Assign( ACanvas.Font );
|
|
ACanvas.Brush.Assign( FFoot.Brush );
|
|
ACanvas.Font.Assign( FFoot.Font );
|
|
t := ARect.Bottom-5-ACanvas.TextHeight(FFoot.Text[0]);
|
|
for i := FFoot.Text.Count - 1 downto 0 do begin
|
|
case FFoot.Alignment of
|
|
taLeftJustify: xpos := XImageMin;
|
|
taCenter: xpos := (ARect.Left+ARect.Right-ACanvas.TextWidth(FFoot.Text[i])) div 2;
|
|
taRightJustify: xpos := XImageMax - ACanvas.TextWidth(FFoot.Text[i]);
|
|
end;
|
|
ACanvas.TextOut( xpos ,t,FFoot.Text[i]);
|
|
t := t - ACanvas.TextHeight(FFoot.Text[i]);
|
|
end;
|
|
ACanvas.Brush.Assign( TmpBrush );
|
|
ACanvas.Font.Assign( TmpFont );
|
|
end;
|
|
end;
|
|
|
|
procedure TChart.DrawAxis(ACanvas : TCanvas; ARect : TRect);
|
|
var
|
|
LargTexte,MaxLargTexte,HautTexte:Integer;
|
|
XTemp,YTemp,XPos:Integer;
|
|
MyText:string;
|
|
Marque,Debut,Pas:Double;
|
|
T: Integer;
|
|
LeftAxisWidth: Integer;
|
|
LeftAxisScale, BottomAxisScale : TAxisScale;
|
|
begin
|
|
// Check AxisScale for both axes
|
|
Case LeftAxis.Inverted of
|
|
true : LeftAxisScale := asDecreasing;
|
|
false: LeftAxisScale := asIncreasing;
|
|
end;
|
|
Case BottomAxis.Inverted of
|
|
true : BottomAxisScale := asDecreasing;
|
|
false: BottomAxisScale := asIncreasing;
|
|
end;
|
|
// Find max mark width
|
|
MaxLargTexte:=0;
|
|
Debut:=FYGraphMax;
|
|
Pas:=1;
|
|
CalculateIntervals(FYGraphMin,FYGraphMax,LeftAxisScale,Debut,Pas);
|
|
if FYGraphMin<>FYGraphMax then begin
|
|
Marque:=Debut;
|
|
case LeftAxisScale of
|
|
asIncreasing:
|
|
begin
|
|
while Marque<=FYGraphMax+Pas*10e-10 do begin
|
|
if (Marque>=FYGraphMin) then begin
|
|
YGraphToImage(Marque,YTemp);
|
|
If (Marque<1e-16) and (Marque>-1e-16) then Marque:=0;
|
|
MyText:=Trim(FloatToStr(Marque));
|
|
LargTexte:=ACanvas.TextWidth(MyText);
|
|
if LargTexte>MaxLargTexte then MaxLargTexte:=LargTexte;
|
|
end;
|
|
Marque:=Marque+Pas;
|
|
end;
|
|
end;
|
|
asDecreasing:
|
|
begin
|
|
while Marque>=FYGraphMin-Pas*10e-10 do begin
|
|
if (Marque<=FYGraphMax) then begin
|
|
YGraphToImage(Marque,YTemp);
|
|
If (Marque<1e-16) and (Marque>-1e-16) then Marque:=0;
|
|
MyText:=Trim(FloatToStr(Marque));
|
|
LargTexte:=ACanvas.TextWidth(MyText);
|
|
if LargTexte>MaxLargTexte then MaxLargTexte:=LargTexte;
|
|
end;
|
|
Marque:=Marque-Pas;
|
|
end;
|
|
end;
|
|
end; {case LeftAxisScale}
|
|
end;
|
|
|
|
YMarkWidth:=10;
|
|
//only consider this width if visible
|
|
if FLeftAxis.Visible and FAxisVisible then
|
|
LeftAxisWidth := ACanvas.TextHeight(FLeftAxis.Title.Caption) + 16
|
|
else LeftAxisWidth := 0;
|
|
|
|
if MaxLargTexte+LeftAxisWidth>YMarkWidth then begin
|
|
YMarkWidth:=MaxLargTexte+LeftAxisWidth;
|
|
if FMirrorX then begin
|
|
XImageMin:=ARect.Right-YMarkWidth-GetLegendWidth(ACanvas);
|
|
XImageMax:=ARect.Left+10;
|
|
end
|
|
else begin
|
|
XImageMin:=ARect.Left+YMarkWidth;
|
|
XImageMax:=ARect.Right-10-GetLegendWidth(ACanvas);
|
|
end;
|
|
|
|
// Update coefs
|
|
if (FXGraphMax-FXGraphMin <>0) and (FYGraphMax-FYGraphMin <> 0) then begin
|
|
case BottomAxisScale of
|
|
asIncreasing:
|
|
begin
|
|
ax:=(XImageMax-XImageMin)/(FXGraphMax-FXGraphMin);
|
|
bx:=XImageMax-ax*FXGraphMax;
|
|
end;
|
|
asDecreasing:
|
|
begin
|
|
ax:=(XImageMax-XImageMin)/(FXGraphMin-FXGraphMax);
|
|
bx:=XImageMin-ax*FXGraphMax;
|
|
end;
|
|
end;
|
|
case LeftAxisScale of
|
|
asIncreasing:
|
|
begin
|
|
ay:=(YImageMax-YImageMin)/(FYGraphMax-FYGraphMin);
|
|
by:=YImageMax-ay*FYGraphMax;
|
|
end;
|
|
asDecreasing:
|
|
begin
|
|
ay:=(YImageMax-YImageMin)/(FYGraphMin-FYGraphMax);
|
|
by:=YImageMin-ay*FYGraphMax;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// Back
|
|
ACanvas.Pen.Style:=psClear;
|
|
ACanvas.Brush.Color := FBackColor;
|
|
ACanvas.Rectangle(XImageMin,YImageMin,XImageMax,YImageMax);
|
|
|
|
// Axes
|
|
if FFrame.Visible then begin
|
|
ACanvas.Pen.Assign( FFrame );
|
|
ACanvas.MoveTo(XImageMin,YImageMin);
|
|
ACanvas.LineTo(XImageMin,YImageMax);
|
|
ACanvas.MoveTo(XImageMin,YImageMin);
|
|
ACanvas.LineTo(XImageMax,YImageMin);
|
|
ACanvas.MoveTo(XImageMin,YImageMax);
|
|
ACanvas.LineTo(XImageMax,YImageMax);
|
|
ACanvas.MoveTo(XImageMax,YImageMin);
|
|
ACanvas.LineTo(XImageMax,YImageMax);
|
|
end;
|
|
|
|
|
|
// Axis Labels
|
|
if FLeftAxis.Visible and FAxisVisible then begin
|
|
{ Canvas.Brush.Color:=Color;
|
|
Canvas.Font.Color:=clBlack;
|
|
}
|
|
if FMirrorX then T := ARect.Right-ACanvas.TextWidth(FLeftAxis.Title.Caption)+5
|
|
else T := 5;
|
|
if FTitle.Visible then
|
|
RotateLabel(ACanvas, T, YImageMin+((YImageMax-YImageMin) div 2)
|
|
+(ACanvas.TextWidth(FLeftAxis.Title.Caption) div 2),
|
|
FLeftAxis.Title.Caption, FLeftAxis.Title.Angle)
|
|
else
|
|
RotateLabel(ACanvas, T, YImageMin+((YImageMax-YImageMin) div 2)
|
|
+(ACanvas.TextWidth(FLeftAxis.Title.Caption) div 2),
|
|
FLeftAxis.Title.Caption, FLeftAxis.Title.Angle);
|
|
end;
|
|
|
|
if FBottomAxis.Visible and FAxisVisible then begin
|
|
RotateLabel(ACanvas, XImageMin+((XImageMax-XImageMin) div 2)
|
|
-(ACanvas.TextWidth(FBottomAxis.Title.Caption) div 2),
|
|
YImageMin+5+ACanvas.TextHeight(FBottomAxis.Title.Caption),
|
|
FBottomAxis.Title.Caption, FBottomAxis.Title.Angle);
|
|
end;
|
|
|
|
// X graduations
|
|
if FBottomAxis.Visible and FAxisVisible then begin
|
|
Debut:=FXGraphMax;
|
|
Pas:=1;
|
|
CalculateIntervals(FXGraphMin,FXGraphMax,BottomAxisScale,Debut,Pas);
|
|
if FXGraphMin<>FXGraphMax then begin
|
|
Marque:=Debut;
|
|
case BottomAxisScale of
|
|
asIncreasing:
|
|
begin
|
|
while Marque<=FXGraphMax+Pas*10e-10 do begin
|
|
if (Marque>=FXGraphMin) then begin
|
|
XGraphToImage(Marque,XTemp);
|
|
ACanvas.Brush.Assign(FGraphBrush);
|
|
if FBottomAxis.Grid.Visible then begin
|
|
ACanvas.Pen.Assign(FBottomAxis.Grid);
|
|
if (XTemp<>XImageMax) and (XTemp<>XImageMin) then
|
|
begin
|
|
ACanvas.MoveTo(XTemp,YImageMin);
|
|
ACanvas.LineTo(XTemp,YImageMax);
|
|
end;
|
|
end;
|
|
ACanvas.Pen.Color:=AxisColor;
|
|
ACanvas.Pen.Style:=psSolid;
|
|
ACanvas.Pen.Mode:=pmCopy;
|
|
ACanvas.MoveTo(XTemp,YImageMin-4);
|
|
ACanvas.LineTo(XTemp,YImageMin+4);
|
|
ACanvas.Brush.Color:=Color;
|
|
If (Marque<1e-16) and (Marque>-1e-16) then Marque:=0;
|
|
MyText:=Trim(FloatToStr(Marque));
|
|
LargTexte:=ACanvas.TextWidth(MyText) div 2;
|
|
XPos:=XTemp-LargTexte;
|
|
if XPos<1 then Xpos:=1;
|
|
if XPos+LargTexte*2>ARect.Right then Xpos:=ARect.Right-LargTexte*2-1;
|
|
ACanvas.TextOut(Xpos,YImageMin+4,MyText);
|
|
end;
|
|
Marque:=Marque+Pas;
|
|
end;
|
|
end;
|
|
asDecreasing:
|
|
begin
|
|
while Marque>=FXGraphMin-Pas*10e-10 do begin
|
|
if (Marque<=FXGraphMax) then begin
|
|
XGraphToImage(Marque,XTemp);
|
|
ACanvas.Brush.Assign(FGraphBrush);
|
|
if FBottomAxis.Grid.Visible then begin
|
|
ACanvas.Pen.Assign(FBottomAxis.Grid);
|
|
if (XTemp<>XImageMax) and (XTemp<>XImageMin) then
|
|
begin
|
|
ACanvas.MoveTo(XTemp,YImageMin);
|
|
ACanvas.LineTo(XTemp,YImageMax);
|
|
end;
|
|
end;
|
|
ACanvas.Pen.Color:=AxisColor;
|
|
ACanvas.Pen.Style:=psSolid;
|
|
ACanvas.Pen.Mode:=pmCopy;
|
|
ACanvas.MoveTo(XTemp,YImageMin-4);
|
|
ACanvas.LineTo(XTemp,YImageMin+4);
|
|
ACanvas.Brush.Color:=Color;
|
|
If (Marque<1e-16) and (Marque>-1e-16) then Marque:=0;
|
|
MyText:=Trim(FloatToStr(Marque));
|
|
LargTexte:=ACanvas.TextWidth(MyText) div 2;
|
|
XPos:=XTemp-LargTexte;
|
|
if XPos<1 then Xpos:=1;
|
|
if XPos+LargTexte*2>ARect.Right then Xpos:=ARect.Right-LargTexte*2-1;
|
|
ACanvas.TextOut(Xpos,YImageMin+4,MyText);
|
|
end;
|
|
Marque:=Marque-Pas;
|
|
end;
|
|
end;
|
|
end; {case BottomAxisScale}
|
|
end;
|
|
end;
|
|
|
|
// Y graduations
|
|
if FLeftAxis.Visible and AxisVisible then begin
|
|
MaxLargTexte:=0;
|
|
Debut:=FYGraphMax;
|
|
Pas:=1;
|
|
CalculateIntervals(FYGraphMin,FYGraphMax,LeftAxisScale,Debut,Pas);
|
|
if FYGraphMin<>FYGraphMax then begin
|
|
Marque:=Debut;
|
|
case LeftAxisScale of
|
|
asIncreasing:
|
|
begin
|
|
while Marque<=FYGraphMax+Pas*10e-10 do begin
|
|
if (Marque>=FYGraphMin) then begin
|
|
YGraphToImage(Marque,YTemp);
|
|
ACanvas.Brush.Assign(FGraphBrush);
|
|
//draw grid
|
|
if FLeftAxis.Grid.Visible then begin
|
|
ACanvas.Pen.Assign(FLeftAxis.Grid);
|
|
if (YTemp<>YImageMax) and (YTemp<>YImageMin) then
|
|
begin
|
|
ACanvas.MoveTo(XImageMin,YTemp);
|
|
ACanvas.LineTo(XImageMax,YTemp);
|
|
end;
|
|
end;
|
|
ACanvas.Pen.Color:=AxisColor;
|
|
ACanvas.Pen.Style:=psSolid;
|
|
ACanvas.Pen.Mode:=pmCopy;
|
|
ACanvas.MoveTo(XImageMin-4,YTemp);
|
|
ACanvas.LineTo(XImageMin+4,YTemp);
|
|
ACanvas.Brush.Color:=Color;
|
|
If (Marque<1e-16) and (Marque>-1e-16) then Marque:=0;
|
|
MyText:=Trim(FloatToStr(Marque));
|
|
LargTexte:=ACanvas.TextWidth(MyText);
|
|
if LargTexte>MaxLargTexte then MaxLargTexte:=LargTexte;
|
|
HautTexte:=ACanvas.TextHeight(MyText) div 2;
|
|
if FMirrorX then
|
|
ACanvas.TextOut(XImageMin+6,YTemp-HautTexte,MyText)
|
|
else
|
|
ACanvas.TextOut(XImageMin-7-LargTexte,YTemp-HautTexte,MyText);
|
|
end;
|
|
Marque:=Marque+Pas;
|
|
end;
|
|
end;
|
|
asDecreasing:
|
|
begin
|
|
while Marque>=FYGraphMin-Pas*10e-10 do begin
|
|
if (Marque<=FYGraphMax) then begin
|
|
YGraphToImage(Marque,YTemp);
|
|
ACanvas.Brush.Assign(FGraphBrush);
|
|
//draw grid
|
|
if FLeftAxis.Grid.Visible then begin
|
|
ACanvas.Pen.Assign(FLeftAxis.Grid);
|
|
if (YTemp<>YImageMax) and (YTemp<>YImageMin) then
|
|
begin
|
|
ACanvas.MoveTo(XImageMin,YTemp);
|
|
ACanvas.LineTo(XImageMax,YTemp);
|
|
end;
|
|
end;
|
|
ACanvas.Pen.Color:=AxisColor;
|
|
ACanvas.Pen.Style:=psSolid;
|
|
ACanvas.Pen.Mode:=pmCopy;
|
|
ACanvas.MoveTo(XImageMin-4,YTemp);
|
|
ACanvas.LineTo(XImageMin+4,YTemp);
|
|
ACanvas.Brush.Color:=Color;
|
|
If (Marque<1e-16) and (Marque>-1e-16) then Marque:=0;
|
|
MyText:=Trim(FloatToStr(Marque));
|
|
LargTexte:=ACanvas.TextWidth(MyText);
|
|
if LargTexte>MaxLargTexte then MaxLargTexte:=LargTexte;
|
|
HautTexte:=ACanvas.TextHeight(MyText) div 2;
|
|
if FMirrorX then
|
|
ACanvas.TextOut(XImageMin+6,YTemp-HautTexte,MyText)
|
|
else
|
|
ACanvas.TextOut(XImageMin-7-LargTexte,YTemp-HautTexte,MyText);
|
|
end;
|
|
Marque:=Marque-Pas;
|
|
end;
|
|
end;
|
|
end; {case LeftAxisScale}
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TChart.DrawLegend(ACanvas : TCanvas; ARect : TRect);
|
|
var
|
|
w,h,x1,y1,x2,y2,i,TH:Integer;
|
|
MySerie:TChartSeries;
|
|
begin
|
|
TmpBrush.Assign(ACanvas.Brush);
|
|
TmpPen.Assign(ACanvas.Pen);
|
|
TmpFont.Assign(ACanvas.Font);
|
|
|
|
w:=GetLegendWidth(ACanvas);
|
|
TH:=ACanvas.TextHeight('I');
|
|
|
|
if only_pie then begin//if only one pie show diferent legend
|
|
MySerie := get_pie;
|
|
h:=5+MySerie.Count*(TH+5);
|
|
end else begin
|
|
h:=5+SeriesInLegendCount*(TH+5);
|
|
end;
|
|
x1 := ARect.Right-w-5;
|
|
y1 := YImageMax;
|
|
x2 := x1+w;
|
|
y2 := y1+h;
|
|
|
|
// Border
|
|
ACanvas.Brush.Assign(FGraphBrush);
|
|
ACanvas.Pen.Assign( FLegend.Frame );
|
|
ACanvas.Font.Assign( FLegend.Font );
|
|
ACanvas.Rectangle(x1,y1,x2,y2);
|
|
|
|
// Lines and Series titles
|
|
if only_pie then begin//if only one pie show diferent legend
|
|
MySerie := get_pie;
|
|
for i := 0 to MySerie.Count - 1 do begin //clean this coord shoould not be published
|
|
ACanvas.Pen.Color := FLegend.Frame.Color;
|
|
ACanvas.Brush.Color := FGraphBrush.Color;
|
|
ACanvas.TextOut(x1+25,y1+5+i*(TH+5), format('%1.2g',[PChartCoord(MySerie.Coord.items[i])^.y])+' '+PChartCoord(MySerie.Coord.items[i])^.Text);
|
|
ACanvas.Pen.Color := clBlack;
|
|
ACanvas.Brush.Color := PChartCoord(MySerie.Coord.items[i])^.Color;
|
|
ACanvas.Rectangle(x1+5,y1+i*(TH+5)+TH div 2, x1+22,y1+10+i*(TH+5)+TH div 2);
|
|
end;
|
|
end else begin
|
|
for i:=0 to SeriesCount-1 do begin
|
|
MySerie:=Series[i];
|
|
if MySerie.ShowInLegend then begin
|
|
ACanvas.Brush.Assign(FGraphBrush);
|
|
ACanvas.TextOut(x1+25,y1+5+i*(TH+5),MySerie.Title);
|
|
ACanvas.Pen.Color := MySerie.SeriesColor;
|
|
if MySerie is TBarSeries then begin
|
|
ACanvas.Pen.Color := clBlack;
|
|
ACanvas.Brush.Assign( (MySerie as TBarSeries).BarBrush);
|
|
ACanvas.Rectangle(x1+5,y1+i*(TH+5)+TH div 2, x1+22,y1+10+i*(TH+5)+TH div 2);
|
|
end else if MySerie is TAreaSeries then begin
|
|
ACanvas.Pen.Color := clBlack;
|
|
ACanvas.Brush.Color := MySerie.SeriesColor;;
|
|
ACanvas.Rectangle(x1+5,y1+i*(TH+5)+TH div 2, x1+22,y1+10+i*(TH+5)+TH div 2);
|
|
end else if (MySerie is TLine) or (MySerie is TSerie) then begin
|
|
ACanvas.MoveTo(x1+5,y1+5+i*(TH+5)+TH div 2);
|
|
ACanvas.LineTo(x1+22,y1+5+i*(TH+5)+TH div 2);
|
|
end else if MySerie is TPieSeries then begin end; //down't draw
|
|
end;
|
|
end;
|
|
end;
|
|
ACanvas.Brush.Assign(TmpBrush);
|
|
ACanvas.Pen.Assign(TmpPen);
|
|
ACanvas.Font.Assign(TmpFont);
|
|
end;
|
|
|
|
procedure TChart.SetAutoUpdateXMin(Value:Boolean);
|
|
begin
|
|
FAutoUpdateXMin:=Value;
|
|
end;
|
|
|
|
procedure TChart.SetAutoUpdateXMax(Value:Boolean);
|
|
begin
|
|
FAutoUpdateXMax:=Value;
|
|
end;
|
|
|
|
procedure TChart.SetAutoUpdateYMin(Value:Boolean);
|
|
begin
|
|
FAutoUpdateYMin:=Value;
|
|
end;
|
|
|
|
procedure TChart.SetAutoUpdateYMax(Value:Boolean);
|
|
begin
|
|
FAutoUpdateYMax:=Value;
|
|
end;
|
|
|
|
procedure TChart.SetXGraphMin(Value:Double);
|
|
begin
|
|
FXGraphMin:=Value;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TChart.SetYGraphMin(Value:Double);
|
|
begin
|
|
FYGraphMin:=Value;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TChart.SetXGraphMax(Value:Double);
|
|
begin
|
|
FXGraphMax:=Value;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TChart.SetYGraphMax(Value:Double);
|
|
begin
|
|
FYGraphMax:=Value;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TChart.SetMirrorX(Value:Boolean);
|
|
begin
|
|
if Value<>FMirrorX then
|
|
begin
|
|
if FMirrorX then
|
|
begin
|
|
XImageMin:=YMarkWidth;
|
|
XImageMax:=Width-10-GetLegendWidth(Canvas);
|
|
FMirrorX:=False;
|
|
end
|
|
else
|
|
begin
|
|
XImageMin:=Width-YMarkWidth-GetLegendWidth(Canvas);
|
|
XImageMax:=10;
|
|
FMirrorX:=True;
|
|
end;
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
procedure TChart.SetTitle(Value:TChartTitle);
|
|
begin
|
|
FTitle.Assign( Value );
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TChart.SetFoot(value: TChartTitle);
|
|
begin
|
|
FFoot.Assign(Value);
|
|
Invalidate;
|
|
end;
|
|
|
|
|
|
function TChart.GetLegendWidth(ACanvas : TCanvas):Integer;
|
|
var
|
|
i,j,k:Integer;
|
|
MySerie:TSerie;
|
|
begin
|
|
if (not FLegend.Visible) or (SeriesInLegendCount = 0) then begin Result:=0; Exit; end;
|
|
|
|
if only_pie then begin//if only one pie show diferent legend
|
|
MySerie := get_pie;
|
|
j := 0;
|
|
for i := 0 to MySerie.Count - 1 do begin //clean this coord should not be published
|
|
k := ACanvas.TextWidth( format('%1.2g',[PChartCoord(MySerie.Coord.items[i])^.y])
|
|
+' '+PChartCoord(MySerie.Coord.items[i])^.Text) ;
|
|
if k>j then j:=k;
|
|
end;
|
|
Result:=j+20+10;
|
|
end else begin
|
|
j:=0;
|
|
for i:=0 to SeriesCount-1 do begin
|
|
MySerie:=Series[i];
|
|
if MySerie.ShowInLegend then begin
|
|
k:=ACanvas.TextWidth(MySerie.Title);
|
|
if k>j then j:=k;
|
|
end;
|
|
end;
|
|
Result:=j+20+10;
|
|
end;
|
|
end;
|
|
|
|
procedure TChart.SetGraphBrush(Value:TBrush);
|
|
begin
|
|
FGraphBrush.Assign(Value);
|
|
end;
|
|
|
|
procedure TChart.AddSerie(Serie:TComponent);
|
|
begin
|
|
if FShowVerticalReticule then DrawVerticalReticule(Canvas,XVMarkOld);
|
|
if FShowReticule then DrawReticule(Canvas,XMarkOld,YMarkOld);
|
|
//disable axis when we have TPie series
|
|
if Serie is TPieSeries then begin
|
|
LeftAxis.Visible := False;
|
|
BottomAxis.Visible := False;
|
|
end;
|
|
Series.Add(Serie);
|
|
TChartSeries(Serie).ParentChart := Self;
|
|
|
|
if Serie is TBarSeries then begin
|
|
(Serie as TBarSeries).SeriesNumber := FNumBarSeries;
|
|
inc(FNumBarSeries); //FIXME: this is never decremented when series is deleted
|
|
end;
|
|
end;
|
|
|
|
procedure TChart.DeleteSerie(Serie:TComponent);
|
|
var
|
|
i:Integer;
|
|
MySerie:TComponent;
|
|
begin
|
|
i:=0;
|
|
while i< SeriesCount do
|
|
begin
|
|
MySerie:=Series[i];
|
|
if Serie=MySerie then
|
|
begin
|
|
Series.Delete(i);
|
|
Invalidate;
|
|
end
|
|
else Inc(i);
|
|
end;
|
|
end;
|
|
|
|
function TChart.GetSerie(i:Integer):TComponent;
|
|
begin
|
|
Result:=Series[i];
|
|
end;
|
|
|
|
procedure TChart.SetAutoXMin(Auto:Boolean);
|
|
begin
|
|
FAutoUpdateXMin:=Auto;
|
|
Refresh(Canvas,Rect(0,0,Width, Height));
|
|
end;
|
|
|
|
procedure TChart.SetAutoXMax(Auto:Boolean);
|
|
begin
|
|
FAutoUpdateXMax:=Auto;
|
|
Refresh(Canvas,Rect(0,0,Width, Height));
|
|
end;
|
|
|
|
procedure TChart.SetAutoYMin(Auto:Boolean);
|
|
begin
|
|
FAutoUpdateYMin:=Auto;
|
|
Refresh(Canvas,Rect(0,0,Width, Height));
|
|
end;
|
|
|
|
procedure TChart.SetAutoYMax(Auto:Boolean);
|
|
begin
|
|
FAutoUpdateYMax:=Auto;
|
|
Refresh(Canvas,Rect(0,0,Width, Height));
|
|
end;
|
|
|
|
procedure TChart.Refresh(ACanvas : TCanvas; ARect : TRect);
|
|
var
|
|
Tolerance,Valeur:Double;
|
|
i:Integer;
|
|
NBPointsMax:Integer;
|
|
Serie:TChartSeries;
|
|
XMinSeries,XMaxSeries,YMinSeries,YMaxSeries:Double;
|
|
LeftAxisScale, BottomAxisScale : TAxisScale;
|
|
begin
|
|
if FShowVerticalReticule then DrawVerticalReticule(ACanvas,XVMarkOld);
|
|
if FShowReticule then DrawReticule(ACanvas,XMarkOld,YMarkOld);
|
|
// Check AxisScale for both axes
|
|
Case LeftAxis.Inverted of
|
|
true : LeftAxisScale := asDecreasing;
|
|
false: LeftAxisScale := asIncreasing;
|
|
end;
|
|
Case BottomAxis.Inverted of
|
|
true : BottomAxisScale := asDecreasing;
|
|
false: BottomAxisScale := asIncreasing;
|
|
end;
|
|
// Search # of points, min and max of all series
|
|
if Zoom then begin
|
|
Zoom:=False;
|
|
Fixed:=True;
|
|
XImageToGraph(ZoomRect.Left,FXGraphMin);
|
|
XImageToGraph(ZoomRect.Right,FXGraphMax);
|
|
YImageToGraph(ZoomRect.Bottom,FYGraphMin);
|
|
YImageToGraph(ZoomRect.Top,FYGraphMax);
|
|
end
|
|
else if not Fixed then begin
|
|
XMinSeries:=MaxDouble;
|
|
XMaxSeries:=MinDouble;
|
|
YMinSeries:=MaxDouble;
|
|
YMaxSeries:=MinDouble;
|
|
NBPointsMax:=0;
|
|
for i:=0 to Series.Count-1 do begin
|
|
Serie := Series[i];
|
|
if Serie.Active and (TChartSeries(Serie).Count>0) then begin
|
|
NBPointsMax:=NBPointsMax+TChartSeries(Serie).Count;
|
|
if TChartSeries(Serie).XGraphMin<XMinSeries then XMinSeries:=TChartSeries(Serie).XGraphMin;
|
|
if TChartSeries(Serie).YGraphMin<YMinSeries then YMinSeries:=TChartSeries(Serie).YGraphMin;
|
|
if TChartSeries(Serie).XGraphMax>XMaxSeries then XMaxSeries:=TChartSeries(Serie).XGraphMax;
|
|
if TChartSeries(Serie).YGraphMax>YMaxSeries then YMaxSeries:=TChartSeries(Serie).YGraphMax;
|
|
end;
|
|
end;
|
|
if XMinSeries>MaxDouble/10 then XMinSeries:=0;
|
|
if YMinSeries>MaxDouble/10 then YMinSeries:=0;
|
|
if XMaxSeries<MinDouble/10 then XMaxSeries:=0;
|
|
if YMaxSeries<MinDouble/10 then YMaxSeries:=0;
|
|
|
|
if YMaxSeries = YMinSeries then begin
|
|
YMaxSeries := YMaxSeries + 1;
|
|
YMinSeries := YMinSeries - 1;
|
|
end;
|
|
if XMaxSeries = XMinSeries then begin
|
|
XMaxSeries := XMaxSeries + 1;
|
|
XMinSeries := XMinSeries - 1;
|
|
end;
|
|
|
|
|
|
// Image coordinates calculation
|
|
// Update max in graph
|
|
// If one point : +/-10% of the point coordinates
|
|
Tolerance:=0.001; //this should be cleaned eventually
|
|
// Tolerance:=0.1;
|
|
|
|
if NBPointsMax > 0 then
|
|
// If several points : automatic +/-10% of interval
|
|
begin
|
|
Valeur:=Tolerance*(XMaxSeries-XMinSeries);
|
|
if Valeur<>0 then
|
|
begin
|
|
if FAutoUpdateXMin then FXGraphMin:=XMinSeries-Valeur;
|
|
if FAutoUpdateXMax then FXGraphMax:=XMaxSeries+Valeur;
|
|
end
|
|
else
|
|
begin
|
|
if FAutoUpdateXMin then FXGraphMin:=XMinSeries-1;
|
|
if FAutoUpdateXMax then FXGraphMax:=XMaxSeries+1;
|
|
end;
|
|
Valeur:=Tolerance*(YMaxSeries-YMinSeries);
|
|
if Valeur<>0 then
|
|
begin
|
|
if FAutoUpdateYMin then FYGraphMin:=YMinSeries-Valeur;
|
|
if FAutoUpdateYMax then FYGraphMax:=YMaxSeries+Valeur;
|
|
end
|
|
else
|
|
begin
|
|
if FAutoUpdateYMin then FYGraphMin:=YMinSeries-1;
|
|
if FAutoUpdateYMax then FYGraphMax:=YMinSeries+1;
|
|
end;
|
|
end
|
|
else
|
|
// 0 Points
|
|
begin
|
|
FXGraphMin:=0;
|
|
FXGraphMax:=0;
|
|
FYGraphMin:=0;
|
|
FYGraphMax:=0;
|
|
end;
|
|
end;
|
|
// Image <-> Graph coeff calculation
|
|
if FXGraphMax<>FXGraphMin then
|
|
begin
|
|
case BottomAxisScale of
|
|
asIncreasing:
|
|
begin
|
|
ax:=(XImageMax-XImageMin)/(FXGraphMax-FXGraphMin);
|
|
bx:=XImageMax-ax*FXGraphMax;
|
|
end;
|
|
asDecreasing:
|
|
begin
|
|
ax:=(XImageMax-XImageMin)/(FXGraphMin-FXGraphMax);
|
|
bx:=XImageMin-ax*FXGraphMax;
|
|
end;
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
ax:=1;
|
|
bx:=0;
|
|
end;
|
|
if FYGraphMax<>FYGraphMin then
|
|
begin
|
|
case LeftAxisScale of
|
|
asIncreasing:
|
|
begin
|
|
ay:=(YImageMax-YImageMin)/(FYGraphMax-FYGraphMin);
|
|
by:=YImageMax-ay*FYGraphMax;
|
|
end;
|
|
asDecreasing:
|
|
begin
|
|
ay:=(YImageMax-YImageMin)/(FYGraphMin-FYGraphMax);
|
|
by:=YImageMin-ay*FYGraphMax;
|
|
end;
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
ay:=1;
|
|
by:=0;
|
|
end;
|
|
Clean(ACanvas,ARect);
|
|
DrawAxis(ACanvas,ARect);
|
|
DisplaySeries(ACanvas);
|
|
DrawTitleFoot(ACanvas, ARect);
|
|
if FLegend.Visible then DrawLegend(ACanvas, ARect);
|
|
if FShowVerticalReticule then DrawVerticalReticule(ACanvas,XVMarkOld);
|
|
if FShowReticule then DrawReticule(ACanvas,XMarkOld,YMarkOld);
|
|
end;
|
|
|
|
procedure TChart.XGraphToImage(Xin:Double;var XOut:Integer);
|
|
begin
|
|
XOut:=Round(ax*XIn+bx);
|
|
end;
|
|
|
|
procedure TChart.YGraphToImage(Yin:Double;var YOut:Integer);
|
|
begin
|
|
YOut:=Round(ay*YIn+by);
|
|
end;
|
|
|
|
procedure TChart.GraphToImage(Xin,Yin:Double;var XOut,YOut:Integer);
|
|
begin
|
|
XGraphToImage(Xin,XOut);
|
|
YGraphToImage(Yin,YOut);
|
|
end;
|
|
|
|
procedure TChart.XImageToGraph(XIn:Integer;var XOut:Double);
|
|
begin
|
|
XOut:=(XIn-bx)/ax;
|
|
end;
|
|
|
|
procedure TChart.YImageToGraph(YIn:Integer;var YOut:Double);
|
|
begin
|
|
YOut:=(YIn-by)/ay;
|
|
end;
|
|
|
|
procedure TChart.ImageToGraph(XIn,YIn:Integer;var XOut,YOut:Double);
|
|
begin
|
|
XImageToGraph(XIn,XOut);
|
|
YImageToGraph(YIn,YOut);
|
|
end;
|
|
|
|
procedure TChart.SaveToBitmapFile(Const FileName:String);
|
|
var tmpR:TRect;
|
|
tmpBitmap: TBitmap;
|
|
begin
|
|
try
|
|
tmpBitmap := TBitmap.Create;
|
|
tmpR:=GetRectangle;
|
|
tmpBitmap.Width := tmpR.Right-tmpR.Left;
|
|
tmpBitmap.Height:= tmpR.Bottom-tmpR.Top;
|
|
tmpBitmap.Canvas.CopyRect(tmpR, Canvas, tmpR);
|
|
tmpBitmap.SaveToFile(FileName);
|
|
finally
|
|
tmpBitmap.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TChart.CopyToClipboardBitmap;
|
|
var tmpBitmap:TBitmap;
|
|
tmpR:TRect;
|
|
begin
|
|
try
|
|
tmpBitmap:=TBitmap.Create;
|
|
tmpR:=GetRectangle;
|
|
tmpBitmap.Width := tmpR.Right-tmpR.Left;
|
|
tmpBitmap.Height:= tmpR.Bottom-tmpR.Top;
|
|
tmpBitmap.Canvas.CopyRect(tmpR, Canvas, tmpR);
|
|
ClipBoard.Assign(tmpBitmap);
|
|
finally
|
|
tmpBitmap.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TChart.DrawOnCanvas(Rect : TRect; ACanvas : TCanvas);
|
|
var tmpCanvas : TCanvas;
|
|
Begin
|
|
PaintOnCanvas(ACanvas,Rect);
|
|
End;
|
|
|
|
|
|
|
|
procedure TChart.DisplaySeries(ACanvas : TCanvas);
|
|
var
|
|
i:Integer;
|
|
Serie:TChartSeries;
|
|
Rgn : HRGN;
|
|
p: array[0..1] of TPoint;
|
|
begin
|
|
if FSeries.Count = 0 then Exit;
|
|
|
|
//set cliping region so we don't draw outsite
|
|
IntersectClipRect(ACanvas.Handle, XImageMin, YImageMax, XImageMax, YImageMin);
|
|
|
|
// Update all series
|
|
for i:=0 to FSeries.Count-1 do begin
|
|
Serie:= TChartSeries( Series[i] );
|
|
if Serie.Active then
|
|
Begin
|
|
Serie.Draw(ACanvas);
|
|
End;
|
|
end;
|
|
|
|
//now disable clipping
|
|
SelectClipRgn(ACanvas.Handle, 0);
|
|
end;
|
|
|
|
procedure TChart.SetShowVerticalReticule(Value:Boolean);
|
|
begin
|
|
if FShowVerticalReticule then begin
|
|
DrawVerticalReticule(Canvas,XVMarkOld);
|
|
FShowVerticalReticule:=False;
|
|
end;
|
|
FShowVerticalReticule:=Value;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TChart.SetShowReticule(Value:Boolean);
|
|
begin
|
|
if Value=False then
|
|
DrawReticule(Canvas,XVMarkOld,YMarkOld);
|
|
FShowReticule:=Value;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TChart.GetPointNextTo(X,Y:Integer;var SerieNumberOut,PointNumberOut,XOut,YOut:Integer);
|
|
var
|
|
j,k,XPoint,YPoint,SerieNumber,PointNumber:Integer;
|
|
Mini,Dist,Xg,Yg,XgOut,YgOut:Double;
|
|
Serie:TComponent;
|
|
TASerie:TSerie;
|
|
T1,T2:Double;
|
|
begin
|
|
Mini:=MaxDouble;
|
|
for SerieNumber:=0 to Series.Count-1 do
|
|
begin
|
|
Serie:=Series[SerieNumber];
|
|
if Serie is TSerie then
|
|
begin
|
|
TASerie:=TSerie(Serie);
|
|
for PointNumber:=0 to TASerie.Count-1 do
|
|
begin
|
|
XPoint:=TASerie.GetXImgValue(PointNumber);
|
|
YPoint:=TASerie.GetYImgValue(PointNumber);
|
|
T1:=X-XPoint;
|
|
T2:=Y-YPoint;
|
|
Dist:=Sqrt(Sqr(T1)+Sqr(T2));
|
|
if Dist<=Mini then
|
|
begin
|
|
Mini:=Dist;
|
|
SerieNumberOut:=SerieNumber;
|
|
PointNumberOut:=PointNumber;
|
|
XOut:=XPoint;
|
|
YOut:=YPoint;
|
|
XgOut:=TASerie.GetXValue(PointNumber);
|
|
YgOut:=TASerie.GetYValue(PointNumber);
|
|
end;
|
|
end;
|
|
if SerieNumberOut=SerieNumber then DoDrawReticule(SerieNumberOut,PointNumberOut,XOut,YOut,XgOut,YgOut);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TChart.GetXPointNextTo(X,Y:Integer;var SerieNumberOut,PointNumberOut,XOut,YOut:Integer);
|
|
var
|
|
j,k,XPoint,YPoint,SerieNumber,PointNumber:Integer;
|
|
Mini,Dist,Xg,Yg:Double;
|
|
Serie:TComponent;
|
|
TASerie:TSerie;
|
|
begin
|
|
Mini:=MaxDouble;
|
|
SerieNumberOut:=-1;
|
|
for SerieNumber:=0 to Series.Count-1 do
|
|
begin
|
|
Serie:=Series[SerieNumber];
|
|
if Serie is TSerie then
|
|
begin
|
|
TASerie:=TSerie(Serie);
|
|
for PointNumber:=0 to TASerie.Count-1 do
|
|
begin
|
|
XPoint:=TASerie.GetXImgValue(PointNumber);
|
|
Dist:=Abs(X-XPoint);
|
|
if Dist<=Mini then
|
|
begin
|
|
Mini:=Dist;
|
|
SerieNumberOut:=SerieNumber;
|
|
PointNumberOut:=PointNumber;
|
|
XOut:=XPoint;
|
|
YOut:=TASerie.GetYImgValue(PointNumber);
|
|
Xg:=TASerie.GetXValue(PointNumber);
|
|
Yg:=TASerie.GetYValue(PointNumber);
|
|
end;
|
|
end;
|
|
if SerieNumberOut=SerieNumber then DoDrawVertReticule(SerieNumberOut,PointNumberOut,XOut,YOut,Xg,Yg);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TChart.GetYPointNextTo(X,Y:Integer;var SerieNumberOut,PointNumberOut,XOut,YOut:Integer);
|
|
var
|
|
j,k,XPoint,YPoint,SerieNumber,PointNumber:Integer;
|
|
Mini,Dist,Xg,Yg:Double;
|
|
Serie:TComponent;
|
|
TASerie:TSerie;
|
|
begin
|
|
Mini:=MaxDouble;
|
|
for SerieNumber:=0 to Series.Count-1 do
|
|
begin
|
|
Serie:=Series[SerieNumber];
|
|
if Serie is TSerie then
|
|
begin
|
|
TASerie:=TSerie(Serie);
|
|
for PointNumber:=0 to TASerie.Count-1 do
|
|
begin
|
|
YPoint:=TASerie.GetYImgValue(PointNumber);
|
|
Dist:=Abs(Y-YPoint);
|
|
if Dist<=Mini then
|
|
begin
|
|
Mini:=Dist;
|
|
SerieNumberOut:=SerieNumber;
|
|
PointNumberOut:=PointNumber;
|
|
XOut:=XPoint;
|
|
YOut:=YPoint;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TChart.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
|
|
begin
|
|
if (X < XImageMax) and (X > XImageMin) and (Y < YImageMin) and (Y > YImageMax)
|
|
and FAllowZoom then begin
|
|
Down:=True;
|
|
XDown:=X;
|
|
YDown:=Y;
|
|
XOld:=X;
|
|
YOld:=Y;
|
|
end;
|
|
end;
|
|
|
|
procedure TChart.DrawReticule(ACanvas : TCanvas; X,Y:Integer);
|
|
begin
|
|
ACanvas.Pen.Style:=psSolid;
|
|
ACanvas.Pen.Mode:=pmXor;
|
|
ACanvas.Pen.Color:=ClWhite;
|
|
ACanvas.Pen.Style:=psSolid;
|
|
ACanvas.Pen.Width:=1;
|
|
|
|
ACanvas.MoveTo(X,YImageMin);
|
|
ACanvas.LineTo(X,YImageMax);
|
|
ACanvas.MoveTo(XImageMin,Y);
|
|
ACanvas.LineTo(XImageMax,Y);
|
|
end;
|
|
|
|
procedure TChart.DrawVerticalReticule(ACanvas : TCanvas; X:Integer);
|
|
begin
|
|
Canvas.Pen.Style:=psSolid;
|
|
Canvas.Pen.Mode:=pmXor;
|
|
Canvas.Pen.Color:=ClWhite;
|
|
Canvas.Pen.Style:=psSolid;
|
|
Canvas.Pen.Width:=1;
|
|
|
|
Canvas.MoveTo(X,YImageMin);
|
|
Canvas.LineTo(X,YImageMax);
|
|
end;
|
|
|
|
procedure TChart.MouseMove(Shift: TShiftState; X, Y: Integer);
|
|
var
|
|
i,SerieNumber,PointNumber,XMin,Xmax,YMin,YMax,Temp:Integer;
|
|
MySerie:TSerie;
|
|
begin
|
|
if Down then
|
|
begin
|
|
Canvas.Brush.Style:=bsClear;
|
|
Canvas.Pen.Style:=psSolid;
|
|
Canvas.Pen.Mode:=pmXor;
|
|
Canvas.Pen.Color:=ClWhite;
|
|
Canvas.Pen.Style:=psSolid;
|
|
Canvas.Pen.Width:=1;
|
|
|
|
Canvas.Rectangle(XDown,YDown,XOld,YOld);
|
|
Canvas.Rectangle(XDown,YDown,X,Y);
|
|
|
|
XOld:=X;
|
|
YOld:=Y;
|
|
end
|
|
else
|
|
begin
|
|
XMin:=XImageMin;
|
|
XMax:=XImageMax;
|
|
YMin:=YImageMin;
|
|
YMax:=YImageMax;
|
|
if XMin>XMax then
|
|
begin
|
|
Temp:=XMin;
|
|
XMin:=XMax;
|
|
XMax:=Temp;
|
|
end;
|
|
if YMin>YMax then
|
|
begin
|
|
Temp:=YMin;
|
|
YMin:=YMax;
|
|
YMax:=Temp;
|
|
end;
|
|
|
|
for i:=0 to SeriesCount-1 do
|
|
begin
|
|
MySerie:=Series[i];
|
|
if FShowVerticalReticule then
|
|
begin
|
|
GetXPointNextTo(X,Y,SerieNumber,PointNumber,XReticule,YReticule);
|
|
if (XReticule<>XVMarkOld) and (XReticule>XMin) and (XReticule<XMax) then
|
|
begin
|
|
DrawVerticalReticule(Canvas,XVMarkOld);
|
|
DrawVerticalReticule(Canvas,XReticule);
|
|
FShowVerticalReticule:=True;
|
|
XVMarkOld:=XReticule;
|
|
end;
|
|
end;
|
|
if FShowReticule then
|
|
begin
|
|
GetPointNextTo(X,Y,SerieNumber,PointNumber,XReticule,YReticule);
|
|
if (XReticule<>XMarkOld) or (YReticule<>YMarkOld) then
|
|
if (XReticule>=XMin) and (XReticule<=XMax) and (YReticule>=YMin) and (YReticule<=YMax) then
|
|
begin
|
|
DrawReticule(Canvas,XMarkOld,YMarkOld);
|
|
DrawReticule(Canvas,XReticule,YReticule);
|
|
FShowReticule:=True;
|
|
XMarkOld:=XReticule;
|
|
YMarkOld:=YReticule;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TChart.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
|
|
begin
|
|
if Down then
|
|
begin
|
|
XMarkOld:=X;
|
|
YMarkOld:=Y;
|
|
|
|
Canvas.Brush.Style:=bsClear;
|
|
Canvas.Pen.Style:=psSolid;
|
|
Canvas.Pen.Mode:=pmXor;
|
|
Canvas.Pen.Color:=ClWhite;
|
|
Canvas.Pen.Style:=psSolid;
|
|
Canvas.Pen.Width:=1;
|
|
|
|
Canvas.Rectangle(XDown,YDown,XOld,YOld);
|
|
|
|
Down:=False;
|
|
if (XDown<XOld) and (YDown<YOld) then
|
|
begin
|
|
Zoom:=True;
|
|
end
|
|
else
|
|
begin
|
|
Zoom:=False;
|
|
Fixed:=False;
|
|
end;
|
|
if XDown<XOld then
|
|
begin
|
|
ZoomRect.Left:=XDown;
|
|
ZoomRect.Right:=XOld;
|
|
end
|
|
else
|
|
begin
|
|
ZoomRect.Left:=XOld;
|
|
ZoomRect.Right:=XDown;
|
|
end;
|
|
if YDown<YOld then
|
|
begin
|
|
ZoomRect.Bottom:=YOld;
|
|
ZoomRect.Top:=YDown;
|
|
end
|
|
else
|
|
begin
|
|
ZoomRect.Bottom:=YDown;
|
|
ZoomRect.Top:=YOld;
|
|
end;
|
|
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
procedure TChart.DoDrawVertReticule(IndexSerie,Index,Xi,Yi:Integer;Xg,Yg:Double);
|
|
begin
|
|
if Assigned(FDrawVertReticule) then FDrawVertReticule(Self,IndexSerie,Index,Xi,Yi,Xg,Yg);
|
|
end;
|
|
|
|
procedure TChart.DoDrawReticule(IndexSerie,Index,Xi,Yi:Integer;Xg,Yg:Double);
|
|
begin
|
|
if Assigned(FDrawReticule) then FDrawReticule(Self,IndexSerie,Index,Xi,Yi,Xg,Yg);
|
|
end;
|
|
|
|
function TChart.GetNewColor:TColor;
|
|
var
|
|
i,j:Integer;
|
|
MySerie:TSerie;
|
|
ColorFound:Boolean;
|
|
begin
|
|
for i:=1 to MaxColor do
|
|
begin
|
|
ColorFound:=False;
|
|
for j:=0 to SeriesCount-1 do
|
|
begin
|
|
MySerie:=Series[j];
|
|
if MySerie.GetColor(0)=Colors[i] then
|
|
ColorFound:=True;
|
|
end;
|
|
if not ColorFound then
|
|
begin
|
|
Result:=Colors[i];
|
|
Exit;
|
|
end;
|
|
end;
|
|
Randomize;
|
|
Result:=RGB(Random(255),Random(255),Random(255));
|
|
end;
|
|
|
|
function TChart.GetRectangle:TRect;
|
|
begin
|
|
Result.Left := 0;
|
|
Result.Top := 0;
|
|
Result.Right := Width;
|
|
Result.Bottom := Height;
|
|
end;
|
|
|
|
procedure TChart.SetLegend(Value:TChartLegend);
|
|
begin
|
|
FLegend.Assign(Value);
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TChart.SetLeftAxis(Value:TChartAxis);
|
|
begin
|
|
FLeftAxis.Assign(Value);
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TChart.SetBottomAxis(Value:TChartAxis);
|
|
begin
|
|
FBottomAxis.Assign(Value);
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TChart.SetFrame(Value: TChartPen);
|
|
begin
|
|
FFrame.Assign(Value);
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TChart.SetBackColor(Value: TColor);
|
|
begin
|
|
FBackColor := Value;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TChart.SetAxisVisible(Value: boolean);
|
|
begin
|
|
FAxisVisible := Value;
|
|
Invalidate;
|
|
end;
|
|
|
|
function TChart.GetChartHeight: integer;
|
|
begin
|
|
result := YImageMax - YImageMin;
|
|
end;
|
|
|
|
function TChart.GetChartWidth: integer;
|
|
begin
|
|
result := XImageMax - XImageMin;
|
|
end;
|
|
|
|
function TChart.GetSeriesCount: integer;
|
|
var i: integer;
|
|
begin
|
|
{result := 0;
|
|
for i := 0 to FSeries.count -1 do
|
|
if TChartSeries(FSeries.Items[i]).Active then
|
|
inc(Result); }
|
|
result := FSeries.count;
|
|
end;
|
|
|
|
|
|
////////////////////////UTILS.... clean a bit
|
|
//checks if only a pie chart is enabled
|
|
function TChart.only_pie: boolean;
|
|
var i: integer;
|
|
begin
|
|
if FSeries.count > 0 then result := true
|
|
else result := false;
|
|
for i := 0 to FSeries.count -1 do begin
|
|
if ( not (TChartSeries(Series.Items[i]) is TPieSeries)) and
|
|
TChartSeries(FSeries.Items[i]).Active then begin
|
|
result := false;
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
//get enabled pie chart
|
|
function TChart.get_pie: pointer;
|
|
var i: integer;
|
|
begin
|
|
result := nil;
|
|
for i := 0 to FSeries.count -1 do begin
|
|
if ( (TChartSeries(Series.Items[i]) is TPieSeries)) and
|
|
TChartSeries(FSeries.Items[i]).Active then begin
|
|
result := TChartSeries(Series.Items[i]) ;
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TChart.SeriesInLegendCount: integer;
|
|
var i: integer;
|
|
begin
|
|
Result := 0;
|
|
for i:=0 to SeriesCount-1 do
|
|
if TChartSeries(Series[i]).ShowInLegend then
|
|
Inc(Result);
|
|
end;
|
|
|
|
procedure TChart.ZoomFull;
|
|
Begin
|
|
Zoom:=False;
|
|
Fixed:=False;
|
|
Invalidate;
|
|
End;
|
|
|
|
|
|
procedure Register;
|
|
begin
|
|
RegisterComponents('Additional', [TChart]);
|
|
end;
|
|
|
|
{$IFDEF fpc}
|
|
initialization
|
|
{$I tagraph.lrs}
|
|
{$ENDIF}
|
|
|
|
|
|
end.
|