{********************************************}
{ TeeChart Pro                               }
{ TPoint3DSeries                             }
{                                            }
{ Copyright (c) 1995-2020 by Steema Software }
{        All Rights Reserved                 }
{********************************************}
unit TeePoin3;
{$I TeeDefs.inc}

interface

Uses {$IFNDEF LINUX}
     {$IFNDEF FMX}
     Windows, Messages,
     {$ENDIF}
     {$ENDIF}
     SysUtils, Classes,

     {$IFDEF D16}
     System.UITypes,
     {$ENDIF}

     {$IFDEF FMX}
     FMX.Types,
     {$IFDEF D17}
     System.UIConsts,
     {$ENDIF}

     {$ELSE}
     Graphics, Controls,
     {$ENDIF}

     {$IFDEF D9}
     Types,
     {$ENDIF}

     {$IFDEF FMX}
     FMXTee.Engine, FMXTee.Series.Surface, FMXTee.Series, FMXTee.Canvas,
     FMXTee.Procs
     {$ELSE}
     TeeProcs, TeEngine, TeeSurfa, Series, TeCanvas
     {$ENDIF};

type
  TPoint3DSeries=class;

  TSeriesClickPointer3DEvent=Procedure( Sender:TPoint3DSeries;
                                        ValueIndex:Integer;
                                        X, Y: Integer) of object;

  TPoint3DSeries = class(TCustom3DPaletteSeries)
  private
    FBaseLine  : TChartHiddenPen;
    FDepthSize : Double;
    FPointer   : TSeriesPointer;
    FLineTransparency : TTeeTransparency;

    { events }
    FOnClickPointer    : TSeriesClickPointer3DEvent;
    FOnGetPointerStyle : TOnGetPointerStyle;

    { internal }
    IOldX     : Integer;
    IOldY     : Integer;
    IOldZ     : Integer;

    Function GetLinePen:TChartPen;
    procedure SetLineTransparency(Value: TTeeTransparency);
    procedure SetBaseLine(const Value: TChartHiddenPen);
    Procedure SetDepthSize(Const Value:Double);
    Procedure SetPointer(const Value:TSeriesPointer);
  protected
    Procedure AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False); override;
    Procedure AssignFormatting(Source:TChartSeries); override;
    Procedure CalcHorizMargins(Var LeftMargin,RightMargin:Integer); override;
    Procedure CalcVerticalMargins(Var TopMargin,BottomMargin:Integer); override;
    Procedure CalcZPositions(ValueIndex:Integer); virtual;
    Function ClickedPointer( ValueIndex,tmpX,tmpY:Integer;
                             x,y:Integer):Boolean; override;
    class Procedure CreateSubGallery(AddSubChart:TChartSubGalleryProc); override;
    Procedure DrawAllValues; override;
    Procedure DrawLegendShape(ValueIndex:Integer; Const Rect:TRect); override;
    Procedure DrawMark( ValueIndex:Integer; Const St:String;
                        APosition:TSeriesMarkPosition); override;
    Procedure DrawValue(ValueIndex:Integer); override;
    Function GetDepthSize(ValueIndex:Integer):Integer; virtual;
    class Function GetEditorClass:String; override;
    procedure MouseDown(ValueIndex:Integer; AtPointer:Boolean; Button:TMouseButton;
                        Shift: TShiftState; X, Y: Integer); override;
    Procedure PrepareForGallery; override;
    function RaiseClicked:Boolean; override;
    Procedure SetParentChart(Const Value:TCustomAxisPanel); override;
    class Procedure SetSubGallery(ASeries:TChartSeries; Index:Integer); override;
    class Function SubGalleryStyles:Boolean; virtual;
  public
    Constructor Create(AOwner: TComponent); override;
    Destructor Destroy; override;

    Function Clicked(x,y:Integer):Integer; override;
    Function MaxZValue: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 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 BaseLine:TChartHiddenPen read FBaseLine write SetBaseLine;
    property DepthSize:Double read FDepthSize write SetDepthSize;
    property LinePen:TChartPen read GetLinePen write SetPen;
    property LineTransparency:TTeeTransparency read FLineTransparency write SetLineTransparency default 0;
    property Pointer:TSeriesPointer read FPointer write SetPointer;
    property TimesZOrder;
    property XValues;
    property YValues;
    property ZValues;

    { events }
    property OnClickPointer:TSeriesClickPointer3DEvent read FOnClickPointer
                                                       write FOnClickPointer;
    property OnGetPointerStyle:TOnGetPointerStyle read FOnGetPointerStyle
                                                  write FOnGetPointerStyle;
  end;

  TBubble3DSeries = class(TPoint3DSeries)
  private
    FRadiusValues : TChartValueList; { <-- Bubble's radius storage }
    Function ApplyRadius( Const Value:Double;
                          AList:TChartValueList;
                          Increment:Boolean):Double;
    Procedure SetRadiusValues(Value:TChartValueList);
  protected
    Procedure AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False); override; { <-- to add random radius values }
    Procedure CalcZPositions(ValueIndex:Integer); override;
    procedure DrawValue(ValueIndex:Integer); override; { <-- main draw method }
    Function GetDepthSize(ValueIndex:Integer):Integer; override;
    Procedure PrepareForGallery; override;
    class Function SubGalleryStyles:Boolean; override;
  public
    Constructor Create(AOwner: TComponent); override;

    Function AddBubble(Const AX,AY,AZ,ARadius:Double; Const AXLabel:String='';
                       AColor:TColor=clTeeColor):Integer;
    Function IsValidSourceOf(Value:TChartSeries):Boolean; override;

    Function MaxYValue:TChartValue; override;  // adds radius
    Function MinYValue:TChartValue; override;  // substracts radius
    Function MaxZValue:TChartValue; override;
    Function MinZValue:TChartValue; override;
  published
    property ColorEachPoint default True;
    property RadiusValues:TChartValueList read FRadiusValues write SetRadiusValues;
  end;

implementation

Uses Math,
     {$IFDEF FMX}
     FMXTee.Chart, FMXTee.Constants, FMXTee.ProConstants
     {$ELSE}
     Chart, TeeConst, TeeProCo
     {$ENDIF};

{ TPoint3DSeries }
Constructor TPoint3DSeries.Create(AOwner: TComponent);
begin
  inherited;
  FPointer:=TSeriesPointer.Create(Self);
  FBaseLine:=TChartHiddenPen.Create(CanvasChanged);
end;

Destructor TPoint3DSeries.Destroy;
begin
  FBaseLine.Free;
  FPointer.Free;
  inherited;
end;

class Function TPoint3DSeries.SubGalleryStyles:Boolean;
begin
  result:=True;
end;

Procedure TPoint3DSeries.SetPointer(const Value:TSeriesPointer);
Begin
  FPointer.Assign(Value);
end;

Procedure TPoint3DSeries.CalcZPositions(ValueIndex:Integer);
var tmp : Integer;
begin
  // do not call inherited here
  MiddleZ:=CalcZPos(ValueIndex);
  tmp:=Math.Max(1,GetDepthSize(ValueIndex));
  StartZ:=MiddleZ-tmp;
  EndZ:=MiddleZ+tmp;
end;

Function TPoint3DSeries.GetLinePen:TChartPen;
begin
  result:=Pen;
end;

type
  TPointerAccess=class(TSeriesPointer);

Procedure TPoint3DSeries.CalcHorizMargins(Var LeftMargin,RightMargin:Integer);
begin
  inherited;
  TPointerAccess(FPointer).CalcHorizMargins(LeftMargin,RightMargin);
end;

Procedure TPoint3DSeries.CalcVerticalMargins(Var TopMargin,BottomMargin:Integer);
begin
  inherited;
  TPointerAccess(FPointer).CalcVerticalMargins(TopMargin,BottomMargin);
end;

Procedure TPoint3DSeries.DrawAllValues;
var tmpBlend : TTeeBlend;
begin
  if Transparency>0 then
     tmpBlend:=ParentChart.Canvas.BeginBlending(TeeZeroRect,Transparency)
  else
     tmpBlend:=nil;

  inherited;

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

Procedure TPoint3DSeries.DrawValue(ValueIndex:Integer);

  procedure DoDrawLine(const X,Y:Integer);

    function CreateBlend:TTeeBlend;
    var tmpPenWidth : Integer;
        Old2D       : TPoint;
        Act2D       : TPoint;
    begin
      if ParentChart.Canvas.SupportsAlpha then
         result:=ParentChart.Canvas.BeginBlending(TeeZeroRect,LineTransparency)
      else
      begin
        tmpPenWidth:={$IFDEF FMX}Round{$ENDIF}(LinePen.Width);

        Old2D:=ParentChart.Canvas.Calculate3DPosition(IOldX, IOldY, IOldZ);
        Act2D:=ParentChart.Canvas.Calculate3DPosition(X, Y, MiddleZ);

        result:=ParentChart.Canvas.BeginBlending(
                             TeeRect(Min(Old2D.X,Act2D.X)-tmpPenWidth,
                                     Min(Old2D.Y,Act2D.Y)-tmpPenWidth,
                                     Max(Old2D.X,Act2D.X)+tmpPenWidth,
                                     Max(Old2D.Y,Act2D.Y)+tmpPenWidth),
                             LineTransparency);
      end;
    end;

  var tmpBlend : TTeeBlend;
  begin
    ParentChart.Canvas.AssignVisiblePen(LinePen);
    ParentChart.Canvas.BackMode:=cbmTransparent;

    if LineTransparency>0 then
       tmpBlend:=CreateBlend
    else
       tmpBlend:=nil;

    ParentChart.Canvas.MoveTo3D(IOldX,IOldY,IOldZ);
    ParentChart.Canvas.LineTo3D(X,Y,MiddleZ);

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

  procedure DoDrawPointer(const X,Y:Integer; const AColor:TColor);
  var tmpStyle : TSeriesPointerStyle;
  begin { emulate TCustomSeries.DrawPointer method }
    Pointer.PrepareCanvas(ParentChart.Canvas,AColor);

    if Assigned(FOnGetPointerStyle) then
       tmpStyle:=FOnGetPointerStyle(Self,ValueIndex)
    else
       tmpStyle:=Pointer.Style;

    Pointer.DrawPointer(ParentChart.Canvas,ParentChart.View3D,
                        X,Y,
                        Pointer.HorizSize,
                        Pointer.VertSize,
                        AColor,tmpStyle);
  end;

  procedure DrawBaseLine(const X,Y:Integer);
  var tmpY,
      tmpZ : Integer;
  begin
    tmpZ:=CalcZPos(ValueIndex);

    tmpY:=Y+Pointer.VertSize;

    if (tmpY - GetVertAxis.IEndPos) < 0 then
    begin
      ParentChart.Canvas.AssignVisiblePen(BaseLine);
      ParentChart.Canvas.MoveTo3D(X,tmpY,tmpZ);
      ParentChart.Canvas.LineTo3D(X,GetVertAxis.IEndPos,tmpZ);
    end;
  end;

  function ShouldDrawLine:Boolean;
  begin
    if TreatNulls=tnDontPaint then
       result:=(DrawValuesForward and
                (ValueIndex>0) and (ValueColor[ValueIndex-1]<>clNone))
                or
               ((not DrawValuesForward)
                 and ((ValueIndex+1)<Count) and (ValueColor[ValueIndex+1]<>clNone))
    else
       result:=True;
  end;

var tmpColor : TColor;
    tmpX     : Integer;
    tmpY     : Integer;
begin
  tmpColor:=ValueColor[ValueIndex];

  if (tmpColor<>clNone) or (TreatNulls=tnIgnore)then
  begin
    CalcZPositions(ValueIndex);

    tmpX:=CalcXPos(ValueIndex);
    tmpY:=CalcYPos(ValueIndex);

    if Pointer.Visible then
       DoDrawPointer(tmpX,tmpY,tmpColor);

    if BaseLine.Visible then
       DrawBaseLine(tmpX,tmpY);

    if (ValueIndex<>FirstDisplayedIndex) and LinePen.Visible and ShouldDrawLine then
       DoDrawLine(tmpX,tmpY);

    IOldX:=tmpX;
    IOldY:=tmpY;
    IOldZ:=MiddleZ;
  end;
end;

Procedure TPoint3DSeries.DrawLegendShape(ValueIndex:Integer; Const Rect:TRect);
var tmpColor : TColor;
    tmp      : TSeriesPointerStyle;
begin
  if FPointer.Visible then
  begin
    tmp:=Pointer.Style;

    if ValueIndex=TeeAllValues then
       tmpColor:=SeriesColor
    else
    begin
      tmpColor:=ValueColor[ValueIndex];

      if Assigned(FOnGetPointerStyle) then
      begin
        tmp:=FOnGetPointerStyle(Self,ValueIndex);
        {$IFNDEF TEEOCX}
        tmpColor:=Pointer.Color;
        {$ENDIF}
      end;
    end;

    TeePointerDrawLegend(Pointer,tmpColor,Rect,LinePen.Visible,tmp);
  end
  else inherited;
end;

Procedure TPoint3DSeries.AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False);
var t : Integer;
Begin
  for t:=1 to NumValues do
    AddXYZ( RandomValue(100), RandomValue(100), RandomValue(100));
end;

Procedure TPoint3DSeries.SetDepthSize(Const Value:Double);
begin
  SetDoubleProperty(FDepthSize,Value);
end;

Function TPoint3DSeries.MaxZValue:TChartValue;
begin
  result:=ZValues.MaxValue+FDepthSize;
end;

Procedure TPoint3DSeries.DrawMark( ValueIndex:Integer; Const St:String;
                                   APosition:TSeriesMarkPosition);
begin
  CalcZPositions(ValueIndex);

  if FPointer.Visible then Marks.ZPosition:=MiddleZ
                      else Marks.ZPosition:=StartZ;

  Marks.ApplyArrowLength(APosition);

  inherited;
end;

Procedure TPoint3DSeries.AssignFormatting(Source:TChartSeries);
begin
  if Source is TPoint3DSeries then
  With TPoint3DSeries(Source) do
  begin
    Self.Pointer   :=FPointer;
    Self.FDepthSize:=FDepthSize;
    Self.BaseLine  :=BaseLine;
  end;

  inherited;
end;

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

Procedure TPoint3DSeries.PrepareForGallery;
begin
  inherited;

  LinePen.Color:={$IFDEF FMX}claNavy{$ELSE}clNavy{$ENDIF};
  ParentChart.View3DOptions.Zoom:=60;
end;

Function TPoint3DSeries.ClickedPointer( ValueIndex,tmpX,tmpY:Integer;
                                        x,y:Integer):Boolean;
var P: TPoint;
begin
  result:=Assigned(ParentChart);

  if result then
  begin
    P:=ParentChart.Canvas.Calculate3DPosition(tmpX,tmpY,CalcZPos(ValueIndex));

    result:=(Abs(P.X-X)<=FPointer.HorizSize) and
            (Abs(P.Y-Y)<=FPointer.VertSize);
  end;
end;

Function TPoint3DSeries.Clicked(x,y:Integer):Integer;
var t : Integer;
begin
  result:=inherited Clicked(x,y);

  if (result=TeeNoPointClicked) and FPointer.Visible then
  for t:=0 to Count-1  do
     if ClickedPointer(t,CalcXPos(t),CalcYPos(t),X,Y) then
     begin
       result:=t;
       break;
     end;
end;

procedure TPoint3DSeries.MouseDown(ValueIndex:Integer; AtPointer:Boolean;
                                   Button:TMouseButton;
                                   Shift: TShiftState; X, Y: Integer);
begin
  inherited;

  // Fixes TV52014945 and TV52015075
  if AtPointer and (not ParentChart.CancelMouse) then
     if Assigned(FOnClickPointer) then
        FOnClickPointer(Self,ValueIndex,x,y);
end;

procedure TPoint3DSeries.SetParentChart(const Value: TCustomAxisPanel);
begin
  inherited;

  if Assigned(Value) then
     FPointer.ParentChart:=Value;
end;

class procedure TPoint3DSeries.CreateSubGallery(
  AddSubChart: TChartSubGalleryProc);
begin
  inherited;
  AddSubChart(TeeMsg_NoPoint);
  AddSubChart(TeeMsg_Lines);
  AddSubChart(TeeMsg_NoLine);
  AddSubChart(TeeMsg_Colors);
  AddSubChart(TeeMsg_Marks);
  AddSubChart(TeeMsg_Hollow);
  AddSubChart(TeeMsg_NoBorder);

  if SubGalleryStyles then
  begin
    AddSubChart(TeeMsg_Point2D);
    AddSubChart(TeeMsg_Triangle);
    AddSubChart(TeeMsg_Star);
    AddSubChart(TeeMsg_Circle);
    AddSubChart(TeeMsg_DownTri);
    AddSubChart(TeeMsg_Cross);
    AddSubChart(TeeMsg_Diamond);
    AddSubChart(TeeMsg_Hexagon);
    AddSubChart(TeeMsg_GalleryDonut);
    AddSubChart(TeeMsg_GalleryArrow);
  end;
end;

class procedure TPoint3DSeries.SetSubGallery(ASeries: TChartSeries;
  Index: Integer);
begin
  With TPoint3DSeries(ASeries) do
  Case Index of
    1: Pointer.Hide;
    2: BaseLine.Visible:=True;
    3: Pen.Hide;
    4: ColorEachPoint:=True;
    5: Marks.Visible:=True;
    6: Pointer.Brush.Style:=bsClear;
    7: Pointer.Pen.Hide;
    8: Pointer.Draw3D:=False;
    9: Pointer.Style:=psTriangle;
   10: Pointer.Style:=psStar;
   11: Pointer.Style:=psCircle;
   12: Pointer.Style:=psDownTriangle;
   13: Pointer.Style:=psCross;
   14: Pointer.Style:=psDiamond;
   15: Pointer.Style:=psHexagon;
   16: Pointer.Style:=psDonut;
   17: Pointer.Style:=psArrow;
  else
   inherited;
  end;
end;

procedure TPoint3DSeries.SetBaseLine(const Value: TChartHiddenPen);
begin
  FBaseLine.Assign(Value);
end;

procedure TPoint3DSeries.SetLineTransparency(Value: TTeeTransparency);
begin
  if FLineTransparency<>Value then
  begin
    FLineTransparency:=Value;
    Repaint;
  end;
end;

function TPoint3DSeries.GetDepthSize(ValueIndex: Integer): Integer;
begin
  result:=ParentChart.DepthAxis.CalcSizeValue(FDepthSize) div 2;
end;

function TPoint3DSeries.RaiseClicked: Boolean;
begin
  result:=(inherited RaiseClicked) or
          (Pointer.Visible and Assigned(FOnClickPointer));
end;

{ TBubble3DSeries }
Constructor TBubble3DSeries.Create(AOwner: TComponent);
begin
  inherited;

  FRadiusValues:=TChartValueList.Create(Self,TeeMsg_ValuesBubbleRadius); { <-- radius storage }

  TPointerAccess(Pointer).AllowChangeSize:=False;

  Pointer.Style:=psCircle;
  ColorEachPoint:=True;
  LinePen.Visible:=False;
end;

function TBubble3DSeries.AddBubble(const AX, AY, AZ, ARadius: Double;
  const AXLabel: String; AColor: TColor): Integer;
begin
  RadiusValues.TempValue:=ARadius;
  result:=AddXYZ(AX,AY,AZ,AXLabel,AColor);
end;

procedure TBubble3DSeries.AddSampleValues(NumValues: Integer;
  OnlyMandatory: Boolean);
Var t : Integer;
    s : TSeriesRandomBounds;
Begin
  s:=RandomBounds(NumValues);

  with s do
  for t:=1 to NumValues do { some sample values to see something in design mode }
  Begin
    AddBubble( tmpX,                { X }
               RandomValue(Round(DifY)), { Y }
               RandomValue(200), { Z }
               (DifY/15.0)+Round(DifY/(10+RandomValue(15)))  { <- Radius }
               );

    tmpX:=tmpX+StepX;
  end;
end;

Function TBubble3DSeries.GetDepthSize(ValueIndex:Integer):Integer;
begin
  result:=ParentChart.DepthAxis.CalcSizeValue(RadiusValues.Value[ValueIndex]);
end;

procedure TBubble3DSeries.DrawValue(ValueIndex: Integer);
var tmpSize : Integer;
begin
  tmpSize:=GetVertAxis.CalcSizeValue(RadiusValues.Value[ValueIndex]);

  TPointerAccess(Pointer).ChangeHorizSize(tmpSize);
  TPointerAccess(Pointer).ChangeVertSize(tmpSize);

  inherited;
end;

function TBubble3DSeries.IsValidSourceOf(Value: TChartSeries): Boolean;
begin // Only 3D Bubbles can be assigned to 3D Bubbles
  result:=Value is TBubble3DSeries;
end;

procedure TBubble3DSeries.SetRadiusValues(Value: TChartValueList);
begin
  SetChartValueList(FRadiusValues,Value);
end;

function TBubble3DSeries.ApplyRadius(const Value: Double;
  AList: TChartValueList; Increment: Boolean): Double;
var t : Integer;
    tmpCount : Integer;
begin
  result:=Value;

  tmpCount:=Count;

  for t:=0 to tmpCount-1 do
  if Increment then
     result:=Math.Max(result,AList.Value[t]+RadiusValues.Value[t])
  else
     result:=Math.Min(result,AList.Value[t]-RadiusValues.Value[t]);
end;

function TBubble3DSeries.MaxYValue: TChartValue;
begin
  result:=ApplyRadius(inherited MaxYValue,YValues,True);
end;

function TBubble3DSeries.MaxZValue: TChartValue;
begin
  result:=ApplyRadius(inherited MaxZValue,ZValues,True);
end;

function TBubble3DSeries.MinYValue: TChartValue;
begin
  result:=ApplyRadius(inherited MinYValue,YValues,False);
end;

function TBubble3DSeries.MinZValue: TChartValue;
begin
  result:=ApplyRadius(inherited MinZValue,ZValues,False);
end;

class Function TBubble3DSeries.SubGalleryStyles:Boolean;
begin
  result:=False;
end;

procedure TBubble3DSeries.CalcZPositions(ValueIndex: Integer);
var tmp : Integer;
begin
  if Pointer.Draw3D and ParentChart.Canvas.SupportsFullRotation then
     inherited
  else
  begin
    StartZ:=CalcZPos(ValueIndex);
    tmp:=Math.Max(1,GetDepthSize(ValueIndex));
    MiddleZ:=StartZ+tmp;
    EndZ:=MiddleZ+tmp;
  end;
end;

procedure TBubble3DSeries.PrepareForGallery;
begin
  inherited;
  Pointer.Gradient.Visible:=True;
end;

initialization
  RegisterTeeSeries(TPoint3DSeries,  @TeeMsg_GalleryPoint3D,
                                     @TeeMsg_Gallery3D,1);
  RegisterTeeSeries(TBubble3DSeries, @TeeMsg_GalleryBubble3D,
                                     @TeeMsg_Gallery3D,1);
finalization
  UnRegisterTeeSeries([TPoint3DSeries,TBubble3DSeries]);
end.
