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

interface

uses
  {$IFNDEF FMX}
  {$IFNDEF LINUX}
  Windows,
  {$ENDIF}
  {$ENDIF}

  Classes,

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

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

  {$IFDEF D9}
  Types,
  {$ENDIF}

  {$IFDEF FMX}
  FMXTee.Canvas, FMXTee.Engine, FMXTee.Series, FMXTee.Series.Point3D,
  FMXTee.Series.Error, FMXTee.Constants, FMXTee.Procs
  {$ELSE}
  TeCanvas, TeEngine, Series, TeePoin3, ErrorBar, TeeConst, TeeProcs
  {$ENDIF};

type
  TErrorsFormat=class(TPersistent)
  private
    FLeft  : TChartPen;
    FRight : TChartPen;
    FTop   : TChartPen;
    FBottom: TChartPen;

    procedure SetLeft(const Value: TChartPen);
    procedure SetRight(const Value: TChartPen);
    procedure SetTop(const Value: TChartPen);
    procedure SetBottom(const Value: TChartPen);
  public
    constructor Create(AOwner:TChartSeries);
    destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;
  published
    property Left: TChartPen read FLeft write SetLeft;
    property Right: TChartPen read FRight write SetRight;
    property Top: TChartPen read FTop write SetTop;
    property Bottom: TChartPen read FBottom write SetBottom;
  end;

  TErrors3DFormat=class(TErrorsFormat)
  private
    FFront  : TChartPen;
    FBack   : TChartPen;

    procedure SetFront(const Value: TChartPen);
    procedure SetBack(const Value: TChartPen);
  public
    Constructor Create(AOwner:TChartSeries);
    Destructor Destroy; override;
    procedure Assign(Source:TPersistent); override;
  published
    property Front: TChartPen read FFront write SetFront;
    property Back: TChartPen read FBack write SetBack;
  end;

  TErrorsBase=Class(TPersistent)
  private
    FLeft       : TChartValueList;
    FRight      : TChartValueList;
    FTop        : TChartValueList;
    FBottom     : TChartValueList;
    FSeriesColor: Boolean;
    FSize       : Integer;
    FSizeUnits  : TErrorWidthUnits;

    ISeries     : TChartSeries;

    procedure PreparePen(ValueIndex: Integer; const APen: TChartPen);
    procedure SetLeft(const Value: TChartValueList);
    procedure SetRight(const Value: TChartValueList);
    procedure SetTop(const Value: TChartValueList);
    procedure SetBottom(const Value: TChartValueList);

    procedure SetSize(Value: Integer);
    procedure SetSizeUnits(const Value:TErrorWidthUnits);
    procedure SetSeriesColor(const Value: Boolean);
  protected
    procedure AddSample(Index:Integer; const X,Y:Double);
    procedure DrawError(X,Y,ALength,HalfSize,AZ:Integer;IsHoriz:Boolean);
    procedure CalcMinMaxIndex(out MinIndex, MaxIndex: Integer; Horizontal: Boolean);
  public
    constructor Create(AOwner:TChartSeries); virtual;

    procedure Assign(Source:TPersistent); override;
  published
    property Left: TChartValueList read FLeft write SetLeft;
    property Right: TChartValueList read FRight write SetRight;
    property Top: TChartValueList read FTop write SetTop;
    property Bottom: TChartValueList read FBottom write SetBottom;

    property SeriesColor:Boolean read FSeriesColor write SetSeriesColor default False;
    property Size: Integer read FSize write SetSize default 100;
    property SizeUnits:TErrorWidthUnits read FSizeUnits write SetSizeUnits default ewuPercent;
  end;

  TErrors=Class(TErrorsBase)
  private
    FFormat : TErrorsFormat;

    procedure SetFormat(const Value: TErrorsFormat);
  public
    constructor Create(AOwner:TChartSeries); override;
    destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;
  published
    property Format: TErrorsFormat read FFormat write SetFormat;
  end;

  TErrors3D=Class(TErrorsBase)
  private
    FFormat : TErrors3DFormat;

    FFront: TChartValueList;
    FBack : TChartValueList;

    procedure SetFormat(const Value: TErrors3DFormat);
    procedure SetFront(const Value: TChartValueList);
    procedure SetBack(const Value: TChartValueList);
  protected
    procedure AddSample(Index:Integer; const X,Y:Double);
    procedure DrawZError(X,Y,Z,ALength,HalfSize:Integer);
  public
    constructor Create(AOwner:TChartSeries); override;
    destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;
  published
    property Format: TErrors3DFormat read FFormat write SetFormat;
    property Front: TChartValueList read FFront write SetFront;
    property Back: TChartValueList read FBack write SetBack;
  end;

  TCustomErrorPointSeries=Class(TPointSeries)
  private
    FErrors : TErrors;
    procedure SetErrors(const Value: TErrors);
  protected
    procedure AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False); override;
    procedure CalcHorizMargins(Var LeftMargin,RightMargin:Integer); override;
    procedure CalcVerticalMargins(Var TopMargin,BottomMargin:Integer); override;
    procedure CalcErrorSize(out Horiz, Vert: Integer);
    procedure DrawValue(ValueIndex:Integer); override;
    class Function GetEditorClass:String; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;
    function Clicked(x,y:Integer):Integer; override;
    function Add(Const AX,AY, ALeft, ARight, ATop, ABottom:Double;
                           Const AXLabel:String='';
                           AColor:TColor=clTeeColor):Integer;
    property Errors: TErrors read FErrors write SetErrors;
  end;

  TErrorPointSeries=Class(TCustomErrorPointSeries)
  published
    property Errors;
  end;

  TCustomErrorPoint3DSeries=Class(TPoint3DSeries)
  private
    FErrors : TErrors3D;
    procedure SetErrors(const Value: TErrors3D);
  protected
    procedure AddSampleValues(NumValues: Integer; OnlyMandatory: Boolean); override;
    procedure CalcHorizMargins(Var LeftMargin,RightMargin:Integer); override;
    procedure CalcVerticalMargins(Var TopMargin,BottomMargin:Integer); override;
    procedure CalcErrorSize(out Horiz, Vert: Integer);
    procedure DrawValue(ValueIndex:Integer); override;
    class Function GetEditorClass:String; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;
    function Clicked(x,y:Integer):Integer; override;
    function Add(Const AX,AY,AZ, ALeft, ARight, ATop, ABottom, AFront, ABack:Double;
                           Const AXLabel:String='';
                           AColor:TColor=clTeeColor):Integer;
    property Errors: TErrors3D read FErrors write SetErrors;
  end;

  TErrorPoint3DSeries=Class(TCustomErrorPoint3DSeries)
  published
    property Errors;
  end;

implementation

uses
  Math,
  {$IFDEF FMX}
  FMXTee.Chart, FMXTee.ProConstants
  {$ELSE}
  Chart, TeeProCo
  {$ENDIF};

type
  TSeriesAccess=class(TChartSeries);

{ TErrorsFormat }

constructor TErrorsFormat.Create(AOwner:TChartSeries);
begin
  inherited Create;

  FLeft:=TSeriesAccess(AOwner).CreateChartPen;
  FRight:=TSeriesAccess(AOwner).CreateChartPen;
  FTop:=TSeriesAccess(AOwner).CreateChartPen;
  FBottom:=TSeriesAccess(AOwner).CreateChartPen;
end;

Destructor TErrorsFormat.Destroy;
begin
  FBottom.Free;
  FTop.Free;
  FRight.Free;
  FLeft.Free;

  inherited;
end;

procedure TErrorsFormat.Assign(Source: TPersistent);
begin
  if Source is TErrorsFormat then
  with TErrorsFormat(Source) do
  begin
    Self.Left:=Left;
    Self.Right:=Right;
    Self.Top:=Top;
    Self.Bottom:=Bottom;
  end
  else
    inherited;
end;

procedure TErrorsFormat.SetBottom(const Value: TChartPen);
begin
  FBottom.Assign(Value);
end;

procedure TErrorsFormat.SetLeft(const Value: TChartPen);
begin
  FLeft.Assign(Value);
end;

procedure TErrorsFormat.SetRight(const Value: TChartPen);
begin
  FRight.Assign(Value);
end;

procedure TErrorsFormat.SetTop(const Value: TChartPen);
begin
  FTop.Assign(Value);
end;

{ TErrors3DFormat }

constructor TErrors3DFormat.Create(AOwner: TChartSeries);
begin
  inherited Create(AOwner);

  FFront:=TSeriesAccess(AOwner).CreateChartPen;
  FBack:=TSeriesAccess(AOwner).CreateChartPen;
end;

destructor TErrors3DFormat.Destroy;
begin
  FFront.Free;
  FBack.Free;

  inherited;
end;

procedure TErrors3DFormat.Assign(Source: TPersistent);
begin
  inherited;

  if Source is TErrorsFormat then
  with TErrors3DFormat(Source) do
  begin
    Self.Front:=Front;
    Self.Back:=Back;
  end;
end;

procedure TErrors3DFormat.SetFront(const Value: TChartPen);
begin
  FFront.Assign(Value);
end;

procedure TErrors3DFormat.SetBack(const Value: TChartPen);
begin
  FBack.Assign(Value);
end;

{ TErrorsBase }

constructor TErrorsBase.Create(AOwner:TChartSeries);
begin
  inherited Create;

  ISeries:=AOwner;

  FLeft:=TChartValueList.Create(AOwner,'LeftErrors');
  FRight:=TChartValueList.Create(AOwner,'RightErrors');
  FTop:=TChartValueList.Create(AOwner,'TopErrors');
  FBottom:=TChartValueList.Create(AOwner,'BottomErrors');

  FSize:=100;
end;

procedure TErrorsBase.PreparePen(ValueIndex: Integer; const APen: TChartPen);
begin
  if FSeriesColor then
     ISeries.ParentChart.Canvas.AssignVisiblePenColor(APen,ISeries.ValueColor[ValueIndex])
  else
     ISeries.ParentChart.Canvas.AssignVisiblePen(APen);
end;

Procedure TErrorsBase.SetLeft(const Value:TChartValueList);
begin
  TSeriesAccess(ISeries).SetChartValueList(FLeft,Value);
end;

Procedure TErrorsBase.SetRight(const Value:TChartValueList);
begin
  TSeriesAccess(ISeries).SetChartValueList(FRight,Value);
end;

Procedure TErrorsBase.SetTop(const Value:TChartValueList);
begin
  TSeriesAccess(ISeries).SetChartValueList(FTop,Value);
end;

Procedure TErrorsBase.SetBottom(const Value:TChartValueList);
begin
  TSeriesAccess(ISeries).SetChartValueList(FBottom,Value);
end;

procedure TErrorsBase.Assign(Source:TPersistent);
begin
  if Source is TErrorsBase then
  with TErrorsBase(Source) do
  begin
    Self.FLeft.Assign(FLeft);
    Self.FRight.Assign(FRight);
    Self.FTop.Assign(FTop);
    Self.FBottom.Assign(FBottom);
    Self.FSeriesColor:=FSeriesColor;
    Self.FSize:=FSize;
    Self.FSizeUnits:=FSizeUnits;
  end
  else
    inherited;
end;

procedure TErrorsBase.SetSize(Value: Integer);
begin
  TSeriesAccess(ISeries).SetIntegerProperty(FSize,Value);
end;

procedure TErrorsBase.SetSizeUnits(const Value: TErrorWidthUnits);
begin
  if FSizeUnits<>Value then
  Begin
    FSizeUnits:=Value;
    ISeries.Repaint;
  end;
end;

procedure TErrorsBase.AddSample(Index: Integer; const X, Y: Double);

  function TempRandom:Double;
  begin
    result:=(20+TSeriesAccess(ISeries).RandomValue(4));
  end;

begin
  FLeft.Value[Index]:=X/TempRandom;
  FRight.Value[Index]:=X/TempRandom;
  FTop.Value[Index]:=Y/TempRandom;
  FBottom.Value[Index]:=Y/TempRandom;
end;

procedure TErrorsBase.SetSeriesColor(const Value: Boolean);
begin
  TSeriesAccess(ISeries).SetBooleanProperty(FSeriesColor,Value);
end;

procedure TErrorsBase.DrawError(X,Y,ALength,HalfSize,AZ:Integer; IsHoriz:Boolean);

  procedure DrawHoriz(XPos:Integer);
  begin
    with ISeries.ParentChart.Canvas do
    begin
      if ISeries.ParentChart.View3D then
      begin
        if (X>XPos) then
          HorizLine3D(XPos,X,Y, AZ)
        else
        HorizLine3D(X,XPos,Y, AZ);
        VertLine3D(XPos,Y-HalfSize,Y+HalfSize, AZ);
      end
      else
      begin
        if (X>XPos) then
          DoHorizLine(XPos,X,Y)
        else
        DoHorizLine(X,XPos,Y);
        DoVertLine(XPos,Y-HalfSize,Y+HalfSize);
      end;
    end;
  end;

  procedure DrawVert(YPos:Integer);
  begin
    with ISeries.ParentChart.Canvas do
    begin
      if ISeries.ParentChart.View3D then
      begin
        if (Y>YPos) then
          VertLine3D(X,YPos,Y, AZ)
        else
        VertLine3D(X,Y,YPos, AZ);
        HorizLine3D(X-HalfSize,X+HalfSize,YPos, AZ);
      end
      else
      begin
        if (Y>YPos) then
          DoVertLine(X,YPos,Y)
        else
        DoVertLine(X,Y,YPos);
        DoHorizLine(X-HalfSize,X+HalfSize,YPos);
      end;
    end;
  end;

begin
  if IsHoriz then
    DrawHoriz(X-ALength)
  else
    DrawVert(Y-ALength);
end;

procedure TErrorsBase.CalcMinMaxIndex(out MinIndex,MaxIndex: Integer; Horizontal: Boolean);
var t : Integer;
begin
  MinIndex:=0;
  MaxIndex:=0;

  if Horizontal then
    for t:=1 to ISeries.Count-1 do
    begin
      if ISeries.XValues.Value[t]-Self.FLeft[t]<ISeries.XValues.Value[MinIndex]-Self.FLeft[MinIndex] then
         MinIndex:=t
      else
      if ISeries.XValues.Value[t]+Self.FRight[t]>ISeries.XValues.Value[MaxIndex]+Self.FRight[MaxIndex] then
         MaxIndex:=t;
    end
  else
    for t:=1 to ISeries.Count-1 do
    begin
      if ISeries.YValues.Value[t]-Self.FBottom[t]<ISeries.YValues.Value[MinIndex]-Self.FBottom[MinIndex] then
         MinIndex:=t
      else
      if ISeries.YValues.Value[t]+Self.FTop[t]>ISeries.YValues.Value[MaxIndex]+Self.FTop[MaxIndex] then
         MaxIndex:=t;
    end;
end;

{ TErrors }

constructor TErrors.Create(AOwner:TChartSeries);
begin
  inherited;
  FFormat:=TErrorsFormat.Create(AOwner);
end;

destructor TErrors.Destroy;
begin
  FFormat.Free;
  inherited;
end;

procedure TErrors.Assign(Source: TPersistent);
begin
  if Source is TErrors then
     Format:=TErrors(Source).FFormat;

  inherited;
end;

procedure TErrors.SetFormat(const Value: TErrorsFormat);
begin
  FFormat.Assign(Value);
end;

{ TErrors3D }

Constructor TErrors3D.Create(AOwner: TChartSeries);
begin
  inherited;

  FFormat:=TErrors3DFormat.Create(AOwner);
  FFront:=TChartValueList.Create(AOwner,'FrontErrors');
  FBack:=TChartValueList.Create(AOwner,'BackErrors');
end;

Destructor TErrors3D.Destroy;
begin
  FFormat.Free;
  inherited;
end;

procedure TErrors3D.Assign(Source: TPersistent);
begin
  if Source is TErrors3D then
  with TErrors3D(Source) do
  begin
    Self.FFormat.Assign(FFormat);
    Self.FFront.Assign(FFront);
    Self.FBack.Assign(FBack);
  end;

  inherited;
end;

procedure TErrors3D.SetFormat(const Value: TErrors3DFormat);
begin
  FFormat.Assign(Value);
end;

procedure TErrors3D.SetFront(const Value: TChartValueList);
begin
  TSeriesAccess(ISeries).SetChartValueList(FFront,Value);
end;

procedure TErrors3D.SetBack(const Value: TChartValueList);
begin
  TSeriesAccess(ISeries).SetChartValueList(FBack,Value);
end;

procedure TErrors3D.DrawZError(X,Y,Z,ALength,HalfSize:Integer);
begin
  with ISeries.ParentChart.Canvas do
  begin
    MoveTo3D(X,Y,Z);
    LineTo3D(X,Y,Z-ALength);
    MoveTo3D(X,Y-HalfSize,Z-ALength);
    LineTo3D(X,Y+HalfSize,Z-ALength);
  end;
end;

procedure TErrors3D.AddSample(Index: Integer; const X, Y: Double);

  function TempRandom:Double;
  begin
    result:=(20+TSeriesAccess(ISeries).RandomValue(4));
  end;

begin
  inherited;
  with ISeries as TErrorPoint3DSeries do
  begin
    FFront.Value[Index]:=ZValues.Range/TempRandom;
    FBack.Value[Index]:=ZValues.Range/TempRandom;
  end;
end;

{ TCustomErrorPointSeries }

constructor TCustomErrorPointSeries.Create(AOwner: TComponent);
begin
  inherited;
  FErrors:=TErrors.Create(self);
end;

destructor TCustomErrorPointSeries.Destroy;
begin
  FErrors.Free;
  inherited;
end;

procedure TCustomErrorPointSeries.AddSampleValues(NumValues: Integer;
  OnlyMandatory: Boolean);
var t : Integer;
begin
  inherited;

  for t:=0 to NumValues-1 do
      FErrors.AddSample(t, XValues.Range,YValues.Range);
end;

procedure TCustomErrorPointSeries.CalcHorizMargins(var LeftMargin,
  RightMargin: Integer);
var
  tmpMin,
  tmpMax : Integer;
begin
  inherited;

  if Count>0 then
  begin
    with Errors do
    begin
      CalcMinMaxIndex(tmpMin, tmpMax, true);

      if Format.FLeft.Visible then
         LeftMargin:=Math.Max(LeftMargin,
           GetHorizAxis.CalcSizeValue(FLeft[tmpMin])+2);

      if Format.FRight.Visible then
         RightMargin:=Math.Max(RightMargin,
           GetHorizAxis.CalcSizeValue(FRight[tmpMax])+2);
    end;
  end;
end;

procedure TCustomErrorPointSeries.CalcVerticalMargins(var TopMargin,
  BottomMargin: Integer);
var
  tmpMin,
  tmpMax : Integer;
begin
  inherited;

  if Count>0 then
  begin
    with Errors do
    begin
      CalcMinMaxIndex(tmpMin, tmpMax, false);

      if Format.FTop.Visible then
         TopMargin:=Math.Max(TopMargin,
           GetVertAxis.CalcSizeValue(FTop[tmpMax])+3);

      if Format.FBottom.Visible then
         BottomMargin:=Math.Max(BottomMargin,
           GetVertAxis.CalcSizeValue(FBottom[tmpMin])+4);
    end;
  end;
end;

procedure TCustomErrorPointSeries.CalcErrorSize(out Horiz, Vert: Integer);
begin
  with FErrors do
  begin
    if FSize=0 then
    begin
      Horiz:=Pointer.HorizSize;
      Vert:=Pointer.VertSize;
    end
    else
    if FSizeUnits=ewuPercent then
    begin
      Horiz:=Round(1.0*FSize*Pointer.HorizSize*0.01);
      Vert:=Round(1.0*FSize*Pointer.VertSize*0.01);
    end
    else
    begin
      Horiz:=FSize;
      Vert:=FSize;
    end;
  end;
end;

procedure TCustomErrorPointSeries.DrawValue(ValueIndex: Integer);
var
  ErrorHorizSize : Integer;
  ErrorVertSize  : Integer;
  XPosPoint : Integer;
  YPosPoint : Integer;

  procedure DrawLeftError;
  var tmpX: Integer;
  begin
    with Errors do
    begin
      tmpX:=GetHorizAxis.CalcSizeValue(FLeft.Value[ValueIndex]);
      if Format.Left.Visible and (Pointer.HorizSize < tmpX) then
      begin
        PreparePen(ValueIndex, Format.Left);
        DrawError(XPosPoint-Pointer.HorizSize, YPosPoint, tmpX-Pointer.HorizSize,
                  ErrorVertSize, MiddleZ, True);
      end;
    end;
  end;

  procedure DrawRightError;
  var tmpX: Integer;
  begin
    with Errors do
    begin
      tmpX:=GetHorizAxis.CalcSizeValue(FRight.Value[ValueIndex]);
      if Format.Right.Visible and (Pointer.HorizSize < tmpX) then
      begin
        PreparePen(ValueIndex, Format.Right);
        DrawError(XPosPoint+Pointer.HorizSize, YPosPoint, -(tmpX-Pointer.HorizSize),
                  ErrorVertSize, MiddleZ, True);
      end;
    end;
  end;

  procedure DrawTopError;
  var tmpY: Integer;
  begin
    with Errors do
    begin
      tmpY:=GetVertAxis.CalcSizeValue(FTop.Value[ValueIndex]);
      if Format.Top.Visible and (Pointer.VertSize < tmpY) then
      begin
        PreparePen(ValueIndex, Format.Top);
        DrawError(XPosPoint, YPosPoint-Pointer.VertSize,
                  tmpY-Pointer.VertSize,
                  ErrorHorizSize, MiddleZ, False);
      end;
    end;
  end;

  procedure DrawBottomError;
  var tmpY: Integer;
  begin
    with Errors do
    begin
      tmpY:=GetVertAxis.CalcSizeValue(FBottom.Value[ValueIndex]);
      if Format.Bottom.Visible and (Pointer.VertSize < tmpY) then
      begin
        PreparePen(ValueIndex, Format.Bottom);
        DrawError(XPosPoint, YPosPoint+Pointer.VertSize,
                  -(tmpY-Pointer.VertSize),
                  ErrorHorizSize, MiddleZ, False);
      end;
    end;
  end;

  procedure DrawPoint;
  begin
    if Pointer.Visible and not IsNull(ValueIndex) then
       DrawPointer(XPosPoint, YPosPoint, ValueColor[ValueIndex], ValueIndex);
  end;

  procedure DrawPointAndErrorWithOrder(LeftFirst, TopFirst: Boolean);
  begin
    if LeftFirst then
    begin
      DrawLeftError;
      if TopFirst then
      begin
        DrawTopError;
        if ParentChart.View3D then
        DrawPoint;
        DrawBottomError;
      end
      else
      begin
        DrawBottomError;
        if ParentChart.View3D then
        DrawPoint;
        DrawTopError;
      end;
      DrawRightError;
      if not ParentChart.View3D then
        DrawPoint;
    end
    else
    begin
      DrawRightError;
      if TopFirst then
      begin
        DrawTopError;
        if ParentChart.View3D then
        DrawPoint;
        DrawBottomError;
      end
      else
      begin
        DrawBottomError;
        if ParentChart.View3D then
        DrawPoint;
        DrawTopError;
      end;
      DrawLeftError;
      if not ParentChart.View3D then
        DrawPoint;
    end;
  end;

begin
  XPosPoint:=CalcXPosValue(XValue[ValueIndex]);
  YPosPoint:=CalcYPosValue(YValue[ValueIndex]);
  CalcErrorSize(ErrorHorizSize, ErrorVertSize);

  if ParentChart.Aspect.Orthogonal or not ParentChart.View3D then
    DrawPointAndErrorWithOrder(true, false)
  else
  case ParentChart.Aspect.Rotation mod 360 of
    0..180:
    begin
      case ParentChart.Aspect.Elevation mod 360 of
        0..90: DrawPointAndErrorWithOrder(false, true);
        91..180: DrawPointAndErrorWithOrder(true, true);
        181..270: DrawPointAndErrorWithOrder(true, false);
        271..360: DrawPointAndErrorWithOrder(false, false);
      end;
    end;
    181..360:
    begin
      case ParentChart.Aspect.Elevation mod 360 of
        0..90: DrawPointAndErrorWithOrder(true, true);
        91..180: DrawPointAndErrorWithOrder(false, true);
        181..270: DrawPointAndErrorWithOrder(false, false);
        271..360: DrawPointAndErrorWithOrder(true, false);
      end;
    end;
  end;
end;

class function TCustomErrorPointSeries.GetEditorClass: String;
begin
  result:='TErrorPointSeriesEditor';
end;

procedure TCustomErrorPointSeries.SetErrors(const Value: TErrors);
begin
  FErrors.Assign(Value);
end;

function TCustomErrorPointSeries.Add(const AX, AY, ALeft, ARight, ATop,
               ABottom: Double; const AXLabel: String; AColor: TColor): Integer;
begin
  with FErrors do
  begin
    FLeft.TempValue:=Abs(ALeft);
    FRight.TempValue:=Abs(ARight);
    FTop.TempValue:=Abs(ATop);
    FBottom.TempValue:=Abs(ABottom);
  end;

  result:=AddXY(AX,AY,AXLabel,AColor);
end;

procedure TCustomErrorPointSeries.Assign(Source: TPersistent);
begin
  if Source is TCustomErrorPointSeries then
     Errors:=TCustomErrorPointSeries(Source).FErrors;

  inherited;
end;

function TCustomErrorPointSeries.Clicked(x, y: Integer): Integer;
var ErrorHorizSize : Integer;
    ErrorVertSize  : Integer;
    P              : TPoint;

  function ClickedError(valueIndex: Integer): Boolean;

    function ClickedHorizontal(const x0, x1, y, t: TCoordinate): Boolean;
    var P0, P1 : TPoint;
    begin
      result:=Assigned(ParentChart);

      if result then
      begin
        P0:=ParentChart.Canvas.Calculate3DPosition(x0,y,MiddleZ);
        P1:=ParentChart.Canvas.Calculate3DPosition(x1,y,MiddleZ);
        result:=PointInLine(P, P0, P1, {$IFDEF FMX}Round{$ENDIF}(t));
      end;
    end;

    function ClickedVertical(const x, y0, y1, t: TCoordinate): Boolean;
    var P0, P1 : TPoint;
    begin
      result:=Assigned(ParentChart);

      if result then
      begin
        P0:=ParentChart.Canvas.Calculate3DPosition(x,y0,MiddleZ);
        P1:=ParentChart.Canvas.Calculate3DPosition(x,y1,MiddleZ);
        result:=PointInLine(P, P0, P1, {$IFDEF FMX}Round{$ENDIF}(t));
      end;
    end;

  var XPosPoint : TCoordinate;
      YPosPoint : TCoordinate;

    function ClickedTopError(const errValue: Double; APen: TChartPen): Boolean;
    begin
      Result:=(errValue>0) and (APen.Visible) and
               (ClickedHorizontal(XPosPoint - ErrorHorizSize, XPosPoint + ErrorHorizSize,
                                  CalcYPosValue(YValues[valueIndex] + errValue), APen.Width)
               or
               ClickedVertical(XPosPoint, YPosPoint - Pointer.VertSize,
                               CalcYPosValue(YValues[valueIndex] + errValue), APen.Width));
    end;

    function ClickedBottomError(const errValue: Double; APen: TChartPen): Boolean;
    begin
      Result:=(errValue>0) and (APen.Visible) and
               (ClickedHorizontal(XPosPoint - ErrorHorizSize, XPosPoint + ErrorHorizSize,
                                  CalcYPosValue(YValues[valueIndex] - errValue), APen.Width)
               or
               ClickedVertical(XPosPoint, YPosPoint + Pointer.VertSize,
                               CalcYPosValue(YValues[valueIndex] - errValue), APen.Width));
    end;

    function ClickedLeftError(const errValue: Double; APen: TChartPen): Boolean;
    begin
      Result:=(errValue>0) and (APen.Visible) and
               (ClickedHorizontal(XPosPoint - Pointer.HorizSize,
                                  CalcXPosValue(XValues[valueIndex] - errValue), YPosPoint, APen.Width)
              or
              ClickedVertical(CalcXPosValue(XValues[valueIndex] - errValue),
                              YPosPoint - ErrorVertSize, YPosPoint + ErrorVertSize, APen.Width));
    end;

    function ClickedRightError(const errValue: Double; APen: TChartPen): Boolean;
    begin
      Result:=(errValue>0) and (APen.Visible) and
               (ClickedHorizontal(XPosPoint + Pointer.HorizSize,
                                  CalcXPosValue(XValues[valueIndex] + errValue), YPosPoint, APen.Width)
              or
              ClickedVertical(CalcXPosValue(XValues[valueIndex] + errValue),
                              YPosPoint - ErrorVertSize, YPosPoint + ErrorVertSize, APen.Width));
    end;

  begin
    XPosPoint:=CalcXPos(valueIndex);
    YPosPoint:=CalcYPos(valueIndex);

    Result:=ClickedTopError(FErrors.FTop.Value[valueIndex], FErrors.FFormat.FTop) or
            ClickedBottomError(FErrors.FBottom.Value[valueIndex], FErrors.FFormat.FBottom) or
            ClickedLeftError(FErrors.FLeft.Value[valueIndex], FErrors.FFormat.FLeft) or
            ClickedRightError(FErrors.FRight.Value[valueIndex], FErrors.FFormat.FRight);
  end;

var t        : Integer;
    tmpIndex : Integer;
begin
  result:=inherited Clicked(x,y);

  if (result=TeeNoPointClicked) and
     (FirstValueIndex>-1) and (LastValueIndex>-1) then
  begin
    P:=TeePoint(x,y);
    tmpIndex:=Math.Min(LastValueIndex,Count-1);
    CalcErrorSize(ErrorHorizSize, ErrorVertSize);

    for t:=FirstValueIndex to tmpIndex do
    begin
      if ClickedError(t) then
      begin
        result:=t;
        exit;
      end;
    end;
  end;
end;

{ TCustomErrorPoint3DSeries }

constructor TCustomErrorPoint3DSeries.Create(AOwner: TComponent);
begin
  inherited;
  Pen.Visible:=false;
  FErrors:=TErrors3D.Create(Self);
end;

Destructor TCustomErrorPoint3DSeries.Destroy;
begin
  FErrors.Free;
  inherited;
end;

function TCustomErrorPoint3DSeries.Add(const AX, AY, AZ, ALeft, ARight,
  ATop, ABottom, AFront, ABack: Double; const AXLabel: String; AColor: TColor): Integer;
begin
  with FErrors do
  begin
    FLeft.TempValue:=Abs(ALeft);
    FRight.TempValue:=Abs(ARight);
    FTop.TempValue:=Abs(ATop);
    FBottom.TempValue:=Abs(ABottom);
    FFront.TempValue:=Abs(AFront);
    FBack.TempValue:=Abs(ABack);
  end;

  result:=AddXYZ(AX,AY,AZ,AXLabel,AColor);
end;

procedure TCustomErrorPoint3DSeries.AddSampleValues(NumValues: Integer;
  OnlyMandatory: Boolean);
var t : Integer;
begin
  inherited;

  for t:=0 to NumValues-1 do
      FErrors.AddSample(t, XValues.Range, YValues.Range);
end;

procedure TCustomErrorPoint3DSeries.CalcHorizMargins(var LeftMargin,
  RightMargin: Integer);
var
  tmpMin,
  tmpMax : Integer;
begin
  inherited;

  if Count>0 then
  begin
    with Errors do
    begin
      CalcMinMaxIndex(tmpMin, tmpMax, true);

      if Format.FLeft.Visible then
         LeftMargin:=Math.Max(LeftMargin,
           GetHorizAxis.CalcSizeValue(FLeft[tmpMin])+2);

      if Format.FRight.Visible then
         RightMargin:=Math.Max(RightMargin,
           GetHorizAxis.CalcSizeValue(FRight[tmpMax])+2);
    end;
  end;
end;

procedure TCustomErrorPoint3DSeries.CalcVerticalMargins(var TopMargin,
  BottomMargin: Integer);
var
  tmpMin,
  tmpMax : Integer;
begin
  inherited;

  if Count>0 then
  begin
    with Errors do
    begin
      CalcMinMaxIndex(tmpMin, tmpMax, false);

      if Format.FTop.Visible then
         TopMargin:=Math.Max(TopMargin,
           GetVertAxis.CalcSizeValue(FTop[tmpMax])+3);

      if Format.FBottom.Visible then
         BottomMargin:=Math.Max(BottomMargin,
           GetVertAxis.CalcSizeValue(FBottom[tmpMin])+4);
    end;
  end;
end;

procedure TCustomErrorPoint3DSeries.SetErrors(const Value: TErrors3D);
begin
  FErrors.Assign(Value);
end;

procedure TCustomErrorPoint3DSeries.Assign(Source: TPersistent);
begin
  if Source is TCustomErrorPoint3DSeries then
     FErrors.Assign(TCustomErrorPoint3DSeries(Source).FErrors);

  inherited;
end;

procedure TCustomErrorPoint3DSeries.CalcErrorSize(out Horiz, Vert: Integer);
begin
  with FErrors do
  begin
    if FSize=0 then
    begin
      Horiz:=Pointer.HorizSize;
      Vert:=Pointer.VertSize;
    end
    else
    if FSizeUnits=ewuPercent then
    begin
      Horiz:=Round(1.0*FSize*Pointer.HorizSize*0.01);
      Vert:=Round(1.0*FSize*Pointer.VertSize*0.01);
    end
    else
    begin
      Horiz:=FSize;
      Vert:=FSize;
    end;
  end;
end;

procedure TCustomErrorPoint3DSeries.DrawValue(ValueIndex: Integer);
var
  ErrorHorizSize : Integer;
  ErrorVertSize  : Integer;
  XPosPoint : Integer;
  YPosPoint : Integer;
  ZPosPoint : Integer;

  procedure DrawLeftError;
  var tmpX: Integer;
  begin
    with Errors do
    begin
      tmpX:=GetHorizAxis.CalcSizeValue(FLeft.Value[ValueIndex]);
      if Format.Left.Visible and (Pointer.HorizSize < tmpX) then
      begin
        PreparePen(ValueIndex, Format.Left);
        DrawError(XPosPoint-Pointer.HorizSize, YPosPoint, tmpX-Pointer.HorizSize,
                  ErrorHorizSize, ZPosPoint, True);
      end;
    end;
  end;

  procedure DrawRightError;
  var tmpX: Integer;
  begin
    with Errors do
    begin
      tmpX:=GetHorizAxis.CalcSizeValue(FRight.Value[ValueIndex]);
      if Format.Right.Visible and (Pointer.HorizSize < tmpX) then
      begin
        PreparePen(ValueIndex, Format.Right);
        DrawError(XPosPoint+Pointer.HorizSize, YPosPoint, -(tmpX-Pointer.HorizSize),
                  ErrorHorizSize, ZPosPoint, True);
      end;
    end;
  end;

  procedure DrawTopError;
  var tmpY: Integer;
  begin
    with Errors do
    begin
      tmpY:=GetVertAxis.CalcSizeValue(FTop.Value[ValueIndex]);
      if Format.Top.Visible and (Pointer.VertSize < tmpY) then
      begin
        PreparePen(ValueIndex, Format.Top);
        DrawError(XPosPoint, YPosPoint-Pointer.VertSize,
                  tmpY-Pointer.VertSize,
                  ErrorVertSize, ZPosPoint, False);
      end;
    end;
  end;

  procedure DrawBottomError;
  var tmpY: Integer;
  begin
    with Errors do
    begin
      tmpY:=GetVertAxis.CalcSizeValue(FBottom.Value[ValueIndex]);
      if Format.Bottom.Visible and (Pointer.VertSize < tmpY) then
      begin
        PreparePen(ValueIndex, Format.Bottom);
        DrawError(XPosPoint, YPosPoint+Pointer.VertSize,
                  -(tmpY-Pointer.VertSize),
                  ErrorVertSize, ZPosPoint, False);
      end;
    end;
  end;

  procedure DrawFrontError;
  var tmpZ: Integer;
  begin
    with Errors do
    begin
      tmpZ:=ParentChart.Axes.Depth.CalcSizeValue(FFront.Value[ValueIndex]);
      if Format.Front.Visible and (Pointer.Depth < tmpZ) then
      begin
        PreparePen(ValueIndex, Format.Front);
        DrawZError(XPosPoint, YPosPoint, ZPosPoint-Pointer.Depth,
                   tmpZ-Pointer.Depth, ErrorVertSize);
      end;
    end;
  end;

  procedure DrawBackError;
  var tmpZ: Integer;
  begin
    with Errors do
    begin
      tmpZ:=ParentChart.Axes.Depth.CalcSizeValue(FBack.Value[ValueIndex]);
      if Format.Back.Visible and (Pointer.Depth < tmpZ) then
      begin
        PreparePen(ValueIndex, Format.Back);
        DrawZError(XPosPoint, YPosPoint, ZPosPoint+Pointer.Depth,
                   -(tmpZ-Pointer.Depth), ErrorVertSize);
      end;
    end;
  end;

  procedure DrawPoint;
  begin
    with Pointer do
    if Visible and not IsNull(ValueIndex) then
    begin
      PrepareCanvas(ParentChart.Canvas,ValueColor[ValueIndex]);
      DrawPointer(ParentChart.Canvas, ParentChart.View3D, XPosPoint, YPosPoint,
                  HorizSize, VertSize, ValueColor[ValueIndex], Style);
    end;
  end;

  procedure DrawPointAndErrorWithOrder(LeftFirst, TopFirst, FrontFirst: Boolean);
  begin
    if LeftFirst then
    begin
      DrawLeftError;
      if TopFirst then
      begin
        DrawTopError;
        if FrontFirst then
        begin
          DrawFrontError;
          DrawPoint;
          DrawBackError;
        end
        else
        begin
          DrawBackError;
          DrawPoint;
          DrawFrontError;
        end;
        DrawBottomError;
      end
      else
      begin
        DrawBottomError;
        if FrontFirst then
        begin
          DrawFrontError;
          DrawPoint;
          DrawBackError;
        end
        else
        begin
          DrawBackError;
          DrawPoint;
          DrawFrontError;
        end;
        DrawTopError;
      end;
      DrawRightError;
    end
    else
    begin
      DrawRightError;
      if TopFirst then
      begin
        DrawTopError;
        if FrontFirst then
        begin
          DrawFrontError;
          DrawPoint;
          DrawBackError;
        end
        else
        begin
          DrawBackError;
          DrawPoint;
          DrawFrontError;
        end;
        DrawBottomError;
      end
      else
      begin
        DrawBottomError;
        if FrontFirst then
        begin
          DrawFrontError;
          DrawPoint;
          DrawBackError;
        end
        else
        begin
          DrawBackError;
          DrawPoint;
          DrawFrontError;
        end;
        DrawTopError;
      end;
      DrawLeftError;
    end;
  end;

begin
  XPosPoint:=CalcXPos(ValueIndex);
  YPosPoint:=CalcYPos(ValueIndex);
  ZPosPoint:=CalcZPos(ValueIndex);
  CalcZPositions(ValueIndex);
  CalcErrorSize(ErrorHorizSize, ErrorVertSize);

  if ParentChart.Aspect.Orthogonal or not ParentChart.View3D then
    DrawPointAndErrorWithOrder(true, false, false)
  else
  case ParentChart.Aspect.Rotation mod 360 of
    0..90:
    begin
      case ParentChart.Aspect.Elevation mod 360 of
        0..90: DrawPointAndErrorWithOrder(false, true, false);
        91..180: DrawPointAndErrorWithOrder(true, true, true);
        181..270: DrawPointAndErrorWithOrder(true, false, true);
        271..360: DrawPointAndErrorWithOrder(false, false, false);
      end;
    end;
    91..180:
    begin
      case ParentChart.Aspect.Elevation mod 360 of
        0..90: DrawPointAndErrorWithOrder(false, true, true);
        91..180: DrawPointAndErrorWithOrder(true, true, false);
        181..270: DrawPointAndErrorWithOrder(true, false, false);
        271..360: DrawPointAndErrorWithOrder(false, false, true);
      end;
    end;
    181..270:
    begin
      case ParentChart.Aspect.Elevation mod 360 of
        0..90: DrawPointAndErrorWithOrder(true, true, true);
        91..180: DrawPointAndErrorWithOrder(false, true, false);
        181..270: DrawPointAndErrorWithOrder(false, false, false);
        271..360: DrawPointAndErrorWithOrder(true, false, true);
      end;
    end;
    271..360:
    begin
      case ParentChart.Aspect.Elevation mod 360 of
        0..90: DrawPointAndErrorWithOrder(true, true, false);
        91..180: DrawPointAndErrorWithOrder(false, true, true);
        181..270: DrawPointAndErrorWithOrder(false, false, true);
        271..360: DrawPointAndErrorWithOrder(true, false, false);
      end;
    end;
  end;
end;

class function TCustomErrorPoint3DSeries.GetEditorClass: String;
begin
  result:='TErrorPoint3DSeriesEditor';
end;

function TCustomErrorPoint3DSeries.Clicked(x, y: Integer): Integer;
var
  ErrorHorizSize : Integer;
  ErrorVertSize  : Integer;
  P              : TPoint;
  ZPosPoint      : Integer;

  function ClickedError(valueIndex: Integer): Boolean;

    function ClickedHorizontal(const x0, x1, y, t: TCoordinate): Boolean;
    var P0, P1 : TPoint;
    begin
      result:=Assigned(ParentChart);

      if result then
      begin
        P0:=ParentChart.Canvas.Calculate3DPosition(x0,y,ZPosPoint);
        P1:=ParentChart.Canvas.Calculate3DPosition(x1,y,ZPosPoint);
        result:=PointInLine(P, P0, P1, {$IFDEF FMX}Round{$ENDIF}(t));
      end;
    end;

    function ClickedVertical(const x, y0, y1, t: TCoordinate): Boolean;
    var P0, P1 : TPoint;
    begin
      result:=Assigned(ParentChart);

      if result then
      begin
        P0:=ParentChart.Canvas.Calculate3DPosition(x,y0,ZPosPoint);
        P1:=ParentChart.Canvas.Calculate3DPosition(x,y1,ZPosPoint);
        result:=PointInLine(P, P0, P1, {$IFDEF FMX}Round{$ENDIF}(t));
      end;
    end;

  var XPosPoint : Integer;
      YPosPoint : Integer;

    function ClickedTopError(errValue: Double; APen: TChartPen): Boolean;
    begin
      Result:=(errValue>0) and (APen.Visible) and
               (ClickedHorizontal(XPosPoint - ErrorHorizSize, XPosPoint + ErrorHorizSize,
                                  CalcYPosValue(YValues[valueIndex] + errValue), APen.Width)
               or
               ClickedVertical(XPosPoint, YPosPoint - Pointer.VertSize,
                               CalcYPosValue(YValues[valueIndex] + errValue), APen.Width));
    end;

    function ClickedBottomError(errValue: Double; APen: TChartPen): Boolean;
    begin
      Result:=(errValue>0) and (APen.Visible) and
               (ClickedHorizontal(XPosPoint - ErrorHorizSize, XPosPoint + ErrorHorizSize,
                                  CalcYPosValue(YValues[valueIndex] - errValue), APen.Width)
               or
               ClickedVertical(XPosPoint, YPosPoint + Pointer.VertSize,
                               CalcYPosValue(YValues[valueIndex] - errValue), APen.Width));
    end;

    function ClickedLeftError(errValue: Double; APen: TChartPen): Boolean;
    begin
      Result:=(errValue>0) and (APen.Visible) and
               (ClickedHorizontal(XPosPoint - Pointer.HorizSize,
                                  CalcXPosValue(XValues[valueIndex] - errValue), YPosPoint, APen.Width)
              or
              ClickedVertical(CalcXPosValue(XValues[valueIndex] - errValue),
                              YPosPoint - ErrorVertSize, YPosPoint + ErrorVertSize, APen.Width));
    end;

    function ClickedRightError(errValue: Double; APen: TChartPen): Boolean;
    begin
      Result:=(errValue>0) and (APen.Visible) and
               (ClickedHorizontal(XPosPoint + Pointer.HorizSize,
                                  CalcXPosValue(XValues[valueIndex] + errValue), YPosPoint, APen.Width)
              or
              ClickedVertical(CalcXPosValue(XValues[valueIndex] + errValue),
                              YPosPoint - ErrorVertSize, YPosPoint + ErrorVertSize, APen.Width));
    end;

    function ClickedFrontError(errValue: Double; APen: TChartPen): Boolean;
    var errorDepth : Integer;
        PS         : TFourPoints;
    begin
      Result:=Assigned(ParentChart) and (errValue>0) and APen.Visible;
      if Result then
      begin
        errorDepth:=ParentChart.Axes.Depth.CalcSizeValue(errValue);

        PS[0]:=ParentChart.Canvas.Calculate3DPosition(XPosPoint,YPosPoint,ZPosPoint+Pointer.Depth);
        PS[1]:=ParentChart.Canvas.Calculate3DPosition(XPosPoint,YPosPoint,ZPosPoint+errorDepth);
        PS[2]:=ParentChart.Canvas.Calculate3DPosition(XPosPoint,YPosPoint-ErrorVertSize,ZPosPoint+errorDepth);
        PS[3]:=ParentChart.Canvas.Calculate3DPosition(XPosPoint,YPosPoint+ErrorVertSize,ZPosPoint+errorDepth);

        Result:=PointInLine(P, PS[0], PS[1], {$IFDEF FMX}Round{$ENDIF}(APen.Width)) or
                PointInLine(P, PS[2], PS[3], {$IFDEF FMX}Round{$ENDIF}(APen.Width));
      end;
    end;

    function ClickedBackError(errValue: Double; APen: TChartPen): Boolean;
    var errorDepth : Integer;
        PS         : TFourPoints;
    begin
      Result:=Assigned(ParentChart) and (errValue>0) and APen.Visible;
      if Result then
      begin
        ZPosPoint:=CalcZPos(valueIndex);
        errorDepth:=ParentChart.Axes.Depth.CalcSizeValue(errValue);

        PS[0]:=ParentChart.Canvas.Calculate3DPosition(XPosPoint,YPosPoint,ZPosPoint-Pointer.Depth);
        PS[1]:=ParentChart.Canvas.Calculate3DPosition(XPosPoint,YPosPoint,ZPosPoint-errorDepth);
        PS[2]:=ParentChart.Canvas.Calculate3DPosition(XPosPoint,YPosPoint-ErrorVertSize,ZPosPoint-errorDepth);
        PS[3]:=ParentChart.Canvas.Calculate3DPosition(XPosPoint,YPosPoint+ErrorVertSize,ZPosPoint-errorDepth);

        Result:=PointInLine(P, PS[0], PS[1], {$IFDEF FMX}Round{$ENDIF}(APen.Width)) or
                PointInLine(P, PS[2], PS[3], {$IFDEF FMX}Round{$ENDIF}(APen.Width));
      end;
    end;

  begin
    XPosPoint:=CalcXPos(valueIndex);
    YPosPoint:=CalcYPos(valueIndex);
    ZPosPoint:=CalcZPos(valueIndex);

    Result:=ClickedTopError(FErrors.FTop.Value[valueIndex], FErrors.FFormat.FTop) or
            ClickedBottomError(FErrors.FBottom.Value[valueIndex], FErrors.FFormat.FBottom) or
            ClickedLeftError(FErrors.FLeft.Value[valueIndex], FErrors.FFormat.FLeft) or
            ClickedRightError(FErrors.FRight.Value[valueIndex], FErrors.FFormat.FRight) or
            ClickedFrontError(FErrors.FFront.Value[valueIndex], FErrors.FFormat.FFront) or
            ClickedBackError(FErrors.FBack.Value[valueIndex], FErrors.FFormat.FBack);
  end;

var tmpIndex : Integer;
    t        : Integer;
begin
  result:=inherited Clicked(x,y);

  if (result=TeeNoPointClicked) and
     (FirstValueIndex>-1) and (LastValueIndex>-1) then
  begin
    P:=TeePoint(X,Y);
    tmpIndex:=Math.Min(LastValueIndex,Count-1);
    CalcErrorSize(ErrorHorizSize, ErrorVertSize);

    for t:=FirstValueIndex to tmpIndex do
    begin
      if ClickedError(t) then
      begin
        result:=t;
        exit;
      end;
    end;
  end;
end;

initialization
  RegisterTeeSeries( TErrorPointSeries, @TeeMsg_GalleryPointError,
                                   @TeeMsg_GalleryStats, 1 );
  RegisterTeeSeries( TErrorPoint3DSeries, @TeeMsg_GalleryPoint3DError,
                                   @TeeMsg_GalleryStats, 1 );
finalization
  UnRegisterTeeSeries([TErrorPointSeries, TErrorPoint3DSeries]);
end.
