mirror of
				https://gitlab.com/freepascal.org/lazarus/lazarus.git
				synced 2025-11-04 10:39:53 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			244 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			ObjectPascal
		
	
	
	
	
	
			
		
		
	
	
			244 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			ObjectPascal
		
	
	
	
	
	
unit TAPolygonSeries;
 | 
						|
 | 
						|
{$mode objfpc}{$H+}
 | 
						|
 | 
						|
interface
 | 
						|
 | 
						|
uses
 | 
						|
  Classes, SysUtils, Graphics,
 | 
						|
  TAChartUtils, TADrawUtils, TACustomSeries, TALegend;
 | 
						|
 | 
						|
type
 | 
						|
  TPolygonSeries = class(TBasicPointSeries)
 | 
						|
  private
 | 
						|
    FBrush: TBrush;
 | 
						|
    FPen: TPen;
 | 
						|
    FPoints: array of TPoint;
 | 
						|
    FStart: array of Integer;
 | 
						|
    procedure SetBrush(AValue: TBrush);
 | 
						|
    procedure SetPen(AValue: TPen);
 | 
						|
  protected
 | 
						|
    procedure GetLegendItems(AItems: TChartLegendItems); override;
 | 
						|
 | 
						|
  public
 | 
						|
    constructor Create(AOwner: TComponent); override;
 | 
						|
    destructor Destroy; override;
 | 
						|
    procedure Draw(ADrawer: IChartDrawer); override;
 | 
						|
    function GetNearestPoint(const AParams: TNearestPointParams;
 | 
						|
      out AResults: TNearestPointResults): Boolean; override;
 | 
						|
 | 
						|
  published
 | 
						|
    property Brush: TBrush read FBrush write SetBrush;
 | 
						|
    property Pen: TPen read FPen write SetPen;
 | 
						|
    property ToolTargets default [nptPoint, nptCustom];
 | 
						|
 | 
						|
    // inherited
 | 
						|
    property AxisIndexX;
 | 
						|
    property AxisIndexY;
 | 
						|
    property Source;
 | 
						|
  end;
 | 
						|
 | 
						|
implementation
 | 
						|
 | 
						|
uses
 | 
						|
  TAChartStrConsts, TAGeometry, TAGraph;
 | 
						|
 | 
						|
constructor TPolygonSeries.Create(AOwner: TComponent);
 | 
						|
begin
 | 
						|
  inherited;
 | 
						|
  ToolTargets := [nptPoint, nptCustom];
 | 
						|
 | 
						|
  FPen := TPen.Create;
 | 
						|
  FPen.OnChange := @StyleChanged;
 | 
						|
  FBrush := TBrush.Create;
 | 
						|
  FBrush.OnChange := @StyleChanged;
 | 
						|
end;
 | 
						|
 | 
						|
destructor TPolygonSeries.Destroy;
 | 
						|
begin
 | 
						|
  FPoints := nil;
 | 
						|
  FStart := nil;
 | 
						|
  FPen.Free;
 | 
						|
  FBrush.Free;
 | 
						|
  inherited;
 | 
						|
end;
 | 
						|
 | 
						|
procedure TPolygonSeries.Draw(ADrawer: IChartDrawer);
 | 
						|
const
 | 
						|
  START_BLOCKSIZE = 100;
 | 
						|
var
 | 
						|
  ext: TDoubleRect;
 | 
						|
  ap, apStart: TDoublePoint;  // axis units
 | 
						|
  gp: TDoublePoint;           // graph units
 | 
						|
  pt: TPoint;                 // image units
 | 
						|
  nSource, nPoints: Integer;
 | 
						|
  nStart: Integer;
 | 
						|
  newPolygon: Boolean;
 | 
						|
  i, j: Integer;
 | 
						|
 | 
						|
  procedure SavePolygonStartIndex(AIndex: Integer);
 | 
						|
  begin
 | 
						|
    if nStart mod START_BLOCKSIZE = 0 then
 | 
						|
      SetLength(FStart, Length(FStart) + START_BLOCKSIZE);
 | 
						|
    FStart[nStart] := AIndex;
 | 
						|
    inc(nStart);
 | 
						|
  end;
 | 
						|
 | 
						|
begin
 | 
						|
  if IsEmpty or (not Active) then
 | 
						|
    exit;
 | 
						|
 | 
						|
  // Do not draw anything if the series extent does not intersect CurrentExtent.
 | 
						|
  with Extent do begin
 | 
						|
    ext.a := AxisToGraph(a);
 | 
						|
    ext.b := AxisToGraph(b);
 | 
						|
  end;
 | 
						|
  NormalizeRect(ext);
 | 
						|
  if not RectIntersectsRect(ext, ParentChart.CurrentExtent) then
 | 
						|
    exit;
 | 
						|
 | 
						|
  nSource := Source.Count;
 | 
						|
  SetLength(FPoints, nSource);
 | 
						|
  SetLength(FStart, START_BLOCKSIZE);
 | 
						|
  nStart := 0;
 | 
						|
  nPoints := 0;
 | 
						|
  newPolygon := true;
 | 
						|
 | 
						|
  for i := 0 to nSource-1 do begin
 | 
						|
    // a new polygon begins with the current index --> store index in array FStart[].
 | 
						|
    if newPolygon then begin
 | 
						|
      SavePolygonStartIndex(nPoints);
 | 
						|
 | 
						|
      // Since the polygon ends if the start point is met again we must store the start point.
 | 
						|
      apStart := Source.Item[i]^.Point;
 | 
						|
    end;
 | 
						|
 | 
						|
    // Get polygon point in image coordinates
 | 
						|
    ap := Source.Item[i]^.Point;
 | 
						|
    if IsRotated then
 | 
						|
      gp := DoublePoint(AxisToGraphX(ap.Y), AxisToGraphY(ap.X))
 | 
						|
    else
 | 
						|
      gp := AxisToGraph(ap);
 | 
						|
    pt := ParentChart.GraphToImage(gp);
 | 
						|
 | 
						|
    // Store image point in FPoints array
 | 
						|
    FPoints[nPoints] := pt;
 | 
						|
    inc(nPoints);
 | 
						|
 | 
						|
    // Current point coincides with the polygon start point
 | 
						|
    // --> polygon is closed, set flag "newPolygon" to be processed in next iteration
 | 
						|
    if (ap = apStart) then
 | 
						|
      newPolygon := not newPolygon;
 | 
						|
  end;
 | 
						|
 | 
						|
  if nPoints = 0 then
 | 
						|
    exit;
 | 
						|
 | 
						|
  // Single polygon --> we can use the standard Polygon() routine
 | 
						|
  if nStart = 1 then
 | 
						|
  begin
 | 
						|
    SetLength(FPoints, nPoints);
 | 
						|
    ADrawer.SetBrush(FBrush);
 | 
						|
    ADrawer.SetPen(FPen);
 | 
						|
    ADrawer.Polygon(FPoints, 0, Length(FPoints));
 | 
						|
    exit;
 | 
						|
  end;
 | 
						|
 | 
						|
  // If we get here we have multiple polygons. We must add points for a
 | 
						|
  // "clean retreat" to the starting point, and then we fill the borderless
 | 
						|
  // polygon and draw the border separately.
 | 
						|
 | 
						|
  // Use length of FStart array as last array element for easier calculation
 | 
						|
  SavePolygonStartIndex(nPoints);
 | 
						|
 | 
						|
  // Trim length of FStart array to occupied length
 | 
						|
  SetLength(FStart, nStart);
 | 
						|
 | 
						|
  SetLength(FPoints, nPoints + Length(FStart) - 1);
 | 
						|
  j := nPoints;
 | 
						|
  for i := High(FStart) downto 1 do
 | 
						|
  begin
 | 
						|
    FPoints[j] := FPoints[FStart[i]];
 | 
						|
    inc(j);
 | 
						|
  end;
 | 
						|
 | 
						|
  // Draw polygon fill
 | 
						|
  if FBrush.Style <> bsClear then
 | 
						|
  begin
 | 
						|
    ADrawer.SetBrush(FBrush);
 | 
						|
    ADrawer.SetPenParams(psClear, clTAColor);
 | 
						|
    ADrawer.Polygon(FPoints, 0, Length(FPoints));
 | 
						|
  end;
 | 
						|
 | 
						|
  // Draw border
 | 
						|
  if FPen.Style <> psClear then
 | 
						|
  begin
 | 
						|
    ADrawer.Pen := FPen;
 | 
						|
    for i := 0 to High(FStart) - 1 do
 | 
						|
      ADrawer.PolyLine(FPoints, FStart[i], FStart[i+1] - FStart[i]);
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
procedure TPolygonSeries.GetLegendItems(AItems: TChartLegendItems);
 | 
						|
begin
 | 
						|
  GetLegendItemsRect(AItems, FBrush, FPen);
 | 
						|
end;
 | 
						|
 | 
						|
{ Is overridden in order to detect tool events inside the polygon (nptCustom).
 | 
						|
  Otherwise only events on the perimenter would be detected. }
 | 
						|
function TPolygonSeries.GetNearestPoint(const AParams: TNearestPointParams;
 | 
						|
  out AResults: TNearestPointResults): Boolean;
 | 
						|
var
 | 
						|
  ext: TDoubleRect;
 | 
						|
begin
 | 
						|
  Result := false;
 | 
						|
  AResults.FDist := sqr(AParams.FRadius) + 1;
 | 
						|
  AResults.FIndex := -1;
 | 
						|
  AResults.FXIndex := 0;
 | 
						|
  AResults.FYIndex := 0;
 | 
						|
 | 
						|
  Result := inherited;
 | 
						|
 | 
						|
  if Result or not ((nptCustom in AParams.FTargets) and (nptCustom in ToolTargets)) then
 | 
						|
    exit;
 | 
						|
 | 
						|
  with Extent do begin
 | 
						|
    ext.a := AxisToGraph(a);
 | 
						|
    ext.b := AxisToGraph(b);
 | 
						|
  end;
 | 
						|
  NormalizeRect(ext);
 | 
						|
  // Do not do anything if the series extent does not intersect CurrentExtent.
 | 
						|
  if not RectIntersectsRect(ext, ParentChart.CurrentExtent) then
 | 
						|
    exit;
 | 
						|
 | 
						|
  Result := IsPointInPolygon(AParams.FPoint, FPoints);
 | 
						|
  if Result then
 | 
						|
  begin
 | 
						|
    AResults.FDist := 0;
 | 
						|
    AResults.FIndex := 0;
 | 
						|
    AResults.FYIndex := 0;
 | 
						|
    AResults.FValue := ParentChart.ImageToGraph(AParams.FPoint);
 | 
						|
    AResults.FImg := AParams.FPoint;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
 | 
						|
procedure TPolygonSeries.SetBrush(AValue: TBrush);
 | 
						|
begin
 | 
						|
  FBrush.Assign(AValue);
 | 
						|
  UpdateParentChart;
 | 
						|
end;
 | 
						|
 | 
						|
procedure TPolygonSeries.SetPen(AValue: TPen);
 | 
						|
begin
 | 
						|
  FPen.Assign(AValue);
 | 
						|
  UpdateParentChart;
 | 
						|
end;
 | 
						|
 | 
						|
 | 
						|
initialization
 | 
						|
  RegisterSeriesClass(TPolygonSeries, @rsPolygonSeries);
 | 
						|
 | 
						|
end.
 | 
						|
 |