{**********************************************}
{  TeeChart Pro                                }
{                                              }
{   TCustom3DSeries                            }
{    TCustom3DPaletteSeries                    }
{     TVector3DSeries                          }
{     TCustom3DGridSeries                      }
{      TSurfaceSeries                          }
{      TContourSeries                          }
{      TWaterFallSeries                        }
{      TColorGridSeries                        }
{      TTowerSeries                            }
{     TBubbleCloudSeries                       }
{                                              }
{  Functions:                                  }
{                                              }
{   TSmoothPoints                              }
{                                              }
{  Copyright (c) 1995-2020 by Steema Software  }
{**********************************************}
unit TeeSurfa;
{$I TeeDefs.inc}

interface

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

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

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

     {$IFDEF FMX}
     FMX.Types,

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

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

     {$ELSE}
     Graphics,
     {$ENDIF}

     {$IFDEF D6}
     Types,
     {$ENDIF}
     {$IFDEF FMX}
     FMXTee.Procs, FMXTee.Engine, FMXTee.Canvas, FMXTee.Chart, FMXTee.Series
     {$ELSE}
     TeeProcs, TeEngine, TeCanvas, TeeFilters, Chart, Series
     {$ENDIF};

type
  // All record and array types defined here (at top),
  // to avoid C++ dcc32 *.hpp generation errors:
  TArrayGrid=Array of TChartValues;

  // Contour Level Segments storage.
  // Each Level has a TLevelSegments (array of Segments).
  // Each Segment contains an array of Points, in axis XY coordinates.

  TLevelPoint=record
     X,Y: TChartValue;
  end;

  TLevelPoint3D=record
     X,Y,Z: TChartValue;
  end;

  TLevelPoints=Array of TLevelPoint;

  TLevelSegment=record
    Count  : Integer;
    Points : TLevelPoints;
  end;

  TLevelLine=record
    A,B : TLevelPoint;
  end;
  
  TLevelSegments=Array of TLevelSegment;

  // Wish: Convert this record into a Collection Item
  TGridPalette=record
    UpToValue : TChartValue;
    Color     : TColor;
    Text      : String;
  end;

  // Wish: Convert this array into a Collection
  TCustom3DPalette=Array of TGridPalette;

  TCellsOrientation=record
       InitX,
       EndX,
       IncX,
       InitZ,
       EndZ,
       IncZ : Integer;
  end;

  TTee3DRGBArray={$IFDEF LCL}TRGBAArray{$ELSE}{$IFDEF FMX}TRGBAArray{$ELSE}TRGBArray{$ENDIF}{$ENDIF};

  TCustom3DSeries=class(TChartSeries)
  private
    FTimesZOrder : Integer;
    FZValues     : TChartValueList;

    // For Surface and TriSurface, OpenGL caching
    IOldChartRect  : TRect;
    IOldStartZ,
    IOldEndZ       : Integer;

    Function BackFaced:Boolean;
    Function GetZValue(Index:Integer):TChartValue;
    Procedure SetTimesZOrder(Const Value:Integer);
    Procedure SetZValue(Index:Integer; Const Value:TChartValue);
    Procedure SetZValues(Const Value:TChartValueList);
  protected
    function AxisRectChanged:Boolean;
    Procedure CalcZOrder; override;
    procedure DrawAllValues; override;
    Procedure DrawMark( ValueIndex:Integer; Const St:String;
                        APosition:TSeriesMarkPosition); override;
    procedure Load(NumX,NumY:Integer; const RGBs:TTee3DRGBArray; Inverted:Boolean=False); overload; virtual;
    Procedure PrepareLegendCanvas( ValueIndex:Integer; Var BackColor:TColor;
                                   Var BrushStyle:{$IFDEF FMX}TBrushKind{$ELSE}TBrushStyle{$ENDIF}); override;
    procedure SaveChartRect;
    Function ValueListOfAxis(const Axis:TChartAxis):TChartValueList; override;
  public
    Constructor Create(AOwner: TComponent); override;

    Procedure Assign(Source:TPersistent); override;

    Procedure AddArray(Const Values:TArrayGrid); overload;
    Function AddXYZ(Const AX,AY,AZ:TChartValue):Integer; overload;
    Function AddXYZ(Const AX,AY,AZ:TChartValue;
                    Const AXLabel:String; AColor:TColor):Integer; overload; virtual;
    Function AssociatedToAxis(const Axis:TChartAxis):Boolean; override;

    Function CalcZPos(ValueIndex:Integer):Integer;
    Function IsValidSourceOf(Value:TChartSeries):Boolean; override;
    procedure Load(const Graphic:TGraphic; Inverted:Boolean=False); overload;
    Function MaxZValue:TChartValue; override;
    Function MinZValue:TChartValue; override;
    property ZValue[Index:Integer]:TChartValue read GetZValue write SetZValue;

    { to be published }
    property TimesZOrder:Integer read FTimesZOrder write SetTimesZOrder default 3;
    property ZValues:TChartValueList read FZValues write SetZValues;
  end;

  TTeePaletteStyle=(psPale,psStrong,psGrayScale,psInvGray,psRainbow,psCustom);

  TChartSurfaceGetColor=Procedure( Sender:TChartSeries;
                                   ValueIndex:Integer;
                                   Var Color:TColor) of object;

  TCustom3DPaletteSeries=class(TCustom3DSeries)
  private
    FEndColor     : TColor;
    FMidColor     : TColor;

    FPalette      : TCustom3DPalette;
    FLegendEvery  : Integer;
    FPaletteMin   : Double;    // overrides automatic palette generation
    FPaletteStep  : Double;    // overrides automatic palette generation
    FPaletteSteps : Integer;
    FPaletteStyle : TTeePaletteStyle;

    FStartColor   : TColor;
    FUseColorRange: Boolean;
    FUsePalette   : Boolean;
    FUsePaletteMin: Boolean;   // overrides automatic palette generation

    FOnGetColor   : TChartSurfaceGetColor;

    { internal }
    IRangeRed    : Integer;
    IEndRed      : Integer;
    IMidRed      : Integer;
    IRangeMidRed : Integer;

    IRangeGreen  : Integer;
    IEndGreen    : Integer;
    IMidGreen    : Integer;
    IRangeMidGreen: Integer;

    IRangeBlue   : Integer;
    IEndBlue     : Integer;
    IMidBlue     : Integer;
    IRangeMidBlue: Integer;

    IRangeAlpha  : Integer;
    IEndAlpha    : Integer;
    IMidAlpha    : Integer;
    IRangeMidAlpha: Integer;

    IValueRangeInv: Double;

    ICustomPalette : Boolean; // When True, FPalette is user-defined.
    IDirtyPalette : Boolean;  // When True, FPalette should be recalculated.

    Procedure CalcColorRange;
    procedure CalcValueRange;
    Procedure CheckPaletteEmpty;
    function DoAddPalette(Const AValue:TChartValue; AColor:TColor):Integer;
    Function RangePercent(const Percent:Double):TColor;
    procedure ReadPalette(Stream: TStream);
    Procedure SetEndColor(Const Value:TColor);
    Procedure SetGalleryPalette;
    procedure SetLegendEvery(const Value: Integer);
    Procedure SetMidColor(Const Value:TColor);
    procedure SetPaletteMin(const Value: Double);
    procedure SetPaletteStep(const Value: Double);
    Procedure SetPaletteSteps(Const Value:Integer);
    procedure SetPaletteStyle(const Value: TTeePaletteStyle);
    Procedure SetRangeColor(var Variable:TColor; const Value:TColor);
    Procedure SetStartColor(Const Value:TColor);
    Procedure SetUseColorRange(Const Value:Boolean);
    Procedure SetUsePalette(Const Value:Boolean);
    procedure SetUsePaletteMin(const Value: Boolean);
    procedure WritePalette(Stream: TStream);
  protected
    DefaultLegend : Boolean;
    PaletteRange : Double;

    Procedure AddValues(Source:TChartSeries); override;
    Procedure AssignFormatting(Source:TChartSeries); override;
    Function CalcLegendIndex(LegendIndex:Integer):Integer; override;
    function CalcLegendString(AStyle:TLegendTextStyle; const AText:String;
                              const AValue:Double; AIndex:Integer):String;
    procedure ChangedColors; virtual;
    class Procedure CreateSubGallery(AddSubChart:TChartSubGalleryProc); override;
    Procedure DefineProperties(Filer:TFiler); override;
    Procedure DoBeforeDrawChart; override;
    Procedure DrawLegendShape(ValueIndex:Integer; Const Rect:TRect); override;
    Procedure GalleryChanged3D(Is3D:Boolean); override;
    procedure GeneratePalette;
    class Function GetEditorClass:String; override;
    Procedure GetPaletteBounds(out AMin,ARange:Double); virtual;
    Function GetValueColor(ValueIndex:Integer):TColor; override;
    Function GetValueColorValue(Const AValue:TChartValue):TColor;
    procedure NotifyColorChange(ValueIndex:Integer); override;
    Procedure NotifyValue(ValueEvent:TValueEvent; ValueIndex:Integer); override;
    Procedure PrepareForGallery; override;
    procedure SetPalette(const Value: TCustom3DPalette); virtual;
    class Procedure SetSubGallery(ASeries:TChartSeries; Index:Integer); override;
  public
    RedFactor    : Double;
    GreenFactor  : Double;
    BlueFactor   : Double;

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

    Function AddPalette(Const AValue:TChartValue; AColor:TColor):Integer; overload;
    procedure AddPalette(Const APalette:TColorArray); overload;
    procedure AddPalette(Const APalette:Array of TColor); overload;

    Procedure Assign(Source:TPersistent); override;
    Procedure Clear; override;
    Procedure ClearPalette;
    Function CountLegendItems:Integer; override;
    Procedure CreateDefaultPalette(NumSteps:Integer=0);
    Procedure CreateRangePalette;
    Function GetSurfacePaletteColor(Const Y:TChartValue):TColor;
    procedure InvertPalette;
    Function LegendItemColor(LegendIndex:Integer):TColor; override;
    Function LegendString( LegendIndex:Integer;
                           LegendTextStyle:TLegendTextStyle ):String; override;
    procedure LoadPaletteFromFile(const FileName:String);
    procedure SavePaletteToFile(const FileName:String);

    property EndColor:TColor read FEndColor write SetEndColor default {$IFDEF FMX}claWhite{$ELSE}clWhite{$ENDIF};
    property MidColor:TColor read FMidColor write SetMidColor default clNone;
    property LegendEvery:Integer read FLegendEvery write SetLegendEvery default 1;
    property Palette:TCustom3DPalette read FPalette write SetPalette;
    property PaletteMin:Double read FPaletteMin write SetPaletteMin;
    property PaletteStep:Double read FPaletteStep write SetPaletteStep;
    property PaletteSteps:Integer read FPaletteSteps write SetPaletteSteps default 32;
    property PaletteStyle:TTeePaletteStyle read FPaletteStyle write SetPaletteStyle default psPale;
    property StartColor:TColor read FStartColor write SetStartColor default {$IFDEF FMX}claNavy{$ELSE}clNavy{$ENDIF};
    property UseColorRange:Boolean read FUseColorRange write SetUseColorRange default True;
    property UsePalette:Boolean read FUsePalette write SetUsePalette default False;
    property UsePaletteMin:Boolean read FUsePaletteMin write SetUsePaletteMin default False;

    { events }
    property OnGetColor:TChartSurfaceGetColor read FOnGetColor write FOnGetColor;
  end;

  { Grid 3D series }
  TChartSurfaceGetY=Function(Sender:TChartSeries; X,Z:Integer):Double of object;

  TCustom3DGridSeries=class(TCustom3DPaletteSeries)
  private
    FIrregularGrid : Boolean;
    FNumXValues    : Integer;
    FNumZValues    : Integer;
    FReuseGrid     : Boolean;
    FOnGetYValue   : TChartSurfaceGetY;
    FXStartIndex    : Integer;
    FZStartIndex    : Integer;

    { internal }
    IDirtyGrid : Boolean;
    INextXCell : Integer;
    INextZCell : Integer;
    INoRandom  : Boolean;

    Function ExistFourGridIndex(X,Z:Integer):Boolean;
    Procedure SetIrregularGrid(Const Value:Boolean);
    Procedure SetNumXValues(Value:Integer);
    Procedure SetNumZValues(Value:Integer);
    function GetValue(X, Z: Integer): TChartValue;
    procedure SetValue(X, Z: Integer; const Value: TChartValue);
  protected
    ValueIndex0    : Integer;
    ValueIndex1    : Integer;
    ValueIndex2    : Integer;
    ValueIndex3    : Integer;

    ISampleFormula : Integer;
    IInGallery     : Boolean;

    Procedure AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False); override;
    Procedure AddValues(Source:TChartSeries); override;
    Procedure DoBeforeDrawChart; override;
    procedure Load(NumX,NumY:Integer; const RGBs:TTee3DRGBArray; Inverted:Boolean=False); override;
    Procedure NotifyValue(ValueEvent:TValueEvent; ValueIndex:Integer); override;
    Function CanCreateValues:Boolean;
  public
    GridIndex      : Array of Array of Integer;

    Constructor Create(AOwner: TComponent); override;

    Procedure Assign(Source:TPersistent); override;

    Procedure Clear; override;
    Procedure CreateValues(NumX,NumZ:Integer); virtual;

    Procedure FillGridIndex; overload;
    Procedure FillGridIndex(StartIndex:Integer); overload;

    Function GetXZValue(X,Z:Integer):TChartValue; virtual;
    Function IsValidSeriesSource(Value:TChartSeries):Boolean; override;
    Function NumSampleValues:Integer; override;
    Procedure ReCreateValues;

    // A "Regular Grid" means X and Z values are equally-spaced integers starting at zero (0,1,2,3,4...)
    // For example, an irregular grid has negative or floating point values (-1.2, -0.6, ... )
    property IrregularGrid:Boolean read FIrregularGrid write SetIrregularGrid default False;

    property NumXValues:Integer read FNumXValues write SetNumXValues default 10;
    property NumZValues:Integer read FNumZValues write SetNumZValues default 10;

    // When True, the slow "FillGridIndex" method is not called at every repaint,
    // and it is the programmer's responsability to fill and maintain the GridIndex array
    // correctly by calling FillGridIndex.
    //
    // Note: FillGridIndex is automatically called in most (if not all) cases.

    property ReuseGridIndex:Boolean read FReuseGrid write FReuseGrid default True;

    property Value[X,Z:Integer]:TChartValue read GetValue write SetValue;

    { events }
    property OnGetYValue:TChartSurfaceGetY read FOnGetYValue write FOnGetYValue;
  end;

  TSurfaceSeries=class(TCustom3DGridSeries)
  private
    { Private declarations }
    FDotFrame       : Boolean;
    FFastBrush      : Boolean;
    FHideCells      : Boolean;
    FSideBrush      : TTeeBrush;
    FSideLines      : TChartHiddenPen;
    FSmoothPalette  : Boolean;
    FWaterFall      : Boolean;
    FWaterLines     : TChartPen;
    FWireFrame      : Boolean;

    { internal }
    FNormals        : Boolean;
    FReuse3D        : Boolean;
    FSameBrush      : Boolean;

    ICachedSurface : TTeeCachedObject;

    procedure FreeCached;

    Procedure SetDotFrame(Value:Boolean);
    procedure SetFastBrush(const Value: Boolean);
    procedure SetHideCells(const Value: Boolean);
    Procedure SetSideBrush(const Value:TChartBrush);
    Procedure SetSideLines(const Value:TChartHiddenPen);
    Procedure SetSmoothPalette(Value:Boolean);
    Procedure SetWaterFall(Value:Boolean);
    Procedure SetWireFrame(Value:Boolean);
    procedure SetWaterLines(const Value: TChartPen);
  protected
    { Protected declarations }
    Points : TFourPoints;

    IsFastBrush : Boolean;
    CanvasDC    : TTeeCanvasHandle;
    IBlender    : TTeeBlend;

    // TEEOPTCALCPOS
    ICalcX          : TAxisCalcPos;
    ICalcY          : TAxisCalcPos;
    ICalcZ          : TAxisCalcPos;

    IXValue         : TChartValues;
    IYValue         : TChartValues;
    IZValue         : TChartValues;

    Procedure AssignFormatting(Source:TChartSeries); override;
    Function CalcPointPos(const Index:Integer):TPoint;
    procedure CanvasChanged(Sender:TObject); override;
    procedure ChangedAxisScale(const Axis:TChartAxis); override;
    class Procedure CreateSubGallery(AddSubChart:TChartSubGalleryProc); override;
    procedure DoBeforeDrawValues; override;
    Procedure DrawAllValues; override;
    Procedure DrawCell(x,z:Integer); virtual;
    Procedure DrawSidePortion(var P:TFourPoints; z0,z1:Integer); virtual;
    Procedure DrawValue(ValueIndex:Integer); override;
    procedure FastCalcPoints(x,z:Integer; CalcColors:Boolean; out V:TSurfaceVertex);
    Function FourGridIndex(x,z:Integer):Boolean;
    class Function GetEditorClass:String; override;
    Procedure NotifyValue(ValueEvent:TValueEvent; ValueIndex:Integer); override;
    Procedure PointsTo2D(Z0,Z1:Integer; out P:TFourPoints);
    Procedure PrepareCalcPos; virtual;
    procedure PrepareFastBrush;
    Procedure PrepareForGallery; override;
    Procedure SetBrushColor(const AColor:TColor);
    class Procedure SetSubGallery(ASeries:TChartSeries; Index:Integer); override;
    function ShouldDrawFast:Boolean; virtual;
    function ShouldDrawSides:Boolean; virtual;

    // Experimental
    property Normals:Boolean read FNormals write FNormals;
    property Reuse3D:Boolean read FReuse3D write FReuse3D;

  public
    { Public declarations }
    Constructor Create(AOwner: TComponent); override;
    Destructor Destroy; override;

    Procedure Assign(Source:TPersistent); override;
    Function CellsOrientation:TCellsOrientation;
    Function Clicked(x,y:Integer):Integer; override;
    Function ClickedCell(x,y:Integer):Integer;
    Function MoreSameZOrder:Boolean; override;

    property WaterFall:Boolean read FWaterFall write SetWaterFall default False;
    property WaterLines:TChartPen read FWaterLines write SetWaterLines;
  published
    { Published declarations }
    property Active;
    property ColorSource;
    property Cursor;
    property FastBrush:Boolean read FFastBrush write SetFastBrush default False;
    property HorizAxis;
    property Marks;
    property ParentChart;
    property DataSource;
    property PercentFormat;
    property SeriesColor;
    property ShowInLegend;
    property Title;
    property ValueFormat;
    property VertAxis;
    property XLabelsSource;

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

    property Brush;
    property DotFrame:Boolean read FDotFrame write SetDotFrame default False;
    property EndColor;
    property HideCells:Boolean read FHideCells write SetHideCells default True;
    property IrregularGrid;
    property MidColor;
    property NumXValues;
    property NumZValues;
    property LegendEvery;
    property Pen;
    property PaletteMin;
    property PaletteStep;
    property PaletteSteps;
    property PaletteStyle;
    property SideBrush:TChartBrush read FSideBrush write SetSideBrush;
    property SideLines:TChartHiddenPen read FSideLines write SetSideLines;
    property SmoothPalette:Boolean read FSmoothPalette write SetSmoothPalette default False;
    property StartColor;
    property UseColorRange;
    property UsePalette;
    property UsePaletteMin;
    property WireFrame:Boolean read FWireFrame write SetWireFrame default False;
    property TimesZOrder;
    property Transparency;
    property XValues;
    property YValues;
    property ZValues;
    { events }
    property OnGetYValue;
    property OnGetColor;
  end;

  TContourSeries=class;

  TOnBeforeDrawLevelEvent=procedure( Sender:TContourSeries;
                                     LevelIndex:Integer) of object;

  TOnGetLevelEvent=procedure( Sender:TContourSeries; LevelIndex:Integer;
                              var Value:Double; var Color:TColor) of object;

  TContourLevel=class(TCollectionItem)
  private
    FColor  : TColor;
    FPen    : TChartPen;
    FText   : String;
    FUpTo   : Double;

    // Line Segments
    FSegmentCount : Integer;
    FSegments     : TLevelSegments;

    ISeries : TContourSeries;

    procedure AddSegment(const A,B:TLevelPoint);
    Procedure CheckAuto;
    function GetPen: TChartPen;
    function IsPenStored: Boolean;
    procedure SetColor(const Value: TColor);
    procedure SetPen(const Value: TChartPen);
    procedure SetText(const Value:String);
    procedure SetUpTo(const Value: Double);
  protected
    function InternalColor:TColor;
    function InternalPen:TChartPen;
  public
    Constructor Create(Collection:TCollection); override;
    Destructor Destroy; override;

    Procedure Assign(Source:TPersistent); override;

    Procedure ClearSegments;
    Function Clicked(x,y:Integer; out SegmentIndex,PointIndex:Integer):Boolean;
    Function ClickedSegment(x,y,SegmentIndex:Integer; out PointIndex:Integer):Boolean;
    procedure DeleteSegment(const Index:Integer);
    Function GetSegmentPoints(SegmentIndex:Integer):TPointArray;
    Function GetSegmentPointsFloat(SegmentIndex:Integer):TPointFloatArray;

    Function DefaultPen:Boolean;

    function SegmentCount:Integer; {$IFDEF 10}inline;{$ENDIF}
    property Segments:TLevelSegments read FSegments;
  published
    property Color:TColor read FColor write SetColor;
    property Pen:TChartPen read GetPen write SetPen stored IsPenStored;
    property Text:String read FText write SetText;
    property UpToValue:Double read FUpTo write SetUpTo;
  end;

  TContourLevels=class(TOwnedCollection)
  private
    Function Get(Index:Integer):TContourLevel;
    Procedure Put(Index:Integer; Const Value:TContourLevel);
  public
    function AddLevel(const Value:Double; const Color:TColor=clTeeColor):TContourLevel;

    Function Clicked(x,y:Integer; out SegmentIndex,PointIndex:Integer):Integer;

    function Contour:TContourSeries;

    function Last:TContourLevel;

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

  TSmoothPoints=class(TPersistent)
  private
    FActive : Boolean;
    ISeries : TChartSeries;
    FInterpolate: Boolean;
    procedure SetActive(const Value: Boolean);
    procedure SetInterpolate(const Value: Boolean);
  public
    Factor : Integer;

    Constructor Create(const Parent:TChartSeries);
    Procedure Assign(Source:TPersistent); override;
    Function Calculate(Const P:Array of TPoint):TPointArray;
    Function CalculateFloat(Const P:Array of TPointFloat):TPointFloatArray;
  published
    property Active:Boolean read FActive write SetActive default False;
    property Interpolate:Boolean read FInterpolate write SetInterpolate default False;
  end;

  TContourMarks=class(TPersistent)
  private
    FAntiOverlap : Boolean;
    FAtSegments  : Boolean;
    FColorLevel  : Boolean;
    FDensity     : Integer;
    FMargin      : Integer;

    IContour : TChartSeries;
    function GetVisible: Boolean;
    procedure SetAntiOverlap(const Value: Boolean);
    procedure SetAtSegments(const Value: Boolean);
    procedure SetColorLevel(const Value: Boolean);
    procedure SetDensity(const Value: Integer);
    procedure SetMargin(const Value: Integer);
    procedure SetVisible(const Value: Boolean);
  public
    Constructor Create;
    procedure Assign(Source:TPersistent); override;
  published
    property AntiOverlap:Boolean read FAntiOverlap write SetAntiOverlap default False;
    property AtSegments:Boolean read FAtSegments write SetAtSegments default True;
    property ColorLevel:Boolean read FColorLevel write SetColorLevel default False;
    property Margin:Integer read FMargin write SetMargin default 0;
    property Density:Integer read FDensity write SetDensity default 4;
    property Visible:Boolean read GetVisible write SetVisible stored False;
  end;

  TIsoSurfaceSeries=class;

  TContourSeries=class(TCustom3DGridSeries)
  private
    FAutomaticLevels: Boolean;
    FCellPen        : TChartHiddenPen;
    FContourMarks   : TContourMarks;
    FFrame          : TChartHiddenPen;
    FLegendLines    : Boolean;
    FLevels         : TContourLevels;
    FLinesColor     : TColor;
    FNumLevels      : Integer;
    FPointer        : TSeriesPointer;
    FSmoothing      : TSmoothPoints;
    FYPosition      : TChartValue;
    FYPositionLevel : Boolean;

    FOnBeforeDrawLevel : TOnBeforeDrawLevelEvent;
    FOnGetLevel        : TOnGetLevelEvent;

    IDirtySegments   : Boolean;
    IFillSeries      : TIsoSurfaceSeries;
    IModifyingLevels : Boolean;

    TheLevel         : TContourLevel;

    procedure AddSegmentLine(const A,B:TLevelPoint);
    procedure ClearAutoLevels;
    procedure DrawFrame(const YPos:Integer);
    function GetCellPen:TChartHiddenPen;
    function GetFilled:Boolean;
    function GetNumLevels: Integer;
    function IsLevelsStored: Boolean;
    function MarkOverlaps(const ABounds:TRect):Boolean;
    procedure SetAutomaticLevels(const Value: Boolean);
    procedure SetCellPen(const Value:TChartHiddenPen);
    procedure SetContourMarks(const Value: TContourMarks);
    procedure SetFilled(const Value: Boolean);
    procedure SetFrame(const Value: TChartHiddenPen);
    procedure SetLegendLines(const Value: Boolean);
    procedure SetLevels(const Value: TContourLevels);
    procedure SetLinesColor(const Value: TColor);
    Procedure SetNumLevels(Value:Integer);
    Procedure SetPointer(Const Value:TSeriesPointer);
    procedure SetSmoothing(const Value: TSmoothPoints);
    Procedure SetYPosition(Const Value:TChartValue);
    Procedure SetYPositionLevel(Value:Boolean);
    procedure TryCreateLevels;
  protected
    procedure AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False); override;
    Procedure AssignFormatting(Source:TChartSeries); override;
    Function CalcLegendIndex(LegendIndex:Integer):Integer; override;
    function CalcXYPoint(const P:TLevelPoint):TPoint; virtual;
    function CalcXYPointFloat(const P:TLevelPoint):TPointFloat; virtual;
    procedure ChangedColors; override;
    function CreateIso:TIsoSurfaceSeries; virtual;
    class Procedure CreateSubGallery(AddSubChart:TChartSubGalleryProc); override;
    Procedure DoBeforeDrawChart; override;
    procedure DrawAllValues; override;
    Procedure DrawLegendShape(ValueIndex:Integer; Const Rect:TRect); override;
    Procedure DrawMark(ValueIndex:Integer; Const St:String;
                       APosition:TSeriesMarkPosition); override;
    procedure DrawMarks; override;
    class Function GetEditorClass:String; override;
    Procedure GetPaletteBounds(out AMin,ARange:Double); override;
    procedure NotifyColorChange(ValueIndex:Integer); override;
    Procedure NotifyValue(ValueEvent:TValueEvent; ValueIndex:Integer); override;
    procedure SetPalette(const Value: TCustom3DPalette); override;
    procedure SetParentChart(const Value: TCustomAxisPanel); override;
    class Procedure SetSubGallery(ASeries:TChartSeries; Index:Integer); override;
    function ValueIndexToLegend(ValueIndex:Integer):Integer; override;
  public
    Constructor Create(AOwner: TComponent); override;
    Destructor Destroy; override;

    Procedure Assign(Source:TPersistent); override;
    Function Clicked(x,y:Integer):Integer; override;
    Function CountLegendItems:Integer; override;
    Procedure CreateAutoLevels;
    Function GetMarkValue(ValueIndex:Integer):Double; override;
    function GetZAxis:TChartAxis;
    Function LegendItemColor(LegendIndex:Integer):TColor; override;
    Function LegendString( LegendIndex:Integer;
                           LegendTextStyle:TLegendTextStyle ):String; override;
    Function LegendToValueIndex(LegendIndex:Integer):Integer; override;

    Function MaxYValue:TChartValue; override;
    Function MinYValue:TChartValue; override;
  published
    property Active;
    property AutomaticLevels:Boolean read FAutomaticLevels write SetAutomaticLevels default True;
    property Brush;
    property CellPen:TChartHiddenPen read GetCellPen write SetCellPen;
    property ColorEachPoint default True;
    property ColorSource;
    property ContourMarks:TContourMarks read FContourMarks write SetContourMarks;
    property Cursor;
    property Filled:Boolean read GetFilled write SetFilled default False;
    property Frame:TChartHiddenPen read FFrame write SetFrame;
    property HorizAxis;
    property Marks;
    property ParentChart;
    property DataSource;
    property PercentFormat;
    property SeriesColor;
    property ShowInLegend;
    property Title;
    property ValueFormat;
    property VertAxis;
    property XLabelsSource;

    property EndColor;
    property IrregularGrid;
    property LegendEvery;

    // Draw simple lines at legend when levels aren't filled.
    property LegendLines:Boolean read FLegendLines write SetLegendLines default False;

    property Levels:TContourLevels read FLevels write SetLevels stored IsLevelsStored;

    // Default color to draw level lines when Brush.Style<>bsClear.
    // "clDefault" means color is same as level color. Default is clBlack.
    property LinesColor:TColor read FLinesColor write SetLinesColor
                               default {$IFDEF FMX}claBlack{$ELSE}clBlack{$ENDIF};

    property MidColor;
    property NumLevels:Integer read GetNumLevels write SetNumLevels default 10;
    property NumXValues;
    property NumZValues;
    property PaletteMin;
    property PaletteStep;
    property PaletteSteps;
    property PaletteStyle;
    property Pen;
    property Pointer:TSeriesPointer read FPointer write SetPointer;
    property Smoothing:TSmoothPoints read FSmoothing write SetSmoothing;
    property StartColor;
    property TimesZOrder;
    property Transparency;
    property UseColorRange default False;
    property UsePalette;
    property UsePaletteMin;
    property XValues;
    property YPosition:TChartValue read FYPosition write SetYPosition;
    property YPositionLevel:Boolean read FYPositionLevel write SetYPositionLevel default False;
    property YValues;
    property ZValues;

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

    property OnBeforeDrawLevel:TOnBeforeDrawLevelEvent read FOnBeforeDrawLevel
                                                       write FOnBeforeDrawLevel;
    property OnGetYValue;
    property OnGetLevel:TOnGetLevelEvent read FOnGetLevel write FOnGetLevel;
  end;

  TWaterFallSeries=class(TSurfaceSeries)
  protected
    class Procedure CreateSubGallery(AddSubChart:TChartSubGalleryProc); override;
    Procedure GalleryChanged3D(Is3D:Boolean); override;
    class Function GetEditorClass:String; override;
    class Procedure SetSubGallery(ASeries:TChartSeries; Index:Integer); override;
  public
    Constructor Create(AOwner: TComponent); override;
    Procedure Assign(Source:TPersistent); override;
  published
    property WaterFall default True;
    property WaterLines;
  end;

  TColorGridSeries=class(TCustom3DGridSeries)
  private
    FBitmap       : TBitmap;
    FCentered     : Boolean;
    FPlane3D      : TCanvas3DPlane;
    FFrame        : TChartHiddenPen;
    FPosition     : Integer;
    FSmoothBitmap : Boolean;
    FXGridEvery   : Integer;
    FZGridEvery   : Integer;
    FXRegularSteps : Boolean;
    FZRegularSteps : Boolean;
    FXRegularStep : Double;
    FZRegularStep : Double;

    IDirtyBitmap  : Boolean;
    IOldBounds    : {$IFDEF FMX}Types.{$ENDIF}TRect;

    function CalcMaxValue(const Value:Double):Double;
    function CalcMinValue(const Value:Double):Double;
    procedure DoCreateBitmap(AWidth,AHeight:Integer);
    procedure SetBitmap(const Value: TBitmap);
    procedure SetCentered(const Value: Boolean);
    procedure SetPlane3D(const Value: TCanvas3DPlane);
    procedure SetFrame(const Value: TChartHiddenPen);
    procedure SetPosition(const Value: Integer);
    procedure SetSmoothBitmap(const Value: Boolean);
    procedure SetXGridEvery(const Value: Integer);
    procedure SetZGridEvery(const Value: Integer);
  protected
    procedure AssignFormatting(Source:TChartSeries); override;
    procedure ChangedColors; override;
    class Procedure CreateSubGallery(AddSubChart:TChartSubGalleryProc); override;
    procedure DrawAllValues; override;
    Procedure DrawMark( ValueIndex:Integer; Const St:String;
                        APosition:TSeriesMarkPosition); override;
    Procedure GalleryChanged3D(Is3D:Boolean); override;
    class Function GetEditorClass:String; override;
    Procedure NotifyValue(ValueEvent:TValueEvent; ValueIndex:Integer); override;
    Procedure PrepareForGallery; override;
    class Procedure SetSubGallery(ASeries:TChartSeries; Index:Integer); override;
  public
    Constructor Create(AOwner:TComponent); override;
    Destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;

    function CellBounds(ValueIndex:Integer):TRect; overload;
    function CellBounds(x,z:Integer):TRect; overload;

    Function CalcXPos(ValueIndex:Integer):Integer; override;
    Function CalcYPos(ValueIndex:Integer):Integer; override;

    Function Clicked(X,Y:Integer):Integer; override;
    Function MinXValue:TChartValue; override;
    Function MaxXValue:TChartValue; override;
    Function MaxYValue:TChartValue; override;
    Function MinYValue:TChartValue; override;
    Function MinZValue:TChartValue; override;
    Function MaxZValue:TChartValue; override;

    property Bitmap:TBitmap read FBitmap write SetBitmap;
  published
    { Published declarations }
    property Active;
    property CenteredPoints:Boolean read FCentered write SetCentered default False;
    property ColorSource;
    property Cursor;
    property DrawPlane3D:TCanvas3DPlane read FPlane3D write SetPlane3D default cpZ;
    property DrawPosition:Integer read FPosition write SetPosition default 0;
    property Frame:TChartHiddenPen read FFrame write SetFrame;
    property HorizAxis;
    property Marks;
    property ParentChart;
    property DataSource;
    property PercentFormat;
    property SeriesColor;
    property ShowInLegend;
    property SmoothBitmap:Boolean read FSmoothBitmap write SetSmoothBitmap default False;
    property Title;
    property Transparency;
    property ValueFormat;
    property VertAxis;
    property XGridEvery:Integer read FXGridEvery write SetXGridEvery default 1;
    property XLabelsSource;
    property ZGridEvery:Integer read FZGridEvery write SetZGridEvery default 1;

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

    property Brush;
    property EndColor;
    property IrregularGrid;
    property MidColor;
    property NumXValues;
    property NumZValues;
    property LegendEvery;
    property Pen;
    property PaletteMin;
    property PaletteStep;
    property PaletteSteps;
    property PaletteStyle;
    property StartColor;
    property UseColorRange;
    property UsePalette;
    property UsePaletteMin;
    property TimesZOrder;
    property XValues;
    property YValues;
    property ZValues;
    { events }
    property OnGetYValue;
    property OnGetColor;
  end;

  TVector3DSeries=class(TCustom3DPaletteSeries)
  private
    FArrowHeight : Integer;
    FArrowWidth  : Integer;
    FStartArrow  : TChartHiddenPen;
    FEndArrow    : TChartPen;
    FEndXValues  : TChartValueList; { <-- Vector end X values }
    FEndYValues  : TChartValueList; { <-- Vector end Y values }
    FEndZValues  : TChartValueList;

    procedure SetArrowHeight(const Value: Integer);
    procedure SetArrowWidth(const Value: Integer);
    Procedure SetEndXValues(const Value:TChartValueList);
    Procedure SetEndYValues(const Value:TChartValueList);
    Procedure SetEndZValues(const Value:TChartValueList);
    procedure SetEndArrow(const Value: TChartPen);
    procedure SetStartArrow(const Value: TChartHiddenPen);
  protected
    Procedure AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False); override;
    Procedure AssignFormatting(Source:TChartSeries); override;
    procedure DrawValue(ValueIndex:Integer); override; { <-- main draw method }
    procedure DrawAllValues; override;
    class Function GetEditorClass:String; override;
  public
    Constructor Create(AOwner: TComponent); override;
    Destructor Destroy; override;

    Function AddVector(Const X0,Y0,Z0,X1,Y1,Z1:Double; Const ALabel:String='';
                     AColor:TColor=clTeeColor):Integer; overload;
    Function IsValidSourceOf(Value:TChartSeries):Boolean; override;
    Function MaxXValue:TChartValue; override;
    Function MinXValue:TChartValue; override;
    Function MaxYValue:TChartValue; override;
    Function MinYValue:TChartValue; override;
    Function MaxZValue:TChartValue; override;
    Function MinZValue:TChartValue; override;
    Function NumSampleValues:Integer; override;
  published
    { Published declarations }
    property Active;
    property ColorSource;
    property Cursor;
    property HorizAxis;
    property Marks;
    property ParentChart;
    property DataSource;
    property PercentFormat;
    property SeriesColor;
    property ShowInLegend;
    property Title;
    property Transparency;
    property ValueFormat;
    property VertAxis;
    property XLabelsSource;

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

    property Brush;
    property EndColor;
    property MidColor;
    property LegendEvery;
    property Pen;
    property PaletteMin;
    property PaletteStep;
    property PaletteSteps;
    property PaletteStyle;
    property StartColor;
    property UseColorRange;
    property UsePalette;
    property UsePaletteMin;
    property TimesZOrder;
    property XValues;
    property YValues;
    property ZValues;
    { events }
    property OnGetColor;

    property ArrowHeight:Integer read FArrowHeight write SetArrowHeight default 4;
    property ArrowWidth:Integer read FArrowWidth write SetArrowWidth default 4;
    property EndArrow:TChartPen read FEndArrow write SetEndArrow;
    property EndXValues:TChartValueList read FEndXValues write SetEndXValues;
    property EndYValues:TChartValueList read FEndYValues write SetEndYValues;
    property EndZValues:TChartValueList read FEndZValues write SetEndZValues;
    property StartArrow:TChartHiddenPen read FStartArrow write SetStartArrow;
  end;

  TTowerStyle=(tsCube,tsRectangle,tsCover,tsCylinder,tsArrow,tsCone,tsPyramid,
               tsSlantCube,tsVisual);

  TTowerStacked=(tsNone,tsStacked,tsStacked100);

  TTowerSeries=class;

  // Private type:
  TTowerPosition=record
    Y : Integer;
    Tower : TChartSeries;
  end;

  // Private type:
  TTowerPositions=Array of TTowerPosition;

  TTowerSeries=class(TCustom3DGridSeries)
  private
    FDark3D       : Boolean;
    FOrigin       : Double;
    FPercDepth    : Integer;
    FPercWidth    : Integer;
    FStacked      : TTowerStacked;
    FTowerStyle   : TTowerStyle;
    FUseOrigin    : Boolean;

    IOffW  : Double;
    IOffD  : Double;

    IBlend : TTeeBlend;
    IChangeBrush : Boolean;
    IFirstIndex : Boolean;
    ISorted : TTowerPositions;

    Function CalcCell(AIndex:Integer; out ATop,ABottom,Z0,Z1:Integer):TRect;
    Function DoCompare(a,b:Integer):Integer;
    Procedure DoDrawCell(x,z:Integer);
    procedure DoSwap(a,b:Integer);
    function GetOriginPos(ValueIndex:Integer):Integer;
    Function MaxMandatoryValue(const Value:TChartValue):TChartValue;
    Function MinMandatoryValue(Const Value:TChartValue):TChartValue;
    Function PointOrigin(ValueIndex:Integer; SumAll:Boolean):Double;
    procedure SetDark3D(const Value: Boolean);
    Procedure SetOrigin(Const Value:Double);
    procedure SetOtherTowers;
    procedure SetPercDepth(const Value: Integer);
    procedure SetPercWidth(const Value: Integer);
    procedure SetStacked(const Value: TTowerStacked);
    procedure SetTowerStyle(const Value: TTowerStyle);
    Procedure SetUseOrigin(Value:Boolean);
  protected
    procedure AssignFormatting(Source: TChartSeries); override;
    class Procedure CreateSubGallery(AddSubChart:TChartSubGalleryProc); override;
    Procedure DrawMark( ValueIndex:Integer; Const St:String;
                        APosition:TSeriesMarkPosition); override;
    procedure DrawAllValues; override;
    class Function GetEditorClass:String; override;
    Procedure PrepareForGallery; override;
    Procedure SetParentChart(Const Value:TCustomAxisPanel); override;
    class Procedure SetSubGallery(ASeries:TChartSeries; Index:Integer); override;
  public
    Constructor Create(AOwner:TComponent); override;

    Procedure Assign(Source:TPersistent); override;

    Function CalcYPos(ValueIndex:Integer):Integer; override;
    Function Clicked(X,Y:Integer):Integer; override;

    Function MinXValue:TChartValue; override;
    Function MaxXValue:TChartValue; override;
    function MaxYValue:TChartValue; override;
    function MinYValue:TChartValue; override;
    Function MinZValue:TChartValue; override;
    Function MaxZValue:TChartValue; override;
  published
    { Published declarations }
    property Active;
    property ColorSource;
    property Cursor;
    property HorizAxis;
    property Marks;
    property ParentChart;
    property DataSource;
    property PercentFormat;
    property SeriesColor;
    property ShowInLegend;
    property Title;
    property ValueFormat;
    property VertAxis;
    property XLabelsSource;

    property Dark3D:Boolean read FDark3D write SetDark3D default True;
    property PercentDepth:Integer read FPercDepth write SetPercDepth default 100;
    property Origin:Double read FOrigin write SetOrigin;
    property Stacked:TTowerStacked read FStacked write SetStacked default tsNone;
    property TowerStyle:TTowerStyle read FTowerStyle write SetTowerStyle default tsCube;
    property Transparency;
    property PercentWidth:Integer read FPercWidth write SetPercWidth default 100;
    property UseOrigin:Boolean read FUseOrigin write SetUseOrigin default False;

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

    property Brush;
    property EndColor;
    property IrregularGrid;
    property MidColor;
    property NumXValues;
    property NumZValues;
    property LegendEvery;
    property Pen;
    property PaletteMin;
    property PaletteStep;
    property PaletteSteps;
    property PaletteStyle;
    property StartColor;
    property UseColorRange;
    property UsePalette;
    property UsePaletteMin;
    property TimesZOrder;
    property XValues;
    property YValues;
    property ZValues;

    { events }
    property OnGetYValue;
    property OnGetColor;
  end;

  TSurfaceSides=class(TPersistent)
  private
    FLevels : Boolean;
    FPen    : TChartHiddenPen;

    ISeries : TSurfaceSeries;
    Procedure CanvasChanged(Sender:TObject);
    function GetBrush:TChartBrush;
    Procedure SetBrush(const Value:TChartBrush);
    Procedure SetLevels(const Value:Boolean);
    Procedure SetPen(const Value:TChartHiddenPen);
  public
    Constructor Create(const Series:TSurfaceSeries);
    Destructor Destroy; override;

    Procedure Assign(Source:TPersistent); override;
  published
    property Brush:TChartBrush read GetBrush write SetBrush;
    property Levels:Boolean read FLevels write SetLevels default True;
    property Pen:TChartHiddenPen read FPen write SetPen;
  end;

  TIsoSurfaceSeries=class(TSurfaceSeries)
  private
    FBandPen : TChartPen;
    FSides   : TSurfaceSides;
    FUseY    : Boolean;
    FYPosition : TChartValue;

    xv : TChartValues;
    zv : TChartValues;
    v  : TChartValues;
    HasImage : Boolean;
    ITransp  : Boolean;
    PaletteLength : Integer;

    IPoints2D     : TPointArray;
    ICalcYPos     : Integer;
    INoFixAlign   : Boolean;

    IDifs         : TObject; // Internal Levels calculation object
    ILevelValue   : TChartValue; // For DrawSegmentLine (Bands)

    procedure DrawSegmentLine(const A,B:TLevelPoint);
    procedure FillSegment2D(const P:Array of TLevelPoint3D);
    procedure FillSegment3D(const P:Array of TLevelPoint3D);
    procedure SetBandPen(const Value: TChartPen);
    procedure SetSides(const Value: TSurfaceSides);
    procedure SetUseY(const Value: Boolean);
    procedure SetYPosition(const Value: TChartValue);
  protected
    procedure AssignFormatting(Source: TChartSeries); override;
    function CalcXYPoint(const P:TLevelPoint3D):TPoint; virtual;
    Procedure DrawAllValues; override;
    Procedure DrawCell(x,z:Integer); override;
    Procedure DrawSidePortion(var P:TFourPoints; Z0,Z1:Integer); override;
    class function GetEditorClass: String; override;
    Procedure PrepareCalcPos; override;
    Procedure PrepareForGallery; override;
    function ShouldDrawFast:Boolean; override;
    function ShouldDrawSides:Boolean; override;
  public
    Constructor Create(AOwner:TComponent); override;
    Destructor Destroy; override;

    Procedure Assign(Source:TPersistent); override;
    Function MinYValue:TChartValue; override;
    Function MaxYValue:TChartValue; override;
  published
    property BandPen:TChartPen read FBandPen write SetBandPen;
    property Sides:TSurfaceSides read FSides write SetSides;
    property UseColorRange default False;
    property UsePalette default True;
    property UseYPosition:Boolean read FUseY write SetUseY default False;
    property YPosition:TChartValue read FYPosition write SetYPosition;
  end;

  // Wrapper class around Custom3DPalette series properties.
  // This class is used just to make published the contained "Palette" property
  // of other Series like TPolarGridSeries, TTreeMapSeries, etc,
  // that do not derive from TCustom3DPaletteSeries.
  TSeriesColorPalette=class(TPersistent)
  private
    IChart   : TCustomChart;

    function GetEndColor: TColor;
    function GetLegendEvery: Integer;
    function GetMidColor: TColor;
    function GetPalette: TCustom3DPalette;
    function GetPaletteMin: Double;
    function GetPaletteStep: Double;
    function GetPaletteSteps: Integer;
    function GetPaletteStyle: TTeePaletteStyle;
    function GetStartColor: TColor;
    function GetUseColorRange: Boolean;
    function GetUsePalette: Boolean;
    function GetUsePaletteMin: Boolean;
    procedure SetEndColor(const Value: TColor);
    procedure SetLegendEvery(const Value: Integer);
    procedure SetMidColor(const Value: TColor);
    procedure SetPalette(const Value: TCustom3DPalette);
    procedure SetPaletteMin(const Value: Double);
    procedure SetPaletteStep(const Value: Double);
    procedure SetPaletteSteps(const Value: Integer);
    procedure SetPaletteStyle(const Value: TTeePaletteStyle);
    procedure SetStartColor(const Value: TColor);
    procedure SetUseColorRange(const Value: Boolean);
    procedure SetUsePalette(const Value: Boolean);
    procedure SetUsePaletteMin(const Value: Boolean);
  public
    Series : TCustom3DPaletteSeries;

    Constructor Create(const AOwner:TChartSeries);
    Destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;

    procedure InitColors(Color:TColor; Const MinValue,MaxValue:TChartValue);

    // Palette should not be published.
    property Palette:TCustom3DPalette read GetPalette write SetPalette;
  published
    property EndColor:TColor read GetEndColor write SetEndColor
                             default {$IFDEF FMX}claWhite{$ELSE}clWhite{$ENDIF};

    property MidColor:TColor read GetMidColor write SetMidColor default clNone;
    property LegendEvery:Integer read GetLegendEvery write SetLegendEvery default 1;
    property PaletteMin:Double read GetPaletteMin write SetPaletteMin;
    property PaletteStep:Double read GetPaletteStep write SetPaletteStep;
    property PaletteSteps:Integer read GetPaletteSteps write SetPaletteSteps default 32;
    property PaletteStyle:TTeePaletteStyle read GetPaletteStyle write SetPaletteStyle default psPale;
    property StartColor:TColor read GetStartColor write SetStartColor
                               default {$IFDEF FMX}claNavy{$ELSE}clNavy{$ENDIF};
    property UseColorRange:Boolean read GetUseColorRange write SetUseColorRange default True;
    property UsePalette:Boolean read GetUsePalette write SetUsePalette default False;
    property UsePaletteMin:Boolean read GetUsePaletteMin write SetUsePaletteMin default False;
  end;

  {.$DEFINE ISOTEST} // Statistics when building contour iso-levels and drawing surfaces
  {$IFDEF ISOTEST}
  var IsoStats:Array[0..26] of Integer;
      TempCounter:Integer;
  {$ENDIF}

  TBubbleCloudSeries=class(TCustom3DPaletteSeries)
  private
    FRotation        : Integer;
    FSeparation      : Integer;
    FSeparationUnits : TTeeUnits;
    FSizeRatio       : Double;

    ICenters  : Array of TPoint;
    ITangents : Array of Integer;

    IOffset  : TPoint;
    IBounds  : TRect;
    IRect    : TRect;
    IScale   : Single;

    function IsSizeRatioStored: Boolean;
    procedure SetRotation(const Value: Integer);
    procedure SetSizeRatio(const Value: Double);
    procedure SetSeparation(const Value: Integer);
    procedure SetSeparationUnits(const Value: TTeeUnits);
  protected
    function BubbleRect(ValueIndex:Integer):TRect;
    function Distance(A,B:Integer):Double;
    Procedure DoBeforeDrawValues; override;
    Procedure DrawMark( ValueIndex:Integer; Const St:String;
                        APosition:TSeriesMarkPosition); override;
    class Function GetEditorClass:String; override;
  public
    Constructor Create(AOwner: TComponent); override;

    Procedure Assign(Source:TPersistent); override;

    Function CalcXPos(ValueIndex:Integer):Integer; override;
    Function CalcYPos(ValueIndex:Integer):Integer; override;

    function Clicked(X, Y: Integer): Integer; override;

    procedure DrawValue(ValueIndex:Integer); override;

    function Radius(ValueIndex:Integer):TChartValue;

    Function UseAxis:Boolean; override;
  published
    { Published declarations }
    property Active;
    property ColorSource;
    property Cursor;
    property HorizAxis;
    property Marks;
    property ParentChart;
    property DataSource;
    property PercentFormat;
    property SeriesColor;
    property ShowInLegend;
    property Title;
    property Transparency;
    property ValueFormat;
    property VertAxis;
    property XLabelsSource;

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

    // inherited
    property Brush;
    property EndColor;
    property MidColor;
    property LegendEvery;
    property Pen;
    property PaletteMin;
    property PaletteStep;
    property PaletteSteps;
    property PaletteStyle;
    property StartColor;
    property UseColorRange;
    property UsePalette;
    property UsePaletteMin;
    property XValues;
    property YValues;

    property Rotation:Integer read FRotation write SetRotation default 0;
    property Separation:Integer read FSeparation write SetSeparation default 0;
    property SeparationUnits:TTeeUnits read FSeparationUnits write SetSeparationUnits default muPercent;
    property SizeRatio:Double read FSizeRatio write SetSizeRatio stored IsSizeRatioStored;
  end;

implementation

uses
  Math,
  {$IFDEF FMX}
  FMXTee.Series.Spline, FMXTee.ProConstants, FMXTee.Constants
  {$ELSE}
  TeeSpline, TeeProCo, TeeConst
  {$ENDIF}
  ;

{ TCustom3DSeries }

Constructor TCustom3DSeries.Create(AOwner: TComponent);
Begin
  inherited;

  HasZValues:=True;
  CalcVisiblePoints:=False;

  FZValues:=TChartValueList.Create(Self,'Z'); { <-- dont translate ! }

  XValues.Order:=loNone;

  FTimesZOrder:=3;
end;

Procedure TCustom3DSeries.SetZValues(Const Value:TChartValueList);
Begin
  SetChartValueList(FZValues,Value); { standard method }
End;

function TCustom3DSeries.AxisRectChanged:Boolean;
begin
  result:=(IOldStartZ<>StartZ) or (IOldEndZ<>EndZ) or
          (not EqualRect(ParentChart.ChartRect,IOldChartRect));
end;

procedure TCustom3DSeries.SaveChartRect;
begin
  IOldChartRect:=ParentChart.ChartRect;
  IOldStartZ:=StartZ;
  IOldEndZ:=EndZ;
end;

procedure TCustom3DSeries.DrawAllValues;
begin
  inherited;
  SaveChartRect;
end;

Procedure TCustom3DSeries.CalcZOrder;
Begin
  inherited;
  ParentChart.MaxZOrder:=FTimesZOrder;
end;

Procedure TCustom3DSeries.DrawMark( ValueIndex:Integer; Const St:String;
                                    APosition:TSeriesMarkPosition);
begin
  if ValueIndex<>-1 then
     Marks.ZPosition:=CalcZPos(ValueIndex);

  inherited;
end;

Procedure TCustom3DSeries.AddArray(Const Values:TArrayGrid);
var x : Integer;
    z : Integer;
begin
  BeginUpdate;
  try
    for x:=Low(Values) to High(Values) do
        for z:=Low(Values[x]) to High(Values[x]) do
            AddXYZ(x,Values[x,z],z);
  finally
    EndUpdate;
  end;
end;

Function TCustom3DSeries.AddXYZ(Const AX,AY,AZ:TChartValue):Integer;
begin
  ZValues.TempValue:=AZ;
  result:=AddXY(AX,AY);
end;

Function TCustom3DSeries.AddXYZ(Const AX,AY,AZ:TChartValue;
                                Const AXLabel:String; AColor:TColor):Integer;
Begin
  ZValues.TempValue:=AZ;
  result:=AddXY(AX,AY,AXLabel,AColor);
end;

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

procedure TCustom3DSeries.Load(NumX,NumY:Integer; const RGBs:TTee3DRGBArray; Inverted:Boolean=False);
var x,y : Integer;
    tmpValue : Cardinal;
begin
  {$IFOPT R+}
  {$DEFINE RANGEON}
  {$ENDIF}

  {$R-}
  for x:=0 to NumX-1 do
      for y:=0 to NumY-1 do
      begin
        with RGBs[y,x] do
             tmpValue:=(Red or (Green shl 8) or (Blue shl 16));

        if Inverted then
           tmpValue:=-tmpValue;

        AddXYZ(x,tmpValue,y);
      end;

  {$IFDEF RANGEON}
  {$R+}
  {$ENDIF}
end;

procedure TCustom3DSeries.Load(const Graphic:TGraphic; Inverted:Boolean=False);
var tmp  : TTee3DRGBArray;
    tmpB : TBitmap;
begin
  BeginUpdate;
  Clear;

  if Graphic is TBitmap then
  begin
    TeeCalcLines(tmp,TBitmap(Graphic));
    tmpB:=nil;
  end
  else
  begin
    tmpB:=TBitmap.Create{$IFDEF FMX}(0,0){$ENDIF};
    tmpB.Assign(Graphic);
    TeeCalcLines(tmp,tmpB);
  end;

  Load(Graphic.Width,Graphic.Height,tmp,Inverted);

  tmp:=nil;
  tmpB.Free;

  EndUpdate;
end;

Procedure TCustom3DSeries.SetTimesZOrder(Const Value:Integer);
Begin
  SetIntegerProperty(FTimesZOrder,Value);
End;

Function TCustom3DSeries.MaxZValue:TChartValue;
begin
  result:=FZValues.MaxValue;
end;

Function TCustom3DSeries.MinZValue:TChartValue;
begin
  result:=FZValues.MinValue;
end;

Procedure TCustom3DSeries.Assign(Source:TPersistent);
begin
  if Source is TCustom3DSeries then
  With TCustom3DSeries(Source) do
  begin
    Self.FZValues.Assign(FZValues);
    Self.FTimesZOrder  :=FTimesZOrder;
  end;

  inherited;
end;

Procedure TCustom3DSeries.SetZValue(Index:Integer; Const Value:TChartValue);
Begin
  ZValues.Value[Index]:=Value;
End;

Function TCustom3DSeries.GetZValue(Index:Integer):TChartValue;
Begin
  result:=ZValues.Value[Index];
End;

function TCustom3DSeries.CalcZPos(ValueIndex: Integer): Integer;
begin
  result:=ParentChart.DepthAxis.CalcYPosValue(ZValues.Value[ValueIndex]);
end;

procedure TCustom3DSeries.PrepareLegendCanvas(ValueIndex: Integer;
  var BackColor: TColor; var BrushStyle: {$IFDEF FMX}TBrushKind{$ELSE}TBrushStyle{$ENDIF});
begin
  inherited;
  
  if TCustomChart(ParentChart).Legend.Symbol.Continuous then
     ParentChart.Canvas.Pen.Style:=psClear;
end;

function TCustom3DSeries.BackFaced: Boolean;
begin
  result:=ParentChart.DepthAxis.Inverted;

  with ParentChart.View3DOptions do
    if (Rotation>90) and (Rotation<270) then
       result:=not result;
end;

function TCustom3DSeries.AssociatedToAxis(const Axis: TChartAxis): Boolean;
begin
  result:=Axis.IsDepthAxis or inherited AssociatedToAxis(Axis);
end;

Function TCustom3DSeries.ValueListOfAxis(const Axis:TChartAxis):TChartValueList;
begin
  if Axis.IsDepthAxis then result:=ZValues
                      else result:=inherited ValueListOfAxis(Axis);
end;

{ TCustom3DPaletteSeries }

Constructor TCustom3DPaletteSeries.Create(AOwner: TComponent);
Begin
  inherited;
  FUseColorRange:=True;
  FPaletteSteps:=32;
  FLegendEvery:=1;
  FStartColor:={$IFDEF FMX}claNavy{$ELSE}clNavy{$ENDIF};
  FEndColor:={$IFDEF FMX}claWhite{$ELSE}clWhite{$ENDIF};
  FMidColor:=clNone;

  { Palette Modifiers }
  RedFactor:=2.0;
  GreenFactor:=1;
  BlueFactor:=1;
  CalcColorRange;
End;

Destructor TCustom3DPaletteSeries.Destroy;
begin
  ClearPalette;
  inherited;
end;

Procedure TCustom3DPaletteSeries.CalcColorRange;
Begin
  IEndRed    :=TRGBA(EndColor).Red;
  IEndGreen  :=TRGBA(EndColor).Green;
  IEndBlue   :=TRGBA(EndColor).Blue;
  IEndAlpha  :=TRGBA(EndColor).Alpha;

  if MidColor<>clNone then
  begin
    IMidRed  :=TRGBA(MidColor).Red;
    IMidGreen:=TRGBA(MidColor).Green;
    IMidBlue :=TRGBA(MidColor).Blue;
    IMidAlpha:=TRGBA(MidColor).Alpha;

    IRangeMidRed  :=TRGBA(StartColor).Red-IMidRed;
    IRangeMidGreen:=TRGBA(StartColor).Green-IMidGreen;
    IRangeMidBlue :=TRGBA(StartColor).Blue-IMidBlue;
    IRangeMidAlpha:=TRGBA(StartColor).Alpha-IMidAlpha;

    IRangeRed  :=IMidRed-IEndRed;
    IRangeGreen:=IMidGreen-IEndGreen;
    IRangeBlue :=IMidBlue-IEndBlue;
    IRangeAlpha:=IMidAlpha-IEndAlpha;
  end
  else
  begin
    IRangeRed  :=TRGBA(StartColor).Red-IEndRed;
    IRangeGreen:=TRGBA(StartColor).Green-IEndGreen;
    IRangeBlue :=TRGBA(StartColor).Blue-IEndBlue;
    IRangeAlpha:=TRGBA(StartColor).Alpha-IEndAlpha;
  end;

  CalcValueRange;
end;

Procedure TCustom3DPaletteSeries.SetRangeColor(var Variable:TColor; const Value:TColor);
Begin
  if Variable<>Value then
  begin
    Variable:=Value;
    CalcColorRange;
    ChangedColors;
    Repaint;
  end;
end;

Procedure TCustom3DPaletteSeries.SetStartColor(Const Value:TColor);
Begin
  SetRangeColor(FStartColor,Value);
End;

Procedure TCustom3DPaletteSeries.SetMidColor(Const Value:TColor);
Begin
  SetRangeColor(FMidColor,Value);
End;

Procedure TCustom3DPaletteSeries.SetEndColor(Const Value:TColor);
Begin
  SetRangeColor(FEndColor,Value);
End;

// Duplicate AddPalette methods to skip "TColorArray <> Array of TColor"
// compiler limitation.

// Use APalette array as a custom user-defined palette
procedure TCustom3DPaletteSeries.AddPalette(Const APalette:Array of TColor);
var t : Integer;
    l : Integer;
begin
  CreateDefaultPalette;

  l:=Length(APalette);
  for t:=0 to PaletteSteps-1 do
      Palette[t].Color:=APalette[t mod l];

  ICustomPalette:=True;

  Repaint;
end;

// Use APalette array as a custom user-defined palette
procedure TCustom3DPaletteSeries.AddPalette(Const APalette:TColorArray);
var t : Integer;
    l : Integer;
begin
  l:=Length(APalette);

  if l>0 then
  begin
    CreateDefaultPalette;

    for t:=0 to PaletteSteps-1 do
        Palette[t].Color:=APalette[t mod l];

    ICustomPalette:=True;

    Repaint;
  end;
end;

function TCustom3DPaletteSeries.DoAddPalette(Const AValue:TChartValue; AColor:TColor):Integer;
var t   : Integer;
    tt  : Integer;
begin
  IDirtyPalette:=False;
  
  // Try to search place to fit
  for t:=0 to High(FPalette) do
  begin
    if AValue<FPalette[t].UpToValue then
    begin
      // Insert
      SetLength(FPalette,Length(FPalette)+1);

      for tt:=High(FPalette) downto t+1 do
          FPalette[tt]:=FPalette[tt-1];

      With FPalette[t] do
      begin
        UpToValue:=AValue;
        Color:=AColor;
      end;

      result:=t;
      exit;
    end;
  end;

  // Append
  result:=Length(FPalette);
  SetLength(FPalette,result+1);

  with FPalette[result] do
  begin
    UpToValue:=AValue;
    Color:=AColor;
  end;
end;

// Add a new color to palette and flag it as custom user-defined palette
function TCustom3DPaletteSeries.AddPalette(Const AValue:TChartValue; AColor:TColor):Integer;
begin
  ICustomPalette:=True;
  result:=DoAddPalette(AValue,AColor);
end;

Procedure TCustom3DPaletteSeries.ClearPalette;
Begin
  FPalette:=nil;
end;

Procedure TCustom3DPaletteSeries.GetPaletteBounds(out AMin,ARange:Double);
begin
  AMin:=MandatoryValueList.MinValue;
  ARange:=MandatoryValueList.Range;
end;

// Create an automatic color palette (array of colors) based on series data
// "Y" values and palette-related properties
Procedure TCustom3DPaletteSeries.CreateDefaultPalette(NumSteps:Integer=0);
var tmpMin     : Double;
    ScaleValue : Double;

  procedure SetRainbowPalette;

    // Special case, internal Rainbow palette is in VCL format:
    {$IFDEF FMX}
    function InvertColor(const AColor:TColor):TColor;
    begin
      with TRGBA(result) do
      begin
        Red:=TRGBA(AColor).Blue;
        Green:=TRGBA(AColor).Green;
        Blue:=TRGBA(AColor).Red;

        // FMX opacity is inverted from VCL:
        if TRGBA(AColor).Alpha=0 then
           Alpha:=255;
      end;
    end;
    {$ENDIF}

  var t : Integer;
  begin
    if Assigned(ParentChart) then
    begin

      // Wish: Instead of using "Length(RainbowPalette)" here use: NumSteps.
      //
      // ie: Needs to calculate "middle" colors in between
      // if the NumSteps is bigger than 25.
      SetLength(ParentChart.ColorPalette,Length(RainbowPalette));
      for t:=0 to High(RainbowPalette) do
          ParentChart.ColorPalette[t]:={$IFDEF FMX}InvertColor{$ENDIF}(RainbowPalette[t]);

      // Default:
      for t:=0 to NumSteps-1 do
          DoAddPalette(tmpMin+ScaleValue*t,ParentChart.GetDefaultColor(t));

      ParentChart.ColorPalette:=nil; // should be OLD palette !!
    end;
  end;

  function ColorOfInterval(const APercent:Double):TColor;
  const
    Delta=127.0;
  begin
    case PaletteStyle of
      psGrayScale: begin
                     result:=Round(APercent);
                     result:=RGB(result,result,result);
                   end;

      psInvGray:   begin
                     result:=255-Round(APercent);
                     result:=RGB(result,result,result);
                   end;

    else
      result:=RGB( Trunc(Delta * (Sin(APercent/RedFactor)+1)) ,
                   Trunc(Delta * (Sin(APercent/GreenFactor)+1)),
                   Trunc(Delta * (Cos(APercent/BlueFactor)+1)));
    end;
  end;

var t          : Integer;
    Scale      : Double;
    tmpL       : Integer;
    tmpYMin    : Double;
    tmpRange   : Double;
Begin
  ICustomPalette:=False;

  if NumSteps=0 then
     NumSteps:=PaletteSteps;

  GetPaletteBounds(tmpYMin,tmpRange);

  if PaletteStep=0 then
  begin
    if PaletteRange=0 then
       ScaleValue:=tmpRange/Math.Max(1,NumSteps-1)
    else
       ScaleValue:=PaletteRange/NumSteps;
  end
  else ScaleValue:=PaletteStep;

  if UsePaletteMin then tmpMin:=PaletteMin
                   else tmpMin:=tmpYMin;

  if PaletteStyle=psCustom then
  begin
    // Expand or contract current custom palette with new UpToValues
    tmpL:=Length(FPalette);
    SetLength(FPalette,NumSteps);

    for t:=0 to NumSteps-1 do
    with Palette[t] do
    begin
      UpToValue:=tmpMin+ScaleValue*t;

      if (t>=tmpL) and (tmpL>0) then
         Color:=Palette[t mod tmpL].Color;
    end;
  end
  else
  begin
    ClearPalette;

    if PaletteStyle=psRainbow then
       SetRainbowPalette
    else
    begin
      case PaletteStyle of
          psPale: Scale:=Pi/NumSteps;
        psStrong: Scale:=2.0*Pi/NumSteps;
      else
        Scale:=255.0/NumSteps;
      end;

      for t:=0 to NumSteps-1 do
          DoAddPalette(tmpMin+ScaleValue*t,ColorOfInterval(Scale*t));
    end;
  end;

  ChangedColors;

  IDirtyPalette:=False;

  Repaint;
end;

Procedure TCustom3DPaletteSeries.SetUseColorRange(Const Value:Boolean);
Begin
  SetBooleanProperty(FUseColorRange,Value);
  //if Value then ColorEachPoint:=False;
  ChangedColors;
End;

Procedure TCustom3DPaletteSeries.SetUsePalette(Const Value:Boolean);
Begin
  SetBooleanProperty(FUsePalette,Value);

  if Value then
  begin
    //ColorEachPoint:=False;
    CheckPaletteEmpty;
  end;

  ChangedColors;
end;

{$DEFINE TEEBINSEARCH} // 30% faster

Function TCustom3DPaletteSeries.GetSurfacePaletteColor(Const Y:TChartValue):TColor;
var {$IFDEF TEEBINSEARCH}
    imin,
    imax,
    imid : Integer;
    {$ELSE}
    t    : Integer;
    tmpCount : Integer;
    {$ENDIF}
Begin
  {$IFDEF TEEBINSEARCH}
  imax:=High(FPalette);
  imin:=0;

  while imin < imax do
  begin
    imid:= (imin+imax) div 2;

    if FPalette[imid].UpToValue<Y then
       imin:=Succ(imid)
    else
       imax:=imid;
  end;

  if imax = imin then
     result:=FPalette[imin].Color
  else
     result:=FPalette[High(FPalette)].Color; { return max }

  {$ELSE}

  tmpCount:=High(FPalette);

  for t:=0 to tmpCount do
  With FPalette[t] do
  if UpToValue>Y then
  begin
    result:=Color;
    exit;
  end;

  result:=FPalette[tmpCount].Color; { return max }
  {$ENDIF}
end;

// Note: The Round function is very slow.
Function TCustom3DPaletteSeries.RangePercent(const Percent:Double):TColor;
var tmp : Single;
begin
  if MidColor=clNone then
     result:=RGBA( IEndRed  +Round(Percent*IRangeRed),
                   IEndGreen+Round(Percent*IRangeGreen),
                   IEndBlue +Round(Percent*IRangeBlue),
                   IEndAlpha+Round(Percent*IRangeAlpha))
  else
  if Percent<0.5 then
  begin
    tmp:=2.0*Percent;

    result:=RGBA( IEndRed  +Round(tmp*IRangeRed),
                  IEndGreen+Round(tmp*IRangeGreen),
                  IEndBlue +Round(tmp*IRangeBlue),
                  IEndAlpha+Round(Percent*IRangeAlpha))
  end
  else
  begin
    tmp:=2.0*(Percent-0.5);

    result:=RGBA( IMidRed  +Round(tmp*IRangeMidRed),
                  IMidGreen+Round(tmp*IRangeMidGreen),
                  IMidBlue +Round(tmp*IRangeMidBlue),
                  IMidAlpha+Round(tmp*IRangeMidAlpha))
  end;
end;

Function TCustom3DPaletteSeries.GetValueColorValue(Const AValue:TChartValue):TColor;
var tmp : Double;
begin
  if UseColorRange then
  begin
    if IValueRangeInv=0 then
       result:=EndColor
    else
    begin
      tmp:=AValue-MandatoryValueList.MinValue;

      if tmp<0 then result:=EndColor
      else
      if AValue>MandatoryValueList.MaxValue then
         result:=StartColor
      else
      begin
        tmp:=tmp*IValueRangeInv;
        result:=RangePercent(Min(1.0,tmp));
      end;
    end;
  end
  else
  if UsePalette and (Length(FPalette)>0) then
     result:=GetSurfacePaletteColor(AValue)
  else
     result:=SeriesColor;
end;

Function TCustom3DPaletteSeries.GetValueColor(ValueIndex:Integer):TColor;
Begin
  result:=InternalColor(ValueIndex);

  if result=clTeeColor then
  begin
    if FUseColorRange or FUsePalette then
       result:=GetValueColorValue(MandatoryValueList.Value[ValueIndex])
    else
       result:=inherited GetValueColor(ValueIndex);
  end;

  if Assigned(FOnGetColor) then
     FOnGetColor(Self,ValueIndex,result);
End;

Function TCustom3DPaletteSeries.CountLegendItems:Integer;
begin
  if (Count>0) and ((not DefaultLegend) and (UseColorRange or UsePalette)) then
  begin
    result:=Length(FPalette);

    if FLegendEvery>1 then
       result:=(result div FLegendEvery)+1;
  end
  else
     result:=inherited CountLegendItems;
end;

function TCustom3DPaletteSeries.CalcLegendString(AStyle:TLegendTextStyle; const AText:String;
                            const AValue:Double; AIndex:Integer):String;

  function IndexString:String;
  begin
    result:=IntToStr(AIndex);
  end;

  function ValueString:String;
  begin
    result:=FormatFloat(ValueFormat,AValue);
  end;

begin
  case AStyle of
          ltsPlain : result:=AText;
     ltsLeftValue,
    ltsLeftPercent : result:=ValueString+TeeColumnSeparator+AText;
    ltsRightValue,
   ltsRightPercent : result:=AText+TeeColumnSeparator+ValueString;
         ltsXValue : result:=IndexString;
     ltsXAndValue,
    ltsXAndPercent : result:=IndexString+TeeColumnSeparator+ValueString;
       ltsXAndText : result:=IndexString+TeeColumnSeparator+AText;
  else
    result:=ValueString;
  end;
end;

Function TCustom3DPaletteSeries.LegendString( LegendIndex:Integer;
                                              LegendTextStyle:TLegendTextStyle
                                              ):String;
var tmpItem : TGridPalette;
    tmpIndex : Integer;
begin
  if (UseColorRange or UsePalette) and (not DefaultLegend) then
  begin
    if CountLegendItems>LegendIndex then
    begin
      tmpIndex:=CalcLegendIndex(LegendIndex);

      if tmpIndex>=0 then
      begin
        tmpItem:=FPalette[tmpIndex];
        result:=CalcLegendString(LegendTextStyle,tmpItem.Text,tmpItem.UpToValue,tmpIndex);
      end;
    end
    else
       result:='';
  end
  else
    result:=inherited LegendString(LegendIndex,LegendTextStyle);
end;

Function TCustom3DPaletteSeries.CalcLegendIndex(LegendIndex:Integer):Integer;
begin
  if UseColorRange or UsePalette then
     result:=High(FPalette)-(FLegendEvery*LegendIndex)
  else
     result:=inherited CalcLegendIndex(LegendIndex);
end;

Function TCustom3DPaletteSeries.LegendItemColor(LegendIndex:Integer):TColor;
begin
  if DefaultLegend then
     result:=inherited LegendItemColor(LegendIndex)
  else
  if UseColorRange then
     result:=GetValueColorValue(FPalette[LegendIndex].UpToValue)
  else
  if UsePalette and (Length(FPalette)>LegendIndex) then
     result:=FPalette[LegendIndex].Color
  else
     result:=inherited LegendItemColor(LegendIndex);
end;

procedure TCustom3DPaletteSeries.LoadPaletteFromFile(const FileName:String);
var st : TStrings;
    i,
    t  : Integer;
    s  : String;
begin
  st:=TStringList.Create;
  try
    st.LoadFromFile(FileName);

    ClearPalette;

    for t:=0 to st.Count-1 do
    begin
      s:=Trim(st[t]);

      if Copy(s,1,2)<>'//' then
      begin
        i:=Pos(' ',s);

        if i>0 then
           AddPalette(StrToFloat(Trim(Copy(s,i+1,Length(s)))),
                      StrToInt(Trim(Copy(s,1,i-1))));
      end;
    end;
  finally
    st.Free;
  end;
end;

procedure TCustom3DPaletteSeries.SavePaletteToFile(const FileName:String);
var st : TStrings;
    t  : Integer;
begin
  st:=TStringList.Create;
  try
    st.Add('// '+TeeMsg_Color+' '+TeeMsg_UpToValue);

    for t:=0 to High(Palette) do
        st.Add(IntToStr(Palette[t].Color)+' '+FormatFloat(ValueFormat, Palette[t].UpToValue));

    st.SaveToFile(FileName);
  finally
    st.Free;
  end;
end;

Procedure TCustom3DPaletteSeries.SetPaletteSteps(Const Value:Integer);
Begin
  FPaletteSteps:=Value;
  CreateDefaultPalette;
End;

Procedure TCustom3DPaletteSeries.SetGalleryPalette;
begin
  if Assigned(ParentChart) and
     Assigned(ParentChart.ColorPalette) then
  begin
    PaletteStyle:=psCustom;
    AddPalette(ParentChart.ColorPalette);
  end
  else
     PaletteStyle:=psRainbow;
end;

Procedure TCustom3DPaletteSeries.PrepareForGallery;
begin
  inherited;

  UseColorRange:=False;
  Pen.Color:={$IFDEF FMX}claBlack{$ELSE}clBlack{$ENDIF};
  UsePalette:=True;
  SetGalleryPalette;
end;

Procedure TCustom3DPaletteSeries.AssignFormatting(Source:TChartSeries);
begin
  if Source is TCustom3DPaletteSeries then
  With TCustom3DPaletteSeries(Source) do
  begin
    Self.FUsePalette   :=FUsePalette;
    Self.FUseColorRange:=FUseColorRange;
    Self.FStartColor   :=FStartColor;
    Self.FEndColor     :=FEndColor;
    Self.FMidColor     :=FMidColor;
    Self.FLegendEvery  :=FLegendEvery;
    Self.FPaletteSteps :=FPaletteSteps;
    Self.FPaletteStyle :=FPaletteStyle;
  end;

  inherited;
end;

Procedure TCustom3DPaletteSeries.Assign(Source:TPersistent);
begin
  if Source is TCustom3DPaletteSeries then
  With TCustom3DPaletteSeries(Source) do
  begin
    Self.FPalette      :=FPalette;
    Self.FUsePaletteMin:=FUsePaletteMin;
    Self.FPaletteStep  :=FPaletteStep;
    Self.FPaletteMin   :=FPaletteMin;
  end;

  inherited;
end;

procedure TCustom3DPaletteSeries.ChangedColors;
begin
end;

// When data has changed, set IDiryPalette to True to indicate the automatic
// color palette should be re-created.
// The color palette is automatic when ICustomPalette is False.
Procedure TCustom3DPaletteSeries.NotifyValue(ValueEvent:TValueEvent; ValueIndex:Integer);
begin
  inherited;

  if (ValueEvent<>veRefresh) or ((LinkedSeries<>nil) and (LinkedSeries.Count>0)) then
     IDirtyPalette:=True;
end;

// Default palette should be re-created when: series has data, and
// when palette is empty or "dirty" and not custom.
Procedure TCustom3DPaletteSeries.CheckPaletteEmpty;
begin
  if (Count>0) and ( (Length(FPalette)=0) or (IDirtyPalette and (not ICustomPalette))) then
     CreateDefaultPalette;
end;

{ internal }
procedure TCustom3DPaletteSeries.CalcValueRange;
begin
  IValueRangeInv:=MandatoryValueList.Range;
  if IValueRangeInv<>0 then IValueRangeInv:=1/IValueRangeInv;
end;

procedure TCustom3DPaletteSeries.ReadPalette(Stream: TStream);
var t    : Integer;
    tmpL : Integer;
begin
  Stream.Read(tmpL,SizeOf(tmpL));
  SetLength(FPalette,tmpL);

  for t:=0 to High(FPalette) do
  with FPalette[t] do
  begin
    Stream.Read(UpToValue,SizeOf(UpToValue));
    Stream.Read(Color,SizeOf(Color));
  end;
end;

procedure TCustom3DPaletteSeries.WritePalette(Stream: TStream);
var t    : Integer;
    tmpL : Integer;
begin
  tmpL:=Length(FPalette);
  Stream.Write(tmpL,SizeOf(tmpL));

  for t:=0 to tmpL-1 do
  with FPalette[t] do
  begin
    Stream.Write(UpToValue,SizeOf(UpToValue));
    Stream.Write(Color,SizeOf(Color));
  end;
end;

Procedure TCustom3DPaletteSeries.DefineProperties(Filer:TFiler);
begin
  inherited;
  Filer.DefineBinaryProperty('CustomPalette',ReadPalette,WritePalette,PaletteStyle=psCustom);
end;

Procedure TCustom3DPaletteSeries.DoBeforeDrawChart;
begin
  inherited;
  CheckPaletteEmpty;
  CalcValueRange;
end;

Procedure TCustom3DPaletteSeries.DrawLegendShape(ValueIndex:Integer;
                                                 Const Rect:TRect);
var R : TRect;
begin
  if (ValueIndex=-1) and UseColorRange then
  begin
    ParentChart.Canvas.Brush.Style:=bsClear;
    ParentChart.Canvas.Rectangle(Rect);  { <-- rectangle }

    with TTeeGradient.Create(nil) do
    try
      // set inverted colors
      StartColor:=Self.EndColor;
      MidColor:=Self.MidColor;
      EndColor:=Self.StartColor;

      // draw
      R:=Rect;

      {$IFDEF FMX}
      R.Inflate(-1,-1);
      {$ELSE}
      InflateRect(R,-1,-1);
      {$ENDIF}
      
      Draw(ParentChart.Canvas,R);
    finally
      Free;
    end;
  end
  else inherited;
end;

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

Procedure TCustom3DPaletteSeries.Clear;
begin
  inherited;

  if FUsePalette then
     if PaletteStyle<>psCustom then
        ClearPalette;
end;

Procedure TCustom3DPaletteSeries.AddValues(Source:TChartSeries);
Begin
  inherited;
  CreateDefaultPalette;
end;

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

class procedure TCustom3DPaletteSeries.SetSubGallery(ASeries: TChartSeries;
  Index: Integer);
begin
  With TCustom3DPaletteSeries(ASeries) do
  Case Index of
    1: begin
         UseColorRange:=True;
         UsePalette:=False;
       end;
  else
    inherited;
  end;
end;

procedure TCustom3DPaletteSeries.CreateRangePalette;
var tmp     : Double;
    tmpMin  : Double;
    Delta   : Double;
    t       : Integer;
begin
  GetPaletteBounds(tmpMin,Delta);

  ClearPalette;

  tmp:=Delta/PaletteSteps;

  for t:=1 to PaletteSteps do
      if Delta>0 then
         DoAddPalette(tmpMin+tmp*t,RangePercent(tmp*t/Delta))
      else
         DoAddPalette(tmpMin,RGB(IEndRed,IEndGreen,IEndBlue));
end;

Procedure TCustom3DPaletteSeries.GalleryChanged3D(Is3D:Boolean);
Const Rots:Array[Boolean] of Integer=(0,335);
      Elev:Array[Boolean] of Integer=(270,340);
      Pers:Array[Boolean] of Integer=(0,160);
begin
  if HasZValues then
  begin
    ParentChart.View3D:=True;

    With ParentChart.View3DOptions do
    begin
      Zoom:=60;
      VertOffset:=-2;
      Rotation:=Rots[Is3D];
      Elevation:=Elev[Is3D];
      Perspective:=Pers[Is3D];
    end;
  end;

  SetGalleryPalette;
end;

procedure TCustom3DPaletteSeries.SetPaletteStyle(const Value: TTeePaletteStyle);
begin
  if FPaletteStyle<>Value then
  begin
    FPaletteStyle:=Value;

    if PaletteStyle<>psCustom then
       ClearPalette;

    ChangedColors;
    Repaint;
  end;
end;

procedure TCustom3DPaletteSeries.NotifyColorChange(ValueIndex:Integer);
begin
  inherited;
  ChangedColors;
end;

procedure TCustom3DPaletteSeries.SetPaletteMin(const Value: Double);
begin
  FPaletteMin:=Value;
  CreateDefaultPalette;
end;

procedure TCustom3DPaletteSeries.SetPaletteStep(const Value: Double);
begin
  FPaletteStep:=Value;
  CreateDefaultPalette;
end;

procedure TCustom3DPaletteSeries.SetUsePaletteMin(const Value: Boolean);
begin
  FUsePaletteMin:=Value;
  CreateDefaultPalette;
end;

procedure TCustom3DPaletteSeries.SetLegendEvery(const Value: Integer);
begin
  SetIntegerProperty(FLegendEvery,Max(1,Value));
end;

procedure TCustom3DPaletteSeries.GeneratePalette;
var t : Integer;
begin
  for t:=0 to High(Palette) do
      Palette[t].Color:=GetValueColorValue(Palette[t].UpToValue);
end;

procedure TCustom3DPaletteSeries.InvertPalette;
var t   : Integer;
    tmpPos : Integer;
    tmp : TColor;
begin
  // Invert palette
  for t:=0 to (Length(Palette) div 2)-1 do
  begin
    tmpPos:=High(Palette)-t;

    // Swap colors
    tmp:=Palette[t].Color;
    Palette[t].Color:=Palette[tmpPos].Color;
    Palette[tmpPos].Color:=tmp;
  end;

  Repaint;
end;

procedure TCustom3DPaletteSeries.SetPalette(const Value: TCustom3DPalette);
var tmp : Integer;
    t   : Integer;
begin
  tmp:=Length(Value);
  SetLength(FPalette,tmp);

  for t:=0 to tmp-1 do
      FPalette[t]:=Value[t];
end;

{ TCustom3DGridSeries }
Constructor TCustom3DGridSeries.Create(AOwner: TComponent);
Begin
  inherited;

  FReuseGrid:=True;
  IDirtyGrid:=True;

  FNumXValues:=10;
  FNumZValues:=10;

  Clear;
End;

Procedure TCustom3DGridSeries.Clear;
begin
  inherited;
  XValues.Order:=loNone;
end;

Procedure TCustom3DGridSeries.FillGridIndex;
begin
  FillGridIndex(0);
end;

Procedure TCustom3DGridSeries.NotifyValue(ValueEvent:TValueEvent; ValueIndex:Integer);
begin
  inherited;
  IDirtyGrid:=True;
end;

// StartIndex means the first point to start
Procedure TCustom3DGridSeries.FillGridIndex(StartIndex:Integer);
Var XCount : Integer;
    ZCount : Integer;

  Procedure InitGridIndex;
  var x : Integer;
      z : Integer;
  begin
    SetLength(GridIndex,XCount+1,ZCount+1);

    for x:=0 to XCount do
        for z:=0 to ZCount do
            GridIndex[x,z]:=-1;
  end;

  Procedure FillIrregularGrid;
  type
    TIrregValues=TChartValues;

    // Adds unique values of "AValue" to "Values" array.
    // Repeated values aren't allowed.
    Procedure SearchValue(Var ACount:Integer; var Values:TIrregValues; Const AValue:TChartValue);
    var t : Integer;
    begin
       for t:=0 to ACount-1 do
           if Values[t]=AValue then Exit;

      Values[ACount]:=AValue;
      Inc(ACount);
    end;

    Procedure SortValues(ACount:Integer; Var Values:TIrregValues);
    var t        : Integer;
        tt       : Integer;
        tmpMin   : TChartValue;
        tmpIndex : Integer;
    begin
      for t:=1 to ACount-2 do {min already at 0}
      begin
        tmpMin:=Values[t];
        tmpIndex:=t;

        for tt:=t+1 to ACount-1 do  {get minimum }
        begin
          if Values[tt]<tmpMin then
          begin
            tmpMin:=Values[tt];
            tmpIndex:=tt;
          end;
        end;

        if tmpIndex<>t then
        begin
          Values[tmpIndex]:=Values[t];
          Values[t]:=tmpMin;
        end;
      end;
    end;

    function SearchSorted(const Value:TChartValue; const ACount:Integer; Var Values:TIrregValues): Integer;
    var L : Integer;
        H : Integer;
    begin
      L:=0;
      H:=Pred(ACount);

      while L <= H do
      begin
        result:=(L + H) shr 1;

        if Values[result]<Value then
           L:=Succ(result)
        else
        begin
          if Values[result]=Value then
             Exit;

          H:=Pred(result);
        end;
    end;

      result:=-1;
    end;

  var
    XVals : TIrregValues;
    ZVals : TIrregValues;

    (* OBSOLETE

    // Adds unique values of "AValue" to "Values" array.
    // Repeated values aren't allowed.
    Procedure SearchXValue(const Value:TChartValue);
    begin
      if SearchSorted(Value,XCount,XVals)=-1 then
      begin
        XVals[XCount]:=Value;
        Inc(XCount);
      end;
    end;

    // Adds unique values of "AValue" to "Values" array.
    // Repeated values aren't allowed.
    Procedure SearchZValue(const Value:TChartValue);
    begin
      if SearchSorted(Value,ZCount,ZVals)=-1 then
      begin
        ZVals[ZCount]:=Value;
        Inc(ZCount);
      end;
    end;
    *)

    procedure SetGridIndex(const Index:Integer);
    var tmpX : Integer;
        tmpZ : Integer;
    begin
      tmpX:=SearchSorted(XValues.Value[Index],XCount,XVals);

      if tmpX<>-1 then
      begin
        tmpZ:=SearchSorted(ZValues.Value[Index],ZCount,ZVals);

        if tmpZ<>-1 then
           GridIndex[Succ(tmpX),Succ(tmpZ)]:=Index;
      end;
    end;

  const
    MaxAllowedCells = 20000;

  var t : Integer;
  begin
    SetLength(XVals,MaxAllowedCells);
    SetLength(ZVals,MaxAllowedCells);

    try
      XCount:=1;
      XVals[0]:=XValues.MinValue;

      ZCount:=1;
      ZVals[0]:=ZValues.MinValue;

      // Adds all unique X and Z values to XVals and ZVals arrays
      // XCount and ZCount are updated to maximum number of non-repeated values
      for t:=StartIndex to Count-1 do
      begin
        SearchValue(XCount,XVals,XValues.Value[t]);
        SearchValue(ZCount,ZVals,ZValues.Value[t]);
      end;

      // Sort X and Z array values in ascending order
      SortValues(XCount,XVals);
      SortValues(ZCount,ZVals);

      InitGridIndex;

      // Use sorted XVals and ZVals to index Mandatory ValueList in grid
      for t:=StartIndex to Count-1 do
          SetGridIndex(t);

    finally
      XVals:=nil;
      ZVals:=nil;
    end;
  end;

  Procedure FillRegularGrid;
  var t : Integer;
      tmpMinX,
      tmpMinZ : Integer;
  begin

    if XValues.MinValue <> 0 Then
       FXStartIndex:=1
    else
       FXStartIndex:=0;

    if ZValues.MinValue <> 0 Then
       FZStartIndex:=1
    else
       FZStartIndex:=0;

    tmpMinX:=Round(XValues.MinValue)-1;
    tmpMinZ:=Round(ZValues.MinValue)-1;

    XCount:=Round(XValues.MaxValue)-tmpMinX;
    ZCount:=Round(ZValues.MaxValue)-tmpMinZ;

    InitGridIndex;

    for t:=StartIndex to Count-1 do
        GridIndex[ Round(XValues.Value[t])-tmpMinX,
                   Round(ZValues.Value[t])-tmpMinZ]:=t;
  end;

begin
  if Count=0 then
  begin
    XCount:=NumXValues;
    ZCount:=NumZValues;

    InitGridIndex;
  end
  else
  begin
    if FIrregularGrid then FillIrregularGrid
                      else FillRegularGrid;

    if XCount<>NumXValues then FNumXValues:=XCount;
    if ZCount<>NumZValues then FNumZValues:=ZCount;
  end;

  IDirtyGrid:=False;
end;

Function TCustom3DGridSeries.GetXZValue(X,Z:Integer):TChartValue;
{$IFDEF TEEOCX}
var tmpResult : Double;
{$ENDIF}

const Factor=1;
var x2,z2,xz,xx,zz : Double;
Begin
  {$IFNDEF TEEOCX}  //ocx event always assigned
  if Assigned(FOnGetYValue) then
     result:=FOnGetYValue(Self,X,Z)
  else
  {$ENDIF}
  // Default sample random surface value formula
  if (csDesigning in ComponentState) or
     (IInGallery) then

     case ISampleFormula of
       0: result:=0.5*Sqr(Cos(x/(NumXValues*0.2)))+
                      Sqr(Cos(z/(NumZValues*0.2)))-
                          Cos(z/(NumZValues*0.5));

       1 : // Hat Formula:
           begin
             x2:=x*x*0.1;
             z2:=z*z*0.1;
             xz:=Sqrt(x2+z2);

             if xz=0 then result:=0
                     else result:=Sin(xz)/xz;
           end;

      else // Complex formula:
           begin
             x2:=x*0.01;
             z2:=z*0.01;

             xx:=x2*x2;
             zz:=z2*z2;

             result:= 3*Sqr(1-x2)*Exp(-xx - Sqr(z2+1))
                 - Factor * (x2*0.2 - Power(x2,3) - Power(z2,5))*exp(-xx-zz)
                 - (1/3) * exp(-Sqr(x2+1) - zz);
       end;
     end
  else
     result:=0;

  {$IFDEF TEEOCX}  //ocx event always assigned return NaN when not in use
  if Assigned(FOnGetYValue) then
  Begin
    tmpResult:=FOnGetYValue(Self,X,Z);

    if not IsNan(tmpResult) then
       result := tmpResult;
  end;
  {$ENDIF}
end;

Function TCustom3DGridSeries.NumSampleValues:Integer;
begin
  result:=NumXValues;
end;

Procedure TCustom3DGridSeries.ReCreateValues;
Begin
  CreateValues(NumXValues,NumZValues);
end;

procedure TCustom3dGridSeries.Load(NumX,NumY:Integer; const RGBs:TTee3DRGBArray; Inverted:Boolean=False);
var x,y : Integer;
    tmpValue : Cardinal;
begin
  NumXValues:=NumX;
  NumZValues:=NumY;
  FillGridIndex;

  {$IFOPT R+}
  {$DEFINE RANGEON}
  {$ENDIF}

  {$R-}
  for x:=0 to NumX-1 do
      for y:=0 to NumY-1 do
      begin
        with RGBs[y,x] do
             tmpValue:=(Red or (Green shl 8) or (Blue shl 16));

        if Inverted then
           Value[x,y]:=-tmpValue
        else
           Value[x,y]:=tmpValue;
      end;

  {$IFDEF RANGEON}
  {$R+}
  {$ENDIF}
end;

Procedure TCustom3DGridSeries.SetNumXValues(Value:Integer);
Begin
  if Value<>NumXValues then
  begin
    FNumXValues:=Value;
    Clear;
    ReCreateValues;
  end;
End;

Procedure TCustom3DGridSeries.SetNumZValues(Value:Integer);
Begin
  if Value<>NumZValues then
  begin
    FNumZValues:=Value;
    Clear;
    ReCreateValues;
  end;
End;

Procedure TCustom3DGridSeries.AddValues(Source:TChartSeries);
Begin
  if Source is TCustom3DGridSeries then
  With TCustom3DGridSeries(Source) do
  begin
    Self.FNumXValues:=NumXValues;
    Self.FNumZValues:=NumZValues;
  end;

  inherited;

//  FillGridIndex;
  Repaint;
end;

Procedure TCustom3DGridSeries.Assign(Source:TPersistent);
begin
  if Source is TCustom3DGridSeries then
  With TCustom3DGridSeries(Source) do
  begin
    Self.FNumXValues   :=NumXValues;
    Self.FNumZValues   :=NumZValues;
    Self.FIrregularGrid:=FIrregularGrid;

    Self.IDirtyGrid    :=True;
  end;

  inherited;
end;

Procedure TCustom3DGridSeries.SetIrregularGrid(Const Value:Boolean);
begin
  SetBooleanProperty(FIrregularGrid,Value);
end;

Function TCustom3DGridSeries.CanCreateValues:Boolean;
begin
  if INoRandom then
     result:=False
  else
     result:= Assigned(FOnGetYValue) or (csDesigning in ComponentState)
              or IInGallery;
end;

Procedure TCustom3DGridSeries.CreateValues(NumX,NumZ:Integer);
var x           : Integer;
    z           : Integer;
    OldCapacity : Integer;
Begin
  if CanCreateValues then
  begin
    FNumXValues:=NumX;
    FNumZValues:=NumZ;

    OldCapacity:=TeeDefaultCapacity;
    TeeDefaultCapacity:=NumX*NumZ;
    try
      Clear;
      BeginUpdate;

      for z:=1 to NumZ do
          for x:=1 to NumX do AddXYZ(X,GetXZValue(X,Z),Z);

      EndUpdate;
    finally
      TeeDefaultCapacity:=OldCapacity;
    end;

    CreateDefaultPalette;
  end;
End;

Procedure TCustom3DGridSeries.AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False);
var OldGallery : Boolean;
Begin
  if NumValues>0 then
  begin
    OldGallery:=IInGallery;
    IInGallery:=True;
    try
      CreateValues(NumValues,NumValues);
    finally
      IInGallery:=OldGallery;
    end;
  end;
End;

Procedure TCustom3DGridSeries.DoBeforeDrawChart;
begin
  inherited;

  if IDirtyGrid or (not ReuseGridIndex) then
     FillGridIndex;
end;

Function TCustom3DGridSeries.ExistFourGridIndex(X,Z:Integer):Boolean;
begin
  ValueIndex0:=GridIndex[x,z];
  if ValueIndex0>-1 then
  begin
    ValueIndex1:=GridIndex[x+INextXCell,z];
    if ValueIndex1>-1 then
    begin
      ValueIndex2:=GridIndex[x+INextXCell,z+INextZCell];
      if ValueIndex2>-1 then
      begin
        ValueIndex3:=GridIndex[x,z+INextZCell];
        result:=ValueIndex3>-1;
        exit;
      end;
    end;
  end;

  result:=False;
end;

function TCustom3DGridSeries.IsValidSeriesSource(Value:TChartSeries):Boolean;
begin
  result:=Value is TCustom3DGridSeries;
end;

function TCustom3DGridSeries.GetValue(X, Z: Integer): TChartValue;
var tmp : Integer;
begin
  if IDirtyGrid then
     FillGridIndex;

  tmp:=GridIndex[x,z];

  if tmp<>-1 then result:=YValues.Value[tmp]
             else Raise Exception.CreateFmt('No value found at XZ: %d %d',[x,z]);
end;

procedure TCustom3DGridSeries.SetValue(X, Z: Integer;
  const Value: TChartValue);
var tmp : Integer;
begin
  if IDirtyGrid then
     AddXYZ(x,Value,z)
  else
  begin
    tmp:=GridIndex[x,z];

    if tmp<>-1 then YValues.Value[tmp]:=Value
               else GridIndex[x,z]:=AddXYZ(x,Value,z);
  end;
end;

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

  INextXCell:=-1;
  INextZCell:=-1;

  FSideBrush:=TTeeBrush.Create(CanvasChanged);
  FSideBrush.Style:=bsClear;
  FSideLines:=TChartHiddenPen.Create(CanvasChanged);

  FWaterLines:=CreateChartPen;

  FHideCells:=True;

  // Default Hover for Surface is False (speed reason)
  Selected.Hover.Hide;
  Selected.Hover.ShapeStyle:=fosEllipse;
end;

Destructor TSurfaceSeries.Destroy;
begin
  FSideLines.Free;
  FSideBrush.Free;
  FWaterLines.Free;
  FreeCached;
  inherited;
end;

class Function TSurfaceSeries.GetEditorClass:String;
begin
  result:='TSurfaceSeriesEditor'; { <-- dont translate ! }
end;

Procedure TSurfaceSeries.PrepareForGallery;
begin
  inherited;
  IInGallery:=True;
  CreateValues(10,10);
end;

Function TSurfaceSeries.CalcPointPos(const Index:Integer):TPoint;
begin
  result.x:=ICalcX(IXValue[Index]);
  result.y:=ICalcY(IYValue[Index]);
end;

function TSurfaceSeries.FourGridIndex(x,z:Integer):Boolean;
begin
  result:=ExistFourGridIndex(x,z);

  if result then
  begin
    Points[0]:=CalcPointPos(ValueIndex0);
    Points[1]:=CalcPointPos(ValueIndex1);
    Points[2]:=CalcPointPos(ValueIndex2);
    Points[3]:=CalcPointPos(ValueIndex3);
  end;
end;

function TSurfaceSeries.CellsOrientation:TCellsOrientation;
var tmp : Integer;
begin
  if FWaterFall then tmp:=0 else tmp:=1;

  with result do
  begin
    if BackFaced then
    begin
      INextZCell:=1;
      InitZ:=1;
      EndZ:=NumZValues-tmp+1;
      IncZ:=1;
    end
    else
    begin
      INextZCell:=-1;
      InitZ:=NumZValues;
      EndZ:=1+tmp-1;
      IncZ:=-1;
    end;

    if DrawValuesForward then
    begin
      INextXCell:=-1;
      InitX:=2;
      EndX:=NumXValues+1;
      IncX:=1;
    end
    else
    begin
      INextXCell:=1;
      InitX:=NumXValues-1;
      EndX:=0;
      IncX:=-1;
    end;
  end;
end;

// Returns Index of Series XYZ cell corner under the XY screen pixel point
function TSurfaceSeries.Clicked(x,y:Integer):Integer;
var t : Integer;
    P : TPoint;
begin
  if (not IDirtyGrid) and (Count>0) then
    for t:=0 to Count-1 do
    begin
      P:=ParentChart.Canvas.Calculate3DPosition(CalcPointPos(t),CalcZPos(t));

      if (Abs(P.X-x)<4) and (Abs(P.Y-y)<4) then
      begin
        result:=t;
        Exit;
      end;
    end;

  result:=TeeNoPointClicked;
end;

// Returns Index of right-bottom cell corner of the Surface Cell rectangle
// that contains the XY screen pixel point
function TSurfaceSeries.ClickedCell(x,y:Integer):Integer;
var tmpX : Integer;
    tmpZ : Integer;
    tmpPoint : TPoint;
    tmpPoints : TFourPoints;
    tmpO      : TCellsOrientation;
begin
  if (not IDirtyGrid) and (Count>0) then
  begin
    INextXCell:=-1;
    INextZCell:=-1;
    tmpPoint:=TeePoint(x,y);

    PrepareCalcPos;

    tmpO:=CellsOrientation;

    with tmpO do
    begin
      INextZCell:=-INextZCell;
      IncZ:=-IncZ;
      tmpZ:=EndZ;

      while tmpZ<>InitZ do
      begin
        tmpX:=InitX;

        while tmpX<>EndX do
        begin
          if FourGridIndex(tmpX,tmpZ) then
          begin
            PointsTo2D(CalcZPos(ValueIndex0),CalcZPos(ValueIndex2),tmpPoints);

            if PointInPolygon(tmpPoint,tmpPoints) then
            begin
              result:=ValueIndex0;
              Exit;
            end;
          end;

          Inc(tmpX,IncX);
        end;

        Inc(tmpZ,IncZ); // front to back
      end;
    end;
  end;

  result:=TeeNoPointClicked;
end;

procedure TSurfaceSeries.SetSideBrush(const Value:TChartBrush);
begin
  FSideBrush.Assign(Value);
end;

procedure TSurfaceSeries.SetSideLines(const Value:TChartHiddenPen);
begin
  FSideLines.Assign(Value);
end;

// Pending FireMonkey: This function should return True when Series ParentChart
// is a TChart3D 
function TSurfaceSeries.ShouldDrawFast:Boolean;
begin
  result:={$IFDEF FMX}False{$ELSE}ParentChart.Canvas.SupportsFullRotation{$ENDIF};
end;

Procedure TSurfaceSeries.DrawSidePortion(var P:TFourPoints; z0,z1:Integer);
begin
  ParentChart.Canvas.PlaneFour3D(P,Z0,Z1);
end;

procedure TSurfaceSeries.CanvasChanged(Sender:TObject);
begin
  inherited;
  FreeCached;
end;

procedure TSurfaceSeries.DoBeforeDrawValues;
begin
  inherited;

  if AxisRectChanged then
     FreeCached;
end;

type
  TChartAccess=class(TCustomChart);
  TAxisPanelAccess=class(TCustomAxisPanel);
  TCanvas3DAccess=class(TCanvas3D);
  TTeeBrushAccess=class(TTeeBrush);
  TCanvasAccess=class(TTeeCanvas);

Procedure TSurfaceSeries.DrawAllValues;

  Procedure DrawAllCells;

    Function PerspectiveCorrection:Boolean;
    //const AngleRange=11;
    var tmp:Integer;
    begin
      result:=(not ParentChart.Canvas.SupportsFullRotation) and
              (ParentChart.View3DOptions.Perspective>0);

      if result then
      begin
        tmp:=ParentChart.View3DOptions.Rotation mod 360;
        result:= (tmp=360) or (tmp=180) or (tmp=0);

// Perspective correction:
//        result:=(Abs(360-tmp)<AngleRange) or
//                (Abs(tmp)<AngleRange) or (Abs(180-tmp)<AngleRange);
      end;
    end;

  var tmpZ : Integer;
      tmpO : TCellsOrientation;
      tmpXInit,
      tmpXEnd,
      tmpX     : Integer;
      tmpPosInit : Boolean;
  begin
    tmpO:=CellsOrientation;

    with tmpO do
    if PerspectiveCorrection then
    begin
      tmpXInit:=InitX;
      tmpXEnd:=EndX;
      tmpPosInit:=True;
      tmpX:=tmpXInit;

      repeat
        tmpZ:=InitZ;

        while tmpZ<>EndZ do
        begin
          DrawCell(tmpX,tmpZ);
          Inc(tmpZ,IncZ);
        end;

        if tmpPosInit then
        begin
          Dec(tmpXEnd,IncX);
          tmpX:=tmpXEnd;
        end
        else
        begin
          Inc(tmpXInit,IncX);
          tmpX:=tmpXInit;
        end;

        tmpPosInit:=not tmpPosInit;

      until tmpXInit=tmpXEnd;
    end
    else
      while InitX<>EndX do
      begin
        tmpZ:=InitZ;

        while tmpZ<>EndZ do
        begin
          DrawCell(InitX,tmpZ);
          Inc(tmpZ,IncZ);
        end;

        Inc(InitX,IncX);
      end;
  end;

  Procedure FastDraw;
  var tmpStyle : TTeeCanvasSurfaceStyle;
  begin
    if FWireFrame or (Brush.Style=bsClear) then
       tmpStyle:=tcsWire
    else
    if FDotFrame then
       tmpStyle:=tcsDot
    else
       tmpStyle:=tcsSolid;

    if Transparency>0 then
       IBlender:=ParentChart.Canvas.BeginBlending(TeeZeroRect,Transparency);

    if not Reuse3D then
       FreeCached;

    ParentChart.Canvas.Surface3D(tmpStyle,FSameBrush,FReuse3D,
                                 FNormals,NumXValues,
                                 NumZValues,FastCalcPoints,ICachedSurface);

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

  Procedure DrawSides(BeforeCells:Boolean);

    Function DrawFrontSideFirst(ZPos:Integer):Boolean;
    var P : TFourPoints;
        tmpBottom : TCoordinate;
        tmpChart  : TCustomChart;
    begin
      tmpChart:=ParentChart as TCustomChart;

      with tmpChart,ChartRect do
      begin
        P[0]:=Canvas.Calculate3DPosition(Right,Top,ZPos);
        tmpBottom:=Bottom+TChartAccess(tmpChart).CalcWallSize(BottomAxis);
        P[1]:=Canvas.Calculate3DPosition(Right,tmpBottom,ZPos);
        P[2]:=Canvas.Calculate3DPosition(Left,tmpBottom,ZPos);
      end;

      result:=not TeeCull(P);
    end;

  var tmpYOrigin : Integer;

    Function CalcOnePoint(Index:Integer; out P0,P1:TPoint):Integer;
    begin
      if Index<>-1 then
      begin
        P0:=CalcPointPos(Index);
        P1.x:=P0.x;
        P1.y:=tmpYOrigin;
        result:=CalcZPos(Index);
      end
      else result:=0;
    end;

    procedure DrawZSide(tmpRow:Integer);
    var t  : Integer;
        Z0 : Integer;
        Z1 : Integer;
        P  : TFourPoints;
    begin
      for t:=NumZValues downto 2 do
      begin
        Z0:=CalcOnePoint(GridIndex[tmpRow,t],P[0],P[1]);
        Z1:=CalcOnePoint(GridIndex[tmpRow,t-1],P[3],P[2]);
        DrawSidePortion(P,Z0,Z1);
      end;
    end;

    procedure DrawXSide(tmpRow:Integer);
    var t  : Integer;
        Z0 : Integer;
        Z1 : Integer;
        P  : TFourPoints;
    begin
      for t:=2 to NumXValues do
      begin
        Z0:=CalcOnePoint(GridIndex[t,tmpRow],P[0],P[1]);
        Z1:=CalcOnePoint(GridIndex[t-1,tmpRow],P[3],P[2]);
        DrawSidePortion(P,Z0,Z1);
      end;
    end;

  var tmpRow   : Integer;
      tmpZ     : Integer;
      tmpLeft  : Boolean;
      tmpFront : Boolean;
      tmpRight : Boolean;
      tmpChart : TCustomChart;
      P        : TFourPoints;
  begin
    With ParentChart.Canvas do
    begin
      AssignBrush(FSideBrush);
      AssignVisiblePen(FSideLines);
    end;

    With GetVertAxis do
    if Inverted then tmpYOrigin:=CalcYPosValue(YValues.MaxValue)
                else tmpYOrigin:=CalcYPosValue(YValues.MinValue);

    tmpChart:=ParentChart as TCustomChart;

    tmpLeft:=TChartAccess(tmpChart).DrawLeftWallFirst;
    tmpRight:=TChartAccess(tmpChart).DrawRightWallAfter;

    if (tmpLeft and (not BeforeCells)) or
       ((not tmpLeft) and BeforeCells) then
    begin
      if GetHorizAxis.Inverted then tmpRow:=NumXValues
                               else tmpRow:=1;

      DrawZSide(tmpRow);
    end;

    if (tmpRight and (not BeforeCells)) or
       ((not tmpRight) and BeforeCells) then
    begin
      if GetHorizAxis.Inverted then tmpRow:=1
                               else tmpRow:=NumXValues;

      DrawZSide(tmpRow);
    end;

    tmpFront:=not DrawFrontSideFirst(EndZ);

    if (tmpFront and BeforeCells) or
       ((not tmpFront) and (not BeforeCells)) then
    begin
      if ParentChart.DepthAxis.Inverted then tmpRow:=1
                                        else tmpRow:=NumZValues;

      DrawXSide(tmpRow);
    end;

    tmpFront:=not DrawFrontSideFirst(0);

    if (tmpFront and (not BeforeCells)) or
       ((not tmpFront) and BeforeCells) then
    begin
      if ParentChart.DepthAxis.Inverted then tmpRow:=NumZValues
                                        else tmpRow:=1;

      DrawXSide(tmpRow);

      with ParentChart.Canvas do
      begin
        Pen.Style:=psSolid;

        tmpZ:=CalcOnePoint(GridIndex[NumXValues,tmpRow],P[0],P[1]);
        VertLine3D(P[0].X,P[0].Y,tmpYOrigin,tmpZ);
      end;
    end;
  end;

  procedure PrepareBrush;
  begin
    With ParentChart.Canvas do
    begin
      AssignBrush(Self.Brush,SeriesColor);
      FSameBrush:=((not FUseColorRange) and (not FUsePalette)) or
                   (TTeeBrushAccess(Self.Brush).HasImage and Self.Brush.Image.Valid);

      if FWireFrame or FDotFrame then
         Brush.Style:=bsClear;

      BackMode:=cbmTransparent;
    end;
  end;

  Procedure PrepareCanvas;
  begin
    With ParentChart.Canvas do
    begin
      if (not Self.Pen.Visible) and
         (not WireFrame) and
         (not DotFrame) then
            Pen.Style:=psClear
      else
        AssignVisiblePen(Self.Pen);

      PrepareBrush;
    end;
  end;

var
  tmpBackRange : Boolean;

  procedure DrawAllSorted;
  type
    TCellParam = record
       Z : TChartValue;
       X,
       Y : Integer;
       Valid : Boolean;
    end;
    TCells=Array of TCellParam;

  var
    Cells : TCells;
    tmpBackCompare : Integer;

    procedure SortCells(const l,r : Integer);

      function Compare(const a,b:TCellParam):Integer;
      begin
        if a.Valid and b.Valid and (a.z<>b.z) then
        begin
          result:=tmpBackCompare;

          if a.z>b.z then
             result:=not result;
        end
        else
           result:=0
      end;

    var i : Integer;
        j : Integer;
        x : Integer;
        tmpSwap,
        tmp : TCellParam;
    begin
      i:=l;
      j:=r;
      x:=(i+j) shr 1;

      tmp:=Cells[x];

      while i<j do
      begin
        //tmp:=Cells[x];

        while Compare(Cells[i],tmp)<0 do inc(i);
        while Compare(tmp,Cells[j])<0 do dec(j);

        if i<j then
        begin
          // Swap cell
          tmpSwap:=Cells[i];
          Cells[i]:=Cells[j];
          Cells[j]:=tmpSwap;

          if i=x then
          begin
            x:=j;
            tmp:=Cells[x];
          end
          else
          if j=x then
          begin
            x:=i;
            tmp:=Cells[x];
          end;
        end;

        if i<=j then
        begin
          inc(i);
          dec(j)
        end;
      end;

      if l<j then SortCells(l,j);
      if i<r then SortCells(i,r);
    end;

    procedure FillCellsToSort;
    var tmpO : TCellsOrientation;
        tmpZ : Integer;
        tmp  : Integer;
    begin
      tmpO:=CellsOrientation;

      tmp:=0;

      with tmpO do
        while InitX<>EndX do
        begin
          tmpZ:=InitZ;

          while tmpZ<>EndZ do
          begin
            Cells[tmp].Valid:=ExistFourGridIndex(InitX,tmpZ);

            if Cells[tmp].Valid then
            begin
              Cells[tmp].X:=InitX;
              Cells[tmp].Y:=tmpZ;
              Cells[tmp].Z:=ZValues.Value[GridIndex[InitX,tmpZ]];
            end;

            Inc(tmpZ,IncZ);
            Inc(tmp);
          end;

          Inc(InitX,IncX);
        end;
    end;

  var t,
      tmpCount : Integer;
      tmpBack  : Boolean;
  begin
    tmpCount:=(NumXValues-1)*(NumZValues-1);

    if (tmpCount>0) then
    begin
      SetLength(Cells,tmpCount);

      try
        FillCellsToSort;

        if ParentChart.DepthAxis.Inverted then
           tmpBack:=not tmpBackRange
        else
           tmpBack:=tmpBackRange;

        if tmpBack then
           tmpBackCompare:=-1
        else
           tmpBackCompare:=1;

        SortCells(0,tmpCount-1);

        for t:=0 to tmpCount-1 do
            if Cells[t].Valid then
               DrawCell(Cells[t].X,Cells[t].Y);

      finally
        Cells:=nil;
      end;
    end;
  end;

  // Returns True if 3D rotation is inside the range of degrees that
  // makes a Grid series with Perspective>0 to show cells in wrong Z order
  function InRangeRotation:Boolean;
  begin
    with ParentChart.View3DOptions do
    begin
      tmpBackRange:=(Rotation>150) and (Rotation<210);
      result:=tmpBackRange or ((Rotation<30) or (Rotation>330));
    end;
  end;

  procedure DrawHover;
  var tmp,
      Z : Integer;
      P : TPoint;
      R : TRect;
      tmpCanvas : TCanvas3D;
      tmpHover : TTeeHover;
  begin
    tmpHover:=Selected.Hover;

    tmp:=Selected.HoverIndex;
    PrepareHoverPen(tmp);

    tmpCanvas:=ParentChart.Canvas;

    P:=CalcPointPos(tmp);

    tmpCanvas.AssignBrush(tmpHover.Brush);

    R:=RectFromCenter(P,4);
    Z:=CalcZPos(tmp);

//    tmpHover.Draw(ParentChart,R,Z);

    case Selected.Hover.ShapeStyle of
      fosRectangle: tmpCanvas.Rectangle(R,Z);
      fosRoundRectangle: tmpCanvas.RoundRect(tmpCanvas.RectFromRectZ(R,Z),tmpHover.RoundSize,tmpHover.RoundSize);
    else
      tmpCanvas.EllipseWithZ(R,Z);
    end;
  end;

var tmpSides : Boolean;
begin
  // DO NOT CALL inherited HERE

  if Count>0 then
  begin
    PrepareCanvas;
    PrepareCalcPos;

    tmpSides:=ShouldDrawSides;

    if ShouldDrawFast then
       FastDraw
    else
    begin
      if tmpSides then
      begin
        DrawSides(True);
        PrepareCanvas;
      end;

      if Transparency>0 then
         IBlender:=ParentChart.Canvas.BeginBlending(TeeZeroRect,Transparency);

      IsFastBrush:=FastBrush and (not WireFrame) and (Brush.Style=bsSolid) and
                   TCanvas3DAccess(ParentChart.Canvas).CheckFastBrush;

      if IsFastBrush then
         PrepareFastBrush;

      if not Pen.Visible then
         TCanvasAccess(ParentChart.Canvas).SetAntiAlias(False);

      if HideCells and (not ParentChart.Canvas.SupportsFullRotation) and InRangeRotation then
         DrawAllSorted
      else
         DrawAllCells;

      if not Pen.Visible then
         TCanvasAccess(ParentChart.Canvas).SetAntiAlias(True);

      if HoverEnabled and (Selected.HoverIndex<>-1) then
         DrawHover;

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

    if tmpSides then
       DrawSides(False);

    SaveChartRect;
  end;
end;

function TSurfaceSeries.ShouldDrawSides:Boolean;
begin
  result:=(FSideBrush.Style<>bsClear) or FSideLines.Visible;
end;

procedure TSurfaceSeries.PrepareFastBrush;
begin
  {$IFNDEF FMX}
  if TCanvas3DAccess(ParentChart.Canvas).CheckFastBrush then
     CanvasDC:=ParentChart.Canvas.Handle;
  {$ENDIF}
end;

type
  TAxisAccess=class(TChartAxis);

procedure TSurfaceSeries.FastCalcPoints(x,z:Integer; CalcColors:Boolean; out V:TSurfaceVertex);

  function YPosValue(AIndex:Integer):Double;
  var tmp : Double;
  begin
    with TAxisAccess(GetVertAxis) do
    begin
      tmp:=(YValues.Value[AIndex]-InternalMinimum)*IAxisSizeRange;

      if Inverted then
         result:=IStartPos+tmp
      else
         result:=IEndPos-tmp;
    end;
  end;

begin
  V.Flag:=False;

  ValueIndex0:=GridIndex[x-1,z];
  if ValueIndex0<>-1 then
  begin
    ValueIndex1:=GridIndex[x,z];
    if ValueIndex1<>-1 then
    begin
      with V.P0 do
      begin
        x:=ICalcX(IXValue[ValueIndex0]);
        y:=YPosValue(ValueIndex0);
        z:=ICalcZ(IZValue[ValueIndex0]);
      end;

      with V.P1 do
      begin
        x:=ICalcX(IXValue[ValueIndex1]);
        y:=YPosValue(ValueIndex1);
        z:=ICalcZ(IZValue[ValueIndex1]);
      end;

      if CalcColors then
      begin
        V.C0:=ValueColor[ValueIndex0];
        V.C1:=ValueColor[ValueIndex1];
      end;

      V.Flag:=True;
    end;
  end;
end;

procedure TSurfaceSeries.FreeCached;
begin
  FreeAndNil(ICachedSurface);
end;

procedure TSurfaceSeries.ChangedAxisScale(const Axis:TChartAxis);
begin
  inherited;
  FreeCached;
end;

Procedure TSurfaceSeries.NotifyValue(ValueEvent:TValueEvent; ValueIndex:Integer);
begin
  inherited;

  if not (csDestroying in ComponentState) then
     FreeCached;
end;

Procedure TSurfaceSeries.PointsTo2D(Z0,Z1:Integer; out P:TFourPoints);
begin
  with ParentChart.Canvas do
  begin
    P[0]:=Calculate3DPosition(Points[0],Z0);
    P[1]:=Calculate3DPosition(Points[1],Z0);
    P[2]:=Calculate3DPosition(Points[2],Z1);
    P[3]:=Calculate3DPosition(Points[3],Z1);
  end;
end;

procedure TSurfaceSeries.SetFastBrush(const Value: Boolean);
begin
  SetBooleanProperty(FFastBrush, Value);
end;

Procedure TSurfaceSeries.DrawCell(X,Z:Integer);
var tmpColor : TColor;
    Z0       : Integer;

  Procedure DrawTheCell;
  var Z1 : Integer;

    procedure DrawDot;
    begin
      With ParentChart.Canvas do
      begin
        With Points[0]{$IFDEF FMX}.Round{$ENDIF} do
             Pixels3D[X,Y,Z0]:=ValueColor[ValueIndex0];

        With Points[1]{$IFDEF FMX}.Round{$ENDIF} do
             Pixels3D[X,Y,Z0]:=ValueColor[ValueIndex1];

        Z1:=CalcZPos(ValueIndex2);

        With Points[2]{$IFDEF FMX}.Round{$ENDIF} do
             Pixels3D[X,Y,Z1]:=ValueColor[ValueIndex2];

        With Points[3]{$IFDEF FMX}.Round{$ENDIF} do
             Pixels3D[X,Y,Z1]:=ValueColor[ValueIndex3];
      end;
    end;

  var tmp     : Integer;
      IPoints : TFourPoints;
  begin
    With ParentChart.Canvas do
    begin
      if FWaterFall then
      begin
        tmp:=GetVertAxis.IEndPos;

        if not FDotFrame then
        begin
          if not FWireFrame then
          begin
            Pen.Style:=psClear;

            if Transparency>0 then
            begin
              IPoints[0]:=Calculate3DPosition(Points[0],Z0);
              IPoints[1]:=Calculate3DPosition(Points[1],Z0);
              IPoints[2]:=Calculate3DPosition(TeePoint(Points[1].X,tmp),Z0);
              IPoints[3]:=Calculate3DPosition(TeePoint(Points[0].X,tmp),Z0);

              if (not SupportsAlpha) and Assigned(IBlender) then
                 IBlender.SetRectangle(RectFromPolygon(IPoints,4));

              Polygon(IPoints);

              if Assigned(IBlender) then
                 IBlender.DoBlend(Transparency);
            end
            else
              PlaneWithZ(Points[0],Points[1],
                         TeePoint(Points[1].X,tmp),
                         TeePoint(Points[0].X,tmp),
                         Z0);
          end;

          AssignVisiblePen(Self.Pen);
          LineWithZ(Points[0],Points[1],Z0);
        end;

        if WaterLines.Visible then
        begin
          AssignVisiblePen(WaterLines);
          VertLine3D(Points[0].X,Points[0].Y,tmp,Z0);
          VertLine3D(Points[1].X,Points[1].Y,tmp,Z0);
        end;

        if FDotFrame then
           DrawDot;
      end
      else
      if FDotFrame then
         DrawDot
      else
      begin
        Z1:=CalcZPos(ValueIndex2);

        if Transparency>0 then
        begin
          PointsTo2D(Z0,Z1,IPoints);

          if (not SupportsAlpha) and Assigned(IBlender) then
             IBlender.SetRectangle(RectFromPolygon(IPoints,4));

          Polygon(IPoints);

          if Assigned(IBlender) then
             IBlender.DoBlend(Transparency);
        end
        else
          PlaneFour3D(Points,Z0,Z1);
      end;
    end;
  end;

var tmp : Boolean;
Begin
  if FWaterFall then
  begin
    tmp:=False;

    if (x<>-1) and (z<>-1) then
    begin
      ValueIndex0:=GridIndex[x,z];

      if ValueIndex0>-1 then
      begin
        ValueIndex3:=ValueIndex0;

        ValueIndex1:=GridIndex[x+INextXCell,z];

        if ValueIndex1>-1 then
        begin
          ValueIndex2:=ValueIndex1;

          Points[0]:=CalcPointPos(ValueIndex0);
          Points[1]:=CalcPointPos(ValueIndex1);
          tmp:=True;
        end;
      end;
    end;
  end
  else
    tmp:=FourGridIndex(X,Z);

  if tmp then
  begin
    if INextXCell=1 then
       if INextZCell=1 then
          tmpColor:=ValueColor[ValueIndex3]
       else
          tmpColor:=ValueColor[ValueIndex0]
    else
       if INextZCell=1 then
          tmpColor:=ValueColor[ValueIndex2]
       else
          tmpColor:=ValueColor[ValueIndex1];

    if tmpColor<>clNone then // <-- not IsNull
    begin
      Z0:=CalcZPos(ValueIndex0);

      if not FSameBrush then
      begin
        if FSmoothPalette then
           with YValues do
           tmpColor:=GetValueColorValue(( Value[ValueIndex0]+
                                          Value[ValueIndex1]+
                                          Value[ValueIndex2]+
                                          Value[ValueIndex3])*0.25);

        if FWireFrame then
           ParentChart.Canvas.Pen.Color:=tmpColor
        else
           SetBrushColor(tmpColor);
      end
      {$IFDEF FMX}
      else
        SetBrushColor(tmpColor);
      {$ENDIF}
      ;

      DrawTheCell;
    end;
  end;
end;

Procedure TSurfaceSeries.DrawValue(ValueIndex:Integer);
begin
  DrawCell(Round(XValues.Value[ValueIndex]),Round(ZValues.Value[ValueIndex]));
end;

Procedure TSurfaceSeries.SetBrushColor(const AColor:TColor);
begin
  {$IFNDEF FMX}
  if IsFastBrush then
     TeeSetDCBrushColor(CanvasDC,AColor)
  else
  {$ENDIF}
  with ParentChart.Canvas.Brush do
       if {$IFDEF FMX}Kind{$ELSE}Style{$ENDIF}<>bsClear then
          if Color<>AColor then
          begin
            {$IFDEF ISOTEST}
            Inc(TempCounter); // <-- Internal perf stats
            {$ENDIF}

            Color:=AColor;
          end;
end;

Procedure TSurfaceSeries.SetDotFrame(Value:Boolean);
Begin
  if Value then
  begin
    Pen.Visible:=True;
    FWireFrame:=False;
  end;

  SetBooleanProperty(FDotFrame,Value);
End;

Procedure TSurfaceSeries.SetWaterFall(Value:Boolean);
Begin
  SetBooleanProperty(FWaterFall,Value);
End;

Procedure TSurfaceSeries.SetWireFrame(Value:Boolean);
Begin
  if Value then
  begin
    Pen.Visible:=True;
    FDotFrame:=False;
  end;
  SetBooleanProperty(FWireFrame,Value);
End;

Procedure TSurfaceSeries.SetSmoothPalette(Value:Boolean);
begin
  SetBooleanProperty(FSmoothPalette,Value);
end;

Procedure TSurfaceSeries.AssignFormatting(Source:TChartSeries);
begin
  if Source is TSurfaceSeries then
  With TSurfaceSeries(Source) do
  begin
    Self.FDotFrame     := FDotFrame;
    Self.SideBrush     := FSideBrush;
    Self.SideLines     := FSideLines;
    Self.FSmoothPalette:= FSmoothPalette;
    Self.WaterLines    := WaterLines;
    Self.FWireFrame    := FWireFrame;
  end;

  inherited;
end;

Procedure TSurfaceSeries.Assign(Source:TPersistent);
begin
  if Source is TSurfaceSeries then
  With TSurfaceSeries(Source) do
  begin
    Self.FHideCells    := FHideCells;
    Self.FWaterFall    := False; // Dont use "Source".FWaterFall property
  end;
  
  inherited;
end;

class procedure TSurfaceSeries.CreateSubGallery(
  AddSubChart: TChartSubGalleryProc);
begin
  inherited;
  AddSubChart(TeeMsg_WireFrame);
  AddSubChart(TeeMsg_DotFrame);
  AddSubChart(TeeMsg_Sides);
  AddSubChart(TeeMsg_NoBorder);
end;

class procedure TSurfaceSeries.SetSubGallery(ASeries: TChartSeries;
  Index: Integer);
begin
  With TSurfaceSeries(ASeries) do
  Case Index of
    2: WireFrame:=True;
    3: DotFrame:=True;
    4: SideBrush.Style:=bsSolid;
    5: Pen.Hide;
  else inherited;
  end;
end;

procedure TSurfaceSeries.SetWaterLines(const Value: TChartPen);
begin
  FWaterLines.Assign(Value);
end;

Function TSurfaceSeries.MoreSameZOrder:Boolean;
begin
  result:=False;
end;

procedure TSurfaceSeries.PrepareCalcPos;
begin
  ICalcX:=GetHorizAxis.CalcXPosValue;
  ICalcY:=GetVertAxis.CalcYPosValue;
  ICalcZ:=ParentChart.DepthAxis.CalcYPosValue;

  IXValue:=XValues.Value;
  IYValue:=YValues.Value;
  IZValue:=ZValues.Value;
end;

procedure TSurfaceSeries.SetHideCells(const Value: Boolean);
begin
  SetBooleanProperty(FHideCells,Value);
end;

{ TContourMarks }

Constructor TContourMarks.Create;
begin
  inherited;
  FAtSegments:=True;
  FDensity:=4;
end;

procedure TContourMarks.Assign(Source: TPersistent);
begin
  if Source is TContourMarks then
  with TContourMarks(Source) do
  begin
    Self.FAntiOverlap:=FAntiOverlap;
    Self.FAtSegments:=FAtSegments;
    Self.FColorLevel:=FColorLevel;
    Self.FDensity:=FDensity;
    Self.FMargin:=FMargin;
  end;

  inherited;
end;

function TContourMarks.GetVisible: Boolean;
begin
  result:=IContour.Marks.Visible;
end;

type
  TSeriesAccess=class(TChartSeries);

procedure TContourMarks.SetAtSegments(const Value: Boolean);
begin
  TSeriesAccess(IContour).SetBooleanProperty(FAtSegments,Value);
end;

procedure TContourMarks.SetColorLevel(const Value: Boolean);
begin
  TSeriesAccess(IContour).SetBooleanProperty(FColorLevel,Value);
end;

procedure TContourSeries.SetContourMarks(const Value: TContourMarks);
begin
  FContourMarks.Assign(Value);
end;

procedure TContourMarks.SetDensity(const Value: Integer);
begin
  TSeriesAccess(IContour).SetIntegerProperty(FDensity,Max(1,Value));
end;

procedure TContourMarks.SetMargin(const Value: Integer);
begin
  TSeriesAccess(IContour).SetIntegerProperty(FMargin,Value);
end;

procedure TContourMarks.SetVisible(const Value: Boolean);
begin
  IContour.Marks.Visible:=Value;
end;

procedure TContourMarks.SetAntiOverlap(const Value: Boolean);
begin
  TSeriesAccess(IContour).SetBooleanProperty(FAntiOverlap,Value);
end;

{ TContourSeries }

Constructor TContourSeries.Create(AOwner: TComponent);
Begin
  inherited;

  IDirtySegments:=True;
  IMandatoryPen:=True;


  // Setting here a default bsClear will not get persisted because Brush.Style
  // has bsSolid as default value.
  // See also "Filled" property:
  Brush.Style:=bsClear;

  FLevels:=TContourLevels.Create(Self,TContourLevel);
  FSmoothing:=TSmoothPoints.Create(Self);
  FAutomaticLevels:=True;
  ColorEachPoint:=True;

  UseColorRange:=False;

  FNumLevels:=10;

  FPointer:=TSeriesPointer.Create(Self);
  FPointer.Size:=2;
  FPointer.Pen.Hide;
  FPointer.Hide;

  Marks.Transparent:=True;

  FContourMarks:=TContourMarks.Create;
  FContourMarks.IContour:=Self;

  FFrame:=TChartHiddenPen.Create(CanvasChanged);

  FLinesColor:={$IFDEF FMX}claBlack{$ELSE}clBlack{$ENDIF};
end;

Destructor TContourSeries.Destroy;
begin
  FCellPen.Free;
  FFrame.Free;
  FContourMarks.Free;
  FPointer.Free;
  FSmoothing.Free;

  IModifyingLevels:=True;
  FLevels.Free;
  IModifyingLevels:=False;

  // Avoid exceptions at other controls like TChartListBox when removing
  // this internal/temporary IsoSurface series

  if Assigned(ParentChart) then
     TAxisPanelAccess(ParentChart).DoNotBroadcast:=True;
  try
    IFillSeries.Free;
  finally
    if Assigned(ParentChart) then
       TAxisPanelAccess(ParentChart).DoNotBroadcast:=False;
  end;

  inherited;
end;

Procedure TContourSeries.AssignFormatting(Source:TChartSeries);
begin
  if Source is TContourSeries then
  With TContourSeries(Source) do
  begin
    Self.CellPen         :=FCellPen;
    Self.FLegendLines    :=FLegendLines;
    Self.FLinesColor     :=FLinesColor;
    Self.Frame           :=Frame;
    Self.FPointer.Assign(FPointer);
  end;

  inherited;
end;

Procedure TContourSeries.Assign(Source:TPersistent);
begin
  if Source is TContourSeries then
  With TContourSeries(Source) do
  begin
    Self.FAutomaticLevels:=FAutomaticLevels;
    Self.Levels          :=FLevels;
    Self.FNumLevels      :=FNumLevels;
    Self.FYPosition      :=FYPosition;
    Self.FYPositionLevel :=FYPositionLevel;
  end;

  inherited;
end;

procedure TContourSeries.ClearAutoLevels;
begin
  IModifyingLevels:=True;
  try
    Levels.Clear;
  finally
    IModifyingLevels:=False;
  end;
end;

Procedure TContourSeries.SetNumLevels(Value:Integer);
begin
  SetIntegerProperty(FNumLevels,Value);

  IDirtySegments:=True;

  if AutomaticLevels then
     ClearAutoLevels
  else
  while Levels.Count<FNumLevels do
        if Levels.Count=0 then
           Levels.AddLevel(0)
        else
           Levels.AddLevel(Levels.Last.UpToValue);
end;

procedure TContourSeries.SetPalette(const Value: TCustom3DPalette);
begin
  inherited;

  // Try to change Level colors (if AutomaticLevels) on Palette change:
  CreateAutoLevels;
end;

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

  if not (csDestroying in ComponentState) then
     FPointer.ParentChart:=Value;
end;

Procedure TContourSeries.SetPointer(Const Value:TSeriesPointer);
begin
  FPointer.Assign(Value);
end;

Procedure TContourSeries.SetYPosition(Const Value:TChartValue);
begin
  if FYPosition<>Value then
  begin
    FYPosition:=value;
    Repaint;
  end;
end;

Procedure TContourSeries.SetYPositionLevel(Value:Boolean);
begin
  SetBooleanProperty(FYPositionLevel,Value);
end;

procedure TContourSeries.DrawMarks;
begin
  if not FContourMarks.AtSegments then
     inherited;
end;

Procedure TContourSeries.DrawMark(ValueIndex:Integer; Const St:String;
                                  APosition:TSeriesMarkPosition);
begin
  if not FContourMarks.AtSegments then
  begin
    APosition.LeftTop.X:=CalcXPos(ValueIndex);
    APosition.LeftTop.Y:=CalcYPosValue(ZValues[ValueIndex]);

    inherited;
  end;
end;

type
  TLevelDifsCalc=procedure(const A,B:TLevelPoint) of object;
  TLevelDifsFill=procedure(const P:Array of TLevelPoint3D) of object;

  {$IFNDEF D6}
  TValueSign=-1..1;
  {$ENDIF}
  
  // Internal class used to calculate Contour levels and IsoSurface cell areas.
  // 5 element arrays (0=Center point, 1,2,3,4=cell corners)
  TLevelDifs=class
  private
    DifY     : Array[0..4] of TChartValue;

    Cell     : Array[0..4] of TLevelPoint;
    Cell3D   : Array[0..4] of TLevelPoint3D;

    MinLevel,
    MaxLevel : TChartValue;

    DiffMin  : TChartValue;
    DiffMax  : TChartValue;
    tmp1     : TChartValue;
    tmp2     : TChartValue;
    tmp3     : TChartValue;
    tmp4     : TChartValue;

    IX,IY,IZ : TChartValues;

    SignHeights : Array[1..4] of TValueSign;
    SignZero    : Integer;

    procedure Loop(const AValue:TChartValue; const AProc:TLevelDifsCalc);
    procedure LoopFill(const ALow,AHigh:TChartValue; const AProc:TLevelDifsFill);
    function Prepare(V0,V1,V2,V3:Integer; Is2D:Boolean):Boolean;
  public
    Constructor Create(const AMin,AMax:TChartValue; const ASeries:TCustom3DGridSeries);
  end;

{$IFNDEF D6}
function Sign(const Value:Double):TValueSign;
begin
  if Value<0 then result:=-1 else
  if Value>0 then result:=1 else result:=0;
end;
{$ENDIF}

Constructor TLevelDifs.Create(const AMin,AMax:TChartValue; const ASeries:TCustom3DGridSeries);
begin
  IX:=ASeries.XValues.Value;
  IY:=ASeries.YValues.Value;
  IZ:=ASeries.ZValues.Value;

  MinLevel:=AMin;
  MaxLevel:=AMax;
end;

procedure TLevelDifs.Loop(const AValue:TChartValue; const AProc:TLevelDifsCalc);

  function PointSect(const p1,p2:Integer):TLevelPoint;
  var tmp : TChartValue;
      dif1,
      dif2 : TChartValue;
      tmp1,
      tmp2 : TLevelPoint;
  begin
    { calculate crossing XY point, return pixel coordinates }

    dif1:=DifY[p1];
    dif2:=DifY[p2];

    tmp1:=Cell[p1];
    tmp2:=Cell[p2];

    tmp:=dif2-dif1;

    if tmp<>0 then
    begin
      tmp:=1/tmp;

      result.X:=(Dif2*tmp1.X-Dif1*tmp2.X)*tmp;
      result.Y:=(Dif2*tmp1.Y-Dif1*tmp2.Y)*tmp;
    end
    else
    begin
      result.X:=tmp2.X-tmp1.X;
      result.Y:=tmp2.Y-tmp1.Y;
    end;
  end;

var Cell0 : TLevelPoint;

  procedure PrepareSigns(const AValue:TChartValue);
  var Corner : Integer;
  begin
    { calc corners dif Y to level Up value }
    DifY[1]:=tmp1-AValue;
    DifY[2]:=tmp3-AValue;
    DifY[3]:=tmp4-AValue;
    DifY[4]:=tmp2-AValue;

    { set dif[0] to Y average }
    DifY[0]:=0.25*(tmp1+tmp2+tmp3+tmp4)-AValue; { average Y value }
    SignZero:=Sign(DifY[0]); { average Y sign }

    { calculate signs for each corner Y value and Y average }
    // Note: calling Math.Sign() is slower than doing this here directly
    for Corner:=1 to 4 do
      if DifY[Corner]>0 then SignHeights[Corner]:= 1 else
      if DifY[Corner]<0 then SignHeights[Corner]:=-1 else
                             SignHeights[Corner]:= 0;

    Cell0:=Cell[0];
  end;

const Sides : Array[-1..1,-1..1,-1..1] of Byte=
                 ( ( (0,0,8),(0,2,5),(7,6,9) ),
                   ( (0,3,4),(1,3,1),(4,3,0) ),
                   ( (9,6,7),(5,2,0),(8,0,0) )  );
var m1   : Integer;
    m3   : Integer;
    Side : Integer;
begin
  PrepareSigns(AValue);

  for m1:=1 to 4 do
  begin
    if m1=4 then m3:=1 else m3:=Succ(m1);

    { find "Side" between one corner and next corner }
    Side:=Sides[SignHeights[m1],SignZero,SignHeights[m3]];

    {$IFDEF ISOTEST}
    Inc(IsoStats[Side]);
    {$ENDIF}

    { if valid side, calculate crossing points and add a new level line }
    case Side of
       1: AProc(Cell[m1],Cell0);
       2: AProc(Cell0,Cell[m3]);
       3: AProc(Cell[m3],Cell[m1]);
       4: AProc(Cell[m1],PointSect(0,m3));
       5: AProc(Cell0,PointSect(m3,m1));
       6: AProc(Cell[m3],PointSect(m1,0));
       7: AProc(PointSect(m1, 0),PointSect(0, m3));
       8: AProc(PointSect( 0,m3),PointSect(m3,m1));
       9: AProc(PointSect(m3,m1),PointSect(m1, 0));
    end;
  end;
end;

procedure TLevelDifs.LoopFill(const ALow,AHigh:TChartValue; const AProc:TLevelDifsFill);
var
  DifL,DifH: Array[0..4] of TChartValue;

  function SectH(const p1,p2:Integer):TLevelPoint3D;
  var dif1,
      dif2,
      tmp : TChartValue;
      tmp1,
      tmp2 : TLevelPoint;
  begin
    { calculate crossing XY point, return pixel coordinates }
    dif1:=DifH[p1];
    dif2:=DifH[p2];

    tmp:=dif2-dif1;

    tmp1:=Cell[p1];
    tmp2:=Cell[p2];

    if tmp<>0 then
    begin
      tmp:=1/tmp;

      result.X:=(Dif2*tmp1.X-Dif1*tmp2.X)*tmp;
      result.Y:=(Dif2*tmp1.Y-Dif1*tmp2.Y)*tmp;
    end
    else
    begin
      result.X:=tmp2.X-tmp1.X;
      result.Y:=tmp2.Y-tmp1.Y;
    end;

    result.Z:=AHigh;
  end;

  function SectL(const p1,p2:Integer):TLevelPoint3D;
  var dif1,
      dif2,
      tmp : TChartValue;
      tmp1,
      tmp2 : TLevelPoint;
  begin
    { calculate crossing XY point, return pixel coordinates }
    dif1:=DifL[p1];
    dif2:=DifL[p2];

    tmp:=dif2-dif1;

    tmp1:=Cell[p1];
    tmp2:=Cell[p2];

    if tmp<>0 then
    begin
      tmp:=1/tmp;

      result.X:=(Dif2*tmp1.X-Dif1*tmp2.X)*tmp;
      result.Y:=(Dif2*tmp1.Y-Dif1*tmp2.Y)*tmp;
    end
    else
    begin
      result.X:=tmp2.X-tmp1.X;
      result.Y:=tmp2.Y-tmp1.Y;
    end;

    result.Z:=ALow;
  end;

var
  SignFill : Array[1..4] of Integer;
  SignFillZero : Integer;
  Cell0 : TLevelPoint3D;

  procedure PrepareSignsFill(const ALow,AHigh:TChartValue);
  var tmp : TChartValue;
  begin
    Cell0:=Cell3D[0];
    tmp:=Cell0.Z;

    DifL[0]:=tmp-ALow;
    DifL[1]:=tmp1-ALow;
    DifL[2]:=tmp3-ALow;
    DifL[3]:=tmp4-ALow;
    DifL[4]:=tmp2-ALow;

    DifH[0]:=tmp-AHigh;
    DifH[1]:=tmp1-AHigh;
    DifH[2]:=tmp3-AHigh;
    DifH[3]:=tmp4-AHigh;
    DifH[4]:=tmp2-AHigh;

    if tmp1<ALow then SignFill[1]:=0
    else
    if tmp1>AHigh then SignFill[1]:=2
    else
       SignFill[1]:=1;

    if tmp2<ALow then SignFill[4]:=0
    else
    if tmp2>AHigh then SignFill[4]:=2
    else
       SignFill[4]:=1;

    if tmp3<ALow then SignFill[2]:=0
    else
    if tmp3>AHigh then SignFill[2]:=2
    else
       SignFill[2]:=1;

    if tmp4<ALow then SignFill[3]:=0
    else
    if tmp4>AHigh then SignFill[3]:=2
    else
       SignFill[3]:=1;

    if tmp<ALow then SignFillZero:=0
    else
    if tmp>AHigh then SignFillZero:=9*2
    else
       SignFillZero:=9*1;
  end;

var m1   : Integer;
    m3   : Integer;
    Side : Integer;
begin
  PrepareSignsFill(ALow,AHigh);

  for m1:=1 to 4 do
  begin
    if m1=4 then m3:=1 else m3:=Succ(m1);

    Side:=SignFillZero+3*SignFill[m3]+SignFill[m1];

    case Side of
      1: AProc([Cell3D[m1],SectL(m1,0),SectL(m1,m3)]);
      2: AProc([SectL(m3,m1),SectL(0,m1),SectH(m1,0),SectH(m1,m3)]);
      3: AProc([Cell3D[m3],SectL(m3,m1),SectL(m3,0)]);
      4: AProc([Cell3D[m1],Cell3D[m3],SectL(m3,0),SectL(m1,0)]);
      5: AProc([Cell3D[m3],SectH(m3,m1),SectH(m1,0),SectL(0,m1),SectL(0,m3)]);
      6: AProc([SectL(0,m3),SectL(m1,m3),SectH(m3,m1),SectH(m3,0)]);
      7: AProc([SectH(m3,0),SectL(0,m3),SectL(m1,0),Cell3D[m1],SectH(m1,m3)]);
      8: AProc([SectH(m1,0),SectH(m3,0),SectL(0,m3),SectL(0,m1)]);
      9: AProc([Cell0,SectL(0,m3),SectL(0,m1)]);
     10: AProc([Cell3D[m1],Cell0,SectL(0,m3),SectL(m1,m3)]);
     11: AProc([SectH(m1,m3),SectL(m3,m1),SectL(m3,0),Cell0,SectH(0,m1)]);
     12: AProc([Cell0,Cell3D[m3],SectL(m3,m1),SectL(0,m1)]);
     13: AProc([Cell0,Cell3D[m1],Cell3D[m3]]);
     14: AProc([Cell0,Cell3D[m3],SectH(m3,m1),SectH(0,m1)]);
     15: AProc([Cell0,SectH(0,m3),SectH(m3,m1),SectL(m1,m3),SectL(m1,0)]);
     16: AProc([Cell3D[m1],Cell0,SectH(0,m3),SectH(m1,m3)]);
     17: AProc([Cell0,SectH(0,m3),SectH(0,m1)]);
     18: AProc([SectL(m1,0),SectL(m3,0),SectH(0,m3),SectH(0,m1)]);
     19: AProc([Cell3D[m1],SectH(m1,0),SectH(0,m3),SectL(m3,0),SectL(m3,m1)]);
     20: AProc([SectL(m3,0),SectL(m3,m1),SectH(m1,m3),SectH(0,m3)]);
     21: AProc([SectL(m1,0),SectH(0,m1),SectH(0,m3),Cell3D[m3],SectL(m3,m1)]);
     22: AProc([Cell3D[m1],Cell3D[m3],SectH(m3,0),SectH(m1,0)]);
     23: AProc([Cell3D[m3],SectH(m3,m1),SectH(m3,0)]);
     24: AProc([SectL(m1,0),SectL(m1,m3),SectH(m3,m1),SectH(0,m1)]);
     25: AProc([Cell3D[m1],SectH(m1,0),SectH(m1,m3)]);
    end;
  end;
end;

function TLevelDifs.Prepare(V0,V1,V2,V3:Integer; Is2D:Boolean):Boolean;
var t : Integer;
begin
  { get Y value for each grid cell corner }
  tmp1:=IY[V0];
  tmp2:=IY[V3];
  tmp3:=IY[V1];
  tmp4:=IY[V2];

  { calc min and max Y value of the 4 cell corners }
  DiffMin:=tmp1;
  if tmp2<DiffMin then DiffMin:=tmp2;
  if tmp3<DiffMin then DiffMin:=tmp3;
  if tmp4<DiffMin then DiffMin:=tmp4;

  if DiffMin<=MaxLevel then { if Min inside all levels }
  begin
    DiffMax:=tmp1;
    if tmp2>DiffMax then DiffMax:=tmp2;
    if tmp3>DiffMax then DiffMax:=tmp3;
    if tmp4>DiffMax then DiffMax:=tmp4;

    result:=DiffMax>=MinLevel; { if Max inside all levels }

    if result then
    begin
      { calc cell screen coordinates for Z }
      Cell[1].Y:=IZ[V0];
      Cell[3].Y:=IZ[V3];
      Cell[2].Y:=IZ[V0];
      Cell[4].Y:=IZ[V3];

      Cell[0].Y:=(Cell[1].Y+Cell[3].Y) * 0.5;  { mid Z pos }

      { calc cell screen coordinates for X }
      Cell[1].X:=IX[V0];
      Cell[2].X:=IX[V1];
      Cell[3].X:=IX[V1];
      Cell[4].X:=IX[V0];

      Cell[0].X:=(Cell[1].X+Cell[2].X) * 0.5;  { mid X pos }

      if not Is2D then
      begin
        for t:=0 to 4 do
        begin
          Cell3D[t].X:=Cell[t].X;
          Cell3D[t].Y:=Cell[t].Y;
        end;

        Cell3D[1].Z:=tmp1;
        Cell3D[2].Z:=tmp3;
        Cell3D[3].Z:=tmp4;
        Cell3D[4].Z:=tmp2;

        Cell3D[0].Z:=0.25*(tmp1+tmp2+tmp3+tmp4);
      end;
    end;
  end
  else
    result:=False;
end;

// (Bottleneck procedure)

// For each calculated line (x1z1->x2z2) in level,
// try to merge it with other lines to define "segments".
// For each segment, try to merge it with other segments to
// construct a longer (and if possible, unique) segment.
procedure TContourSeries.AddSegmentLine(const A,B:TLevelPoint);
const
  Tolerance=0.0001;

  // After adding a new point to a segment, check if this segment
  // should be "connected" to another existing segment.
  procedure CheckOtherSegments(NumSegment,AtPos:Integer);
  var t  : Integer;
      tt : Integer;
      P  : TLevelPoint;
      tmpNumPoints : Integer;
      tmpLength    : Integer;
      tmpP         : TLevelPoint;
  begin
    with TheLevel do
    begin
      P:=FSegments[NumSegment].Points[AtPos];
      tmpLength:=FSegments[NumSegment].Count;

      for t:=0 to FSegmentCount-1 do
      if t<>NumSegment then
      with FSegments[t] do
      begin
        tmpNumPoints:=Count;

        tmpP.X:=Points[0].X;
        tmpP.Y:=Points[0].Y;

        if (Abs(P.X-tmpP.X)<=Tolerance) and (Abs(P.Y-tmpP.Y)<=Tolerance) then
        begin
          with FSegments[NumSegment] do
          begin
            Count:=tmpLength+tmpNumPoints-1;
            SetLength(Points,Count);
          end;

          if AtPos<>0 then
          begin
            for tt:=1 to tmpNumPoints-1 do
                FSegments[NumSegment].Points[tmpLength+tt-1]:=Points[tt];
          end
          else
          begin
            with FSegments[NumSegment] do
            for tt:=tmpLength-1 downto 0 do
                Points[tt+tmpNumPoints-1]:=Points[tt];

            for tt:=0 to tmpNumPoints-2 do
                FSegments[NumSegment].Points[tt]:=Points[tmpNumPoints-1-tt];
          end;

          DeleteSegment(t);
          break;
        end
        else
        begin
          tmpP.X:=Points[tmpNumPoints-1].X;
          tmpP.Y:=Points[tmpNumPoints-1].Y;

          if (Abs(P.X-tmpP.X)<=Tolerance) and (Abs(P.Y-tmpP.y)<=Tolerance) then
          begin
            with FSegments[NumSegment] do
            begin
              Count:=tmpLength+tmpNumPoints-1;
              SetLength(Points,Count);
            end;

            if AtPos<>0 then
            begin
              for tt:=0 to tmpNumPoints-2 do
                  FSegments[NumSegment].Points[tmpLength+tt]:=Points[tmpNumPoints-2-tt];
            end
            else
            begin
              with FSegments[NumSegment] do
              for tt:=tmpLength-1 downto 0 do
                  Points[tt+tmpNumPoints-1]:=Points[tt];

              for tt:=0 to tmpNumPoints-2 do
                  FSegments[NumSegment].Points[tt]:=Points[tt];
            end;

            DeleteSegment(t);
            break;
          end;
        end;
      end;
    end;
  end;

  procedure DoInsert(ASegment,AIndex:Integer; const P:TLevelPoint);
  begin
    with TheLevel.FSegments[ASegment] do
    begin
      Inc(Count);
      SetLength(Points,Count);

      if AIndex=0 then
         // Calling Move its 25% faster:
         System.Move(Points[0],Points[1],SizeOf(TLevelPoint)*(Count-1))
      else
         AIndex:=Count-1;

      Points[AIndex]:=P;
    end;

    if TheLevel.FSegmentCount>1 then
       CheckOtherSegments(ASegment,AIndex);
  end;

var t : Integer;
    tmp : TLevelPoint;
begin
  with TheLevel do
  begin
    // Find appropiate segment for point, if any
    for t:=0 to FSegmentCount-1 do
    with FSegments[t] do
    begin
      tmp:=Points[0];

      if (Abs(A.X-tmp.x)<=Tolerance) and (Abs(A.Y-tmp.y)<=Tolerance) then
      begin
        DoInsert(t,0,B);
        Exit;
      end
      else
      if (Abs(B.X-tmp.x)<=Tolerance) and (Abs(B.Y-tmp.y)<=Tolerance) then
      begin
        DoInsert(t,0,A);
        Exit;
      end
      else
      begin
        tmp:=Points[Count-1];

        if (Abs(A.X-tmp.x)<=Tolerance) and (Abs(A.Y-tmp.y)<=Tolerance) then
        begin
          DoInsert(t,Count-1,B);
          Exit;
        end
        else
        if (Abs(B.X-tmp.x)<=Tolerance) and (Abs(B.Y-tmp.y)<=Tolerance) then
        begin
          DoInsert(t,Count-1,A);
          Exit;
        end;
      end;
    end;

    // Creates a new segment, initializes it with two points (x1z1 and x2z2)
    AddSegment(A,B);
  end;
end;

function TContourSeries.CreateIso:TIsoSurfaceSeries;
begin
  result:=TIsoSurfaceSeries.Create(Self);
end;

procedure TContourSeries.DrawFrame(const YPos:Integer);
var R : TRect;
begin
  ParentChart.Canvas.Brush.Style:=bsClear;
  ParentChart.Canvas.BackMode:=cbmTransparent;
  ParentChart.Canvas.AssignVisiblePen(Frame);

  With GetHorizAxis do
  begin
    R.Left:=CalcPosValue(MinXValue);
    R.Right:=CalcPosValue(MaxXValue);
  end;

  With GetZAxis do
  begin
    R.Top:=CalcPosValue(MinZValue);
    R.Bottom:=CalcPosValue(MaxZValue);
  end;

  if ParentChart.View3D then
     ParentChart.Canvas.RectangleY(R.Left,YPos,R.Right,{$IFDEF FMX}Round{$ENDIF}(R.Top),{$IFDEF FMX}Round{$ENDIF}(R.Bottom))
  else
     ParentChart.Canvas.Rectangle(R);
end;

function TContourSeries.MarkOverlaps(const ABounds:TRect):Boolean;
var t : Integer;
    tmp : TSeriesMarkPosition;
begin
  with Marks.Positions do
  for t:=0 to Count-1 do
  begin
    tmp:=Position[t];

    if tmp<>nil then
       if TeeRectIntersects(tmp.Bounds,ABounds) then
       begin
         result:=True;
         Exit;
       end;
  end;

  result:=False;
end;

function Hypot(const P1,P2:TPointFloat):Single;
begin
  result:=Sqrt(Sqr(P1.X-P2.X)+Sqr(P1.Y-P2.Y));
end;

procedure TContourSeries.DrawAllValues;
var
  tmpY : Integer; // Current Level Y position in pixels

  procedure MoveToPoint(const APoint:TPointFloat);
  begin
    if ParentChart.View3D then
       ParentChart.Canvas.MoveTo3D(APoint.X,tmpY,APoint.Y)
    else
       ParentChart.Canvas.MoveTo(APoint);
  end;

  procedure LineToPoint(const APoint:TPointFloat);
  begin
    if ParentChart.View3D then
       ParentChart.Canvas.LineTo3D(APoint.X,tmpY,APoint.Y)
    else
       ParentChart.Canvas.LineTo(APoint);
  end;

type
  TTempLevel=record { temporary cache of level properties }
    Level     : TContourLevel;
    UpToValue : TChartValue;
    Color     : TColor;
    LineColor : TColor;
    Pen       : TChartPen;
  end;

var
  ILevels      : Array of TTempLevel;
  tmpNumLevels : Integer; { speed opt. }

  Procedure DrawLevelLines;
  var P : {$IFDEF FMX}TPointArray{$ELSE}TeCanvas.TPointFloatArray{$ENDIF};

    procedure DrawPointers(const Color,LineColor:TColor);
    var t : Integer;
    begin
      Pointer.PrepareCanvas(ParentChart.Canvas,Color);

      if ParentChart.View3D then
         for t:=0 to High(P) do
         begin
           StartZ:=Round(P[t].Y);
           EndZ:=StartZ;
           MiddleZ:=EndZ;
           Pointer.Draw(Round(P[t].X),tmpY);
         end
      else
         for t:=0 to High(P) do
             Pointer.Draw(Round(P[t].X),Round(P[t].Y));

      ParentChart.Canvas.AssignVisiblePenColor(Pen,LineColor);
    end;

  var
    Position   : TSeriesMarkPosition;

    procedure DrawLevelMark(X,Y,Angle:Single; const Text:String; Color:TColor;
                            const APen:TTeePen; const APenColor:TColor);
    var oldAng   : Integer;
        oldColor : TColor;
        tmpPos   : TSeriesMarkPosition;
    begin
      if ParentChart.View3D then
      begin
        Marks.ZPosition:=Round(Y);
        Y:=tmpY;
      end;

      Marks.Positions.MoveTo(Position,Round(X),Round(Y));

      if (not FContourMarks.AntiOverlap) or (not MarkOverlaps(Position.Bounds)) then
      begin
        oldAng:=Marks.Angle;
        Marks.Angle:=oldAng+Round(Angle);

        oldColor:=Marks.Font.Color;
        if FContourMarks.ColorLevel then
           Marks.Font.Color:=Color;

        Marks.DrawItem(Marks,Color,Text,Position);

        if FContourMarks.AntiOverlap then
        begin
          tmpPos:=TSeriesMarkPosition.Create;
          tmpPos.Assign(Position);
          Marks.Positions.Add(tmpPos);
        end;

        Marks.Angle:=oldAng;
        if FContourMarks.ColorLevel then
           Marks.Font.Color:=oldColor;

        ParentChart.Canvas.AssignVisiblePenColor(APen,APenColor);
      end;
    end;

    var
      tmpDrawMarks : Boolean;
      tmpSt    : String;
      tmpZAxis : TChartAxis; // cached

    // Special draw marks at single values (top and bottom levels)
    procedure DrawSingleMark(const Value:TChartValue; const Color:TColor);
    var t  : Integer;
        PX : Integer;
        PZ : Integer;
    begin
      for t:=0 to Count-1 do
      if SameValue(MandatoryValueList.Value[t],Value) then
      begin
        PX:=GetHorizAxis.CalcXPosValue(XValues.Value[t]);
        PZ:=tmpZAxis.CalcPosValue(ZValues.Value[t]);

        if Pointer.Visible then
        begin
          SetLength(P,1);
          P[0].X:=PX;
          P[0].Y:=PZ;

          DrawPointers(Color,Color);

          P:=nil;
        end
        else
        if ParentChart.View3D then
           ParentChart.Canvas.Pixels3D[PX,tmpY,PZ]:=Color
        else
           ParentChart.Canvas.Pixels[PX,PZ]:=Color;

        if tmpDrawMarks then
           DrawLevelMark(PX,PZ,0,tmpSt,Color,Pen,Color);
      end;
    end;

    procedure FillLevels;
    var t : Integer;
    begin
      if not Assigned(IFillSeries) then
      begin
        IFillSeries:=CreateIso;
        IFillSeries.HideCells:=False; // <-- Faster
        IFillSeries.UseColorRange:=False;
        IFillSeries.INoRandom:=True; // <-- avoid fillsamplevalues when setting ParentChart
        IFillSeries.ManualData:=True;
        IFillSeries.FastBrush:=True;
      end;

      //DB removed. If Self.DataSource<>nil, source gets assigned to tmpIso.
      //tmpIso.Assign(Self);
      //tmpIso.DataSource:=nil;

      // DB Changed to Assign only axes and Values:
      IFillSeries.HorizAxis:=HorizAxis;
      IFillSeries.CustomHorizAxis:=CustomHorizAxis;
      IFillSeries.VertAxis:=VertAxis;
      IFillSeries.CustomVertAxis:=CustomVertAxis;
      IFillSeries.IrregularGrid:=IrregularGrid;

      // Set values directly, instead of calling AssignValues (speed reason):


      IFillSeries.XValues.Value:=XValues.Value;
      IFillSeries.XValues.Count:=Count;

      IFillSeries.YValues.Value:=YValues.Value;
      IFillSeries.YValues.Count:=Count;

      IFillSeries.ZValues.Value:=ZValues.Value;
      IFillSeries.ZValues.Count:=Count;

      IFillSeries.FNumXValues:=NumXValues;
      IFillSeries.FNumZValues:=NumZValues;

      IFillSeries.GridIndex:=GridIndex;

      //IFillSeries.AssignValues(Self);

      // Set palette to match:

      // Use internal FPaletteSteps etc to avoid "CreateDefaultPalette"
      IFillSeries.FPaletteSteps:=NumLevels;
      SetLength(IFillSeries.FPalette,NumLevels);

      for t:=0 to NumLevels-1 do
      begin
        IFillSeries.FPalette[t].UpToValue:=ILevels[t].UpToValue;
        IFillSeries.FPalette[t].Color:=ILevels[t].Color;
      end;

      // Other attributes:

      IFillSeries.UseYPosition:=not FYPositionLevel;
      IFillSeries.YPosition:=FYPosition;

      if Assigned(FCellPen) then
         IFillSeries.Pen:=FCellPen
      else
         IFillSeries.Pen.Hide;

      IFillSeries.BandPen.Hide;
      IFillSeries.Transparency:=Transparency;

      IFillSeries.FPaletteStyle:=FPaletteStyle;
      IFillSeries.CalcColorRange;

      // Draw the Iso surface:
      TAxisPanelAccess(ParentChart).DoNotBroadcast:=True;

      // Use protected FParent access to avoid being added to ParentChart.SeriesList
      TSeriesAccess(IFillSeries).FParent:=ParentChart;
      try
        IFillSeries.Added;
        IFillSeries.DrawAllValues;
      finally
        TSeriesAccess(IFillSeries).FParent:=nil;
        TAxisPanelAccess(ParentChart).DoNotBroadcast:=False;
      end;
    end;

    procedure DrawSingleLevelWithMarks(const ASize:TPoint; const APen:TTeePen; const APenColor:TColor);
    var tmpTotWidth : TCoordinate;
        tt : Integer;
        tmpStart : Integer;
        tmpMarks : Integer;
        tmpLength : Integer;
        tmpLen   : Single;
        tmpDist  : Single;
        ttt      : Integer;
        tmpC     : TPointFloat;
        tmpAngle : Single;
        tmpAng   : Single;
    begin
      tmpTotWidth:=ASize.X+(2*FContourMarks.Margin);
      MoveToPoint(P[0]);

      tt:=1;
      tmpMarks:=0;
      tmpStart:=0;

      tmpLength:=Length(P);

      while tt<tmpLength do
      begin
        tmpLen:=Hypot(P[tt],P[tmpStart]);

        if tmpLen>tmpTotWidth then
        begin
          tmpDist:=Hypot(P[tt],P[tt-1]);

          Inc(tmpMarks);

          if tmpMarks mod FContourMarks.Density=0 then
          begin
            if tmpDist>tmpTotWidth then
            begin
              for ttt:=tmpStart+1 to tt-1 do
                  LineToPoint(P[ttt]);

              tmpStart:=tt-1;
              tmpLen:=tmpDist;
            end;

            if tmpStart=tt-1 then
               LineToPoint(PointAtDistance(P[tt],P[tt-1],(tmpDist-tmpTotWidth)*0.5));

            //TODO: Half tmpLen might not be the right center place if
            // points are too curved
            tmpC:=PointAtDistance(P[tt],P[tmpStart],tmpLen*0.5);

            tmpAngle:=ArcTan2(P[tt].X-P[tmpStart].X,P[tt].Y-P[tmpStart].Y)+Pi*0.5;

            tmpAng:=tmpAngle*HalfDivPi;
            if tmpAng<0 then tmpAng:=360+tmpAng;

            if (tmpAng>90) and (tmpAng<270) then
            begin
              tmpAng:=tmpAng+180;

              if tmpAng>360 then
                  tmpAng:=tmpAng-360;
            end;

            DrawLevelMark(tmpC.X,tmpC.Y+ASize.Y*0.5,tmpAng,tmpSt,Color,APen,APenColor);

            if tmpDist>=tmpTotWidth then
               MoveToPoint(PointAtDistance(P[tt],P[tt-1],(tmpDist+tmpTotWidth)*0.5))
            else
               MoveToPoint(P[tt]);
          end
          else
          for ttt:=tmpStart to tt do
              LineToPoint(P[ttt]);

          tmpStart:=tt;
        end;

        Inc(tt);
      end;

      for ttt:=tmpStart to tmpLength-1 do
          LineToPoint(P[ttt]);
    end;

    procedure DrawPolyline3D;
    var t : Integer;
    begin
      ParentChart.Canvas.MoveTo3D(P[0].X,tmpY,P[0].Y);

      for t:=1 to High(P) do
          ParentChart.Canvas.LineTo3D(P[t].X,tmpY,P[t].Y);
    end;

  var TheLevel : Integer;
      t        : Integer;
      tmp      : Integer;
      tmpSize  : TPoint;
      tmpBlend     : TTeeBlend;
      tmpColor : TColor;
      tmpPen : TTeePen;
  begin
    if Transparency>0 then
       tmpBlend:=ParentChart.Canvas.BeginBlending(TeeZeroRect,Transparency)
    else
       tmpBlend:=nil;

    P:=nil;

    tmpY:=GetVertAxis.CalcYPosValue(FYPosition);

    Position:=TSeriesMarkPosition.Create;

    ParentChart.Canvas.BackMode:=cbmTransparent;

    // Fill contour cells:
    if Filled then
       FillLevels;

    tmpZAxis:=GetZAxis;

    Marks.Positions.Clear;

    { for each level, draw lines }
    for TheLevel:=0 to tmpNumLevels-1 do
    with ILevels[TheLevel] do
    begin
      { calculate Y position of the Level Up value }
      if FYPositionLevel then
         tmpY:=GetVertAxis.CalcYPosValue(UpToValue);

      tmpDrawMarks:=Marks.Visible and FContourMarks.AtSegments and
                    (TheLevel mod Marks.DrawEvery=0);

      if tmpDrawMarks then
      begin
        // Get mark text:
        case Marks.Style of
          smsSeriesTitle: tmpSt:=SeriesTitleOrName(Self);
           smsPointIndex: tmpSt:=IntToStr(TheLevel);
        else
          tmpSt:=FormatFloat(ValueFormat,UpToValue);
        end;

        if Assigned(OnGetMarkText) then
           OnGetMarkText(Self,TheLevel,tmpSt);

        // Get mark size:
        CalculateMarkPosition(Marks,tmpSt,0,0,Position);
        tmpSize.X:=Position.Width;
        tmpSize.Y:=Position.Height;
      end;

      if PrepareHoverPen(TheLevel) then
      begin
        tmpPen:=Selected.Hover.Pen;
        tmpColor:=tmpPen.Color;
      end
      else
      begin
        ParentChart.Canvas.AssignVisiblePenColor(Pen,LineColor);
        tmpPen:=Pen;
        tmpColor:=LineColor;
      end;

      ParentChart.Canvas.Brush.Style:=bsClear;

      { call event just before drawing, allow changing pen }
      if Assigned(FOnBeforeDrawLevel) then
         FOnBeforeDrawLevel(Self,TheLevel);

      tmp:=Level.FSegmentCount-1;

      if ((TheLevel=0) or (TheLevel=tmpNumLevels-1)) and (tmp=-1) and
         (Pointer.Visible or tmpDrawMarks) then
            DrawSingleMark(UpToValue,Color);

      for t:=0 to tmp do
      begin
        {$IFDEF FMX}
        P:=Level.GetSegmentPoints(t);

        if Smoothing.Active then
           P:=Smoothing.Calculate(P);

        {$ELSE}
        P:=Level.GetSegmentPointsFloat(t);

        if Smoothing.Active then
           P:=Smoothing.CalculateFloat(P);
        {$ENDIF}

        if tmpDrawMarks then
           DrawSingleLevelWithMarks(tmpSize,tmpPen,tmpColor)
        else
        if ParentChart.View3D then
           DrawPolyline3D
        else
        {$IFDEF FMX}
           ParentChart.Canvas.Polyline(P);
        {$ELSE}
           ParentChart.Canvas.PolylineFloat(P);
        {$ENDIF}

        if Pointer.Visible then
           DrawPointers(Color,LineColor);

        //P:=nil;
      end;
    end;

    Position.Free;

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

  procedure PrepareLevels;
  var TheLevel : Integer;
      tmpRealCount : Integer;
      tmpStop : Boolean;
      tmpAlpha : Byte;
  begin
    // Flags to detect repeated levels with same UpToValue:
    tmpRealCount:=tmpNumLevels;
    tmpStop:=False;

    { store cache of levels }
    for TheLevel:=0 to tmpNumLevels-1 do
    begin
      with ILevels[TheLevel] do
      begin
        Level:=Levels[TheLevel];
        UpToValue:=Level.UpToValue;
        Color:=Level.InternalColor;

        Pen:=Level.InternalPen;

        if Filled then
        begin
          LineColor:=LinesColor;

          if LineColor=clTeeColor then
             LineColor:=Color;
        end
        else
           LineColor:=Color;

        // Take into account possible Pen.Transparency:
        tmpAlpha:=TTeeCanvas.ColorAlpha(Pen.Color);

        if tmpAlpha>0 then
           LineColor:=TTeeCanvas.ColorFrom(LineColor,tmpAlpha);
      end;

      // Optimization. Detect repeated levels (with same UpToValue)
      // This might happen when adding all points with the same Y value
      // (see http://bugs.teechart.net/show_bug.cgi?id=124)

      if (not tmpStop) and (TheLevel>0) and (ILevels[TheLevel].UpToValue=ILevels[TheLevel-1].UpToValue) then
      begin
        tmpRealCount:=TheLevel;
        tmpStop:=True;
      end;
    end;

    tmpNumLevels:=tmpRealCount;
  end;

  procedure CalculateLevels;
  var Difs : TLevelDifs;

    procedure CalcCellLevels;
    var TheLevel : Integer;
        tmpValue : TChartValue;
    begin
      // Any null cell should not be evaluated
      if Assigned(FColors) and // <-- speed optimization
         (IsNull(ValueIndex0) or
          IsNull(ValueIndex1) or
          IsNull(ValueIndex2) or
          IsNull(ValueIndex3)) then
           Exit;

        if Difs.Prepare(ValueIndex0,ValueIndex1,ValueIndex2,ValueIndex3,True) then
        begin
          for TheLevel:=0 to tmpNumLevels-1 do { for each level }
          begin
            tmpValue:=ILevels[TheLevel].UpToValue;

            if (tmpValue>=Difs.DiffMin) and (tmpValue<=Difs.DiffMax) then { if inside one level }
            begin
              { calculate all lines for one level in this XZ cell }
              Self.TheLevel:=ILevels[TheLevel].Level;
              Difs.Loop(tmpValue,AddSegmentLine);
            end;
          end;
        end;
    end;

  var t,
      x,z : Integer;
  begin
    // Remove previously calculated Level Segments
    for t:=0 to FLevels.Count-1 do
        FLevels[t].ClearSegments;

    { prepare default params }
    INextXCell:=1;
    INextZCell:=1;

    Difs:=TLevelDifs.Create(ILevels[0].UpToValue,ILevels[tmpNumLevels-1].UpToValue,Self);
    try
      for z:=1 to NumZValues-1 do { for each cell }
      begin
        for x:=1 to NumXValues-1 do
        if ExistFourGridIndex(x,z) then  { if xz values exist }
           CalcCellLevels;
      end;
    finally
      Difs.Free;
    end;
  end;

begin
  tmpNumLevels:=NumLevels;

  if (Count>0) and (tmpNumLevels>0) then
  begin
    SetLength(ILevels,tmpNumLevels);
    try
      PrepareLevels;

      if IDirtySegments then
      begin
        CalculateLevels;
        IDirtySegments:=False;
      end;

      if GetVertAxis<>nil then
         DrawLevelLines;

      if Frame.Visible then
         DrawFrame(tmpY);
    finally
      ILevels:=nil;
    end;
  end;
end;

procedure TContourSeries.AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False);
begin
  inherited;
  FYPosition:=0.5*(YValues.MaxValue+YValues.MinValue); { mid vertical pos }

  TryCreateLevels;
end;

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

Function TContourSeries.CalcLegendIndex(LegendIndex:Integer):Integer;
begin
//  result:=FLegendEvery*LegendIndex;
  result:=NumLevels-(FLegendEvery*LegendIndex)-1;
end;

Function TContourSeries.CountLegendItems:Integer;
begin
  result:=FLevels.Count div FLegendEvery;
end;

Function TContourSeries.LegendItemColor(LegendIndex:Integer):TColor;
begin
  // inverted legend
  result:=Levels[{CalcLegendIndex}(LegendIndex)].InternalColor;
end;

Function TContourSeries.LegendString( LegendIndex:Integer;
                                      LegendTextStyle:TLegendTextStyle ):String;
var tmpLevel : TContourLevel;
begin
  tmpLevel:=Levels[CalcLegendIndex(LegendIndex)];
  result:=CalcLegendString(LegendTextStyle,tmpLevel.Text,tmpLevel.UpToValue,tmpLevel.Index);
end;

Function TContourSeries.LegendToValueIndex(LegendIndex:Integer):Integer;
begin
  result:=CalcLegendIndex(LegendIndex)
end;

Function TContourSeries.MaxYValue:TChartValue;
begin
  if ParentChart.View3D then result:=inherited MaxYValue
                        else result:=ZValues.MaxValue;
end;

Function TContourSeries.MinYValue:TChartValue;
var tmp : TCustomAxisPanel;
begin
  tmp:=ParentChart;

  if (tmp<>nil) and tmp.View3D then
     result:=inherited MinYValue
  else
     result:=ZValues.MinValue;
end;

Procedure TContourSeries.CreateAutoLevels; // Calculate all Level values and colors
Var t        : Integer;
    tmp      : Double;
    tmpMin   : Double;
    tmpUpTo  : Double;
    tmpColor : TColor;
    Old      : Boolean;
    tmpLevel : TContourLevel;
begin
  if AutomaticLevels and (NumLevels>0) then
  begin
    IModifyingLevels:=True;
    try
      if Assigned(ParentChart) then
      begin
        Old:=ParentChart.AutoRepaint;
        ParentChart.AutoRepaint:=False;
      end
      else Old:=False;

      if FLevels.Count<>NumLevels then
         FLevels.Clear;

      tmp:=MandatoryValueList.Range/Max(1,NumLevels-1); // v8 changed to include max value

      tmpMin:=MandatoryValueList.MinValue;

      for t:=0 to NumLevels-1 do
      begin
        tmpUpTo:=tmpMin+tmp*t;

        if ColorEachPoint then
           tmpColor:=clTeeColor
        else
           tmpColor:=GetValueColorValue(tmpUpTo);

        if FLevels.Count<>NumLevels then
           tmpLevel:=FLevels.AddLevel(tmpUpTo,tmpColor)
        else
        begin
          tmpLevel:=FLevels.Get(t);

          if Assigned(tmpLevel.FPen) then
             tmpLevel.FColor:=tmpLevel.FPen.Color
          else
             tmpLevel.FColor:=tmpColor;

          tmpLevel.FUpTo:=tmpUpTo;
        end;

        if Assigned(FOnGetLevel) then
           FOnGetLevel(Self,t,tmpLevel.FUpTo,tmpLevel.FColor);

        if Assigned(tmpLevel.FPen) then
           tmpLevel.FPen.Color:=tmpLevel.FColor;
      end;

      if Assigned(ParentChart) then
         ParentChart.AutoRepaint:=Old;
    finally
      IModifyingLevels:=False;
    end;
  end;
end;

Procedure TContourSeries.DoBeforeDrawChart;
begin
  inherited;
  TryCreateLevels;
end;

function TContourSeries.IsLevelsStored: Boolean;
begin
  result:=not AutomaticLevels;
end;

procedure TContourSeries.SetLegendLines(const Value: Boolean);
begin
  SetBooleanProperty(FLegendLines, Value);
end;

procedure TContourSeries.SetLevels(const Value: TContourLevels);
begin
  FLevels.Assign(Value);
end;

procedure TContourSeries.SetLinesColor(const Value: TColor);
begin
  SetColorProperty(FLinesColor,Value);       
end;

procedure TContourSeries.SetAutomaticLevels(const Value: Boolean);
begin
  SetBooleanProperty(FAutomaticLevels,Value);
  if AutomaticLevels then
     ClearAutoLevels;
end;

function TContourSeries.GetCellPen:TChartHiddenPen;
begin
  if not Assigned(FCellPen) then
     FCellPen:=TChartHiddenPen.Create(CanvasChanged);

  result:=FCellPen;
end;

procedure TContourSeries.SetCellPen(const Value:TChartHiddenPen);
begin
  if Assigned(Value) then
     CellPen.Assign(Value)
  else
     FreeAndNil(FCellPen);
end;

// Filled property exists as a nice easy-to-remember way to set Brush.Style,
// and also because Brush.Style default is bsSolid, and so bsClear will not get
// persisted into the DFM / FMX:
function TContourSeries.GetFilled:Boolean;
begin
  result:=Brush.Style<>bsClear;
end;

function TContourSeries.GetNumLevels: Integer;
begin
  if AutomaticLevels then
     result:=FNumLevels
  else
     result:=Levels.Count;
end;

class procedure TContourSeries.CreateSubGallery(
  AddSubChart: TChartSubGalleryProc);
begin
  inherited;
  AddSubChart(TeeMsg_Colors);
  AddSubChart(TeeMsg_Positions);
  AddSubChart(TeeMsg_Filled);
  AddSubChart(TeeMsg_Marks);
end;

class procedure TContourSeries.SetSubGallery(ASeries: TChartSeries;
  Index: Integer);
begin
  with TContourSeries(ASeries) do
  Case Index of
    2: ColorEachPoint:=True;
    3: YPositionLevel:=True;
    4: Filled:=True;
    5: Marks.Visible:=True;
  else inherited;
  end
end;

function TContourSeries.ValueIndexToLegend(ValueIndex:Integer):Integer;
begin
  result:=CalcLegendIndex(ValueIndex);
end;

function TContourSeries.Clicked(x, y: Integer): Integer;
var SegmentIndex : Integer;
    PointIndex   : Integer;
begin
  result:=Levels.Clicked(x,y,SegmentIndex,PointIndex);
end;

{ returns "Z" axis, depending on 2D or 3D }
function TContourSeries.GetZAxis: TChartAxis;
begin
  if ParentChart.View3D then result:=ParentChart.DepthAxis
                        else result:=GetVertAxis;
end;

procedure TContourSeries.SetFilled(const Value: Boolean);
begin
  if Value then
     Brush.Style:=bsSolid
  else
  begin
    Brush.Style:=bsClear;
    Brush.Image:=nil;
  end;
end;

procedure TContourSeries.SetSmoothing(const Value: TSmoothPoints);
begin
  FSmoothing.Assign(Value);
end;

{ TContourLevel }

type
  TOwnedCollectionAccess=class(TOwnedCollection);

Constructor TContourLevel.Create(Collection: TCollection);
begin
  inherited;

  ISeries:=TContourSeries(TOwnedCollectionAccess(Collection).GetOwner);
  CheckAuto;
end;

Destructor TContourLevel.Destroy;
begin
  CheckAuto;

  With ISeries do
       if not AutomaticLevels then
          Repaint;

  FPen.Free;
  ClearSegments;

  inherited;
end;

function TContourLevel.InternalColor:TColor;
begin
  if Color=clTeeColor then
     if ISeries.UseColorRange or ISeries.UsePalette then
        result:=ISeries.GetValueColorValue(UpToValue)
     else
     if ISeries.ColorEachPoint then
        result:=ISeries.ParentChart.GetDefaultColor(Index)
     else
        result:=ISeries.Color
  else
     result:=Color;
end;

Procedure TContourLevel.ClearSegments;
var t : Integer;
begin
  for t:=0 to FSegmentCount-1 do
      FSegments[t].Points:=nil;

  FSegmentCount:=0;
  FSegments:=nil;
end;

Procedure TContourLevel.AddSegment;
begin
  Inc(FSegmentCount);
  SetLength(FSegments,FSegmentCount);

  with FSegments[FSegmentCount-1] do
  begin
    SetLength(Points,2);
    Count:=2;
    Points[0]:=A;
    Points[1]:=B;
  end;
end;

Procedure TContourLevel.CheckAuto;
begin
  With ISeries do
  if not IModifyingLevels then
     FAutomaticLevels:=False;
end;

procedure TContourLevel.SetColor(const Value: TColor);
begin
  if FColor<>Value then
  begin
    ISeries.SetColorProperty(FColor,Value);
    if Assigned(FPen) then FPen.Color:=Color;
    CheckAuto;
  end;
end;

procedure TContourLevel.SetUpTo(const Value: Double);
begin
  if FUpTo<>Value then
  begin
    FUpTo:=Value;
    CheckAuto;

    ISeries.IDirtySegments:=True;
    ISeries.Repaint;
  end;
end;

procedure TContourLevel.Assign(Source: TPersistent);
begin
  if Source is TContourLevel then
  With TContourLevel(Source) do
  Begin
    Self.FColor  :=FColor;
    Self.FUpTo   :=FUpTo;

    if Assigned(FPen) then Pen.Assign(FPen)
                      else FreeAndNil(FPen);
  end
  else inherited;
end;

function TContourLevel.GetPen: TChartPen;
begin
  if not Assigned(FPen) then
  begin
    FPen:=ISeries.CreateChartPen;
    FPen.Color:=InternalColor;
  end;

  result:=FPen;
end;

function TContourLevel.IsPenStored: Boolean;
begin
  result:=not DefaultPen;
end;

function TContourLevel.DefaultPen: Boolean;
begin
  result:=not Assigned(FPen);
end;

procedure TContourLevel.SetPen(const Value: TChartPen);
begin
  if Assigned(Value) then Pen.Assign(Value)
  else
  begin
    FreeAndNil(FPen);
    ISeries.Repaint;
  end;
end;

procedure TContourLevel.SetText(const Value:String);
begin
  ISeries.SetStringProperty(FText,Value);
end;

function TContourLevel.InternalPen: TChartPen;
begin
  if Assigned(FPen) then result:=FPen
                    else result:=ISeries.Pen;
end;

function TContourLevel.Clicked(x, y: Integer; out SegmentIndex,
  PointIndex: Integer): Boolean;
var t : Integer;
begin
  for t:=0 to FSegmentCount-1 do
  if ClickedSegment(x,y,t,PointIndex) then
  begin
    SegmentIndex:=t;
    result:=True;
    exit;
  end;

  result:=False;
end;

function TContourLevel.ClickedSegment(x, y, SegmentIndex: Integer;
  out PointIndex: Integer): Boolean;
var P        : {$IFNDEF FMX}TeCanvas.{$ENDIF}TPointArray;
    tmp      : TPoint;
    t        : Integer;
begin
  result:=False;

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

  P:=GetSegmentPoints(SegmentIndex);
  tmp:=TeePoint(x,y);

  for t:=0 to High(P)-1 do
  if PointInLine(tmp,P[t],P[t+1],0) then
  begin
    PointIndex:=t;
    result:=True;
    break;
  end;

  P:=nil;
end;

// Remove "Index" segment
Procedure TContourLevel.DeleteSegment(const Index:Integer);
var t : Integer;
begin
  FSegments[Index].Points:=nil; // deallocate memory

  for t:=Index to FSegmentCount-2 do
      FSegments[t]:=FSegments[t+1];

  Dec(FSegmentCount);
  SetLength(FSegments,FSegmentCount);
end;

Function TContourLevel.GetSegmentPoints(SegmentIndex: Integer):{$IFNDEF FMX}TeCanvas.{$ENDIF}TPointArray;
var tmp : Integer;
    t   : Integer;
begin
  tmp:=FSegments[SegmentIndex].Count;
  SetLength(result,tmp);

  with FSegments[SegmentIndex] do
  for t:=0 to tmp-1 do
      result[t]:=ISeries.CalcXYPoint(Points[t]);
end;

Function TContourLevel.GetSegmentPointsFloat(SegmentIndex:Integer):TPointFloatArray;
var tmp : Integer;
    t   : Integer;
begin
  tmp:=FSegments[SegmentIndex].Count;
  SetLength(result,tmp);

  with FSegments[SegmentIndex] do
  for t:=0 to tmp-1 do
      result[t]:=ISeries.CalcXYPointFloat(Points[t]);
end;

function TContourLevel.SegmentCount:Integer; {$IFDEF 10}inline;{$ENDIF}
begin
  result:=FSegmentCount;
end;

procedure TContourSeries.SetFrame(const Value: TChartHiddenPen);
begin
  FFrame.Assign(Value);
end;

{ TContourLevels }

function TContourLevels.Contour:TContourSeries;
begin
  result:=TContourSeries(GetOwner);
end;

function TContourLevels.AddLevel(const Value:Double; const Color:TColor=clTeeColor):TContourLevel;
begin
  result:=TContourLevel(Add);
  result.FUpTo:=Value;
  result.FColor:=Color;

  Contour.IDirtySegments:=True;
end;

function TContourLevels.Last:TContourLevel;
begin
  if Count=0 then
     result:=nil
  else
     result:=Items[Count-1];
end;

function TContourLevels.Clicked(x, y: Integer; out SegmentIndex,
                                PointIndex: Integer): Integer;
var t : Integer;
begin
  for t:=0 to Count-1 do
  if Items[t].Clicked(x,y,SegmentIndex,PointIndex) then
  begin
    result:=t;
    exit;
  end;

  result:=-1;
end;

function TContourLevels.Get(Index: Integer): TContourLevel;
begin
  result:=TContourLevel(inherited Items[Index]);
end;

procedure TContourLevels.Put(Index: Integer; Const Value: TContourLevel);
begin
  Items[Index].Assign(Value);
  Contour.IDirtySegments:=True;
end;

function TContourSeries.CalcXYPoint(const P:TLevelPoint):TPoint;
begin
  result.x:=GetHorizAxis.CalcXPosValue(P.X);
  result.y:=GetZAxis.CalcPosValue(P.Y);
end;

function TContourSeries.CalcXYPointFloat(const P:TLevelPoint):TPointFloat;
begin
  result.x:=GetHorizAxis.CalcXPosValue(P.X);
  result.y:=GetZAxis.CalcPosValue(P.Y);
end;

procedure TContourSeries.TryCreateLevels;
begin
  if not IModifyingLevels then
     if AutomaticLevels and (NumLevels>0) then
        CreateAutoLevels;
end;

Function TContourSeries.GetMarkValue(ValueIndex:Integer):Double;
begin
  result:=Levels[ValueIndex].UpToValue;
end;

Procedure TContourSeries.GetPaletteBounds(out AMin,ARange:Double);
begin
  if Levels.Count>0 then
  begin
    AMin:=Levels[0].UpToValue;
    ARange:=Levels.Last.UpToValue-AMin;
  end
  else
    inherited;
end;

procedure TContourSeries.ChangedColors;
begin
  inherited;
  IDirtySegments:=True;
end;

procedure TContourSeries.NotifyColorChange(ValueIndex:Integer);
begin
  inherited;
  IDirtySegments:=True;
end;

procedure TContourSeries.NotifyValue(ValueEvent: TValueEvent;
  ValueIndex: Integer);
begin
  inherited;
  IDirtySegments:=True;
end;

Procedure TContourSeries.DrawLegendShape(ValueIndex:Integer; Const Rect:TRect);
var tmp : TChartPen;
begin
  if LegendLines and (not Filled) then
  begin
    if ValueIndex=-1 then
       ParentChart.Canvas.AssignVisiblePenColor(Pen,Color)
    else
    // legend inverted
    with Levels[ValueIndex] do
    begin
      tmp:=TChartPen.Create(nil);
      try
        tmp.Assign(InternalPen);
        tmp.EndStyle:=esFlat;
        ParentChart.Canvas.AssignVisiblePenColor(tmp,InternalColor);
      finally
        tmp.Free;
      end;
    end;

    with Rect do
         ParentChart.Canvas.DoHorizLine(Left,Right,(Top+Bottom) {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF});
  end
  else
  with ParentChart.Canvas do
  begin
    Brush.Style:=bsSolid;

    if ValueIndex=-1 then
       Brush.Color:=Color
    else
       Brush.Color:=LegendItemColor(ValueIndex);

    inherited;
  end;
end;

{ TWaterFallSeries }
Constructor TWaterFallSeries.Create(AOwner: TComponent);
Begin
  inherited;
  FWaterFall:=True;
  FHideCells:=False;
end;

procedure TWaterFallSeries.GalleryChanged3D(Is3D: Boolean);
begin
  inherited;
  ParentChart.View3D:=Is3D;
end;

class function TWaterFallSeries.GetEditorClass: String;
begin
  result:='TWaterFallEditor';
end;

class procedure TWaterFallSeries.SetSubGallery(ASeries: TChartSeries;
  Index: Integer);
begin
  with TWaterFallSeries(ASeries) do
  Case Index of
    6: WaterLines.Hide;
  else inherited;
  end;
end;

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

procedure TWaterFallSeries.Assign(Source: TPersistent);
begin
  inherited;
  FWaterFall:=True;
end;

{ TColorGridSeries }

Constructor TColorGridSeries.Create(AOwner:TComponent);
begin
  inherited;

  IDirtyBitmap:=True;

  DoCreateBitmap({$IFDEF FMX}1,1{$ELSE}0,0{$ENDIF});

  Marks.Callout.Length:=0;

  FFrame:=TChartHiddenPen.Create(CanvasChanged);

  FXGridEvery:=1;
  FZGridEvery:=1;

  FPlane3D:=cpZ;
end;

Destructor TColorGridSeries.Destroy;
begin
  FFrame.Free;
  FBitmap.Free;
  inherited;
end;

procedure TColorGridSeries.DoCreateBitmap(AWidth,AHeight:Integer);
begin
  FBitmap:=TBitmap.Create{$IFDEF FMX}(AWidth,AHeight){$ENDIF};

  {$IFNDEF FMX}
  if (AWidth*AHeight) > 0 then
     TeeSetBitmapSize(FBitmap,AWidth,AHeight);
  {$ENDIF}

  {$IFNDEF FMX}
  {$IFNDEF LCL}
  FBitmap.IgnorePalette:=True;
  {$ENDIF}
  FBitmap.PixelFormat:=TeePixelFormat;
  FBitmap.TransparentColor:=clWhite;
  {$ENDIF}
end;

procedure TColorGridSeries.AssignFormatting(Source:TChartSeries);
begin
  if Source is TColorGridSeries then
  with TColorGridSeries(Source) do
  begin
    Self.FCentered:=FCentered;
    Self.FSmoothBitmap:= FSmoothBitmap;
    Self.Frame:=Frame;
  end;

  inherited;
end;

procedure TColorGridSeries.Assign(Source:TPersistent);
begin
  if Source is TColorGridSeries then
  with TColorGridSeries(Source) do
  begin
    Self.FPlane3D:=FPlane3D;
    Self.FPosition:=FPosition;
  end;

  inherited;
end;

function TColorGridSeries.CellBounds(ValueIndex:Integer):TRect;
begin
  result:=CellBounds( Round(XValues.Value[ValueIndex]),
                      Round(ZValues.Value[ValueIndex]));
end;

function TColorGridSeries.CellBounds(x,z:Integer):TRect;
var tmpIndex : Integer;
    tmp      : TCoordinate;
begin
  tmpIndex:=GridIndex[x,z];

  With GetHorizAxis do
  begin
    result.Left:=CalcPosValue(CalcMinValue(XValues.Value[tmpIndex]));

    if (x<NumXValues) then
       result.Right:=CalcPosValue(CalcMinValue(XValues.Value[GridIndex[x+1,z]]))
    else
    if (NumXValues=1) then
      result.Right:=CalcPosValue(XValues.MaxValue+1)
    else
    begin
      if x>1 then
         tmp:=result.Left-CalcPosValue(CalcMinValue(XValues.Value[GridIndex[x-1,z]]))
      else
         tmp:=1;

      if CenteredPoints Then
         tmp:=tmp+1;

      result.Right:=result.Left+tmp;
    end;
  end;

  With GetVertAxis do
  begin
    result.Top:=CalcPosValue(CalcMinValue(ZValues.Value[tmpIndex]));

    if z<NumZValues then
       result.Bottom:=CalcPosValue(CalcMinValue(ZValues.Value[GridIndex[x,z+1]]))
    else if (NumZValues=1) then
       result.Bottom:=CalcPosValue(ZValues.MaxValue+1)
    else
    begin
      if z>1 then
         tmp:=result.Top-CalcPosValue(CalcMinValue(ZValues.Value[GridIndex[x,z-1]])) //+1
      else
         tmp:=1;

      result.Bottom:=result.Top+tmp;
    end;
  end;
end;

{$IFDEF FMX}
// Makes sure the R rectangle Left is smaller than Right and
// Top is smaller than Bottom. Returns corrected rectangle.
Function OrientRect(Const R:Types.TRect):Types.TRect;
var tmp : Integer;
begin
  result:=R; // ???? Rect(R.Left,R.Top,R.Right,R.Bottom); //mod to support GDI+

  with result do
  begin
    if Left>Right then
    begin
      tmp:=Left; Left:=Right; Right:=tmp;
    end;

    if Top>Bottom then
    begin
      tmp:=Top; Top:=Bottom; Bottom:=tmp;
    end;
  end;
end;
{$ENDIF}

procedure TColorGridSeries.DrawAllValues;
var tmpDec : Integer;

  Procedure FillBitmap(const tmpBounds:{$IFDEF FMX}Types.{$ENDIF}TRect; var HasNulls:Boolean);
  {$IFNDEF FMX}
  {$IFNDEF LCL}
  {$DEFINE USE_SCANLINE}
  {$ENDIF}
  {$ENDIF}

  {$IFDEF USE_SCANLINE}
  type
    TColors=Array[0..0] of TRGB;
    PColors=^TColors;
  {$ENDIF}

  var z        : Integer;
      x        : Integer;
      {$IFDEF USE_SCANLINE}
      P        : PColors;
      Dif      : TeeNativeInt;
      {$ENDIF}
      tmp      : TColor;
      tmpIndex : Integer;

      startXVal,
      endXVal,
      xIdxOffset  : Integer;

      startZVal,
      endZVal,
      zIdxOffset  : Integer;

      {$IFDEF FMX}
      {$IFDEF D17}
      tmpData : TBitmapData;
      {$ENDIF}
      {$ENDIF}
  begin
    HasNulls:=False;

    {$IFDEF USE_SCANLINE}
    P:=FBitmap.ScanLine[0];

    if FBitmap.Height>1 then
       Dif:=TeeNativeInt(FBitmap.ScanLine[1])-TeeNativeInt(P)
    else
       Dif:=0;
    {$ENDIF}

    if FXStartIndex=0 then
    begin
      startXVal:=0;
      xIdxOffset:=1;
      endXVal:=tmpBounds.Right-1-tmpDec;
    end
    else
    begin
      startXVal:=tmpBounds.Left;
      xIdxOffset:=0;
      endXVal:=tmpBounds.Right-tmpDec;
    end;

    if FZStartIndex=0 then
    Begin
      startZVal:=0;
      zIdxOffset:=1;
      endZVal:=tmpBounds.Bottom-1-tmpDec;
    end
    else
    Begin
      startZVal:=tmpBounds.Top;
      zIdxOffset:=0;
      endZVal:=tmpBounds.Bottom-tmpDec;
    end;

    {$IFDEF D24}
    {$IFDEF CPUX64}

    {$IFNDEF D25}
    tmpIndex:=-1; // To avoid warning
    {$ENDIF}

    {$ENDIF}
    {$ELSE}
    {$IFNDEF D23}
    //tmpIndex:=-1; // To avoid warning
    {$ENDIF}
    {$ENDIF}

    {$IFDEF FMX}
    {$IFDEF D17}
    if FBitmap.Map(TMapAccess.{$IFDEF D20}Write{$ELSE}maWrite{$ENDIF},tmpData) then
    begin
    {$ENDIF}
    {$ENDIF}

      {$IFNDEF D25}
      tmpIndex:=-1; // hint fix
      {$ENDIF}

      for z:=startZVal to endZVal do
      begin
        for x:=startXVal to endXVal do
        begin
          if (x<=-1) or (z<=-1) then
             exit
          else
             tmpIndex:=GridIndex[x+xIdxOffset,z+zIdxOffset];

          if tmpIndex<>-1 then
          begin
            tmp:=ValueColor[tmpIndex];

            if tmp=clNone then // null?
            begin
              tmp:={$IFDEF FMX}claWhite{$ELSE}clWhite{$ENDIF};
              HasNulls:=True;
            end;
          end
          else
          begin
            tmp:={$IFDEF FMX}claWhite{$ELSE}clWhite{$ENDIF};
            HasNulls:=True;
          end;

          {$IFNDEF USE_SCANLINE}
          {$IFDEF FMX}
          {$IFDEF D17}
          tmpData.SetPixel(X-tmpBounds.Left, Z-tmpBounds.Top,tmp);
          {$ELSE}
          FBitmap.Pixels[X-tmpBounds.Left, Z-tmpBounds.Top]:=tmp;
          {$ENDIF}
          {$ELSE}
          {$IFDEF MSWINDOWS}
          SetPixel(FBitmap.Canvas.Handle, X-tmpBounds.Left, Z-tmpBounds.Top, tmp);
          {$ELSE}
          FBitmap.Canvas.Pixels[X-tmpBounds.Left, Z-tmpBounds.Top]:=tmp;
          {$ENDIF}
          {$ENDIF}

          {$ELSE}

          {$IFOPT R+}
          {$DEFINE RANGEON}
          {$ENDIF}

          {$R-}
          With P^[x-startXVal] do
          begin
            Red  :=Byte(tmp);
            Green:=Byte(tmp shr 8);
            Blue :=Byte(tmp shr 16);
          end;

          {$IFDEF RANGEON}
          {$R+}
          {$ENDIF}

          {$ENDIF}
        end;

        {$IFDEF USE_SCANLINE}
        P:=PColors(PChar(TeeNativeInt(P)+Dif));
        {$ENDIF}
      end;

    {$IFDEF D17}
    {$IFDEF FMX}
      FBitmap.Unmap(tmpData);
    end;
    {$ENDIF}
    {$ENDIF}
  end;

  Function CalcDestRectangle(const tmpBounds:TRect):TRect;
  var IAxisSizeRange,
      MaxMin : Double;
  begin
    With GetHorizAxis do
    begin
      if FXStartIndex=0 Then
      Begin
        if Maximum-Minimum = 0 then MaxMin:=1 else MaxMin := Maximum-Minimum;
        IAxisSizeRange:=IAxisSize/MaxMin;
        Result.Left:=CalcPosValue(MinXValue);

        if Inverted then
           result.Right:=Round(Result.Left-(IAxisSizeRange*(MaxXValue-MinXValue)))
        else
           result.Right:=Round(Result.Left+(IAxisSizeRange*(MaxXValue-MinXValue)));
      end
      else
      begin
        result.Left:=CalcPosValue(Max(MinXValue,CalcMinValue(tmpBounds.Left)));
        result.Right:=CalcPosValue(Min(MaxXValue,CalcMaxValue(tmpBounds.Right)));
      end;
    end;

    With GetVertAxis do
    begin
      if FZStartIndex=0 Then
      Begin
        if Maximum-Minimum = 0 then MaxMin:=1 else MaxMin := Maximum-Minimum;
        IAxisSizeRange:=IAxisSize/(MaxMin);
        Result.Top:=CalcPosValue(MinZValue);

        if Inverted then
           result.Bottom:=Round(Result.Top+(IAxisSizeRange*(MaxZValue-MinZValue)))
        else
           result.Bottom:=Round(Result.Top-(IAxisSizeRange*(MaxZValue-MinZValue)));
      end
      else
      begin
        result.Top:=CalcPosValue(Max(MinZValue,CalcMinValue(tmpBounds.Top)));
        result.Bottom:=CalcPosValue(Min(MaxZValue,CalcMaxValue(tmpBounds.Bottom)));
      end;
    end;
  end;

  function CalcDrawPos:Integer;
  begin
    result:=DrawPosition;

    case FPlane3D of
      cpX: Inc(result,(GetHorizAxis.IStartPos+GetHorizAxis.IEndPos) div 2);
      cpY: Inc(result,(GetVertAxis.IStartPos+GetVertAxis.IEndPos) div 2);
      cpZ: Inc(result,MiddleZ);
    end;
  end;

  Procedure DrawBitmap(const tmpBounds : TRect);

    procedure InternalDraw(const ABitmap:TBitmap);
    var R : TRect;
    begin
      R:=CalcDestRectangle(tmpBounds);

      if ParentChart.View3D then
      begin
        if (not ParentChart.View3DOptions.Orthogonal) or (FPlane3D<>cpZ) then
        begin
          case FPlane3D of
            cpX: begin
                   R.Left:=ParentChart.DepthAxis.CalcPosValue(MinZValue);
                   R.Right:=ParentChart.DepthAxis.CalcPosValue(MaxZValue);
                 end;
            cpY: begin
                   R.Top:=ParentChart.DepthAxis.CalcPosValue(MinZValue);
                   R.Bottom:=ParentChart.DepthAxis.CalcPosValue(MaxZValue);
                 end;
            cpZ: ;
          end;

          ParentChart.Canvas.StretchDraw(R,ABitmap,CalcDrawPos,FPlane3D);
        end
        else
        with ParentChart.Canvas do
             StretchDraw(CalcRect3D(R,MiddleZ+DrawPosition),ABitmap)
      end
      else
      with ParentChart.Canvas do
           StretchDraw(CalcRect3D(R,MiddleZ+DrawPosition),ABitmap)
    end;

  begin
    if not SmoothBitmap then
       ParentChart.Canvas.StretchDrawQuality:=sqLow;

    InternalDraw(FBitmap);

    ParentChart.Canvas.StretchDrawQuality:=sqHigh;
  end;

  function CalcGridIndex(const AIndex:Integer; IsZ:Boolean):Integer;
  begin
    result:=0;
    
    if (AIndex>=0) then
    begin
      if IsZ then
      begin
        if (AIndex<Length(GridIndex[1])) then
           result:=GridIndex[1,AIndex];  //TV52013783
      end
      else
        if (AIndex<Length(GridIndex)) then
           result:=GridIndex[AIndex,1]; //TV52013783

      if result<0 then
      begin
        if Length(GridIndex[AIndex])>AIndex then // B1202
           result:=GridIndex[AIndex,AIndex];

        if result<0 then
           result:=0;
      end;
    end;
  end;

  Procedure DrawGrid(const R:TRect);
  var z   : Integer;
      x   : Integer;
      tmp : Integer;
      tmpZMin  : Integer;
      tmpZMax  : Integer;
      tmpPos   : Integer;
      tmpSub   : TChartValue;
      tmpValue : TChartValue;
      tmpGrid : Integer;
  begin
    ParentChart.Canvas.BackMode:=cbmTransparent;
    ParentChart.Canvas.AssignVisiblePen(Pen);

    if CenteredPoints then
       tmpSub:=0.5
    else
       tmpSub:=0;

    tmpPos:=CalcDrawPos;

    tmpZMin:=ParentChart.DepthAxis.CalcPosValue(MinZValue);
    tmpZMax:=ParentChart.DepthAxis.CalcPosValue(MaxZValue);

    for z:=0 to ((NumZValues-tmpDec-1) div FZGridEvery) do // TV52012634
    begin
      tmpGrid:=CalcGridIndex(1+(z*FZGridEvery),True);

      tmpValue:=ZValues.Value[tmpGrid]-tmpSub;
      tmp:=GetVertAxis.CalcPosValue(tmpValue);

      with ParentChart.Canvas do
      if ParentChart.View3D then
      case FPlane3D of
        cpX: ZLine3D(tmpPos,tmp,tmpZMin,tmpZMax);
        cpY: HorizLine3D(R.Left,R.Right,tmpPos,ParentChart.DepthAxis.CalcPosValue(tmpValue));
        cpZ: HorizLine3D(R.Left,R.Right,tmp,tmpPos);
      end
      else DoHorizLine(R.Left,R.Right,tmp);
    end;

    for x:=0 to ((NumXValues-tmpDec-1) div FXGridEvery) do // TV52012634
    begin
      tmpGrid:=CalcGridIndex(1+(x*FXGridEvery),False);

      tmpValue:=XValues.Value[tmpGrid]-tmpSub;
      tmp:=GetHorizAxis.CalcPosValue(tmpValue);

      with ParentChart.Canvas do
      if ParentChart.View3D then
      case FPlane3D of
        cpX: VertLine3D(tmpPos,R.Top,R.Bottom,ParentChart.DepthAxis.CalcPosValue(tmpValue));
        cpY: ZLine3D(tmp,tmpPos,tmpZMin,tmpZMax);
        cpZ: VertLine3D(tmp,R.Top,R.Bottom,tmpPos);
      end
      else DoVertLine(tmp,R.Top,R.Bottom);
    end;
  end;

var
  tmpBounds : {$IFDEF FMX}Types.{$ENDIF}TRect;

  procedure DrawUsingBitmap;
  var HasNulls : Boolean;
      tmpW,
      tmpH     : TCoordinate;
      {$IFDEF FMX}
      tmpRectF : TRect;
      {$ENDIF}

      RecreateBitmap : Boolean;
  begin
    tmpW:=Abs(1+tmpBounds.Right-tmpBounds.Left)-tmpDec;
    tmpH:=Abs(1+tmpBounds.Bottom-tmpBounds.Top)-tmpDec;

    if FXStartIndex=0 Then
       tmpW:=Ceil(MaxXValue);

    if FZStartIndex=0 Then
       tmpH:=Ceil(MaxZValue);

    // Reset internal bitmap?

    RecreateBitmap:=IDirtyBitmap or
        ({$IFDEF FMX}tmpBounds<>IOldBounds{$ELSE}not EqualRect(tmpBounds,IOldBounds){$ENDIF});

    if RecreateBitmap then
    begin
      FBitmap.Free;
      DoCreateBitmap({$IFDEF FMX}Round{$ENDIF}(tmpW),{$IFDEF FMX}Round{$ENDIF}(tmpH));
    end;

    if (FBitmap.Width * FBitmap.Height)>0 then // TV52010597
    begin
      // Fill colors
      HasNulls:=False;

      if Brush.Style<>bsClear then
      begin
        if RecreateBitmap then
           FillBitmap(tmpBounds,HasNulls);
      end
      else
         HasNulls:=True;

      {$IFNDEF FMX}
      // Force VCL TBitmap to re-use new "null" transparent pixels
      if HasNulls then
      begin
        FBitmap.Transparent:=False;
        FBitmap.Transparent:=True;
      end;
      {$ENDIF}

      if MinXValue>1 then
      begin
        Inc(tmpBounds.Left,Ceil(MinXValue)-1);
        Inc(tmpBounds.Right,Ceil(MinXValue)-1);
      end;

      if MinZValue>1 then
      begin
        Inc(tmpBounds.Top,Ceil(MinZValue)-1);
        Inc(tmpBounds.Bottom,Ceil(MinZValue)-1);
      end;

      // Draw bitmap
      {$IFDEF FMX}
      tmpRectF.Left:=tmpBounds.Left;
      tmpRectF.Top:=tmpBounds.Top;
      tmpRectF.Right:=tmpBounds.Right;
      tmpRectF.Bottom:=tmpBounds.Bottom;
      DrawBitmap(tmpRectF);
      {$ELSE}
      DrawBitmap(tmpBounds);
      {$ENDIF}
    end;

    IDirtyBitmap:=False;
    IOldBounds:=tmpBounds;
  end;

  procedure DrawCellByCell;
  var z : Integer;
      x : Integer;
      tmpIndex : Integer;
      tmp  : TColor;
      tmpR : TRect;

      {$IFNDEF FMX}
      IsFastBrush : Boolean;
      CanvasDC    : TTeeCanvasHandle;
      {$ENDIF}
  begin
    with TCanvas3DAccess(ParentChart.Canvas) do
    begin
      AssignBrush(Self.Brush);
      Pen.Style:=psClear;
      Brush.Style:=bsSolid;

      {$IFNDEF FMX}
      IsFastBrush:=(not IsNoBMPGrid) and CheckFastBrush;
      if IsFastBrush then
         CanvasDC:=Handle
      else
         CanvasDC:=0;
      {$ENDIF}

      SetAntiAlias(False);
    end;

    for z:=1 to NumZValues do
    begin
      for x:=1 to NumXValues do
      begin
        tmpIndex:=GridIndex[x,z];

        if tmpIndex<>-1 then
        begin
          tmp:=ValueColor[tmpIndex];

          if tmp<>clNone then // null?
          begin
            tmpR:=CellBounds(x,z);

            with ParentChart.Canvas do
            begin
              {$IFNDEF FMX}
              if IsFastBrush then
                 TeeSetDCBrushColor(CanvasDC,tmp)
              else
              {$ENDIF}
                 Brush.Color:=tmp;

              RectangleWithZ(tmpR,MiddleZ);
            end;
          end;
        end;
      end;
    end;

    // Re-enable antialias (only if Canvas AntiAlias is True)
    TCanvasAccess(ParentChart.Canvas).SetAntiAlias(True);
  end;

  procedure DrawFrame(const DestBounds:TRect);
  var tmpMinZ  : Integer;
      tmpMaxZ  : Integer;
  begin
    with ParentChart.Canvas do
    begin
      Brush.Style:=bsClear;
      BackMode:=cbmTransparent;
      AssignVisiblePen(Self.Frame);

      if ParentChart.View3D then
      begin
        tmpMinZ:=ParentChart.DepthAxis.CalcPosValue(MinZValue);
        tmpMaxZ:=ParentChart.DepthAxis.CalcPosValue(MaxZValue);

        case FPlane3D of
          cpX: with destBounds do
                    RectangleZ(CalcDrawPos,Top,Bottom,tmpMinZ,tmpMaxZ);

          cpY: with destBounds do
                    RectangleY(Left,CalcDrawPos,Right,tmpMinZ,tmpMaxZ);
        else
          RectangleWithZ(destBounds,CalcDrawPos);
        end
      end
      else
        Rectangle(destBounds);
    end;
  end;

var tmpBlend : TTeeBlend;
    destBounds,
    tmpR     : TRect;
    {$IFDEF FMX}
    tmpRectF : TRect;
    {$ENDIF}
begin
  if Count>0 then
  begin
    { In non-centered mode, size of grid is one less (x-1, z-1) }
    tmpDec:=0;  //MM

    FXRegularSteps := False;
    FZRegularSteps := False;
    FXRegularStep :=-1;
    FZRegularStep :=-1;

    // v7 Calculate min and max bitmap bounds when axes are zoomed
    with tmpBounds do
    begin
      with GetHorizAxis do
      begin
        Left:=Trunc(CalcPosPoint(IStartPos));
        Right:=Trunc(CalcPosPoint(IEndPos));
        if Inverted then Inc(Left) else Inc(Right);
      end;

      with GetVertAxis do
      begin
        Top:=Trunc(CalcPosPoint(IStartPos));
        Bottom:=Trunc(CalcPosPoint(IEndPos));
        if Inverted then Inc(Bottom) else Inc(Top);
      end;
    end;

    // Restrict min and max bounds limits to 1..NumValues
    // Warning: tmpBounds is expressed in integers, starting at 1.
    {$IFDEF FMX}
    tmpBounds:=OrientRect(tmpBounds);
    {$ELSE}
    tmpBounds:=OrientRectangle(tmpBounds);
    {$ENDIF}

    begin
      Dec(tmpBounds.Left,Round(MinXValue));
      Dec(tmpBounds.Top,Round(MinZValue));
      Dec(tmpBounds.Right,Round(MinXValue));
      Dec(tmpBounds.Bottom,Round(MinZValue));

      if (FXStartIndex<>0) then
         if tmpBounds.Left<1 then tmpBounds.Left:=1;

      if (FZStartIndex<>0) then
         if tmpBounds.Top<1 then tmpBounds.Top:=1;

      if tmpBounds.Right>NumXValues then tmpBounds.Right:=NumXValues;
      if tmpBounds.Bottom>NumZValues then tmpBounds.Bottom:=NumZValues;
    end;

    {$IFDEF FMX}
    tmpRectF.Left:=tmpBounds.Left;
    tmpRectF.Top:=tmpBounds.Top;
    tmpRectF.Right:=tmpBounds.Right;
    tmpRectF.Bottom:=tmpBounds.Bottom;
    destBounds:=CalcDestRectangle(tmpRectF);
    {$ELSE}
    destBounds:=CalcDestRectangle(tmpBounds);
    {$ENDIF}

    if Transparency<>0 then
    begin
      if ParentChart.Canvas.SupportsAlpha then
         tmpBlend:=ParentChart.Canvas.BeginBlending(TeeZeroRect,Transparency)
      else
      begin
        tmpR:=DestBounds;

        if ParentChart.View3D then
           if ParentChart.View3DOptions.Orthogonal then
              tmpR:=ParentChart.Canvas.CalcRect3D(tmpR,MiddleZ)
           else
              tmpR:=ParentChart.Canvas.RectFromRectZ(tmpR,MiddleZ);

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

    if ParentChart.Canvas.IsNoBMPGrid or IrregularGrid then
       DrawCellByCell
    else
       DrawUsingBitmap;

    if Pen.Visible then
       DrawGrid(DestBounds);

    if Frame.Visible then
       DrawFrame(DestBounds);

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

procedure TColorGridSeries.SetXGridEvery(const Value: Integer);
begin
  SetIntegerProperty(FXGridEvery,Value);
end;

procedure TColorGridSeries.SetZGridEvery(const Value: Integer);
begin
  SetIntegerProperty(FZGridEvery,Value);
end;

procedure TColorGridSeries.SetSmoothBitmap(const Value: Boolean);
begin
  SetBooleanProperty(FSmoothBitmap,Value);
end;

procedure TColorGridSeries.SetPlane3D(const Value: TCanvas3DPlane);
begin
  if FPlane3D<>Value then
  begin
    FPlane3D:=Value;
    Repaint;
  end;
end;

procedure TColorGridSeries.SetFrame(const Value: TChartHiddenPen);
begin
  FFrame.Assign(Value);
end;

function TColorGridSeries.CalcMinValue(const Value:Double):Double;
begin
  if CenteredPoints then
     result:=Value-0.5
  else
     result:=Value;
end;

function TColorGridSeries.CalcXPos(ValueIndex: Integer): Integer;
begin
  result:=inherited CalcXPos(ValueIndex);

  if not FCentered then
    Inc(result, CalcXSizeValue(0.5));
end;

function TColorGridSeries.CalcYPos(ValueIndex: Integer): Integer;
begin
  result:=CalcYPosValue(ZValues.Value[ValueIndex]);

  if not FCentered then
     Dec(result, CalcYSizeValue(0.5));
end;

function TColorGridSeries.CalcMaxValue(const Value:Double):Double;
begin
  if CenteredPoints then
  begin
    result:=Value+0.5;
    if IrregularGrid then
       result:=result-1;
  end
  else
  if IrregularGrid then
     result:=Value
  else
     result:=Value+1;
end;

Function TColorGridSeries.MaxXValue:TChartValue;

  function CalcVal : TChartValue;
  var incr : TChartValue;
  begin
   incr:=0;

   if IrregularGrid then
   begin
     With GetHorizAxis do
     begin
       if NumXValues > 1 Then
       begin
         incr:=Abs(XValues.Value[NumXValues-1]-XValues.Value[NumXValues-2]);

         FXRegularSteps:=True;
         FXRegularStep:=incr;
       end
       else
       if NumXValues=1 Then
          incr:=XValues.Value[0];

       if incr < MinAxisIncrement Then
       begin
        incr := 1;
        FXRegularStep:=incr;
       end;

       result:=XValues.MaxValue+incr
     end;
   end
   else
     result:=XValues.MaxValue;
  end;

begin
  result:=CalcMaxValue(CalcVal);
end;

function TColorGridSeries.MinXValue: TChartValue;
begin
  result:=CalcMinValue(XValues.MinValue);
end;

Function TColorGridSeries.MaxZValue:TChartValue;

  function CalcVal : TChartValue;
  var incr : TChartValue;
  begin
   incr:=0;

   if IrregularGrid then
     with GetVertAxis do
     Begin
       if ZValues.Count>0 Then
          if NumZValues>1 Then
          begin
            incr:=Abs(ZValues.Value[(NumZValues)]-ZValues.Value[(NumZValues*2)]);

            FZRegularSteps:=True;
            FZRegularStep:=incr;
          end
          else
          if NumZValues=1 Then
             incr:=ZValues.Value[0];

       if incr < MinAxisIncrement Then
       Begin
         incr := 1;
         FZRegularStep:=incr;
       end;

       result:=ZValues.MaxValue+incr
     end
   else
     result:=ZValues.MaxValue;
  end;

begin
  result:=CalcMaxValue(CalcVal);
end;

Function TColorGridSeries.MaxYValue:TChartValue;
begin
  result:=MaxZValue;
end;

Function TColorGridSeries.MinZValue:TChartValue;
begin
  result:=CalcMinValue(ZValues.MinValue);
end;

Function TColorGridSeries.MinYValue:TChartValue;
begin
  result:=MinZValue;
end;

Procedure TColorGridSeries.GalleryChanged3D(Is3D:Boolean);
begin
  inherited;
  ParentChart.View3D:=Is3D;
  ParentChart.ClipPoints:=True;
end;

class function TColorGridSeries.GetEditorClass: String;
begin
  result:='TColorGridEditor';
end;

Procedure TColorGridSeries.NotifyValue(ValueEvent:TValueEvent; ValueIndex:Integer);
begin
  inherited;
  IDirtyBitmap:=True;
end;

procedure TColorGridSeries.PrepareForGallery;
begin
  inherited;
  IInGallery:=True;
  CreateValues(8,4);
end;

procedure TColorGridSeries.ChangedColors;
begin
  inherited;
  IDirtyBitmap:=True;
end;

class procedure TColorGridSeries.CreateSubGallery(
  AddSubChart: TChartSubGalleryProc);
begin
  inherited;
  AddSubChart(TeeMsg_NoGrid);
  AddSubChart(TeeMsg_BottomAxis);
  AddSubChart(TeeMsg_LeftAxis);
end;

class procedure TColorGridSeries.SetSubGallery(ASeries: TChartSeries;
  Index: Integer);
begin
  Case Index of
    2: ASeries.Pen.Hide;
    3: TColorGridSeries(ASeries).DrawPlane3D:=cpY;
    4: TColorGridSeries(ASeries).DrawPlane3D:=cpX;
  else inherited
  end;
end;

function TColorGridSeries.Clicked(X, Y: Integer): Integer;
var i      : Integer;
    XPos   : Double;
    YPos   : Double;
    ZIndex : Integer;
    YDif   : Double;
    XDif   : Double;
    tmpZDecAmount: Double;
    isIrregular: Boolean;
    z   : Integer;
    xx  : Integer;
    tmp : Integer;
    tmpIndex : Integer;
    tmpGrid : Integer;
    tmpValue : TChartValue;
    tmpSub   : TChartValue;
    lastTmp  : Integer;
begin
  result:=TeeNoPointClicked;

  if GridIndex=nil then
     Exit;

  ZIndex:=-1;
  XPos:=GetHorizAxis.CalcPosPoint(X);
  YPos:=GetVertAxis.CalcPosPoint(Y);

  isIrregular:=IrregularGrid; //and not (FXRegularSteps and FZRegularSteps);

  if CenteredPoints then
    if isIrregular then
      tmpZDecAmount:= FZRegularStep / 2.0
    else
      tmpZDecAmount:= FZRegularStep
  else
     tmpZDecAmount:=0;

  if isIrregular then { irregular grid - slower algorithm }
  begin
    if CenteredPoints then
      tmpSub:=0.5
    else
      tmpSub:=0;

    if Count > 0 then
    begin
      lastTmp:=GetVertAxis.IEndPos;
      for z:=0 to (NumZValues div FZGridEvery) do
      begin
        tmpIndex:=1+(z*FZGridEvery);
        tmpValue:=0;

        if (tmpIndex>=0) and (tmpIndex<length(GridIndex)) then
        begin
          tmpGrid:=GridIndex[1, tmpIndex];
          if tmpGrid < 0 then
          begin
            if tmpIndex<Length(GridIndex[tmpIndex]) then
            begin
              tmpGrid:=GridIndex[tmpIndex,tmpIndex];
              if tmpGrid < 0 then tmpGrid:=0;
            end
            else tmpGrid:=0;
          end;

          if tmpGrid<ZValues.Count then
            tmpValue:=ZValues.Value[tmpGrid]-tmpSub;
        end
        else if FZRegularSteps then
        begin
          if tmpIndex-1<Length(GridIndex[1]) then
          begin
            tmpGrid:=GridIndex[1, tmpIndex - 1];
            tmpValue:=FZRegularStep + ZValues.Value[tmpGrid]-tmpSub;
          end
          { unused:
          else tmpGrid:=0
          };
        end;

        tmp:=GetVertAxis.CalcPosValue(tmpValue);
      //Fix Bug number Id1855
        if not GetVertAxis.Inverted  then
        begin
          if (Y >= tmp) and (Y <= lastTmp) then
          begin
              ZIndex := z;
              break;
          end;
          lastTmp:=tmp;
        end
        else
        begin
          if (Y <= tmp) and (Y >= lastTmp) then
           begin
              ZIndex := z;
              break;
          end;
          lastTmp:=tmp;
          end;
      end;

      if ZIndex <> -1 then
      begin
        lastTmp:=0;
        for xx:=0 to (NumXValues div FXGridEvery) do
        begin
          tmpIndex:=1+(xx*FXGridEvery);
          tmpValue:=0;

          if (tmpIndex>=0) and (tmpIndex<length(GridIndex)) then
          begin
            tmpGrid:=GridIndex[tmpIndex,1];
            if tmpGrid < 0 then
            begin
              if tmpIndex<Length(GridIndex[tmpIndex]) then
              begin
                tmpGrid:=GridIndex[tmpIndex,tmpIndex];
                if tmpGrid < 0 then tmpGrid:=0;
              end
              else tmpGrid:=0;
            end;
            tmpValue:=XValues.Value[tmpGrid]-tmpSub;
          end
          else if FXRegularSteps then
          begin
            if tmpIndex-1<Length(GridIndex[1]) then
            begin
              tmpGrid:=GridIndex[1, tmpIndex - 1];
              tmpValue:=FXRegularStep + ZValues.Value[tmpGrid]-tmpSub;
            end
            { unused:
            else tmpGrid:=0
            };
          end;

          tmp:=GetHorizAxis.CalcPosValue(tmpValue);
          //Fix Bug number Id1855
         if not GetHorizAxis.Inverted then
          begin
            if (X <= tmp) and (X >= lastTmp) then
            begin
             result := GridIndex[xx, ZIndex];
             Exit;
            end;
              lastTmp:=tmp;
          end
          else
          begin
            if (X >= tmp) and (X <= lastTmp) then
            begin
              result := GridIndex[xx, ZIndex];
              Exit;
            end;
            lastTmp:=tmp;
          end;

        end;
      end;
    end;
  end
  else { indexed grid - faster algorithm }
  begin
    YDif := (YPos + tmpZDecAmount - ZValues.MinValue);
    if YDif >=0 then i := Trunc(YDif) else i := -1;

    if (i >= 0) and (i < NumZValues) then ZIndex := i;
    if ZIndex <> -1 then { search further only if it makes sense }
    begin
      XDif := (XPos + tmpZDecAmount - XValues.MinValue);
      if XDif >=0 then i := Trunc(XDif) else i := -1;

      if (i >= 0) and (i < NumXValues) then
      begin
        result := GridIndex[i+1,ZIndex+1];
        Exit;
      end;
    end;
  end;
end;

procedure TColorGridSeries.DrawMark(ValueIndex: Integer; const St: String;
  APosition: TSeriesMarkPosition);
var tmp : Double;
begin
  with APosition do
  begin
    if CenteredPoints or IrregularGrid then tmp:=0
                                       else tmp:=0.5;

    LeftTop.Y:=GetVertAxis.CalcPosValue(ZValues.Value[ValueIndex]+tmp)-(Height div 2);
    LeftTop.X:=GetHorizAxis.CalcPosValue(XValues.Value[ValueIndex]+tmp)-(Width div 2);
  end;

  inherited;
end;

procedure TColorGridSeries.SetCentered(const Value: Boolean);
begin
  SetBooleanProperty(FCentered,Value);
end;

procedure TColorGridSeries.SetBitmap(const Value: TBitmap);
var x   : Integer;
    z   : Integer;
    tmp : TColor;
    {$IFDEF FMX}
    {$IFDEF D17}
    bd  : TBitmapData;
    {$ENDIF}
    {$ENDIF}
begin
  FBitmap.Assign(Value);

  {$IFNDEF FMX}
  FBitmap.PixelFormat:=TeePixelFormat;
  {$ENDIF}

  BeginUpdate;
  try
    Clear;
    CenteredPoints:=True;

    NumXValues:=FBitmap.Width;
    NumZValues:=FBitmap.Height;

    for x:=0 to FBitmap.Width-1 do
        for z:=0 to FBitmap.Height-1 do
        begin
          {$IFDEF D7}
          {$IFDEF FMX}
          {$IFDEF D17}
          FBitmap.Map(TMapAccess.{$IFDEF D20}Read{$ELSE}maRead{$ENDIF}, bd);
          tmp:=bd.GetPixel(x,z);
          {$ELSE}
          tmp:=FBitmap.Pixels[x,z];
          {$ENDIF}
          {$ELSE}
          tmp:=FBitmap.Canvas.Pixels[x,FBitmap.Height-z-1];
          {$ENDIF}

          {$ELSE}
          tmp:=FBitmap.Canvas.Pixels[x,z];
          {$ENDIF}

          AddXYZ(x+1,tmp,z+1,'',tmp);
        end;
  finally
    EndUpdate;
  end;
end;

{ TSmoothPoints function }

Constructor TSmoothPoints.Create(const Parent: TChartSeries);
begin
  inherited Create;

  ISeries:=Parent;
  Factor:=4;
end;

procedure TSmoothPoints.Assign(Source: TPersistent);
begin
  if Source is TSmoothPoints then
     FActive:=TSmoothPoints(Source).FActive
  else
     inherited;
end;

Function TSmoothPoints.CalculateFloat(Const P:Array of TPointFloat):TPointFloatArray;
var Spline : TBSpline;
    t      : Integer;
    tmp    : TDataType;
begin
  Spline:=TBSpline.Create(P);
  try
    Spline.Interpolated:=Interpolate;
    Spline.Fragments:=Length(P)*Factor;

    SetLength(result,Spline.Fragments+1);

    with Spline do
    begin
      tmp:=1.0/Fragments;

      for t:=0 to Fragments do
      with Value(t*tmp) do
      begin
        result[t].X:=X;
        result[t].Y:=Y;
      end;
    end;

  finally
    Spline.Free;
  end;
end;

function TSmoothPoints.Calculate(const P: Array of TPoint): {$IFNDEF FMX}TeCanvas.{$ENDIF}TPointArray;
var Spline : TBSpline;
    t      : Integer;
    tmp    : TDataType;
begin
  Spline:=TBSpline.Create;
  try
    for t:=0 to High(P) do
    begin
      Spline.AddPoint(P[t].X,P[t].Y);
      Spline.Knuckle[t]:=False;
    end;

    Spline.Interpolated:=Interpolate;
    Spline.Fragments:=Length(P)*Factor;

    SetLength(result,Spline.Fragments+1);

    with Spline do
    begin
      tmp:=1.0/Fragments;

      for t:=0 to Fragments do
      with Value(t*tmp) do
      begin
        result[t].X:=Round(X);
        result[t].Y:=Round(Y);
      end;
    end;

  finally
    Spline.Free;
  end;
end;

procedure TSmoothPoints.SetActive(const Value: Boolean);
begin
  TSeriesAccess(ISeries).SetBooleanProperty(FActive,Value);
end;

procedure TSmoothPoints.SetInterpolate(const Value: Boolean);
begin
  TSeriesAccess(ISeries).SetBooleanProperty(FInterpolate,Value);
end;

procedure TColorGridSeries.SetPosition(const Value: Integer);
begin
  SetIntegerProperty(FPosition,Value);
end;

{ TVector3DSeries }
type
  TValueListAccess=class(TChartValueList);

Constructor TVector3DSeries.Create(AOwner: TComponent);
begin
  inherited;
  CalcVisiblePoints:=False;
  TValueListAccess(XValues).InitDateTime(False);
  FEndXValues:=TChartValueList.Create(Self,TeeMsg_ValuesArrowEndX);
  FEndYValues:=TChartValueList.Create(Self,TeeMsg_ValuesArrowEndY);
  FEndZValues:=TChartValueList.Create(Self,TeeMsg_ValuesVectorEndZ);
  FArrowWidth:=4;
  FArrowHeight:=4;
  FStartArrow:=TChartHiddenPen.Create(CanvasChanged);
  FStartArrow.Color:=clTeeColor;
  FEndArrow:=CreateChartPen;
  FEndArrow.Color:=clTeeColor;
end;

function TVector3DSeries.AddVector(const X0, Y0, Z0, X1, Y1, Z1: Double;
  const ALabel: String; AColor: TColor): Integer;
begin
  EndXValues.TempValue:=X1;
  EndYValues.TempValue:=Y1;
  EndZValues.TempValue:=Z1;
  result:=AddXYZ(X0,Y0,Z0,ALabel,AColor);
end;

function TVector3DSeries.IsValidSourceOf(Value: TChartSeries): Boolean;
begin
  result:=Value is TVector3DSeries;
end;

function TVector3DSeries.MaxZValue: TChartValue;
begin
  result:=Math.Max(inherited MaxZValue,FEndZValues.MaxValue);
end;

function TVector3DSeries.MinZValue: TChartValue;
begin
  result:=Math.Min(inherited MinZValue,FEndZValues.MinValue);
end;

procedure TVector3DSeries.SetEndXValues(const Value: TChartValueList);
begin
  SetChartValueList(FEndXValues,Value);
end;

procedure TVector3DSeries.SetEndYValues(const Value: TChartValueList);
begin
  SetChartValueList(FEndYValues,Value);
end;

procedure TVector3DSeries.SetEndZValues(const Value: TChartValueList);
begin
  SetChartValueList(FEndZValues,Value);
end;

procedure TVector3DSeries.DrawValue(ValueIndex: Integer);
var tmpColor : TColor;
    tmpSin   : Extended;
    tmpCos   : Extended;

  Procedure DrawArrow(APen:TChartPen; P:TPoint; Z:Integer);
  begin
    with ParentChart.Canvas do
    begin
      if APen.Color=clTeeColor then
         AssignVisiblePenColor(APen,tmpColor)
      else
         AssignVisiblePen(APen);

      MoveTo3D(P.X + (-FArrowWidth*tmpCos - FArrowHeight*tmpSin),
             P.Y + (+FArrowWidth*tmpSin - FArrowHeight*tmpCos),Z);

      LineTo3D(P.X,P.Y,Z);
      LineTo3D(P.X + Round(-FArrowWidth*tmpCos + FArrowHeight*tmpSin),
             P.Y + Round(+FArrowWidth*tmpSin + FArrowHeight*tmpCos),Z);
    end;
  end;

var StartZ     : Integer;
    EndZ       : Integer;
    StartPoint, tmpStart : TPoint;
    EndPoint, tmpEnd : TPoint;
    tmpAngle   : Double;
begin
  with ParentChart.Canvas do
  begin
    BackMode:=cbmTransparent;
    tmpColor:=ValueColor[ValueIndex];
    AssignVisiblePenColor(Self.Pen,tmpColor);

    StartPoint.X:=CalcXPos(ValueIndex);
    StartPoint.Y:=CalcYPos(ValueIndex);
    StartZ:=CalcZPos(ValueIndex);

    MoveTo3D( StartPoint.X,StartPoint.Y,StartZ );

    EndPoint.X:=CalcXPosValue(EndXValues.Value[ValueIndex]);
    EndPoint.Y:=CalcYPosValue(EndYValues.Value[ValueIndex]);
    EndZ:=ParentChart.DepthAxis.CalcYPosValue(EndZValues.Value[ValueIndex]);

    LineTo3D( EndPoint.X,EndPoint.Y,EndZ );

    if StartArrow.Visible or EndArrow.Visible then
    begin
      //tmpStart & tmpEnd discarded after arrow angle calc
      tmpStart:=StartPoint;
      tmpEnd:=EndPoint;
      tmpStart:=Calculate3DPosition(tmpStart,StartZ);
      tmpEnd:=Calculate3DPosition(tmpEnd,EndZ);

      tmpAngle:=ArcTan2(tmpStart.Y-tmpEnd.Y,tmpEnd.X-tmpStart.X);
      SinCos(tmpAngle,tmpSin,tmpCos);

      if StartArrow.Visible then DrawArrow(StartArrow,StartPoint,StartZ);
      if EndArrow.Visible then DrawArrow(EndArrow,EndPoint,EndZ);
    end;
  end;
end;

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

  inherited;

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

Procedure TVector3DSeries.AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False);
var t         : Integer;
    x0,y0,z0,
    x1,y1,z1  : Double;
    tmpPi,
    tmp,
    u,v       : Double;
    tmpSin : {$IFDEF D15}Double{$ELSE}Extended{$ENDIF};
    tmpCos : {$IFDEF D15}Double{$ELSE}Extended{$ENDIF};
begin
  u:=0;
  v:=0;
  tmp:=NumValues*0.6;
  tmpPi:=8*Pi/NumValues;

  for t:=1 to NumValues do
  begin
    SinCos(v,tmpSin,tmpCos);

    x0:=u+tmpSin;
    y0:=u;
    z0:=tmpCos;

    x1:=x0+tmpSin;
    y1:=y0+tmp*tmpCos*tmpSin;
    z1:=z0+tmpCos*u;

    AddVector(x0,y0,z0,x1,y1,z1);

    u:=u+1;
    v:=v+tmpPi;
  end;
end;

function TVector3DSeries.MaxXValue: TChartValue;
begin
  result:=Math.Max(inherited MaxXValue,FEndXValues.MaxValue);
end;

function TVector3DSeries.MaxYValue: TChartValue;
begin
  result:=Math.Max(inherited MaxYValue,FEndYValues.MaxValue);
end;

function TVector3DSeries.MinXValue: TChartValue;
begin
  result:=Math.Min(inherited MinXValue,FEndXValues.MinValue);
end;

function TVector3DSeries.MinYValue: TChartValue;
begin
  result:=Math.Min(inherited MinYValue,FEndYValues.MinValue);
end;

function TVector3DSeries.NumSampleValues: Integer;
begin
  result:=250;
end;

procedure TVector3DSeries.AssignFormatting(Source: TChartSeries);
begin
  if Source is TVector3DSeries then
  with TVector3DSeries(Source) do
  begin
    Self.FArrowHeight :=FArrowHeight;
    Self.FArrowWidth  :=FArrowWidth;
    Self.EndArrow     :=EndArrow;
    Self.StartArrow   :=StartArrow;
  end;

  inherited;
end;

procedure TVector3DSeries.SetArrowHeight(const Value: Integer);
begin
  SetIntegerProperty(FArrowHeight,Value);
end;

procedure TVector3DSeries.SetArrowWidth(const Value: Integer);
begin
  SetIntegerProperty(FArrowWidth,Value);
end;

procedure TVector3DSeries.SetEndArrow(const Value: TChartPen);
begin
  FEndArrow.Assign(Value);
end;

procedure TVector3DSeries.SetStartArrow(const Value: TChartHiddenPen);
begin
  FStartArrow.Assign(Value);
end;

destructor TVector3DSeries.Destroy;
begin
  FStartArrow.Free;
  FEndArrow.Free;
  inherited;
end;

class function TVector3DSeries.GetEditorClass: String;
begin
  result:='TVectorSeriesEditor';
end;

{ TTowerSeries }

Constructor TTowerSeries.Create(AOwner: TComponent);
begin
  inherited;
  FPercDepth:=100;
  FPercWidth:=100;
  FDark3D:=True;
end;

Function TTowerSeries.CalcYPos(ValueIndex:Integer):Integer;
var tmpValue : TChartValue;
    tmp      : Double;
begin
  Case FStacked of
    tsNone: result:=inherited CalcYPos(ValueIndex)
  else
  begin
    tmpValue:=Abs(YValues.Value[ValueIndex])+PointOrigin(ValueIndex,False);

    if FStacked=tsStacked then
       result:=CalcYPosValue(tmpValue)
    else
    begin
      tmp:=PointOrigin(ValueIndex,True);

      if tmp<>0 then result:=CalcYPosValue(tmpValue*100.0/tmp)
                else result:=GetVertAxis.IEndPos;
    end;
  end;
  end;
end;

function TTowerSeries.Clicked(X, Y: Integer): Integer;
var tmpX : Integer;
    tmpZ : Integer;
    P    : TPoint;

  function ClickedCell(out res:Integer):Boolean; // v8
  var
    tmpR : TRect;
    z0   : Integer;
    z1   : Integer;
    tmpTop    : Integer;
    tmpBottom : Integer;

    function PointInCover:Boolean;
    var tmpP : TFourPoints;
    begin
      with ParentChart.Canvas do
      begin
        tmpP[0]:=Calculate3DPosition(tmpR.Left,tmpTop,z0);
        tmpP[1]:=Calculate3DPosition(tmpR.Right,tmpTop,z0);
        tmpP[2]:=Calculate3DPosition(tmpR.Right,tmpTop,z1);
        tmpP[3]:=Calculate3DPosition(tmpR.Left,tmpTop,z1);
      end;

      result:=PointInPolygon(P,tmpP);
    end;

    function PointInFront:Boolean;
    var tmpP : TFourPoints;
    begin
      with ParentChart.Canvas do
      begin
        tmpP[0]:=Calculate3DPosition(tmpR.Left,tmpTop,z0);
        tmpP[1]:=Calculate3DPosition(tmpR.Right,tmpTop,z0);
        tmpP[2]:=Calculate3DPosition(tmpR.Right,tmpBottom,z0);
        tmpP[3]:=Calculate3DPosition(tmpR.Left,tmpBottom,z0);
      end;

      result:=PointInPolygon(P,tmpP);
    end;

  var tmp : Integer;
  begin
    result:=False;

    tmp:=GridIndex[tmpX,tmpZ];

    if (tmp<>-1) and (InternalColor(tmp)<>clNone) then
    begin
      tmpR:=CalcCell(tmp,tmpTop,tmpBottom,z0,z1);

      case TowerStyle of
        tsCover: result:=PointInCover;
      else
        result:=PointInFront or PointInCover;
      end;

      if result then
      begin
        res:=tmp;
        exit;
      end;
    end;
  end;

begin
  if (Count>0) and (Length(GridIndex)>0) then
  begin
    IOffW:=FPercWidth*0.005;
    IOffD:=FPercDepth*0.005;
    P:=TeePoint(x,y);

    if not BackFaced then // invert logic (compared to DrawAllValues)
      if not DrawValuesForward then
        for tmpX:=NumXValues downto 1 do
            for tmpZ:=1 to NumZValues do
            begin
              if ClickedCell(result) then exit;
            end
      else
        for tmpX:=NumXValues downto 1 do
            for tmpZ:=1 to NumZValues do
            begin
              if ClickedCell(result) then exit;
            end
    else
      if not DrawValuesForward then
        for tmpX:=NumXValues downto 1 do
            for tmpZ:=NumZValues downto 1 do
            begin
              if ClickedCell(result) then exit;
            end
      else
        for tmpX:=1 to NumXValues do
            for tmpZ:=NumZValues downto 1 do
            begin
              if ClickedCell(result) then exit;
            end;
  end;

  result:=TeeNoPointClicked;
end;

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

  AddSubChart(TeeMsg_NoBorder);
  AddSubChart(TeeMsg_SingleColor);
  AddSubChart(TeeMsg_Marks);
  AddSubChart(TeeMsg_Hollow);
  AddSubChart(TeeMsg_Rectangle);
  AddSubChart(TeeMsg_Cover);
  AddSubChart(TeeMsg_Circle);
  AddSubChart(TeeMsg_GalleryArrow);
  AddSubChart(TeeMsg_Cone);
  AddSubChart(TeeMsg_Pyramid);
  AddSubChart(TeeMsg_Stack)
end;

procedure TTowerSeries.DrawMark(ValueIndex: Integer; const St: String;
  APosition: TSeriesMarkPosition);
begin
  Marks.ApplyArrowLength(APosition);
  inherited;
end;

class function TTowerSeries.GetEditorClass: String;
begin
  result:='TTowerSeriesEditor';
end;

function TTowerSeries.MaxXValue: TChartValue;
begin
  result:=XValues.MaxValue+0.5;
end;

function TTowerSeries.MaxZValue: TChartValue;
begin
  result:=ZValues.MaxValue+0.5;
end;

function TTowerSeries.MinXValue: TChartValue;
begin
  result:=XValues.MinValue-0.5;
end;

Function TTowerSeries.MaxMandatoryValue(const Value:TChartValue):TChartValue;
var t   : Integer;
    tmp : TChartValue;
begin
  if FStacked=tsStacked100 then
     result:=100
  else
  begin
    result:=Value;

    if FStacked=tsStacked then
    for t:=0 to Count-1 do
    Begin
      tmp:=PointOrigin(t,False)+MandatoryValueList.Value[t];

      if tmp>result then result:=tmp;
    end;

    if FUseOrigin and (result<FOrigin) then result:=FOrigin;
  end;
end;

function TTowerSeries.MaxYValue:TChartValue;
begin
  result:=MaxMandatoryValue(inherited MaxYValue);
end;

Function TTowerSeries.MinMandatoryValue(Const Value:TChartValue):TChartValue;
var t   : Integer;
    tmp : TChartValue;
begin
  if FStacked=tsStacked100 then
     result:=0
  else
  begin
    result:=Value;

    if FStacked=tsStacked then
    for t:=0 to Count-1 do
    Begin
      tmp:=PointOrigin(t,False)+MandatoryValueList.Value[t];

      if tmp<result then result:=tmp;
    end;

    if FUseOrigin and (result>FOrigin) then result:=FOrigin;
  end;
end;

Function TTowerSeries.MinYValue:TChartValue;
Begin
  result:=MinMandatoryValue(inherited MinYValue);
end;

function TTowerSeries.MinZValue: TChartValue;
begin
  result:=ZValues.MinValue-0.5;
end;

procedure TTowerSeries.PrepareForGallery;
begin
  inherited;
  IInGallery:=True;
  CreateValues(5,5);
end;

Procedure TTowerSeries.SetParentChart(Const Value:TCustomAxisPanel);
begin
  inherited;
  SetOtherTowers;
end;

class procedure TTowerSeries.SetSubGallery(ASeries: TChartSeries;
  Index: Integer);
var tmpTower : TTowerSeries;
begin
  case Index of
    2: ASeries.Pen.Hide;
    3: TTowerSeries(ASeries).UsePalette:=False;
    4: ASeries.Marks.Visible:=True;
    5: ASeries.Brush.Style:=bsClear;
    6: TTowerSeries(ASeries).TowerStyle:=tsRectangle;
    7: TTowerSeries(ASeries).TowerStyle:=tsCover;
    8: TTowerSeries(ASeries).TowerStyle:=tsCylinder;
    9: TTowerSeries(ASeries).TowerStyle:=tsArrow;
   10: TTowerSeries(ASeries).TowerStyle:=tsCone;
   11: TTowerSeries(ASeries).TowerStyle:=tsPyramid;
   12: begin
         tmpTower:=(ASeries as TTowerSeries);
         ASeries.ParentChart.AddSeries(TTowerSeries).FillSampleValues(tmpTower.NumXValues);
         tmpTower.Stacked:=tsStacked;
       end;  
  else
    inherited;
  end;
end;

function TTowerSeries.GetOriginPos(ValueIndex:Integer):Integer;

  procedure CalcFromEnd;
  begin
    if FUseOrigin then result:=CalcPosValue(PointOrigin(ValueIndex,False))
                  else result:=GetVertAxis.IEndPos;
  end;

var tmp : Double;
begin
  if FStacked=tsStacked100 then
  begin
    tmp:=PointOrigin(ValueIndex,True);

    if tmp<>0 then result:=CalcPosValue(PointOrigin(ValueIndex,False)*100.0/tmp)
              else result:=0;
  end
  else
  if FStacked=tsStacked then
     if IFirstIndex then
        CalcFromEnd
     else
        result:=CalcPosValue(PointOrigin(ValueIndex,False))
  else
     CalcFromEnd;
end;

Function TTowerSeries.CalcCell(AIndex:Integer; out ATop,ABottom,Z0,Z1:Integer):TRect;
var tmpValue : TChartValue;
begin
  tmpValue:=XValues.Value[AIndex];
  result.Left:=CalcXPosValue(tmpValue-IOffW);
  result.Right:=CalcXPosValue(tmpValue+IOffW);

  ATop:=CalcYPos(AIndex);
  result.Top:=ATop;

  ABottom:=GetOriginPos(AIndex);
  result.Bottom:=ABottom;

  if result.Top>result.Bottom then
     {$IFDEF FMX}SwapSingle{$ELSE}SwapInteger{$ENDIF}(result.Top,result.Bottom);

  tmpValue:=ZValues.Value[AIndex];
  z0:=ParentChart.DepthAxis.CalcYPosValue(tmpValue-IOffD);
  z1:=ParentChart.DepthAxis.CalcYPosValue(tmpValue+IOffD);
end;

type
  TVisualAccess=class(TVisualBlock);

Procedure TTowerSeries.DoDrawCell(x,z:Integer);
var R : TRect;
    tmpR1,
    tmpR2,
    tmpR3 : TRect;
    tmp : Integer;
    z0  : Integer;
    z1  : Integer;
    tmpBottom: Integer;
    tmpTop   : Integer;
    tmpColor : TColor;
    tmpMidX  : Integer;
    tmpFour  : TFourPoints;
    Old      : Integer;
    tmpVisual : TVisualBlock;
    tmpHover : Boolean;
begin
  tmp:=GridIndex[x,z];

  if (tmp<>-1) and IsPainted(tmp) then
  begin
    tmpColor:=ValueColor[tmp];

    if IChangeBrush then
       ParentChart.Canvas.AssignBrush(Self.Brush,tmpColor);

    tmpHover:=PrepareHoverPen(tmp);

    R:=CalcCell(tmp,tmpTop,tmpBottom,z0,z1);

    if Assigned(IBlend) and (Transparency>0) then
    begin
      if TowerStyle=tsCover then
      begin
        tmpFour[0]:=ParentChart.Canvas.Calculate3DPosition(R.Left,tmpTop,z0);
        tmpFour[1]:=ParentChart.Canvas.Calculate3DPosition(R.Right,tmpTop,z0);
        tmpFour[2]:=ParentChart.Canvas.Calculate3DPosition(R.Right,tmpTop,z1);
        tmpFour[3]:=ParentChart.Canvas.Calculate3DPosition(R.Left,tmpTop,z1);
        tmpR3:=RectFromPolygon(tmpFour,4);
      end
      else
      begin
        tmpR1:=ParentChart.Canvas.RectFromRectZ(R,Z0);
        tmpR2:=ParentChart.Canvas.RectFromRectZ(R,Z1);

        {$IFDEF FMX}
        tmpR3:=TRectF.Union(tmpR1,tmpR2);
        {$ELSE}
        UnionRect(tmpR3,tmpR1,tmpR2);
        {$ENDIF}
      end;

      if Assigned(IBlend) then
         IBlend.SetRectangle(tmpR3);
    end;

    with R,ParentChart.Canvas do
    case TowerStyle of
      tsCube     : Cube(R,z0,z1,Dark3D);
      tsRectangle: RectangleWithZ(R,(z0+z1) div 2);
      tsCover    : RectangleY(Left,tmpTop,Right,z0,z1);
      tsCylinder : {$IFDEF FMX}with Round do{$ENDIF} Cylinder(True,Left,Top,Right,Bottom,z0,z1,Dark3D);
      tsArrow    : begin
                     tmpMidX:={$IFDEF FMX}System.Round((Left+Right)*0.5){$ELSE}(Left+Right) div 2{$ENDIF};
                     Arrow(True, TeePoint(tmpMidX,tmpBottom),
                                 TeePoint(tmpMidX,tmpTop),
                                 {$IFDEF FMX}System.Round{$ENDIF}(Right-Left),
                                 {$IFDEF FMX}System.Round{$ENDIF}(Right-Left) div 2,
                                 z0,z1,50);
                   end;
      tsCone     : {$IFDEF FMX}with Round do{$ENDIF} Cone(True,Left,tmpTop,Right,tmpBottom,z0,z1,Dark3D,0);
      tsPyramid  : {$IFDEF FMX}with Round do{$ENDIF} Pyramid(True,Left,tmpTop,Right,tmpBottom,z0,z1,Dark3D);
      tsSlantCube:
          begin
           Old:=TeeNumCylinderSides;
           TeeNumCylinderSides:=4;
           {$IFDEF FMX}with Round do{$ENDIF}
              Cylinder(True,Left,Top,Right,Bottom,z0,z1,Dark3D);
           TeeNumCylinderSides:=Old;
          end;

      tsVisual :
          begin
            tmpVisual:=Visuals[tmp];

            if Assigned(tmpVisual) then
            with TVisualAccess(tmpVisual) do
            begin
              SetBounds(R,z0,z1,ParentChart.Height);
              SetFormatting(Self.Pen,Self.Brush,False,tmpColor,Transparency);

              TCanvas3DAccess(ParentChart.Canvas).BeginEntity(IntToStr(tmp),tmpVisual);
              TCanvas3DAccess(ParentChart.Canvas).EndEntity;
            end;
          end;
    end;

    if Assigned(IBlend) and (Transparency>0) then
       IBlend.DoBlend(Transparency);

    if tmpHover then
       ParentChart.Canvas.AssignVisiblePen(Pen);
  end;
end;

Function TTowerSeries.DoCompare(a,b:Integer):Integer;
begin
  if ISorted[a].Y>ISorted[b].Y then
     result:=1
  else
  if ISorted[a].Y<ISorted[b].Y then
     result:=-1
  else
     result:=0;
end;

procedure TTowerSeries.DoSwap(a,b:Integer);
var tmp : TTowerPosition;
begin
  tmp:=ISorted[a];
  ISorted[a]:=ISorted[b];
  ISorted[b]:=tmp;
end;

procedure TTowerSeries.DrawAllValues;
var
  ITowers : Array of TTowerSeries;
  ITowersCount : Integer;
  tmpNeedsSort : Boolean;

  procedure GuessTowers;
  var t : Integer;
      tmp : TChartSeries;
  begin
    ITowersCount:=0;

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

      if tmp.Active and SameClass(tmp) and (tmp.Count>0) then
      begin
        Inc(ITowersCount);

        SetLength(ITowers,ITowersCount);
        ITowers[ITowersCount-1]:=TTowerSeries(tmp);

        TTowerSeries(tmp).IChangeBrush:=True;
      end;
    end;

    SetLength(ISorted,ITowersCount);
  end;

  Procedure DrawCell(x,z:Integer);

    procedure SortPoints;
    var t : Integer;
        tmpTower : TTowerSeries;
    begin
      for t:=0 to ITowersCount-1 do
      begin
        tmpTower:=ITowers[t];

        ISorted[t].Tower:=tmpTower;
        ISorted[t].Y:=tmpTower.CalcYPos(tmpTower.GridIndex[x,z]);
      end;

      TeeSort(0,ITowersCount-1,DoCompare,DoSwap);
    end;

  var t : Integer;
      tmpTower : TTowerSeries;
  begin
    if tmpNeedsSort then
    begin
      SortPoints;

      for t:=ITowersCount-1 downto 0 do
      begin
        tmpTower:=TTowerSeries(ISorted[t].Tower);

        tmpTower.IOffW:=tmpTower.FPercWidth*0.005;
        tmpTower.IOffD:=tmpTower.FPercDepth*0.005;

        tmpTower.DoDrawCell(x,z);
      end;
    end
    else
       DoDrawCell(x,z);
  end;

  Procedure DrawCells;
  var x : Integer;
      z : Integer;
  begin
    IOffW:=FPercWidth*0.005;
    IOffD:=FPercDepth*0.005;

    if Transparency>0 then
       IBlend:=ParentChart.Canvas.BeginBlending(TeeZeroRect,Transparency)
    else
       IBlend:=nil;

    if BackFaced then
      if not DrawValuesForward then
        for x:=NumXValues downto 1 do
            for z:=1 to NumZValues do DrawCell(x,z)
      else
        for x:=1 to NumXValues do
            for z:=1 to NumZValues do DrawCell(x,z)
    else
      if not DrawValuesForward then
        for x:=NumXValues downto 1 do
            for z:=NumZValues downto 1 do DrawCell(x,z)
      else
        for x:=1 to NumXValues do
            for z:=NumZValues downto 1 do DrawCell(x,z);

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

  procedure CalcFirstIndex;
  var t   : Integer;
      tmp : TChartSeries;
  begin
    IFirstIndex:=True;

    if FStacked<>tsNone then
    begin
      for t:=0 to ParentChart.SeriesCount-1 do
      begin
        tmp:=ParentChart[t];

        if tmp=Self then
           break
        else
        if tmp.Active and SameClass(Self) then
        begin
          IFirstIndex:=False;
          break;
        end;
      end;
    end;
  end;

  procedure DoDraw;
  begin
    IChangeBrush:=tmpNeedsSort or (Brush.Style<>bsClear);

    with ParentChart.Canvas do
    begin
      AssignVisiblePen(Self.Pen);
      AssignBrush(Self.Brush,SeriesColor);

      if tmpNeedsSort then
         GuessTowers;

      DrawCells;

      ITowers:=nil;
      ISorted:=nil;
    end;
  end;

begin
  tmpNeedsSort:=(Stacked<>tsNone) and (not ParentChart.Canvas.SupportsFullRotation);

  CalcFirstIndex;

  if tmpNeedsSort then
  begin
    if IFirstIndex then
       DoDraw
  end
  else
    DoDraw;
end;

procedure TTowerSeries.SetPercDepth(const Value: Integer);
begin
  SetIntegerProperty(FPercDepth,Value);
end;

procedure TTowerSeries.SetPercWidth(const Value: Integer);
begin
  SetIntegerProperty(FPercWidth,Value);
end;

procedure TTowerSeries.SetOrigin(const Value: Double);
begin
  SetDoubleProperty(FOrigin,Value);
end;

procedure TTowerSeries.SetUseOrigin(Value: Boolean);
begin
  SetBooleanProperty(FUseOrigin,Value);
end;

procedure TTowerSeries.SetOtherTowers;
var t : Integer;
begin
  if Assigned(ParentChart) then
     for t:=0 to ParentChart.SeriesCount-1 do
         if SameClass(ParentChart[t]) then
            TTowerSeries(ParentChart[t]).FStacked:=FStacked;
end;

procedure TTowerSeries.SetStacked(const Value: TTowerStacked);
begin
  if FStacked<>Value then
  begin
    FStacked:=Value;
    SetOtherTowers;
    Repaint;
  end;
end;

procedure TTowerSeries.SetTowerStyle(const Value: TTowerStyle);
begin
  if FTowerStyle<>Value then
  begin
    FTowerStyle:=Value;
    Repaint;
  end;
end;

Function TTowerSeries.PointOrigin(ValueIndex:Integer; SumAll:Boolean):Double;
var t         : Integer;
    tmpSeries : TChartSeries;
    tmp       : Double;
Begin
  result:=FOrigin;

  if Assigned(ParentChart) then
  with ParentChart do
  begin
    for t:=0 to SeriesCount-1 do
    Begin
      tmpSeries:=Series[t];

      if (not SumAll) and (tmpSeries=Self) then
         Break
      else
      With tmpSeries do
      if Active and tmpSeries.SameClass(Self) and (Count>ValueIndex) then
      if not IsNull(ValueIndex) then
      begin
        tmp:=GetOriginValue(ValueIndex);

        //TV52012757
        if tmp<0 then
           result:=result-tmp
        else
           result:=result+tmp;
      end;
    end;
  end;
end;

procedure TTowerSeries.SetDark3D(const Value: Boolean);
begin
  SetBooleanProperty(FDark3D,Value);
end;

procedure TTowerSeries.AssignFormatting(Source: TChartSeries);
begin
  if Source is TTowerSeries then
  With TTowerSeries(Source) do
  begin
    Self.FDark3D       :=FDark3D;
    Self.FPercDepth    :=FPercDepth;
    Self.FPercWidth    :=FPercWidth;
    Self.FTowerStyle   :=FTowerStyle;
  end;

  inherited;
end;

procedure TTowerSeries.Assign(Source: TPersistent);
begin
  if Source is TTowerSeries then
  With TTowerSeries(Source) do
  begin
    Self.FOrigin    :=FOrigin;
    Self.FStacked   :=FStacked;
    Self.FUseOrigin :=FUseOrigin;
  end;

  inherited;
end;

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

  FBandPen:=CreateChartPen;
  FSides:=TSurfaceSides.Create(Self);

  UseColorRange:=False;
  UsePalette:=True;

  Pen.Hide;
end;

Destructor TIsoSurfaceSeries.Destroy;
begin
  FSides.Free;
  FBandPen.Free;
  inherited;
end;

function TIsoSurfaceSeries.CalcXYPoint(const P:TLevelPoint3D):TPoint;
begin
  result.X:=ICalcX(P.X);
  result.Y:=ICalcY(P.Y);
end;

Procedure TIsoSurfaceSeries.DrawAllValues;
var OldFast : Boolean;
begin
  // Set palette colors calculated from "range" colors
  // (StartColor, MidColor and EndColor)
  if UseColorRange then
     GeneratePalette;

  PaletteLength:=Length(Palette);

  if PaletteLength=0 then
     Exit;

  // Pending: Do not force disable FastBrush (problem is with TGLCanvas)
  OldFast:=ParentChart.Canvas.SupportsFullRotation and FastBrush;
  if OldFast then
     FastBrush:=False;

  xv:=XValues.Value;
  zv:=ZValues.Value;
  v:=MandatoryValueList.Value;

  INoFixAlign:=TCanvas3DAccess(ParentChart.Canvas).INoFixAlign;

  HasImage:=Brush.Image.Valid;

  ITransp:=(Transparency>0) and
           (not ParentChart.Canvas.SupportsFullRotation);

  ICalcYPos:=CalcYPosValue(YPosition);

  IDifs:=TLevelDifs.Create(Palette[0].UpToValue,Palette[PaletteLength-1].UpToValue,Self);
  try
    // Disable antialias to speed-up:
    TCanvasAccess(ParentChart.Canvas).SetAntiAlias(False);

    inherited;

    TCanvasAccess(ParentChart.Canvas).SetAntiAlias(True);

  finally
    IDifs.Free;
  end;

  IPoints2D:=nil;

  if OldFast then
     FastBrush:=True;
end;

procedure TIsoSurfaceSeries.PrepareCalcPos;
begin
  inherited;

  if Assigned(ParentChart) and (not ParentChart.View3D) then
     IYValue:=ZValues.Value;
end;

procedure TIsoSurfaceSeries.FillSegment2D(const P:Array of TLevelPoint3D);
var tmp : Integer;
    t   : Integer;
begin
  tmp:=Length(P);

  if Length(IPoints2D)<>tmp then
     SetLength(IPoints2D,tmp);

  for t:=0 to tmp-1 do
      IPoints2D[t]:=CalcXYPoint(P[t]);

  ParentChart.Canvas.Polygon(IPoints2D);
end;

procedure TIsoSurfaceSeries.FillSegment3D(const P:Array of TLevelPoint3D);
var tmp : Integer;
    t   : Integer;
    tmpP : TLevelPoint3D;
    Points3D : Array of TPoint3D;
    tmpAxis : TChartAxis;
begin
  tmpAxis:=ParentChart.DepthAxis;

  tmp:=Length(P);

  SetLength(Points3D,tmp);

  for t:=0 to tmp-1 do
  begin
    tmpP:=P[t];

    Points3D[t].x:=ICalcX(tmpP.X);

    if UseYPosition then
       Points3D[t].y:=ICalcYPos
    else
       Points3D[t].y:=ICalcY(tmpP.Z);

    Points3D[t].z:=tmpAxis.CalcPosValue(tmpP.Y);
  end;

  ParentChart.Canvas.Polygon3D(Points3D);
end;

procedure TIsoSurfaceSeries.DrawSegmentLine(const A,B:TLevelPoint);

  function CalcPoint(const Value:TLevelPoint):TPoint3D;
  begin
    result.x:=ICalcX(Value.X);

    if ParentChart.View3D then
    begin
      if UseYPosition then
         result.y:=ICalcYPos
      else
         result.y:=ICalcY(ILevelValue);

      result.z:=ParentChart.DepthAxis.CalcYPosValue(Value.Y);;
    end
    else
      result.y:=ICalcY(Value.Y);
  end;

var P0,P1 : TPoint3D;
begin
  P0:=CalcPoint(A);
  P1:=CalcPoint(B);

  if ParentChart.View3D then
  with ParentChart.Canvas do
  begin
    MoveTo3D(P0);
    LineTo3D(P1);
  end
  else
  if INoFixAlign then
     ParentChart.Canvas.Line(P0.x,P0.y,P1.x,P1.y)
  else
     ParentChart.Canvas.Line(P0.x-1,P0.y-1,P1.x-1,P1.y-1);
end;

procedure TIsoSurfaceSeries.DrawCell(x, z: Integer);

  procedure DrawLines;
  var t : Integer;
  begin
    // Enable antialias, if possible:
    TCanvasAccess(ParentChart.Canvas).SetAntiAlias(True);

    ParentChart.Canvas.AssignVisiblePen(BandPen);

    for t:=0 to PaletteLength-1 do { for each level }
    begin
      ILevelValue:=Palette[t].UpToValue;

      if (ILevelValue>=TLevelDifs(IDifs).DiffMin) and (ILevelValue<=TLevelDifs(IDifs).DiffMax) then { if inside one level }
         { calculate all lines for one level in this XZ cell }
         TLevelDifs(IDifs).Loop(ILevelValue,DrawSegmentLine);
    end;

    ParentChart.Canvas.Pen.Style:=psClear;

    // Disable antialias again:
    TCanvasAccess(ParentChart.Canvas).SetAntiAlias(False);
  end;

  procedure PrepareBrush(const AColor:TColor);
  begin
    with ParentChart.Canvas.Brush do
    if {$IFDEF FMX}Kind{$ELSE}Style{$ENDIF}=bsClear then
    begin
      {$IFDEF FMX}Kind:=Self.Brush.Kind{$ELSE}Style:=Self.Brush.Style{$ENDIF};

      PrepareFastBrush;
      SetBrushColor(AColor);
    end
    else
    // Optimization, do not call SetBrushColor:
    begin
      {$IFNDEF FMX}
      if IsFastBrush then
         TeeSetDCBrushColor(CanvasDC,AColor)
      else
      {$ENDIF}
         Color:=AColor;
    end;
  end;

  procedure PrepareLevel(Level:Integer);
  var tmp : TColor;
  begin
    tmp:=Palette[Level].Color;

    if tmp=clNone then
       ParentChart.Canvas.Brush.Style:=bsClear // <-- null level, do not paint
    else
    begin
      if HasImage then
      begin
        // ParentChart.Canvas.BackColor:=Palette[tmpPal].Color;
        ParentChart.Canvas.Brush.Bitmap{$IFDEF FMX}.Bitmap{$ENDIF}:=Self.Brush.Image.Bitmap;
      end
      else
      if UseColorRange or UsePalette then
         PrepareBrush(tmp);
    end;
  end;

var
  IPoints  : TFourPoints;

  procedure SetBlendRectangle;
  var tmpR : TRect;
  begin
    if ParentChart.View3D then
    begin
      if UseYPosition then
      begin
        Points[0].Y:=ICalcYPos;
        Points[1].Y:=ICalcYPos;
        Points[2].Y:=ICalcYPos;
        Points[3].Y:=ICalcYPos;
      end;

      PointsTo2D(CalcZPos(ValueIndex0),CalcZPos(ValueIndex2),IPoints);

      IBlender.SetRectangle(RectFromPolygon(IPoints,4));
    end
    else
    begin
      tmpR.Left:=CalcXPos(ValueIndex0);  // Points[0].X ?
      tmpR.Top:=GetVertAxis.CalcYPosValue(ZValues.Value[ValueIndex0]);
      tmpR.Right:=CalcXPos(ValueIndex2); // Points[2].X ?
      tmpR.Bottom:=GetVertAxis.CalcYPosValue(ZValues.Value[ValueIndex2]);

      IBlender.SetRectangle(tmpR);
    end;
  end;

  procedure FillCellLevels(const AProc:TLevelDifsFill);
  var t : Integer;
      tmpValue : TChartValue;
  begin
    // Loop all levels, starting from second level (index 1)
    for t:=1 to PaletteLength-1 do
    begin
      tmpValue:=Palette[t].UpToValue;

      // Skip levels below min cell value
      if tmpValue>=TLevelDifs(IDifs).MinLevel then
      begin
        // Fill cell parts for this level:
        PrepareLevel(t);

        TLevelDifs(IDifs).LoopFill(Palette[Pred(t)].UpToValue, tmpValue, AProc);

        // Stop if level value is bigger than cell max:
        if tmpValue>TLevelDifs(IDifs).MaxLevel then
           break;
      end;
    end;
  end;

var OldStyle : TPenStyle;
    tmpColor : TColor;
    tmpCall  : Boolean;
begin
  if FourGridIndex(X,Z) then
  begin
    tmpColor:=InternalColor(ValueIndex0);

    if tmpColor=clTeeColor then
    begin
      tmpCall:=False;

      if TLevelDifs(IDifs).Prepare(ValueIndex2,ValueIndex3,ValueIndex0,ValueIndex1,False) then
      begin
        with ParentChart.Canvas.Pen do
        begin
          OldStyle:=Style;

          if OldStyle<>psClear then
          begin
            tmpCall:=True;
            Style:=psClear;
          end;
        end;

        if tmpCall then
        begin
          {$IFNDEF FMX}
          // Important speed optimization:
          with ParentChart.Canvas.Brush do
          if Style<>Self.Brush.Style then
             Style:=Self.Brush.Style;
          {$ENDIF}

          PrepareFastBrush;
        end;

        if ITransp and (not ParentChart.Canvas.SupportsAlpha) and Assigned(IBlender) then
           SetBlendRectangle;

        if ParentChart.View3D then
           FillCellLevels(FillSegment3D)
        else
           FillCellLevels(FillSegment2D);

        // Draw IsoBand lines:
        if BandPen.Visible then
           DrawLines;

        if ITransp then
           if Assigned(IBlender) then
              IBlender.DoBlend(Transparency);

        // Restore canvas
        if tmpCall then
        with ParentChart.Canvas do
        begin
          Brush.Style:=bsClear;

          if Pen.Style<>OldStyle then
             AssignVisiblePen(Self.Pen);
        end;
      end;
    end
    else
    begin
      PrepareBrush(tmpColor);
      tmpCall:=True;
    end;

     if tmpCall then  // Important speed opt.
        inherited;
  end;
end;

Procedure TIsoSurfaceSeries.PrepareForGallery;
begin
  inherited;
  ParentChart.Axes.FastCalc:=True;
  FastBrush:=True;
  BandPen.Hide;
end;

Procedure TIsoSurfaceSeries.DrawSidePortion(var P:TFourPoints; Z0,Z1:Integer);
var t   : Integer;
    IsZ : Boolean;
    P3D : Array[0..3] of {$IFDEF FMX}TPoint3DFloat{$ELSE}TPoint3D{$ENDIF};

  procedure Fill(Outline:Boolean);
  var Old : TPenStyle;
  begin
    if Sides.Levels then
    with ParentChart.Canvas.Brush do
         if {$IFDEF FMX}Kind{$ELSE}Style{$ENDIF}<>bsClear then
         begin
           Color:=Palette[t].Color;

           if Color=clNone then
              Exit;
         end;

    if not Outline then
    begin
      Old:=ParentChart.Canvas.Pen.Style;
      ParentChart.Canvas.Pen.Style:=psClear;
    end
    else Old:=psSolid;

    { Strange bug, horiz lines outside axes:
    if IsZ then
    begin
      P3D[0].y:=P[0].y;
      P3D[1].y:=P[1].y;
      P3D[2].y:=P[2].y;
      P3D[3].y:=P[3].y;

      ParentChart.Canvas.Polygon3D(P3D);
    end
    else
    }
      ParentChart.Canvas.PlaneFour3D(P,Z0,Z1);

    if not Outline then
       ParentChart.Canvas.Pen.Style:=Old;
  end;

  procedure DrawLine;
  begin
    if Sides.Pen.Visible then
    with ParentChart.Canvas do
    begin
      AssignVisiblePen(Sides.Pen);
      MoveTo3D(P[1].X,P[1].Y-1,Z0);
      LineTo3D(P[2].X,P[2].Y-1,Z1);
      Pen.Style:=psClear;
    end;
  end;

var tmp,
    tmp0,
    tmp3,
    tmpMin,
    tmpMin2,
    tmpPrev : TCoordinate;
    tmpDif  : Integer;
    Dif     : Double;
begin
  tmp0:=P[0].Y;
  tmp3:=P[3].Y;

  IsZ:=z0<>z1;

  if tmp3=tmp0 then
     Dif:=0
  else
  if IsZ then
  begin
    Dif:=Abs((z1-z0)/(tmp3-tmp0));

    P3D[0].x:=P[0].x;
    P3D[1].x:=P[1].x;
    P3D[2].x:=P[2].x;
    P3D[3].x:=P[3].x;

    P3D[0].z:=z0;
    P3D[1].z:=z0;
    P3D[2].z:=z1;
    P3D[3].z:=z1;
  end
  else
    Dif:=Abs((P[0].X-P[3].X)/(tmp3-tmp0));

  tmpMin:=tmp0;
  tmpMin2:=tmp3;

  if tmp0>tmp3 then
     {$IFDEF FMX}
     SwapSingle(tmpMin,tmpMin2)
     {$ELSE}
     SwapInteger(tmpMin,tmpMin2)
     {$ENDIF}
     ;

  tmpPrev:=P[1].Y;

  ParentChart.Canvas.AssignBrush(Sides.Brush);

  for t:=0 to PaletteLength-1 do
  begin
    tmp:=GetVertAxis.CalcYPosValue(Palette[t].UpToValue);

    if tmp<tmpPrev then
    begin
      P[1].Y:=tmpPrev;
      P[2].Y:=tmpPrev;

      if tmp<tmpMin2 then
      begin
        if tmp<tmpMin then
           tmp:=tmpMin;

        if tmpPrev>tmpMin2 then
        begin
          P[0].Y:=tmpMin2;
          P[3].Y:=tmpMin2;

          Fill(False);
          DrawLine;

          P[1].Y:=tmpMin2;
          P[2].Y:=tmpMin2;
        end;

        P[0].Y:=tmp;
        P[3].Y:=tmp;

        tmpDif:=Round(Dif*(tmp-tmpMin));

        if tmp3=tmpMin2 then
        begin
          if IsZ then
             P3D[3].z:=P3D[0].z-tmpDif
          else
             P[3].X:=P[0].X-tmpDif;

          Fill(False);

          if IsZ then
             P3D[2].z:=P3D[3].z
          else
             P[2].X:=P[3].X;
        end
        else
        begin
          if IsZ then
             P3D[0].z:=P3D[3].z+tmpDif
          else
             P[0].X:=P[3].X+tmpDif;

          Fill(False);

          if IsZ then
             P3D[1].z:=P3D[0].z
          else
             P[1].X:=P[0].X;
        end;
      end
      else
      begin
        P[0].Y:=tmp;
        P[3].Y:=tmp;
        Fill(True);
      end;

      if tmp<=tmpMin then
         Break;

      tmpPrev:=tmp;

      DrawLine;
    end;
  end;
end;

function TIsoSurfaceSeries.ShouldDrawSides:Boolean;
begin
  result:=(inherited ShouldDrawSides) or Sides.Pen.Visible;
end;

function TIsoSurfaceSeries.ShouldDrawFast:Boolean;
begin
  result:=False;
end;

procedure TIsoSurfaceSeries.SetBandPen(const Value: TChartPen);
begin
  FBandPen.Assign(Value);
end;

procedure TIsoSurfaceSeries.SetSides(const Value: TSurfaceSides);
begin
  FSides.Assign(Value);
end;

procedure TIsoSurfaceSeries.SetUseY(const Value: Boolean);
begin
  SetBooleanProperty(FUseY,Value);
end;

procedure TIsoSurfaceSeries.SetYPosition(const Value: TChartValue);
begin
  if FYPosition<>Value then
  begin
    FYPosition:=Value;
    Repaint;
  end;
end;

class function TIsoSurfaceSeries.GetEditorClass: String;
begin
  result:='TIsoSurfaceEditor';
end;

procedure TIsoSurfaceSeries.AssignFormatting(Source: TChartSeries);
begin
  if Source is TIsoSurfaceSeries then
  with TIsoSurfaceSeries(Source) do
  begin
    Self.BandPen:=BandPen;
    Self.Sides:=Sides;
  end;

  inherited;
end;

procedure TIsoSurfaceSeries.Assign(Source: TPersistent);
begin
  if Source is TIsoSurfaceSeries then
  with TIsoSurfaceSeries(Source) do
  begin
    Self.FUseY:=FUseY;
    Self.FYPosition:=FYPosition;
  end;

  inherited;
end;

Function TIsoSurfaceSeries.MinYValue:TChartValue;
begin
  if Assigned(ParentChart) and ParentChart.View3D then
     result:=inherited MinYValue
  else
     result:=MinZValue;
end;

Function TIsoSurfaceSeries.MaxYValue:TChartValue;
begin
  if Assigned(ParentChart) and ParentChart.View3D then
     result:=inherited MaxYValue
  else
     result:=MaxZValue;
end;

{ TSurfaceSides }

Constructor TSurfaceSides.Create(const Series:TSurfaceSeries);
begin
  inherited Create;
  ISeries:=Series;
  FLevels:=True;
  FPen:=TChartHiddenPen.Create(CanvasChanged);
end;

Destructor TSurfaceSides.Destroy;
begin
  FPen.Free;
  inherited;
end;

procedure TSurfaceSides.Assign(Source: TPersistent);
begin
  if Source is TSurfaceSides then
  with TSurfaceSides(Source) do
  begin
    // Self.Brush:=Brush; already done at Surface series
    Self.FLevels:=FLevels;
    Self.Pen:=Pen;
  end
  else
    inherited;
end;

procedure TSurfaceSides.CanvasChanged(Sender: TObject);
begin
  ISeries.Repaint;
end;

function TSurfaceSides.GetBrush:TChartBrush;
begin
  result:=ISeries.SideBrush;
end;

procedure TSurfaceSides.SetBrush(const Value: TChartBrush);
begin
  ISeries.SideBrush:=Value;
end;

procedure TSurfaceSides.SetLevels(const Value: Boolean);
begin
  if FLevels<>Value then
  begin
    FLevels:=Value;
    ISeries.Repaint;
  end;
end;

procedure TSurfaceSides.SetPen(const Value: TChartHiddenPen);
begin
  FPen.Assign(Value);
end;

type
  // Internal class with published properties so the TGrid3DSeriesEditor
  // can find them via reflection (RTTI).
  TPaletteSeries=class(TCustom3DPaletteSeries)
  published
    property EndColor;
    property MidColor;
    property LegendEvery;
    property PaletteMin;
    property PaletteStep;
    property PaletteSteps;
    property PaletteStyle;
    property StartColor;
    property UseColorRange;
    property UsePalette;
    property UsePaletteMin;
  end;

  // Links the internal Palette series with an external Chart, to enable
  // "invalidate".
  TSpecialChart=class(TCustomChart)
  private
    ILink : TChartSeries;
  public
    procedure Invalidate; override;
  end;

{ TSpecialChart }

procedure TSpecialChart.Invalidate;
begin
  if Assigned(ILink) then
     ILink.Repaint;
end;

{ TSeriesColorPalette }

Constructor TSeriesColorPalette.Create(const AOwner:TChartSeries);
begin
  inherited Create;

  Series:=TPaletteSeries.Create(nil);
  IChart:=TSpecialChart.Create(nil);
  TSpecialChart(IChart).ILink:=AOwner;
  Series.ParentChart:=IChart;
end;

Destructor TSeriesColorPalette.Destroy;
begin
  Series.Free;
  IChart.Free;
  inherited;
end;

type
  TPaletteAccess=class(TCustom3DPaletteSeries);

procedure TSeriesColorPalette.Assign(Source:TPersistent);
begin
  if Source is TSeriesColorPalette then
     Series.Assign(TSeriesColorPalette(Source).Series)
  else
     inherited;
end;

procedure TSeriesColorPalette.InitColors(Color:TColor; Const MinValue,MaxValue:TChartValue);
begin
  Series.BeginUpdate;
  Series.Clear;
  Series.AddXYZ(0,MinValue,0);
  Series.AddXYZ(0,MaxValue,0);
  Series.Color:=Color;
  TPaletteAccess(Series).DoBeforeDrawChart;
  Series.EndUpdate;
end;

function TSeriesColorPalette.GetEndColor: TColor;
begin
  result:=Series.EndColor;
end;

function TSeriesColorPalette.GetLegendEvery: Integer;
begin
  result:=Series.LegendEvery;
end;

function TSeriesColorPalette.GetMidColor: TColor;
begin
  result:=Series.MidColor;
end;

function TSeriesColorPalette.GetPalette: TCustom3DPalette;
begin
  result:=Series.Palette;
end;

function TSeriesColorPalette.GetPaletteMin: Double;
begin
  result:=Series.PaletteMin;
end;

function TSeriesColorPalette.GetPaletteStep: Double;
begin
  result:=Series.PaletteStep;
end;

function TSeriesColorPalette.GetPaletteSteps: Integer;
begin
  result:=Series.PaletteSteps;
end;

function TSeriesColorPalette.GetPaletteStyle: TTeePaletteStyle;
begin
  result:=Series.PaletteStyle;
end;

function TSeriesColorPalette.GetStartColor: TColor;
begin
  result:=Series.StartColor;
end;

function TSeriesColorPalette.GetUseColorRange: Boolean;
begin
  result:=Series.UseColorRange;
end;

function TSeriesColorPalette.GetUsePalette: Boolean;
begin
  result:=Series.UsePalette;
end;

function TSeriesColorPalette.GetUsePaletteMin: Boolean;
begin
  result:=Series.UsePaletteMin;
end;

procedure TSeriesColorPalette.SetEndColor(const Value: TColor);
begin
  Series.EndColor:=Value;
end;

procedure TSeriesColorPalette.SetLegendEvery(const Value: Integer);
begin
  Series.LegendEvery:=Value;
end;

procedure TSeriesColorPalette.SetMidColor(const Value: TColor);
begin
  Series.MidColor:=Value;
end;

procedure TSeriesColorPalette.SetPalette(const Value: TCustom3DPalette);
begin
  Series.Palette:=Value;
end;

procedure TSeriesColorPalette.SetPaletteMin(const Value: Double);
begin
  Series.PaletteMin:=Value;
end;

procedure TSeriesColorPalette.SetPaletteStep(const Value: Double);
begin
  Series.PaletteStep:=Value;
end;

procedure TSeriesColorPalette.SetPaletteSteps(const Value: Integer);
begin
  Series.PaletteSteps:=Value;
end;

procedure TSeriesColorPalette.SetPaletteStyle(const Value: TTeePaletteStyle);
begin
  Series.PaletteStyle:=Value;
end;

procedure TSeriesColorPalette.SetStartColor(const Value: TColor);
begin
  Series.StartColor:=Value;
end;

procedure TSeriesColorPalette.SetUseColorRange(const Value: Boolean);
begin
  Series.UseColorRange:=Value;
end;

procedure TSeriesColorPalette.SetUsePalette(const Value: Boolean);
begin
  Series.UsePalette:=Value;
end;

procedure TSeriesColorPalette.SetUsePaletteMin(const Value: Boolean);
begin
  Series.UsePaletteMin:=Value;
end;

{ TBubbleCloudSeries }

Constructor TBubbleCloudSeries.Create(AOwner: TComponent);
begin
  inherited;

  FTimesZOrder:=1;
  HasZValues:=False;

  // Disable "palette" legend mode
  DefaultLegend:=True;

  FSizeRatio:=1.2;
  
  Marks.AutoPosition:=False;
  Marks.AutoSize:=True;
  Marks.Visible:=True;
  Marks.Transparent:=True;
  Marks.Font.Color:=clWhite;
end;

function TBubbleCloudSeries.UseAxis: Boolean;
begin
  result:=False;
end;

procedure OffsetPoint(var P:TPoint; const AOffset:TPoint);
begin
  P.X:=P.X+AOffset.X;
  P.Y:=P.Y+AOffset.Y;
end;

function YCenter(const R:TRect):TCoordinate;
begin
  result:=(R.Bottom+R.Top) {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF};
end;

function TBubbleCloudSeries.Distance(A,B:Integer):Double;
begin
  result:=TeeHypot({$IFDEF FPC}1.0*{$ENDIF} // <-- resolve overload
            ICenters[A].X-ICenters[B].X,ICenters[A].Y-ICenters[B].Y);
end;

Procedure TBubbleCloudSeries.DoBeforeDrawValues;

  function CalcRadius(Index:Integer):Single;
  begin
    if FSeparationUnits=muPercent then
       result:=Radius(Index)*(1+(FSeparation*0.01)) // %
    else
       result:=Radius(Index)+FSeparation; // pixels
  end;

  procedure CalcBounds;
  var t,
      r : Integer;
      p : TPoint;
  begin
    if Count=0 then
       IBounds:=TeeZeroRect
    else
    begin
      r:=Round(CalcRadius(0));
      IBounds:=RectFromCenter(ICenters[0],r);

      for t:=1 to Count-1 do
      begin
        r:=Round(CalcRadius(t));
        p:=ICenters[t];

        IBounds.Left:=Min(IBounds.Left,p.x-r);
        IBounds.Top:=Min(IBounds.Top,p.y-r);
        IBounds.Right:=Max(IBounds.Right,p.x+r);
        IBounds.Bottom:=Max(IBounds.Bottom,p.y+r);
      end;
    end;
  end;

  function CircleIntersect(Index:Integer):Integer;
  var t : Integer;
      tmpR,
      tmp,
      d : Double;
  begin
    tmpR:=CalcRadius(Index);

    for t:=0 to Index-1 do
    begin
      d:=Distance(t,Index);

      tmp:=CalcRadius(t);

      if d<(tmp+tmpR-2) then
      begin
        result:=t;
        Exit;
      end;
    end;

    result:=-1;
  end;

  procedure CalcCenters;
  var tmpR0,
      tmpR1,
      tmpR2 : TChartValue;
      tmp0 : Integer;

    procedure CalcOneCenter(t:Integer);
    var a,h,d : Double;
        P2 : TPoint;
        difX,
        difY : TCoordinate;
    begin
      d:=Distance(tmp0,t-1);

      if d=0 then
         ICenters[t]:=ICenters[t-1]
      else
      begin
        a:=(Sqr(tmpR0+tmpR2)-Sqr(tmpR1+tmpR2)+Sqr(d))/(2*d);

        difX:=(ICenters[t-1].X-ICenters[tmp0].X);
        difY:=(ICenters[t-1].Y-ICenters[tmp0].Y);

        P2.X:=Round(ICenters[tmp0].X+a*difX/d);
        P2.Y:=Round(ICenters[tmp0].Y+a*difY/d);

        h:=Sqrt(Abs(Sqr(tmpR0+tmpR2)-Sqr(a)));

        ICenters[t].X:=Round(P2.X+h*difY/d);
        ICenters[t].Y:=Round(P2.Y-h*difX/d);
      end;
    end;

  var tmpCenter : TPoint;
      n,
      t : Integer;

      tmpSin   : {$IFDEF D15}Double{$ELSE}Extended{$ENDIF};
      tmpCos   : {$IFDEF D15}Double{$ELSE}Extended{$ENDIF};

      tmpRot : Single;
  begin
    tmpCenter.X:=(IRect.Right-IRect.Left) {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF};
    tmpCenter.Y:=(IRect.Bottom-IRect.Top) {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF};

    SetLength(ICenters,Count);
    SetLength(ITangents,Count);

    if Count>0 then
    begin
      ICenters[0]:=tmpCenter;

      tmpR0:=CalcRadius(0);
      tmp0:=0;

      if Count>1 then
      begin
        tmpR1:=CalcRadius(1);

        //ICenters[tmp0].Y:=ICenters[tmp0].Y-Round(tmpR0);

        SinCos(FRotation*TeePiStep,tmpSin,tmpCos);

        tmpRot:=tmpR0+tmpR1;

        ICenters[1].X:=tmpCenter.X+Round(tmpSin*tmpRot);
        ICenters[1].Y:=tmpCenter.Y+Round(tmpCos*tmpRot);

        if Count>2 then
        begin
          t:=2;

          repeat
            tmpR2:=CalcRadius(t);

            CalcOneCenter(t);

            // Check overlapping

            n:=CircleIntersect(t);

            if n<>-1 then
            begin
              tmpR0:=CalcRadius(n);
              tmp0:=n;

              CalcOneCenter(t);
            end;

            ITangents[t]:=tmp0;

            Inc(t);

            tmpR1:=tmpR2;

          until t=Count;
        end;
      end;
    end;
  end;

  procedure RescaleCenters;
  var t : Integer;
  begin
    Exit;

    if IScale<>1 then
    begin
      for t:=0 to Count-1 do
      begin
        ICenters[t].X:=IOffset.X+Round(ICenters[t].X*IScale);
        ICenters[t].Y:=IOffset.Y+Round(ICenters[t].Y*IScale);
      end;
    end;
  end;

var tmpS,
    tmpC : TPoint;

    HorizScale,
    VertScale : Double;
begin
  inherited;

  YValues.Order:=loDescending;
  YValues.Sort;

  tmpC.X:=(GetHorizAxis.IEndPos+GetHorizAxis.IStartPos) div 2;
  tmpC.Y:=(GetVertAxis.IEndPos+GetVertAxis.IStartPos) div 2;

  tmpS.X:=(GetHorizAxis.IEndPos-GetHorizAxis.IStartPos) div 2;
  tmpS.Y:=(GetVertAxis.IEndPos-GetVertAxis.IStartPos) div 2;

  HorizScale:=1;
  VertScale:=0.5;

  IRect.Left:=Round(tmpC.X-HorizScale*tmpS.X);
  IRect.Top:=Round(tmpC.Y-VertScale*tmpS.Y);
  IRect.Right:=Round(tmpC.X+HorizScale*tmpS.X);
  IRect.Bottom:=Round(tmpC.Y+VertScale*tmpS.Y);

  IOffset:=TeeZeroPoint;
  IScale:=1;

  CalcCenters;
  CalcBounds;

  IOffset.Y:=YCenter(IRect)-YCenter(IBounds);
//  IScale:=Abs(IRect.Bottom-IRect.Top)/Abs(IBounds.Bottom-IBounds.Top);

  RescaleCenters;
end;

Procedure TBubbleCloudSeries.Assign(Source:TPersistent);
begin
  if Source is TBubbleCloudSeries then
  with TBubbleCloudSeries(Source) do
  begin
    Self.FRotation:=FRotation;
    Self.FSeparation:=FSeparation;
    Self.FSeparationUnits:=FSeparationUnits;
    Self.FSizeRatio:=FSizeRatio;
  end;

  inherited;
end;

function TBubbleCloudSeries.CalcXPos(ValueIndex: Integer): Integer;
begin
  if Length(ICenters)>ValueIndex then
     result:={$IFDEF FMX}Round{$ENDIF}(ICenters[ValueIndex].X)
  else
     result:=inherited CalcXPos(ValueIndex);
end;

function TBubbleCloudSeries.CalcYPos(ValueIndex: Integer): Integer;
begin
  if Length(ICenters)>ValueIndex then
     result:={$IFDEF FMX}Round{$ENDIF}(ICenters[ValueIndex].Y)
  else
     result:=inherited CalcYPos(ValueIndex);
end;

function TBubbleCloudSeries.Radius(ValueIndex:Integer):TChartValue;
var tmpMin,
    tmpRange,
    tmp : TChartValue;
begin
  tmpRange:=MandatoryValueList.Range;

  if Count<2 then
     tmp:=1
  else
  begin
    tmpMin:=(FSizeRatio-1)*tmpRange;

    if (tmpMin+tmpRange)=0 then
       tmp:=0
    else
       tmp:=Max(0,tmpMin+MandatoryValueList.Value[ValueIndex]-MandatoryValueList.MinValue)/
            (tmpMin+tmpRange);
  end;

  result:=IScale*0.5*0.5*tmp*Min(IRect.Right-IRect.Left,IRect.Bottom-IRect.Top);
end;

Procedure TBubbleCloudSeries.DrawMark( ValueIndex:Integer; Const St:String;
                        APosition:TSeriesMarkPosition);
var tmpSize : Single;
    tmpL    : Integer;
    Old     : TColor;
begin
  if Marks.AutoSize then
  begin
    tmpL:=Length(St);

    if tmpL=0 then
       tmpSize:=1
    else
       tmpSize:=1.0*Radius(ValueIndex)/tmpL;

    if tmpSize<1 then tmpSize:=1;

    Marks.Font.SizeFloat:=tmpSize;
  end;

  APosition.LeftTop:=ICenters[ValueIndex];
  APosition.LeftTop.X:=APosition.LeftTop.X-(APosition.Width div 2);

  ParentChart.Canvas.AssignFont3D(Marks.Font);
  APosition.LeftTop.Y:=IOffset.Y+APosition.LeftTop.Y-(ParentChart.Canvas.FontHeight div 2);

  Old:=Marks.Font.Color;

  if GetValueColor(ValueIndex)=Old then
     Marks.Font.Color:={$IFDEF FMX}claBlack{$ELSE}clBlack{$ENDIF};

  inherited;

  Marks.Font.Color:=Old;
end;

class Function TBubbleCloudSeries.GetEditorClass:String;
begin
  result:='TBubbleCloudEditor';
end;

function TBubbleCloudSeries.BubbleRect(ValueIndex:Integer):TRect;
var P : TPoint;
begin
  P.X:=ICenters[ValueIndex].X+IOffset.X;
  P.Y:=ICenters[ValueIndex].Y+IOffset.Y;

  result:=RectFromCenter(P,Round(Radius(ValueIndex)));
end;

{.$DEFINE TEEDEBUG}

procedure TBubbleCloudSeries.DrawValue(ValueIndex: Integer);
var tmpR : TRect;
    IBlender : TTeeBlend;
    tmpCanvas : TCanvas3D;

    {$IFDEF TEEDEBUG}
    tmp0 : Integer;

    r0,
    r1,
    r2,
    d,a : Double;
    P0,
    P2,
    P3 : TPoint;
    {$ENDIF}
begin
  inherited;

  tmpCanvas:=ParentChart.Canvas;

  if not PrepareHoverPen(ValueIndex) then
     tmpCanvas.AssignVisiblePen(Pen);

  tmpCanvas.AssignBrush(Brush,tmpCanvas.ColorFrom(GetValueColor(ValueIndex),
                        {$IFDEF FMX}255-{$ENDIF}tmpCanvas.ColorAlpha(Brush.Color)));

  tmpR:=BubbleRect(ValueIndex);

  if Transparency>0 then
     IBlender:=tmpCanvas.BeginBlending(TeeZeroRect,Transparency)
  else
     IBlender:=nil;

  if ParentChart.View3D then
     tmpCanvas.EllipseWithZ(tmpR,StartZ)
  else
     tmpCanvas.Ellipse(tmpR);

  if Transparency>0 then
     tmpCanvas.EndBlending(IBlender);

  {$IFDEF TEEDEBUG}
  if ValueIndex=Selected.HoverIndex then
  if ValueIndex>1 then
  begin
    tmpCanvas.Brush.Style:=bsClear;

    r2:=Radius(ValueIndex);

    tmp0:=ITangents[ValueIndex];

    r0:=Radius(tmp0);
    tmpR:=RectFromCenter(ICenters[tmp0],Round(r0+r2));

    OffsetRect(tmpR,IOffset.X,IOffset.Y);

    tmpCanvas.Ellipse(tmpR);

    r1:=Radius(ValueIndex-1);
    tmpR:=RectFromCenter(ICenters[ValueIndex-1],Round(r1+r2));

    OffsetRect(tmpR,IOffset.X,IOffset.Y);

    tmpCanvas.Ellipse(tmpR);

    //d:=(r0+r1);

    d:=Distance(tmp0,ValueIndex-1);

    a:=(Sqr(r0+r2)-Sqr(r1+r2)+Sqr(d))/(2.0*d);

    P2.X:=Round(ICenters[tmp0].X+a*(ICenters[ValueIndex-1].X-ICenters[tmp0].X)/d);
    P2.Y:=Round(ICenters[tmp0].Y+a*(ICenters[ValueIndex-1].Y-ICenters[tmp0].Y)/d);
    OffsetPoint(P2,IOffset);

    P0:=ICenters[tmp0];
    OffsetPoint(P0,IOffset);

    tmpCanvas.Line(P0,P2);

    tmpCanvas.Rectangle(RectFromCenter(P2,10));

    P3:=ICenters[ValueIndex];
    OffsetPoint(P3,IOffset);

    tmpCanvas.Line(P2,P3);
  end;

  if ValueIndex=0 then
  begin
    tmpCanvas.Brush.Style:=bsClear;

    tmpR:=IBounds;
    OffsetRect(tmpR,IOffset.X,IOffset.Y);
    tmpCanvas.Rectangle(tmpR);
  end;
  {$ENDIF}
end;

function TBubbleCloudSeries.Clicked(X, Y: Integer): Integer;
var t : Integer;
    P : TPoint;
begin
  result:=TeeNoPointClicked;

  P:=TeePoint(X,Y);

  if Length(ICenters)=Count then
  for t:=Count-1 downto 0 do
  if PointInEllipse(P,BubbleRect(t)) then
  begin
    result:=t;
    break;
  end;
end;

function TBubbleCloudSeries.IsSizeRatioStored: Boolean;
begin
  result:=FSizeRatio<>1.2;
end;

procedure TBubbleCloudSeries.SetSizeRatio(const Value: Double);
begin
  SetDoubleProperty(FSizeRatio,Value);
end;

procedure TBubbleCloudSeries.SetRotation(const Value: Integer);
begin
  SetIntegerProperty(FRotation,Value);
end;

procedure TBubbleCloudSeries.SetSeparation(const Value: Integer);
begin
  SetIntegerProperty(FSeparation,Value);
end;

procedure TBubbleCloudSeries.SetSeparationUnits(const Value: TTeeUnits);
begin
  if FSeparationUnits<>Value then
  begin
    FSeparationUnits:=Value;
    Repaint;
  end;
end;

initialization
  RegisterTeeSeries( TSurfaceSeries, @TeeMsg_GallerySurface,
                                     @TeeMsg_Gallery3D,1);
  RegisterTeeSeries( TContourSeries, @TeeMsg_GalleryContour,
                                     @TeeMsg_Gallery3D,1);
  RegisterTeeSeries( TWaterFallSeries, @TeeMsg_GalleryWaterFall,
                                       @TeeMsg_Gallery3D,1);
  RegisterTeeSeries( TColorGridSeries, @TeeMsg_GalleryColorGrid,
                                       @TeeMsg_Gallery3D,1);
  RegisterTeeSeries( TVector3DSeries, @TeeMsg_GalleryVector3D,
                                      @TeeMsg_Gallery3D,1);
  RegisterTeeSeries( TTowerSeries,    @TeeMsg_GalleryTower,
                                      @TeeMsg_Gallery3D,1);
  RegisterTeeSeries( TIsoSurfaceSeries,
                                    @TeeMsg_IsoSurfaceSeries,
                                    @TeeMsg_Gallery3D,1);
  RegisterTeeSeries(TBubbleCloudSeries, @TeeMsg_GalleryBubbleCloud,
                                     @TeeMsg_GallerySamples,1);
finalization
  UnRegisterTeeSeries([TSurfaceSeries,TContourSeries,TWaterFallSeries,
                       TColorGridSeries,TVector3DSeries,TTowerSeries,
                       TIsoSurfaceSeries,TBubbleCloudSeries]);
end.

