{********************************************}
{ TeeChart Circular Gauge style              }
{ Copyright (c) 2000-2020 by Steema Software }
{********************************************}
unit TeeCircularGauge;
{$I TeeDefs.inc}

interface

uses
  {$IFNDEF LINUX}
  {$IFNDEF FMX}
  Windows, Messages,
  {$ENDIF}
  {$ENDIF}
  SysUtils, Classes, Math,
  {$IFDEF D10}
  Types,
  {$ENDIF}

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

  {$IFDEF FMX}
  FMX.Types,

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

  {$ELSE}
  Graphics, ExtCtrls,
  {$ENDIF}

  {$IFDEF FMX}
  FMXTee.Engine, FMXTee.Canvas, FMXTee.Series, FMXTee.Gauges.Numeric,
  FMXTee.Gauges.Linear
  {$ELSE}
  TeeLinearGauge, TeEngine, TeCanvas, TeeNumericGauge, Series
  {$ENDIF};

type
  TCircularGauge=class;

  TGaugeHand=class(TGaugeSeriesPointer)
  private
    FDistance : Integer;
    FOffset   : Integer;
    FValue    : Double;

    procedure SetDistance(const Value: Integer);
    procedure SetOffset(const Value: Integer);
    procedure SetValue(const Value: Double);
  public
    Constructor Create(const AOwner: TChartSeries);

    procedure Assign(Source:TPersistent); override;
    procedure Draw(const Angle: Double; const Center, EndP: TPoint; Off: Integer);
  published
    property Distance: Integer read FDistance write SetDistance default 30;
    property HorizSize default 5;
    property Offset: Integer read FOffset write SetOffset default 80;
    property Value: Double read FValue write SetValue;
  end;

  TGaugeHandItem=class(TCollectionItem)
  private
    FFormat : TGaugeHand;

    function GetValue:Double;
    procedure SetFormat(const Value:TGaugeHand);
    procedure SetValue(const Value:Double);
  public
    Constructor Create(Collection: TCollection); override;
    Destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;
  published
    property Format:TGaugeHand read FFormat write SetFormat;
    property Value:Double read GetValue write SetValue;
  end;

  TGaugeHands=class(TOwnedCollection)
  private
    Function Get(Index:Integer):TGaugeHandItem; {$IFDEF D10}inline;{$ENDIF}
    Procedure Put(Index:Integer; const Value:TGaugeHandItem);
  public
    Function Add(const AValue:Double):TGaugeHandItem;

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

  TCircularGauge=class(TCustomGaugeMinMax)
  private
    FCenter       : TGaugeSeriesPointer;
    FEndPoint     : TSeriesPointer;
    FHands        : TGaugeHands;
    FLabelsInside : Boolean;
    FRotateLabels : Boolean;
    FTotalAngle   : Double;

    FCircled  : Boolean;
    FRotAngle : Double;
    FXRadius  : Integer;
    FYRadius  : Integer;

    FNumericGauge : TNumericGauge;
    FLinearGauge  : TLinearGauge;
    FAutoPositionNumericGauge: Boolean;
    FAutoValueNumericGauge: Boolean;
    FAutoPositionLinearGauge: Boolean;
    FAutoValueLinearGauge: Boolean;

    FFillToSemiCircle: Boolean;

    function CalcLinearGaugeBounds: TRect;
    function CalcNumericGaugeBounds: TRect;
    function CircularFrame:TCircularFrame; {$IFDEF D9}inline;{$ENDIF}
    procedure DrawEnd(const P: TPoint);

    function GetDisplayTotalAngle:Integer;
    function GetDisplayRotationAngle:Integer;
    function GetHand:TGaugeHand;
    function GetHandOffset:Integer;
    function GetHands:TGaugeHands;
    function GetLinearGauge: TLinearGauge;
    function GetNumericGauge: TNumericGauge;

    procedure SetCenter(const Value: TGaugeSeriesPointer);
    procedure SetCircled(const Value: Boolean);
    procedure SetDisplayTotalAngle(const Value: Integer);
    procedure SetDisplayRotationAngle(const Value: Integer);
    procedure SetEndPoint(const Value: TSeriesPointer);
    procedure SetHand(const Value: TGaugeHand);
    procedure SetHandOffset(const Value: Integer);
    procedure SetHands(const Value: TGaugeHands);
    procedure SetHandsParentChart;
    procedure SetLabelsInside(const Value: Boolean);
    procedure SetLinearGauge(const Value: TLinearGauge);
    procedure SetNumericGauge(const Value: TNumericGauge);
    procedure SetRotateLabels(const Value: Boolean);
    procedure SetRotAngle(const Value: Double);
    procedure SetTotalAngle(const Value: Double);

    procedure SetAutoPositionLinearGauge(const Value: Boolean);
    procedure SetAutoValueLinearGauge(const Value: Boolean);
    procedure SetAutoPositionNumericGauge(const Value: Boolean);
    procedure SetAutoValueNumericGauge(const Value: Boolean);
    procedure SetFillToSemicircle(const Value: Boolean);
  protected
    ICenter: TPoint;
    IStartAngle: Double;

    function CalcDistance(Distance: Integer): Integer;
    procedure CalcOrigRect; override;
    function CalcPoint(const Angle: Double; const Center: TPoint;
      const RadiusX, RadiusY: Double): TPoint;
    Procedure CalcRadius;
    function CalcValue(const AValue: Double): Double;
    class Procedure CreateSubGallery(AddSubChart:TChartSubGalleryProc); override;
    Procedure DoBeforeDrawValues; override;
    procedure DrawAllValues; override;
    procedure DrawAxis; override;
    procedure DrawAxisMinorTick(const Inner, Outer: TPoint); override;
    procedure DrawAxisTick(const Inner, InnerPlus, InnerMinus, Outer,
      OuterPlus, OuterMinus: TPoint); override;
    procedure DrawCenter; virtual;
    procedure DrawColorLine(const Line: TGaugePointerRange); override;
    procedure DrawFrame;
    procedure DrawFace; override;
    procedure DrawHand; override;

    procedure DrawMark( ValueIndex:Integer; Const St:String;
                        APosition:TSeriesMarkPosition); override;

    function CreateFrame: TFramedBorder; override;
    class Function GetEditorClass: String; override;
    function GetLabel(const AValue:TChartValue):String; virtual;
    procedure SetAxisOnce; override;
    procedure SetGaugePalette(const Palette:Array of TColor); override;
    procedure SetMinimum(const Value: TChartValue); override;
    Procedure SetParentChart(const Value: TCustomAxisPanel); override;
    class Procedure SetSubGallery(ASeries:TChartSeries; Index:Integer); override;
    procedure SetValues; override;

    function ValueToAngle(const AValue:Double):Double;
  public
    Constructor Create(AOwner: TComponent); override;
    Destructor Destroy; override;

    procedure Assign(Source: TPersistent); override;
    function Axis: TChartAxis; override;
    function UseAxis: Boolean; override;

    // From TCircledSeries
    function CircleXCenter: Integer;
    function CircleYCenter: Integer;

    property LinearGauge: TLinearGauge read GetLinearGauge write SetLinearGauge;
    property NumericGauge: TNumericGauge read GetNumericGauge write SetNumericGauge;
    property XRadius: Integer read FXRadius;
    property YRadius: Integer read FYRadius;
  published
    property Active;
    property Cursor;
    property ParentChart;
    property DataSource; { after ParentChart }
    property Marks;
    property PercentFormat;
    property ShowInLegend;
    property Title;
    property ValueFormat;

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

    property XLabelsSource;
    property XValues;
    property YValues;

    property AllowDrag;
    property AutoPositionLinearGauge: Boolean read FAutoPositionLinearGauge
                                              write SetAutoPositionLinearGauge default True;
    property AutoValueLinearGauge: Boolean read FAutoValueLinearGauge
                                           write SetAutoValueLinearGauge default True;
    property AutoPositionNumericGauge: Boolean read FAutoPositionNumericGauge
                                               write SetAutoPositionNumericGauge default True;
    property AutoValueNumericGauge: Boolean read FAutoValueNumericGauge
                                            write SetAutoValueNumericGauge default True;

    property Center: TGaugeSeriesPointer read FCenter write SetCenter;
    property Circled: Boolean read FCircled write SetCircled default True;
    property DisplayTotalAngle:Integer read GetDisplayTotalAngle
                                       write SetDisplayTotalAngle;
    property DisplayRotationAngle:Integer read GetDisplayRotationAngle
                                          write SetDisplayRotationAngle;
    property FillToSemiCircle: Boolean read FFillToSemiCircle
                                       write SetFillToSemicircle default True;

    property EndPoint: TSeriesPointer read FEndPoint write SetEndPoint;
    property Face;
    property Frame;
    property GreenLine stored False;
    property Hand:TGaugeHand read GetHand write SetHand stored False;
    property HandOffset: Integer read GetHandOffset write SetHandOffset;
    property Hands:TGaugeHands read GetHands write SetHands;
    property LabelsInside: Boolean read FLabelsInside write SetLabelsInside default True;
    property Maximum;
    property Minimum;
    property MinorTickDistance default 3;
    property MinorTicks;
    property Ranges;
    property RedLine stored False;
    property RotateLabels: Boolean read FRotateLabels write SetRotateLabels default True;
    property RotationAngle: Double read FRotAngle write SetRotAngle;
    property Ticks;
    property TotalAngle: Double read FTotalAngle write SetTotalAngle;
    property Value;
  end;

  TClockGaugeStyle=(cgsDecimal,cgsRoman);

  TClockGauge=class(TCircularGauge)
  private
    FCustomTime : Boolean;
    FSnap       : Boolean;
    FStyle      : TClockGaugeStyle;
    FTime       : TDateTime;

    ITimer      : TTimer;

    function GetHours:TGaugeHand;
    function GetMinutes:TGaugeHand;
    function GetSeconds:TGaugeHand;
    function GetTime:TDateTime;
    function IsTimeStored: Boolean;
    procedure SetCustomTime(const Value:Boolean);
    procedure SetSnap(const Value: Boolean);
    procedure SetStyle(const Value:TClockGaugeStyle);
    procedure SetTime(const Value:TDateTime);
    procedure TimerExpired(Sender:TObject);
  protected
    function GetLabel(const AValue:TChartValue):String; override;
    procedure Loaded; override;
    procedure SetActive(Value:Boolean); override;
    procedure SetParentChart(const Value:TCustomAxisPanel); override;
  public
    Constructor Create(AOwner: TComponent); override;
    Destructor Destroy; override;

    property Hours:TGaugeHand read GetHours;
    property Minutes:TGaugeHand read GetMinutes;
    property Seconds:TGaugeHand read GetSeconds;
    property Timer:TTimer read ITimer;
  published
    property CustomTime:Boolean read FCustomTime write SetCustomTime default False;
    property SnapToSeconds:Boolean read FSnap write SetSnap default True;
    property Style:TClockGaugeStyle read FStyle write SetStyle default cgsRoman;
    property Time:TDateTime read GetTime write SetTime stored IsTimeStored;
  end;

implementation

uses
  {$IFDEF FMX}
  FMXTee.Procs, FMXTee.Chart, FMXTee.ProConstants
  {$ELSE}

  {$IFDEF LCL}
  Types,
  {$ENDIF}

  Chart, TeeProcs, TeeProCo
  {$ENDIF};

{ TGaugeHandItem }

type
  TGaugeHandsAccess=class(TGaugeHands);

Constructor TGaugeHandItem.Create(Collection: TCollection);
begin
  inherited;
  FFormat:=TGaugeHand.Create(TGaugeHandsAccess(Collection).GetOwner as TChartSeries);
end;

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

procedure TGaugeHandItem.Assign(Source:TPersistent);
begin
  if Source is TGaugeHandItem then
     FFormat.Assign(TGaugeHandItem(Source).FFormat)
  else
     inherited;
end;

function TGaugeHandItem.GetValue:Double;
begin
  result:=FFormat.Value;
end;

procedure TGaugeHandItem.SetValue(const Value:Double);
begin
  FFormat.Value:=Value;
end;

procedure TGaugeHandItem.SetFormat(const Value:TGaugeHand);
begin
  FFormat.Assign(Value);
end;

{ TGaugeHands }

Function TGaugeHands.Get(Index:Integer):TGaugeHandItem;
begin
  result:=TGaugeHandItem(inherited Items[Index]);
end;

Procedure TGaugeHands.Put(Index:Integer; const Value:TGaugeHandItem);
begin
  inherited Items[Index]:=Value;
end;

Function TGaugeHands.Add(const AValue:Double):TGaugeHandItem;
begin
  result:=TGaugeHandItem(inherited Add);

  with result.Format do
  begin
    GaugeStyle:=gpHand;
    Brush.Color:={$IFDEF FMX}claRed{$ELSE}clRed{$ENDIF};
    Transparency:=70;
    Distance:=25;
    Value:=AValue;
  end;
end;

{ TCircularGauge }

type
  TFramedAccess = class(TFramedBorder);

const
  BlackPalette: Array [0 .. 23] of TColor =
    ($282828, $323232, $646464, $0F0F0F, $484848, $0F0F0F,

    $282864, // 6
    clNone, $646464,

    $646464, clNone, $1EC81E,

    $3C73C8, // 12
    {$IFDEF FMX}claRed{$ELSE}clRed{$ENDIF}, // $3C73C8,
    $3C73C8, $0A3296,

    $828282, // 16
    $828282, clNone,

    $1E1E1E, $646464, $646464, $646464, $646464);

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

  Face.ShapeStyle := fosEllipse;

  FTotalAngle := 300;

  GreenLine.StartValue := 0;
  GreenLine.EndValue := 70;
  GreenLine.Pen.Hide;

  RedLine.StartValue := 80;
  RedLine.EndValue := 100;
  RedLine.Pen.Hide;

  FLabelsInside := True;
  FRotateLabels := True;
  MinorTickDistance := 3;

  FCircled := True;
  ICircled := True;

  FCenter := TGaugeSeriesPointer.Create(Self);
  FCenter.GaugeStyle := gpCenter;
  FCenter.Size := 16;
  FCenter.Gradient.Visible := True;
  FCenter.Pen.Hide;
  FCenter.Style := psCircle;
  FCenter.Shadow.Visible := True;
  FCenter.Shadow.Size := 2;

  FEndPoint := TSeriesPointer.Create(Self);
  FEndPoint.Style := psCircle;
  FEndPoint.Color := {$IFDEF FMX}claWhite{$ELSE}clWhite{$ENDIF};

  FAutoPositionLinearGauge := True;
  FAutoValueLinearGauge := True;
  FAutoPositionNumericGauge := True;
  FAutoValueNumericGauge := True;

  FFillToSemiCircle := True;

  TFramedAccess(Frame).SetDefaultCircled(True);

  Frame.Width:=6;

  Frame.OuterBrush.Gradient.MidColor:={$IFDEF FMX}claWhite{$ELSE}clWhite{$ENDIF};
  Frame.MiddleBrush.Gradient.MidColor:={$IFDEF FMX}claWhite{$ELSE}clWhite{$ENDIF};
  Frame.InnerBrush.Gradient.MidColor:={$IFDEF FMX}claWhite{$ELSE}clWhite{$ENDIF};

  Frame.OuterBrush.Gradient.StartColor:=RGB(192,192,192);
  Frame.OuterBrush.Gradient.EndColor:=RGB(64,64,64);
  Frame.MiddleBrush.Gradient.StartColor:=RGB(128,128,128);
  Frame.MiddleBrush.Gradient.EndColor:=RGB(130,130,130);
  Frame.InnerBrush.Gradient.StartColor:=RGB(64,64,64);
  Frame.InnerBrush.Gradient.EndColor:=RGB(128,128,128);

  DisplayTotalAngle := 360;
  DisplayRotationAngle := 0;
end;

Destructor TCircularGauge.Destroy;
begin
  FHands.Free;
  FLinearGauge.Free;
  FNumericGauge.Free;
  FEndPoint.Free;
  FCenter.Free;
  inherited;
end;

function TCircularGauge.GetHandOffset:Integer;
begin
  result:=Hand.Offset;
end;

function TCircularGauge.GetHands:TGaugeHands;
begin
  if not Assigned(FHands) then
  begin
    FHands:=TGaugeHands.Create(Self,TGaugeHandItem);
    FHands.Add(0);
  end;

  result:=FHands;
end;

procedure TCircularGauge.SetHandsParentChart;
var t : Integer;
begin
  for t:=0 to Hands.Count-1 do
      Hands[t].Format.ParentChart:=ParentChart;
end;

procedure TCircularGauge.SetHands(const Value: TGaugeHands);
begin
  Hands.Assign(Value);
  SetHandsParentChart;
end;

procedure TCircularGauge.SetHandOffset(const Value: Integer);
begin
  Hand.Offset:=Value;
end;

function TCircularGauge.CircularFrame:TCircularFrame;
begin
  result:=TCircularFrame(Frame);
end;

procedure TCircularGauge.DrawFace;
var tmp : TTeeBasePath;
begin
  if Face.Visible then
  begin
    if DisplayTotalAngle < 360 then
    begin
      tmp:=ParentChart.Canvas.CreatePath;
      try
        tmp.ArcTo(INewRect.Left,INewRect.Top,INewRect.Right-INewRect.Left,INewRect.Bottom-INewRect.Top,
                  135+DisplayRotationAngle,DisplayTotalAngle);
        tmp.Close;

        ParentChart.Canvas.AssignBrush(Face.Brush,Face.Color);
        tmp.Draw(ParentChart.Canvas);
      finally
        tmp.Free;
      end;
    end
    else
      inherited;
  end;

  DrawColorLines;
end;

procedure TCircularGauge.DrawColorLine(const Line: TGaugePointerRange);
var
  tmpR: TRect;
  minRadius: Double;
  tmp: Integer;
begin
  if Line.Visible then
  begin
    Line.StartValue := Max(Minimum, Line.StartValue);
    Line.EndValue := Min(Maximum, Line.EndValue);

    if Line.StartValue < Line.EndValue then
    begin
      tmpR := INewRect;
      minRadius := Min(XRadius, YRadius) * Line.Position * 0.01;
      tmp := -Round(minRadius);

      InflateRect(tmpR, tmp, tmp);

      Line.Draw(CalcValue(Line.StartValue), CalcValue(Line.EndValue), tmpR);
    end;
  end;
end;

function TCircularGauge.CircleXCenter: Integer;
begin
  with IOrigRect{$IFDEF FMX}.Round{$ENDIF} do
       result := (Left+Right) div 2;
end;

function TCircularGauge.CircleYCenter: Integer;
begin
  with IOrigRect{$IFDEF FMX}.Round{$ENDIF} do
       result := (Top+Bottom) div 2;
end;

procedure TCircularGauge.CalcOrigRect;
var tmp : Integer;
begin
  inherited;

  if Circled then
  begin
    INewRect := IOrigRect;
    CalcRadius;

    TCircledSeries.AdjustScreenRatio(ParentChart.Canvas, FXRadius, FYRadius,
      IOrigRect, ParentChart.Printing);

    INewRect := IOrigRect;
    tmp:=-Round(CircularFrame.CalcWidth(INewRect));
    InflateRect(INewRect,tmp,tmp);

    CalcRadius;
  end;
end;

procedure TCircularGauge.SetValues;
begin
  inherited;

  IStartAngle :=  ((360 - TotalAngle) * 0.5) + RotationAngle;
  CalcRadius;
end;

function TCircularGauge.CalcPoint(const Angle: Double;
    const Center: TPoint; const RadiusX, RadiusY: Double): TPoint;
var
  tmpSin, tmpCos: {$IFDEF D15}Double{$ELSE}Extended{$ENDIF};
begin
  SinCos(Angle, tmpSin, tmpCos);
  result.x := Center.x - {$IFNDEF FMX}Round{$ENDIF}(RadiusX * tmpCos);
  result.y := Center.y - {$IFNDEF FMX}Round{$ENDIF}(RadiusY * tmpSin);
end;

procedure TCircularGauge.DrawAxisTick(const Inner, InnerPlus, InnerMinus,
  Outer, OuterPlus, OuterMinus: TPoint);
begin
  Ticks.Draw(Inner, InnerPlus, InnerMinus, Outer, OuterPlus, OuterMinus);
end;

procedure TCircularGauge.DrawAxisMinorTick(const Inner, Outer: TPoint);
begin
  MinorTicks.Draw(Inner, TeeZeroPoint, TeeZeroPoint, Outer, TeeZeroPoint,
    TeeZeroPoint);
end;

function TCircularGauge.GetLabel(const AValue:TChartValue):String;
begin
  result:=FormatFloat(ValueFormat, AValue);
end;

type
  TAxisAccess=class(TChartAxis);
  TShapeAccess=class(TCustomTextShape);
  TTeePanelAccess=class(TCustomTeePanel);

  {$IFDEF FMX}
  TCanvasAccess=class(TTeeCanvas);
  {$ENDIF}

function TCircularGauge.ValueToAngle(const AValue:Double):Double;
begin
  result:=(IStartAngle - 90) + (AValue * TotalAngle / IRange);
end;

procedure TCircularGauge.DrawAxis;

  // Avoid "NAN" (or "-NAN") ArcCos results
  function TeeArcCos(const Value:{$IFDEF CPUX64}Double{$ELSE}Extended{$ENDIF}):Double;
  begin
    if Value<-1 then
       result:=Pi
    else
    if Value>1 then
       result:=0
    else
       result:=ArcCos(Value);
  end;

  // Returns the angle needed to draw the length on the circumference of the circle from a point
  function CalcAngleFromLength(const Point: TPoint; const Length: Double): Double;
  var
    radius,
    tmp : Double;
  begin
    if Circled then
       radius := XRadius
    else
       radius := TeeDistance(Abs(ICenter.x - Point.x), Abs(ICenter.y - Point.y));

    // law of cosines
    tmp := Sqr(radius) * 2;

    if tmp = 0 then
       result := 0
    else
       result := TeeArcCos({$IFDEF CPUX64}Double{$ENDIF}((tmp - Sqr(Length)) / tmp));
  end;

  procedure DrawArc(const AStart,ASweep:Single);
  begin
    ParentChart.Canvas.Arc(INewRect{$IFDEF FMX}.Round{$ENDIF}, AStart,AStart+ASweep);
  end;

  function HalfSize(const APen:TTeePen):Integer;
  begin
    result:={$IFDEF FMX}Round{$ENDIF}(APen.Width {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF});
  end;

var
  tmp: Double;
  tmpS: String;

  tmpValue,
  tmpStep,
  tmpStep2,
  tmpAngle,
  tmpAngle2,
  tmpXRad,
  tmpYRad,
  tmpHalf,
  tmp2 : Single;

  x, y,
  tmpFontH,
  tmpXRadius,
  tmpYRadius,
  tmpW,
  t: Integer;

  P3Plus,
  P3Minus,
  P4Plus,
  P4Minus,
  P3, P4: TPoint;

  tmpPen : TTeePen;
  tmpStart,
  tmpEnd : Double;
begin
  if (Axis <> nil) and Axis.Visible then
  begin
    tmpXRadius := XRadius;
    tmpYRadius := YRadius;

    if Axis.Axis.Visible then
    begin
      tmp:=270-(RotationAngle * 2);

      if AxisRanges.Count=0 then
      begin
        ParentChart.Canvas.AssignVisiblePen(Axis.Axis);
        DrawArc(IStartAngle+tmp,TotalAngle);
        tmpW:=1+HalfSize(Axis.Axis);
      end
      else
      begin
        tmpW:=0;

        tmp:=-2*RotationAngle;

        for t:=0 to AxisRanges.Count-1 do
        begin
          tmpPen:=AxisRanges[t].Format.Pen;
          ParentChart.Canvas.AssignVisiblePen(tmpPen);

          tmpStart:=ValueToAngle(Maximum-AxisRanges[t].StartValue)+tmp;
          tmpEnd:=ValueToAngle(Maximum-AxisRanges[t].EndValue)+tmp;

          DrawArc(tmpStart,tmpEnd-tmpStart);
          tmpW:=Max(tmpW,1+HalfSize(tmpPen));
        end;
      end;

      Dec(tmpXRadius, tmpW);
      Dec(tmpYRadius, tmpW);
    end;

    if Axis.Title.Visible and (Axis.Title.Caption <> '') then
    begin
      TShapeAccess(Axis.Title).CalcBounds(ParentChart);

      x := CircleXCenter - Round((tmpXRadius / 2.5) + (Axis.Title.Width * 0.5));
      y := CircleYCenter - Round(Axis.Title.Height * 0.5);

      Axis.Title.MoveTo(x,y);
      Axis.Title.Draw;
    end;

    tmpStep:=Axis.Increment;

    TTeePanelAccess(ParentChart).TryAssignFont(Axis.Texts.Font);

    if tmpStep=0 then
    begin
      // Calculate best label increment based on radius, font size and TotalAngle:
      Axis.SetMinMax(Minimum,Maximum);

      Axis.IAxisSize:=Round(TotalAngle*TeePiStep*Sqrt((Sqr(tmpXRadius)+Sqr(tmpYRadius))*0.5));
      TAxisAccess(Axis).AdjustMaxMin;
      tmpStep:=Max(Axis.CalcIncrement,(Maximum-Minimum)/1000);
    end;

    if Axis.Ticks.Visible or Axis.Labels then
    begin
      Axis.TickLength := Ticks.VertSize;
      Axis.Ticks.Assign(Ticks.Pen);

      ParentChart.Canvas.AssignVisiblePen(Ticks.Pen);

      tmpXRad := (tmpXRadius - Axis.TickLength);
      tmpYRad := (tmpYRadius - Axis.TickLength);

      if Axis.Labels then
         tmpFontH := ParentChart.Canvas.FontHeight
      else
         tmpFontH:=0;

      if (IRange <> 0) and (tmpStep <> 0) then
      begin
        tmpValue := 0;

        while ((TotalAngle<360) and (tmpValue<=IRange)) or
              ((TotalAngle>=360) and (tmpValue<IRange)) do
        begin
          tmpAngle := ValueToAngle(tmpValue);
          tmp := TeePiStep * tmpAngle;

          P3 := CalcPoint(tmp, ICenter, tmpXRad, tmpYRad);
          P4 := CalcPoint(tmp, ICenter, tmpXRadius, tmpYRadius);

          tmp2 := TeePiStep * (tmpAngle + (Ticks.HorizSize * 0.1));
          P3Plus := CalcPoint(tmp2, ICenter, tmpXRad, tmpYRad);

          tmp2 := TeePiStep * (tmpAngle - (Ticks.HorizSize * 0.1));
          P3Minus := CalcPoint(tmp2, ICenter, tmpXRad, tmpYRad);

          tmp2 := TeePiStep * (tmpAngle + (Ticks.HorizSize * 0.2));
          P4Plus := CalcPoint(tmp2, ICenter, tmpXRadius, tmpYRadius);

          tmp2 := TeePiStep * (tmpAngle - (Ticks.HorizSize * 0.2));
          P4Minus := CalcPoint(tmp2, ICenter, tmpXRadius, tmpYRadius);

          DrawAxisTick(P3, P3Plus, P3Minus, P4, P4Plus, P4Minus);

          if Axis.Labels then
          begin
            tmpS:=GetLabel(tmpValue + Minimum);

            ParentChart.Canvas.BackMode := cbmTransparent;

            if RotateLabels then
            begin
              tmpAngle2 := CalcAngleFromLength(P4,0)*0.5;

              if LabelsInside then
                 P3 := CalcPoint(tmp - tmpAngle2, ICenter, tmpXRad -
                                (tmpFontH * 0.25), tmpYRad - (tmpFontH * 0.25))
              else
                 P3 := CalcPoint(tmp - tmpAngle2, ICenter,
                                 tmpXRadius + tmpFontH, tmpYRadius + tmpFontH);

              {$IFDEF FMX}
              TCanvasAccess(ParentChart.Canvas).IUseTextCenter:=True;
              TCanvasAccess(ParentChart.Canvas).ITextCenter:=P3;
              {$ENDIF}

              with P3{$IFDEF FMX}.Round{$ENDIF} do
                 ParentChart.Canvas.RotateLabel(x, y, tmpS, 90 - tmpAngle);

              {$IFDEF FMX}
              TCanvasAccess(ParentChart.Canvas).IUseTextCenter:=False;
              {$ENDIF}
            end
            else
            begin
              tmpHalf:=(tmpFontH * 0.5);

              if LabelsInside then
              begin
                P3:=CalcPoint(tmp, ICenter, tmpXRad - tmpHalf, tmpYRad - tmpHalf);

                (*
                tmpAngle2 := tmpAngle - 90;

                if tmpAngle2 < 0 then
                   tmpAngle2 := -tmpAngle2;

                P3.Y:=P3.y - {$IFNDEF FMX}Round{$ENDIF}(tmpFontH * ((1.0 / 180.0) * tmpAngle2));
                *)

              end
              else
                P3:=CalcPoint(tmp, ICenter, tmpXRadius + tmpFontH, tmpYRadius + tmpHalf);

              {$IFDEF FMX}
              P3.Y:=P3.Y-tmpHalf;
              {$ELSE}
              Dec(P3.Y,tmpFontH div 2);
              {$ENDIF}

              ParentChart.Canvas.TextOut(P3.x, P3.y, tmpS);
            end
          end;

          tmpValue := tmpValue + tmpStep;
        end;
      end;
    end;

    if (IRange <> 0) and MinorTicks.Visible and (Axis.MinorTickCount > 0) then
    begin
      if tmpStep <> 0 then
      begin
        Axis.MinorTickLength := MinorTicks.VertSize;
        Axis.MinorTicks.Assign(MinorTicks.Pen);
        ParentChart.Canvas.AssignVisiblePen(MinorTicks.Pen);

        tmpXRad := tmpXRadius - Axis.MinorTickLength - MinorTickDistance;
        tmpYRad := tmpYRadius - Axis.MinorTickLength - MinorTickDistance;

        tmpStep2 := tmpStep / (Axis.MinorTickCount + 1);
        tmpValue := 0;

        repeat
          for t := 1 to Axis.MinorTickCount do
          begin
            tmpAngle := ValueToAngle(tmpValue + t * tmpStep2);

            tmp := TeePiStep * tmpAngle;

            DrawAxisMinorTick(CalcPoint(tmp, ICenter, tmpXRad, tmpYRad),
              CalcPoint(tmp, ICenter, tmpXRadius - MinorTickDistance,
                tmpYRadius - MinorTickDistance));
          end;

          tmpValue := tmpValue + tmpStep;

        until tmpValue > (Maximum - Minimum - tmpStep);
      end;
    end;
  end;
end;

procedure TCircularGauge.DrawAllValues;
begin
  inherited;

  if Assigned(FLinearGauge) then
  begin
    if FAutoValueLinearGauge then
       FLinearGauge.Value := Value;

    FLinearGauge.CustomBounds := CalcLinearGaugeBounds;
  end;

  if Assigned(FNumericGauge) then
  begin
    if FAutoValueNumericGauge then
       FNumericGauge.Value := Value;

    FNumericGauge.CustomBounds := CalcNumericGaugeBounds;
  end;
end;

function TCircularGauge.CalcValue(const AValue: Double): Double;
var IAngleInc : Double;
begin
  if IRange=0 then
     IAngleInc:=1  // Protection agains division by zero
  else
     IAngleInc:= TotalAngle / IRange;

  result := ((AValue - Minimum) * IAngleInc) + IStartAngle;
end;

function TCircularGauge.CalcDistance(Distance: Integer): Integer;
var
  tmp: Integer;
begin
  if Distance > 0 then
  begin
    tmp := Min(XRadius, YRadius);

    if Axis.Visible then
       Dec(tmp, Axis.TickLength);

    result := Round(Distance * tmp * 0.01);
  end
  else
    result := 0;
end;

procedure TCircularGauge.SetGaugePalette(const Palette:Array of TColor);
var t : Integer;
begin
  inherited;

  if Assigned(FHands) then
  for t:=0 to Hands.Count-1 do
  with Hands[t].Format do
  begin
    Pen.Color:=GetPaletteColor(12);
    Gradient.EndColor:=GetPaletteColor(13);
    Gradient.MidColor:=GetPaletteColor(14);
    Gradient.StartColor:=GetPaletteColor(15);
  end;
end;

procedure TCircularGauge.DrawHand;

  Function CalcPointMaxDistance(const Angle: Double; DistancePct: Integer): TPoint;
  var
    MaxDistance: Double;
    ADist: Double;
  begin
    if DistancePct > 0 then
    begin
      result := CalcPoint(TeePiStep * Angle, ICenter, FXRadius, FYRadius);

      MaxDistance := Min(FXRadius, FYRadius);

      if Axis.Visible then
         MaxDistance := MaxDistance - Axis.TickLength;

      MaxDistance := Round(DistancePct * MaxDistance * 0.01);
      ADist := TeeDistance(ICenter.x - result.x, ICenter.y - result.y);

      if ADist > MaxDistance then
         result := PointAtDistance(ICenter, result, Round(ADist - MaxDistance));
    end
    else
      result := ICenter;
  end;

  procedure DoDrawHand(const AHand:TGaugeHand);
  var
    tmpAngle: Double;
    tmpP: TPoint;
  begin
    tmpAngle:=ValueToAngle(AHand.Value - Minimum);

    tmpP:=CalcPointMaxDistance(tmpAngle, 95);

    if AHand.Visible then
    begin
      AHand.VertSize := Max(1, CalcDistance(100-AHand.Offset));
      AHand.Draw(tmpAngle, ICenter, CalcPointMaxDistance(tmpAngle, AHand.Offset),
                   CalcDistance(AHand.Distance));
    end;

    DrawEnd(tmpP);
  end;

var t : Integer;
begin
  if Hands.Count>0 then
  begin
    Hands[0].Value:=Value;

    for t:=0 to Hands.Count-1 do
        DoDrawHand(Hands[t].FFormat);
  end;

  DrawCenter;
end;

procedure TCircularGauge.DrawCenter;
begin
  if Center.Visible then
  begin
    Center.PrepareCanvas(ParentChart.Canvas, Center.Color);

    Center.Gradient.Angle := 90 + Round(CalcValue(Value));
    Center.Draw(CircleXCenter, CircleYCenter);
  end;
end;

procedure TCircularGauge.DrawEnd(const P: TPoint);
begin
  if EndPoint.Visible then
  begin
    EndPoint.PrepareCanvas(ParentChart.Canvas, EndPoint.Color);
    EndPoint.Draw(P);
  end;
end;

procedure TCircularGauge.Assign(Source: TPersistent);
begin
  if Source is TCircularGauge then
    with TCircularGauge(Source) do
    begin
      Self.Center := Center;
      Self.FCircled := FCircled;
      Self.EndPoint := EndPoint;
      Self.Hands := FHands;
      Self.FLabelsInside := FLabelsInside;
      Self.FRotateLabels := FRotateLabels;
      Self.FRotAngle := FRotAngle;
      Self.FTotalAngle := FTotalAngle;
      Self.FAutoPositionNumericGauge := FAutoPositionNumericGauge;
      Self.FAutoPositionLinearGauge := FAutoPositionLinearGauge;
      Self.FAutoValueNumericGauge := FAutoValueNumericGauge;
      Self.FAutoValueLinearGauge := FAutoValueLinearGauge;

      Self.NumericGauge:=FNumericGauge;
      Self.LinearGauge:=FLinearGauge;
    end;

  inherited;

  TFramedAccess(Frame).SetDefaultCircled(True); // TV52012495
end;

class function TCircularGauge.GetEditorClass: String;
begin
  result := 'TCircularGaugeEditor';
end;

class Procedure TCircularGauge.CreateSubGallery(AddSubChart:TChartSubGalleryProc);
begin
  inherited;
  AddSubChart(TeeMsg_Light);
  AddSubChart(TeeMsg_Flat);
  AddSubChart(TeeMsg_ThemeFacts);
end;

class Procedure TCircularGauge.SetSubGallery(ASeries:TChartSeries; Index:Integer);
var Gauge : TCustomGauge;
begin
 Gauge:=TCustomGauge(ASeries);

 case Index of
   1: begin
        Gauge.Face.Transparent:=True;
      end;

   2: begin
        Gauge.Frame.Hide;
        Gauge.Face.Transparent:=True;
      end;
   3: begin
        Gauge.Face.Transparent:=True;

        with Gauge.Frame.InnerBrush.Gradient do
        begin
          StartColor:=25284;
          MidColor:=clWhite;
          EndColor:=27349;
          Balance:=4;
          Visible:=True;
          Direction:=gdRadial;
        end;

      end;
 else
   inherited;
 end;
end;

Procedure TCircularGauge.DoBeforeDrawValues;
begin
  inherited;
  CalcRadius;
end;

Procedure TCircularGauge.CalcRadius;
begin
  with INewRect{$IFDEF FMX}.Round{$ENDIF} do
  begin
    FXRadius := (Right - Left) div 2;
    FYRadius := (Bottom - Top) div 2;

    ICenter.x := (Right + Left) div 2;
    ICenter.y := (Top + Bottom) div 2;
  end;
end;

procedure TCircularGauge.SetCenter(const Value: TGaugeSeriesPointer);
begin
  FCenter.Assign(Value);
end;

procedure TCircularGauge.SetCircled(const Value: Boolean);
begin
  SetBooleanProperty(FCircled, Value);
  ICircled := Value;
end;

procedure TCircularGauge.SetEndPoint(const Value: TSeriesPointer);
begin
  FEndPoint.Assign(Value);
end;

function TCircularGauge.Axis: TChartAxis;
begin
  result := GetHorizAxis;
end;

procedure TCircularGauge.SetLabelsInside(const Value: Boolean);
begin
  SetBooleanProperty(FLabelsInside, Value);
end;

procedure TCircularGauge.SetLinearGauge(const Value: TLinearGauge);
begin
  if Assigned(Value) then LinearGauge.Assign(Value)
                     else FreeAndNil(FLinearGauge);
end;

procedure TCircularGauge.SetMinimum(const Value: TChartValue);
begin
  inherited;
  Self.Value := Max(Value, Self.Value);
end;

procedure TCircularGauge.SetNumericGauge(const Value: TNumericGauge);
begin
  if Assigned(Value) then NumericGauge.Assign(Value)
                     else FreeAndNil(FNumericGauge);
end;

procedure TCircularGauge.SetRotateLabels(const Value: Boolean);
begin
  SetBooleanProperty(FRotateLabels, Value);
end;

procedure TCircularGauge.SetRotAngle(const Value: Double);
begin
  SetDoubleProperty(FRotAngle, Value);
end;

procedure TCircularGauge.setTotalAngle(const Value: Double);
begin
  SetDoubleProperty(FTotalAngle, Value);

  if FTotalAngle>360 then
     FTotalAngle:=360
  else
  if FTotalAngle<0 then
     FTotalAngle:=0;
end;

procedure TCircularGauge.SetAutoPositionLinearGauge(const Value: Boolean);
begin
  SetBooleanProperty(FAutoPositionLinearGauge, Value);
end;

procedure TCircularGauge.SetAutoPositionNumericGauge(const Value: Boolean);
begin
  SetBooleanProperty(FAutoPositionNumericGauge, Value);
end;

procedure TCircularGauge.SetAutoValueLinearGauge(const Value: Boolean);
begin
  SetBooleanProperty(FAutoValueLinearGauge, Value);
end;

procedure TCircularGauge.SetAutoValueNumericGauge(const Value: Boolean);
begin
  SetBooleanProperty(FAutoValueNumericGauge, Value);
end;

procedure TCircularGauge.SetAxisOnce;
begin
  inherited;
  Axis.Title.Caption:='';
  Axis.Increment:=0;
  Axis.Texts.Font.Color:={$IFDEF FMX}claSilver{$ELSE}clSilver{$ENDIF};

  //Axis.Texts.Font.Size:=12;
end;

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

  if not (csDestroying in ComponentState) then
  begin
    SetHandsParentChart;
    Center.ParentChart:=Value;
    EndPoint.ParentChart:=Value;
  end;
end;

function TCircularGauge.UseAxis: Boolean;
begin
  result := False;
end;

function TCircularGauge.GetDisplayTotalAngle:Integer;
begin
  result:=CircularFrame.TotalAngle;
end;

function TCircularGauge.GetDisplayRotationAngle:Integer;
begin
  result:=CircularFrame.RotationAngle;
end;

function TCircularGauge.GetHand:TGaugeHand;
begin
  if Hands.Count=0 then
     Hands.Add(0);

  result:=FHands[0].FFormat;
end;

type
  TLinearGaugeAccess=class(TLinearGauge);

function TCircularGauge.GetLinearGauge: TLinearGauge;
var
  axisH, axisV: TChartAxis;
begin
  if not Assigned(FLinearGauge) then
  begin
    axisH := TChartAxis.Create(ParentChart);
    axisH.Horizontal := True;
    axisV := TChartAxis.Create(ParentChart);
    axisV.Horizontal := False;

    FLinearGauge := TLinearGauge.Create(ParentChart);
    ParentChart.AddSeries(FLinearGauge);
    FLinearGauge.Embedded := True;

    FLinearGauge.HorizAxis := aCustomHorizAxis;
    FLinearGauge.VertAxis := aCustomVertAxis;
    FLinearGauge.Horizontal := False;
    FLinearGauge.Visible := False;
    FLinearGauge.CustomHorizAxis := axisH;
    FLinearGauge.CustomVertAxis := axisV;

    with TLinearGaugeAccess(FLinearGauge).Axis do
    begin
      Title.Visible := False;
      Texts.Font.Size := 7;
      Texts.Font.Color := GetPaletteColor(16);
    end;

    FLinearGauge.RedLine.Visible := False;
    FLinearGauge.GreenLine.Visible := False;
    FLinearGauge.Frame.Visible := False;
    FLinearGauge.Face.Transparent := True;
    FLinearGauge.MaxValueIndicator.Visible := False;
    FLinearGauge.CustomBounds := CalcLinearGaugeBounds;
  end;

  result := FLinearGauge;
end;

function TCircularGauge.GetNumericGauge: TNumericGauge;
var
  axisH, axisV: TChartAxis;
begin
  if not Assigned(FNumericGauge) then
  begin
    axisH := TChartAxis.Create(ParentChart);
    axisH.Horizontal := True;
    axisV := TChartAxis.Create(ParentChart);
    axisV.Horizontal := False;

    FNumericGauge := TNumericGauge.Create(ParentChart);
    ParentChart.AddSeries(FNumericGauge);
    FNumericGauge.Embedded := True;

    FNumericGauge.HorizAxis := aCustomHorizAxis;
    FNumericGauge.VertAxis := aCustomVertAxis;
    FNumericGauge.Visible := False;
    FNumericGauge.CustomHorizAxis := axisH;
    FNumericGauge.CustomVertAxis := axisV;

    // Fixed memory leak
    {$IFDEF AUTOREFCOUNT}
    FNumericGauge.TextMarker.DisposeOf;
    FNumericGauge.UnitsMarker.DisposeOf;
    {$ELSE}
    FNumericGauge.TextMarker.Free;
    FNumericGauge.UnitsMarker.Free;
    {$ENDIF}

    FNumericGauge.ValueMarker.Shape.Font.Size := 12;
    FNumericGauge.CustomBounds := CalcNumericGaugeBounds;
  end;

  result := FNumericGauge;
end;

function TCircularGauge.CalcLinearGaugeBounds: TRect;
var tmpH,
    tmpWidth : TCoordinate;
begin
  if FAutoPositionLinearGauge then
  begin
    tmpH:=INewRect.Bottom - INewRect.Top;

    if LinearGauge.Horizontal then
    begin
      tmpWidth:=((INewRect.Right - INewRect.Left) {$IFDEF FMX}/{$ELSE}div{$ENDIF} 7);

      result := TeeRect(ICenter.x - tmpWidth,ICenter.y + {$IFNDEF FMX}Round{$ENDIF}(tmpH / 2.90),
                        ICenter.x + tmpWidth,ICenter.y + {$IFNDEF FMX}Round{$ENDIF}(tmpH / 2.15));
    end
    else
    begin
      tmpWidth:=((INewRect.Right - INewRect.Left) {$IFDEF FMX}/{$ELSE}div{$ENDIF} 10);

      result := TeeRect(ICenter.x - tmpWidth,ICenter.y + {$IFNDEF FMX}Round{$ENDIF}(tmpH / 5.0),
                        ICenter.x + tmpWidth,ICenter.y + {$IFNDEF FMX}Round{$ENDIF}(0.5*tmpH));
    end;
  end
  else
    result := FLinearGauge.CustomBounds;
end;

function TCircularGauge.CalcNumericGaugeBounds: TRect;
var tmpH,
    tmpWidth : TCoordinate;
begin
  if FAutoPositionNumericGauge then
  begin
    tmpWidth:=((INewRect.Right - INewRect.Left) {$IFDEF FMX}/{$ELSE}div{$ENDIF} 8);
    tmpH:=INewRect.Bottom - INewRect.Top;

    result := TeeRect(ICenter.x - tmpWidth,
                      ICenter.y + {$IFNDEF FMX}Round{$ENDIF}(tmpH / 2.75),
                      ICenter.x + tmpWidth,
                      ICenter.y + {$IFNDEF FMX}Round{$ENDIF}(tmpH / 2.15));
  end
  else
    result := FNumericGauge.CustomBounds;
end;

procedure TCircularGauge.SetDisplayRotationAngle(const Value: Integer);
begin
  CircularFrame.RotationAngle:=Value;
end;

procedure TCircularGauge.SetDisplayTotalAngle(const Value: Integer);
begin
  CircularFrame.TotalAngle:=Value;
end;

procedure TCircularGauge.DrawMark( ValueIndex:Integer; Const St:String;
                                   APosition:TSeriesMarkPosition);
begin
  APosition.LeftTop.X:=CircleXCenter-(APosition.Width {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF});
  APosition.LeftTop.Y:=CircleYCenter+2*APosition.Height;

  inherited;
end;

function TCircularGauge.CreateFrame:TFramedBorder;
begin
  result:=TCircularFrame.Create;
  result.ParentChart:=ParentChart;
  TCircularFrame(result).SetDefaultCircled(True);

  result.Visible:=True;
  TFramedAccess(result).DefaultVisible:=True;
end;

procedure TCircularGauge.SetFillToSemicircle(const Value: Boolean);
begin
//  CircularFrame.ExtendInner := Value;
  SetBooleanProperty(FFillToSemiCircle, Value);
end;

procedure TCircularGauge.DrawFrame;
begin
  if Frame.Visible then
  begin
    CalcOrigRect;
    Frame.Draw(IOrigRect);
  end;
end;

procedure TCircularGauge.SetHand(const Value: TGaugeHand);
begin
  if Hands.Count>0 then
     Hand.Assign(Value);
end;

type
  TGradientAccess=class(TCustomTeeGradient);
  TTeeShadowAccess=class(TTeeShadow);

{ TGaugeHand }

Constructor TGaugeHand.Create(const AOwner: TChartSeries);
begin
  inherited;

  FDistance := 30;
  FOffset := 80;

  GaugeStyle := gpHand;

  HorizSize := 5;
  VertSize := FDistance;
  Brush.Style := bsSolid;
  Brush.Color := {$IFDEF FMX}claBlack{$ELSE}clBlack{$ENDIF};
  Pen.Visible := False;
  Pen.Width := 1;

  Gradient.Visible:=True;
  TGradientAccess(Gradient).IDefVisible:=True;

  TGradientAccess(Gradient).InitDirection(gdLeftRight);

  TTeeShadowAccess(Shadow).InitValues(True,{$IFDEF FMX}claBlack{$ELSE}clBlack{$ENDIF},5);
end;

procedure TGaugeHand.Assign(Source: TPersistent);
begin
  if Source is TGaugeHand then
  begin
    FDistance:=TGaugeHand(Source).FDistance;
    FOffset:=TGaugeHand(Source).FOffset;
    FValue:=TGaugeHand(Source).FValue;
  end;

  inherited;
end;

procedure TGaugeHand.Draw(const Angle: Double; const Center, EndP: TPoint;
  Off: Integer);

var
  tmpSin,
  tmpCos: {$IFDEF D15}Double{$ELSE}Extended{$ENDIF};

  Function CalcPoint(const tmpP: TPoint; const A: Integer): TPoint;
  begin
    result.x := tmpP.x - Round(A * tmpCos);
    result.y := tmpP.y - Round(A * tmpSin);
  end;

var
  tmpP: TPointArray;

  procedure CalcPoints(xOff, yOff: Integer);
  var
    tmpP0, tmpP1, tmpP2: TPoint;
  begin
    tmpP0 := PointAtDistance(EndP, Center, -Off);

    tmpP0.x := tmpP0.x + xOff;
    tmpP0.y := tmpP0.y + yOff;

    tmpP[0] := CalcPoint(tmpP0, -HorizSize);
    tmpP[1] := CalcPoint(tmpP0, HorizSize);

    if Style=psArrow then
       tmpP:=ParentChart.Canvas.GetArrowPoints(50,tmpP0,EndP,2*HorizSize,VertSize)
    else
    begin
      tmpP2.x := EndP.x + xOff;
      tmpP2.y := EndP.y + yOff;

      if Style = psTriangle then
         tmpP[2] := tmpP2
      else
      begin
        // Default, Needle

        tmpP1 := PointAtDistance(Center, EndP, 12);
        tmpP1.x := tmpP1.x + xOff;
        tmpP1.y := tmpP1.y + yOff;

        tmpP[2] := CalcPoint(tmpP1, HorizSize - 2);
        tmpP[3] := tmpP2;
        tmpP[4] := CalcPoint(tmpP1, -HorizSize + 2);
      end;
    end;
  end;

begin
  SinCos((Angle + 90) * TeePiStep, tmpSin, tmpCos);

  if Style=psTriangle then
     SetLength(tmpP, 3)
  else
     SetLength(tmpP, 5);

  if Shadow.Visible then
  begin
    CalcPoints(Shadow.HorizSize, Shadow.VertSize);
    Shadow.Draw(ParentChart.Canvas, tmpP);
  end;

  PrepareCanvas(ParentChart.Canvas, Color);
  CalcPoints(0, 0);

  ParentChart.Canvas.Polygon(tmpP);

  tmpP:= nil;
end;

procedure TGaugeHand.SetDistance(const Value: Integer);
begin
  if FDistance <> Value then
  begin
    FDistance := Value;
    Repaint;
  end;
end;

procedure TGaugeHand.SetOffset(const Value: Integer);
begin
  if FOffset <> Value then
  begin
    FOffset := Value;
    Repaint;
  end;
end;

procedure TGaugeHand.SetValue(const Value: Double);
begin
  if FValue<>Value then
  begin
    FValue:=Value;
    Repaint;
  end;
end;

{ TClockGauge }

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

  IGalleryStatic:=True; // No animations at gallery

  Face.Visible:=False;
  EndPoint.Hide;

  Center.Gradient.Direction:=gdRadial;
  Center.Size:=8;

  Hand.Gradient.StartColor:={$IFDEF FMX}claNavy{$ELSE}clNavy{$ENDIF};
  Hand.Gradient.EndColor:={$IFDEF FMX}claBlack{$ELSE}clBlack{$ENDIF};

  Hand.Distance:=20;
  Hand.Offset:=60;

  Hands.Add(0).Format.Gradient.StartColor:={$IFDEF FMX}claNavy{$ELSE}clNavy{$ENDIF};
  Hands[1].Format.Gradient.EndColor:={$IFDEF FMX}claBlack{$ELSE}clBlack{$ENDIF};

  Hands.Add(0).Format.Style:=psTriangle;

  Hand.Pen.Hide;
  Hands[1].Format.Pen.Hide;
  Hands[2].Format.Pen.Hide;

  Hands[2].Format.Gradient.StartColor:={$IFDEF FMX}claNavy{$ELSE}clNavy{$ENDIF};
  Hands[2].Format.Gradient.EndColor:={$IFDEF FMX}claBlack{$ELSE}clBlack{$ENDIF};

  Hand.Shadow.Color:={$IFDEF FMX}claSilver{$ELSE}clSilver{$ENDIF};
  Hands[1].Format.Shadow.Color:={$IFDEF FMX}claSilver{$ELSE}clSilver{$ENDIF};
  Hands[2].Format.Shadow.Color:={$IFDEF FMX}claSilver{$ELSE}clSilver{$ENDIF};

  Hand.Shadow.Size:=2;
  Hands[1].Format.Shadow.Size:=2;
  Hands[2].Format.Shadow.Size:=2;

  Hands[2].Format.Size:=2;

  TotalAngle:=360;
  RotationAngle:=210;
  RotateLabels:=False;
  Minimum:=1;
  Maximum:=13;

  Ranges.Clear;

  FSnap:=True;

  ITimer:=TTimer.Create(Self);

  with ITimer do
  begin
    Interval:=1000;
    Enabled:=True;
    OnTimer:=TimerExpired;
  end;

  TimerExpired(Self);
end;

Destructor TClockGauge.Destroy;
begin
  ITimer.Free;
  inherited;
end;

function TClockGauge.GetHours:TGaugeHand;
begin
  result:=Hand;
end;

function TClockGauge.GetMinutes:TGaugeHand;
begin
  result:=Hands[1].Format;
end;

function TClockGauge.GetSeconds:TGaugeHand;
begin
  result:=Hands[2].Format;
end;

function TClockGauge.IsTimeStored: Boolean;
begin
  result:=FCustomTime;
end;

procedure TClockGauge.SetCustomTime(const Value:Boolean);
begin
  SetBooleanProperty(FCustomTime,Value);

  if FCustomTime then
     ITimer.Enabled:=False
  else
  if Visible then
     ITimer.Enabled:=True;
end;

function TClockGauge.GetTime:TDateTime;
begin
  if not FCustomTime then
     FTime:=Now;

  result:=FTime;
end;

procedure TClockGauge.SetParentChart(const Value:TCustomAxisPanel);
Begin
  inherited;

  if Assigned(ParentChart) then
     if not (csLoading in ComponentState) then
     begin
       Axis.MinorTickCount:=4;
       Axis.Increment:=1;
       Axis.Texts.Font.Color:={$IFDEF FMX}claBlack{$ELSE}clBlack{$ENDIF};
     end;
end;

function TClockGauge.GetLabel(const AValue:TChartValue):String;
Const RomanNumber:Array[1..12] of String=
  ('I','II','III','IV','V','VI','VII','VIII','IX','X','XI','XII');
begin
  if FStyle=cgsDecimal then
     result:=inherited GetLabel(AValue)
  else
     result:=RomanNumber[Round(AValue)];
end;

procedure TClockGauge.SetActive(Value:Boolean);
Begin
  inherited;
  ITimer.Enabled:=Visible;
end;

procedure TClockGauge.SetStyle(const Value:TClockGaugeStyle);
begin
  if FStyle<>Value then
  begin
    FStyle:=Value;
    Repaint;
  end;
end;

procedure TClockGauge.SetTime(const Value:TDateTime);
begin
  FCustomTime:=True;
  FTime:=Value;
  Repaint;
end;

Procedure TClockGauge.TimerExpired(Sender:TObject);
var Hour,
    Min,
    Sec,
    Msec : Word;
    tmpMin : Single;
begin
  ITimer.Enabled:=False;

  DecodeTime(Time,Hour,Min,Sec,Msec);

  tmpMin:=(Min+(Sec/60));

  Value:=Hour+(tmpMin/60);

  Hands[0].Value:=Value;
  Hands[1].Value:=tmpMin*12/60;

  if SnapToSeconds then
     Msec:=0;

  Hands[2].Value:=(Sec+(MSec*0.001)) *12/60;

  ITimer.Enabled:=Visible;
end;

procedure TClockGauge.Loaded;
begin
  inherited;
  TimerExpired(Self);
end;

procedure TClockGauge.SetSnap(const Value: Boolean);
begin
  if FSnap<>Value then
  begin
    FSnap:=Value;

    if FSnap then
       ITimer.Interval:=1000
    else
       ITimer.Interval:=50;
  end;
end;

initialization
  RegisterTeeSeries(TCircularGauge, @TeeMsg_CircularGauge,
                                 @TeeMsg_GalleryGauges, 1);

  RegisterTeeSeries(TClockGauge, @TeeMsg_GalleryClock,
                                 @TeeMsg_GalleryGauges, 1);
finalization
  UnRegisterTeeSeries([TCircularGauge]);
end.
