{**********************************************}
{   TErrorSeries                               }
{   TErrorBarSeries (derived from TBarSeries)  }
{                                              }
{   Copyright (c) 1995-2020 by Steema Software }
{**********************************************}
unit ErrorBar;
{$I TeeDefs.inc}

interface

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

  {$IFDEF D18}
  System.Generics.Collections,
  {$ENDIF}     

     {$IFDEF D16}
     System.Types, System.UITypes,
     {$ENDIF}
     
     {$IFDEF FMX}
     FMX.Types,
     {$IFDEF D17}
     System.UIConsts,
     {$ENDIF}
     {$IFDEF D19}
     FMX.Graphics,
     {$ENDIF}

     {$ELSE}
     Graphics,
     {$ENDIF}

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

type
  TErrorSeriesStyle=( essLeft,essRight,essLeftRight,
                      essTop,essBottom,essTopBottom);

  TErrorWidthUnits=(ewuPercent,ewuPixels);

  TCustomErrorSeries=class(TBarSeries)
  private
    { Private declarations }
    FErrorPen        : TChartPen;
    FErrorStyle      : TErrorSeriesStyle;
    FErrorValues     : TChartValueList;
    FErrorWidth      : Integer;
    FErrorWidthUnits : TErrorWidthUnits;
    { internal }
    IDrawBar         : Boolean;
    Function GetErrorValue(Index:Integer):Double;
    Procedure PrepareErrorPen(ValueIndex:Integer);
    Procedure SetErrorStyle(Value:TErrorSeriesStyle);
    Procedure SetErrorValue(Index:Integer; Const Value:Double);
    Procedure SetErrorValues(Value:TChartValueList);
    Procedure SetErrorWidthUnits(Value:TErrorWidthUnits);
    Procedure SetErrorWidth(Value:Integer);
    procedure SetErrorPen(const Value: TChartPen);
  protected
    { Protected declarations }
    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 DrawError(const X,Y,AWidth,AHeight,AHeight2:TCoordinate; Draw3D:Boolean);
    Procedure DrawLegendShape(ValueIndex:Integer; Const Rect:TRect); override;
    class Function GetEditorClass:String; override;
    Procedure PrepareForGallery; override;
    Procedure PrepareLegendCanvas( ValueIndex:Integer; Var BackColor:TColor;
                                   Var BrushStyle:{$IFDEF FMX}TBrushKind{$ELSE}TBrushStyle{$ENDIF}); override;
    Procedure SetSeriesColor(AColor:TColor); override;
    class Function SubGalleryStack:Boolean; override;
  public
    { Public declarations }
    Constructor Create(AOwner: TComponent); override;
    Destructor Destroy; override;

    Function AddErrorBar(Const AX,AY,AError:Double;
                         Const AXLabel:String='';
                         AColor:TColor=clTeeColor):Integer;
    Procedure DrawBar(BarIndex,StartPos,EndPos:Integer); override;
    Function MinYValue:TChartValue; override;
    Function MaxYValue:TChartValue; override;
    Function Clicked(x,y:Integer):Integer; override;
    property ErrorValue[Index:Integer]:Double read GetErrorValue
                                              write SetErrorValue;

    { To be published declarations }
    property ErrorPen:TChartPen read FErrorPen write SetErrorPen;
    property ErrorStyle:TErrorSeriesStyle read FErrorStyle write SetErrorStyle
                                          default essTopBottom;
    property ErrorValues:TChartValueList read FErrorValues write SetErrorValues;
    property ErrorWidth:Integer read FErrorWidth write SetErrorWidth default 100;
    property ErrorWidthUnits:TErrorWidthUnits read FErrorWidthUnits
                                              write SetErrorWidthUnits default ewuPercent;
  end;

  TErrorSeries=class(TCustomErrorSeries)
  public
  published
    property ErrorPen;
    property ErrorStyle;
    property ErrorValues;
    property ErrorWidth;
    property ErrorWidthUnits;
  end;

  // Only supported ErrorStyle for ErrorBar series is essTop
  // When the bar is bottom-up (bar Value is lower then bar Origin), then
  // the error is displayed at bottom.

  TErrorBarSeries=class(TCustomErrorSeries)
  protected
    Procedure PrepareForGallery; override;
  public
    Constructor Create(AOwner:TComponent); override;
  published
    property ErrorPen;
    property ErrorValues;
    property ErrorWidth;
    property ErrorWidthUnits;
  end;

  TBaseHighLowSeries=class(TChartSeries)
  private
    FLow     : TChartValueList;

    function GetHigh: TChartValueList;
    procedure SetHigh(const Value: TChartValueList);
    procedure SetLow(const Value: TChartValueList);
  protected
    Procedure AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False); override;
  public
    Constructor Create(AOwner: TComponent); override;

    Function AddHighLow(Const AX,AHigh,ALow:Double;
                        Const AXLabel:String='';
                         AColor:TColor=clTeeColor):Integer;
    Function IsValidSourceOf(Value:TChartSeries):Boolean; override;
    Function MaxYValue:TChartValue; override;
    Function MinYValue:TChartValue; override;
  published
    property HighValues:TChartValueList read GetHigh write SetHigh;
    property LowValues:TChartValueList read FLow write SetLow;

    property Active;
    property ColorEachPoint;
    property ColorSource;
    property Cursor;
    property Depth;
    property HorizAxis;
    property Marks;
    property ParentChart;
    { datasource below parentchart }
    property DataSource;
    property Pen;
    property PercentFormat;
    property SeriesColor;
    property ShowInLegend;
    property Title;
    property ValueFormat;
    property VertAxis;
    property XLabelsSource;
    property XValues;

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

  THighLowPoints=Array of TPoint;

  THighLowPolygon=class
  private
    IPoints  : THighLowPoints;
    FIndex   : Integer;
    FIsUpValue  : Boolean;
  protected
  public
    property Polygon:THighLowPoints read IPoints write IPoints;
    property RegionIndex:Integer read FIndex write FIndex;
    property IsUpValue:Boolean read FIsUpValue write FIsUpValue;
  end;

  TTeeHighLowPolygon=class(TList{$IFDEF D18}<THighLowPolygon>{$ENDIF})
  public
    procedure Clear; {$IFNDEF D18}override;{$ENDIF}
  end;

  THighLowSeries=class(TBaseHighLowSeries)
  private
    FHighPen      : TChartPen;
    FLowPen       : TChartPen;
    FLowBrush     : TTeeBrush;
    FPHighLow     :  TTeeHighLowPolygon;

    OldX  : Integer;
    OldY0 : Integer;
    OldY1 : Integer;

    function GetHighBrush: TChartBrush;
    procedure SetHighBrush(const Value: TChartBrush);
    procedure SetHighPen(const Value: TChartPen);
    procedure SetLowBrush(const Value: TChartBrush);
    procedure SetLowPen(const Value: TChartPen);
    function InterpolateLines(vList: Integer; xVal:Double) : Double;
  protected
    Procedure AssignFormatting(Source:TChartSeries); override;
    class Procedure CreateSubGallery(AddSubChart:TChartSubGalleryProc); override;
    Procedure DrawValue(ValueIndex:Integer); override;
    Procedure DoBeforeDrawValues; override;
    class Function GetEditorClass:String; override;
    class Procedure SetSubGallery(ASeries:TChartSeries; Index:Integer); override;
  public
    Constructor Create(AOwner: TComponent); override;
    Destructor Destroy; override;

    function Clicked(x, y: Integer): Integer; override;
  published
    property HighBrush:TChartBrush read GetHighBrush write SetHighBrush;
    property HighPen:TChartPen read FHighPen write SetHighPen;
    property LowBrush:TChartBrush read FLowBrush write SetLowBrush;
    property LowPen:TChartPen read FLowPen write SetLowPen;
    property Polygons:TTeeHighLowPolygon read FPHighLow write FPHighLow;
    property Transparency;
  end;

implementation

Uses Math,

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

{ TCustomErrorSeries }
Constructor TCustomErrorSeries.Create(AOwner: TComponent);
Begin
  inherited;
  IDrawBar:=False;
  FErrorPen:=CreateChartPen;
  FErrorValues:=TChartValueList.Create(Self,TeeMsg_ValuesStdError); { <-- Std Error storage }
  FErrorStyle:=essTopBottom;
  FErrorWidth:=100;
  FErrorWidthUnits:=ewuPercent;
  Marks.Hide;
end;

Destructor TCustomErrorSeries.Destroy;
begin
  FErrorPen.Free;
  inherited;
end;

Procedure TCustomErrorSeries.CalcHorizMargins(Var LeftMargin,RightMargin:Integer);
begin
  inherited;
  if (FErrorStyle=essLeft) or (FErrorStyle=essLeftRight) then
     LeftMargin  :=Math.Max(LeftMargin, {$IFDEF FMX}Round{$ENDIF}(ErrorPen.Width));
  if (FErrorStyle=essRight) or (FErrorStyle=essLeftRight) then
     RightMargin :=Math.Max(RightMargin,{$IFDEF FMX}Round{$ENDIF}(ErrorPen.Width));
end;

Procedure TCustomErrorSeries.CalcVerticalMargins(Var TopMargin,BottomMargin:Integer);
begin
  inherited;
  if (FErrorStyle=essTop) or (FErrorStyle=essTopBottom) then
     TopMargin    :=Math.Max(TopMargin, {$IFDEF FMX}Round{$ENDIF}(ErrorPen.Width));
  if (FErrorStyle=essBottom) or (FErrorStyle=essTopBottom) then
     BottomMargin :=Math.Max(BottomMargin, {$IFDEF FMX}Round{$ENDIF}(ErrorPen.Width));
end;

Procedure TCustomErrorSeries.SetErrorPen(Const Value:TChartPen);
Begin
  FErrorPen.Assign(Value);
  if not IDrawBar then SeriesColor:=FErrorPen.Color;
End;

Procedure TCustomErrorSeries.PrepareErrorPen(ValueIndex:Integer);
begin
  With ParentChart.Canvas do
  begin
    if (ValueIndex<>TeeAllValues) and (not IDrawBar) then
       AssignVisiblePenColor(ErrorPen,ValueColor[ValueIndex])
    else
       AssignVisiblePen(ErrorPen);

    BackMode:=cbmTransparent;
  end;
end;

Procedure TCustomErrorSeries.SetErrorWidth(Value:Integer);
Begin
  SetIntegerProperty(FErrorWidth,Value);
End;

Procedure TCustomErrorSeries.DrawError(const X,Y,AWidth,AHeight,
              AHeight2:TCoordinate; Draw3D:Boolean);

var
  tmpW : TCoordinate;

  Procedure DrawHoriz(const XPos:TCoordinate);
  begin
    With ParentChart.Canvas do
    begin
      if Draw3D then
      begin
        HorizLine3D(X,XPos,Y,MiddleZ);
        VertLine3D(XPos,Y-tmpW,Y+tmpW,MiddleZ);
      end
      else
      begin
        DoHorizLine(X,XPos,Y);
        DoVertLine(XPos,Y-tmpW,Y+tmpW);
      end;
    end;
  end;

  Procedure DrawVert(const YPos:TCoordinate);
  begin
    With ParentChart.Canvas do
    begin
      if Draw3D then
      begin
        VertLine3D(X,Y,YPos,MiddleZ);
        HorizLine3D(X-tmpW,X+tmpW,YPos,MiddleZ);
      end
      else
      begin
        DoVertLine(X,Y,YPos);
        DoHorizLine(X-tmpW,X+tmpW,YPos);
      end;
    end;
  end;

begin
  tmpW:=AWidth {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF};

  Case FErrorStyle of
    essLeft     : DrawHoriz(X-AHeight);
    essRight    : DrawHoriz(X+AHeight2);

    essLeftRight: begin
                    DrawHoriz(X-AHeight);
                    DrawHoriz(X+AHeight2);
                  end;

    essTop      : DrawVert(Y-AHeight);
    essBottom   : DrawVert(Y+AHeight2);

    essTopBottom: begin
                    DrawVert(Y-AHeight);
                    DrawVert(Y+AHeight2);
                  end;
  end;
end;

Procedure TCustomErrorSeries.DrawBar(BarIndex,StartPos,EndPos:Integer);
var tmpError    : Double;
    tmpHeight   : Integer;
    tmpHeight2  : Integer;

  procedure CalcErrorSizes(const Axis:TChartAxis; const AValue:TChartValue);
  begin
    tmpHeight:=Axis.CalcSizeValue(AValue-tmpError,AValue);
    tmpHeight2:=Axis.CalcSizeValue(AValue,AValue+tmpError);

    if Axis.Inverted then
       SwapInteger(tmpHeight, tmpHeight2);
  end;

  procedure DoDrawError;
  var tmpXCenter,
      tmpWidth,
      tmpBarWidth : TCoordinate;
      tmp        : Integer;
  begin
    tmpXCenter:=(BarBounds.Right+BarBounds.Left) {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF};

    if MultiBar=mbStacked100 then // TV52010945
       tmp:=CalcYPosValue(100)
    else
       tmp:=CalcYPosValue(YValues.Value[BarIndex]);

    tmpBarWidth:=BarBounds.Right-BarBounds.Left;

    if FErrorWidth=0 then tmpWidth:=tmpBarWidth
    else
    if FErrorWidthUnits=ewuPercent then
       tmpWidth:=Round(1.0*FErrorWidth*tmpBarWidth*0.01)
    else
       tmpWidth:=FErrorWidth;

    PrepareErrorPen(BarIndex);
    DrawError(tmpXCenter,tmp,tmpWidth,tmpHeight,tmpHeight2,ParentChart.View3D);
  end;

begin
  tmpError:=FErrorValues.Value[BarIndex];

  if ErrorPen.Visible and (tmpError<>0) then
  begin
    { MS : simplified and allows vertical/horizontal style 5.01 }
    case FErrorStyle of
      essLeft,
      essRight,
      essLeftRight : CalcErrorSizes(GetHorizAxis,XValues.Value[BarIndex]);
    else
      CalcErrorSizes(GetVertAxis,YValues.Value[BarIndex]);
    end;

    if IDrawBar then
       inherited;

    DoDrawError;
  end
  else
  if IDrawBar then
     inherited;
end;

Procedure TCustomErrorSeries.SetErrorWidthUnits(Value:TErrorWidthUnits);
Begin
  if FErrorWidthUnits<>Value then
  Begin
    FErrorWidthUnits:=Value;
    Repaint;
  end;
end;

Procedure TCustomErrorSeries.SetErrorStyle(Value:TErrorSeriesStyle);
begin
  if FErrorStyle<>Value then
  begin
    FErrorStyle:=Value;
    Repaint;
  end;
end;

Procedure TCustomErrorSeries.SetErrorValues(Value:TChartValueList);
Begin
  SetChartValueList(FErrorValues,Value); { standard method }
End;

Function TCustomErrorSeries.AddErrorBar( Const AX,AY,AError:Double;
                                         Const AXLabel:String;
                                         AColor:TColor):Integer;
Begin
  FErrorValues.TempValue:=Abs(AError);
  result:=AddXY(AX,AY,AXLabel,AColor);
End;

Procedure TCustomErrorSeries.AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False);
Var t : Integer;
    s : TSeriesRandomBounds;
Begin
  s:=RandomBounds(NumValues);

  with s do
  for t:=1 to NumValues do
  Begin
    AddErrorBar( tmpX,
                 RandomValue(Round(DifY)),
                 DifY/(20+RandomValue(4)));

    tmpX:=tmpX+StepX;
  end;
end;

Function TCustomErrorSeries.MaxYValue:TChartValue;
Var t      : Integer;
    tmp    : TChartValue;
    tmpErr : TChartValue;
Begin
  if IDrawBar then result:=inherited MaxYValue else result:=0;

  for t:=0 to Count-1 do
  if IDrawBar then
  Begin
    tmpErr:=FErrorValues.Value[t];
    tmp:=YValues.Value[t];

    if tmp<0 then tmp:=tmp-tmpErr else tmp:=tmp+tmpErr;
    if tmp>result then result:=tmp;
  end
  else
  begin
    tmp:=YValues.Value[t];

    if ((ErrorStyle=essTop) and not GetVertAxis.Inverted) or
        (ErrorStyle=essTopBottom) then
      tmp:=tmp+FErrorValues.Value[t];

    if t=0 then
       result:=tmp
    else
       result:=Math.Max(result,tmp);
  end;
End;

Function TCustomErrorSeries.MinYValue:TChartValue;
Var t      : Integer;
    tmp    : TChartValue;
    tmpErr : TChartValue;
Begin
  if IDrawBar then result:=inherited MinYValue else result:=0;

  for t:=0 to Count-1 do
  if IDrawBar then
  Begin
    tmpErr:=FErrorValues.Value[t];
    tmp:=YValues.Value[t];

    if tmp<0 then tmp:=tmp-tmpErr else tmp:=tmp+tmpErr;
    if tmp<result then result:=tmp;
  end
  else
  begin
    tmp:=YValues.Value[t];

    if ((ErrorStyle=essBottom) and not GetVertAxis.Inverted) or
        (ErrorStyle=essTopBottom) then
      tmp:=tmp-FErrorValues.Value[t];

    if t=0 then
       result:=tmp
    else
       result:=Math.Min(result,tmp);
  end;
End;

Function TCustomErrorSeries.GetErrorValue(Index:Integer):Double;
Begin
  result:=FErrorValues.Value[Index];
End;

Procedure TCustomErrorSeries.SetErrorValue(Index:Integer; Const Value:Double);
Begin
  FErrorValues.Value[Index]:=Value;
End;

class Function TCustomErrorSeries.GetEditorClass:String;
Begin
  result:='TErrorSeriesEditor'; // Do not localize
end;

Procedure TCustomErrorSeries.AssignFormatting(Source:TChartSeries);
begin
  if Source is TCustomErrorSeries then
  With TCustomErrorSeries(Source) do
  begin
    Self.ErrorPen        :=FErrorPen;
    Self.FErrorStyle     :=FErrorStyle;
    Self.FErrorWidth     :=FErrorWidth;
    Self.FErrorWidthUnits:=FErrorWidthUnits;
  end;

  inherited;
end;

Procedure TCustomErrorSeries.PrepareForGallery;
begin
  inherited;
  ErrorPen.Color:={$IFDEF FMX}TAlphaColors.Red{$ELSE}clRed{$ENDIF};
  SeriesColor:={$IFDEF FMX}TAlphaColors.Blue{$ELSE}clBlue{$ENDIF};
end;

Procedure TCustomErrorSeries.SetSeriesColor(AColor:TColor);
begin
  inherited;
  if not IDrawBar then ErrorPen.Color:=AColor;
end;

Procedure TCustomErrorSeries.DrawLegendShape(ValueIndex:Integer; Const Rect:TRect);
begin
  With Rect do
    DrawError( (Left+Right) {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF},
               (Top+Bottom) {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF},
               Right-Left,((Bottom-Top) {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF})-1,
               ((Bottom-Top) {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF})-1,False);
end;

class Function TCustomErrorSeries.SubGalleryStack:Boolean;
begin
  result:=False; // Do not show stacked styles at sub-gallery
end;

procedure TCustomErrorSeries.PrepareLegendCanvas(ValueIndex: Integer;
  var BackColor: TColor; var BrushStyle: {$IFDEF FMX}TBrushKind{$ELSE}TBrushStyle{$ENDIF});
begin
  PrepareErrorPen(ValueIndex);
end;

function TCustomErrorSeries.Clicked(x, y: Integer): Integer;

  Function ClickedHorizontal(const P: TPoint; x0, x1, y: Integer): Boolean;
  begin
    result:=PointInLine(P, x0, y, x1, y, 3);
  end;

  Function ClickedVertical(const P: TPoint; x, y0, y1: Integer): Boolean;
  begin
    result:=PointInLine(P, x, y0, x, y1, 3);
  end;

var t           : Integer;
    P           : TPoint;
    tmpError    : Double;
    tmpBarWidth : Integer;
    tmpWidth    : Integer;
    checkTop    : Boolean;
    checkBottom : Boolean;
    checkLeft   : Boolean;
    checkRight  : Boolean;
    tmpIndex    : Integer;
Begin
  if Assigned(ParentChart) then
    ParentChart.Canvas.Calculate2DPosition(X,Y,StartZ);

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

    if IDrawBar then
      for t:=FirstValueIndex to tmpIndex do
        if InternalClicked(t,P) then
        begin
          result:=t;
          exit;
        end;

    tmpBarWidth:={$IFDEF FMX}Round{$ENDIF}(BarBounds.Right-BarBounds.Left);
    checkTop:=(FErrorStyle=essTop) or (FErrorStyle=essTopBottom);
    checkBottom:=(FErrorStyle=essBottom) or (FErrorStyle=essTopBottom);
    checkLeft:=(FErrorStyle=essLeft) or (FErrorStyle=essLeftRight);
    checkRight:=(FErrorStyle=essRight) or (FErrorStyle=essLeftRight);

    if FErrorWidth=0 then tmpWidth:=tmpBarWidth
    else
    if FErrorWidthUnits=ewuPercent then
       tmpWidth:=Round(1.0*FErrorWidth*tmpBarWidth*0.01)
    else
       tmpWidth:=FErrorWidth;

    if Assigned(ParentChart) then
      ParentChart.Canvas.Calculate2DPosition(X,Y,MiddleZ);

    P:=TeePoint(x,y);

    for t:=FirstValueIndex to tmpIndex do
    begin
      tmpError:=FErrorValues.Value[t];

      if checkTop then
      begin
        if ClickedHorizontal(P, CalcXPos(t), CalcXPos(t) + tmpWidth,
                            CalcYPosValue(YValues[t] + tmpError))
           or
           ClickedVertical(P, CalcXPos(t) + tmpWidth div 2, CalcYPos(t),
                            CalcYPosValue(YValues[t] + tmpError)) then
        begin
          result:=t;
          exit;
        end;
      end;

      if checkBottom then
      begin
        if ClickedHorizontal(P, CalcXPos(t), CalcXPos(t) + tmpWidth,
                            CalcYPosValue(YValues[t] - tmpError))
           or
           ClickedVertical(P, CalcXPos(t) + tmpWidth div 2, CalcYPos(t),
                            CalcYPosValue(YValues[t] - tmpError)) then
        begin
          result:=t;
          exit;
        end;
      end;

      if checkLeft then
      begin
        if ClickedHorizontal(P, CalcXPosValue(XValues[t] - tmpError) + tmpWidth div 2,
                            CalcXPos(t) + tmpWidth div 2, CalcYPos(t))
           or
           ClickedVertical(P, CalcXPosValue(XValues[t] - tmpError) + tmpWidth div 2,
                          CalcYPos(t) - tmpWidth div 2,
                          CalcYPos(t) + tmpWidth div 2) then
        begin
          result:=t;
          exit;
        end;
      end;

      if checkRight then
      begin
        if ClickedHorizontal(P, CalcXPos(t) + tmpWidth div 2,
                            CalcXPosValue(XValues[t] + tmpError) + tmpWidth div 2,
                            CalcYPos(t))
          or
          ClickedVertical(P, CalcXPosValue(XValues[t] + tmpError) + tmpWidth div 2,
                          CalcYPos(t) - tmpWidth div 2,
                          CalcYPos(t) + tmpWidth div 2) then
        begin
          result:=t;
          exit;
        end;
      end;
    end;
  end;

  result:=TeeNoPointClicked;
end;

{ TErrorBarSeries }
Constructor TErrorBarSeries.Create(AOwner: TComponent);
Begin
  inherited;
  IDrawBar:=True;
  FErrorStyle:=essTop;
end;

Procedure TErrorBarSeries.PrepareForGallery;
begin
  ErrorPen.Width:=2;
  inherited;
end;

{ TBaseHighLowSeries }
Constructor TBaseHighLowSeries.Create(AOwner: TComponent);
begin
  inherited;
  HighValues.Name:=TeeMsg_ValuesHigh;
  FLow:=TChartValueList.Create(Self,TeeMsg_ValuesLow);
end;

function TBaseHighLowSeries.AddHighLow(const AX, AHigh, ALow: Double;
                                       const AXLabel:String='';
                                       AColor:TColor=clTeeColor): Integer;
begin
  FLow.TempValue:=ALow;
  result:=AddXY(AX,AHigh,AXLabel,AColor); // TV52014025
end;

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

Function TBaseHighLowSeries.MaxYValue:TChartValue;
Begin
  result:=Math.Max(inherited MaxYValue,FLow.MaxValue);
End;

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

procedure TBaseHighLowSeries.AddSampleValues(NumValues: Integer; OnlyMandatory:Boolean=False);
Var t   : Integer;
    tmp : Double;
    s   : TSeriesRandomBounds;
Begin
  s:=RandomBounds(NumValues);
  with s do
  begin
    tmp:=RandomValue(Round(DifY));
    for t:=1 to NumValues do
    Begin
      tmp:=tmp+RandomValue(Round(DifY/5.0))-(DifY/10.0);
      if t mod 3 = 0 then    //show overlap effect
              AddHighLow(tmpX,{ X }
                 tmp, { High }
                 (tmp+Round(DifY/6))-RandomValue(Round(DifY/4))) { Low }
      else
        AddHighLow(tmpX,{ X }
                 tmp, { High }
                 tmp-RandomValue(Round(DifY/5.0))); { Low }
      tmpX:=tmpX+StepX;
    end;
  end;
end;

function TBaseHighLowSeries.GetHigh: TChartValueList;
begin
  result:=YValues;
end;

procedure TBaseHighLowSeries.SetHigh(const Value: TChartValueList);
begin
  SetChartValueList(YValues,Value);
end;

procedure TBaseHighLowSeries.SetLow(const Value: TChartValueList);
begin
  FLow.Assign(Value);
end;

{ THighLowSeries }
Constructor THighLowSeries.Create(AOwner: TComponent);
begin
  inherited;
  CalcVisiblePoints:=False;
  Pen.Color:=clTeeColor;
  FHighPen:=CreateChartPen;
  FLowPen:=CreateChartPen;
  FLowBrush:=TTeeBrush.Create(CanvasChanged);
  LowBrush.Style:=bsClear;
  HighBrush.Style:=bsClear;
  FPHighLow := TTeeHighLowPolygon.Create;
end;

Destructor THighLowSeries.Destroy;
begin
  FHighPen.Free;
  FLowPen.Free;
  FLowBrush.Free;
  FPHighLow.Clear;
  FPHighLow.Free;
  inherited;
end;

procedure THighLowSeries.AssignFormatting(Source: TChartSeries);
begin
  if Source is THighLowSeries then
  With THighLowSeries(Source) do
  begin
    Self.HighPen :=HighPen;
    Self.LowPen  :=LowPen;
    Self.LowBrush:=LowBrush;
  end;

  inherited;
end;

function THighLowSeries.Clicked(x, y: Integer): Integer;
var t  : Integer;
    X0 : Integer;
    P  : TPoint;
    PS : TFourPoints;
begin
  result:=TeeNoPointClicked;

  if (FirstValueIndex<>-1) and (LastValueIndex<>-1) then
  begin
    P:=TeePoint(x,y);

    for t:=FirstValueIndex to LastValueIndex do
    begin
      X0:=CalcXPos(t);

      PS[2]:=ParentChart.Canvas.Calculate3DPosition(X0,CalcYPosValue(FLow.Value[t]),MiddleZ);
      PS[3]:=ParentChart.Canvas.Calculate3DPosition(X0,CalcYPos(t),MiddleZ);

      if (t<>FirstValueIndex) and PointInPolygon(P,PS) then
      begin
        result:=t;
        break;
      end;

      PS[0]:=PS[3];
      PS[1]:=PS[2];
    end;
  end;
end;

Procedure THighLowSeries.DoBeforeDrawValues;
Var xMin,xMax,j,sY1,sY2, counter, polyItems : Integer;
    ys : Integer;
    tmpVal : Double;
    IsHigh,oldIsHigh : Boolean;
    HighPoints,LowPoints : THighLowPoints;
    tmpBrush : TChartBrush;
    tmpColor : TColor;
    tmpBlend : TTeeBlend;
    oldBrushStyle : {$IFDEF FMX}TBrushKind{$ELSE}TBrushStyle{$ENDIF};

  function MakeLastPolygon(Index: Integer; aHighPoints,aLowPoints : THighLowPoints; oldisHigh : Boolean): THighLowPolygon;
  var lengthFP,p : Integer;
      tmpHighLowPolygon : THighLowPolygon;
      FPHighLow : THighLowPoints;
  Begin
    //new THighLowPolygon object
    tmpHighLowPolygon := THighLowPolygon.Create;
    tmpHighLowPolygon.RegionIndex:=counter;

    //prepare size for THighLowPolygon Polygon
    lengthFP := Length(aHighPoints)+Length(aLowPoints);
    SetLength(FPHighLow,lengthFP);

    //populate THighLowPolygon object Polygon
    for p:=0 to Length(HighPoints)-1 do
    Begin
      TPoint(FPHighLow[p]):= TeePoint(aHighPoints[p].X,aHighPoints[p].Y);
    end;

    //LengthFP:=Length(FPHighLow);
    for p:=Length(LowPoints)-1 Downto 0 do
      TPoint(FPHighLow[lengthFP-p-1]) := TeePoint(aLowPoints[p].X,aLowPoints[p].Y);

    tmpHighLowPolygon.Polygon := FPHighLow;
    tmpHighLowPolygon.IsUpValue:=oldisHigh;
    inc(counter);

    result:=tmpHighLowPolygon;
  end;

  procedure ColorPolygon(xMin, xMax : Integer);
  var i : Integer;
  Begin
    sY1:=0;
    sY2:=0;

    for i:=0 to 1 do                //0:lowValues, 1:HighValues
    Begin
      tmpVal := InterpolateLines(i,GetHorizAxis.CalcPosPoint(j));
      ys := GetVertAxis.CalcPosValue(tmpVal);
      if (i=0) then
        sY1:=ys  //lowvalues
      else
        sY2:=ys;  //highvalues
    end;

    if ((GetHorizAxis.CalcPosPoint(j)>=XValues[0]) and //limit zone colouring to valid data area
         (GetHorizAxis.CalcPosPoint(j)<=XValues.MaxValue)) then
    begin
      if ((sY1>sY2) and (not self.MandatoryAxis.Inverted)) or ((sY1<sY2) and (self.MandatoryAxis.Inverted)) then   //[inverse] lower pixels = higher value
      Begin
        isHigh:=true;
        if isHigh<>oldIsHigh then
        Begin
          if length(HighPoints) > 0 then
            Polygons.Add(MakeLastPolygon(counter,HighPoints,LowPoints,oldisHigh));
          oldIsHigh:=isHigh;
          SetLength(HighPoints,0);
          SetLength(LowPoints,0);
        end
        else if j=xMin then
        Begin
          SetLength(HighPoints,0);
          SetLength(LowPoints,0);
        end;
      end
      else
      Begin
        isHigh:=false;
        if isHigh<>oldIsHigh then
        Begin
          if length(HighPoints) > 0 then
            Polygons.Add(MakeLastPolygon(counter,HighPoints,LowPoints,oldisHigh));
          oldIsHigh:=isHigh;
          SetLength(HighPoints,0);
          SetLength(LowPoints,0);
        end
        else if j=xMin then
        Begin
          SetLength(HighPoints,0);
          SetLength(LowPoints,0);
        end;
      end;

      SetLength(HighPoints,length(HighPoints)+1);
      SetLength(LowPoints,length(LowPoints)+1);

      if (sY1>sY2) then
      Begin
        HighPoints[length(HighPoints)-1]:=TeePoint(j,sY2);
        LowPoints[length(LowPoints)-1]:=TeePoint(j,sY1);
      end
      else
      Begin
        HighPoints[length(HighPoints)-1]:=TeePoint(j,sY1);
        LowPoints[length(LowPoints)-1]:=TeePoint(j,sY2);
      end;
    end;
  end;


Begin
  inherited;

  Polygons.Clear;
  counter := 0;

  ParentChart.Canvas.ClipRectangle(ParentChart.ChartRect);

  tmpColor:=clwhite;
  oldIsHigh:=False;

  xMin := Trunc(GetHorizAxis.CalcXPosValue(GetHorizAxis.Minimum));
  xMax := Round(GetHorizAxis.CalcXPosValue(GetHorizAxis.Maximum));

  if (GetHorizAxis.Inverted) then
  Begin
    for j:=xMin downto xMax-1 do
      ColorPolygon(xMin,xMax);
  end
  else
  Begin
    for j:=xMin to xMax-1 do
      ColorPolygon(xMin,xMax);
  end;

  if length(HighPoints) > 0 then
    Polygons.Add(MakeLastPolygon(Counter, HighPoints,LowPoints,oldisHigh));

  for polyItems:=0 to self.Polygons.Count-1 do
  Begin
    if (THighLowPolygon(Polygons[polyItems]).FIsUpValue) then
      tmpBrush:=HighBrush
    else
      tmpBrush:=LowBrush;

    oldBrushStyle:=bsClear;
    if ColorEachPoint then
    Begin
      oldBrushStyle := tmpBrush.Style;
      tmpBrush.Style:=bsClear;
    end;

    // set brush
    begin
      with ParentChart.Canvas do
      begin
        if Transparency>0 then
        begin
          if SupportsAlpha then
             tmpBlend:=BeginBlending(TeeZeroRect,Transparency)
          else
          begin
            tmpBlend:=BeginBlending(RectFromPolygon(THighLowPolygon(Polygons[polyItems]).Polygon,
                               Length(THighLowPolygon(Polygons[polyItems]).Polygon)),Transparency);
          end;
        end
        else tmpBlend:=nil;

        if tmpBrush.Style=bsSolid then
          tmpColor := tmpBrush.Color;

        AssignBrush(tmpBrush,tmpColor);
        Pen.Style:=psClear;  // no pen to draw polygon

        Polygon(THighLowPolygon(Polygons[polyItems]).Polygon);

        if Transparency>0 then EndBlending(tmpBlend);

      end;
    end;

    if ColorEachPoint then
      tmpBrush.Style := oldBrushStyle;

  end;

  ParentChart.Canvas.UnClipRectangle;
end;

procedure THighLowSeries.DrawValue(ValueIndex: Integer);
Var X : Integer;
    View3D : Boolean;

  Procedure DrawLine(APen:TChartPen; BeginY,EndY:Integer);
  begin
    if APen.Visible then
    With ParentChart.Canvas do
    begin
      AssignVisiblePen(APen);
      if View3D then LineWithZ(OldX,BeginY,X,EndY,MiddleZ)
                else Line(OldX,BeginY,X,EndY);
    end;
  end;

  procedure DrawValueLine(AColor:TColor);
  var tmp : TColor;
  begin
    tmp:=Pen.Color;
    if tmp=clTeeColor then tmp:=AColor;

    with ParentChart.Canvas do
    begin
      AssignVisiblePenColor(Self.Pen,tmp);
      if View3D then VertLine3D(OldX,OldY0,OldY1,MiddleZ)
                else DoVertLine(OldX,OldY0,OldY1);
    end;
  end;

var Y0   : Integer;
    Y1   : Integer;
    tmpBrush : TChartBrush;
    tmpColor : TColor;
    IPoints  : TFourPoints;
    P        : TFourPoints;
    tmpBlend : TTeeBlend;
    tmpFirst : Integer;
begin
  // calculate coordinates
  x:=CalcXPos(ValueIndex);
  y0:=CalcYPos(ValueIndex);
  y1:=CalcYPosValue(FLow.Value[ValueIndex]);

  tmpFirst:=FirstDisplayedIndex;
  if ValueIndex<>tmpFirst then
  begin
    tmpColor:=ValueColor[ValueIndex-1];
    View3D:=ParentChart.View3D;

    if (not IsNull(ValueIndex)) and (not IsNull(ValueIndex-1)) then
    begin

      // Determine brush to use (high or low)
      if (LowValues.Value[ValueIndex]<HighValues.Value[ValueIndex]) then
         tmpBrush:=HighBrush
      else
         tmpBrush:=LowBrush;

      // set brush
      if (tmpBrush.Style<>bsClear) and (self.ColorEachPoint) then
      begin

        P[0].X:=OldX;
        P[0].Y:=OldY0;
        P[1].X:=OldX;
        P[1].Y:=OldY1;
        P[2].X:=X;
        P[2].Y:=Y1;
        P[3].X:=X;
        P[3].Y:=Y0;

        with ParentChart.Canvas do
        begin
          if Transparency>0 then
          begin
            if SupportsAlpha then
               tmpBlend:=BeginBlending(TeeZeroRect,Transparency)
            else
            begin
              IPoints[0]:=Calculate3DPosition(P[0],MiddleZ);
              IPoints[1]:=Calculate3DPosition(P[1],MiddleZ);
              IPoints[2]:=Calculate3DPosition(P[2],MiddleZ);
              IPoints[3]:=Calculate3DPosition(P[3],MiddleZ);

              tmpBlend:=BeginBlending(RectFromPolygon(IPoints,4),Transparency);
            end;
          end
          else tmpBlend:=nil;

          AssignBrush(tmpBrush,tmpColor);
          Pen.Style:=psClear;  // no pen to draw polygon

          // draw plane
          if View3D then PlaneWithZ(P,MiddleZ)
                    else Polygon(P);

          if Transparency>0 then EndBlending(tmpBlend);
        end;
      end;

      // draw lines
      DrawLine(HighPen,OldY0,Y0);
      DrawLine(LowPen,OldY1,Y1);
    end;

    // draw vertical lines
    if Pen.Visible and (not IsNull(ValueIndex-1)) then
    begin
      DrawValueLine(tmpColor);

      if DrawValuesForward then tmpFirst:=LastValueIndex
                           else tmpFirst:=FirstValueIndex;
      if ValueIndex=tmpFirst then
      begin
        OldX:=X;
        OldY0:=Y0;
        OldY1:=Y1;
        DrawValueLine(ValueColor[ValueIndex]);
      end;
    end;
  end;

  OldX:=X;
  OldY0:=Y0;
  OldY1:=Y1;
end;

procedure THighLowSeries.SetHighPen(const Value: TChartPen);
begin
  FHighPen.Assign(Value);
end;

procedure THighLowSeries.SetLowPen(const Value: TChartPen);
begin
  FLowPen.Assign(Value);
end;

class function THighLowSeries.GetEditorClass: String;
begin
  result:='THighLowEditor'; // Do not localize
end;

function THighLowSeries.GetHighBrush: TChartBrush;
begin
  result:=Brush;
end;

procedure THighLowSeries.SetHighBrush(const Value: TChartBrush);
begin
  Brush:=Value;
end;

procedure THighLowSeries.SetLowBrush(const Value: TChartBrush);
begin
  FLowBrush.Assign(Value);
end;

function THighLowSeries.InterpolateLines(vList: Integer; xVal:Double) : Double;
Var yValues,tmpXValues : TChartValueList;
    len, i, index : Integer;
    dx,dy : Double;
Begin
  if vList=0 then
    yValues:=self.LowValues
  else
    yValues:=self.HighValues;

  len:=yValues.Count;

		  if (self.XValues.Count>0) then
			   tmpXValues:=XValues
		  else
      Begin
        tmpXValues := TChartValueList.Create(Self,'tmpXValues');
        SetLength(tmpXValues.Value,len);
			  for i:=0 to len-1 do
			    tmpXValues[i]:=i;
		  end;

		  for index:=0 to len-1 do
      Begin
			  if (xValues[index]>xVal) then
			    break;
		  end;

		  if (index<1) then
			  index:=1
		  else
			  if (index>=len) then
			    index:=len-1;

		  dx := tmpXValues[index] - tmpXValues[index-1];
		  dy := YValues[index] - YValues[index-1];

		  if dx<>0 then
			  result := dy*(xVal - tmpXValues[index-1])/dx + YValues[index-1]
		  else
			  result := 0;
end;

class procedure THighLowSeries.CreateSubGallery(
  AddSubChart: TChartSubGalleryProc);
begin
  inherited;
  AddSubChart(TeeMsg_Filled);
  AddSubChart(TeeMsg_NoLines);
  AddSubChart(TeeMsg_NoHigh);
  AddSubChart(TeeMsg_NoLow);
end;

class procedure THighLowSeries.SetSubGallery(ASeries: TChartSeries;
  Index: Integer);
begin
  With THighLowSeries(ASeries) do
  Case Index of
    1: Brush.Style:=bsSolid;
    2: Pen.Hide;
    3: HighPen.Hide;
    4: LowPen.Hide;
  else inherited;
  end;
end;

{ TTeeHighLowPolygon }
procedure TTeeHighLowPolygon.Clear;
var t : Integer;
begin
  if Assigned(self) then
  begin
    if Count > 0 then
      for t:=Count-1 downto 0 do
      Begin
          THighLowPolygon(self[t]).{$IFDEF D18}DisposeOf{$ELSE}Free{$ENDIF};
          //THighLowPolygon(self[t]).DisposeOf;
          self.Remove(self[t]);
      End;
  end;

  inherited Clear;
end;

initialization
  RegisterTeeSeries(TErrorBarSeries, @TeeMsg_GalleryErrorBar,
                                     @TeeMsg_GalleryStats,1);
  RegisterTeeSeries(TErrorSeries, @TeeMsg_GalleryError,
                                  @TeeMsg_GalleryStats,1);
  RegisterTeeSeries(THighLowSeries, @TeeMsg_GalleryHighLow,
                                    @TeeMsg_GalleryStats,1);
finalization
  UnRegisterTeeSeries( [TErrorBarSeries,TErrorSeries,THighLowSeries]);
end.
