{********************************************}
{    TeeChart Map Series                     }
{ Copyright (c) 2000-2020 by Steema Software }
{    All Rights Reserved                     }
{********************************************}
unit TeeMapSeries;
{$I TeeDefs.inc}

interface

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

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

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

     {$IFDEF D21} // Inline Hint
     System.Math.Vectors,
     {$ENDIF}

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

     {$ELSE}
     Graphics,
      {$IFDEF D6}
      Types,
      {$ENDIF}
     {$ENDIF}

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

type
  TTeePolygon=class;

  TPolygonSeries=class(TChartSeries)
  protected
    Procedure CanvasChanged(Sender:TObject); override;
    Procedure DrawLegendShape(ValueIndex:Integer; Const Rect:TRect); override;
    Procedure NotifyValue(ValueEvent:TValueEvent; ValueIndex:Integer); override;
    procedure PrepareLegendCanvas(ValueIndex:Integer; Var BackColor:TColor;
                                Var BrushStyle:{$IFDEF FMX}TBrushKind{$ELSE}TBrushStyle{$ENDIF}); override;
    Procedure SetActive(Value:Boolean); override;
    Procedure SetSeriesColor(AColor:TColor); override;
  public
    Procedure FillSampleValues(NumValues:Integer); override;
    Function Polygon:TTeePolygon; {$IFDEF D9}inline;{$ENDIF}
  end;

  TMapSeries=class;

  TPolygonConcave=(pcUnknown, pcYes, pcNo);

  TTeePolygons=Array of TTeePolygon;

  TTeePolygon=class(TCollectionItem)
  private
    FClosed      : Boolean;
    FCode        : String; // For countries: ISO3166-A2. For USA Counties: FIPS, etc
    FGradient    : TChartGradient;
    FOffset      : TFloatXY;
    FParentBrush : Boolean;
    FParentPen   : Boolean;
    FPoints      : TPolygonSeries;
    FSize        : Single;
    FTransparency: TTeeTransparency;

    IBounds      : TRect;
    IBoundsDone  : Boolean;

    ICentroidDone: Boolean;
    ICentroid    : {$IFDEF FMX}TPoint{$ELSE}TPointFloat{$ENDIF};

    IVisibleDone : Boolean;
    IVisible     : Boolean;

    IConcave     : TPolygonConcave; // Is Convex or Concave ?
    ITriangles   : TPointFloatArray; // Triangles from FPoints
    ITriangleCached: TTeeCachedObject;

    IPointsDone  : Boolean;

    procedure CalcOffset(var X,Y:Single);
    procedure ClearCachedPoints;
    procedure DataChanged;
    procedure DoResize(const ACenter:TPointFloat; const X, Y: Single);
    procedure FreeCached;

    function GetBrush: TChartBrush;
    function GetColor: TColor;
    function GetGradient: TChartGradient;
    function GetPen: TChartPen;
    function GetPicture: TBackImage;
    Function GetText:String;
    function GetTransparent: Boolean;
    function GetZ: Double; {$IFDEF D9}inline;{$ENDIF}

    function IsConcave:Boolean;

    procedure OffsetChanged(Source:TObject);
    function PointRect:TRect;
    function PointValues:TPointFloatArray;
    procedure SetBrush(const Value: TChartBrush);
    procedure SetClosed(const Value: Boolean);
    Procedure SetCode(Const Value:String);
    procedure SetColor(const Value: TColor);
    procedure SetGradient(const Value: TChartGradient);
    procedure SetOffset(const Value: TFloatXY);
    procedure SetParentBrush(const Value: Boolean);
    procedure SetParentPen(const Value: Boolean);
    procedure SetPen(const Value: TChartPen);
    procedure SetPicture(const Value: TBackImage);
    Procedure SetSize(const Value:Single);
    Procedure SetText(const Value:String);
    procedure SetTransparency(const Value: TTeeTransparency);
    procedure SetTransparent(const Value: Boolean);
    procedure SetZ(const Value: Double);
    function ValueBounds:TRectF;
  protected
    IPoints      : TPointArray;

    ITrianglePoints3D,
    IPoints3D    : Array of TPoint3D;

    Transform    : TTeeTransform;

    ZNear,
    ZFar : Integer;
    
    function Clicked(const AIndex,X,Y:Integer):Boolean;
    procedure DefineProperties(Filer: TFiler); override;
    procedure DoDraw(const ACanvas:TCanvas3D; Index:Integer; AColor:TColor;
                     ATransp:TTeeTransparency);
    procedure DoOffset(const X,Y:Single);
    function GetDisplayName: string; override;
  public
    {$IFDEF AUTOREFCOUNT}[weak]{$ENDIF}
    ParentSeries : TMapSeries;

    {$IFDEF AUTOREFCOUNT}[weak]{$ENDIF}
    ParentShape : TTeePolygon;

    Constructor Create(Collection:TCollection); override;
    Destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;

    Function AddXY(Const Point:TFloatPoint):Integer; overload;
    Function AddXY(Const X,Y:Double):Integer; overload;
    Procedure Draw(const ACanvas:TCanvas3D; ValueIndex:Integer; const CheckHover:Boolean=False);
    Function GetPoints:TPointArray;

    procedure OffsetValues(const X,Y:Single);
    procedure Resize(const X,Y:Single);
    function Siblings:TTeePolygons;
    Function Visible:Boolean;

    Function Bounds:TRect;
    function Centroid:{$IFDEF FMX}TPoint{$ELSE}TPointFloat{$ENDIF};
    property Points:TPolygonSeries read FPoints;
  published
    property Brush:TChartBrush read GetBrush write SetBrush;
    property Closed:Boolean read FClosed write SetClosed default True;
    property Code:String read FCode write SetCode;
    property Color:TColor read GetColor write SetColor default {$IFDEF FMX}claWhite{$ELSE}clWhite{$ENDIF};
    property Gradient:TChartGradient read GetGradient write SetGradient;
    property Offset:TFloatXY read FOffset write SetOffset;
    property ParentBrush:Boolean read FParentBrush write SetParentBrush default True;
    property ParentPen:Boolean read FParentPen write SetParentPen default True;
    property Pen:TChartPen read GetPen write SetPen;
    property Picture:TBackImage read GetPicture write SetPicture;
    property Size:Single read FSize write SetSize;
    property Text:String read GetText write SetText;
    property Transparency:TTeeTransparency read FTransparency write SetTransparency default 0;
    property Transparent:Boolean read GetTransparent write SetTransparent default False;
    property Z:Double read GetZ write SetZ;
  end;

  TTeePolygonList=class;

  TTeePolygonGroup=class
  private
    FCode,
    FText : String;

    function GetColor:TColor;
    function GetVisible:Boolean;
    function HasShape(const AShape:TTeePolygon):Boolean;

    procedure SetCode(const Value:String);
    procedure SetColor(const Value:TColor);
    procedure SetText(const Value:String);
    procedure SetVisible(const Value:Boolean);
  protected
    {$IFDEF AUTOREFCOUNT}[weak]{$ENDIF}
    Map : TMapSeries;
  public
    Shapes : TTeePolygons;

    Constructor Create(const AMap:TMapSeries); virtual;
    Destructor Destroy; override;

    procedure Add(const AMap:TMapSeries); overload;
    procedure Add(const AShape:TTeePolygon); overload;
    procedure Add(const AShapes:Array of Integer); overload;
    procedure Add(AFrom,ATo:Integer); overload;
    procedure Add(const AShapes:TTeePolygonList; AFrom,ATo:Integer); overload;

    function Count:Integer; {$IFDEF D9}inline;{$ENDIF}
    procedure Hide;
    procedure Offset(const X,Y:Single);
    procedure Resize(const X,Y:Single);
    procedure Show;

    property Code:String read FCode write SetCode;
    property Color:TColor read GetColor write SetColor;
    property Text:String read FText write SetText;
    property Visible:Boolean read GetVisible write SetVisible;
  end;

  TTeePolygonGroups=class({$IFDEF D18}TObjectList<TTeePolygonGroup>{$ELSE}TList{$ENDIF})
  private
    IMap : TMapSeries;

    Function Get(Index:Integer):TTeePolygonGroup; {$IFDEF D10}inline;{$ENDIF}
    function GetByCode(const ACode: String): TTeePolygonGroup;
    function GetByName(const AName: String): TTeePolygonGroup;
    function IndexWithShape(const AShape:TTeePolygon):Integer;
    Procedure Put(Index:Integer; Const Value:TTeePolygonGroup);
  protected
    function IndexOfCode(const ACode:String):Integer;
  public
    function Add(const AName:String):TTeePolygonGroup; overload;
    function Add(const AName:String; const ACode:String; const AShapes:TTeePolygons):TTeePolygonGroup; overload;

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

    property ByCode[const ACode:String]:TTeePolygonGroup read GetByCode;
    property ByName[const AName:String]:TTeePolygonGroup read GetByName; //default;
    property Group[Index:Integer]:TTeePolygonGroup read Get write Put; default;
  end;

  TTeePolygonList=class(TOwnedCollection)
  private
    Procedure Delete(Start,Quantity:Integer); overload;
    Function Get(Index:Integer):TTeePolygon; {$IFDEF D10}inline;{$ENDIF}
    function GetByCode(const ACode: String): TTeePolygon;
    function GetByName(const AName: String): TTeePolygon;
    Procedure Put(Index:Integer; Const Value:TTeePolygon);
  protected
    function IndexOfCode(const ACode:String):Integer;
  public
    Function Add:TTeePolygon;
    property ByCode[const ACode:String]:TTeePolygon read GetByCode;
    property ByName[const AName:String]:TTeePolygon read GetByName; //default;
    procedure Exchange(A,B:Integer);
    Function Owner:TMapSeries; {$IFDEF D10}inline;{$ENDIF}
    property Polygon[Index:Integer]:TTeePolygon read Get write Put; default;
  end;

  TMapPersistent=class(TPersistent)
  protected
    {$IFDEF AUTOREFCOUNT}[weak]{$ENDIF}
    IMap : TMapSeries;
  public
    Constructor Create(const AMap:TMapSeries); virtual;
  end;

  TMapMarksText=(mtLabel, mtCode, mtBoth);

  TMapMarks=class(TMapPersistent)
  private
    FAutoSize : Boolean;
    FAutoTextColor : Boolean;
    FCentroid : Boolean;
    FStyle    : TMapMarksText;
    FUpperCase : Boolean;

    procedure SetAutoSize(const Value:Boolean);
    procedure SetAutoTextColor(const Value:Boolean);
    procedure SetCentroid(const Value:Boolean);
    procedure SetStyle(const Value:TMapMarksText);
    procedure SetUppercase(const Value:Boolean);
  public
    Constructor Create(const AMap:TMapSeries); override;

    procedure Assign(Source:TPersistent); override;
  published
    property AutoSize:Boolean read FAutoSize write SetAutoSize default True;
    property AutoTextColor:Boolean read FAutoTextColor write SetAutoTextColor default True;
    property Centroid:Boolean read FCentroid write SetCentroid default True;
    property TextStyle:TMapMarksText read FStyle write SetStyle default mtLabel;
    property Uppercase:Boolean read FUppercase write SetUppercase default False;
  end;

  TMapLegendSortBy=(mlsText,mlsValue,mlsNone);

  TMapLegendSort=class(TMapPersistent)
  private
    FBy : TMapLegendSortBy;
    FInverted : Boolean;

    procedure SetBy(const Value:TMapLegendSortBy);
    procedure SetInverted(const Value:Boolean);
  public
    procedure Assign(Source:TPersistent); override;
  published
    property By:TMapLegendSortBy read FBy write SetBy default mlsText;
    property Inverted:Boolean read FInverted write SetInverted default False;
  end;

  TMapLegendStyle=(lsPalette,lsTexts,lsGroups);

  TMapLegend=class(TMapPersistent)
  private
    FIncludeUnNamed : Boolean;
    FIncludeZero : Boolean;
    FSort : TMapLegendSort;
    FStyle : TMapLegendStyle;

    procedure SetIncludeUnNamed(const Value:Boolean);
    procedure SetIncludeZero(const Value:Boolean);
    procedure SetStyle(const Value:TMapLegendStyle);
    procedure SetSort(const Value:TMapLegendSort);
  public
    VisibleOnly : Boolean;

    Constructor Create(const AMap:TMapSeries); override;
    Destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;
  published
    property IncludeUnNamed:Boolean read FIncludeUnNamed write SetIncludeUnNamed default False;
    property IncludeZero:Boolean read FIncludeZero write SetIncludeZero default False;
    property Sort:TMapLegendSort read FSort write SetSort;
    property Style:TMapLegendStyle read FStyle write SetStyle default lsPalette;
  end;

  TMapRenderOptions=class(TMapPersistent)
  private
    FBrush : TTeeBrush;
    FMeridians : TTeePen;
    FParallels : TTeePen;
    FRotation : TPointXYFloat;

    XCenterDeg : Integer;
    YCenterRad : Single;

    CosYCenter,
    SinYCenter : {$IFDEF D15}Single{$ELSE}Extended{$ENDIF};

    IHorizCalc,
    IVertCalc : TAxisCalcPos;

    IOffsetX,
    IOffsetY : Single;

    function CalcPoint(const X,Y:Single):TPointFloat; overload;
    function CalcPoint(const X,Y:TChartValue; out Z:Single):TPoint; overload;
    //function CalcPoint3D(var P:TPointFloat):TPoint3D;
    function CalcPoint2D(const P:TPointFloat):TPoint3D; overload;
    function CalcPoint2D(const X,Y:TChartValue):TPoint; overload;

    procedure DrawSphere(const ACanvas:TCanvas3D; const R:TRect);

    procedure Prepare;

    procedure SetBrush(const Value:TTeeBrush);
    procedure SetMeridians(const Value:TTeePen);
    procedure SetParallels(const Value:TTeePen);
    procedure SetRotation(const Value:TPointXYFloat);
  protected
    procedure CalcSpherical(var X,Y:Single); overload;
    procedure CalcSpherical(var X,Y:Single; out Z:Single); overload;
  public
    Constructor Create(const AMap:TMapSeries); override;
    Destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;
  published
    property Brush:TTeeBrush read FBrush write SetBrush;
    property Meridians:TTeePen read FMeridians write SetMeridians;
    property Parallels:TTeePen read FParallels write SetParallels;
    property Rotation:TPointXYFloat read FRotation write SetRotation;
  end;

  TMapRenderMode=(rmDefault, rm2D, rm3D);

  TMapSeries=class(TCustom3DPaletteSeries)
  private
    FEmboss      : TTeeEmboss;
    FGroups      : TTeePolygonGroups;
    FMapLegend   : TMapLegend;
    FMapMarks    : TMapMarks;
    FOffset      : TFloatXY;
    FPointer     : TSeriesPointer; // <-- To display single point Lat,Lon
    FRender      : TMapRenderMode;
    FRenderOptions : TMapRenderOptions;
    FShadow      : TTeeShadow;
    FShapes      : TTeePolygonList;
    FTransparent : Boolean;

    I3DList      : Array of Integer;
    ILegendItems : Array of Integer;

    IValueBoundsDone : Boolean;
    IValueBounds : TRectF;

    function AutoColor(const AColor:TColor):TColor;
    function CalcPointSize:Single;
    procedure CalcValueBounds;
    Function CalcVisibleLegendItems:Integer;
    procedure ClearCachedPoints;
    Function CompareLegendItem(a,b:Integer):Integer;
    Function CompareOrder(a,b:Integer):Integer;
    Function CompareOrderRender(a,b:Integer):Integer;
    function DisplayAtLegend(const AShape:TTeePolygon):Boolean;
    procedure DoDelete(ValueIndex:Integer);
    procedure EnsureShapeCount;
    procedure FreeCached;
    function GetPointer:TSeriesPointer;
    function GetPointSize:Single;
    Function GetPolygon(Index:Integer):TTeePolygon; {$IFDEF D10}inline;{$ENDIF}
    procedure InvalidateLegend;
    procedure OffsetChanged(Sender: TObject);
    procedure SetEmboss(const Value: TTeeEmboss);
    procedure SetMapLegend(const Value:TMapLegend);
    procedure SetMapMarks(const Value: TMapMarks);
    procedure SetOffset(const Value: TFloatXY);
    procedure SetPointer(const Value:TSeriesPointer);
    procedure SetPointSize(const Value: Single);
    procedure SetRender(const Value:TMapRenderMode);
    procedure SetRenderOptions(const Value: TMapRenderOptions);
    procedure SetShadow(const Value: TTeeShadow);
    procedure SetShapes(const Value: TTeePolygonList);
    procedure SetTransparent(const Value:Boolean);
    procedure SortLegendItems;
    procedure SwapLegendItem(a,b:Integer);
    Procedure SwapPolygon(a,b:Integer);
  protected
    IAdding,
    ILoading : Boolean;
    Reuse3D : Boolean;
    CanSaveData : Boolean;
    MinSize : Integer;

    Procedure AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False); override;
    procedure AssignFormatting(Source:TChartSeries); override;
    Procedure CalcHorizMargins(Var LeftMargin,RightMargin:Integer); override;
    Function CalcLegendIndex(LegendIndex:Integer):Integer; override;
    procedure CalcSelectionPos(ValueIndex:Integer; out X,Y:Integer); override;
    Procedure CalcVerticalMargins(Var TopMargin,BottomMargin:Integer); override;
    function CanDrawMark(ValueIndex:Integer):Boolean; override;
    procedure ChangedAxisScale(const Axis:TChartAxis); override;
    class procedure CreateSubGallery(AddSubChart: TChartSubGalleryProc); override;
    procedure DefineProperties(Filer: TFiler); override;
    procedure DoBeforeDrawChart; override;
    procedure DoBeforeDrawValues; override;
    Procedure DrawAllValues; override;
    Procedure DrawMark( ValueIndex:Integer; Const St:String;
                        APosition:TSeriesMarkPosition); override;
    Procedure DrawValue(ValueIndex:Integer); override;
    Procedure GalleryChanged3D(Is3D:Boolean); override;
    class Function GetEditorClass:String; override;
    function GetMarkLabel(ValueIndex:Integer):String; override;
    Procedure GetPaletteBounds(out AMin,ARange:Double); override;
    function IsVisible:Boolean;
    procedure SetParentChart(const Value:TCustomAxisPanel); override;
    class procedure SetSubGallery(ASeries: TChartSeries; Index: Integer); override;
    Function ValueIndexToLegend(ValueIndex:Integer):Integer; override;
  public
    {$IFDEF MAPDEBUG}
    _Crossed,
    _Painted : Integer;
    {$ENDIF}

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

    Function AddXY(Const AXValue,AYValue:TChartValue; Const ALabel:String='';
                         AColor:TColor=clTeeColor):Integer; override;
    procedure Assign(Source:TPersistent); override;
    Procedure Clear; override;
    procedure ClearLegend;
    Function Clicked(x,y:Integer):Integer; override;
    Function CountLegendItems:Integer; override;
    Procedure Delete(ValueIndex:Integer); overload; override;
    Procedure Delete(Start,Quantity:Integer; RemoveGap:Boolean=False); overload; 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 MaxXValue:TChartValue; override;
    Function MaxYValue:TChartValue; override;
    Function MinXValue:TChartValue; override;
    Function MinYValue:TChartValue; override;
    Function NumSampleValues:Integer; override;
    procedure SwapValueIndex(A,B:Integer); override;

    property Groups:TTeePolygonGroups read FGroups;
    property Polygon[Index:Integer]:TTeePolygon read GetPolygon; default;
  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 Brush;
    property Emboss:TTeeEmboss read FEmboss write SetEmboss;
    property EndColor;
    property MapLegend:TMapLegend read FMapLegend write SetMapLegend;
    property MapMarks:TMapMarks read FMapMarks write SetMapMarks;
    property MidColor;
    property LegendEvery;
    property Offset:TFloatXY read FOffset write SetOffset;
    property Pen;
    property PaletteMin;
    property PaletteStep;
    property PaletteSteps;
    property PaletteStyle;
    property Pointer:TSeriesPointer read GetPointer write SetPointer;
    property PointSize:Single read GetPointSize write SetPointSize;
    property RenderMode:TMapRenderMode read FRender write SetRender default rmDefault;
    property RenderOptions:TMapRenderOptions read FRenderOptions write SetRenderOptions;
    property Shadow:TTeeShadow read FShadow write SetShadow;
    property Shapes:TTeePolygonList read FShapes write SetShapes;
    property StartColor;
    property Transparent:Boolean read FTransparent write SetTransparent default False;
    property Transparency;
    property UseColorRange;
    property UsePalette;
    property UsePaletteMin;
    property TimesZOrder;
    property XValues;
    property YValues;
    property ZValues;

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

  {$IFDEF D6}
  TTeeKMLSource=class
  public
    class procedure Load(const ASeries:TChartSeries; const FileName:String);
  end;
  {$ENDIF}

implementation

Uses Math,
     {$IFDEF LCL}
     XMLRead, DOM,
     {$ELSE}
     {$IFDEF D16}
     XML.XMLDom, XML.XMLIntf, System.Zip,
     {$ELSE}
     {$IFDEF D6}
     msxml,
     {$ENDIF}
     {$ENDIF}
     {$ENDIF}

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

type
  TSeriesAccess=class(TCustomChartElement);

{$IFDEF D6}

{$IFDEF LCL}
type
  INode=TDOMNode;
  IXMLDoc=TXMLDocument;
  INodeList=TDOMNodeList;
  
function selectNode(const Node:INode; const Path:String):INode;
begin
  result:=nil; // pending  Node.FindNode(Path) ?
end;

{$ELSE}

type
  IXMLDoc={$IFDEF D16}IDOMDocument{$ELSE}IXMLDOMDocument{$ENDIF};
  INode={$IFDEF D16}IDOMNode{$ELSE}IXMLDOMNode{$ENDIF};
  INodeList={$IFDEF D16}IDOMNodeList{$ELSE}IXMLDOMNodeList{$ENDIF};

function LoadXML(const AFile:String):IXMLDoc;

  function StreamToString(const AStream:TStream):String;
  begin
    SetLength(result,AStream.Size);
    AStream.Read(Pointer(result)^,AStream.Size);

    {$IFDEF D12}
    result:=TEncoding.Default.GetString(TBytes(result));
    {$ENDIF}
  end;

var {$IFDEF D16}
    z : TZipFile;
    t : Integer;
    s : TStream;
    h : TZipHeader;
    {$ENDIF}
    tmpStream : TFileStream;
    tmpXML : String;
begin
  tmpStream:=TFileStream.Create(AFile,fmOpenRead);
  try
    {$IFDEF D16}
    if UpperCase(ExtractFileExt(AFile))='.KMZ' then
    begin
      z:=TZipFile.Create;
      try
        z.Open(tmpStream,TZipMode.zmRead);

        for t:=0 to z.FileCount-1 do
        if UpperCase(ExtractFileExt(z.FileNames[t]))='.KML' then
        begin
          z.Read(t,s,h);
          try
            tmpXML:=StreamToString(s);
          finally
            s.Free;
          end;

          break;
        end
        else
          tmpXML:='';

      finally
        z.Free;
      end;
    end
    else
    {$ENDIF}
      tmpXML:=StreamToString(tmpStream);

  finally
    tmpStream.Free;
  end;

  {$IFDEF D16}
  result := GetDom.createDocument('', '', nil);
  (result as IDOMParseoptions).validate:=False;
  (result as IDomPersist).LoadXML(tmpXML);
  if (result as IDOMParseError).errorCode<>0 then
     raise Exception.Create('Cannot load XML file: '+AFile);
  {$ELSE}
  result:=CoDOMDocument.Create;

  if Assigned(result) then
  begin
    if not result.loadXML(tmpXML) then
       raise Exception.Create('Cannot load XML file: '+AFile);
  end
  else
    raise Exception.Create('Cannot create CoDOMDocument');
  {$ENDIF}
end;

{$IFDEF D16} // From XMLUtil.pas
function Split0(Str: string; const SubStr: string): TStringList;
var
  S, Tmp: string;
  Slist: TStringList;
  I: Integer;
begin
  Slist := TStringList.Create;
  S := Str;
  while (length(S) > 0) do
  begin
    I := ansipos(substr, S);
    if (I = 0) then
    begin
      Slist.Add(s);
      delete(S, 1, length(S));
    end
    else
    begin
      if (I = 1) then
      begin
        delete(S, 1, length(SubStr));
      end
      else
      begin
        Tmp := S;
        delete(Tmp, I, length(Tmp));
        slist.Add(Tmp);
        delete(S, 1, I + length(SubStr)-1);
      end;
    end;
  end;
  Result := Slist;
end;

function getIndex_Attrs(Tag: string; var Idx: Integer; var Attrs: string): string;
var
  I, Iend: Integer;
  SIdx: string;
begin
  Attrs := '';
  I := ansipos('@', tag);
  if I > 0 then
  begin
    Attrs := tag;
    delete(Attrs, 1, I);
    delete(Tag, I, length(Tag));
  end;
  I := ansipos('[', Tag);
  if (I > 0) then
  begin
    SIdx := tag;
    delete(SIdx, 1, I);
    IEnd := ansipos(']', SIdx);
    delete(SIdx, IEnd, length(SIdx));
    if SIdx = '*' then
      Idx := -3
    else
      Idx := strtoint(SIdx);
    delete(Tag, I, length(Tag));
  end;
  Result := Tag;
end;

function FindNode(const CurrEl: IDOMNode; CurrTag: Integer; const Tags: TStrings): IDOMNode;
var
  Tag: string;
  Childs: IDOMNodeList;
  I, Idx, CompareIdx: Integer;
  Attrs: string;
begin
  Result := CurrEl;
  if CurrTag < Tags.count then
  begin
    Result := nil;
    Tag := Tags[CurrTag];
    Idx := -2;
    CompareIdx := 0;
    Tag := getIndex_Attrs(Tag, Idx, Attrs);
    if (Tag <> '') and (CurrEl <> nil) then
    begin
      Childs := CurrEl.childNodes;
      if (Idx <> -2) then
      begin
        for I := 0 to Childs.length-1 do
        begin
          if (ExtractLocalName(Childs.item[I].localName) = Tag) or (Childs.item[I].NodeName = Tag) then
          begin
            if (Idx = -3) or (Idx = CompareIdx) then
            begin
              Result := FindNode(Childs.item[i], CurrTag + 1, Tags);
              break;
            end
            else
              CompareIdx := CompareIdx + 1;
          end
        end;
      end
      else //Try each, until found
      begin
        if Tag = '..' then //Special case, go back one step
          Result := FindNode(CurrEl.parentNode, CurrTag + 1, Tags)
        else
        begin
          for I := 0 to Childs.length-1 do
          begin
            if (ExtractLocalName(Childs.item[I].localName) = Tag) or (Childs.item[I].NodeName = Tag) then
            begin
              Result := FindNode(Childs.item[I], CurrTag + 1, Tags);
              if (Result <> nil) then
                break;
            end;
          end
        end;
      end;
    end
    else
      if (Tag = '') and (length(Attrs) > 0) then
        Result := CurrEl;
    if Result <> nil then
      if length(Attrs) <> 0  then
        Result := Result.attributes.getNamedItem(Attrs);
  end;
end;
{$ENDIF}

function selectNode(const Node:INode; const Path:String):INode;
{$IFDEF D16} // From XMLUtil.pas
var
  Tags: TStringList;
  IStart: Integer;
{$ENDIF}
begin
  {$IFDEF D16}
  if (Path = '') or (Node = nil) then
     result := Node
  else
  begin
    Tags := Split0(Path, '\');
    try
      IStart := 0;
      if ExtractLocalName(Node.NodeName) = Tags.Strings[0] then //sync tag with node..special case
        IStart := 1;

      result:=FindNode(Node, IStart, Tags);
    finally
      Tags.Free;
    end;
  end;
  {$ELSE}

  if Assigned(Node) then
     result:=Node.selectSingleNode(Path)
  else
     result:=nil;
  {$ENDIF}
end;

{$ENDIF}

function FindChild(const ANode:INode; const UpperCaseName:String):INode;
var tmp : INodeList;
    t   : Integer;
begin
  if Assigned(ANode) then
  begin
    tmp:=ANode.childNodes;

    for t:=0 to tmp.length-1 do
    if UpperCase(tmp[t].nodeName)=UpperCaseName then
    begin
      result:=tmp[t];
      Exit;
    end;
  end;

  result:=nil;
end;

{$IFDEF D16}
{$DEFINE TEEXMLTEXT}
{$ENDIF}

{$IFDEF LCL}
{$DEFINE TEEXMLTEXT}
{$ENDIF}

function NodeText(const ANode:INode):String;
{$IFDEF TEEXMLTEXT}
var tmp : INode;
{$ENDIF}
begin
  {$IFDEF TEEXMLTEXT}
  tmp:=FindChild(ANode,'#TEXT');

  if Assigned(tmp) then
     result:=tmp.nodeValue
  else
     result:='';

  {$ELSE}
  result:=ANode.text;
  {$ENDIF}
end;

class procedure TTeeKMLSource.Load(const ASeries:TChartSeries; const FileName:String);

  procedure AddPlacemark(const ANode:INode);
  var NodeName,
      NodeCoord : INode;
      Lat,Lon,tmpZ : Single;
      tmpLat,
      tmpLon,
      tmpAlt,
      tmpXYZ,
      s:String;

      t,
      tmpIndex : Integer;
      tmpPoints : TChartSeries;
      tmpShape : TTeePolygon;
  begin
    NodeName:=selectNode(ANode,'name');

    if Assigned(NodeName) then
    begin
      NodeCoord:=selectNode(selectNode(ANode,'Point'),'coordinates');

      if Assigned(NodeCoord) then
      begin
        s:=Trim(NodeText(NodeCoord));

        if s<>'' then
        begin
          tmpLat:=TeeExtractField(s,1,',');
          tmpLon:=TeeExtractField(s,2,',');
          tmpAlt:=TeeExtractField(s,3,',');

          if TryStrToFloat(tmpLat,Lat) and
             TryStrToFloat(tmpLon,Lon) then
          begin
            tmpZ:=0;

            if tmpAlt<>'' then
               TryStrToFloat(tmpAlt,tmpZ);

            if ASeries is TMapSeries then
            begin
              tmpIndex:=ASeries.Add(0,NodeText(NodeName));
              tmpShape:=TMapSeries(ASeries).Shapes[tmpIndex];
              tmpShape.Points.AddXY(Lat,Lon);
              TMapSeries(ASeries).ZValues.Value[tmpIndex]:=tmpZ;
            end
            else
              ASeries.AddXY(Lat,Lon,NodeText(NodeName));
          end;
        end;
      end
      else
      begin
        NodeCoord:=selectNode(selectNode(ANode,'LineString'),'coordinates');

        if Assigned(NodeCoord) then
        begin
          s:=Trim(NodeText(NodeCoord));

          if s<>'' then
          begin
            if ASeries is TMapSeries then
            begin
              tmpIndex:=ASeries.Add(0,NodeText(NodeName));
              tmpShape:=TMapSeries(ASeries).Shapes[tmpIndex];
              tmpPoints:=tmpShape.Points;
              tmpShape.FClosed:=False;
            end
            else
              tmpPoints:=ASeries;

            for t:=1 to TeeNumFields(s,' ') do
            begin
              tmpXYZ:=TeeExtractField(s,t,' ');

              tmpLat:=TeeExtractField(tmpXYZ,1,',');
              tmpLon:=TeeExtractField(tmpXYZ,2,',');
              tmpAlt:=TeeExtractField(tmpXYZ,3,',');

              if TryStrToFloat(tmpLat,Lat) and
                 TryStrToFloat(tmpLon,Lon) then
                   tmpPoints.AddXY(Lat,Lon);
            end;
          end;

        end;
      end;
    end;
  end;

  procedure Scan(const List:INode);
  var t : Integer;
      Node : INode;
  begin
    for t:=0 to List.childNodes.length-1 do
    begin
      Node:=List.childNodes[t];

      if SameText(Node.nodeName,'PLACEMARK') then
         AddPlaceMark(Node)
      else
         Scan(Node);
    end;
  end;

var XMLDoc : IXMLDoc;
    Node : INode;
begin
  {$IFDEF LCL}
  ReadXMLFile(XMLDoc,FileName);
  {$ELSE}
  XMLDoc:=LoadXML(FileName);
  {$ENDIF}

  Node:=FindChild(XMLDoc,'KML');

  if not Assigned(Node) then
     Node:=FindChild(XMLDoc,'DOCUMENT');

  if Assigned(Node) and (Node.childNodes.length>0) then
  begin
    if ASeries is TMapSeries then
       TMapSeries(ASeries).ILoading:=True;

    try
      Scan(Node);
    finally
      if ASeries is TMapSeries then
         TMapSeries(ASeries).ILoading:=False;
    end;
  end;
end;
{$ENDIF}

{ TTeePolygon }

type
  TComponentAccess=class(TComponent);

Constructor TTeePolygon.Create(Collection: TCollection);
begin
  inherited;
  ParentSeries:=TTeePolygonList(Collection).Owner as TMapSeries;

  FClosed:=True;
  FPoints:=TPolygonSeries.Create(nil);
  FPoints.Tag:=ObjectToTag(Self);
  FPoints.XValues.Order:=loNone;

  //FPoints.ShowInLegend:=False; <-- speed problem

  TComponentAccess(FPoints).SetDesigning(False);

  FParentPen:=True;
  FParentBrush:=True;

  FOffset:=TFloatXY.Create;
  FOffset.OnChange:=OffsetChanged;

  ParentSeries.EnsureShapeCount;
end;

Destructor TTeePolygon.Destroy;
var tmp : Integer;
begin
  ITriangleCached.Free;

  FPoints.Tag:=0;
  FPoints.Free;

  FGradient.Free;

  Transform.Free;

  FOffset.Free;

  // Delete the Parent Series ValueIndex on destroying:

  // If Parent Series is empty, its not necessary to do this, as
  // TCollectionItem.Index in XE4 and up is very expensive.
  if Assigned(ParentSeries) and (ParentSeries.Count>0) then
     if not (csDestroying in ParentSeries.ComponentState) then
     begin
       tmp:=Index; // Cache, as its is expensive in XE4 and up.

       if ParentSeries.Count>tmp then
          ParentSeries.DoDelete(tmp);
     end;

  inherited;
end;

procedure TTeePolygon.FreeCached;
begin
  IBoundsDone:=False;
  ICentroidDone:=False;
  IVisibleDone:=False;
end;

procedure TTeePolygon.ClearCachedPoints;
begin
  IPointsDone:=False;
end;

procedure TTeePolygon.OffsetChanged(Source:TObject);
begin
  FreeCached;
  ParentSeries.Repaint;
end;

procedure TTeePolygon.Assign(Source:TPersistent);
begin
  if Source is TTeePolygon then
  begin
    FClosed:=TTeePolygon(Source).FClosed;
    FCode:=TTeePolygon(Source).FCode;
    Gradient:=TTeePolygon(Source).FGradient;
    FParentBrush:=TTeePolygon(Source).FParentBrush;
    FParentPen:=TTeePolygon(Source).FParentPen;
    FPoints.AssignValues(TTeePolygon(Source).FPoints);
    FSize:=TTeePolygon(Source).FSize;
    FTransparency:=TTeePolygon(Source).FTransparency;
    Offset:=TTeePolygon(Source).FOffset;

    IConcave:=TTeePolygon(Source).IConcave;

    ClearCachedPoints;
  end
  else
    inherited;
end;

function TTeePolygon.AddXY(const Point:TFloatPoint): Integer;
begin
  result:=FPoints.AddXY(Point.X,Point.Y);
end;

function TTeePolygon.AddXY(const X,Y: Double): Integer;
begin
  result:=FPoints.AddXY(X,Y);
end;

function TTeePolygon.Siblings:TTeePolygons;
var L : Integer;

  procedure Add(const AShape:TTeePolygon);
  begin
    SetLength(result,L+1);
    result[L]:=AShape;
    Inc(L);
  end;

var tmp : TTeePolygonList;
    t : Integer;
begin
  result:=nil;
  L:=0;

  tmp:=ParentSeries.Shapes;

  if ParentShape=nil then
  begin
    for t:=0 to tmp.Count-1 do
        if tmp[t].ParentShape=Self then
           Add(tmp[t]);
  end
  else
  begin
    Add(ParentShape);

    for t:=0 to tmp.Count-1 do
        if tmp[t].ParentShape=ParentShape then
           Add(tmp[t]);
  end;
end;

procedure TTeePolygon.CalcOffset(var X,Y:Single);
begin
  X:=ParentSeries.FOffset.X+FOffset.X;
  Y:=ParentSeries.FOffset.Y+FOffset.Y;
end;

// Cached table of Sin Cos values (for speed)
type
  TSinCos=record
     Sin,
     Cos : {$IFDEF D15}Single{$ELSE}Extended{$ENDIF};
  end;

var
  SinCosTable:Array[-3600..3599] of TSinCos;
  SinCosReady:Boolean=False;

procedure CheckSinCosTable;
var t : Integer;
begin
  if not SinCosReady then
  begin
    // Precalculate, in tenths of degree

    for t:=-3600 to 3599 do
        SinCos(TeePiStep*0.1*t,SinCosTable[t].Sin,SinCosTable[t].Cos);

    SinCosReady:=True;
  end;
end;

type
  TCanvas3DAccess=class(TCanvas3D);

{ return the array of Points in screen (pixel) 2D coordinates }
Function TTeePolygon.GetPoints:TPointArray;

  (*
  function Project3D(const Longitude,Latitude:TChartValue):TPoint3D;
  var latS, latC,
      lonS, lonC : {$IFDEF D15}Single{$ELSE}Extended{$ENDIF};
  begin
    SinCos(Longitude*TeePiStep,lonS,lonC);
    SinCos((Latitude*TeePiStep)-Pi*1.5,latS,latC);

    result.x:=IHorizCalc(latC*lonC);
    result.y:=IVertCalc(latC*lonS);
    result.z:=IDepthCalc(latS);  // -1 .. +1
  end;
  *)

var
  tmpCount : Integer;
  tmpX     : TChartValues;
  tmpY     : TChartValues;

  procedure DoSpherical;
  var
    ICross : TIntArray;

    // Remember crossing point
    procedure AddCross(const Index:Integer);
    var L :Integer;
    begin
      L:=Length(ICross);
      SetLength(ICross,L+1);
      ICross[L]:=Index;
    end;

    // Split polygon, removing segments that are in "back" face (z<0)
    procedure DeleteCrossed;
    var t,
        L,
        tmpFrom,
        tmpRemove : Integer;
    begin
      L:=High(ICross);

      if (L+1) mod 2=0 then
         t:=L-1
      else
         t:=L;

      while t>=0 do
      begin
        tmpFrom:=ICross[t];

        if t=L then
           tmpRemove:=tmpCount-tmpFrom
        else
           tmpRemove:=ICross[t+1]-tmpFrom;

        tmpCount:=tmpCount-tmpRemove;

        if tmpCount-tmpFrom>0 then
           System.Move(IPoints[tmpFrom+tmpRemove],IPoints[tmpFrom],SizeOf(TPoint)*(tmpCount-tmpFrom));

        SetLength(IPoints,tmpCount);

        Dec(t,2);
      end;
    end;

  var t : Integer;
      Z : Single;
      tmpTotal,
      tmpZ : Integer;
      Flag : Boolean;
  begin
    ZNear:=-MaxInt;
    ZFar:=MaxInt;

    Flag:=True;

    tmpTotal:=0;

    for t:=0 to tmpCount-1 do
    begin
      IPoints[tmpTotal]:=ParentSeries.FRenderOptions.CalcPoint(tmpX[t],tmpY[t],Z);

      // First point or not-duplicated ?
      if (tmpTotal=0) or
         (
           (IPoints[tmpTotal].X<>IPoints[tmpTotal-1].X) or
           (IPoints[tmpTotal].Y<>IPoints[tmpTotal-1].Y)
         ) then
      begin
        // Check crossing font <-> back face
        if Z<0 then
        begin
          if Flag then
          begin
            AddCross(tmpTotal);
            Flag:=not Flag;
          end;
        end
        else
        if not Flag then
        begin
          AddCross(tmpTotal);
          Flag:=not Flag;
        end;

        // Inc counter
        Inc(tmpTotal);

        // Calculate far and near "z" depth pos
        tmpZ:=Round(10000*Z);

        if tmpZ>=0 then
           if tmpZ>ZNear then
              ZNear:=tmpZ;

        if tmpZ<ZFar then
           ZFar:=tmpZ;
      end;
    end;

    // Adjust total IPoints size (trim trailing due to duplicates)
    if tmpTotal<>tmpCount then
    begin
      tmpCount:=tmpTotal;
      SetLength(IPoints,tmpCount);
    end;

    // Crosses front <-> back face?
    if (ZFar<0) and (ZNear>=0) then
    begin
      {$IFDEF MAPDEBUG}
      Inc(ParentSeries._Crossed);
      {$ENDIF}

      // Cull (clip / split points)
      DeleteCrossed;

      // Flag to draw
      ZFar:=0;
    end;
  end;

  procedure Prepare;
  begin
    tmpCount:=FPoints.Count;

    ParentSeries.FRenderOptions.Prepare;

    tmpX:=FPoints.XValues.Value;
    tmpY:=FPoints.YValues.Value;

    CalcOffset(ParentSeries.FRenderOptions.IOffsetX,ParentSeries.FRenderOptions.IOffsetY);
  end;

  {
  procedure Triangulate3D;
  var t : Integer;
  begin
    SetLength(IPoints3D,tmpCount);

    for t:=0 to tmpCount-1 do
        IPoints3D[t]:=ParentSeries.RenderOptions.CalcPoint3D(ITriangles[t]);
  end;
  }

  procedure Triangulate2D;
  var t : Integer;
  begin
    SetLength(ITrianglePoints3D,tmpCount);

    for t:=0 to tmpCount-1 do
        ITrianglePoints3D[t]:=ParentSeries.FRenderOptions.CalcPoint2D(ITriangles[t]);
  end;

  procedure Triangulate;
  begin
    if ParentSeries.RenderMode<>rm3D then
    begin
      {$IFNDEF FMX}
      if not Assigned(ITriangles) then
         ITriangles:=TCanvas3DAccess(ParentSeries.ParentChart.Canvas).Tessellate(tmpX,tmpY);
      {$ENDIF}

      tmpCount:=Length(ITriangles);

      if tmpCount>0 then
         Triangulate2D;
    end;
  end;

  {
  procedure CalcPoints3D;
  var t : Integer;
  begin
    if not IPointsDone then
    begin
      Prepare;

      tmpDepth:=ParentSeries.GetZAxis.CalcPosValue;

      if Length(IPoints3D)<>tmpCount then
         SetLength(IPoints3D,tmpCount);

      for t:=0 to tmpCount-1 do
          IPoints3D[t]:=Project3D(tmpOffsetX+tmpX[t],tmpOffsetY+tmpY[t]);

      IPointsDone:=True;
    end;
  end;
  }

  procedure DoNormal;
  var t,
      tmpTotal : Integer;
  begin
    tmpTotal:=0;

    for t:=0 to tmpCount-1 do
    begin
      IPoints[tmpTotal]:=ParentSeries.FRenderOptions.CalcPoint2D(tmpX[t],tmpY[t]);

      // First point or not-duplicated ?
      if (tmpTotal=0) or
         (
           (IPoints[tmpTotal].X<>IPoints[tmpTotal-1].X) or
           (IPoints[tmpTotal].Y<>IPoints[tmpTotal-1].Y)
         ) then
      begin
        // Inc counter
        Inc(tmpTotal);
      end;
    end;

    // Adjust total IPoints size (trim trailing due to duplicates)
    if tmpTotal<>tmpCount then
    begin
      tmpCount:=tmpTotal;
      SetLength(IPoints,tmpCount);
    end;
  end;

begin
  if not IPointsDone then
  begin
    Prepare;

    ZFar:=1;

    if Length(IPoints)<>tmpCount then
       SetLength(IPoints,tmpCount);

    if ParentSeries.RenderMode=rm3D then
    begin
      CheckSinCosTable;

      DoSpherical;
      ICentroidDone:=False;
    end
    else
      DoNormal;

    // OpenGL does not support concave polygons, so we should manually
    // triangulate the polygon ourselves
    if (not ParentSeries.ParentChart.Canvas.SupportsConcave) and IsConcave then
       Triangulate;

    IPointsDone:=True;
  end;

  result:=IPoints;
end;

function ContainsRect(R1:TRectF; const R2:TRectF): Boolean; overload;
begin
  if R2.Left > R1.Left then R1.Left := R2.Left;
  if R2.Right < R1.Right then R1.Right := R2.Right;

  if not (R1.Right<R1.Left) then
  begin
    if R2.Top > R1.Top then R1.Top := R2.Top;
    if R2.Bottom < R1.Bottom then R1.Bottom := R2.Bottom;

    result:=not (R1.Bottom<R1.Top);
  end
  else
    result:=False;
end;

function TTeePolygon.ValueBounds:TRectF;
var tmpX,
    tmpY : Single;
    tmpList : TChartValueList;
begin
  CalcOffset(tmpX,tmpY);

  tmpList:=FPoints.XValues;
  result.Left:=tmpList.MinValue+tmpX;
  result.Right:=tmpList.MaxValue+tmpX;

  tmpList:=FPoints.YValues;
  result.Top:=tmpList.MinValue+tmpY;
  result.Bottom:=tmpList.MaxValue+tmpY;
end;

type
  TAxisAccess=class(TChartAxis);

// Returns True if the polygon contains points that lie inside
// the chart "ChartRect" (the visible chart area).
Function TTeePolygon.Visible:Boolean;

  function VerySmallBounds:Boolean;
  var tmp : TRect;
  begin
    tmp:=Bounds;

    result:=((tmp.Right-tmp.Left)<ParentSeries.MinSize) or
            ((tmp.Bottom-tmp.Top)<ParentSeries.MinSize);
  end;

  function IsInsideAxes:Boolean;
  var tmp : TRectF;
      tmpAxis : TChartAxis;
  begin
    tmpAxis:=ParentSeries.GetHorizAxis;
    tmp.Left:=TAxisAccess(tmpAxis).InternalMinimum;
    tmp.Right:=TAxisAccess(tmpAxis).InternalMaximum;

    tmpAxis:=ParentSeries.GetVertAxis;
    tmp.Top:=TAxisAccess(tmpAxis).InternalMinimum;
    tmp.Bottom:=TAxisAccess(tmpAxis).InternalMaximum;

    result:=ContainsRect(ValueBounds,tmp);
  end;

  function CalcVisible:Boolean;
  begin
    if (ParentSeries.MinSize>0) and (Points.Count>1) and VerySmallBounds then
       result:=False
    else
    begin
      result:=(not ParentSeries.ParentChart.ClipPoints) or (ParentSeries.RenderMode=rm3D);

      if not result then
         result:=IsInsideAxes;
    end;
  end;

begin
  if not IVisibleDone then
  begin
    IVisible:=CalcVisible;
    IVisibleDone:=True;
  end;

  result:=IVisible;
end;

function IsConvexPolygon(const X,Y:TChartValues):Boolean;
var n,i,j,k,
    flag : Integer;
    z : Single;
begin
  n:=Length(X);

  if n < 3 then
     result:=False
  else
  begin
    flag:=0;

    for i:=0 to n-1 do
    begin
      j := (i + 1) mod n;
      k := (i + 2) mod n;
      z := (X[j] - X[i]) * (Y[k] - Y[j]);
      z := z- (Y[j] - Y[i]) * (X[k] - X[j]);

      if z < 0 then
         flag:=flag or 1
      else
      if z > 0 then
         flag:=flag or 2;

      if flag=3 then
      begin
        result:=False;
        Exit;
      end;
    end;

    result:=flag <> 0;
  end;
end;

function TTeePolygon.IsConcave:Boolean;
begin
  if IConcave=pcUnknown then
  begin
    if IsConvexPolygon(FPoints.XValues.Value,FPoints.YValues.Value) then
       IConcave:=pcNo
    else
       IConcave:=pcYes;
  end;

  result:=IConcave=pcYes;
end;

procedure TTeePolygon.OffsetValues(const X, Y: Single);

  procedure DoChange(const AList:TChartValueList; const AValue:Single);
  var tmp : TChartValues;
      t : Integer;
  begin
    tmp:=AList.Value;

    for t:=0 to AList.Count-1 do
        tmp[t]:=tmp[t]+AValue;

    AList.Modified:=True;
  end;

var tmp : TPolygonSeries;
begin
  tmp:=Points;

  if X<>0 then
     DoChange(tmp.XValues,X);

  if Y<>0 then
     DoChange(tmp.YValues,Y);

  DataChanged;
end;

function TTeePolygon.PointRect:TRect;
var tmpA : Integer;
    tmpB : Integer;
    tmp  : Integer;
    tmpX : TCoordinate;
    tmpY : TCoordinate;
begin
  tmp:=Round(ParentSeries.CalcPointSize);

  tmpA:=((tmp+1) div 2)-1;

  if (tmp+1) mod 2 = 0 then
     tmpB:=tmpA+1
  else
     tmpB:=tmpA+2;

  tmpX:=IPoints[0].X;
  tmpY:=IPoints[0].Y;

  with result do
  begin
    Left:=tmpX-tmpA;
    Top:=tmpY-tmpA;
    Right:=tmpX+tmpB;
    Bottom:=tmpY+tmpB;
  end;
end;

function TTeePolygon.PointValues:TPointFloatArray;
var t,
    tmp : Integer;
    tmpX,
    tmpY : TChartValues;
begin
  tmpX:=Points.XValues.Value;
  tmpY:=Points.YValues.Value;

  tmp:=Points.Count;
  SetLength(result,tmp);

  for t:=0 to tmp-1 do
  begin
    result[t].x:=tmpX[t];
    result[t].y:=tmpY[t];
  end;
end;

procedure TTeePolygon.DoResize(const ACenter:TPointFloat; const X, Y: Single);
var t : Integer;
    tmpX,
    tmpY : TChartValues;
begin
  tmpX:=Points.XValues.Value;
  tmpY:=Points.YValues.Value;

  for t:=0 to Points.Count-1 do
  begin
    tmpX[t]:=(tmpX[t]*X)+ACenter.x;
    tmpY[t]:=(tmpY[t]*Y)+ACenter.y;
  end;

  Points.XValues.Modified:=True;
  Points.YValues.Modified:=True;

  DataChanged;
end;

procedure TTeePolygon.Resize(const X, Y: Single);

  function CenterOfValues:TPointFloat;
  var tmpR : TRectF;
  begin
    tmpR:=PolygonBounds(PointValues);

    result.X:=(tmpR.Left+tmpR.Right)*0.5;
    result.Y:=(tmpR.Top+tmpR.Bottom)*0.5;
  end;

var tmp : TPointFloat;
begin
  tmp:=CenterOfValues;

  tmp.x:=tmp.x-(X*tmp.x);
  tmp.y:=tmp.y-(Y*tmp.y);

  DoResize(tmp,X,Y);
end;

procedure TTeePolygon.DoOffset(const X,Y:Single);
begin
  FOffset.X:=X;
  FOffset.Y:=Y;
end;

function TTeePolygon.GetDisplayName: string;
begin
  result:=Text;

  if result='' then
     result:=Code;

  if result='' then
     result:=inherited GetDisplayName;
end;

procedure TTeePolygon.DoDraw(const ACanvas:TCanvas3D; Index:Integer; AColor:TColor;
                             ATransp:TTeeTransparency);

  procedure DrawTriangles(const AZ:Integer);
  var t : Integer;
  begin
    for t:=0 to Length(ITrianglePoints3D)-1 do
        ITrianglePoints3D[t].z:=AZ;

    ACanvas.Triangles(ITrianglePoints3D,ParentSeries.Reuse3D,ITriangleCached);
  end;

  function CreateBlend(const Is3D:Boolean; const AZ:Integer):TTeeBlend;

    function Bounds3D:TRect;
    var tmpP : TPointArray;
    begin
      tmpP:=ACanvas.Calc3DPoints(GetPoints,AZ);
      try
        result:=PolygonBounds(tmpP);
      finally
        tmpP:=nil;
      end;
    end;

  var tmpR : TRect;
  begin
    if ACanvas.SupportsAlpha then
       result:=ACanvas.BeginBlending(TeeZeroRect,ATransp)
    else
    begin
      if Is3D then
         tmpR:=Bounds3D
      else
         tmpR:=Bounds;

      result:=ACanvas.BeginBlending(tmpR,ATransp)
    end;
  end;

var tmpIs3D  : Boolean;
    tmpBlend : TTeeBlend;
    tmpZ     : Integer;
begin
  tmpIs3D:=ParentSeries.ParentChart.View3D;

  // Calculate "Z" depth position
  if ParentSeries.RenderMode=rm2D then
     tmpZ:=ParentSeries.MiddleZ
  else
  if tmpIs3D then tmpZ:=ParentSeries.CalcZPos(Index)
             else tmpZ:=0;

  if ATransp>0 then
     tmpBlend:=CreateBlend(tmpIs3D,tmpZ)
  else
     tmpBlend:=nil;

  // Draw the shape...
  with ACanvas do
  begin
    // Special case for shapes with just a single point
    if FPoints.Count=1 then
       ParentSeries.Pointer.Draw(IPoints[0])

       (*
       // Experimental code to improve speed and visual quality on very small pointers,
       // drawing them as single pixels (size=1 pixel) or fast rectangles without border (size<3 pixels)

       if ParentSeries.PointSize>1 then
       begin
         if ParentSeries.PointSize<3 then
            Pen.Style:=psClear;

         if tmpIs3D then
            Rectangle(PointRect,tmpZ)
         else
            Rectangle(PointRect);
       end
       else
          if tmpIs3D then
             Pixels3D[{$IFDEF FMX}Round{$ENDIF}(IPoints[0].X),
                      {$IFDEF FMX}Round{$ENDIF}(IPoints[0].Y),tmpZ]:=AColor
          else
             Pixels[{$IFDEF FMX}Round{$ENDIF}(IPoints[0].X),
                    {$IFDEF FMX}Round{$ENDIF}(IPoints[0].Y)]:=AColor
       *)

    else
    // Normal polygon shapes...
    if tmpIs3D then
    begin
      if ParentSeries.RenderMode=rm3D then
         if Self.IsConcave then
            Polygon3DConcave(IPoints3D)
         else
            Polygon3D(IPoints3D)
      else
      if Self.Closed then
         if SupportsConcave or (not Self.IsConcave) then
            PolygonWithZ(IPoints,tmpZ)
         else
         begin
           DrawTriangles(tmpZ);

           if Pen.Style<>psClear then
              Polyline(IPoints,tmpZ);
         end
      else
      if Pen.Style<>psClear then
         Polyline(IPoints,tmpZ);
    end
    else
    begin
      if Self.Closed then
         if SupportsConcave or (not Self.IsConcave) then
            Polygon(IPoints)
         else
         begin
           if ParentSeries.RenderMode=rm3D then
              PolygonConcave(IPoints)
           else
              DrawTriangles(0);

           if Pen.Style<>psClear then
              Polyline(IPoints);
         end
      else
      if Pen.Style<>psClear then
         Polyline(IPoints);
    end;
  end;

  if ATransp>0 then
     ACanvas.EndBlending(tmpBlend);
end;

type
  TTeeBrushAccess=class(TTeeBrush);

{ Draw the polygon... }
procedure TTeePolygon.Draw(const ACanvas: TCanvas3D; ValueIndex:Integer;
                           const CheckHover:Boolean=False);

  function IsHover:Boolean;
  begin
    result:=ParentSeries.PrepareHoverPen(ValueIndex);

    if not result then
       if ParentShape<>nil then
          result:=ParentSeries.PrepareHoverPen(ParentShape.Index);
  end;

  procedure DoBeginEntity;
  var tmpSt : String;
      tmpBounds : TRect;
  begin
    tmpSt:=Text;

    if tmpSt='' then
       tmpSt:='Item '+IntToStr(Index);

    if Assigned(Transform) then
    begin
      tmpBounds:=Bounds;
      Transform.Origin.X:=(tmpBounds.Left+tmpBounds.Right)*0.5;
      Transform.Origin.Y:=(tmpBounds.Top+tmpBounds.Bottom)*0.5;
    end;

    TCanvas3DAccess(ACanvas).BeginEntity(tmpSt,nil,Transform);
  end;

  function PrepareFormatting:TColor;

    function CalculatePen:TTeePen;
    begin
      if ParentPen then result:=ParentSeries.Pen
                   else result:=Pen;
    end;

    function BoundsWidth:TCoordinate;
    begin
      if not IBoundsDone then
         Bounds;

      result:=IBounds.Right-IBounds.Left;
    end;

  var tmpBrush : TColor;
  begin
    // set pen and brush...
    if (not CheckHover) or (not IsHover) then
       // Speed opt, do not draw line around small polygons
       if BoundsWidth<3 then
          ACanvas.Pen.Style:=psClear
       else
          ACanvas.AssignVisiblePen(CalculatePen);

    // Consider null values
    if ParentSeries.IsPainted(ValueIndex) then
       result:=ParentSeries.ValueColor[ValueIndex]
    else
       result:=clNone;

    if result=clNone then // TV52012444
       ACanvas.Brush.Style:=bsClear
    else
    if TTeeBrushAccess(Brush).HasImage and Brush.Image.Valid then
       ACanvas.AssignBrush(Brush,result)
    else
    if ParentBrush then
    begin
      tmpBrush:=ParentSeries.Brush.Color;

      if tmpBrush=clTeeColor then
         tmpBrush:=result
      else
         tmpBrush:=ACanvas.ColorFrom(result,ACanvas.ColorAlpha(tmpBrush));

      ACanvas.AssignBrush(ParentSeries.Brush,tmpBrush);
    end
    else
      ACanvas.AssignBrush(Brush,result);
  end;

var tmp      : Integer;
    tmpID    : Boolean;
    tmpColor : TColor;
begin
  if FPoints.Active and (FPoints.Count>0) and Visible then
  begin
    if not ParentSeries.Reuse3D then
       FreeAndNil(ITriangleCached);

    tmpID:=TCanvas3DAccess(ACanvas).SupportsID or Assigned(Transform);

    if tmpID then
       DoBeginEntity;

    tmpColor:=PrepareFormatting;

    GetPoints;

    tmp:=Transparency;
    DoDraw(ACanvas,ValueIndex,tmpColor,tmp);

    if tmpID then
       TCanvas3DAccess(ACanvas).EndEntity;
  end;
end;

procedure TTeePolygon.DataChanged;
begin
  IConcave:=pcUnknown;
  IBoundsDone:=False;
  ICentroidDone:=False;
  IVisibleDone:=False;
  ITriangles:=nil;

  ClearCachedPoints;

  ParentSeries.IValueBoundsDone:=False;

  ParentSeries.Repaint;
end;

function TTeePolygon.GetBrush: TChartBrush;
begin
  result:=FPoints.Brush;
end;

function TTeePolygon.GetColor: TColor;
begin
  result:=ParentSeries.ValueColor[Index];
end;

function TTeePolygon.GetPicture: TBackImage;
begin
  result:=FPoints.Legend.Picture;
  result.OnChange:=nil;
end;

function TTeePolygon.GetPen: TChartPen;
begin
  result:=FPoints.Pen;
end;

procedure TTeePolygon.SetBrush(const Value: TChartBrush);
begin
  FPoints.Brush:=Value;
end;

procedure TTeePolygon.SetColor(const Value: TColor);
begin
  Points.SeriesColor:=Value;

  // ParentSeries.ValueColor[Index]:=Value; <--- automatically done at ColorChanged event  
end;

procedure TTeePolygon.SetClosed(const Value: Boolean);
begin
  ParentSeries.SetBooleanProperty(FClosed,Value);
end;

Function TTeePolygon.GetGradient: TChartGradient;
begin
  if not Assigned(FGradient) then
     FGradient:=TChartGradient.Create(TSeriesAccess(ParentSeries).CanvasChanged);

  result:=FGradient;
end;

procedure TTeePolygon.SetGradient(const Value: TChartGradient);
begin
  if Assigned(Value) then
     Gradient.Assign(Value)
  else
     FreeAndNil(FGradient);
end;

procedure TTeePolygon.SetPen(const Value: TChartPen);
begin
  FPoints.Pen:=Value;
end;

procedure TTeePolygon.SetPicture(const Value: TBackImage);
begin
  FPoints.Legend.Picture:=Value;
end;

Procedure TTeePolygon.SetSize(const Value:Single);
begin
  ParentSeries.SetSingleProperty(FSize,Value);
end;

procedure TTeePolygon.SetZ(const Value: Double);
begin
  with ParentSeries do
  begin
    ZValues.Value[Index]:=Value;
    ZValues.Modified:=True;

    ClearLegend;
    Repaint;
  end;
end;

function TTeePolygon.GetZ: Double;
begin
  result:=ParentSeries.ZValues.Value[Index];
end;

function TTeePolygon.GetText: String;
begin
  result:=ParentSeries.Labels[Index];
end;

procedure TTeePolygon.SetCode(const Value: String);
begin
  ParentSeries.SetStringProperty(FCode,Value);
end;

procedure TTeePolygon.SetText(const Value: String);
begin
  ParentSeries.Labels[Index]:=Value;
  ParentSeries.ClearLegend;
end;

procedure TTeePolygon.SetOffset(const Value: TFloatXY);
begin
  FOffset.Assign(Value);
end;

procedure TTeePolygon.SetParentBrush(const Value: Boolean);
begin
  ParentSeries.SetBooleanProperty(FParentBrush,Value);
end;

procedure TTeePolygon.SetParentPen(const Value: Boolean);
begin
  ParentSeries.SetBooleanProperty(FParentPen,Value);
end;

(*
function TTeePolygon.ZPosition:Integer;
var x : Integer;
    y : Integer;
    P : TPoint;
    tmpZ : Integer;
begin
  result:=0;
  With ParentSeries do
  if FPoints.Count>0 then
  begin
    X:=GetHorizAxis.CalcPosValue(FPoints.XValues.Value[0]);
    Y:=GetVertAxis.CalcPosValue(FPoints.YValues.Value[0]);
    tmpZ:=ParentSeries.CalcZPos(0);
    P:=ParentSeries.ParentChart.Canvas.Calculate3DPosition(TeePoint(x,y),tmpZ);
  end;
end;
*)

function TTeePolygon.Centroid:{$IFDEF FMX}TPoint{$ELSE}TPointFloat{$ENDIF};
begin
  if not ICentroidDone then
  begin
    ICentroid:=PolygonCentroid(GetPoints);
    ICentroidDone:=True;
  end;

  result:=ICentroid;
end;

function TTeePolygon.Clicked(const AIndex,X,Y:Integer): Boolean;
var tmpX,
    tmpY: Integer;
begin
  if Points.Visible and Visible then
  begin
    GetPoints;

    if ParentSeries.RenderMode=rm3D then
       if ZFar<0 then
       begin
         result:=False;
         Exit;
       end;

    tmpX:=X;
    tmpY:=Y;

    ParentSeries.ParentChart.Canvas.Calculate2DPosition(tmpX,tmpY,ParentSeries.CalcZPos(AIndex));

    result:=((Points.Count=1) and PointInRect(PointRect,tmpX,tmpY))
            or
            PointInPolygon(TeePoint(tmpX,tmpY),IPoints);
  end
  else
    result:=False;
end;

function TTeePolygon.Bounds:TRect;
begin
  if not IBoundsDone then
  begin
    IBounds:=PolygonBounds(GetPoints);
    IBoundsDone:=True;
  end;

  result:=IBounds;
end;

procedure TTeePolygon.SetTransparency(const Value: TTeeTransparency);
begin
  if FTransparency<>Value then
  begin
    FTransparency:=Value;
    ParentSeries.Repaint;
  end;
end;

procedure TTeePolygon.DefineProperties(Filer: TFiler);
begin
  inherited;
  Filer.DefineBinaryProperty('Data', Points.ReadData, Points.WriteData,
                             ParentSeries.ManualData);
end;

function TTeePolygon.GetTransparent: Boolean;
var tmp : TBrush;
begin
  if ParentBrush then tmp:=ParentSeries.Brush
                 else tmp:=Brush;

  result:=tmp.Style=bsClear;
end;

procedure TTeePolygon.SetTransparent(const Value: Boolean);
begin
  ParentBrush:=False;

  if Value then
     Brush.Style:=bsClear
  else
     Brush.Style:=bsSolid;
end;

{ TTeePolygonGroup }

Constructor TTeePolygonGroup.Create(const AMap:TMapSeries);
begin
  inherited Create;
  Map:=AMap;
end;

Destructor TTeePolygonGroup.Destroy;
begin
  Shapes:=nil;
  inherited;
end;

function TTeePolygonGroup.Count:Integer;
begin
  result:=Length(Shapes);
end;

function TTeePolygonGroup.GetVisible:Boolean;
var t : Integer;
begin
  for t:=0 to Count-1 do
  if Shapes[t].Points.Visible then
  begin
    result:=True;
    Exit;
  end;

  result:=False;
end;

function TTeePolygonGroup.HasShape(const AShape:TTeePolygon):Boolean;
var t : Integer;
    tmp,
    tmpShape : TTeePolygon;
begin
  tmpShape:=AShape;

  if tmpShape.ParentShape<>nil then
     tmpShape:=tmpShape.ParentShape;

  for t:=0 to Count-1 do
  begin
    tmp:=Shapes[t];

    if (tmp=tmpShape) or (tmp.ParentShape=tmpShape) then
    begin
      result:=True;
      Exit;
    end;
  end;

  result:=False;
end;

function TTeePolygonGroup.GetColor:TColor;
begin
  if Count>0 then
     result:=Shapes[0].Color
  else
     result:=clNone;
end;

procedure TTeePolygonGroup.SetCode(const Value:String);
begin
  TSeriesAccess(Map).SetStringProperty(FCode,Value);
end;

procedure TTeePolygonGroup.SetText(const Value:String);
begin
  {TSeriesAccess}(Map).SetStringProperty(FText,Value);
end;

procedure TTeePolygonGroup.SetColor(const Value:TColor);
var t : Integer;
begin
  for t:=0 to Count-1 do
      Shapes[t].Color:=Value;
end;

procedure TTeePolygonGroup.SetVisible(const Value:Boolean);
var t : Integer;
begin
  for t:=0 to Count-1 do
      Shapes[t].Points.Visible:=Value;
end;

procedure TTeePolygonGroup.Add(const AMap:TMapSeries);
begin
  Add(AMap.Shapes,0,AMap.Shapes.Count-1);
end;

procedure TTeePolygonGroup.Add(const AShape:TTeePolygon);
var l : Integer;
begin
  l:=Count;
  SetLength(Shapes,l+1);
  Shapes[l]:=AShape;
end;

procedure TTeePolygonGroup.Add(const AShapes:Array of Integer);
var l,
    t : Integer;
begin
  l:=Count;
  SetLength(Shapes,l+Length(AShapes));

  for t:=Low(AShapes) to High(AShapes) do
      Shapes[l+t-Low(AShapes)]:=Map.Shapes[AShapes[t]];
end;

procedure TTeePolygonGroup.Add(AFrom,ATo:Integer);
begin
  Add(Map.Shapes,AFrom,ATo);
end;

procedure TTeePolygonGroup.Add(const AShapes:TTeePolygonList; AFrom,ATo:Integer);
var l,
    t : Integer;
begin
  l:=Count;
  SetLength(Shapes,l+(ATo-AFrom+1));

  for t:=AFrom to ATo do
      Shapes[l+t-AFrom]:=AShapes[t];
end;

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

procedure TTeePolygonGroup.Offset(const X, Y: Single);
var t : Integer;
begin
  for t:=0 to Count-1 do
      Shapes[t].OffsetValues(X,Y);
end;

procedure TTeePolygonGroup.Resize(const X, Y: Single);

  function CenterOfValues:TPointFloat;
  var tmpR,
      tmpR2 : TRectF;
      t : Integer;
  begin
    tmpR:=PolygonBounds(Shapes[0].PointValues);

    for t:=1 to Count-1 do
    begin
      tmpR2:=PolygonBounds(Shapes[t].PointValues);

      if tmpR2.Left<tmpR.Left then
         tmpR.Left:=tmpR2.Left;

      if tmpR2.Right>tmpR.Right then
         tmpR.Right:=tmpR2.Right;

      if tmpR2.Top<tmpR.Top then
         tmpR.Top:=tmpR2.Top;

      if tmpR2.Bottom>tmpR.Bottom then
         tmpR.Bottom:=tmpR2.Bottom;
    end;

    result.X:=(tmpR.Left+tmpR.Right)*0.5;
    result.Y:=(tmpR.Top+tmpR.Bottom)*0.5;
  end;


var t : Integer;
    tmp : TPointFloat;
begin
  if Count>0 then
  begin
    tmp:=CenterOfValues;

    tmp.x:=tmp.x-(X*tmp.x);
    tmp.y:=tmp.y-(Y*tmp.y);

    for t:=0 to Count-1 do
        Shapes[t].DoResize(tmp,X,Y);
  end;
end;

procedure TTeePolygonGroup.Show;
begin
  Visible:=True;
end;

{ TTeePolygonGroups }

Function TTeePolygonGroups.Get(Index:Integer):TTeePolygonGroup;
begin
  result:=TTeePolygonGroup(Items[Index]);
end;

function TTeePolygonGroups.IndexWithShape(const AShape:TTeePolygon):Integer;
var t : Integer;
begin
  for t:=0 to Count-1 do
      if Group[t].HasShape(AShape) then
      begin
        result:=t;
        Exit;
      end;

  result:=-1;
end;

Procedure TTeePolygonGroups.Put(Index:Integer; Const Value:TTeePolygonGroup);
begin
  Items[Index]:=Value;
end;

function TTeePolygonGroups.GetByCode(const ACode: String): TTeePolygonGroup;
var tmp : Integer;
begin
  tmp:=IndexOfCode(ACode);

  if tmp=-1 then
     result:=nil
  else
     result:=Group[tmp];
end;

function TTeePolygonGroups.GetByName(const AName: String): TTeePolygonGroup;
var t : Integer;
begin
  for t:=0 to Count-1 do
      if Group[t].Text=AName then
      begin
        result:=Group[t];
        Exit;
      end;

  result:=nil;
end;

function TTeePolygonGroups.IndexOfCode(const ACode:String):Integer;
var t : Integer;
begin
  for t:=0 to Count-1 do
      if Group[t].Code=ACode then
      begin
        result:=t;
        Exit;
      end;

  result:=-1;
end;

function TTeePolygonGroups.Add(const AName:String):TTeePolygonGroup;
begin
  result:=TTeePolygonGroup.Create(IMap);
  result.FText:=AName;

  inherited Add(result);
end;

function TTeePolygonGroups.Add(const AName:String; const ACode:String; const AShapes:TTeePolygons):TTeePolygonGroup;
begin
  result:=Add(AName);
  result.FCode:=ACode;
  result.Shapes:=AShapes;
end;

{$IFNDEF D18}
procedure TTeePolygonGroups.Clear;
var t : Integer;
begin
  for t:=0 to Count-1 do
      Group[t].Free;

  inherited;
end;
{$ENDIF}

{ TTeePolygonList }

function TTeePolygonList.IndexOfCode(const ACode:String):Integer;
var t : Integer;
    tmp : TTeePolygon;
begin
  for t:=0 to Count-1 do
  begin
    tmp:=Polygon[t];

    if tmp.ParentShape=nil then
       if SameText(tmp.Code,ACode) then
       begin
         result:=t;
         Exit;
       end;
  end;

  result:=-1;
end;

function TTeePolygonList.Add: TTeePolygon;
begin
  result:=inherited Add as TTeePolygon;
end;

type
  TChartSeriesAccess=class(TChartSeries);

procedure TTeePolygonList.Exchange(A,B:Integer);
var tmp : TCollectionItem;
begin
  tmp:=Items[B];
  Items[A].Index:=B;
  tmp.Index:=A;

  TChartSeriesAccess(Owner).FColors.Exchange(A,B);
  Owner.Labels.Exchange(A,B);
  Owner.MandatoryValueList.Exchange(A,B);
end;

procedure TTeePolygonList.Delete(Start, Quantity: Integer);
var t: Integer;
begin
  for t:=1 to Quantity do
      {$IFDEF D18}
      Items[Start].DisposeOf;
      {$ELSE}
      Items[Start].Free;
      {$ENDIF}
end;

function TTeePolygonList.Get(Index: Integer): TTeePolygon;
begin
  result:=TTeePolygon(Items[Index]);
end;

function TTeePolygonList.GetByCode(const ACode: String): TTeePolygon;
var t : Integer;
begin
  for t:=0 to Count-1 do
  begin
    result:=Polygon[t];

    if SameText(result.Code,ACode) then
       Exit;
  end;

  result:=nil;
end;

function TTeePolygonList.GetByName(const AName: String): TTeePolygon;
var t : Integer;
begin
  for t:=0 to Count-1 do
  begin
    result:=Polygon[t];

    if SameText(result.Text,AName) then
       Exit;
  end;

  result:=nil;
end;

function TTeePolygonList.Owner: TMapSeries;
begin
  result:=TMapSeries(GetOwner);
end;

procedure TTeePolygonList.Put(Index: Integer; const Value: TTeePolygon);
begin
  Items[Index]:=Value;
end;

{ TMapLegend }

Constructor TMapLegend.Create(const AMap:TMapSeries);
begin
  inherited;
  FSort:=TMapLegendSort.Create(IMap);
end;

Destructor TMapLegend.Destroy;
begin
  FSort.Free;
  inherited;
end;

procedure TMapLegend.Assign(Source:TPersistent);
begin
  if Source is TMapLegend then
  begin
    FIncludeUnNamed:=TMapLegend(Source).FIncludeUnNamed;
    FIncludeZero:=TMapLegend(Source).FIncludeZero;
    FStyle:=TMapLegend(Source).FStyle;
    Sort:=TMapLegend(Source).Sort;
  end
  else
    inherited;
end;

procedure TMapLegend.SetSort(const Value:TMapLegendSort);
begin
  FSort.Assign(Value);
end;

procedure TMapLegend.SetIncludeUnNamed(const Value:Boolean);
begin
  if FIncludeUnNamed<>Value then
  begin
    FIncludeUnNamed:=Value;
    IMap.InvalidateLegend;
  end;
end;

procedure TMapLegend.SetIncludeZero(const Value:Boolean);
begin
  if FIncludeZero<>Value then
  begin
    FIncludeZero:=Value;
    IMap.InvalidateLegend;
  end;
end;

procedure TMapLegend.SetStyle(const Value:TMapLegendStyle);
begin
  if FStyle<>Value then
  begin
    FStyle:=Value;
    IMap.InvalidateLegend;
  end;
end;

{ TMapPersistent }

Constructor TMapPersistent.Create(const AMap:TMapSeries);
begin
  inherited Create;
  IMap:=AMap;
end;

{ TMapMarks }

Constructor TMapMarks.Create;
begin
  inherited;

  FAutoSize:=True;
  FAutoTextColor:=True;
  FCentroid:=True;
end;

procedure TMapMarks.Assign(Source:TPersistent);
begin
  if Source is TMapMarks then
  begin
    FAutoSize:=TMapMarks(Source).FAutoSize;
    FAutoTextColor:=TMapMarks(Source).FAutoTextColor;
    FCentroid:=TMapMarks(Source).FCentroid;
    FStyle:=TMapMarks(Source).FStyle;
    FUpperCase:=TMapMarks(Source).FUpperCase;
  end
  else
    inherited;
end;

procedure TMapMarks.SetAutoSize(const Value:Boolean);
begin
  IMap.SetBooleanProperty(FAutoSize,Value);
end;

procedure TMapMarks.SetAutoTextColor(const Value:Boolean);
begin
  IMap.SetBooleanProperty(FAutoTextColor,Value);
end;

procedure TMapMarks.SetCentroid(const Value:Boolean);
begin
  IMap.SetBooleanProperty(FCentroid,Value);
end;

procedure TMapMarks.SetUppercase(const Value:Boolean);
begin
  IMap.SetBooleanProperty(FUppercase,Value);
end;

procedure TMapMarks.SetStyle(const Value:TMapMarksText);
begin
  if FStyle<>Value then
  begin
    FStyle:=Value;
    IMap.Repaint;
  end;
end;

{ TMapLegendSort }

procedure TMapLegendSort.SetBy(const Value:TMapLegendSortBy);
begin
  if FBy<>Value then
  begin
    FBy:=Value;
    IMap.InvalidateLegend;
  end;
end;

procedure TMapLegendSort.SetInverted(const Value:Boolean);
begin
  if FInverted<>Value then
  begin
    FInverted:=Value;
    IMap.InvalidateLegend;
  end;
end;

procedure TMapLegendSort.Assign(Source:TPersistent);
begin
  if Source is TMapLegendSort then
  begin
    FBy:=TMapLegendSort(Source).FBy;
    FInverted:=TMapLegendSort(Source).FInverted;

    IMap.ClearLegend;
  end
  else
    inherited;
end;

{ TMapRenderOptions }

Constructor TMapRenderOptions.Create(const AMap:TMapSeries);
const
  OceanColor={$IFDEF FMX}claNavy{$ELSE}clNavy{$ENDIF};

begin
  inherited;

  FRotation:=TPointXYFloat.Create(nil,0,IMap.CanvasChanged);

  FBrush:=TTeeBrush.Create(IMap.CanvasChanged);
  TTeeBrushAccess(FBrush).InitColor(OceanColor);
  FBrush.BackColor:=OceanColor;

  FBrush.Gradient.EndColor:=OceanColor;

  FMeridians:=TTeePen.Create(IMap.CanvasChanged);
  FParallels:=TTeePen.Create(IMap.CanvasChanged);
end;

Destructor TMapRenderOptions.Destroy;
begin
  FParallels.Free;
  FMeridians.Free;
  FBrush.Free;
  FRotation.Free;

  inherited;
end;

procedure TMapRenderOptions.CalcSpherical(var X,Y:Single);
var tmp,
    sinX,
    cosX,
    sinY,
    cosY : {$IFDEF D15}Single{$ELSE}Extended{$ENDIF};

    tmpX : Integer;
begin
  tmpX:=Round(X*10)-XCenterDeg;

  while tmpX>3599 do
        Dec(tmpX,3600);

  while tmpX<-3600 do
        Inc(tmpX,3600);

  with SinCosTable[tmpX] do
  begin
    sinX:=Sin;
    cosX:=Cos;
  end;

  with SinCosTable[Round(Y*10)] do
  begin
    sinY:=Sin;
    cosY:=Cos;
  end;

  X:=2*HalfDivPi*(cosY*sinX);

  tmp:=cosY*cosX;

  Y:=HalfDivPi*((CosYCenter*sinY)-(SinYCenter*tmp));
end;

procedure TMapRenderOptions.CalcSpherical(var X,Y:Single; out Z:Single);
var tmp,
    sinX,
    cosX,
    sinY,
    cosY : {$IFDEF D15}Single{$ELSE}Extended{$ENDIF};

    tmpX : Integer;
begin
  tmpX:=Round(X*10)-XCenterDeg;

  while tmpX>3599 do
        Dec(tmpX,3600);

  while tmpX<-3600 do
        Inc(tmpX,3600);

  with SinCosTable[tmpX] do
  begin
    sinX:=Sin;
    cosX:=Cos;
  end;

  with SinCosTable[Round(Y*10)] do
  begin
    sinY:=Sin;
    cosY:=Cos;
  end;

  X:=2*HalfDivPi*(cosY*sinX);

  tmp:=cosY*cosX;

  Y:=HalfDivPi*((CosYCenter*sinY)-(SinYCenter*tmp));
  Z:=(SinYCenter*sinY)+(CosYCenter*tmp);
end;

function TMapRenderOptions.CalcPoint(const X,Y:Single):TPointFloat;
var tmpX,
    tmpY : Single;
begin
  tmpX:=X;
  tmpY:=Y;

  CalcSpherical(tmpX,tmpY);

  result.X:=IHorizCalc(tmpX);
  result.Y:=IVertCalc(tmpY);
end;

function TMapRenderOptions.CalcPoint(const X,Y:TChartValue; out Z:Single):TPoint;
var tmpX,
    tmpY : Single;
begin
  tmpX:=X;
  tmpY:=Y;

  CalcSpherical(tmpX,tmpY,Z);

  result.X:=IHorizCalc(IOffsetX+tmpX);
  result.Y:=IVertCalc(IOffsetY+tmpY);
end;

{
function TMapRenderOptions.CalcPoint3D(var P:TPointFloat):TPoint3D;
var Z : Single;
begin
  CalcSpherical(P.x,P.y,Z);

  result.x:=IHorizCalc(IOffsetX+P.x);
  result.y:=IVertCalc(IOffsetY+P.y);
  result.z:=Round(100*Z);
end;
}

function TMapRenderOptions.CalcPoint2D(const X,Y:TChartValue):TPoint;
begin
  result.X:=IHorizCalc(IOffsetX+X);
  result.Y:=IVertCalc(IOffsetY+Y);
end;

function TMapRenderOptions.CalcPoint2D(const P:TPointFloat):TPoint3D;
begin
  result.X:=IHorizCalc(IOffsetX+P.x);
  result.Y:=IVertCalc(IOffsetY+P.y);
end;

procedure TMapRenderOptions.DrawSphere(const ACanvas:TCanvas3D; const R:TRect);
var
  P : TPointFloatArray;

  procedure DrawParallel(const Lat:Integer);
  var t : Integer;
  begin
    for t:=0 to 35 do
        P[t]:=CalcPoint(-90+Rotation.X+(t*5),Lat);

    {$IFDEF FMX}
    ACanvas.Polyline(P);
    {$ELSE}
    ACanvas.PolylineFloat(P);
    {$ENDIF}
  end;

  procedure DrawMeridian(const Lon:Integer);
  var t : Integer;
  begin
    for t:=0 to 35 do
        P[t]:=CalcPoint(Lon,-90+Rotation.Y+(t*5));

    {$IFDEF FMX}
    ACanvas.Polyline(P);
    {$ELSE}
    ACanvas.PolylineFloat(P);
    {$ENDIF}
  end;

var t : Integer;
    tmp : Single;
begin
  if Brush.Style<>bsClear then
  begin
    ACanvas.AssignBrush(Brush);
    ACanvas.Ellipse(R);
  end;

  if Meridians.Visible or Parallels.Visible then
  begin
    IHorizCalc:=IMap.GetHorizAxis.CalcXPosValue;
    IVertCalc:=IMap.GetVertAxis.CalcYPosValue;

    SetLength(P,36);
  end;

  if Meridians.Visible then
  begin
    ACanvas.AssignVisiblePen(Meridians);

    for t:=0 to 35 do
    begin
      tmp:=Round((t*10)-Rotation.X) mod 360;

      if (tmp>0) and (tmp<180) then
         DrawMeridian(-90+(t*10));
    end;
  end;

  if Parallels.Visible then
  begin
    ACanvas.AssignVisiblePen(Parallels);

    for t:=-8 to 8 do
    begin
      tmp:=(t*10)-Rotation.Y;

      if (tmp>-90) and (tmp<90) then
         DrawParallel(t*10);
    end;
  end;
end;

procedure TMapRenderOptions.Prepare;
begin
  XCenterDeg:=Round(Rotation.X*10);
  YCenterRad:=TeePiStep*(Rotation.Y);

  SinCos(YCenterRad,SinYCenter,CosYCenter);

  IHorizCalc:=IMap.GetHorizAxis.CalcXPosValue;
  IVertCalc:=IMap.GetVertAxis.CalcYPosValue;
end;

procedure TMapRenderOptions.SetBrush(const Value:TTeeBrush);
begin
  FBrush.Assign(Value);
end;

procedure TMapRenderOptions.SetMeridians(const Value:TTeePen);
begin
  FMeridians.Assign(Value);
end;

procedure TMapRenderOptions.SetParallels(const Value:TTeePen);
begin
  FParallels.Assign(Value);
end;

procedure TMapRenderOptions.SetRotation(const Value:TPointXYFloat);
begin
  FRotation.Assign(Value);
end;

procedure TMapRenderOptions.Assign(Source:TPersistent);
begin
  if Source is TMapRenderOptions then
  begin
    Brush:=TMapRenderOptions(Source).FBrush;
    Meridians:=TMapRenderOptions(Source).FMeridians;
    Parallels:=TMapRenderOptions(Source).FParallels;
    Rotation:=TMapRenderOptions(Source).FRotation;
  end
  else
     inherited;
end;

{ TMapSeries }

type
  TValueListAccess=class(TChartValueList);

Constructor TMapSeries.Create(AOwner: TComponent);
begin
  inherited;

  FOffset:=TFloatXY.Create;
  FOffset.OnChange:=OffsetChanged;

  FEmboss:=TTeeEmboss.Create(CanvasChanged);
  FShadow:=TTeeShadow.Create(CanvasChanged);

  FShapes:=TTeePolygonList.Create(Self,TTeePolygon);

  FGroups:=TTeePolygonGroups.Create;
  FGroups.IMap:=Self;

  // Do not show X and Y values at TChartGrid:
  TValueListAccess(XValues).Internal:=True;
  TValueListAccess(YValues).Internal:=True;

  ZValues.Name:=TeeMsg_Value;

  FMapLegend:=TMapLegend.Create(Self);
  FMapMarks:=TMapMarks.Create(Self);

  Marks.Automatic.Move:=False;

  Marks.Clip:=True;

  CalcVisiblePoints:=False;
  YMandatory:=False;
  MandatoryValueList:=ZValues;

  FRenderOptions:=TMapRenderOptions.Create(Self);
end;

Destructor TMapSeries.Destroy;
begin
  FRenderOptions.Free;
  FreeAndNil(FGroups);
  FreeAndNil(FShapes);
  FMapLegend.Free;
  FMapMarks.Free;
  FEmboss.Free;
  FShadow.Free;
  FOffset.Free;
  FreeAndNil(FPointer);

  inherited;
end;

procedure TMapSeries.OffsetChanged(Sender:TObject);
begin
  FreeCached;
  Repaint;
end;

procedure TMapSeries.AssignFormatting(Source:TChartSeries);
begin
  if Source is TMapSeries then
  with TMapSeries(Source) do
  begin
    Self.Emboss:=FEmboss;
    Self.Shadow:=FShadow;
    Self.Pointer:=FPointer;
    Self.FTransparent:=Transparent;
  end;

  inherited;
end;

procedure TMapSeries.Assign(Source:TPersistent);
var t : Integer;
begin
  inherited;

  // Force not automatic moving of marks:
  Marks.Automatic.Move:=False;

  if Source is TMapSeries then
  begin
    for t:=0 to TMapSeries(Source).Count-1 do
        Shapes.Add.Assign(TMapSeries(Source).Polygon[t]);

    FRender:=TMapSeries(Source).FRender;
    RenderOptions:=TMapSeries(Source).FRenderOptions;
    FMapLegend.Assign(TMapSeries(Source).FMapLegend);
    FMapMarks.Assign(TMapSeries(Source).FMapMarks);
    Offset:=TMapSeries(Source).Offset;

    // Pending: Groups.CopyFrom

    IValueBoundsDone:=False;
    ClearLegend;
  end;
end;

procedure TMapSeries.ClearLegend;
begin
  // Reset flag to "nil", so next repaint will force recalculating custom
  // legend items (if necessary)
  ILegendItems:=nil;
end;

procedure TMapSeries.SetRenderOptions(const Value: TMapRenderOptions);
begin
  FRenderOptions.Assign(Value);
end;

procedure TMapSeries.SetShadow(const Value: TTeeShadow);
begin
  FShadow.Assign(Value);
end;

procedure TMapSeries.SetEmboss(const Value: TTeeEmboss);
begin
  FEmboss.Assign(Value);
end;

procedure TMapSeries.DefineProperties(Filer: TFiler);
begin
  DontSaveData:=not CanSaveData;
  ForceSaveData:=False;
  inherited;
end;

procedure TMapSeries.DrawValue(ValueIndex: Integer);
begin
  {$IFOPT C+}
  if Shapes.Count>ValueIndex then
  {$ENDIF}
     Shapes[ValueIndex].Draw(ParentChart.Canvas,ValueIndex);
end;

Procedure TMapSeries.Delete(ValueIndex:Integer);
var tmp : TTeePolygon;
begin
  inherited;

  if Assigned(FShapes) then
  begin
    tmp:=Shapes[ValueIndex];

    tmp.ParentSeries:=nil; // Avoid tmp.Destroy to re-delete !

    {$IFDEF D18}
    tmp.DisposeOf;
    {$ELSE}
    tmp.Free;
    {$ENDIF}
  end;
end;

Procedure TMapSeries.Delete(Start,Quantity:Integer; RemoveGap:Boolean=False);
begin
  inherited;

  if Assigned(FShapes) then
     FShapes.Delete(Start,Quantity);
end;

function TMapSeries.GetZAxis:TChartAxis;
begin
  if Assigned(ParentChart) then
     result:=ParentChart.Axes.Depth
  else
     result:=nil;
end;

procedure TMapSeries.CalcValueBounds;

  procedure InitBounds(const AShape:TChartSeries; const APolygon:TTeePolygon);
  var tmp : Single;
  begin
    tmp:=APolygon.FOffset.X;
    IValueBounds.Left:=AShape.MinXValue+tmp;
    IValueBounds.Right:=AShape.MaxXValue+tmp;

    tmp:=APolygon.FOffset.Y;
    IValueBounds.Top:=AShape.MinYValue+tmp;
    IValueBounds.Bottom:=AShape.MaxYValue+tmp;
  end;

  procedure CheckBounds(const AShape:TChartSeries; const APolygon:TTeePolygon);
  var tmp : TChartValue;
      tmpOff : Single;
  begin
    tmpOff:=APolygon.FOffset.X;

    tmp:=AShape.MinXValue+tmpOff;

    if tmp<IValueBounds.Left then
       IValueBounds.Left:=tmp;

    tmp:=AShape.MaxXValue+tmpOff;

    if tmp>IValueBounds.Right then
       IValueBounds.Right:=tmp;

    tmpOff:=APolygon.FOffset.Y;

    tmp:=AShape.MinYValue+tmpOff;

    if tmp<IValueBounds.Top then
       IValueBounds.Top:=tmp;

    tmp:=AShape.MaxYValue+tmpOff;

    if tmp>IValueBounds.Bottom then
       IValueBounds.Bottom:=tmp;
  end;

  procedure InitBounds3D;
  begin
    IValueBounds.Left:=-1;
    IValueBounds.Right:=1;
    IValueBounds.Top:=-1;
    IValueBounds.Bottom:=1;
  end;

var t : Integer;
    tmpFirst : Boolean;
    tmp : TPolygonSeries;
    tmpP : TTeePolygon;
begin
  if FRender=rm3D then
     InitBounds3D
  else
  begin
    tmpFirst:=True;

    for t:=0 to Shapes.Count-1 do
    begin
      tmpP:=Shapes[t];

      tmp:=tmpP.FPoints;

      if tmp.Visible and (tmp.Count>0) then
      begin
        if tmpFirst then
        begin
          InitBounds(tmp,tmpP);
          tmpFirst:=False;
        end
        else
          CheckBounds(tmp,tmpP);
      end;
    end;

    if tmpFirst then
       IValueBounds:=TeeZeroRectF;
  end;

  IValueBoundsDone:=True;
end;

function TMapSeries.MaxXValue: TChartValue;
begin
  if not IValueBoundsDone then
     CalcValueBounds;

  result:=IValueBounds.Right;
end;

function TMapSeries.MaxYValue: TChartValue;
begin
  if not IValueBoundsDone then
     CalcValueBounds;

  result:=IValueBounds.Bottom;
end;

function TMapSeries.MinXValue: TChartValue;
begin
  if not IValueBoundsDone then
     CalcValueBounds;

  result:=IValueBounds.Left;
end;

function TMapSeries.MinYValue: TChartValue;
begin
  if not IValueBoundsDone then
     CalcValueBounds;

  result:=IValueBounds.Top;
end;

function TMapSeries.GetMarkLabel(ValueIndex:Integer):String;
var tmp : String;
begin
  if FMapMarks.FStyle=mtCode then
     result:=Shapes[ValueIndex].Code
  else
  begin
    result:=Labels[ValueIndex];

    if FMapMarks.FStyle=mtBoth then
    begin
      //tmp:=IntToStr(ValueIndex); // <-- debug
      tmp:=Shapes[ValueIndex].Code;

      if result='' then
         result:=tmp
      else
         result:=result+TeeLineSeparator+tmp;
    end;
  end;

  if FMapMarks.UpperCase then
     result:=UpperCase(result);
end;

procedure TMapSeries.SwapValueIndex(A,B:Integer);
begin
  inherited;

  Shapes.Exchange(A,B);

  Repaint;
end;

procedure TMapSeries.GetPaletteBounds(out AMin, ARange: Double);

  function CalcMinMax:Double;
  var t : Integer;
      tmp : TChartValues;
      tmpFirst : Boolean;
  begin
    AMin:=0;

    tmp:=MandatoryValueList.Value;

    tmpFirst:=True;
    result:=0;

    for t:=0 to Count-1 do
    begin
      if not IsNull(t) then
      begin
        if tmpFirst then
        begin
          AMin:=tmp[t];
          result:=AMin;

          tmpFirst:=False;
        end
        else
        begin
          if tmp[t]<AMin then
             AMin:=tmp[t]
          else
          if tmp[t]>result then
             result:=tmp[t];
        end;
      end;
    end;
  end;

var tmpMax : Double;
begin
  if (TreatNulls=tnIgnore) or (Count=0) then
     inherited
  else
  begin
    tmpMax:=CalcMinMax;
    ARange:=tmpMax-AMin;
  end;
end;

function TMapSeries.GetPointer:TSeriesPointer;
begin
  if not Assigned(FPointer) then
  begin
    FPointer:=TSeriesPointer.Create(Self);
    FPointer.SizeFloat:=0.1;
    FPointer.SizeUnits:=suAxis;

    FPointer.ParentChart:=ParentChart;
  end;

  result:=FPointer;
end;

function TMapSeries.GetPointSize:Single;
begin
  if Assigned(FPointer) then
     result:=FPointer.SizeFloat
  else
     result:=1;
end;

procedure TMapSeries.SetMapLegend(const Value:TMapLegend);
begin
  FMapLegend.Assign(Value);
end;

procedure TMapSeries.SetMapMarks(const Value: TMapMarks);
begin
  FMapMarks.Assign(Value);
end;

procedure TMapSeries.SetOffset(const Value: TFloatXY);
begin
  FOffset.Assign(Value);
end;

procedure TMapSeries.SetPointer(const Value:TSeriesPointer);
begin
  if Assigned(Value) then
     Pointer.Assign(Value)
  else
     FreeAndNil(FPointer);
end;

procedure TMapSeries.SetPointSize(const Value: Single);
begin
  Pointer.SizeFloat:=Value;
end;

procedure TMapSeries.SetRender(const Value:TMapRenderMode);
begin
  if FRender<>Value then
  begin
    FRender:=Value;
    Repaint;
  end;
end;

procedure TMapSeries.SetShapes(const Value: TTeePolygonList);
begin
  FShapes.Assign(Value);
end;

procedure TMapSeries.DoDelete(ValueIndex:Integer);
begin
  inherited Delete(ValueIndex);
end;

procedure TMapSeries.CalcHorizMargins(var LeftMargin,
  RightMargin: Integer);
begin
  inherited;

  if Pen.Visible then
  begin
    Inc(LeftMargin,{$IFDEF FMX}Round{$ENDIF}(Pen.Width));
    Inc(RightMargin,{$IFDEF FMX}Round{$ENDIF}(Pen.Width));
  end;
end;

procedure TMapSeries.CalcVerticalMargins(var TopMargin,
  BottomMargin: Integer);
begin
  inherited;

  Inc(BottomMargin);

  if Pen.Visible then
  begin
    Inc(TopMargin,{$IFDEF FMX}Round{$ENDIF}(Pen.Width));
    Inc(BottomMargin,{$IFDEF FMX}Round{$ENDIF}(Pen.Width));
  end;
end;

Function TMapSeries.NumSampleValues;
begin
  result:=12;
end;

Function TMapSeries.AddXY(Const AXValue,AYValue:TChartValue; Const ALabel:String='';
                         AColor:TColor=clTeeColor):Integer;
begin
  result:=inherited AddXY(AXValue,AYValue,ALabel,AColor);

  if (not IAdding) and (ILoading or (not (csReading in ComponentState))) then
     Shapes.Add;
end;

procedure TMapSeries.AddSampleValues(NumValues: Integer; OnlyMandatory:Boolean=False);

  Procedure AddShape(Const X,Y:Array of Integer; Const AText:String);
  var t : Integer;
      tmpX : Integer;
      tmpY : Integer;
  begin
    if Count>NumSampleValues then
    begin
      tmpX:=RandomValue(NumSampleValues);
      tmpY:=RandomValue(NumSampleValues);
    end
    else
    begin
      tmpX:=0;
      tmpY:=0;
    end;

    AddXY(0,0);

    With Shapes[Count-1] do
    begin
      Points.BeginUpdate;
      try
        for t:=Low(X) to High(X) do
            AddXY(tmpX+X[t],tmpY+Y[t]);

        Text:=AText;
        Z:=RandomValue(1000)/1000.0;

      finally
        Points.EndUpdate;
      end;
    end;
  end;

Const AX:Array[0..13] of Integer=(1,3,4,4,5,5,6,6,4,3,2,1,2,2);
      AY:Array[0..13] of Integer=(7,5,5,7,8,9,10,11,11,12,12,11,10,8);
      BX:Array[0..8]  of Integer=(5,7,8,8,7,6,5,4,4);
      BY:Array[0..8]  of Integer=(4,4,5,6,7,7,8,7,5);
      CX:Array[0..15] of Integer=(9,10,11,11,12,9,8,7,6,6,5,5,6,7,8,8);
      CY:Array[0..15] of Integer=(5,6,6,7,8,11,11,12,11,10,9,8,7,7,6,5);
      DX:Array[0..7]  of Integer=(12,14,15,14,13,12,11,11);
      DY:Array[0..7]  of Integer=(5,5,6,7,7,8,7,6);
      EX:Array[0..10] of Integer=(4,6,7,7,6,6,5,4,3,3,2);
      EY:Array[0..10] of Integer=(11,11,12,13,14,15,16,16,15,14,13);
      FX:Array[0..11] of Integer=(7,8,9,11,10,8,7,6,5,5,6,6);
      FY:Array[0..11] of Integer=(13,14,14,16,17,17,18,18,17,16,15,14);
      GX:Array[0..11] of Integer=(10,12,12,14,13,11,9,8,7,7,8,9);
      GY:Array[0..11] of Integer=(10,12,13,15,16,16,14,14,13,12,11,11);
      HX:Array[0..9]  of Integer=(17,19,18,18,17,15,14,13,15,16);
      HY:Array[0..9]  of Integer=(11,13,14,16,17,15,15,14,12,12);
      IX:Array[0..14] of Integer=(15,16,17,16,15,14,14,13,12,11,10,11,12,13,14);
      IY:Array[0..14] of Integer=(6,6,7,8,8,9,10,11,12,11,10,9,8,7,7);
      JX:Array[0..11] of Integer=(15,16,16,17,17,16,15,13,12,12,14,14);
      JY:Array[0..11] of Integer=(8,8,9,10,11,12,12,14,13,12,10,9);
      KX:Array[0..9]  of Integer=(17,19,20,20,19,17,16,16,17,16);
      KY:Array[0..9]  of Integer=(5,5,6,8,8,10,9,8,7,6);
      LX:Array[0..6]  of Integer=(19,20,21,21,19,17,17);
      LY:Array[0..6]  of Integer=(8,8,9,11,13,11,10);

var t : Integer;
begin
  for t:=0 to NumValues-1 do
  case t mod NumSampleValues of
    0: AddShape(AX,AY,'A');
    1: AddShape(BX,BY,'B');
    2: AddShape(CX,CY,'C');
    3: AddShape(DX,DY,'D');
    4: AddShape(EX,EY,'E');
    5: AddShape(FX,FY,'F');
    6: AddShape(GX,GY,'G');
    7: AddShape(HX,HY,'H');
    8: AddShape(IX,IY,'I');
    9: AddShape(JX,JY,'J');
   10: AddShape(KX,KY,'K');
   11: AddShape(LX,LY,'L');
  end;
end;

type
  TTeeCanvas3DAccess=class(TTeeCanvas3D);

function TMapSeries.Clicked(x, y: Integer): Integer;
var t    : Integer;
    tmpChart : TCustomAxisPanel;
begin
  result:=TeeNoPointClicked;
  tmpChart:=ParentChart;

  if Assigned(tmpChart) then
  begin
    {$IFNDEF FMX}
    // Special case for VCL Map series, when chart has been invalidated,
    // we should not call Shape.Visible because this will remember a cached
    // shaped "bounds" that is not correct until the chart is repainted
    if tmpChart.BufferedDisplay and
       (tmpChart.Canvas is TTeeCanvas3D) and
       TTeeCanvas3DAccess(tmpChart.Canvas).FDirty then
         Exit;
    {$ENDIF}

    for t:=Shapes.Count-1 downto 0 do
        if Shapes[t].Clicked(t,X,Y) then
        begin
          result:=t;
          break;
        end;
  end;
end;

procedure TMapSeries.CalcSelectionPos(ValueIndex:Integer; out X,Y:Integer);
var tmp : {$IFDEF FMX}TPoint{$ELSE}TPointFloat{$ENDIF};
begin
  tmp:=FShapes[ValueIndex].Centroid;

  X:=Round(tmp.X);
  Y:=Round(tmp.Y);
end;

{$IFNDEF FMX}
function PointRound(const P:TPointFloat):TPoint;
begin
  result.X:=Round(P.x);
  result.Y:=Round(P.y);
end;
{$ENDIF}

function TMapSeries.AutoColor(const AColor:TColor):TColor;
begin
  if (Byte(AColor)<$80) or
     (Byte(AColor shr 8)<$80) or
     (Byte(AColor shr 16)<$80) then
     result:=clWhite
  else
     result:={$IFDEF FMX}claBlack{$ELSE}clBlack{$ENDIF};
end;

function TMapSeries.CanDrawMark(ValueIndex:Integer):Boolean;
var tmp : TTeePolygon;
begin
  if ValueIndex>=Shapes.Count then
     result:=False
  else
  begin
    tmp:=Shapes[ValueIndex];
    result:=tmp.Points.Visible and tmp.Visible;

    if result then
       if RenderMode=rm3D then
          if tmp.ZFar<0 then
             result:=False;
  end;
end;

type
  TTeePanelAccess=class(TCustomTeePanel);

procedure TMapSeries.DrawMark(ValueIndex: Integer; const St: String;
  APosition: TSeriesMarkPosition);

  procedure ResizeMark(const AFactor:Single);
  begin
    APosition.Width:=Round(APosition.Width*AFactor);
    APosition.Height:=Round(APosition.Height*AFactor);
  end;

  procedure MoveMarkInteger(const X,Y:Integer); overload;
  begin
    APosition.LeftTop.X:=X-(APosition.Width {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF});
    APosition.LeftTop.Y:=Y-(APosition.Height {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF});
  end;

  procedure MoveMark(const X,Y:Single); overload;
  begin
    {$IFDEF FMX}
    APosition.LeftTop.X:=X-(APosition.Width*0.5);
    APosition.LeftTop.Y:=Y-(APosition.Height*0.5);
    {$ELSE}
    MoveMarkInteger(Round(X),Round(Y));
    {$ENDIF}
  end;

  procedure CalcRectCenter(const R:TRect; out AX,AY:Integer);
  begin
    with RectCenter(R) do
    begin
      AX:={$IFDEF FMX}System.Round{$ENDIF}(X);
      AY:={$IFDEF FMX}System.Round{$ENDIF}(Y);
    end;
  end;

  procedure DoMoveMarkToCenter(const R:TRect);
  var tmpX,
      tmpY : Integer;
  begin
    // the best polygon "center"
    if MapMarks.Centroid then
       CalcSelectionPos(ValueIndex,tmpX,tmpY)
    else
       CalcRectCenter(R,tmpX,tmpY);  // the center of polygon bound rect

    MoveMark(tmpX,tmpY);
  end;

var
  tmpW,
  tmpH : TCoordinate;

  function CalcTextSize:Single;
  begin
    if RenderMode=rm3D then
       result:=2
    else
       result:=4;

    result:=result*(tmpW/APosition.Width);
  end;

var
  tmpS : String;

  function WrapText:Single;
  var i : Integer;
  begin
    result:=0;

    i:=Pos(' ',tmpS);

    if i>0 then
    begin
      tmpS:=Copy(tmpS,1,i-1)+TeeLineSeparator+Copy(tmpS,i+1,Length(tmpS));

      CalculateMarkPosition(Marks,tmpS,
          {$IFDEF FMX}Round{$ENDIF}(APosition.LeftTop.X),
          {$IFDEF FMX}Round{$ENDIF}(APosition.LeftTop.Y),
            APosition);

      result:=CalcTextSize;
    end
  end;

const
  MinFontSize=5;

  function TryCalcTextSize:Single;
  begin
    result:=CalcTextSize;

    if (result<MinFontSize) and Marks.Automatic.Wrap then
       result:=WrapText;
  end;

  function MarkCanFit(const OldSize:Single):Boolean;
  var tmpSize : Single;
  begin
    if MapMarks.AutoSize then
    begin
      tmpSize:=TryCalcTextSize;

      // Very small text font size, do not draw
      if tmpSize<MinFontSize then
      begin
        result:=False;
        Exit;
      end;

      // Set new auto font size
      Marks.Font.SizeFloat:=tmpSize;
      TTeePanelAccess(ParentChart).TryAssignFont(Marks.Font);

      ResizeMark(tmpSize/OldSize);
    end
    else
    if Marks.Automatic.Hide then
       if (APosition.Width>tmpW) or (APosition.Height>tmpH) then
       begin
         result:=False;
         Exit;
       end;

    result:=True;
  end;

  procedure MovePointMark(const P:TPoint);
  begin
    MoveMark(P.X,P.Y-APosition.Height-Pointer.VertSize);
  end;

var tmpR : TRect;
    tmpPoint: Boolean;
    tmpShape : TTeePolygon;
    Old : Single;
begin
  if APosition.Width<4 then
     Exit;

  tmpShape:=Shapes[ValueIndex];

  tmpR:=tmpShape.Bounds;

  if TeeRectIntersects(tmpR,ParentChart.ChartBounds) then
  begin
    tmpS:=St;

    tmpPoint:=tmpShape.Points.Count=1;

    if tmpPoint then
    begin
      if Length(tmpShape.IPoints)>=1 then
      begin
        MovePointMark(tmpShape.IPoints[0]);
        Old:=0;
      end
      else
         Exit;
    end
    else
    begin
      tmpW:=(tmpR.Right-tmpR.Left);
      tmpH:=(tmpR.Bottom-tmpR.Top);

      Old:=Marks.Font.SizeFloat;

      if MarkCanFit(Old) then
         DoMoveMarkToCenter(tmpR)
      else
         Exit;
    end;

    // Calculate best font color
    if Marks.Transparent and MapMarks.AutoTextColor then
       Marks.Font.Color:=AutoColor(ValueColor[ValueIndex]);

    inherited DrawMark(ValueIndex,tmpS,APosition);

    // Restore font size
    if MapMarks.AutoSize and (not tmpPoint) then
       Marks.Font.SizeFloat:=Old;
  end;
end;

class function TMapSeries.GetEditorClass: String;
begin
  result:='TMapSeriesEditor';
end;

procedure TMapSeries.Clear;
begin
  inherited;

  if Assigned(Shapes) and
    (not (csLoading in ComponentState)) then
  begin
    Groups.Clear;
    Shapes.Clear;
  end;

  IValueBoundsDone:=False;
  ClearLegend;
end;

class procedure TMapSeries.CreateSubGallery(
  AddSubChart: TChartSubGalleryProc);
begin
  inherited;
  AddSubChart(TeeMsg_Colors);
end;

// Returns True when any Shape in the MapSeries is visible
// (min max bounding box is inside axes rectangle)
function TMapSeries.IsVisible:Boolean;
var tmp : TRectF;
    t   : Integer;
begin
  // Pending: Cache result until axes are changed,
  // or any Shape point changes.

  if ParentChart.ClipPoints and (RenderMode<>rm3D) then
  begin
    tmp.Left:=GetHorizAxis.Minimum;
    tmp.Right:=GetHorizAxis.Maximum;
    tmp.Top:=GetVertAxis.Minimum;
    tmp.Bottom:=GetVertAxis.Maximum;

    result:=False;

    for t:=0 to Shapes.Count-1 do
        if ContainsRect(Shapes[t].ValueBounds,tmp) then
        begin
          result:=True;
          break;
        end;
  end
  else
    result:=True;
end;

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

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

class procedure TMapSeries.SetSubGallery(ASeries: TChartSeries;
  Index: Integer);
begin
  with TMapSeries(ASeries) do
  Case Index of
    2: ColorEachPoint:=True;
  else inherited;
  end
end;

procedure TMapSeries.GalleryChanged3D(Is3D: Boolean);
begin
  if Is3D then inherited
          else ParentChart.View3D:=False;
end;

function TMapSeries.CalcPointSize:Single;
begin
  if Pointer.SizeUnits=suAxis then
     result:=CalcYSizeValue(PointSize)
  else
     result:=PointSize;
end;

Function TMapSeries.CompareOrder(a,b:Integer):Integer;
var tmpA : TChartValue;
    tmpB : TChartValue;
begin
  tmpA:=ZValues.Value[a];
  tmpB:=ZValues.Value[b];

  if tmpA>tmpB then
     result:=1
  else
  if tmpA<tmpB then
     result:=-1
  else
     result:=0;
end;

Function TMapSeries.CompareOrderRender(a,b:Integer):Integer;
var tmpA,
    tmpB : Integer;
begin
  tmpA:=Shapes[I3DList[a]].ZFar; // ZValues.Value[a];
  tmpB:=Shapes[I3DList[b]].ZFar; // ZValues.Value[b];

  if tmpA>tmpB then
     result:=1
  else
  if tmpA<tmpB then
     result:=-1
  else
     result:=0;
end;

Function TMapSeries.GetPolygon(Index:Integer):TTeePolygon;
begin
  result:=FShapes[Index];
end;

Procedure TMapSeries.SwapPolygon(a,b:Integer);
var tmp : Integer;
begin
  tmp:=I3DList[b];
  I3DList[b]:=I3DList[a];
  I3DList[a]:=tmp;
end;

procedure TMapSeries.SetTransparent(const Value:Boolean);
var t : Integer;
begin
  if FTransparent<>Value then
  begin
    FTransparent:=Value;

    for t:=0 to Shapes.Count-1 do
        Shapes[t].Transparent:=Transparent;

    Repaint;
  end;
end;

procedure TMapSeries.EnsureShapeCount;
var Old : Boolean;
begin
  if Count<Shapes.Count then
  begin
    // Protect against Stack Overflow:
    Old:=IAdding;
    IAdding:=True;

    while Count<Shapes.Count do
          AddXY(0,0);

    IAdding:=Old;
  end;
end;

procedure TMapSeries.FreeCached;
var t : Integer;
begin
  for t:=0 to Shapes.Count-1 do
      Shapes[t].FreeCached;

  if Reuse3D then
     for t:=0 to Count-1 do
         FreeAndNil(Shapes[t].ITriangleCached);
end;

procedure TMapSeries.DoBeforeDrawChart;
begin
  inherited;
  DefaultLegend:=FMapLegend.FStyle<>lsPalette;
end;

procedure TMapSeries.DoBeforeDrawValues;
begin
  inherited;

  if AxisRectChanged then
     FreeCached;
end;

function TMapSeries.DisplayAtLegend(const AShape:TTeePolygon):Boolean;
begin
  result:=AShape.ParentShape=nil;

  if result then
  begin
    result:=(MapLegend.IncludeZero or (AShape.Z<>0))
            and
            (MapLegend.IncludeUnNamed or (AShape.Text<>''));

    // Problem: ChartRect is not yet calculated
    if MapLegend.VisibleOnly and result then
       result:=TeeRectIntersects(AShape.Bounds,ParentChart.ChartRect);
  end;
end;

function TMapSeries.CompareLegendItem(a,b:Integer):Integer;
var tmpA,
    tmpB : TChartValue;
    tmpIndexA,
    tmpIndexB: Integer;
begin
  tmpIndexA:=ILegendItems[a];
  tmpIndexB:=ILegendItems[b];

  if MapLegend.Sort.By=mlsText then
  begin
    if MapLegend.Style=lsTexts then
       result:=CompareText(Labels[tmpIndexA],Labels[tmpIndexB])
    else
       result:=CompareText(Groups[tmpIndexA].Text,Groups[tmpIndexB].Text);
  end
  else
  begin
    // By value, default is descending order (inverted)

    if MapLegend.Style=lsTexts then
    begin
      tmpA:=MandatoryValueList.Value[tmpIndexA];
      tmpB:=MandatoryValueList.Value[tmpIndexB];
    end
    else
    begin
      // Pending: check shapes.count>0
      tmpA:=MandatoryValueList.Value[Groups[tmpIndexA].Shapes[0].Index];
      tmpB:=MandatoryValueList.Value[Groups[tmpIndexB].Shapes[0].Index];
    end;

    if tmpA>tmpB then
       result:=-1
    else
    if tmpA<tmpB then
       result:=1
    else
       result:=0;
  end;

  if MapLegend.Sort.Inverted then
     if result=1 then result:=-1 else
     if result=-1 then result:=1;
end;

procedure TMapSeries.SwapLegendItem(a,b:Integer);
begin
  SwapInteger(ILegendItems[a],ILegendItems[b]);
end;

procedure TMapSeries.SortLegendItems;
begin
  TeeSort(0,High(ILegendItems),CompareLegendItem,SwapLegendItem);
end;

Function TMapSeries.CalcVisibleLegendItems:Integer;
var t : Integer;
begin
  if MapLegend.Style=lsGroups then
  begin
    result:=Groups.Count;

    if Groups.Count>0 then
    begin
      SetLength(ILegendItems,result);

      for t:=0 to result-1 do
          ILegendItems[t]:=t;
    end;
  end
  else
  begin
    if MapLegend.VisibleOnly then
    begin
      GetHorizAxis.AdjustMaxMin;
      GetVertAxis.AdjustMaxMin;
    end;
    
    result:=0;

    for t:=0 to Shapes.Count-1 do
        if DisplayAtLegend(Shapes[t]) then
        begin
          Inc(result);

          if result>Length(ILegendItems) then
             SetLength(ILegendItems,result+64);

          ILegendItems[result-1]:=t;
        end;

    SetLength(ILegendItems,result);
  end;

  if FMapLegend.Sort.By<>mlsNone then
     SortLegendItems;
end;

procedure TMapSeries.InvalidateLegend;
begin
  ClearLegend;
  Repaint;
end;

Function TMapSeries.CountLegendItems:Integer;
begin
  if MapLegend.Style=lsPalette then
     result:=inherited CountLegendItems
  else
  begin
    if ILegendItems=nil then
       CalcVisibleLegendItems;

    result:=Length(ILegendItems);
  end;
end;

Function TMapSeries.CalcLegendIndex(LegendIndex:Integer):Integer;
begin
  if MapLegend.Style=lsPalette then
     result:=inherited CalcLegendIndex(LegendIndex)
  else
     result:=ILegendItems[LegendIndex];
end;

Function TMapSeries.LegendItemColor(LegendIndex:Integer):TColor;
var tmp : TTeePolygons;
begin
  {$IFNDEF D7}
  tmp:=nil;
  {$ENDIF}
  
  if MapLegend.Style=lsGroups then
  begin
    tmp:=Groups[LegendIndex].Shapes;

    if tmp=nil then
       result:=inherited LegendItemColor(LegendIndex)
    else
       result:=tmp[0].Color
  end
  else
    result:=inherited LegendItemColor(LegendIndex);
end;

Function TMapSeries.LegendString(LegendIndex:Integer; LegendTextStyle:TLegendTextStyle):String;
begin
  if MapLegend.Style=lsGroups then
     result:=Groups[LegendIndex].FText
  else
     result:=inherited LegendString(LegendIndex,LegendTextStyle);
end;

Function TMapSeries.LegendToValueIndex(LegendIndex:Integer):Integer;
var tmp : TTeePolygons;
    tmpShape : TTeePolygon;
begin
  {$IFNDEF D7}
  tmp:=nil;
  {$ENDIF}

  if MapLegend.Style=lsPalette then
    if UseColorRange or UsePalette then
       result:=-1
    else
       result:=inherited LegendToValueIndex(LegendIndex)
  else
  if MapLegend.Style=lsGroups then
  begin
    tmp:=Groups[ILegendItems[LegendIndex]].Shapes;

    if tmp=nil then
       result:=-1
    else
    begin
      tmpShape:=tmp[0];

      // Problem: Maps with small shapes and no parent shape
      while tmpShape.ParentShape<>nil do
            tmpShape:=tmpShape.ParentShape;

      result:=tmpShape.Index;
    end;
  end
  else
    result:=ILegendItems[LegendIndex];
end;

Function TMapSeries.ValueIndexToLegend(ValueIndex:Integer):Integer;
var t : Integer;
begin
  result:=-1;

  if ValueIndex<>-1 then
     if MapLegend.Style=lsGroups then
        result:=Groups.IndexWithShape(Shapes[ValueIndex])
     else
     for t:=Low(ILegendItems) to High(ILegendItems) do
         if ILegendItems[t]=ValueIndex then
         begin
           result:=t;
           Exit;
         end;
end;

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

  if MapLegend.Style<>lsPalette then
     if MapLegend.VisibleOnly then
        ClearLegend;
end;

// To solve incompatible types in XE2 vs our TeCanvas.pas Point3D:
function TeePoint3D(const x,y,z:Single):TPoint3DFloat; {$IFDEF D9}inline;{$ENDIF}
begin
  result.X:=X;
  result.Y:=Y;
  result.Z:=Z;
end;

procedure TMapSeries.ClearCachedPoints;
var t : Integer;
begin
  for t:=0 to Shapes.Count-1 do
      Shapes[t].ClearCachedPoints;
end;

procedure TMapSeries.DrawAllValues;

  Procedure DrawAllSorted;
  var t : Integer;
      tmpCount : Integer;
      tmp : TTeePolygon;
  begin
    tmpCount:=Shapes.Count;

    if tmpCount>0 then
    begin
      SetLength(I3DList,tmpCount);
      try
        for t:=0 to tmpCount-1 do
        begin
          tmp:=Shapes[t];
          tmp.GetPoints;

          I3DList[t]:=t;
        end;

        if FRender=rm3D then
           TeeSort(0,tmpCount-1,CompareOrderRender,SwapPolygon)
        else
           TeeSort(0,tmpCount-1,CompareOrder,SwapPolygon);

        for t:=tmpCount-1 downto 0 do
        begin
          tmp:=Shapes[I3DList[t]];

          if tmp.ZFar>=0 then
          begin
            tmp.Draw(ParentChart.Canvas,I3DList[t]);

            {$IFDEF MAPDEBUG}
            Inc(_Painted);
            {$ENDIF}
          end;
        end;
      finally
        I3DList:=nil;
      end;
    end;
  end;

  procedure TryDrawShadow(const AShadow:TTeeShadow);
  var t : Integer;
      tmpP : TPointArray;
      tmpTransform : TTeeTransform;
  begin
    {$IFNDEF D7}
    tmpP:=nil; // Skip warning
    {$ENDIF}

    if (RenderMode<>rm3D) and AShadow.ShouldDraw then
    begin
      if ParentChart.Canvas.SupportsConcave then
         tmpTransform:=nil
      else
      begin
        // Draw shadows using GDI+ (or OpenGL) Transform objects
        tmpTransform:=TTeeTransform.Create;
        tmpTransform.Enabled:=True;
        tmpTransform.Translate:=TeePoint3D(1.0*AShadow.HorizSize, -AShadow.VertSize, -1);
        tmpTransform.Scale:=TeePoint3D(1.0,1,1);

        TCanvas3DAccess(ParentChart.Canvas).BeginEntity('',nil,tmpTransform);
      end;

      for t:=FirstValueIndex to LastValueIndex do
      with Shapes[t] do
      if Points.Active and (Points.Count>1) and Visible then
      begin
        GetPoints;

        if ParentChart.Canvas.SupportsConcave or (not IsConcave) then
        begin
          if ParentChart.View3D then
          begin
            tmpP:=ParentChart.Canvas.Calc3DPoints(IPoints,CalcZPos(t));
            try
              AShadow.Draw(ParentChart.Canvas,tmpP);
            finally
              tmpP:=nil;
            end;
          end
          else
            AShadow.Draw(ParentChart.Canvas,IPoints);
        end
        else
          ParentChart.Canvas.Triangles(ITrianglePoints3D,Reuse3D,ITriangleCached);
      end;

      if not ParentChart.Canvas.SupportsConcave then
      begin
        tmpTransform.Free;
        TCanvas3DAccess(ParentChart.Canvas).EndEntity;
      end;
    end;
  end;

  procedure TryDrawHover;
  var tmp : Integer;
  begin
    if HoverEnabled then
    begin
      tmp:=Selected.HoverIndex;

      if tmp<>-1 then
         Shapes[tmp].Draw(ParentChart.Canvas,tmp,True);
    end;
  end;

  procedure DrawSphere;
  const
    PiAngle=360/Pi;
    HalfPiAngle=PiAngle*0.5;

  var tmpCalc : TAxisCalcPos;
      tmpR : TRect;
      tmp : Single;
  begin
    tmpCalc:=GetHorizAxis.CalcXPosValue;
    tmp:=FOffset.X;
    tmpR.Left:=tmpCalc(tmp-PiAngle);
    tmpR.Right:=tmpCalc(tmp+PiAngle);

    tmpCalc:=GetVertAxis.CalcYPosValue;
    tmp:=FOffset.Y;
    tmpR.Top:=tmpCalc(tmp+HalfPiAngle);
    tmpR.Bottom:=tmpCalc(tmp-HalfPiAngle);

    FRenderOptions.DrawSphere(ParentChart.Canvas,tmpR);
  end;

var tmpBlend : TTeeBlend;
begin
  // TODO: Clear cached points only when Axes or ChartRect have changed.
  // See TCustom3DSeries.CheckCachedSurface and
  //     TCustom3DSeries.CheckCachedSurface

  {$IFDEF MAPDEBUG}
  _Painted:=0;
  _Crossed:=0;
  {$ENDIF}

  if IsVisible then // <-- Avoid drawing Map shapes outside axes
  begin
    ClearCachedPoints;

    if RenderMode=rm3D then
       FRenderOptions.Prepare;

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

    TryDrawShadow(Shadow);
    TryDrawShadow(Emboss);

    if RenderMode=rm3D then
    begin
      CheckSinCosTable;

      DrawSphere;

      DrawAllSorted;
      SaveChartRect;
    end
    else
       inherited;

    TryDrawHover;

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

{ TPolygonSeries }
procedure TPolygonSeries.NotifyValue(ValueEvent: TValueEvent; ValueIndex: Integer);
var tmp : TTeePolygon;
begin
  inherited;

  if not (csDestroying in ComponentState) then
  begin
    tmp:=Polygon;

    if Assigned(tmp) and (not tmp.ParentSeries.ILoading) then
       tmp.DataChanged;
  end;
end;

procedure TPolygonSeries.SetActive(Value: Boolean);
begin
  inherited;
  Polygon.ParentSeries.Repaint;
end;

Function TPolygonSeries.Polygon:TTeePolygon;
begin
  result:=TTeePolygon(Tag);
end;

procedure TPolygonSeries.SetSeriesColor(AColor: TColor);
begin
  inherited;

  // Prevent changing color when caller is Chart.PaintSeriesLegend
  if not Assigned(ParentChart) then
     Polygon.ParentSeries.ValueColor[Polygon.Index]:=AColor;
end;

procedure TPolygonSeries.PrepareLegendCanvas(ValueIndex:Integer; Var BackColor:TColor;
                                   Var BrushStyle:{$IFDEF FMX}TBrushKind{$ELSE}TBrushStyle{$ENDIF});
begin
  inherited;

  with Polygon do
  begin
    ParentSeries.DoBeforeDrawChart;

    if (not Assigned(FGradient)) or (not Gradient.Visible) then
       ParentChart.Canvas.Brush.Color:=Color;
  end;
end;

Procedure TPolygonSeries.CanvasChanged(Sender:TObject);
begin
  if Polygon<>nil then
     Polygon.ParentSeries.CanvasChanged(Sender);

  inherited;
end;

procedure TPolygonSeries.DrawLegendShape(ValueIndex: Integer;
  const Rect: TRect);
begin
  if Assigned(Polygon.FGradient) then
     with Polygon.Gradient do
     if Visible then
        Draw(ParentChart.Canvas,Rect)
     else
        inherited
  else
    inherited;
end;

procedure TPolygonSeries.FillSampleValues(NumValues: Integer);
begin { do nothing, sample values are provided by Owner Map Series }
end;

initialization
  RegisterTeeSeries( TMapSeries, @TeeMsg_GalleryMap,
                                 @TeeMsg_GalleryExtended,1);
finalization
  UnRegisterTeeSeries([TMapSeries]);
end.
