{********************************************************************}
{                                                                    }
{ written by TMS Software                                            }
{            copyright © 2013                                        }
{            Email : info@tmssoftware.com                            }
{            Web : http://www.tmssoftware.com                        }
{                                                                    }
{ The source code is given as is. The author is not responsible      }
{ for any possible damage done due to the use of this code.          }
{ The complete source code remains property of the author and may    }
{ not be distributed, published, given or sold in any form as such.  }
{ No parts of the source code can be included in any other component }
{ or application without written authorization of the author.        }
{********************************************************************}

unit FMX.TMSScope;

interface

{$I TMSDEFS.INC}

uses
  FMX.Types, FMX.Objects, Classes, Types,
  SysUtils, UITypes, Math, FMX.TMSBasecontrol, UIConsts, FMX.Styles
  {$IFDEF DELPHIXE5_LVL}
  , FMX.Graphics
  {$ENDIF}
  ;

const
  MAJ_VER = 1; // Major version nr.
  MIN_VER = 5; // Minor version nr.
  REL_VER = 0; // Release nr.
  BLD_VER = 1; // Build nr.

  // version history
  // v1.0.0.0 : first release
  // v1.0.4.2 : Reorganized packages
  // v1.0.5.0 : New : support for update 4
  // v1.0.5.1 : Improved : TMSIOS define
  // v1.0.5.2 : New : support for update 4 hotfix 1
  // v1.0.5.3 : Improved : Data reset when toggling active
  // v1.5.0.0 : New : support for XE4
  // v1.5.0.1 : Fixed : Issue with adding data without animation
  //          : Improved : clearing grid offset

type
  TTMSFMXScope = class;

  TTMSFMXGraphStyle = (gsLine, gsBar);

  TTMSFMXScrollDirection = (sdRightToLeft, sdLeftToRight);

  TTMSFMXDrawDivisionLine = procedure(Sender: TObject; ADivision: Integer; AValue: Single; var LineColor: TAlphaColor) of object;
  TTMSFMXDrawDivisionText = procedure(Sender: TObject; ADivision: Integer; AValue: Single; ATextFont: TFont; var ATextColor: TAlphaColor;var AText: String) of object;

  TTMSFMXDrawSubDivisionLine = procedure(Sender: TObject; ADivision, ASubDivision: Integer; AValue: Single; var LineColor: TAlphaColor) of object;
  TTMSFMXDrawSubDivisionText = procedure(Sender: TObject; ADivision, ASubDivision: Integer; AValue: Single; ATextFont: Tfont; var ATextColor: TAlphaColor; var AText: String) of object;

  TSerieArray = array of Single;

  TTMSFMXChannel = class(TCollectionItem)
  private
    FOwner: TTMSFMXScope;
    FColor: TAlphaColor;
    FWidth: Integer;
    FStyle: TTMSFMXGraphStyle;
    FSerie: TSerieArray;
    FVisible: Boolean;
    FValueFormat: String;
    FShowValue: Boolean;
    FFont: TFont;
    procedure SetColor(Value: TAlphaColor);
    procedure SetWidth(Value: Integer);
    procedure SetStyle(Value: TTMSFMXGraphStyle);
    procedure SetVisible(const Value: boolean);
    procedure SetShowValue(const Value: Boolean);
    procedure SetValueFormat(const Value: String);
    procedure SetFont(const Value: TFont);
  protected
    procedure AddValue(Value: Single);
    property Serie: TSerieArray read FSerie write FSerie;
  public
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
  published
    property Color: TAlphaColor read FColor write SetColor default claYellow;
    property Width: Integer read FWidth write SetWidth default 1;
    property Style: TTMSFMXGraphStyle read FStyle write SetStyle default gsLine;
    property Visible : Boolean read Fvisible write SetVisible default true;
    property ShowValue: Boolean read FShowValue write SetShowValue default false;
    property ValueFormat: String read FValueFormat write SetValueFormat;
    property Font: TFont read FFont write SetFont;
  end;

  TTMSFMXChannels = class(TTMSFMXBaseCollection)
  private
    FScope: TTMSFMXScope;
    function GetItem(Index: Integer): TTMSFMXChannel;
    procedure SetItem(Index: Integer; Value: TTMSFMXChannel);
  protected
    function GetOwner: TPersistent; override;
    procedure Update(Item: TCollectionItem); override;
  public
    constructor Create(Scope2: TTMSFMXScope);
    function Add: TTMSFMXChannel;
    property Items[Index: Integer]: TTMSFMXChannel read GetItem write SetItem; default;
  end;

  TTMSFMXNeedDataEvent = procedure(Sender: TObject;
    Channel: Integer; var Value: Single) of object;

  TTMSFMXYAxisPosition = (ypLeft, ypRight);

  TTMSFMXFormatType = (ftFloat, ftNormal);


  TTMSFMXYAxis = class(TPersistent)
  private
    FOwner: TTMSFMXScope;
    FFont: TFont;
    FVisible: Boolean;
    FPosition: TTMSFMXYAxisPosition;
    FFormatType: TTMSFMXFormatType;
    FFormat: string;
    FSize: Single;
    FDivisions: Integer;
    FSubDivisions: Integer;
    FShowSubDivisions: Boolean;
    FShowSubDivisionText: Boolean;
    FShowDivisions: Boolean;
    FShowDivisionText: Boolean;
    FSubDivisionSize: Single;
    FDivisionSize: Single;
    FSubDivisionColor: TAlphaColor;
    FSubDivisionFont: TFont;
    FDivisionColor: TAlphaColor;
    FDivisionFont: TFont;
    FSubDivisionTextColor: TAlphaColor;
    FDivisionTextColor: TAlphaColor;
    procedure SetDivisionFont(const Value: TFont);
    procedure SetPosition(const Value: TTMSFMXYAxisPosition);
    procedure SetVisible(const Value: Boolean);
    procedure SetFormat(const Value: string);
    procedure SetFormatType(const Value: TTMSFMXFormatType);
    procedure SetSize(const Value: Single);
    procedure SetDivisions(const Value: Integer);
    procedure SetShowDivisions(const Value: Boolean);
    procedure SetShowDivisionText(const Value: Boolean);
    procedure SetShowSubDivisions(const Value: Boolean);
    procedure SetShowSubDivisionText(const Value: Boolean);
    procedure SetSubDivisions(const Value: Integer);
    procedure SetDivisionColor(const Value: TAlphaColor);
    procedure SetDivisionSize(const Value: Single);
    procedure SetSubDivisionColor(const Value: TAlphaColor);
    procedure SetSubDivisionFont(const Value: TFont);
    procedure SetSubDivisionSize(const Value: Single);
    procedure SetDivisionTextColor(const Value: TAlphaColor);
    procedure SetSubDivisionTextColor(const Value: TAlphaColor);
  protected
  public
    constructor Create(AOwner: TTMSFMXScope);
    destructor Destroy; override;
  published
    property DivisionColor: TAlphaColor read FDivisionColor write SetDivisionColor default claLime;    
    property SubDivisionColor: TAlphaColor read FSubDivisionColor write SetSubDivisionColor default claGreen;
    property DivisionSize: Single read FDivisionSize write SetDivisionSize;
    property SubDivisionSize: Single read FSubDivisionSize write SetSubDivisionSize;    
    property Visible: Boolean read FVisible write SetVisible default True;
    property Position: TTMSFMXYAxisPosition read FPosition write SetPosition default ypLeft;
    property DivisionFont: TFont read FDivisionFont write SetDivisionFont;
    property SubDivisionFont: TFont read FSubDivisionFont write SetSubDivisionFont;
    property DivisionTextColor: TAlphaColor read FDivisionTextColor write SetDivisionTextColor default claLime;
    property SubDivisionTextColor: TAlphaColor read FSubDivisionTextColor write SetSubDivisionTextColor default claGreen;
    property Format: string read FFormat write SetFormat;
    property FormatType: TTMSFMXFormatType read FFormatType write SetFormatType default ftNormal;
    property Size: Single read FSize write SetSize;
    property Divisions: Integer read FDivisions write SetDivisions default 5;
    property SubDivisions: Integer Read FSubDivisions write SetSubDivisions default 3;
    property ShowDivisions: Boolean read FShowDivisions write SetShowDivisions default True;
    property ShowSubDivisions: Boolean read FShowSubDivisions write SetShowSubDivisions default True;
    property ShowDivisionText: Boolean read FShowDivisionText write SetShowDivisionText default True;
    property ShowSubDivisionText: Boolean read FShowSubDivisionText write SetShowSubDivisionText default False;
  end;

  [ComponentPlatformsAttribute(TMSPlatforms)]
  TTMSFMXScope = class(TTMSFMXBaseControl)
  private
    FMinValue: Single;
    FMaxValue: Single;
    FActive: Boolean;
    FAnimateGrid: Boolean;
    FGridOffset, FGridExtraOffset: Single;
    FBaseColor: TAlphaColor;
    FTimer: TTimer;
    FChannels: TTMSFMXChannels;
    FGridLineWidth: Integer;
    FGridColor: TAlphaColor;
    FFrequency: Single;
    FOnNeedData: TTMSFMXNeedDataEvent;
    FDirection: TTMSFMXScrollDirection;
    FAutoUpdate: Boolean;
    FYAxis: TTMSFMXYAxis;
    FOnDrawSubDivisionLine: TTMSFMXDrawSubDivisionLine;
    FOnDrawSubDivisionText: TTMSFMXDrawSubDivisionText;
    FOnDrawDivisionLine: TTMSFMXDrawDivisionLine;
    FOnDrawDivisionText: TTMSFMXDrawDivisionText;
    function GetInterval: Integer;
    procedure SetInterval(Value: Integer);
    procedure SetActive(Value: Boolean);
    procedure SetMaxValue(Value: Single);
    procedure SetMinValue(Value: Single);
    procedure SetChannels(Value: TTMSFMXChannels);
    procedure SetGridLineWidth(Value: Integer);
    procedure SetGridColor(Value: TAlphaColor);
    procedure SetBaseColor(Value: TAlphaColor);
    procedure SetFrequency(Value: Single);
    procedure TimerEvent(Sender: TObject);
    procedure setDirection(const Value: TTMSFMXScrollDirection);
    procedure SetAutoUpdate(const Value: Boolean);
    procedure SetYAxis(const Value: TTMSFMXYAxis);
    function GetVersion: String;
    function GetVersionNr: Integer;
  protected
    function GetInstance: NativeUInt; override;
    procedure DrawGrid(ACanvas: TCanvas);
    procedure DrawGraph(ACanvas: TCanvas);
    procedure DrawAxis(ACanvas: TCanvas);
    function GetScopeRect: TRectF;
    function GetGridSize: Single;
    function GetBaseInt: Single;
    function GetMaxLength: Integer;
  public
    procedure UpdateControlCanvas;
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Clear;
    procedure UpdateData(Channel: Integer; Value: Single);
    procedure AddData;
    procedure ResetGrid;
    procedure Paint; override;
    function GetPlotValue(AValue: Single): Single;
    property GridOffset: Single read FGridOffset write FGridOffset;
    property GridExtraOffset: Single read FGridExtraOffset write FGridExtraOffset;
  published
    property Version: String read GetVersion;
    property YAxis: TTMSFMXYAxis read FYAxis write SetYAxis;
    property AutoUpdate: Boolean read FAutoUpdate write SetAutoUpdate default True;
    property Interval: Integer read GetInterval write SetInterval;
    property AnimateGrid: Boolean read FAnimateGrid write FAnimateGrid default True;
    property MaxValue: Single read FMaxValue write SetMaxValue;
    property MinValue: Single read FMinValue write SetMinValue;
    property Channels: TTMSFMXChannels read FChannels write SetChannels;
    property Direction:TTMSFMXScrollDirection read FDirection write setDirection default sdRightToLeft;
    property GridLineWidth: Integer read FGridLineWidth write SetGridLineWidth default 1;
    property GridColor: TAlphaColor read FGridColor write SetGridColor default claGreen;
    property BaseColor: TAlphaColor read FBaseColor write SetBaseColor default claLime;
    property Frequency: Single read FFrequency write SetFrequency;
    property Active: Boolean read FActive write SetActive default false;
    property OnNeedData: TTMSFMXNeedDataEvent read FOnNeedData write FOnNeedData;
    property OnDrawDivisionLine: TTMSFMXDrawDivisionLine read FOnDrawDivisionLine write FOnDrawDivisionLine;
    property OnDrawSubDivisionLine: TTMSFMXDrawSubDivisionLine read FOnDrawSubDivisionLine write FOnDrawSubDivisionLine;
    property OnDrawDivisionText: TTMSFMXDrawDivisionText read FOnDrawDivisionText write FOnDrawDivisionText;
    property OnDrawSubDivisionText: TTMSFMXDrawSubDivisionText read FOnDrawSubDivisionText write FOnDrawSubDivisionText;

    property Fill;

    property Align;
    property Anchors;
    property DragMode;
    property Hint;
    property PopupMenu;
    property ShowHint;
    property Visible;
    property OnClick;
    property OnDblClick;
    property OnDragDrop;
    property OnDragOver;
    property OnMouseDown;
    property OnMouseMove;
    property OnMouseUp;
  end;


implementation

{ TTMSFMXChannel }

constructor TTMSFMXChannel.Create(Collection: TCollection);
begin
  inherited Create(Collection);
  FOwner := (collection as TTMSFMXChannels).FScope;
  FColor := claYellow;
  FWidth := 1;
  FStyle := gsLine;
  FVisible := true;
  FFont := TFont.Create;
end;

destructor TTMSFMXChannel.Destroy;
begin
  FFont.Free;
  inherited;
end;

procedure TTMSFMXChannel.SetColor(Value: TAlphaColor);
begin
  if FColor <> Value then
  begin
    FColor := Value;
    FOwner.Repaint;
  end;
end;

procedure TTMSFMXChannel.SetFont(const Value: TFont);
begin
  if FFont <> Value then
  begin
    FFont.Assign(Value);
    FOwner.Repaint;
  end;
end;

procedure TTMSFMXChannel.SetWidth(Value: Integer);
begin
  if FWidth <> Value then
  begin
    FWidth := Value;
    FOwner.Repaint;
  end;
end;

procedure TTMSFMXChannel.SetShowValue(const Value: Boolean);
begin
  if FShowValue <> Value then
  begin
    FShowValue := Value;
    FOwner.Repaint;
  end;
end;

procedure TTMSFMXChannel.SetStyle(Value: TTMSFMXGraphStyle);
begin
  if FStyle <> Value then
  begin
    FStyle := Value;
    FOwner.Repaint;
  end;
end;

procedure TTMSFMXChannel.AddValue(Value: Single);
begin
  if Length(FSerie) = 0 then
  begin
    SetLength(FSerie, Length(Serie) + 1);
    FSerie[Length(Serie) - 1] := 0;
  end;

  SetLength(FSerie, Length(Serie) + 1);
  FSerie[Length(Serie) - 1] := Value;
end;

procedure TTMSFMXChannel.SetValueFormat(const Value: String);
begin
  if FValueFormat <> Value then
  begin
    FValueFormat := Value;
    FOwner.Repaint;
  end;
end;

procedure TTMSFMXChannel.SetVisible(const Value: boolean);
begin
  if Fvisible <> Value then
  begin
    Fvisible := Value;
    FOwner.Repaint;
  end;
end;

{ TTMSFMXChannels }

constructor TTMSFMXChannels.Create(Scope2: TTMSFMXScope);
begin
  inherited Create(TTMSFMXChannel);
  FScope := Scope2;
end;

function TTMSFMXChannels.Add: TTMSFMXChannel;
begin
  Result := TTMSFMXChannel(inherited Add);
end;

function TTMSFMXChannels.GetItem(Index: Integer): TTMSFMXChannel;
begin
  Result := TTMSFMXChannel(inherited GetItem(Index));
end;

function TTMSFMXChannels.GetOwner: TPersistent;
begin
  Result := FScope;
end;

procedure TTMSFMXChannels.SetItem(Index: Integer; Value: TTMSFMXChannel);
begin
  inherited SetItem(Index, Value);
end;

procedure TTMSFMXChannels.Update(Item: TCollectionItem);
begin
  FScope.UpdateControlCanvas;
end;

{ TTMSFMXScope }

constructor TTMSFMXScope.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  ClipChildren := True;
  Width := 450;
  Height := 301;
  FAutoUpdate := true;
  FActive := false;
  FMinValue := -100;
  FMaxValue := 100;
  FChannels := TTMSFMXChannels.Create(Self);
  FGridLineWidth := 1;
  FGridColor := claGreen;
  FDirection:=sdRightToLeft;
  FFrequency := 1;
  FGridOffset := 0;
  FBaseColor := claLime;
  FAnimateGrid := True;
  FTimer := TTimer.Create(nil);
  FTimer.Enabled := FActive and FAutoUpdate;
  FTimer.Interval := 100;
  FTimer.OnTimer := TimerEvent;

  FYAxis := TTMSFMXYAxis.Create(Self);

  if (csDesigning in ComponentState) and not (csLoading in ComponentState) then
  begin
    {$IFDEF DELPHIXE6_LVL}
    Fill.Kind := TBrushKind.Solid;
    {$ELSE}
    Fill.Kind := TBrushKind.bkSolid;
    {$ENDIF}
    Fill.Color := claBlack;
  end;
end;

destructor TTMSFMXScope.Destroy;
begin
  FYAxis.Free;
  FTimer.Free;
  FChannels.Free;
  inherited Destroy;
end;

procedure TTMSFMXScope.Clear;
var
  I: Integer;
begin
  for I := 0 to FChannels.Count - 1 do
    SetLength(FChannels[I].FSerie, 0);

  ResetGrid;
end;

procedure TTMSFMXScope.DrawGrid(ACanvas: TCanvas);
var
  I, Y: Single;
  R: TRectF;
  sz: Single;
begin
  R := GetScopeRect;

  I := -FGridOffset;
  ACanvas.Stroke.Color := GridColor;
  ACanvas.StrokeThickness := GridLineWidth;
  {$IFDEF DELPHIXE6_LVL}
  ACanvas.Stroke.Kind := TBrushKind.Solid;
  {$ELSE}
  ACanvas.Stroke.Kind := TBrushKind.bkSolid;
  {$ENDIF}

  sz := GetGridSize;

  case Direction of
    sdRightToLeft:
    begin
      while I <= r.Width do
      begin
        if (I >= 0) and (I <= r.Left + R.Width) then
          ACanvas.DrawLine(PointF(I + r.Left, r.Top), PointF(I + r.Left, r.Top + r.Height), Opacity);
        I := I + sz;
      end;
    end;
    sdLeftToRight:
    begin
      while I >= -r.Width do
      begin
        if (I >= -r.Width) and (I <= 0) then
          ACanvas.DrawLine(PointF(r.Width + I + R.Left, r.Top), PointF(r.Width + I + R.Left, r.Top + r.Height), Opacity);
        I := I - sz;
      end;
    end;
  end;

  Y := R.Top;
  while Y < r.Height + r.Top do
  begin
    Y := Y + sz;
    if Y > r.Height + r.Top then
      break;
    ACanvas.DrawLine(PointF(r.Left, Y), PointF(r.Left + r.Width, Y), Opacity);
  end;

  ACanvas.Stroke.Color := BaseColor;
  ACanvas.DrawLine(PointF(r.Left, GetBaseInt), PointF(r.Left + r.Width, GetBaseInt), Opacity);
  ACanvas.Stroke.Color := GridColor;
  ACanvas.DrawRect(r, 0, 0, AllCorners, Opacity);
end;

procedure TTMSFMXScope.DrawAxis(ACanvas: TCanvas);
var
  R: TRectF;
  iSubDiv, iDiv: Integer;
  yVal: Single;
  val, valto: Single;
  valSub: Single;
  clDiv, clSubDiv: TAlphaColor;
  strDiv: String;
  strSubDiv: String;
  tw, th: Single;
  ftDiv: Tfont;
  cltxt: TAlphaColor;
begin
  ftDiv := TFont.Create;
  if YAxis.Visible then
  begin
    R := GetScopeRect;

    val := 0;
    yVal := GetBaseInt;
    if YAxis.ShowDivisions then
    begin
      clDiv := YAxis.DivisionColor;
      case YAxis.FormatType of
        ftFloat: strDiv := FormatFloat(YAxis.Format, val);
        ftNormal: strDiv := Format(YAxis.Format, [val]);
      end;

      ftDiv.Assign(YAxis.DivisionFont);
      cltxt := YAxis.DivisionTextColor;
      if Assigned(OnDrawDivisionText) then
        OnDrawDivisionText(Self, -1, val, ftDiv, cltxt, strDiv);

      if Assigned(OnDrawDivisionLine) then
        OnDrawDivisionLine(Self, -1, val, clDiv);

      Canvas.Stroke.Color := clDiv;

      Canvas.Font.Assign(ftDiv);
      Canvas.Fill.Color := clTxt;

      tw := Canvas.TextWidth(strDiv);
      th := Canvas.TextHeight(strDiv);

      case YAxis.Position of
        ypLeft:
        begin
          Canvas.DrawLine(PointF(R.Left, yVal), PointF(r.Left - YAxis.DivisionSize, yVal), Opacity);
          if YAxis.ShowDivisionText then
            {$IFDEF DELPHIXE6_LVL}
            Canvas.FillText(RectF(R.Left - YAxis.DivisionSize - tw - 3, Yval - th / 2, R.Left - YAxis.DivisionSize - 3, YVal + th / 2), strDiv, False, Opacity, FillTextFlags, TTextAlign.Center);
            {$ELSE}
            Canvas.FillText(RectF(R.Left - YAxis.DivisionSize - tw - 3, Yval - th / 2, R.Left - YAxis.DivisionSize - 3, YVal + th / 2), strDiv, False, Opacity, FillTextFlags, TTextAlign.taCenter);
            {$ENDIF}
        end;
        ypRight:
        begin
          Canvas.DrawLine(PointF(R.Right, yVal), PointF(r.Right + YAxis.DivisionSize, yVal), Opacity);
          if YAxis.ShowDivisionText then
            {$IFDEF DELPHIXE6_LVL}
            Canvas.FillText(RectF(R.Right + YAxis.DivisionSize + 3, Yval - th / 2, R.Right + YAxis.DivisionSize + 3 + tw, YVal + th / 2), strDiv, False, Opacity, FillTextFlags, TTextAlign.Center);
            {$ELSE}
            Canvas.FillText(RectF(R.Right + YAxis.DivisionSize + 3, Yval - th / 2, R.Right + YAxis.DivisionSize + 3 + tw, YVal + th / 2), strDiv, False, Opacity, FillTextFlags, TTextAlign.taCenter);
            {$ENDIF}
        end;
      end;
    end;

    for iDiv := 0 to YAxis.Divisions do
    begin
      val := (((FMaxValue - FMinValue) / YAxis.Divisions) * iDiv);
      yVal := r.Top + (R.Height - (r.Height / (FMaxValue - FMinValue) * val));
      val := val + FMinValue;
      if Val <> 0 then
      begin
        if YAxis.ShowDivisions then
        begin
          clDiv := YAxis.DivisionColor;
          case YAxis.FormatType of
            ftFloat: strDiv := FormatFloat(YAxis.Format, val);
            ftNormal: strDiv := Format(YAxis.Format, [val]);
          end;

          ftDiv.Assign(YAxis.DivisionFont);
          cltxt := YAxis.DivisionTextColor;
          if Assigned(OnDrawDivisionText) then
            OnDrawDivisionText(Self, iDiv, val, ftDiv, cltxt, strDiv);

          if Assigned(OnDrawDivisionLine) then
            OnDrawDivisionLine(Self, iDiv, val, clDiv);

          Canvas.Stroke.Color := clDiv;

          Canvas.Font.Assign(ftDiv);
          Canvas.Fill.Color := clTxt;

          tw := Canvas.TextWidth(strDiv);
          th := Canvas.TextHeight(strDiv);

          case YAxis.Position of
            ypLeft:
            begin
              Canvas.DrawLine(PointF(R.Left, yVal), PointF(r.Left - YAxis.DivisionSize, yVal), Opacity);
              if YAxis.ShowDivisionText then
                {$IFDEF DELPHIXE6_LVL}
                Canvas.FillText(RectF(R.Left - YAxis.DivisionSize - tw - 3, Yval - th / 2, R.Left - YAxis.DivisionSize - 3, YVal + th / 2), strDiv, False, Opacity, FillTextFlags, TTextAlign.Center);
                {$ELSE}
                Canvas.FillText(RectF(R.Left - YAxis.DivisionSize - tw - 3, Yval - th / 2, R.Left - YAxis.DivisionSize - 3, YVal + th / 2), strDiv, False, Opacity, FillTextFlags, TTextAlign.taCenter);
                {$ENDIF}
            end;
            ypRight:
            begin
              Canvas.DrawLine(PointF(R.Right, yVal), PointF(r.Right + YAxis.DivisionSize, yVal), Opacity);
              if YAxis.ShowDivisionText then
                {$IFDEF DELPHIXE6_LVL}
                Canvas.FillText(RectF(R.Right + YAxis.DivisionSize + 3, Yval - th / 2, R.Right + YAxis.DivisionSize + 3 + tw, YVal + th / 2), strDiv, False, Opacity, FillTextFlags, TTextAlign.Center);
                {$ELSE}
                Canvas.FillText(RectF(R.Right + YAxis.DivisionSize + 3, Yval - th / 2, R.Right + YAxis.DivisionSize + 3 + tw, YVal + th / 2), strDiv, False, Opacity, FillTextFlags, TTextAlign.taCenter);
                {$ENDIF}
            end;
          end;
        end;
      end;

      if iDiv = YAxis.Divisions then
        Exit;

      for iSubDiv := 1 to YAxis.SubDivisions - 1 do
      begin
        val := (FMaxValue - FMinValue) / YAxis.Divisions * iDiv;
        valTo := (FMaxValue - FMinValue) / YAxis.Divisions * (iDiv + 1);
        valSub := val + ((valto - val) / YAxis.SubDivisions * iSubDiv);
        yVal := r.Top + (r.Height - (r.Height / (FMaxValue - FMinValue) * valSub));
        valSub := valSub + FMinValue;
        if valSub <> 0 then
        begin
          if YAxis.ShowSubDivisions then
          begin
            clSubDiv := YAxis.SubDivisionColor;

            case YAxis.FormatType of
              ftFloat: strSubDiv := FormatFloat(YAxis.Format, valSub);
              ftNormal: strSubDiv := Format(YAxis.Format, [valSub]);
            end;

            ftDiv.Assign(YAxis.SubDivisionFont);
            cltxt := YAxis.SubDivisionTextColor;
            if Assigned(OnDrawSubDivisionText) then
              OnDrawSubDivisionText(Self, iDiv, iSubDiv, valSub, ftDiv, cltxt, strSubDiv);

            if Assigned(OnDrawSubDivisionLine) then
              OnDrawSubDivisionLine(Self, iDiv, iSubDiv, valSub, clSubDiv);

            Canvas.Stroke.Color := clSubDiv;

            Canvas.Font.Assign(ftDiv);
            Canvas.Fill.Color := clTxt;

            tw := Canvas.TextWidth(strDiv);
            th := Canvas.TextHeight(strDiv);

            case YAxis.Position of
              ypLeft:
              begin
                Canvas.DrawLine(PointF(R.Left, yVal), PointF(r.Left - YAxis.SubDivisionSize, yVal), Opacity);
                if YAxis.ShowSubDivisionText then
                  {$IFDEF DELPHIXE6_LVL}
                  Canvas.FillText(RectF(R.Left - YAxis.SubDivisionSize - tw - 3, Yval - th / 2, R.Left - YAxis.SubDivisionSize - 3, YVal + th / 2), strSubDiv, False, Opacity, FillTextFlags, TTextAlign.Center);
                  {$ELSE}
                  Canvas.FillText(RectF(R.Left - YAxis.SubDivisionSize - tw - 3, Yval - th / 2, R.Left - YAxis.SubDivisionSize - 3, YVal + th / 2), strSubDiv, False, Opacity, FillTextFlags, TTextAlign.taCenter);
                  {$ENDIF}
              end;
              ypRight:
              begin
                Canvas.DrawLine(PointF(R.Right, yVal), PointF(r.Right + YAxis.SubDivisionSize, yVal), Opacity);
                if YAxis.ShowSubDivisionText then
                  {$IFDEF DELPHIXE6_LVL}
                  Canvas.FillText(RectF(R.Right + YAxis.SubDivisionSize + 3, Yval - th / 2, R.Right + YAxis.SubDivisionSize + 3 + tw, YVal + th / 2), strSubDiv, False, Opacity, FillTextFlags, TTextAlign.Center);
                  {$ELSE}
                  Canvas.FillText(RectF(R.Right + YAxis.SubDivisionSize + 3, Yval - th / 2, R.Right + YAxis.SubDivisionSize + 3 + tw, YVal + th / 2), strSubDiv, False, Opacity, FillTextFlags, TTextAlign.taCenter);
                  {$ENDIF}
              end;
            end;
          end;
        end;
      end;
    end;
  end;

  ftDiv.free;
end;

procedure TTMSFMXScope.DrawGraph(ACanvas: TCanvas);
var
  i, j: integer;
  X: Single;
  Chn: TTMSFMXChannel;
  ptf, ptf2, ptf3: TPointF;
  ptfex: Single;
  val, valx: Single;
  R, rtext, rt: TRectF;
  str: String;
  tw, th: Single;
begin
  R := GetScopeRect;

  x := r.Left;
  if FDirection = sdRightToLeft then
    x := x + r.Width;
  for I := FChannels.Count - 1 downto 0 do
  begin

    Chn := FChannels[I];
    if not chn.Visible then
      continue;

    ACanvas.Stroke.Color := Chn.Color;
    ACanvas.StrokeThickness := Chn.Width;
    {$IFDEF DELPHIXE6_LVL}
    ACanvas.Stroke.Kind := TBrushKind.Solid;
    {$ELSE}
    ACanvas.Stroke.Kind := TBrushKind.bkSolid;
    {$ENDIF}

    val := 0;
    valx := 0;
    for J := 0 to Length(chn.Serie) - 1 do
    begin
      case Direction of
        sdRightToLeft: val := ((J + 1) * Frequency);
        sdLeftToRight: val := -((J + 1) * Frequency);
      end;

      case Direction of
        sdRightToLeft:
        begin
          if FGridExtraOffset <> 0 then
            valx := X - FGridExtraOffset + val
          else
            valx := X - FGridOffset + val;
        end;
        sdLeftToRight:
        begin
          if FGridExtraOffset <> 0 then
            valx := X - FGridExtraOffset + val
          else
            valx := X - FGridOffset + val;
        end;
      end;

      case Direction of
        sdRightToLeft:
        begin
          ptf := PointF(valx - Frequency, GetPlotValue(Chn.Serie[J]));
          if J + 1 < Length(chn.Serie) then
            ptf2 := PointF(valx, GetPlotValue(Chn.Serie[J + 1]));
        end;
        sdLeftToRight:
        begin
          if J + 1 < Length(chn.Serie) then
            ptf := PointF(valx , GetPlotValue(Chn.Serie[J + 1]));

          ptf2 := PointF(valx + Frequency, GetPlotValue(Chn.Serie[J]));
        end;
      end;

      if (ptf.X >= R.Left) and (ptf.X <= r.Left + r.Width) then
      begin
        if (Chn.Style = gsBar)  then
        begin
          if (J < Length(chn.Serie) - 1) then
          begin
            case Direction of
              sdRightToLeft:
              begin
                ptf := PointF(valx - Frequency, GetPlotValue(Chn.Serie[J]));
                if J + 1 < Length(chn.Serie) then
                  ptf2 := PointF(valx - Frequency, GetPlotValue(Chn.Serie[J + 1]));
              end;
              sdLeftToRight:
              begin
                if J + 1 < Length(chn.Serie) then
                  ptf2 := PointF(valx + Frequency , GetPlotValue(Chn.Serie[J + 1]));

                ptf := PointF(valx + Frequency, GetPlotValue(Chn.Serie[J]));
              end;
            end;
            ACanvas.DrawLine(ptf, ptf2, Opacity);
            ptf3.X := ptf2.X;
            ptf3.Y := ptf2.Y;
            case Direction of
              sdRightToLeft: ptf3.X := ptf3.X + Frequency;
              sdLeftToRight: ptf3.X := ptf3.X - Frequency;
            end;
            ACanvas.DrawLine(ptf2, ptf3, Opacity);
            ptf2 := ptf3;
          end;
        end
        else
          ACanvas.DrawLine(ptf, ptf2, Opacity);

      end;
    end;
  end;


  for I := 0 to Channels.Count - 1 do
  begin
    chn := Channels[I];
    if chn.Visible and chn.ShowValue and (Length(chn.Serie) > 0) then
    begin
      str := FormatFloat(Chn.ValueFormat, Chn.Serie[Length(chn.Serie) - 1]);

      ACanvas.Font.Assign(Chn.Font);
      tw := ACanvas.TextWidth(str);
      th := ACanvas.TextHeight(str);

      ptfex := GetPlotValue(Chn.Serie[Length(chn.Serie) - 1]);

      case Direction of
        sdRightToLeft: rtext := RectF(x - tw - 10, ptfex, x - 10, ptfex + th);
        sdLeftToRight: rtext := RectF(x, ptfex, x + tw, ptfex + th);
      end;

      rt := rtext;
      Rt.Left := Rt.Left - 2;
      Rt.Right := Rt.Right + 2;
      Rt.Top := Rt.Top - 2;
      Rt.Bottom := Rt.Bottom + 2;
      ACanvas.Fill.Color := Fill.Color;
      ACanvas.FillRect(rt, 5, 5, AllCorners, Opacity);
      ACanvas.Stroke.Color := chn.Color;
      ACanvas.DrawRect(rt, 5, 5, AllCorners, Opacity);
      ACanvas.Fill.Color := chn.Color;
      {$IFDEF DELPHIXE6_LVL}
      ACanvas.FillText(rtext, str, false, Opacity, [], TTextAlign.Center);
      {$ELSE}
      ACanvas.FillText(rtext, str, false, Opacity, [], TTextAlign.taCenter);
      {$ENDIF}
    end;
  end;

end;

procedure TTMSFMXScope.Paint;
var
  R: TRectF;
begin
  inherited;
  R := GetScopeRect;

  Canvas.Fill.Assign(Fill);
  Canvas.FillRect(BoundsRect, 0, 0, AllCorners, AbsoluteOpacity);

  DrawGrid(Canvas);
  DrawGraph(Canvas);
  DrawAxis(Canvas);
end;

procedure TTMSFMXScope.ResetGrid;
begin
  GridOffset := 0;
  GridExtraOffset := 0;
  Repaint;
end;

procedure TTMSFMXScope.TimerEvent(Sender: TObject);
var
  I: Integer;
  Value: Single;
  R: TRectF;
begin
  if AnimateGrid then
  begin
    R := GetScopeRect;
    if FDirection = sdRightToLeft then
    begin
      FGridOffset := FGridOffset + Frequency;
      if FGridOffset >= R.Width then
      begin
        FGridExtraOffset := R.Width;
        FGridOffset := -  (GetGridSize - (Round(R.Width) mod Round(GetGridSize)));
      end;
    end
    else
    begin
      FGridOffset := FGridOffset - Frequency;
      if FGridOffset <= -R.Width then
      begin
        FGridExtraOffset := -R.Width - Frequency;
        FGridOffset := (GetGridSize - (Round(R.Width) mod Round(GetGridSize)));
      end;
    end;
  end;

  for I := 0 to FChannels.Count - 1 do
  begin
    Value := FMinValue;

    if Assigned(FOnNeedData) then
      FOnNeedData(Self, I, Value);

    Channels[I].AddValue(Value);
  end;

  UpdateControlCanvas;
end;

procedure TTMSFMXScope.SetActive(Value: Boolean);
begin
  if FActive <> Value then
  begin
    FActive := Value;
    if not (csDesigning in ComponentState) then
      FTimer.Enabled := Value and FAutoUpdate;
  end;
end;

function TTMSFMXScope.GetBaseInt: Single;
var
  R: TRectF;
begin
  R := GetScopeRect;
  Result := r.Top + (r.Height / (FMaxValue - FMinValue) * FMaxValue);
end;

function TTMSFMXScope.GetGridSize: Single;
var
  R: TRectF;
begin
  R := GetScopeRect;
  Result := r.Height / YAxis.Divisions;
end;

function TTMSFMXScope.GetInstance: NativeUInt;
begin
  Result := HInstance;
end;

function TTMSFMXScope.GetInterval: Integer;
begin
  Result := FTimer.Interval;
end;

function TTMSFMXScope.GetMaxLength: Integer;
var
  I: Integer;
begin
  Result := 0;
  for I := 0 to Channels.Count - 1 do
  begin
    if Length(Channels[I].FSerie) > Result then
      Result := Length(Channels[I].FSerie) - 1;
  end;
end;

function TTMSFMXScope.GetPlotValue(AValue: Single): Single;
var
  R: TRectF;
begin
  R := GetScopeRect;
  Result := r.Top + (R.Height - (r.Height / (FMaxValue - FMinValue) * (AValue - FMinValue)));
end;

function TTMSFMXScope.GetScopeRect: TRectF;
begin
  Result := BoundsRect;
  if YAxis.Visible then
  begin
    case YAxis.Position of
      ypLeft: Result.Left := YAxis.size;
      ypRight: Result.Right := Result.Right - YAxis.Size;
    end;
  end;

  Result.Height := Result.Height - 10;
  Result.Top := 10;
end;

function TTMSFMXScope.GetVersion: String;
var
  vn: Integer;
begin
  vn := GetVersionNr;
  Result := IntToStr(Hi(Hiword(vn)))+'.'+IntToStr(Lo(Hiword(vn)))+'.'+IntToStr(Hi(Loword(vn)))+'.'+IntToStr(Lo(Loword(vn)));
end;

function TTMSFMXScope.GetVersionNr: integer;
begin
  Result := MakeLong(MakeWord(BLD_VER,REL_VER),MakeWord(MIN_VER,MAJ_VER));
end;

procedure TTMSFMXScope.SetInterval(Value: Integer);
begin
  FTimer.Interval := Value;
end;

procedure TTMSFMXScope.SetMaxValue(Value: Single);
begin
  if (FMaxValue <> Value) and (Value > FMinValue) then
  begin
    FMaxValue := Value;
    Clear;
    UpdateControlCanvas;
  end;
end;

procedure TTMSFMXScope.SetMinValue(Value: Single);
begin
  if (FMinValue <> Value) and (Value < FMaxValue) then
  begin
    FMinValue := Value;
    Clear;
    UpdateControlCanvas;
  end;
end;

procedure TTMSFMXScope.SetYAxis(const Value: TTMSFMXYAxis);
begin
  FYAxis.Assign(Value);
end;

procedure TTMSFMXScope.SetChannels(Value: TTMSFMXChannels);
begin
  FChannels.Assign(Value);
end;

procedure TTMSFMXScope.SetGridLineWidth(Value: Integer);
begin
  if FGridLineWidth <> Value then
  begin
    FGridLineWidth := Value;
    UpdateControlCanvas;
  end;
end;

procedure TTMSFMXScope.SetGridColor(Value: TAlphaColor);
begin
  if FGridColor <> Value then
  begin
    FGridColor := Value;
    UpdateControlCanvas;
  end;
end;

procedure TTMSFMXScope.SetFrequency(Value: Single);
begin
  if FFrequency <> Value then
  begin
    FFrequency := Value;
    UpdateControlCanvas;
  end;
end;

procedure TTMSFMXScope.SetBaseColor(Value: TAlphaColor);
begin
  if FBaseColor <> Value then
  begin
    FBaseColor := Value;
    UpdateControlCanvas;
  end;
end;

procedure TTMSFMXScope.setDirection(const Value: TTMSFMXScrollDirection);
begin
  if FDirection <> Value then
  begin
    FDirection := Value;
    UpdateControlCanvas;
  end;
end;

procedure TTMSFMXScope.SetAutoUpdate(const Value: Boolean);
begin
  FAutoUpdate := Value;
  FTimer.Enabled :=FAutoUpdate and FActive;
end;

procedure TTMSFMXScope.AddData;
var
  R: TRectF;
begin
  R := GetScopeRect;
  if FDirection = sdRightToLeft then
  begin
    FGridOffset := FGridOffset + Frequency;
    if FGridOffset >= R.Width then
    begin
      FGridExtraOffset := R.Width;
      FGridOffset := -  (GetGridSize - (Round(R.Width) mod Round(GetGridSize)));
    end;
  end
  else
  begin
    FGridOffset := FGridOffset - Frequency;
    if FGridOffset <= -R.Width then
    begin
      FGridExtraOffset := -R.Width - Frequency;
      FGridOffset := (GetGridSize - (Round(R.Width) mod Round(GetGridSize)));
    end;
  end;

  UpdateControlCanvas;
end;

procedure TTMSFMXScope.UpdateControlCanvas;
  procedure DeleteX(var A: TSerieArray; const Index: Cardinal);
  var
    ALength: Cardinal;
    i: Cardinal;
  begin
    ALength := Length(A);
    Assert(ALength > 0);
    Assert(Index < ALength);
    for i := Index + 1 to ALength - 1 do
      A[i - 1] := A[i];
    SetLength(A, ALength - 1);
  end;
var
  I: Integer;
  R: TRectF;
begin
  r := GetScopeRect;
  for I := 0 to Channels.Count - 1 do
  begin
    if Length(Channels[I].FSerie) > (R.Width / Frequency) + 1 then
    begin
      DeleteX(Channels[I].FSerie, 0);
    end;
  end;

  Repaint;
end;

procedure TTMSFMXScope.UpdateData(Channel: Integer; Value: Single);
begin
  if FChannels.Count = 0 then
    Exit;

  Channel := Max(0, Min(Channel, FChannels.Count - 1));
  Channels[Channel].AddValue(Value);
end;

{ TTMSFMXYAxis }

constructor TTMSFMXYAxis.Create(AOwner: TTMSFMXScope);
begin
  FOwner := AOwner;
  FFont := TFont.Create;
  FFormat := '%.2f';
  FFormatType := ftNormal;
  FSize := 50;

  FDivisionColor := claLime;
  FSubDivisionColor := claGreen;
  FDivisionSize := 10;
  FSubDivisionSize := 7;
  FVisible := True;
  FPosition := ypLeft;
  FDivisionFont := TFont.Create;
  FSubDivisionFont := TFont.Create;
  FDivisionTextColor := claLime;
  FSubDivisionTextColor := claGreen;
  FDivisions := 5;
  FSubDivisions := 3;
  FShowDivisions := True;
  FShowSubDivisions := True;
  FShowDivisionText := True;
  FShowSubDivisionText := False;
end;

destructor TTMSFMXYAxis.Destroy;
begin
  FFont.Free;
  inherited;
end;

procedure TTMSFMXYAxis.SetDivisions(const Value: Integer);
begin
  if FDivisions <> Value then
  begin
    FDivisions := Max(1,  Value);
    FOwner.UpdateControlCanvas;
  end;
end;

procedure TTMSFMXYAxis.SetDivisionColor(const Value: TAlphaColor);
begin
  if FDivisionColor <> Value then
  begin
    FDivisionColor := Value;
    FOwner.UpdateControlCanvas;
  end;
end;

procedure TTMSFMXYAxis.SetDivisionSize(const Value: Single);
begin
  if FDivisionSize <> Value then
  begin
    FDivisionSize := Value;
    FOwner.UpdateControlCanvas;    
  end;
end;

procedure TTMSFMXYAxis.SetDivisionTextColor(const Value: TAlphaColor);
begin
  if FDivisionTextColor <> Value then
  begin
    FDivisionTextColor := Value;
    FOwner.UpdateControlCanvas;
  end;
end;

procedure TTMSFMXYAxis.SetDivisionFont(const Value: TFont);
begin
  if FFont <> Value then
  begin
    FDivisionFont.Assign(Value);
    FOwner.UpdateControlCanvas;
  end;
end;

procedure TTMSFMXYAxis.SetFormat(const Value: string);
begin
  if FFormat <> Value then
  begin
    FFormat := Value;
    FOwner.UpdateControlCanvas;
  end;
end;

procedure TTMSFMXYAxis.SetFormatType(const Value: TTMSFMXFormatType);
begin
  if FFormatType <> Value then
  begin
    FFormatType := Value;
    FOwner.UpdateControlCanvas;
  end;
end;

procedure TTMSFMXYAxis.SetPosition(const Value: TTMSFMXYAxisPosition);
begin
  if FPosition <> Value then
  begin
    FPosition := Value;
    FOwner.UpdateControlCanvas;
  end;
end;

procedure TTMSFMXYAxis.SetShowDivisions(const Value: Boolean);
begin
  if FShowDivisions <> Value then
  begin
    FShowDivisions := Value;
    FOwner.UpdateControlCanvas;
  end;
end;

procedure TTMSFMXYAxis.SetShowDivisionText(const Value: Boolean);
begin
  if FShowDivisionText <> Value then
  begin
    FShowDivisionText := Value;
    FOwner.UpdateControlCanvas;
  end;
end;

procedure TTMSFMXYAxis.SetShowSubDivisions(const Value: Boolean);
begin
  if FShowSubDivisions <> Value then
  begin
    FShowSubDivisions := Value;
    FOwner.UpdateControlCanvas;
  end;
end;

procedure TTMSFMXYAxis.SetShowSubDivisionText(const Value: Boolean);
begin
  if FShowSubDivisionText <> Value then
  begin
    FShowSubDivisionText := Value;
    FOwner.UpdateControlCanvas;
  end;
end;

procedure TTMSFMXYAxis.SetSize(const Value: Single);
begin
  if FSize <> Value then
  begin
    FSize := Value;
    FOwner.UpdateControlCanvas;
  end;
end;

procedure TTMSFMXYAxis.SetSubDivisions(const Value: Integer);
begin
  if FSubDivisions <> Value then
  begin
    FSubDivisions := Value;
    FOwner.UpdateControlCanvas;    
  end;
end;

procedure TTMSFMXYAxis.SetSubDivisionColor(const Value: TAlphaColor);
begin
  if FSubDivisionColor <> Value then
  begin
    FSubDivisionColor := Value;
    FOwner.UpdateControlCanvas;
  end;
end;

procedure TTMSFMXYAxis.SetSubDivisionFont(const Value: TFont);
begin
  if FSubDivisionFont <> Value then
  begin
    FSubDivisionFont.Assign(Value);
    FOwner.UpdateControlCanvas;
  end;
end;

procedure TTMSFMXYAxis.SetSubDivisionSize(const Value: Single);
begin
  if FSubDivisionSize <> Value then
  begin
    FSubDivisionSize := Value;
    FOwner.UpdateControlCanvas;    
  end;
end;

procedure TTMSFMXYAxis.SetSubDivisionTextColor(const Value: TAlphaColor);
begin
  if FSubDivisionTextColor <> Value then
  begin
    FSubDivisionTextColor := Value;
    FOwner.UpdateControlCanvas;
  end;
end;

procedure TTMSFMXYAxis.SetVisible(const Value: Boolean);
begin
  if FVisible <> Value then
  begin
    FVisible := Value;
    FOwner.UpdateControlCanvas;
  end;
end;

end.

