{********************************************}
{ TBooleanSeries and TBinarySeries           }
{ Copyright (c) 2000-2020 by Steema Software }
{********************************************}
unit TeeDigitalSeries;
{$I TeeDefs.inc}

interface

uses
  {$IFDEF FMX}
  FMXTee.Engine, FMXTee.Series, FMXTee.Chart,
  {$ELSE}
  TeEngine, Chart, Series,
  {$ENDIF}
  Classes;

type
  TBaseBoolean=class(TFastLineSeries)
  private
    FOffText,
    FOnText : String;

    IAxis : TChartAxis;

    function GetBoolean(Index:Integer):Boolean;
    procedure SetBoolean(Index:Integer; const Value:Boolean);
  protected
    function GetMarkLabel(ValueIndex:Integer):String; override;
  public
    Constructor Create(AOwner:TComponent); override;
    Destructor Destroy; override;

    function Changes:Integer;

    procedure VerticalPosition(const APosition,ATotal:Integer);

    property BooleanValue[Index:Integer]:Boolean read GetBoolean write SetBoolean;
  published
    property OffText:String read FOffText write FOffText;
    property OnText:String read FOnText write FOnText;
  end;

  TBooleanSeries=class(TBaseBoolean)
  protected
    procedure AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False); override;
  public
    procedure Add(const Value:Boolean);
    procedure AddXY(const X:TChartValue; const Value:Boolean);
  end;

  TBinarySeries=class(TChartSeries)
  private
    FMask : Int64;
    ISeries : TChartSeriesList;

    procedure AddValues;
    procedure FreeSeries;
    procedure ReCreateSeries;
    procedure Remove(const ASeries:TBaseBoolean);
    procedure RepositionAxes;
    procedure SetMask(const Value: Int64);
    function GetStairs: Boolean;
    procedure SetStairs(const Value: Boolean);
  protected
    Procedure AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False); override;
    Procedure SetParentChart(Const Value:TCustomAxisPanel); override;
  public
    Constructor Create(AOwner:TComponent); override;
    Destructor Destroy; override;

    procedure Add(const Value:Int64);
    procedure CreateBits(const AMask:Int64);
  published
    property Mask:Int64 read FMask write SetMask default $FFFF;
    property Stairs:Boolean read GetStairs write SetStairs default True;
  end;

implementation

uses
  {$IFDEF FMX}
  FMX.Controls, FMXTee.ProConstants,
  {$ELSE}
  Controls, TeeProCo,
  {$ENDIF}
  SysUtils;

{ TBaseBoolean }

Constructor TBaseBoolean.Create(AOwner: TComponent);
begin
  inherited;
  Stairs:=True;

  FOffText:='OFF';
  FOnText:='ON';
end;

Destructor TBaseBoolean.Destroy;
begin
  if Owner is TBinarySeries then
     TBinarySeries(Owner).Remove(Self);

  IAxis.Free;
  inherited;
end;

function TBaseBoolean.Changes: Integer;
var t : Integer;
    tmp,
    Last : Boolean;
begin
  result:=0;

  if Count>0 then
  begin
    Last:=BooleanValue[0];

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

      if tmp<>Last then
      begin
        Last:=tmp;
        Inc(result);
      end;
    end;
  end;
end;

function TBaseBoolean.GetMarkLabel(ValueIndex: Integer): String;
begin
  if (ValueIndex>=0) and (ValueIndex<Count) then
  begin
    if Round(MandatoryValueList[ValueIndex])=0 then
       result:=FOffText
    else
       result:=FOnText;
  end
  else
    result:='';
end;

procedure TBaseBoolean.VerticalPosition(const APosition, ATotal: Integer);
const
  AxisTopMargin = 1; // 2%
  AxisBottomMargin = 1; // 2%

var tmpAxis : TChartAxis;
begin
  if ParentChart<>nil then
  begin
    tmpAxis:=IAxis;

    if tmpAxis=nil then
       tmpAxis:=CustomVertAxis;

    if tmpAxis=nil then
    begin
      IAxis:=ParentChart.CustomAxes.Add;
      IAxis.Increment:=1;
      CustomVertAxis:=IAxis;
    end;

    IAxis.StartPosition:=APosition* (100 div ATotal) +AxisBottomMargin;
    IAxis.EndPosition:=IAxis.StartPosition + (100 div ATotal) - AxisTopMargin-AxisBottomMargin;
  end;
end;

function TBaseBoolean.GetBoolean(Index: Integer): Boolean;
begin
  result:=MandatoryValueList[Index]<>0;
end;

procedure TBaseBoolean.SetBoolean(Index: Integer; const Value: Boolean);
begin
  MandatoryValueList[Index]:=Ord(Value);
end;

{ TBooleanSeries }

procedure TBooleanSeries.Add(const Value: Boolean);
begin
  inherited Add(Ord(Value));
end;

procedure TBooleanSeries.AddXY(const X: TChartValue; const Value: Boolean);
begin
  inherited AddXY(X,Ord(Value));
end;

Procedure TBooleanSeries.AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False);
var t : Integer;
begin
  for t:=0 to NumValues-1 do
      Add(Random(1000)<500);
end;

{ TBinarySeries }

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

  FMask:=$FFFF;

  ISeries:=TChartSeriesList.Create;

  ShowInLegend:=False;
  Visible:=False;
end;

Destructor TBinarySeries.Destroy;
begin
  FreeSeries;
  ISeries.Free;

  inherited;
end;

procedure TBinarySeries.FreeSeries;
var t : Integer;
begin
  while ISeries.Count>0 do
      ISeries[0].Free;
end;

procedure TBinarySeries.Add(const Value: Int64);
begin
  inherited Add(Value);
end;

// Workaround for 32bit cpu

type
  TBitPosition64=0..63;
  T64=packed record
    Lo,Hi : Cardinal;
  end;

function IsBitSet(const Value:Int64; const Position:TBitPosition64):Boolean;
begin
  if Position<32 then
     result:=(T64(Value).Lo and (1 shl Position)) <> 0
  else
     result:=(T64(Value).Hi and (1 shl (Position-32))) <> 0
end;

procedure TBinarySeries.CreateBits;
var tmp : TBooleanSeries;
    t : Integer;
begin
  for t:=Low(TBitPosition64) to High(TBitPosition64) do
  begin
    tmp:=TBooleanSeries.Create(Self);
    tmp.Cursor:=crHandPoint;
    tmp.Title:='Bit '+IntToStr(t);

    ISeries.Add(tmp);
    tmp.ParentChart:=ParentChart;

    tmp.Visible:=IsBitSet(AMask,t);
  end;
end;

procedure TBinarySeries.AddValues;
var t,
    tt : Integer;
begin
  for t:=0 to ISeries.Count-1 do
      for tt:=0 to Count-1 do
          TBooleanSeries(ISeries[t]).Add( IsBitSet(Round(MandatoryValueList[tt]),t) );
end;

Procedure TBinarySeries.AddSampleValues(NumValues:Integer; OnlyMandatory:Boolean=False);
var t : Integer;
begin
  for t:=0 to NumValues-1 do
      Add(Round(Random($FFFF)));

  ReCreateSeries;
  AddValues;
end;

procedure TBinarySeries.ReCreateSeries;
begin
  if ParentChart=nil then
     FreeSeries
  else
  begin
    // Create one series for each of all bits in the mask
    if ISeries.Count=0 then
       CreateBits($FFFF); // use first 32 bits

    RepositionAxes;
  end;
end;

procedure TBinarySeries.RepositionAxes;

  // Return the number of series that are visible (active)
  function CountVisibleSeries:Integer;
  var t : Integer;
  begin
    result:=0;

    for t:=0 to ISeries.Count-1 do
        if ISeries[t].Active then
           Inc(result);
  end;

var ActiveCount,
    ActivePosition,
    t : Integer;
begin
  ActiveCount:=CountVisibleSeries;

  ActivePosition:=0;

  for t:=0 to ISeries.Count-1 do
      if ISeries[t].Active then
      begin
        if ISeries[t] is TBooleanSeries then
        begin
          // Reset the vertical position of one series

          TBooleanSeries(ISeries[t]).VerticalPosition(ActivePosition,ActiveCount);
          Inc(ActivePosition);
        end;
      end;
end;

procedure TBinarySeries.SetParentChart(const Value: TCustomAxisPanel);
var Old : TCustomAxisPanel;
begin
  Old:=ParentChart;
  
  inherited;

  if Old<>ParentChart then
     ReCreateSeries;

  if Old=nil then
     if ParentChart<>nil then
        ParentChart.View3D:=False;
end;

procedure TBinarySeries.SetMask(const Value: Int64);
begin
  if FMask<>Value then
  begin
    FreeSeries;
    ReCreateSeries;
  end;
end;

procedure TBinarySeries.Remove(const ASeries: TBaseBoolean);
var tmp : Integer;
begin
  tmp:=ISeries.IndexOf(ASeries);

  if tmp<>-1 then
     ISeries.Delete(tmp);
end;

function TBinarySeries.GetStairs: Boolean;
begin
  if ISeries.Count=0 then
     result:=True
  else
     result:=TBooleanSeries(ISeries[0]).Stairs;
end;

procedure TBinarySeries.SetStairs(const Value: Boolean);
var t : Integer;
begin
  for t:=0 to ISeries.Count-1 do
      TBooleanSeries(ISeries[t]).Stairs:=Value;
end;

// Register the Series at Chart gallery
initialization
  RegisterTeeSeries( TBooleanSeries, @TeeMsg_GalleryBoolean,
                                   @TeeMsg_GalleryDigital, 1 );
  RegisterTeeSeries( TBinarySeries, @TeeMsg_GalleryBinary,
                                   @TeeMsg_GalleryDigital, 1 );
finalization
  UnRegisterTeeSeries([TBooleanSeries]);
  UnRegisterTeeSeries([TBinarySeries]);
end.
