{***********************************************}
{   TArrowSeries (derived from TPointSeries)    }
{   Copyright (c) 1995-2020 by Steema Software  }
{***********************************************}
unit ArrowCha;
{$I TeeDefs.inc}

interface

{
  TArrowSeries derives from standard TPointSeries.
  Each point in the series is drawn like an Arrow.

  Each arrow has initial and end X,Y values that are used to draw the
  Arrow with its corresponding screen size.

  Arrow head size is controlled with ArrowWidth and ArrowHeight properties.
  Arrow body size is determined by ArrowPercent property (from 0 to 100% of
  ArrowWidth value).

  Inherits all functionality from TPointSeries and its ancestor TCustomSeries.
}
Uses {$IFNDEF LINUX}
     {$IFNDEF FMX}
     Windows, 
     {$ENDIF}
     {$ENDIF}

     {$IFDEF FMX}
     System.UITypes, System.Types, FMX.Types,
     {$ELSE}
     Graphics,
     {$ENDIF}

     {$IFNDEF FMX}
     {$IFDEF D8}
     Types,
     {$ENDIF}
     {$ENDIF}

     Classes,
     {$IFDEF FMX}
     FMXTee.Canvas, FMXTee.Engine, FMXTee.Series
     {$ELSE}
     TeEngine, Series
     {$ENDIF}
     ;

type
  {$IFDEF NODEF}{$REGION 'Documentation'}{$ENDIF}
  /// <summary>
  ///   <para>
  ///     Displays series values as Arrows.
  ///   </para>
  ///   <para>
  ///     Each Arrow has origin X0,Y0 and destination X1,Y1 coordinates.
  ///   </para>
  /// </summary>
  {$IFDEF NODEF}{$ENDREGION}{$ENDIF}
  TArrowSeries=class(TPointSeries)
  private
    FEndXValues : TChartValueList;
    FEndYValues : TChartValueList; { <-- Arrows end X,Y values }
    FFill2D     : Boolean;
    FPercent    : Integer;

    Function GetArrowHeight:Integer;
    Function GetArrowWidth:Integer;
    procedure GetArrowPoints(ValueIndex:Integer; out P0,P1:TPoint);
    Function GetStartXValues:TChartValueList;
    Function GetStartYValues:TChartValueList;
    procedure SetArrowHeight(Value:Integer);
    procedure SetArrowWidth(Value:Integer);
    Procedure SetEndXValues(Value:TChartValueList);
    Procedure SetEndYValues(Value:TChartValueList);
    procedure SetFill2D(Value:Boolean);
    procedure SetPercent(const Value:Integer);
    Procedure SetStartXValues(Value:TChartValueList);
    Procedure SetStartYValues(Value:TChartValueList);
  protected
    Procedure AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False); override; { <-- to add random arrow values }
    procedure AssignFormatting(Source:TChartSeries); override;
    class Function CanDoExtra:Boolean; override; { <-- for sub-gallery }
    class Procedure CreateSubGallery(AddSubChart:TChartSubGalleryProc); override;
    procedure DrawValue(ValueIndex:Integer); override; { <-- main draw method }
    class Function GetEditorClass:String; override;
    Procedure PrepareForGallery; override;
  public
    Constructor Create(AOwner: TComponent); override;

    Function AddArrow(Const X0,Y0,X1,Y1:Double; Const ALabel:String='';
                     AColor:TColor=clTeeColor):Integer;

    Function Clicked(x,y:Integer):Integer; override;
    Function IsValidSourceOf(Value:TChartSeries):Boolean; override;
    Function MaxXValue:TChartValue; override;
    Function MinXValue:TChartValue; override;
    Function MaxYValue:TChartValue; override;
    Function MinYValue:TChartValue; override;
  published
    property Active;
    property ColorEachPoint;
    property ColorSource;
    property Cursor;
    property HorizAxis;
    property Marks;
    property ParentChart;
    property DataSource;
    property PercentFormat;
    property SeriesColor;
    property ShowInLegend;
    property Title;
    property Transparency;
    property ValueFormat;
    property VertAxis;
    property XLabelsSource;

    { events }
    property AfterDrawValues;
    property BeforeDrawValues;
    property OnAfterAdd;
    property OnBeforeAdd;
    property OnClearValues;
    property OnClick;
    property OnDblClick;
    property OnGetMarkText;
    property OnMouseEnter;
    property OnMouseLeave;

    property ArrowHeight:Integer read GetArrowHeight write SetArrowHeight stored False;
    property ArrowPercent:Integer read FPercent write SetPercent default 50;  // % of ArrowWidth
    property ArrowWidth:Integer read GetArrowWidth write SetArrowWidth stored False;
    property EndXValues:TChartValueList read FEndXValues write SetEndXValues;
    property EndYValues:TChartValueList read FEndYValues write SetEndYValues;
    property Fill2D:Boolean read FFill2D write SetFill2D default False;
    property StartXValues:TChartValueList read GetStartXValues write SetStartXValues;
    property StartYValues:TChartValueList read GetStartYValues write SetStartYValues;
  end;

implementation

uses
  Math, 

  {$IFDEF FMX}
  FMXTee.Procs, FMXTee.Constants, FMXTee.Chart
  {$ELSE}
  TeeProcs, TeeConst, TeCanvas, Chart
  {$ENDIF}
  ;

type
  TValueListAccess=class(TChartValueList);

{ TArrowSeries }
Constructor TArrowSeries.Create(AOwner: TComponent);
Begin
  inherited;
  CalcVisiblePoints:=False;

  TValueListAccess(XValues).InitDateTime(True);

  FEndXValues :=TChartValueList.Create(Self,TeeMsg_ValuesArrowEndX);
  TValueListAccess(FEndXValues).InitDateTime(True);

  FEndYValues :=TChartValueList.Create(Self,TeeMsg_ValuesArrowEndY);

  Pointer.InflateMargins:=False;
  Marks.Frame.Hide;
  Marks.Transparent:=True;

  FPercent:=50;
end;

Procedure TArrowSeries.SetEndXValues(Value:TChartValueList);
Begin
  SetChartValueList(FEndXValues,Value); { standard method }
end;

Procedure TArrowSeries.SetEndYValues(Value:TChartValueList);
Begin
  SetChartValueList(FEndYValues,Value); { standard method }
end;

{ Helper method, special to Arrow series }
Function TArrowSeries.AddArrow( Const X0,Y0,X1,Y1:Double;
                                Const ALabel:String; AColor:TColor):Integer;
Begin
  FEndXValues.TempValue:=X1;
  FEndYValues.TempValue:=Y1;
  result:=AddXY(X0,Y0,ALabel,AColor);
end;

procedure TArrowSeries.AssignFormatting(Source:TChartSeries);
begin
  if Source is TArrowSeries then
  with TArrowSeries(Source) do
  begin
    Self.FPercent:=FPercent;
    Self.FFill2D:=FFill2D;
  end;

  inherited;
end;

Procedure TArrowSeries.AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False);
Var tmpDifX : Integer;
    tmpDifY : Integer;
    t       : Integer;
    tmpX0   : Double;
    tmpY0   : Double;
Begin
  With RandomBounds(NumValues) do
  begin
    tmpDifY:=Round(DifY);
    tmpDifX:=Round(StepX*NumValues);

    for t:=1 to NumValues do { some sample values to see something in design mode }
    Begin
      tmpX0:=tmpX+RandomValue(tmpDifX);
      tmpY0:=MinY+RandomValue(tmpDifY);
      AddArrow( tmpX0,tmpY0,
                tmpX0+RandomValue(tmpDifX),  { X1 }
                tmpY0+RandomValue(tmpDifY)   { Y1 }
                );
    end;
  end;
end;

Function TArrowSeries.GetArrowWidth:Integer;
Begin
  result:=Pointer.HorizSize;
end;

Function TArrowSeries.GetArrowHeight:Integer;
Begin
  result:=Pointer.VertSize;
end;

procedure TArrowSeries.SetArrowWidth(Value:Integer);
Begin
  Pointer.HorizSize:=Value;
End;

procedure TArrowSeries.SetArrowHeight(Value:Integer);
Begin
  Pointer.VertSize:=Value;
End;

procedure TArrowSeries.GetArrowPoints(ValueIndex:Integer; out P0,P1:TPoint);
begin
  P0.x:=CalcXPos(ValueIndex);
  P0.y:=CalcYPos(ValueIndex);
  P1.x:=CalcXPosValue(EndXValues.Value[ValueIndex]);
  P1.y:=CalcYPosValue(EndYValues.Value[ValueIndex]);
end;

{ This overrided method is the main paint for Arrow points. }
procedure TArrowSeries.DrawValue(ValueIndex:Integer);
var p0       : TPoint;
    p1       : TPoint;
    tmpColor : TColor;
    tmpP     : TPointArray;
    tmpR     : TRect;
    tmpBlend : TTeeBlend;
    tmpFilled: Boolean;
Begin
  tmpColor:=ValueColor[ValueIndex];

  if Fill2D or ParentChart.View3D then
     Pointer.PrepareCanvas(ParentChart.Canvas,tmpColor)
  else
     ParentChart.Canvas.AssignVisiblePenColor(Pointer.Pen,tmpColor);

  PrepareHoverPen(ValueIndex);

  GetArrowPoints(ValueIndex,P0,P1);

  tmpP:=ParentChart.Canvas.GetArrowPoints(FPercent,P0,P1,Pointer.HorizSize,Pointer.VertSize,MiddleZ);

  if Transparency>0 then
  begin
    if ParentChart.Canvas.SupportsAlpha then
       tmpR:=TeeZeroRect
    else
    begin
      tmpR:=PolygonBounds(tmpP);
      tmpR.Right:=tmpR.Right+1;
      tmpR.Bottom:=tmpR.Bottom+1;
    end;

    tmpBlend:=ParentChart.Canvas.BeginBlending(tmpR,Transparency);
  end
  else
    tmpBlend:=nil;

  tmpFilled:=Fill2D or ParentChart.View3D;

  ParentChart.Canvas.Arrow(tmpFilled,P0,P1,Pointer.HorizSize,Pointer.VertSize,StartZ,EndZ,FPercent);

  if Transparency>0 then
     ParentChart.Canvas.EndBlending(tmpBlend);

  tmpP:=nil;
end;

Function TArrowSeries.MaxXValue:TChartValue;
Begin
  result:=Math.Max(inherited MaxXValue,FEndXValues.MaxValue);
end;

Function TArrowSeries.MinXValue:TChartValue;
Begin
  result:=Math.Min(inherited MinXValue,FEndXValues.MinValue);
end;

Function TArrowSeries.MaxYValue:TChartValue;
Begin
  result:=Math.Max(inherited MaxYValue,FEndYValues.MaxValue);
end;

Function TArrowSeries.MinYValue:TChartValue;
Begin
  result:=Math.Min(inherited MinYValue,FEndYValues.MinValue);
end;

class Function TArrowSeries.GetEditorClass:String;
Begin
  result:='TArrowSeriesEditor'; { <-- dont translate ! }
end;

Function TArrowSeries.GetStartXValues:TChartValueList;
Begin
  result:=XValues;
End;

procedure TArrowSeries.SetPercent(const Value:Integer);
begin
  SetIntegerProperty(FPercent,Value);
end;

Procedure TArrowSeries.SetStartXValues(Value:TChartValueList);
Begin
  SetXValues(Value);
End;

Function TArrowSeries.GetStartYValues:TChartValueList;
Begin
  result:=YValues;
End;

Procedure TArrowSeries.SetStartYValues(Value:TChartValueList);
Begin
  SetYValues(Value);
End;

Procedure TArrowSeries.PrepareForGallery;
Begin
  inherited;

  FillSampleValues(3);
  ArrowWidth :=12;
  ArrowHeight:=12;
end;

Function TArrowSeries.IsValidSourceOf(Value:TChartSeries):Boolean;
begin
  result:=Value is TArrowSeries;
end;

class procedure TArrowSeries.CreateSubGallery(AddSubChart: TChartSubGalleryProc);
begin
  inherited;
end;

// For sub-gallery only. Arrow series do not allow sub-styles.
class function TArrowSeries.CanDoExtra: Boolean;
begin
  result:=False;
end;

function TArrowSeries.Clicked(x, y: Integer): Integer;
var t  : Integer;
    P  : TPoint;
    P0 : TPoint;
    P1 : TPoint;
    tmpP : TPointArray;
    tmpClick : Boolean;
begin
  result:=TeeNoPointClicked;

  tmpP:=nil;

  P:=TeePoint(x,y);

  for t:=0 to Count-1 do
  begin
    GetArrowPoints(t,P0,P1);

    tmpP:=ParentChart.Canvas.GetArrowPoints(FPercent,P0,P1,Pointer.HorizSize,Pointer.VertSize,MiddleZ);

    if Assigned(tmpP) then
    begin
      try
        if ParentChart.View3D then
        begin
          tmpClick:=PointInPolygon(P,tmpP);

          if tmpClick then
             IClickedPart:=scpPointer;
        end
        else
          tmpClick:=PointInLine(P,P0,P1,3) or
                    PointInLine(P,P1,tmpP[5],3) or
                    PointInLine(P,P1,tmpP[3],3);

          if tmpClick then
             IClickedPart:=scpSegment;
      finally
        tmpP:=nil;
      end;

      if tmpClick then
      begin
        result:=t;
        break;
      end;
    end;
  end;
end;

procedure TArrowSeries.SetFill2D(Value: Boolean);
begin
  SetBooleanProperty(FFill2D,Value);
end;

initialization
  RegisterTeeSeries(TArrowSeries,
                    @TeeMsg_GalleryArrow,
                    @{$IFDEF TEELITE}TeeMsg_GalleryStandard{$ELSE}TeeMsg_GalleryExtended{$ENDIF},2);
end.
