{**********************************************}
{   TeeChart Linear Gauge style                }
{   Copyright (c) 2011-2020 by Steema Software }
{**********************************************}
unit TeeLinearGauge;
{$I TeeDefs.inc}

interface

uses
  {$IFNDEF LINUX}
  {$IFNDEF FMX}
  Windows, Messages,
  {$ENDIF}
  {$ENDIF}
  SysUtils, Classes,
          
  {$IFDEF D17}
  System.UIConsts,
  {$ENDIF}

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

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

  {$ELSE}

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

  Graphics,
  {$ENDIF}

  {$IFDEF D9}
  Types,
  {$ENDIF}

  {$IFDEF FMX}
  FMXTee.Canvas, FMXTee.ProConstants, FMXTee.Procs, FMXTee.Engine, FMXTee.Chart,
  FMXTee.Gauges.Numeric
  {$ELSE}
  TeeNumericGauge, TeCanvas, Chart, TeeProCo, TeeProcs, TeEngine
  {$ENDIF};

type
  TGaugePointerStyles=(gpHand, gpCenter, gpTick, gpMinorTick, gpColorLine);

  TGaugeSeriesPointer=class(TSeriesPointer)
  private
    FGaugeStyle : TGaugePointerStyles;

    procedure SetGaugeStyle(const Value: TGaugePointerStyles);
  protected
    PXMinus,
    PXPlus,
    PYMinus,
    PYPlus      : Integer;

    PXMinusSmall,
    PXPlusSmall,
    PYMinusSmall,
    PYPlusSmall : Integer;

    IStartAngle : Double;

    procedure SetValues(x, y, horiz, vert:Integer);
  public
    procedure Assign(Source:TPersistent); override;
    procedure Draw(const Inner,InnerPlus,InnerMinus,
                   Outer,OuterPlus,OuterMinus:TPoint); overload;
  published
    property GaugeStyle:TGaugePointerStyles read FGaugeStyle write SetGaugeStyle;
  end;

  TGaugePointerRange=class(TGaugeSeriesPointer)
  private
    FEndValue   : Double;
    FPosition   : Integer;
    FSpiralled  : Boolean;
    FStartValue : Double;

    procedure SetEndValue(const Value: Double);
    procedure SetPosition(const Value: Integer);
    procedure SetSpiralled(const Value: Boolean);
    procedure SetStartValue(const Value: Double);
  public
    procedure Assign(Source:TPersistent); override;
    procedure Draw(const StartAngle,EndAngle:Double; const Rectangle:TRect);
  published
    property EndValue:Double read FEndValue write SetEndValue;
    property Position:Integer read FPosition write SetPosition default 50; // %
    property Spiralled:Boolean read FSpiralled write SetSpiralled default True;
    property StartValue:Double read FStartValue write SetStartValue;
  end;

  TGaugeRange=class(TCollectionItem)
  private
    FFormat : TGaugePointerRange;

    function GetEndValue:Double;
    function GetStartValue:Double;
    procedure SetEndValue(const Value: Double);
    procedure SetFormat(const Value:TGaugePointerRange);
    procedure SetStartValue(const Value: Double);
  public
    Constructor Create(Collection: TCollection); override;
    Destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;

    property EndValue:Double read GetEndValue write SetEndValue;
    property StartValue:Double read GetStartValue write SetStartValue;
  published
    property Format:TGaugePointerRange read FFormat write SetFormat;
  end;

  TGaugeRanges=class(TOwnedCollection)
  private
    Function Get(Index:Integer):TGaugeRange; {$IFDEF D10}inline;{$ENDIF}
    Procedure Put(Index:Integer; const Value:TGaugeRange);
  public
    Function Add(const StartValue,EndValue:Double; const Color:TColor={$IFDEF FMX}claBlack{$ELSE}clBlack{$ENDIF}):TGaugeRange;

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

  TCustomGaugeMinMax=class {$IFDEF D11}abstract{$ENDIF} (TCustomGauge)
  private
    FAllowDrag  : Boolean;
    FAxisRanges : TGaugeRanges;
    FHand       : TGaugeSeriesPointer;
    FHorizontal : Boolean;
    FMaximum    : TChartValue;
    FMinimum    : TChartValue;
    FMinorDist  : Integer;
    FMinorTicks : TGaugeSeriesPointer;
    FRanges     : TGaugeRanges;
    FTicks      : TGaugeSeriesPointer;

    IAxisDone   : Boolean;

    function CanSetAxisOnce:Boolean;
    function CreateHand:TGaugeSeriesPointer;
    function GetGreenLine:TGaugePointerRange;
    function GetRedLine:TGaugePointerRange;
    procedure SetAxisRanges(const Value: TGaugeRanges);
    procedure SetGreenLine(const Value: TGaugePointerRange);
    procedure SetHand(const Value: TGaugeSeriesPointer);
    procedure SetHorizontal(const Value: Boolean);
    procedure SetMaximum(const Value: TChartValue);
    procedure SetMinorDist(const Value: Integer);
    procedure SetMinorTicks(const Value: TGaugeSeriesPointer);
    procedure SetRanges(const Value: TGaugeRanges);
    procedure SetRangesParentChart(const ARanges:TGaugeRanges);
    procedure SetRedLine(const Value: TGaugePointerRange);
    procedure SetTicks(const Value: TGaugeSeriesPointer);
  protected
    IRange : Double;

    Procedure AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False); override;
    procedure DrawAllValues; override;
    procedure DrawAxis; virtual; abstract;
    procedure DrawAxisMinorTick(const Inner,Outer:TPoint); virtual;
    procedure DrawAxisTick(const Inner,InnerPlus,InnerMinus,Outer,OuterPlus,OuterMinus:TPoint); virtual;
    procedure DrawColorLine(const Line:TGaugePointerRange); virtual;
    procedure DrawColorLines;
    procedure DrawHand; virtual;
    procedure PrepareForGallery; override;
    procedure SetAxisOnce; virtual;
    procedure SetGaugePalette(const Palette:Array of TColor); override;
    procedure SetMinimum(const Value: TChartValue); virtual;
    Procedure SetParentChart(const Value:TCustomAxisPanel); override;
    procedure SetValues; override;
  public
    Constructor Create(AOwner:TComponent); override;
    Destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;
    function Axis:TChartAxis; virtual; abstract;

    property AllowDrag:Boolean read FAllowDrag write FAllowDrag default True;
    property AxisRanges:TGaugeRanges read FAxisRanges write SetAxisRanges;
    property GreenLine:TGaugePointerRange read GetGreenLine write SetGreenLine;
    property Hand:TGaugeSeriesPointer read FHand write SetHand;
    property Horizontal:Boolean read FHorizontal write SetHorizontal default True;
    property Maximum:TChartValue read FMaximum write SetMaximum;
    property Minimum:TChartValue read FMinimum write SetMinimum;
    property MinorTickDistance:Integer read FMinorDist write SetMinorDist default 0;
    property MinorTicks:TGaugeSeriesPointer read FMinorTicks write SetMinorTicks;
    property Ranges:TGaugeRanges read FRanges write SetRanges;
    property RedLine:TGaugePointerRange read GetRedLine write SetRedLine;
    property Ticks:TGaugeSeriesPointer read FTicks write SetTicks;
  published
    property ShowInLegend default False;
  end;

  TLinearGauge=class(TCustomGaugeMinMax)
  private
    FMaxIndicator    : TGaugeSeriesPointer;
    FUseValuePalette : Boolean;
    FValueArea       : TTeeShape;

    IMaxValue        : TChartValue;

    procedure SetMaxIndicator(const Value: TGaugeSeriesPointer);
    procedure SetUseValuePalette(const Value: Boolean);
    procedure SetValueArea(const Value: TTeeShape);
  protected
    IAxisRect        : TRect;

    class procedure CreateSubGallery(AddSubChart: TChartSubGalleryProc); override;
    class Function GetEditorClass:String; override;
    procedure DrawAllValues; override;
    procedure DrawAxis; override;
    procedure DrawColorLine(const Line:TGaugePointerRange); override;
    procedure DrawHand; override;
    procedure GalleryChanged3D(Is3D: Boolean); override;
    Procedure PrepareForGallery; override;
    Procedure SetParentChart(const Value:TCustomAxisPanel); override;
    procedure SetValue(const AValue: TChartValue); override;
    procedure SetValues; override;
    class procedure SetSubGallery(ASeries: TChartSeries; Index: Integer); override;
  public
    Constructor Create(AOwner:TComponent); override;
    Destructor Destroy; override;

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

    property Embedded:Boolean read IInternalUse write IInternalUse;
  published
    property Active;
    property Cursor;
    property ParentChart;
    property DataSource;  { after parentchart }
    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 Face;
    property Frame;
    property GreenLine stored False;
    property Hand stored False;
    property Horizontal;
    property Maximum;
    property MaxValueIndicator:TGaugeSeriesPointer read FMaxIndicator write SetMaxIndicator;
    property Minimum;
    property MinorTickDistance;
    property MinorTicks;
    property Ranges;
    property RedLine stored False;
    property Ticks;
    property UseValueColorPalette:Boolean read FUseValuePalette
                                         write SetUseValuePalette default False;
    property Value;
    property ValueArea:TTeeShape read FValueArea write SetValueArea;
  end;

implementation

uses
  Math,
  {$IFDEF FMX}
  FMXTee.Constants
  {$ELSE}

  {$IFDEF LCL}
  LCLType, Types,
  {$ENDIF}

  TeeConst
  {$ENDIF};

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
    );

{ TLinearGauge }

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

  FValueArea:=TTeeShape.Create(nil);
  FValueArea.Transparent:=True;

  Value:=Round(Random(100));

  IMaxValue:=100;

  GreenLine.FStartValue:=0;
  GreenLine.FEndValue:=40;

  RedLine.FStartValue:=80;
  RedLine.FEndValue:=100;

  FMaxIndicator:=TGaugeSeriesPointer.Create(Self);

  FMaxIndicator.Style:=psTriangle;

  FMaxIndicator.Brush.Color:={$IFDEF FMX}claRed{$ELSE}clRed{$ENDIF};
  FMaxIndicator.VertSize:=5;
  FMaxIndicator.Draw3D:=False;
end;

Destructor TLinearGauge.Destroy;
begin
  FMaxIndicator.Free;
  FValueArea.Free;
  inherited;
end;

procedure TLinearGauge.Assign(Source: TPersistent);
begin
  if Source is TLinearGauge then
  with TLinearGauge(Source) do
  begin
    Self.FHorizontal:=FHorizontal;
    Self.MaxValueIndicator:=MaxValueIndicator;
    Self.FUseValuePalette:=FUseValuePalette;
    Self.ValueArea:=ValueArea;
  end;

  inherited;
end;

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

procedure TLinearGauge.DrawAllValues;

  function GetPixelValue(const Value:Double):Integer;
  begin
    if IRange=0 then
       result:=0
    else
    begin
      if FHorizontal then
         result:={$IFDEF FMX}Round{$ENDIF}(IAxisRect.Right-IAxisRect.Left)
      else
         result:={$IFDEF FMX}Round{$ENDIF}(IAxisRect.Bottom-IAxisRect.Top);

      result:=Round((Value - Minimum) * ( result / IRange));
    end;
  end;

begin
  if not FHorizontal then
  begin
    FMaxIndicator.Style:=psLeftTriangle;
    Face.Gradient.Direction:=gdRightLeft;
  end;

  inherited;

  if FMaxIndicator.Visible then
  begin
    FMaxIndicator.PrepareCanvas(ParentChart.Canvas,FMaxIndicator.Color);

    if FHorizontal then
       FMaxIndicator.Draw(TeePoint(IAxisRect.Left + GetPixelValue(IMaxValue),IAxisRect.Bottom))
    else
       FMaxIndicator.Draw(TeePoint(IAxisRect.Right,IAxisRect.Bottom - GetPixelValue(IMaxValue)));
  end;
end;

procedure TLinearGauge.DrawHand;
var pixelStep : Single;
    tmp       : Integer;
begin
  if Hand.Visible then
  begin
    if UseValueColorPalette then
    with Hand.Gradient do
    begin
      Visible:=True;

      if FHorizontal then
         Direction:=gdLeftRight
      else
         Direction:=gdBottomTop;
    end
    else
      Hand.Gradient.Visible:=False;

    Hand.PrepareCanvas(ParentChart.Canvas,Hand.Color);

    if FHorizontal then pixelStep:=(IAxisRect.Right-IAxisRect.Left)
                   else pixelStep:=(IAxisRect.Bottom-IAxisRect.Top);

    pixelStep:=(pixelStep / IRange) * (Value-Minimum);

    Hand.Draw3D:=False;

    tmp:=Round(pixelStep*0.5);

    if tmp<>0 then
    begin
      if FHorizontal then
         Hand.HorizSize:=tmp
      else
         Hand.VertSize:=tmp;

      if FHorizontal then
         tmp:={$IFDEF FMX}Round{$ENDIF}(IAxisRect.Bottom-IAxisRect.Top) div 2
      else
         tmp:={$IFDEF FMX}Round{$ENDIF}(IAxisRect.Right-IAxisRect.Left) div 2;

      if tmp<>0 then
      begin
        if FHorizontal then
        begin
          Hand.VertSize:=tmp;
          Hand.Draw({$IFDEF FMX}Round{$ENDIF}(IAxisRect.Left+Hand.HorizSize),
                    {$IFDEF FMX}Round{$ENDIF}(IAxisRect.Top+Hand.VertSize),
                    Hand.Color,psRectangle);
        end
        else
        begin
          Hand.HorizSize:=tmp;
          Hand.Draw({$IFDEF FMX}Round{$ENDIF}(IAxisRect.Left + Hand.HorizSize),
                    {$IFDEF FMX}Round{$ENDIF}(IAxisRect.Bottom-Hand.VertSize),
                    Hand.Color,psRectangle);
        end;
      end;
    end;
  end;
end;

class function TLinearGauge.GetEditorClass: String;
begin
  result:='TLinearGaugeEditor';
end;

procedure TLinearGauge.SetMaxIndicator(const Value: TGaugeSeriesPointer);
begin
  FMaxIndicator.Assign(Value);
end;

procedure TLinearGauge.SetUseValuePalette(const Value: Boolean);
begin
  SetBooleanProperty(FUseValuePalette,Value);
end;

procedure TLinearGauge.SetValue(const AValue: TChartValue);
begin
  if AValue>IMaxValue then
     IMaxValue:=AValue;

  inherited;
end;

procedure TLinearGauge.SetValueArea(const Value: TTeeShape);
begin
  FValueArea.Assign(Value);
end;

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

procedure TLinearGauge.DrawAxis;

  procedure CalcAxisRectangle;
  var tmp,
      tmpW,
      tmpH : TCoordinate;
  begin
    tmpW:=(INewRect.Right-INewRect.Left);
    tmpH:=(INewRect.Bottom-INewRect.Top);

    with IAxisRect do
    begin
      if FHorizontal then
      begin
        tmp:={$IFNDEF FMX}Round{$ENDIF}(tmpW * 0.8);
        Left := (INewRect.Left + (tmpW {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF})) -
                (tmp {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF});
        Right:=Left+tmp;

        tmp:=Ticks.VertSize;
        Top := INewRect.Top + ((tmpH - tmp) {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF});
        Bottom:=Top+tmp;
      end
      else
      begin
        tmp:={$IFNDEF FMX}Round{$ENDIF}(tmpH * 0.8);
        Top := (INewRect.Top + (tmpH {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF})) -
               (tmp {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF});
        Bottom:=Top+tmp;

        tmp:=Ticks.VertSize;
        Left := INewRect.Left + ((tmpW - tmp) {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF});
        Right:=Left+tmp;
      end;
    end;
  end;

  procedure DoInflateRect(const APen:TTeePen; AFactor:Integer);
  var tmpW,
      tmp : TCoordinate;
  begin
    tmpW:=AFactor*APen.Width;
    tmp:=tmpW {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF};

    {$IFDEF FMX}
    IAxisRect.Inflate(tmpW,tmpW);
    IAxisRect.Offset(tmp,tmp);
    {$ELSE}
    InflateRect(IAxisRect,tmpW,tmpW);
    OffsetRect(IAxisRect,tmp,tmp);
    {$ENDIF}
  end;

var tmpValue,
    tmpStep,
    tmpStep2,
    pixelStep,
    pixelStepMin : Double;

    tmpW : Single;

    tmpSize : TCoordinate;

    count,
    tmpTickSize,
    tmpFontH,
    t:Integer;

    P3Minus,
    P3Plus,
    P4Minus,
    P4Plus,
    P3, P4 : TPoint;
    tmpAxis : Boolean;
begin
  if (Axis<>nil) and Axis.Visible then
  begin
    tmpStep:=Axis.Increment;

    CalcAxisRectangle;

    if tmpStep=0 then
    begin
      // Calculate best label increment based on gauge size and font size:

      Axis.SetMinMax(Minimum,Maximum);

      if FHorizontal then
         Axis.IAxisSize:={$IFDEF FMX}Round{$ENDIF}(IAxisRect.Right-IAxisRect.Left)
      else
         Axis.IAxisSize:={$IFDEF FMX}Round{$ENDIF}(IAxisRect.Bottom-IAxisRect.Top);

      TAxisAccess(Axis).AdjustMaxMin;

      TTeePanelAccess(ParentChart).TryAssignFont(Axis.Texts.Font);
      tmpStep:=Axis.CalcIncrement;
    end;

    if tmpStep=0 then
       tmpStep:=IRange * 0.1;

    ParentChart.Canvas.AssignVisiblePen(Axis.Axis);

    tmpAxis:=Axis.Axis.Visible and (Axis.Axis.Width > 0);

    if tmpAxis then
       DoInflateRect(Axis.Axis,1);

    ValueArea.Draw(ParentChart,IAxisRect);

    if tmpAxis then
       DoInflateRect(Axis.Axis,-1);

    if FHorizontal then
       tmpW:=(IAxisRect.Right-IAxisRect.Left)
    else
       tmpW:=(IAxisRect.Bottom-IAxisRect.Top);

    if Axis.Title.Visible and (Axis.Title.Caption<>'') then
      if FHorizontal then
         TAxisAccess(Axis).DrawTitle(Round(IAxisRect.Left+(tmpW*0.5)),
                                     {$IFDEF FMX}Round{$ENDIF}(IAxisRect.Bottom) +
                                     (ParentChart.Canvas.FontHeight div 2))
       else
         TAxisAccess(Axis).DrawTitle({$IFDEF FMX}Round{$ENDIF}(IAxisRect.Right) +
                                     (ParentChart.Canvas.FontHeight + 10),
                                     Round(IAxisRect.Top+(tmpW*0.5)));

    if Axis.Ticks.Visible or Axis.Texts.Visible then
    begin
      if (IRange<>0) and (tmpStep<>0) then
      begin
        Axis.TickLength:=Ticks.VertSize;
        Axis.Ticks.Assign(Ticks.Pen);

        ParentChart.Canvas.AssignVisiblePen(Ticks.Pen);

        if FHorizontal then
           tmpValue := Minimum
        else
           tmpValue := Maximum;

        pixelStep := tmpW / IRange;
        pixelStep :=pixelStep*tmpStep;

        count:=0;

        tmpTickSize:=Round(Ticks.HorizSize * 0.5);

        if FHorizontal then
        begin
          tmpSize:=(IAxisRect.Bottom-IAxisRect.Top);

          TTeePanelAccess(ParentChart).TryAssignFont(Axis.Texts.Font);
          tmpFontH:=ParentChart.Canvas.FontHeight;

          repeat
            P3 := TeePoint(IAxisRect.Left + Round(pixelStep * Count), IAxisRect.Top);
            P4 := TeePoint(P3.X, P3.Y + tmpSize);

            P3Minus := TeePoint(P3.X - tmpTickSize, P3.Y);
            P3Plus := TeePoint(P3.X + tmpTickSize, P3.Y);
            P4Minus := TeePoint(P4.X - tmpTickSize, P4.Y);
            P4Plus := TeePoint(P4.X + tmpTickSize, P4.Y);

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

            if Axis.Texts.Visible then
               Axis.DrawAxisLabel({$IFDEF FMX}Round{$ENDIF}(P3.X),
                     {$IFDEF FMX}Round{$ENDIF}(P3.Y) - tmpFontH, 0,
                     FormatFloat(ValueFormat, tmpValue));

            tmpValue :=tmpValue+tmpStep;

            Inc(count);

          until tmpValue>Maximum
        end
        else
        begin
          tmpSize:=(IAxisRect.Right-IAxisRect.Left);

          repeat
            P3 := TeePoint(IAxisRect.Left, IAxisRect.Top+ Round(pixelStep * Count));
            P4 := TeePoint(P3.x+ tmpSize, P3.y );

            P3Minus := TeePoint(P3.x, P3.y - tmpTickSize);
            P3Plus := TeePoint(P3.x, P3.y + tmpTickSize);
            P4Minus := TeePoint(P4.x, P4.y - tmpTickSize);
            P4Plus := TeePoint(P4.x, P4.y  + tmpTickSize);

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

            if Axis.Texts.Visible then
               Axis.DrawAxisLabel({$IFDEF FMX}Round{$ENDIF}(P3.X)-2,
                     {$IFDEF FMX}Round{$ENDIF}(P3.Y),0,
                     FormatFloat(ValueFormat, tmpValue));

            tmpValue :=tmpValue-tmpStep;

            Inc(count);

          until tmpValue<Minimum;
        end;
      end;

      if (IRange<>0) and MinorTicks.Visible and (Axis.MinorTickCount>0) then
      begin
        if tmpStep<>0 then
        begin
          if FHorizontal then
             Axis.MinorTickLength := MinorTicks.VertSize
          else
             Axis.MinorTickLength := MinorTicks.HorizSize;

          Axis.MinorTicks.Assign(MinorTicks.Pen);
          ParentChart.Canvas.AssignVisiblePen(MinorTicks.Pen);

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

          count := 0;
          pixelStep := tmpW / IRange;
          pixelStepMin := pixelStep * tmpStep2;
          pixelStep :=pixelStep * tmpStep;

          repeat
            for t:=1 to Axis.MinorTickCount do
            begin
              if FHorizontal then
              begin
                P3 := TeePoint(IAxisRect.Left + Round(pixelStep * count) +
                       Round(pixelStepMin * t), IAxisRect.Top + MinorTickDistance);
                P4 := TeePoint(P3.X, P3.Y + (((IAxisRect.Bottom-IAxisRect.Top) -
                       Axis.Axis.Width) {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF} ));
              end
              else
              begin
                P3 := TeePoint(IAxisRect.Left + MinorTickDistance ,
                       IAxisRect.Top + Round(pixelStep * count) +
                       Round(pixelStepMin * t) );
                P4 := TeePoint(P3.X+ (((IAxisRect.Right-IAxisRect.Left) -
                       Axis.Axis.Width) {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF} ), P3.Y );
              end;

              DrawAxisMinorTick(P3, P4);
            end;

            Inc(count);
            tmpValue:=tmpValue+tmpStep;

          until not (tmpValue <= (Maximum - tmpStep));
        end;
      end;
    end;
  end;

  DrawColorLines;
end;

procedure TLinearGauge.DrawColorLine(const Line:TGaugePointerRange);
var pixelStep,
    tmpStart,
    tmpEnd : Double;
    tmpR   : TRect;
begin
  if Line.Visible then
  begin
    if FHorizontal then
       pixelStep := (IAxisRect.Right-IAxisRect.Left) / IRange
    else
       pixelStep := (IAxisRect.Bottom-IAxisRect.Top) / IRange;

    tmpStart := pixelStep * (Line.StartValue - Minimum);
    tmpEnd := pixelStep * (Line.EndValue - Minimum);

    if FHorizontal then
       tmpR:=TeeRect(IAxisRect.Left + Round(tmpStart),
                     IAxisRect.Bottom + Axis.Axis.Width,
                     IAxisRect.Left + Round(tmpEnd),
                     IAxisRect.Bottom + Axis.Axis.Width + Line.VertSize)
    else
       tmpR:=TeeRect(IAxisRect.Right + Axis.Axis.Width,
                     IAxisRect.Bottom - Round(tmpEnd),
                     IAxisRect.Right + Axis.Axis.Width + Line.VertSize,
                     IAxisRect.Bottom -  Round(tmpStart));

    ParentChart.Canvas.AssignVisiblePen(Line.Pen);
    ParentChart.Canvas.AssignBrush(Line.Brush);

    if Line.Gradient.Visible then
       if FHorizontal then
          ParentChart.Canvas.Gradient.Direction:=gdLeftRight
       else
          ParentChart.Canvas.Gradient.Direction:=gdBottomTop;

    ParentChart.Canvas.Rectangle(tmpR);
  end;
end;

function TLinearGauge.Axis:TChartAxis;
begin
  if FHorizontal then
     result:=GetHorizAxis
  else
     result:=GetVertAxis;
end;

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

  if not (csDestroying in ComponentState) then
  begin
    MaxValueIndicator.ParentChart:=ParentChart;
    ValueArea.ParentChart:=ParentChart;
  end;
end;

procedure TLinearGauge.SetValues;
begin
  CalcOrigRect;
  INewRect:=IOrigRect;
  inherited;
end;

procedure TLinearGauge.PrepareForGallery;
begin
  inherited;
  Axis.Title.Hide;
end;

// Force 2D for Linear Gauges
procedure TLinearGauge.GalleryChanged3D(Is3D: Boolean);
begin
  ParentChart.View3D:=False;
end;

class procedure TLinearGauge.CreateSubGallery(AddSubChart: TChartSubGalleryProc);
begin
  inherited;
  AddSubChart(TeeMsg_Vertical);
end;

class procedure TLinearGauge.SetSubGallery(ASeries: TChartSeries;
  Index: Integer);
begin
  inherited;
  TLinearGauge(ASeries).Horizontal:=Index=0;
end;

{ TGaugeRange }

type
  TGaugeRangesAccess=class(TGaugeRanges);

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

  FFormat:=TGaugePointerRange.Create(TGaugeRangesAccess(Collection).GetOwner as TChartSeries);
  FFormat.FPosition:=50;
  FFormat.FSpiralled:=True;
end;

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

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

function TGaugeRange.GetEndValue:Double;
begin
  result:=FFormat.EndValue;
end;

function TGaugeRange.GetStartValue:Double;
begin
  result:=FFormat.StartValue;
end;

procedure TGaugeRange.SetEndValue(const Value: Double);
begin
  FFormat.EndValue:=Value;
end;

procedure TGaugeRange.SetFormat(const Value:TGaugePointerRange);
begin
  FFormat.Assign(Value);
end;

procedure TGaugeRange.SetStartValue(const Value: Double);
begin
  FFormat.StartValue:=Value;
end;

{ TGaugeRanges }

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

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

Function TGaugeRanges.Add(const StartValue,EndValue:Double; const Color:TColor={$IFDEF FMX}claBlack{$ELSE}clBlack{$ENDIF}):TGaugeRange;
begin
  result:=TGaugeRange(inherited Add);

  if Count>1 then
     result.Format.Assign(Get(0).Format);

  result.StartValue:=StartValue;
  result.EndValue:=EndValue;
  result.Format.Color:=Color;
  result.Format.Gradient.StartColor:=Color;
end;

{ TCustomGaugeMinMax }

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

  FAllowDrag:=True;

  FAxisRanges:=TGaugeRanges.Create(Self,TGaugeRange);
  FRanges:=TGaugeRanges.Create(Self,TGaugeRange);

  FMinorTicks:=TGaugeSeriesPointer.Create(Self);
  with FMinorTicks do
  begin
    GaugeStyle:=gpMinorTick;
    Color:=clNone;
    VertSize:=1;
    HorizSize:=1;
  end;

  FTicks:=TGaugeSeriesPointer.Create(Self);
  with FTicks do
  begin
    GaugeStyle:=gpTick;
    Brush.Style:=bsClear;
    VertSize:=10;
  end;

  FHand:=CreateHand;

  SetGaugePalette(BlackPalette);

  CalcVisiblePoints:=False;
  ShowInLegend:=False;
  FHorizontal:=True;

  FMaximum:=100;
end;

Destructor TCustomGaugeMinMax.Destroy;
begin
  FHand.Free;
  FRanges.Free;
  FAxisRanges.Free;
  FMinorTicks.Free;
  FTicks.Free;

  if Axis<>nil then
  with Axis do
  begin
    Title.Visible:=False;
    Title.Caption:='';
  end;

  inherited;
end;

procedure TCustomGaugeMinMax.SetGaugePalette(const Palette:Array of TColor);

  procedure SetLineProperties(const Line:TGaugePointerRange; Color1,Color2,Color3:Integer);
  begin
    with Line do
    begin
      GaugeStyle:=gpColorLine;
      VertSize:=5;

      Brush.Style:=bsSolid;
      Brush.Color:=GetPaletteColor(Color3);

      with Gradient do
      begin
        Visible:=True;

        if FHorizontal then
           Direction:=gdLeftRight
        else
           Direction:=gdBottomTop;

        StartColor:=GetPaletteColor(Color1);
        MidColor:=GetPaletteColor(Color2);
        EndColor:=GetPaletteColor(Color3);
      end;
    end;
  end;

begin
  inherited;

  SetLineProperties(GreenLine,9,10,11);
  SetLineProperties(RedLine,6,7,8);
  FMinorTicks.Pen.Color:=GetPaletteColor(23);
  FTicks.Pen.Color:=GetPaletteColor(22);
  FHand.Pen.Color:=GetPaletteColor(12);
end;

function TCustomGaugeMinMax.CreateHand:TGaugeSeriesPointer;
begin
  result:=TGaugeSeriesPointer.Create(Self);

  with result do
  begin
    FGaugeStyle:=gpHand;
    Brush.Color:={$IFDEF FMX}claRed{$ELSE}clRed{$ENDIF};
    Transparency:=70;
  end;
end;

procedure TCustomGaugeMinMax.Assign(Source: TPersistent);
begin
  if Source is TCustomGaugeMinMax then
  with TCustomGaugeMinMax(Source) do
  begin
    Self.FAllowDrag :=FAllowDrag;
    Self.AxisRanges :=FAxisRanges;
    Self.Hand       :=FHand;
    Self.FMaximum   :=FMaximum;
    Self.FMinimum   :=FMinimum;
    Self.FMinorDist :=FMinorDist;
    Self.Ranges     :=FRanges;
    Self.MinorTicks :=FMinorTicks;
    Self.Ticks      :=Ticks;
  end;

  inherited;
end;

procedure TCustomGaugeMinMax.DrawColorLine(const Line:TGaugePointerRange);
begin
end;

procedure TCustomGaugeMinMax.DrawColorLines;
var t : Integer;
begin
  for t:=0 to FRanges.Count-1 do
      DrawColorLine(FRanges[t].FFormat);
end;

procedure TCustomGaugeMinMax.DrawHand;
begin
end;

function TCustomGaugeMinMax.CanSetAxisOnce:Boolean;
begin
  result:=Assigned(ParentChart) and (not IAxisDone) and
         (not (csReading in ComponentState)) and (not (csLoading in ComponentState));
end;

Procedure TCustomGaugeMinMax.SetParentChart(const Value:TCustomAxisPanel);
begin
  inherited;

  if not (csDestroying in ComponentState) then
  begin
    SetRangesParentChart(FAxisRanges);
    SetRangesParentChart(FRanges);

    Hand.ParentChart:=ParentChart;
    MinorTicks.ParentChart:=ParentChart;
    Ticks.ParentChart:=ParentChart;

    if CanSetAxisOnce then
       SetAxisOnce;
  end;
end;

procedure TCustomGaugeMinMax.SetAxisOnce;
begin
  with Axis do
  begin
    Axis.Visible:=True;
    Title.Visible:=True;

    // Removed: Title.Caption:=TeeMsg_AxisTitle;

    Title.Font.Color:=GetPaletteColor(21);

    Texts.Visible:=True;
    Texts.Font.Color:=GetPaletteColor(20);
    Texts.Font.Name:=TeeMsg_DefaultFontName;

    //Texts.Font.Size:=10;

    Texts.Font.Style:=[{$IFDEF FMX}TFontStyle.{$ENDIF}fsBold];
  end;

  IAxisDone:=True; // TV52012914
end;

function TCustomGaugeMinMax.GetGreenLine:TGaugePointerRange;
begin
  if FRanges.Count=0 then
     result:=FRanges.Add(0,0,{$IFDEF FMX}TAlphaColors.Green{$ELSE}clGreen{$ENDIF}).FFormat
  else
     result:=FRanges[0].FFormat;
end;

procedure TCustomGaugeMinMax.SetGreenLine(const Value: TGaugePointerRange);
begin
  GreenLine.Assign(Value);
end;

procedure TCustomGaugeMinMax.SetHand(const Value: TGaugeSeriesPointer);
begin
  FHand.Assign(Value);
end;

procedure TCustomGaugeMinMax.SetMaximum(const Value: TChartValue);
begin
  if FMaximum<>Value then
  begin
    FMaximum:=Value;
    Repaint;
  end;
end;

procedure TCustomGaugeMinMax.SetMinimum(const Value: TChartValue);
begin
  if FMinimum<>Value then
  begin
    FMinimum:=Value;
    Repaint;
  end;
end;

procedure TCustomGaugeMinMax.SetMinorDist(const Value: Integer);
begin
  SetIntegerProperty(FMinorDist,Value);
end;

procedure TCustomGaugeMinMax.SetMinorTicks(const Value: TGaugeSeriesPointer);
begin
  FMinorTicks.Assign(Value);
end;

function TCustomGaugeMinMax.GetRedLine:TGaugePointerRange;
begin
  if FRanges.Count>1 then
     result:=FRanges[1].FFormat
  else
  begin
    GetGreenLine; // Force creating Green, if it does not exist yet.

    result:=FRanges.Add(0,0,{$IFDEF FMX}claRed{$ELSE}clRed{$ENDIF}).FFormat;
  end;
end;

procedure TCustomGaugeMinMax.SetRangesParentChart(const ARanges:TGaugeRanges);
var t : Integer;
begin
  for t:=0 to aRanges.Count-1 do
      ARanges[t].FFormat.ParentChart:=ParentChart;
end;

procedure TCustomGaugeMinMax.SetAxisRanges(const Value: TGaugeRanges);
begin
  FAxisRanges.Assign(Value);
  SetRangesParentChart(FAxisRanges);
end;

procedure TCustomGaugeMinMax.SetRanges(const Value: TGaugeRanges);
begin
  FRanges.Assign(Value);
  SetRangesParentChart(FRanges);
end;

procedure TCustomGaugeMinMax.SetRedLine(const Value: TGaugePointerRange);
begin
  RedLine.Assign(Value);
end;

procedure TCustomGaugeMinMax.SetTicks(const Value: TGaugeSeriesPointer);
begin
  FTicks.Assign(Value);
end;

procedure TCustomGaugeMinMax.DrawAllValues;
begin
  inherited;
  DrawAxis;
  DrawHand;
end;

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

procedure TCustomGaugeMinMax.DrawAxisMinorTick(const Inner,Outer:TPoint);
var tmp : Integer;
begin
  tmp:=MinorTicks.HorizSize div 2;

  MinorTicks.Draw(Inner, TeePoint(Inner.X + tmp, Inner.Y),
                  TeePoint(Inner.X - tmp, Inner.Y), Outer,
                  TeePoint(Inner.X + tmp, Inner.Y + MinorTicks.VertSize),
                  TeePoint(Inner.X - tmp, Inner.Y + MinorTicks.VertSize));
end;

procedure TCustomGaugeMinMax.SetValues;
begin
  inherited;
  IRange:=(Maximum - Minimum);
end;

procedure TCustomGaugeMinMax.AddSampleValues(NumValues: Integer;
  OnlyMandatory: Boolean);
begin
  Value:=Minimum+Random(Round(Maximum-Minimum));
end;

procedure TCustomGaugeMinMax.SetHorizontal(const Value: Boolean);
begin
  if FHorizontal<>Value then
  begin
    Axis.Title.Caption:='';
    SetBooleanProperty(FHorizontal,Value);
    SetAxisOnce; // force reset axis colors, fonts, etc.
  end;
end;

procedure TCustomGaugeMinMax.PrepareForGallery;
begin
  inherited;
  Axis.Texts.Font.Size:=6;
  Value:=Random(100);
end;

{ TGaugePointerRange }

procedure TGaugePointerRange.Assign(Source: TPersistent);
begin
  if Source is TGaugePointerRange then
  with TGaugePointerRange(Source) do
  begin
    Self.FEndValue:=FEndValue;
    Self.FPosition:=FPosition;
    Self.FSpiralled:=FSpiralled;
    Self.FStartValue:=FStartValue;
  end;

  inherited;
end;

procedure TGaugePointerRange.Draw(const StartAngle,EndAngle:Double;
                                  const Rectangle:TRect);

var
  tmpSin,
  tmpCos : {$IFDEF D15}Double{$ELSE}Extended{$ENDIF};
  count  : Integer;
  circle : TPointFloatArray;
  tmpX   : Integer;
  tmpY   : Integer;

  procedure AddPoint(const Radius:Double);
  begin
    circle[count].X:=tmpX+(Radius * tmpCos);
    circle[count].Y:=tmpY+(Radius * tmpSin);
    Inc(count);
  end;

var tmp,
    sweepAngle    : Integer;
    tmpStartAngle : Double;
    tmpEndAngle   : Double;
    i             : Integer;
    containerMin  : TCoordinate;
    a,b,
    theta         : Double;
begin
  if Visible then
  begin
    PrepareCanvas(ParentChart.Canvas, Color);

    sweepAngle:=Abs(Round(EndAngle-StartAngle));

    tmpStartAngle:=StartAngle+90;
    tmpEndAngle:=tmpStartAngle+sweepAngle;

    tmp:=Round(tmpEndAngle)-Round(tmpStartAngle);

    if tmp<2 then
       Exit;

    SetLength(circle,2*tmp);

    containerMin:=Min((Rectangle.Right-Rectangle.Left),(Rectangle.Bottom-Rectangle.Top));

    RectCenter(Rectangle,tmpX,tmpY);

    count:=0;

    //CDI this is a circle
    for i:=Round(tmpStartAngle) to Round(tmpEndAngle)-1 do
    begin
      SinCos(i*TeePiStep,tmpSin,tmpCos);
      AddPoint(containerMin*0.5);
    end;

    a:= containerMin*0.5;
    b:= -VertSize*0.01;

    //CDI and this is a logarithmic spiral drawn backwards so last/first
    //points of graphicspath draw ok.
    for i:=Round(tmpEndAngle)-1 downto Round(tmpStartAngle) do
    begin
      theta:= i * TeePiStep;
      SinCos(theta,tmpSin,tmpCos);

      if Spiralled then AddPoint(a * Exp(theta * b))
                   else AddPoint(a-VertSize);
    end;

    Gradient.Angle:= Round(StartAngle - 90);
    ParentChart.Canvas.Gradient.Angle:=Gradient.Angle;

    {$IFDEF FMX}
    ParentChart.Canvas.Polygon(circle);
    {$ELSE}
    ParentChart.Canvas.PolygonFloat(circle);
    {$ENDIF}

    // Release memory
    circle:=nil;
  end;
end;

procedure TGaugePointerRange.SetEndValue(const Value: Double);
begin
  if FEndValue<>Value then
  begin
    FEndValue:=Value;
    Repaint;
  end;
end;

procedure TGaugePointerRange.SetPosition(const Value: Integer);
begin
  if FPosition<>Value then
  begin
    FPosition:=Value;
    Repaint;
  end;
end;

procedure TGaugePointerRange.SetStartValue(const Value: Double);
begin
  if FStartValue<>Value then
  begin
    FStartValue:=Value;
    Repaint;
  end;
end;

procedure TGaugePointerRange.SetSpiralled(const Value: Boolean);
begin
  if FSpiralled<>Value then
  begin
    FSpiralled:=Value;
    Repaint;
  end;
end;

{ TGaugeSeriesPointer }
procedure TGaugeSeriesPointer.SetValues(x, y, horiz, vert:Integer);
begin
  IStartAngle := 90 + (ArcTan2(Shadow.VertSize,Shadow.HorizSize) * HalfDivPi);
  PXMinus := x - horiz;
  PXPlus := x + horiz;
  PYMinus := y - vert;
  PYPlus := y + vert;

  PXMinusSmall := PXMinus + 2;
  PXPlusSmall := PXPlus - 2;

  PYMinusSmall := PYMinus + (horiz * 2);
  PYPlusSmall := PYPlus - (horiz * 2);
end;

procedure TGaugeSeriesPointer.Draw(const Inner, InnerPlus, InnerMinus, Outer, OuterPlus, OuterMinus:TPoint);
begin
  if Visible then
  begin
    if GaugeStyle=gpTick then
    begin
      PrepareCanvas(ParentChart.Canvas,Color);
      ParentChart.Canvas.Polygon([OuterPlus, OuterMinus, InnerMinus, InnerPlus]);
    end
    else
    begin
      PrepareCanvas(ParentChart.Canvas,Color);

      if GaugeStyle=gpMinorTick then
         Draw({$IFDEF FMX}Round{$ENDIF}(Outer.X),
              {$IFDEF FMX}Round{$ENDIF}(Outer.Y), Color, psCircle)
      else
         Draw({$IFDEF FMX}Round{$ENDIF}(Outer.X),
              {$IFDEF FMX}Round{$ENDIF}(Outer.Y), Color, Style);
    end;
  end;
end;

procedure TGaugeSeriesPointer.SetGaugeStyle(const Value: TGaugePointerStyles);
begin
  if FGaugeStyle<>Value then
  begin
    FGaugeStyle:=Value;
    Repaint;
  end;
end;

procedure TGaugeSeriesPointer.Assign(Source: TPersistent);
begin
  if Source is TGaugeSeriesPointer then
     FGaugeStyle:=TGaugeSeriesPointer(Source).FGaugeStyle;

  inherited;
end;

initialization
  RegisterTeeSeries(TLinearGauge, @TeeMsg_LinearGauge,
                                  @TeeMsg_GalleryGauges,1);

  RegisterTeeSeriesFunction(TLinearGauge,nil, @TeeMsg_LinearGaugeVertical,
                                 @TeeMsg_GalleryGauges,1,1);
finalization
  UnRegisterTeeSeries([TLinearGauge]);
end.

