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

interface

uses {$IFDEF LCL}
     LCLIntf, LMessages, LCLType,
     {$ENDIF}

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

     SysUtils, Classes,

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

     {$IFDEF FMX}
     System.UITypes, System.Types, FMX.Types, FMX.Controls,

     {$IFDEF D19}
     FMX.Graphics,
     {$ENDIF}

     {$ELSE}

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

     Graphics, Controls,
     {$ENDIF}

     {$IFDEF TEETRIAL}
     TeeAbout,
     {$ENDIF}

     {$IFDEF FMX}
     FMXTee.Procs, FMXTee.Canvas
     {$ELSE}
     {$IFDEF D16}
     VCLTee.TeeProcs, VCLTee.TeCanvas
     {$ELSE}
     TeeProcs, TeCanvas
     {$ENDIF}
     {$ENDIF};

{$IFDEF TEEVALUEEXTENDED}
{$DEFINE TEENOASM} // ASM works only for Single and Double, not for Extended.
{$ENDIF}

Const ChartMarkColor   = {$IFDEF FMX}$FFFFFFE1{$ELSE}$E1FFFF{$ENDIF}; { default Series Mark back color }

      MinAxisIncrement :TAxisValue = {$IFDEF TEEVALUESINGLE}
                                     1.40129846432482e-45
                                     {$ELSE}
                                     {$IFDEF WIN64}
                                     1.40129846432482e-45
                                     {$ELSE}
                                     4.94065645841247e-324  // MinDouble
                                     {$ENDIF}
                                     {$ENDIF}; // Epsilon 0.000000000001;  { <-- "double" for BCB }

      MinAxisRange     :TAxisValue = 0.0000000001;    { <-- "double" for BCB }
      DTMinValue       :Double = -657435;         { TDateTime minimum value - 1 }
      DTMaxValue       :Double = 2958466;         { TDateTime maximum value + 1 }
      TeeAllValues     = -1;

      {$IFDEF D6}
      clTeeColor   = TColor(clDefault);
      {$ELSE}
      clTeeColor   = TColor($20000000);
      {$ENDIF}

      ChartSamplesMax   = 1000;
      TeeAutoZOrder     = -1;
      TeeAutoDepth      = -1;
      TeeNoPointClicked = -1;
      TeeDef3DPercent   = 15;

      TeeColumnSeparator: Char = #6;  // To separate columns in Legend

      // Index of first custom axis (0 to 5 are pre-created axes: Left,Top,
      // Right,Bottom,Depth and DepthTop.
      TeeInitialCustomAxis = 6;

      TeeDefaultLabelsSeparation = 10;
      TeeDefaultLabelsMargin = 10;

var   TeeCheckMarkArrowColor : Boolean=False; // when True, the Marks arrow pen
                                              // color is changed if the point has
                                              // the same color.

      TeeRandomAtRunTime     : Boolean=False; // adds random values at run-time too

      clTeeGallery1:Integer = 0; // index of ColorPalette[] global variable
      clTeeGallery2:Integer = 3; // index of ColorPalette[] global variable

{$IFNDEF D16}
{$DEFINE TEELISTARRAY}
{$ENDIF}

type
  TCustomAxisPanel=class;

  TCustomChartElement=class(TComponent)
  private
    FActive       : Boolean;
    FBrush        : TTeeBrush;
    FPen          : TTeePen;
    FShowInEditor : Boolean;

    {$IFDEF AUTOREFCOUNT}[weak]{$ENDIF}
    FTagObject  : TObject;
  protected
    FParent       : TCustomAxisPanel;

    DontSerialize : Boolean;
    IInternalUse  : Boolean;

    Procedure CanvasChanged(Sender:TObject); virtual;
    Function CreateChartPen:TTeePen;

    class Function GetEditorClass:String; virtual;

    Procedure SetActive(Value:Boolean); virtual;
    Procedure SetBooleanProperty(var Variable:Boolean; Value:Boolean); {$IFDEF D9}inline;{$ENDIF}
    procedure SetBrush(const Value: TChartBrush);
    Procedure SetColorProperty(var Variable:TColor; Value:TColor);
    Procedure SetDoubleProperty(var Variable:Double; Const Value:Double);
    Procedure SetIntegerProperty(var Variable:Integer; Value:Integer);
    Procedure SetParentChart(Const Value:TCustomAxisPanel); virtual;
    procedure SetParentComponent(AParent: TComponent); override;
    procedure SetPen(const Value: TTeePen); virtual;
    Procedure SetSingleProperty(var Variable:Single; Const Value:Single);
    Procedure SetStringProperty(var Variable:String; Const Value:String);
  public
    Constructor Create(AOwner: TComponent); override;
    Destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;

    class Function EditorClass:String;  // Returns class name of editor form

    Function GetParentComponent: TComponent; override;
    Function HasParent:Boolean; override;

    Procedure Repaint;

    property Active:Boolean read FActive write SetActive default True;
    property Brush:TChartBrush read FBrush write SetBrush;
    property ParentChart:TCustomAxisPanel read FParent write SetParentChart stored False;
    property Pen:TTeePen read FPen write SetPen;

    property TagObject:TObject read FTagObject write FTagObject;

    // Alias for Active property.
    property Visible:Boolean read FActive write SetActive default True;

  published
    property ShowInEditor:Boolean read FShowInEditor write FShowInEditor default True;
  end;

  TCustomChartSeries=class;

  TChartSeries=class;
  
  TThemedSeries=class;

  {$IFDEF TEEVALUESINGLE}
  TChartValue=Single;
  {$ELSE}
  {$IFDEF TEEVALUEDOUBLE}
  TChartValue=Double;
  {$ELSE}
  {$IFDEF TEEVALUEEXTENDED}
  TChartValue=Extended;
  {$ELSE}
  TChartValue=Double;  { <-- default }
  {$ENDIF}
  {$ENDIF}
  {$ENDIF}
  
  {$IFDEF TEEARRAY}
  TChartValues=Array of TChartValue;
  {$ELSE}
  TChartValues=Array of TChartValue;
  PChartValue=^TChartValue;
  {$ENDIF}

  TChartListOrder=(loNone,loAscending,loDescending);

  TChartValueList=class(TPersistent)
  private
    FCount       : Integer;
    FDateTime    : Boolean;

    {$IFNDEF TEEARRAY}
    FList        : TList;
    {$ENDIF}

    FMaxValue    : TChartValue;
    FMinValue    : TChartValue;

    {$IFDEF TEEMULTIPLIER}
    FMultiplier  : Double;  { obsolete }
    {$ENDIF}

    FName        : String;
    FOrder       : TChartListOrder;

    {$IFDEF AUTOREFCOUNT}[weak]{$ENDIF}
    FOwner       : TChartSeries;

    FTempValue   : TChartValue;
    FTotal       : TChartValue;
    FTotalABS    : TChartValue;
    FValueSource : String;

    { internal }
    IDefDateTime : Boolean;

    {$IFOPT C+}
    function GetCount:Integer;
    procedure SetCount(const Value:Integer);
    {$ENDIF}

    Function CompareValueIndex(a,b:Integer):Integer;
    Function GetMaxValue:TChartValue;
    Function GetMinValue:TChartValue;
    Function GetTotal:TChartValue;
    Function GetTotalABS:TChartValue;
    function IsDateStored: Boolean;
    procedure SetDateTime(Const Value:Boolean);

    procedure SetModified(const Value:Boolean);

    {$IFDEF TEEMULTIPLIER}
    Function IsMultiStored:Boolean;
    Procedure SetMultiplier(Const Value:Double); { obsolete }
    {$ELSE}
    procedure ReadMultiplier(Reader: TReader);
    {$ENDIF}

    Procedure SetValueSource(Const Value:String);
  protected
    FModified : Boolean;
    IData     : TObject;
    Internal  : Boolean; // When True, do not show at TChartGrid

    Function AddChartValue:Integer; overload;
    Function AddChartValue(Const AValue:TChartValue):Integer; overload; virtual;
    Procedure ClearValues; virtual;

    {$IFNDEF TEEMULTIPLIER}
    procedure DefineProperties(Filer: TFiler); override;
    {$ENDIF}

    Function GetValue(ValueIndex:Integer):TChartValue;
    Procedure InitDateTime(Value:Boolean);
    Procedure InsertChartValue(ValueIndex:Integer; Const AValue:TChartValue); virtual;
    Procedure RecalcStats; overload;
    procedure RecalcStats(StartIndex:Integer); overload;
    procedure RecalcStats(const AMin,AMax,ATotal,ATotalABS:TChartValue); overload;
    Procedure SetValue(ValueIndex:Integer; Const AValue:TChartValue);
  public
    {$IFDEF TEEARRAY}
    Value    : TChartValues;
    {$ENDIF}

    Constructor Create(AOwner:TChartSeries; Const AName:String); virtual;
    Destructor Destroy; override;

    Procedure Assign(Source:TPersistent); override;

    {$IFNDEF TEEARRAY}
    Function Count:Integer; virtual;
    {$ELSE}
    property Count:Integer read {$IFOPT C+}GetCount{$ELSE}FCount{$ENDIF}
                           write {$IFOPT C+}SetCount{$ELSE}FCount{$ENDIF};
    {$ENDIF}

    Procedure Delete(ValueIndex:Integer); overload; virtual;
    Procedure Delete(Start,Quantity:Integer); overload;
    {$IFDEF TEEARRAY}
    Procedure Exchange(Index1,Index2:Integer);
    {$ENDIF}
    Procedure FillSequence;
    Function First:TChartValue;
    Function Last:TChartValue;
    Function Locate(Const AValue:TChartValue):Integer; overload;
    Function Locate(Const AValue:TChartValue; FirstIndex,LastIndex:Integer):Integer; overload;
    Function Range:TChartValue;
    Procedure Scroll; dynamic;
    Procedure Sort;

    // Renamed from ToString to ValueToString (RAD 2008)
    Function ValueToString(const Index:Integer):String;

    {$IFNDEF D12}
    // Compatibility with pre RAD 2008:
    Function ToString(const Index:Integer):String;
    {$ENDIF}

    property MaxValue:TChartValue read GetMaxValue;
    property MinValue:TChartValue read GetMinValue;
    property Modified:Boolean read FModified write SetModified;
    property Owner:TChartSeries read FOwner;
    property TempValue:TChartValue read FTempValue write FTempValue;
    property Total:TChartValue read GetTotal;
    property TotalABS:TChartValue read GetTotalABS write FTotalABS;

    {$IFDEF TEEARRAY}
    property Items[Index:Integer]:TChartValue read GetValue write SetValue; default;
    {$ELSE}
    property Value[Index:Integer]:TChartValue read GetValue write SetValue; default;
    {$ENDIF}
  published
    property DateTime:Boolean read FDateTime write SetDateTime stored IsDateStored;
    property Name:String read FName write FName;

    {$IFDEF TEEMULTIPLIER}
    property Multiplier:Double read FMultiplier write SetMultiplier stored IsMultiStored; { obsolete }
    {$ENDIF}

    property Order:TChartListOrder read FOrder write FOrder;
    property ValueSource:String read FValueSource write SetValueSource;
  end;

  TMarginUnits=(maPercentFont, maPercentSize, maPixels);

  TMargins=class(TPersistent)
  private
    FLeft   : Integer;
    FTop    : Integer;
    FRight  : Integer;
    FBottom : Integer;
    FUnits  : TMarginUnits;

    DefaultHoriz,
    DefaultVert : Integer;

    ILast   : TRect;
    IParent : TTeeCustomShapeBrushPen;

    function IsLeftStored:Boolean;
    function IsRightStored:Boolean;
    function IsTopStored:Boolean;
    function IsBottomStored:Boolean;

    procedure SetBottom(const Value: Integer);

    procedure SetDefaultHoriz(const Value:Integer);
    procedure SetDefaultVert(const Value:Integer);

    procedure SetIntegerProperty(var Variable: Integer; const Value: Integer);
    procedure SetLeft(const Value: Integer);
    procedure SetRight(const Value: Integer);
    procedure SetTop(const Value: Integer);
    procedure SetUnits(const Value: TMarginUnits);
  public
    Constructor Create(const AParent:TTeeCustomShapeBrushPen);

    procedure Assign(Source:TPersistent); override;
    procedure Calculate(const Width,Height,FontSize:Integer);
    function HorizSize:Integer;
    procedure Reset;
    function VertSize:Integer;

    property Size:TRect read ILast;
  published
    property Left:Integer read FLeft write SetLeft stored IsLeftStored;
    property Top:Integer read FTop write SetTop stored IsTopStored;
    property Right:Integer read FRight write SetRight stored IsRightStored;
    property Bottom:Integer read FBottom write SetBottom stored IsBottomStored;
    property Units:TMarginUnits read FUnits write SetUnits default maPercentFont;
  end;

  TCustomTextShape=class(TTeeCustomShape)
  private
    FClipText  : Boolean;
    FCursor    : TCursor;
    FMargins   : TMargins;
    FTextAlign : TAlignment;
    FTextFormat: TTextFormat;
    FVertTextAlign: TVertTextAlign;

    Procedure DoDrawText(const Panel:TCustomTeePanel; const Rect:TRect; Angle:Integer=0; AZ:Integer=0);
    function IsTextAlignStored:Boolean;

    procedure SetClipText(const Value: Boolean);
    procedure SetMargins(const Value: TMargins);
    procedure SetText(const Value: String);
    procedure SetTextAlign(const Value: TAlignment);
    procedure SetTextFormat(const Value:TTextFormat);
    procedure SetVertTextAlign(const Value: TVertTextAlign);
  protected
    FText      : String;

    IDefaultTextAlign : TAlignment;
    IDraw2D : Boolean;
    ITextOffset : Integer; // <-- TAnnotationTool

    INumLines  : Integer;

    procedure CalcBounds(const Panel:TCustomTeePanel); override;
    procedure CalcTextAlign(const Panel:TCustomTeePanel;
                            out X: TCoordinate;
                            const R: TRect; AText:String); virtual;
    function CalcTextSize(const Panel:TCustomTeePanel; S:String;
                          const TextFormat:TTextFormat; out NumLines:Integer):TPoint; virtual;

    procedure ChangedText; virtual;

    Procedure DoDraw(const Panel:TCustomTeePanel; const Rect:TRect; Angle:Integer=0; AZ:Integer=0); override;
    Procedure DoDraw(const Panel:TCustomTeePanel; const Points:TPointArray;
                     const Rect : TRect; Angle:Integer=0; AZ:Integer=0); override;

    procedure DrawString(const Panel:TCustomTeePanel;
                         const X:TCoordinate;
                         const Y,Z:Integer;
                         const Text:String;
                         AllowHTML:Boolean;
                         AAngle:Integer;
                         const RotationCenter:TPoint); virtual;

    function FirstChildPos:Integer; override;
    function GetChildClass:TTeeCustomShapeClass; override;
    class function GetEditorClass:String; override;
    procedure InitTextAlignment(const Value:TAlignment);
  public
    Constructor Create(const AOwner: TCustomTeePanel); override;
    Destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;

    property Alignment;
    property AutoSize;
    property Bevel;
    property BevelWidth;
    property Brush;
    property Children;
    property ChildLayout;
    property Color;
    property Emboss;
    property Font;
    property Frame;
    property Gradient;
    property Picture;
    property Shadow;
    property ShapeCallout;
    property ShapeStyle;
    property Transparency;
    property Transparent;
    property Visible;

    property ClipText:Boolean read FClipText write SetClipText default True;
    property Cursor:TCursor read FCursor write FCursor default crDefault;
    property Margins:TMargins read FMargins write SetMargins;
    property Text:String read FText write SetText;
    property TextAlignment:TAlignment read FTextAlign
                                      write SetTextAlign stored IsTextAlignStored;
    property TextFormat:TTextFormat read FTextFormat write SetTextFormat default ttfNormal;
    property VertTextAlign:TVertTextAlign read FVertTextAlign write SetVertTextAlign default vtaCenter;
  end;

  TAxisTitlePosition=(tpStart, tpCenter, tpEnd);

  TChartAxisTitle=class(TCustomTextShape)
  private
    FPosition     : TAxisTitlePosition;

    IDefaultAngle : Integer;

    function GetCaption:String;
    Function IsAngleStored:Boolean;
    Procedure SetCaption(Const Value:String);
    Procedure SetPosition(Const Value:TAxisTitlePosition);
  public
    Constructor Create(const AOwner: TCustomTeePanel); override;

    Procedure Assign(Source:TPersistent); override;

    function Clicked(x,y:Integer):Boolean; overload;
    Function Clicked(const P:TPoint):Boolean; overload;
  published
    property Angle stored IsAngleStored;
    property Caption:String read GetCaption write SetCaption;
    property Font;
    property Position:TAxisTitlePosition read FPosition write SetPosition default tpCenter;
    property TextAlignment default taCenter;
    property Visible default True;

    // TV52012115
    property Bevel;
    property BevelWidth;
    property Brush;
    property Children;
    property ChildLayout;
    property Color;
    property Gradient;
    property Picture;
    property Emboss;
    property Pen;
    property RoundSize;
    property Shadow;
    property ShapeCallout;
    property ShapeStyle;
    property TextFormat;
    property Transparency;
    property Transparent default True;
  end;

  AxisException=class(Exception);

  TAxisLabelStyle=(talAuto,talNone,talValue,talMark,talText,talPointValue);
  TAxisLabelAlign=(alDefault,alOpposite,alCenter);

  TAxisCalcPos=function(const Value:TChartValue):Integer of object;
  TAxisCalcPosPoint=function(const Value:Integer):TAxisValue of object;

  TCustomSeriesList=class;

  TAxisGridPen=class(TTeePen)
  private
    FCentered  : Boolean;
    FDrawAlways: Boolean;
    FDrawEvery : Integer;
    FZ         : Double;

    IDefaultZ : Double;

    function IsZStored:Boolean;
    procedure ReadZZero(Reader: TReader);
    procedure SetCentered(const Value: Boolean);
    procedure SetDrawAlways(const Value: Boolean);
    procedure SetDrawEvery(const Value: Integer);
    procedure SetZ(const Value:Double);
    procedure WriteZZero(Writer: TWriter);
  protected
    procedure DefineProperties(Filer: TFiler); override;
  public
    Constructor Create(const OnChangeEvent:TNotifyEvent);

    Procedure Assign(Source:TPersistent); override;

    property Centered:Boolean read FCentered write SetCentered default False;
  published
    property DrawAlways:Boolean read FDrawAlways write SetDrawAlways default False;
    property Color default {$IFDEF FMX}TAlphaColors.LtGray{$ELSE}clLtGray{$ENDIF};
    property DrawEvery:Integer read FDrawEvery write SetDrawEvery default 1;
    property Width {$IFNDEF FMX}default 1{$ENDIF};
    property ZPosition:Double read FZ write SetZ stored IsZStored;
  end;

  TAxisLabelsFormat=class(TCustomTextShape)
  published
    property Bevel;
    property BevelWidth;
    property Brush;
    property Children;
    property ChildLayout;
    property Emboss;
    property Font;
    property Frame;
    property Gradient;
    property Picture;
    property RoundSize;
    property Shadow;
    property ShapeStyle;
    property Transparency;
    property Transparent;
    property Visible default True;

    property ClipText;
    property Cursor;
    property Margins;

    // Note: The "Text" property is not published here, as its calculated by the Axis

    property TextAlignment default taCenter;
    property TextFormat;
    property VertTextAlign;
  end;

  TChartAxis=class;

  TAxisItems=class;

  TAxisItem=class(TPersistent)
  private
    FFormat   : TAxisLabelsFormat;
    FLabelPos : Integer;
    FPicture  : TTeePicture;
    FText     : String;
    FValue    : TAxisValue;

    IAxisItems : TAxisItems;

    function GetFormat:TAxisLabelsFormat;
    procedure SetFormat(const Value:TAxisLabelsFormat);
    procedure SetText(const Value: String);
    procedure SetValue(const Value: TAxisValue);
  protected
    function ActiveFormat:TAxisLabelsFormat;
  public
    Destructor Destroy; override;

    procedure Repaint;

    property LabelPos:Integer read FLabelPos write FLabelPos;
    property Picture:TTeePicture read FPicture;
  published
    property Format:TAxisLabelsFormat read GetFormat write SetFormat;
    property Text:String read FText write SetText;
    property Value:TAxisValue read FValue write SetValue;
  end;

  TTeeHover=class(TCustomTextShape)
  public
    Constructor Create(const AOwner: TCustomTeePanel); override;
  published
    // property Pen; // <-- pending
    property Visible;
  end;

  TTeeSelected=class(TCustomTextShape)
  private
    FHover : TTeeHover;
    FHoverIndex : Integer;
    FItemIndex : Integer;

    IElement : TObject;

    procedure DoRepaint;
    procedure SetHover(const Value:TTeeHover);
    procedure SetHoverIndex(const Value:Integer);
    procedure SetItemIndex(const Value:Integer);
  public
    Constructor CreateChart(const AOwner: TObject);
    Destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;

    property HoverIndex:Integer read FHoverIndex write SetHoverIndex default -1;
    property ItemIndex:Integer read FItemIndex write SetItemIndex default -1;
  published
    property Hover:TTeeHover read FHover write SetHover;
  end;

  TAxisItems=class({$IFDEF D18}TObjectList<TAxisItem>{$ELSE}TList{$ENDIF})
  private
    FAuto   : Boolean;
    FFormat : TAxisLabelsFormat;
    FSelected : TTeeSelected;

    IAxis   : TChartAxis;
    IHeight : Integer;
    IRemoving : Boolean;

    function Get(Index:Integer):TAxisItem;
    function GetSelected:TTeeSelected;

    Function InternalAdd(const Value: TAxisValue; const Text:String='';
                         const APicture : TTeePicture=nil):TAxisItem;

    procedure ReadItems(Stream: TStream);
    procedure SetAuto(const Value:Boolean);
    procedure WriteItems(Stream: TStream);
  protected
    IDrawAll : Boolean; // See TAxisBreaksTool.SetAxis

    IRect : TRect;

    function RectOf(Index:Integer):TRect;
    procedure InitCalcRect;

  public
    Constructor Create(const Axis:TChartAxis);
    Destructor Destroy; override;

    Function Add(const Value: TAxisValue; const Text:String=''):TAxisItem;
    Procedure CopyFrom(const Source:TAxisItems);

    {$IFNDEF D18}
    Procedure Clear; override;
    {$ENDIF}

    procedure Delete(Index:Integer);

    property Automatic:Boolean read FAuto write SetAuto default True;
    property Format:TAxisLabelsFormat read FFormat;
    property Selected:TTeeSelected read GetSelected;

    property Item[Index:Integer]:TAxisItem read Get; default;
  end;

  TPenLineMode=(lmLine, lmCylinder, lmRectangle);

  TChartPen=TTeePen;

  TChartAxisPen=class(TChartPen)
  private
    FLineMode : TPenLineMode;

    procedure SetLineMode(const Value:TPenLineMode);
  public
    Constructor Create(const OnChangeEvent:TNotifyEvent);
  published
    property EndStyle default esFlat;
    property LineMode:TPenLineMode read FLineMode write SetLineMode default lmLine;
    {$IFNDEF FMX} // FMX Width is Single, so we cannot add "default"
    property Width default 2;
    {$ENDIF}
  end;

  // Flags to automatically configure text items to "fit" inside available space,
  // without overlapping other texts.
  //
  // Precedence order is:
  //
  // First try to Move items to free-space positions,
  // if this is not a good solution, try to Wrap (split) text strings in
  // multiple lines of text.
  // If not good enough, try to display text Rotated (45,90 or 270 degree).
  // Finally, try to Resize (decrease Font.Size), and if this does not help,
  // Hide the texts that overlap.

  TAutomaticText=class(TPersistent)
  private
    FHide,
    FMove,
    FResize,
    FRotate,
    FTrim,
    FWrap : Boolean;

    IOnChange : TNotifyEvent;

    procedure SetBooleanProperty(var Variable:Boolean; const Value:Boolean);

    procedure SetHide(const Value:Boolean);
    procedure SetMove(const Value:Boolean);
    procedure SetResize(const Value:Boolean);
    procedure SetRotate(const Value:Boolean);
    procedure SetTrim(const Value:Boolean);
    procedure SetWrap(const Value:Boolean);
  public
    Constructor Create(const AOnChange:TNotifyEvent);

    procedure Assign(Source:TPersistent); override;
  published
    property Hide:Boolean read FHide write SetHide default True;
    property Move:Boolean read FMove write SetMove default True;
    property Resize:Boolean read FResize write SetResize default True;
    property Rotate:Boolean read FRotate write SetRotate default True;
    property Trim:Boolean read FTrim write SetTrim default True;
    property Wrap:Boolean read FWrap write SetWrap default True;
  end;

  TAxisDrawLabelEvent=procedure(Sender:TChartAxis; var X,Y,Z:Integer; var Text:String;
                                var DrawLabel:Boolean) of object;

  // internal event, used at TGridBandTool class (only)
  TAxisDrawGrids=procedure(Sender:TChartAxis) of object;

  TAxisOnGetNextLabel=Procedure( Sender:TChartAxis; LabelIndex:Integer;
                                 var LabelValue:TAxisValue; var Stop:Boolean) of object;

  TChartAxisLabelValue=Procedure( Sender:TChartAxis; var tmpValue:TAxisValue) of object;

  TChartCustomAxes=class(TOwnedCollection)
  private
    Function Get(Index:Integer):TChartAxis;
    Procedure Put(Index:Integer; Const Value:TChartAxis);
  public
    function Add:TChartAxis;
    procedure ResetScales(const Axis:TChartAxis);
    property Items[Index:Integer]:TChartAxis read Get write Put; default;
  end;

  TAxisLabelsPosition=(lpAtTick, lpBeforeTick, lpAfterTick, lpCenter);

  TAxisLabels=class(TPersistent)
  private
    FAlign      : TAxisLabelAlign;
    FAlternate  : Boolean;
    FAngle      : Integer;
    FAutomatic  : TAutomaticText;
    FBehind     : Boolean;
    FGridIgnoresSize : Boolean;
    FMargin     : Integer;
    FOnAxis     : Boolean;
    FSeparation : Integer;
    FSize       : Integer;
    FStyle      : TAxisLabelStyle;
    FExponent   : Boolean;
    FMultiLine  : Boolean;
    FRoundFirst : Boolean;
    FPosition   : TAxisLabelsPosition;
    FUseImages  : Boolean;

    IHeightFactor : Integer;
    IMaxImageHeight : Integer;

    function GetFont:TTeeFont;
    function GetFormat:TAxisLabelsFormat;
    function GetSelected:TTeeSelected;
    function GetVisible:Boolean;

    function ParentChart:TCustomAxisPanel; {$IFDEF D9}inline;{$ENDIF}

    Procedure SetAlign(const Value:TAxisLabelAlign);
    Procedure SetAlternate(const Value:Boolean);
    Procedure SetAngle(const Value:Integer);
    Procedure SetAutomatic(const Value:TAutomaticText);
    Procedure SetBehind(const Value:Boolean);
    procedure SetExponent(const Value: Boolean);
    Procedure SetFont(const Value:TTeeFont);
    Procedure SetFormat(const Value:TAxisLabelsFormat);
    Procedure SetGridIgnoresSize(const Value:Boolean);
    Procedure SetMargin(const Value:Integer);
    Procedure SetMultiLine(const Value:Boolean);
    Procedure SetOnAxis(const Value:Boolean);
    procedure SetPosition(const Value:TAxisLabelsPosition);
    Procedure SetRoundFirst(const Value:Boolean);
    Procedure SetSelected(const Value:TTeeSelected);
    Procedure SetSeparation(const Value:Integer);
    Procedure SetSize(const Value:Integer);
    Procedure SetStyle(const Value:TAxisLabelStyle);
    Procedure SetUseImages(const Value:Boolean);
    Procedure SetVisible(const Value:Boolean);
  protected
    Axis : TChartAxis;
  public
    Constructor Create;
    Destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;

    // Pending to publish:
    property Automatic:TAutomaticText read FAutomatic write SetAutomatic;
  published
    property Align:TAxisLabelAlign read FAlign write SetAlign default alDefault;
    property Alternate:Boolean read FAlternate write SetAlternate default False;
    property Angle:Integer read FAngle write SetAngle default 0;
    property Behind:Boolean read FBehind write SetBehind default False;
    property Exponent:Boolean read FExponent write SetExponent default False;
    property Font:TTeeFont read GetFont write SetFont stored False;
    property Format:TAxisLabelsFormat read GetFormat write SetFormat;
    property GridIgnoresLabelSize:Boolean read FGridIgnoresSize write SetGridIgnoresSize default False;
    property MarginToAxis:Integer read FMargin write SetMargin default TeeDefaultLabelsMargin;
    property MultiLine:Boolean read FMultiLine write SetMultiLine default False;
    property OnAxis:Boolean read FOnAxis write SetOnAxis default True;
    property RoundFirst:Boolean read FRoundFirst write SetRoundFirst default True;
    property Selected:TTeeSelected read GetSelected write SetSelected;
    property Separation:Integer read FSeparation write SetSeparation default TeeDefaultLabelsSeparation;
    property Size:Integer read FSize write SetSize default 0;
    property Style:TAxisLabelStyle read FStyle write SetStyle default talAuto;
    property Position:TAxisLabelsPosition read FPosition write SetPosition default lpAtTick;
    property UseImages:Boolean read FUseImages write SetUseImages default True;
    property Visible:Boolean read GetVisible write SetVisible default True;
  end;

  TChartHiddenPen=TTeeHiddenPen;

  TAxisBackShape=class(TTeeShape)
  published
    property Transparency; // bug 492
    property Visible default False;
  end;

  TTicks=class(TDarkGrayPen);

  TAxisTicks=Array of Integer;

  TAxisValueExtended = {$IFDEF TEEVALUESINGLE}Double{$ELSE}Extended{$ENDIF};

  TChartAxis=class(TCollectionItem)
  private
    { Scales }
    FAutomatic        : Boolean;
    FAutomaticMaximum : Boolean;
    FAutomaticMinimum : Boolean;
    FDesiredIncrement : TAxisValue;
    FMaximumValue     : TAxisValue;
    FMinimumValue     : TAxisValue;
    FLogarithmic      : Boolean;
    FLogarithmicBase  : Double;
    FMaximumOffset    : Integer;
    FMinimumOffset    : Integer;
    FMaximumRound     : Boolean;
    FMinimumRound     : Boolean;
    FResizeChart      : Boolean;

    { Items }
    FAxisItems        : TAxisItems;

    { Labels }
    FLabels           : TAxisLabels;

    FPosLabels        : Integer;
    FAxisValuesFormat : String;
    FDateTimeFormat   : String;
    FExactDateTime    : Boolean;

    { Axis }
    FAxis             : TChartAxisPen;
    FPosAxis          : Integer;
    FZPosition        : Double;

    { title }
    FTitleSize        : Integer;
    FPosTitle         : Integer;

    { grid }
    FGrid             : TAxisGridPen;

    { ticks }
    FMinorGrid        : TChartHiddenPen;

    FMinorTickCount   : Integer;
    FMinorTickLength  : Integer;
    FMinorTicks       : TTicks;

    FTicks            : TTicks;
    FTicksInner       : TTicks;
    FTickInnerLength  : Integer;
    FTickLength       : Integer;
    FTickOnLabelsOnly : Boolean;

    FUsePartnerAxis   : Boolean;
    FPartnerAxis      : TChartAxis;

    { SubAxis }
    FSubAxes          : TChartCustomAxes;

    { other }
    FInverted         : Boolean;
    FHorizontal       : Boolean;
    FOtherSide        : Boolean;
    FParentChart      : TCustomAxisPanel;
    FVisible          : Boolean;
    FStartPosition    : Double;
    FEndPosition      : Double;
    FPositionPercent  : Double;
    FPosUnits         : TTeeUnits;

    FShape            : TAxisBackShape;

    // Events
    FOnDrawLabel      : TAxisDrawLabelEvent;

    FMaster           : TChartAxis;

    { internal }
    IAxisDateTime     : Boolean;
    IAxisID           : String;
    IAxisLogSizeRange : TChartValue;
    ICenterPos        : Integer;
    IDepthAxis        : Boolean;
    ILogMax           : TAxisValue;
    ILogMin           : TAxisValue;
    IRange            : TAxisValueExtended;
    IRangeLog         : TAxisValueExtended;
    IRangeZero        : Boolean;
    ISeriesList       : TCustomSeriesList;
    IsVisible         : Boolean;
    IWallSize         : Integer;
    IZPos             : Integer;

    procedure AssignToSub(const Axis:TChartAxis);
    Procedure CalcAllSeries;
    function CalcIsDateTime(const ADateTime:TDateTimeStep):Boolean;
    Procedure CalcRoundScales;
    Function DepthAxisAlign:Integer;
    Function DepthAxisPos:Integer;

    Function GetAxisSeriesLabel(const AStyle:TAxisLabelStyle;
                            const ASeries:TChartSeries; AIndex:Integer;
                            out AValue:TAxisValue;
                            out ALabel:String;
                            out APicture:TTeePicture):Boolean;

    function GetAxisTitle:TChartAxisTitle;
    function GetLabelsAlign:TAxisLabelAlign;
    function GetLabelsAlternate:Boolean;
    function GetLabelsAngle:Integer;
    function GetLabelsBehind:Boolean;
    function GetLabelsExponent:Boolean;
    function GetLabelsGridIgnoresSize:Boolean;
    function GetLabelsMultiLine:Boolean;
    function GetLabelsOnAxis:Boolean;
    function GetLabelsSeparation:Integer;
    function GetLabelsSize:Integer;
    function GetLabelStyle:TAxisLabelStyle;
    function GetRoundFirstLabel:Boolean;

    function GetParentAxis:TChartAxis;
    Function InflateAxisPos(Value:Integer; Amount:Integer):Integer;
    Procedure InflateAxisRect(var R:TRect; const Value:Integer);
    Function RoundLogPower(const Value:TAxisValueExtended):TAxisValue;
    Procedure SetAutomatic(Value:Boolean);
    Procedure SetAutomaticMinimum(Value:Boolean);
    Procedure SetAutomaticMaximum(Value:Boolean);
    Procedure SetAutoMinMax(var Variable:Boolean; Var2,Value:Boolean);
    Procedure SetAxis(const Value:TChartAxisPen);
    procedure SetAxisTitle(const Value:TChartAxisTitle);
    Procedure SetDateTimeFormat(Const Value:String);
    Procedure SetDesiredIncrement(Const Value:TAxisValue);
    Procedure SetExactDateTime(Value:Boolean);
    Procedure SetGrid(const Value:TAxisGridPen);
    procedure SetGridCentered(Value:Boolean);
    Procedure SetLabels(Value:Boolean);
    Procedure SetLabelsAlign(Value:TAxisLabelAlign);
    Procedure SetLabelsAlternate(Value:Boolean);
    Procedure SetLabelsAngle(const Value:Integer);
    Procedure SetLabelsFont(const Value:TTeeFont);
    Procedure SetLabelsFormat(const Value:TAxisLabelsFormat);
    Procedure SetLabelsMultiLine(Value:Boolean);
    Procedure SetLabelsOnAxis(Value:Boolean);
    Procedure SetLabelsSeparation(Value:Integer);
    Procedure SetLabelsSize(Value:Integer);
    Procedure SetLabelStyle(Value:TAxisLabelStyle);
    Procedure SetLabelsBehind(Value:Boolean);
    Procedure SetLabelsGridIgnoresSize(Value:Boolean);
    Procedure SetLogarithmic(Value:Boolean);
    Procedure SetLogarithmicBase(const Value:Double);
    Procedure SetMaximum(Const Value:TAxisValue);
    Procedure SetMinimum(Const Value:TAxisValue);
    Procedure SetMaximumRound(Const Value:Boolean);
    Procedure SetMinimumRound(Const Value:Boolean);
    Procedure SetMinorGrid(const Value:TChartHiddenPen);
    Procedure SetMinorTickCount(Value:Integer);
    Procedure SetMinorTickLength(Value:Integer);
    Procedure SetMinorTicks(const Value:TTicks);
    procedure SetShape(const Value:TAxisBackShape);
    procedure SetStartPosition(Const Value:Double);
    procedure SetEndPosition(Const Value:Double);
    procedure SetPositionPercent(Const Value:Double);
    procedure SetPosUnits(const Value: TTeeUnits);
    procedure SetRoundFirstLabel(Value:Boolean);
    Procedure SetTickLength(Value:Integer);
    Procedure SetTickInnerLength(Value:Integer);
    Procedure SetTicks(const Value:TTicks);
    Procedure SetTicksInner(const Value:TTicks);
    procedure SetTickOnLabelsOnly(Value:Boolean);
    Procedure SetTitleSize(Value:Integer);
    Procedure SetValuesFormat(Const Value:String);
    Procedure SetVisible(Value:Boolean);

    Function ApplyPosition(APos:Integer; Const R:TRect):Integer;
    Function CalcDateTimeIncrement(MaxNumLabels:Integer):TAxisValue;
    Function CalcLabelsIncrement(MaxNumLabels:Integer):TAxisValue;
    Procedure CalcRect(var R:TRect; InflateChartRectangle:Boolean);
    Function CalcZPos:Integer;
    Procedure DrawGridLine(tmp:Integer);
    Function GetGridCentered:Boolean;
    Function GetLabels:Boolean; {$IFDEF D9}inline;{$ENDIF}
    Function GetLabelsFont:TTeeFont; {$IFDEF D9}inline;{$ENDIF}
    Function GetLabelsFormat:TAxisLabelsFormat; {$IFDEF D9}inline;{$ENDIF}
    Function GetRectangleEdge(Const R:TRect):Integer;
    function GetShape:TAxisBackShape;
    function GetSubAxes:TChartCustomAxes;

    function IncDec(Increment:Boolean;
                    var Value:TAxisValue;
                    const AnIncrement:TAxisValue;
                    const ADateTime:TDateTimeStep):Boolean; overload;

    function IncDec(Increment:Boolean;
                    var Value:TAxisValue;
                    const AnIncrement:TAxisValue):Boolean; overload;

    Function InternalCalcDepthPosValue(Const Value:TChartValue):Integer;
    Procedure InternalCalcRange;
    Procedure InternalCalcPositions;
    Function InternalCalcSize( const AFormat:TCustomTextShape;
                               AAngle:Integer;
                               const AText:String;
                               ASize:Integer):Integer;

    Function InternalLabelSize(Const Value:TAxisValue; IsWidth:Boolean):Integer;
    Procedure MaxLabelsValue(out AWidth:Integer; out ALines: Integer);
    function IsAxisValuesFormatStored:Boolean;
    Function IsCustom:Boolean;
    Function IsEndStored:Boolean;
    function IsLogBaseStored: Boolean;
    Function IsMaxStored:Boolean;
    Function IsMinStored:Boolean;
    Function IsPosStored:Boolean;
    function IsShapeStored:Boolean;
    Function IsStartStored:Boolean;
    function IsSubAxesStored:Boolean;
    function IsZStored: Boolean;
    Function LogXPosValue(Const Value:TChartValue):Integer;
    Function LogYPosValue(Const Value:TChartValue):Integer;
    function MaxImageWidth:Integer;
    procedure SetHorizontal(const Value: Boolean);
    Procedure SetCalcPosValue;
    procedure SetOtherSide(const Value: Boolean);
    procedure SetLabelsExponent(Value: Boolean);
    procedure SetMaximumOffset(const Value: Integer);
    procedure SetMinimumOffset(const Value: Integer);
    procedure SetSubAxes(const Value:TChartCustomAxes);
    procedure SetZPosition(const Value: Double);
    Function XPosValue(Const Value:TChartValue):Integer;
    Function YPosValue(Const Value:TChartValue):Integer;
    Function XPosValueCheck(Const Value:TChartValue):Integer;
    Function YPosValueCheck(Const Value:TChartValue):Integer;
  protected
    IAxisGeo       : Boolean;
    FAxisTitle     : TChartAxisTitle;
    IAxisSizeRange : {$IFDEF TEENOASM}TAxisValueExtended{$ELSE}TAxisValue{$ENDIF};
    IExtraSpace    : Integer;
    IGapSize       : Integer;
    IHideBackGrid  : Boolean;
    IHideSideGrid  : Boolean;
    IUseTeeMaxPixelPos: Boolean;  //Allow AxisBreak unlimited precalc

    IMaximum       : TAxisValue;
    IMinimum       : TAxisValue;

    OnCalcMaxLabel : TAxisOnGetNextLabel; // internal event, used by TAxisBreaksTool class
    OnDrawAxis     : TNotifyEvent;
    OnNextLabelValue : TChartAxisLabelValue;

    Transform      : TTeeTransform;

    OnDrawGrids    : TAxisDrawGrids;

    Constructor CreateWithParent(const AParent:TCustomAxisPanel; const Collection:TCollection);

    Function AxisRect:TRect;

    procedure DefineProperties(Filer: TFiler); override;
    Procedure DrawGrids(NumTicks:Integer);
    procedure DrawLineSegment(AStart,AEnd:Integer);
    Procedure DrawTitle(X,Y:Integer);
    Function InternalCalcLabelStyle:TAxisLabelStyle; virtual;
    Procedure InternalSetInverted(Value:Boolean);
    Procedure InternalSetMaximum(Const Value:TAxisValue);
    Procedure InternalSetMinimum(Const Value:TAxisValue);
    Procedure RecalcSizeCenter;
    Procedure SetInverted(Value:Boolean); virtual;
    Procedure SetItems(const Value:TAxisItems);
    Function SizeLabels:Integer;
    Function SizeTickAxis(const UseTickLength:Boolean=True):Integer;

    property InternalMaximum:TAxisValue read IMaximum;
    property InternalMinimum:TAxisValue read IMinimum;
  public
    IStartPos     : Integer;
    IEndPos       : Integer;
    IAxisSize     : Integer;
    CalcXPosValue : TAxisCalcPos;
    CalcYPosValue : TAxisCalcPos;
    CalcPosValue  : TAxisCalcPos;
    CalcPosPoint  : TAxisCalcPosPoint;
    Tick          : TAxisTicks;

    Constructor Create(const Chart:TCustomAxisPanel); reintroduce; overload;
    Constructor Create(Collection:TCollection); overload; override;

    Destructor Destroy; override;

    Procedure AdjustMaxMin;
    Procedure AdjustMaxMinRect(Const Rect:TRect);
    procedure Assign(Source: TPersistent); override;
    Function CalcIncrement:TAxisValue;
    Function CalcLabelStyle:TAxisLabelStyle;
    Procedure CalcMinMax(out AMin,AMax:TAxisValue);
    Function InternalCalcPosPoint(const Value:Integer):TAxisValue;
    Function CalcSizeValue(Const Value:TAxisValue):Integer; overload;
    Function CalcSizeValue(Const Start,Finish:TAxisValue):Integer; overload;
    Function CalcXYIncrement(MaxLabelSize:Integer):TAxisValue;

    Procedure CustomDraw( APosLabels,APosTitle,APosAxis:Integer;
                          GridVisible:Boolean);
    Procedure CustomDrawMinMax( APosLabels,
                                APosTitle,
                                APosAxis:Integer;
                                GridVisible:Boolean;
                                Const AMinimum,AMaximum,AIncrement:TAxisValue);
    Procedure CustomDrawMinMaxStartEnd( APosLabels,
                                        APosTitle,
                                        APosAxis:Integer;
                                        GridVisible:Boolean;
                                        Const AMinimum,AMaximum,AIncrement:TAxisValue;
                                        AStartPos,AEndPos:Integer);
    Procedure CustomDrawStartEnd( APosLabels,APosTitle,APosAxis:Integer;
                                  GridVisible:Boolean; AStartPos,AEndPos:Integer);

    Function Clicked(const P:TPoint):Boolean; overload;
    Function Clicked(x,y:Integer):Boolean; overload;

    Procedure Draw(CalcPosAxis:Boolean);

    procedure DrawAxisLabel(x,y,Angle:Integer; const St:String; Format:TCustomTextShape=nil;
                            z:Integer=0;
                            const APicture:TTeePicture=nil;
                            ItemIndex:Integer=-1);

    procedure Hide;
    Function IsDateTime:Boolean;
    Function LabelWidth(Const Value:TAxisValue):Integer;
    Function LabelHeight(Const Value:TAxisValue):Integer;
    Function LabelValue(Const Value:TAxisValue):String; virtual;
    Function MaxLabelsWidth:Integer;

    Procedure Scroll(Const Offset:TAxisValue; CheckLimits:Boolean=False);
    Procedure SetMinMax(const AMin,AMax:TAxisValue);

    class function ToDegree(const Value:Single):String; overload; {$IFDEF D9}static;{$ENDIF}
    class function ToDegree(const Value:Single; const IsLat:Boolean):String; overload; {$IFDEF D9}static;{$ENDIF}

    { public }
    property CenterPosition: Integer read ICenterPos;
    property IsDepthAxis  : Boolean read IDepthAxis;
    property Items        : TAxisItems read FAxisItems write SetItems;
    property MasterAxis   : TChartAxis read FMaster write FMaster;
    property PosAxis      : Integer read FPosAxis;
    property PosLabels    : Integer read FPosLabels;
    property PosTitle     : Integer read FPosTitle;
    property ParentChart  : TCustomAxisPanel read FParentChart;
    property Texts        : TAxisLabels read FLabels;
    property ResizeChart  : Boolean read FResizeChart write FResizeChart;
  published
    property Automatic:Boolean read FAutomatic write SetAutomatic default True;
    property AutomaticMaximum:Boolean read FAutomaticMaximum write SetAutomaticMaximum default True;
    property AutomaticMinimum:Boolean read FAutomaticMinimum write SetAutomaticMinimum default True;
    property Axis:TChartAxisPen read FAxis write SetAxis;
    property AxisValuesFormat:String read FAxisValuesFormat
                                     write SetValuesFormat stored IsAxisValuesFormatStored;
    property DateTimeFormat:String read FDateTimeFormat write SetDateTimeFormat;
    property ExactDateTime:Boolean read FExactDateTime write SetExactDateTime default True;
    property Grid:TAxisGridPen read FGrid write SetGrid;
    property GridCentered:Boolean read GetGridCentered write SetGridCentered default False;
    property Increment:TAxisValue read FDesiredIncrement write SetDesiredIncrement;
    property Inverted:Boolean read FInverted write SetInverted default False;

    property Horizontal:Boolean read FHorizontal write SetHorizontal stored IsCustom;
    property OtherSide:Boolean read FOtherSide write SetOtherSide stored IsCustom;

    // "LabelsXX" will be deprecated in future releases: (Use Texts.XXX properties)
    property Labels:Boolean read GetLabels write SetLabels default True;
    property LabelsAlign:TAxisLabelAlign read GetLabelsAlign write SetLabelsAlign default alDefault;
    property LabelsAlternate:Boolean read GetLabelsAlternate write SetLabelsAlternate default False;
    property LabelsAngle:Integer read GetLabelsAngle write SetLabelsAngle default 0;
    property LabelsBehind:Boolean read GetLabelsBehind write SetLabelsBehind default False;
    property LabelsExponent:Boolean read GetLabelsExponent write SetLabelsExponent default False;
    property LabelsFont:TTeeFont read GetLabelsFont write SetLabelsFont stored False;
    property LabelsFormat:TAxisLabelsFormat read GetLabelsFormat write SetLabelsFormat;
    property LabelsGridIgnoresLabelSize:Boolean read GetLabelsGridIgnoresSize write SetLabelsGridIgnoresSize default False;
    property LabelsMultiLine:Boolean read GetLabelsMultiLine write SetLabelsMultiLine default False;
    property LabelsOnAxis:Boolean read GetLabelsOnAxis write SetLabelsOnAxis default True;
    property LabelsSeparation:Integer read GetLabelsSeparation
                                      write SetLabelsSeparation default TeeDefaultLabelsSeparation;
    property LabelsSize:Integer read GetLabelsSize write SetLabelsSize default 0;
    property LabelStyle:TAxisLabelStyle read GetLabelStyle write SetLabelStyle default talAuto;

    property Logarithmic:Boolean read FLogarithmic write SetLogarithmic default False;
    property LogarithmicBase:Double read FLogarithmicBase write SetLogarithmicBase stored IsLogBaseStored;

    property Maximum:TAxisValue read FMaximumValue write SetMaximum stored IsMaxStored;
    property MaximumOffset:Integer read FMaximumOffset write SetMaximumOffset default 0;
    property MaximumRound:Boolean read FMaximumRound write SetMaximumRound default False;

    property Minimum:TAxisValue read FMinimumValue write SetMinimum stored IsMinStored;
    property MinimumOffset:Integer read FMinimumOffset write SetMinimumOffset default 0;
    property MinimumRound:Boolean read FMinimumRound write SetMinimumRound default False;

    property MinorGrid:TChartHiddenPen read FMinorGrid write SetMinorGrid;
    property MinorTickCount:Integer read FMinorTickCount write SetMinorTickCount default 3;
    property MinorTickLength:Integer read FMinorTickLength write SetMinorTickLength default 2;
    property MinorTicks:TTicks read FMinorTicks write SetMinorTicks;
    property StartPosition:Double read FStartPosition write SetStartPosition
                                    stored IsStartStored;
    property EndPosition:Double read FEndPosition write SetEndPosition
                                    stored IsEndStored;
    property PositionPercent:Double read FPositionPercent write SetPositionPercent
                                    stored IsPosStored;
    property PartnerAxis:TChartAxis read FPartnerAxis write FPartnerAxis;
    property PositionUnits:TTeeUnits read FPosUnits write SetPosUnits default muPercent;
    property RoundFirstLabel:Boolean read GetRoundFirstLabel write SetRoundFirstLabel default True;
    property Shape:TAxisBackShape read GetShape write SetShape stored IsShapeStored;
    property SubAxes:TChartCustomAxes read GetSubAxes write SetSubAxes stored IsSubAxesStored;
    property TickInnerLength:Integer read FTickInnerLength write SetTickInnerLength default 0;
    property TickLength:Integer read FTickLength write SetTickLength default 4;
    property Ticks:TTicks read FTicks write SetTicks;
    property TicksInner:TTicks read FTicksInner write SetTicksInner;
    property TickOnLabelsOnly:Boolean read FTickOnLabelsOnly write SetTickOnLabelsOnly default True;
    property Title:TChartAxisTitle read GetAxisTitle write SetAxisTitle;
    property TitleSize:Integer read FTitleSize write SetTitleSize default 0;
    property UsePartnerAxis:Boolean read FUsePartnerAxis write FUsePartnerAxis default False;
    property Visible:Boolean read FVisible write SetVisible default True;
    property ZPosition:Double read FZPosition write SetZPosition stored IsZStored;

    // Events
    property OnDrawLabel:TAxisDrawLabelEvent read FOnDrawLabel write FOnDrawLabel;
  end;

  TChartDepthAxis=class(TChartAxis)
  private
    FLabelsAsTitles : Boolean;
    Procedure SetLabelsAsTitles(Value:Boolean);
  protected
    Function InternalCalcLabelStyle:TAxisLabelStyle; override;
    Procedure SetInverted(Value:Boolean); override;
  public
    Constructor Create(const Chart:TCustomAxisPanel); reintroduce; overload;
    Constructor Create(Collection:TCollection); overload; override;
  published
    property LabelsAsSeriesTitles:Boolean read FLabelsAsTitles write SetLabelsAsTitles default True;
    property Visible default False;
  end;

  TAxisOnGetLabel=Procedure( Sender:TChartAxis; Series:TChartSeries;
                             ValueIndex:Integer; var LabelText:String) of object;

  TSeriesClick=procedure( Sender:TChartSeries;
                          ValueIndex:Integer;
                          Button:TMouseButton;
                          Shift: TShiftState;
                          X, Y: Integer) of object;

  TValueEvent=(veClear,veAdd,veDelete,veRefresh,veModify);

  THorizAxis = (aTopAxis,aBottomAxis,aBothHorizAxis,aCustomHorizAxis);
  TVertAxis  = (aLeftAxis,aRightAxis,aBothVertAxis,aCustomVertAxis);

  TChartClickedPartStyle=( cpNone,
                           cpLegend,
                           cpAxis,
                           cpSeries,
                           cpTitle,
                           cpFoot,
                           cpChartRect,
                           cpSeriesMarks,
                           cpSeriesPointer,
                           cpSubTitle,
                           cpSubFoot,
                           cpAxisTitle );

  TChartClickedPart=Packed Record
    Part       : TChartClickedPartStyle;
    PointIndex : Integer;
    ASeries    : TChartSeries;
    AAxis      : TChartAxis;
  end;

  {$IFNDEF D18}
  {$IFDEF D9}
  TSeriesEnumerator={$IFDEF D11}record{$ELSE}class{$ENDIF}
  private
    FIndex: Integer;
    FList: TCustomSeriesList;
  public
    Constructor Create(const List:TCustomSeriesList);

    function GetCurrent:TChartSeries; inline;
    function MoveNext: Boolean; // cannot be inlined due to usage inside "while"
    property Current:TChartSeries read GetCurrent;
  end;
  {$ENDIF}
  {$ENDIF}

  TCustomSeriesList=class({$IFDEF D18}TList<TChartSeries>{$ELSE}TList{$ENDIF})
  private
    FOwner : TCustomAxisPanel;

    {$IFNDEF D18}
    function Get(Index:Integer):TChartSeries; {$IFDEF D9}inline;{$ENDIF}
    procedure Put(Index:Integer; const Value:TChartSeries);
    {$ENDIF}
  public
    procedure ClearValues;
    procedure FillSampleValues(Num:Integer=0);
    function First:TChartSeries;

    {$IFNDEF D18}
    function Add(const ASeries:TChartSeries):Integer; {$IFDEF D9}inline;{$ENDIF}
    {$IFDEF D9}
    function GetEnumerator: TSeriesEnumerator;
    {$ENDIF}
    {$ENDIF}

    function Last:TChartSeries;

    {$IFNDEF D18}
    property Items[Index:Integer]:TChartSeries read Get write Put; default;
    {$ENDIF}

    property Owner:TCustomAxisPanel read FOwner;
  end;

  TSeriesGroup=class;
  TSeriesGroups=class;

  TChartSeriesList=class(TCustomSeriesList)
  private
    FGroups : TSeriesGroups;

    function GetAllActive: Boolean;
    procedure SetAllActive(const Value: Boolean);
    procedure SetGroups(const Value: TSeriesGroups);
  public
    Destructor Destroy; override;

    Function AddGroup(const Name:String):TSeriesGroup;
    procedure Assign(const Source:TChartSeriesList);
    function Clicked(const X,Y:Integer; out ValueIndex:Integer):TChartSeries;
    function Find(const AName:String):TChartSeries;

    property AllActive:Boolean read GetAllActive write SetAllActive;
    property Groups:TSeriesGroups read FGroups write SetGroups;
  end;

  TSeriesGroupActive=(gaYes, gaNo, gaSome);

  TSeriesGroup=class(TCollectionItem)
  private
    FName   : String;
    FSeries : TCustomSeriesList;
    FTempSeries: TStringList;

    function GetActive: TSeriesGroupActive;
    procedure ReadSeries(Reader: TReader);
    procedure SetActive(const Value:TSeriesGroupActive);
    procedure SetSeries(const Value: TCustomSeriesList);
    procedure WriteSeries(Writer: TWriter);
  protected
    procedure DefineProperties(Filer: TFiler); override;
  public
    Constructor Create(Collection:TCollection); overload; override;
    Destructor Destroy; override;

    procedure Add(const Series:TChartSeries);
    procedure Assign(Source:TPersistent); override;
    procedure Hide;
    procedure Show;
    property Series:TCustomSeriesList read FSeries write SetSeries;
  published
    property Active:TSeriesGroupActive read GetActive write SetActive default gaYes;
    property Name:String read FName write FName;
  end;

  TSeriesGroups=class(TOwnedCollection)
  private
    Function Get(Index:Integer):TSeriesGroup;
    Procedure Put(Index:Integer; Const Value:TSeriesGroup);
  public
    function Contains(const Series:TChartSeries):Integer;
    function FindByName(const Name:String; CaseSensitive:Boolean=False):TSeriesGroup;

    property Items[Index:Integer]:TSeriesGroup read Get write Put; default;
  end;

  TAxisCalcPosLabels=function(Axis:TChartAxis; Value:Integer):Integer of object;

  TChartAxes=class({$IFDEF D18}TObjectList<TChartAxis>{$ELSE}TList{$ENDIF})
  private
    FChart    : TCustomAxisPanel;

    IFastCalc : Boolean;  // default False

    {$IFNDEF D18}
    function Get(Index:Integer):TChartAxis;
    {$ENDIF}

    function GetAuto:Boolean;
    function GetBottomAxis:TChartAxis;
    function GetDepthAxis:TChartDepthAxis;
    function GetDepthTopAxis:TChartDepthAxis;
    function GetLeftAxis:TChartAxis;
    function GetRightAxis:TChartAxis;
    function GetTopAxis:TChartAxis;
    function GetBehind: Boolean;
    function GetVisible: Boolean;

    procedure SetAuto(const Value: Boolean);
    procedure SetBehind(const Value: Boolean);
    procedure SetFastCalc(const Value: Boolean);
    procedure SetVisible(const Value: Boolean);

  protected
    CalcPosLabels : TAxisCalcPosLabels;

  public
    procedure Clear; {$IFNDEF D18}override;{$ENDIF}
    function Clicked(x,y:Integer):TChartAxis;
    procedure Reset;

    {$IFNDEF D18}
    property Items[Index:Integer]:TChartAxis read Get; default;
    {$ENDIF}

    procedure Hide;

    // property Anchors:TAnchors (to customize "CustomChartRect" alignment)

    property Bottom:TChartAxis read GetBottomAxis;
    property Depth:TChartDepthAxis read GetDepthAxis;
    property DepthTop:TChartDepthAxis read GetDepthTopAxis;
    property Left:TChartAxis read GetLeftAxis;
    property Right:TChartAxis read GetRightAxis;
    property Top:TChartAxis read GetTopAxis;

    property Automatic:Boolean read GetAuto write SetAuto;
    property Behind:Boolean read GetBehind write SetBehind;
    property FastCalc:Boolean read IFastCalc write SetFastCalc;
    property Visible:Boolean read GetVisible write SetVisible;
  end;

  TTeeCustomDesigner=class(TObject)
  public
    Procedure Refresh; dynamic;
    Procedure Repaint; dynamic;
  end;

  TChartSeriesEvent=( seAdd, seRemove, seChangeTitle, seChangeColor, seSwap,
                      seChangeActive, seDataChanged, seAfterRemove);

  TChartChangePage=class(TTeeEvent);

  TChartToolEvent=( cteAfterDraw,
                    cteBeforeDrawSeries,
                    cteBeforeDraw,
                    cteBeforeDrawAxes,
                    cteAfterDrawSeries,
                    cteMouseLeave);

  TChartMouseEvent=( cmeDown, cmeMove, cmeUp);
  TWheelMouseEvent=( wmeDown, wmeMove, wmeUp);
  TChartDragDropEvent=( cdeOver, cdeDrop, cdeStart, cdeEnd );
  TChartKeyEvent=( ckeDown, ckeUp, ckePress );

  {$IFDEF FMX}
  TDragState = (dsDragEnter, dsDragLeave, dsDragMove);
  {$ENDIF}

  TTeeCustomTool=class(TCustomChartElement)
  private
    Function SameClass(const tmpTool:TTeeCustomTool):Boolean;
  protected
    Procedure ChartEvent(AEvent:TChartToolEvent); virtual;
    Procedure ChartMouseEvent( AEvent: TChartMouseEvent;
                               Button:TMouseButton;
                               Shift: TShiftState; X, Y: Integer); virtual;
    procedure ChartDragDropEvent(AEvent:TChartDragDropEvent; Source: TObject; X, Y: Integer;
                                 State: TDragState; var Accept: Boolean;
                                 var DragObject: TDragObject); virtual;
    procedure ChartKeyEvent(AEvent: TChartKeyEvent; var Key: Word;
                            var Key1: Char; Shift: TShiftState); virtual;
    Procedure SeriesEvent(AEvent:TChartToolEvent; ASeries:TChartSeries); virtual;
    procedure SetParentChart(const Value: TCustomAxisPanel); override;
    Procedure WheelMouseEvent( AEvent: TWheelMouseEvent;
                               WheelDelta:Integer;
                               X, Y: Integer); virtual;
  public
    class Function Description:String; virtual;
    class Function LongDescription:String; virtual;
  end;

  TTeeCustomToolClass=class of TTeeCustomTool;

  TChartTools=class({$IFDEF D18}TList<TTeeCustomTool>{$ELSE}TList{$ENDIF})
  private
    FOwner : TCustomAxisPanel;

    {$IFNDEF D18}
    Function Get(Index:Integer):TTeeCustomTool;
    {$ENDIF}

    procedure InternalAdd(ATool:TTeeCustomTool);
    procedure SetActive(Value:Boolean);
  public
    Function Add(const Tool:TTeeCustomTool):TTeeCustomTool; overload;
    Function Add({$IFNDEF BCB}const{$ENDIF} Tool:TTeeCustomToolClass):TTeeCustomTool; overload;

    procedure Clear; {$IFNDEF D18}override;{$ENDIF}

    property Active:Boolean write SetActive;

    {$IFNDEF D18}
    property Items[Index:Integer]:TTeeCustomTool read Get; default;
    {$ENDIF}

    property Owner:TCustomAxisPanel read FOwner;
  end;

  TTeeCustomAnimation=class {$IFDEF D11}abstract{$ENDIF} (TTeeCustomTool)
  protected
    procedure SetParentChart(const Value: TCustomAxisPanel); override;
  public
    procedure Play; virtual; abstract;
    function Title:String;
  end;

  TTeeCustomAnimationClass=class of TTeeCustomAnimation;

  TChartAnimations=class(TChartTools)
  private
    Function Get(Index:Integer):TTeeCustomAnimation;
  public
    function Add(const Value:TTeeCustomAnimation):TTeeCustomAnimation; overload;
    function AddClass(const Value:TTeeCustomAnimationClass):TTeeCustomAnimation; overload;

    property Items[Index:Integer]:TTeeCustomAnimation read Get; default;
  end;

  // Base object for tools that have a Series property
  TTeeCustomToolSeries=class(TTeeCustomTool)
  private
    FSeries : TChartSeries;
  protected
    class Function GetEditorClass:String; override;
    Function GetHorizAxis:TChartAxis;
    Function GetVertAxis:TChartAxis;
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    procedure SetSeries(const Value: TChartSeries); virtual;
  public
    Procedure Assign(Source:TPersistent); override;

    property Series:TChartSeries read FSeries write SetSeries;
  end;

  // Base object for tools that have an Axis property
  TTeeCustomToolAxis=class(TTeeCustomTool)
  private
    FAxis: TChartAxis;

    procedure ReadAxis(Reader: TReader);
    procedure WriteAxis(Writer: TWriter);
  protected
    procedure DefineProperties(Filer: TFiler); override;
    class Function GetEditorClass:String; override;
    procedure InternalSetAxis(const Value: TChartAxis);
    procedure SetAxis(const Value: TChartAxis); virtual;
  public
    Procedure Assign(Source:TPersistent); override;

    property Axis:TChartAxis read FAxis write SetAxis stored False;
  end;

  TChartPage=class(TPersistent)
  private
    FAutoScale     : Boolean;
    FCurrent       : Integer;
    FPointsPerPage : Integer;
    FScaleLastPage : Boolean;

    IParent : TCustomAxisPanel;

    Procedure SetAutoScale(const Value:Boolean);
    procedure SetCurrent(Value:Integer);
    Procedure SetPointsPerPage(const Value:Integer);
    Procedure SetScaleLastPage(const Value:Boolean);
  public
    Constructor Create;

    procedure Assign(Source:TPersistent); override;
    function Count:Integer;
    function FirstValueIndex:Integer;
    Procedure NextPage;
    Procedure PreviousPage;

    property Parent:TCustomAxisPanel read IParent;
  published
    property AutoScale:Boolean read FAutoScale write SetAutoScale default False;
    property Current:Integer read FCurrent write SetCurrent default 1;
    property MaxPointsPerPage:Integer read FPointsPerPage write SetPointsPerPage default 0;
    property ScaleLastPage:Boolean read FScaleLastPage write SetScaleLastPage default True;
  end;

  TTeeSeriesEvent=class(TTeeEvent)
  public
    Event  : TChartSeriesEvent;
    Series : TCustomChartSeries;
  end;

  TSeriesNotifyEvent=Procedure(Sender:TCustomChartSeries) of object;

  TSeriesDrawValue=procedure(Series:TChartSeries; Index:Integer) of object;

  TValidDataSourceEvent=procedure(Sender:TCustomAxisPanel; ASeries:TChartSeries;
                                  AComponent:TComponent; var Valid:Boolean) of object;

  TChartSeriesClass=class of TChartSeries;

  TCustomAxisPanel=class {$IFDEF D11}abstract{$ENDIF} (TCustomTeePanelExtended)
  private
    { Private declarations }
    F3DPercent        : Integer;
    FAnimations       : TChartAnimations;
    FAxes             : TChartAxes;
    FAxisBehind       : Boolean;
    FAxisVisible      : Boolean;
    FClipPoints       : Boolean;
    FCustomAxes       : TChartCustomAxes;
    FHover            : TTeeHover;
    FSeriesList       : TChartSeriesList;
    FDepthAxis        : TChartDepthAxis;
    FDepthTopAxis     : TChartDepthAxis;
    FTopAxis          : TChartAxis;
    FBottomAxis       : TChartAxis;
    FLeftAxis         : TChartAxis;
    FRightAxis        : TChartAxis;
    FView3DWalls      : Boolean;

    FOnAddSeries      : TSeriesNotifyEvent;
    FOnBeforeDrawChart: TNotifyEvent;
    FOnBeforeDrawAxes : TNotifyEvent;
    FOnBeforeDrawSeries:TNotifyEvent;
    FOnGetAxisLabel   : TAxisOnGetLabel;
    FOnGetNextAxisLabel:TAxisOnGetNextLabel;
    FOnPageChange     : TNotifyEvent;
    FOnRemoveSeries   : TSeriesNotifyEvent;

    FPage             : TChartPage;

    FCustomWidth3D    : Boolean;
    FMaxZOrder        : Integer;
    FSeriesWidth3D    : Integer;
    FSeriesHeight3D   : Integer;

    FTools            : TChartTools;

    InvertedRotation  : Boolean;

    Procedure BroadcastToolEvent(AEvent:TChartToolEvent);
    Procedure BroadcastAnimationEvent(AEvent:TChartToolEvent);
    Procedure CalcInvertedRotation;
    procedure ChangedAxisScale(const Axis:TChartAxis);
    Procedure CheckOtherSeries(const Dest,Source:TChartSeries);

    {$IFNDEF TEELITEEMB}
    Procedure DoSeriesParallel(Index:Integer);
    {$ENDIF}

    function FindSameSeriesClass(const ASeries:TCustomChartSeries):TChartSeries;
    function FindThemedSeries:TChartSeries;
    function FindSameToolClass(const ATool:TTeeCustomTool):TTeeCustomTool;
    function FindSameAxisClass(const AAxis:TChartAxis):TChartAxis;
    Function GetAxisSeriesMaxPoints(const Axis:TChartAxis):TChartSeries;
    function GetPage:Integer;
    function GetPointsPerPage:Integer;
    function GetScaleLastPage:Boolean;

    // Commented "inline" to avoid using System.Generics.Collections everywhere
    Function GetSeries(Index:Integer):TChartSeries; // {$IFDEF D9}inline;{$ENDIF}
    
    function GetSeriesGroups:TSeriesGroups;
    Procedure InternalAddSeries(const ASeries:TCustomChartSeries);
    procedure InternalDoMouseLeave;
    Function InternalMinMax(const AAxis:TChartAxis; IsMin,IsX:Boolean):TAxisValue;
    function IsCustomAxesStored: Boolean;
    function IsSeriesGroupsStored:Boolean;
    Function NoActiveSeries(const AAxis:TChartAxis):Boolean;

    procedure ReadMaxPointsPerPage(Reader: TReader); // obsolete
    procedure ReadPage(Reader: TReader); // obsolete
    procedure ReadScaleLastPage(Reader: TReader); // obsolete

    Procedure Set3DPercent(Value:Integer);
    Procedure SetAxisBehind(Value:Boolean);
    Procedure SetAxisVisible(Value:Boolean);
    Procedure SetBottomAxis(const Value:TChartAxis);
    procedure SetClipPoints(Value:Boolean);
    Procedure SetCustomAxes(const Value:TChartCustomAxes);
    Procedure SetDepthAxis(const Value:TChartDepthAxis);
    procedure SetDepthTopAxis(const Value: TChartDepthAxis);
    Procedure SetHover(const Value:TTeeHover);
    Procedure SetLeftAxis(const Value:TChartAxis);
    procedure SetPages(const Value:TChartPage);
    Procedure SetPointsPerPage(const Value:Integer);
    Procedure SetRightAxis(const Value:TChartAxis);
    Procedure SetScaleLastPage(const Value:Boolean);
    procedure SetSeriesGroups(const Value:TSeriesGroups);
    Procedure SetTopAxis(const Value:TChartAxis);
    procedure SetView3DWalls(Value:Boolean);
  protected
    { Protected declarations }
    DoNotBroadcast   : Boolean;

    FOnDrawValue       : TSeriesDrawValue;
    FOnValidDataSource : TValidDataSourceEvent;

    LegendColor      : TColor;
    LegendPen        : TChartPen;

    ParallelThreads  : Integer;

    procedure ApplyThemeSeries(const ASeries:TCustomChartSeries);
    procedure ApplyThemeTools(const ATool:TTeeCustomTool);
    procedure ApplyThemeAxes(const AAxis:TChartAxis);
    Procedure BroadcastSeriesEvent(const ASeries:TCustomChartSeries; Event:TChartSeriesEvent);
    Function CalcIsAxisVisible(const Axis:TChartAxis):Boolean;
    Procedure CalcWallsRect; virtual; abstract;
    Function CalcWallSize(Axis:TChartAxis):Integer; virtual; abstract;
    function CheckMouseAxes(x,y:Integer):Boolean;
    Function CheckMouseSeries(x,y:Integer):Boolean;
    procedure ColorPaletteChanged;

    procedure DefineProperties(Filer: TFiler); override;

    procedure DoOnAfterDraw; virtual;
    procedure DoOnBeforeDrawAxes; virtual;
    procedure DoOnBeforeDrawChart; virtual;
    procedure DoOnBeforeDrawSeries; virtual;
    procedure DrawAxesSeries;
    procedure DrawTitlesAndLegend(BeforeSeries:Boolean); virtual; abstract;
    Function DrawBackWallAfter(Z:Integer):Boolean; virtual;
    procedure DrawLegendShape(const Rect:TRect; const AGraphic:TGraphic=nil); virtual; abstract;
    Procedure DrawWalls; virtual; abstract;
    function GetDesignOwner:TComponent; dynamic;
    function HasActiveSeries(const Axis:TChartAxis; Default:Boolean=False):Boolean;
    Procedure InternalDraw(Const UserRectangle: TRect); override;
    Function IsAxisVisible(const Axis:TChartAxis):Boolean;
    Procedure Loaded; override;
    procedure RemovedDataSource( ASeries: TChartSeries;
                                 AComponent: TComponent ); dynamic;
    Procedure SetPage(const Value:Integer);

    {$IFNDEF D11}
    Procedure GetChildren(Proc:TGetChildProc; Root:TComponent); override;
    {$ENDIF}

    {$IFDEF FMX}
    procedure DoMouseLeave; override;
    {$ELSE}
    {$IFDEF USEWINDOWS}
    procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;
    {$ENDIF}
    {$ENDIF}
  public
    { Public declarations }
    Designer : TTeeCustomDesigner; { used only at Design-Time }
    ColorPalette : TColorArray;

    Constructor Create(AOwner: TComponent); override;
    Destructor Destroy; override;

    { public methods }
    procedure Assign(Source:TPersistent); override;
    Function ActiveSeriesLegend(ItemIndex:Integer):TChartSeries;

    Function AddSeries(const ASeries:TChartSeries):TChartSeries; overload;
    Function AddSeries({cannot use const (BCB hpp)} ASeriesClass:TChartSeriesClass):TChartSeries; overload;
    procedure AddSeries({cannot use const (BCB hpp)} SeriesArray:Array of TChartSeries); overload;

    Procedure CalcSize3DWalls;
    Procedure CheckDatasource(ASeries:TChartSeries); virtual;
    Function CountActiveSeries:Integer;
    Procedure ExchangeSeries(a,b:Integer); overload;
    Procedure ExchangeSeries(a,b:TCustomChartSeries); overload;
    Function FormattedValueLegend(ASeries:TChartSeries; ValueIndex:Integer):String; virtual;
    Procedure FreeAllSeries( SeriesClass:TChartSeriesClass=nil );
    Function GetAxisSeries(const Axis:TChartAxis):TChartSeries;

    {$IFDEF D11}
    Procedure GetChildren(Proc:TGetChildProc; Root:TComponent); override;
    {$ENDIF}

    Function GetDefaultColor(Index:Integer):TColor;
    Function GetFreeSeriesColor(CheckBackground:Boolean=True; const Series:TChartSeries=nil):TColor;
    Function GetMaxValuesCount:Integer;
    Function IsFreeSeriesColor(AColor: TColor; CheckBackground: Boolean;
                               Series:TChartSeries=nil):Boolean; virtual; abstract;
    Function IsValidDataSource(ASeries: TChartSeries; AComponent: TComponent):Boolean; dynamic;
    Function MaxXValue(const AAxis: TChartAxis):TAxisValue;
    Function MaxYValue(const AAxis: TChartAxis):TAxisValue;
    Function MinXValue(const AAxis: TChartAxis):TAxisValue;
    Function MinYValue(const AAxis: TChartAxis):TAxisValue;
    Function MaxMarkWidth:Integer;
    Function MaxTextWidth:Integer;
    Function NumPages:Integer; dynamic;
    procedure PrintPages(FromPage: Integer=1; ToPage: Integer=0);
    Procedure RemoveSeries(const ASeries: TCustomChartSeries); overload;
    Procedure RemoveSeries(SeriesIndex: Integer); overload;

    property Series[Index: Integer]:TChartSeries read GetSeries; default;
    Function SeriesCount:Integer; {$IFDEF D9}inline;{$ENDIF}

    Function SeriesLegend(ItemIndex: Integer; OnlyActive: Boolean):TChartSeries;
    Function SeriesTitleLegend(SeriesIndex: Integer; OnlyActive: Boolean=False):String;

    { public properties }
    property Axes:TChartAxes read FAxes;
    property AxesList:TChartAxes read FAxes; // compatibility v4
    property CustomAxes:TChartCustomAxes read FCustomAxes write SetCustomAxes stored IsCustomAxesStored;
    property CustomWidth3D:Boolean read FCustomWidth3D write FCustomWidth3D default False;
    property MaxZOrder:Integer read FMaxZOrder write FMaxZOrder;
    property SeriesWidth3D:Integer read FSeriesWidth3D;
    property SeriesHeight3D:Integer read FSeriesHeight3D;

    { to be published properties }
    property Animations:TChartAnimations read FAnimations;
    property AxisBehind:Boolean read FAxisBehind write SetAxisBehind default True;
    property AxisVisible:Boolean read FAxisVisible write SetAxisVisible default True;
    property BottomAxis:TChartAxis read FBottomAxis write SetBottomAxis;
    property Chart3DPercent:Integer read F3DPercent write Set3DPercent
                                    default TeeDef3DPercent;  // obsolete;
    property ClipPoints:Boolean read FClipPoints write SetClipPoints default True;
    property Color;
    property DepthAxis:TChartDepthAxis read FDepthAxis write SetDepthAxis;
    property DepthTopAxis:TChartDepthAxis read FDepthTopAxis write SetDepthTopAxis;
    property Hover:TTeeHover read FHover write SetHover;
    property LeftAxis:TChartAxis read FLeftAxis write SetLeftAxis;

    property MaxPointsPerPage:Integer read GetPointsPerPage write SetPointsPerPage;
    property Page:Integer read GetPage write SetPage;
    property Pages:TChartPage read FPage write SetPages;

    property RightAxis:TChartAxis read FRightAxis write SetRightAxis;
    property ScaleLastPage:Boolean read GetScaleLastPage write SetScaleLastPage;

    property SeriesGroups:TSeriesGroups read GetSeriesGroups
                          write SetSeriesGroups stored IsSeriesGroupsStored;

    property SeriesList:TChartSeriesList read FSeriesList;

    property Tools:TChartTools read FTools;
    property TopAxis:TChartAxis read FTopAxis write SetTopAxis;
    property View3DWalls:Boolean read FView3DWalls write SetView3DWalls default True;

    { to be published events }
    property OnAddSeries:TSeriesNotifyEvent read FOnAddSeries write FOnAddSeries;
    property OnBeforeDrawChart: TNotifyEvent read FOnBeforeDrawChart write FOnBeforeDrawChart;
    property OnBeforeDrawAxes:TNotifyEvent read FOnBeforeDrawAxes write FOnBeforeDrawAxes;
    property OnBeforeDrawSeries:TNotifyEvent read FOnBeforeDrawSeries write FOnBeforeDrawSeries;
    property OnGetAxisLabel:TAxisOnGetLabel read FOnGetAxisLabel write FOnGetAxisLabel;
    property OnGetNextAxisLabel:TAxisOnGetNextLabel read FOnGetNextAxisLabel
                                                    write FOnGetNextAxisLabel;
    property OnPageChange:TNotifyEvent read FOnPageChange write FOnPageChange;
    property OnRemoveSeries:TSeriesNotifyEvent read FOnRemoveSeries write FOnRemoveSeries;
    property OnValidDataSource:TValidDataSourceEvent read FOnValidDataSource write FOnValidDataSource;
  end;

  TSeriesMarkPosition=class
  public
    ArrowFrom : TPoint;  // XY position of arrow origin (series point)
    ArrowFix  : Boolean; // When True, custom positions cannot change ArrowFrom
    ArrowTo   : TPoint;  // XY position of arrow destination (mark box left top)
    Custom    : Boolean; // When True, mark box position is not recalculated
    Height    : Integer;
    LeftTop   : TPoint;
    Width     : Integer;
    MidPoint  : TPoint;
    HasMid    : Boolean; // When True, MidPoint is used to draw arrow

    Procedure Assign(const Source:TSeriesMarkPosition);
    Function Bounds:TRect;
    procedure Offset(const P:TPoint);
  end;

  TSeriesMarks=class;

  TSeriesMarksPositions=class({$IFDEF D18}TObjectList<TSeriesMarkPosition>{$ELSE}TList{$ENDIF})
  private
    IMarks : TSeriesMarks;

    procedure DeleteMany(const Start,Quantity:Integer);
    Function Get(Index:Integer):TSeriesMarkPosition;
    Procedure Put(Index:Integer; const APosition:TSeriesMarkPosition);
  public
    Procedure Automatic(Index:Integer);

    {$IFNDEF D18}
    procedure Clear; override;
    {$ENDIF}

    Function ExistCustom:Boolean;
    procedure MoveTo(var Position:TSeriesMarkPosition;
                     const XPos,YPos:TCoordinate);

    property Position[Index:Integer]:TSeriesMarkPosition read Get
                                    write Put; default;
  end;

  TMarksItem=class(TCustomTextShape)
  private
    //TV52014497: WriteItems creates FText even if before was nil (just because it access Text property)
    //We need an additional mean to know if it has been really assigned at any time
    ICustomText : Boolean;

    IText : TStrings;

    function GetText:TStrings;
    procedure SetText(const Value:TStrings);
    procedure TextChanged(Sender:TObject);
  protected
    procedure ChangedText; override;
  public
    Destructor Destroy; override;
  published
    property Bevel;
    property BevelWidth;
    property Children;
    property ChildLayout;
    property Color default ChartMarkColor;
    property Emboss;
    property Font;
    property Gradient;
    property Picture;
    property Shadow;
    property ShapeCallout;
    property ShapeStyle;
    property Text:TStrings read GetText write SetText; // deprecated, overrides inherited Text:String
    property Transparency;
    property Transparent;
  end;

  TMarksItems=class({$IFDEF D18}TObjectList<TMarksItem>{$ELSE}TList{$ENDIF})
  private
    IMarks : TTeeCustomShape;
    ILoadingCustom : Boolean;

    function Get(Index:Integer):TMarksItem;
    function InternalGet(Index:Integer):TMarksItem;
  public
    {$IFNDEF D18}
    Procedure Clear; override;
    {$ENDIF}

    property Format[Index:Integer]:TMarksItem read Get; default;
  end;

  // For custom Marks text, use Series OnGetMarkText event

  TSeriesMarksStyle=( smsValue,             { 1234 }
                      smsPercent,           { 12 % }
                      smsLabel,             { Cars } // If label is empty, no mark will be displayed
                      smsLabelPercent,      { Cars 12 % }
                      smsLabelValue,        { Cars 1234 }
                      smsLegend,            { (Legend.Style) }
                      smsPercentTotal,      { 12 % of 1234 }
                      smsLabelPercentTotal, { Cars 12 % of 1234 }
                      smsXValue,            { 1..2..3.. or 21/6/2014 }
                      smsXY,                { 123 456 }
                      smsSeriesTitle,       { Series1 }
                      smsPointIndex,        { 1..2..3... }
                      smsPercentRelative,   { 100%..90%..120%... }
                      smsLabelPercentValue, { Cars 12 % 1234 }
                      smsLabelOrValue       { Cars or 1234 }  // If label is empty, Value will be used
                      );

  TSeriesPointerStyle=( psRectangle,psCircle,psTriangle,psDownTriangle,
                        psCross,psDiagCross,psStar,psDiamond,psSmallDot,
                        psNothing,psLeftTriangle,psRightTriangle,
                        psHexagon,psVisual,psDonut,psArrow

                        // PENDING: Automatic style depending on Series Index'th in Chart,
                        //          (without using psNothing or psVisual)
                        // , psAuto
                         );

  TPointerSizeUnits=(suPixels,suAxis);

  TSeriesPointer=class(TTeeCustomShapeBrushPen)
  private
    FDark3D    : Boolean;
    FDarkPen   : Integer;
    FDepth     : Integer;
    FDraw3D    : Boolean;
    FEmboss    : TTeeEmboss;
    FFullGradient : Boolean;
    FHorizSize : Integer;
    FInflate   : Boolean;
    FCenterPixel   : Boolean;


    FPicture   : TTeePicture;

    {$IFDEF AUTOREFCOUNT}[weak]{$ENDIF}
    FSeries    : TChartSeries;

    FShadow    : TTeeShadow;
    FSizeFloat : Single;
    FSizeUnits : TPointerSizeUnits;
    FStyle     : TSeriesPointerStyle;
    FTransparency : TTeeTransparency;
    FVertSize  : Integer;
    FSelected  : TTeeSelected;

    function GetColor: TColor;
    function GetEmboss:TTeeEmboss;
    function GetGradient:TTeeGradient;
    function GetPicture: TTeePicture;
    function GetShadow:TTeeShadow;
    function GetSize:Integer;
    function GetSelected:TTeeSelected;

    function IsFullStored:Boolean;

    procedure SetColor(const Value: TColor);
    Procedure SetDark3D(Value:Boolean);
    Procedure SetDarkPen(Value:Integer);
    Procedure SetDepth(Value:Integer);
    Procedure SetDraw3D(Value:Boolean);
    procedure SetEmboss(const Value:TTeeEmboss);
    procedure SetFullGradient(Value: Boolean);
    procedure SetGradient(const Value: TTeeGradient);
    Procedure SetHorizSize(Value:Integer);
    Procedure SetInflate(Value:Boolean);
    Procedure SetCenterPixel(Value:Boolean);
    procedure SetPicture(const Value: TTeePicture);
    procedure SetSize(const Value:Integer);
    procedure SetSizeFloat(const Value:Single);
    procedure SetSizeUnits(const Value:TPointerSizeUnits);
    procedure SetShadow(const Value:TTeeShadow);
    Procedure SetStyle(Value:TSeriesPointerStyle);
    procedure SetTransparency(const Value: TTeeTransparency);
    Procedure SetVertSize(Value:Integer);
    Procedure SetSelected(Const Value:TTeeSelected);
  protected
    AllowChangeSize : Boolean;
    DefaultFullGradient : Boolean;
    Z               : Integer;

    Procedure CalcHorizMargins(var LeftMargin,RightMargin:Integer);
    procedure CalcSize(out AHoriz,AVert:Integer);
    Procedure CalcVerticalMargins(var TopMargin,BottomMargin:Integer);
    Procedure Change3D(Value:Boolean);
    Procedure ChangeHorizSize(NewSize:Integer);
    Procedure ChangeStyle(NewStyle:TSeriesPointerStyle);
    Procedure ChangeVertSize(NewSize:Integer);
    function GetStartZ:Integer;
    function GetMiddleZ:Integer;
    function GetEndZ:Integer;
    Procedure Prepare;
  public
    Constructor Create(const AOwner:TChartSeries);
    Destructor Destroy; override;

    Procedure Assign(Source:TPersistent); override;

    function Clicked(const AStyle:TSeriesPointerStyle; const P:TPoint; X,Y:Integer):Boolean;
    Procedure Draw(const P:TPoint); overload;
    Procedure Draw(X,Y:Integer); overload;
    Procedure Draw(px,py:Integer; ColorValue:TColor; AStyle:TSeriesPointerStyle); overload;
    Procedure DrawPointer( const ACanvas:TCanvas3D;
                           Is3D:Boolean; px,py,Horiz,Vert:Integer;
                           ColorValue:TColor; AStyle:TSeriesPointerStyle);
    Procedure PrepareCanvas(const ACanvas:TCanvas3D; ColorValue:TColor);

    property Color:TColor read GetColor write SetColor;
    property ParentSeries:TChartSeries read FSeries;
    property Size:Integer read GetSize write SetSize;  // do not move to published
    property SizeFloat:Single read FSizeFloat write SetSizeFloat;  // do not move to published
    property CenterPixel:Boolean read FCenterPixel write SetCenterPixel;
  published
    property Brush;
    property Dark3D:Boolean read FDark3D write SetDark3D default True;
    property DarkPen:Integer read FDarkPen write SetDarkPen default 0;
    Property Depth:Integer read FDepth write SetDepth default 0;
    Property Draw3D:Boolean read FDraw3D write SetDraw3D default True;
    property Emboss:TTeeEmboss read GetEmboss write SetEmboss;
    property FullGradient:Boolean read FFullGradient write SetFullGradient stored IsFullStored;
    property Gradient:TTeeGradient read GetGradient write SetGradient;
    Property HorizSize:Integer read FHorizSize write SetHorizSize default 4;
    property InflateMargins:Boolean read FInflate write SetInflate;
    property Pen;
    property Picture:TTeePicture read GetPicture write SetPicture;
    property Shadow:TTeeShadow read GetShadow write SetShadow;
    property SizeUnits:TPointerSizeUnits read FSizeUnits write SetSizeUnits default suPixels;
    property Style:TSeriesPointerStyle read FStyle write SetStyle;
    property Transparency:TTeeTransparency read FTransparency write SetTransparency default 0;
    Property VertSize:Integer read FVertSize write SetVertSize default 4;
    Property Visible;
    property Selected:TTeeSelected read GetSelected write SetSelected;
  end;

  TArrowHeadStyle=(ahNone,ahLine,ahSolid);

  TChartArrowPen={$IFDEF LCL}class(TWhitePen){$ELSE}TWhitePen{$ENDIF};

  TCallout=class(TSeriesPointer)
  private
    FArrow     : TChartArrowPen;
    FArrowHead : TArrowHeadStyle;
    FDistance  : Integer;
    FArrowHeadSize: Integer;

    procedure SetDistance(const Value: Integer);
    procedure SetArrow(const Value: TChartArrowPen);
    procedure SetArrowHead(const Value: TArrowHeadStyle);
    procedure SetArrowHeadSize(const Value: Integer);
  protected
    procedure Draw(AColor:TColor; AFrom,AMid,ATo:TPoint; Z:Integer; MidPoint:Boolean=False); overload;
    procedure Draw(const AParent:TCustomTeePanel; AColor:TColor; AFrom,AMid,ATo:TPoint; Z:Integer; MidPoint:Boolean=False); overload;
  public
    Constructor Create(const AOwner:TChartSeries);
    Destructor Destroy; override;

    Procedure Assign(Source:TPersistent); override;
  published
    property Arrow:TChartArrowPen read FArrow write SetArrow;
    property ArrowHead:TArrowHeadStyle read FArrowHead write SetArrowHead default ahNone;
    property ArrowHeadSize:Integer read FArrowHeadSize write SetArrowHeadSize default 8;
    property Distance:Integer read FDistance write SetDistance default 0;
    property Draw3D default False;
    property InflateMargins default True;
    property Style default psRectangle;
    property Visible default True;
  end;

  TMarksCallout=class(TCallout)
  private
    FLength : Integer;

    procedure ApplyArrowLength(const APosition:TSeriesMarkPosition);
    Function IsLengthStored:Boolean;
    procedure SetLength(const Value:Integer);
  protected
    DefaultLength : Integer;
  public
    Constructor Create(const AOwner: TChartSeries);
    Procedure Assign(Source:TPersistent); override;
  published
    property Length:Integer read FLength write SetLength stored IsLengthStored;
    property Visible default False;
  end;

  TSeriesMarksSymbol=class(TTeeCustomShape)
  private
    Function ShouldDraw:Boolean;
  public
    Constructor Create(const AOwner:TCustomTeePanel); override;
  published
    property Bevel;
    property BevelWidth;
    property Brush;
    property Children;
    property ChildLayout;
    property Emboss;
    property Frame;
    property Gradient;
    property Pen;
    property Picture;
    property Shadow;
    property ShapeCallout;
    property ShapeStyle;
    property Transparency;
    property Visible default False;
  end;

  TSeriesMarkShape=class(TCustomTextShape)
  private
    FMarkerStyle : TSeriesMarksStyle;

    IParent : TSeriesMarks;

    Procedure SetStyle(const Value:TSeriesMarksStyle);
  public
  published
    property Bevel;
    property BevelWidth;
    property Brush;
    property Children;
    property ChildLayout;
    property Emboss;
    property Font;
    property Frame;
    property Gradient;
    property Picture;
    property RoundSize;
    property Shadow;
    property ShapeStyle default fosRoundRectangle;
    property Transparency;
    property Transparent;
    property Visible;

    property ClipText;
    property Cursor;
    property Margins;

    // Note: The "Text" property is not published here, as its calculated by the Series

    property TextAlignment default taCenter;
    property TextFormat;
    property VertTextAlign;

    property Style:TSeriesMarksStyle read FMarkerStyle
                                     write SetStyle default smsLabelOrValue;
  end;

  TTailAlign = ( ttaNear, ttaCenter, ttaFar );
  TTailEdge = ( tteLeft, tteTop, tteRight, tteBottom );

  TMarksTail=class(TPersistent)
  private
    FAutoAlign : Boolean;
    FCustomPoint : Boolean;
    FCustomPointPos : TPoint;
    FHeight : Integer;
    FAlign : TTailAlign;
    FMargin : Integer;
    FVisible : Boolean;
    FWidth : Integer;
    FEdge : TTailEdge;

    ISeries : TChartSeries;

    function GetPoints(const R:TRect; const AFrom,ATo:TPoint):TPointArray; overload;
    function GetPoints(const align:TTailAlign; const R:TRect):TPointArray; overload;

    procedure SetAlign(const Value : TTailAlign);
    procedure SetCustomPoint(Value : Boolean);
    procedure SetCustomPointPos(Value : TPoint);
    procedure SetHeight(Value : Integer);
    procedure SetVisible(Value : Boolean);
    procedure SetAutoAlign(Value : Boolean);
    procedure SetWidth(Value : Integer);
    procedure SetEdge(Value : TTailEdge);
  protected
  public
    Constructor Create(const AOwner: TChartSeries);

    procedure Assign(Source:TPersistent); override;

    property CustomPoint:Boolean read FCustomPoint write SetCustomPoint default False;
    property CustomPointPos:TPoint read FCustomPointPos write SetCustomPointPos;
    property Edge:TTailEdge read FEdge write SetEdge default tteBottom;
  published
    property AutoAlign:Boolean read FAutoAlign write SetAutoAlign default True;
    property Height:Integer read FHeight write SetHeight default 8;
    property Align:TTailAlign read FAlign write SetAlign default ttaCenter;
    property Margin:Integer read FMargin write FMargin default 0;
    property Visible:Boolean read FVisible write SetVisible default False;
    property Width:Integer read FWidth write SetWidth default 8;
  end;

  TSeriesMarksImages=(miSymbol, miOnly, miNone);

  THoverElement=(hePrevious, heCurrent, heNext, heJoin);

  THoverElements=set of THoverElement;

  TSeriesMarks=class(TSeriesMarkShape)
  private
    FAngle       : Integer;
    FAutomatic   : TAutomaticText;
    FCallout     : TMarksCallout;
    FClip        : Boolean;
    FDrawEvery   : Integer;
    FFontSeriesColor : Boolean;
    FItems       : TMarksItems;
    FMultiLine   : Boolean;
    FOnTop       : Boolean;
    FPositions   : TSeriesMarksPositions;
    FUseImages   : TSeriesMarksImages;
    FUseSeriesTransparency : Boolean;

    {$IFDEF AUTOREFCOUNT}[weak]{$ENDIF}
    FSeries      : TChartSeries;
    FSoftClip    : Boolean;
    FSymbol      : TSeriesMarksSymbol;
    FTail        : TMarksTail;
    FZPosition   : Integer;

    function CalcEvery:Integer;
    procedure Delete(const ValueIndex:Integer);
    function GetArrowLength:Integer;
    function GetArrowPen:TChartArrowPen;
    function GetAutoPosition:Boolean;
    Function GetBackColor:TColor;
    Function GetCallout:TMarksCallout;
    function GetItem(Index:Integer):TMarksItem;
    Function GetSymbol:TSeriesMarksSymbol;
    function GetTail : TMarksTail;
    function GetTextAlign:TAlignment;
    Procedure InternalDraw(Index:Integer; AColor:TColor; const AText:String; APosition:TSeriesMarkPosition);
    procedure ReadItems(Stream: TStream);
    Procedure SetAngle(Value:Integer);
    procedure SetCallout(const Value: TMarksCallout);
    Procedure SetArrowPen(const Value:TChartArrowPen);
    Procedure SetArrowLength(Value:Integer);
    Procedure SetAutomatic(const Value:TAutomaticText);
    Procedure SetAutoPosition(Value:Boolean);
    Procedure SetBackColor(Value:TColor);
    Procedure SetClip(Value:Boolean);
    Procedure SetDrawEvery(Value:Integer);
    procedure SetFontSeriesColor(const Value: Boolean);
    Procedure SetMultiline(Value:Boolean);
    procedure SetOnTop(const Value: Boolean);
    procedure SetSoftClip(const Value: Boolean);
    procedure SetSymbol(const Value: TSeriesMarksSymbol);
    procedure SetTail(const Value: TMarksTail);
    procedure SetTextAlign(const Value: TAlignment);
    procedure SetUseImages(const Value: TSeriesMarksImages);
    procedure SetUseSeriesTransparency(const Value: Boolean);
    procedure WriteItems(Stream: TStream);
  protected
    function AntiOverlap(First, ValueIndex:Integer; const APosition:TSeriesMarkPosition):Boolean;
    function ConvertTo2D(const Point,P:TPoint):TPoint;
    procedure DefineProperties(Filer: TFiler); override;
    function GetChildClass:TTeeCustomShapeClass; override;
    procedure InitEmboss(const AEmboss:TTeeEmboss); override;
    procedure InitShadow(const AShadow:TTeeShadow); override;
    Function MarkItem(ValueIndex:Integer):TTeeCustomShape;
    procedure SetParent(Value:TCustomTeePanel); override;
    Function TextWidth(ValueIndex:Integer):Integer;
    Function TotalBounds(ValueIndex:Integer; const APosition:TSeriesMarkPosition):TRect;
    Procedure UsePosition(Index:Integer; var MarkPosition:TSeriesMarkPosition);
  public
    Constructor Create(AOwner:TChartSeries); overload;
    Destructor Destroy; override;

    Procedure ApplyArrowLength(const APosition:TSeriesMarkPosition);
    Procedure Assign(Source:TPersistent); override;
    procedure Clear;

    Function Clicked(X,Y:Integer):Integer; overload;
    Function Clicked(const P:TPoint):Integer; overload;

    Procedure DrawItem(const Shape:TTeeCustomShape; AColor:TColor;
                       const Text:String; const APosition:TSeriesMarkPosition);
    class procedure InitGradient(const AGradient:TTeeGradient);

    property Automatic:TAutomaticText read FAutomatic write SetAutomatic;

    property FontSeriesColor:Boolean read FFontSeriesColor write SetFontSeriesColor default False;
    property OnTop:Boolean read FOnTop write SetOnTop default False;
    property Item[Index:Integer]:TMarksItem read GetItem; default;
    property Items:TMarksItems read FItems;
    property ParentSeries:TChartSeries read FSeries;
    property Positions:TSeriesMarksPositions read FPositions;
    procedure ResetPositions;
    property ZPosition : Integer read FZPosition write FZPosition;
  published
    property Angle:Integer read FAngle write SetAngle default 0;
    property Arrow:TChartArrowPen read GetArrowPen write SetArrowPen; // obsolete
    property ArrowLength:Integer read GetArrowLength write SetArrowLength stored False; // obsolete
    property AutoPosition:Boolean read GetAutoPosition write SetAutoPosition default True; // deprecated
    property BackColor:TColor read GetBackColor write SetBackColor default ChartMarkColor;
    property Callout:TMarksCallout read GetCallout write SetCallout;
    property Clip:Boolean read FClip write SetClip default False;
    property DrawEvery:Integer read FDrawEvery write SetDrawEvery default 1;
    property MultiLine:Boolean read FMultiLine write SetMultiLine default False;
    property SoftClip:Boolean read FSoftClip write SetSoftClip default False;
    property Symbol:TSeriesMarksSymbol read GetSymbol write SetSymbol;
    property Tail:TMarksTail read GetTail write SetTail;
    property TextAlign:TAlignment read GetTextAlign write SetTextAlign default taCenter;
    property UseImages:TSeriesMarksImages read FUseImages write SetUseImages default miSymbol;
    property UseSeriesTransparency:Boolean read FUseSeriesTransparency write SetUseSeriesTransparency default True;

    { inherited }
    property Color default ChartMarkColor;
    property RoundSize {$IFNDEF IOS}{$IFNDEF ANDROID}default 4{$ENDIF}{$ENDIF};
  end;

  TDataSourcesList=class{$IFNDEF D18}(TList){$ENDIF}
  private
    {$IFDEF D18}
    List : Array of TComponent;
    {$ENDIF}

    Series : TChartSeries;

    function InheritedAdd(const Value:TComponent):Integer;
    procedure InheritedClear;

    {$IFDEF D18}
    function Get(Index:Integer):TComponent;
    {$ENDIF}

  {$IFNDEF D18}
  protected
    procedure Notify(Ptr: Pointer; Action: TListNotification); override;
  {$ENDIF}

  public
    function Add(const Value:TComponent):Integer;
    procedure Clear; {$IFNDEF D18}override;{$ENDIF}

    {$IFDEF D18}
    function Count:Integer;
    function IndexOf(const Value:TComponent):Integer;
    procedure Remove(const Value:TComponent);

    property Items[Index:Integer]:TComponent read Get; default;
    {$ENDIF}
  end;

  TSeriesOnBeforeAdd=Function(Sender:TChartSeries):Boolean of object;
  TSeriesOnAfterAdd=Procedure(Sender:TChartSeries; ValueIndex:Integer) of object;
  TSeriesOnClear=Procedure(Sender:TChartSeries) of object;
  TSeriesOnGetMarkText=Procedure(Sender:TChartSeries; ValueIndex:Integer; var MarkText:String) of object;

  TSeriesRecalcOptions=set of (rOnDelete, rOnModify, rOnInsert, rOnClear);

  TFunctionPeriodStyle=( psNumPoints, psRange );
  TFunctionPeriodAlign=( paFirst,paCenter,paLast );

  TTeeFunction=class(TComponent)
  private
    FCalcByValue : Boolean;
    FIncludeNulls: Boolean;
    FPeriod      : Double;
    FPeriodStyle : TFunctionPeriodStyle;
    FPeriodAlign : TFunctionPeriodAlign;
    FParent      : TChartSeries;

    Procedure SetCalcByValue(Const Value:Boolean);
    Procedure SetIncludeNulls(Const Value:Boolean);
    Procedure SetPeriod(Const Value:Double);
    Procedure SetParentSeries(const AParent:TChartSeries);
    Procedure SetPeriodAlign(Value:TFunctionPeriodalign);
    Procedure SetPeriodStyle(Value:TFunctionPeriodStyle);
  protected
    IUpdating        : Boolean;
    CanUsePeriod     : Boolean;  // function uses Period property ?
    NoSourceRequired : Boolean;  // function requires source Series ?
    SingleSource     : Boolean;  // function allows more than one source ?
    HideSourceList   : Boolean;  // For single-source, allow select value-list ?

    Procedure AddFunctionXY(YMandatorySource:Boolean; const tmpX,tmpY:Double);
    Procedure CalculatePeriod( Source:TChartSeries;
                               Const tmpX:Double;
                               FirstIndex,LastIndex:Integer); virtual;
    Procedure AddValue(const Value:Double; const Values:TChartValues; Index:Integer); virtual;
    Function CalculateValue(const Values:TChartValues):Double; virtual;
    Procedure CalculateAllPoints(Source:TChartSeries; NotMandatorySource:TChartValueList); virtual;
    Procedure CalculateByPeriod(Source:TChartSeries; NotMandatorySource:TChartValueList); virtual;
    procedure Clear; dynamic;
    Procedure DoCalculation( Source:TChartSeries;
                             NotMandatorySource:TChartValueList); virtual;
    class function GallerySource:TChartSeriesClass; virtual;
    class Function GetEditorClass:String; virtual;
    Procedure InternalSetPeriod(const APeriod:Double);
    Function IsValidSource(const Value:TChartSeries):Boolean; dynamic;
    Procedure PrepareForGallery(const Chart:TCustomAxisPanel); virtual;
    procedure SetParentComponent(Value: TComponent); override;
    procedure SplitAxes;

    Function ValueList(const ASeries:TChartSeries):TChartValueList;
  public
    Constructor Create(AOwner: TComponent); override;
    Procedure Assign(Source:TPersistent); override;

    procedure AddPoints(Source:TChartSeries); dynamic;
    procedure BeginUpdate;
    Function Calculate(const SourceSeries:TChartSeries; First,Last:Integer):Double; virtual;
    Function CalculateMany(const SourceSeriesList:TDataSourcesList; ValueIndex:Integer):Double; virtual;
    Function CalculateManyByValue(const SourceSeriesList:TDataSourcesList; Value:Double):Double; virtual;
    procedure EndUpdate;
    function GetParentComponent: TComponent; override;
    function HasParent: Boolean; override;
    property ParentSeries:TChartSeries read FParent write SetParentSeries;
    Procedure ReCalculate;
  published
    property CalcByValue:Boolean read FCalcByValue write SetCalcByValue default False;
    property IncludeNulls:Boolean read FIncludeNulls write SetIncludeNulls default True;
    property Period:Double read FPeriod write SetPeriod;
    property PeriodAlign:TFunctionPeriodAlign read FPeriodAlign
                                              write SetPeriodAlign default paCenter;
    property PeriodStyle:TFunctionPeriodStyle read FPeriodStyle
                                              write SetPeriodStyle default psNumPoints;
  end;

  TTeeMovingFunction=class(TTeeFunction)
  protected
    Procedure DoCalculation( Source:TChartSeries;
                             NotMandatorySource:TChartValueList); override;
  public
    Constructor Create(AOwner:TComponent); override;
  published
    property Period;
    property PeriodAlign default paLast;
  end;

  TChartValueLists=class({$IFDEF D18}TObjectList<TChartValueList>{$ELSE}TList{$ENDIF})
  private
    Function Get(Index:Integer):TChartValueList;
  public
    {$IFNDEF D18}
    Procedure Clear; override;
    {$ENDIF}

    property ValueList[Index:Integer]:TChartValueList read Get; default;
  end;

  TSeriesLegend=class(TCustomTextShape)
  published
    property Picture;
    property Text;
    property Visible;
  end;

  TChartSeriesStyle=set of ( tssIsTemplate,
                             tssDenyChangeType,
                             tssDenyDelete,
                             tssDenyClone,
                             tssIsPersistent,
                             tssHideDataSource );

  TCustomChartSeries=class(TCustomChartElement)
  private
    FIdentifier : String;  { DecisionCube }
    FLegend     : TSeriesLegend;
    FStyle      : TChartSeriesStyle; { DecisionCube }
    FTitle      : String;

    function GetIndex:Integer;
    function GetLegend:TSeriesLegend;
    function GetLegendTitle:String;
    function GetShowInLegend:Boolean;
    procedure ReadIdentifier(Reader: TReader);
    procedure WriteIdentifier(Writer: TWriter);
    procedure ReadStyle(Reader: TReader);
    procedure WriteStyle(Writer: TWriter);

    Procedure RepaintDesigner;
    Procedure SetLegend(const Value:TSeriesLegend);
    Procedure SetLegendTitle(const Value:String);
    Procedure SetShowInLegend(const Value:Boolean);
    Procedure SetTitle(const Value:String);
  protected
    Transform : TTeeTransform;

    Procedure Added; dynamic;
    Procedure CalcHorizMargins(var LeftMargin,RightMargin:Integer); virtual;
    Procedure CalcVerticalMargins(var TopMargin,BottomMargin:Integer); virtual;
    procedure DefineProperties(Filer: TFiler); override;
    procedure DoBeforeDrawChart; virtual;
    Procedure GalleryChanged3D(Is3D:Boolean); dynamic;
    procedure RecalcAxisMargins(const Axis:TChartAxis; var R:TRect); dynamic;
    Procedure Removed; dynamic;
    Procedure SetActive(Value:Boolean); override;
  public
    Destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;
    Function SameClass(const tmpSeries:TChartSeries):Boolean;

    property SeriesIndex:Integer read GetIndex;

    { to be published }
    property Legend:TSeriesLegend read GetLegend write SetLegend;
    property LegendTitle:String read GetLegendTitle write SetLegendTitle; // deprecated Use Legend.Text
    property ShowInLegend:Boolean read GetShowInLegend write SetShowInLegend default True; // deprecated Use Legend.Visible
    property Title:String read FTitle write SetTitle;

    { For Decision Cube, hidden }
    property Identifier:String read FIdentifier write FIdentifier;
    property Style:TChartSeriesStyle read FStyle write FStyle default [];
  end;

  TSeriesRandomBounds=record
    tmpX,
    StepX,
    tmpY,
    MinY,
    DifY : Double;
  end;

  TTeeFunctionClass=class of TTeeFunction;

  TTeeSeriesType=class(TObject)
  public
    SeriesClass      : TChartSeriesClass;
    FunctionClass    : TTeeFunctionClass;
    Description      : PString;
    GalleryPage      : PString;
    NumGallerySeries : Integer;
    SubIndex         : Integer;

    function Caption:String;
  end;

  TChartSubGalleryProc=Function(Const AName:String):TCustomAxisPanel of object;

  TLegendTextStyle=( ltsPlain,ltsLeftValue,ltsRightValue,
                     ltsLeftPercent,ltsRightPercent,ltsXValue,ltsValue,
                     ltsPercent,ltsXAndValue,ltsXAndPercent,ltsXAndText,
                     ltsXAndValueAndText);

  TeeFormatFlags = (tfNoMandatory,tfColor,tfLabel,tfMarkPosition,tfPictures);
  TeeFormatFlag  = set of TeeFormatFlags;

  TLabelsList=class{$IFNDEF D18}(TList){$ENDIF}
  private
    {$IFDEF D18}
    List : Array of String;
    {$ENDIF}

    {$IFDEF AUTOREFCOUNT}[weak]{$ENDIF}
    Series : TChartSeries;

    FCaseSensitive : Boolean;

    IAnyEmpty : Boolean;

    Procedure DeleteLabel(ValueIndex:Integer);
    Function GetLabel(ValueIndex:Integer):String;
    Procedure SetLabel(ValueIndex:Integer; Const ALabel:String);
    Procedure InsertLabel(ValueIndex:Integer; Const ALabel:String);
    procedure SetCaseSensitive(Value: Boolean);
  protected
    procedure Resize(const ACount:Integer);
  public
    {$IFDEF D18}
    Destructor Destroy; override;
    {$ENDIF}

    procedure Assign(const Source:TLabelsList);

    {$IFDEF D18}
    procedure Clear;
    function Count:Integer;
    {$ELSE}
    procedure Clear; override;
    {$ENDIF}

    procedure Exchange(const A,B:Integer);

    Function IndexOfLabel(const ALabel:String; CaseSensitive:Boolean=True):Integer;
    property Labels[Index:Integer]:String read GetLabel write SetLabel; default;
    property CaseSensitive: Boolean read FCaseSensitive write SetCaseSensitive default True;
  end;

  TVisualsList=class({$IFDEF D18}TObjectList<TVisualBlock>{$ELSE}TList{$ENDIF})
  private
    {$IFDEF AUTOREFCOUNT}[weak]{$ENDIF}
    FSeries   : TChartSeries;

    FTemplate : TVisualBlock;

    Procedure DeleteVisual(ValueIndex:Integer);
    Function GetVisual(ValueIndex:Integer):TVisualBlock;
    Procedure InsertVisual(ValueIndex:Integer; const AVisual:TVisualBlock);
    Procedure SetTemplate(const Value:TVisualBlock);
    Procedure SetVisual(ValueIndex:Integer; const AVisual:TVisualBlock);
  public
    procedure Assign(const Source:TVisualsList);

    {$IFNDEF D18}
    procedure Clear; override;
    {$ENDIF}

    procedure Edit(Index:Integer=-1); virtual;

    property Series:TChartSeries read FSeries;
    property Template:TVisualBlock read FTemplate write SetTemplate;
    property Visuals[Index:Integer]:TVisualBlock read GetVisual write SetVisual; default;
  end;

  TTeePictureList=class{$IFNDEF D18}(TList){$ENDIF}
  private
    {$IFDEF D18}
    List : Array of TTeePicture;
    {$ENDIF}

    {$IFDEF AUTOREFCOUNT}[weak]{$ENDIF}
    Series : TChartSeries;

    Procedure Delete(const ValueIndex:Integer);
    Function Get(const ValueIndex:Integer):TTeePicture;
    Procedure Put(const ValueIndex:Integer; Const APicture:TTeePicture);
    Procedure Insert(const ValueIndex:Integer; Const APicture:TTeePicture);
  public
    OwnsPictures,
    Stored : Boolean;

    {$IFDEF D18}
    Destructor Destroy; override;
    {$ENDIF}

    procedure Assign(const Source:TTeePictureList);

    {$IFDEF D18}
    procedure Clear;
    function Count:Integer;
    procedure Exchange(A,B:Integer);
    {$ELSE}
    procedure Clear; override;
    {$ENDIF}

    class function Copy(const APicture:TTeePicture):TTeePicture; {$IFDEF D9}static;{$ENDIF}
    class function FromGraphic(const AGraphic:TGraphic):TTeePicture; {$IFDEF D9}static;{$ENDIF}
    function IndexOf(const APicture:TTeePicture):Integer;

    property Items[const Index:Integer]:TTeePicture read Get write Put; default;
  end;

  TDetailAggregate=(daSum, daCount, daAverage, daMin, daMax);

  TSeriesDetail=class(TCustomSeriesList)
  private
    FAggregate : TDetailAggregate;

    IParent : TChartSeries;

    procedure SetAggregate(const Value:TDetailAggregate);
  protected
    function Calculate(const Values:TChartValueList):TChartValue;
  public
    Destructor Destroy; override;

    function CreateNew(const AParent:TChartSeries):TChartSeries;
    procedure ReCalculate;

    property Aggregate:TDetailAggregate read FAggregate write SetAggregate default daSum;
  end;

  TTreatNullsStyle=(tnDontPaint, tnSkip, tnIgnore);

  TColorsList=TList{$IFDEF D18}<TColor>{$ENDIF};

  TIntArray=Array of Integer;

  TSeriesClickedPartStyle=( scpNone,
                           scpSegment,
                           scpPointer,
                           scpMark );

  TChartSeries=class(TCustomChartSeries {$IFNDEF D6}{$IFNDEF FPC}, IInterface{$ENDIF}{$ENDIF})
  private
    FColor              : TColor;
    FColorEachPoint     : Boolean;
    FColorSource        : String;
    FCursor             : TCursor;
    FDataSources        : TDataSourcesList;
    FDepth              : Integer;
    FDetail             : TSeriesDetail;
    FGetHorizAxis       : TChartAxis;
    FGetVertAxis        : TChartAxis;
    FHoverElement       : THoverElements;
    FLabels             : TLabelsList;
    FLabelsSource       : String;
    FLinkedSeries       : TCustomSeriesList;
    FMarks              : TSeriesMarks;
    FPercentFormat      : String;
    FPictures           : TTeePictureList;
    FSelected           : TTeeSelected;
    FTransparency       : TTeeTransparency;
    FTreatNulls         : TTreatNullsStyle;

    FTempDataSources    : TStringList;
    FValueFormat        : String;
    FValuesList         : TChartValueLists;
    FVisuals            : TVisualsList;
    FX                  : TChartValueList;
    FY                  : TChartValueList;
    FDefaultNull        : TChartValue;

    FHorizAxis          : THorizAxis;
    FCustomHorizAxis    : TChartAxis;
    FCustomVertAxis     : TChartAxis;
    FZOrder             : Integer;

    FVertAxis           : TVertAxis;
    FRecalcOptions      : TSeriesRecalcOptions;

    {$IFDEF TEEOCX}
    FTagAx              : Longint;
    {$ENDIF}
    FTeeFunction        : TTeeFunction;

    { Private }
    IsMouseInside       : Boolean;
    ILabelOrder         : TChartListOrder;
    ISeriesColor        : TColor; // Color assigned when creating the Series.

    { Events }
    FAfterDrawValues    : TNotifyEvent;
    FBeforeDrawValues   : TNotifyEvent;
    FOnAfterAdd         : TSeriesOnAfterAdd;
    FOnBeforeAdd        : TSeriesOnBeforeAdd;
    FOnClearValues      : TSeriesOnClear;
    FOnClick            : TSeriesClick;
    FOnDblClick         : TSeriesClick;
    FOnGetMarkText      : TSeriesOnGetMarkText;
    FOnMouseEnter       : TNotifyEvent;
    FOnMouseLeave       : TNotifyEvent;

    procedure BeginEntity;
    Procedure BroadcastEvent(AEvent:TChartToolEvent);
    Function CanAddRandomPoints:Boolean;
    Procedure ChangeInternalColor(Value:TColor);
    Function CompareLabelIndex(a,b:Integer):Integer;
    Function GetDataSource:TComponent;
    Function GetDataSources:TDataSourcesList;
    function GetDetail:TSeriesDetail;
    function GetImage:TBackImage;
    function GetLinkedSeries:TCustomSeriesList;
    function GetMarks:TSeriesMarks;
    function GetMarkTextStyle(MultiLine:Boolean; ValueIndex:Integer; AStyle:TSeriesMarksStyle):String;
    function GetPictures:TTeePictureList;
    function GetSelected:TTeeSelected;
    function GetVisuals:TVisualsList;
    function GetXLabel(Index: Integer):String;
    Function GetZOrder:Integer;
    Procedure GrowColors;
    Function InternalAddDataSource(const Value:TComponent):Integer;
    Function InternalSetDataSource(const Value:TComponent; ClearAll:Boolean):Integer;
    Procedure InternalRemoveDataSource(const Value:TComponent);
    Function IsDataSourceStored:Boolean;
    Function IsPercentFormatStored:Boolean;
    function IsValidDataSource(const Value:TComponent):Boolean;
    Function IsValueFormatStored:Boolean;
    Procedure NotifyColorChanged;
    procedure ReadCustomHorizAxis(Reader: TReader);
    procedure ReadCustomVertAxis(Reader: TReader);
    procedure ReadDataSources(Reader: TReader);
    procedure ReadDetail(Stream: TStream);
    Procedure RemoveAllLinkedSeries;
    Procedure SetColorSource(Const Value:String);
    Procedure SetCustomHorizAxis(const Value:TChartAxis);
    Procedure SetCustomVertAxis(const Value:TChartAxis);
    Procedure SetDataSource(const Value:TComponent);
    procedure SetDepth(const Value: Integer);
    Procedure SetHorizAxis(const Value:THorizAxis);
    Procedure SetImage(const Value:TBackImage);
    Procedure SetLabelsSource(Const Value:String);
    procedure SetMarks(const Value:TSeriesMarks);
    Procedure SetPercentFormat(Const Value:String);
    Procedure SetSelected(Const Value:TTeeSelected);
    procedure SetTreatNulls(const Value:TTreatNullsStyle);
    Procedure SetValueColor(ValueIndex:Integer; AColor:TColor);
    Procedure SetVertAxis(const Value:TVertAxis);
    procedure SetXLabel(Index: Integer; const Value: String);
    Procedure SetXValue(Index:Integer; Const Value:TChartValue);
    Procedure SetYValue(Index:Integer; Const Value:TChartValue);
    Procedure SetZOrder(Value:Integer);
    procedure WriteCustomHorizAxis(Writer: TWriter);
    procedure WriteCustomVertAxis(Writer: TWriter);
    procedure WriteDataSources(Writer: TWriter);
    procedure WriteDetail(Stream: TStream);
  protected
    DontSaveData     : Boolean;
    ForceSaveData    : Boolean;

    FColors             : TColorsList; // moved from private

    FFirstVisibleIndex  : Integer;
    FLastVisibleIndex   : Integer;

    IHideWhenVisible : Boolean;
    INumSampleValues : Integer;
    IUpdating        : Integer;
    IUseSeriesColor  : Boolean;
    IUseNotMandatory : Boolean;
    IZOrder          : Integer;
    ILegend          : TTeeCustomShape;
    IFirstDrawIndex  : Integer;
    IMandatoryPen    : Boolean; // TV52010830
    IMandatoryPen2D  : Boolean; // TV52010830
    FZoomSeries      : Boolean;
    INoAxisMinMax    : Boolean;
    IClickedPart     : TSeriesClickedPartStyle;

    ParentSeries     : TChartSeries; // Used only in Detail series

    Function AddChartValue(Source:TChartSeries; ValueIndex:Integer):Integer; virtual;
    Procedure Added; override;
    Procedure AddedValue(Source:TChartSeries; ValueIndex:Integer); virtual;
    Procedure AddLinkedSeries(const ASeries:TChartSeries);
    Procedure AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False); dynamic;
    Procedure AddValues(Source:TChartSeries); virtual;

    procedure AssignFormatting(Source:TChartSeries); virtual;

    function AxisLabelStyle(const AAxis:TChartAxis):TAxisLabelStyle; virtual;

    procedure BeforeCalcAxis; virtual;
    procedure CalcFirstLastPage(out First,Last:Integer);
    Procedure CalcZOrder; virtual;
    procedure CalcDepthPositions; virtual;
    Function CalcLegendIndex(LegendIndex:Integer):Integer; virtual;
    procedure CalcSelectionPos(ValueIndex:Integer; out X,Y:Integer); virtual;
    procedure CalculateMarkPosition(const Shape:TTeeCustomShape;
                                    const AText:String;
                                    XPos,YPos:Integer;
                                    var APosition:TSeriesMarkPosition);

    function CanBeforeParallel:Boolean; virtual;
    function CanDrawMark(ValueIndex:Integer):Boolean; virtual;

    procedure ChangedAxisScale(const Axis:TChartAxis); virtual;
    Function CheckMouse(x,y:Integer):Boolean; virtual;
    Procedure ClearLists; virtual;

    Function ClickedPointer( ValueIndex,tmpX,tmpY:Integer;
                             x,y:Integer):Boolean; overload; virtual;

    Function ClickedPointer( ValueIndex,tmpX,tmpY:Integer;
                             const P:TPoint):Boolean; overload;
    class Procedure CreateSubGallery(AddSubChart:TChartSubGalleryProc); virtual;
    procedure DefineProperties(Filer: TFiler); override;
    Procedure DeletedValue(Source:TChartSeries; ValueIndex:Integer); virtual;
    procedure DoAfterDrawValues; virtual;
    procedure DoBeforeDrawValues; virtual;
    procedure DoBeforeParallel; virtual;
    procedure DrawAllValues; virtual;
    Procedure DrawLegendShape(ValueIndex:Integer; Const Rect:TRect); virtual;
    Procedure DrawMark(ValueIndex:Integer; Const St:String; APosition:TSeriesMarkPosition); virtual;
    procedure DrawMarks; virtual;
    procedure DrawValue(ValueIndex:Integer); virtual;
    procedure DrawHiddenValue(ValueIndex:Integer; const StartValue,EndValue:TAxisValue;
                              GapSize:Integer; Axis:TChartAxis); virtual;
    function DrawSeriesAscending:Boolean; virtual;
    Function FirstInZOrder:Boolean;
    Procedure GetChildren(Proc:TGetChildProc; Root:TComponent); override;
    function GetMarkLabel(ValueIndex:Integer):String; virtual;
    Function GetMarkText(ValueIndex:Integer):String;
    Function GetSeriesColor:TColor;
    Function GetValueColor(ValueIndex:Integer):TColor; virtual;
    Function GetxValue(ValueIndex:Integer):TChartValue; virtual;  { conflicts with c++ wingdi.h }
    Function GetyValue(ValueIndex:Integer):TChartValue; virtual;  { conflicts with c++ wingdi.h }

    function HasLabels:Boolean;
    function HasPointLabels:Boolean; virtual;
    function HoverEnabled:Boolean; virtual;
    function HoverOverMark:Integer;
    function HoverOverSegment:Integer;
    Function InternalColor(ValueIndex:Integer):TColor;
    function InvertedLegend:Boolean; virtual;
    Function IsColorStored:Boolean;
    function IsDataStored:Boolean;
    function IsPainted(const ValueIndex:Integer):Boolean;
    Function IsPointInChartRect(ValueIndex:Integer):Boolean; virtual;
    Procedure Loaded; override;
    Function MandatoryAxis:TChartAxis;
    function MarkOverlap(ValueIndex:Integer; const A,B:TRect):TPoint; virtual;
    procedure Notification( AComponent: TComponent;
                            Operation: TOperation); override;

    procedure NotifyColorChange(ValueIndex:Integer); virtual;
    Procedure NotifyNewValue(Sender:TChartSeries; ValueIndex:Integer); virtual;
    Procedure NotifyValue(ValueEvent:TValueEvent; ValueIndex:Integer); virtual;

    procedure MouseDown(ValueIndex:Integer; AtPointer:Boolean; Button:TMouseButton;
                        Shift: TShiftState; X, Y: Integer); virtual;
    Function MoreSameZOrder:Boolean; virtual;
    Procedure PrepareForGallery; dynamic;
    function PrepareHoverPen(ValueIndex:Integer):Boolean; virtual;
    Procedure PrepareLegendCanvas( ValueIndex:Integer; var BackColor:TColor;
                                   var BrushStyle:{$IFDEF FMX}TBrushKind{$ELSE}TBrushStyle{$ENDIF}); virtual;
    class Function RandomValue(const Range:Integer):Integer;
    procedure ReadData(Stream: TStream);
    Procedure Removed; override;
    Procedure RemoveLinkedSeries(const ASeries:TChartSeries);
    Procedure SetChartValueList( const AValueList:TChartValueList;
                                 const Value:TChartValueList);
    Procedure SetColorEachPoint(Value:Boolean); virtual;
    procedure SetDefaultColor;
    Procedure SetHorizontal;
    procedure SetMarkText(ValueIndex:Integer; const Value:String);
    Procedure SetParentChart(Const Value:TCustomAxisPanel); override;
    Procedure SetSeriesColor(AColor:TColor); virtual;

    class Procedure SetSubGallery(ASeries:TChartSeries; Index:Integer); virtual;

    procedure SetTransparency(const Value:TTeeTransparency); virtual;
    Procedure SetValueFormat(Const Value:String); virtual;

    Procedure SetXValues(const Value:TChartValueList);
    Procedure SetYValues(const Value:TChartValueList);
    function ShouldHoverMark(ValueIndex:Integer):Boolean; virtual;
    Function SortDrawPoints(FirstPoint, LastPoint: Integer; SingleSeries:Boolean):TIntArray; virtual;
    Function SortDrawSeries(FirstSeries: Integer; LastSeries: Integer;
                            ValueIndex: Integer):TIntArray; virtual;
    Function ValueListOfAxis(const Axis:TChartAxis):TChartValueList; virtual;
    procedure WriteData(Stream: TStream); dynamic;
    Procedure RecalcGetAxis;
    function RaiseClicked:Boolean; virtual;
    Function ValueIndexToLegend(ValueIndex:Integer):Integer; virtual;

  public
    CalcVisiblePoints     : Boolean;
    DrawBetweenPoints     : Boolean;
    AllowSinglePoint      : Boolean;
    HasZValues            : Boolean;
    StartZ                : Integer;
    MiddleZ               : Integer;
    EndZ                  : Integer;
    MandatoryValueList    : TChartValueList;
    ManualData            : Boolean;
    NotMandatoryValueList : TChartValueList;
    YMandatory            : Boolean;

    Constructor Create(AOwner: TComponent); override;
    Destructor Destroy; override;

    Function Add(Const AValue:TChartValue; Const ALabel:String='';
                       AColor:TColor=clTeeColor):Integer; overload; virtual;

    // Master-Detail
    function Add(const ADetail:Array of TChartValue;
                 const ALabel:String='';
                 AColor:TColor=clTeeColor):Integer; overload;

    {$IFDEF D12}
    Function AddArray(Const Values:Array of Integer):Integer; overload;
    {$ENDIF}

    Function AddArray(Const Values:Array of TChartValue):Integer; overload;
    Function AddArray(Const XValues, YValues:Array of TChartValue):Integer; overload;
    Function AddNull(Const Value:TChartValue):Integer; overload;
    Function AddNull(Const ALabel:String=''):Integer; overload; virtual;
    Function AddNullXY(Const X,Y:TChartValue; Const ALabel:String=''):Integer; virtual;
    Function AddX(Const AXValue:TChartValue; Const ALabel:String='';
                         AColor:TColor=clTeeColor):Integer;
    Function AddXY(Const AXValue,AYValue:TChartValue; Const ALabel:String='';
                         AColor:TColor=clTeeColor):Integer; virtual;
    Function AddY(Const AYValue:TChartValue; Const ALabel:String='';
                         AColor:TColor=clTeeColor):Integer;

    procedure Assign(Source:TPersistent); override;
    procedure AssignFormat(const Source:TChartSeries);

    Function AssociatedToAxis(const Axis:TChartAxis):Boolean; virtual;
    Procedure BeginUpdate;
    Procedure CheckOrder; dynamic;
    Procedure Clear; virtual;

    Function Count:Integer; {$IFOPT C-}{$IFDEF D9}inline;{$ENDIF}{$ENDIF}
    Function CountLegendItems:Integer; virtual;
    Procedure Delete(ValueIndex:Integer); overload; virtual;
    Procedure Delete(Start,Quantity:Integer; RemoveGap:Boolean=False); overload; virtual;
    Procedure EndUpdate;
    Procedure FillSampleValues(NumValues:Integer=0); dynamic;
    Function FirstDisplayedIndex:Integer;
    Function LastDisplayedIndex:Integer;
    Function IsNull(ValueIndex:Integer):Boolean;
    Function IsValidSourceOf(Value:TChartSeries):Boolean; dynamic;
    Function IsValidSeriesSource(Value:TChartSeries):Boolean; dynamic;
    Function LegendToValueIndex(LegendIndex:Integer):Integer; virtual;
    Function LegendItemColor(LegendIndex:Integer):TColor; virtual;
    Function LegendString(LegendIndex:Integer; LegendTextStyle:TLegendTextStyle):String; virtual;
    property LinkedSeries:TCustomSeriesList read GetLinkedSeries;
    Function MaxXValue:TChartValue; virtual;
    Function MaxYValue:TChartValue; virtual;
    Function MaxZValue:TChartValue; virtual;
    Function MinXValue:TChartValue; virtual;
    Function MinYValue:TChartValue; virtual;
    Function MinZValue:TChartValue; virtual;
    Function NumSampleValues:Integer; dynamic;
    Function RandomBounds(NumValues:Integer):TSeriesRandomBounds;
    Procedure RemoveDataSource(const Value:TComponent);
    Procedure SetNull(ValueIndex:Integer; Null:Boolean=True);

    Procedure SortByLabels(Order:TChartListOrder=loAscending);
    Function VisibleCount:Integer; { <-- Number of VISIBLE points (Last-First+1) }

    property ValuesList:TChartValueLists read FValuesList;
    property XValue[Index:Integer]:TChartValue read GetxValue write SetXValue;
    property YValue[Index:Integer]:TChartValue read GetyValue write SetYValue;

    property ZOrder:Integer read GetZOrder write SetZOrder default TeeAutoZOrder;
    Function MaxMarkWidth:Integer;

    Function CalcXPos(ValueIndex:Integer):Integer; virtual;
    Function CalcXPosValue(Const Value:Double):Integer; virtual; //{$IFDEF D9}inline;{$ENDIF}
    Function CalcXSizeValue(Const Value:TAxisValue):Integer; {$IFDEF D9}inline;{$ENDIF}

    Function CalcYPos(ValueIndex:Integer):Integer; virtual;
    Function CalcYPosValue(Const Value:Double):Integer; virtual; //{$IFDEF D9}inline;{$ENDIF}
    Function CalcYSizeValue(Const Value:TAxisValue):Integer; {$IFDEF D9}inline;{$ENDIF}

    Function CalcPosValue(Const Value:TAxisValue):Integer;

    Function XScreenToValue(ScreenPos:Integer):Double; virtual; //{$IFDEF D9}inline;{$ENDIF}
    Function YScreenToValue(ScreenPos:Integer):Double; virtual; //{$IFDEF D9}inline;{$ENDIF}

    Function XValueToText(Const AValue:TAxisValue):String;
    Function YValueToText(Const AValue:TAxisValue):String;

    Procedure CalcFirstLastVisibleIndex; virtual;
    Procedure ColorRange( const AValueList:TChartValueList;
                          Const FromValue,ToValue:TChartValue;
                          AColor:TColor);
    Procedure CheckDataSource;

    { Public Properties }

    property Detail:TSeriesDetail read GetDetail;
    property Labels:TLabelsList read FLabels;
    property Pictures:TTeePictureList read GetPictures;
    property Visuals:TVisualsList read GetVisuals;

    property XLabel[Index:Integer]:String read GetXLabel write SetXLabel;
                  // {$IFDEF D11}deprecated 'Please use Labels[Index] property.';{$ENDIF}

    property ValueMarkText[Index:Integer]:String read GetMarkText write SetMarkText;

    property ValueColor[Index:Integer]:TColor read GetValueColor write SetValueColor;
    property XValues:TChartValueList read FX write SetXValues;
    property YValues:TChartValueList read FY write SetYValues;

    Function GetYValueList(AListName:String):TChartValueList; virtual;
    property GetVertAxis:TChartAxis read FGetVertAxis;
    property GetHorizAxis:TChartAxis read FGetHorizAxis;
    Function MarkPercent(ValueIndex:Integer; AddTotal:Boolean=False):String;

    Function Clicked(x,y:Integer):Integer; overload; virtual;
    function Clicked(const X,Y:Single):Integer; overload;
    Function Clicked(const P:TPoint):Integer; overload;

    Procedure RefreshSeries;
    property FirstValueIndex:Integer read FFirstVisibleIndex;
    property LastValueIndex:Integer read FLastVisibleIndex;
    Function GetOriginValue(ValueIndex:Integer):Double; virtual;
    Function GetMarkValue(ValueIndex:Integer):Double; virtual;

    Procedure AssignValues(const Source:TChartSeries);
    Function DrawValuesForward:Boolean; virtual;
    Function DrawSeriesForward(ValueIndex:Integer):Boolean; virtual;
    procedure SwapValueIndex(a,b:Integer); dynamic;
    property RecalcOptions: TSeriesRecalcOptions read FRecalcOptions
                                                 write FRecalcOptions
                                       default [ rOnDelete,
                                                 rOnModify,
                                                 rOnInsert,
                                                 rOnClear];
    Function GetCursorValueIndex:Integer;
    Procedure GetCursorValues(out XValue,YValue:TChartValue);
    Procedure DrawLegend(ValueIndex:Integer; Const Rect:TRect); virtual;
    Function UseAxis:Boolean; virtual;
    Function CanZoomScroll:Boolean; virtual;
    procedure SetFunction(AFunction:TTeeFunction); virtual;

    property SeriesColor:TColor read GetSeriesColor
                                write SetSeriesColor stored IsColorStored;
                  // {$IFDEF D11}deprecated 'Please use Color property.';{$ENDIF}

    property DefaultNullValue:TChartValue read FDefaultNull write FDefaultNull;

    { other }
    property CustomHorizAxis:TChartAxis read FCustomHorizAxis write SetCustomHorizAxis;
    property CustomVertAxis:TChartAxis read FCustomVertAxis write SetCustomVertAxis;
    property DataSources:TDataSourcesList read GetDataSources;
    property FunctionType:TTeeFunction read FTeeFunction write SetFunction;
    property Image:TBackImage read GetImage write SetImage;

    Procedure CheckOtherSeries(const Source:TChartSeries);
    Procedure ReplaceList(const OldList,NewList:TChartValueList);

    {$IFDEF TEEOCX}
    property TagAx: Longint read FTagAx write FTagAx default 0;
    {$ENDIF}

    { to be published }
    property Active;
    property Color:TColor read GetSeriesColor write SetSeriesColor stored IsColorStored;  // replaces SeriesColor
    property ColorEachPoint:Boolean read FColorEachPoint write SetColorEachPoint default False;
    property ColorSource:String read FColorSource write SetColorSource;
    property Cursor:TCursor read FCursor write FCursor default crDefault;
    property Depth:Integer read FDepth write SetDepth default TeeAutoDepth;
    property HorizAxis:THorizAxis read FHorizAxis write SetHorizAxis default aBottomAxis;
    property Marks:TSeriesMarks read GetMarks write SetMarks;
    property ParentChart;

    { datasource below parentchart }
    property DataSource:TComponent read GetDataSource write SetDataSource stored IsDatasourceStored;
    property PercentFormat:String read FPercentFormat write SetPercentFormat stored IsPercentFormatStored;
    property ShowInLegend;
    property Title;
    property Transparency:TTeeTransparency read FTransparency write SetTransparency default 0;
    property TreatNulls:TTreatNullsStyle read FTreatNulls write SetTreatNulls default tnDontPaint;

    property ValueFormat:String read FValueFormat write SetValueFormat stored IsValueFormatStored;
    property VertAxis:TVertAxis read FVertAxis write SetVertAxis default aLeftAxis;
    property XLabelsSource:String read FLabelsSource write SetLabelsSource;

    { events }
    property AfterDrawValues:TNotifyEvent read FAfterDrawValues
                                           write FAfterDrawValues;
    property BeforeDrawValues:TNotifyEvent read FBeforeDrawValues
                                           write FBeforeDrawValues;
    property OnAfterAdd:TSeriesOnAfterAdd read FOnAfterAdd write FOnAfterAdd;
    property OnBeforeAdd:TSeriesOnBeforeAdd read FOnBeforeAdd write FOnBeforeAdd;
    property OnClearValues:TSeriesOnClear read FOnClearValues
                                          write FOnClearValues;
    property OnClick:TSeriesClick read FOnClick write FOnClick;
    property OnDblClick:TSeriesClick read FOnDblClick write FOnDblClick;
    property OnGetMarkText:TSeriesOnGetMarkText read FOnGetMarkText
                                                write FOnGetMarkText;
    property OnMouseEnter:TNotifyEvent read FOnMouseEnter write FOnMouseEnter;
    property OnMouseLeave:TNotifyEvent read FOnMouseLeave write FOnMouseLeave;
  published
    property HoverElement:THoverElements read FHoverElement write FHoverElement;
    property Legend;
    property LegendTitle;
    property Selected:TTeeSelected read GetSelected write SetSelected;
  end;

  TThemedSeries=class(TChartSeries)
  private
  protected
  public
  published
    property Active;
    property ColorEachPoint;
    property ColorSource;
    property Cursor;
    property Depth;
    property HorizAxis;
    property Marks;
    property ParentChart;
    property DataSource;
    property PercentFormat;
    property SeriesColor;
    property ShowInLegend;
    property Title;
    property Transparency;
    property ValueFormat;
    property VertAxis;
    property XLabelsSource;

    { inherited published }
    property XValues;
    property YValues;
  end;

  ChartException=class(Exception);

  // Series Source mode: Clear or Append
  TSourceLoadMode=(lmClear, lmAppend);

  TTeeSeriesSource=class(TComponent)
  private
    FActive   : Boolean;
    FLoadMode : TSourceLoadMode;
    FSeries   : TChartSeries;
  protected
    Procedure Loaded; override;
    procedure Notification( AComponent: TComponent;
                            Operation: TOperation); override;
    procedure SetActive(const Value: Boolean); virtual;
    procedure SetSeries(const Value: TChartSeries); virtual;
  public
    KeepDataOnClose : Boolean;

    Constructor Create(AOwner: TComponent); override;
    Destructor Destroy; override;

    class Function Available(AChart:TCustomAxisPanel):Boolean; virtual;
    class Function Description:String; virtual; { bcos BCB, do not make abstract }
    class Function Editor:TComponentClass; virtual; { bcos BCB, do not make abstract }
    class Function HasNew:Boolean; virtual;
    class Function HasSeries(ASeries:TChartSeries):Boolean; virtual;

    Procedure Close; virtual;
    Procedure Load; virtual; // abstract;
    Procedure Open; virtual;
    Procedure Refresh;

    property Active:Boolean read FActive write SetActive default False;
    property LoadMode:TSourceLoadMode read FLoadMode write FLoadMode default lmClear;
    property Series:TChartSeries read FSeries write SetSeries;
  end;

  TTeeSeriesSourceClass=class of TTeeSeriesSource;

  TTeeFieldType=(tftNumber,tftDateTime,tftText,tftNone);

  // Used by TeePolar series to return Angle and Radius values for a given XY pixel position
  ITeeScreenToPoint=interface
    ['{FE1CD916-A647-4B27-8580-3842E4019919}']
    procedure ScreenToPoint(X,Y,ValueIndex:Integer; ChangeX,ChangeY:Boolean);
  end;

var
  TeeAxisClickGap    : Integer=3;    { minimum pixels distance to trigger axis click }
  TeeDefaultCapacity : Integer=1000; { default TList.Capacity to speed-up Lists }

{ List of registered Series Source components }
function TeeSources:TList{$IFDEF TEELISTMETA}<TTeeSeriesSourceClass>{$ENDIF}; // QC115377

{ Returns the Series Name property (or "Seriesxx" index if Name is empty). }
Function SeriesNameOrIndex(const ASeries:TCustomChartSeries):String;

// Returns the Series Title, or the Series Name property if Title is empty).
// If SingleLine is True, CRLF (line feed) characters are removed.
Function SeriesTitleOrName(const ASeries:TCustomChartSeries; const SingleLine:Boolean=False):String;

Procedure FillSeriesItems(const AItems:TStrings; const AList:TCustomSeriesList; UseTitles:Boolean=True);

Procedure ShowMessageUser(Const S:String);

{ Returns if a Series has "X" values }
Function HasNoMandatoryValues(const ASeries:TChartSeries):Boolean;

{ Returns True if ASeries has text labels }
Function HasLabels(const ASeries:TChartSeries):Boolean;

{ Returns True if ASeries has colors }
Function HasColors(const ASeries:TChartSeries):Boolean;

{ Returns set indicating Series contents (colors, labels, etc) }
Function SeriesGuessContents(const ASeries:TChartSeries):TeeFormatFlag;

// Draws the associated series bitmap icon at the specified LeftTop location
procedure TeeDrawBitmapEditor(const Canvas: TCanvas; const Element:TCustomChartElement; Left,Top:Integer);

// Cross-platform IntersectRect function
function TeeRectIntersects(out Dest:TRect; const A,B:TRect):Boolean; overload;
function TeeRectIntersects(const A,B:TRect):Boolean; overload; {$IFDEF D9}inline;{$ENDIF}

function IsZero(const A: TAxisValue; Epsilon: TAxisValue = 0): Boolean;
{$IFNDEF D6} // From D6
function IsNan(const AValue: Double): Boolean;
{$ENDIF}

implementation

Uses
     {$IFDEF FMX}
     FMX.Dialogs, FMX.Platform,

     {$IFNDEF TEENOPRINT}
     FMX.Printer,
     {$ENDIF}

     {$ELSE}
     Printers,
     {$ENDIF}

     {$IFDEF D6}
      {$IFNDEF FMX}
      Types,
      {$ENDIF}
     {$ENDIF}
     {$IFDEF TEEARRAY}
      {$IFOPT R+}
       {$IFDEF D6}
       RtlConsts,
       {$ELSE}
       Consts,
       {$ENDIF}
      {$ENDIF}
     {$ENDIF}
     Math,

     {$IFDEF FMX}
     FMXTee.Html, FMXTee.Constants
     {$ELSE}
     TeeConst
     {$ENDIF}
     ;

{$IFNDEF D6}
function IsInfinite(const AValue: Double): Boolean;
begin
  Result := ((PInt64(@AValue)^ and $7FF0000000000000) = $7FF0000000000000) and
            ((PInt64(@AValue)^ and $000FFFFFFFFFFFFF) = $0000000000000000);
end;
{$ENDIF}

{$IFNDEF D16}
{$IFNDEF CPUX64}
type
  TExtendedRec = packed record
    Mantissa: Int64;
    Exponent: Word;
  end;
  PExtendedRec = ^TExtendedRec;
{$ENDIF}

function IsInfiniteExtended(const AValue: Extended): Boolean;
begin
{$IFDEF CPUX64}
  Result := ((PInt64(@AValue)^ and $7FF0000000000000) = $7FF0000000000000) and
            ((PInt64(@AValue)^ and $000FFFFFFFFFFFFF) = $0000000000000000);
{$ELSE !CPUX64}
  Result := ((PExtendedRec(@AValue)^.Exponent and $7FFF) = $7FFF) and
            ((PExtendedRec(@AValue)^.Mantissa and $FFFFFFFFFFFFFFFF) = $8000000000000000);
{$ENDIF}
end;
{$ENDIF}

function TeeRectIntersects(const A,B:TRect):Boolean;
var tmp : TRect;
begin
  result:=TeeRectIntersects(tmp,A,B);
end;

function TeeRectIntersects(out Dest:TRect; const A,B:TRect):Boolean;
begin
  {$IFDEF FMX}
  result:=IntersectRect(Dest,A,B); //A.IntersectsWith(B);
  {$ELSE}
  {$IFDEF MSWINDOWS}
  result:=Windows.IntersectRect(Dest,A,B);
  {$ELSE}
  result:=IntersectRect(Dest,A,B);
  {$ENDIF}
  {$ENDIF}
end;

{ Returns a "good" value, bigger than "OldStep", as 2..5..10..etc }
Function TeeNextStep(Const OldStep:TAxisValue):TAxisValue;
Begin
  if IsInfinite(OldStep) then result:=1
  else
  if OldStep>=10 then result:=10*TeeNextStep(0.1*OldStep)
  else
  if OldStep<1 then result:=0.1*TeeNextStep(OldStep*10)
  //Power(10,1+Round(Log10(OldStep)))
  else
  if OldStep<2 then result:=2 else
  if OldStep<5 then result:=5 else result:=10
end;

{ Determine what a Series point is made of }
Function SeriesGuessContents(const ASeries:TChartSeries):TeeFormatFlag;
begin
  if HasNoMandatoryValues(ASeries) then result:=[tfNoMandatory]
                                   else result:=[];

  if HasColors(ASeries) then result:=result+[tfColor];
  if HasLabels(ASeries) then result:=result+[tfLabel];

  if ASeries.Marks.Positions.ExistCustom then
     result:=result+[tfMarkPosition];

  if (ASeries.FPictures<>nil) and (ASeries.FPictures.Count>0) and
     ASeries.FPictures.Stored then
        result:=result+[tfPictures];
end;

{$IFDEF FMX}
const
  clWhite=TAlphaColors.White;
{$ENDIF}

{ TChartAxisTitle }

Constructor TChartAxisTitle.Create(const AOwner: TCustomTeePanel);
begin
  inherited;

  FTextAlign:=taCenter;
  FPosition:=tpCenter;

  InitTransparent(True);
end;

Procedure TChartAxisTitle.Assign(Source:TPersistent);
Begin
  if Source is TChartAxisTitle then
  With TChartAxisTitle(Source) do
  Begin
    Self.FPosition:=FPosition;
  end;

  inherited;
end;

Function TChartAxisTitle.IsAngleStored:Boolean;
begin
  result:=Angle<>IDefaultAngle;
end;

Procedure TChartAxisTitle.SetPosition(const Value:TAxisTitlePosition);
Begin
  if FPosition<>Value then
  begin
    FPosition:=Value;
    Repaint;
  end;
end;

function TChartAxisTitle.GetCaption:String;
begin
  result:=TeeExtractField(Text,1,TeeLineSeparator);
end;

Procedure TChartAxisTitle.SetCaption(Const Value:String);
Begin
  Text:=Value;
end;

Function TChartAxisTitle.Clicked(const P:TPoint):Boolean;
begin
  result:=Clicked({$IFDEF FMX}Round{$ENDIF}(P.X),
                  {$IFDEF FMX}Round{$ENDIF}(P.Y));
end;

function TChartAxisTitle.Clicked(x,y:Integer):Boolean;
begin
  result:=Visible and (Caption<>'') and ClickedShape(TeePoint(x,y),ShapeBounds);
end;

{ TChartAxisPen }

Constructor TChartAxisPen.Create(const OnChangeEvent:TNotifyEvent);
Begin
  inherited;
  Width:=2;
  DefaultWidth:=2;
  InitEndStyle(esFlat);
end;

procedure TChartAxisPen.SetLineMode(const Value:TPenLineMode);
begin
  if FLineMode<>Value then
  begin
    FLineMode:=Value;
    Changed;
  end;
end;

{ TAxisLabels }

Constructor TAxisLabels.Create;
begin
  inherited;

  FMargin:=TeeDefaultLabelsMargin; { % }
  FSeparation:=TeeDefaultLabelsSeparation; { % }
  FAlign:=alDefault;
  FStyle:=talAuto;
  FOnAxis:=True;
  FRoundFirst:=True;
  FUseImages:=True;
end;

Destructor TAxisLabels.Destroy;
begin
  FAutomatic.Free;
  inherited;
end;

procedure TAxisLabels.Assign(Source:TPersistent);
begin
  if Source is TAxisLabels then
  with TAxisLabels(Source) do
  begin
    Self.FAlign     :=FAlign;
    Self.FAlternate :=FAlternate;
    Self.FAngle     :=FAngle;
    Self.Automatic  :=FAutomatic;
    Self.FBehind    :=FBehind;
    Self.FExponent  :=FExponent;
    Self.FGridIgnoresSize:=FGridIgnoresSize;
    Self.FMargin    :=FMargin;
    Self.FMultiLine :=FMultiLine;
    Self.FOnAxis    :=FOnAxis;
    Self.FPosition  :=FPosition;
    Self.FSeparation:=FSeparation;
    Self.FSize      :=FSize;
    Self.FStyle     :=FStyle;
    Self.FRoundFirst:=FRoundFirst;
    Self.FUseImages :=FUseImages;
  end
  else
    inherited;
end;

function TAxisLabels.GetVisible:Boolean;
begin
  result:=Format.Visible;
end;

Procedure TAxisLabels.SetVisible(const Value:Boolean);
begin
  Format.Visible:=Value;
end;

Procedure TAxisLabels.SetFont(const Value:TTeeFont);
begin
  Format.Font:=Value;
end;

Procedure TAxisLabels.SetFormat(const Value:TAxisLabelsFormat);
begin
  Axis.FAxisItems.Format.Assign(Value);
end;

Procedure TAxisLabels.SetSelected(const Value:TTeeSelected);
begin
  Axis.FAxisItems.Selected.Assign(Value);
end;

procedure TAxisLabels.SetRoundFirst(const Value:Boolean);
begin
  ParentChart.SetBooleanProperty(FRoundFirst,Value);
end;

procedure TAxisLabels.SetMultiLine(const Value:Boolean);
begin
  ParentChart.SetBooleanProperty(FMultiLine,Value);
end;

procedure TAxisLabels.SetExponent(const Value:Boolean);
begin
  ParentChart.SetBooleanProperty(FExponent,Value);
end;

Procedure TAxisLabels.SetBehind(const Value:Boolean);
Begin
  ParentChart.SetBooleanProperty(FBehind,Value);
end;

Procedure TAxisLabels.SetGridIgnoresSize(const Value:Boolean);
Begin
  ParentChart.SetBooleanProperty(FGridIgnoresSize,Value);
end;

procedure TAxisLabels.SetPosition(const Value:TAxisLabelsPosition);
begin
  if FPosition<>Value then
  begin
    FPosition:=Value;
    ParentChart.Invalidate;
  end;
end;

Procedure TAxisLabels.SetAlign(const Value:TAxisLabelAlign);
Begin
  if FAlign<>Value then
  begin
    FAlign:=Value;
    ParentChart.Invalidate;
  end;
end;

Procedure TAxisLabels.SetAlternate(const Value:Boolean);
Begin
  ParentChart.SetBooleanProperty(FAlternate,Value);
end;

Procedure TAxisLabels.SetAngle(const Value:Integer);
Begin
  ParentChart.SetIntegerProperty(FAngle,Value mod 360);
end;

Procedure TAxisLabels.SetAutomatic(const Value:TAutomaticText);
begin
  FAutomatic.Assign(Value);
end;

Procedure TAxisLabels.SetMargin(const Value:Integer);
Begin
  ParentChart.SetIntegerProperty(FMargin,Value);
end;

Procedure TAxisLabels.SetSeparation(const Value:Integer);
Begin
  if Value<0 then Raise AxisException.Create(TeeMsg_AxisLabelSep);
  ParentChart.SetIntegerProperty(FSeparation,Value);
end;

Procedure TAxisLabels.SetSize(const Value:Integer);
Begin
  ParentChart.SetIntegerProperty(FSize,Value);
end;

Procedure TAxisLabels.SetOnAxis(const Value:Boolean);
Begin
  ParentChart.SetBooleanProperty(FOnAxis,Value);
end;

Procedure TAxisLabels.SetStyle(const Value:TAxisLabelStyle);
begin
  if FStyle<>Value then
  begin
    FStyle:=Value;
    ParentChart.Invalidate;
  end;
end;

Procedure TAxisLabels.SetUseImages(const Value:Boolean);
begin
  ParentChart.SetBooleanProperty(FUseImages,Value);
end;

function TAxisLabels.GetFont:TTeeFont;
begin
  result:=Format.Font;
end;

function TAxisLabels.GetFormat:TAxisLabelsFormat;
begin
  result:=Axis.FAxisItems.Format;
end;

function TAxisLabels.GetSelected:TTeeSelected;
begin
  result:=Axis.FAxisItems.Selected;
end;

function TAxisLabels.ParentChart:TCustomAxisPanel;
begin
  result:=Axis.ParentChart;
end;

type
  TOwnedCollectionAccess=class(TOwnedCollection);

{ TChartAxis }

Constructor TChartAxis.CreateWithParent(const AParent:TCustomAxisPanel; const Collection:TCollection);
var tmpOnChange : TNotifyEvent;
begin
  if Assigned(AParent) and
    ((AParent.CustomAxes<>Collection) or (AParent.Axes.Count>=TeeInitialCustomAxis)) then
  begin
     inherited Create(Collection);
     ResizeChart:=False;
  end
  else
  begin
     inherited Create(nil);
     ResizeChart:=True;
  end;

  FParentChart:=AParent;

  SetCalcPosValue;

  ISeriesList:=TCustomSeriesList.Create;

  FAxisItems:=TAxisItems.Create(Self);

  FLogarithmicBase:=10;

  FAutomatic:=True;
  FAutomaticMaximum:=True;
  FAutomaticMinimum:=True;

  FAxisValuesFormat:=TeeMsg_DefValueFormat;

  if Assigned(ParentChart) then
     tmpOnChange:=ParentChart.CanvasChanged
  else
     tmpOnChange:=nil;

  FLabels:=TAxisLabels.Create;
  FLabels.Axis:=Self;

  FLabels.FAutomatic:=TAutomaticText.Create(tmpOnChange);
  FLabels.FAutomatic.Trim:=False; // <-- Pending to set to True

  FTickOnLabelsOnly:=True;

  FTicks:=TTicks.Create(tmpOnChange);
  FTickLength:=4;

  FMinorTicks:=TTicks.Create(tmpOnChange);
  FMinorTickLength:=2;
  FMinorTickCount:=3;

  FTicksInner:=TTicks.Create(tmpOnChange);
  FGrid:=TAxisGridPen.Create(tmpOnChange);
  FMinorGrid:=TChartHiddenPen.Create(tmpOnChange);

  FAxis:=TChartAxisPen.Create(tmpOnChange);

  FVisible         :=True;
  FExactDateTime   :=True;
  FEndPosition     :=100;
end;

function CollectionOwner(const Collection:TCollection):TPersistent;
begin
  result:=TOwnedCollectionAccess(Collection).GetOwner;
end;

Constructor TChartAxis.Create(Collection:TCollection);
var tmp : TPersistent;
Begin
  tmp:=CollectionOwner(Collection);

  if tmp is TChartAxis then
  begin
    CreateWithParent(TChartAxis(tmp).ParentChart,Collection);
    FMaster:=TChartAxis(tmp);
  end
  else
  begin
    CreateWithParent(TCustomAxisPanel(tmp),Collection);

    FParentChart.FAxes.Add(Self);
    FParentChart.ApplyThemeAxes(Self);
  end;
end;

Constructor TChartAxis.Create(const Chart: TCustomAxisPanel);
begin
  Create(Chart.CustomAxes);
end;

Destructor TChartAxis.Destroy;

  Procedure ResetSeriesAxes;
  var t : Integer;
  begin
    With FParentChart do
    for t:=0 to SeriesCount-1 do
    With TChartSeries(Series[t]) do
    begin
      if CustomHorizAxis=Self then
      begin
        CustomHorizAxis:=nil;
        HorizAxis:=aBottomAxis;
      end;

      if CustomVertAxis=Self then
      begin
        CustomVertAxis:=nil;
        VertAxis:=aLeftAxis;
      end;
    end;
  end;

begin
  FShape.Free;
  FSubAxes.Free;
  FMinorTicks.Free;
  FTicks.Free;
  FTicksInner.Free;
  FGrid.Free;
  FMinorGrid.Free;
  FAxis.Free;
  FAxisTitle.Free;
  FAxisItems.Free;
  FLabels.Free;

  ResetSeriesAxes;
  ISeriesList.Free;
  FParentChart.FAxes.Remove(Self);

  Transform.Free;

  Tick:=nil;

  inherited;
end;

function TChartAxis.GetParentAxis:TChartAxis;
begin
  if Assigned(Collection) and (CollectionOwner(Collection) is TChartAxis) then
     result:=TChartAxis(CollectionOwner(Collection))
  else
     result:=nil;
end;

function TChartAxis.GetAxisTitle:TChartAxisTitle;
begin
  if not Assigned(FAxisTitle) then
  begin
    FAxisTitle:=TChartAxisTitle.Create(ParentChart);

    if not Horizontal then
      if (Self=ParentChart.FLeftAxis) or (not OtherSide) then
         FAxisTitle.InitAngle(90)
      else
      if (Self=ParentChart.FRightAxis) or OtherSide then
         FAxisTitle.InitAngle(270);

    FAxisTitle.IDefaultAngle:=FAxisTitle.Angle;
  end;

  result:=FAxisTitle;
end;

type
  TShadowAccess=class(TTeeShadow);
  TEmbossAccess=class(TTeeEmboss);
  TTeeCustomShapeAccess=class(TTeeCustomShape);

function TChartAxis.GetShape:TAxisBackShape;
begin
  if not Assigned(FShape) then
  begin
    FShape:=TAxisBackShape.Create(ParentChart);

    FShape.Visible:=False;
    TTeeCustomShapeAccess(FShape).DefaultVisible:=False;

    TShadowAccess(FShape.Shadow).DefaultVisible:=False;

    // Special case for this shape, it should draw in 3D
    TTeeCustomShapeAccess(FShape).FDraw3D:=True;

    FShape.Shadow.Visible:=False;
  end;

  result:=FShape;
end;

function TChartAxis.GetSubAxes:TChartCustomAxes;
begin
  if not Assigned(FSubAxes) then
     FSubAxes:=TChartCustomAxes.Create(Self,TChartAxis);

  result:=FSubAxes;
end;

procedure TChartAxis.SetSubAxes(const Value:TChartCustomAxes);
begin
  if Assigned(Value) then
     SubAxes.Assign(Value)
  else
     FreeAndNil(FSubAxes);
end;

function TChartAxis.IsShapeStored:Boolean;
begin
  result:=Assigned(FShape);
end;

function TChartAxis.IsSubAxesStored:Boolean;
begin
  result:=Assigned(FSubAxes) and (FSubAxes.Count>0);
end;

function TChartAxis.CalcIsDateTime(const ADateTime:TDateTimeStep):Boolean;
begin
  result:=FExactDateTime and IAxisDateTime and (ADateTime>=dtHalfMonth);
end;

function TChartAxis.IncDec(Increment:Boolean;
                           var Value:TAxisValue;
                           const AnIncrement:TAxisValue;
                           const ADateTime:TDateTimeStep):Boolean;
var Old : TAxisValue;
begin
  Old:=Value;

  TeeDateTimeIncrement(Increment,Value,AnIncrement,ADateTime);

  result:=not SameValue(Old,Value);
end;

function TChartAxis.IncDec(Increment:Boolean;
                           var Value:TAxisValue;
                           const AnIncrement:TAxisValue):Boolean;
var tmp : TAxisValueExtended;
begin
  //DB: Commented Jan-2012, fix breaks in XE2:
  //http://qc.embarcadero.com/wc/qcmain.aspx?d=102898
  //TV52014609 avoid floating point infinitesimal problems in labels (eg, 2E-07 - 2E-07 = -5.826E-22)
  {
  if Increment then Value:=StrToFloat(FloatToStr(Value))+StrToFloat(FloatToStr(AnIncrement))
               else Value:=StrToFloat(FloatToStr(Value))-StrToFloat(FloatToStr(AnIncrement));
  }
  {
  if SameValue(Value,Value+AnIncrement) then
     Value:=0
  else
  }
  if Increment then tmp:=Value+AnIncrement
               else tmp:=Value-AnIncrement;

  {$IFDEF TEEVALUEEXTENDED}
  result:=True;
  {$ELSE}
  result:=Abs(tmp)<=MaxDouble;

  if result then
  {$ENDIF}
     Value:=tmp;
end;

procedure TChartAxis.Hide;
begin
  Visible:=False;
end;

{ An axis is "DateTime" if at least one Active Series with
  datetime values is associated to it }
Function TChartAxis.IsDateTime:Boolean;
var tmpSeries : Integer;
    tmpList   : TChartValueList;
    tmpMaster : TChartAxis;
Begin
  tmpMaster:=Self;
  while Assigned(tmpMaster.FMaster) do
        tmpMaster:=tmpMaster.FMaster;

  with ParentChart do
  for tmpSeries:=0 to SeriesCount-1 do
      With Series[tmpSeries] do
      if Active then
      begin
        tmpList:=ValueListOfAxis(tmpMaster);

        if Assigned(tmpList) then
        begin
          result:=tmpList.DateTime;
          exit;
        end;
      end;

  result:=False;
end;

Procedure TChartAxis.SetTicks(const Value:TTicks);
Begin
  FTicks.Assign(Value);
end;

Procedure TChartAxis.SetMinorTicks(const Value:TTicks);
Begin
  FMinorTicks.Assign(Value);
end;

procedure TChartAxis.SetShape(const Value:TAxisBackShape);
begin
  if Assigned(Value) then
     Shape.Assign(Value)
  else
     FreeAndNil(FShape);
end;

Procedure TChartAxis.SetTicksInner(const Value:TTicks);
Begin
  FTicksInner.Assign(Value);
end;

Procedure TChartAxis.SetGrid(const Value:TAxisGridPen);
Begin
  FGrid.Assign(Value);
end;

Procedure TChartAxis.SetMinorGrid(const Value:TChartHiddenPen);
Begin
  FMinorGrid.Assign(Value);
end;

Procedure TChartAxis.SetGridCentered(Value:Boolean);
Begin
  Grid.Centered:=Value;
end;

Procedure TChartAxis.SetAxis(const Value:TChartAxisPen);
Begin
  FAxis.Assign(Value);
end;

Function TChartAxis.IsPosStored:Boolean;
begin
  result:=FPositionPercent<>0;
end;

Function TChartAxis.IsStartStored:Boolean;
begin
  result:=FStartPosition<>0;
end;

Function TChartAxis.IsEndStored:Boolean;
begin
  result:=FEndPosition<>100;
end;

Function TChartAxis.IsCustom:Boolean;
begin
  result:=(GetParentAxis<>nil) or
          (ParentChart.Axes.IndexOf(Self)>=TeeInitialCustomAxis);
end;

procedure TChartAxis.SetHorizontal(const Value: Boolean);
begin
  ParentChart.SetBooleanProperty(FHorizontal,Value);
  SetCalcPosValue;
end;

procedure TChartAxis.SetOtherSide(const Value: Boolean);
begin
  ParentChart.SetBooleanProperty(FOtherSide,Value);
end;

Function TChartAxis.InternalCalcPosPoint(const Value:Integer):TAxisValue;

  Function InternalCalcPos(Const A,B:TAxisValue):TAxisValue;
  begin
    if (Horizontal and FInverted) or
       ((not Horizontal) and (not FInverted)) then result:=A
                                              else result:=B
  end;

var tmp : TAxisValueExtended;
Begin
  if FLogarithmic then
  Begin
    if Value=IStartPos then result:=InternalCalcPos(IMaximum,IMinimum)
    else
    if Value=IEndPos then result:=InternalCalcPos(IMinimum,IMaximum)
    else
    begin
      tmp:=IRangeLog;
      if (tmp=0) or (IAxisSize=0) then result:=IMinimum
      else
      begin
        if FInverted then tmp:=((IEndPos-Value)*tmp/IAxisSize)
                     else tmp:=((Value-IStartPos)*tmp/IAxisSize);
        if Horizontal then result:=Exp(ILogMin+tmp)
                      else result:=Exp(ILogMax-tmp);
      end;
    end;
  end
  else
  if IAxisSize>0 then
  begin
    if FInverted then tmp:=IEndPos-Value
                 else tmp:=Value-IStartPos;

    tmp:=tmp*IRange/IAxisSize;

    if Horizontal then result:=IMinimum+tmp
                  else result:=IMaximum-tmp;
  end
  else result:=0;
end;

Procedure TChartAxis.SetDateTimeFormat(Const Value:String);
Begin
  ParentChart.SetStringProperty(FDateTimeFormat,Value);
end;

procedure TChartAxis.SetAxisTitle(const Value:TChartAxisTitle);
begin
  if Assigned(Value) then Title.Assign(Value)
                     else FreeAndNil(FAxisTitle);
end;

procedure TChartAxis.SetStartPosition(Const Value:Double);
begin
  ParentChart.SetDoubleProperty(FStartPosition,Value);
end;

procedure TChartAxis.SetEndPosition(Const Value:Double);
begin
  ParentChart.SetDoubleProperty(FEndPosition,Value);
end;

procedure TChartAxis.SetPositionPercent(Const Value:Double);
begin
  ParentChart.SetDoubleProperty(FPositionPercent,Value);
end;

procedure TChartAxis.SetRoundFirstLabel(Value:Boolean);
begin
  FLabels.RoundFirst:=Value;
end;

Procedure TChartAxis.SetLabelsMultiLine(Value:Boolean);
begin
  FLabels.MultiLine:=Value;
end;

Procedure TChartAxis.SetLabelsExponent(Value:Boolean);
begin
  FLabels.Exponent:=Value;
end;

procedure TChartAxis.SetZPosition(const Value: Double);
begin
  ParentChart.SetDoubleProperty(FZPosition,Value);

  if FZPosition=0 then
     FZPosition:=0.01;  // VCL bug double=0 streaming
end;

procedure TChartAxis.SetTickOnLabelsOnly(Value:Boolean);
begin
  ParentChart.SetBooleanProperty(FTickOnLabelsOnly,Value);
end;

function TChartAxis.CalcDateTimeIncrement(MaxNumLabels:Integer):TAxisValue;
var TempNumLabels : Integer;
begin
  result:=Math.Max(FDesiredIncrement,DateTimeStep[Low(DateTimeStep)]);

  if (result>0) and (MaxNumLabels>0) then
  begin
    if (IRange/Result)>1000000 then Result:=IRange/1000000;

    Repeat
      TempNumLabels:=Round(IRange/result);
      if TempNumLabels>MaxNumLabels then
         if result<DateTimeStep[dtOneYear] then
         begin
            if result<DateTimeStep[dtOneSecond] then
               result:=TeeNextStep(result)  // less than one second
            else
               result:=NextDateTimeStep(result) // regular datetime steps
         end
         else
            result:=2.0*result;  // years

    Until (TempNumLabels<=MaxNumLabels){ or (result=DateTimeStep[dtOneYear])};

  end;
  result:=Math.Max(result,DateTimeStep[Low(DateTimeStep)]);
end;

Function TChartAxis.RoundLogPower(const Value:TAxisValueExtended):TAxisValue;
begin
  result:=IntPower(LogarithmicBase,Round(LogN(LogarithmicBase,Value)));
end;

Function TChartAxis.CalcLabelsIncrement(MaxNumLabels:Integer):TAxisValue;

  procedure InternalCalcLabelsIncrement;

    Function AnySeriesHasLessThan(Num:Integer):Boolean;

      function IsAssociated(const ASeries:TChartSeries):Boolean;
      begin
        result:=(
                 (ASeries.YMandatory and Horizontal) or
                 ((not ASeries.YMandatory) and (not Horizontal))
                )
                and ASeries.AssociatedToAxis(Self);
      end;

    var t : Integer;
        tmp : TChartSeries;
    begin
      for t:=0 to ParentChart.SeriesCount-1 do
      begin
        tmp:=ParentChart[t];

        if tmp.Active and IsAssociated(tmp) then
        begin
          result:=tmp.Count<=Num;

          if result then
             Exit;
        end;
      end;

      result:=False;
    end;

  var TempNumLabels : Integer;
      tmp           : TAxisValueExtended;
      tmpInf        : Boolean;
  begin
    TempNumLabels:=MaxNumLabels+1;

    if FDesiredIncrement<=0 then
    begin
      if IRangeZero then
         result:=1 // <-- TODO: What can we use instead of 1? A magnitude of Min?
      else
      begin
        tmp:=Abs(IRange)/Succ(MaxNumLabels);

        if Logarithmic then
           result:=RoundLogPower(tmp)
        else
           result:=tmp;

         if (IRange>=1.1) and // 1.1 = 1 + 2*margins (ie: Line Pen.Width)
            AnySeriesHasLessThan(MaxNumLabels) then
             result:=Math.Max(1,result);
      end;
    end
    else result:=FDesiredIncrement;

    tmpInf:=False;

    if (FLabels.FSeparation<>0) and ((IRange<>0) and (result<>0)) then
    repeat
      tmp:=IRange/result;

      if Abs(tmp)<MaxLongint then
      begin
        TempNumLabels:=Round(tmp);

        if TempNumLabels>MaxNumLabels then
           if Logarithmic then
              result:=result*LogarithmicBase
           else
           if FDesiredIncrement<=0 then  // <-- TeeNextStep only when Increment is Automatic (zero)
              result:=TeeNextStep(result)
           else
              result:=2*result;  // <-- Otherwise, respect custom Increment and try doubling it
      end
      else
      if Logarithmic then
         result:=result*LogarithmicBase
      else
         result:=TeeNextStep(result);

      {$IFDEF D16}
      tmpInf:=IsInfinite(result);
      {$ELSE}
        {$IFDEF TEEVALUEEXTENDED}
        tmpInf:=IsInfiniteExtended(result);
        {$ELSE}
        tmpInf:=IsInfinite(result);
        {$ENDIF}
      {$ENDIF}

    Until (TempNumLabels<=MaxNumLabels) or (result>IRange) or tmpInf;

    if tmpInf then result:=IRange
              else result:=Math.Max(result,MinAxisIncrement);
  end;

Begin
  if MaxNumLabels>0 then
  Begin
    if IAxisDateTime then result:=CalcDateTimeIncrement(MaxNumLabels)
                     else InternalCalcLabelsIncrement;
  end
  else
  if IAxisDateTime then result:=DateTimeStep[Low(DateTimeStep)]
                   else result:=MinAxisIncrement;
end;

Function TChartAxis.LabelWidth(Const Value:TAxisValue):Integer;
begin
  result:=InternalLabelSize(Value,True);
end;

Function TChartAxis.LabelHeight(Const Value:TAxisValue):Integer;
begin
  result:=InternalLabelSize(Value,False);
end;

Function TChartAxis.InternalLabelSize(Const Value:TAxisValue; IsWidth:Boolean):Integer;
var tmp      : Integer;
    tmpMulti : Boolean;
Begin
  if LabelsGridIgnoresLabelSize then
     result := 1
  else
  Begin
    result:=ParentChart.MultiLineTextWidth(LabelValue(Value),tmp);

    if IsWidth then
       tmpMulti:=(FLabels.Angle=90) or (FLabels.Angle=270)
    else
       tmpMulti:=(FLabels.Angle=0) or (FLabels.Angle=180);

    if tmpMulti then result:=ParentChart.Canvas.FontHeight*tmp;
  end;
end;

Function TChartAxis.IsMaxStored:Boolean;
Begin { dont store max property if automatic }
  result:=(not FAutomatic) and (not FAutomaticMaximum);
end;

Function TChartAxis.IsMinStored:Boolean;
Begin{ dont store min property if automatic }
  result:=(not FAutomatic) and (not FAutomaticMinimum);
end;

Function TChartAxis.CalcXYIncrement(MaxLabelSize:Integer):TAxisValue;
var tmp : Integer;
begin
  if (MaxLabelSize>0) and (IAxisSize>0) then  // TV52014138
  begin
    if FLabels.Separation>0 then
       Inc(MaxLabelSize,Round(0.01*FLabels.Separation*MaxLabelSize));

    tmp:=Round((1.0*IAxisSize)/MaxLabelSize)
  end
  else tmp:=1;

  result:=CalcLabelsIncrement(tmp)
end;

{$IFNDEF D6}
function IsNan(const AValue: Double): Boolean;
begin
  Result := ((PInt64(@AValue)^ and $7FF0000000000000)  = $7FF0000000000000) and
            ((PInt64(@AValue)^ and $000FFFFFFFFFFFFF) <> $0000000000000000)
end;
{$ENDIF}

Function TChartAxis.CalcIncrement:TAxisValue;

  function MaxLabelSizeWithMidPoint(const AMin,AMax:TAxisValue):Integer;
  var tmpMid : TAxisValue;
      tmp2   : Integer;
  begin
    // Do not use (tmpMin+tmpMax)*0.5 to avoid overflow
    tmpMid:=AMin+(AMax-AMin)*0.5;

    if IsInfinite(tmpMid) then
       tmpMid:=0;

    while Frac(tmpMid)=0 do  // TV52012014
    begin
      if (tmpMid=0) or IsNan(tmpMid) then
         break
      else
         tmpMid:=tmpMid*0.5;
    end;

    tmp2:=InternalLabelSize(tmpMid,Horizontal);

    result:=Math.Max(Math.Max(InternalLabelSize(AMin,Horizontal),tmp2),
                  InternalLabelSize(AMax,Horizontal));
  end;

var tmp    : Integer;
    tmpMin : TAxisValue;
    tmpMax : TAxisValue;
begin
  if RoundFirstLabel and (Increment<>0) then
  begin
    if Abs(IMinimum)>High(Int64)  then tmpMin:=Increment*IMinimum //NCR TV52014949
                                  else tmpMin:=Increment*Round(IMinimum);
    tmpMax:=Increment*Int(IMaximum);
  end
  else
  begin
    tmpMin:=IMinimum;
    tmpMax:=IMaximum;
  end;

  if Increment=0 then
     tmp:=MaxLabelSizeWithMidPoint(tmpMin,tmpMax)
  else
  begin
    tmp:=Math.Max(InternalLabelSize(tmpMin,Horizontal),
                  InternalLabelSize(tmpMax,Horizontal));

    tmp:=Math.Max(Math.Max(tmp,InternalLabelSize(tmpMin+Increment,Horizontal)),
                               InternalLabelSize(tmpMax-Increment,Horizontal));
  end;

  result:=CalcXYIncrement(tmp);

  (*
  // Special case with automatic increment and fraction min and max:
  if (not LabelsAlternate) and (Increment=0) and (result>=2) then
  begin
    Increment:=1;
    try
      result:=CalcIncrement;  // <-- repeat, try again
    finally
      Increment:=0;
    end;
  end;
  *)

  if LabelsAlternate then
     if Logarithmic then
        result:=result/LogarithmicBase
     else
        result:=result*0.5;
end;

Procedure TChartAxis.AdjustMaxMinRect(Const Rect:TRect);
var tmpMin : TAxisValue;
    tmpMax : TAxisValue;

  Procedure RecalcAdjustedMinMax(const Pos1,Pos2:Integer);
  var OldStart : Integer;
      OldEnd   : Integer;
  Begin
    OldStart :=IStartPos;
    OldEnd   :=IEndPos;
    IStartPos:=IStartPos+Pos1;
    IEndPos  :=IEndPos-Pos2;

    IAxisSize:=IEndPos-IStartPos;

    tmpMin:=InternalCalcPosPoint(OldStart);
    tmpMax:=InternalCalcPosPoint(OldEnd);
  end;

var t : Integer;
    tmpValue : TAxisValue;
begin
  with Rect{$IFDEF FMX}.Round{$ENDIF} do
  if Horizontal then
     ReCalcAdjustedMinMax(Left, Right)
  else
     ReCalcAdjustedMinMax(Top, Bottom);

  InternalCalcPositions;

  IMaximum:=tmpMax;
  IMinimum:=tmpMin;

  if IMinimum>IMaximum then
  begin
    tmpValue:=IMinimum;
    IMinimum:=IMaximum;
    IMaximum:=tmpValue;
  end;

  InternalCalcRange;

  if Assigned(FSubAxes) then
  for t:=0 to FSubAxes.Count-1 do
  begin
    FSubAxes[t].IMinimum:=IMinimum;
    FSubAxes[t].IMaximum:=IMaximum;
    FSubAxes[t].InternalCalcRange;
    FSubAxes[t].InternalCalcPositions;
  end;
end;

Procedure TChartAxis.CalcMinMax(out AMin,AMax:TAxisValue);
Begin
  if Automatic or AutomaticMaximum then
     AMax:=ParentChart.InternalMinMax(Self,False,Horizontal);

  if Automatic or AutomaticMinimum then
     AMin:=ParentChart.InternalMinMax(Self,True,Horizontal);
end;

const
  FuzzFactor = 1000;
  ExtendedResolution = 1E-19 * FuzzFactor;
  DoubleResolution   = 1E-15 * FuzzFactor;
  SingleResolution   = 1E-7 * FuzzFactor;

function IsZero(const A: TAxisValue; Epsilon: TAxisValue = 0): Boolean;
begin
  if Epsilon = 0 then
    Epsilon := {$IFDEF TEEVALUEEXTENDED}ExtendedResolution{$ELSE}DoubleResolution{$ENDIF};
  Result := Abs(A) <= Epsilon;
end;

Procedure TChartAxis.InternalCalcRange;
begin
  IRange:=IMaximum-IMinimum;

  IRangeZero:={$IFDEF TEENOASM}IRange=0{$ELSE}IsZero(IRange){$ENDIF};

  if IRangeZero then
     IAxisSizeRange:=0
  else
  {$IFDEF CPUX64}
  if IRange<=10*MinAxisIncrement then // Overflow check
     IAxisSizeRange:=0
  else
  {$ENDIF}
     IAxisSizeRange:=IAxisSize/IRange;

  if FLogarithmic then
  begin
    if IMinimum<=0 then ILogMin:=0 else ILogMin:=ln(IMinimum);
    if IMaximum<=0 then ILogMax:=0 else ILogMax:=ln(IMaximum);

    IRangeLog:=ILogMax-ILogMin;

    if IsZero(IRangeLog) then
       IAxisLogSizeRange:=0
    else
       IAxisLogSizeRange:=IAxisSize/IRangeLog;
  end;

  IZPos:=CalcZPos;
end;

Procedure TChartAxis.CalcRoundScales;
var tmpInc : TAxisValue;
    tmp    : Boolean;
    tmpAny : Boolean;
Begin
  if (IAxisSize<>0) and (not IRangeZero) then
  begin
    tmp:=MaximumRound and (Automatic or AutomaticMaximum);
    tmpAny:=tmp;

    if tmp then
    begin
      tmpInc:=CalcIncrement;

      if tmpInc<>0 then
      begin
        IMaximum:=IMaximum/tmpInc;
        IMaximum:=tmpInc*Ceil(IMaximum);
      end;
    end
    else
      tmpInc:=0;

    tmp:=MinimumRound and (Automatic or AutomaticMinimum);
    tmpAny:=tmpAny or tmp;

    if tmp then
    begin
      if tmpInc=0 then
         tmpInc:=CalcIncrement;

      if tmpInc<>0 then
      begin
        IMinimum:=IMinimum/tmpInc;
        IMinimum:=tmpInc*Floor(IMinimum);
      end;
    end;

    if tmpAny then
       InternalCalcRange;
  end;
end;

Procedure TChartAxis.AdjustMaxMin;
var tmpStop : Boolean;
    tmpGap  : TAxisValue;
Begin
  CalcMinMax(FMinimumValue,FMaximumValue);
  IMaximum:=FMaximumValue;
  IMinimum:=FMinimumValue;

  if Assigned(OnCalcMaxLabel) then
  begin
    tmpStop:=False;
    tmpGap:=IMaximum;
    OnCalcMaxLabel(Self,-1,tmpGap,tmpStop);
    IMaximum:=IMaximum-tmpGap;
  end;

  InternalCalcRange;
  CalcRoundScales;
end;

procedure TChartAxis.Assign(Source: TPersistent);
Begin
  if Source is TChartAxis then
  With TChartAxis(Source) do
  Begin
    Self.FAxis.Assign(FAxis);
    Self.FAxisItems.CopyFrom(FAxisItems);
    Self.FLabels.Assign(FLabels);
    Self.Ticks                :=FTicks;
    Self.TicksInner           :=FTicksInner;
    Self.FTitleSize           :=FTitleSize;
    Self.Grid                 :=FGrid;
    Self.MinorTicks           :=FMinorTicks;
    Self.MinorGrid            :=FMinorGrid;
    Self.FTickLength          :=FTickLength;
    Self.FMinorTickLength     :=FMinorTickLength;
    Self.FMinorTickCount      :=FMinorTickCount;
    Self.FTickInnerLength     :=FTickInnerLength;
    Self.FAxisValuesFormat    :=FAxisValuesFormat;
    Self.FDesiredIncrement    :=FDesiredIncrement;

    Self.FMaximumValue        :=FMaximumValue;
    Self.FMaximumOffset       :=FMaximumOffset;
    Self.FMaximumRound        :=FMaximumRound;

    Self.FMinimumValue        :=FMinimumValue;
    Self.FMinimumOffset       :=FMinimumOffset;
    Self.FMinimumRound        :=FMinimumRound;

    Self.FAutomatic           :=FAutomatic;
    Self.FAutomaticMaximum    :=FAutomaticMaximum;
    Self.FAutomaticMinimum    :=FAutomaticMinimum;

    Self.Title                :=FAxisTitle;
    Self.FDateTimeFormat      :=FDateTimeFormat;
    Self.GridCentered         :=GridCentered;
    Self.FLogarithmic         :=FLogarithmic;
    Self.FLogarithmicBase     :=FLogarithmicBase;
    Self.FInverted            :=FInverted;
    Self.FExactDateTime       :=FExactDateTime;
    Self.FTickOnLabelsOnly    :=FTickOnLabelsOnly;
    Self.IDepthAxis           :=IDepthAxis;
    Self.FStartPosition       :=FStartPosition;
    Self.FEndPosition         :=FEndPosition;
    Self.FPositionPercent     :=FPositionPercent;
    Self.FPosUnits            :=FPosUnits;
    Self.FVisible             :=FVisible;

    Self.FHorizontal          :=FHorizontal;
    Self.FOtherSide           :=FOtherSide;

    Self.SetShape(FShape);
    Self.SetSubAxes(FSubAxes);

    Self.FPartnerAxis         :=FPartnerAxis;
    Self.FUsePartnerAxis      :=FUsePartnerAxis;
  end
  else inherited;
end;

class function TChartAxis.ToDegree(const Value:Single):String;
const
  OneDiv60=1/60;
  MaxLongSingle:Single=2E15;
  MinLongSingle:Single=-2E15;

var Degrees,
    Minutes,
    Seconds : Integer;
    FloatSeconds : Single;
begin
  if Value>MaxLongSingle then
     result:='+INF'
  else
  if Value<MinLongSingle then
     result:='-INF'
  else
  begin
    Degrees:=Trunc(Value);

    result:=TeeStr(Degrees)+'°';

    FloatSeconds:=(Value-Degrees)*3600;
    Minutes:=Trunc(FloatSeconds*OneDiv60);
    Seconds:=Trunc(FloatSeconds-(Minutes*60));

    if (Seconds<>0) or (Minutes<>0) then
    begin
      result:=result+TeeStr(Minutes)+'''';

      if Seconds<>0 then
         result:=result+TeeStr(Seconds)+'"';
    end;
  end;
end;

class function TChartAxis.ToDegree(const Value:Single; const IsLat:Boolean):String;
begin
  result:=ToDegree(Abs(Value));

  if IsLat then
     if Value<0 then
        result:=result+' S'
     else
        result:=result+' N'
  else
     if Value<0 then
        result:=result+' W'
     else
        result:=result+' E';
end;

Function TChartAxis.LabelValue(Const Value:TAxisValue):String;
var tmp : String;
Begin
  if IAxisDateTime then
  begin
    if FDateTimeFormat='' then tmp:=DateTimeDefaultFormat(IRange)
                          else tmp:=FDateTimeFormat;

    DateTimeToString(result,tmp,Value);
  end
  else
  if IAxisGeo then
     result:=ToDegree(Value,not Horizontal)
  else
     result:=FormatFloat(FAxisValuesFormat,Value);

  if Assigned(ParentChart.FOnGetAxisLabel) then
     ParentChart.FOnGetAxisLabel(TChartAxis(Self),nil,-1,Result);

  if FLabels.MultiLine then
     result:=ReplaceChar(result,' ',TeeLineSeparator);
end;

Function TChartAxis.InternalCalcLabelStyle:TAxisLabelStyle;
var t : Integer;
    tmpParent : TChartAxis;
    tmp : TChartSeries;
begin
  tmpParent:=GetParentAxis;

  if Assigned(tmpParent) then
     result:=tmpParent.InternalCalcLabelStyle
  else
  begin
    result:=talNone;

    for t:=0 to ParentChart.SeriesCount-1 do
    begin
      tmp:=ParentChart.Series[t];

      if tmp.Active and tmp.AssociatedToAxis(Self) then
      begin
        result:=tmp.AxisLabelStyle(Self);

        if result<>talValue then
           break;
      end;
    end;
  end;
end;

Function TChartAxis.CalcLabelStyle:TAxisLabelStyle;
begin
  if FLabels.Style=talAuto then result:=InternalCalcLabelStyle
                           else result:=FLabels.Style;
end;

Procedure TChartAxis.MaxLabelsValue(out AWidth:Integer; out ALines: Integer);
var tmp    : TAxisValue;
    tmpA   : TAxisValueExtended;
    tmpB   : TAxisValueExtended;
    tmpNum : Integer;
    limit : Integer;
begin
  tmp:=CalcIncrement;

  if (IsDateTime and FExactDateTime) or RoundFirstLabel then
  begin
    tmpA:=tmp*Int(IMinimum/tmp);
    tmpB:=tmp*Int(IMaximum/tmp);
  end
  else
  begin
    tmpA:=IMinimum;
    tmpB:=IMaximum;
  end;

  with ParentChart do
  begin
    if tmp<>0 then
    begin
      // Calculate maximum label width, considering all labels:
      AWidth:=0;
      ALines:=0;
      limit:=5000;

      repeat  // TV52012721  modified 23_7_08 MM see bug report
        AWidth:=Math.Max(AWidth,MultiLineTextWidth(LabelValue(tmpB),tmpNum));
        ALines:=Math.Max(ALines,tmpNum);

        tmpB:=tmpB-tmp;
        Dec(limit);
      until (tmpB<tmpA) or (limit=0);
    end
    else
    begin
      // Calculate maximum label width using only min and max labels (faster):
      AWidth:=MultiLineTextWidth(LabelValue(tmpA),ALines);
      AWidth:=Math.Max(AWidth,MultiLineTextWidth(LabelValue(tmpB),tmpNum));
      ALines:=Math.Max(ALines,tmpNum);
    end;

    Inc(AWidth,Canvas.TextWidth(' '));

    // Not yet perfect:
    //if FLabels.Exponent and (Pos('E',AxisValuesFormat)>0) then
    //   result:=result-5;
  end;
end;

Function TChartAxis.MaxLabelsWidth:Integer;

  Function MaxAxisTextWidth:Integer;
  var tt, tmp, tmpResult, tmpSeries : Integer;
      tmpLabel : String;
  begin
    tmpResult:=0;

    With ParentChart do
    for tmpSeries:=0 to SeriesCount-1 do
        With Series[tmpSeries] do
        for tt:=0 to Count-1 do
        begin
          tmpLabel:=Labels[tt];
          if Assigned(ParentChart.FOnGetAxisLabel) then
             ParentChart.FOnGetAxisLabel(TChartAxis(Self),Series[tmpSeries],tt,tmpLabel);

          if tmpLabel<>'' then
             tmpResult:=Max(tmpResult,ParentChart.MultiLineTextWidth(tmpLabel,tmp,LabelsFormat.TextFormat));
        end;

    result:=tmpResult;
  end;

var t   : Integer;
    tmp : Integer;
begin
  if Items.Automatic then
    Case CalcLabelStyle of
 talPointValue,
      talValue : MaxLabelsValue(result,tmp);
      talMark  : result:=ParentChart.MaxMarkWidth;
      talText  : result:=MaxAxisTextWidth  // TF02014509
    else
    {talNone : } result:=0;
    end
  else
  begin
    result:=0;

    for t:=0 to Items.Count-1 do
    begin
      ParentChart.PrepareFont3D(Items[t].ActiveFormat.Font);
      result:=Max(result,ParentChart.MultiLineTextWidth(Items[t].Text,tmp,LabelsFormat.TextFormat));
    end;
  end;
end;

Function TChartAxis.GetLabels:Boolean;
begin
  result:=FLabels.Visible;
end;

Function TChartAxis.GetLabelsFormat:TAxisLabelsFormat;
begin
  result:=FLabels.Format;
end;

Function TChartAxis.GetLabelsFont:TTeeFont;
begin
  result:=FLabels.Font;
end;

Procedure TChartAxis.SetLabels(Value:Boolean);
Begin
  FLabels.Visible:=Value;
end;

Procedure TChartAxis.SetLabelsFormat(const Value:TAxisLabelsFormat);
begin
  FAxisItems.Format.Assign(Value);
end;

Procedure TChartAxis.SetLabelsFont(const Value:TTeeFont);
begin
  FLabels.Font:=Value;
end;

Function TChartAxis.DepthAxisAlign:Integer;
begin
  if OtherSide then result:=TA_LEFT
               else result:=TA_RIGHT;
end;

Function TChartAxis.DepthAxisPos:Integer;
begin
  if OtherSide then
     result:={$IFDEF FMX}Round{$ENDIF}(ParentChart.ChartRect.Bottom-CalcZPos)
  else
     result:={$IFDEF FMX}Round{$ENDIF}(ParentChart.ChartRect.Top+CalcZPos) // TV52010193
end;

function TChartAxis.GetLabelsAlign:TAxisLabelAlign;
begin
  result:=FLabels.Align;
end;

function TChartAxis.GetLabelsAlternate:Boolean;
begin
  result:=FLabels.Alternate;
end;

function TChartAxis.GetLabelsAngle:Integer;
begin
  result:=FLabels.Angle;
end;

function TChartAxis.GetLabelsBehind:Boolean;
begin
  result:=FLabels.Behind;
end;

function TChartAxis.GetLabelsExponent:Boolean;
begin
  result:=FLabels.Exponent;
end;

function TChartAxis.GetLabelsGridIgnoresSize:Boolean;
begin
  result:=FLabels.GridIgnoresLabelSize;
end;

function TChartAxis.GetLabelsMultiLine:Boolean;
begin
  result:=FLabels.MultiLine;
end;

function TChartAxis.GetLabelsOnAxis:Boolean;
begin
  result:=FLabels.OnAxis;
end;

function TChartAxis.GetLabelsSeparation:Integer;
begin
  result:=FLabels.Separation;
end;

function TChartAxis.GetLabelsSize:Integer;
begin
  result:=FLabels.Size;
end;

function TChartAxis.GetLabelStyle:TAxisLabelStyle;
begin
  result:=FLabels.Style;
end;

function TChartAxis.GetRoundFirstLabel:Boolean;
begin
  result:=FLabels.RoundFirst;
end;

Procedure TChartAxis.SetAutomatic(Value:Boolean);
Begin
  ParentChart.SetBooleanProperty(FAutomatic,Value);

  if not (csLoading in ParentChart.ComponentState) then
  begin
    FAutomaticMinimum:=Value;
    FAutomaticMaximum:=Value;
  end;
end;

Procedure TChartAxis.SetAutoMinMax(var Variable:Boolean; Var2,Value:Boolean);
Begin
  ParentChart.SetBooleanProperty(Variable,Value);
  if Value then
  begin { if both are automatic, then Automatic should be True too }
    if Var2 then FAutomatic:=True;
  end
  else FAutomatic:=False;
end;

Procedure TChartAxis.SetAutomaticMinimum(Value:Boolean);
Begin
  SetAutoMinMax(FAutomaticMinimum,FAutomaticMaximum,Value);
end;

Procedure TChartAxis.SetAutomaticMaximum(Value:Boolean);
Begin
  SetAutoMinMax(FAutomaticMaximum,FAutomaticMinimum,Value);
end;

Function TChartAxis.IsAxisValuesFormatStored:Boolean;
begin
  result:=FAxisValuesFormat<>TeeMsg_DefValueFormat;
end;

Procedure TChartAxis.SetValuesFormat(Const Value:String);
Begin
  ParentChart.SetStringProperty(FAxisValuesFormat,Value);
end;

Procedure TChartAxis.SetInverted(Value:Boolean);
Begin
  if Assigned(FMaster) then
     FMaster.FInverted:=Value;

  ParentChart.SetBooleanProperty(FInverted,Value);
end;

Procedure TChartAxis.SetItems(const Value:TAxisItems);
Begin
  FAxisItems.CopyFrom(Value);
end;

Procedure TChartAxis.InternalSetInverted(Value:Boolean);
Begin
  FInverted:=Value;
end;

Procedure TChartAxis.SetLabelsBehind(Value:Boolean);
Begin
  FLabels.Behind:=Value;
end;

Procedure TChartAxis.SetLabelsGridIgnoresSize(Value:Boolean);
Begin
  FLabels.GridIgnoresLabelSize:=Value;
end;

Procedure TChartAxis.SetLogarithmicBase(const Value:Double);
begin
  if (Value=1) or (Value<=0) then
     raise AxisException.Create(TeeMsg_AxisLogBase);

  ParentChart.SetDoubleProperty(FLogarithmicBase,Value);
end;

Procedure TChartAxis.SetLogarithmic(Value:Boolean);
Begin
  if Value then
  begin
    AdjustMaxMin;

    if ((IMinimum<0) or (IMaximum<0)) then
       Raise AxisException.Create(TeeMsg_AxisLogNotPositive);
  end;

  ParentChart.SetBooleanProperty(FLogarithmic,Value);

  if Assigned(FMaster) then
     FMaster.Logarithmic:=Value;

  SetCalcPosValue;
end;

Procedure TChartAxis.SetCalcPosValue;
begin
  CalcPosPoint := InternalCalcPosPoint;

  if IDepthAxis then
  begin
    CalcXPosValue:=InternalCalcDepthPosValue;
    CalcYPosValue:=InternalCalcDepthPosValue;
  end
  else
  if Logarithmic then
  begin
    CalcXPosValue:=LogXPosValue;
    CalcYPosValue:=LogYPosValue;
  end
  else
  begin
    if ParentChart.Axes.IFastCalc then
    begin
      CalcXPosValue:=XPosValue;
      CalcYPosValue:=YPosValue;
    end
    else
    begin
      CalcXPosValue:=XPosValueCheck;
      CalcYPosValue:=YPosValueCheck;
    end;
  end;

  if Horizontal then CalcPosValue:=CalcXPosValue
                else CalcPosValue:=CalcYPosValue;
end;

Procedure TChartAxis.SetLabelsAlign(Value:TAxisLabelAlign);
Begin
  FLabels.Align:=Value;
end;

Procedure TChartAxis.SetLabelsAlternate(Value:Boolean);
Begin
  FLabels.Alternate:=Value;
end;

Procedure TChartAxis.SetLabelsAngle(const Value:Integer);
Begin
  FLabels.Angle:=Value;
end;

Procedure TChartAxis.SetLabelsSeparation(Value:Integer);
Begin
  FLabels.Separation:=Value;
end;

Procedure TChartAxis.SetLabelsSize(Value:Integer);
Begin
  FLabels.Size:=Value;
end;

Procedure TChartAxis.SetTitleSize(Value:Integer);
Begin
  ParentChart.SetIntegerProperty(FTitleSize,Value);
end;

Procedure TChartAxis.SetLabelsOnAxis(Value:Boolean);
Begin
  FLabels.OnAxis:=Value;
end;

Procedure TChartAxis.SetExactDateTime(Value:Boolean);
begin
  ParentChart.SetBooleanProperty(FExactDateTime,Value);
end;

Procedure TChartAxis.SetLabelStyle(Value:TAxisLabelStyle);
begin
  FLabels.Style:=Value;
end;

Procedure TChartAxis.SetVisible(Value:Boolean);
Begin
  ParentChart.SetBooleanProperty(FVisible,Value);
end;

Procedure TChartAxis.SetDesiredIncrement(Const Value:TAxisValue);
Begin
  if Value<0 then
     raise AxisException.Create(TeeMsg_AxisIncrementNeg);

  if IsDateTime then
     DateTimeToStr(Value);

  if FDesiredIncrement<>Value then
  begin
    FDesiredIncrement:=Value;
    ParentChart.Invalidate;
  end;
end;

Procedure TChartAxis.SetMinimum(Const Value:TAxisValue);
Begin
  if (not (csReading in ParentChart.ComponentState)) and
     (Value>FMaximumValue) then
       Raise AxisException.Create(TeeMsg_AxisMinMax);

  InternalSetMinimum(Value);
end;

Procedure TChartAxis.InternalSetMinimum(Const Value:TAxisValue);
Begin
  if FMinimumValue<>Value then
  begin
    FMinimumValue:=Value;
    ParentChart.ChangedAxisScale(Self);
  end;
end;

Procedure TChartAxis.SetMaximum(Const Value:TAxisValue);
Begin
  if (not (csReading in ParentChart.ComponentState)) and
     (Value<FMinimumValue) then
       Raise AxisException.Create(TeeMsg_AxisMaxMin);

  InternalSetMaximum(Value);
end;

Procedure TChartAxis.SetMaximumRound(Const Value:Boolean);
begin
  ParentChart.SetBooleanProperty(FMaximumRound,Value);
end;

Procedure TChartAxis.SetMinimumRound(Const Value:Boolean);
begin
  ParentChart.SetBooleanProperty(FMinimumRound,Value);
end;

Procedure TChartAxis.SetMinMax(const AMin,AMax:TAxisValue);
Begin
  FAutomatic:=False;
  FAutomaticMinimum:=False;
  FAutomaticMaximum:=False;

  if AMin>AMax then
  begin
    InternalSetMinimum(AMax);
    InternalSetMaximum(AMin);
  end
  else
  begin
    InternalSetMinimum(AMin);
    InternalSetMaximum(AMax);
  end;

  (*
    The changes using TAxisValueExtended allow really small/big min and max,
    so this check is not necessary:

  // Avoid setting Min = Max. Add a minimum difference between axis Max and Min.
  if SameValue(FMinimumValue,FMaximumValue) then
  begin
    InternalSetMinimum(FMinimumValue-MinAxisRange);
    InternalSetMaximum(FMaximumValue+MinAxisRange);
  end;
  *)

  ParentChart.CustomAxes.ResetScales(Self);
end;

Procedure TChartAxis.InternalSetMaximum(Const Value:TAxisValue);
Begin
  if FMaximumValue<>Value then
  begin
    FMaximumValue:=Value;
    ParentChart.ChangedAxisScale(Self);
  end;
end;

Procedure TChartAxis.SetTickLength(Value:Integer);
Begin
  ParentChart.SetIntegerProperty(FTickLength,Value);
end;

Procedure TChartAxis.SetMinorTickLength(Value:Integer);
Begin
  ParentChart.SetIntegerProperty(FMinorTickLength,Value);
end;

Procedure TChartAxis.SetMinorTickCount(Value:Integer);
Begin
  ParentChart.SetIntegerProperty(FMinorTickCount,Value);
End;

Procedure TChartAxis.SetTickInnerLength(Value:Integer);
Begin
  ParentChart.SetIntegerProperty(FTickInnerLength,Value);
end;

type
  TCanvas3DAccess=class(TCanvas3D);

Procedure TChartAxis.DrawTitle(X,Y:Integer);
var tmpPos : TPoint;

    tmpW,
    tmpH,
    tmpOffset,
    tmpHalfH : Integer;
    Old : Boolean;

    tmpSin : {$IFDEF D15}Double{$ELSE}Extended{$ENDIF};
    tmpCos : {$IFDEF D15}Double{$ELSE}Extended{$ENDIF};
begin
  TCanvas3DAccess(ParentChart.Canvas).BeginEntity('Title', nil, FAxisTitle.Transform);

  ParentChart.PrepareFont3D(FAxisTitle.Font);

  ParentChart.Canvas.BackMode:=cbmTransparent;

  if IsDepthAxis then
  begin
    ParentChart.Canvas.TextAlign:=DepthAxisAlign;

    ParentChart.Canvas.RotateLabel3D(X,Y,ParentChart.Width3D div 2,
           FAxisTitle.Caption,FAxisTitle.Angle,
           FAxisTitle.TextFormat=ttfHtml); // TV52010541
  end
  else
  begin
    if ParentChart.View3D and
       (not ParentChart.View3DOptions.Orthogonal) then
    begin
      Old:=FLabels.Exponent;
      FLabels.Exponent:=False;

      DrawAxisLabel(X,Y,FAxisTitle.Angle,FAxisTitle.Caption,FAxisTitle,IZPos);

      FLabels.Exponent:=Old;
    end
    else
    begin
      FAxisTitle.CalcBounds(ParentChart);

      tmpW:={$IFDEF FMX}Round{$ENDIF}(FAxisTitle.Width);
      tmpH:={$IFDEF FMX}Round{$ENDIF}(FAxisTitle.Height);

      if Horizontal then
      begin
        tmpPos.X:=X-(tmpW div 2);

        tmpPos.Y:=Y;

        if FAxisTitle.Angle=90 then
           if OtherSide then
              tmpPos.Y:=tmpPos.Y-tmpH
           else
              tmpPos.Y:=tmpPos.Y+tmpH;

        if OtherSide then
        begin
          tmpPos.Y:=tmpPos.Y-tmpH;

          if FAxisTitle.Shadow.Visible then
             tmpPos.Y:=tmpPos.Y-FAxisTitle.Shadow.VertSize;
        end;
      end
      else
      begin
        tmpHalfH:=(tmpH div 2);

        if OtherSide then
           tmpOffset:=tmpHalfH
        else
        begin
          tmpOffset:=-tmpHalfH;

          if FAxisTitle.Shadow.Visible then
             Dec(tmpOffset,FAxisTitle.Shadow.VertSize);
        end;

        SinCos(FAxisTitle.Angle*TeePiStep,tmpSin,tmpCos);

        if OtherSide then
           tmpSin:=Abs(tmpSin)
        else
           tmpSin:=2-Abs(tmpSin);

        tmpPos.X:=Round(tmpSin*tmpW*0.5);

        tmpPos.X:=X-tmpPos.X+tmpOffset;

        tmpPos.Y:=Y-tmpHalfH;
      end;

      tmpPos:=ParentChart.Canvas.Calculate3DPosition(tmpPos,IZPos);

      FAxisTitle.MoveTo(tmpPos);
      FAxisTitle.Draw;
    end;
  end;

  TCanvas3DAccess(ParentChart.Canvas).EndEntity;
end;

// pending: cache into variable (for speed)
Function TChartAxis.CalcZPos:Integer;
begin
  if IDepthAxis then result:=ParentChart.ChartHeight
                else result:=ParentChart.Width3D;

  result:=Round(result*ZPosition*0.01);
end;

// Returns True when Small rectangle is totally inside Big rectangle:
function RectangleContains(const Big,Small:TRect):Boolean;
begin
  result:=(Small.Left>=Big.Left) and
          (Small.Right<=Big.Right) and
          (Small.Top>=Big.Top) and
          (Small.Bottom<=Big.Bottom);
end;

type TAxisLabelsFormatAccess=class(TAxisLabelsFormat);

procedure TChartAxis.DrawAxisLabel(x,y,Angle:Integer; Const St:String;
                                   Format:TCustomTextShape=nil;
                                   z:Integer=0;
                                   const APicture:TTeePicture=nil;
                                   ItemIndex:Integer=-1);

  function ExponentPosition:Integer;
  begin
    result:=Pos('E',Uppercase(Format.Text));
  end;

  function BoundsInsideShape:Boolean;
  begin
    result:=True;

    if Assigned(FShape) and FShape.Visible and (not FShape.Transparent) then
       if not ParentChart.View3D then
          if not TeeRectIntersects(FShape.ShapeBounds,Format.ShapeBounds) then
             result:=False;
  end;

var
  tmpZ : Integer;

  function ShouldDraw:Boolean;
  var tmpSt : String;
  begin
    result:=True;

    if Assigned(FOnDrawLabel) then
    begin
      tmpSt:=Format.Text;
      FOnDrawLabel(Self,X,Y,tmpZ,tmpSt,result);

     if result then
        Format.Text:=tmpSt;
    end;
  end;

  Procedure DrawExponentLabel(const AText:String);
  var tmpW   : Integer;
      tmpW2  : Integer;
      tmpH   : Integer;
      i      : Integer;
      tmp    : String;
      tmpSub : String;
      Old    : Single;
  begin
    i:=ExponentPosition;

    With ParentChart.Canvas do
    if i=0 then
       TextOut3D(x,y,tmpZ,AText)
    else
    begin
      tmp:=Copy(AText,1,i-1);
      tmpSub:=Copy(AText,i+1,Length(AText)-1);
      tmpH:=FontHeight;

      Old:=Font.SizeFloat;

      tmpW:=TextWidth(tmp)+1;
      TextOut3D(x,y,tmpZ,tmp);
      Font.SizeFloat:=0.75*Font.SizeFloat;

      if TextAlign=TA_LEFT then
         TextOut3D(x+tmpW,y-(tmpH div 2)+2,tmpZ,tmpSub)
      else
      Begin
        if TextAlign=TA_CENTER then
           tmpW2:= (TextWidth(tmpSub) div 2)
        else
           tmpW2 := 0;

        TextOut3D(x+(tmpW div 2)+tmpW2,y-(tmpH div 2)+2,tmpZ,tmpSub);
      end;

      Font.SizeFloat:=Old;
    end;
  end;

  procedure DoDrawExponent;
  var tmpSt2 : String;
      tmpX   : TCoordinate;
  begin
    tmpSt2:=Format.Text;

    tmpX:=X;
    Format.CalcTextAlign(ParentChart,tmpX,Format.ShapeBounds,tmpSt2);

    X:={$IFDEF FMX}Round{$ENDIF}(tmpX);
    Y:={$IFDEF FMX}Round{$ENDIF}(Format.ShapeBounds.Top);

    if not Format.Transparent then
    begin
      Inc(X,Format.Margins.HorizSize);
      Inc(Y,Format.Margins.VertSize);
    end;

    DrawExponentLabel(tmpSt2);
  end;

  procedure DrawPicture;
  var tmp  : TRect;
      tmpX,
      tmpY : TCoordinate;
      tmpW,
      tmpH : Integer;
  begin
    tmp:=Format.ShapeBounds;

    if Horizontal then
    begin
      tmpX:={$IFDEF FMX}System.Round((tmp.Left+tmp.Right)*0.5){$ELSE}(tmp.Left+tmp.Right) div 2{$ENDIF};
      tmpW:=(APicture.Width div 2);
      tmp.Left:=tmpX-tmpW;
      tmp.Right:=tmpX+tmpW;

      if OtherSide then
      begin
        tmp.Bottom:=tmp.Top;
        tmp.Top:=tmp.Bottom-APicture.Height;
      end
      else
      begin
        tmp.Top:=tmp.Bottom;
        tmp.Bottom:=tmp.Top+APicture.Height;
      end;
    end
    else
    begin
      if OtherSide then
      begin
        tmp.Left:=tmp.Right;
        tmp.Right:=tmp.Left+APicture.Width;
      end
      else
      begin
        tmp.Right:=tmp.Left;
        tmp.Left:=tmp.Right-APicture.Width;
      end;

      tmpY:={$IFDEF FMX}System.Round((tmp.Top+tmp.Bottom)*0.5){$ELSE}(tmp.Top+tmp.Bottom) div 2{$ENDIF};
      tmpH:=(APicture.Height div 2);
      tmp.Top:=tmpY-tmpH;
      tmp.Bottom:=tmpY+tmpH;
    end;

    ParentChart.Canvas.StretchDraw(tmp,APicture.Filtered);
  end;

  procedure DoRotateBounds(const AWidth,AHeight,ALineCount:Integer);
  var tmpAngle : Single;
      tmpXdisp,
      tmpYdisp : Single;
  begin
    with Format.ShapeBounds do
    begin
      tmpAngle:={$IFDEF FMX}Width*0.5{$ELSE}(Right-Left)*0.5{$ENDIF};

      if OtherSide then
         tmpAngle:=tmpAngle-0.25*AHeight;

      tmpYDisp:=tmpAngle*Abs(Sin(Angle*TeePiStep));

      if OtherSide then
         tmpYDisp:=-tmpYDisp
      else
      if Horizontal and (ALineCount=1) then
         tmpYDisp:=tmpYDisp+AHeight;

      if Horizontal then
      begin
        Top:=Y+{$IFNDEF FMX}Round{$ENDIF}(tmpYDisp);
        Bottom:=Top+AHeight;

        if FLabels.Align=alCenter then
           tmpXDisp:=(tmpAngle+AHeight)*Abs(Sin(Angle*TeePiStep))
        else
        begin
          tmpXDisp:=tmpAngle*Cos(Angle*TeePiStep);

          if OtherSide then
          begin
            if Angle>180 then
               tmpXDisp:=-tmpXDisp;
          end
          else
            if Angle<180 then
               tmpXDisp:=-tmpXDisp;
        end;

        Left:=Left+{$IFNDEF FMX}Round{$ENDIF}(tmpXDisp);
        Right:=Left+AWidth;
      end
      else
      begin
        if OtherSide then
           Left:=X-{$IFNDEF FMX}Round{$ENDIF}(AWidth*0.5)+AHeight
        else
           Left:=X-{$IFNDEF FMX}Round{$ENDIF}(AWidth*0.5)-AHeight;

        Right:=Left+AWidth;
      end;
    end;
  end;

  function CalcHorizAlign:TCanvasTextAlign;
  const Aligns:Array[Boolean,Boolean] of Integer=(
              (TA_RIGHT +TA_TOP, TA_LEFT  +TA_TOP    ),    { vertical }
              (TA_CENTER+TA_TOP, TA_CENTER+TA_BOTTOM ) );  { horizontal }
  begin
    if FLabels.Align=alCenter then
    begin
      result:=TA_CENTER;
      Dec(X,SizeLabels div 2);
    end
    else
    if Horizontal or (FLabels.Align=alDefault) then
       result:=Aligns[Horizontal,OtherSide]
    else
      if OtherSide then
      begin
        result:=TA_RIGHT;
        Inc(X,SizeLabels);
      end
      else
      begin
        result:=TA_LEFT;
        Dec(X,SizeLabels);
      end;
  end;

  procedure SaveBounds;
  var P: TFourPoints;
  begin
    if ItemIndex>-1 then
    begin
      RectToFourPoints(Format.ShapeBounds,Angle,P);
      TAxisLabelsFormatAccess(Items[ItemIndex].Format).FRotatedBounds:=P;
    end;
  end;

var
  tmpShapeH : Integer;
  tmpShapeW : Integer;

  Delta    : Integer;
  n        : Integer;
  tmpH     : Integer;
  tmpAlign : TCanvasTextAlign;
  tmpAlign2: TCanvasTextAlign;
  tmpW     : Integer;
  tmpNum   : Integer;

  tmpXdisp,
  tmpYdisp : Single;
begin
  Delta:=Round(ParentChart.Canvas.TextSize(TeeCharForHeight).Y);

  tmpH:=Delta div 2;

  while Angle<0 do
        Angle:=360+Angle;

  Angle:=Angle mod 360;

  tmpAlign:=CalcHorizAlign;

  if (not Horizontal) and (not IsDepthAxis) then
     Dec(Y,tmpH);

  if IsDepthAxis then
     tmpZ:=z
  else
     tmpZ:=IZPos;

  n:=TeeNumFields(St,TeeLineSeparator);

  if not Assigned(Format) then
     Format:=FAxisItems.Format;

  Format.Text:=St;

  Format.CalcBounds(ParentChart);

  tmpShapeW:={$IFDEF FMX}Round{$ENDIF}(Format.Width);
  tmpShapeH:={$IFDEF FMX}Round{$ENDIF}(Format.Height);

  if Format.TextFormat=ttfNormal then
     tmpNum:=n
  else
     ParentChart.MultiLineTextWidth(St,tmpNum,Format.TextFormat);

  tmpH:=Delta*tmpNum;

  if LabelsExponent then
  begin
    if Horizontal then
    Begin
      tmpXdisp:=format.Width*0.1;
      tmpYdisp:=tmpShapeH*0.5;
    end
    else
    Begin
      tmpXdisp:=0;
      tmpYdisp:=tmpShapeH*0.125;
    end;
  end
  else
  begin
    tmpXdisp:=0;
    tmpYdisp:=0;
  end;

  if Horizontal then
     y:=y-Delta
  else
     y:=y-Round(0.5*Delta*(n+1));

  tmpAlign2:=tmpAlign;

  with Format.ShapeBounds do
  begin
    Left:=X-{$IFNDEF FMX}Round{$ENDIF}(tmpXdisp);
    Top:=Y+Delta+{$IFNDEF FMX}Round{$ENDIF}(tmpYdisp);

    if tmpAlign2>=TA_BOTTOM then
    begin
      Top:=Top-tmpH;
      Dec(tmpAlign2,TA_BOTTOM);
    end
    else
    if Horizontal and OtherSide then
       Top:=Top-tmpH;

    Right:=Left+tmpShapeW;
    Bottom:=Top+tmpShapeH;

    if tmpAlign2=TA_RIGHT then
       Format.MoveTo(TeePoint(Left-tmpShapeW,Top))
    else
    if tmpAlign2=TA_CENTER then
    begin
      if (not Horizontal) and OtherSide then
      begin
        Left:=Left+(SizeLabels-(tmpShapeW div 2));
        Right:=Left+tmpShapeW;
      end
      else
      begin
        tmpW:={$IFDEF FMX}System.Round{$ENDIF}(Right-Left) div 2;

        Right:=Right-tmpW;
        Left:=Left-tmpW;
      end;
    end;
  end;

  // Rotated labels
  if (Angle mod 180)<>0 then
     DoRotateBounds(tmpShapeW,tmpShapeH,tmpNum);

  if ParentChart.View3D then
     Format.MoveTo(ParentChart.Canvas.Calculate3DPosition(Format.ShapeBounds.TopLeft,tmpZ));

  SaveBounds;

  if ShouldDraw and BoundsInsideShape then
  begin
    if FLabels.Exponent and (ExponentPosition>0) then
       DoDrawExponent
    else
       Format.Draw(Format.ShapeBounds,Angle,tmpZ);

    if FLabels.UseImages and (APicture<>nil) then
       DrawPicture;
  end;
end;

Procedure TChartAxis.Scroll(Const Offset:TAxisValue; CheckLimits:Boolean=False);
Begin
  // Note: Calling InternalMinMax can be very costly, so do it only when appropiate:

  if (not CheckLimits) or
     ( ((Offset>0) and (FMaximumValue< ( ParentChart.InternalMinMax(Self,False,Horizontal)-Offset) )) or
       ((Offset<0) and (FMinimumValue> ( ParentChart.InternalMinMax(Self,True,Horizontal)-Offset) ))
     ) then
  begin
    FAutomatic:=False;

    FAutomaticMaximum:=False;
    FMaximumValue:=FMaximumValue+Offset;

    FAutomaticMinimum:=False;
    FMinimumValue:=FMinimumValue+Offset;

    ParentChart.Invalidate;

    ParentChart.ChangedAxisScale(Self);
  end;
end;

Function TChartAxis.InternalCalcDepthPosValue(Const Value:TChartValue):Integer;
begin
  if IRangeZero then result:=ICenterPos
  else
  if FInverted then result:=Round(IAxisSizeRange*(IMaximum-Value))
               else result:=Round(IAxisSizeRange*(Value-IMinimum));
end;

Function TChartAxis.LogXPosValue(Const Value:TChartValue):Integer;
begin
  if IRangeLog=0 then result:=ICenterPos
  else
  begin
    if Value<=0 then
       if FInverted then result:=IEndPos
                    else result:=IStartPos
    else
    begin
      if FInverted then result:=Round((ILogMax-ln(Value))*IAxisLogSizeRange)
                   else result:=Round((ln(Value)-ILogMin)*IAxisLogSizeRange);
      result:=IStartPos+result;
    end;
  end;
end;

Function TChartAxis.LogYPosValue(Const Value:TChartValue):Integer;
begin
  if IRangeLog=0 then result:=ICenterPos
  else
  begin
    if Value<=0 then
       if not FInverted then result:=IEndPos
                        else result:=IStartPos
    else
    begin
      if FInverted then result:=Round((ILogMax-ln(Value))*IAxisLogSizeRange)
                   else result:=Round((ln(Value)-ILogMin)*IAxisLogSizeRange);

      result:=IEndPos-result;
    end;
  end;
end;

// The following routines perform axis calculations WITH overflow checking
// to avoid Windows GDI limits on pixel coordinates.
// See below for faster routines.
//
// To switch between slow and fast, use Chart1.Axes.FastCalc property.

var TeeMaxPixelPos:Integer=0;

Function TChartAxis.XPosValueCheck(Const Value:TChartValue):Integer;
var tmp : TAxisValue;
begin
  if IRangeZero then result:=ICenterPos
  else
  begin
    tmp:=(Value-IMinimum)*IAxisSizeRange;

    //if IUseTeeMaxPixelPos then
    //begin

      if FInverted then tmp:=IEndPos-tmp
                   else tmp:=IStartPos+tmp;

      if tmp> TeeMaxPixelPos then result:=TeeMaxPixelPos
      else
      if tmp<-TeeMaxPixelPos then result:=-TeeMaxPixelPos
      else
         result:=Round(tmp);

    {end  //#2037
    else
    if FInverted then result:=IEndPos-Round(tmp)
                 else result:=IStartPos+Round(tmp);}
  end;
end;

Function TChartAxis.YPosValueCheck(Const Value:TChartValue):Integer;
var tmp : TAxisValue;
begin
  if IRangeZero then result:=ICenterPos
  else
  begin
    tmp:=(Value-IMinimum)*IAxisSizeRange;

    //if IUseTeeMaxPixelPos then
    //begin

      if FInverted then tmp:=IStartPos+tmp
                   else tmp:=IEndPos-tmp;

      if tmp> TeeMaxPixelPos then result:= TeeMaxPixelPos
      else
      if tmp<-TeeMaxPixelPos then result:=-TeeMaxPixelPos
      else
         result:=Round(tmp);

    {end;   #2037
    else
    if FInverted then tmpResult:=IStartPos+Round(tmp)
                 else tmpResult:=IEndPos-Round(tmp); }
  end;
end;

// The following routines perform axis calculations WITHOUT overflow checking.
// These are 5 to 10% faster than the above ones.
// See above for slower (with checking) routines.
//
// To switch between slow and fast, use Chart1.Axes.FastCalc property.

Function TChartAxis.XPosValue(Const Value:TChartValue):Integer;
//{$IFDEF TEENOASM}
Var tmpResult : Double;
//{$ENDIF}
begin
  if IRangeZero then result:=ICenterPos
  else
  begin
    //{$IFNDEF TEENOASM}      //#1914 asm approach loses precision
    {asm
      fld Value
      fsub qword ptr [eax+IMinimum]
      fmul qword ptr [eax+IAxisSizeRange]
      sub esp, 8
      fistp qword ptr [esp]
      pop [result]
      pop ecx
    end;}
    //{$ELSE}
    tmpResult:=(Value-IMinimum)*IAxisSizeRange;
    if tmpResult >  TeeMaxPixelPos then
      result:=TeeMaxPixelPos
    else if tmpResult < -TeeMaxPixelPos then
      result:=-TeeMaxPixelPos
    else
      result := Round(tmpResult);
    //{$ENDIF}
    if Inverted then result:=IEndPos-result
                else Inc(result,IStartPos);
  end;
end;

Function TChartAxis.YPosValue(Const Value:TChartValue):Integer;
//{$IFDEF TEENOASM}
Var tmpResult : Double;
//{$ENDIF}
begin
  if IRangeZero then result:=ICenterPos
  else
  begin
    //{$IFNDEF TEENOASM}     // #1914 asm approach loses precision
    {asm
      fld Value
      fsub qword ptr [eax+IMinimum]
      fmul qword ptr [eax+IAxisSizeRange]
      sub esp, 8
      fistp qword ptr [esp]
      pop [result]
      pop ecx
    end;}
    //{$ELSE}
    tmpResult:=(Value-IMinimum)*IAxisSizeRange;
    if tmpResult >  TeeMaxPixelPos then
      result:=TeeMaxPixelPos
    else if tmpResult < -TeeMaxPixelPos then
      result:=-TeeMaxPixelPos
    else
      result := Round(tmpResult);
    //{$ENDIF}
    if Inverted then Inc(result,IStartPos)
                else result:=IEndPos-result;
  end;
end;

Function TChartAxis.CalcSizeValue(Const Value:TAxisValue):Integer;
begin
  result:=CalcSizeValue(0,Value);
end;

Function TChartAxis.CalcSizeValue(Const Start,Finish:TAxisValue):Integer;
var tmp : TAxisValue;
begin
  result:=0;

  tmp:=Abs(Finish-Start);

  if tmp>0 then
    if FLogarithmic then
    Begin
      result:=CalcPosValue(Finish)-CalcPosValue(Start);

      {
      if IRangeLog<>0 then
         result:=Round(ln(Value)*IAxisLogSizeRange);
      }
    end
    else
    if IRange<>0 then
       result:=Round(tmp*IAxisSizeRange);
end;

Function TChartAxis.AxisRect:TRect;
var tmpPos1 : Integer;
    Pos1    : Integer;
    tmpPos2 : Integer;
    Pos2    : Integer;
begin
  if IStartPos>IEndPos then
  begin
    tmpPos1:=IEndPos;
    tmpPos2:=IStartPos;
  end
  else
  begin
    tmpPos1:=IStartPos;
    tmpPos2:=IEndPos;
  end;

  if FLabels.Visible then
  begin
    if PosAxis>FPosLabels then
    begin
      Pos1:=FPosLabels;
      Pos2:=PosAxis+TeeAxisClickGap;
    end
    else
    begin
      Pos1:=PosAxis-TeeAxisClickGap;
      Pos2:=FPosLabels;
    end;
  end
  else
  begin
    Pos1:=PosAxis;
    Pos2:=Pos1;
  end;

  if Horizontal then result:=TeeRect(tmpPos1,Pos1,tmpPos2,Pos2)
                else result:=TeeRect(Pos1,tmpPos1,Pos2,tmpPos2);
end;

Function TChartAxis.Clicked(const P:TPoint):Boolean;
var tmpR : TRect;
    tmpZ : Integer;
Begin
  result:=ParentChart.IsAxisVisible(Self);

  if result then
  begin
    if ParentChart.View3D then
    begin
      if OtherSide then tmpZ:=ParentChart.Width3D
                   else tmpZ:=0;

      tmpR:=ParentChart.Canvas.CalcRect3D(AxisRect,tmpZ);
    end
    else
      tmpR:=AxisRect;

    result:=PointInRect(tmpR,P);

    // pending: DepthAxis and click hit detection with 3D rotation.
  end;
end;

Function TChartAxis.Clicked(x,y:Integer):Boolean;
begin
  result:=Clicked(TeePoint(x,y));
end;

Procedure TChartAxis.CustomDrawMinMaxStartEnd( APosLabels,
                                               APosTitle,
                                               APosAxis:Integer;
                                               GridVisible:Boolean;
                                               Const AMinimum,AMaximum,
                                                     AIncrement:TAxisValue;
                                               AStartPos,AEndPos:Integer);

  Procedure SetInternals;
  begin
    IMaximum :=FMaximumValue;
    IMinimum :=FMinimumValue;
    InternalCalcRange;
  end;

var OldMin       : TAxisValue;
    OldMax       : TAxisValue;
    OldIncrement : TAxisValue;
    OldAutomatic : Boolean;
begin
  OldMin      :=FMinimumValue;
  OldMax      :=FMaximumValue;
  OldIncrement:=FDesiredIncrement;
  OldAutomatic:=FAutomatic;

  try
    FAutomatic       :=False;
    FMinimumValue    :=AMinimum;
    FMaximumValue    :=AMaximum;
    FDesiredIncrement:=AIncrement;

    SetInternals;

    CustomDrawStartEnd(APosLabels,APosTitle,APosAxis,GridVisible,AStartPos,AEndPos);
  finally
    FMinimumValue    :=OldMin;
    FMaximumValue    :=OldMax;
    FDesiredIncrement:=OldIncrement;
    FAutomatic       :=OldAutomatic;
    SetInternals;
  end;
end;

Procedure TChartAxis.CustomDrawMinMax( APosLabels,
                                       APosTitle,
                                       APosAxis:Integer;
                                       GridVisible:Boolean;
                                       Const AMinimum,AMaximum,
                                       AIncrement:TAxisValue);
begin
  CustomDrawMinMaxStartEnd(APosLabels,APosTitle,APosAxis,GridVisible,
        AMinimum,AMaximum,AIncrement,IStartPos,IEndPos);
end;

Procedure TChartAxis.CustomDraw( APosLabels,APosTitle,APosAxis:Integer;
                                 GridVisible:Boolean);
begin
  InternalCalcPositions;
  CustomDrawStartEnd(APosLabels,APosTitle,APosAxis,GridVisible,IStartPos,IEndPos);
End;

Procedure TChartAxis.CustomDrawStartEnd( APosLabels,APosTitle,APosAxis:Integer;
                                         GridVisible:Boolean; AStartPos,AEndPos:Integer);
var OldGridVisible : Boolean;
    OldChange      : TNotifyEvent;
Begin
  FPosLabels:=APosLabels;
  FPosTitle :=APosTitle;
  FPosAxis  :=APosAxis;
  IStartPos :=AStartPos;
  IEndPos   :=AEndPos;

  RecalcSizeCenter;

  OldGridVisible:=FGrid.Visible;
  OldChange:=FGrid.OnChange;
  FGrid.OnChange:=nil;
  FGrid.Visible:=GridVisible;

  Draw(False);

  FGrid.Visible:=OldGridVisible;
  FGrid.OnChange:=OldChange;

  InternalCalcPositions;
end;

Procedure TChartAxis.RecalcSizeCenter;
begin
  IAxisSize:=IEndPos-IStartPos;
  ICenterPos:=(IStartPos+IEndPos) div 2;
  InternalCalcRange;
end;

Procedure TChartAxis.InternalCalcPositions;

  Procedure DoCalculation(AStartPos:Integer; ASize:Integer);
  begin
    IStartPos:=AStartPos+Round(0.01*ASize*FStartPosition);
    IEndPos:=AStartPos+Round(0.01*ASize*FEndPosition);
  end;

begin
  With ParentChart do
  if IsDepthAxis then DoCalculation(0,Width3D) else
  if Horizontal then DoCalculation({$IFDEF FMX}Round{$ENDIF}(ChartRect.Left),ChartWidth)
                else DoCalculation({$IFDEF FMX}Round{$ENDIF}(ChartRect.Top),ChartHeight);

  RecalcSizeCenter;
end;

Function TChartAxis.ApplyPosition(APos:Integer; Const R:TRect):Integer;
var tmpSize : Integer;
begin
  result:=APos;

  if FPositionPercent<>0 then
  begin
    if FPosUnits=muPercent then
    begin
      with R do
      if Horizontal then tmpSize:={$IFDEF FMX}System.Round{$ENDIF}(Bottom-Top)
                    else tmpSize:={$IFDEF FMX}System.Round{$ENDIF}(Right-Left);

      tmpSize:=Round(0.01*FPositionPercent*tmpSize);
    end
    else tmpSize:=Round(FPositionPercent);  // pixels

    if OtherSide then tmpSize:=-tmpSize;
    if Horizontal then tmpSize:=-tmpSize;

    result:=APos+tmpSize;
  end;
end;

Function TChartAxis.GetRectangleEdge(Const R:TRect):Integer;
begin
  With R{$IFDEF FMX}.Round{$ENDIF} do
  if OtherSide then
     if Horizontal then result:=Top
                   else result:=Right
  else
     if Horizontal then result:=Bottom
                   else result:=Left;
end;

Procedure TChartAxis.DrawGridLine(tmp:Integer);

  Function GetLimit(Const Max: Boolean):Integer;
  begin
    if FUsePartnerAxis and Assigned(FPartnerAxis) then
    begin
      if Max and (not FPartnerAxis.Inverted) or
        (not Max) and FPartnerAxis.Inverted then
        result:=FPartnerAxis.CalcPosValue(FPartnerAxis.Maximum)
      else
        result:=FPartnerAxis.CalcPosValue(FPartnerAxis.Minimum);
    end
    else
    begin
      if Horizontal then
      begin
        if Max then result:={$IFDEF FMX}Round{$ENDIF}(ParentChart.ChartRect.Top + 1) //#2247
               else result:={$IFDEF FMX}Round{$ENDIF}(ParentChart.ChartRect.Bottom);
      end
      else
      begin
        if Max then result:={$IFDEF FMX}Round{$ENDIF}(ParentChart.ChartRect.Right)
               else result:={$IFDEF FMX}Round{$ENDIF}(ParentChart.ChartRect.Left + 1); //#2247
      end;
    end
  end;

var tmpZ : Integer;
begin
  if (tmp>IStartPos) and (tmp<IEndPos) then
  With ParentChart, Canvas, ChartRect{$IFDEF FMX}.Round{$ENDIF} do
  begin
    if IsDepthAxis then
    begin
      if not IHideBackGrid then
         VertLine3D(Left,Top,Bottom,tmp);

      if not IHideSideGrid then
         HorizLine3D(Left,Right,Bottom,tmp);
    end
    else
    begin
      if View3D then
      begin
        if AxisBehind then
        begin
          if not IHideBackGrid then
             if Horizontal then
                VertLine3D(tmp,GetLimit(True),GetLimit(False),Width3D)
             else
                HorizLine3D(GetLimit(False),GetLimit(True),tmp,Width3D);

          if not IHideSideGrid then
          begin
            tmpZ:=Round(Width3D*Grid.ZPosition*0.01);

            if (tmpZ<>Width3D) then
               if Horizontal then ZLine3D(tmp,PosAxis,tmpZ,Width3D)
                             else ZLine3D(PosAxis,tmp,tmpZ,Width3D);
          end;
        end
        else
        if Horizontal then
           VertLine3D(tmp,GetLimit(True),GetLimit(False),0) // in-front grid
        else
           HorizLine3D(GetLimit(False),GetLimit(True),tmp,0); // in-front grid
      end
      else
      if Horizontal then DoVertLine(tmp,GetLimit(True),GetLimit(False))
                    else DoHorizLine(GetLimit(False),GetLimit(True),tmp);
    end;
  end;
end;

procedure TChartAxis.DefineProperties(Filer: TFiler);
begin
  inherited;

  Filer.DefineBinaryProperty('Items',FAxisItems.ReadItems,FAxisItems.WriteItems,
                             not FAxisItems.FAuto);
end;

Procedure TChartAxis.DrawGrids(NumTicks:Integer);
var t        : Integer;
    tmpEvery : Integer;
begin
  if Assigned(OnDrawGrids) then
     OnDrawGrids(Self);

  if FGrid.Visible then
  begin
    TCanvas3DAccess(ParentChart.Canvas).BeginEntity('Grid');

    with ParentChart.Canvas do
    begin
      BackMode:=cbmTransparent;
      Brush.Style:=bsClear;

      if FGrid.Color=clTeeColor then
         AssignVisiblePenColor(FGrid, {$IFDEF FMX}TAlphaColors.Gray{$ELSE}clGray{$ENDIF})
      else
         AssignVisiblePen(FGrid);
    end;

    // To avoid "div by zero"
    tmpEvery:=Max(1,FGrid.FDrawEvery);

    if Grid.Centered then
    begin
      for t:=1 to NumTicks-1 do
          if t mod tmpEvery=0 then
             DrawGridLine(Round(0.5*(Tick[t]+Tick[t-1])))
    end
    else
      for t:=0 to NumTicks-1 do
          if t mod tmpEvery=0 then
             DrawGridLine(Tick[t]);

    TCanvas3DAccess(ParentChart.Canvas).EndEntity;
  end;
end;

procedure TChartAxis.DrawLineSegment(AStart,AEnd:Integer);

  procedure HorizMode(Left,Right,Y:Integer);
  var tmp : Integer;
  begin
    tmp:={$IFDEF FMX}Round{$ENDIF}(FAxis.Width);

    if ParentChart.View3D then
    case FAxis.LineMode of
      lmLine: ParentChart.Canvas.HorizLine3D(Left,Right,Y,IZPos);
  lmCylinder: ParentChart.Canvas.Cylinder(False,Left,Y-tmp,Right,Y+tmp,
                                          IZPos-tmp,IZPos+tmp,True);
    else
      ParentChart.Canvas.Cube(Left,Right,Y-tmp,Y+tmp,IZPos-tmp,IZPos+tmp);
    end
    else
      ParentChart.Canvas.DoHorizLine(Left,Right,Y);
  end;

var tmp : Integer;
begin
  With ParentChart,Canvas do
  if IsDepthAxis then
  begin
    if View3D then
    begin
      if OtherSide then
         tmp:={$IFDEF FMX}Round{$ENDIF}(ChartRect.Bottom)+CalcWallSize(BottomAxis)-IZPos
      else
         tmp:={$IFDEF FMX}Round{$ENDIF}(ChartRect.Top)-IZPos;

      MoveTo3D(PosAxis,tmp,AStart);
      LineTo3D(PosAxis,tmp,AEnd);
    end;
  end
  else
  begin
    tmp:=IWallSize + {$IFDEF FMX}Round{$ENDIF}(Self.FAxis.Width {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF});

    if Horizontal then
       if OtherSide then
          // Top axis
          HorizMode(AStart,AEnd,PosAxis)
       else
       begin
          // Bottom axis
         HorizMode(AStart-CalcWallSize(LeftAxis),
                   AEnd+CalcWallSize(RightAxis),PosAxis+tmp)
       end
    else
    begin
      if not OtherSide then
         tmp:=-tmp;

      if ParentChart.View3D then
      case FAxis.LineMode of
        lmLine: ParentChart.Canvas.VertLine3D(PosAxis+tmp,AStart,
                                   AEnd+CalcWallSize(BottomAxis),IZPos);
    lmCylinder: ParentChart.Canvas.Cylinder(True,PosAxis+tmp-{$IFDEF FMX}Round{$ENDIF}(Pen.Width),
                  AStart,PosAxis+tmp+{$IFDEF FMX}Round{$ENDIF}(Pen.Width),AEnd+CalcWallSize(BottomAxis),
                  IZPos-{$IFDEF FMX}Round{$ENDIF}(Pen.Width),
                  IZPos+{$IFDEF FMX}Round{$ENDIF}(Pen.Width),True);
      else
        ParentChart.Canvas.Cube(PosAxis+tmp-{$IFDEF FMX}Round{$ENDIF}(Pen.Width),
                  PosAxis+tmp+{$IFDEF FMX}Round{$ENDIF}(Pen.Width),
                  AStart,
                  AEnd+CalcWallSize(BottomAxis),
                  IZPos-{$IFDEF FMX}Round{$ENDIF}(Pen.Width),
                  IZPos+{$IFDEF FMX}Round{$ENDIF}(Pen.Width));
      end
      else
        ParentChart.Canvas.DoVertLine(PosAxis+tmp,AStart,
                                      AEnd+CalcWallSize(BottomAxis));

    end;
  end;
end;

// Find all active (visible) Series that are associated with this axis
Procedure TChartAxis.CalcAllSeries;
var t : Integer;
begin
  ISeriesList.Clear;

  with ParentChart do
  for t:=0 to SeriesCount-1 do
      with Series[t] do
           if Active and AssociatedToAxis(Self) then
              ISeriesList.Add(Series[t]);
end;

Function TChartAxis.GetAxisSeriesLabel(const AStyle:TAxisLabelStyle;
                            const ASeries:TChartSeries; AIndex:Integer;
                            out AValue:TAxisValue;
                            out ALabel:String;
                            out APicture:TTeePicture):Boolean;
begin
  result:=False;

  with ASeries do
  if (AIndex>=FFirstVisibleIndex) and (AIndex<=FLastVisibleIndex) then
  begin

    { even if the series has no text labels... 5.01 }
    case AStyle of
      talMark : ALabel:=ValueMarkText[AIndex];
      talText : ALabel:=Labels[AIndex];
talPointValue : if MandatoryAxis=Self then // DB
                   ALabel:=LabelValue(MandatoryValueList.Value[AIndex])
                else
                   ALabel:=LabelValue(NotMandatoryValueList.Value[AIndex])
    end;

    if Assigned(ParentChart.FOnGetAxisLabel) then
       ParentChart.FOnGetAxisLabel(TChartAxis(Self),ASeries,AIndex,ALabel);

    if ALabel<>'' then
    begin
      if Self.IsDepthAxis then
         AValue:=ASeries.ValueListOfAxis(Self)[AIndex]
      else
      if HasPointLabels then
         AValue:=AIndex
      else
      if Horizontal then
         AValue:=XValues.Value[AIndex]
      else
         AValue:=YValues.Value[AIndex];

      if FPictures=nil then
         APicture:=nil
      else
         APicture:=FPictures[AIndex];

      result:=True;
    end;
  end;
end;

Procedure TChartAxis.Draw(CalcPosAxis:Boolean);
var tmpValue    : TAxisValue;
    tmpNumTicks : Integer;

  Procedure DrawTicksGrid;

    Procedure InternalDrawTick(tmp,Delta,tmpTickLength:Integer);
    var tmpPos : Integer;
    Begin
      with ParentChart,Canvas do
      Begin
        if IsDepthAxis then
           if OtherSide then
              HorizLine3D(PosAxis+Delta,PosAxis+Delta+tmpTickLength,DepthAxisPos,tmp)
           else
              HorizLine3D(PosAxis-Delta,PosAxis-Delta-tmpTickLength,DepthAxisPos,tmp)
        else
        if OtherSide then
        Begin
          if Horizontal then
             VertLine3D(tmp,PosAxis-Delta,PosAxis-Delta-tmpTickLength,IZPos)
          else
          begin
            Inc(Delta,IWallSize);
            HorizLine3D(PosAxis+Delta,PosAxis+Delta+tmpTickLength,tmp,IZPos)
          end;
        end
        else
        begin
          Inc(Delta,IWallSize);

          // Bottom axis
          if Horizontal then
          begin
            tmpPos:=PosAxis+Delta+{$IFDEF FMX}Round{$ENDIF}(Self.FAxis.Width)-1;

            if View3D then
               VertLine3D(tmp,tmpPos,tmpPos+tmpTickLength,IZPos)
            else
               DoVertLine(tmp,tmpPos,tmpPos+tmpTickLength)
          end
          else
          begin
            // Left axis
            tmpPos:=PosAxis-Delta-{$IFDEF FMX}Round{$ENDIF}(Self.FAxis.Width)+1;

            if View3D then
               HorizLine3D(tmpPos,tmpPos-tmpTickLength,tmp,IZPos)
            else
               DoHorizLine(tmpPos,tmpPos-tmpTickLength,tmp);
          end;
        end;
      end;
    end;

    Procedure ProcessMinorTicks(IsGrid:Boolean);

      Procedure AProc(APos:Integer);
      begin
        if (APos>IStartPos) and (APos<IEndPos) then
           if IsGrid then DrawGridLine(APos)
                     else InternalDrawTick(APos,1,FMinorTickLength);
      end;

    var tmpInvCount : TAxisValue;
        tmpTicks    : TChartValues;

      procedure DrawLogMinorTicks(tmpValue:TAxisValueExtended);
      var tmpDelta    : TAxisValueExtended;
          tt          : Integer;
          tmpLength   : Integer;
      begin
        tmpLength:=Length(tmpTicks);

        for tt:=0 to tmpLength-1 do
            if tmpTicks[tt]=tmpValue then exit;

        SetLength(tmpTicks,tmpLength+1);
        tmpTicks[tmpLength]:=tmpValue;

        tmpDelta:=((tmpValue*FLogarithmicBase)-tmpValue)*tmpInvCount;

        for tt:=1 to FMinorTickCount do
        begin
          tmpValue:=tmpValue+tmpDelta;

          if (tmpValue<=IMaximum) and (tmpValue>=IMinimum) then
             AProc(CalcPosValue(tmpValue));
        end;
      end;

    var t        : Integer;
        tt       : Integer;
        tmpDelta : TAxisValue;
        tmpValue : TAxisValue;
    begin
      tmpInvCount:=1.0/Succ(FMinorTickCount);

      if tmpNumTicks>1 then
      if not FLogarithmic then
      begin
        tmpDelta:=1.0*(Tick[1]-Tick[0])*tmpInvCount;

        for t:=1 to FMinorTickCount do
        begin
          AProc(Tick[0]-Round(t*tmpDelta));
          AProc(Tick[tmpNumTicks-1]+Round(t*tmpDelta));
        end;
      end;

      if FLogarithmic then
      begin
        tmpTicks:=nil;
        try
          if tmpNumTicks>0 then  // fix first tick
          begin
            tmpValue:=CalcPosPoint(Tick[tmpNumTicks-1])/LogarithmicBase;
            DrawLogMinorTicks(tmpValue);

            for t:=0 to tmpNumTicks-3 do
                DrawLogMinorTicks(CalcPosPoint(Tick[t]));

            DrawLogMinorTicks(CalcPosPoint(Tick[tmpNumTicks-1]));
          end;
        finally
          tmpTicks:=nil;
        end;
      end
      else
      for t:=1 to tmpNumTicks-1 do
      begin
        tmpDelta:=1.0*(Tick[t]-Tick[t-1])*tmpInvCount;

        for tt:=1 to FMinorTickCount do
            AProc(Tick[t]-Round(tt*tmpDelta));
      end;
    end;

    Procedure ProcessTicks(const ID:String; const APen:TChartPen; AOffset,ALength:Integer);
    var t : Integer;
    begin
      if APen.Visible then
      begin
        TCanvas3DAccess(ParentChart.Canvas).BeginEntity(ID);

        ParentChart.Canvas.AssignVisiblePen(APen);

        for t:=0 to tmpNumTicks-1 do
            InternalDrawTick(Tick[t],AOffset,ALength);

        TCanvas3DAccess(ParentChart.Canvas).EndEntity;
      end;
    end;

    Procedure ProcessMinor(const APen:TChartPen; IsGrid:Boolean);
    begin
      if (tmpNumTicks>0) and APen.Visible then
      begin
        With ParentChart.Canvas do
        begin
          BackMode:=cbmTransparent;
          Brush.Style:=bsClear;
          AssignVisiblePen(APen);
        end;

        ProcessMinorTicks(IsGrid);
      end;
    end;

  begin
    With ParentChart.Canvas do
    begin
      Brush.Style:=bsClear;
      BackMode:=cbmTransparent;
    end;

    IWallSize:=ParentChart.CalcWallSize(Self);

    if FAxis.Visible then
    begin
      ParentChart.Canvas.AssignVisiblePen(FAxis);

      if FAxis.LineMode<>lmLine then
      begin
        ParentChart.Canvas.Pen.Style:=psClear;
        ParentChart.Canvas.Brush.Style:=bsSolid;
        ParentChart.Canvas.Brush.Color:=FAxis.Color;
      end;

      if Assigned(OnDrawAxis) then
         OnDrawAxis(Self)
      else
         DrawLineSegment(IStartPos,IEndPos);
    end;

    if FTickLength<>0 then
       ProcessTicks('Ticks',FTicks,1,FTickLength);

    DrawGrids(tmpNumTicks);

    if FTickInnerLength<>0 then
       ProcessTicks('InnerTicks',FTicksInner,-1,-FTickInnerLength);

    if FMinorTickLength<>0 then
    begin
      TCanvas3DAccess(ParentChart.Canvas).BeginEntity('MinorTicks');
      ProcessMinor(FMinorTicks,False);
      TCanvas3DAccess(ParentChart.Canvas).EndEntity;
    end;

    ProcessMinor(FMinorGrid,True);
    ParentChart.Canvas.BackMode:=cbmOpaque;
  end;

  Procedure AddTick(Const APos:Integer);
  begin
    SetLength(Tick,tmpNumTicks+1);
    Tick[tmpNumTicks]:=APos;
    Inc(tmpNumTicks);
  end;

var
  tmpAlternate : Boolean;

  Procedure DrawThisLabel(AIndex:Integer; const Value:TChartValue; LabelPos:Integer;
                          const tmpSt:String;
                          const APicture:TTeePicture=nil;
                          Format:TCustomTextShape=nil);
  var tmpZ   : Integer;
      tmpPos : Integer;
      tmpOff : Integer;
  begin
    if ParentChart.FHover.Visible and Texts.Selected.Hover.Visible and (Texts.Selected.HoverIndex=AIndex) then
    begin
      Format:=Texts.Selected.Hover;
      Format.Font.Size:=FLabels.Font.Size; // <-- Forcing matching font sizes for Hover
    end;

    if Assigned(Format) then
       ParentChart.PrepareFont3D(Format.Font)
    else
       ParentChart.PrepareFont3D(FLabels.Font);

    with ParentChart.Canvas do
    begin
      {$IFNDEF FMX}
      // trick
      Brush.Style:=bsSolid;
      Brush.Style:=bsClear;
      {$ENDIF}

      BackMode:=cbmTransparent;

      if IsDepthAxis then
         TextAlign:=DepthAxisAlign;
    end;

    if IsDepthAxis then
    begin
      if (ParentChart.View3DOptions.Rotation=360) or ParentChart.View3DOptions.Orthogonal then
         tmpZ:=LabelPos+(ParentChart.Canvas.FontHeight div 2)
      else
         tmpZ:=LabelPos;

      if OtherSide then tmpPos:=PosLabels
                   else tmpPos:=PosLabels-2-(ParentChart.Canvas.TextWidth(TeeCharForHeight) div 2);

      DrawAxisLabel(tmpPos,DepthAxisPos,FLabels.Angle,tmpSt,Format,tmpZ,APicture,AIndex);
    end
    else
    begin
      if LabelsAlternate then
      begin
        if tmpAlternate then
           tmpPos:=PosLabels
        else
        begin
          if Horizontal then
             tmpOff:=ParentChart.Canvas.FontHeight
          else
             tmpOff:=-MaxLabelsWidth;

         if OtherSide then
            tmpPos:=PosLabels-tmpOff
         else
            tmpPos:=PosLabels+tmpOff;
        end;

        tmpAlternate:=not tmpAlternate;
      end
      else tmpPos:=PosLabels;

      if Horizontal then
         DrawAxisLabel(LabelPos,tmpPos,FLabels.Angle,tmpSt,Format,0,APicture,AIndex)
      else
         DrawAxisLabel(tmpPos,LabelPos,FLabels.Angle,tmpSt,Format,0,APicture,AIndex);
    end;
  end;

  var
    tmpLabelStyle : TAxisLabelStyle;

  Procedure AddTickAndSaveLabel(const Value:TAxisValue; const Pos:Integer;
                                const Text:String;
                                const APicture:TTeePicture=nil;
                                const Format:TCustomTextShape=nil);
  var tmpPos : Integer;
      tmpW   : Integer;
  begin
    if TickOnLabelsOnly then
       AddTick(Pos);

    if FLabels.Position=lpAtTick then
       tmpPos:=Pos
    else
    begin
      tmpW:=ParentChart.Canvas.TextWidth(Text) div 2;

      if FLabels.Position=lpAfterTick then
         tmpPos:=Pos+tmpW
      else
         tmpPos:=Pos-tmpW;
    end;

    if FLabels.Visible and Texts.Visible then
    begin
      if not Assigned(Format) then
         with FAxisItems.InternalAdd(Value,Text,APicture) do
              FLabelPos:=tmpPos;

      if FLabels.Behind then
         DrawThisLabel(FAxisItems.Count-1,Value,tmpPos,Text,APicture,Format)
    end;
  end;

  Procedure AxisLabelsSeries;

    procedure CalcFirstLastAllSeries(out tmpFirst,tmpLast:Integer);
    var t : Integer;
    begin
      tmpFirst:=High(Integer);
      tmpLast:=-1;

      for t:=0 to ISeriesList.Count-1 do
      with ISeriesList[t] do
      begin
        if (FLabels.Count>0) or (LabelStyle=talPointValue) then
        begin
          CalcFirstLastVisibleIndex;

          if (FFirstVisibleIndex<tmpFirst) and (FFirstVisibleIndex<>-1) then
             tmpFirst:=FFirstVisibleIndex;

          if FLastVisibleIndex>tmpLast then
             tmpLast:=FLastVisibleIndex;
        end;
      end;
    end;

    function DuplicatedTick(const APos:Integer):Boolean;
    var t : Integer;
    begin
      for t:=0 to High(Tick) do
          if Tick[t]=APos then
          begin
            result:=True;
            Exit;
          end;

      result:=False;
    end;

  var
    tmpLabelSize : Integer;
    tmpLabelW    : Boolean;
    OldPosLabel  : Integer;
    tmp          : Integer;
    OldSizeLabel : Integer;

    function DoesLabelFit(const ALabel:String):Boolean;
    var tmpNum : Integer;
    begin
      if tmpLabelW then
         tmpLabelSize:=ParentChart.MultiLineTextWidth(ALabel,tmpNum)
      else
      begin
        tmpNum:=ParentChart.LinesOfText(ALabel);
        tmpLabelSize:=ParentChart.Canvas.FontHeight*tmpNum;
      end;

      if (FLabels.Separation<>0) and (OldPosLabel<>-1) then
      begin
        Inc(tmpLabelSize,Trunc(0.02*tmpLabelSize*FLabels.Separation));
        tmpLabelSize:=tmpLabelSize div 2;

        if tmp>=OldPosLabel then
           result:=(tmp-tmpLabelSize)>=(OldPosLabel+OldSizeLabel)
        else
           result:=(tmp+tmpLabelSize)<=(OldPosLabel-OldSizeLabel);
      end
      else
        result:=True;
    end;

    // Calculate how many labels should we skip, when there are more than
    // the number of available pixels
    function CalcSuggestedStep(const ACount:Integer):Integer;
    var tmp : TCoordinate;
    begin
      if Horizontal then
         tmp:=ParentChart.ChartWidth
      else
         tmp:=ParentChart.ChartHeight;

      if (ACount>tmp) and (tmp>0) then
         result:=Max(1,Round(2*ACount/tmp)) // Half = 2 pixels per char
      else
         result:=1;
    end;

    // Returns True when there is at least one series that has at least one label empty ('')
    function AnyEmptyLabel:Boolean;
    var t : Integer;
    begin
      for t:=0 to ISeriesList.Count-1 do
          if ISeriesList[t].FLabels.IAnyEmpty then
          begin
            result:=True;
            Exit;
          end;

      result:=False;
    end;

  var s,t          : Integer;
      tmpFirst     : Integer;
      tmpLast      : Integer;
      tmpStep      : Integer;
      tmpSt        : String;
      tmpValue     : TAxisValue;
      tmpDraw      : Boolean;
      tmpSt2       : String;
      tmpPicture   : TTeePicture;
      tmpDuplicated: Boolean;
  begin
    CalcAllSeries;
    CalcFirstLastAllSeries(tmpFirst,tmpLast);

    if tmpFirst<>High(Integer) then
    begin
      tmpLabelW:=Horizontal;

      Case Texts.Angle of
         90,270: tmpLabelW:=not tmpLabelW;
      end;

      if Assigned(ParentChart.FOnGetAxisLabel) or AnyEmptyLabel then
         tmpStep:=1  // <-- no possible optimization, due to usage of OnGetAxisLabel
      else
         tmpStep:=CalcSuggestedStep(tmpLast-tmpFirst+1);

      for s:=0 to ISeriesList.Count-1 do
      begin
        OldPosLabel :=-1;
        OldSizeLabel:= 0;

        t:=tmpFirst;

        while t<=tmpLast do
        begin
          if GetAxisSeriesLabel(tmpLabelStyle, ISeriesList[s], t, tmpValue, tmpSt, tmpPicture) then
           if (tmpValue>=IMinimum) and (tmpValue<=IMaximum) then
           begin
             tmp:=Self.CalcPosValue(tmpValue);

             tmpDuplicated:=DuplicatedTick(tmp); //QC Entry 88720

             if (not TickOnLabelsOnly) and (not tmpDuplicated) then
                AddTick(tmp);

             if FLabels.Visible and (tmpSt<>'') then
             begin
               tmpDraw:=DoesLabelFit(tmpSt);

               if (FLabels.Separation<>0) and (OldPosLabel<>-1) then
               begin
                 if (not tmpDraw) and Texts.Automatic.Trim then
                 begin
                   tmpSt2:=tmpSt;

                   while tmpSt2<>'' do
                   begin
                     Delete(tmpSt2,Length(tmpSt2),1);
                     tmpDraw:=DoesLabelFit(tmpSt2);

                     if tmpDraw then
                     begin
                       tmpSt:=tmpSt2;
                       break;
                     end;
                   end;
                 end;

                 if tmpDraw then
                 begin
                   if not tmpDuplicated then
                     AddTickAndSaveLabel(tmpValue,tmp,tmpSt,tmpPicture);

                   OldPosLabel:=tmp;
                   OldSizeLabel:=tmpLabelSize;
                 end;
               end
               else
               begin
                 if not tmpDuplicated then
                   AddTickAndSaveLabel(tmpValue,tmp,tmpSt,tmpPicture);

                 OldPosLabel:=tmp;
                 OldSizeLabel:=tmpLabelSize div 2;
               end;
             end;
           end;

          Inc(t,tmpStep);
        end;
      end;
    end;

    ISeriesList.Clear;
  end;

  var IIncrement       : TAxisValue;
      tmpWhichDateTime : TDateTimeStep;

  function InternalDrawLabel(const IsDateTime:Boolean; var Value:TAxisValue; DecValue:Boolean):Boolean;
  var tmp : Integer;
  begin
    tmp:=CalcPosValue(Value);

    if (FLabels.OnAxis and (IGapSize<1)) or ((tmp>IStartPos) and (tmp<IEndPos)) then
    begin
      if not TickOnLabelsOnly then
         AddTick(tmp);

      if Texts.Visible or (Grid.Visible and Grid.DrawAlways) then
         AddTickAndSaveLabel(Value,tmp,LabelValue(Value));
    end;

    result:=not DecValue;

    if not result then
       if IsDateTime then
          result:=IncDec(False,Value,IIncrement,tmpWhichDateTime)
       else
          result:=IncDec(False,Value,IIncrement);
  end;

  Procedure DoDefaultLogLabels;

    Function TryDrawLogLabels(Value:TAxisValue;
                              Multiply:Boolean;
                              LogDec:Integer;
                              const Base:Double):Integer;
    var tmp : TAxisValue;
        tmpIsDateTime : Boolean;
        tmpValue : TAxisValueExtended;
    begin
      result:=0;

      tmp:=Power(FLogarithmicBase,LogN(FLogarithmicBase,Value)-LogDec);

      if Base>1 then
      begin
        tmpIsDateTime:=CalcIsDateTime(tmpWhichDateTime);

        while Value<=IMaximum do
        begin
          if Value>=IMinimum then
          begin
            InternalDrawLabel(tmpIsDateTime,Value,False);
            Inc(result);
          end;

          if Multiply then tmpValue:=Value*Base
                      else tmpValue:=Value+tmp;

          if tmpValue>IMaximum then
             break
          else
             Value:=tmpValue;
        end;
      end;
    end;

    {.$DEFINE TEELOGLABELS}

  var tmpValue2 : TAxisValue;
      {$IFDEF TEELOGLABELS}
      tmpExp : TAxisValue;
      IntExp : Integer;
      {$ENDIF}
  begin
    if IMinimum<>IMaximum then
    begin
      if IMinimum<=0 then
      begin
        if IMinimum=0 then IMinimum:=0.1
                      else IMinimum:=RoundLogPower(MinAxisRange);

        tmpValue:=IMinimum;
      end
      else
      {$IFDEF TEELOGLABELS}
      begin
        tmpExp:=LogN(FLogarithmicBase,IMinimum);

        if Abs(Frac(tmpExp)) > 0.001
        then
        begin
          IntExp := Trunc(tmpExp);
          if tmpExp < 0 then Dec(IntExp);
        end
        else IntExp := Round(tmpExp);

        tmpValue:=IntPower(FLogarithmicBase,IntExp);
      end;
      {$ELSE}
        tmpValue:=RoundLogPower(IMinimum);
      {$ENDIF}

      if MinorGrid.Visible then
      begin
        tmpValue2:=tmpValue;
        if tmpValue2>=IMinimum then
           tmpValue2:=Power(FLogarithmicBase,LogN(FLogarithmicBase,IMinimum)-1);

        if tmpValue2<IMinimum then
           AddTick(CalcPosValue(tmpValue2));
      end;

      if TryDrawLogLabels(tmpValue,True,0,FLogarithmicBase)=0 then
         TryDrawLogLabels(tmpValue,False,1,FLogarithmicBase);

      // For minor grids only...
      if MinorGrid.Visible and (tmpValue>IMaximum) then
         AddTick(CalcPosValue(tmpValue));
    end;
  end;

  procedure IncreaseGapValue;
  var tmpStop : Boolean;
      tmpGap  : TAxisValue;
  begin
    tmpStop:=False;

    tmpGap:=tmpValue;
    OnCalcMaxLabel(Self,-1,tmpGap,tmpStop);

    tmpGap:=tmpValue+tmpGap;
    OnCalcMaxLabel(Self,-1,tmpGap,tmpStop);

    tmpValue:=tmpValue+tmpGap;
  end;

  Procedure DoDefaultLabels;
  var tmp : TAxisValue;
      tmpIsDateTime : Boolean;
  Begin
    if Abs(IRange/IIncrement)<10000 then  { if less than 10000 labels... }
    Begin
      tmpValue:=IMaximum/IIncrement;

      { calculate the maximum value... }
      if IAxisDateTime and FExactDateTime and
         (tmpWhichDateTime<>dtNone) and (tmpWhichDateTime>=dtOneDay) then
             tmpValue:=TeeRoundDate(IMaximum,tmpWhichDateTime)
      else
      if SameValue(IMinimum,IMaximum) or (not RoundFirstLabel) then
         tmpValue:=IMaximum
      else
         tmpValue:=IIncrement*Trunc(tmpValue);

      tmpIsDateTime:=CalcIsDateTime(tmpWhichDateTime);

      { adjust the maximum value to be inside "IMinimum" and "IMaximum" }

      if tmpIsDateTime then
      begin
        while tmpValue>IMaximum do
              if not IncDec(False,tmpValue,IIncrement,tmpWhichDateTime) then
                 break;
      end
      else
        while tmpValue>IMaximum do
          if not IncDec(False,tmpValue,IIncrement) then
             break;

      { Draw the labels... }
      if IRangeZero then
         InternalDrawLabel(tmpIsDateTime,tmpValue,False)  { Maximum is equal to Minimum. Draw one label only }
      else
      begin
        if Assigned(OnCalcMaxLabel) then
           IncreaseGapValue;

        { do the loop and draw labels... }

        (*
         // Removed checking. Min axis increment is now Epsilon
        if SameValue(IMaximum,IMinimum) or
           SameValue(tmpValue,tmpValue-IIncrement) then

           //(FloatToStr(tmpValue)=FloatToStr(tmpValue-IIncrement)) then { fix zooming when axis Max=Min }

           InternalDrawLabel(tmpIsDateTime,tmpValue,False)
        else
        *)
        begin
          { draw labels until "tmpValue" is less than IMinimum }

          // Commented, negative minimum labels never show
          if IMinimum>=0 then
             tmp:=(IMinimum-MinAxisIncrement)/(1.0+MinAxisIncrement)
          else
             tmp:=IMinimum;

          while tmpValue>=tmp do
          begin
            if Assigned(OnNextLabelValue) then
            Begin
              OnNextLabelValue(Self, tmpValue);

              if tmpValue >= tmp then
                 if not InternalDrawLabel(tmpIsDateTime,tmpValue,True) then
                    break;
            end
            else
            if not InternalDrawLabel(tmpIsDateTime,tmpValue,True) then
               break;
          end;
        end;
      end;
    end;
  end;

  Procedure DoNotCustomLabels;
  begin
    if Logarithmic and (FDesiredIncrement=0) then DoDefaultLogLabels
                                             else DoDefaultLabels;
  end;

  Procedure DoCustomLabels;
  Const DifFloat = 0.0000001;
  var LabelIndex  : Integer;
      Stop        : Boolean;
      LabelInside : Boolean;
      tmpIsDateTime : Boolean;
  Begin
    tmpValue:=IMinimum;
    Stop:=True;
    LabelIndex:=0;

    {$IFDEF D24}
    {$IFDEF CPUX64}
    {$IFNDEF D25}
    LabelInside:=False;  // To avoid warning
    {$ENDIF}
    {$ENDIF}
    {$ELSE}
    LabelInside:=False;  // To avoid warning
    {$ENDIF}

    tmpIsDateTime:=CalcIsDateTime(tmpWhichDateTime);

    repeat
      ParentChart.FOnGetNextAxisLabel(TChartAxis(Self),LabelIndex,tmpValue,Stop);

      if Stop then
      Begin
        if LabelIndex=0 then DoNotCustomLabels;
        Exit;
      end
      else
      begin { Trick with doubles... }
        LabelInside:=(tmpValue>=(IMinimum-DifFloat)) and
                     (tmpValue<=(IMaximum+DifFloat));

        if LabelInside then
           InternalDrawLabel(tmpIsDateTime,tmpValue,False);

        Inc(LabelIndex);
      end;

    until (not LabelInside) or (LabelIndex>2000) or Stop; { maximum 2000 labels... }
  end;

  Procedure DrawAxisTitle;

    function CalcTitlePos(IsInverted:Boolean):Integer;
    var tmpSize : Integer;
        tmpPos  : TAxisTitlePosition;
    begin
      if FAxisTitle.Position=tpCenter then
         result:=ICenterPos
      else
      begin
        tmpSize:=({$IFDEF FMX}Round{$ENDIF}(FAxisTitle.Width) div 2);

        tmpPos:=FAxisTitle.Position;

        if IsInverted then
           if tmpPos=tpStart then tmpPos:=tpEnd
                             else tmpPos:=tpStart;

        if tmpPos=tpStart then
           result:=IStartPos+tmpSize
        else
           result:=IEndPos-tmpSize
      end;
    end;

  begin
    if Assigned(FAxisTitle) then
    if FAxisTitle.Visible and (FAxisTitle.Caption<>'') then
    begin
      if IsDepthAxis then DrawTitle(PosTitle,DepthAxisPos)
      else
      if Horizontal then DrawTitle(CalcTitlePos(Inverted),PosTitle)
                    else DrawTitle(PosTitle,CalcTitlePos(not Inverted));
    end;
  end;

  Procedure DepthAxisLabels;
  var t     : Integer;
      tmp   : Integer;
      tmpSt : String;
  begin
    if ParentChart.CountActiveSeries>0 then
      for t:=Trunc(IMinimum) to Trunc(IMaximum) do
      Begin
        tmpValue:=IMaximum-t-0.5;
        tmp:=Self.CalcYPosValue(tmpValue);

        if not TickOnLabelsOnly then
           AddTick(tmp);

        if FLabels.Visible then
        begin
          With ParentChart do
          begin
            tmpSt:=SeriesTitleLegend(t,True);
            if Assigned(FOnGetAxisLabel) then
               FOnGetAxisLabel(TChartAxis(Self),nil,t,tmpSt);
          end;

          AddTickAndSaveLabel(tmpValue,tmp,tmpSt);
        end;
      end;
  end;

  Procedure DrawCustomLabels;
  var t : Integer;
      tmpItem : TAxisItem;
      tmpSt   : String;
  begin
    for t:=0 to Items.Count-1 do
    begin
      tmpItem:=Items[t];

      if (tmpItem.Value>=InternalMinimum) and (tmpItem.Value<=InternalMaximum) then
      begin
        tmpItem.FLabelPos:=CalcPosValue(tmpItem.Value);

        if not TickOnLabelsOnly then
           AddTick(tmpItem.FLabelPos);

        if tmpItem.ActiveFormat.Visible then
        begin
          tmpSt:=tmpItem.FText;
          if tmpSt='' then
             tmpSt:=LabelValue(tmpItem.Value);

          AddTickAndSaveLabel(tmpItem.Value,tmpItem.FLabelPos,tmpSt,nil,tmpItem.ActiveFormat);
        end;
      end;
    end;
  end;

  function CalcDateTimeStep:TDateTimeStep;
  begin
    result:=FindDateTimeStep(FDesiredIncrement);

    if result<>dtNone then
       while (IIncrement>DateTimeStep[result]) and
             (result<>dtOneYear) do
                 result:=Succ(result);
  end;

  procedure DoDrawLabels;
  begin
    if FLabels.Behind then
       TCanvas3DAccess(ParentChart.Canvas).BeginEntity('Labels'); // Do not localize

    tmpAlternate:=Horizontal;

    tmpNumTicks:=0;
    Tick:=nil;

    if Items.Automatic then
    begin
      Items.Clear;

      tmpLabelStyle:=CalcLabelStyle;

      if (tmpLabelStyle<>talNone) or (Grid.Visible and Grid.DrawAlways) then
      begin
        // Assign font before CalcIncrement !
        ParentChart.PrepareFont3D(Texts.Font);
        IIncrement:=CalcIncrement;

        if IAxisDateTime and FExactDateTime and (FDesiredIncrement<>0) then
           tmpWhichDateTime:=CalcDateTimeStep
        else
           tmpWhichDateTime:=dtNone;

        if ((IIncrement>0) or
           ( (tmpWhichDateTime>=dtHalfMonth) and (tmpWhichDateTime<=dtOneYear)) )
           and (IMaximum>=IMinimum) then
        begin
          case tmpLabelStyle of
           talValue: if Assigned(ParentChart.FOnGetNextAxisLabel) then
                        DoCustomLabels
                     else
                        DoNotCustomLabels;

     talPointValue,
            talMark: AxisLabelsSeries;

            talText: if IsDepthAxis and (Self as TChartDepthAxis).LabelsAsSeriesTitles then
                        DepthAxisLabels
                     else
                        AxisLabelsSeries;
          end;
        end;
      end;
    end
    else
      DrawCustomLabels;

    if FLabels.Behind then
       TCanvas3DAccess(ParentChart.Canvas).EndEntity;
  end;

  procedure DrawSubAxis(const Axis:TChartAxis);
  begin
    if Axis.Visible then
    begin
      AssignToSub(Axis);

      Axis.IStartPos:=IStartPos;
      Axis.IEndPos:=IEndPos;
      Axis.RecalcSizeCenter;

      Axis.Draw(False);
    end;
  end;

  procedure DrawBack;
  var tmp : Integer;
  begin
    tmp:=SizeLabels+3;

    { Pending: IMaxImageWidth for vertical axes
    if FLabels.UseImages then
       Inc(tmp,FLabels.IMaxImageHeight);
    }

    if FHorizontal then
       Inc(tmp,LabelsFormat.Margins.VertSize)
    else
       Inc(tmp,LabelsFormat.Margins.HorizSize);

    if FLabels.Position=lpAtTick then
       Inc(tmp,SizeTickAxis)
    else
       tmp:=Max(SizeTickAxis-2,tmp);

    if FOtherSide then
       tmp:=-tmp;

    if FHorizontal then
       FShape.ShapeBounds:=TeeRect(IStartPos,PosAxis,IEndPos,PosAxis+tmp)
    else
       FShape.ShapeBounds:=TeeRect(PosAxis-tmp,IStartPos,PosAxis,IEndPos);

    FShape.Draw;
  end;

  procedure DrawLabelsOnTop;
  var t : Integer;
  begin
    TCanvas3DAccess(ParentChart.Canvas).BeginEntity('Labels'); // Do not localize

    for t:=0 to Items.Count-1 do
    with Items[t] do
       if Items.IDrawAll or
          ((Value>=IMinimum) and (Value<=IMaximum)) or //TV52015428
          SameValue(Value,IMinimum) then  // <-- Ensure floating point binary comparison
            if ActiveFormat.Visible then
               DrawThisLabel(t,Value, LabelPos, Text, Picture, ActiveFormat);

    TCanvas3DAccess(ParentChart.Canvas).EndEntity;
  end;

var t : Integer;
    tmpParent : TChartAxis;
begin
  TCanvas3DAccess(ParentChart.Canvas).BeginEntity(IAxisID,nil,Transform);

  tmpParent:=GetParentAxis;

  if Assigned(tmpParent) then
     IAxisDateTime:=tmpParent.IAxisDateTime
  else
     IAxisDateTime:=IsDateTime;

  if CalcPosAxis then
     FPosAxis:=ApplyPosition(GetRectangleEdge(ParentChart.ChartRect),ParentChart.ChartRect);

  if Assigned(FShape) and FShape.Visible then
     DrawBack;

  DrawAxisTitle;
  DoDrawLabels;
  DrawTicksGrid;

  if FLabels.Visible and (not FLabels.Behind) then
     DrawLabelsOnTop;

  TCanvas3DAccess(ParentChart.Canvas).EndEntity;

  if Assigned(FSubAxes) then
  for t:=0 to FSubAxes.Count-1 do
         DrawSubAxis(FSubAxes[t]);
end;

Function TChartAxis.SizeTickAxis(const UseTickLength:Boolean=True):Integer;
begin
  if FAxis.Visible then result:={$IFDEF FMX}Round{$ENDIF}(FAxis.Width+1)
                   else result:=0;

  if UseTickLength and FTicks.Visible then
     Inc(result,FTickLength);

  if FMinorTicks.Visible then
     result:=Math.Max(result,FMinorTickLength);
end;

// Returns the pixel size of axis labels (width or height depending on Axis
// Horizontal property).
// For "Alternate" labels, size is duplicated.
Function TChartAxis.SizeLabels:Integer;

  // Calculate the maximum number of text lines of all labels,
  // when Items are automatic.
  procedure CalcHeightFactor;
  var tmpLabelStyle:TAxisLabelStyle;
      tmpNumLines : Integer;
      t, s : Integer;
      tmpHeight : Integer;
      Series : TChartSeries;
      tmpValue : TAxisValue;
      tmpSt : String;
      tmpPicture : TTeePicture;
  begin
    FLabels.IHeightFactor:=1;
    FLabels.IMaxImageHeight:=0;

    if Horizontal and (FLabels.Angle=0) then // For non-rotated labels only
    begin
      tmpLabelStyle:=CalcLabelStyle;

      if Items.Automatic then
      begin
        if tmpLabelStyle<>talValue then
        begin
          CalcAllSeries;

          for s:=0 to ISeriesList.Count-1 do
          begin
            Series:=ISeriesList[s];
            Series.CalcFirstLastVisibleIndex;

            if Series.FirstDisplayedIndex<>-1 then
            for t:=Series.FirstDisplayedIndex to Series.LastDisplayedIndex do
            begin
              if GetAxisSeriesLabel(tmpLabelStyle, Series, t, tmpValue, tmpSt, tmpPicture) then
                 if (tmpValue>=IMinimum) and (tmpValue<=IMaximum) then
                 begin
                   tmpNumLines:=ParentChart.LinesOfText(tmpSt);

                   if tmpNumLines>FLabels.IHeightFactor then
                      FLabels.IHeightFactor:=tmpNumLines;

                   if tmpPicture<>nil then
                   begin
                     tmpHeight:=tmpPicture.Height;

                     if tmpHeight>FLabels.IMaxImageHeight then
                        FLabels.IMaxImageHeight:=tmpHeight;
                   end;
                 end;
            end;
          end;
        end
        else
          MaxLabelsValue(tmpHeight,FLabels.IHeightFactor);
      end;
    end;
  end;

begin
  if FLabels.IHeightFactor=0 then
     CalcHeightFactor;

  result:=InternalCalcSize(FLabels.Format,FLabels.Angle,'',FLabels.Size)*FLabels.IHeightFactor;

  if FLabels.Alternate then
     result:=result*2;
end;

function TChartAxis.MaxImageWidth:Integer;

  function MaxWidth(const APictures:TTeePictureList):Integer;
  var t : Integer;
  begin
    result:=0;

    if APictures<>nil then
       for t:=0 to APictures.Count-1 do
           if APictures[t]<>nil then
              result:=Max(result,APictures[t].Width);
  end;

  function SameOrientation(const ASeries:TChartSeries):Boolean;
  begin
    if Horizontal then
       result:=ASeries.YMandatory
    else
       result:=not ASeries.YMandatory;
  end;

var tmp : TChartSeries;
    {$IFNDEF D9}
    t : Integer;
    {$ENDIF}
begin
  result:=0;

  {$IFDEF D9}
  for tmp in ParentChart.SeriesList do
  begin
  {$ELSE}
  for t:=0 to ParentChart.SeriesCount-1 do
  begin
    tmp:=ParentChart[t];
  {$ENDIF}
      if (tmp.FPictures<>nil) and tmp.Active and SameOrientation(tmp) and
         tmp.AssociatedToAxis(Self) then
            result:=Math.Max(result,MaxWidth(tmp.FPictures));
  end;
end;

function TChartAxis.InternalCalcSize( const AFormat:TCustomTextShape;
                                      AAngle:Integer;
                                      const AText:String;
                                      ASize:Integer):Integer;
var tmpPen : TTeePen;
    tmpShadow : TTeeShadow;
    tmpHeight : Boolean;
    tmpNumLines : Integer;
Begin
  if ASize<>0 then
     result:=ASize
  else
  begin
    ParentChart.PrepareFont3D(AFormat.Font);

    tmpHeight:=( Horizontal and ((AAngle mod 180)=0)) or
               ( (not Horizontal) and ((AAngle=90) or (AAngle=270)));

    if tmpHeight then
       result:=ParentChart.Canvas.FontHeight
    else
    begin
      if AText='' then
         result:=MaxLabelsWidth
      else
      if AFormat.TextFormat=ttfNormal then
         result:=ParentChart.Canvas.TextWidth(AText)
      else
         result:=ParentChart.MultiLineTextWidth(AText,tmpNumLines,AFormat.TextFormat);

      // Reduce space taken by rotated labels, depending on angle:
      if Horizontal then
         result:=Round(result*Abs(Sin(AAngle*TeePiStep)))+ParentChart.Canvas.FontHeight
      else
         result:=Round(result*Abs(Cos(AAngle*TeePiStep)));
    end;

    if not AFormat.Transparent then
    begin
      tmpPen:=Items.Format.Pen;

      if tmpPen.Visible then
         Inc(result,2* {$IFDEF FMX}Round{$ENDIF}(tmpPen.Width));

      tmpShadow:=Items.Format.Shadow;

      if tmpShadow.ShouldDraw then
         Inc(result,Abs(tmpShadow.Size));
    end;
  end;
end;

// Increases or decreases "Value" by the specified "Amount", depending on the
// axis orientation (vertical or horizontal), and if the axis is the default side
// (left and bottom), or "Other" side (top and right)
Function TChartAxis.InflateAxisPos(Value:Integer; Amount:Integer):Integer;
Begin
  result:=Value;

  if Horizontal then
     if OtherSide then Dec(result,Amount) else Inc(result,Amount)
  else
     if OtherSide then Inc(result,Amount) else Dec(result,Amount);
end;

// Reduce "R" rectangle at the axis edge, by Value pixels.
// Limits maximum reduction to avoid edge-crossing/overlap.
Procedure TChartAxis.InflateAxisRect(var R:TRect; const Value:Integer);
Begin
  With R do
  if Horizontal then
     if OtherSide then
        Top:=Min(Bottom-1,Top+Value)
     else
        Bottom:=Max(Top+1,Bottom-Value)
  else
     if OtherSide then
        Right:=Max(Left+1,Right-Value)
     else
        Left:=Min(Right-1,Left+Value);
end;

// Copy important properties from parent axis to subaxis "Axis"
procedure TChartAxis.AssignToSub(const Axis:TChartAxis);
begin
  Axis.FHorizontal:=FHorizontal;
  Axis.FOtherSide:=FOtherSide;
  Axis.FInverted:=FInverted;
  Axis.FLogarithmic:=FLogarithmic;
  Axis.FLogarithmicBase:=FLogarithmicBase;
end;

Procedure TChartAxis.CalcRect(var R:TRect; InflateChartRectangle:Boolean);

  // Reduce the "R" chart rectangle and return the pixel position of the axis edge:
  function CalcLabelsRect(const ASize:Integer):Integer;
  begin
    InflateAxisRect(R,ASize);
    result:=GetRectangleEdge(R);
  end;

  // Process sub-axis (repeat recursive)
  procedure CalcRectSubAxis(const Axis:TChartAxis);
  begin
    if Axis.Visible then
    begin
      AssignToSub(Axis);

      Axis.SetCalcPosValue;

      Axis.CalcRect(R,True);
      Axis.FPosAxis:=ApplyPosition(GetRectangleEdge(R),R);
    end;
  end;

var tmp : Integer;
    t   : Integer;
    tmpMargin : Integer;
    tmpTitleSize : Integer;
    tmpHasTitle : Boolean;
begin
  // If this axis has any child subaxes, process them first:

  if Assigned(FSubAxes) then
  for t:=0 to FSubAxes.Count-1 do
      CalcRectSubAxis(FSubAxes[t]);

  IAxisDateTime:=IsDateTime;

  if InflateChartRectangle then
  begin
    // See TeeTools TAxisScrollBar
    if IExtraSpace>0 then
       InflateAxisRect(R,IExtraSpace);

    tmpHasTitle:=Assigned(FAxisTitle) and FAxisTitle.Visible and
                (FAxisTitle.Caption<>'');

    if IsDepthAxis then
       if OtherSide then FPosTitle:={$IFDEF FMX}Round{$ENDIF}(R.Right)
                    else FPosTitle:={$IFDEF FMX}Round{$ENDIF}(R.Left)
    else
    if tmpHasTitle then
    begin
      with FAxisTitle do
           tmpTitleSize:=InternalCalcSize(FAxisTitle,Angle,Caption,FTitleSize);

      FPosTitle:=CalcLabelsRect(tmpTitleSize);
    end;

    if FLabels.Visible then
    begin
      // Reset to zero, to force recalculation at SizeLabels
      FLabels.IHeightFactor:=0;

      tmp:=SizeLabels;

      if FLabels.UseImages then
         if Horizontal then
            Inc(tmp,FLabels.IMaxImageHeight)
         else
            Inc(tmp,MaxImageWidth);

      with ParentChart.Axes do
      if Assigned(CalcPosLabels) then
         tmp:=CalcPosLabels(Self,tmp);

      // Calculate labels position applying a % margin from axis
      tmpMargin:=Round(ParentChart.Canvas.FontHeight*FLabels.FMargin*0.01);
      FPosLabels:=InflateAxisPos(CalcLabelsRect(tmp),tmpMargin);
    end;

    tmp:=ParentChart.CalcWallSize(Self)+SizeTickAxis(FLabels.Position=lpAtTick);
    if tmp>0 then
       InflateAxisRect(R,tmp);

    FPosTitle:=ApplyPosition(FPosTitle,R);
    FPosLabels:=ApplyPosition(FPosLabels,R);
  end
  else
  begin
    FPosAxis:=ApplyPosition(GetRectangleEdge(R),R);
    FPosLabels:=InflateAxisPos(FPosAxis,SizeTickAxis);
    FPosTitle:=InflateAxisPos(FPosLabels,SizeLabels);
  end;
end;

// Z position persistence:
function TChartAxis.IsZStored: Boolean;
begin
  result:=(not IsDepthAxis) and
          (
            (OtherSide and (FZPosition<>100)) or
            ((not OtherSide) and (FZPosition>0.011))  // VCL bug double=0 streaming
          );
end;

procedure TChartAxis.SetMaximumOffset(const Value: Integer);
begin
  ParentChart.SetIntegerProperty(FMaximumOffset,Value);
end;

procedure TChartAxis.SetMinimumOffset(const Value: Integer);
begin
  ParentChart.SetIntegerProperty(FMinimumOffset,Value);
end;

function TChartAxis.IsLogBaseStored: Boolean;
begin
  result:=LogarithmicBase<>10;
end;

function TChartAxis.GetGridCentered: Boolean;
begin
  result:=Grid.Centered;
end;

procedure TChartAxis.SetPosUnits(const Value: TTeeUnits);
begin
  if FPosUnits<>Value then
  begin
    FPosUnits:=Value;
    ParentChart.Invalidate;
  end;
end;

{ TChartDepthAxis }
Constructor TChartDepthAxis.Create(Collection:TCollection);
Begin
  inherited Create(Collection);
  FLabelsAsTitles:=True;
end;

Constructor TChartDepthAxis.Create(const Chart: TCustomAxisPanel);
begin
  inherited Create(Chart);
  FLabelsAsTitles:=True;
end;

function TChartDepthAxis.InternalCalcLabelStyle: TAxisLabelStyle;
var t : Integer;
begin
  result:=talText;

  With ParentChart do
  for t:=0 to SeriesCount-1 do
  With Series[t] do
  if Active then
     if HasZValues or (MinZValue<>MaxZValue) then
     begin
       result:=talValue;
       break;
     end;
end;

Procedure TChartDepthAxis.SetLabelsAsTitles(Value:Boolean);
Begin
  ParentChart.SetBooleanProperty(FLabelsAsTitles,Value);
end;

{ TSeriesMarksPosition }

Procedure TSeriesMarkPosition.Assign(const Source:TSeriesMarkPosition);
begin
  ArrowFrom :=Source.ArrowFrom;
  ArrowTo   :=Source.ArrowTo;
  ArrowFix  :=Source.ArrowFix;
  Custom    :=Source.Custom;
  LeftTop   :=Source.LeftTop;
  Height    :=Source.Height;
  Width     :=Source.Width;
  MidPoint  :=Source.MidPoint;
  HasMid    :=Source.HasMid;
end;

Function TSeriesMarkPosition.Bounds:TRect;
begin
  {$IFDEF FMX}
  result:=RectF(LeftTop.X,LeftTop.Y,Width,Height);
  {$ELSE}
  result:={$IFDEF D6}Types.{$ELSE}Classes.{$ENDIF}Bounds(LeftTop.X,LeftTop.Y,Width,Height);
  {$ENDIF}
end;

// Move P xy (as delta)
procedure TSeriesMarkPosition.Offset(const P:TPoint);
begin
  LeftTop.X:=LeftTop.X+P.X;
  LeftTop.Y:=LeftTop.Y+P.Y;

  ArrowTo.X:=ArrowTo.X+P.X;
  ArrowTo.Y:=ArrowTo.Y+P.Y;

  if HasMid then
  begin
    MidPoint.X:=MidPoint.X+P.X;
    MidPoint.Y:=MidPoint.Y+P.Y;
  end;
end;

// Set the "other" Depth axis Inverted property to same value
procedure TChartDepthAxis.SetInverted(Value: Boolean);
begin
  inherited;

  if Self=ParentChart.Axes.Depth then
     ParentChart.Axes.DepthTop.FInverted:=Inverted
  else
     ParentChart.Axes.Depth.FInverted:=Inverted
end;

{ TChartValueList }
Constructor TChartValueList.Create(AOwner:TChartSeries; Const AName:String);
Begin
  inherited Create;

  FModified:=True;

  {$IFDEF TEEMULTIPLIER}
  FMultiplier:=1;
  {$ENDIF}

  FOwner:=AOwner;

  if Assigned(FOwner) then
     FOwner.FValuesList.Add(Self);

  FName:=AName;

  {$IFNDEF TEEARRAY}
  FList:=TList.Create;
  {$ENDIF}

  ClearValues;
end;

Destructor TChartValueList.Destroy;
Begin
  {$IFDEF TEEARRAY}
  ClearValues;
  {$ELSE}
  FList.Free;
  {$ENDIF}

  inherited;
end;

Function TChartValueList.First:TChartValue;
begin
  result:=Value[0]
end;

{$IFNDEF TEEARRAY}
Function TChartValueList.Count:Integer;
begin
  result:=FList.Count; { <-- virtual }
end;
{$ENDIF}

Function TChartValueList.Last:TChartValue;
Begin
  result:=Value[Count-1]
end;

Procedure TChartValueList.Delete(ValueIndex:Integer);
begin
  Delete(ValueIndex,1);
end;

Procedure TChartValueList.Delete(Start,Quantity:Integer);
{$IFNDEF TEEARRAY}
var t : Integer;
{$ENDIF}
begin
  {$IFOPT C+}
  Assert( (Start+Quantity-1) < Count,'Quantity of data to delete must be less than Count.');
  {$ENDIF}

  {$IFDEF TEEARRAY}

  if Quantity>(Count-Start) then
     Quantity:=Count-Start;

  Count:=Count-Quantity;

  if Start<Count then
     System.Move(Value[Start+Quantity],Value[Start],SizeOf(TChartValue)*(Count-Start));

  {$ELSE}

  for t:=1 to Quantity do
  begin
    Dispose(PChartValue(FList[Start]));
    FList.Delete(Start);
  end;
  {$ENDIF}

  Modified:=True;
end;

Procedure TChartValueList.InsertChartValue(ValueIndex:Integer; Const AValue:TChartValue);
{$IFDEF TEEARRAY}
var tmp : Integer;
{$ELSE}
var p   : PChartValue;
{$ENDIF}
Begin
  {$IFDEF TEEARRAY}

  tmp:=Length(Value);
  if Count>=tmp then
     SetLength(Value,tmp+TeeDefaultCapacity);

  tmp:=Count-ValueIndex;
  if tmp>0 then
     System.Move(Value[ValueIndex],Value[ValueIndex+1],SizeOf(TChartValue)*tmp);

  Value[ValueIndex]:=AValue;
  Count:=Count+1;

  {$ELSE}
  New(p);
  p^:=AValue;
  FList.Insert(ValueIndex,p);  { <- virtual }
  {$ENDIF}

  Modified:=True;
end;

Function TChartValueList.AddChartValue(Const AValue:TChartValue):Integer;
begin
  TempValue:=AValue;
  result:=AddChartValue;
end;

Function TChartValueList.AddChartValue:Integer;

  {$IFDEF TEEARRAY}
  Procedure IncrementArray;
  var tmp : Integer;
  begin
    tmp:=Length(Value);
    if (Count+1)>tmp then
       SetLength(Value,tmp+TeeDefaultCapacity);
  end;
  {$ENDIF}

  procedure DirectAdd;
  var
    {$IFDEF TEEARRAY}
    tmp : Integer;
    {$ELSE}
    p   : PChartValue;
    {$ENDIF}
  begin
    {$IFDEF TEEARRAY}
    result:=Count;

    tmp:=Length(Value);
    if result>=tmp then
       SetLength(Value,tmp+TeeDefaultCapacity);

    Value[result]:=FTempValue;
    Count:=Count+1;

    {$ELSE}
    New(p);    { virtual }
    p^:=TempValue;

    result:=FList.Add(p)
    {$ENDIF}
  end;

  procedure DoInsert;
  var
    {$IFDEF TEEARRAY}
    t : Integer;
    {$ELSE}
    p : PChartValue;
    {$ENDIF}
  begin
    {$IFDEF TEEARRAY}

    IncrementArray;

    for t:=Count downto Succ(result) do Value[t]:=Value[t-1];

    Value[result]:=FTempValue;
    Count:=Count+1;

    {$ELSE}
    New(p);    { virtual }
    p^:=TempValue;

    FList.Insert(result,p);
    {$ENDIF}
  end;

var tmp : Integer;
Begin
  if FOrder=loNone then
     DirectAdd
  else
  begin
    tmp:={$IFDEF TEEARRAY}Count{$ELSE}FList.Count{$ENDIF}-1;

    if (tmp=-1) or
       ( (FOrder=loAscending) and (FTempValue>={$IFDEF TEEARRAY}Value[tmp]{$ELSE}PChartValue(FList[tmp])^{$ENDIF}) ) or
       ( (FOrder=loDescending) and (FTempValue<={$IFDEF TEEARRAY}Value[tmp]{$ELSE}PChartValue(FList[tmp])^{$ENDIF}) ) then
          DirectAdd
    else
    Begin
      if FOrder=loAscending then
         while (tmp>=0) and
               ({$IFDEF TEEARRAY}Value[tmp]{$ELSE}PChartValue(FList[tmp])^{$ENDIF}>FTempValue) do
               Dec(tmp)
      else
         while (tmp>=0) and
               ({$IFDEF TEEARRAY}Value[tmp]{$ELSE}PChartValue(FList[tmp])^{$ENDIF}<FTempValue) do
               Dec(tmp);

      result:=tmp+1;

      DoInsert;
    end;
  end;

  Modified:=True;
end;

{$IFDEF TEEMULTIPLIER}
Procedure TChartValueList.SetMultiplier(Const Value:Double);
Begin
  if Value<>FMultiplier then
  begin
    FMultiplier:=Value;
    Modified:=True;
    FOwner.NotifyValue(veRefresh,0);
    FOwner.Repaint;
  end;
end;
{$ENDIF}

Function TChartValueList.GetValue(ValueIndex:Integer):TChartValue;
begin
  {$IFDEF TEEARRAY}
  {$IFOPT R+}
  if (ValueIndex<0) or (ValueIndex>(Count-1)) then
     Raise ChartException.CreateFmt(SListIndexError,[ValueIndex]);
  {$ENDIF}
  result:=Value[ValueIndex]{$IFDEF TEEMULTIPLIER}*FMultiplier{$ENDIF};
  {$ELSE}
  result:=PChartValue(FList[ValueIndex])^{$IFDEF TEEMULTIPLIER}*FMultiplier{$ENDIF};
  {$ENDIF}
end;

function TChartValueList.Locate(Const AValue:TChartValue; FirstIndex,LastIndex:Integer):Integer;
begin
  for result:=FirstIndex to LastIndex do
      if Value[result]=AValue then
         exit;

  result:=-1;
end;

function TChartValueList.Locate(Const AValue:TChartValue):Integer;
Begin
  result:=Locate(AValue,0,Count-1);
end;

procedure TChartValueList.SetValue(ValueIndex:Integer; const AValue:TChartValue);
begin
  {$IFDEF TEEARRAY}

  {$IFOPT R+}
  if (ValueIndex<0) or (ValueIndex>(Count-1)) then
     Raise ChartException.CreateFmt(SListIndexError,[ValueIndex]);
  {$ENDIF}

  Value[ValueIndex]:=AValue;
  {$ELSE}
  PChartValue(FList[ValueIndex])^:=AValue;
  {$ENDIF}

  FModified:=True;
  FOwner.NotifyValue(veModify,ValueIndex);
end;

// Triggers notification of Values[] array manual modifications:
procedure TChartValueList.SetModified(const Value:Boolean);
begin
  if FModified<>Value then
  begin
    FModified:=Value;

    if FModified then
       FOwner.NotifyValue(veModify,TeeAllValues);
  end;
end;

Function TChartValueList.GetMaxValue:TChartValue;
begin
  if Modified then RecalcStats;
  result:=FMaxValue;
end;

Function TChartValueList.GetMinValue:TChartValue;
begin
  if Modified then RecalcStats;
  result:=FMinValue;
end;

Function TChartValueList.GetTotal:TChartValue;
begin
  if Modified then RecalcStats;
  result:=FTotal;
end;

Function TChartValueList.GetTotalABS:TChartValue;
begin
  if Modified then RecalcStats;
  result:=FTotalABS;
end;

procedure TChartValueList.RecalcStats(StartIndex:Integer);
var t : Integer;
    tmpValue : TChartValue;
    tmpOffset : Integer;
begin
  {$IFDEF TEEARRAY}
  if Count>Length(Value) then  // HV suggestion
     Count:=Length(Value);
  {$ENDIF}

  if Count>0 then
  begin

    if StartIndex=0 then
    begin
      tmpValue:=Value[0];
      FMinValue:=tmpValue;
      FMaxValue:=tmpValue;
      FTotal:=tmpValue;
      FTotalABS:=Abs(tmpValue);
      tmpOffset:=1;
    end
    else tmpOffset:=0;

    for t:=StartIndex+tmpOffset to Count-1 do
    Begin
      tmpValue:=Value[t];

      if tmpValue<FMinValue then FMinValue:=tmpValue else
      if tmpValue>FMaxValue then FMaxValue:=tmpValue;

      FTotal:=FTotal+tmpValue;
      FTotalABS:=FTotalABS+Abs(tmpValue);
    end;

    FModified:=False;
  end
  else
     RecalcStats(0,0,0,0);
end;

Procedure TChartValueList.RecalcStats;
Begin
  RecalcStats(0);
end;

// Passing pre-calculated values, to avoid this ValueList object to spend time
// recalculating them.
procedure TChartValueList.RecalcStats(const AMin,AMax,ATotal,ATotalABS:TChartValue);
begin
  {$IFDEF TEEARRAY}
  if Count>Length(Value) then  // HV suggestion
     Count:=Length(Value);
  {$ENDIF}

  FMinValue:=AMin;
  FMaxValue:=AMax;
  FTotal:=ATotal;
  FTotalABS:=ATotalABS;

  FModified:=False;
end;

procedure TChartValueList.SetDateTime(Const Value:Boolean);
Begin
  FOwner.SetBooleanProperty(FDateTime,Value);
end;

Procedure TChartValueList.ClearValues;
{$IFNDEF TEEARRAY}
var t : Integer;
{$ENDIF}
Begin
  {$IFDEF TEEARRAY}
  Count:=0;
  Value:=nil;
  {$ELSE}
  for t:=0 to FList.Count-1 do Dispose(PChartValue(FList[t])); { virtual }
  FList.Clear;
  {$ENDIF}

  FModified:=True;
//  RecalcStats;      //causes positioning problems for mapseries gallery page
end;

Function TChartValueList.Range:TChartValue;
begin
  if Modified then RecalcStats;
  result:=FMaxValue-FMinValue;
end;

Procedure TChartValueList.Scroll;
var t      : Integer;
    tmpVal : {$IFDEF TEEARRAY}TChartValue{$ELSE}PChartValue{$ENDIF};
Begin
{$IFNDEF TEEARRAY}
  With FList do  { virtual }
{$ENDIF}
  if Count>0 then
  Begin
    {$IFDEF TEEARRAY}
    tmpVal:=Value[0];
    for t:=1 to Count-1 do Value[t-1]:=Value[t];
    Value[Count-1]:=tmpVal;
    {$ELSE}
    tmpVal:=Items[0];
    for t:=1 to Count-1 do Items[t-1]:=Items[t];
    Items[Count-1]:=tmpVal;
    {$ENDIF}
  end;
end;

Procedure TChartValueList.SetValueSource(Const Value:String);
Begin
  if FValueSource<>Value then
  begin
    FValueSource:=Value;
    FOwner.CheckDataSource;
  end;
end;

{$IFDEF TEEARRAY}
Procedure TChartValueList.Exchange(Index1,Index2:Integer);
var tmp : TChartValue;
begin
  {$IFOPT R+}
  if (Index1<0) or (Index1>(Count-1)) then
     Raise ChartException.CreateFmt(SListIndexError,[Index1]);
  if (Index2<0) or (Index2>(Count-1)) then
     Raise ChartException.CreateFmt(SListIndexError,[Index2]);
  {$ENDIF}

  tmp:=Value[Index1];
  Value[Index1]:=Value[Index2];
  Value[Index2]:=tmp;

  FModified:=True;
  FOwner.NotifyValue(veModify,Index1);
end;
{$ENDIF}

Procedure TChartValueList.FillSequence;
var t : Integer;
begin
  for t:=0 to Count-1 do Value[t]:=t;
{$IFDEF TEEARRAY}
  FModified:=True;
  FOwner.NotifyValue(veModify,0);
{$ENDIF}
end;

{$IFOPT C+}
function TChartValueList.GetCount:Integer;
var tmp : Integer;
begin
  tmp:={$IFDEF TEEARRAY}Length(Value){$ELSE}FList.Count{$ENDIF};

  Assert( FCount <= tmp,
    'ValueList Count ('+IntToStr(FCount)+') greater than array length ('+IntToStr(tmp)+').');
  result:=FCount;
end;

procedure TChartValueList.SetCount(const Value:Integer);
begin
  Assert( FCount>=0, 'ValueList Count must be greater than zero.');
  FCount:=Value;
end;
{$ENDIF}

Function TChartValueList.CompareValueIndex(a,b:Integer):Integer;
var tmpA : TChartValue;
    tmpB : TChartValue;
begin
  tmpA:=Value[a];
  tmpB:=Value[b];

  if tmpA<tmpB then result:=-1
  else
  if tmpA>tmpB then result:=1
  else
  begin
    // Use "a" and "b" indexes to avoid "unstable" results:

    if a<b then
       result:=-1  // -1 always, for both cases FOrder ascending or descending
    else
       result:=0;

    Exit;
  end;

  if FOrder=loDescending then
     result:=-result;
end;

procedure TChartValueList.Sort;
begin
  if Order<>loNone then
     TeeSort(0,Count-1,CompareValueIndex,Owner.SwapValueIndex);
end;

Procedure TChartValueList.Assign(Source:TPersistent);
begin
  if Source is TChartValueList then
  With TChartValueList(Source) do
  begin
    Self.FOrder      :=FOrder;
    Self.FDateTime   :=FDateTime;
    {$IFDEF TEEMULTIPLIER}
    Self.FMultiplier :=FMultiplier;
    {$ENDIF}
    Self.FValueSource:=FValueSource;
  end;
end;

{$IFDEF TEEMULTIPLIER}
function TChartValueList.IsMultiStored: Boolean;
begin
  result:=FMultiplier<>1;
end;
{$ELSE}
procedure TChartValueList.ReadMultiplier(Reader: TReader);
begin
  if Reader.ReadFloat<>1 then
     ShowMessageUser('Error: Multiplier property is obsolete.'+#13+
                 'Modify TeeDefs.inc (enable Multiplier) and recompile.');
end;

procedure TChartValueList.DefineProperties(Filer: TFiler);
begin
  inherited;
  Filer.DefineProperty('Multiplier',ReadMultiplier,nil,False);
end;
{$ENDIF}

// Renamed from ToString to ValueToString (RAD 2008)
function TChartValueList.ValueToString(const Index: Integer): String;
begin
  result:=FloatToStr(Value[Index]);
end;

{$IFNDEF D12}
// Compatibility with pre RAD 2008:
Function TChartValueList.ToString(const Index:Integer):String;
begin
  result:=ValueToString(Index);
end;
{$ENDIF}

function TChartValueList.IsDateStored: Boolean;
begin
  result:=FDateTime<>IDefDateTime;
end;

procedure TChartValueList.InitDateTime(Value: Boolean);
begin
  FDateTime:=Value;
  IDefDateTime:=Value;
end;

{ TMargins }
Constructor TMargins.Create(const AParent:TTeeCustomShapeBrushPen);
begin
  inherited Create;

  {$IFNDEF IOS}{$IFNDEF ANDROID}
  IParent:=AParent;
  {$ENDIF}{$ENDIF}

  SetDefaultHoriz(10);
  SetDefaultVert(5);

  FUnits:=maPercentFont;
end;

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

function TMargins.IsLeftStored:Boolean;
begin
  result:=Left<>DefaultHoriz;
end;

function TMargins.IsRightStored:Boolean;
begin
  result:=Right<>DefaultHoriz;
end;

function TMargins.IsTopStored:Boolean;
begin
  result:=Top<>DefaultVert;
end;

function TMargins.IsBottomStored:Boolean;
begin
  result:=Bottom<>DefaultVert;
end;

procedure TMargins.SetDefaultHoriz(const Value:Integer);
begin
  DefaultHoriz:=Value;
  FLeft:=DefaultHoriz;
  FRight:=DefaultHoriz;
end;

procedure TMargins.SetDefaultVert(const Value:Integer);
begin
  DefaultVert:=Value;
  FTop:=DefaultVert;
  FBottom:=DefaultVert;
end;

procedure TMargins.SetIntegerProperty(var Variable:Integer; const Value: Integer);
begin
  if Variable<>Value then
  begin
    Variable:=Value;
    IParent.Repaint;
  end;
end;

procedure TMargins.SetBottom(const Value: Integer);
begin
  SetIntegerProperty(FBottom,Value);
end;

procedure TMargins.SetLeft(const Value: Integer);
begin
  SetIntegerProperty(FLeft,Value);
end;

procedure TMargins.SetUnits(const Value: TMarginUnits);
begin
  if FUnits<>Value then
  begin
    FUnits:=Value;
    IParent.Repaint;
  end;
end;

procedure TMargins.SetRight(const Value: Integer);
begin
  SetIntegerProperty(FRight,Value);
end;

procedure TMargins.SetTop(const Value: Integer);
begin
  SetIntegerProperty(FTop,Value);
end;

procedure TMargins.Reset;
begin
  ILast:=TeeZeroRect;
end;

procedure TMargins.Calculate(const Width, Height,FontSize:Integer);
var tmp : Single;
begin
  case Units of
  maPercentFont : begin
                    tmp:=0.01*FontSize;

                    ILast.Left:={$IFNDEF FMX}Round{$ENDIF}(Left*tmp);
                    ILast.Right:={$IFNDEF FMX}Round{$ENDIF}(Right*tmp);
                    ILast.Top:={$IFNDEF FMX}Round{$ENDIF}(Top*tmp);
                    ILast.Bottom:={$IFNDEF FMX}Round{$ENDIF}(Bottom*tmp);
                  end;
  maPercentSize : begin
                    tmp:=0.01*Width;

                    ILast.Left:={$IFNDEF FMX}Round{$ENDIF}(Left*tmp);
                    ILast.Right:={$IFNDEF FMX}Round{$ENDIF}(Right*tmp);

                    tmp:=0.01*Height;

                    ILast.Top:={$IFNDEF FMX}Round{$ENDIF}(Top*tmp);
                    ILast.Bottom:={$IFNDEF FMX}Round{$ENDIF}(Bottom*tmp);
                  end;
  else
    begin
      // Pixels
      ILast.Left:=Left;
      ILast.Right:=Right;
      ILast.Top:=Top;
      ILast.Bottom:=Bottom;
    end;
  end;
end;

function TMargins.HorizSize:Integer;
begin
  result:={$IFDEF FMX}Round{$ENDIF}(ILast.Left+ILast.Right);
end;

function TMargins.VertSize:Integer;
begin
  result:={$IFDEF FMX}Round{$ENDIF}(ILast.Top+ILast.Bottom);
end;

{ TSeriesMarksPositions }

Procedure TSeriesMarksPositions.Put(Index:Integer; const APosition:TSeriesMarkPosition);
begin
  While Index>=Count do Add(nil);

  if Items[Index]=nil then Items[Index]:=TSeriesMarkPosition.Create;

  With Position[Index] do
  begin
    Custom:=True;
    Assign(APosition);
  end;
end;

procedure TSeriesMarksPositions.DeleteMany(const Start,Quantity:Integer);
var t : Integer;
begin
  if Count>Start then
     for t:=Start to Start+Quantity-1 do
         Delete(Start);
end;

Function TSeriesMarksPositions.Get(Index:Integer):TSeriesMarkPosition;
begin
  if Index<Count then
     result:=TSeriesMarkPosition(List{$IFDEF TEELISTARRAY}^{$ENDIF}[Index])
  else
     result:=nil
end;

Procedure TSeriesMarksPositions.Automatic(Index:Integer);
begin
  if (Index<Count) and Assigned(Items[Index]) then
     Position[Index].Custom:=False;
end;

{$IFNDEF D18}
procedure TSeriesMarksPositions.Clear;
var t : Integer;
begin
  for t:=0 to Count-1 do Position[t].Free;
  inherited;
end;
{$ENDIF}

Function TSeriesMarksPositions.ExistCustom:Boolean;
var t : Integer;
begin
  result:=False;

  for t:=0 to Count-1 do
  if Assigned(Position[t]) and Position[t].Custom then
  begin
    result:=True;
    Exit;
  end;
end;

procedure TSeriesMarksPositions.MoveTo(var Position:TSeriesMarkPosition;
                                       const XPos,YPos:TCoordinate);
var tmp : TCoordinate;
begin
  With Position do
  begin
    ArrowTo.X:=XPos;
    ArrowTo.Y:=YPos;

    ArrowFrom:=ArrowTo;
       
    LeftTop.X:=ArrowTo.X-(Width div 2);
    LeftTop.Y:=ArrowTo.Y-Height+1;
  end;

  if Assigned(IMarks.FSymbol) then
  with IMarks.FSymbol.ShapeBounds do
  begin
    tmp:=Bottom-Top;
    Top:=Position.LeftTop.Y+2;
    Bottom:=Top+tmp;

    tmp:=Right-Left;
    Left:=Position.LeftTop.X+2;
    Right:=Left+tmp;
  end;
end;

{ TMarksItem }

Destructor TMarksItem.Destroy;
begin
  IText.Free;
  inherited;
end;

procedure TMarksItem.ChangedText;
begin
  inherited;
  ICustomText:=True;
end;

Procedure TMarksItem.TextChanged(Sender:TObject);
Begin
  FText:=IText.Text; // Change inherited FText:String with TStrings
  ICustomText:=True;
  ParentChart.CanvasChanged(Sender);
end;

function TMarksItem.GetText:TStrings;
var Old : Boolean;
begin
  if not Assigned(IText) then
  begin
    IText:=TStringList.Create;
    TStringList(IText).OnChange:=TextChanged;
  end;

  Old:=ICustomText;
  IText.Text:=FText; // Force always returning the inherited FText:String
  ICustomText:=Old;

  result:=IText;
end;

procedure TMarksItem.SetText(const Value:TStrings);
begin
  if Assigned(Value) then
     Text.Assign(Value)
  else
  begin
    FreeAndNil(IText);
    FText:='';
    ICustomText:=False;
  end;
end;

{ TMarksItems }

function TMarksItems.InternalGet(Index:Integer):TMarksItem;
begin
  if Count>Index then
     result:=Items[Index]
  else
     result:=nil;
end;

function TMarksItems.Get(Index:Integer):TMarksItem;
begin
  while Index>(Count-1) do Add(nil);

  result:=InternalGet(Index);

  if not Assigned(result) then
  begin
    result:=TMarksItem.Create(IMarks.ParentChart);
    result.Assign(IMarks);

    Items[Index]:=result;
  end;
end;

{$IFNDEF D18}
procedure TMarksItems.Clear;
var t : Integer;
begin
  for t:=0 to Count-1 do
      TMarksItem(Items[t]).Free;

  inherited;

  if Assigned(IMarks) then
     IMarks.Repaint;
end;
{$ENDIF}

{ TCustomTextShape }

Constructor TCustomTextShape.Create(const AOwner: TCustomTeePanel);
begin
  inherited;

  FClipText:=True;
  FCursor:=crDefault;

  FTextAlign:=taLeftJustify;
  IDefaultTextAlign:=taLeftJustify;

  FMargins:=TMargins.Create(Self);
end;

Destructor TCustomTextShape.Destroy;
begin
  FreeAndNil(FMargins);
  inherited;
end;

procedure TCustomTextShape.Assign(Source:TPersistent);
begin
  if Source is TCustomTextShape then
  with TCustomTextShape(Source) do
  begin
    Self.FClipText :=FClipText;
    Self.FCursor   :=FCursor;
    Self.Margins   :=Margins;
    Self.FText     :=FText;
    Self.FTextAlign:=FTextAlign;
    Self.FTextFormat:=FTextFormat;
    Self.FVertTextAlign:=FVertTextAlign;
  end;

  inherited;
end;

type
  TTeePanelAccess=class(TCustomTeePanel);

function TCustomTextShape.FirstChildPos:Integer;
var tmpNum : Integer;
begin
  result:=inherited FirstChildPos;

  TTeePanelAccess(ParentChart).TryAssignFont(Font);

  if (not Transparent) and Pen.Visible then
     Inc(result, {$IFDEF FMX}Round{$ENDIF}(Pen.Width) div 2);

  if ChildLayout=slTopBottom then
     Inc(result,{$IFDEF FMX}Round{$ENDIF}(Margins.Size.Top)+INumLines*ParentChart.Canvas.FontHeight)
  else
     Inc(result,{$IFDEF FMX}Round{$ENDIF}(Margins.Size.Left)+ParentChart.MultiLineTextWidth(Text,tmpNum,TextFormat));
end;

procedure TCustomTextShape.InitTextAlignment(const Value:TAlignment);
begin
  FTextAlign:=Value;
  IDefaultTextAlign:=Value;
end;

function TCustomTextShape.GetChildClass:TTeeCustomShapeClass;
begin
  result:=TCustomTextShape;
end;

class function TCustomTextShape.GetEditorClass:String;
begin
  result:='TTextShapeEditor';
end;

procedure TCustomTextShape.SetTextFormat(const Value:TTextFormat);
begin
  if FTextFormat<>Value then
  begin
    FTextFormat:=Value;
    Repaint;
  end;
end;

function TCustomTextShape.CalcTextSize(const Panel:TCustomTeePanel; S:String;
                const TextFormat:TTextFormat; out NumLines:Integer):TPoint;
begin
  result:=Panel.MultiLineTextSize(S,TextFormat,NumLines);
end;

procedure TCustomTextShape.CalcBounds(const Panel:TCustomTeePanel);
var tmp : TPoint;
    tmpW : Integer;
    tmpH : Integer;
    tmpPen : Integer;
    tmpFontH : Integer;
begin
  inherited;

  INumLines:=0;

  if Text<>'' then
  begin
    if IDraw2D then
       TTeePanelAccess(Panel).TryAssignFont(Font)
    else
       Panel.PrepareFont3D(Font);

    // Always call CalcTextSize to obtain INumLines, even if AutoSize=False
    tmp:=CalcTextSize(Panel,Text,TextFormat,INumLines {,Transparent});  // Pending: When Transparent, use typographic width

    if AutoSize then
    begin
      tmpW:={$IFDEF FMX}Round{$ENDIF}(tmp.X);
      tmpH:={$IFDEF FMX}Round{$ENDIF}(tmp.Y);

      tmpFontH:=tmpH;

      if (not Transparent) and Pen.Visible then
      begin
        tmpPen:=(1+{$IFDEF FMX}Round{$ENDIF}(Pen.Width));

        Inc(tmpW,tmpPen);
        Inc(tmpH,tmpPen);
      end;

      if Transparent then
         Margins.Reset
      else
         Margins.Calculate(tmpW,tmpH,tmpFontH);

      tmpW:=tmpW+2*Margins.HorizSize;

      if ChildLayout=slTopBottom then
      begin
        // Vertical layout:
        if tmpW>ShapeBounds.Right-ShapeBounds.Left then
           ShapeBounds.Right:=ShapeBounds.Left+tmpW;

        ShapeBounds.Bottom:=ShapeBounds.Bottom+Margins.VertSize+tmpH;
      end
      else
      begin
        // Horizontal layout:
        if tmpH>ShapeBounds.Bottom-ShapeBounds.Top then
           ShapeBounds.Bottom:=ShapeBounds.Top+tmpH;

        ShapeBounds.Right:=ShapeBounds.Right+Margins.HorizSize+tmpW;
      end;
    end;
  end;
end;

procedure TCustomTextShape.SetClipText(const Value: Boolean);
begin
  if FClipText<>Value then
  begin
    FClipText:=Value;
    Repaint;
  end;
end;

procedure TCustomTextShape.ChangedText;
begin
  Repaint;
end;

procedure TCustomTextShape.SetText(const Value: String);
begin
  if FText<>Value then
  begin
    FText:=Value;
    ChangedText;
  end;
end;

procedure TCustomTextShape.SetTextAlign(const Value: TAlignment);
begin
  if FTextAlign<>Value then
  begin
    FTextAlign:=Value;
    Repaint;
  end;
end;

type
  TCanvasAccess=class(TCanvas3D);

procedure TCustomTextShape.DrawString(const Panel: TCustomTeePanel;
                                const X:TCoordinate;
                                const Y, Z: Integer;
                                const Text: String;
                                AllowHTML: Boolean;
                                AAngle:Integer;
                                const RotationCenter:TPoint);
var tmpForce2D : Boolean;
begin
  tmpForce2D:=IDraw2D or ((Z=0) and (not Panel.Canvas.SupportsFullRotation));

  if AAngle=0 then
     if tmpForce2D then
        Panel.Canvas.TextOut(X, Y, Text, AllowHTML)
     else
        Panel.Canvas.TextOut3D({$IFDEF FMX}Round{$ENDIF}(X),
                               {$IFDEF FMX}Round{$ENDIF}(Y),
                               {$IFDEF FMX}Round{$ENDIF}(Z),
                               Text)
  else
  begin
    TCanvasAccess(Panel.Canvas).IUseTextCenter:=True;
    TCanvasAccess(Panel.Canvas).ITextCenter:=RotationCenter;

    if tmpForce2D then
       Panel.Canvas.RotateLabel({$IFDEF FMX}Round{$ENDIF}(X), Y, Text, AAngle, AllowHTML)
    else
       Panel.Canvas.RotateLabel3D({$IFDEF FMX}Round{$ENDIF}(X), Y, Z, Text, AAngle, AllowHTML);

    TCanvasAccess(Panel.Canvas).IUseTextCenter:=False;
  end;
end;

Procedure TCustomTextShape.DoDraw(const Panel:TCustomTeePanel; const Rect:TRect; Angle:Integer=0; AZ:Integer=0);
begin
  inherited;
  DoDrawText(Panel,Rect,Angle,AZ);
end;

Procedure TCustomTextShape.DoDrawText(const Panel:TCustomTeePanel; const Rect:TRect; Angle:Integer=0; AZ:Integer=0);
var x     : TCoordinate;
    t     : Integer;
    tmp   : String;
    tmpS  : String;
    tmpTop    : Integer;
    tmpHeight : Integer;
    tmpCenter : TPoint;
    tmpNumLines : Integer;
    tmpClip : Boolean;
begin
  Panel.Canvas.BackMode:=cbmTransparent;

  if IDraw2D then
     TTeePanelAccess(Panel).TryAssignFont(Font)
  else
     Panel.PrepareFont3D(Font);

  tmpHeight:=0;

  tmp:=FText;

  {$IFNDEF POSIX}
  tmp:=ReplaceChar(tmp,#$A);
  {$ENDIF}

  if tmp='' then tmp:=' ';  { at least one space character... }

  tmpTop:={$IFDEF FMX}Round{$ENDIF}(Rect.Top+Margins.Size.Top);

  if (not Transparent) and Pen.Visible then
     Inc(tmpTop, 1+{$IFDEF FMX}Round{$ENDIF}(Pen.Width) div 2);

  CalcTextAlign(Panel, x, Rect, tmp);

  x:=x+ITextOffset;

  tmpCenter.X:=(Rect.Left+Rect.Right) {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF};
  tmpCenter.Y:=(Rect.Top+Rect.Bottom) {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF};

  tmpClip:=ClipText and ((ITextOffset<>0) or ((not AutoSize) and (Angle=0)));

  if tmpClip then
     Panel.Canvas.ClipRectangle(Rect);

  if not Panel.Canvas.SupportsFullRotation then
     AZ:=0; // Only OpenGL needs Z

  try
    for t:=1 to INumLines do
    begin
      tmpS:=TeeExtractField(tmp,t,TeeLineSeparator);

      if tmpS<>'' then
         DrawString(Panel,x,
                    tmpTop,
                    AZ,
                    tmpS,
                    TextFormat=ttfHtml,
                    Angle,
                    tmpCenter);

      if INumLines>1 then
      begin
        if TextFormat=ttfHtml then
           Inc(tmpTop,{$IFDEF FMX}Round{$ENDIF}(Panel.MultiLineTextSize(tmpS,TextFormat,tmpNumlines).Y))
        else
        begin
          if tmpHeight=0 then
             tmpHeight:=Panel.Canvas.FontHeight;

          Inc(tmpTop,tmpHeight);
        end;
      end;
    end;

  finally
    if tmpClip then
       Panel.Canvas.UnClipRectangle;
  end;
end;

Procedure TCustomTextShape.DoDraw(const Panel:TCustomTeePanel; const Points:TPointArray;
                                  const Rect : TRect; Angle:Integer=0; AZ:Integer=0);
begin
  inherited;
  DoDrawText(Panel,Rect,Angle,AZ);
end;

procedure TCustomTextShape.CalcTextAlign(const Panel: TCustomTeePanel;
                                out X: TCoordinate;
                                const R: TRect; AText:String);
begin
  case TextAlignment of
    taLeftJustify:
    begin
      Panel.Canvas.TextAlign:=TA_LEFT;
      x:=R.Left+Margins.Size.Left;
    end;

    taCenter:
    begin
      Panel.Canvas.TextAlign:=TA_CENTER;
      x:=((R.Left+R.Right) {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF});
      {$IFDEF FPCLINUX}
      x:=x-Panel.Canvas.TextWidth(AText) div 2;
      {$ENDIF}
    end;
  else
    begin
      Panel.Canvas.TextAlign:=TA_RIGHT;
      x:=R.Right-Margins.Size.Right;
      {$IFDEF FPCLINUX}
      x:=x-Panel.Canvas.TextWidth(AText);
      {$ENDIF}
    end;
  end;
end;

procedure TCustomTextShape.SetMargins(const Value: TMargins);
begin
  FMargins.Assign(Value);
end;

function TCustomTextShape.IsTextAlignStored:Boolean;
begin
  result:=FTextAlign<>IDefaultTextAlign;
end;

procedure TCustomTextShape.SetVertTextAlign(const Value: TVertTextAlign);
begin
  if FVertTextAlign<>Value then
  begin
    FVertTextAlign:=Value;
    Repaint;
  end;
end;

{ TSeriesMarkShape }

Procedure TSeriesMarkShape.SetStyle(const Value:TSeriesMarksStyle);
begin
  if FMarkerStyle<>Value then
  begin
    FMarkerStyle:=Value;

    if Assigned(ParentChart) then
       ParentChart.Invalidate
    else
    if Assigned(IParent) then
       IParent.ParentSeries.Repaint;
  end;
end;

{ TAutomaticText }

Constructor TAutomaticText.Create(const AOnChange:TNotifyEvent);
begin
  inherited Create;

  FHide:=True;
  FMove:=True;
  FResize:=True;
  FRotate:=True;
  FTrim:=True;
  FWrap:=True;

  IOnChange:=AOnChange;
end;

procedure TAutomaticText.SetBooleanProperty(var Variable:Boolean; const Value:Boolean);
begin
  if Variable<>Value then
  begin
    Variable:=Value;
    IOnChange(Self);
  end;
end;

procedure TAutomaticText.SetHide(const Value:Boolean);
begin
  SetBooleanProperty(FHide,Value);
end;

procedure TAutomaticText.SetMove(const Value:Boolean);
begin
  SetBooleanProperty(FMove,Value);
end;

procedure TAutomaticText.SetResize(const Value:Boolean);
begin
  SetBooleanProperty(FResize,Value);
end;

procedure TAutomaticText.SetRotate(const Value:Boolean);
begin
  SetBooleanProperty(FRotate,Value);
end;

procedure TAutomaticText.SetTrim(const Value:Boolean);
begin
  SetBooleanProperty(FTrim,Value);
end;

procedure TAutomaticText.SetWrap(const Value:Boolean);
begin
  SetBooleanProperty(FWrap,Value);
end;

procedure TAutomaticText.Assign(Source:TPersistent);
begin
  if Source is TAutomaticText then
  begin
    FHide:=TAutomaticText(Source).FHide;
    FMove:=TAutomaticText(Source).FMove;
    FResize:=TAutomaticText(Source).FResize;
    FRotate:=TAutomaticText(Source).FRotate;
    FTrim:=TAutomaticText(Source).FTrim;
    FWrap:=TAutomaticText(Source).FWrap;
  end
  else
    inherited;
end;

{ TMarksTail }

Constructor TMarksTail.Create(const AOwner: TChartSeries);
begin
  inherited Create;

  ISeries:=AOwner;

  FHeight:=8;
  FWidth:=8;
  FAutoAlign:=True;

  FAlign:=ttaCenter;
  FEdge :=tteBottom;
end;

procedure TMarksTail.Assign(Source:TPersistent);
begin
  if Source is TMarksTail then
  begin
    FAutoAlign:=TMarksTail(Source).FAutoAlign;
    //FCustomPoint:=TMarksTail(Source).FCustomPoint;
    FCustomPointPos:=TMarksTail(Source).FCustomPointPos;
    FHeight:=TMarksTail(Source).FHeight;
    FMargin:=TMarksTail(Source).FMargin;
    FAlign:=TMarksTail(Source).FAlign;
    FVisible:=TMarksTail(Source).FVisible;
    FWidth:=TMarksTail(Source).FWidth;
  end
  else
    inherited;
end;

function TMarksTail.GetPoints(const Align:TTailAlign; const R:TRect):TPointArray;
var pointerCenterX,
    pointerCenterY,
    XEdge,
    YEdge,
    rfWidth,
    rfHeight : TCoordinate;

    tmpHalfW : TCoordinate;
Begin
  SetLength(result,7);

  rfWidth:=R.Right-R.Left;
  rfHeight:=R.Bottom-R.Top;

  PointerCenterX:=R.Left;
  PointerCenterY:=R.Top;

  tmpHalfW:=FWidth{$IFDEF FMX}*0.5{$ELSE} div 2{$ENDIF};

  XEdge:=R.Left;
  YEdge:=R.Top;

  //non pointer Edge
  if FEdge = tteBottom then
     YEdge :=R.Bottom
  else
  if FEdge = tteTop then
     YEdge :=R.Top
  else
  if FEdge = tteLeft then
     XEdge :=R.Left
  else
  if FEdge = tteRight then
     XEdge :=R.Right;

  //pointer Edge
  if (align = ttaNear) and ((FEdge = tteBottom) or (FEdge = tteTop)) then
    pointerCenterX :=R.Left + tmpHalfW
  else
  if (align = ttaCenter) and ((FEdge = tteBottom) or (FEdge = tteTop)) then
     pointerCenterX :=R.Left + (rfWidth {$IFDEF FMX}*0.5{$ELSE} div 2{$ENDIF})
  else
  if (align = ttaFar) and ((FEdge = tteBottom) or (FEdge = tteTop))  then
     pointerCenterX :=R.Left + rfWidth - tmpHalfW
  else
  if (align = ttaNear) and ((FEdge = tteLeft) or (FEdge = tteRight))  then
     pointerCenterY :=R.Top + tmpHalfW
  else
  if (align = ttaCenter) and ((FEdge = tteLeft) or (FEdge = tteRight)) then
     pointerCenterY :=R.Top + (rfHeight {$IFDEF FMX}*0.5{$ELSE} div 2{$ENDIF})
  else
  if (align = ttaFar) and ((FEdge = tteLeft) or (FEdge = tteRight)) then
     pointerCenterY :=R.Top + rfHeight - tmpHalfW;

  if (FEdge = tteBottom) or (FEdge = tteTop) then
  Begin
    result[0].X := R.Left;
    result[0].Y := R.Top;
    result[1].X := R.Left;
    result[1].Y := R.Bottom;

    if FEdge = tteBottom then
    Begin
      result[2].X := pointerCenterX - tmpHalfW;
      result[2].Y := YEdge;

      if FCustomPoint then
         result[3]:=FCustomPointPos
      else
      begin
        result[3].X := pointerCenterX;
        result[3].Y := YEdge + FHeight;
      end;

      result[4].X := pointerCenterX + tmpHalfW;
      result[4].Y := YEdge;
      result[5].X := R.Right;
      result[5].Y := YEdge;
      result[6].X := R.Right;
      result[6].Y := R.Top;
    end;

    if FEdge = tteTop then
    Begin
      result[2].X := R.Right;
      result[2].Y := R.Bottom;
      result[3].X := R.Right;
      result[3].Y := YEdge;
      result[4].X := pointerCenterX + tmpHalfW;
      result[4].Y := YEdge;

      if FCustomPoint then
         result[5]:=FCustomPointPos
      else
      begin
        result[5].X := pointerCenterX;
        result[5].Y := YEdge - FHeight;
      end;

      result[6].X := pointerCenterX - tmpHalfW;
      result[6].Y := YEdge;
    end;
  end
  else
  if (FEdge = tteLeft) or (FEdge = tteRight) then
  Begin
    result[0].X := R.Left;
    result[0].Y := R.Top;

    if FEdge = tteLeft then
    Begin
      result[1].X := XEdge;
      result[1].Y := pointerCenterY - tmpHalfW;

      if FCustomPoint then
         result[2]:=FCustomPointPos
      else
      begin
        result[2].X := XEdge - FHeight;
        result[2].Y := pointerCenterY;
      end;

      result[3].X := XEdge;
      result[3].Y := pointerCenterY + tmpHalfW;
      result[4].X := XEdge;
      result[4].Y := R.Bottom;
      result[5].X := R.Right;
      result[5].Y := R.Bottom;
      result[6].X := R.Right;
      result[6].Y := R.Top;
    end;

    if FEdge = tteRight then
    Begin
      result[1].X := R.Left;
      result[1].Y := R.Bottom;
      result[2].X := XEdge;
      result[2].Y := R.Bottom;
      result[3].X := XEdge;
      result[3].Y := pointerCenterY + tmpHalfW;

      if FCustomPoint then
         result[4]:=FCustomPointPos
      else
      begin
        result[4].X := XEdge + FHeight;
        result[4].Y := pointerCenterY;
      end;

      result[5].X := XEdge;
      result[5].Y := pointerCenterY - tmpHalfW;
      result[6].X := XEdge;
      result[6].Y := R.Top;
    end
  end;
end;

function TMarksTail.GetPoints(const R:TRect; const AFrom,ATo:TPoint):TPointArray;
begin
  if FAutoAlign and (not FCustomPoint) then
  begin
    if ISeries.YMandatory then
       if AFrom.Y>=ATo.Y then
          Edge := tteBottom
       else
          Edge := tteTop
    else
       // 'horizontal' series
       if AFrom.X<=ATo.X then
          Edge := tteLeft
       else
          Edge := tteRight;

    result:=GetPoints(ttaCenter,R);
  end
  else
    result:=GetPoints(FAlign,R);
end;

procedure TMarksTail.SetCustomPoint(Value: Boolean);
begin
  ISeries.SetBooleanProperty(FCustomPoint, Value);
end;

procedure TMarksTail.SetCustomPointPos(Value: TPoint);
begin
  FCustomPointPos:=Value;
  ISeries.Repaint;
end;

procedure TMarksTail.SetAlign(const Value : TTailAlign);
begin
  if FAlign <> Value then
  begin
    FAlign:=Value;
    ISeries.Repaint;
  end;
end;

procedure TMarksTail.SetAutoAlign(Value : Boolean);
Begin
  if FAutoAlign <> Value then
  begin
    FAutoAlign:=Value;
    ISeries.Repaint;
  end;
end;

procedure TMarksTail.SetHeight(Value: Integer);
begin
  ISeries.SetIntegerProperty(FHeight,Value);
end;

procedure TMarksTail.SetVisible(Value: Boolean);
begin
  ISeries.SetBooleanProperty(FVisible,Value);
end;

procedure TMarksTail.SetWidth(Value: Integer);
begin
  ISeries.SetIntegerProperty(FWidth,Value);
end;

procedure TMarksTail.SetEdge(Value : TTailEdge);
Begin
  if FEdge <> Value then
  begin
    FEdge:=Value;
    ISeries.Repaint;
  end;
end;

{ TSeriesMarks }

Constructor TSeriesMarks.Create(AOwner:TChartSeries);
Begin
  inherited Create(nil);

  IParent:=Self;

  FSeries:=AOwner;
  FMarkerStyle:=smsLabelOrValue;

  FAutomatic:=TAutomaticText.Create(CanvasChanged);

  FDrawEvery:=1;

  FPositions:=TSeriesMarksPositions.Create;
  FPositions.IMarks:=Self;

  FItems:=TMarksItems.Create;
  FItems.IMarks:=Self;

  FTextAlign:=taCenter;

  FMargins.SetDefaultHoriz(20);

  InitGradient(Gradient);

  { inherited }
  {$IFNDEF IOS}
  {$IFNDEF ANDROID}
  RoundSize:=4;
  {$ELSE}
  RoundSize:=0; // Speed penalty with FMX GPU Canvas for RoundRect FillPath
  {$ENDIF}
  {$ENDIF}

  FUseImages:=miSymbol;
  FUseSeriesTransparency:=True;

  Color:=ChartMarkColor;
  Visible:=False;
end;

Destructor TSeriesMarks.Destroy;
begin
  FAutomatic.Free;
  FSymbol.Free;
  FCallout.Free;
  FPositions.Free;
  FTail.Free;

  FItems.IMarks:=nil;
  FItems.Free;

  inherited;
end;

type
  TGradientAccess=class(TTeeGradient);

class procedure TSeriesMarks.InitGradient(const AGradient:TTeeGradient);
begin
  with TGradientAccess(AGradient) do
  begin
    InitDirection(gdRightLeft);
    IDefEnd:=clWhite;
    IDefStart:={$IFDEF FMX}TAlphaColors.Silver{$ELSE}clSilver{$ENDIF};
  end;
end;

Procedure TSeriesMarks.Assign(Source:TPersistent);
var t : Integer;
Begin
  if Source is TSeriesMarks then
  With TSeriesMarks(Source) do
  Begin
    Self.FAngle      :=FAngle;
    Self.Automatic   :=FAutomatic;

    // Not. This is series dependent: FAutoPosition

    Self.Callout     :=FCallout;
    Self.FClip       :=FClip;
    Self.FDrawEvery  :=FDrawEvery;
    Self.FMarkerStyle:=FMarkerStyle;
    Self.FMultiLine  :=FMultiLine;
    Self.FOnTop      :=FOnTop;
    Self.FSoftClip   :=FSoftClip;
    Self.Symbol      :=FSymbol;
    Self.Tail        :=FTail;
    Self.FUseImages  :=FUseImages;
    Self.FUseSeriesTransparency:=FUseSeriesTransparency;

    Self.Items.Clear;
    for t:=0 to Items.Count-1 do
        Self.Items[t].Assign(Items[t]);
  end;

  inherited;
end;

Function TSeriesMarks.GetBackColor:TColor;
begin
  result:=Color;
end;

function TSeriesMarks.GetItem(Index:Integer):TMarksItem;
begin
  result:=FItems[Index];
end;

Procedure TSeriesMarks.SetBackColor(Value:TColor);
begin
  Color:=Value;
end;

Procedure TSeriesMarks.SetAutomatic(const Value:TAutomaticText);
begin
  FAutomatic.Assign(Value);
end;

Procedure TSeriesMarks.SetAutoPosition(Value:Boolean);
begin
  FAutomatic.Move:=Value;
end;

Procedure TSeriesMarks.ResetPositions;
var t : Integer;
begin
  for t:=0 to Positions.Count-1 do Positions.Position[t].Custom:=False;
  ParentSeries.Repaint;
end;

Function TSeriesMarks.GetCallout:TMarksCallout;
var Old : Boolean;
begin
  if not Assigned(FCallout) then
  begin
    if Assigned(ParentChart) then
    begin
      Old:=ParentChart.AutoRepaint;
      ParentChart.AutoRepaint:=False;

      FCallout:=TMarksCallout.Create(FSeries);
      FCallout.ParentChart:=ParentChart;

      ParentChart.AutoRepaint:=Old;
    end
    else
      FCallout:=TMarksCallout.Create(FSeries);
  end;

  result:=FCallout;
end;

Procedure TSeriesMarks.SetCallout(const Value:TMarksCallout);
begin
  if Assigned(Value) then
     Callout.Assign(Value)
  else
     FreeAndNil(FCallout);
end;

Procedure TSeriesMarks.SetArrowLength(Value:Integer);
Begin
  Callout.Length:=Value;
end;

Procedure TSeriesMarks.SetDrawEvery(Value:Integer);
begin
  ParentSeries.SetIntegerProperty(FDrawEvery,Value);
end;

Procedure TSeriesMarks.SetAngle(Value:Integer);
begin
  ParentSeries.SetIntegerProperty(FAngle,Value);
end;

procedure TSeriesMarks.SetFontSeriesColor(const Value: Boolean);
begin
  ParentSeries.SetBooleanProperty(FFontSeriesColor,Value);
end;

procedure TSeriesMarks.SetOnTop(const Value: Boolean);
begin
  ParentSeries.SetBooleanProperty(FOnTop,Value);
end;

procedure TSeriesMarks.SetSoftClip(const Value: Boolean);
begin
  ParentSeries.SetBooleanProperty(FSoftClip,Value);
end;

procedure TSeriesMarks.SetUseSeriesTransparency(const Value: Boolean);
Begin
  ParentSeries.SetBooleanProperty(FUseSeriesTransparency,Value);
end;

Procedure TSeriesMarks.SetMultiLine(Value:Boolean);
Begin
  ParentSeries.SetBooleanProperty(FMultiLine,Value);
end;

// Calculates the minimum "Draw Every" step, when there are more marks to
// display than available pixels
function TSeriesMarks.CalcEvery:Integer;
var tmp : Integer;
    tmpNum : Integer;
begin
  if ParentSeries.CalcVisiblePoints then
  begin
    tmpNum:=ParentSeries.FLastVisibleIndex-ParentSeries.FFirstVisibleIndex+1;

    if ParentChart=nil then
       tmp:=0
    else
    if ParentSeries.MandatoryAxis.Horizontal then
       tmp:=ParentChart.ChartHeight div 2
    else
       tmp:=ParentChart.ChartWidth div 2;

    if (tmp>0) and (tmpNum>tmp) then
       result:=Max(FDrawEvery,Round(tmpNum/tmp))
    else
       result:=FDrawEvery; // <-- use default step (1)
  end
  else
    result:=FDrawEvery;
end;

procedure TSeriesMarks.Delete(const ValueIndex:Integer);
begin
  if FPositions.Count>ValueIndex then
  begin
    {$IFNDEF D18}
    FPositions[ValueIndex].Free;
    {$ENDIF}

    FPositions.Delete(ValueIndex);
  end;

  if FItems.Count>ValueIndex then
  begin
    {$IFNDEF D18}
    FItems[ValueIndex].Free;
    {$ENDIF}

    FItems.Delete(ValueIndex);
  end;
end;

Function TSeriesMarks.Clicked(X,Y:Integer):Integer;
begin
  result:=Clicked(TeePoint(X,Y));

  if result<>-1 then
     ParentSeries.IClickedPart:=scpMark;
end;

Function TSeriesMarks.Clicked(const P:TPoint):Integer;
var tmp : TSeriesMarkPosition;
    R   : TRect;
    tmpEvery : Integer;
begin
  tmpEvery:=CalcEvery;

  with FPositions do
  for result:=Count-1 downto 0 do  // LastVisible downto FirstVisible
      if (result mod tmpEvery)=0 then
      begin
        tmp:=FPositions.Position[result];

        if Assigned(tmp) then
        begin
          if (result<FItems.Count) and not FItems[result].Visible then
             Continue;

          R.TopLeft:=ConvertTo2D(tmp.ArrowTo,tmp.LeftTop);
          R.Right:=R.Left+tmp.Width;
          R.Bottom:=R.Top+tmp.Height;

          if ClickedShape(P,R) then
          begin
            ParentSeries.IClickedPart:=scpMark;
            Exit;
          end;
        end;
      end;

  result:=TeeNoPointClicked;
end;

Procedure TSeriesMarks.UsePosition( Index:Integer; var MarkPosition:TSeriesMarkPosition);
var tmp : TSeriesMarkPosition;
    Old : TPoint;
begin
  with FPositions do
  begin
    while Index>=Count do Add(nil);

    if Items[Index]=nil then
    begin
      tmp:=TSeriesMarkPosition.Create;
      tmp.Custom:=False;
      Items[Index]:=tmp;
    end;

    tmp:=Position[Index];

    if tmp.Custom then
    begin
      if tmp.ArrowFix or MarkPosition.ArrowFix then
         Old:=MarkPosition.ArrowFrom;

      MarkPosition.Assign(tmp);

      if tmp.ArrowFix or MarkPosition.ArrowFix then
         MarkPosition.ArrowFrom:=Old;

      MarkPosition.Custom:=False;
    end
    else
      tmp.Assign(MarkPosition);
  end;
end;

Procedure TSeriesMarks.ApplyArrowLength(const APosition:TSeriesMarkPosition);
begin
  if Assigned(FCallout) then
     FCallout.ApplyArrowLength(APosition);
end;

Procedure TSeriesMarks.SetClip(Value:Boolean);
Begin
  ParentSeries.SetBooleanProperty(FClip,Value);
end;

function TSeriesMarks.ConvertTo2D(const Point,P:TPoint):TPoint;
var tmpDifX   : TCoordinate;
    tmpDifY   : TCoordinate;
    tmpPos2D  : TPoint;
begin
  if ParentChart.View3D and (not ParentChart.Canvas.Supports3DText) then
  begin
    tmpDifX:=Point.X-P.X;
    tmpDifY:=Point.Y-P.Y;
    tmpPos2D:=ParentChart.Canvas.Calculate3DPosition(Point,ZPosition);

    result.X:=tmpPos2D.X-tmpDifX;
    result.Y:=tmpPos2D.Y-tmpDifY;
  end
  else
    result:=P;
end;

Procedure TSeriesMarks.DrawItem(const Shape:TTeeCustomShape; AColor:TColor;
                                const Text:String; const APosition:TSeriesMarkPosition);

  procedure TryDrawCallout;
  var tmpBlend  : TTeeBlend;
  begin
    if Callout.Visible or Callout.Arrow.Visible then
    begin
      if Callout.Transparency>0 then
         if ParentChart.Canvas.SupportsAlpha then
            tmpBlend:=ParentChart.Canvas.BeginBlending(TeeZeroRect,Callout.Transparency)
         else
            tmpBlend:=ParentChart.Canvas.BeginBlending(TeeRect(APosition.ArrowFrom.X,
                                           APosition.ArrowFrom.Y,
                                           APosition.ArrowTo.X,
                                           APosition.ArrowTo.Y),
                                           Callout.Transparency)
      else
         tmpBlend:=nil;

      Callout.Draw(AColor,APosition.ArrowFrom,APosition.MidPoint,
                          APosition.ArrowTo,ZPosition,APosition.HasMid);

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

var tmpBounds : TRect;
    tmpH      : Integer;
    tmpSymbol : Boolean;
    tmpPenW   : Integer;
    OldC      : TColor;
begin
  if Assigned(FCallout) then
     TryDrawCallout;

  APosition.LeftTop:=ConvertTo2D(APosition.ArrowTo,APosition.LeftTop);

  tmpBounds.TopLeft:=APosition.LeftTop;
  tmpBounds.Right:=tmpBounds.Left+APosition.Width;
  tmpBounds.Bottom:=tmpBounds.Top+APosition.Height;

  if APosition.Custom then
     OffsetRect(tmpBounds,ParentChart.ChartBounds.Left,
                          ParentChart.ChartBounds.Top);

  Shape.Shadow.Clip:=Clip;
  Shape.Emboss.Clip:=Clip;

  OldC:=Shape.Font.Color;

  if FontSeriesColor then
     Shape.Font.Color:=AColor;

  Shape.ShapeBounds:=tmpBounds;

  if Assigned(FTail) and FTail.Visible then
     Shape.Draw(tmpBounds,FTail.GetPoints(tmpBounds,APosition.ArrowFrom,APosition.ArrowTo),
                FAngle mod 360,ZPosition)
  else
     Shape.Draw(tmpBounds,FAngle mod 360,ZPosition);

  Shape.Font.Color:=OldC;

  tmpSymbol:=Assigned(FSymbol) and FSymbol.ShouldDraw;

  if tmpSymbol then
  begin
    FSymbol.Color:=AColor;
    FSymbol.Transparent:=False;
    tmpH:=ParentChart.Canvas.FontHeight;

    tmpPenW:=Shape.Pen.HalfWidth+FSymbol.Pen.HalfWidth;

    FSymbol.ShapeBounds:=TeeRect( tmpBounds.Left+4+tmpPenW,
                                  tmpBounds.Top+3+tmpPenW,
                                  tmpBounds.Left+2*tmpPenW+tmpH-1,
                                  tmpBounds.Top+2*tmpPenW+tmpH-2);

    FSymbol.Draw(FSymbol.ShapeBounds,FAngle mod 360,ZPosition);
  end;
end;

Procedure TSeriesMarks.InternalDraw( Index:Integer; AColor:TColor;
                                     const AText:String; APosition:TSeriesMarkPosition);

  procedure DrawSymbolPicture(const APicture:TTeePicture; const AItem:TTeeCustomShape);
  var OldPen : Boolean;
      OldBrush : {$IFDEF FMX}TBrushKind{$ELSE}TBrushStyle{$ENDIF};
  begin
    Symbol.Picture.Graphic:=APicture.Filtered;
    OldPen:=Symbol.Pen.Visible;
    OldBrush:=Symbol.Brush.Style;

    Symbol.Pen.Hide;
    Symbol.Brush.Style:=bsClear;

    DrawItem(AItem,AColor,AText,APosition);

    Symbol.Pen.Visible:=OldPen;
    Symbol.Brush.Style:=OldBrush;
    Symbol.Picture.Assign(nil);
  end;

var tmp : TTeeSelected;
    tmpItem : TTeeCustomShape;
    tmpOld : TTeeFont;
    tmpPic : TTeePicture;
    tmpR : TRect;
begin
  UsePosition(Index,APosition);

  tmpItem:=MarkItem(Index);

  tmpOld:=nil;

  if ParentSeries.ShouldHoverMark(Index) then
  begin
    tmp:=ParentSeries.Selected;

    tmpOld:=TTeeFont.Create;
    tmpOld.Assign(tmpItem.Font);

    tmpItem.Font.Color:=tmp.Hover.Font.Color;
    tmpItem.Font.Style:=tmp.Hover.Font.Style;
    // Font.Size, etc ?
  end;

  tmpPic:=nil;

  if UseImages<>miNone then
     if FSeries.FPictures<>nil then
        tmpPic:=FSeries.FPictures[Index];

  if (tmpPic=nil) or (UseImages=miSymbol) then
  begin
    if (tmpPic<>nil) and Symbol.Visible then
       DrawSymbolPicture(tmpPic,tmpItem)
    else
       DrawItem(tmpItem,AColor,AText,APosition);
  end
  else
  begin
    tmpR:=APosition.Bounds;

    // Pending: Adjust tmpR on proportional fit between tmpPic size and
    // depending on series horizontal or vertical, tmpR width or height

    ParentChart.Canvas.StretchDraw(tmpR,tmpPic.Filtered);
  end;

  if Assigned(tmpOld) then
  begin
    tmpItem.Font:=tmpOld;
    tmpOld.Free;
  end;
end;

Function TSeriesMarks.MarkItem(ValueIndex:Integer):TTeeCustomShape;
begin
  if FItems.Count>ValueIndex then
  begin
    result:=FItems.InternalGet(ValueIndex);

    if not Assigned(result) then
       result:=Self;
  end
  else result:=Self;
end;

Function TSeriesMarks.TotalBounds(ValueIndex:Integer; const APosition:TSeriesMarkPosition):TRect;
var P : TPoint;
    tmp: TCoordinate;
begin
  result:=APosition.Bounds;

  with MarkItem(ValueIndex) do
  if not Transparent then
  begin
    if Pen.Visible then
    begin
      result.Right:=result.Right+Pen.Width;
      result.Bottom:=result.Bottom+Pen.Width;
    end;

    with Shadow do
    if Visible then
    begin
      if HorizSize>0 then result.Right:=result.Right+HorizSize
      else
      if HorizSize<0 then result.Left:=result.Left-HorizSize;

      if VertSize>0 then result.Bottom:=result.Bottom+VertSize
      else
      if VertSize<0 then result.Top:=result.Top-VertSize;
    end;

    if Assigned(FEmboss) then
    with FEmboss do
    if Visible then
    begin
      if HorizSize>0 then result.Left:=result.Left+HorizSize
      else
      if HorizSize<0 then result.Right:=result.Right-HorizSize;

      if VertSize>0 then result.Top:=result.Top+VertSize
      else
      if VertSize<0 then result.Bottom:=result.Bottom-VertSize;
    end;
  end;

  P:=result.TopLeft;
  ConvertTo2D(APosition.ArrowTo,P);

  tmp:=result.Left-P.X;
  result.Left:=result.Left-tmp;
  result.Right:=result.Right-tmp;

  tmp:=result.Top-P.Y;
  result.Top:=result.Top-tmp;
  result.Bottom:=result.Bottom-tmp;
end;

// If ValueIndex mark overlaps another previously painted mark, try to avoid the overlapping.
// If its not possible to avoid, return False to skip painting this ValueIndex mark.
function TSeriesMarks.AntiOverlap(First, ValueIndex:Integer; const APosition:TSeriesMarkPosition):Boolean;
var tmpBounds : TRect;
    t         : Integer;
    tmpR      : TRect;
    tmpP      : TPoint;
    tmpPos    : TSeriesMarkPosition;
begin
  result:=True;

  if ValueIndex>First then
  begin
    tmpBounds:=TotalBounds(ValueIndex,APosition);

    for t:=ValueIndex-1 downto First do
    begin
      tmpPos:=Positions[t];

      if tmpPos<>nil then
      begin
        tmpR:=TotalBounds(t,tmpPos);

        // Overlaps?
        if TeeRectIntersects(tmpR,tmpBounds) then
        begin
          tmpP:=ParentSeries.MarkOverlap(ValueIndex,tmpBounds,tmpR);

          APosition.Offset(tmpP);

          tmpBounds.Left:=tmpBounds.Left+tmpP.X;
          tmpBounds.Top:=tmpBounds.Top+tmpP.Y;

          // Check again overlapping:

          if TeeRectIntersects(tmpR,tmpBounds) then // overlaps again ?
          begin
            result:=False;
            Exit;
          end;
        end;
      end;
    end;
  end;
end;

{ Returns the length in pixels for the ValueIndex th mark text.
  It checks if the Mark has multiple lines of text. }
function TSeriesMarks.TextWidth(ValueIndex: Integer): Integer;
var tmpNum : Integer;
begin
  result:=ParentChart.MultiLineTextWidth(ParentSeries.GetMarkText(ValueIndex),tmpNum,TextFormat);
end;

function TSeriesMarks.GetArrowLength: Integer;
begin
  result:=Callout.Length;
end;

function TSeriesMarks.GetAutoPosition:Boolean;
begin
  result:=FAutomatic.Move;
end;

function TSeriesMarks.GetArrowPen: TChartArrowPen;
begin
  result:=Callout.Arrow;
end;

procedure TSeriesMarks.SetArrowPen(const Value: TChartArrowPen);
begin
  Callout.Arrow:=Value;
end;

procedure TSeriesMarks.Clear;
begin
  Positions.Clear;

  if (not FItems.ILoadingCustom) and (FItems.Count>0) then
     FItems.Clear;
end;

procedure TSeriesMarks.InitShadow(const AShadow:TTeeShadow);
begin
  TShadowAccess(AShadow).InitValues(AShadow.Visible,
        {$IFDEF FMX}TAlphaColors.Gray{$ELSE}clGray{$ENDIF}, 3);
end;

procedure TSeriesMarks.InitEmboss(const AEmboss:TTeeEmboss);
begin
  TEmbossAccess(AEmboss).InitValues(AEmboss.Visible,
        {$IFDEF FMX}TAlphaColors.Gray{$ELSE}clGray{$ENDIF}, 3);
end;

type
  TReaderAccess=class(TReader);

{ internal. Loads series marks items from the DFM stream }
procedure TSeriesMarks.ReadItems(Stream: TStream);
var tmpCount : Integer;
    t        : Integer;
    Reader   : TReader;
    tmp      : TMarksItem;
begin
  Stream.Read(tmpCount,SizeOf(tmpCount));

  FItems.ILoadingCustom:=tmpCount>0;

  if FItems.ILoadingCustom then
  begin
    Reader:=TReader.Create(Stream,1024);

    for t:=0 to tmpCount-1 do
    begin
      tmp:=Items[t];
      Reader.ReadListBegin;

      while not Reader.EndOfList do
            TReaderAccess(Reader).ReadProperty(tmp);

      Reader.ReadListEnd;
    end;

    Reader.Free;
  end;
end;

type
  TWriterAccess=class(TWriter);

{ internal. stores series marks items into the DFM stream }
procedure TSeriesMarks.WriteItems(Stream: TStream);
var tmpCount : Integer;
    t        : Integer;
    Writer   : TWriter;
begin
  tmpCount:=Items.Count;
  Stream.Write(tmpCount,SizeOf(tmpCount));

  Writer:=TWriter.Create(Stream,1024);

  for t:=0 to tmpCount-1 do
  begin
    Writer.WriteListBegin;

    if Assigned(Items[t]) then
       TWriterAccess(Writer).WriteProperties(Items[t]);

    Writer.WriteListEnd;
  end;

  Writer.Free;
end;

procedure TSeriesMarks.DefineProperties(Filer: TFiler);
begin
  inherited;
  Filer.DefineBinaryProperty('Items',ReadItems,WriteItems,Items.Count>0);
end;

function TSeriesMarks.GetChildClass:TTeeCustomShapeClass;
begin
  result:=TSeriesMarkShape;
end;

// Compatibility:
function TSeriesMarks.GetTextAlign:TAlignment;
begin
  result:=TextAlignment;
end;

Function TSeriesMarks.GetSymbol:TSeriesMarksSymbol;
begin
  if not Assigned(FSymbol) then
     FSymbol:=TSeriesMarksSymbol.Create(ParentChart);

  result:=FSymbol;
end;

procedure TSeriesMarks.SetSymbol(const Value: TSeriesMarksSymbol);
begin
  if Assigned(Value) then
     Symbol.Assign(Value)
  else
     FreeAndNil(FSymbol);
end;

procedure TSeriesMarks.SetTail(const Value: TMarksTail);
begin
  if Assigned(Value) then
     Tail.Assign(Value)
  else
     FreeAndNil(FTail);
end;

// Compatibility:
procedure TSeriesMarks.SetTextAlign(const Value: TAlignment);
begin
  TextAlignment:=Value;
end;

function TSeriesMarks.GetTail : TMarksTail;
Begin
  if FTail=nil then
     FTail:=TMarksTail.Create(ParentSeries);

  result:=FTail;
end;

procedure TSeriesMarks.SetUseImages(const Value: TSeriesMarksImages);
begin
  if FUseImages<>Value then
  begin
    FUseImages:=Value;
    Repaint;
  end;
end;

procedure TSeriesMarks.SetParent(Value: TCustomTeePanel);
begin
  inherited;

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

{ TTeeFunction }

Constructor TTeeFunction.Create(AOwner: TComponent);
begin
  inherited;

  CanUsePeriod :=True;
  FIncludeNulls:=True;
  FPeriodAlign :=paCenter;
  FPeriodStyle :=psNumPoints;
end;

Procedure TTeeFunction.BeginUpdate;
begin
  IUpdating:=True;
end;

Procedure TTeeFunction.EndUpdate;
begin
  if IUpdating then
  begin
    IUpdating:=False;
    Recalculate;
  end;
end;

Procedure TTeeFunction.Recalculate;
begin
  if (not IUpdating) and Assigned(FParent) then FParent.CheckDataSource;
end;

procedure TTeeFunction.SplitAxes;
var tmp : TChartAxes;
begin
  ParentSeries.VertAxis:=aRightAxis;

  tmp:=ParentSeries.ParentChart.Axes;

  tmp.Left.EndPosition:=70;
  tmp.Right.StartPosition:=70;
end;

Function TTeeFunction.ValueList(const ASeries:TChartSeries):TChartValueList;
var tmp : String;
begin
  if Assigned(ParentSeries) then tmp:=ParentSeries.MandatoryValueList.ValueSource
                            else tmp:='';

  With ASeries do
  if tmp='' then result:=MandatoryValueList
            else result:=GetYValueList(tmp);
end;

Procedure TTeeFunction.CalculateAllPoints( Source:TChartSeries;
                                           NotMandatorySource:TChartValueList);
var tmpX : Double;
    tmpY : Double;
begin
  with ParentSeries do
  begin
    tmpY:=Calculate(Source,TeeAllValues,TeeAllValues);

    if not AllowSinglePoint then
    begin
      tmpX:=NotMandatorySource.MinValue;
      AddFunctionXY(Source.YMandatory,tmpX,tmpY);
      tmpX:=NotMandatorySource.MaxValue;
      AddFunctionXY(Source.YMandatory,tmpX,tmpY);
    end
    else { centered point }
    if (not Source.YMandatory) and YMandatory then
    begin
      With NotMandatorySource do tmpX:=MinValue+0.5*Range;
      AddXY(tmpX,tmpY);
    end
    else
    begin
      With NotMandatorySource do tmpX:=MinValue+0.5*Range;

      if YMandatory then
         AddFunctionXY(Source.YMandatory,tmpX,tmpY)
      else
         AddXY(tmpY,tmpX)
    end;
  end;
end;

Procedure TTeeFunction.CalculatePeriod( Source:TChartSeries;
                                        Const tmpX:Double;
                                        FirstIndex,LastIndex:Integer);
begin
  AddFunctionXY( Source.YMandatory, tmpX, Calculate(Source,FirstIndex,LastIndex) );
end;

Procedure TTeeFunction.CalculateByPeriod( Source:TChartSeries;
                                          NotMandatorySource:TChartValueList);
var tmpX     : Double;
    tmpCount : Integer;
    tmpFirst : Integer;
    tmpLast  : Integer;
    tmpBreakPeriod:TAxisValue;
    PosFirst : Double;
    PosLast  : Double;
    tmpStep  : TDateTimeStep;
    tmpCalc  : Boolean;
    tmpIsDateTime : Boolean;
    tmpSkipBreak  : Boolean;
begin
  tmpStep:=FindDateTimeStep(FPeriod);

  tmpFirst:=0;

  with ParentSeries do
  begin
    tmpCount:=Source.Count;
    tmpBreakPeriod:=NotMandatorySource.Value[tmpFirst];

    if GetHorizAxis=nil then
       tmpIsDateTime:=False
    else
       tmpIsDateTime:=GetHorizAxis.CalcIsDateTime(tmpStep);

    Repeat
      PosLast:=0;

      if FPeriodStyle=psNumPoints then
      begin
        tmpLast:=tmpFirst+Round(FPeriod)-1;

        With NotMandatorySource do
        begin
          PosFirst:=Value[tmpFirst];
          if tmpLast<tmpCount then PosLast:=Value[tmpLast];
        end;
      end
      else
      begin
        tmpLast:=tmpFirst;
        PosFirst:=tmpBreakPeriod;

        if tmpIsDateTime then
           tmpSkipBreak:=GetHorizAxis.IncDec(True,tmpBreakPeriod,FPeriod,tmpStep)
        else
           tmpSkipBreak:=GetHorizAxis.IncDec(True,tmpBreakPeriod,FPeriod);

        if tmpSkipBreak then
        begin
          PosLast:=tmpBreakPeriod-(FPeriod*0.001);

          While tmpLast<(tmpCount-1) do
            if NotMandatorySource.Value[tmpLast+1]<tmpBreakPeriod then Inc(tmpLast)
                                                                  else break;
        end;
      end;

      tmpCalc:=False;

      if tmpLast<tmpCount then
      begin
        { align periods }
        if FPeriodAlign=paFirst then tmpX:=PosFirst else
        if FPeriodAlign=paLast then tmpX:=PosLast else
                                    tmpX:=(PosFirst+PosLast)*0.5;

        if (FPeriodStyle=psRange) and (NotMandatorySource.Value[tmpFirst]<tmpBreakPeriod) then
           tmpCalc:=True;

        if (FPeriodStyle=psNumPoints) or tmpCalc then
           CalculatePeriod(Source,tmpX,tmpFirst,tmpLast)
        else
           AddFunctionXY( Source.YMandatory, tmpX, 0 );
      end;

      if (FPeriodStyle=psNumPoints) or tmpCalc then
         tmpFirst:=tmpLast+1;

    until tmpFirst>tmpCount-1;
  end;
end;

procedure TTeeFunction.Clear;
begin  // does nothing, see Bollinger function override
end;

Procedure TTeeFunction.AddFunctionXY(YMandatorySource:Boolean; const tmpX,tmpY:Double);
begin
  if YMandatorySource then ParentSeries.AddXY(tmpX,tmpY)
                      else ParentSeries.AddXY(tmpY,tmpX)
end;

procedure TTeeFunction.AddPoints(Source:TChartSeries);

    Procedure CalculateFunctionMany;
    var t        : Integer;
        tmpX     : Double;
        tmpY     : Double;
        XList    : TChartValueList;
    begin
      XList:=Source.NotMandatoryValueList;

      // Find datasource with bigger number of points... 5.02
      with ParentSeries do
      for t:=0 to DataSources.Count-1 do
      if Assigned(DataSources[t]) and
         (TChartSeries(DataSources[t]).Count>Source.Count) then
      begin
        Source:=TChartSeries(DataSources[t]);
        XList:=Source.NotMandatoryValueList;
      end;

      // use source to calculate points...
      if Assigned(XList) then
      for t:=0 to Source.Count-1 do
      begin
        tmpX:=XList.Value[t];
        tmpY:=CalculateMany(ParentSeries.DataSources,t);

        if not Source.YMandatory then
           SwapDouble(tmpX,tmpY);

        ParentSeries.AddXY( tmpX,tmpY,Source.Labels[t]);
      end;
    end;

    Procedure CalculateFunctionManyByValue;

      function MaxCountSources:Integer;
      var t: Integer;
          Source  : TChartSeries;
      begin
        Result:=0;

        for t:=0 to ParentSeries.DataSources.Count-1 do
        begin
          Source:=TChartSeries(ParentSeries.DataSources[t]);
          Result:=Max(Result,Source.Count);
        end;
      end;

      function CommonX(const X: Double):Boolean;
      var t : Integer;
      begin
        for t:=0 to ParentSeries.DataSources.Count-1 do
        begin
          if TChartSeries(ParentSeries.DataSources[t]).NotMandatoryValueList.Locate(X)<0 then
          begin
            Result:=False;
            Exit;
          end;
        end;

        Result:=True;
      end;

    var t,i       : Integer;
        k         : Integer;
        MaxCount  : Integer;
        Source    : TChartSeries;
        XList     : TChartValueList;
        tmpList   : TChartValueList;
        tmpLabels : TStringList;
        tmpX,tmpY : Double;
        tmpLabel  : String;
    begin
      MaxCount:=MaxCountSources;
      tmpList:=TChartValueList.Create(nil,'');
      tmpLabels:=TStringList.Create;
      try
        for t:=0 to MaxCount-1 do
        begin
          //Get and sort value[t] from all datasources
          tmpList.ClearValues;
          tmpLabels.Clear;

          for i:=0 to ParentSeries.DataSources.Count-1 do
          begin
            Source:=TChartSeries(ParentSeries.DataSources[i]);
            XList:=Source.NotMandatoryValueList;

            if t<XList.Count then
            begin
              tmpList.AddChartValue(XList.Value[t]);

              if Source.Labels[i]<>'' then
                 tmpLabels.AddObject(FloatToStr(XList.Value[t]),Source);
            end;
          end;

          tmpList.Sort;

          //Add all X values (if not already added). If not common to all sources add it as null
          for i:=0 to tmpList.Count-1 do
          begin
            tmpX:=tmpList.Value[i];

            if ParentSeries.NotMandatoryValueList.Locate(tmpX)<0 then
            begin
              tmpY:=CalculateManyByValue(ParentSeries.DataSources,tmpX);

              k:=tmpLabels.IndexOf(FloatToStr(tmpX));

              if k>=0 then
                 tmpLabel:=TChartSeries(tmpLabels.Objects[k]).Labels[t]
              else
                 tmpLabel:='';

              if CommonX(tmpX) then
                 ParentSeries.AddXY(tmpX,tmpY,tmpLabel)
              else
              if FIncludeNulls then
                 ParentSeries.AddNullXY(tmpX,tmpY,tmpLabel);
            end;
          end;
        end;
      finally
        tmpList.Free;
        tmpLabels.Free;
      end;
    end;

begin
  if (not IUpdating) and Assigned(Source) then
  With ParentSeries do
  begin
    if DataSources.Count>1 then
    begin
      if FCalcByValue then
         CalculateFunctionManyByValue
      else
         CalculateFunctionMany;
    end
    else
    if Source.Count>0 then
       DoCalculation(Source,Source.NotMandatoryValueList);
  end;
end;

Procedure TTeeFunction.DoCalculation( Source:TChartSeries;
                                      NotMandatorySource:TChartValueList);
begin
  if FPeriod=0 then CalculateAllPoints(Source,NotMandatorySource)
               else CalculateByPeriod(Source,NotMandatorySource);
end;

Procedure TTeeFunction.SetParentSeries(const AParent:TChartSeries);
begin
  if AParent<>FParent then
  begin
    if Assigned(FParent) then FParent.FTeeFunction:=nil;
    AParent.SetFunction(Self);
  end;
end;

Procedure TTeeFunction.AddValue(const Value:Double; const Values:TChartValues; Index:Integer);
begin
  Values[Index]:=Value;
end;

Function TTeeFunction.CalculateValue(const Values:TChartValues):Double;
begin
  result:=0;  { abstract }
end;

Function TTeeFunction.Calculate(const SourceSeries:TChartSeries; First,Last:Integer):Double;
var t,tmpLen  : Integer;
    tmpSource,
    tmpValues : TChartValues;
begin
  if First=TeeAllValues then
  begin
    First:=0;
    Last:=SourceSeries.Count-1;
  end;

  if FIncludeNulls then
     SetLength(tmpValues,Last-First+1);

  tmpLen:=0;

  tmpSource:=ValueList(SourceSeries).Value;

  for t:=First to Last do
  if FIncludeNulls or (not SourceSeries.IsNull(t)) then
  begin
    if not FIncludeNulls then
       SetLength(tmpValues,tmpLen+1);

    AddValue(tmpSource[t],tmpValues,tmpLen);

    Inc(tmpLen);
  end;

  result:=CalculateValue(tmpValues);
end;

Function TTeeFunction.CalculateMany(const SourceSeriesList:TDataSourcesList; ValueIndex:Integer):Double;
var tmpList  : TChartValueList;
    t,tmpLen : Integer;
    SourceSeries: TChartSeries;
    tmpValues: TChartValues;
begin
  if FIncludeNulls then
     SetLength(tmpValues,SourceSeriesList.Count);

  tmpLen:=0;

  for t:=0 to SourceSeriesList.Count-1 do
  begin
    SourceSeries:=TChartSeries(SourceSeriesList[t]);
    tmpList:=ValueList(SourceSeries);

    if (tmpList.Count>ValueIndex) and
       (FIncludeNulls or (not SourceSeries.IsNull(t))) then
    begin
      if not FIncludeNulls then
         SetLength(tmpValues,tmpLen+1);

      AddValue(tmpList.Value[ValueIndex],tmpValues,tmpLen);

      Inc(tmpLen);
    end
  end;

  result:=CalculateValue(tmpValues);
end;

Function TTeeFunction.CalculateManyByValue(const SourceSeriesList:TDataSourcesList; Value:Double):Double;
var t,k,tmpLen : Integer;
    tmpList,
    xList      : TChartValueList;
    SourceSeries: TChartSeries;
    tmpValues  : TChartValues;
begin
  if FIncludeNulls then
     SetLength(tmpValues,SourceSeriesList.Count);

  tmpLen:=0;

  for t:=0 to SourceSeriesList.Count-1 do
  begin
    SourceSeries:=TChartSeries(SourceSeriesList[t]);

    tmpList:=ValueList(SourceSeries);

    xList:=SourceSeries.NotMandatoryValueList;

    k:=xList.Locate(Value);

    if k>=0 then
    if FIncludeNulls or not SourceSeries.IsNull(k) then
    begin
      if not FIncludeNulls then
         SetLength(tmpValues,tmpLen+1);

      AddValue(tmpList.Value[k],tmpValues,tmpLen);

      Inc(tmpLen);
    end;
  end;

  result:=CalculateValue(tmpValues);
end;

Procedure TTeeFunction.Assign(Source:TPersistent);
begin
  if Source is TTeeFunction then
  With TTeeFunction(Source) do
  begin
    Self.FCalcByValue :=FCalcByValue;
    Self.FIncludeNulls:=FIncludeNulls;
    Self.FPeriod      :=FPeriod;
    Self.FPeriodStyle :=FPeriodStyle;
    Self.FPeriodAlign :=FPeriodAlign;
  end;
end;

Procedure TTeeFunction.SetCalcByValue(Const Value:Boolean);
begin
  if Value<>FCalcByValue then
  begin
    FCalcByValue:=Value;
    Recalculate;
  end;
end;

Procedure TTeeFunction.SetIncludeNulls(Const Value:Boolean);
begin
  if Value<>FIncludeNulls then
  begin
    FIncludeNulls:=Value;
    Recalculate;
  end;
end;

Procedure TTeeFunction.InternalSetPeriod(Const APeriod:Double);
begin
  FPeriod:=APeriod;
end;

Procedure TTeeFunction.SetPeriod(Const Value:Double);
begin
  if Value<0 then
     Raise ChartException.Create(TeeMsg_FunctionPeriod);

  if FPeriod<>Value then
  begin
    FPeriod:=Value;
    Recalculate;
  end;
end;

Procedure TTeeFunction.SetPeriodAlign(Value:TFunctionPeriodalign);
begin
  if Value<>FPeriodAlign then
  begin
    FPeriodAlign:=Value;
    Recalculate;
  end;
end;

Procedure TTeeFunction.SetPeriodStyle(Value:TFunctionPeriodStyle);
begin
  if Value<>FPeriodStyle then
  begin
    FPeriodStyle:=Value;
    Recalculate;
  end;
end;

function TTeeFunction.GetParentComponent: TComponent;
begin
  result:=FParent;
end;

procedure TTeeFunction.SetParentComponent(Value: TComponent);
begin
  if Value is TChartSeries then
     ParentSeries:=TChartSeries(Value);
end;

function TTeeFunction.HasParent: Boolean;
begin
  result:=True;
end;

Function TTeeFunction.IsValidSource(const Value:TChartSeries):Boolean;
Begin
  result:=True; { abstract }
end;

{ TeeMovingFunction }
Constructor TTeeMovingFunction.Create(AOwner:TComponent);
begin
  inherited;
  InternalSetPeriod(1);
  SingleSource:=True;
  FPeriodAlign:=paLast;
end;

// Calls "Calculate" function for each group of Period points.
// ie: When Period=10, calls:
//   Calculate(0,9) Calculate(1,10) Calculate(2,11)... etc
Procedure TTeeMovingFunction.DoCalculation( Source:TChartSeries;
                                      NotMandatorySource:TChartValueList);
var t   : Integer;
    P   : Integer;
    tmp : TChartValue;
begin
  P:=Round(FPeriod);

  for t:=P-1 to Source.Count-1 do
  begin
    case FPeriodAlign of
      paFirst  : tmp:=NotMandatorySource.Value[t-P+1];
      paCenter : tmp:=(NotMandatorySource.Value[t-P+1]+NotMandatorySource.Value[t])*0.5;
    else
    { paLast } tmp:=NotMandatorySource.Value[t];
    end;

    AddFunctionXY( Source.YMandatory, tmp, Calculate(Source,t-P+1,t));
  end;
end;

class function TTeeFunction.GetEditorClass: String;
begin
  result:='TTeeFunctionEditor'; // Default editor 
end;

class function TTeeFunction.GallerySource:TChartSeriesClass;
begin
  result:=nil; // Default: TFastLineSeries
end;

Procedure TTeeFunction.PrepareForGallery(const Chart:TCustomAxisPanel);
var tmp : Integer;
begin
  tmp:=Chart.SeriesCount-1;
  Chart[tmp].FunctionType:=Self;

  if tmp>0 then
     Chart[tmp].DataSource:=Chart[tmp-1];
end;

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

  FActive:=True;
  FBrush:=TTeeBrush.Create(CanvasChanged);
  FPen:=CreateChartPen;
  FShowInEditor:=True;
end;

Destructor TCustomChartElement.Destroy;
begin
  FPen.Free;
  FBrush.Free;
  ParentChart:=nil;
  inherited;
end;

procedure TCustomChartElement.Assign(Source:TPersistent);
Begin
  if Source is TCustomChartElement then
  With TCustomChartElement(Source) do
  begin
    Self.FActive:= FActive;
    Self.Brush  := FBrush;
    Self.Pen    := FPen;
    Self.Tag    := Tag;
    Self.TagObject:=TagObject;
  end
  else inherited;
end;

Procedure TCustomChartElement.CanvasChanged(Sender:TObject);
Begin
  Repaint;
end;

Function TCustomChartElement.CreateChartPen:TChartPen;
begin
  result:=TChartPen.Create(CanvasChanged);
end;

// Returns class name of editor form (public)
class Function TCustomChartElement.EditorClass:String;
begin
  result:=GetEditorClass;
end;

// Returns the Form class name that allows editing our properties (protected)
class Function TCustomChartElement.GetEditorClass:String;
Begin
  result:='';
end;

procedure TCustomChartElement.SetBrush(const Value: TChartBrush);
begin
  FBrush.Assign(Value);
end;

procedure TCustomChartElement.SetPen(const Value: TChartPen);
begin
  FPen.Assign(Value);
end;

Procedure TCustomChartElement.Repaint;
Begin
  if Assigned(FParent) then FParent.Invalidate;
end;

Procedure TCustomChartElement.SetActive(Value:Boolean);
Begin
  SetBooleanProperty(FActive,Value);
end;

{ Changes the Parent property }
Procedure TCustomChartElement.SetParentChart(Const Value:TCustomAxisPanel);
begin
  FParent:=Value;

  {$IFDEF TEETRIAL}
  if Assigned(FParent) and Assigned(TeeAboutBoxProc) and
     (not (csDesigning in FParent.ComponentState)) then
          TeeAboutBoxProc;
  {$ENDIF}
end;

{ Changes the Variable parameter with the Value and repaints if different }
Procedure TCustomChartElement.SetBooleanProperty(var Variable:Boolean; Value:Boolean);
Begin
  if Variable<>Value then
  begin
    Variable:=Value;   Repaint;
  end;
end;

{ Changes the Variable parameter with the Value and repaints if different }
Procedure TCustomChartElement.SetColorProperty(var Variable:TColor; Value:TColor);
Begin
  if Variable<>Value then
  begin
    Variable:=Value; Repaint;
  end;
end;

{ Changes the Variable parameter with the Value and repaints if different }
Procedure TCustomChartElement.SetDoubleProperty(var Variable:Double; Const Value:Double);
begin
  if Variable<>Value then
  begin
    Variable:=Value; Repaint;
  end;
end;

{ Changes the Variable parameter with the Value and repaints if different }
Procedure TCustomChartElement.SetSingleProperty(var Variable:Single; Const Value:Single);
begin
  if Variable<>Value then
  begin
    Variable:=Value; Repaint;
  end;
end;

{ Changes the Variable parameter with the Value and repaints if different }
Procedure TCustomChartElement.SetIntegerProperty(var Variable:Integer; Value:Integer);
Begin
  if Variable<>Value then
  begin
    Variable:=Value;   Repaint;
  end;
end;

{ Changes the Variable parameter with the Value and repaints if different }
Procedure TCustomChartElement.SetStringProperty(var Variable:String; Const Value:String);
Begin
  if Variable<>Value then
  begin
    Variable:=Value;   Repaint;
  end;
end;

{ Internal. VCL mechanism to define parentship between chart and series. }
function TCustomChartElement.GetParentComponent: TComponent;
begin
  result:=FParent;
end;

{ Internal. VCL mechanism to define parentship between chart and series. }
procedure TCustomChartElement.SetParentComponent(AParent: TComponent);
begin
  if AParent is TCustomAxisPanel then
     ParentChart:=TCustomAxisPanel(AParent);
end;

{ Internal. VCL mechanism to define parentship between chart and series. }
function TCustomChartElement.HasParent: Boolean;
begin
  result:=True;
end;

{ TCustomChartSeries }

Destructor TCustomChartSeries.Destroy;
begin
  FLegend.Free;
  inherited;
end;

Procedure TCustomChartSeries.Added; { virtual }
begin
  if Assigned(FLegend) then
     FLegend.ParentChart:=ParentChart;
end;

{ copy basic properties from one series to self }
procedure TCustomChartSeries.Assign(Source:TPersistent);
Begin
  if Source is TCustomChartSeries then
  With TCustomChartSeries(Source) do
  begin
    Self.FIdentifier:=FIdentifier;
    Self.Legend     :=FLegend;
    Self.FStyle     :=FStyle;
    Self.FTitle     :=FTitle;
  end;

  inherited;
end;

function TCustomChartSeries.GetIndex:Integer;
begin
  if Assigned(ParentChart) then
     result:=ParentChart.SeriesList.IndexOf(Self as TChartSeries)
  else
     result:=-1;
end;

{ Internal. Used in Decision Graph component. }
procedure TCustomChartSeries.DefineProperties(Filer: TFiler);
begin
  inherited;
  Filer.DefineProperty('Identifier',ReadIdentifier,WriteIdentifier,Identifier<>'');  { <-- don't translate }
  Filer.DefineProperty('Style',ReadStyle,WriteStyle,Style<>[]);  { <-- don't translate }
end;

{ Internal. Used in Decision Graph component. }
procedure TCustomChartSeries.ReadIdentifier(Reader: TReader);
begin
  Identifier:=Reader.ReadString;
end;

{ Internal. Used in Decision Graph component. }
procedure TCustomChartSeries.WriteIdentifier(Writer: TWriter);
begin
  Writer.WriteString(Identifier);
end;

{ Internal. Used in Decision Graph component. }
procedure TCustomChartSeries.ReadStyle(Reader: TReader);
var tmp : Integer;
begin
  tmp:=Reader.ReadInteger;
  FStyle:=[];
  if (tmp and 1)=1 then FStyle:=FStyle+[tssIsTemplate];
  if (tmp and 2)=2 then FStyle:=FStyle+[tssDenyChangeType];
  if (tmp and 4)=4 then FStyle:=FStyle+[tssDenyDelete];
  if (tmp and 8)=8 then FStyle:=FStyle+[tssDenyClone];
  if (tmp and 16)=16 then FStyle:=FStyle+[tssIsPersistent];
  if (tmp and 32)=32 then FStyle:=FStyle+[tssHideDataSource];
end;

{ Internal. Used in Decision Graph component (Decision Cube components). }
procedure TCustomChartSeries.WriteStyle(Writer: TWriter);
var tmp : Integer;
begin
  tmp:=0;
  if tssIsTemplate     in FStyle then Inc(tmp);
  if tssDenyChangeType in FStyle then Inc(tmp,2);
  if tssDenyDelete     in FStyle then Inc(tmp,4);
  if tssDenyClone      in FStyle then Inc(tmp,8);
  if tssIsPersistent   in FStyle then Inc(tmp,16);
  if tssHideDataSource in FStyle then Inc(tmp,32);
  Writer.WriteInteger(tmp);
end;

{ Returns the number of pixels for horizontal margins }
Procedure TCustomChartSeries.CalcHorizMargins(var LeftMargin,RightMargin:Integer);
begin
  LeftMargin:=0; RightMargin:=0;  { abstract }
end;

{ Returns the number of pixels for vertical margins }
Procedure TCustomChartSeries.CalcVerticalMargins(var TopMargin,BottomMargin:Integer);
begin
  TopMargin:=0; BottomMargin:=0;  { abstract }
end;

procedure TCustomChartSeries.DoBeforeDrawChart;
begin
end;

{ Called when the Gallery "3D" option is changed. }
Procedure TCustomChartSeries.GalleryChanged3D(Is3D:Boolean);
begin
  ParentChart.View3D:=Is3D;
end;

// Special case for 2nd pass margins (ie: Candle and Bubble series)
procedure TCustomChartSeries.RecalcAxisMargins(const Axis:TChartAxis; var R:TRect);
begin
  // Empty.
end;

Procedure TCustomChartSeries.Removed;
begin
  FParent:=nil;
end;

{ Returns True when the tmpSeries parameter is of the same class }
Function TCustomChartSeries.SameClass(const tmpSeries:TChartSeries):Boolean;
begin
  result:=((Self is tmpSeries.ClassType) or (tmpSeries is Self.ClassType));
end;

{ Tells the design-time Series Designer window to repaint }
Procedure TCustomChartSeries.RepaintDesigner;
begin
  if Assigned(ParentChart) and Assigned(ParentChart.Designer) then
     ParentChart.Designer.Repaint;
end;

Procedure TCustomChartSeries.SetLegend(const Value:TSeriesLegend);
begin
  if Assigned(Value) then
     Legend.Assign(Value)
  else
     FreeAndNil(FLegend);
end;

{ show / hide Series }
Procedure TCustomChartSeries.SetActive(Value:Boolean);
Begin
  if Active<>Value then
  begin
    inherited;

    if Assigned(ParentChart) then
       ParentChart.BroadcastSeriesEvent(Self,seChangeActive);

    RepaintDesigner;
  end;
end;

function TCustomChartSeries.GetLegend:TSeriesLegend;
begin
  if not Assigned(FLegend) then
     FLegend:=TSeriesLegend.Create(ParentChart);

  result:=FLegend;
end;

function TCustomChartSeries.GetLegendTitle:String;
begin
  if Assigned(FLegend) then
     result:=FLegend.Text
  else
     result:='';
end;

Procedure TCustomChartSeries.SetLegendTitle(const Value:String);
Begin
  Legend.Text:=Value;
end;

function TCustomChartSeries.GetShowInLegend:Boolean;
begin
  if Assigned(FLegend) then
     result:=FLegend.Visible
  else
     result:=True;
end;

Procedure TCustomChartSeries.SetShowInLegend(const Value:Boolean);
Begin
  Legend.Visible:=Value;
end;

{ change the Series "Title" (custom text) }
Procedure TCustomChartSeries.SetTitle(Const Value:String);
Begin
  SetStringProperty(FTitle,Value);

  if Assigned(ParentChart) then
     ParentChart.BroadcastSeriesEvent(Self,seChangeTitle);

  RepaintDesigner;
end;

{ TChartValueLists }
{$IFNDEF D18}
Procedure TChartValueLists.Clear;
var t : Integer;
begin
  for t:=0 to Count-1 do ValueList[t].Free;
  inherited;
end;
{$ENDIF}

Function TChartValueLists.Get(Index:Integer):TChartValueList;
begin
  result:=TChartValueList({$IFOPT R-}List{$ELSE}inherited Items{$ENDIF}[Index]);
end;

{ TVisualsList }

Procedure TVisualsList.DeleteVisual(ValueIndex:Integer);
begin
  if (Count>ValueIndex) and Assigned(List{$IFDEF TEELISTARRAY}^{$ENDIF}[ValueIndex]) then
     if TVisualBlock(List{$IFDEF TEELISTARRAY}^{$ENDIF}[ValueIndex]).Owner=Series then
        TVisualBlock(List{$IFDEF TEELISTARRAY}^{$ENDIF}[ValueIndex]).Free;

  Delete(ValueIndex);
end;

type
  TVisualBlockClass=class of TVisualBlock;
  TVisualAccess=class(TVisualBlock);

Function TVisualsList.GetVisual(ValueIndex:Integer):TVisualBlock;

  function CloneVisual(const Visual:TVisualBlock):TVisualBlock;
  var tmp : TVisualBlockClass;
  begin
    tmp:=TVisualBlockClass(Visual.ClassType);
    result:=tmp.Create(Series);
    result.Assign(Visual);
  end;

begin
  if (Count<=ValueIndex) or ((ValueIndex>=0) and (List{$IFDEF TEELISTARRAY}^{$ENDIF}[ValueIndex]=nil)) then
  begin
    if Assigned(Template) and TVisualAccess(Template).IsCloneable then
    begin
      SetVisual(ValueIndex,CloneVisual(Template));
      result:=TVisualBlock(List{$IFDEF TEELISTARRAY}^{$ENDIF}[ValueIndex]);
    end
    else
       result:=nil;
  end
  else
  begin
    if (ValueIndex>=0) then
       result:=TVisualBlock(List{$IFDEF TEELISTARRAY}^{$ENDIF}[ValueIndex])
    else
       result:=nil;
  end;
end;

Procedure TVisualsList.SetTemplate(const Value:TVisualBlock);
begin
  if FTemplate<>Value then
  begin
    if Assigned(FTemplate) then
       FTemplate.RemoveFreeNotification(Series);
    
    FTemplate:=Value;

    if Assigned(FTemplate) then
       FTemplate.FreeNotification(Series);

    Clear;
  end;
end;

Procedure TVisualsList.SetVisual(ValueIndex:Integer; const AVisual:TVisualBlock);

  procedure AddPending;
  var t : Integer;
  begin
    if Count<=ValueIndex then
       for t:=Count to ValueIndex do
           Add(nil);
  end;

begin
  if (Count>ValueIndex) and Assigned(List{$IFDEF TEELISTARRAY}^{$ENDIF}[ValueIndex]) then
      if TVisualBlock(List{$IFDEF TEELISTARRAY}^{$ENDIF}[ValueIndex]).Owner=Series then
         TVisualBlock(List{$IFDEF TEELISTARRAY}^{$ENDIF}[ValueIndex]).Free;

  if not Assigned(AVisual) then
  begin
    if (Count>0) and (ValueIndex<Count) then
       List{$IFDEF TEELISTARRAY}^{$ENDIF}[ValueIndex]:=nil;
  end
  else
  begin
    AddPending;
    List{$IFDEF TEELISTARRAY}^{$ENDIF}[ValueIndex]:=AVisual;
  end;

  Series.Repaint;
end;

Procedure TVisualsList.InsertVisual(ValueIndex:Integer; const AVisual:TVisualBlock);

  Procedure InternalInsert(const P:TVisualBlock);
  begin
    while Count<ValueIndex do
          Add(nil);

    Insert(ValueIndex,P);
  end;

Begin
  if not Assigned(AVisual) then
  begin
    if Count>0 then
       InternalInsert(nil);
  end
  else
    InternalInsert(AVisual);
end;

procedure TVisualsList.Assign(const Source:TVisualsList);
var t : Integer;
begin
  Clear;

  for t:=0 to Source.Count-1 do
      InsertVisual(t,Source[t]);
end;

{$IFNDEF D18}

procedure TVisualsList.Clear;
var t : Integer;
begin
  for t:=0 to Count-1 do
      if Assigned(List{$IFDEF TEELISTARRAY}^{$ENDIF}[t]) then
         if TVisualBlock(List{$IFDEF TEELISTARRAY}^{$ENDIF}[t]).Owner=Series then
            TVisualBlock(List{$IFDEF TEELISTARRAY}^{$ENDIF}[t]).Free;

  Series.Repaint;

  inherited;
end;
{$ENDIF}

procedure TVisualsList.Edit(Index:Integer=-1);
begin
  if Assigned(TeeOnEditVisual) then
     if Index=-1 then
     begin
       if TeeOnEditVisual(Series.ParentChart,Template) then
          Clear;
     end
     else
     if TeeOnEditVisual(Series.ParentChart,Visuals[Index]) then
        Series.Repaint;
end;

{ TTeeHover }

type
  TTeePenAccess=class(TTeePen);

Constructor TTeeHover.Create(const AOwner: TCustomTeePanel);
var tmpRed : TColor;
begin
  inherited;

  tmpRed:={$IFDEF FMX}TAlphaColors.Red{$ELSE}clRed{$ENDIF};

  //Font.Style:=[{$IFDEF FMX}TFontStyle.{$ENDIF}fsBold];
  Font.Color:=tmpRed;

  Pen.Color:=TTeeCanvas.ColorFrom(tmpRed,128);

  Pen.Width:=3;
  TTeePenAccess(Pen).DefaultWidth:=3;
end;

{ TTeeSelected }

Constructor TTeeSelected.CreateChart(const AOwner: TObject);
begin
  inherited Create(nil);

  IElement:=AOwner;

  Visible:=False;
  Pen.Color:={$IFDEF FMX}TAlphaColors.Red{$ELSE}clRed{$ENDIF};
  Pen.Width:=2;

  FHoverIndex:=-1;
  FItemIndex:=-1;

  FHover:=TTeeHover.Create(nil);
end;

Destructor TTeeSelected.Destroy;
begin
  FHover.Free;
  inherited;
end;

procedure TTeeSelected.Assign(Source:TPersistent);
begin
  if Source is TTeeSelected then
  begin
    FHover.Assign(TTeeSelected(Source).FHover);
  end;

  inherited;
end;

procedure TTeeSelected.SetHover(const Value:TTeeHover);
begin
  FHover.Assign(Value);
end;

procedure TTeeSelected.DoRepaint;
begin
  if IElement is TCustomChartElement then
     TCustomChartElement(IElement).Repaint
  else
  if IElement is TTeeCustomShapeBrushPen then
     TTeeCustomShapeBrushPen(IElement).Repaint
  else
  if IElement is TCustomTeePanel then
     TCustomTeePanel(IElement).Invalidate;
end;

procedure TTeeSelected.SetItemIndex(const Value: Integer);
begin
  if FItemIndex<>Value then
  begin
    FItemIndex:=Value;

    if Visible then
       DoRepaint;
  end;
end;

procedure TTeeSelected.SetHoverIndex(const Value: Integer);
begin
  if FHoverIndex<>Value then
  begin
    FHoverIndex:=Value;

    if FHover.Visible then
       DoRepaint;
  end;
end;

{ TSeriesDetail }

Destructor TSeriesDetail.Destroy;
var t : Integer;
begin
  for t:=0 to Count-1 do
      {$IFDEF AUTOREFCOUNT}
      Items[t].DisposeOf;
      {$ELSE}
      Items[t].Free;
      {$ENDIF}

  inherited;
end;

function TSeriesDetail.CreateNew(const AParent:TChartSeries):TChartSeries;
begin
  result:=TChartSeriesClass(AParent.ClassType).Create(AParent);
  result.ManualData:=True;
  result.ParentSeries:=AParent; // <-- Used by Chart to avoid destroying it.

  Add(result);
end;

function CalcSum(const Values:TChartValues):TAxisValue;
var t : Integer;
begin
  result:=0;

  for t:=Low(Values) to High(Values) do
      result:=result+Values[t];
end;

function TSeriesDetail.Calculate(const Values:TChartValueList):TChartValue;
var t,
    tmpCount : Integer;
begin
  tmpCount:=Values.Count;

  if tmpCount=0 then
     result:=0
  else
  case Aggregate of
    daCount: result:=tmpCount;
      daSum: result:={$IFDEF TEEVALUEDOUBLE}Sum{$ELSE}CalcSum{$ENDIF}(Values.Value);
  daAverage: result:={$IFDEF TEEVALUEDOUBLE}Sum{$ELSE}CalcSum{$ENDIF}(Values.Value)/tmpCount;
  else
     begin
       result:=Values[0];

       if Aggregate=daMax then
       begin
         for t:=1 to tmpCount-1 do
             if Values[t]>result then
                result:=Values[t];
       end
       else
         for t:=1 to tmpCount-1 do
             if Values[t]<result then
                result:=Values[t];
     end;
  end;
end;

procedure TSeriesDetail.ReCalculate;
var t : Integer;
begin
  IParent.BeginUpdate;
  try
    while IParent.Count<Count do
          IParent.Add(0,Items[IParent.Count].Title);

    for t:=0 to Count-1 do
        IParent.MandatoryValueList[t]:=Calculate(Items[t].MandatoryValueList);
  finally
    IParent.Repaint;
    IParent.EndUpdate;
  end;
end;

procedure TSeriesDetail.SetAggregate(const Value:TDetailAggregate);
begin
  if FAggregate<>Value then
  begin
    FAggregate:=Value;
    ReCalculate;
  end;
end;

{ TTeePictureList }

class function TTeePictureList.Copy(const APicture:TTeePicture):TTeePicture;
begin
  result:=TTeePicture.Create;
  result.Assign(APicture);
end;

class function TTeePictureList.FromGraphic(const AGraphic:TGraphic):TTeePicture;
begin
  result:=TTeePicture.Create;
  result.Graphic:=AGraphic;
end;

Procedure TTeePictureList.Delete(const ValueIndex:Integer);
{$IFDEF D18}
var t : Integer;
{$ENDIF}
Begin
  {$IFDEF D18}
  if OwnsPictures then
     List[ValueIndex].Free;

  for t:=ValueIndex to Count-2 do
      List[t]:=List[t+1];

  SetLength(List,Count-1);
  {$ELSE}

  if OwnsPictures then
     if (Count>ValueIndex) and Assigned(List{$IFDEF TEELISTARRAY}^{$ENDIF}[ValueIndex]) then
        TTeePicture(List{$IFDEF TEELISTARRAY}^{$ENDIF}[ValueIndex]).Free;

  inherited Delete(ValueIndex);
  {$ENDIF}
end;

Function TTeePictureList.Get(const ValueIndex:Integer):TTeePicture;
begin
  {$IFDEF D18}
  if (ValueIndex<0) or (ValueIndex>=Count) then
     result:=nil
  else
     result:=List[ValueIndex];
  {$ELSE}

  if (Count<=ValueIndex) or (List{$IFDEF TEELISTARRAY}^{$ENDIF}[ValueIndex]=nil) then
     result:=nil
  else
     result:=TTeePicture(List{$IFDEF TEELISTARRAY}^{$ENDIF}[ValueIndex]);
  {$ENDIF}
end;

Procedure TTeePictureList.Put(const ValueIndex:Integer; Const APicture:TTeePicture);

  procedure AddPending;
  var t : Integer;
  begin
    if Count<=ValueIndex then
    begin
      {$IFDEF D18}
      SetLength(List,ValueIndex+1);
      {$ENDIF}

      for t:=Count to ValueIndex do
          {$IFDEF D18}List[t]:=nil{$ELSE}Add(nil){$ENDIF};
    end;
  end;

begin
  {$IFDEF D18}
  if OwnsPictures then
     if (Count>0) and (ValueIndex<Count) then
        List[ValueIndex].Free;

  if APicture=nil then
  begin
    if (Count>0) and (ValueIndex<Count) then
       List[ValueIndex]:=nil;
  end
  else
  begin
    AddPending;
    List[ValueIndex]:=APicture;
  end;

  {$ELSE}
  if OwnsPictures then
     if (Count>ValueIndex) and Assigned(List{$IFDEF TEELISTARRAY}^{$ENDIF}[ValueIndex]) then
         TTeePicture(List{$IFDEF TEELISTARRAY}^{$ENDIF}[ValueIndex]).Free;

  if APicture=nil then
  begin
    if (Count>0) and (ValueIndex<Count) then
       List{$IFDEF TEELISTARRAY}^{$ENDIF}[ValueIndex]:=nil
  end
  else
  begin
    AddPending;

    List{$IFDEF TEELISTARRAY}^{$ENDIF}[ValueIndex]:=APicture;
  end;
  {$ENDIF}

  Series.Repaint;
end;

Procedure TTeePictureList.Insert(const ValueIndex:Integer; Const APicture:TTeePicture);
{$IFDEF D18}
var t : Integer;
{$ENDIF}
begin
  if (APicture<>nil) or (Count>0) then
  begin
    {$IFDEF D18}
    SetLength(List, Max(Count,ValueIndex)+1);

    while Count<ValueIndex-1 do
       List[Count-1]:=nil;

    for t:=Count-1 downto ValueIndex+1 do
        List[t]:=List[t-1];

    List[ValueIndex]:=APicture;
    {$ELSE}

    while Count<ValueIndex do
          Add(nil);

    inherited Insert(ValueIndex,APicture);
    {$ENDIF}
  end;
end;

{$IFDEF D18}
Destructor TTeePictureList.Destroy;
begin
  Clear;
  inherited;
end;

procedure TTeePictureList.Clear;
var t : Integer;
begin
  if OwnsPictures then
     for t:=0 to Count-1 do
         List[t].Free;

  List:=nil;

  inherited;
end;

function TTeePictureList.Count:Integer;
begin
  result:=Length(List);
end;

procedure TTeePictureList.Exchange(A,B:Integer);
var tmp : TTeePicture;
begin
  tmp:=List[A];
  List[A]:=List[B];
  List[B]:=tmp;
end;

{$ELSE}
procedure TTeePictureList.Clear;
var t : Integer;
begin
  if OwnsPictures then
     for t:=0 to Count-1 do
         TTeePicture(List[t]).Free;

  inherited;
end;
{$ENDIF}

procedure TTeePictureList.Assign(const Source:TTeePictureList);
var t : Integer;
begin
  Clear;

  {$IFDEF D18}
  SetLength(List,Source.Count);
  {$ENDIF}

  for t:=0 to Count-1 do
  begin
    {$IFDEF D18}
    List[t]:=Copy(Source[t]);
    {$ELSE}
    inherited Add(Copy(Source[t]));
    {$ENDIF}
  end;
end;

Function TTeePictureList.IndexOf(const APicture:TTeePicture):Integer;
var t : Integer;
begin
  for t:=0 to Count-1 do
      if Items[t]=APicture then
      begin
        result:=t;
        Exit;
      end;

  result:=-1;
end;

{ TChartSeries }

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

  FZOrder:=TeeAutoZOrder;
  FDepth:=TeeAutoDepth;
  FHorizAxis:=aBottomAxis;
  FVertAxis:=aLeftAxis;
  FValuesList:=TChartValueLists.Create;

  FX:=TChartValueList.Create(Self,TeeMsg_ValuesX);
  FX.FOrder:=loAscending;
  NotMandatoryValueList:=FX;

  FY:=TChartValueList.Create(Self,TeeMsg_ValuesY);
  MandatoryValueList:=FY;
  YMandatory:=True;

  FLabels:=TLabelsList.Create;
  FLabels.Series:=Self;

  FColor:=clTeeColor;
  ISeriesColor:=clTeeColor;

  FCursor:=crDefault;

  FValueFormat      :=TeeMsg_DefValueFormat;
  FPercentFormat    :=TeeMsg_DefPercentFormat;

  FRecalcOptions    := [rOnDelete,rOnModify,rOnInsert,rOnClear];

  FFirstVisibleIndex:=-1;
  FLastVisibleIndex :=-1;
  AllowSinglePoint  :=True;
  CalcVisiblePoints :=True;
  IUseSeriesColor   :=True;
  IUseNotMandatory  :=True;
  FDefaultNull      :=0.0;
end;

{ Frees all properties }
Destructor TChartSeries.Destroy;
var t : Integer;
Begin
  if Assigned(ParentSeries) and Assigned(ParentSeries.FDetail) then
     ParentSeries.FDetail.Remove(Self);

  if Assigned(FTeeFunction) then
     FreeAndNil(FTeeFunction);

  RemoveAllLinkedSeries;

  {$IFNDEF D18}
  if Assigned(DataSource) and (DataSource.Owner=Self) then
     DataSource.Free;
  {$ENDIF}

  FreeAndNil(FDataSources);

  if Assigned(FLinkedSeries) then
  for t:=0 to FLinkedSeries.Count-1 do
      FLinkedSeries[t].RemoveDataSource(Self);

  Clear;

  FreeAndNil(FLinkedSeries);
  FValuesList.Free;
  FColors.Free;
  FVisuals.Free;
  FLabels.Free;
  FPictures.Free;
  FreeAndNil(FMarks);
  Transform.Free;
  FSelected.Free;

  inherited;
end;

function TChartSeries.GetDetail:TSeriesDetail;
begin
  if not Assigned(FDetail) then
  begin
    FDetail:=TSeriesDetail.Create;
    FDetail.IParent:=Self;
  end;

  result:=FDetail;
end;

function TChartSeries.GetImage:TBackImage;
begin
  result:=Legend.Picture;
end;

function TChartSeries.GetPictures:TTeePictureList;
begin
  if not Assigned(FPictures) then
  begin
    FPictures:=TTeePictureList.Create;
    FPictures.Stored:=True;
    FPictures.OwnsPictures:=True;

    FPictures.Series:=Self;
  end;

  result:=FPictures;
end;

function TChartSeries.GetLinkedSeries:TCustomSeriesList;
begin
  if not Assigned(FLinkedSeries) then
     FLinkedSeries:=TCustomSeriesList.Create;

  result:=FLinkedSeries;
end;

// This getter exists mainly to increase speed when creating many TMapSeries TTeePolygon
function TChartSeries.GetMarks:TSeriesMarks;
begin
  if not Assigned(FMarks) then
  begin
    FMarks:=TSeriesMarks.Create(Self);
    FMarks.DefaultVisible:=False;
    FMarks.ParentChart:=ParentChart;
  end;

  result:=FMarks;
end;

{ Returns True when in design-mode or when we want random sample
  points for the series at run-time. }
Function TChartSeries.CanAddRandomPoints:Boolean;
begin
  result:=TeeRandomAtRunTime or (csDesigning in ComponentState);
end;

Procedure TChartSeries.ChangeInternalColor(Value:TColor);
begin
  ISeriesColor:=Value;
  SeriesColor:=ISeriesColor;
end;

{ Called when the series is added into a Chart series list. }
Procedure TChartSeries.Added;
begin
  inherited;

  if FColor=clTeeColor then
  begin
    ChangeInternalColor(FParent.GetFreeSeriesColor);
    NotifyColorChanged;
  end;

  RecalcGetAxis;
  CheckDataSource;
end;

{ Called when the series is removed from a Chart series list. }
Procedure TChartSeries.Removed;
begin
  if Cursor=FParent.Cursor then { restore cursor }
     FParent.Cursor:=FParent.OriginalCursor;

  RemoveAllLinkedSeries;

  inherited;
end;

function TChartSeries.AxisLabelStyle(const AAxis:TChartAxis):TAxisLabelStyle;
begin
  result:=talValue;

  if not HasZValues then // (ie: MapSeries should not use text)
     if (AAxis.Horizontal and YMandatory) or
       ((not AAxis.Horizontal) and (not YMandatory)) then

       if HasPointLabels then
          result:=talPointValue
       else
       if HasLabels then
          result:=talText
end;

procedure TChartSeries.BeforeCalcAxis;
begin
end;

procedure TChartSeries.CalcDepthPositions;
begin
  StartZ:=IZOrder*ParentChart.SeriesWidth3D;

  if Depth=-1 then EndZ:=StartZ+ParentChart.SeriesWidth3D
              else EndZ:=StartZ+Depth;

  MiddleZ:=(StartZ+EndZ) div 2;

  if Assigned(FMarks) then
     FMarks.ZPosition:=MiddleZ;
end;

{ internal. Called by Chart. Raises OnMouseEnter or OnMouseLeave events, and
  changes Cursor. Returns True if Clicked and Cursor <> crDefault. }
Function TChartSeries.CheckMouse(x,y:Integer):Boolean;
var tmpResult : Boolean;
    tmp : Integer;
begin
  result:=False;
  IClickedPart:=scpNone;

  tmpResult:=(Cursor<>crDefault) or Assigned(FOnMouseEnter) or Assigned(FOnMouseLeave);

  if tmpResult or HoverEnabled then
  begin
    tmp:=Clicked(X,Y);

    if tmp<>-1 then { <-- mouse is over a Series value ! }
    Begin
      if Cursor<>crDefault then
         if IsPainted(tmp) then
            ParentChart.Cursor:=Cursor;

      if not IsMouseInside then
      begin
        IsMouseInside:=True;

        if Assigned(FOnMouseEnter) then
           FOnMouseEnter(Self);
      end;

      result:=tmpResult;
    end
    else
    begin
      if Marks.Visible then
         tmp:=Marks.Clicked(x,y);

      if tmp=-1 then
      begin
        if IsMouseInside then
        begin
          IsMouseInside:=False;

          if Assigned(FOnMouseLeave) then
             FOnMouseLeave(Self);
        end;
      end;
    end;

    Selected.HoverIndex:=tmp;
  end;
end;

function TChartSeries.GetSelected:TTeeSelected;
begin
  if not Assigned(FSelected) then
     FSelected:=TTeeSelected.CreateChart(Self);

  result:=FSelected;
end;

function TChartSeries.GetVisuals:TVisualsList;
begin
  if not Assigned(FVisuals) then
  begin
    FVisuals:=TVisualsList.Create;
    FVisuals.FSeries:=Self;
  end;

  result:=FVisuals;
end;

{ internal. VCL streaming mechanism. Stores the Function, if any. }
Procedure TChartSeries.GetChildren(Proc:TGetChildProc; Root:TComponent);
begin
  inherited;
  if Assigned(FunctionType) then Proc(FunctionType);
end;

{ Sets the chart that will contain and display the series }
Procedure TChartSeries.SetParentChart(Const Value:TCustomAxisPanel);
Begin
  if FParent<>Value then
  Begin
    if Assigned(FParent) then
       FParent.RemoveSeries(Self);

    inherited;

    if Assigned(FMarks) then
       FMarks.ParentChart:=FParent;

    if Assigned(FParent) then
       FParent.InternalAddSeries(Self);
  end;
end;

procedure TChartSeries.ChangedAxisScale(const Axis:TChartAxis);
begin  // Nothing
end;

{ Associates the series to a function }
procedure TChartSeries.SetFunction(AFunction:TTeeFunction);
begin
  if FTeeFunction<>AFunction then
  begin
    FTeeFunction.Free;

    { set new function }
    FTeeFunction:=AFunction;

    if Assigned(FTeeFunction) then
    begin
      FTeeFunction.FParent:=Self;
      ManualData:=False;

      FTeeFunction.FreeNotification(Self);
    end;

    { force recalculation (add points using function or clear) }
    CheckDataSource;
  end;
end;

// When the data source is destroyed, sets it to nil.
procedure TChartSeries.Notification( AComponent: TComponent;
                                     Operation: TOperation);
begin
  inherited;

  if Operation=opRemove then
  begin
    if AComponent=FTeeFunction then
       FTeeFunction:=nil
    else
      // If "AComponent" is the same as series datasource,
      // then set datasource to nil:
      if Assigned(FDataSources) then
         InternalRemoveDataSource(AComponent)
      else
      if (AComponent is TVisualBlock) and Assigned(FVisuals) and (AComponent=FVisuals.FTemplate) then
         FVisuals.FTemplate:=nil;
  end;
end;

{ Hidden properties. Defines them to store / load from the DFM stream }
procedure TChartSeries.DefineProperties(Filer: TFiler);

  // Pending: Try to improve speed here.
  // Returns True when Self series data equals to ASeries data
  // (data means X,Y values,Labels, etc, etc)
  function SameData(const ASeries:TChartSeries):Boolean;
  var tmpA,
      tmpB : TMemoryStream;
  begin
    tmpA:=TMemoryStream.Create;
    tmpB:=TMemoryStream.Create;
    try
      WriteData(tmpA);
      ASeries.WriteData(tmpB);

      result:=(tmpA.Size=tmpB.Size) and CompareMem(tmpA.Memory,tmpB.Memory,tmpA.Size);
    finally
      tmpB.Free;
      tmpA.Free;
    end;
  end;

var tmpWrite : Boolean;
begin
  inherited;

  Filer.DefineProperty('DataSources',ReadDataSources,WriteDataSources,
       Assigned(FDataSources) and (FDataSources.Count>1));  { <-- don't translate }

  Filer.DefineProperty('CustomHorizAxis', ReadCustomHorizAxis,
                                          WriteCustomHorizAxis,Assigned(FCustomHorizAxis));  { <-- don't translate }
  Filer.DefineProperty('CustomVertAxis', ReadCustomVertAxis,
                                          WriteCustomVertAxis,Assigned(FCustomVertAxis));  { <-- don't translate }

  // For XE7 MultiView and any other inherited Form/Frame:
  tmpWrite:=IsDataStored;

  if tmpWrite and (Filer.Ancestor<>nil) then
     tmpWrite:=not SameData(TChartSeries(Filer.Ancestor));

  Filer.DefineBinaryProperty('Data',ReadData,WriteData,tmpWrite);

  Filer.DefineBinaryProperty('Detail',ReadDetail,WriteDetail,tmpWrite and Assigned(FDetail));
end;

function TChartSeries.IsDataStored:Boolean;
begin
  result:=ForceSaveData or (ManualData and (not DontSaveData));
end;

function TChartSeries.IsPainted(const ValueIndex:Integer):Boolean;
begin
  result:=(TreatNulls=tnIgnore) or (not IsNull(ValueIndex));
end;

Procedure TChartSeries.SetImage(const Value:TBackImage);
begin
  Legend.Picture:=Value;
end;

{$IFDEF D12}
{$DEFINE D12WIN32}
{$ENDIF}

procedure TChartSeries.ReadDetail(Stream: TStream);
var t,
    tmpCount : Integer;
begin
  Detail.Clear;

  Stream.Read(FDetail.FAggregate,Sizeof(FDetail.FAggregate));

  { read the number of detail series }
  Stream.Read(tmpCount,Sizeof(tmpCount));

  for t:=0 to tmpCount-1 do
      FDetail.Add(Stream.ReadComponent(nil) as TChartSeries);
end;

procedure TChartSeries.WriteDetail(Stream: TStream);
var t : Integer;
    tmpCount : Integer;
begin
  Stream.Write(FDetail.FAggregate,Sizeof(FDetail.FAggregate));

  tmpCount:=FDetail.Count;

  Stream.Write(tmpCount,Sizeof(tmpCount));

  for t:=0 to tmpCount-1 do
      Stream.WriteComponent(FDetail[t]);
end;

type
  // This internal class is necessary to read/write a TGraphic from/to a Stream,
  // because TPicture lives in Graphics.pas unit, where FileFormats is private.
  // (ie: TGraphicClass cannot be determined from TGraphic.ClassName here)
  {$IFDEF FMX}
  TTeeFMXPictureComponent=class(TComponent)
  {$ELSE}
  TTeePictureComponent=class(TComponent)
  {$ENDIF}
  private
    FPicture : TTeePicture;
  public
    Constructor Create(AOwner:TComponent); override;
    Constructor CreateFrom(const APicture:TTeePicture);

    Destructor Destroy; override;
  published
    property Picture:TTeePicture read FPicture write FPicture;
  end;

  {$IFDEF FMX}
  // Duplicate name to avoid "class already registered" error at initialization
  TTeePictureComponent=TTeeFMXPictureComponent;
  {$ENDIF}

Constructor TTeePictureComponent.Create(AOwner:TComponent);
begin
  inherited;
  FPicture:=TTeePicture.Create;
end;

Constructor TTeePictureComponent.CreateFrom(const APicture:TTeePicture);
begin
  inherited Create(nil);
  FPicture:=APicture;
end;

Destructor TTeePictureComponent.Destroy;
begin
  FPicture.Free;
  inherited;
end;

procedure TChartSeries.ReadData(Stream: TStream);

  Function ReadColor:TColor;
  begin
    Stream.Read(result,Sizeof(result));
  end;

  Function ReadLabel:String;
  var L    : Byte;
      LInt  : Integer;

      {$IFDEF D12WIN32}
      Bytes : TBytes;
      Encoding : TEncoding;

      {$IFNDEF D18}
      tmpSt : AnsiString;
      {$ENDIF}

      {$ENDIF}
  begin
    L:=0;

    { read the label length }
    Stream.Read(L, SizeOf(L));

    {$IFDEF D12WIN32}
    if L=255 then
    begin
      Stream.Read(LInt, SizeOf(LInt));

      SetLength(Bytes,LInt);
      Stream.Read(Bytes{$IFNDEF D18}[0]{$ENDIF},LInt);

      {$IFDEF D25}
      Encoding:=TEncoding.Default;
      {$ELSE}
      Encoding:=TEncoding.{$IFNDEF USEWINDOWS}Unicode{$ELSE}Default{$ENDIF};
      {$ENDIF}

      result:=Encoding.GetString(Bytes{$IFNDEF D18},0,LInt{$ENDIF});
    end
    else
    begin
      // Old version, non-Unicode strings:

      {$IFDEF D18}
      SetLength(Bytes, L);
      Stream.Read(Bytes, L);
      result:=TEncoding.Default.GetString(Bytes);
      {$ELSE}
      SetString(tmpSt, nil, L);
      Stream.Read(Pointer(tmpSt)^, L);
      result:=String(tmpSt);
      {$ENDIF}
    end;

    {$ELSE}

    { read the label contents }
    if L=255 then
    begin
      // Old version reading new format:
      Stream.Read(LInt, SizeOf(LInt));
      SetString(Result, nil, LInt);
      Stream.Read(Pointer(Result)^, LInt);
    end
    else
    begin
      SetString(Result, nil, L);
      Stream.Read(Pointer(Result)^, L);
    end;
    {$ENDIF}
  end;

var tmpFormat       : TeeFormatFlag;
    ValuesListCount : Integer;
    NotYMandatory   : Boolean;

  Procedure ReadSeriesPoint(Index:Integer);

    procedure ReadImage;
    var tmp : TTeePictureComponent;
        tmpOk : Boolean;
    begin
      Stream.Read(tmpOk,SizeOf(tmpOk));

      if tmpOk then
      begin
        tmp:=TTeePictureComponent(Stream.ReadComponent(nil));
        try
          Pictures[Index]:=tmp.Picture;
        finally
          tmp.FPicture:=nil;
          tmp.Free;
        end;
      end
      else
         Pictures[Index]:=nil;
    end;

  var tmpFloat : Double;
      Ax       : Double;
      Ay       : Double;
      AColor   : TColor;
      ALabel   : String;
      tt       : Integer;
      tmp      : TSeriesMarkPosition;
  begin
    { read the "X" value if exists }
    if tfNoMandatory in tmpFormat then Stream.Read(AX,Sizeof(AX))
                                  else AX:=Index;
    { read the "Y" value }
    Stream.Read(AY,Sizeof(AY));

    { Swap X <--> Y if necessary... }
    if NotYMandatory then SwapDouble(AX,AY);

    { read the Color value if exists }
    if tfColor in tmpFormat then AColor:=ReadColor
                            else AColor:=clTeeColor;

    { read the Label value if exists }
    if tfLabel in tmpFormat then ALabel:=ReadLabel
                            else ALabel:='';

    { read the rest of lists values }
    for tt:=2 to ValuesListCount-1 do
    begin
      Stream.Read(tmpFloat,SizeOf(tmpFloat));
      ValuesList[tt].TempValue:=tmpFloat;
    end;

    { add the new point }
    AddXY(AX,AY,ALabel,AColor);

    if tfMarkPosition in tmpFormat then
    begin
      tmp:=TSeriesMarkPosition.Create;
      With tmp,Stream do
      begin
        Read(ArrowFrom,SizeOf(ArrowFrom));
        Read(ArrowFix,SizeOf(ArrowFix));
        Read(ArrowTo,SizeOf(ArrowTo));
        Read(Custom,SizeOf(Custom));
        Read(Height,SizeOf(Height));
        Read(LeftTop,SizeOf(LeftTop));
        Read(Width,SizeOf(Width));
      end;

      Marks.Positions.Add(tmp);
    end;

    if tfPictures in tmpFormat then
       ReadImage;
  end;

var tmpCount : Integer;
    t        : Integer;
begin
  { empty the Series }
  Clear;

  { read the point flags }
  Stream.Read(tmpFormat,SizeOf(tmpFormat));

  { read the number of points }
  Stream.Read(tmpCount,Sizeof(tmpCount));

  { read each point }
  NotYMandatory:=MandatoryValueList<>YValues;
  ValuesListCount:=ValuesList.Count;

  BeginUpdate;
  try
    for t:=0 to tmpCount-1 do ReadSeriesPoint(t);
  finally
    EndUpdate;
  end;

  ManualData:=True;
end;

{ internal. Loads data sources from the DFM stream }
procedure TChartSeries.ReadDataSources(Reader: TReader);
begin
  Reader.ReadListBegin;
  FTempDataSources:=TStringList.Create;
  With FTempDataSources do
  begin
    Clear;
    while not Reader.EndOfList do Add(Reader.ReadString);
  end;
  Reader.ReadListEnd;
end;

procedure TChartSeries.WriteData(Stream: TStream);

  {$IFDEF D12WIN32}
  Procedure WriteLabel(Const AString:String);
  var LInt  : Integer;
      LByte : Byte;
      Bytes : TBytes;
  begin
    // Flag for Unicode strings:
    LByte:=255;
    Stream.Write(LByte,SizeOf(LByte));

    Bytes:=TEncoding.Default.GetBytes(AString); // TEncoding.Unicode always?
    LInt:=Length(Bytes);
    Stream.Write(LInt,SizeOf(LInt));
    Stream.Write(Bytes[0],LInt);
  end;
  {$ELSE}

  Procedure WriteLabel(Const AString:String);
  var L : Byte;
  begin
    L:=Length(AString);
    Stream.Write(L,SizeOf(L));
    Stream.Write(PChar(AString)^,L);
  end;
  {$ENDIF}

var tmpFormat      : TeeFormatFlag;
    tmpNoMandatory : TChartValueList;

  Procedure WriteSeriesPoint(Index:Integer);

    procedure WriteImage;
    var tmp : TTeePicture;
        tmpOk : Boolean;
        tmpP : TTeePictureComponent;
    begin
      tmp:=FPictures[Index];
      tmpOk:=tmp<>nil;

      Stream.Write(tmpOk,SizeOf(tmpOk));

      if tmpOk then
      begin
        tmpP:=TTeePictureComponent.CreateFrom(tmp);
        try
          Stream.WriteComponent(tmpP);
        finally
          tmpP.FPicture:=nil;
          tmpP.Free;
        end;
      end;
    end;

  var tmpFloat : Double;
      tt       : Integer;
      tmpColor : TColor;
      APosition : TSeriesMarkPosition;
  begin
    { write the "X" point value, if exists }
    if tfNoMandatory in tmpFormat then
    begin
      tmpFloat:=tmpNoMandatory.Value[Index];
      Stream.Write(tmpFloat,Sizeof(tmpFloat));
    end;

    { write the "Y" point value }
    tmpFloat:=MandatoryValueList.Value[Index];
    Stream.Write(tmpFloat,Sizeof(tmpFloat));

    { write the Color point value, if exists }
    if tfColor in tmpFormat then
    begin
      tmpColor:=InternalColor(Index);
      Stream.Write(tmpColor,Sizeof(tmpColor));
    end;

    { write the Label point value, if exists }
    if tfLabel in tmpFormat then WriteLabel(Labels[Index]);

    { write the rest of values (always) }
    for tt:=2 to ValuesList.Count-1 do
    begin
      tmpFloat:=ValuesList[tt].Value[Index];
      Stream.Write(tmpFloat,SizeOf(tmpFloat));
    end;

    if tfMarkPosition in tmpFormat then
    begin
      if not Assigned(Marks.Positions[Index]) then  // v8
      begin
        APosition:=TSeriesMarkPosition.Create;
        Marks.UsePosition(Index,APosition);
      end;

      With Marks.Positions[Index],Stream do
      begin
        Write(ArrowFrom,SizeOf(ArrowFrom));
        Write(ArrowFix,SizeOf(ArrowFix));
        Write(ArrowTo,SizeOf(ArrowTo));
        Write(Custom,SizeOf(Custom));
        Write(Height,SizeOf(Height));
        Write(LeftTop,SizeOf(LeftTop));
        Write(Width,SizeOf(Width));
      end;
    end;

    if tfPictures in tmpFormat then
       WriteImage;
  end;

var t   : Integer;
    tmp : Integer;
begin
  { write the "flag" containing the format of a point }
  tmpFormat:=SeriesGuessContents(Self);

  Stream.Write(tmpFormat,SizeOf(tmpFormat));

  { write all points. pre-calculate tmpNoMandatory }
  tmpNoMandatory:=NotMandatoryValueList;

  { write the number of points }
  tmp:=Count;
  Stream.Write(tmp,Sizeof(tmp));
  for t:=0 to tmp-1 do
      WriteSeriesPoint(t);
end;

{ internal. stores data sources into the DFM stream }
procedure TChartSeries.WriteDataSources(Writer: TWriter);
var t : Integer;
begin
  With Writer do
  begin
    WriteListBegin;

    With DataSources do
    for t:=0 to Count-1 do
        WriteString(TComponent(Items[t]).Name);

    WriteListEnd;
  end;
end;

{ Returns True (the default), when visible points in the series
  should be drawn in ascending order. }
Function TChartSeries.DrawValuesForward:Boolean;
begin
  if YMandatory then
  begin
    result:=not GetHorizAxis.Inverted;

    if ParentChart.InvertedRotation then
       result:=not result;
  end
  else
     result:=not GetVertAxis.Inverted;
end;

Function TChartSeries.SortDrawSeries(FirstSeries, LastSeries, ValueIndex: Integer):TIntArray;
begin
  result:=nil;
end;

Function TChartSeries.SortDrawPoints(FirstPoint, LastPoint: Integer; SingleSeries:Boolean):TIntArray;
begin
  result:=nil;
end;

{ Returns True (the default), when several series sharing the
  same Z order should be drawn in ascending order (the same order
  the series have inside the SeriesList. }
Function TChartSeries.DrawSeriesForward(ValueIndex:Integer):Boolean;
begin
  result:=not ParentChart.InvertedRotation; { abstract }
end;

function TChartSeries.HasPointLabels:Boolean;
begin
  result:=False;
end;

function TChartSeries.HasLabels:Boolean;
var t : Integer;
begin
  result:=False;

  if FLabels.Count>0 then
     for t:=0 to FLabels.Count-1 do
         if FLabels[t]<>'' then
         begin
           result:=True;
           Exit;
         end;
end;


{ Returns the series Name (or "Seriesxx" index if Name is empty). }
Function SeriesNameOrIndex(const ASeries:TCustomChartSeries):String;
begin
  result:=ASeries.Name;

  if (result='') and Assigned(ASeries.ParentChart) then
     result:=TeeMsg_Series+TeeStr(1+ASeries.SeriesIndex);
end;

{ Calls DrawValue for all points that are visible. }
procedure TChartSeries.DrawAllValues;
var
  tmpCanID : Boolean;

  procedure DoDrawIndex(const Index:Integer);
  begin
    if tmpCanID then
       TCanvas3DAccess(ParentChart.Canvas).BeginEntity(TeeStr(Index),Visuals[Index]);

    DrawValue(Index);

    if tmpCanID then
       TCanvas3DAccess(ParentChart.Canvas).EndEntity;
  end;

  procedure DoDrawHidden(Index:Integer);
  begin
    if tmpCanID then
       TCanvas3DAccess(ParentChart.Canvas).BeginEntity(TeeStr(Index),Visuals[Index]);

    ParentChart.FOnDrawValue(Self,Index);

    if tmpCanID then
       TCanvas3DAccess(ParentChart.Canvas).EndEntity;
  end;

var t : Integer;
    OrderedPoints : TIntArray;
Begin
  tmpCanID:=TCanvas3DAccess(ParentChart.Canvas).SupportsID;

  if tmpCanID then
     if Assigned(Visuals.Template) and
        (not TVisualAccess(Visuals.Template).IsCloneable) then
             tmpCanID:=False;

  OrderedPoints:=SortDrawPoints(FFirstVisibleIndex, FLastVisibleIndex, True);

  if Assigned(OrderedPoints) then
  try
    if Assigned(ParentChart.FOnDrawValue) then
    begin
      if DrawValuesForward then
         for t:=0 to High(OrderedPoints) do DoDrawHidden(OrderedPoints[t])
      else
         for t:=High(OrderedPoints) downto 0 do DoDrawHidden(OrderedPoints[t]);
    end
    else
    begin
      if DrawValuesForward then
         for t:=0 to High(OrderedPoints) do DoDrawIndex(OrderedPoints[t])
      else
         for t:=High(OrderedPoints) downto 0 do DoDrawIndex(OrderedPoints[t]);
    end;
  finally
    OrderedPoints:=nil;
  end
  else
  if Assigned(ParentChart.FOnDrawValue) then
  begin
    if DrawValuesForward then
       for t:=FFirstVisibleIndex to FLastVisibleIndex do
           DoDrawHidden(t)
    else
       for t:=FLastVisibleIndex downto FFirstVisibleIndex do
           DoDrawHidden(t);
  end
  else
  if DrawValuesForward then
  begin
     // Normal "left to right" order
     for t:=FFirstVisibleIndex to FLastVisibleIndex do
         DoDrawIndex(t);
  end
  else
  begin
     // "Right to left"
     for t:=FLastVisibleIndex downto FFirstVisibleIndex do
         DoDrawIndex(t);
  end;
end;

{ tell the Chart to refresh series data using Series.DataSource(s) }
Procedure TChartSeries.CheckDataSource;
begin
  if (IUpdating=0) and Assigned(FParent) then FParent.CheckDataSource(Self);
end;

{ Change the "source" (ie: Table Field name) associated
  to series points colors. }
Procedure TChartSeries.SetColorSource(Const Value:String);
Begin
  if FColorSource<>Value then
  Begin
    FColorSource:=Value;
    CheckDataSource;
  end;
end;

{ Change the "source" (ie: Table Field name) associated
  to series text labels. }
Procedure TChartSeries.SetLabelsSource(Const Value:String);
Begin
  if FLabelsSource<>Value then
  Begin
    FLabelsSource:=Value;
    CheckDataSource;
  end;
end;

{ Returns the point index that is under the XY screen mouse position. }
{ Returns -1 if no points are found under XY }
Function TChartSeries.GetCursorValueIndex:Integer;
Begin
  result:=Clicked(ParentChart.GetCursorPos);
end;

{ Returns the X and Y values that correspond to the mouse
  screen position. }
Procedure TChartSeries.GetCursorValues(out XValue,YValue:TChartValue);
Begin
  With ParentChart.GetCursorPos{$IFDEF FMX}.Round{$ENDIF} do { mouse XY position }
  begin
    { convert XY pixel position to axis scales }
    XValue:=GetHorizAxis.CalcPosPoint(X);
    YValue:=GetVertAxis.CalcPosPoint(Y);
  end;
end;

// Returns the "default" data source component.
// It is the first component in the DataSources list.
Function TChartSeries.GetDataSource:TComponent;
begin
  if Assigned(FDataSources) and (FDataSources.Count>0) then
     result:=FDataSources[0]
  else
     result:=nil;
end;

Function TChartSeries.GetDataSources:TDataSourcesList;
begin
  if not Assigned(FDataSources) then
  begin
    FDataSources:=TDataSourcesList.Create;
    FDataSources.Series:=Self;
  end;

  result:=FDataSources;
end;

function TChartSeries.HoverOverMark:Integer;
begin
  if HoverEnabled and (IClickedPart=scpMark) then
     result:=Selected.HoverIndex
  else
     result:=-1;
end;

function TChartSeries.HoverOverSegment:Integer;
begin
  if HoverEnabled and (IClickedPart=scpSegment) then
     result:=Selected.HoverIndex
  else
     result:=-1;
end;

{ add the Value component as datasource. if Value is a Series,
  add ourselves to Value list of "linked" series }
Function TChartSeries.InternalAddDataSource(const Value:TComponent):Integer;
var Old : Boolean;
begin
  if Assigned(Value) then
  begin
    result:=DataSources.InheritedAdd(Value);

    ManualData:=False;

    if Value is TChartSeries then
       TChartSeries(Value).AddLinkedSeries(Self)
    else
    begin
      Value.FreeNotification(Self);

      { set ourselves as the Series for Value DataSource }
      if Value is TTeeSeriesSource then
      with TTeeSeriesSource(Value) do
      begin
        if FSeries<>Self then
        begin
          Old:=Active;

          Close;

          if Value.Owner=FSeries then
          begin
            FSeries.RemoveComponent(Value);
            Self.InsertComponent(Value);
          end;

          if Assigned(FSeries) then
             FSeries.RemoveFreeNotification(Value);

          FSeries:=Self;

          FSeries.FreeNotification(Value);

          if Old then
             Open;
        end;
      end;
    end;
  end
  else
    result:=-1;
end;

{ When this series is a datasource for other series,
  this method tells the other series to remove the reference to itself. }
Procedure TChartSeries.RemoveAllLinkedSeries;
var t : Integer;
begin
  if Assigned(FDataSources) then
     for t:=0 to FDataSources.Count-1 do
     if Assigned(FDataSources[t]) and
        ({$IFNDEF D18}TObject{$ENDIF}(FDataSources[t]) is TChartSeries) then
           TChartSeries(FDataSources[t]).RemoveLinkedSeries(Self);
end;

Function TChartSeries.InternalSetDataSource(const Value:TComponent; ClearAll:Boolean):Integer;

  Function DoSetDataSource:Integer;
  Begin
    result:=-1;

    if DataSources.IndexOf(Value)=-1 then
    begin
      if Value is TChartSeries then
         CheckOtherSeries(TChartSeries(Value));

      if ClearAll and (not (csLoading in ComponentState)) then
         DataSources.Clear;

      result:=InternalAddDataSource(Value);

      CheckDataSource;
    end;
  end;

var tmp : String;
begin
  if not Assigned(FParent) then
     if IsValidDataSource(Value) then
        result:=DoSetDataSource
     else
        raise ChartException.Create(TeeMsg_SeriesSetDataSource)
  else
  if FParent.IsValidDataSource(Self,Value) then
     result:=DoSetDataSource
  else
  begin
    tmp:=Value.Name;
    if tmp='' then
       tmp:=Value.ClassName;

    raise ChartException.CreateFmt(TeeMsg_SeriesInvDataSource,[tmp]);
  end;
end;

Procedure TChartSeries.InternalRemoveDataSource(const Value:TComponent);
Begin
  {$IFNDEF TEEOCX}
  if not (csDestroying in ComponentState) then
  {$ENDIF}
  begin

    if Assigned(Value) then
       Value.RemoveFreeNotification(Self); // TV52012428

    if Assigned(FParent) and
       Assigned(DataSource) then
         FParent.RemovedDataSource(Self,Value);

    if Value=DataSource then
    begin
      {$IFDEF TEEOCX}
      if Assigned(DataSource) then
      {$ENDIF}
         if Assigned(FDataSources) then  // DB
            FDataSources.Clear;

      CheckDataSource;
    end
    else
    // Special case:  Call CheckDataSource anyway, as the Series might have
    // no DataSources but FunctionType "NoSourceRequired".
    if (Assigned(FunctionType) and FunctionType.NoSourceRequired) or
       (Assigned(FDataSources) and (FDataSources.IndexOf(Value)<>-1)) then
       CheckDataSource;
  end
  {$IFNDEF TEEOCX}
  else
  if Assigned(FDataSources) and (FDataSources.IndexOf(Value)<>-1) then
     FDataSources.Remove(Value);
  {$ENDIF}
end;

{ Replaces the DataSource for the series. }
Procedure TChartSeries.SetDataSource(const Value:TComponent);
begin
  if Assigned(Value) then
     InternalSetDataSource(Value,True)
  else
     InternalRemoveDataSource(DataSource);
end;

Function TChartSeries.IsValidSourceOf(Value:TChartSeries):Boolean;
Begin
  result:=Value<>Self; { abstract }
end;

Function TChartSeries.IsValidDataSource(const Value:TComponent):Boolean;
Begin
  result:=(Self<>Value) and
          (Value is TChartSeries) and
          IsValidSeriesSource(TChartSeries(Value));
end;

Function TChartSeries.IsValidSeriesSource(Value:TChartSeries):Boolean;
Begin
  result:=(not Assigned(FTeeFunction)) or FTeeFunction.IsValidSource(Value); { abstract }
end;

{ Returns the value list that has the AListName parameter. }
Function TChartSeries.GetYValueList(AListName:String):TChartValueList;
var t : Integer;
Begin
  AListName:=AnsiUppercase(AListName);

  for t:=2 to FValuesList.Count-1 do
  if AListName=AnsiUpperCase(FValuesList[t].Name) then
  Begin
    result:=FValuesList[t];
    exit;
  end;

  result:=MandatoryValueList;
end;

Procedure TChartSeries.CheckOtherSeries(const Source:TChartSeries);
Begin
  if Assigned(FParent) then FParent.CheckOtherSeries(Self,Source);
end;

{ Replaces the colors for points in the value list that
  have values between FromValue and ToValue. }
Procedure TChartSeries.ColorRange( const AValueList:TChartValueList;
                                   Const FromValue,ToValue:TChartValue; AColor:TColor);
var t        : Integer;
    tmpValue : TChartValue;
begin
  for t:=0 to AValueList.Count-1 do
  Begin
    tmpValue:=AValueList.Value[t];

    if (tmpValue>=FromValue) and (tmpValue<=ToValue) and (not IsNull(t)) then
        ValueColor[t]:=AColor;
  end;

  Repaint;
end;

{ Returns the point index that is under the X Y pixel screen position. }
Function TChartSeries.Clicked(x,y:Integer):Integer;
begin
  Result:=TeeNoPointClicked; { abstract, no point clicked }
end;

Function TChartSeries.Clicked(const X,Y:Single):Integer;
var tmpX,
    tmpY : Integer;
begin
  tmpX:=Round(X);
  tmpY:=Round(Y);

  result:=Clicked(tmpX,tmpY); // Use tmp variables to avoid re-entrancy

  if result<>-1 then
     IClickedPart:=scpSegment;
end;

Function TChartSeries.Clicked(const P:TPoint):Integer;
begin
  result:=Clicked(P.X,P.Y);

  if result<>-1 then
     IClickedPart:=scpSegment;
end;

{ Obtains pointers to the associated axes.
  This is done to optimize speed (cache) when locating the
  associated axes. }
Procedure TChartSeries.RecalcGetAxis;
Begin
  if Assigned(FParent) then
  begin
    { horizontal axis }
    FGetHorizAxis:=FParent.FBottomAxis;
    Case FHorizAxis of
      aTopAxis        : FGetHorizAxis:=FParent.FTopAxis;
      aCustomHorizAxis: if Assigned(FCustomHorizAxis) then FGetHorizAxis:=FCustomHorizAxis;
    end;

    { vertical axis }
    FGetVertAxis:=FParent.LeftAxis;
    Case FVertAxis of
      aRightAxis     : FGetVertAxis:=FParent.FRightAxis;
      aCustomVertAxis: if Assigned(FCustomVertAxis) then FGetVertAxis:=FCustomVertAxis;
    end;
  end
  else
  begin
    FGetHorizAxis:=nil;
    FGetVertAxis:=nil;
  end;
end;

{ internal. read custom horizontal axis from DFM stream }
procedure TChartSeries.ReadCustomHorizAxis(Reader: TReader);
begin
  CustomHorizAxis:=FParent.CustomAxes[Reader.ReadInteger];
end;

{ internal. read custom vertical axis from DFM stream }
procedure TChartSeries.ReadCustomVertAxis(Reader: TReader);
begin
  CustomVertAxis:=FParent.CustomAxes[Reader.ReadInteger];
end;

{ internal. store custom horizontal axis into DFM stream }
procedure TChartSeries.WriteCustomHorizAxis(Writer: TWriter);
begin
  Writer.WriteInteger(FCustomHorizAxis.Index);
end;

{ internal. store custom vertical axis into DFM stream }
procedure TChartSeries.WriteCustomVertAxis(Writer: TWriter);
begin
  Writer.WriteInteger(FCustomVertAxis.Index);
end;

{ change the series horizontal axis }
Procedure TChartSeries.SetHorizAxis(const Value:THorizAxis);
Begin
  if FHorizAxis<>Value then
  begin
    FHorizAxis:=Value;

    if (HorizAxis<>aCustomHorizAxis) and (not (csLoading in ComponentState)) then
       FCustomHorizAxis:=nil;

    RecalcGetAxis;
    Repaint;
  end;
end;

{ change the series vertical axis }
Procedure TChartSeries.SetVertAxis(const Value:TVertAxis);
Begin
  if FVertAxis<>Value then
  begin
    FVertAxis:=Value;

    if (VertAxis<>aCustomVertAxis) and (not (csLoading in ComponentState)) then
       FCustomVertAxis:=nil;

    RecalcGetAxis;
    Repaint;
  end;
end;

Procedure TChartSeries.SetChartValueList( const AValueList:TChartValueList;
                                          const Value:TChartValueList);
Begin
  AValueList.Assign(Value);
  Repaint;
end;

Procedure TChartSeries.SetXValues(const Value:TChartValueList);
Begin
  SetChartValueList(FX,Value);
end;

Procedure TChartSeries.SetYValues(const Value:TChartValueList);
Begin
  SetChartValueList(FY,Value);
end;

Function TChartSeries.ValueListOfAxis(const Axis:TChartAxis):TChartValueList;
begin
  if AssociatedToAxis(Axis) then
     if Axis.Horizontal then result:=FX
                        else result:=FY
  else
     result:=nil;
end;

{ Calculates the range of points that are visible.
  This depends on the series associated vertical and horizontal axis
  scales (minimum and maximum).  }
Procedure TChartSeries.CalcFirstLastVisibleIndex;

  Function CalcMinMaxValue(A,B,C,D:Integer):TAxisValue;
  begin
   if YMandatory then
   With GetHorizAxis do
   begin
     if (MaximumOffset <> 0) or (MinimumOffset <> 0) then  //CDI TF02014319
     begin
       if A > C then
       begin
          if Inverted then
             result:=CalcPosPoint(C - MaximumOffset + 1)  //+1, otherwise some x values are not painted under some circumstances due to round issues
          else
             result:=CalcPosPoint(A - MaximumOffset + 1);
       end
       else
       begin
         if Inverted then
            result:=CalcPosPoint(C + MinimumOffset - 1)  //-1, idem
         else
            result:=CalcPosPoint(A + MinimumOffset - 1);
       end;
     end
     else
     begin
       if Inverted then
          result:=CalcPosPoint(C)
       else
          result:=CalcPosPoint(A);
     end;
   end
   else
   With GetVertAxis do
   begin
     if (MaximumOffset <> 0) or (MinimumOffset <> 0) then
     begin
       if B > D then
          if Inverted then
             result:=CalcPosPoint(B + MinimumOffset)
          else
             result:=CalcPosPoint(D + MinimumOffset)
       else
       begin
         if Inverted then
            result:=CalcPosPoint(B - MaximumOffset)
         else
            result:=CalcPosPoint(D - MaximumOffset);
       end;
     end
     else
     if Inverted then
        result:=CalcPosPoint(B)
     else
        result:=CalcPosPoint(D);
   end;
  end;

var tmpMin       : TAxisValue;
    tmpMax       : TAxisValue;
    tmpLastIndex : Integer;
Begin
  FFirstVisibleIndex:=-1;
  FLastVisibleIndex:=-1;

  if Count>0 then
  Begin
    tmpLastIndex:=Count-1;

    if CalcVisiblePoints and (NotMandatoryValueList.Order<>loNone) then
    begin

      { NOTE: The code below does NOT use a "divide by 2" (bubble)
              algorithm because the tmpMin value might not have any
              correspondence with a Series point.
              When the Series point values are "floating" (not integer)
              the "best" solution is to do a lineal all-traverse search.

              However, this code can still be optimized.
              It will be revisited for next coming releases.
      }


      With ParentChart.ChartRect{$IFDEF FMX}.Round{$ENDIF} do
           tmpMin:=CalcMinMaxValue(Left,Top,Right,Bottom);

      FFirstVisibleIndex:=0;

      if NotMandatoryValueList.First <= tmpMin then  //TF02014701
      begin
        while NotMandatoryValueList.Value[FFirstVisibleIndex]<tmpMin do
        Begin
          Inc(FFirstVisibleIndex);

          if FFirstVisibleIndex>tmpLastIndex then
          begin
            FFirstVisibleIndex:=-1;
            break;
          end;
        end;
      end;

      if FFirstVisibleIndex>=0 then
      Begin
        With ParentChart.ChartRect{$IFDEF FMX}.Round{$ENDIF} do
             tmpMax:=CalcMinMaxValue(Right,Bottom,Left,Top);

        if NotMandatoryValueList.Last<=tmpMax then
           FLastVisibleIndex:=tmpLastIndex
        else
        Begin
          FLastVisibleIndex:=FFirstVisibleIndex;

          //TF02014811
          while (FLastVisibleIndex < tmpLastIndex) and (NotMandatoryValueList.Value[FLastVisibleIndex]<tmpMax) do
             Inc(FLastVisibleIndex);

          while (not DrawBetweenPoints) and
             (FLastVisibleIndex > 0) and
             (NotMandatoryValueList.Value[FLastVisibleIndex]>tmpMax) do
                Dec(FLastVisibleIndex);
        end;
      end;
    end
    else
    begin
      FFirstVisibleIndex:=0;
      FLastVisibleIndex:=tmpLastIndex;
    end;
  end;
end;

procedure TChartSeries.DrawValue(ValueIndex:Integer);
begin { nothing }
end;

procedure TChartSeries.DrawHiddenValue(ValueIndex:Integer;
                                       const StartValue,EndValue:TAxisValue;
                                       GapSize:Integer;
                                       Axis:TChartAxis);
begin  {nothing}
end;

Function TChartSeries.IsValueFormatStored:Boolean;
Begin
  result:=FValueFormat<>TeeMsg_DefValueFormat;
end;

Function TChartSeries.IsDataSourceStored:Boolean;
Begin
  result:=DataSources.Count=1;;
end;

Function TChartSeries.IsPercentFormatStored:Boolean;
Begin
  result:=FPercentFormat<>TeeMsg_DefPercentFormat;
end;

Procedure TChartSeries.DeletedValue(Source:TChartSeries; ValueIndex:Integer);
Begin
  Delete(ValueIndex);
end;

Function TChartSeries.AddChartValue(Source:TChartSeries; ValueIndex:Integer):Integer;
var t    : Integer;
    tmp  : Integer;
    tmpX : TChartValue;
    tmpY : TChartValue;
    tmpValue : TChartValue;
begin
  tmpX:=Source.FX.Value[ValueIndex];
  tmpY:=Source.FY.Value[ValueIndex];

  { if we are adding values from (for example) an Horizontal Bar series... }
  if YMandatory<>Source.YMandatory then
  begin
    tmpValue:=tmpX;
    tmpX:=tmpY;
    tmpY:=tmpValue;
  end;

  { pending: if ...FY.Order<>loNone then (inverted) }
  result:=FX.AddChartValue(tmpX);
  FY.InsertChartValue(result,tmpY);

  { rest of lists... }
  tmp:=Source.ValuesList.Count-1;

  for t:=2 to FValuesList.Count-1 do
  begin
    if t<=tmp then
       tmpY:=Source.FValuesList[t].Value[ValueIndex]
    else
       tmpY:=0;

    FValuesList[t].InsertChartValue(result,tmpY);
  end;
end;

{ Called when a new value is added to the series. }
Procedure TChartSeries.AddedValue(Source:TChartSeries; ValueIndex:Integer);
var tmpIndex : Integer;
    tmpSource,
    tmpDetail : TChartSeries;
Begin
  tmpIndex:=AddChartValue(Source,ValueIndex);

  if Assigned(Source.FColors) and (Source.FColors.Count>ValueIndex) then
  begin
    if not Assigned(FColors) then
       GrowColors;

    FColors.Insert(tmpIndex,Source.FColors[ValueIndex]);
  end;

  if Source.FLabels.Count>ValueIndex then
     Labels.InsertLabel(tmpIndex,Source.Labels[ValueIndex]);

  if Source.FPictures<>nil then
     if Source.FPictures.Count>ValueIndex then
        Pictures.Insert(tmpIndex,TTeePictureList.Copy(Source.Pictures[ValueIndex]));

  // Visuals
  // if Source.FVisuals.Count>ValueIndex then
  //    Visuals.InsertVisual(tmpIndex,Clone(Source.Visuals[ValueIndex]));

  if Assigned(Source.FDetail) and (Source.FDetail.Count>ValueIndex) then
  begin
    tmpSource:=Source.FDetail[ValueIndex];
    tmpDetail:=TChartSeriesClass(tmpSource.ClassType).Create(Self);
    tmpDetail.Assign(tmpSource);

    Detail.Insert(tmpIndex,tmpDetail);
  end;

  if IUpdating=0 then // v8
     NotifyNewValue(Self,tmpIndex);
end;

Function TChartSeries.LegendToValueIndex(LegendIndex:Integer):Integer;
begin
  result:=LegendIndex;
end;

Function TChartSeries.ValueIndexToLegend(ValueIndex:Integer):Integer;
begin
  result:=ValueIndex;
end;

{ Returns the formatted string corresponding to the LegendIndex
  point. The string is used to show at the Legend.
  Uses the LegendTextStyle property to do the appropiate formatting.
}
Function TChartSeries.LegendString( LegendIndex:Integer;
                                    LegendTextStyle:TLegendTextStyle ):String;
var ValueIndex : Integer;

  Function CalcXValue:String;
  var tmpAxis : TChartAxis;
  begin
    tmpAxis:=GetHorizAxis;
    if Assigned(tmpAxis) then
       result:=tmpAxis.LabelValue(XValues.Value[ValueIndex])
    else
       result:=FormatFloat(ValueFormat,XValues.Value[ValueIndex])
  end;

var tmpValue : TChartValue;

  Function ValueToStr:String;
  begin
    if MandatoryValueList.DateTime then
       DateTimeToString(result,DateTimeDefaultFormat(tmpValue),tmpValue)
    else
       result:=FormatFloat(ValueFormat,tmpValue);
  end;

  Function CalcPercentSt:String;
  begin
    With MandatoryValueList do
    if TotalAbs=0 then
       result:=FormatFloat(PercentFormat,100)
    else
       result:=FormatFloat(PercentFormat,100.0*tmpValue/TotalAbs);
  end;

begin
  ValueIndex:=CalcLegendIndex(LegendIndex);
  result:=Labels[ValueIndex];

  if LegendTextStyle<>ltsPlain then
  begin
    tmpValue:=GetMarkValue(ValueIndex);

    Case LegendTextStyle of
      ltsLeftValue   : begin
                         if result<>'' then result:=TeeColumnSeparator+result;
                         result:=ValueToStr+result;
                       end;
      ltsRightValue  : begin
                         if result<>'' then result:=result+TeeColumnSeparator;
                         result:=result+ValueToStr;
                       end;
      ltsLeftPercent : begin
                         if result<>'' then
                            result:=TeeColumnSeparator+result;

                         result:=CalcPercentSt+result;
                       end;
      ltsRightPercent: begin
                         if result<>'' then
                            result:=result+TeeColumnSeparator;

                         result:=result+CalcPercentSt;
                       end;
      ltsXValue      : result:=CalcXValue;
      ltsValue       : result:=ValueToStr;
      ltsPercent     : result:=CalcPercentSt;
      ltsXandValue   : result:=CalcXValue+TeeColumnSeparator+ValueToStr;
      ltsXandPercent : result:=CalcXValue+TeeColumnSeparator+CalcPercentSt;
      ltsXAndText    : result:=CalcXValue+TeeColumnSeparator+result;
 ltsXandValueAndText : result:=CalcXValue+TeeColumnSeparator+ValueToStr+TeeColumnSeparator+result;
    end;
  end;
end;

{ Clears and adds all points values from Source series. }
procedure TChartSeries.AddValues(Source:TChartSeries);
var t : Integer;
begin
  BeginUpdate; // before Clear
  try
    Clear;

    if FunctionType=nil then { "Copy Function", copy values... }
    begin
      if IsValidSourceOf(Source) then
         for t:=0 to Source.Count-1 do
             AddedValue(Source,t)
    end
    else
    begin
      XValues.DateTime:=Source.XValues.DateTime;
      YValues.DateTime:=Source.YValues.DateTime;
      FunctionType.AddPoints(Source); { calculate function }
    end;

  finally
    EndUpdate; // propagate changes...
  end;
end;

{ Copies (adds) all values from Source series. }
Procedure TChartSeries.AssignValues(const Source:TChartSeries);
begin
  AddValues(Source);
end;

{ Notifies all series and tools that data (points) has changed. }
procedure TChartSeries.RefreshSeries;
begin
  NotifyValue(veRefresh,0);

  if Assigned(ParentChart) then
     ParentChart.BroadcastSeriesEvent(Self,seDataChanged);
end;

// Raises OnClick or OnDblClick events.
// Derived series might call OnClickPointer event.
procedure TChartSeries.MouseDown(ValueIndex:Integer; AtPointer:Boolean;
                                 Button:TMouseButton;
                                 Shift: TShiftState; X, Y: Integer);
begin
  if ssDouble in Shift then
  begin
    if Assigned(OnDblClick) then
       OnDblClick(Self,ValueIndex,Button,Shift,x,y)
  end
  else
  if Assigned(OnClick) then
     OnClick(Self,ValueIndex,Button,Shift,x,y);
end;

{ Returns True if there are more series that share the same Z order.
  For example: Stacked Bars. }
Function TChartSeries.MoreSameZOrder:Boolean;
var tmpSeries : TChartSeries;
    t         : Integer;
Begin
  if FParent.ApplyZOrder then  { fix 4.02 }
  for t:=0 to FParent.SeriesCount-1 do
  Begin
    tmpSeries:=FParent.Series[t];

    if tmpSeries<>Self then
    With tmpSeries do
    if FActive and (not HasZValues) and (ZOrder=Self.ZOrder) then
    Begin
      result:=True;
      exit;
    end;
  end;

  result:=False;
End;

// Returns True when series should be drawn in ascending order (0 to SeriesCount-1).
// Overriden in Bar Series when MultiBar = mbSideAll
function TChartSeries.DrawSeriesAscending:Boolean;
begin
  with ParentChart do
  begin
    result:=not DepthAxis.Inverted;

    if View3D and not Canvas.SupportsFullRotation and
       (View3DOptions.Rotation>=90) and (View3DOptions.Rotation<270) then
       result:=not result;
  end;
end;

{ Returns True if the series is the first one with the same Z order.
  Some series allow sharing the same Z order (example: Stacked Bars). }
Function TChartSeries.FirstInZOrder:Boolean;
var tmpSeries : TChartSeries;
    t         : Integer;
Begin
  if FActive then
  begin
    result:=True;
    for t:=0 to FParent.SeriesCount-1 do
    Begin
     tmpSeries:=FParent.Series[t];
     if tmpSeries=Self then break
     else
       With tmpSeries do
       if FActive and (ZOrder=Self.ZOrder) then
       Begin
         result:=False;
         break;
       end;
    end;
  end
  else result:=False;
end;

Procedure TChartSeries.BeginEntity;
begin
  if Assigned(Transform) then
  begin
    Transform.Origin.X:=ParentChart.ChartXCenter;
    Transform.Origin.Y:=ParentChart.ChartYCenter;
  end;

  TCanvas3DAccess(ParentChart.Canvas).BeginEntity(SeriesNameOrIndex(Self),nil,Transform);
end;

Procedure TChartSeries.BroadcastEvent(AEvent:TChartToolEvent);
var t : Integer;
begin
  if Assigned(ParentChart) then
  with ParentChart.Tools do
     for t:=0 to Count-1 do
       with Items[t] do
            if Active then
               SeriesEvent(AEvent, Self);
end;

// Default is False.
// Some series (ie: TFastLineSeries) will return True if there is the
// possibility of doing multi-thread (multi CPU) parallel processing
function TChartSeries.CanBeforeParallel:Boolean;
begin
  result:=False;
end;

// Default is empty. See CanBeforeParallel.
procedure TChartSeries.DoBeforeParallel;
begin
end;

{ Calls the BeforeDrawValues event. }
procedure TChartSeries.DoBeforeDrawValues;
Begin
  BroadcastEvent(cteBeforeDrawSeries);

  if Assigned(FBeforeDrawValues) then
     FBeforeDrawValues(Self);
end;

{ Calls the AfterDrawValues event. }
procedure TChartSeries.DoAfterDrawValues;
begin
  BroadcastEvent(cteAfterDrawSeries);

  if Assigned(FAfterDrawValues) then
     FAfterDrawValues(Self);
end;

Function TChartSeries.CalcLegendIndex(LegendIndex:Integer):Integer;
begin
  result:=LegendIndex;
end;

procedure TChartSeries.CalcSelectionPos(ValueIndex:Integer; out X,Y:Integer);
begin
  X:=CalcXPos(ValueIndex);
  Y:=CalcYPos(ValueIndex);
end;

function TChartSeries.MarkOverlap(ValueIndex:Integer; const A,B:TRect):TPoint;
begin
  if YMandatory then
  begin
    result.X:=0;

    if A.Top<B.Top then
       result.Y:=-(A.Bottom-B.Top)
    else
       result.Y:=-(A.Top-B.Bottom);
  end
  else
  begin
    result.Y:=0;

    if A.Left<B.Left then
       result.X:=-(A.Right-B.Left)
    else
       result.X:=-(A.Left-B.Right);
  end;
end;

procedure TChartSeries.CalculateMarkPosition(const Shape:TTeeCustomShape;
                                             const AText:String;
                                             XPos,YPos:Integer;
                                             var APosition:TSeriesMarkPosition);
var tmpW     : Integer;
    tmpH     : Integer;
    tmpPenW  : Integer;
    tmpFontH : Integer;
begin
  // Do not change "Text", use private "FText" to avoid trigger ChangedText:
  TCustomTextShape(Shape).FText:=AText;

  TCustomTextShape(Shape).CalcBounds(FParent);

  with FParent do
  begin
    tmpW:={$IFDEF FMX}Round{$ENDIF}(Shape.Width);
    tmpH:={$IFDEF FMX}Round{$ENDIF}(Shape.Height);

    tmpFontH:=tmpH;

    if Assigned(FMarks.FSymbol) and FMarks.FSymbol.ShouldDraw then
    begin
      Inc(tmpW,tmpFontH-4);

      if FMarks.FSymbol.Pen.Width>1 then
      begin
        tmpPenW:={$IFDEF FMX}Round{$ENDIF}(FMarks.FSymbol.Pen.Width);
        Inc(tmpW,tmpPenW);
        Inc(tmpH,tmpPenW);
      end;
    end;

    APosition.Width:=tmpW;
    APosition.Height:=tmpH;

    if Assigned(FMarks.FSymbol) then
    with FMarks.FSymbol.ShapeBounds do
    begin
      Top:=0;
      Bottom:=tmpFontH;
      Left:=0;
      Right:=Bottom;
    end;

    Marks.Positions.MoveTo(APosition,XPos,YPos);
  end;
end;

function TChartSeries.CanDrawMark(ValueIndex:Integer):Boolean;
begin
  result:=not IsNull(ValueIndex);

  if result and FMarks.SoftClip then
     result:=IsPointInChartRect(ValueIndex);
end;

function TChartSeries.ShouldHoverMark(ValueIndex:Integer):Boolean;

  function IsNext: Boolean;
  begin
    result:=(heNext in HoverElement) and (Selected.HoverIndex=Pred(ValueIndex));
  end;

  function IsPrevious: Boolean;
  begin
    result:=(hePrevious in HoverElement) and (Selected.HoverIndex=ValueIndex);
  end;

begin
  result:=(HoverOverMark=ValueIndex) and (heCurrent in HoverElement);
  result:=result or ((HoverOverSegment<>-1) and (IsPrevious or IsNext) and
          (heJoin in HoverElement));
end;

{ Draws all Marks for all points in the series that are visible. }
procedure TChartSeries.DrawMarks;
var
  tmpEvery : Integer;

  function DrawThisMark(AIndex:Integer):Boolean;
  begin
    result:=((AIndex mod tmpEvery)=0) and CanDrawMark(AIndex);
  end;

var t         : Integer;
    tt        : Integer;
    St        : String;
    APosition : TSeriesMarkPosition;
    tmpMark   : TTeeCustomShape;
    tmpTransp : Boolean;
    tmpCanID  : Boolean;
    tmpShape  : TSeriesMarkShape;
    tmpBlend  : TTeeBlend;
    tmpOld    : TTeeFont;
Begin
  tmpCanID:=TCanvas3DAccess(ParentChart.Canvas).SupportsID;

  APosition:=TSeriesMarkPosition.Create;

  tmpTransp:=Marks.UseSeriesTransparency and (Transparency>0);

  if tmpTransp then
     tmpBlend:=ParentChart.Canvas.BeginBlending(TeeZeroRect,Transparency)
  else
     tmpBlend:=nil;

  try
    if FFirstVisibleIndex>-1 then
    begin
      tmpEvery:=FMarks.CalcEvery;

      for t:=FFirstVisibleIndex to FLastVisibleIndex do
      if DrawThisMark(t) then
      begin
        St:=GetMarkText(t);

        if St<>'' then
        Begin
          tmpMark:=FMarks.MarkItem(t);

          if tmpMark.Visible then
          begin
            for tt:=0 to FMarks.Children.Count-1 do
            begin
              tmpShape:=TSeriesMarkShape(FMarks.Children[tt].Shape);
              tmpShape.Text:=GetMarkTextStyle(FMarks.MultiLine,t,tmpShape.Style);
            end;

            if ShouldHoverMark(t) then
            begin
              tmpOld:=TTeeFont.Create;
              tmpOld.Assign(tmpMark.Font);

              tmpMark.Font.Style:=Selected.Hover.Font.Style;
              // Font.Color, Size, etc ?
            end
            else
              tmpOld:=nil;

            CalculateMarkPosition(tmpMark,St,CalcXPos(t),CalcYPos(t),APosition);

            if Assigned(tmpOld) then
            begin
              tmpMark.Font:=tmpOld;
              tmpOld.Free;
            end;

            // Pending: DrawMark should just accumulate/store Mark, not draw it.

            if tmpCanID then
               TCanvas3DAccess(ParentChart.Canvas).BeginEntity(TeeStr(t));

            DrawMark(t,St,APosition);

            if tmpCanID then
               TCanvas3DAccess(ParentChart.Canvas).EndEntity;
          end;
        end;
      end;
    end;

    // Pending: Second loop here for Automatic marks, do real draw here.

  finally
    APosition.Free;
  end;

  if tmpTransp then
     ParentChart.Canvas.EndBlending(tmpBlend);
end;

{ Draws a point Mark using the APosition coordinates. }
Procedure TChartSeries.DrawMark( ValueIndex:Integer; Const St:String;
                                 APosition:TSeriesMarkPosition);
Begin
  if FMarks.Automatic.Move then
     if not FMarks.AntiOverlap(FirstValueIndex,ValueIndex,APosition) then
        Exit; // Dont draw if there's no way to anti-overlap

  FMarks.InternalDraw(ValueIndex,ValueColor[ValueIndex],St,APosition);
end;

{ Triggers the OnAfterAdd event when a new point is added. }
Procedure TChartSeries.NotifyNewValue(Sender:TChartSeries; ValueIndex:Integer);
Begin
  if Assigned(FOnAfterAdd) then
     FOnAfterAdd(Sender,ValueIndex);

  NotifyValue(veAdd,ValueIndex);

  if FActive then
     Repaint;
end;

// Returns the index of the first displayed point.
// When the chart is rotated, the first point displayed might be
// the last "visible" in the series.
Function TChartSeries.FirstDisplayedIndex:Integer;
begin
  if DrawValuesForward then result:=FirstValueIndex
                       else result:=LastValueIndex;
end;

// Returns the index of the series point that is
// displayed last when the series is drawn.
Function TChartSeries.LastDisplayedIndex:Integer;
begin
  if DrawValuesForward then result:=LastValueIndex
                       else result:=FirstValueIndex;
end;

{ Returns True is the ValueIndex point in the Series is Null. }
Function TChartSeries.IsNull(ValueIndex:Integer):Boolean;
begin
  result:={Assigned(FColors) and} (ValueColor[ValueIndex]=clNone);
end;

{ Adds a new Null (missing) point to the series, with specified Value. }
Function TChartSeries.AddNull(Const Value:TChartValue):Integer;
begin
  result:=Add(Value,'',clNone);
end;

{ Adds a new Null (missing) point to the series.
  The ALabel parameter is optional. }
Function TChartSeries.AddNull(Const ALabel:String):Integer;
begin
  result:=Add(DefaultNullValue,ALabel,clNone);
end;

{ Adds a new Null (missing) point to the Series with the specified X and
  Y values.
  The ALabel parameter is optional. }
Function TChartSeries.AddNullXY(Const X,Y:TChartValue; Const ALabel:String):Integer;
begin
  result:=AddXY(X,Y,ALabel,clNone);
end;

{$IFDEF D12}
Function TChartSeries.AddArray(Const Values:Array of Integer):Integer;
var t : Integer;
begin
  BeginUpdate;
  try
    result:=High(Values)-Low(Values)+1;
    for t:=Low(Values) to High(Values) do Add(Values[t]);
  finally
    EndUpdate;
  end;
end;
{$ENDIF}

{ Adds the Values array parameter into the series. }
Function TChartSeries.AddArray(Const Values:Array of TChartValue):Integer;
var t : Integer;
begin
  BeginUpdate;
  try
    result:=Length(Values);

    for t:=Low(Values) to High(Values) do
        Add(Values[t]);
  finally
    EndUpdate;
  end;
end;

{ Adds the XValues and YValues arrays parameter into the series. }
Function TChartSeries.AddArray(Const XValues, YValues:Array of TChartValue):Integer;
var t : Integer;
begin
  BeginUpdate;
  try
    result:=High(XValues)-Low(XValues)+1;
    for t:=Low(XValues) to High(XValues) do AddXY(XValues[t], YValues[t]);
  finally
    EndUpdate;
  end;
end;

// Adds an array of Detail values. The ALabel and AColor parameters are optional.
function TChartSeries.Add(const ADetail:Array of TChartValue;
                          const ALabel:String;
                          AColor:TColor):Integer;
var tmp : TChartSeries;
    tmpOld : TSeriesRecalcOptions;
begin
  // Make sure Detail has enough series
  while Detail.Count<Count do
        FDetail.CreateNew(Self);

  tmp:=FDetail.CreateNew(Self);
  tmp.Title:=ALabel;

  tmpOld:=tmp.RecalcOptions;
  tmp.RecalcOptions:=[];
  try
    tmp.AddArray(ADetail);

    result:=Add(FDetail.Calculate(tmp.MandatoryValueList),ALabel,AColor);

    // Attempt at least to set an initial color
    tmp.Color:=GetValueColor(result);

    // Notify listeners
    RefreshSeries;
  finally
    tmp.RecalcOptions:=tmpOld;
  end;
end;

// Adds a new point to the Series. The ALabel and AColor parameters are optional.
// Returns index of newly added point.
Function TChartSeries.Add(Const AValue:TChartValue; Const ALabel:String; AColor:TColor):Integer;
Begin
  if YMandatory then { speed opt. }
     result:=AddXY(FX.Count, AValue, ALabel, AColor )
  else
     result:=AddXY(AValue, FY.Count, ALabel, AColor );
end;

Function TChartSeries.AddX(Const AXValue:TChartValue; Const ALabel:String; AColor:TColor):Integer;
Begin
  result:=AddXY(AXValue,FX.Count,ALabel,AColor);
end;

Function TChartSeries.AddY(Const AYValue:TChartValue; Const ALabel:String; AColor:TColor):Integer;
Begin
  result:=AddXY(FX.Count,AYValue,ALabel,AColor);
end;

{ Adds a new point into the Series. }
Function TChartSeries.AddXY( Const AXValue,AYValue:TChartValue;
                             Const ALabel:String; AColor:TColor):Integer;
var t : Integer;
Begin
  FX.TempValue:=AXValue;
  FY.TempValue:=AYValue;

  if (not Assigned(FOnBeforeAdd)) or FOnBeforeAdd(Self) then
  Begin
    if YMandatory then
    begin
      result:=FX.AddChartValue;
      FY.InsertChartValue(result,FY.TempValue);
    end
    else
    begin
      result:=FY.AddChartValue;
      FX.InsertChartValue(result,FX.TempValue);
    end;

    for t:=2 to ValuesList.Count-1 do
        with ValuesList[t] do InsertChartValue(result,TempValue);

    if Assigned(FColors) then
       FColors.Insert(result,{$IFNDEF D18}Pointer{$ENDIF}(AColor))
    else
    if AColor<>clTeeColor then
    begin
      GrowColors;
      FColors.Insert(result,{$IFNDEF D18}Pointer{$ENDIF}(AColor));
    end;

    if (ALabel<>'') or (FLabels.Count>0) then
       Labels.InsertLabel(result,ALabel);

    if FPictures<>nil then
       FPictures.Insert(result,nil);

    if IUpdating=0 then
       NotifyNewValue(Self,result);
  end
  else result:=-1;
end;

Function TChartSeries.Count:Integer; { <-- Total number of points in the series }
Begin
  result:={$IFDEF D9}XValues{$ELSE}FX{$ENDIF}.Count;
end;

{ Virtual. Returns the number of points to show at the Legend.
  Some series override this function to return other values. }
Function TChartSeries.CountLegendItems:Integer;
begin
  result:=Count
end;

{ Returns the number of points that are inside the axis limits. (visible) }
Function TChartSeries.VisibleCount:Integer;
var i: Integer;
Begin
  {if (FFirstVisibleIndex=-1) or (FLastVisibleIndex=-1) then
    result:=0
  else
    result:=FLastVisibleIndex-FFirstVisibleIndex;}

  result:=0;

  if (FFirstVisibleIndex=0) and (FLastVisibleIndex=Count-1) then
     result:=Count
  else
  if (FFirstVisibleIndex<>-1) and (FLastVisibleIndex<>-1) then
  begin
    if NotMandatoryValueList.Order = loNone then
    begin
      for i:=0 to Count-1 do
        if (XValue[i] >= GetHorizAxis.Minimum) and (XValue[i] <= GetHorizAxis.Maximum) and
           (YValue[i] >= GetVertAxis.Minimum) and (YValue[i] <= GetVertAxis.Maximum) then
          Inc(result);
    end
    else
    begin
      for i:=FFirstVisibleIndex to FLastVisibleIndex do
        if (MandatoryValueList.Value[i] >= MandatoryAxis.Minimum) and (MandatoryValueList.Value[i] <= MandatoryAxis.Maximum) then
          Inc(result);
    end;
  end;
end;

{ Returns the string showing a "percent" value for a given point.
  For example: "25%"
  The optional "AddTotal" parameter, when True, returns for example:
    "25% of 1234".
}
Function TChartSeries.MarkPercent(ValueIndex:Integer; AddTotal:Boolean=False):String;
var tmp  : TAxisValue;
    tmpF : String;
Begin
  With MandatoryValueList do
  Begin
    if TotalAbs<>0 then tmp:=100.0*Abs(GetMarkValue(ValueIndex))/TotalAbs
                   else tmp:=100;

    result:=FormatFloat(FPercentFormat,tmp);

    if AddTotal then
    begin
      if Marks.MultiLine then tmpF:=TeeMsg_DefaultPercentOf2
                         else tmpF:=TeeMsg_DefaultPercentOf;

      FmtStr(result,tmpF,[result,FormatFloat(FValueFormat,TotalAbs)]);
    end;
  end;
end;

Function TChartSeries.GetOriginValue(ValueIndex:Integer):Double;
begin
  result:=GetMarkValue(ValueIndex);
end;

Function TChartSeries.GetMarkValue(ValueIndex:Integer):Double;
begin
  result:=MandatoryValueList.Value[ValueIndex];
end;

function TChartSeries.GetMarkLabel(ValueIndex:Integer):String;
begin
  result:=Labels[ValueIndex];
end;

function TChartSeries.GetMarkTextStyle(MultiLine:Boolean; ValueIndex:Integer; AStyle:TSeriesMarksStyle):String;

  Function GetAValue:String;
  begin
    result:=FormatFloat(FValueFormat,GetMarkValue(ValueIndex));
  end;

  Function LabelOrValue(ValueIndex:Integer):String;
  Begin
    result:=GetMarkLabel(ValueIndex);

    if result='' then
       result:=GetAValue;
  End;

  Function GetAXValue:String;
  begin
    if not Assigned(GetHorizAxis) then
       result:=FormatFloat(FValueFormat,NotMandatoryValueList.Value[ValueIndex])
    else
       result:=GetHorizAxis.LabelValue(NotMandatoryValueList.Value[ValueIndex]);
  end;

var tmp : String;
    tmpValue : Double;
begin
  if MultiLine then tmp:=TeeLineSeparator
               else tmp:=' ';

  Case AStyle of
             smsValue: result:=GetAValue;
           smsPercent: result:=MarkPercent(ValueIndex);
      smsLabelOrValue: result:=LabelOrValue(ValueIndex);
      smsLabelPercent: result:=LabelOrValue(ValueIndex)+tmp+MarkPercent(ValueIndex);
        smsLabelValue: begin
                         result:=GetMarkLabel(ValueIndex);

                         if result<>'' then
                            result:=result+tmp;

                         result:=result+GetAValue;
                       end;

            smsLegend: begin
                         result:=Self.ParentChart.FormattedValueLegend(Self,ValueIndex);

                         if not MultiLine then
                            result:=ReplaceChar(result,TeeColumnSeparator,' ');
                       end;

      smsPercentTotal: result:=MarkPercent(ValueIndex,True);
 smsLabelPercentTotal: result:=LabelOrValue(ValueIndex)+tmp+MarkPercent(ValueIndex,True);
            smsXValue: result:=GetAXValue;
                smsXY: result:=GetAXValue+tmp+GetAValue;
       smsSeriesTitle: result:=SeriesTitleOrName(Self);
        smsPointIndex: result:=IntToStr(ValueIndex);

   smsPercentRelative: if ValueIndex=0 then
                         result:=FormatFloat(PercentFormat,100)
                       else
                       begin
                         tmpValue:=GetMarkValue(0);

                         if tmpValue=0 then
                            result:=FormatFloat(PercentFormat,0)
                         else
                            result:=FormatFloat(PercentFormat,
                                                GetMarkValue(ValueIndex)*100/tmpValue);
                       end;

 smsLabelPercentValue: result:=LabelOrValue(ValueIndex)+tmp+MarkPercent(ValueIndex)+tmp+GetAValue;

             smsLabel: result:=GetMarkLabel(ValueIndex);
  else
    result:='';
  end;
end;

{ Returns the string corresponding to the Series Mark text
  for a given ValueIndex point, using the Marks.Style property. }
function TChartSeries.GetMarkText(ValueIndex:Integer):String;
var tmpItem : TMarksItem;
begin
  with Marks do
  begin
    // Check if an specific Marks Item has custom Text
    tmpItem:=FItems.InternalGet(ValueIndex);

    // Custom Text is used ( ie: Series1.Marks[123].Text.Add('hello') )
    if Assigned(tmpItem) and
       tmpItem.ICustomText then //TV52014487 (now we use this instead of 'Assigned(tmpItem.FText)')
    begin
      result:=tmpItem.FText;

      if Copy(result,Length(result)-1,2)=TeeTextLineSeparator then
         System.Delete(result,Length(result)-1,2);
    end
    else
      // No custom text, use Marks.Style
      result:=GetMarkTextStyle(MultiLine,ValueIndex,FMarkerStyle);
  end;

  if Assigned(FOnGetMarkText) then
     FOnGetMarkText(Self,ValueIndex,result);
end;

procedure TChartSeries.SetMarkText(ValueIndex:Integer; const Value:String);
begin
  Labels[ValueIndex]:=Value;
end;

procedure TChartSeries.SetTransparency(const Value:TTeeTransparency);
begin
  if FTransparency<>Value then
  begin
    FTransparency:=Value;
    Repaint;
  end;
end;

Procedure TChartSeries.SetValueFormat(Const Value:String);
Begin
  SetStringProperty(FValueFormat,Value);
end;

Procedure TChartSeries.SetPercentFormat(Const Value:String);
Begin
  SetStringProperty(FPercentFormat,Value);
end;

Procedure TChartSeries.SetSelected(Const Value:TTeeSelected);
begin
  if Assigned(Value) then
     Selected.Assign(Value)
  else
     FreeAndNil(FSelected);
end;

procedure TChartSeries.SetTreatNulls(const Value:TTreatNullsStyle);
begin
  if FTreatNulls<>Value then
  begin
    FTreatNulls:=Value;
    Repaint;
  end;
end;

// Changes visual properties appropiate to show the series at the gallery.
Procedure TChartSeries.PrepareForGallery;
begin
  inherited;

  FillSampleValues(4);

  With Marks do
  begin
    Visible:=False;
    Font.Size:=7;
    Callout.Length:=4;
    DrawEvery:=2;
  end;

  FColorEachPoint:=True;

  if SeriesIndex=0 then
     SeriesColor:=ParentChart.GetDefaultColor(clTeeGallery1)
  else
     SeriesColor:=ParentChart.GetDefaultColor(clTeeGallery2)
end;

Function TChartSeries.NumSampleValues:Integer;
Begin
  result:=25;  { default number or random values at design time }
end;

function TChartSeries.RaiseClicked:Boolean;
begin
  result:=Assigned(FOnClick) or Assigned(FOnDblClick);
end;

class Function TChartSeries.RandomValue(const Range:Integer):Integer;
begin
  result:=System.Random(Range);
end;

// Calculates appropiate random limits used to add sample
// values (FillSampleValues method).
Function TChartSeries.RandomBounds(NumValues:Integer):TSeriesRandomBounds;
var MinX : Double;
    MaxX : Double;
    MaxY : Double;
Begin
  result.MinY:=0;
  MaxY:=ChartSamplesMax;

  if Assigned(FParent) and (FParent.GetMaxValuesCount>0) then
  begin
    result.MinY:=FParent.MinYValue(GetVertAxis);
    MaxY:=FParent.MaxYValue(GetVertAxis);
    if MaxY=result.MinY then
       if MaxY=0 then MaxY:=ChartSamplesMax
                 else MaxY:=2.0*result.MinY;

    MinX:=FParent.MinXValue(GetHorizAxis);
    MaxX:=FParent.MaxXValue(GetHorizAxis);
    if MaxX=MinX then
       if MaxX=0 then MaxX:=NumValues-1
                 else MaxX:=2.0*MinX;

    if not YMandatory then
    begin
      SwapDouble(MinX,result.MinY);
      SwapDouble(MaxX,MaxY);
    end;
  end
  else
  begin
    if XValues.DateTime then
    begin
      MinX:=Date;
      MaxX:=MinX+NumValues-1;
    end
    else
    begin
      MinX:=0;
      MaxX:=NumValues-1;
    end;
  end;

  result.StepX:=MaxX-MinX;
  if NumValues>1 then result.StepX:=result.StepX/(NumValues-1);

  result.DifY:=MaxY-result.MinY;

  if result.DifY>MaxLongint then
     result.DifY:=MaxLongint
  else
  if result.DifY<-MaxLongint then
     result.DifY:=-MaxLongint;

  result.tmpY:=result.MinY+RandomValue(100)*result.DifY*0.01;
  result.tmpX:=MinX;
end;

Procedure TChartSeries.AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False);
var t    : Integer;
    tmpH : Double;
    s    : TSeriesRandomBounds;
begin
  s:=RandomBounds(NumValues);

  with s do
  begin
    tmpH:=DifY*0.25;

    for t:=1 to NumValues do
    begin
      tmpY:=Abs(tmpY+RandomValue(100)*tmpH*0.01-(tmpH*0.5));

      if OnlyMandatory then
         Add(tmpY)
      else
      begin
        if YMandatory then AddXY(tmpX,tmpY)
                      else AddXY(tmpY,tmpX);

        tmpX:=tmpX+StepX;
      end;
    end;
  end;
end;

// Sorts all points in the series using the Labels list.
// Warning: non-mandatory values (X) are modified (they are not preserved).
Procedure TChartSeries.SortByLabels(Order:TChartListOrder=loAscending);
begin
  if Order<>loNone then
  begin
    ILabelOrder:=Order;
    TeeSort(0,Count-1,CompareLabelIndex,SwapValueIndex);
    ILabelOrder:=loNone;
    NotMandatoryValueList.FillSequence;  // force set X values (lost them)
    Repaint;
  end;
end;

{ Sorts all points in the series if the Order property is used.
  By default, Series.XValues.Order is set to "ascending". }
Procedure TChartSeries.CheckOrder;
var Modified: boolean;
begin
  Modified:=False;

  if MandatoryValueList.Order<>loNone then
  begin
    MandatoryValueList.Sort;
    // If NotMand list has a ValueSource, do not call FillSequence
    if NotMandatoryValueList.ValueSource='' then
       NotMandatoryValueList.FillSequence;

    Modified:=True;
  end;

  if NotMandatoryValueList.Order<>loNone then
  begin
    NotMandatoryValueList.Sort;
    Modified:=True;
  end;

  if Modified then Repaint;
end;

// Adds random point to the series. Used at design-time to
// show sample values.
procedure TChartSeries.FillSampleValues(NumValues:Integer=0);
begin
  if NumValues=0 then
     NumValues:=INumSampleValues;

  if NumValues<=0 then
     NumValues:=NumSampleValues;

  Clear;

  RandSeed:=Random(7774444);

  BeginUpdate;
  try
    AddSampleValues(NumValues);
    CheckOrder;
  finally
    EndUpdate;
  end;

  ManualData:=False;
end;

procedure TChartSeries.AssignFormatting(Source:TChartSeries);
var Old : Boolean;
begin
  with Source do
  begin
    Self.ShowInLegend:=ShowInLegend;
    Self.Brush:=Brush;
    Self.Pen:=Pen;

    // Do not use "Color" property directly here,
    // because setting it will disable FColor = clTeeColor (default color)
    Self.FColor:=FColor;
    Self.ISeriesColor:=ISeriesColor;

    if FColor=clTeeColor then
      if Assigned(Self.FParent) then
         Self.ChangeInternalColor(Self.FParent.GetFreeSeriesColor);

    // Visible should not be set here. See TChartSeries.Assign.
    Old:=Self.Marks.Visible;
    Self.Marks:=Marks;
    Self.Marks.Visible:=Old;

    Self.FValueFormat    := FValueFormat;
    Self.FPercentFormat  := FPercentFormat;
    Self.FCustomHorizAxis:= FCustomHorizAxis;
    Self.FHorizAxis      := FHorizAxis;
    Self.FCustomVertAxis := FCustomVertAxis;
    Self.FVertAxis       := FVertAxis;
    Self.FCursor         := FCursor;
    Self.FDepth          := FDepth;
    Self.Selected        := Selected;
    Self.FTransparency   := FTransparency;
    Self.FTreatNulls     := FTreatNulls;
  end;
end;

procedure TChartSeries.AssignFormat(const Source:TChartSeries);
var t : Integer;
begin
  with TChartSeries(Source) do
  begin
    Self.DataSource:=nil;

    if Self.YMandatory=YMandatory then
    begin
      Self.FX.Assign(FX);
      Self.FY.Assign(FY);
    end
    else
    begin
      Self.FY.Assign(FX);
      Self.FX.Assign(FY);
    end;

    Self.FLabelsSource:=FLabelsSource;
    Self.FColorSource :=FColorSource;

    Self.FColorEachPoint:=FColorEachPoint;

    { other lists }
    for t:=2 to Math.Min(Self.ValuesList.Count-1,ValuesList.Count-1) do
        Self.ValuesList[t].Assign(ValuesList[t]);

    Self.AssignFormatting(Source);

    Self.ChangeInternalColor(ISeriesColor);

    Self.FRecalcOptions:=FRecalcOptions;

    Self.RecalcGetAxis;
  end;
end;

procedure TChartSeries.Assign(Source:TPersistent);

  Procedure SelfSetDataSources(const Value:TDataSourcesList);
  var t : Integer;
  begin
    DataSources.Clear;

    for t:=0 to Value.Count-1 do
        InternalAddDataSource(Value[t]);
  end;

  Procedure AssignColors;
  var t : Integer;
  begin
    if Assigned(TChartSeries(Source).FColors) then
    begin
      if Assigned(FColors) then FColors.Clear
                           else FColors:=TList{$IFDEF D18}<TColor>{$ENDIF}.Create;

      with TChartSeries(Source) do
           for t:=0 to FColors.Count-1 do
               Self.FColors.Add(FColors[t]);
    end
    else
      FreeAndNil(FColors);
  end;

begin
  inherited;

  if Source is TChartSeries then
  begin
    AssignFormat(TChartSeries(Source));

    // This is done here, after calling AssignFormat.
    // (to avoid changing Marks visibility when applying a Theme)
    Marks.Visible:=TChartSeries(Source).Marks.Visible;

    With TChartSeries(Source) do
    begin
      if Assigned(FunctionType) and (Self.FunctionType=nil) then
         FunctionType.ParentSeries:=Self;

      Self.ManualData:=ManualData;

      if (DataSource=nil) or ManualData then // TV52010358
      begin
        if ManualData or (not (csDesigning in ComponentState)) then
        begin
          (*
          Innecessary, because AssignValues will assign everything again:

          AssignColors;

          if FImages=nil then
          begin
            Self.FImages.Free;
            Self.FImages:=nil;
          end
          else
            Self.Images.Assign(FImages);

          Self.Labels.Assign(Labels);
          *)

          Self.AssignValues(TChartSeries(Source));
        end;
      end
      else SelfSetDataSources(DataSources); { <-- important !!! }

      Self.CheckDataSource;
    end;
  end;
end;

Procedure TChartSeries.SetColorEachPoint(Value:Boolean);
Begin
  if FColorEachPoint<>Value then
  begin
    FColorEachPoint:=Value;
    NotifyColorChange(TeeAllValues);
  end;
end;

{ This method replaces a value list with a new one. }
Procedure TChartSeries.ReplaceList(const OldList,NewList:TChartValueList);
var t : Integer;
begin
  t:=FValuesList.IndexOf(NewList);
  if t<>-1 then
     FValuesList.Delete(t);

  t:=FValuesList.IndexOf(OldList);
  if t<>-1 then
  begin
    NewList.Name:=OldList.Name;

    if OldList=FX then
    begin
      FX:=NewList;
      NotMandatoryValueList:=NewList;
    end
    else
    if OldList=FY then
    begin
      FY:=NewList;
      MandatoryValueList:=NewList;
    end;

    {$IFDEF AUTOREFCOUNT}
    FValuesList.Delete(t);
    {$ELSE}
    FValuesList[t].Free;
    {$ENDIF}

    FValuesList.Items[t]:=NewList;
  end;
end;

{ Returns an X value. }
Function TChartSeries.GetxValue(ValueIndex:Integer):TChartValue; { "x" in lowercase for BCB }
Begin
  result:=FX.Value[ValueIndex]
end;

{ Returns an Y value. }
Function TChartSeries.GetyValue(ValueIndex:Integer):TChartValue; { "y" in lowercase for BCB }
Begin
  result:=FY.Value[ValueIndex]
end;

{ Changes an Y value. Tells the chart to repaint. }
Procedure TChartSeries.SetYValue(Index:Integer; Const Value:TChartValue);
Begin
  FY.Value[Index]:=Value;
  {$IFDEF TEEARRAY}
  FY.Modified:=True;
  {$ENDIF}
  Repaint;
end;

{ Changes an X value. Tells the chart to repaint. }
Procedure TChartSeries.SetXValue(Index:Integer; Const Value:TChartValue);
Begin
  FX.Value[Index]:=Value;
  {$IFDEF TEEARRAY}
  FX.Modified:=True;
  {$ENDIF}
  Repaint;
end;

Function TChartSeries.IsPointInChartRect(ValueIndex:Integer):Boolean;
begin
  result:=PointInRect(ParentChart.ChartRect,CalcXPos(ValueIndex),CalcYPos(ValueIndex));
end;

function TChartSeries.HoverEnabled:Boolean;
begin
  result:=(not Assigned(FParent)) or FParent.Hover.Visible;
  result:=result and Assigned(FSelected) and Selected.Hover.Visible;
end;

Function TChartSeries.InternalColor(ValueIndex:Integer):TColor;
Begin
  if Assigned(FColors) and (FColors.Count>ValueIndex) then
     result:=TColor(FColors{$IFOPT R-}.List{$ENDIF}[ValueIndex])
  else
     result:=clTeeColor;
end;

{ Returns the color for a given "ValueIndex" point.
  If the "ColorEachPoint" property is used (True) then
  returns a color from the color palette. }
Function TChartSeries.GetValueColor(ValueIndex:Integer):TColor;
Begin
  result:=InternalColor(ValueIndex);

  if result=clTeeColor then
     if FColorEachPoint then
     begin
       if Assigned(FParent) then
          result:=FParent.GetDefaultColor(ValueIndex)
       else
          result:=GetDefaultColor(ValueIndex)
     end
     else result:=SeriesColor;
end;

{ Creates the color list and adds the default color (clTeeColor)
  for all the points in the series. }
Procedure TChartSeries.GrowColors;
var t : Integer;
begin
  FColors:=TList{$IFDEF D18}<TColor>{$ENDIF}.Create;

  for t:=0 to Count-1 do
      FColors.Add({$IFNDEF D18}Pointer{$ENDIF}(clTeeColor));
end;

{ Sets the AColor parameter into the color list.
  It is optimized to prevent allocating memory when all the
  colors are the default (clTeeColor). }
Procedure TChartSeries.SetValueColor(ValueIndex:Integer; AColor:TColor);

  procedure CheckCount;
  begin
    while Count<=ValueIndex do
          AddNull;
  end;

Begin
  if Assigned(FColors) then
     CheckCount
  else
  if AColor=clTeeColor then
     Exit
  else
  begin
    CheckCount;

    if not Assigned(FColors) then
       GrowColors;
  end;

  FColors[ValueIndex]:={$IFNDEF D18}Pointer{$ENDIF}(AColor);

  NotifyColorChange(ValueIndex);
end;

procedure TChartSeries.NotifyColorChange(ValueIndex:Integer);
begin
  Repaint;
end;

Function TChartSeries.ClickedPointer( ValueIndex,tmpX,tmpY:Integer;
                             const P:TPoint):Boolean;
begin
  result:=ClickedPointer(ValueIndex, tmpX, tmpY,
            {$IFDEF FMX}Round{$ENDIF}(P.X),
            {$IFDEF FMX}Round{$ENDIF}(P.Y));

  if result then
     IClickedPart:=scpPointer;
end;

Function TChartSeries.ClickedPointer( ValueIndex,tmpX,tmpY:Integer;
                             x,y:Integer):Boolean;
begin
  //Empty for override by TCustomSeries
  result:=false;
end;

{ Clears (empties) all lists of values, colors and labels. }
Procedure TChartSeries.ClearLists;
var t : Integer;
Begin
  { Values }
  for t:=0 to FValuesList.Count-1 do
      FValuesList[t].ClearValues;

  { Colors }
  FreeAndNil(FColors);

  { Labels }
  Labels.Clear;

  { Pictures }
  if FPictures<>nil then
     FPictures.Clear;

  { Marks.Positions }
  if Assigned(FMarks) then
     FMarks.Clear;

  { Visuals }
  if Assigned(FVisuals) then
     FVisuals.Clear;

  FreeAndNil(FDetail);
end;

{ Removes all points.
  Triggers all events and notifications and repaints. }
Procedure TChartSeries.Clear;
begin
  FFirstVisibleIndex:=-1;
  FLastVisibleIndex:=-1;

  ClearLists;

  if Assigned(FOnClearValues) then
     FOnClearValues(Self);

  NotifyValue(veClear,0);

  if Assigned(FunctionType) then
     FunctionType.Clear;

  if FActive and Assigned(FParent) and
    (not (csDestroying in FParent.ComponentState)) then Repaint;
end;

{ Returns True is the series is associated to Axis parameter.
  Associated means either the horizontal or vertical axis of the Legend
  includes the Axis parameter. }
Function TChartSeries.AssociatedToAxis(const Axis:TChartAxis):Boolean;
Begin
  result:=UseAxis and ((
         (Axis.Horizontal and ((FHorizAxis=aBothHorizAxis) or (GetHorizAxis=Axis))) or
         ((not Axis.Horizontal) and ((FVertAxis=aBothVertAxis) or (GetVertAxis=Axis)))
        ) or Axis.IsDepthAxis);
end;

{ Draws a rectangle at the Legend that corresponds to the ValueIndex
  point position. If the point color is the same as the Legend
  background color (color conflict),
  then an alternative color is used. }
Procedure TChartSeries.DrawLegendShape(ValueIndex:Integer; Const Rect:TRect);
var tmpBlend : TTeeBlend;
    tmp      : TGraphic;
begin
  if Transparency>0 then
     tmpBlend:=ParentChart.Canvas.BeginBlending(Rect,Transparency)
  else
     tmpBlend:=nil;

  tmp:=nil;

  if ValueIndex=TeeAllValues then
  begin
    if Assigned(FLegend) and FLegend.HasPicture and FLegend.Picture.Valid then
       tmp:=FLegend.Picture.Filtered;
  end
  else
  begin
    FParent.Canvas.Brush.Color:=LegendItemColor(ValueIndex);

    if FPictures<>nil then
       if FPictures[ValueIndex]<>nil then
          tmp:=FPictures[ValueIndex].Filtered;
  end;

  FParent.DrawLegendShape(Rect,tmp);

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

{ Virtual. Returns the color of the corresponding position in
  the Legend. By default the color the the same as the point color.
  Other series (Surface, etc) return a color from the "palette". }
Function TChartSeries.LegendItemColor(LegendIndex:Integer):TColor;
begin
  result:=ValueColor[LegendIndex];
end;

type
  TPanelAccess=class(TCustomTeePanelExtended);

{ For a given ValueIndex point in the series, paints it at the Legend. }
Procedure TChartSeries.DrawLegend(ValueIndex:Integer; Const Rect:TRect);
var
  tmpColor : TColor;

  procedure DrawSymbol;
  var tmpBack  : TColor;
      OldStyle : {$IFDEF FMX}TBrushKind{$ELSE}TBrushStyle{$ENDIF};
      tmpStyle : {$IFDEF FMX}TBrushKind{$ELSE}TBrushStyle{$ENDIF};
      OldStyleChanged: Boolean;
      tmpW     : Integer;
  begin
    { if not empty brush... }
    if tmpColor<>clNone then
    begin
      FParent.Canvas.Brush.Color:=tmpColor;

      { set background color and style }
      tmpBack:=FParent.LegendColor;
      tmpStyle:=Self.Brush.Style;

      // By default, set current Series.Pen
      FParent.Canvas.AssignVisiblePen(Pen);

      // Give Series opportunity to change pen & brush
      PrepareLegendCanvas(ValueIndex,tmpBack,tmpStyle);

      // After calling PrepareLegendCanvas, set custom symbol pen, if any...
      if Assigned(FParent.LegendPen) then
         FParent.Canvas.AssignVisiblePen(FParent.LegendPen);

      tmpW:={$IFDEF FMX}Round{$ENDIF}((Rect.Bottom-Rect.Top) {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF});

      if FParent.Canvas.Pen.Width>tmpW then
         FParent.Canvas.Pen.Width:=tmpW;

      { if back color is "default", use Legend back color }
      if tmpBack=clTeeColor then
      begin
        tmpBack:=FParent.LegendColor;

        if tmpBack=clTeeColor then
           tmpBack:=TPanelAccess(FParent).GetBackColor;
      end;

      OldStyleChanged:=False;
      OldStyle:=bsSolid;

      { if brush has no bitmap, set brush style }
      With Brush do
      if Style<>tmpStyle then
      begin
        if (not Assigned(Bitmap)) {$IFNDEF FMX}or Bitmap.Empty{$ENDIF} then
        begin
          OldStyle:=Style;
          OldStyleChanged:=True;
          Style:=tmpStyle;
        end;
      end;

      { final setting }
      with FParent do
           SetBrushCanvas(Canvas.Brush.Color,Self.Brush,tmpBack);

      { draw shape }
      DrawLegendShape(ValueIndex,Rect);

      if OldStyleChanged then
         Self.Brush.Style:=OldStyle;
    end;
  end;

var Old : Boolean;
begin
  Old:=FParent.AutoRepaint;
  FParent.AutoRepaint:=False;
  try
    { set brush color }
    if ValueIndex=-1 then tmpColor:=SeriesColor
                     else tmpColor:=LegendItemColor(ValueIndex);

    DrawSymbol;
  finally
    FParent.AutoRepaint:=Old;
  end;
end;

function TChartSeries.PrepareHoverPen(ValueIndex:Integer):Boolean;
begin
  result:=HoverEnabled and (ValueIndex<>-1) and (Selected.HoverIndex=ValueIndex);

  if result then
     ParentChart.Canvas.AssignVisiblePen(Selected.Hover.Pen)
end;

Procedure TChartSeries.PrepareLegendCanvas( ValueIndex:Integer; var BackColor:TColor;
                                            var BrushStyle:{$IFDEF FMX}TBrushKind{$ELSE}TBrushStyle{$ENDIF});
begin { abstract }
end;

{ Calculates the correct "Z" order for the series }
Procedure TChartSeries.CalcZOrder;
Begin
  if FZOrder=TeeAutoZOrder then
  begin
    if FParent.View3D then
    begin
      Inc(FParent.FMaxZOrder);
      IZOrder:=FParent.FMaxZOrder;
    end
    else IZOrder:=0;
  end
  else FParent.FMaxZOrder:=Math.Max(FParent.FMaxZOrder,ZOrder);
End;

Function TChartSeries.CalcXPosValue(Const Value:Double):Integer;
Begin
  result:=GetHorizAxis.CalcXPosValue(Value);
end;

{ Returns the axis value for a given horizontal ScreenPos pixel position }
Function TChartSeries.XScreenToValue(ScreenPos:Integer):Double;
Begin
  result:=GetHorizAxis.CalcPosPoint(ScreenPos);
end;

Function TChartSeries.CalcXSizeValue(Const Value:TAxisValue):Integer;
Begin
  result:=GetHorizAxis.CalcSizeValue(Value);
end;

Function TChartSeries.CalcYPosValue(Const Value:Double):Integer;
Begin
  result:=GetVertAxis.CalcYPosValue(Value);
end;

Function TChartSeries.CalcPosValue(Const Value:TAxisValue):Integer;
Begin
  if YMandatory then result:=CalcYPosValue(Value)
                else result:=CalcXPosValue(Value);
end;

Function TChartSeries.XValueToText(Const AValue:TAxisValue):String;
Begin
  result:=GetHorizAxis.LabelValue(AValue);
end;

Function TChartSeries.YValueToText(Const AValue:TAxisValue):String;
begin
  result:=GetVertAxis.LabelValue(AValue);
end;

{ Returns the axis value for a given vertical ScreenPos pixel position }
Function TChartSeries.YScreenToValue(ScreenPos:Integer):Double;
Begin
  result:=GetVertAxis.CalcPosPoint(ScreenPos);
end;

Function TChartSeries.CalcYSizeValue(Const Value:TAxisValue):Integer;
Begin
  result:=GetVertAxis.CalcSizeValue(Value);
end;

Function TChartSeries.CalcXPos(ValueIndex:Integer):Integer;
Begin
  result:=GetHorizAxis.CalcXPosValue(XValues.Value[ValueIndex]);
end;

Function TChartSeries.CalcYPos(ValueIndex:Integer):Integer;
begin
  result:=GetVertAxis.CalcYPosValue(YValues.Value[ValueIndex]);
end;

{ Removes a point in the series }
Procedure TChartSeries.Delete(ValueIndex:Integer);
var t : Integer;
Begin
  if ValueIndex<Count then
  begin
    { Values }
    for t:=0 to FValuesList.Count-1 do
        FValuesList[t].Delete(ValueIndex);

    { Color }
    if Assigned(FColors) and (FColors.Count>ValueIndex) then
       FColors.Delete(ValueIndex);

    { Label }
    if Labels.Count>ValueIndex then
       Labels.DeleteLabel(ValueIndex);

    { Pictures }
    if (FPictures<>nil) and (FPictures.Count>ValueIndex) then
       FPictures.Delete(ValueIndex);

    { Visual }
    if Assigned(FVisuals) and (FVisuals.Count>ValueIndex) then
       FVisuals.DeleteVisual(ValueIndex);

    if Assigned(FDetail) and (FDetail.Count>ValueIndex) then
    begin
      {$IFNDEF D18}
      FDetail[ValueIndex].Free;
      {$ENDIF}

      FDetail.Delete(ValueIndex);
    end;

    { Marks Position and Item }
    if Assigned(FMarks) then
       FMarks.Delete(ValueIndex);

    { notify deletion }
    NotifyValue(veDelete,ValueIndex);

    { Repaint chart }
    if FActive then
       Repaint;
  end
  else
    Raise ChartException.CreateFmt(TeeMsg_SeriesDelete,[ValueIndex,Count-1]);
end;

// Deletes "Quantity" number of points from "Start" index.
// Faster than calling Delete repeteadly.
// If RemoveGap is True, "X" values are re-calculated to be sequential,
// to eliminate gaps created by deletion.
Procedure TChartSeries.Delete(Start,Quantity:Integer; RemoveGap:Boolean=False);
var t : Integer;
begin
  {$IFOPT C+}
  Assert( Quantity>0 , 'Quantity of data points to delete must be greater than zero.');
  Assert( (Start+Quantity-1) < Count,'Quantity of data to delete must be less than Count.');
  {$ENDIF}

  if Start<Count then
  begin
    {$IFDEF TEEARRAY}

    if Quantity>(Count-Start) then
       Quantity:=Count-Start;

    { Values }
    for t:=0 to ValuesList.Count-1 do
        ValuesList[t].Delete(Start,Quantity);

    { Color }
    if Assigned(FColors) then
    begin
      if FColors.Count>Start then
         for t:=Start to Start+Quantity-1 do
             FColors.Delete(Start);

      if FColors.Count=0 then
         FreeAndNil(FColors);
    end;

    { Label }
    if FLabels.Count>Start then
       for t:=Start to Start+Quantity-1 do
           Labels.DeleteLabel(Start);

    { Visual }
    if Assigned(FVisuals) and (FVisuals.Count>Start) then
      for t:=Start to Start+Quantity-1 do
          FVisuals.DeleteVisual(Start);

    { Detail }
    if Assigned(FDetail) and (FDetail.Count>Start) then
      for t:=Start to Start+Quantity-1 do
      begin
        {$IFNDEF D18}
        FDetail[Start].Free;
        {$ENDIF}

        FDetail.Delete(Start);
      end;

    { Marks.Position }
    if Assigned(FMarks) then
       FMarks.FPositions.DeleteMany(Start,Quantity);

    { notify deletion }
    NotifyValue(veDelete,-1);

    { Repaint chart }
    if Active then
       Repaint;

    {$ELSE}
    for t:=1 to Quantity do
        Delete(Start);
    {$ENDIF}

    if RemoveGap then
       NotMandatoryValueList.FillSequence;
  end;
end;

{ Exchanges one point with another.
  Also the point color and point label, with safe checking. }
procedure TChartSeries.SwapValueIndex(a,b:Integer);
var t : Integer;
begin
  for t:=0 to FValuesList.Count-1 do
      FValuesList[t].{$IFNDEF TEEARRAY}FList.{$ENDIF}Exchange(a,b);

  if Assigned(FColors) then
     FColors.Exchange(a,b);

  if FLabels.Count>0 then
     FLabels.Exchange(a,b);

  if Assigned(FVisuals) and (FVisuals.Count>0) then
     FVisuals.Exchange(a,b);
end;

procedure TChartSeries.SetMarks(const Value:TSeriesMarks);
begin
  Marks.Assign(Value);
end;

Function TChartSeries.GetSeriesColor:TColor;
begin
  if FColor=clTeeColor then result:=ISeriesColor
                       else result:=FColor;
end;

Procedure TChartSeries.NotifyColorChanged;
Begin
  if Assigned(ParentChart) then
     ParentChart.BroadcastSeriesEvent(Self,seChangeColor);

  RepaintDesigner;
end;

Procedure TChartSeries.SetSeriesColor(AColor:TColor);
Begin
  if (FColor<>clTeeColor) or (AColor<>ISeriesColor) then
  begin
    SetColorProperty(FColor,AColor);
    ISeriesColor:=FColor;
    NotifyColorChanged;
  end;
end;

Function TChartSeries.MaxXValue:TChartValue;
Begin
  result:=FX.MaxValue;
end;

Function TChartSeries.MaxYValue:TChartValue;
Begin
  result:=FY.MaxValue;
end;

Function TChartSeries.MinXValue:TChartValue;
Begin
  result:=FX.MinValue;
end;

Function TChartSeries.MinYValue:TChartValue;
Begin
  result:=FY.MinValue;
end;

Function TChartSeries.MaxZValue:TChartValue;
begin
  result:=ZOrder;
end;

Function TChartSeries.MinZValue:TChartValue;
begin
  result:=ZOrder;
end;

{ Returns the length in pixels of the longest visible Mark text }
Function TChartSeries.MaxMarkWidth:Integer;
var t : Integer;
    tmpCount : Integer;
Begin
  result:=0;
  tmpCount:=Count;

  for t:=0 to tmpCount-1 do
  if Marks.MarkItem(t).Visible then
     result:=Math.Max(result,Marks.TextWidth(t));
end;

Procedure TChartSeries.AddLinkedSeries(const ASeries:TChartSeries);
Begin
  if LinkedSeries.IndexOf(ASeries)=-1 then
     FLinkedSeries.Add(ASeries);
end;

Procedure TChartSeries.RemoveDataSource(const Value:TComponent);
begin
  DataSources.Remove(Value);
end;

Procedure TChartSeries.SetNull(ValueIndex:Integer; Null:Boolean=True);
begin
  if Null then
     ValueColor[ValueIndex]:=clNone   // set point to null
  else
     ValueColor[ValueIndex]:=clTeeColor; // back to default (non-null) color
end;

Procedure TChartSeries.RemoveLinkedSeries(const ASeries:TChartSeries);
Begin
  if Assigned(FLinkedSeries) then
     FLinkedSeries.Remove(ASeries);
end;

Procedure TChartSeries.BeginUpdate;
begin
  Inc(IUpdating);
end;

Procedure TChartSeries.EndUpdate;
begin
  if IUpdating>0 then
  begin
    Dec(IUpdating);
    if IUpdating=0 then RefreshSeries;
  end;
end;

{ This method is called whenever the series points are added,
  deleted, modified, etc. }
Procedure TChartSeries.NotifyValue(ValueEvent:TValueEvent; ValueIndex:Integer);

  procedure RefreshLinked(Index:Integer);
  begin
    with FLinkedSeries[Index] do
    Case ValueEvent of
      veClear  : if rOnClear in FRecalcOptions then
                    Clear;

      veDelete : if rOnDelete in FRecalcOptions then
                    if FunctionType=nil then
                       DeletedValue(Self,ValueIndex);

      veAdd    : if rOnInsert in FRecalcOptions then
                    if FunctionType=nil then
                       AddedValue(Self,ValueIndex);

      veModify : if rOnModify in FRecalcOptions then
                    AddValues(Self);

      veRefresh: AddValues(Self);
    end;
  end;

var t,
    tmp : Integer;
Begin
  if IUpdating=0 then
  begin
    // Tell TeeFunction series to refresh
    if Assigned(FLinkedSeries) then
       for t:=0 to FLinkedSeries.Count-1 do
           RefreshLinked(t);

    // If this series is a Detail series, tell its ParentSeries to recalculate
    if (RecalcOptions<>[]) and Assigned(ParentSeries) then
    if not (csDestroying in ComponentState) then
    if ParentSeries.IUpdating=0 then
    begin
      ParentSeries.BeginUpdate;
      try
        tmp:=ParentSeries.Detail.IndexOf(Self);

        if tmp<>-1 then
           ParentSeries.MandatoryValueList[tmp]:=ParentSeries.Detail.Calculate(MandatoryValueList);
      finally
        ParentSeries.EndUpdate;
        ParentSeries.Repaint;
      end;
    end;
  end;
end;

Function TChartSeries.UseAxis:Boolean;
begin
  result:=True;
end;

Function TChartSeries.CanZoomScroll:Boolean;
begin
  result:=UseAxis;
end;

Procedure TChartSeries.Loaded;
var t : Integer;
begin
  inherited;

  { when the DataSource has multiple Series, load them... }
  if Assigned(FTempDataSources) then
  begin
    if FTempDataSources.Count>0 then
    begin
      DataSources.Clear;
      for t:=0 to FTempDataSources.Count-1 do
          InternalAddDataSource( Owner.FindComponent(FTempDataSources[t]) );
    end;

    FTempDataSources.Free;
  end;

  { finally, gather points from datasource after loading }
  CheckDatasource;
end;

Procedure TChartSeries.SetCustomHorizAxis(const Value:TChartAxis);
begin
  FCustomHorizAxis:=Value;
  if Assigned(Value) then FHorizAxis:=aCustomHorizAxis
                     else FHorizAxis:=aBottomAxis;
  RecalcGetAxis;
  Repaint;
end;

Function TChartSeries.GetZOrder:Integer;
begin
  if FZOrder=TeeAutoZOrder then result:=IZOrder
                           else result:=FZOrder;
end;

Procedure TChartSeries.SetZOrder(Value:Integer);
begin
  SetIntegerProperty(FZOrder,Value);
  if FZOrder=TeeAutoZOrder then IZOrder:=0 else IZOrder:=FZOrder;
end;

Procedure TChartSeries.SetCustomVertAxis(const Value:TChartAxis);
begin
  FCustomVertAxis:=Value;
  if Assigned(Value) then FVertAxis:=aCustomVertAxis
                     else FVertAxis:=aLeftAxis;
  RecalcGetAxis;
  Repaint;
end;

class procedure TChartSeries.CreateSubGallery(AddSubChart: TChartSubGalleryProc);
begin
  AddSubChart(TeeMsg_Normal);
end;

class procedure TChartSeries.SetSubGallery(ASeries: TChartSeries;
  Index: Integer);
begin
end;

procedure TChartSeries.SetDepth(const Value: Integer);
begin
  SetIntegerProperty(FDepth,Value);
end;

{ Tells the series to swap X and Y values }
procedure TChartSeries.SetHorizontal;
begin
  MandatoryValueList:=XValues;
  NotMandatoryValueList:=YValues;
  YMandatory:=False;
end;

// Returns in First and Last parameters the index of points
// that start and end the current Chart "Page"
procedure TChartSeries.CalcFirstLastPage(out First,Last:Integer);
var tmpCount : Integer;
    tmpMax   : Integer;
begin
  tmpCount:=Count;

  if tmpCount=0 then
  begin
    First:=-1;
    Last:=-1;
  end
  else
  begin
    tmpMax:=ParentChart.MaxPointsPerPage;

    if tmpMax>0 then
    begin
      // Special case when MaxPointsPerPage = 1
      if tmpMax=1 then First:=ParentChart.Page-1
                  else First:=(ParentChart.Page-1)*tmpMax;

      if tmpCount<=First then
         First:=Math.Max(0,(tmpCount div tmpMax)-1)*tmpMax;

      Last:=First+tmpMax-1;
      if tmpCount<=Last then
         Last:=First+(tmpCount mod tmpMax)-1;
    end
    else
    begin
      First:=0;
      Last:=tmpCount-1;
    end;
  end;
end;

function TChartSeries.CompareLabelIndex(a, b: Integer): Integer;
var tmpA : String;
    tmpB : String;
begin
  tmpA:=Labels[a];
  tmpB:=Labels[b];
  if (Labels.FCaseSensitive) and (AnsiLowerCase(tmpA) <> AnsiLowerCase(tmpB)) then
  begin
    tmpA:=AnsiLowerCase(tmpA);
    tmpB:=AnsiLowerCase(tmpB);
  end;
  if tmpA<tmpB then result:=-1 else
  if tmpA>tmpB then result:= 1 else result:= 0;
  if ILabelOrder=loDescending then result:=-result;
end;

function TChartSeries.GetXLabel(Index: Integer): String;
begin
  result:=Labels[Index];
end;

procedure TChartSeries.SetXLabel(Index: Integer; const Value: String);
begin
  Labels[Index]:=Value;
end;

function TChartSeries.InvertedLegend:Boolean;
begin
  result:=False;
end;

function TChartSeries.IsColorStored: Boolean;
begin
  result:=FColor<>clTeeColor;
end;

procedure TChartSeries.SetDefaultColor;
begin
  if FColor<>clTeeColor then
  begin
    FColor:=clTeeColor;
    ISeriesColor:=clTeeColor;

    if Assigned(ParentChart) then
       ParentChart.ColorPaletteChanged;

    Repaint;
  end;
end;

function TChartSeries.MandatoryAxis: TChartAxis;
begin
  if YMandatory then result:=GetVertAxis else result:=GetHorizAxis
end;

{$IFNDEF D18}
{$IFDEF D9}

{ TSeriesEnumerator }
Constructor TSeriesEnumerator.Create(const List:TCustomSeriesList);
begin
  {$IFNDEF D11}
  inherited Create;
  {$ENDIF}
  FIndex:=-1;
  FList:=List;
end;

function TSeriesEnumerator.MoveNext: Boolean;
begin
  if FIndex < FList.Count - 1 then
  begin
    Inc(FIndex);
    result:=True;
  end
  else result:=False;
end;
{$ENDIF}

{ TCustomSeriesList }

function TCustomSeriesList.Add(const ASeries:TChartSeries):Integer;
begin
  result:=inherited Add(ASeries);
end;

{$IFDEF D9}
function TCustomSeriesList.GetEnumerator:TSeriesEnumerator;
begin
  result:=TSeriesEnumerator.Create(Self);
end;
{$ENDIF}

{$ENDIF}

function TCustomSeriesList.First:TChartSeries;
begin
  result:={$IFDEF D18}Items[0]{$ELSE}Get(0){$ENDIF};
end;

function TCustomSeriesList.Last:TChartSeries;
begin
  result:={$IFDEF D18}Items[Count-1]{$ELSE}Get(Count-1){$ENDIF};
end;

{$IFNDEF D18}
procedure TCustomSeriesList.Put(Index:Integer; const Value:TChartSeries);
begin
  inherited Items[Index]:=Value;
end;

function TCustomSeriesList.Get(Index:Integer):TChartSeries;
begin
  result:=TChartSeries({$IFOPT R-}List{$ELSE}inherited Items{$ENDIF}[Index]);
end;

{$IFDEF D9} // This method should be BELOW TCustomSeriesList.Get (due to inline)
function TSeriesEnumerator.GetCurrent:TChartSeries;
begin
  result:=FList[FIndex];
end;
{$ENDIF}

{$ENDIF}

procedure TCustomSeriesList.ClearValues;
{$IFDEF D9}
var s : TChartSeries;
{$ELSE}
var t : Integer;
{$ENDIF}
begin
  {$IFDEF D9}
  for s in Self do s.Clear;
  {$ELSE}
  for t:=0 to Count-1 do Items[t].Clear;
  {$ENDIF}
end;

procedure TCustomSeriesList.FillSampleValues(Num:Integer=0);
{$IFDEF D9}
var s : TChartSeries;
{$ELSE}
var t : Integer;
{$ENDIF}
begin
  {$IFDEF D9}
  for s in Self do s.FillSampleValues(Num);
  {$ELSE}
  for t:=0 to Count-1 do Items[t].FillSampleValues(Num);
  {$ENDIF}
end;

{ TChartSeriesList }
Destructor TChartSeriesList.Destroy;
begin
  FGroups.Free;
  inherited;
end;

function TChartSeriesList.AddGroup(const Name: String): TSeriesGroup;
begin
  result:=Groups.Add as TSeriesGroup;
  result.Name:=Name;
end;

procedure TChartSeriesList.Assign(const Source:TChartSeriesList);
begin
  // Inherited TList should not be assigned, as items aren't owned by this list.Self
  FGroups.Assign(Source.FGroups);
end;

// Returns the Series and ValueIndex'th point that contains the XY pixel coordinates
function TChartSeriesList.Clicked(const X,Y:Integer; out ValueIndex:Integer):TChartSeries;
var t : Integer;
begin
  for t:=Count-1 downto 0 do // Inverted Z-order
  begin
    result:=Items[t];

    if result.Active then
    begin
      ValueIndex:=result.Clicked(x,y);

      if ValueIndex<>TeeNoPointClicked then
         Exit;
    end;
  end;

  ValueIndex:=TeeNoPointClicked;
  result:=nil;
end;

function TChartSeriesList.Find(const AName:String):TChartSeries;
var t : Integer;
begin
  for t:=0 to Count-1 do
      if Items[t].Name=AName then
      begin
        result:=Items[t];
        Exit;
      end;

  result:=nil;
end;

function TChartSeriesList.GetAllActive: Boolean;
var t : Integer;
begin
  result:=True;
  for t:=0 to Count-1 do
  if not Items[t].Visible then
  begin
    result:=False;
    exit;
  end;
end;

procedure TChartSeriesList.SetAllActive(const Value: Boolean);
var t : Integer;
begin
  for t:=0 to Count-1 do Items[t].Visible:=Value;
end;

procedure TChartSeriesList.SetGroups(const Value: TSeriesGroups);
begin
  FGroups.Assign(Value);
end;

{ TChartAxes }

procedure TChartAxes.Clear;
begin
  while Count>0 do
        {$IFDEF AUTOREFCOUNT}
        Delete(0);
        {$ELSE}
        Items[0].Free;
        {$ENDIF}

  inherited;
end;

function TChartAxes.Clicked(x,y:Integer):TChartAxis;
var t : Integer;
begin
  result:=nil;

  for t:=0 to Count-1 do
      if Items[t].Visible and Items[t].Clicked(x,y) then
      begin
        result:=Items[t];
        break;
      end;
end;

{$IFNDEF D18}
function TChartAxes.Get(Index:Integer):TChartAxis;
begin
  result:=TChartAxis({$IFOPT R-}List{$ELSE}inherited Items{$ENDIF}[Index]);
end;
{$ENDIF}

procedure TChartAxes.Reset;
begin
  SetAuto(True);
end;

procedure TChartAxes.Hide;
begin
  Visible:=False;
end;

function TChartAxes.GetDepthAxis:TChartDepthAxis;
begin
  result:=FChart.DepthAxis;
end;

function TChartAxes.GetDepthTopAxis:TChartDepthAxis;
begin
  result:=FChart.DepthTopAxis;
end;

function TChartAxes.GetLeftAxis:TChartAxis;
begin
  result:=FChart.LeftAxis;
end;

function TChartAxes.GetTopAxis:TChartAxis;
begin
  result:=FChart.TopAxis;
end;

function TChartAxes.GetRightAxis:TChartAxis;
begin
  result:=FChart.RightAxis;
end;

function TChartAxes.GetBottomAxis:TChartAxis;
begin
  result:=FChart.BottomAxis;
end;

procedure TChartAxes.SetFastCalc(const Value: Boolean);
var t : Integer;
begin
  IFastCalc:=Value;
  for t:=0 to Count-1 do Items[t].SetCalcPosValue;
end;

function TChartAxes.GetBehind: Boolean;
begin
  result:=FChart.AxisBehind;
end;

function TChartAxes.GetVisible:Boolean;
begin
  result:=FChart.AxisVisible;
end;

// Returns True when ALL chart axes are Automatic
function TChartAxes.GetAuto:Boolean;
var t : Integer;
begin
  result:=True;

  for t:=0 to Count-1 do
      if not Items[t].Automatic then
      begin
        result:=False;
        break;
      end;
end;

// Sets ALL chart axes Automatic to Value
procedure TChartAxes.SetAuto(const Value: Boolean);
var t : Integer;
begin
  for t:=0 to Count-1 do
      Items[t].Automatic:=Value;
end;

procedure TChartAxes.SetBehind(const Value: Boolean);
begin
  FChart.AxisBehind:=Value;
end;

procedure TChartAxes.SetVisible(const Value: Boolean);
begin
  FChart.AxisVisible:=Value;
end;

{ TChartCustomAxes }

function TChartCustomAxes.Get(Index: Integer):TChartAxis;
begin
  result:=TChartAxis(inherited Items[Index]);
end;

procedure TChartCustomAxes.Put(Index:Integer; Const Value:TChartAxis);
begin
  Items[Index].Assign(Value);
end;

function TChartCustomAxes.Add:TChartAxis;
var tmp : TPersistent;
begin
  result:=TChartAxis(inherited Add);

  tmp:=GetOwner;

  if tmp is TChartAxis then
  begin
    result.FMaster:=TChartAxis(tmp);

    result.FHorizontal:=TChartAxis(tmp).FHorizontal;
    result.FOtherSide:=TChartAxis(tmp).FOtherSide;
    result.Grid.Hide;
    result.SetCalcPosValue;
  end;
end;

// For linked custom axes, change their Min and Max
procedure TChartCustomAxes.ResetScales(const Axis:TChartAxis);
var t : Integer;
begin
  for t:=0 to Count-1 do
  if Items[t].FMaster=Axis then
     Items[t].SetMinMax(Axis.Minimum,Axis.Maximum);
end;

{ TTeeCustomDesigner }
Procedure TTeeCustomDesigner.Refresh;
begin
end;

Procedure TTeeCustomDesigner.Repaint;
begin
end;

{ TTeeCustomAnimation }

procedure TTeeCustomAnimation.SetParentChart(const Value: TCustomAxisPanel);
begin
  if ParentChart<>Value then
  begin
    if Assigned(ParentChart) then
    begin
      ParentChart.Animations.Remove(Self);

      if not Assigned(Value) then
         ParentChart.Invalidate; // TV52010361
    end;

    FParent:=Value; // <-- DO NOT CALL "inherited"

    if Assigned(ParentChart) then
       ParentChart.Animations.InternalAdd(Self);
  end;
end;

// To use at animations editor dialogs:
function TTeeCustomAnimation.Title:String;
begin
  result:=Name;

  if result='' then
  begin
    result:=Description;

    if result='' then
       result:=ClassName; // <-- last option
  end;
end;

{ TChartAnimations }
function TChartAnimations.Get(Index: Integer): TTeeCustomAnimation;
begin
  result:=TTeeCustomAnimation({$IFOPT R-}List{$ELSE}inherited Items{$ENDIF}[Index]);
end;

function TChartAnimations.Add(const Value:TTeeCustomAnimation):TTeeCustomAnimation;
begin
  result:=TTeeCustomAnimation(inherited Add(Value));
end;

function TChartAnimations.AddClass(const Value:TTeeCustomAnimationClass):TTeeCustomAnimation;
begin
  result:=Add(Value.Create(Owner.Owner));
end;

{ TTeeCustomTool }
procedure TTeeCustomTool.ChartEvent(AEvent: TChartToolEvent);
begin
end;

Procedure TTeeCustomTool.SeriesEvent(AEvent:TChartToolEvent; ASeries:TChartSeries);
begin
end;

procedure TTeeCustomTool.ChartDragDropEvent(AEvent: TChartDragDropEvent; Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean; var DragObject: TDragObject);
begin
end;

procedure TTeeCustomTool.ChartKeyEvent(AEvent: TChartKeyEvent; var Key: Word;
                                       var Key1: Char; Shift: TShiftState);
begin
end;

Procedure TTeeCustomTool.ChartMouseEvent(AEvent: TChartMouseEvent;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
end;

Procedure TTeeCustomTool.WheelMouseEvent( AEvent: TWheelMouseEvent;
                                          WheelDelta:Integer;
                                          X, Y: Integer);
begin
end;

class function TTeeCustomTool.Description: String;
begin
  result:='';
end;

class function TTeeCustomTool.LongDescription: String;
begin
  result:='';
end;

procedure TTeeCustomTool.SetParentChart(const Value: TCustomAxisPanel);
begin
  if ParentChart<>Value then
  begin
    if Assigned(ParentChart) then
    begin
      ParentChart.Tools.Remove(Self);

      if (not Assigned(Value)) and Assigned(ParentChart) then
         ParentChart.Invalidate; // TV52010361
    end;

    inherited;

    if Assigned(ParentChart) then
       ParentChart.Tools.InternalAdd(Self);
  end;
end;

{ Returns True when the tmpTool parameter is of the same class }
Function TTeeCustomTool.SameClass(const tmpTool:TTeeCustomTool):Boolean;
begin
  result:=((Self is tmpTool.ClassType) or (tmpTool is Self.ClassType));
end;

{ TChartTools }

procedure TChartTools.InternalAdd(ATool:TTeeCustomTool);
begin
  if Assigned(Owner) and (Owner.Tools.IndexOf(ATool)=-1) then
    Owner.ApplyThemeTools(ATool);

  inherited Add(ATool);
end;

Function TChartTools.Add(const Tool:TTeeCustomTool):TTeeCustomTool;
begin
  if Assigned(Owner) then
     Tool.ParentChart:=Owner
  else
     InternalAdd(Tool);

  result:=Tool;
end;

function TChartTools.Add({$IFNDEF BCB}const{$ENDIF} Tool:TTeeCustomToolClass):TTeeCustomTool;
begin
  result:=Add(Tool.Create(Owner));
end;

procedure TChartTools.Clear;
begin
  while Count>0 do
        {$IFDEF AUTOREFCOUNT}
        Items[0].DisposeOf;
        {$ELSE}
        Items[0].Free;
        {$ENDIF}

  inherited;
end;

{$IFNDEF D18}
function TChartTools.Get(Index: Integer): TTeeCustomTool;
begin
  result:=TTeeCustomTool({$IFOPT R-}List{$ELSE}inherited Items{$ENDIF}[Index]);
end;
{$ENDIF}

procedure TChartTools.SetActive(Value:Boolean);
var t : Integer;
begin
  for t:=0 to Count-1 do
      {$IFDEF D18}Items[t]{$ELSE}Get(t){$ENDIF}.Active:=Value;
end;

{ TTeeCustomToolSeries }
class function TTeeCustomToolSeries.GetEditorClass: String;
begin
  result:='TSeriesToolEditor';
end;

Function TTeeCustomToolSeries.GetHorizAxis:TChartAxis;
var i: Integer;
begin
  if Assigned(FSeries) then
  begin
    if FSeries.HorizAxis=aCustomHorizAxis then
       result:=FSeries.CustomHorizAxis
    else
       result:=FSeries.GetHorizAxis;
  end
  else
  with ParentChart do
  begin
    if HasActiveSeries(BottomAxis) then
       result:=BottomAxis
    else
    if HasActiveSeries(TopAxis) then
       result:=TopAxis
    else
    begin
      result:=BottomAxis;

      for i:=0 to ParentChart.CustomAxes.Count-1 do
        if ParentChart.CustomAxes[i].Horizontal and
           HasActiveSeries(ParentChart.CustomAxes[i]) then
        begin
          result:=ParentChart.CustomAxes[i];
          exit;
        end;
    end;
  end;
end;

Function TTeeCustomToolSeries.GetVertAxis:TChartAxis;
var i: Integer;
begin
  if Assigned(FSeries) then
  begin
    if FSeries.VertAxis=aCustomVertAxis  then
       result:=FSeries.CustomVertAxis
    else
       result:=FSeries.GetVertAxis;
  end
  else
  with ParentChart do
  begin
    if HasActiveSeries(LeftAxis) then
       result:=LeftAxis
    else
    if HasActiveSeries(RightAxis) then
       result:=RightAxis
    else
    begin
       result:=LeftAxis;

       for i:=0 to ParentChart.CustomAxes.Count-1 do
        if not ParentChart.CustomAxes[i].Horizontal and
           HasActiveSeries(ParentChart.CustomAxes[i]) then
        begin
          result:=ParentChart.CustomAxes[i];
          exit;
        end;
    end;
  end;
end;

procedure TTeeCustomToolSeries.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited;
  if (Operation=opRemove) and Assigned(FSeries) and (AComponent=FSeries) then
     Series:=nil;
end;

procedure TTeeCustomToolSeries.SetSeries(const Value: TChartSeries);
begin
  if FSeries<>Value then
  begin
    if Assigned(FSeries) then
       FSeries.RemoveFreeNotification(Self);

    FSeries:=Value;

    if Assigned(FSeries) then
       FSeries.FreeNotification(Self);
  end;
end;

Procedure TTeeCustomToolSeries.Assign(Source:TPersistent);
begin
  if Source is TTeeCustomToolSeries then
     FSeries:=TTeeCustomToolSeries(Source).FSeries;

  inherited;
end;

{ TTeeCustomToolAxis }

procedure TTeeCustomToolAxis.DefineProperties(Filer: TFiler);
begin
  inherited;
  Filer.DefineProperty('AxisID',ReadAxis,WriteAxis,Assigned(Axis));
end;

class function TTeeCustomToolAxis.GetEditorClass: String;
begin
  result:='TAxisToolEditor';
end;

procedure TTeeCustomToolAxis.ReadAxis(Reader: TReader);
var tmp : Integer;
begin
  tmp:=Reader.ReadInteger;

  if Assigned(ParentChart) then
     Axis:=ParentChart.Axes[tmp];
end;

procedure TTeeCustomToolAxis.InternalSetAxis(const Value: TChartAxis);
begin
  FAxis:=Value;
end;

procedure TTeeCustomToolAxis.SetAxis(const Value: TChartAxis);
begin
  if (FAxis<>Value) or (Assigned(FAxis) and (FAxis.ParentChart<>ParentChart)) then
  begin
    InternalSetAxis(Value);

    if Assigned(FAxis) then ParentChart:=FAxis.ParentChart
                       else ParentChart:=nil;
    Repaint;
  end;
end;

Procedure TTeeCustomToolAxis.Assign(Source:TPersistent);
begin
  if Source is TTeeCustomToolAxis then
     FAxis:=TTeeCustomToolAxis(Source).FAxis;

  inherited;
end;

procedure TTeeCustomToolAxis.WriteAxis(Writer: TWriter);
begin
  // Use Axis.ParentChart instead of Self.ParentChart to avoid potential AV.
  Writer.WriteInteger(Axis.ParentChart.Axes.IndexOf(Axis));
end;

{ TCustomAxisPanel }

Constructor TCustomAxisPanel.Create(AOwner: TComponent);
begin
  inherited;
  AutoRepaint :=False;

  F3DPercent:=TeeDef3DPercent;

  FAnimations:=TChartAnimations.Create;
  FAnimations.FOwner:=Self;

  FTools:=TChartTools.Create;
  FTools.FOwner:=Self;

  FAxisBehind:=True;
  FAxisVisible:=True;

  FSeriesList:=TChartSeriesList.Create;
  FSeriesList.FOwner:=Self;
  FSeriesList.FGroups:=TSeriesGroups.Create(Self,TSeriesGroup);

  FView3DWalls:=True;

  FClipPoints:=True;

  { create FIRST the collections... }
  FAxes:=TChartAxes.Create;

  {$IFDEF D18}
  FAxes.OwnsObjects:=False;
  {$ENDIF}

  FAxes.FChart:=Self;

  FCustomAxes:=TChartCustomAxes.Create(Self,TChartAxis);

  { Create axes... }
  FBottomAxis:=TChartAxis.Create(FCustomAxes);

  with FBottomAxis do
  begin
    IAxisID:='Bottom'; // Do not localize
    FHorizontal:=True;
    SetCalcPosValue;
  end;

  FTopAxis:=TChartAxis.Create(FCustomAxes);

  with FTopAxis do
  begin
    IAxisID:='Top'; // Do not localize
    FHorizontal:=True;
    FOtherSide:=True;
    FZPosition:=100; { 100% }
    SetCalcPosValue;
    FGrid.FZ:=100;
    FGrid.IDefaultZ:=100;
  end;

  FLeftAxis:=TChartAxis.Create(FCustomAxes);
  FLeftAxis.IAxisID:='Left'; // Do not localize

  FRightAxis:=TChartAxis.Create(FCustomAxes);

  with FRightAxis do
  begin
    IAxisID:='Right'; // Do not localize
    FOtherSide:=True;
    FZPosition:=100; { 100% }
    FGrid.FZ:=100;
    FGrid.IDefaultZ:=100;
  end;

  // Note: Do not add a Create constructor to TChartDepthAxis,
  // VCL streaming TCollection (CustomAxes property)
  // does not accept more than one class.
  FDepthAxis:=TChartDepthAxis.Create(FCustomAxes);

  with FDepthAxis do
  begin
    IAxisID:='Depth'; // Do not localize
    FVisible:=False;
    IDepthAxis:=True;
    SetCalcPosValue;
    FOtherSide:=True;
  end;

  // Create DepthTop axis *after* DepthAxis...
  FDepthTopAxis:=TChartDepthAxis.Create(FCustomAxes);

  with FDepthTopAxis do
  begin
    IAxisID:='DepthTop'; // Do not localize
    FVisible:=False;
    IDepthAxis:=True;
    SetCalcPosValue;
  end;

  // Paging default values
  FPage:=TChartPage.Create;
  FPage.IParent:=Self;

  FHover:=TTeeHover.Create(Self);

  AutoRepaint :=True;
end;

Destructor TCustomAxisPanel.Destroy;

  procedure ResetToolsAxis;
  var t : Integer;
  begin
    for t:=0 to FTools.Count-1 do
    if FTools[t] is TTeeCustomToolAxis then
       TTeeCustomToolAxis(FTools[t]).FAxis:=nil;
  end;

Begin
  FreeAllSeries;

  if FAnimations<>nil then // <-- Prevent no Create
  begin
    FAnimations.Clear;
    FAnimations.Free;
  end;

  FPage.Free;

  if FAxes<>nil then // <-- Prevent no Create
  begin
    FAxes.Clear;
    FAxes.Free;
  end;

  FCustomAxes.Free;
  FSeriesList.Free;
  Designer.Free;

  if FTools<>nil then // <-- Prevent no Create
  begin
    ResetToolsAxis;

    FTools.Clear;
    FTools.Free;
  end;

  FHover.Free;

  inherited;
end;

Procedure TCustomAxisPanel.Set3DPercent(Value:Integer);
Const Max3DPercent = 100;
      Min3DPercent = 1;
Begin
  if Value<Min3DPercent then
     Value:=Min3DPercent
  else
  if Value>Max3DPercent then
     Value:=Max3DPercent;

  SetIntegerProperty(F3DPercent,Value);
end;

procedure TCustomAxisPanel.ReadMaxPointsPerPage(Reader: TReader); // obsolete
begin
  Pages.MaxPointsPerPage:=Reader.ReadInteger;
end;

procedure TCustomAxisPanel.ReadPage(Reader: TReader); // obsolete
begin
  Pages.Current:=Reader.ReadInteger;
end;

procedure TCustomAxisPanel.ReadScaleLastPage(Reader: TReader); // obsolete
begin
  Pages.ScaleLastPage:=Reader.ReadBoolean;
end;

procedure TCustomAxisPanel.DefineProperties(Filer: TFiler);
begin
  inherited;
  Filer.DefineProperty('MaxPointsPerPage',ReadMaxPointsPerPage,nil,False);
  Filer.DefineProperty('Page',ReadPage,nil,False);
  Filer.DefineProperty('ScaleLastPage',ReadScaleLastPage,nil,False);
end;

{ TChartPage }
Constructor TChartPage.Create;
begin
  inherited;
  FCurrent:=1;
  FScaleLastPage:=True;
end;

Procedure TChartPage.SetAutoScale(const Value:Boolean);
begin
  IParent.SetBooleanProperty(FAutoScale,Value);
end;

// Changes the current page to Value
procedure TChartPage.SetCurrent(Value:Integer);
var tmp : Integer;
begin
  { Allow "Page" to be into: >= 1 , <= NumPages }
  tmp:=Count;
  if Value>tmp then Value:=tmp;
  if Value<1 then Value:=1;

  if FCurrent<>Value then
  begin
    IParent.SetIntegerProperty(FCurrent,Value);
    IParent.BroadcastTeeEventClass(TChartChangePage);

    { Trigged "changed page" event }
    if Assigned(IParent.FOnPageChange) then
       IParent.FOnPageChange(Self);
  end;
end;

Procedure TChartPage.SetScaleLastPage(const Value:Boolean);
Begin
  IParent.SetBooleanProperty(FScaleLastPage,Value);
end;

procedure TChartPage.Assign(Source:TPersistent);
begin
  if Source is TChartPage then
  with TChartPage(Source) do
  begin
    Self.FAutoScale     := FAutoScale;
    Self.FCurrent       := FCurrent;
    Self.FPointsPerPage := FPointsPerPage;
    Self.FScaleLastPage := FScaleLastPage;
  end
  else inherited;
end;

Procedure TChartPage.NextPage;
begin
  if (MaxPointsPerPage>0) and (Current<Count) then
     Current:=Current+1;
end;

Procedure TChartPage.PreviousPage;
begin
  if (MaxPointsPerPage>0) and (Current>1) then
     Current:=Current-1;
end;

{ Calculates the number of pages. This only applies
  when the MaxPointsPerPage property is used.
  If it is not used (is zero), then returns 1 (one page only).
}
Function TChartPage.Count:Integer;

  Function CalcNumPages(const AAxis:TChartAxis):Integer;
  var tmp       : Integer;
      t         : Integer;
      FirstTime : Boolean;
  Begin
    { By default, one single page }
    result:=1;

    { Calc max number of points for all active series
      associated to "AAxis" axis }
    tmp:=0;
    FirstTime:=True;

    with IParent do
    for t:=0 to SeriesCount-1 do
    With Series[t] do
    if FActive and AssociatedToAxis(AAxis) then
       if FirstTime or (Count>tmp) then
       begin
         tmp:=Count;
         FirstTime:=False;
       end;

    { If there are points... divide into pages... }
    if tmp>0 then
    Begin
      result:=tmp div FPointsPerPage;

      { extra page for remaining points... }
      if (tmp mod FPointsPerPage)>0 then
         Inc(result);
    end;
  end;

Begin
  with IParent do
  if (FPointsPerPage>0) and (SeriesCount>0) then
  begin
    if Series[0].YMandatory then
       result:=Math.Max(CalcNumPages(FTopAxis),CalcNumPages(FBottomAxis))
    else
       result:=Math.Max(CalcNumPages(FLeftAxis),CalcNumPages(FRightAxis));
  end
  else result:=1;
End;

function TChartPage.FirstValueIndex:Integer;
begin
  result:=(Current-1)*MaxPointsPerPage;
end;

Procedure TChartPage.SetPointsPerPage(const Value:Integer);
var tmp : Integer;
Begin
  if Value<0 then Raise ChartException.Create(TeeMsg_MaxPointsPerPage)
             else
             begin
               IParent.SetIntegerProperty(FPointsPerPage,Value);

               tmp:=Count;
               if FCurrent>tmp then
                  Current:=tmp;

               IParent.BroadcastTeeEventClass(TChartChangePage);
             end;
end;

Procedure TCustomAxisPanel.SetPage(const Value:Integer);
Begin
  FPage.Current:=Value;
end;

Procedure TCustomAxisPanel.SetScaleLastPage(const Value:Boolean);
Begin
  FPage.ScaleLastPage:=Value;
End;

{ Returns the number of series that are Active (visible). }
Function TCustomAxisPanel.CountActiveSeries:Integer;
var t : Integer;
Begin
  result:=0;
  for t:=0 to SeriesCount-1 do if SeriesList[t].Active then Inc(result);
end;

Function TCustomAxisPanel.NumPages:Integer;
begin
  result:=FPage.Count;
end;

{ Sends pages to the printer. This only applies when the
  Chart has more than one page (MaxPointsPerPage property).
  If no parameters are passed, all pages are printed.
  If only one page exists, then it's printed. }
procedure TCustomAxisPanel.PrintPages(FromPage, ToPage: Integer);
{$IFNDEF TEENOPRINT}
var tmpOld : Integer;
    t      : Integer;
    R      : TRect;
{$ENDIF}
begin
  {$IFNDEF TEENOPRINT}
  if Name<>'' then
     Printer.Title:=Name;

  Printer.BeginDoc;
  try
    if ToPage=0 then ToPage:=Pages.Count;
    if FromPage=0 then FromPage:=1;

    tmpOld:=Pages.Current;
    try
      R:=ChartPrintRect;

      for t:=FromPage to ToPage do
      begin
        Pages.Current:=t;
        PrintPartial(R);

        if t<ToPage then
           Printer.NewPage;
      end;
    finally
      Pages.Current:=tmpOld;
    end;

    Printer.EndDoc;
  except
    on Exception do
    begin
      Printer.Abort;

      if Printer.Printing then
         Printer.EndDoc;

      Raise;
    end;
  end;
  {$ENDIF}
end;

procedure TCustomAxisPanel.SetPages(const Value:TChartPage);
begin
  FPage.Assign(Value);
end;

Procedure TCustomAxisPanel.SetPointsPerPage(const Value:Integer);
begin
  FPage.MaxPointsPerPage:=Value;
end;

type
  TSeriesAccess=class(TChartSeries);

{ Destroys and removes all Series in Chart. The optional parameter
  defines a series class (for example: TLineSeries) to remove only
  the series of that class. }
Procedure TCustomAxisPanel.FreeAllSeries( SeriesClass:TChartSeriesClass=nil );
var t   : Integer;
    tmp : TCustomChartSeries;
begin
  t:=0;

  if SeriesList<>nil then // <-- prevent Chart Destroy on Create
  while t<SeriesCount do
  begin
    tmp:=Series[t];

    if (not Assigned(SeriesClass)) or (tmp is SeriesClass) then
    begin
      tmp.ParentChart:=nil;

      // Do not destroy series that are Detail series
      if not Assigned(TSeriesAccess(tmp).ParentSeries) then
         tmp.Free;
    end
    else Inc(t);
  end;
end;

Procedure TCustomAxisPanel.Loaded;
var t, tt: Integer;
begin
  inherited;

  if SeriesGroups.Count>0 then
  begin
    for t:=0 to SeriesGroups.Count-1 do
    begin
       with SeriesGroups[t] do
       begin
          try
              if (Assigned(FTempSeries)) and (FTempSeries.Count>0) then
              begin
              for tt:=0 to FSeries.Count-1 do
                FSeries.Delete(tt);

              for tt:=0 to FTempSeries.Count-1 do
                FSeries.Add(SeriesList.Items[StrToInt(FTempSeries[tt])]);
              end;
          finally
            Freeandnil(FTempSeries);
          end;
       end;
    end;
  end;
end;

Function TCustomAxisPanel.IsAxisVisible(const Axis:TChartAxis):Boolean;
begin
  result:=Axis.IsVisible;
end;

function TCustomAxisPanel.GetDesignOwner:TComponent;
begin
  result:=Owner;
end;

function TCustomAxisPanel.HasActiveSeries(const Axis:TChartAxis; Default:Boolean=False):Boolean;
var t : Integer;
begin
  result:=Default;

  for t:=0 to SeriesCount-1 do
  With Series[t] do
    if FActive then
       if UseAxis then
       begin
         result:=AssociatedToAxis(Axis);
         if result then
            Exit;
       end
       else
       begin
         result:=False;
         exit;
       end;
end;

{ Steps to determine if an axis is Visible:

  1) The global Chart.AxisVisible property is True... and...
  2) The Axis Visible property is True... and...
  3) At least there is a Series Active and associated to the axis and
     the Series has the "UseAxis" property True.
}
Function TCustomAxisPanel.CalcIsAxisVisible(const Axis:TChartAxis):Boolean;
Begin
  result:=FAxisVisible and Axis.FVisible;

  if result then
     if Axis.IsDepthAxis then result:=View3D
                         else result:=HasActiveSeries(Axis,True);
end;

{ Returns if no series are active (visible) and associated to
  the Axis parameter }
Function TCustomAxisPanel.NoActiveSeries(const AAxis:TChartAxis):Boolean;
var t : Integer;
begin
  for t:=0 to SeriesCount-1 do
  With Series[t] do
  if Active and AssociatedToAxis(AAxis) then
  begin
    result:=False;
    Exit;
  end;

  result:=True;
end;

function TCustomAxisPanel.IsSeriesGroupsStored:Boolean;
begin
  result:=SeriesGroups.Count>0;
end;

// Tell Series change of Axis min/max.
// For example: Surface and TriSurface will use it to discard "Reuse3D"
procedure TCustomAxisPanel.ChangedAxisScale(const Axis:TChartAxis);
var t : Integer;
begin
  for t:=0 to SeriesCount-1 do
      Series[t].ChangedAxisScale(Axis);

  Invalidate;
end;

{ Calculates the minimum or maximum for the Axis parameter.
  This function is used internally. }
Function TCustomAxisPanel.InternalMinMax(const AAxis:TChartAxis; IsMin,IsX:Boolean):TAxisValue;

  function CalcPageMinMax:TAxisValue;
  var tmpSeries : TChartSeries;
      tmpFirstPoint : Integer;
      tmpLastPoint  : Integer;
      tmpList       : TChartValueList;
      tmpNumPoints  : Integer;
      tmp           : TChartValue;
  Begin
    tmpSeries:=GetAxisSeriesMaxPoints(AAxis);

    if Assigned(tmpSeries) and (tmpSeries.Count>0) then
    Begin
      tmpSeries.CalcFirstLastPage(tmpFirstPoint,tmpLastPoint);

      if IsX then tmpList:=tmpSeries.XValues
             else tmpList:=tmpSeries.YValues;

      if IsMin then
         result:=tmpList.Value[tmpFirstPoint]
      else
      begin
        result:=tmpList.Value[tmpLastPoint];

        if not FPage.ScaleLastPage then
        begin
          tmpNumPoints:=tmpLastPoint-tmpFirstPoint+1;

          if tmpNumPoints<FPage.MaxPointsPerPage then
          begin
            tmp:=tmpList.Value[tmpFirstPoint];

            if tmp=result then
               result:=tmp+FPage.MaxPointsPerPage/tmpNumPoints
            else
               result:=tmp+FPage.MaxPointsPerPage*(result-tmp)/tmpNumPoints;
          end;
        end;
      end;
    end
    else
      result:=0;
  end;

var FirstTime : Boolean;

  procedure CalcResult(const Value:TChartValue);
  begin
    if FirstTime or
     ( IsMin and (Value<result) ) or
     ( (not IsMin) and (Value>result) ) then
    Begin
      FirstTime:=False;
      result:=Value;
    end;
  end;

var t             : Integer;
    tmpPagingAxis : Boolean;
    tmp           : TChartValue;
    tmpSeries     : TChartSeries;
    tt            : Integer;
    tmpList       : TChartValueList;
    tmpAutoScale  : Boolean;
    tmpFirst      : Integer;
    tmp2          : TChartValue;
Begin
  if AAxis.IsDepthAxis then
  begin
    if ((AAxis.CalcLabelStyle=talValue) or not (AAxis as TChartDepthAxis).LabelsAsSeriesTitles) then
    begin
      result:=0;
      FirstTime:=True;

      for tt:=0 to SeriesCount-1 do
      With Series[tt] do
      if Active then
         if IsMin then CalcResult(MinZValue)
                  else CalcResult(MaxZValue);
    end
    else
    if IsMin then result:=-0.5
             else result:=MaxZOrder+0.5;
  end
  else
  begin
    result:=0;

    tmpSeries:=GetAxisSeries(AAxis);

    if Assigned(tmpSeries) then
    begin
      if tmpSeries.YMandatory then tmpPagingAxis:=IsX
                              else tmpPagingAxis:=not IsX;
    end
    else tmpPagingAxis:=IsX;

    if (FPage.MaxPointsPerPage>0) and tmpPagingAxis then
       result:=CalcPageMinMax
    else
    begin
      tmpAutoScale:=(FPage.MaxPointsPerPage>0) and FPage.AutoScale;

      FirstTime:=True;

      for t:=0 to SeriesCount-1 do
      With Series[t] do
      if (FActive or NoActiveSeries(AAxis)) and (Count>0) and (not INoAxisMinMax) then
      Begin
        if (      IsX  and ((HorizAxis=aBothHorizAxis) or (GetHorizAxis=AAxis)) ) or
           ( (Not IsX) and ((VertAxis=aBothVertAxis) or (GetVertAxis =AAxis)) )  then
        Begin
          if tmpAutoScale then
          begin
            tmpFirst:=Pages.FirstValueIndex;

            if Count>tmpFirst then
            begin
              if IsX then tmpList:=XValues
                     else tmpList:=YValues;

              tmp:=tmpList.Value[tmpFirst];

              for tt:=tmpFirst+1 to Min(tmpFirst+Pages.MaxPointsPerPage,Count)-1 do
              begin
                tmp2:=tmpList.Value[tt];

                if IsMin then
                begin
                  if tmp2<tmp then tmp:=tmp2;
                end
                else
                  if tmp2>tmp then tmp:=tmp2;
              end;

              CalcResult(tmp);
            end;
          end
          else
          begin
            if IsMin then
               if IsX then tmp:=MinXValue else tmp:=MinYValue
            else
               if IsX then tmp:=MaxXValue else tmp:=MaxYValue;

            CalcResult(tmp);
          end;
        end;
      end;
    end;
  end;
end;

{ Calculate the Maximum value of horizontal (X) Axis }
Function TCustomAxisPanel.MaxXValue(const AAxis:TChartAxis):TAxisValue;
Begin
  result:=InternalMinMax(AAxis,False,True);
end;

{ Calculate the Maximum value of vertical (Y) Axis }
Function TCustomAxisPanel.MaxYValue(const AAxis:TChartAxis):TAxisValue;
Begin
  result:=InternalMinMax(AAxis,False,False);
end;

{ Calculate the Minimum value of horizontal (X) Axis }
Function TCustomAxisPanel.MinXValue(const AAxis:TChartAxis):TAxisValue;
Begin
  result:=InternalMinMax(AAxis,True,True);
end;

{ Calculate the Minimum value of vertical (Y) Axis }
Function TCustomAxisPanel.MinYValue(const AAxis:TChartAxis):TAxisValue;
Begin
  result:=InternalMinMax(AAxis,True,False);
end;

{ When the Legend style is "series", returns the series that
  corresponds to the Legend "ItemIndex" position.
  The "OnlyActive" parameter, when False takes into account all series,
  regardless if they are visible (active) or not. }
Function TCustomAxisPanel.SeriesLegend(ItemIndex:Integer; OnlyActive:Boolean):TChartSeries;
var t   : Integer;
    tmp : Integer;
begin
  tmp:=0;

  for t:=0 to SeriesCount-1 do
  With Series[t] do
  if ShowInLegend and ((not OnlyActive) or Active) then
     if tmp=ItemIndex then
     begin
       result:=Series[t];
       exit;
     end
     else Inc(tmp);

  result:=nil;
end;

{ Returns the Active series (visible) that corresponds to the
  ItemIndex position in the Legend }
Function TCustomAxisPanel.ActiveSeriesLegend(ItemIndex:Integer):TChartSeries;
begin
  result:=SeriesLegend(ItemIndex,True);
end;

function RemoveLineBreaks(const S:String):String;
var i : Integer;
begin
  result:=S;

  result:=ReplaceChar(result, TeeColumnSeparator, ' ');

  i:=Pos(#10#13,result);

  if i>0 then
     Delete(result,i,1)
  else
  begin
    i:=Pos(TeeTextLineSeparator,result);

    if i>0 then
       Delete(result,i,1)
  end;

  result:=ReplaceChar(result, TeeLineSeparator, ' ');

  {$IFNDEF POSIX}
  result:=ReplaceChar(result,#10,' ');
  {$ENDIF}
end;

{ Returns the string corresponding to the SeriesIndex th series for Legend }
// Result string is a single line of text (that is, line feed breaks are removed)
Function TCustomAxisPanel.SeriesTitleLegend(SeriesIndex:Integer; OnlyActive:Boolean):String;
var tmpSeries : TChartSeries;
Begin
  if OnlyActive then tmpSeries:=ActiveSeriesLegend(SeriesIndex)
                else tmpSeries:=SeriesLegend(SeriesIndex,False);

  if Assigned(tmpSeries) then
  begin
    result:=tmpSeries.LegendTitle;

    if result='' then
       result:=SeriesTitleOrName(tmpSeries,True)
    else
       result:=RemoveLineBreaks(result);
  end
  else result:='';
end;

{ Returns the width in pixels for the longest "Label" of all series,
  regardless if the series is active (visible) or not. }
Function TCustomAxisPanel.MaxTextWidth:Integer;
var t  : Integer;
    tt : Integer;
    tmp: Integer;
Begin
  result:=0;
  for t:=0 to SeriesCount-1 do
  With Series[t] do
       for tt:=0 to Labels.Count-1 do
           result:=Math.Max(result,MultiLineTextWidth(Labels[tt],tmp));
end;

{ Returns the longest width in pixels for all the active (visible)
  series marks. }
Function TCustomAxisPanel.MaxMarkWidth:Integer;
var t : Integer;
Begin
  result:=0;
  for t:=0 to SeriesCount-1 do
  With Series[t] do
       if Active then
          result:=Math.Max(result,MaxMarkWidth);
end;

{$IFDEF TEEOCX}
{$DEFINE TEEUSERANGE}
{$ELSE}
{$IFOPT R+}
{$DEFINE TEEUSERANGE}
{$ELSE}
{$IFOPT D+}
{$DEFINE TEEUSERANGE}
{$ENDIF}
{$ENDIF}
{$ENDIF}

{ Return the Index th series in SeriesList }
Function TCustomAxisPanel.GetSeries(Index:Integer):TChartSeries;
Begin
  result:=TChartSeries(FSeriesList{$IFNDEF TEEUSERANGE}.List{$ENDIF}[Index]);
end;

type
  TView3DAccess=class(TView3DOptions);

{ Calculates internal parameters for the series Width and Height
  in 3D mode. }
Procedure TCustomAxisPanel.CalcSize3DWalls;
var tmpNumSeries : Integer;
    tmp          : Double;
Begin
  if View3D then
  Begin
    if ApplyZOrder then tmpNumSeries:=Math.Max(1,MaxZOrder+1)
                   else tmpNumSeries:=1;

    tmp:=0.001*Chart3DPercent;
    if not View3DOptions.Orthogonal then
       tmp:=tmp*2;

    if CustomWidth3D then
    begin
      FSeriesWidth3D:=Round(Width3D/tmpNumSeries);
      FSeriesHeight3D:=Round(FSeriesWidth3D/tmp);

      Height3D:=Round(FSeriesHeight3D/tmpNumSeries);
    end
    else
    begin
      FSeriesWidth3D :=Round(tmp*ChartWidth);

      tmp:=TView3DAccess(View3DOptions).CalcOrthoRatio;
      if tmp>1 then FSeriesWidth3D:=Round(FSeriesWidth3D/tmp);

      FSeriesHeight3D:=Round(FSeriesWidth3D*tmp);

      Height3D:=FSeriesHeight3D * tmpNumSeries;
      Width3D :=FSeriesWidth3D  * tmpNumSeries;
    end;
  end
  else
  begin
    FSeriesWidth3D :=0;
    FSeriesHeight3D:=0;
    Width3D        :=0;
    Height3D       :=0;
  end;
end;

{ Returns the number of series in the chart }
Function TCustomAxisPanel.SeriesCount:Integer;
begin
  result:=SeriesList.Count;
end;

{ Triggers an event for all "tools" }
Procedure TCustomAxisPanel.BroadcastToolEvent(AEvent:TChartToolEvent);
var t : Integer;
begin
  for t:=0 to Tools.Count-1 do
  With Tools[t] do
  if Active then ChartEvent(AEvent);
end;

{ Triggers an event for all "animations" }
Procedure TCustomAxisPanel.BroadcastAnimationEvent(AEvent:TChartToolEvent);
var t : Integer;
begin
  for t:=0 to Animations.Count-1 do
  With Animations[t] do
  if Active then ChartEvent(AEvent);
end;

Procedure TCustomAxisPanel.CalcInvertedRotation;
begin
  InvertedRotation:=False;

  if View3D then
    if View3DOptions.Orthogonal then
    begin
      if View3DOptions.OrthoAngle>90 then
         InvertedRotation:=True;
    end
    else
    begin
      if (View3DOptions.Rotation<180) then
         InvertedRotation:=True;
    end;
end;

{$IFNDEF TEELITEEMB}
Procedure TCustomAxisPanel.DoSeriesParallel(Index:Integer);
begin
  with Series[Index] do
  if Active and CanBeforeParallel then
     DoBeforeParallel;
end;
{$ENDIF}

{ Main drawing method. Draws everything. }
Procedure TCustomAxisPanel.InternalDraw(Const UserRectangle:TRect);

  { Calculate the "Z" order for all Series }
  Procedure SetSeriesZOrder;
  var tmpSeries : Integer;
      tmpOk     : Boolean;
  begin
    FMaxZOrder:=0;
    tmpOk:=ApplyZOrder and View3D;

    if tmpOk then
    Begin
      FMaxZOrder:=-1;
      for tmpSeries:=0 to SeriesCount-1 do
          With Series[tmpSeries] do if FActive and not IInternalUse then CalcZOrder;
    end;

    { invert Z Orders }
    if not DepthAxis.Inverted then
    for tmpSeries:=0 to SeriesCount-1 do
    With Series[tmpSeries] do
    if FActive and not IInternalUse then
       if tmpOk then IZOrder:=MaxZOrder-ZOrder else IZOrder:=0;
  end;

  { Calculate the Start, End and Middle "Z" coordinates for all Series }
  Procedure SetSeriesZPositions;
  var tmp : Integer;
  begin
    for tmp:=0 to SeriesCount-1 do
    with Series[tmp] do
         if Active then CalcDepthPositions;
  end;

  { Trigger DoBeforeCalcAxis for all Series }
  Procedure DoBeforeCalcAxis;
  var tmp : Integer;
  begin
    for tmp:=0 to SeriesCount-1 do
    with Series[tmp] do
         if Active then BeforeCalcAxis;
  end;

  procedure CalcAxisRect;

    Procedure RecalcPositions;
    var tmp : Integer;
    begin
      with FAxes do
         for tmp:=0 to Count-1 do
             {$IFDEF D18}Items[tmp]{$ELSE}Get(tmp){$ENDIF}.InternalCalcPositions;
    end;

  var tmpR : TRect;
      OldR : TRect;
      tt,
      tmp  : Integer;
      tmpAxis : TChartAxis;
  begin
    with FAxes do
    for tmp:=0 to Count-1 do
    begin
      tmpAxis:={$IFDEF D18}Items[tmp]{$ELSE}Get(tmp){$ENDIF};
      tmpAxis.IsVisible:=CalcIsAxisVisible(tmpAxis);

      for tt:=0 to tmpAxis.SubAxes.Count-1 do
          tmpAxis.SubAxes[tt].IsVisible:=tmpAxis.IsVisible;

      tmpAxis.AdjustMaxMin;
    end;

    RecalcPositions;

    tmpR:=ChartRect;

    with FAxes do
    for tmp:=0 to Count-1 do
    begin
      tmpAxis:={$IFDEF D18}Items[tmp]{$ELSE}Get(tmp){$ENDIF};

      if IsAxisVisible(tmpAxis) then
      begin
        OldR:=tmpR;

        tmpAxis.CalcRect(OldR,tmpAxis.ResizeChart);  { <-- inflate only for first 5 axes }

        if TeeRectIntersects(OldR,OldR,tmpR) then
           tmpR:=OldR;
      end
      else
      if Assigned(CalcPosLabels) then
         tmpAxis.InflateAxisRect(tmpR,CalcPosLabels(tmpAxis,0));
    end;

    ChartRect:=tmpR;

    RecalcWidthHeight;

    RecalcPositions;
  end;

  // Calculate all axis offsets
  Procedure CalcSeriesRect;

    // Calculate one axis offsets, according to series margins
    Procedure CalcSeriesAxisRect(const Axis:TChartAxis);

      // Apply offsets in pixels
      Procedure ApplyOffsets(var a,b:TCoordinate);
      begin
        if Axis.Inverted then
        begin
          b:=b+Axis.MinimumOffset;
          a:=a+Axis.MaximumOffset;
        end
        else
        begin
          a:=a+Axis.MinimumOffset;
          b:=b+Axis.MaximumOffset;
        end;
      end;

      // Apply gap size in pixels
      Procedure ApplyGapSize(var a,b:TCoordinate);
      begin
        if Axis.Inverted then
           a:=a+Axis.IGapSize
        else
           b:=b+Axis.IGapSize;
      end;

    var tmpR : TRect;
        tmpDoMin  : Boolean;
        tmpDoMax  : Boolean;

      procedure ApplyMaxMinOffsets;
      var tmpHasOffsets, tmpHasGapSize : Boolean;
      begin
        tmpHasOffsets:=(Axis.MaximumOffset<>0) or (Axis.MinimumOffset<>0);
        tmpHasGapSize:=(Axis.IGapSize>0);

        if tmpHasOffsets then
           if Axis.Horizontal then ApplyOffsets(tmpR.Left,tmpR.Right)
                              else ApplyOffsets(tmpR.Bottom,tmpR.Top);

        if tmpHasGapSize then
        begin
           if Axis.Horizontal then ApplyGapSize(tmpR.Left,tmpR.Right)
                              else ApplyGapSize(tmpR.Bottom,tmpR.Top);
        end;

        Axis.AdjustMaxMinRect(tmpR);
        Axis.CalcRoundScales;

        if tmpHasOffsets and Axis.Labels then //TV52014795
        with Axis do
             FPosTitle:=InflateAxisPos(FPosLabels,SizeLabels);
      end;

      procedure CalcSeriesMargins;
      var a         : Integer;
          b         : Integer;
          tmpSeries : Integer;
      begin
        a:=0;
        b:=0;
        
        for tmpSeries:=0 to SeriesCount-1 do
        With Series[tmpSeries] do
        if Active then
           if AssociatedToAxis(Axis) then
              if Axis.Horizontal then
              begin
                CalcHorizMargins(a,b);

                with tmpR do
                begin
                  if tmpDoMin then Left :=Math.Max(Left,a);
                  if tmpDoMax then Right:=Math.Max(Right,b);
                end;
              end
              else
              begin
                CalcVerticalMargins(a,b);

                with tmpR do
                begin
                  if tmpDoMin then Bottom:=Math.Max(Bottom,b);
                  if tmpDoMax then Top   :=Math.Max(Top,a);
                end;
              end;
      end;

    Begin
      if Axis.IDepthAxis and (not View3D) then
         Exit;

      tmpDoMin:=Axis.AutomaticMinimum;
      tmpDoMax:=Axis.AutomaticMaximum;

      tmpR:=TeeZeroRect;

      if tmpDoMin or tmpDoMax then  // <--- See TCustomBarSeries CalcBarWidth
         CalcSeriesMargins;

      ApplyMaxMinOffsets;
    end;

    function IsZeroRect(const R:TRect):Boolean;
    begin
      with R do
        result:=(Left=0) and (Right=0) and (Top=0) and (Bottom=0);
    end;

    procedure CalcAxisMargins(const Axis:TChartAxis);
    var t : Integer;
        tmpR : TRect;
    begin
      if Axis.AutomaticMaximum or Axis.AutomaticMinimum then
      begin
        tmpR:=TeeZeroRect;

        for t:=0 to SeriesCount-1 do
        with Series[t] do
          if Active then
             RecalcAxisMargins(Axis,tmpR);

        if not IsZeroRect(tmpR) then
           Axis.AdjustMaxMinRect(tmpR);
      end;
    end;

  var t : Integer;
  begin
    for t:=0 to Axes.Count-1 do
        CalcSeriesAxisRect(Axes[t]);

    // 2nd pass
    for t:=0 to Axes.Count-1 do
        CalcAxisMargins(Axes[t]);
  end;

var tmp : TRect;
    Old : Boolean;
    t   : Integer;
Begin
  Old:=AutoRepaint;
  AutoRepaint:=False;

  CalcInvertedRotation;

  tmp:=UserRectangle;

  if not InternalCanvas.SupportsFullRotation then
     PanelPaint(tmp);

  BroadcastToolEvent(cteBeforeDraw);
  BroadcastAnimationEvent(cteBeforeDraw);

  DoOnBeforeDrawChart;

  for t:=0 to SeriesCount-1 do
  With Series[t] do if Active then DoBeforeDrawChart;

  if InternalCanvas.SupportsFullRotation then
  begin
    SetSeriesZOrder;
    CalcWallsRect;
  end;

  if not IsEmbedded then
  begin
    if InternalCanvas.SupportsFullRotation then
    begin
      InternalCanvas.Projection(Width3D,ChartBounds,ChartRect);
      PanelPaint(tmp);
    end;

    DrawTitlesAndLegend(True);
  end;

  if not InternalCanvas.SupportsFullRotation then
  begin
    SetSeriesZOrder;
    CalcWallsRect;
  end;

  DoBeforeCalcAxis;

  tmp:=ChartRect;

  CalcAxisRect;
  SetSeriesZPositions;
  CalcSeriesRect;

  if not IsEmbedded then
     if not InternalCanvas.SupportsFullRotation then
        InternalCanvas.Projection(Width3D,ChartBounds,ChartRect);

  DrawAxesSeries;

  DrawTitlesAndLegend(False);

  if Zoom.Active then
     DrawZoomRectangle;

  BroadcastToolEvent(cteAfterDraw);
  BroadcastAnimationEvent(cteAfterDraw);

  Canvas.ResetState;

  DoOnAfterDraw;

  AutoRepaint:=Old;
end;

procedure TCustomAxisPanel.DrawAxesSeries;

  { Draw all Axes (normal and custom) }
  Procedure DrawAllAxis;
  var t       : Integer;
      tmpAxis : TChartAxis;
  Begin
    if AxisVisible then
    begin
      TCanvas3DAccess(Canvas).BeginEntity('Axes');

      DoOnBeforeDrawAxes;

      With FAxes do
      for t:=0 to Count-1 do
      begin
        tmpAxis:=FAxes[t];
        if IsAxisVisible(tmpAxis) then
        begin
          if (tmpAxis<>Axes.Bottom) or (not DrawBackWallAfter(0)) then
             tmpAxis.Draw(True);
        end;
      end;

      TCanvas3DAccess(Canvas).EndEntity;
    end;
  end;

  procedure DrawAllSeries;
  var
    ActiveRegion : Boolean;

    { Create a clipping 3D region (cube) and apply it to Canvas }
    Procedure ClipRegionCreate;
    var tmpR : TRect;
    Begin
      if CanClip then
      begin
        tmpR:=ChartRect;
        tmpR.Top:=tmpR.Top-1;
        tmpR.Bottom:=tmpR.Bottom+1;
        Canvas.ClipCube(tmpR,0,Width3D);
        ActiveRegion:=True;
      end;
    end;

    { Remove the clipping region }
    Procedure ClipRegionDone;
    Begin
      if ActiveRegion then
      begin
        Canvas.UnClipRectangle;
        ActiveRegion:=False;
      end;
    end;

    { Draw the "Marks" of the ASeries }
    Procedure DrawMarksSeries(const ASeries:TChartSeries);
    begin
      if Assigned(ASeries.FMarks) and (ASeries.Count>0) then
      with ASeries,FMarks do
      if Visible then
      begin
        if FClip then
           ClipRegionCreate;

        TCanvas3DAccess(ParentChart.Canvas).BeginEntity('Marks');

        DrawMarks;

        TCanvas3DAccess(ParentChart.Canvas).EndEntity;

        if FClip then
           ClipRegionDone;
      end;
    end;

    { Draw one single Series }
    Procedure DrawSeries(const TheSeries:TChartSeries);

      { Draw one single point (ValueIndex) for all Series }
      Procedure DrawAllSeriesValue(ValueIndex:Integer);

        { If the ASeries parameter has the same "Z" order than the current
          series, draw the point }
        Procedure TryDrawSeries(const ASeries:TChartSeries);
        begin
          with ASeries do
          if FActive and (ZOrder=TheSeries.ZOrder) and (ValueIndex<Count) and
             (not HasZValues) then
                 DrawValue(ValueIndex)
        end;

      var t             : Integer;
          OrderedSeries : TIntArray;
      Begin
        OrderedSeries:=TheSeries.SortDrawSeries(TheSeries.SeriesIndex,
                                                SeriesCount, ValueIndex);

        with TheSeries do
        if Assigned(OrderedSeries) then
        try
          if (ValueIndex>=TheSeries.Count) or DrawSeriesForward(ValueIndex) then
             for t:=0 to High(OrderedSeries) do TryDrawSeries(ParentChart[OrderedSeries[t]])
          else
             for t:=High(OrderedSeries) downto 0 do TryDrawSeries(ParentChart[OrderedSeries[t]]);
        finally
          OrderedSeries:=nil;
        end
        else
          if (ValueIndex>=TheSeries.Count) or DrawSeriesForward(ValueIndex) then
             for t:=TheSeries.SeriesIndex to SeriesCount-1 do
                 TryDrawSeries(Series[t])
          else
             for t:=SeriesCount-1 downto TheSeries.SeriesIndex do
                 TryDrawSeries(Series[t]);

      end;

    var t             : Integer;
        tmpFirst      : Integer;
        tmpLast       : Integer;
        OrderedPoints : TIntArray;
    begin
      OrderedPoints:=nil;

      ActiveRegion:=False;  { <-- VERY IMPORTANT !!!! }

      with TheSeries do
      if (not Canvas.SupportsFullRotation) and View3D and MoreSameZOrder then
      Begin
        if FirstInZOrder then
        Begin
          tmpFirst:=-1;
          tmpLast :=-1;

          for t:=TheSeries.SeriesIndex to SeriesCount-1 do
          With Series[t] do
          if Active and (ZOrder=TheSeries.ZOrder) then
          Begin
            CalcFirstLastVisibleIndex;

            if FFirstVisibleIndex<>-1 then
            Begin
              if tmpFirst=-1 then tmpFirst:=FFirstVisibleIndex
                             else tmpFirst:=Math.Min(tmpFirst,FFirstVisibleIndex);
              if tmpLast=-1 then tmpLast:=FLastVisibleIndex
                            else tmpLast:=Math.Max(tmpLast,FLastVisibleIndex);

              DoBeforeDrawValues;

              if ClipPoints and (not ActiveRegion) then
                 ClipRegionCreate;
            end;
          end;

          { values }
          if tmpFirst<>-1 then
          begin
            OrderedPoints:=SortDrawPoints(tmpFirst, tmpLast, False);

            if Assigned(OrderedPoints) then
            try
              if DrawValuesForward then
                 for t:=0 to High(OrderedPoints) do DrawAllSeriesValue(OrderedPoints[t])
              else
                 for t:=High(OrderedPoints) downto 0 do DrawAllSeriesValue(OrderedPoints[t]);
            finally
              OrderedPoints:=nil;
            end
            else
              if DrawValuesForward then
                 for t:=tmpFirst to tmpLast do DrawAllSeriesValue(t)
              else
                 for t:=tmpLast downto tmpFirst do DrawAllSeriesValue(t);
          end;

          { Region }
          ClipRegionDone;

          { Marks and DoAfterDrawValues }
          for t:=TheSeries.SeriesIndex to SeriesCount-1 do
          With Series[t] do
          if Active and (ZOrder=TheSeries.ZOrder) and (FFirstVisibleIndex<>-1) then
          begin
            if not Series[t].Marks.OnTop then
               DrawMarksSeries(Series[t]);

            DoAfterDrawValues;
          end;
        end;
      end
      else
      begin
        CalcFirstLastVisibleIndex;

        if FFirstVisibleIndex<>-1 then
        begin
          if not IHideWhenVisible then
             if UseAxis and ClipPoints then
                ClipRegionCreate;

          TheSeries.BeginEntity;

          DoBeforeDrawValues;

          if not IHideWhenVisible then
          begin
             if (ParallelThreads<=1) or (not CanBeforeParallel) then
                DoBeforeParallel;

            DrawAllValues;

            ClipRegionDone;

            if not TheSeries.Marks.OnTop then
               DrawMarksSeries(TheSeries);
          end;

          DoAfterDrawValues;

          TCanvas3DAccess(ParentChart.Canvas).EndEntity;
        end;
      end;
    end;

    procedure DoDraw(AFirst,ALast,AInc:Integer);
    var t : Integer;
        tmpID : Boolean;
    begin
      t:=AFirst;

      repeat
        if Series[t].Active then
           DrawSeries(Series[t]);

        Inc(t,AInc);
      until t=ALast;

      // Draw OnTop Marks

      tmpID:=TCanvas3DAccess(Canvas).SupportsID;

      t:=AFirst;

      repeat
        if Series[t].Active and Series[t].Marks.OnTop then
        begin
          ActiveRegion:=False;

          // BeginEntity again to avoid clash with 'Series_' entity created above.
          if tmpID then
             TCanvas3DAccess(Canvas).BeginEntity(SeriesNameOrIndex(Series[t])+'_Top');

          DrawMarksSeries(Series[t]);

          if tmpID then
             TCanvas3DAccess(Canvas).EndEntity;
        end;

        Inc(t,AInc);
      until t=ALast;
    end;

  begin
    TCanvas3DAccess(Canvas).BeginEntity('Series');

    // Pending: Solve the case of drawing 2 or more Bar series with
    // MultiBar=mbSideAll, and other series, for example a Line series.

    if SeriesCount>0 then
       if Canvas.SupportsFullRotation or Series[0].DrawSeriesAscending then
          DoDraw(0,SeriesCount,1)
       else
          DoDraw(SeriesCount-1,-1,-1);

    TCanvas3DAccess(Canvas).EndEntity;
  end;

  {$IFNDEF TEELITEEMB}
  procedure TryParallelSeries;

    function CanBeforeParallel:Boolean;
    var t : Integer;
        tmp : TChartSeries;
    begin
      for t:=0 to SeriesCount-1 do
      begin
        tmp:=Series[t];

        if tmp.Active and tmp.CanBeforeParallel then
        begin
          result:=True;
          Exit;
        end;
      end;

      result:=False;
    end;

  begin
    if CanBeforeParallel then
       TTeeCPU.ParallelFor(0,SeriesCount-1,ParallelThreads,DoSeriesParallel
           {$IFDEF MSWINDOWS},tpHigher{$ENDIF});
  end;
  {$ENDIF}

begin
  DrawWalls;

  if AxisBehind then
  begin
    BroadcastToolEvent(cteBeforeDrawAxes);
    BroadcastAnimationEvent(cteBeforeDrawAxes);
    DrawAllAxis;
  end;

  BroadcastToolEvent(cteBeforeDrawSeries);
  BroadcastAnimationEvent(cteBeforeDrawSeries);

  {$IFNDEF TEELITEEMB}
  if ParallelThreads>1 then
     TryParallelSeries;
  {$ENDIF}

  DoOnBeforeDrawSeries;

  DrawAllSeries;

  BroadcastToolEvent(cteAfterDrawSeries);
  BroadcastAnimationEvent(cteAfterDrawSeries);

  if not AxisBehind then
  begin
    BroadcastToolEvent(cteBeforeDrawAxes);
    BroadcastAnimationEvent(cteBeforeDrawAxes);
    DrawAllAxis;
  end;
end;

procedure TCustomAxisPanel.DoOnBeforeDrawAxes;
Begin
  if Assigned(FOnBeforeDrawAxes) then FOnBeforeDrawAxes(Self);
end;

procedure TCustomAxisPanel.DoOnBeforeDrawChart;
Begin
  if Assigned(FOnBeforeDrawChart) then FOnBeforeDrawChart(Self);
end;

procedure TCustomAxisPanel.DoOnBeforeDrawSeries;
Begin
  if Assigned(FOnBeforeDrawSeries) then FOnBeforeDrawSeries(Self);
end;

procedure TCustomAxisPanel.ColorPaletteChanged;
var t : Integer;
begin
  for t:=0 to SeriesCount-1 do
  with Series[t] do
  if FColor=clTeeColor then
     ChangeInternalColor(GetFreeSeriesColor(True,Series[t]));
end;

procedure TCustomAxisPanel.DoOnAfterDraw;
Begin
  if Assigned(FOnAfterDraw) then FOnAfterDraw(Self);
end;

procedure TCustomAxisPanel.SetView3DWalls(Value:Boolean);
Begin
  SetBooleanProperty(FView3DWalls,Value);
end;

{ Internal for VCL streaming mechanism.
  Stores all Series in the DFM. }
Procedure TCustomAxisPanel.GetChildren(Proc:TGetChildProc; Root:TComponent);
var t : Integer;
begin
  inherited;

  for t:=0 to SeriesCount-1 do
      if not Series[t].DontSerialize then
         Proc(Series[t]);
end;

procedure TCustomAxisPanel.InternalDoMouseLeave;

  procedure RemoveHover;
  var t : Integer;
  begin
    for t:=0 to Axes.Count-1 do
        Axes[t].Texts.Selected.HoverIndex:=-1;

    for t:=0 to SeriesCount-1 do
        Series[t].Selected.HoverIndex:=-1;
  end;

begin
  RemoveHover;

  BroadcastToolEvent(cteMouseLeave);
  BroadcastAnimationEvent(cteMouseLeave);
end;

{$IFDEF FMX}
procedure TCustomAxisPanel.DoMouseLeave;
begin
  inherited;
  InternalDoMouseLeave;
end;
{$ELSE}
{$IFDEF USEWINDOWS}
procedure TCustomAxisPanel.CMMouseLeave(var Message: TMessage);
begin
  inherited;
  InternalDoMouseLeave;
end;
{$ENDIF}
{$ENDIF}

{ Abstract virtual }
procedure TCustomAxisPanel.RemovedDataSource( ASeries: TChartSeries;
                                              AComponent: TComponent );
begin
  if (AComponent is TTeeSeriesSource) and (AComponent=ASeries.DataSource) then
     TTeeSeriesSource(AComponent).Series:=nil;
end;

procedure TCustomAxisPanel.SetClipPoints(Value:Boolean);
Begin
  SetBooleanProperty(FClipPoints,Value);
end;

Procedure TCustomAxisPanel.SetCustomAxes(const Value:TChartCustomAxes);
begin
  FCustomAxes.Assign(Value);
end;

Procedure TCustomAxisPanel.SetHover(const Value:TTeeHover);
begin
  FHover.Assign(Value);
end;

procedure TCustomAxisPanel.SetLeftAxis(const Value:TChartAxis);
begin
  FLeftAxis.Assign(Value);
end;

procedure TCustomAxisPanel.SetDepthAxis(const Value:TChartDepthAxis);
begin
  FDepthAxis.Assign(Value);
end;

procedure TCustomAxisPanel.SetDepthTopAxis(const Value: TChartDepthAxis);
begin
  FDepthTopAxis.Assign(Value);
end;

procedure TCustomAxisPanel.SetRightAxis(const Value:TChartAxis);
begin
  FRightAxis.Assign(Value);
end;

function TCustomAxisPanel.GetSeriesGroups:TSeriesGroups;
begin
  result:=FSeriesList.FGroups;
end;

procedure TCustomAxisPanel.SetSeriesGroups(const Value:TSeriesGroups);
begin
  FSeriesList.FGroups.Assign(Value);
end;

procedure TCustomAxisPanel.SetTopAxis(const Value:TChartAxis);
begin
  FTopAxis.Assign(Value);
end;

procedure TCustomAxisPanel.SetBottomAxis(const Value:TChartAxis);
begin
  FBottomAxis.Assign(Value);
end;

{ Deletes (not destroys) a given Series.
  Triggers a "removed series" event and repaints }
Procedure TCustomAxisPanel.RemoveSeries(const ASeries:TCustomChartSeries);
Begin
  RemoveSeries(ASeries.SeriesIndex);
end;

Procedure TCustomAxisPanel.RemoveSeries(SeriesIndex: Integer);
var tmp : TChartSeries;
begin
  if SeriesIndex>=0 then
  begin
    tmp:=SeriesList[SeriesIndex];

    BroadcastSeriesEvent(tmp,seRemove);

    tmp.Removed;
    FSeriesList.Delete(SeriesIndex);

    if Assigned(FOnRemoveSeries) then
       FOnRemoveSeries(tmp);

    BroadcastSeriesEvent(tmp,seAfterRemove);

    Invalidate;
  end;
end;

{ Returns the first series that is Active (visible) and
  associated to a given Axis. }
Function TCustomAxisPanel.GetAxisSeries(const Axis:TChartAxis):TChartSeries;
var t : Integer;
Begin
  for t:=0 to SeriesCount-1 do
  begin
    result:=Series[t];

    with result do
    if (FActive or NoActiveSeries(Axis) ) and AssociatedToAxis(Axis) then
        Exit;
  end;

  result:=nil;
end;

// Returns the series that is Active (visible) and
// associated to a given Axis, and has maximum number of points.
// This function is only used when calculating min and max for axis
// when MaxPointsPerPage is > 0.  (Paging is activated)
Function TCustomAxisPanel.GetAxisSeriesMaxPoints(const Axis:TChartAxis):TChartSeries;
var t   : Integer;
    tmp : Integer;
    tmpSeries : TChartSeries;
Begin
  result:=nil;
  tmp:=-1;
  for t:=0 to SeriesCount-1 do
  begin
    tmpSeries:=Series[t];
    With tmpSeries do
    if (FActive or NoActiveSeries(Axis) ) and
       AssociatedToAxis(Axis) then
       if tmpSeries.Count>tmp then
       begin
         tmp:=tmpSeries.Count;
         result:=tmpSeries;
       end;
  end;
end;

function TCustomAxisPanel.GetPage:Integer;
begin
  result:=Pages.Current;
end;

function TCustomAxisPanel.GetPointsPerPage:Integer;
begin
  result:=Pages.MaxPointsPerPage;
end;

function TCustomAxisPanel.GetScaleLastPage:Boolean;
begin
  result:=Pages.ScaleLastPage;
end;

Function TCustomAxisPanel.FormattedValueLegend(ASeries:TChartSeries; ValueIndex:Integer):String;
begin { virtual, overrided at TChart }
  result:='';
end;

Function TCustomAxisPanel.GetDefaultColor(Index:Integer):TColor;
begin
  if Assigned(ColorPalette) then
     result:=ColorPalette[Low(ColorPalette)+(Index mod Succ(High(ColorPalette)-Low(ColorPalette)))]
  else
     result:={$IFDEF FMX}FMXTee.Procs{$ELSE}{$IFDEF D16}VCLTee.{$ENDIF}TeeProcs{$ENDIF}.GetDefaultColor(Index);
end;

{ Returns a color from the "color palette" global array that is
  not used by any series. ( SeriesColor ) }
Function TCustomAxisPanel.GetFreeSeriesColor(CheckBackground:Boolean=True; const Series:TChartSeries=nil):TColor;
var t : Integer;
    l : Integer;
Begin
  t:=0;

  if Assigned(ColorPalette) then l:=High(ColorPalette)
                            else l:=High(TeeColorPalette);

  Repeat
    result:=GetDefaultColor(t);
    Inc(t);
  Until (t>l) or IsFreeSeriesColor(result,CheckBackground,Series);

  // If there are not enough colors in the palette, get again starting from zero:
  if t>l then
     result:=GetDefaultColor(SeriesCount);
end;

Function TCustomAxisPanel.AddSeries(const ASeries:TChartSeries):TChartSeries;
begin
  ASeries.ParentChart:=Self;
  result:=ASeries;
end;

// Wrong BCB hpp generation (BCB bug) when using "const", (can't compile GridToChart demo)
procedure TCustomAxisPanel.AddSeries({const} SeriesArray:Array of TChartSeries);
var
  {$IFDEF D9}
  Series : TChartSeries;
  {$ELSE}
  t : Integer;
  {$ENDIF}
begin
  {$IFDEF D9}
  for Series in SeriesArray do
      Series.ParentChart:=Self;
  {$ELSE}
  for t:=Low(SeriesArray) to High(SeriesArray) do
      SeriesArray[t].ParentChart:=Self;
  {$ENDIF}
end;

function TCustomAxisPanel.AddSeries(ASeriesClass:TChartSeriesClass):TChartSeries;
begin
  result:=AddSeries(ASeriesClass.Create(Owner));
end;

{ internal. Calls CheckMouse for all visible Axes. }
function TCustomAxisPanel.CheckMouseAxes(x,y:Integer):Boolean;

  function CheckMouse(const Axis:TChartAxis):Boolean;
  var t: Integer;
  begin
    Axis.Items.InitCalcRect;

    for t:=0 to Axis.Items.Count-1 do
      if PointInPolygon(TeePoint(x,y), TAxisLabelsFormatAccess(Axis.Items[t].Format).FRotatedBounds) then
      begin
        Axis.Texts.Selected.HoverIndex:=t;

        result:=True;
        Exit;
      end;

    Axis.Texts.Selected.HoverIndex:=-1;
    result:=False;
  end;

  procedure ProcessAxis(const Axis:TChartAxis; var AResult:Boolean);
  var t : Integer;
  begin
    if Axis.IsVisible and Axis.Texts.Selected.Hover.Visible then
       if CheckMouse(Axis) then
          AResult:=True;  // do not break. Process all axes.

    for t:=0 to Axis.SubAxes.Count-1 do
        ProcessAxis(Axis.SubAxes[t],AResult);
  end;

var t : Integer;
begin
  result:=False;

  if Hover.Visible then
     for t:=0 to FAxes.Count-1 do
         ProcessAxis(FAxes.{$IFDEF D18}Items[t]{$ELSE}Get(t){$ENDIF},result);
end;

{ internal. Calls CheckMouse for all visible Series. }
function TCustomAxisPanel.CheckMouseSeries(x,y:Integer):Boolean;
var t : Integer;
begin
  result:=False;

  // Call CheckMouse for all active visible series
  for t:=SeriesCount-1 downto 0 do
      with Series[t] do
      if Active then
         if CheckMouse(x,y) then
            result:=True;
end;

{ Triggers an event specific to Series }
Procedure TCustomAxisPanel.BroadcastSeriesEvent(const ASeries:TCustomChartSeries;
                                                Event:TChartSeriesEvent);
var tmp : TTeeSeriesEvent;
begin
  if (not DoNotBroadcast) and (Listeners.Count>0) then
  begin
    tmp:=TTeeSeriesEvent.Create;
    try
      tmp.Event:=Event;
      tmp.Series:=ASeries;
      BroadcastTeeEvent(tmp);
    finally
      tmp.Free;
    end;
  end;
end;

Procedure TCustomAxisPanel.InternalAddSeries(const ASeries:TCustomChartSeries);
Begin
  if SeriesList.IndexOf(ASeries as TChartSeries)=-1 then  // If ASeries does not already exists
  begin
    ApplyThemeSeries(ASeries); // <-- this should be done before calling "Added" to not overwrite SeriesColor

    with ASeries do
    begin
      FParent:=Self;
      Added;
    end;

    FSeriesList.Add(ASeries as TChartSeries);

    BroadcastSeriesEvent(ASeries,seAdd);

    if Assigned(FOnAddSeries) then
       FOnAddSeries(ASeries);

    Invalidate;
  end;
end;

function TCustomAxisPanel.FindSameSeriesClass(const ASeries:TCustomChartSeries):TChartSeries;
var t : Integer;
begin
  result:=nil;

  for t:=0 to SeriesCount-1 do
      if Series[t].SameClass(ASeries as TChartSeries) then
      begin
        result:=Series[t];
        break;
      end;
end;

function TCustomAxisPanel.FindThemedSeries:TChartSeries;
var t : Integer;
begin
  result:=nil;

  for t:=0 to SeriesCount-1 do
    if (Series[t] is TThemedSeries) then
    begin
      result:=Series[t];
      break;
    end;
end;

procedure TCustomAxisPanel.ApplyThemeSeries(const ASeries:TCustomChartSeries);
var tmp : TChartSeries;
    tmpPanel : TCustomAxisPanel;
begin
  if Assigned(Theme) then
  begin
    tmpPanel:=TCustomAxisPanel(Theme);

    tmp:=tmpPanel.FindSameSeriesClass(ASeries);

    if not Assigned(tmp) then
    begin
      tmp:=tmpPanel.FindThemedSeries;

      if (tmp=nil) or (tmpPanel.SeriesCount=0) then
      begin
        tmpPanel.AddSeries(TChartSeries);
        tmp:=tmpPanel[0];
      end;
    end;

    TSeriesAccess(ASeries).AssignFormatting(tmp);
  end;
end;

function TCustomAxisPanel.FindSameToolClass(const ATool:TTeeCustomTool):TTeeCustomTool;
var t : Integer;
begin
  result:=nil;

  for t:=0 to Tools.Count-1 do
      if Tools[t].SameClass(ATool as TTeeCustomTool) then
      begin
        result:=Tools[t];
        break;
      end;
end;

type
  TToolsAccess=class(TTeeCustomTool);

procedure TCustomAxisPanel.ApplyThemeTools(const ATool:TTeeCustomTool);
var tmp : TTeeCustomTool;
    tmpPanel : TCustomAxisPanel;
begin
  if Assigned(Theme) then
  begin
    tmpPanel:=TCustomAxisPanel(Theme);

    tmp:=tmpPanel.FindSameToolClass(ATool);

    if not Assigned(tmp) then
    begin
      //tmp:=tmpPanel.FindThemedSeries;

      if (tmp=nil) or (tmpPanel.SeriesCount=0) then
      begin
        tmpPanel.Tools.Add(TTeeCustomTool);
        tmp:=tmpPanel.Tools[0];
      end;
    end;

    TToolsAccess(ATool).Assign(tmp);//.AssignFormatting(tmp);
  end;
end;  

function TCustomAxisPanel.FindSameAxisClass(const AAxis:TChartAxis):TChartAxis;
var t : Integer;
    tmp: TChartAxis;
begin
  result:=nil;

  for t:=0 to Axes.Count-1 do
  begin
    tmp:=Axes[t];

    if (tmp.Visible) and (tmp.Horizontal = AAxis.Horizontal) then
    begin
      result:=tmp;
      break;
    end;
  end;
end;

type
  TAxisAccess=class(TChartAxis);

procedure TCustomAxisPanel.ApplyThemeAxes(const AAxis:TChartAxis);
var tmp : TChartAxis;
    tmpPanel : TCustomAxisPanel;
begin
  if Assigned(Theme) then
  begin
    tmpPanel:=TCustomAxisPanel(Theme);

    tmp:=tmpPanel.FindSameAxisClass(AAxis);

    if not Assigned(tmp) then
    begin

      if (tmp=nil) or (tmpPanel.SeriesCount=0) then
      begin
        tmpPanel.CustomAxes.Add();
        tmp:=tmpPanel.CustomAxes[0];
      end;
    end;

    TAxisAccess(AAxis).Assign(tmp);//.AssignFormatting(tmp);
  end;
end;

{ Returns the number of points of the Active (visible) Series
  that has more points. }
Function TCustomAxisPanel.GetMaxValuesCount:Integer;
var t         : Integer;
    FirstTime : Boolean;
Begin
  result:=0;
  FirstTime:=True;

  for t:=0 to SeriesCount-1 do
  with Series[t] do
  if Active and ( FirstTime or (Count>result) ) then
  begin
    result:=Count;
    FirstTime:=False;
  end;
end;

Procedure TCustomAxisPanel.SetAxisBehind(Value:Boolean);
Begin
  SetBooleanProperty(FAxisBehind,Value);
end;

Procedure TCustomAxisPanel.SetAxisVisible(Value:Boolean);
Begin
  SetBooleanProperty(FAxisVisible,Value);
end;

{ Prevents a circular relationship between series and
  series DataSource. Raises an exception. }
Procedure TCustomAxisPanel.CheckOtherSeries(const Dest,Source:TChartSeries);
var t : Integer;
Begin
  if Assigned(Source) then
  if Source.DataSource=Dest then
     Raise ChartException.Create(TeeMsg_CircularSeries)
  else
     if Source.DataSource is TChartSeries then
     for t:=0 to Source.DataSources.Count-1 do
        CheckOtherSeries(Dest,TChartSeries(Source.DataSources[t]));
end;

{ Returns True when the AComponent is a valid DataSource for the ASeries
  parameter. }
Function TCustomAxisPanel.IsValidDataSource(ASeries:TChartSeries; AComponent:TComponent):Boolean;
Begin
  result:=ASeries.IsValidDataSource(AComponent);

  { check virtual "data source" }
  if not result then
     if AComponent is TTeeSeriesSource then
        result:=TTeeSeriesSource(AComponent).Available(Self);

  if result and Assigned(FOnValidDataSource) then
     FOnValidDataSource(Self,ASeries,AComponent,result);
end;

{ When ASeries has a DataSource that is another Series,
  calls the AddValues method to add (copy) all the points from
  the DataSource to the ASeries. }
Procedure TCustomAxisPanel.CheckDatasource(ASeries:TChartSeries);
Begin
  With ASeries do
  if not (csLoading in ComponentState) then
  begin
     if Assigned(DataSource) then
     begin
       if DataSource is TChartSeries then
          AddValues(TChartSeries(DataSource))
       else
       if DataSource is TTeeSeriesSource then
          TTeeSeriesSource(DataSource).Refresh;
     end
     else
     if Assigned(FunctionType) then
     begin
       if FunctionType.NoSourceRequired then
       begin
         BeginUpdate;  // before Clear
         try
           Clear;
           FunctionType.AddPoints(nil); // calculate function
         finally
           EndUpdate; // propagate changes...
         end;
       end;
     end
     else
     if not ManualData then
        if CanAddRandomPoints then
           FillSampleValues(NumSampleValues)
  end;
end;

function TCustomAxisPanel.DrawBackWallAfter(Z: Integer): Boolean;
var P : TFourPoints;
begin
  if Canvas.SupportsFullRotation then
     result:=False
  else
  begin
    Canvas.FourPointsFromRect(ChartRect,Z,P);
    result:=not TeeCull(P);
  end;
end;

// Swaps one series with another in the series list.
// Triggers a series event (swap event) and repaints.
Procedure TCustomAxisPanel.ExchangeSeries(a,b:Integer);
var tmpIndex : Integer;
    tmp      : Integer;
begin
  if Series[a].IInternalUse then
    for tmp:=a to SeriesCount-1 do
      if not Series[tmp].IInternalUse then
      begin
        a:=tmp;
        break;
      end;

  if Series[b].IInternalUse then
    for tmp:=b to SeriesCount-1 do
      if not Series[tmp].IInternalUse then
      begin
        b:=tmp;
        break;
      end;

  SeriesList.Exchange(a,b);
  tmpIndex:=Series[a].ComponentIndex;
  Series[a].ComponentIndex:=Series[b].ComponentIndex;
  Series[b].ComponentIndex:=tmpIndex;

  // If both Series are contained in the same series group,
  // exchange them too.
  tmp:=SeriesList.Groups.Contains(Series[a]);
  if (tmp<>-1) and (SeriesList.Groups[tmp].Series.IndexOf(Series[b])<>-1) then
     with SeriesList.Groups[tmp].Series do
          Exchange(IndexOf(Series[a]),IndexOf(Series[b]));

  BroadcastSeriesEvent(nil,seSwap);
  Invalidate;
end;

Procedure TCustomAxisPanel.ExchangeSeries(a,b:TCustomChartSeries);
var tmp : TCustomAxisPanel;
begin
  if a.ParentChart=b.ParentChart then
     ExchangeSeries(a.SeriesIndex,b.SeriesIndex)  // same chart
  else
  begin
    // switch charts
    tmp:=a.ParentChart;
    a.ParentChart:=b.ParentChart;
    b.ParentChart:=tmp;
  end;
end;

{ Copies all settings from one chart to self. }
Procedure TCustomAxisPanel.Assign(Source:TPersistent);
begin
  if Source is TCustomAxisPanel then
  With TCustomAxisPanel(Source) do
  begin
    Self.F3DPercent        := F3DPercent;
    Self.FAxisBehind       := FAxisBehind;
    Self.FAxisVisible      := FAxisVisible;
    Self.BottomAxis        := FBottomAxis;
    Self.FClipPoints       := ClipPoints;
    Self.ColorPalette      := ColorPalette;
    Self.CustomAxes        := FCustomAxes;
    Self.FCustomWidth3D    := FCustomWidth3D;
    Self.Hover             := FHover;
    Self.LeftAxis          := FLeftAxis;
    Self.DepthAxis         := FDepthAxis;
    Self.DepthTopAxis      := FDepthTopAxis;
    Self.Pages             := FPage;
    Self.RightAxis         := FRightAxis;
    Self.TopAxis           := FTopAxis;
    Self.FView3DWalls      := FView3DWalls;
  end;

  inherited;
end;

function TCustomAxisPanel.IsCustomAxesStored: Boolean;
begin
  result:=FCustomAxes.Count>0;
end;

{ TTeeSeriesSource }

{ Base abstract class for Series "datasources" }

Constructor TTeeSeriesSource.Create(AOwner: TComponent);
begin
  inherited;
  FActive:=False;
end;

Destructor TTeeSeriesSource.Destroy;
begin
  Close;
  inherited;
end;

procedure TTeeSeriesSource.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited;
  if (Operation=opRemove) and Assigned(FSeries) and (AComponent=FSeries) then
     Series:=nil;
end;

procedure TTeeSeriesSource.SetSeries(const Value: TChartSeries);
var Old : TChartSeries;
begin
  if FSeries<>Value then
  begin
    {$IFNDEF TEEOCX}
    Close;

    if Assigned(FSeries) then
       FSeries.RemoveFreeNotification(Self);
    {$ENDIF}

    Old:=FSeries;

    FSeries:=Value;

    if Assigned(FSeries) then
    begin
      FSeries.FreeNotification(Self);
      FSeries.DataSource:=Self;
    end
    else
    if Assigned(Old) then
       Old.DataSource:=nil;

    {$IFDEF TEEOCX}
    Close;
    {$ENDIF}
  end;
end;

{ Load data into Series or Clear Series }
procedure TTeeSeriesSource.SetActive(const Value: Boolean);
begin
  if FActive<>Value then
  begin
    if Assigned(FSeries) then
    begin
      if Value then
      begin
        { if True, load points into Series }
        if not (csLoading in ComponentState) then
           Load;
      end
      else
      if (not (csDestroying in ComponentState)) and
         (not (csDestroying in FSeries.ComponentState)) then
              if (LoadMode=lmClear) and (not KeepDataOnClose) then
                 FSeries.Clear; { remove Series points }
    end;

    FActive:=Value;
  end;
end;

procedure TTeeSeriesSource.Close;
begin { set Active property to False }
  if (not (csLoading in ComponentState)) and
     (not (csDestroying in ComponentState)) then
           Active:=False;
end;

Procedure TTeeSeriesSource.Load; // virtual; abstract;
begin
end;

procedure TTeeSeriesSource.Open;
begin { set Active property to True }
  Active:=True;
end;

Procedure TTeeSeriesSource.Refresh;
begin { close and re-open the datasource }
  if Active and (not (csLoading in ComponentState)) then
     Load;
end;

{ after loading from DFM, and if Active, load data }
Procedure TTeeSeriesSource.Loaded;
begin
  inherited;
  Refresh;
end;

{ return True when the AChart parameter supports this datasource }
class function TTeeSeriesSource.Available(AChart: TCustomAxisPanel): Boolean;
begin
  result:=True;
end;

class Function TTeeSeriesSource.Description:String;
begin
  result:='';
end;

class Function TTeeSeriesSource.Editor:TComponentClass;
begin
  result:=nil;
end;

{ return True when Series DataSource is same class }
class function TTeeSeriesSource.HasSeries(ASeries: TChartSeries): Boolean;
begin
  result:=ASeries.DataSource.ClassNameIs(ClassName);
end;

{ returns True if this series source allows "New,Edit,Delete" buttons
  at Series DataSource editor dialog }
class function TTeeSeriesSource.HasNew: Boolean;
begin
  result:=False;
end;

{ Utility functions }

{ Returns the series Title (or series Name if Title is empty). }
// SingleLine =True means we dont want linefeeds in the string (#13#10 or #10#13 etc)
Function SeriesTitleOrName(const ASeries:TCustomChartSeries; const SingleLine:Boolean=False):String;
begin
  result:=ASeries.Title;

  if Trim(result)='' then
     result:=SeriesNameOrIndex(ASeries)
  else
  if SingleLine then
     result:=RemoveLineBreaks(result);
end;

{ Adds all Series to the AItems parameter. }
Procedure FillSeriesItems(const AItems:TStrings; const AList:TCustomSeriesList;
                          UseTitles:Boolean=True);
var t   : Integer;
    tmp : TChartSeries;
begin
  AItems.BeginUpdate;
  try
    with AList do
    for t:=0 to Count-1 do
    begin
      tmp:=Items[t];

      if (((csDesigning in tmp.ComponentState) and
         (not TSeriesAccess(tmp).DontSerialize)) or tmp.ShowInEditor) and
         not tmp.IInternalUse then
           if UseTitles then
              AItems.AddObject(SeriesTitleOrName(tmp,True),tmp)
           else
              AItems.AddObject(tmp.Name,tmp);
    end;
  finally
    AItems.EndUpdate;
  end;
end;

{ List of registered Series Source components }
var FTeeSources:TList{$IFDEF TEELISTMETA}<TTeeSeriesSourceClass>{$ENDIF}; // QC115377

function TeeSources:TList{$IFDEF TEELISTMETA}<TTeeSeriesSourceClass>{$ENDIF};
begin
  if not Assigned(FTeeSources) then
     FTeeSources:=TList{$IFDEF TEELISTMETA}<TTeeSeriesSourceClass>{$ENDIF}.Create;

  result:=FTeeSources;
end;

Procedure ShowMessageUser(Const S:String);
{$IFDEF FMX}

{$IFDEF D24}
var DialogSvc: IFMXDialogServiceSync;
{$ENDIF}

{$ELSE}
var St : Array[0..1023] of Char;
{$ENDIF}
begin
  {$IFDEF FMX}

  {$IFDEF D24}
  if TPlatformServices.Current.SupportsPlatformService(IFMXDialogServiceSync, DialogSvc) then
     DialogSvc.ShowMessageSync(S);
  {$ELSE}
  ShowMessage(S);
  {$ENDIF}

  {$ELSE}
  MessageBox(0, StrPCopy(St,S),'',MB_OK or MB_ICONSTOP {$IFDEF MSWINDOWS}or MB_TASKMODAL{$ENDIF});
  {$ENDIF}
end;

{ Returns if a Series has "X" values (or Y values for HorizBar series) }
Function HasNoMandatoryValues(const ASeries:TChartSeries):Boolean;
var t        : Integer;
    tmpCount : Integer;
    tmp      : TChartValueList;
begin
  result:=False;

  With ASeries do
  if IUseNotMandatory and (Count>0) then
  begin
    tmp:=ASeries.NotMandatoryValueList;

    if (tmp.First=0) and (tmp.Last=Count-1) then
    begin
      tmpCount:=Math.Min(10000,Count-1);

      for t:=0 to tmpCount do
      if tmp.Value[t]<>t then
      begin
        result:=True;
        Exit;
      end;
    end
    else result:=True;
  end;
end;

{ Returns if a Series has Colors }
Function HasColors(const ASeries:TChartSeries):Boolean;
var t        : Integer;
    tmpCount : Integer;
    tmpColor : TColor;
    tmpSeriesColor:TColor;
begin
  result:=False;
  With ASeries do
  begin
    tmpSeriesColor:=SeriesColor;
    tmpCount:=Math.Min(10000,Count-1);
    for t:=0 to tmpCount do
    begin
      tmpColor:=InternalColor(t);
//      tmpColor:=ValueColor[t];
      if (tmpColor<>clTeeColor) and
         (tmpColor<>tmpSeriesColor) then
      begin
        result:=True;
        exit;
      end;
    end;
  end;
end;

{ Returns if a Series has labels }
Function HasLabels(const ASeries:TChartSeries):Boolean;
var t        : Integer;
    tmpCount : Integer;
begin
  result:=False;

  if ASeries.Labels.Count>0 then
  begin
    tmpCount:=Math.Min(100,ASeries.Count-1);

    for t:=0 to tmpCount do
    if ASeries.Labels[t]<>'' then
    begin
      result:=True;
      Exit;
    end;
  end;
end;

{ TDataSourcesList }

{$IFDEF D18}
function TDataSourcesList.Get(Index:Integer):TComponent;
begin
  result:=List[Index];
end;

function TDataSourcesList.Count:Integer;
begin
  result:=Length(List);
end;

function TDataSourcesList.IndexOf(const Value:TComponent):Integer;
var t : Integer;
begin
  for t:=0 to Count-1 do
    if List[t]=Value then
       Exit(t);

  result:=-1;
end;

procedure TDataSourcesList.Remove(const Value:TComponent);
var t : Integer;
    i : Integer;
    c : Integer;
begin
  i:=IndexOf(Value);

  if i>=0 then
  begin
    c:=Count-1;
    for t:=i to c-1 do
        List[t]:=List[t+1];
    SetLength(List,c);
  end;
end;
{$ELSE}

// When calling Series1.DataSources.Remove (or Delete) methods,
// Notify is called to make sure the Series does its cleaning.
procedure TDataSourcesList.Notify(Ptr: Pointer; Action: TListNotification);
begin
  inherited;

  if Action=lnDeleted then
     Series.InternalRemoveDataSource(Ptr)
end;
{$ENDIF}

// When adding a source directly using Series1.DataSources.Add( xxx )
// this method calls the Series internal method.
function TDataSourcesList.Add(const Value:TComponent):Integer;
begin
  result:=Series.InternalSetDataSource(Value,False);
end;

// Call the TList Add method directly
function TDataSourcesList.InheritedAdd(const Value:TComponent):Integer;
begin
  {$IFDEF D18}
  result:=Length(List);
  SetLength(List, result+1);
  List[result]:=Value;
  {$ELSE}
  result:=inherited Add(Value);
  {$ENDIF}
end;

// Call the TList Clear method directly
procedure TDataSourcesList.InheritedClear;
begin
  {$IFDEF D18}
  List:=nil;
  {$ELSE}
  inherited Clear;
  {$ENDIF}
end;

// When calling Series1.DataSources.Clear, this method
// does all the appropiate cleaning.
procedure TDataSourcesList.Clear;
begin
  Series.RemoveAllLinkedSeries;
  InheritedClear;

  // Remove all points in series, unless data is "manual".
  if not Series.ManualData then
     Series.Clear;
end;

{ TLabelsList }

{ Returns the label string for the corresponding point (ValueIndex).
  Returns an empty string is no labels exist. }
function TLabelsList.GetLabel(ValueIndex: Integer): String;
begin
  // Here there's already a range protection.
  // No need to access inherited Items with R+ compiler option.

  {$IFDEF D18}
  if (ValueIndex<0) or (ValueIndex>=Count) then
     result:=''
  else
     result:=List[ValueIndex];
  {$ELSE}

  if (Count<=ValueIndex) or (List{$IFDEF TEELISTARRAY}^{$ENDIF}[ValueIndex]=nil) then
     result:=''
  else
     result:=PString(List{$IFDEF TEELISTARRAY}^{$ENDIF}[ValueIndex])^;

  {$ENDIF}
end;

{ Replaces a Label into the labels list.
  Allocates the memory for the ALabel string parameter. }
function TLabelsList.IndexOfLabel(const ALabel: String; CaseSensitive:Boolean=True): Integer;
var t   : Integer;
    tmp : String;
begin
  if CaseSensitive then
  begin
    for t:=0 to Count-1 do
    if Labels[t]=ALabel then
    begin
      result:=t;
      exit;
    end;
  end
  else
  begin
    tmp:=UpperCase(ALabel);
    for t:=0 to Count-1 do
    if UpperCase(Labels[t])=tmp then
    begin
      result:=t;
      exit;
    end;
  end;

  result:=-1;
end;

procedure TLabelsList.SetLabel(ValueIndex: Integer; const ALabel: String);

  procedure AddPending;
  var t : Integer;
  begin
    if Count<=ValueIndex then
    begin
      {$IFDEF D18}
      SetLength(List,ValueIndex+1);
      {$ENDIF}

      for t:=Count to ValueIndex do
          {$IFDEF D18}List[t]:=''{$ELSE}Add(nil){$ENDIF};
    end;
  end;

{$IFNDEF D18}
var P : PString;
{$ENDIF}
begin
  {$IFDEF D18}
  if ALabel='' then
  begin
    IAnyEmpty:=True;

    if (Count>0) and (ValueIndex<Count) then
       List[ValueIndex]:='';
  end
  else
  begin
    AddPending;
    List[ValueIndex]:=ALabel;
  end;

  {$ELSE}
  if (Count>ValueIndex) and Assigned(List{$IFDEF TEELISTARRAY}^{$ENDIF}[ValueIndex]) then
      Dispose(PString(List{$IFDEF TEELISTARRAY}^{$ENDIF}[ValueIndex]));

  if ALabel='' then
  begin
    IAnyEmpty:=True;

    if (Count>0) and (ValueIndex<Count) then
       List{$IFDEF TEELISTARRAY}^{$ENDIF}[ValueIndex]:=nil
  end
  else
  begin
    AddPending;

    New(P);
    P^:=ALabel;
    List{$IFDEF TEELISTARRAY}^{$ENDIF}[ValueIndex]:=P;
  end;
  {$ENDIF}

  Series.Repaint;
end;

{ Allocates memory for the ALabel string parameter and inserts it
  into the labels list. }
Procedure TLabelsList.InsertLabel(ValueIndex:Integer; Const ALabel:String);

  Procedure InternalInsert{$IFNDEF D18}(const P:PString){$ENDIF};
  {$IFDEF D18}
  var t : Integer;
  {$ENDIF}
  begin
    {$IFDEF D18}
    SetLength(List, Max(Count,ValueIndex)+1);

    while Count<ValueIndex-1 do
       List[Count-1]:='';

    for t:=Count-1 downto ValueIndex+1 do
        List[t]:=List[t-1];

    List[ValueIndex]:=ALabel;
    {$ELSE}

    while Count<ValueIndex do
          Add(nil);

    Insert(ValueIndex,P);
    {$ENDIF}
  end;

{$IFNDEF D18}
var P : PString;
{$ENDIF}
Begin
  if ALabel='' then
  begin
    IAnyEmpty:=True;

    if Count>0 then
       InternalInsert{$IFNDEF D18}(nil){$ENDIF};
  end
  else
  begin
    {$IFDEF D18}
    InternalInsert;
    {$ELSE}
    New(P);
    P^:=ALabel;
    InternalInsert(P);
    {$ENDIF}
  end;
end;

procedure TLabelsList.Assign(const Source: TLabelsList);
var t : Integer;
begin
  Clear;

  for t:=0 to Source.Count-1 do
      InsertLabel(t,Source[t]);

  FCaseSensitive:=Source.FCaseSensitive;
end;

{$IFDEF D18}
Destructor TLabelsList.Destroy;
begin
  List:=nil;
  inherited;
end;

procedure TLabelsList.Clear;
begin
  List:=nil;
  IAnyEmpty:=False;
end;

function TLabelsList.Count:Integer;
begin
  result:=Length(List);
end;

{$ELSE}
procedure TLabelsList.Clear;
var t : Integer;
begin
  for t:=0 to Count-1 do
      if Assigned(List{$IFDEF TEELISTARRAY}^{$ENDIF}[t]) then
         Dispose(PString(List{$IFDEF TEELISTARRAY}^{$ENDIF}[t]));

  IAnyEmpty:=False;

  inherited;
end;
{$ENDIF}

procedure TLabelsList.Exchange(const A,B:Integer);
var tmp : String;
begin
  tmp:=Labels[B];
  Labels[B]:=Labels[A];
  Labels[A]:=tmp;
end;

Procedure TLabelsList.DeleteLabel(ValueIndex:Integer);
{$IFDEF D18}
var t : Integer;
{$ENDIF}
Begin
  {$IFDEF D18}

  (*
  // Faster alternative:
  tmp:=Count-1;

  // Refcounted Strings need Finalize before deleting them
  Finalize(List[ValueIndex]);

  if tmp-Index>0 then
     System.Move(List[Index+1],List[Index],SizeOf(String)*(tmp-Index));

  // Last Refcounted Strings need to Initialize before deleting them because SetLength
  for t:=tmp to Count-1 do
      Initialize(List[t]);
  *)

  // Slower loop
  for t:=ValueIndex to Count-2 do
      List[t]:=List[t+1];

  SetLength(List,Count-1);
  {$ELSE}

  if (Count>ValueIndex) and Assigned(List{$IFDEF TEELISTARRAY}^{$ENDIF}[ValueIndex]) then
     Dispose(PString(List{$IFDEF TEELISTARRAY}^{$ENDIF}[ValueIndex]));

  Delete(ValueIndex);
  {$ENDIF}
end;

procedure TLabelsList.Resize(const ACount:Integer);
{$IFNDEF D18}
var t : Integer;
{$ENDIF}
begin
  {$IFDEF D18}
  SetLength(List,ACount);
  {$ELSE}
  Clear;

  for t:=0 to ACount-1 do
      Add(nil);
  {$ENDIF}
end;

procedure TLabelsList.SetCaseSensitive(Value: Boolean);
begin
  Series.SetBooleanProperty(FCaseSensitive,Value);
end;

{ TSeriesPointer }

Constructor TSeriesPointer.Create(const AOwner:TChartSeries);
Begin
  // Before calling inherited Create:
  if Assigned(AOwner) then
     ParentChart:=AOwner.ParentChart;

  inherited Create;

  AllowChangeSize:=True;

  FSeries:=AOwner;
  FInflate:=True;
  FCenterPixel:=True;
  FHorizSize:=4;
  FVertSize:=4;
  FDark3D:=True;
  FDraw3D:=True;

  //FStyle:=psRectangle;  <-- not necessary
end;

Destructor TSeriesPointer.Destroy;
begin
  FPicture.Free;
  FEmboss.Free;
  FShadow.Free;
  FSelected.Free;
  inherited;
end;

Procedure TSeriesPointer.ChangeStyle(NewStyle:TSeriesPointerStyle);
Begin
  FStyle:=NewStyle;
end;

Procedure TSeriesPointer.ChangeHorizSize(NewSize:Integer);
Begin
  FHorizSize:=NewSize;
End;

Procedure TSeriesPointer.ChangeVertSize(NewSize:Integer);
Begin
  FVertSize:=NewSize;
End;

function TSeriesPointer.GetGradient:TTeeGradient;
begin
  result:=Brush.Gradient;
end;

function TSeriesPointer.GetEmboss:TTeeEmboss;
begin
  if not Assigned(FEmboss) then
  begin
    FEmboss:=TTeeEmboss.Create(CanvasChanged);
    TEmbossAccess(FEmboss).InitValues(False,FEmboss.Color,3);
  end;

  result:=FEmboss;
end;

function TSeriesPointer.GetShadow:TTeeShadow;
begin
  if not Assigned(FShadow) then
  begin
    FShadow:=TTeeShadow.Create(CanvasChanged);
    TShadowAccess(FShadow).InitValues(False,FShadow.Color,3);
  end;

  result:=FShadow;
end;

Procedure TSeriesPointer.SetHorizSize(Value:Integer);
Begin
  if Value<1 then Value:=1;

  if HorizSize<>Value then
  begin
    FHorizSize:=Value;
    Repaint;
  end;
end;

function TSeriesPointer.GetPicture: TTeePicture;
begin
  if not Assigned(FPicture) then
  begin
     FPicture:=TTeePicture.Create;
     FPicture.OnChange:=CanvasChanged;
  end;

  result:=FPicture;
end;

procedure TSeriesPointer.SetPicture(const Value: TTeePicture);
begin
  if Assigned(Value) then Picture.Assign(Value)
                     else FreeAndNil(FPicture);
end;

procedure TSeriesPointer.SetSize(const Value:Integer);
begin
  HorizSize:=Value;
  VertSize:=Value;
end;

procedure TSeriesPointer.SetSizeFloat(const Value:Single);
begin
  FSizeFloat:=Value;
  // Repaint; // pending
end;

procedure TSeriesPointer.SetSizeUnits(const Value:TPointerSizeUnits);
begin
  FSizeUnits:=Value;
  // Repaint; // pending
end;

Procedure TSeriesPointer.SetInflate(Value:Boolean);
begin
  if FInflate<>Value then
  begin
    FInflate:=Value;
    Repaint;
  end;
end;

Procedure TSeriesPointer.SetCenterPixel(Value:Boolean);
begin
  if FCenterPixel<>Value then
  begin
    FCenterPixel:=Value;
    Repaint;
  end;
end;

Procedure TSeriesPointer.SetVertSize(Value:Integer);
Begin
  if Value<1 then Value:=1;

  if FVertSize<>Value then
  begin
    FVertSize:=Value;
    Repaint;
  end;
end;

Procedure TSeriesPointer.SetDark3D(Value:Boolean);
Begin
  if FDark3D<>Value then
  begin
    FDark3D:=Value;
    Repaint;
  end;
end;

Procedure TSeriesPointer.SetDepth(Value:Integer);
begin
  if Depth<>Value then
  begin
    FDepth:=Value;
    Repaint;
  end;
end;

Procedure TSeriesPointer.SetDraw3D(Value:Boolean);
Begin
  if FDraw3D<>Value then
  begin
    FDraw3D:=Value;
    Repaint;
  end;
end;

Procedure TSeriesPointer.SetSelected(Const Value:TTeeSelected);
begin
  if Assigned(Value) then
     Selected.Assign(Value)
  else
     FreeAndNil(FSelected);
end;

Procedure TSeriesPointer.Change3D(Value:Boolean);
Begin
  FDraw3D:=Value;
end;

Procedure TSeriesPointer.CalcHorizMargins(var LeftMargin,RightMargin:Integer);
var tmp : Integer;
begin
  if Visible and FInflate then
  begin
    if Pen.Visible then
       tmp:=1+{$IFDEF FMX}Round{$ENDIF}(Pen.Width {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF})
    else
       tmp:=1;

    LeftMargin :=Math.Max(LeftMargin, HorizSize+tmp);
    RightMargin:=Math.Max(RightMargin,HorizSize+tmp);
  end;
end;

Procedure TSeriesPointer.CalcVerticalMargins(var TopMargin,BottomMargin:Integer);
var tmp : Integer;
begin
  if Visible and FInflate then
  begin
    if Pen.Visible then
       tmp:={$IFDEF FMX}Round{$ENDIF}(Pen.Width {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF})
    else
       tmp:=1;

    TopMargin   :=Math.Max(TopMargin,   VertSize+tmp);
    BottomMargin:=Math.Max(BottomMargin,VertSize+tmp);
  end;
end;

procedure TSeriesPointer.CalcSize(out AHoriz,AVert:Integer);
begin
  if FSizeFloat=0 then
  begin
    AHoriz:=HorizSize;
    AVert:=VertSize;
  end
  else
  begin
    if FSizeUnits=suPixels then
       AHoriz:=Round(FSizeFloat)
    else
       AHoriz:=ParentSeries.CalcYSizeValue(FSizeFloat);

    AVert:=AHoriz;
  end;
end;

procedure TSeriesPointer.SetShadow(const Value:TTeeShadow);
begin
  if Assigned(Value) then
     Shadow.Assign(Value)
  else
     FreeAndNil(FShadow);
end;

procedure TSeriesPointer.SetEmboss(const Value:TTeeEmboss);
begin
  if Assigned(Value) then
     Emboss.Assign(Value)
  else
     FreeAndNil(FEmboss);
end;

procedure TSeriesPointer.SetFullGradient(Value: Boolean);
begin
  if FFullGradient<>Value then
  begin
    FFullGradient:=Value;
    Repaint;
  end;
end;

Procedure TSeriesPointer.SetStyle(Value:TSeriesPointerStyle);
Begin
  if FStyle<>Value then
  begin
    FStyle:=Value;
    Repaint;
  end;
end;

Procedure TSeriesPointer.Prepare;
begin
  PrepareCanvas(ParentChart.Canvas,Color);
end;

Procedure TSeriesPointer.PrepareCanvas(const ACanvas:TCanvas3D; ColorValue:TColor);
var tmp : TColor;
Begin
  if Pen.Visible then
  begin
    if DarkPen<>0 then
    begin
      if DarkPen>=0 then
         tmp:=ApplyDark(ColorValue,FDarkPen)
      else
         tmp:=ApplyBright(ColorValue,-FDarkPen);

      ACanvas.AssignVisiblePenColor(Pen,ACanvas.ColorFrom(tmp,ACanvas.ColorAlpha(Pen.Color)))
    end
    else
    if Pen.Color=clTeeColor then
       ACanvas.AssignVisiblePenColor(Pen,ColorValue) { use default point color }
    else
       ACanvas.AssignVisiblePen(Pen); { use fixed Pen.Color }
  end
  else
  if ACanvas.Pen.Visible then
     ACanvas.Pen.Style:=psClear;

  tmp:=Brush.Color;
  if tmp=clTeeColor then
     if Brush.Style=bsSolid then tmp:=ColorValue
                            else tmp:=Brush.BackColor;

  ACanvas.AssignBrush(Brush,tmp,ColorValue);
end;

function TSeriesPointer.Clicked(const AStyle:TSeriesPointerStyle; const P:TPoint; X,Y:Integer):Boolean;
var R        : TRect;
    tmpMidX  : Integer;
    tmpMidY  : Integer;
    tmp      : TPointArray;
begin
  {$IFNDEF D7}
  tmp:=nil; // <-- Fix: Hint
  {$ENDIF}

  R:=RectFromCenter(X,Y,HorizSize,VertSize);

  RectCenter(R,tmpMidX,tmpMidY);

  case AStyle of
     psDiamond       : result:=PointInPolygon( P,[ TeePoint(tmpMidX,R.Top),
                                                   TeePoint(R.Right,tmpMidY),
                                                   TeePoint(tmpMidX,R.Bottom),
                                                   TeePoint(R.Left,tmpMidY)] );
     psLeftTriangle  : result:=PointInHorizTriangle( P,R.Top,R.Bottom,R.Right,R.Left);
     psRightTriangle : result:=PointInHorizTriangle( P,R.Top,R.Bottom,R.Left,R.Right);
     psTriangle      : result:=PointInTriangle( P,R.Left,R.Right,R.Bottom,R.Top);
     psDownTriangle  : result:=PointInTriangle( P,R.Left,R.Right,R.Top,R.Bottom);
     psHexagon,
     psDonut,
     psCircle        : result:=PointInEllipse( P, R );
     psArrow         : begin
                         tmp:=ParentChart.Canvas.GetArrowPoints(50,
                                TeePoint(tmpMidX,R.Bottom),
                                TeePoint(tmpMidX,R.Top),HorizSize,VertSize);

                         result:=PointInPolygon(P,tmp);
                         tmp:=nil;
                       end;
  else
     result:=(AStyle<>psNothing) and
             (Abs(X-P.X)<HorizSize) and
             (Abs(Y-P.Y)<VertSize);
  end;

  if result then
     ParentSeries.IClickedPart:=scpPointer;
end;

Procedure TSeriesPointer.DrawPointer( const ACanvas:TCanvas3D;
                                      Is3D:Boolean;
                                      px,py,Horiz,Vert:Integer;
                                      ColorValue:TColor;
                                      AStyle:TSeriesPointerStyle);

var PXMinus : Integer;
    PXPlus  : Integer;
    PYMinus : Integer;
    PYPlus  : Integer;
    tmpZ    : Integer;

  Procedure DrawDiagonalCross;
  Begin
    with ACanvas do
    begin
      if Is3D then
      begin
        LineWithZ(PXMinus, PYMinus, PXPlus+1,PYPlus+1,tmpZ);
        LineWithZ(PXPlus,  PYMinus, PXMinus-1,PYPlus+1,tmpZ);
      end
      else
      begin
        Line(PXMinus, PYMinus, PXPlus+1,PYPlus+1);
        Line(PXPlus , PYMinus, PXMinus-1,PYPlus+1);
      end;
    end;
  end;

  Procedure DrawCross;
  Begin
    with ACanvas do
    begin
      if Is3D then
      begin
        VertLine3D(PX,PYMinus,PYPlus+1,tmpZ);
        HorizLine3D(PXMinus,PXPlus+1,PY,tmpZ);
      end
      else
      begin
        DoVertLine(PX,PYMinus,PYPlus+1);
        DoHorizLine(PXMinus,PXPlus+1,PY);
      end;
    end;
  end;

var PT : TTrianglePoints;

  Procedure CalcTriangle3D;
  begin
    PT[0]:=ACanvas.Calculate3DPosition(PT[0],tmpZ);
    PT[1]:=ACanvas.Calculate3DPosition(PT[1],tmpZ);
    PT[2]:=ACanvas.Calculate3DPosition(PT[2],tmpZ);
  end;

  Procedure VertTriangle(DeltaY:Integer);
  begin
    if (not Draw3D) or (not Is3D) then
    begin
      PT[0]:=TeePoint(PXMinus,PY+DeltaY);
      PT[1]:=TeePoint(PXPlus,PY+DeltaY);
      PT[2]:=TeePoint(PX,PY-DeltaY);

      if Is3D then CalcTriangle3D;
    end;

    with ACanvas do
    if Is3D and Self.Draw3D then
       Pyramid( True, PXMinus,PY-DeltaY,PXPlus,PY+DeltaY,tmpZ,GetEndZ,FDark3D)
    else
       Polygon(PT);
  end;

  procedure HexagonPoints(out P:TPointArray);
  var tmp : Integer;
      t   : Integer;
  begin
    SetLength(P,6);

    tmp:=Vert div 2;

    P[0]:=TeePoint(PX,PYMinus);
    P[1]:=TeePoint(PXPlus,PY-tmp);
    P[2]:=TeePoint(PXPlus,PY+tmp);
    P[3]:=TeePoint(PX,PYPlus);
    P[4]:=TeePoint(PXMinus,PY+tmp);
    P[5]:=TeePoint(PXMinus,PY-tmp);

    if Is3D then
       for t:=0 to 5 do
           P[t]:=ACanvas.Calculate3DPosition(P[t],tmpZ);
  end;

  Procedure HorizTriangle(Left,Right:Integer);
  begin
    PT[0]:=TeePoint(Left,PY);
    PT[1]:=TeePoint(Right,PYPlus);
    PT[2]:=TeePoint(Right,PYMinus);

    if Is3D then CalcTriangle3D;

    ACanvas.Polygon(PT);
  end;

  procedure DrawHexagon;
  var P : TPointArray;
  begin
    HexagonPoints(P);
    ACanvas.Polygon(P);
    P:=nil;
  end;

  procedure DrawDiamond;
  var PF : TFourPoints;
  begin
    PF[0]:=TeePoint(PXMinus,PY);
    PF[1]:=TeePoint(PX,PYMinus);
    PF[2]:=TeePoint(PXPlus,PY);
    PF[3]:=TeePoint(PX,PYPlus);

    if Is3D then
    begin
      PF[0]:=ACanvas.Calculate3DPosition(PF[0],tmpZ);
      PF[1]:=ACanvas.Calculate3DPosition(PF[1],tmpZ);
      PF[2]:=ACanvas.Calculate3DPosition(PF[2],tmpZ);
      PF[3]:=ACanvas.Calculate3DPosition(PF[3],tmpZ);
    end;

    ACanvas.Polygon(PF);
  end;

  procedure DrawShadowEmboss(const AShadow:TTeeShadow);
  var P   : TPointArray;
      tmp : TRect;
  begin
    tmp:=TeeRect(PXMinus,PYMinus,PXPlus,PYPlus);

    AShadow.ZPosition:=tmpZ; // OpenGL
    
    case AStyle of
    psRectangle: AShadow.Draw(ACanvas,tmp);
       psCircle: AShadow.DrawEllipse(ACanvas,tmp);
      psHexagon: begin
                   HexagonPoints(P);
                   AShadow.Draw(ACanvas,P);
                   P:=nil;
                 end;
        psDonut: AShadow.DrawEllipse(ACanvas,tmp,50);
        psArrow: begin
                   P:=ACanvas.GetArrowPoints(50,TeePoint(PX,PYMinus),TeePoint(PX,PYPlus),Horiz,Vert);
                   AShadow.Draw(ACanvas,P);
                   P:=nil;
                 end;
    end;
  end;

  procedure TryDrawShadow(const AShadow:TTeeShadow);
  var
    OldBrush : TColor;
    OldBrushStyle: {$IFDEF FMX}TBrushKind{$ELSE}TBrushStyle{$ENDIF};
    OldPen   : TPenStyle;
  begin
    if Assigned(AShadow) and AShadow.ShouldDraw then
    begin
      OldBrush:=ACanvas.Brush.Color;
      OldBrushStyle:=ACanvas.Brush.Style;
      OldPen:=ACanvas.Pen.Style;

      DrawShadowEmboss(AShadow);

      ACanvas.Brush.Color:=OldBrush;
      ACanvas.Brush.Style:=OldBrushStyle;
      ACanvas.Pen.Style:=OldPen;
    end;
  end;

  function CalcBlend:TTeeBlend;
  var tmpR : TRect;
  begin
    if ACanvas.SupportsAlpha then
       result:=ACanvas.BeginBlending(TeeZeroRect,Transparency)
    else
    begin
      tmpR:=TeeRect(PXMinus,PYMinus,PXPlus+1,PYPlus+1);

      if (AStyle=psRectangle) and Is3D and Self.Draw3D and ACanvas.View3DOptions.Orthogonal then
      begin
        tmpR.Right:=tmpR.Right + (GetEndZ-tmpZ);
        tmpR.Top:=tmpR.Top - (GetEndZ-tmpZ);
      end;

      result:=ACanvas.BeginBlending(ACanvas.RectFromRectZ(tmpR,tmpZ),Transparency)
    end;
  end;

  procedure DrawPicture;
  var tmpR : TRect;
  begin
    tmpR:=TeeRect(PXMinus,PYMinus,PXPlus,PYPlus);

    if Is3D and ParentChart.View3DOptions.Orthogonal then
       tmpR:=ACanvas.CalcRect3D(tmpR,tmpZ);

    if Is3D and (not ParentChart.View3DOptions.Orthogonal) then
       ACanvas.StretchDraw(tmpR,FPicture.Filtered,tmpZ)
    else
    begin
      if AStyle=psCircle then
         ACanvas.ClipEllipse(tmpR);

      ACanvas.StretchDraw(tmpR,FPicture.Filtered);

      if AStyle=psCircle then
         ACanvas.UnClipRectangle;
    end;
  end;

  procedure PrepareGradient;
  var Old : Boolean;
  begin
    Old:=ParentChart.AutoRepaint;
    ParentChart.AutoRepaint:=False;
    Gradient.EndColor:=ColorValue;

    ACanvas.Gradient.EndColor:=ColorValue;

    ParentChart.AutoRepaint:=Old;
  end;

var tmpBlend : TTeeBlend;
    tmpP : TPoint;
begin
  if FCenterPixel then
    PXMinus:=PX-Horiz
  else
    PXMinus:=PX+1-Horiz;

  PXPlus :=PX+Horiz;

  if FCenterPixel then
    PYMinus:=PY-Vert
  else
    PYMinus:=PY+1-Vert;
  PYPlus :=PY+Vert;
  tmpZ:=GetStartZ;

  TryDrawShadow(Shadow);
  TryDrawShadow(Emboss);

  if Transparency>0 then
     tmpBlend:=CalcBlend
  else
     tmpBlend:=nil;

  if Gradient.Visible and (not FullGradient) then
     PrepareGradient;

  with ACanvas do
    case AStyle of
    psRectangle: if Is3D then
                    if Self.FDraw3D then
                       Cube(PXMinus,PXPlus,PYMinus,PYPlus,tmpZ,GetEndZ,FDark3D)
                    else
                       RectangleWithZ(TeeRect(PXMinus,PYMinus,PXPlus+1,PYPlus+1),tmpZ)
                 else
                    Rectangle(PXMinus,PYMinus,PXPlus+1,PYPlus+1);

       psCircle: if Is3D then
                    if Self.FDraw3D and SupportsFullRotation then
                       if FSizeFloat=0 then
                          Sphere(PX,PY,GetMiddleZ,Horiz)
                       else
                          Sphere(PX,PY,GetMiddleZ,FSizeFloat)
                    else
                       EllipseWithZ(PXMinus,PYMinus,PXPlus,PYPlus,tmpZ)
                 else
                    Ellipse(PXMinus,PYMinus,PXPlus,PYPlus);

     psTriangle: VertTriangle( Vert);
 psDownTriangle: VertTriangle(-Vert);
 psLeftTriangle: HorizTriangle(PXMinus,PXPlus);
psRightTriangle: HorizTriangle(PXPlus,PXMinus);
        psCross: DrawCross;
    psDiagCross: DrawDiagonalCross;

         psStar: begin
                   DrawCross;
                   DrawDiagonalCross;
                 end;

      psDiamond: DrawDiamond;
     psSmallDot: if Is3D then Pixels3D[PX,PY,GetMiddleZ]:=Brush.Color
                         else Pixels[PX,PY]:=Brush.Color;
      psHexagon: DrawHexagon;

        psDonut: if Is3D then
                    if ACanvas.View3DOptions.Orthogonal then
                    begin
                      tmpP:=ACanvas.Calculate3DPosition(PX,PY,tmpZ);
                      Donut({$IFDEF FMX}Round{$ENDIF}(tmpP.X),{$IFDEF FMX}Round{$ENDIF}(tmpP.Y),Horiz,Vert,0,DegToRad(360),50);
                    end
                    else
                      EllipseWithZ(PXMinus,PYMinus,PXPlus,PYPlus,tmpZ)
                 else
                    Donut(PX,PY,Horiz,Vert,0,DegToRad(360),50);

        psArrow: ACanvas.Arrow(Brush.Style<>bsClear,TeePoint(PX,PYMinus),TeePoint(PX,PYPlus),Horiz,Vert,tmpZ);
  end;

  if Assigned(FPicture) and FPicture.Valid then
     DrawPicture;

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

Procedure TSeriesPointer.Draw(const P:TPoint);
begin
  Draw({$IFDEF FMX}Round{$ENDIF}(P.X),
       {$IFDEF FMX}Round{$ENDIF}(P.Y), Brush.Color, Style)
end;

Procedure TSeriesPointer.Draw(X,Y:Integer);
begin
  Draw(X,Y,Brush.Color,Style)
end;

procedure TSeriesPointer.Draw(px,py:Integer; ColorValue:TColor; AStyle:TSeriesPointerStyle);
var tmpHoriz : Integer;
    tmpVert  : Integer;
begin
  if FSizeFloat=0 then
  begin
    if FSizeUnits=suPixels then
    begin
      tmpHoriz:=FHorizSize;
      tmpVert:=FVertSize;
    end
    else
    begin
      tmpHoriz:=ParentSeries.CalcYSizeValue(FHorizSize);
      tmpVert:=ParentSeries.CalcYSizeValue(FVertSize);
    end;
  end
  else
  begin
    if FSizeUnits=suPixels then
       tmpHoriz:=Round(FSizeFloat)
    else
       tmpHoriz:=ParentSeries.CalcYSizeValue(FSizeFloat);

    tmpVert:=tmpHoriz;
  end;

  DrawPointer(ParentChart.Canvas,ParentChart.View3D,px,py,tmpHoriz,tmpVert,ColorValue,AStyle);
end;

Procedure TSeriesPointer.SetDarkPen(Value:Integer);
begin
  if FDarkPen<>Value then
  begin
    FDarkPen:=Value;
    Repaint;
  end;
end;

Procedure TSeriesPointer.Assign(Source:TPersistent);
begin
  if Source is TSeriesPointer then
  With TSeriesPointer(Source) do
  begin
    Self.FDark3D    :=FDark3D;
    Self.FDepth     :=FDepth;
    Self.FDraw3D    :=FDraw3D;
    Self.FDarkPen   :=FDarkPen;
    Self.FFullGradient:=FFullGradient;
    Self.FHorizSize :=FHorizSize;
    Self.FInflate   :=FInflate;
    Self.Emboss     :=FEmboss;
    Self.Picture    :=FPicture;
    Self.Shadow     :=FShadow;
    Self.FSizeFloat :=FSizeFloat;
    Self.FSizeUnits :=FSizeUnits;
    Self.FStyle     :=FStyle;
    Self.FTransparency:=FTransparency;
    Self.FVertSize  :=FVertSize;
  end;

  inherited;
end;

procedure TSeriesPointer.SetGradient(const Value: TTeeGradient);
begin
  Brush.Gradient:=Value;
end;

function TSeriesPointer.GetColor: TColor;
begin
  result:=Brush.Color;
end;

function TSeriesPointer.GetSize:Integer;
begin
  result:=Max(HorizSize,VertSize);
end;

function TSeriesPointer.GetStartZ:Integer;
begin
  if Assigned(FSeries) then
     if Depth=0 then
        result:=FSeries.StartZ
     else
        result:=FSeries.MiddleZ-Depth
  else
     result:=Z;
end;

function TSeriesPointer.GetMiddleZ:Integer;
begin
  if Assigned(FSeries) then
     result:=FSeries.MiddleZ
  else
     result:=Z;
end;

function TSeriesPointer.GetEndZ:Integer;
begin
  if Assigned(FSeries) then
     if Depth=0 then
        result:=FSeries.EndZ
     else
        result:=FSeries.MiddleZ+Depth
  else
     result:=Z;
end;

function TSeriesPointer.GetSelected:TTeeSelected;
begin
  if not Assigned(FSelected) then
     FSelected:=TTeeSelected.CreateChart(Self);

  result:=FSelected;
end;

function TSeriesPointer.IsFullStored:Boolean;
begin
  result:=FFullGradient<>DefaultFullGradient;
end;

procedure TSeriesPointer.SetColor(const Value: TColor);
begin
  Brush.Color:=Value;
end;

procedure TSeriesPointer.SetTransparency(const Value: TTeeTransparency);
begin
  if FTransparency<>Value then
  begin
    FTransparency:=Value;
    Repaint;
  end;
end;

type
  TTeeBrushAccess=class(TTeeBrush);

{ TCallout }
Constructor TCallout.Create(const AOwner:TChartSeries);
begin
  inherited Create(AOwner);

  FArrow:=TChartArrowPen.Create(CanvasChanged);
  FInflate:=True;

  FullGradient:=True;
  DefaultFullGradient:=True;

  FStyle:=psRectangle;

  Visible:=True;

  FDraw3D:=False;
  FArrowHeadSize:=8;

  TTeeBrushAccess(Brush).InitColor({$IFDEF FMX}TAlphaColors.Black{$ELSE}clBlack{$ENDIF});
end;

Destructor TCallout.Destroy;
begin
  FArrow.Free;
  inherited;
end;

Procedure TCallout.Assign(Source:TPersistent);
begin
  if Source is TCallout then
  with TCallout(Source) do
  begin
    Self.FDistance:=Distance;
    Self.Arrow:=Arrow;
    Self.FArrowHead:=FArrowHead;
    Self.FArrowHeadSize:=FArrowHeadSize;
  end;
  
  inherited;
end;

procedure TCallout.SetDistance(const Value: Integer);
begin
  if FDistance<>Value then
  begin
    FDistance:=Value;
    Repaint;
  end;
end;

procedure TCallout.SetArrow(const Value: TChartArrowPen);
begin
  FArrow.Assign(Value);
end;

procedure TCallout.Draw(AColor:TColor; AFrom,AMid,ATo:TPoint; Z:Integer; MidPoint:Boolean=False);
begin
  Draw(ParentChart,AColor,AFrom,AMid,ATo,Z,MidPoint);
end;

procedure TCallout.Draw(const AParent:TCustomTeePanel; AColor:TColor;
                        AFrom,AMid,ATo:TPoint; Z:Integer; MidPoint:Boolean=False);
Const ArrowColors : Array[Boolean] of TColor=
         ({$IFDEF FMX}TAlphaColors.Black{$ELSE}clBlack{$ENDIF},clWhite);

var tmpFrom   : TPoint;
    tmpCanvas : TCanvas3D;
//    tmpX      : TCoordinate;
//    tmpY      : TCoordinate;
begin
  tmpCanvas:=AParent.Canvas;

  {
    Offset not necessary. It breaks SubChart series with Marks.
    Probably it was used for metafile or Chart1.Draw with xy origin not 0,0

  tmpX:=AParent.ChartBounds.Left;
  tmpY:=AParent.ChartBounds.Top;

  if (tmpX<>0) or (tmpY<>0) then
  begin
    AFrom.X:=AFrom.X+tmpX;
    AFrom.Y:=AFrom.Y+tmpY;

    AMid.X:=AMid.X+tmpX;
    AMid.Y:=AMid.Y+tmpY;

    ATo.X:=ATo.X+tmpX;
    ATo.Y:=ATo.Y+tmpY;
  end;
  }

  if Arrow.Visible then
  begin
    Prepare;

    if not (ArrowHead=ahSolid) then // TF02014794
      tmpCanvas.Brush.Style:=bsClear; // TV52012936

    if TeeCheckMarkArrowColor and
       ( (Arrow.Color=AColor) or (Arrow.Color=AParent.Color) ) then
       tmpCanvas.AssignVisiblePenColor(Arrow,
                    ArrowColors[AParent.Color={$IFDEF FMX}TAlphaColors.Black{$ELSE}clBlack{$ENDIF}])
    else
       tmpCanvas.AssignVisiblePen(Arrow);

    case ArrowHead of
      ahLine   : tmpCanvas.Arrow(False,ATo,AFrom,ArrowHeadSize,ArrowHeadSize,Z);
      ahSolid  : tmpCanvas.Arrow(True,ATo,AFrom,ArrowHeadSize,ArrowHeadSize,Z);
    else
      if AParent.View3D then
         if MidPoint then
         begin
           tmpCanvas.LineWithZ(AFrom,AMid,Z);
           tmpCanvas.LineWithZ(AMid,ATo,Z);
         end
         else tmpCanvas.LineWithZ(AFrom,ATo,Z)
      else
      if MidPoint then
      begin
        tmpCanvas.Line(AFrom,AMid);
        tmpCanvas.Line(AMid,ATo);
      end
      else
         tmpCanvas.Line(AFrom,ATo);
    end;
  end;

  if (ArrowHead=ahNone) and Visible then
  begin
    Prepare;

    tmpFrom:=AFrom;

    if AParent.View3D then
       tmpFrom:=tmpCanvas.Calculate3DPosition(tmpFrom,Z);

    DrawPointer(tmpCanvas,
                False,
                {$IFDEF FMX}Round{$ENDIF}(tmpFrom.X),
                {$IFDEF FMX}Round{$ENDIF}(tmpFrom.Y),
                HorizSize,
                VertSize,
                Color,
                Style);
  end;
end;

procedure TCallout.SetArrowHead(const Value: TArrowHeadStyle);
begin
  if FArrowHead<>Value then
  begin
    FArrowHead:=Value;
    Repaint;
  end;
end;

procedure TCallout.SetArrowHeadSize(const Value: Integer);
begin
  if FArrowHeadSize<>Value then
  begin
    FArrowHeadSize:=Value;
    Repaint;
  end;
end;

{ TMarksCallout }

Constructor TMarksCallout.Create(const AOwner: TChartSeries);
begin
  inherited;

  FLength:=8;
  DefaultLength:=8;

  Visible:=False;
  DefaultVisible:=False;
end;

procedure TMarksCallout.Assign(Source: TPersistent);
begin
  if Source is TMarksCallout then
  With TMarksCallout(Source) do
  begin
    Self.FLength:=FLength;
  end;
  inherited;
end;

procedure TMarksCallout.SetLength(const Value: Integer);
begin
  ParentSeries.SetIntegerProperty(FLength,Value)
end;

procedure TMarksCallout.ApplyArrowLength(const APosition:TSeriesMarkPosition);
var tmp : Integer;
begin
  tmp:=Length+Distance;

  With APosition do
  begin
    LeftTop.Y:=LeftTop.Y-tmp;
    ArrowTo.Y:=ArrowTo.Y-tmp;
    ArrowFrom.Y:=ArrowFrom.Y-Distance;
  end;
end;

Function TMarksCallout.IsLengthStored:Boolean;
begin
  result:=Length<>DefaultLength;
end;

{ TAxisGridPen }
Constructor TAxisGridPen.Create(const OnChangeEvent:TNotifyEvent);
begin
  inherited;
  FDrawEvery:=1;
  InitColor({$IFDEF FMX}TAlphaColors.LtGray{$ELSE}clLtGray{$ENDIF});
  Width:=1;
end;

Procedure TAxisGridPen.Assign(Source:TPersistent);
begin
  if Source is TAxisGridPen then
  with TAxisGridPen(Source) do
  begin
    Self.FCentered:=FCentered;
    Self.FDrawAlways:=FDrawAlways;
    Self.FDrawEvery:=FDrawEvery;
    Self.FZ:=FZ;
  end;

  inherited;
end;

procedure TAxisGridPen.DefineProperties(Filer: TFiler);
begin
  inherited;
  Filer.DefineProperty('ZZero',ReadZZero,WriteZZero,(IDefaultZ<>0) and (FZ=0));
end;

procedure TAxisGridPen.ReadZZero(Reader: TReader);
begin
  if Reader.ReadBoolean then
     FZ:=0;
end;

procedure TAxisGridPen.WriteZZero(Writer: TWriter);
begin
  Writer.WriteBoolean(FZ=0);
end;

function TAxisGridPen.IsZStored: Boolean;
begin
  result:=FZ<>IDefaultZ;
end;

procedure TAxisGridPen.SetDrawAlways(const Value: Boolean);
begin
  if FDrawAlways<>Value then
  begin
    FDrawAlways:=Value;
    Changed;
  end;
end;

procedure TAxisGridPen.SetDrawEvery(const Value: Integer);
begin
  if FDrawEvery<>Value then
  begin
    FDrawEvery:=Value;
    Changed;
  end;
end;

procedure TAxisGridPen.SetCentered(const Value: Boolean);
begin
  if FCentered<>Value then
  begin
    FCentered:=Value;
    Changed;
  end;
end;

procedure TAxisGridPen.SetZ(const Value: Double);
begin
  if FZ<>Value then
  begin
    FZ:=Value;
    Changed;
  end;
end;

{ TAxisItem }

Destructor TAxisItem.Destroy;
begin
  FFormat.Free;

  if not IAxisItems.IRemoving then
  begin
    IAxisItems.Remove(Self);
    Repaint;
  end;

  inherited;
end;

function TAxisItem.GetFormat:TAxisLabelsFormat;
begin
  if not Assigned(FFormat) then
  begin
    FFormat:=TAxisLabelsFormat.Create(IAxisItems.IAxis.ParentChart);
    TTeeCustomShapeAccess(FFormat).InitTransparent(True);
  end;

  result:=FFormat;
end;

function TAxisItem.ActiveFormat:TAxisLabelsFormat;
begin
  if Assigned(FFormat) then
     result:=FFormat
  else
     result:=IAxisItems.FFormat;
end;

procedure TAxisItem.SetFormat(const Value: TAxisLabelsFormat);
begin
  if Assigned(Value) then Format.Assign(Value)
                     else FreeAndNil(FFormat);
end;

procedure TAxisItem.SetText(const Value: String);
begin
  if FText<>Value then
  begin
    FText:=Value;
    Repaint;
  end;
end;

procedure TAxisItem.Repaint;
begin
  IAxisItems.IAxis.ParentChart.Invalidate;
end;

procedure TAxisItem.SetValue(const Value: TAxisValue);
begin
  if FValue<>Value then
  begin
    FValue:=Value;
    Repaint;
  end;
end;

{ TAxisItems }

Constructor TAxisItems.Create(const Axis:TChartAxis);
begin
  inherited Create;
  IAxis:=Axis;

  FAuto:=True;
  FFormat:=TAxisLabelsFormat.Create(IAxis.ParentChart);
  FFormat.TextAlignment:=taCenter;

  TTeeCustomShapeAccess(FFormat).InitTransparent(True);
end;

Destructor TAxisItems.Destroy;
begin
  FSelected.Free;
  FFormat.Free;

  inherited;
end;

procedure TAxisItems.InitCalcRect;
begin
  IAxis.ParentChart.Canvas.AssignFont(IAxis.Texts.Font);

  IHeight:=IAxis.ParentChart.Canvas.FontHeight;

  if IAxis.Horizontal then
  begin
    if IAxis.OtherSide then
    begin
      IRect.Bottom:=IAxis.PosLabels;
      IRect.Top:=IRect.Bottom-IHeight;
    end
    else
    begin
      IRect.Top:=IAxis.PosLabels;
      IRect.Bottom:=IRect.Top+IHeight;
    end;
  end
  else
  begin
    if IAxis.OtherSide then
       IRect.Left:=IAxis.PosLabels
    else
       IRect.Right:=IAxis.PosLabels;
  end;
end;

function TAxisItems.RectOf(Index:Integer):TRect;
var tmpItem : TAxisItem;
    tmp,
    tmpW,
    tmpH : Integer;
    tmpP : TPoint;
begin
  tmpItem:=Item[Index];

  tmp:=tmpItem.LabelPos;

  tmpW:=IAxis.ParentChart.Canvas.TextWidth(tmpItem.Text);

  if IAxis.LabelsAngle mod 180 = 0 then
     tmpH:=IHeight
  else
  begin
    tmpH:=tmpW;
    tmpW:=IHeight;
  end;

  if IAxis.Horizontal then
  begin
    IRect.Left:=tmp-(tmpW div 2);
    IRect.Right:=IRect.Left+tmpW;

    if IAxis.OtherSide then
       IRect.Top:=IRect.Bottom-tmpH
    else
       IRect.Bottom:=IRect.Top+tmpH;
  end
  else
  begin
    IRect.Top:=tmp-(tmpH div 2);
    IRect.Bottom:=IRect.Top+tmpH;

    if IAxis.OtherSide then
       IRect.Right:=IRect.Left+tmpW
    else
       IRect.Left:=IRect.Right-tmpW
  end;

  result:=IRect;

  if IAxis.ParentChart.View3D then
  begin
    tmpP:=IAxis.ParentChart.Canvas.Calculate3DPosition(result.TopLeft,IAxis.IZPos);
    OffsetRect(result,tmpP.X-result.Left,tmpP.Y-result.Top);
  end;
end;

function TAxisItems.InternalAdd(const Value: TAxisValue; const Text:String;
                                const APicture : TTeePicture):TAxisItem;
begin
  result:=TAxisItem.Create;

  result.IAxisItems:=Self;
  result.FValue:=Value;
  result.FText:=Text;
  result.FPicture:=APicture;

  inherited Add(result);
end;

procedure TAxisItems.ReadItems(Stream: TStream);
var tmpCount : Integer;
    t        : Integer;
    Reader   : TReader;
    tmp      : TAxisItem;
begin
  Stream.Read(tmpCount,SizeOf(tmpCount));

  if tmpCount>0 then
  begin
    Clear;

    FAuto:=False;

    Reader:=TReader.Create(Stream,1024);

    for t:=0 to tmpCount-1 do
    begin
      tmp:=Add(0);

      Reader.ReadListBegin;

      while not Reader.EndOfList do
            TReaderAccess(Reader).ReadProperty(tmp);

      Reader.ReadListEnd;
    end;

    Reader.Free;
  end;
end;

procedure TAxisItems.WriteItems(Stream: TStream);
var tmpCount : Integer;
    t        : Integer;
    Writer   : TWriter;
begin
  tmpCount:=Count;
  Stream.Write(tmpCount,SizeOf(tmpCount));

  Writer:=TWriter.Create(Stream,1024);

  for t:=0 to tmpCount-1 do
  begin
    Writer.WriteListBegin;
    TWriterAccess(Writer).WriteProperties(Items[t]);
    Writer.WriteListEnd;
  end;

  Writer.Free;
end;

procedure TAxisItems.SetAuto(const Value:Boolean);
begin
  IAxis.ParentChart.SetBooleanProperty(FAuto,Value);
end;

function TAxisItems.GetSelected:TTeeSelected;
begin
  if not Assigned(FSelected) then
  begin
    FSelected:=TTeeSelected.CreateChart(IAxis.ParentChart); // <-- FPC bug !

    FSelected.FHover.ParentChart:=IAxis.ParentChart;
    FSelected.FHover.Assign(FFormat);
    FSelected.FHover.Font.Style:=[{$IFDEF FMX}TFontStyle.{$ENDIF}fsBold];
  end;

  result:=FSelected;
end;

Function TAxisItems.Add(const Value: TAxisValue; const Text:String):TAxisItem;
begin
  result:=InternalAdd(Value,Text);
  FAuto:=False;
end;

procedure TAxisItems.CopyFrom(const Source: TAxisItems);
var t : Integer;
begin
  Format.Assign(Source.Format);
  Selected.Assign(Source.Selected);

  if not Source.FAuto then
  begin
    Clear;

    FAuto:=False;

    for t:=0 to Source.Count-1 do
        with Source[t] do Add(Value,Text);
  end;
end;

procedure TAxisItems.Delete(Index:Integer);
begin
  IRemoving:=True;
  Item[Index].{$IFDEF AUTOREFCOUNT}DisposeOf{$ELSE}Free{$ENDIF};
  IRemoving:=False;

  inherited Delete(Index);

  IAxis.ParentChart.Invalidate;
end;

{$IFNDEF D18}
procedure TAxisItems.Clear;
var t : Integer;
begin
  IRemoving:=True;

  for t:=0 to Count-1 do
      Item[t].Free;

  IRemoving:=False;

  inherited;

  IAxis.ParentChart.Invalidate;
end;
{$ENDIF}

function TAxisItems.Get(Index: Integer): TAxisItem;
begin
  result:={$IFNDEF D18}TAxisItem{$ENDIF}(inherited Items[Index]);
end;

// Draws the associated series bitmap icon at the specified LeftTop location
procedure TeeDrawBitmapEditor(const Canvas: TCanvas; const Element:TCustomChartElement; Left,Top:Integer);
var tmpBitmap : TBitmap;
begin
  tmpBitmap:=TBitmap.Create{$IFNDEF D19}{$IFDEF FMX}(0,0){$ENDIF}{$ENDIF};
  try
    TeeGetBitmapEditor(Element,tmpBitmap);

    {$IFDEF FMX}
    Canvas.DrawBitmap(tmpBitmap,
                      RectF(0,0,tmpBitmap.Width,tmpBitmap.Height),
                      RectF(Left,Top,Left+tmpBitmap.Width,Top+tmpBitmap.Height),
                      1);
    {$ELSE}
    Canvas.Draw(Left,Top,tmpBitmap);
    {$ENDIF}
  finally
    tmpBitmap.Free;
  end;
end;

{ TSeriesGroup }

Constructor TSeriesGroup.Create(Collection: TCollection);
begin
  inherited;

  FSeries:=TCustomSeriesList.Create;
  FSeries.FOwner:=TCustomAxisPanel(CollectionOwner(Collection));

  FName:='Group'; // Do not localize

  if Assigned(Collection) then
     FName:=FName+IntToStr(Collection.Count);
end;

Destructor TSeriesGroup.Destroy;
begin
  FSeries.Free;
  inherited;
end;

procedure TSeriesGroup.Add(const Series:TChartSeries);
begin
  FSeries.Add(Series);
end;

procedure TSeriesGroup.Assign(Source:TPersistent);
begin
  if Source is TSeriesGroup then
  begin
    FName:=TSeriesGroup(Source).FName;
    Series:=TSeriesGroup(Source).Series;
  end
  else
    inherited;
end;

procedure TSeriesGroup.Hide;
begin
  Active:=gaNo;
end;

procedure TSeriesGroup.Show;
begin
  Active:=gaYes;
end;

function TSeriesGroup.GetActive: TSeriesGroupActive;
var tmpActive : Integer;
    t         : Integer;
begin
  if FSeries.Count=0 then result:=gaYes
  else
  begin
    tmpActive:=0;
    for t:=0 to FSeries.Count-1 do
    if FSeries[t].Active then Inc(tmpActive);

    if tmpActive=FSeries.Count then
       result:=gaYes
    else
    if tmpActive=0 then result:=gaNo
    else
       result:=gaSome;
  end;
end;

procedure TSeriesGroup.SetActive(const Value: TSeriesGroupActive);
var t : Integer;
begin
  if (Value<>gaSome) and (Active<>Value) then
  for t:=0 to FSeries.Count-1 do
      if Value=gaYes then FSeries[t].Active:=True
      else
      if Value=gaNo then FSeries[t].Active:=False;
end;

procedure TSeriesGroup.SetSeries(const Value: TCustomSeriesList);
{$IFDEF D18}
var t : Integer;
{$ELSE}
{$IFNDEF D6}
var t : Integer;
{$ENDIF}
{$ENDIF}
begin
  {$IFDEF D18}
  FSeries.Clear;
  for t:=0 to Value.Count-1 do FSeries.Add(Value[t]);
  {$ELSE}

  {$IFDEF D6}
  FSeries.Assign(Value);
  {$ELSE}
  FSeries.Clear;
  for t:=0 to Value.Count-1 do FSeries.Add(Value[t]);
  {$ENDIF}

  {$ENDIF}
end;

procedure TSeriesGroup.DefineProperties(Filer: TFiler);
begin
  inherited;
  Filer.DefineProperty('Series',ReadSeries,WriteSeries,FSeries.Count > 0);
end;

procedure TSeriesGroup.WriteSeries(Writer: TWriter);
var t : Integer;
begin
  With Writer do
  begin
    WriteListBegin;

    With Series do
    for t:=0 to Count-1 do
        WriteString(TeeStr(Series[t].SeriesIndex));

    WriteListEnd;
  end;
end;

procedure TSeriesGroup.ReadSeries(Reader: TReader);
begin
  Reader.ReadListBegin;
  FTempSeries:=TStringList.Create;
  With FTempSeries do
  begin
    Clear;
    while not Reader.EndOfList do Add(Reader.ReadString);
  end;
  Reader.ReadListEnd;
end;

{ TSeriesGroups }

// Returns first group index than contains Series, or -1 if any.
function TSeriesGroups.Contains(const Series: TChartSeries): Integer;
var t : Integer;
begin
  result:=-1;

  for t:=0 to Count-1 do
  if Items[t].Series.IndexOf(Series)<>-1 then
  begin
    result:=t;
    break;
  end;
end;

function TSeriesGroups.FindByName(const Name:String; CaseSensitive:Boolean=False):TSeriesGroup;

  function StringsEqual(const A,B:String):Boolean;
  begin
    result:=(CaseSensitive and (A=B)) or
            ((not CaseSensitive) and (UpperCase(A)=UpperCase(B)));
  end;

var t : Integer;
begin
  for t:=0 to Count-1 do
  begin
    result:=Items[t];

    if StringsEqual(result.Name,Name) then
       Exit;
  end;

  result:=nil;
end;

function TSeriesGroups.Get(Index: Integer): TSeriesGroup;
begin
  result:=TSeriesGroup(inherited Items[Index]);
end;

procedure TSeriesGroups.Put(Index: Integer; const Value: TSeriesGroup);
begin
  inherited Items[Index]:=Value;
end;

{ TSeriesMarksSymbol }

Constructor TSeriesMarksSymbol.Create(const AOwner: TCustomTeePanel);
var Old : Boolean;
begin
  inherited;

  if Assigned(AOwner) then
  begin
    Old:=AOwner.AutoRepaint;
    AOwner.AutoRepaint:=False;
  end
  else
    Old:=False;

  Visible:=False;
  DefaultVisible:=False;

  Transparent:=False;

  Shadow.Size:=1;
  TShadowAccess(Shadow).DefaultSize:=1;

  Emboss.Size:=1;
  TEmbossAccess(Emboss).DefaultSize:=1;

  if Assigned(AOwner) then
     AOwner.AutoRepaint:=Old;
end;

function TSeriesMarksSymbol.ShouldDraw: Boolean;
begin
  result:=Visible and (not Transparent);
end;

function TTeeSeriesType.Caption:String;
begin
  result:=ReplaceChar(Description^,#13,' ');
end;

procedure RegisterBasicClasses;
begin
  TeeActivateGroup;

  {$IFDEF FMX}
  GroupDescendentsWith(TCustomChartElement, TFmxObject);
  GroupDescendentsWith(TTeeFunction, TFmxObject);
  GroupDescendentsWith(TTeeSeriesSource, TFmxObject);
  GroupDescendentsWith(TTeeCustomShapeBrushPen, TFmxObject);
  GroupDescendentsWith(TChartAxis, TFmxObject);

  {$ELSE}
  {$IFDEF D6}
  GroupDescendentsWith(TCustomChartElement, TControl);
  GroupDescendentsWith(TTeeFunction, TControl);
  GroupDescendentsWith(TTeeSeriesSource, TControl);
  GroupDescendentsWith(TTeeCustomShapeBrushPen, TControl);
  GroupDescendentsWith(TChartAxis, TControl);
  {$ENDIF}
  {$ENDIF}

  RegisterClasses([ TChartAxisTitle,TChartAxis,TChartDepthAxis,
                    TSeriesMarks,TThemedSeries,
                    TTeePictureComponent ]);
end;

initialization
  {$IFDEF TEETRIAL}
  TeeIsTrial:=True;
  {$ENDIF}

  // See TChartAxis XPosValueCheck function...
  TeeMaxPixelPos:=$7FFF;

  //TV52014638, when TeeMaxPixelPos=$3FFFFFF Windows.LineTo can take
  //quite a long time to draw if Pen width is > 1
  //if IsWindowsNT then TeeMaxPixelPos:=$3FFFFFF
  //               else TeeMaxPixelPos:=$7FFF;

  RegisterBasicClasses;
finalization
  FreeAndNil(FTeeSources);

  // UnRegister basic classes
  UnRegisterClasses([ TChartAxisTitle,TChartAxis,
                      TChartDepthAxis,TSeriesMarks,TThemedSeries,
                      TTeePictureComponent ]);
end.
