mirror of
				https://gitlab.com/freepascal.org/lazarus/lazarus.git
				synced 2025-10-31 20:31:33 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			315 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			ObjectPascal
		
	
	
	
	
	
			
		
		
	
	
			315 lines
		
	
	
		
			8.0 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;
 | |
| 
 | |
|   TDrawFuncHelper = class
 | |
|   strict private
 | |
|   type
 | |
|     TOnPoint = procedure (AXg, AXa: Double) of object;
 | |
|   var
 | |
|     FAxisToGraphXr, FAxisToGraphYr, FGraphToAxisXr: TTransformFunc;
 | |
|     FCalc: TTransformFunc;
 | |
|     FChart: TChart;
 | |
|     FDomainExclusions: TIntervalList;
 | |
|     FDrawer: IChartDrawer;
 | |
|     FExtent: TDoubleRect;
 | |
|     FExtentYMax: PDouble;
 | |
|     FExtentYMin: PDouble;
 | |
|     FGraphStep: Double;
 | |
|     FImageToGraph: TImageToGraphFunc;
 | |
|     FNearestPointParams: ^TNearestPointParams;
 | |
|     FNearestPointResults: ^TNearestPointResults;
 | |
|     FMakeDP: TMakeDoublePoint;
 | |
|     FPrev: TDoublePoint;
 | |
|     FPrevInExtent: Boolean;
 | |
|     FSeries: TCustomChartSeries;
 | |
| 
 | |
|     procedure CalcAt(AXg, AXa: Double; out APt: TDoublePoint; out AIn: Boolean);
 | |
|     procedure CheckForNearestPoint(AXg, AXa: Double);
 | |
|     procedure ForEachPoint(AXg, AXMax: Double; AOnMoveTo, AOnLineTo: TOnPoint);
 | |
|     procedure LineTo(AXg, AXa: Double);
 | |
|     procedure MoveTo(AXg, AXa: Double);
 | |
|     procedure UpdateExtent(AXg, AXa: Double);
 | |
|     function XRange: TDoubleInterval;
 | |
|   public
 | |
|     constructor Create(
 | |
|       ASeries: TCustomChartSeries; ADomainExclusions:
 | |
|       TIntervalList; 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;
 | |
| 
 | |
| implementation
 | |
| 
 | |
| uses
 | |
|   Math, SysUtils,
 | |
|   TAGeometry, TAMath;
 | |
| 
 | |
| function DoublePointRotated(AX, AY: Double): TDoublePoint;
 | |
| begin
 | |
|   Result.X := AY;
 | |
|   Result.Y := AX;
 | |
| end;
 | |
| 
 | |
| { TDrawFuncHelper }
 | |
| 
 | |
| procedure TDrawFuncHelper.CalcAt(
 | |
|   AXg, AXa: Double; out APt: TDoublePoint; out AIn: Boolean);
 | |
| begin
 | |
|   APt := FMakeDP(AXg, FAxisToGraphYr(FCalc(AXa)));
 | |
|   AIn := (FExtent.a <= APt) and (APt <= FExtent.b);
 | |
| end;
 | |
| 
 | |
| procedure TDrawFuncHelper.CalcAxisExtentY(
 | |
|   AMinX, AMaxX: Double; var AMinY, AMaxY: Double);
 | |
| begin
 | |
|   FExtentYMin := @AMinY;
 | |
|   FExtentYMax := @AMaxY;
 | |
|   with XRange do
 | |
|     ForEachPoint(AMinX, AMaxX, @UpdateExtent, @UpdateExtent);
 | |
| end;
 | |
| 
 | |
| procedure TDrawFuncHelper.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 TDrawFuncHelper.Create(
 | |
|   ASeries: TCustomChartSeries; ADomainExclusions: TIntervalList;
 | |
|   ACalc: TTransformFunc; AStep: Integer);
 | |
| begin
 | |
|   FChart := ASeries.ParentChart;
 | |
|   FExtent := FChart.CurrentExtent;
 | |
|   FSeries := ASeries;
 | |
|   FDomainExclusions := ADomainExclusions;
 | |
|   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 TDrawFuncHelper.DrawFunction(ADrawer: IChartDrawer);
 | |
| begin
 | |
|   FDrawer := ADrawer;
 | |
|   with XRange do
 | |
|     ForEachPoint(FStart, FEnd, @MoveTo, @LineTo);
 | |
| end;
 | |
| 
 | |
| procedure TDrawFuncHelper.ForEachPoint(
 | |
|   AXg, AXMax: Double; AOnMoveTo, AOnLineTo: TOnPoint);
 | |
| var
 | |
|   hint: Integer;
 | |
|   xa, xg1, xa1: Double;
 | |
| begin
 | |
|   if FGraphStep = 0 then exit;
 | |
| 
 | |
|   hint := 0;
 | |
|   xa := FGraphToAxisXr(AXg);
 | |
|   if FDomainExclusions.Intersect(xa, xa, hint) then
 | |
|     AXg := FAxisToGraphXr(xa);
 | |
| 
 | |
|   if AXg < AXMax then
 | |
|     AOnMoveTo(AXg, xa);
 | |
| 
 | |
|   while AXg < AXMax do begin
 | |
|     xg1 := AXg + FGraphStep;
 | |
|     xa1 := FGraphToAxisXr(xg1);
 | |
|     if 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;
 | |
| 
 | |
| function TDrawFuncHelper.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 TDrawFuncHelper.LineTo(AXg, AXa: Double);
 | |
| var
 | |
|   p, t: TDoublePoint;
 | |
|   inExtent: Boolean;
 | |
| begin
 | |
|   CalcAt(AXg, AXa, p, inExtent);
 | |
|   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 TDrawFuncHelper.MoveTo(AXg, AXa: Double);
 | |
| begin
 | |
|   CalcAt(AXg, AXa, FPrev, FPrevInExtent);
 | |
|   if FPrevInExtent then
 | |
|     FDrawer.MoveTo(FChart.GraphToImage(FPrev));
 | |
| end;
 | |
| 
 | |
| procedure TDrawFuncHelper.UpdateExtent(AXg, AXa: Double);
 | |
| begin
 | |
|   Unused(AXg);
 | |
|   UpdateMinMax(FCalc(AXa), FExtentYMin^, FExtentYMax^);
 | |
| end;
 | |
| 
 | |
| function TDrawFuncHelper.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;
 | |
| 
 | |
| { 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.
 | |
| 
 | 
