mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-27 17:53:57 +02:00
406 lines
11 KiB
ObjectPascal
406 lines
11 KiB
ObjectPascal
{
|
|
|
|
Basic code for function series of TAChart.
|
|
|
|
*****************************************************************************
|
|
See the file COPYING.modifiedLGPL.txt, included in this distribution,
|
|
for details about the license.
|
|
*****************************************************************************
|
|
|
|
Authors: Alexander Klenin
|
|
|
|
}
|
|
unit TACustomFuncSeries;
|
|
|
|
{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes,
|
|
TAChartUtils, TACustomSeries, TADrawUtils, TAGraph, TATypes;
|
|
|
|
type
|
|
{ TBasicFuncSeries }
|
|
|
|
TBasicFuncSeries = class(TCustomChartSeries)
|
|
strict private
|
|
FExtent: TChartExtent;
|
|
procedure SetExtent(AValue: TChartExtent);
|
|
protected
|
|
procedure AfterAdd; override;
|
|
procedure GetBounds(var ABounds: TDoubleRect); override;
|
|
public
|
|
procedure Assign(ASource: TPersistent); override;
|
|
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;
|
|
|
|
TMakeDoublePoint = function (AX, AY: Double): TDoublePoint;
|
|
|
|
TCustomDrawFuncHelper = class
|
|
private
|
|
FCalc: TTransformFunc;
|
|
FChart: TChart;
|
|
FDrawer: IChartDrawer;
|
|
FExtentYMax: PDouble;
|
|
FExtentYMin: PDouble;
|
|
FImageToGraph: TImageToGraphFunc;
|
|
FNearestPointParams: ^TNearestPointParams;
|
|
FNearestPointResults: ^TNearestPointResults;
|
|
FMakeDP: TMakeDoublePoint;
|
|
FPrev: TDoublePoint;
|
|
FPrevInExtent: Boolean;
|
|
procedure CalcAt(AXg, AXa: Double; out APt: TDoublePoint; out AIn: Boolean);
|
|
procedure CheckForNearestPoint(AXg, AXa: Double);
|
|
procedure LineTo(AXg, AXa: Double);
|
|
procedure MoveTo(AXg, AXa: Double);
|
|
procedure UpdateExtent(AXg, AXa: Double);
|
|
function XRange: TDoubleInterval;
|
|
protected
|
|
type
|
|
TOnPoint = procedure (AXg, AXa: Double) of object;
|
|
protected
|
|
FAxisToGraphXr, FAxisToGraphYr, FGraphToAxisXr: TTransformFunc;
|
|
FExtent: TDoubleRect;
|
|
FGraphStep: Double;
|
|
FSeries: TCustomChartSeries;
|
|
procedure ForEachPoint(AXg, AXMax: Double; AOnMoveTo, AOnLineTo: TOnPoint); virtual; abstract;
|
|
public
|
|
constructor Create(ASeries: TCustomChartSeries; ACalc: TTransformFunc; AStep: Integer);
|
|
procedure CalcAxisExtentY(AMinX, AMaxX: Double; var AMinY, AMaxY: Double);
|
|
procedure DrawFunction(ADrawer: IChartDrawer);
|
|
function GetNearestPoint(
|
|
const AParams: TNearestPointParams;
|
|
out AResults: TNearestPointResults): Boolean;
|
|
end;
|
|
|
|
TDrawFuncHelper = class(TCustomDrawFuncHelper)
|
|
private
|
|
FDomainExclusions: TIntervalList;
|
|
protected
|
|
procedure ForEachPoint(AXg, AXMax: Double; AOnMoveTo, AOnLineTo: TOnPoint); override;
|
|
public
|
|
constructor Create(ASeries: TCustomChartSeries; ADomainExclusions: TIntervalList;
|
|
ACalc: TTransformFunc; AStep: Integer);
|
|
end;
|
|
|
|
TPointsDrawFuncHelper = class(TCustomDrawFuncHelper)
|
|
private
|
|
FStartIndex: Integer;
|
|
protected
|
|
procedure ForEachPoint(AXg, AXMax: Double; AOnMoveTo, AOnLineTo: TOnPoint); override;
|
|
public
|
|
constructor Create(ASeries: TBasicPointSeries; AMinX, AMaxX: Double;
|
|
AStartIndex: Integer; ACalc: TTransformFunc; AStep: Integer);
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
Math, SysUtils,
|
|
TAGeometry, TAMath;
|
|
|
|
function DoublePointRotated(AX, AY: Double): TDoublePoint;
|
|
begin
|
|
Result.X := AY;
|
|
Result.Y := AX;
|
|
end;
|
|
|
|
|
|
{ TCustomDrawFuncHelper }
|
|
|
|
procedure TCustomDrawFuncHelper.CalcAt(
|
|
AXg, AXa: Double; out APt: TDoublePoint; out AIn: Boolean);
|
|
begin
|
|
APt := FMakeDP(AXg, FAxisToGraphYr(FCalc(AXa)));
|
|
if IsNaN(APt) then
|
|
AIn := false
|
|
else
|
|
AIn := (FExtent.a <= APt) and (APt <= FExtent.b);
|
|
end;
|
|
|
|
procedure TCustomDrawFuncHelper.CalcAxisExtentY(
|
|
AMinX, AMaxX: Double; var AMinY, AMaxY: Double);
|
|
begin
|
|
FExtentYMin := @AMinY;
|
|
FExtentYMax := @AMaxY;
|
|
with XRange do
|
|
ForEachPoint(AMinX, AMaxX, @UpdateExtent, @UpdateExtent);
|
|
end;
|
|
|
|
procedure TCustomDrawFuncHelper.CheckForNearestPoint(AXg, AXa: Double);
|
|
var
|
|
inExtent: Boolean;
|
|
gp: TDoublePoint;
|
|
ip: TPoint;
|
|
d: Integer;
|
|
begin
|
|
CalcAt(AXg, AXa, gp, inExtent);
|
|
if not inExtent then exit;
|
|
ip := FChart.GraphToImage(gp);
|
|
d := FNearestPointParams^.FDistFunc(FNearestPointParams^.FPoint, ip);
|
|
if d >= FNearestPointResults^.FDist then exit;
|
|
FNearestPointResults^.FDist := d;
|
|
FNearestPointResults^.FImg := ip;
|
|
FNearestPointResults^.FValue.X := AXa;
|
|
end;
|
|
|
|
constructor TCustomDrawFuncHelper.Create( ASeries: TCustomChartSeries;
|
|
ACalc: TTransformFunc; AStep: Integer);
|
|
begin
|
|
FChart := ASeries.ParentChart;
|
|
FExtent := FChart.CurrentExtent;
|
|
FSeries := ASeries;
|
|
FCalc := ACalc;
|
|
|
|
with FSeries do
|
|
if IsRotated then begin
|
|
FAxisToGraphXr := @AxisToGraphY;
|
|
FAxisToGraphYr := @AxisToGraphX;
|
|
FGraphToAxisXr := @GraphToAxisY;
|
|
FMakeDP := @DoublePointRotated;
|
|
FImageToGraph := @FChart.YImageToGraph;
|
|
AStep := -AStep;
|
|
end
|
|
else begin
|
|
FAxisToGraphXr := @AxisToGraphX;
|
|
FAxisToGraphYr := @AxisToGraphY;
|
|
FGraphToAxisXr := @GraphToAxisX;
|
|
FMakeDP := @DoublePoint;
|
|
FImageToGraph := @FChart.XImageToGraph;
|
|
end;
|
|
FGraphStep := FImageToGraph(AStep) - FImageToGraph(0);
|
|
end;
|
|
|
|
procedure TCustomDrawFuncHelper.DrawFunction(ADrawer: IChartDrawer);
|
|
begin
|
|
FDrawer := ADrawer;
|
|
with XRange do
|
|
ForEachPoint(FStart, FEnd, @MoveTo, @LineTo);
|
|
end;
|
|
|
|
function TCustomDrawFuncHelper.GetNearestPoint(
|
|
const AParams: TNearestPointParams;
|
|
out AResults: TNearestPointResults): Boolean;
|
|
var
|
|
x: Integer;
|
|
r: TDoubleInterval;
|
|
begin
|
|
AResults.FIndex := -1;
|
|
AResults.FDist := Sqr(AParams.FRadius) + 1;
|
|
FNearestPointParams := @AParams;
|
|
FNearestPointResults := @AResults;
|
|
|
|
with AParams do
|
|
if FOptimizeX then begin
|
|
x := TPointBoolArr(FPoint)[FSeries.IsRotated];
|
|
r := DoubleInterval(FImageToGraph(x - FRadius), FImageToGraph(x + FRadius));
|
|
EnsureOrder(r.FStart, r.FEnd);
|
|
end
|
|
else
|
|
r := DoubleInterval(NegInfinity, SafeInfinity);
|
|
with XRange do
|
|
ForEachPoint(
|
|
Max(r.FStart, FStart), Min(r.FEnd, FEnd),
|
|
@CheckForNearestPoint, @CheckForNearestPoint);
|
|
|
|
Result := AResults.FDist < Sqr(AParams.FRadius) + 1;
|
|
end;
|
|
|
|
procedure TCustomDrawFuncHelper.LineTo(AXg, AXa: Double);
|
|
var
|
|
p, t: TDoublePoint;
|
|
inExtent: Boolean;
|
|
begin
|
|
CalcAt(AXg, AXa, p, inExtent);
|
|
if IsNaN(p) then
|
|
exit;
|
|
t := p;
|
|
if inExtent and FPrevInExtent then
|
|
FDrawer.LineTo(FChart.GraphToImage(p))
|
|
else if LineIntersectsRect(FPrev, t, FExtent) then begin
|
|
FDrawer.MoveTo(FChart.GraphToImage(FPrev));
|
|
FDrawer.LineTo(FChart.GraphToImage(t));
|
|
end;
|
|
FPrevInExtent := inExtent;
|
|
FPrev := p;
|
|
end;
|
|
|
|
procedure TCustomDrawFuncHelper.MoveTo(AXg, AXa: Double);
|
|
begin
|
|
CalcAt(AXg, AXa, FPrev, FPrevInExtent);
|
|
if FPrevInExtent then
|
|
FDrawer.MoveTo(FChart.GraphToImage(FPrev));
|
|
end;
|
|
|
|
procedure TCustomDrawFuncHelper.UpdateExtent(AXg, AXa: Double);
|
|
begin
|
|
Unused(AXg);
|
|
UpdateMinMax(FCalc(AXa), FExtentYMin^, FExtentYMax^);
|
|
end;
|
|
|
|
function TCustomDrawFuncHelper.XRange: TDoubleInterval;
|
|
begin
|
|
if FSeries.IsRotated then
|
|
Result := DoubleInterval(FExtent.a.Y, FExtent.b.Y)
|
|
else
|
|
Result := DoubleInterval(FExtent.a.X, FExtent.b.X);
|
|
end;
|
|
|
|
|
|
{ TDrawFuncHelper }
|
|
|
|
constructor TDrawFuncHelper.Create(ASeries: TCustomChartSeries;
|
|
ADomainExclusions: TIntervalList; ACalc: TTransformFunc; AStep: Integer);
|
|
begin
|
|
inherited Create(ASeries, ACalc, AStep);
|
|
FDomainExclusions := ADomainExclusions;
|
|
end;
|
|
|
|
procedure TDrawFuncHelper.ForEachPoint(
|
|
AXg, AXMax: Double; AOnMoveTo, AOnLineTo: TOnPoint);
|
|
var
|
|
hint: Integer;
|
|
xa, xg1, xa1, dx: Double;
|
|
begin
|
|
if FGraphStep = 0 then exit;
|
|
|
|
dx := abs(FGraphStep);
|
|
hint := 0;
|
|
xa := FGraphToAxisXr(AXg);
|
|
if Assigned(FDomainExclusions) and FDomainExclusions.Intersect(xa, xa, hint) then
|
|
AXg := FAxisToGraphXr(xa);
|
|
|
|
if AXg < AXMax then
|
|
AOnMoveTo(AXg, xa);
|
|
|
|
while AXg < AXMax do begin
|
|
xg1 := AXg + dx;
|
|
xa1 := FGraphToAxisXr(xg1);
|
|
if Assigned(FDomainExclusions) and FDomainExclusions.Intersect(xa, xa1, hint) then
|
|
begin
|
|
AOnLineTo(FAxisToGraphXr(xa), xa);
|
|
xg1 := FAxisToGraphXr(xa1);
|
|
if xg1 < AXMax then
|
|
AOnMoveTo(xg1, xa1);
|
|
end
|
|
else
|
|
AOnLineTo(xg1, xa1);
|
|
AXg := xg1;
|
|
xa := xa1;
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TPointsDrawFuncHelper }
|
|
|
|
type
|
|
TBasicPointSeriesAccess = class(TBasicPointSeries);
|
|
|
|
constructor TPointsDrawFuncHelper.Create(
|
|
ASeries: TBasicPointSeries; AMinX, AMaxX: Double; AStartIndex: Integer;
|
|
ACalc: TTransformFunc; AStep: Integer);
|
|
begin
|
|
inherited Create(ASeries, ACalc, AStep);
|
|
FExtent.a.X := Min(AMinX, AMaxX);
|
|
FExtent.b.X := Max(AMaxX, AMinX);
|
|
FStartIndex := AStartIndex;
|
|
end;
|
|
|
|
procedure TPointsDrawFuncHelper.ForEachPoint(
|
|
AXg, AXMax: Double; AOnMoveTo, AOnLineTo: TOnPoint);
|
|
var
|
|
xa, xg1, xa1, dx, xfg: Double;
|
|
j, n: Integer;
|
|
ser: TBasicPointSeriesAccess;
|
|
begin
|
|
if FGraphStep = 0 then exit;
|
|
|
|
if not (FSeries is TBasicPointSeries) then
|
|
raise EChartError.CreateFmt(
|
|
'[%s.ForEachPoint] Series %s must be a TBasicPointSeries',
|
|
[ClassName, NameOrClassName(FSeries)]
|
|
);
|
|
|
|
ser := TBasicPointSeriesAccess(FSeries);
|
|
n := Length(ser.FGraphPoints);
|
|
dx := abs(FGraphStep);
|
|
|
|
xa := FAxisToGraphXr(AXg);
|
|
j := Max(0, FStartIndex - ser.FLoBound);
|
|
while (j < n) and (xa > ser.FGraphPoints[j].X) do inc(j);
|
|
if j < n then xfg := ser.FGraphPoints[j].X else exit;
|
|
AOnMoveTo(AXg, xa);
|
|
|
|
while AXg < AXMax do begin
|
|
xg1 := AXg + dx;
|
|
xa1 := FGraphToAxisXr(xg1);
|
|
if (j >= 0) and (xg1 > xfg) then begin
|
|
xg1 := xfg;
|
|
xa1 := ser.GetXValue(j + ser.FLoBound);
|
|
inc(j);
|
|
while (j < n) and (xfg > ser.FGraphPoints[j].X) do inc(j); // skip unordered points
|
|
if j < n then xfg := ser.FGraphPoints[j].X else xfg := Infinity;
|
|
end;
|
|
AOnLineTo(xg1, xa1);
|
|
AXg := xg1;
|
|
xa := xa1;
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TBasicFuncSeries }
|
|
|
|
procedure TBasicFuncSeries.AfterAdd;
|
|
begin
|
|
inherited AfterAdd;
|
|
FExtent.SetOwner(FChart);
|
|
end;
|
|
|
|
procedure TBasicFuncSeries.Assign(ASource: TPersistent);
|
|
begin
|
|
if ASource is TBasicFuncSeries then
|
|
with TBasicFuncSeries(ASource) do
|
|
Self.Extent := FExtent;
|
|
inherited Assign(ASource);
|
|
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;
|
|
|
|
end.
|
|
|