lazarus/components/tachart/tafuncseries.pas
2010-10-17 10:02:16 +00:00

465 lines
12 KiB
ObjectPascal

{
Function series for TAChart.
*****************************************************************************
* *
* See the file COPYING.modifiedLGPL.txt, included in this distribution, *
* for details about the copyright. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
* *
*****************************************************************************
Authors: Alexander Klenin
}
unit TAFuncSeries;
{$H+}
interface
uses
Classes, Graphics,
TAChartUtils, TACustomSeries, TALegend, TASources, TATypes;
const
DEF_COLORMAP_STEP = 4;
type
TFuncCalculateEvent = procedure (const AX: Double; out AY: Double) of object;
TFuncSeriesStep = 1..MaxInt;
{ TBasicFuncSeries }
TBasicFuncSeries = class(TCustomChartSeries)
private
FExtent: TChartExtent;
procedure SetExtent(AValue: TChartExtent);
protected
procedure AfterAdd; override;
procedure GetBounds(var ABounds: TDoubleRect); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Active default true;
property Extent: TChartExtent read FExtent write SetExtent;
property ShowInLegend;
property Title;
property ZPosition;
end;
{ TFuncSeries }
TFuncSeries = class(TBasicFuncSeries)
private
FDomainExclusions: TIntervalList;
FOnCalculate: TFuncCalculateEvent;
FPen: TChartPen;
FStep: TFuncSeriesStep;
procedure SetOnCalculate(const AValue: TFuncCalculateEvent);
procedure SetPen(const AValue: TChartPen);
procedure SetStep(AValue: TFuncSeriesStep);
protected
procedure GetLegendItems(AItems: TChartLegendItems); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Draw(ACanvas: TCanvas); override;
function IsEmpty: Boolean; override;
public
property DomainExclusions: TIntervalList read FDomainExclusions;
published
property AxisIndexY;
property OnCalculate: TFuncCalculateEvent
read FOnCalculate write SetOnCalculate;
property Pen: TChartPen read FPen write SetPen;
property Step: TFuncSeriesStep read FStep write SetStep default 2;
end;
TFuncCalculate3DEvent =
procedure (const AX, AY: Double; out AZ: Double) of object;
{ TColorMapSeries }
TColorMapSeries = class(TBasicFuncSeries)
private
FBrush: TBrush;
FColorSource: TCustomChartSource;
FInterpolate: Boolean;
FOnCalculate: TFuncCalculate3DEvent;
FStepX: TFuncSeriesStep;
FStepY: TFuncSeriesStep;
procedure SetBrush(AValue: TBrush);
procedure SetColorSource(AValue: TCustomChartSource);
procedure SetInterpolate(AValue: Boolean);
procedure SetOnCalculate(AValue: TFuncCalculate3DEvent);
procedure SetStepX(AValue: TFuncSeriesStep);
procedure SetStepY(AValue: TFuncSeriesStep);
protected
procedure GetLegendItems(AItems: TChartLegendItems); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
public
function ColorByValue(AValue: Double): TColor;
procedure Draw(ACanvas: TCanvas); override;
function IsEmpty: Boolean; override;
published
property AxisIndexX;
property AxisIndexY;
property Brush: TBrush read FBrush write SetBrush;
property ColorSource: TCustomChartSource read FColorSource write SetColorSource;
property Interpolate: Boolean
read FInterpolate write SetInterpolate default false;
property OnCalculate: TFuncCalculate3DEvent
read FOnCalculate write SetOnCalculate;
property StepX: TFuncSeriesStep
read FStepX write SetStepX default DEF_COLORMAP_STEP;
property StepY: TFuncSeriesStep
read FStepY write SetStepY default DEF_COLORMAP_STEP;
end;
implementation
uses
Math, SysUtils, TAGraph;
{ TBasicFuncSeries }
procedure TBasicFuncSeries.AfterAdd;
begin
inherited AfterAdd;
FExtent.SetOwner(FChart);
end;
constructor TBasicFuncSeries.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FExtent := TChartExtent.Create(FChart);
end;
destructor TBasicFuncSeries.Destroy;
begin
FreeAndNil(FExtent);
inherited Destroy;
end;
procedure TBasicFuncSeries.GetBounds(var ABounds: TDoubleRect);
begin
with Extent do begin
if UseXMin then ABounds.a.X := XMin;
if UseYMin then ABounds.a.Y := YMin;
if UseXMax then ABounds.b.X := XMax;
if UseYMax then ABounds.b.Y := YMax;
end;
end;
procedure TBasicFuncSeries.SetExtent(AValue: TChartExtent);
begin
if FExtent = AValue then exit;
FExtent.Assign(AValue);
UpdateParentChart;
end;
{ TFuncSeries }
constructor TFuncSeries.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FDomainExclusions := TIntervalList.Create;
FDomainExclusions.OnChange := @StyleChanged;
FPen := TChartPen.Create;
FPen.OnChange := @StyleChanged;
FStep := 2;
end;
destructor TFuncSeries.Destroy;
begin
FreeAndNil(FDomainExclusions);
FreeAndNil(FPen);
inherited;
end;
procedure TFuncSeries.Draw(ACanvas: TCanvas);
var
ygMin, ygMax: Double;
function CalcY(AXg: Double): Integer;
var
yg: Double;
begin
OnCalculate(AXg, yg);
Result := FChart.YGraphToImage(EnsureRange(AxisToGraphY(yg), ygMin, ygMax));
end;
var
x, xmax, hint: Integer;
xg, xg1: Double;
begin
if not Assigned(OnCalculate) then exit;
x := FChart.ClipRect.Left;
if Extent.UseXMin then
x := Max(FChart.XGraphToImage(Extent.XMin), x);
xmax := FChart.ClipRect.Right;
if Extent.UseXMax then
xmax := Min(FChart.XGraphToImage(Extent.XMax), xmax);
ygMin := FChart.CurrentExtent.a.Y;
if Extent.UseYMin and (ygMin < Extent.YMin) then
ygMin := Extent.YMin;
ygMax := FChart.CurrentExtent.b.Y;
if Extent.UseYMax and (ygMax < Extent.YMax) then
ygMax := Extent.YMax;
ExpandRange(ygMin, ygMax, 1);
hint := 0;
xg := FChart.XImageToGraph(x);
if DomainExclusions.Intersect(xg, xg, hint) then
x := FChart.XGraphToImage(xg);
ACanvas.MoveTo(x, CalcY(xg));
ACanvas.Pen.Assign(Pen);
while x < xmax do begin
Inc(x, FStep);
xg1 := FChart.XImageToGraph(x);
if DomainExclusions.Intersect(xg, xg1, hint) then begin
ACanvas.LineTo(FChart.XGraphToImage(xg), CalcY(xg));
x := FChart.XGraphToImage(xg1);
ACanvas.MoveTo(x, CalcY(xg1));
end
else
ACanvas.LineTo(x, CalcY(xg1));
xg := xg1;
end;
end;
procedure TFuncSeries.GetLegendItems(AItems: TChartLegendItems);
begin
AItems.Add(TLegendItemLine.Create(Pen, Title));
end;
function TFuncSeries.IsEmpty: Boolean;
begin
Result := not Assigned(OnCalculate);
end;
procedure TFuncSeries.SetOnCalculate(const AValue: TFuncCalculateEvent);
begin
if TMethod(FOnCalculate) = TMethod(AValue) then exit;
FOnCalculate := AValue;
UpdateParentChart;
end;
procedure TFuncSeries.SetPen(const AValue: TChartPen);
begin
if FPen = AValue then exit;
FPen.Assign(AValue);
UpdateParentChart;
end;
procedure TFuncSeries.SetStep(AValue: TFuncSeriesStep);
begin
if FStep = AValue then exit;
FStep := AValue;
UpdateParentChart;
end;
{ TColorMapSeries }
function TColorMapSeries.ColorByValue(AValue: Double): TColor;
var
lb, ub: Integer;
c1, c2: TColor;
v1, v2: Double;
begin
if ColorSource = nil then exit(clTAColor);
ColorSource.FindBounds(AValue, Infinity, lb, ub);
if Interpolate and InRange(lb, 1, ColorSource.Count - 1) then begin
with ColorSource[lb - 1]^ do begin
v1 := X;
c1 := Color;
end;
with ColorSource[lb]^ do begin
v2 := X;
c2 := Color;
end;
if v2 <= v1 then
Result := c1
else
Result := InterpolateRGB(c1, c2, (AValue - v1) / (v2 - v1));
end
else
Result := ColorSource[EnsureRange(lb, 0, ColorSource.Count - 1)]^.Color;
end;
constructor TColorMapSeries.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FBrush := TBrush.Create;
FBrush.OnChange := @StyleChanged;
FStepX := DEF_COLORMAP_STEP;
FStepY := DEF_COLORMAP_STEP;
end;
destructor TColorMapSeries.Destroy;
begin
FreeAndNil(FBrush);
inherited Destroy;
end;
procedure TColorMapSeries.Draw(ACanvas: TCanvas);
var
ext: TDoubleRect;
bounds: TDoubleRect =
(coords: (Infinity, Infinity, NegInfinity, NegInfinity));
r: TRect;
pt, next, offset: TPoint;
gp: TDoublePoint;
v: Double;
begin
if IsEmpty then exit;
ext := ParentChart.CurrentExtent;
GetBounds(bounds);
bounds.a := AxisToGraph(bounds.a);
bounds.b := AxisToGraph(bounds.b);
if not RectIntersectsRect(ext, bounds) then exit;
r.TopLeft := ParentChart.GraphToImage(ext.a);
r.BottomRight := ParentChart.GraphToImage(ext.b);
NormalizeRect(r);
offset := ParentChart.GraphToImage(ZeroDoublePoint);
ACanvas.Brush := Brush;
ACanvas.Pen.Style := psClear;
pt.Y := (r.Top div StepY - 1) * StepY + offset.Y mod StepY;
while pt.Y <= r.Bottom do begin
next.Y := pt.Y + StepY;
if next.Y <= r.Top then begin
pt.Y := next.Y;
continue;
end;
pt.X := (r.Left div StepX - 1) * StepX + offset.X mod StepX;
while pt.X <= r.Right do begin
next.X := pt.X + StepX;
if next.X <= r.Left then begin
pt.X := next.X;
continue;
end;
gp := GraphToAxis(ParentChart.ImageToGraph((pt + next) div 2));
OnCalculate(gp.X, gp.Y, v);
if ColorSource <> nil then
ACanvas.Brush.Color := ColorByValue(v);
ACanvas.Rectangle(
Max(pt.X, r.Left), Max(pt.Y, r.Top),
Min(next.X, r.Right) + 1, Min(next.Y, r.Bottom) + 1);
pt.X := next.X;
end;
pt.Y := next.Y;
end;
end;
procedure TColorMapSeries.GetLegendItems(AItems: TChartLegendItems);
var
i: Integer;
prev: Double;
function ItemTitle(const AText: String; AX: Double): String;
const
FORMATS: array [1..3] of String = ('z ≤ %1:g', '%g < z ≤ %g', '%g < z');
var
idx: Integer;
begin
if AText <> '' then exit(AText);
if ColorSource.Count = 1 then exit('');
if i = 0 then idx := 1
else if i = ColorSource.Count - 1 then idx := 3
else idx := 2;
Result := Format(FORMATS[idx], [prev, AX]);
end;
var
li: TLegendItemBrushRect;
begin
case Legend.Multiplicity of
lmSingle: AItems.Add(TLegendItemBrushRect.Create(Brush, Title));
lmPoint:
if ColorSource <> nil then begin
prev := 0.0;
for i := 0 to ColorSource.Count - 1 do
with ColorSource[i]^ do begin
li := TLegendItemBrushRect.Create(Brush, ItemTitle(Text, X));
li.Color := Color;
AItems.Add(li);
prev := X;
end;
end;
end;
end;
function TColorMapSeries.IsEmpty: Boolean;
begin
Result := not Assigned(OnCalculate);
end;
procedure TColorMapSeries.SetBrush(AValue: TBrush);
begin
if FBrush = AValue then exit;
FBrush := AValue;
UpdateParentChart;
end;
procedure TColorMapSeries.SetColorSource(AValue: TCustomChartSource);
begin
if FColorSource = AValue then exit;
FColorSource := AValue;
UpdateParentChart;
end;
procedure TColorMapSeries.SetInterpolate(AValue: Boolean);
begin
if FInterpolate = AValue then exit;
FInterpolate := AValue;
UpdateParentChart;
end;
procedure TColorMapSeries.SetOnCalculate(AValue: TFuncCalculate3DEvent);
begin
if FOnCalculate = AValue then exit;
FOnCalculate := AValue;
UpdateParentChart;
end;
procedure TColorMapSeries.SetStepX(AValue: TFuncSeriesStep);
begin
if FStepX = AValue then exit;
FStepX := AValue;
UpdateParentChart;
end;
procedure TColorMapSeries.SetStepY(AValue: TFuncSeriesStep);
begin
if FStepY = AValue then exit;
FStepY := AValue;
UpdateParentChart;
end;
initialization
RegisterSeriesClass(TFuncSeries, 'Function series');
RegisterSeriesClass(TColorMapSeries, 'Color map series');
end.