{********************************************}
{ TSeriesStatsTool and Editor Dialog         }
{ Copyright (c) 2006-2020 by Steema Software }
{********************************************}
unit TeeSeriesStats;
{$I TeeDefs.inc}

// Tool that provides easy access to choosen Series statistics like
// trend, averages, minimum and maximum values, etc.

interface

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

  {$IFDEF FMX}
  FMX.Types, System.UITypes,
  {$ELSE}
  Graphics, Controls,
  {$ENDIF}

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

type
  TSeriesStatsTool=class(TTeeCustomToolSeries)
  protected
    class Function GetEditorClass:String; override;
    function GetStatFunction(FuncType:TTeeFunctionClass):TTeeFunction;
  public
    class Function Description:String; override;
    procedure FreeStatsSeries;
    function Kurtosis:Double;
    class Function LongDescription:String; override;
    function Mean:Double;
    function Skewness:Double;
    procedure Statistics(const Lines:TStrings);
    function StdDeviation:Double;
  published
    property Active;
    property Series;
  end;

implementation

uses
  Math,
  {$IFDEF FMX}
  FMXTee.Chart.Functions, FMXTee.Functions.Stats, FMXTee.Functions.CurveFitting,
  FMXTee.Constants, FMXTee.ProConstants
  {$ELSE}
  TeeFunci, StatChar, CurvFitt, TeeConst, TeeProCo
  {$ENDIF}
  ;

{ TSeriesStatsTool }

class function TSeriesStatsTool.Description: String;
begin
  result:=TeeMsg_Statistics;
end;

function TSeriesStatsTool.GetStatFunction(FuncType:TTeeFunctionClass):TTeeFunction;
var t   : Integer;
    tmp : TTeeFunction;
begin
  result:=nil;

  if ParentChart<>nil then 
  for t:=0 to ParentChart.SeriesCount-1 do
  begin
    tmp:=ParentChart[t].FunctionType;
    if Assigned(tmp) and (tmp is FuncType) and (tmp.ParentSeries.DataSource=Series) then
    begin
      result:=tmp;
      exit;
    end;
  end;
end;

class function TSeriesStatsTool.GetEditorClass: String;
begin
  result:='TSeriesStatsEditor';
end;

procedure TSeriesStatsTool.Statistics(const Lines: TStrings);

  function AtPosition(const Value:TChartValue):String;
  var tmpIndex : Integer;
  begin
    tmpIndex:=Series.MandatoryValueList.Locate(Value);
    if tmpIndex<>-1 then
       result:=' at position '+IntToStr(tmpIndex)
    else
       result:='';
  end;

  procedure AddFunction(FunctionClass:TTeeFunctionClass; const Description:String);
  begin
    with FunctionClass.Create(nil) do
    try
      Lines.Add(Description+' '+FormatFloat(Series.ValueFormat,Calculate(Series,-1,-1)));
    finally
      Free;
    end;
  end;

begin
  if Assigned(Series) then
  begin
    Lines.Add('Number of values: '+IntToStr(Series.Count));

    with Series,MandatoryValueList do
    begin
      Lines.Add('Maximum value: '+FormatFloat(ValueFormat,MaxValue)+AtPosition(MaxValue));
      Lines.Add('Minimum value: '+FormatFloat(ValueFormat,MinValue)+AtPosition(MinValue));

      Lines.Add('');
      Lines.Add('Range: '+FormatFloat(ValueFormat,Range));

      Lines.Add('');

      Lines.Add('Average: '+FormatFloat(ValueFormat,Mean));

      AddFunction(TMedianTeeFunction,'Median:');
      AddFunction(TModeTeeFunction,'Mode:');
      AddFunction(TStdDeviationFunction,'Std. Deviation:');
      AddFunction(TVarianceFunction,'Variance:');
      AddFunction(TRMSFunction,'Root Mean Square:');
      AddFunction(TCorrelationFunction,'Correlation:');

      Lines.Add('Skewness: '+FormatFloat(ValueFormat,Skewness));
      Lines.Add('Kurtosis: '+FormatFloat(ValueFormat,Kurtosis));
    end;
  end;
end;

function TSeriesStatsTool.StdDeviation:Double;
begin
  with TStdDeviationFunction.Create(nil) do
  try
    result:=Calculate(Series,-1,-1);
  finally
    Free;
  end;
end;

function TSeriesStatsTool.Mean:Double;
begin
  with Series,MandatoryValueList do
  if Count>0 then
     result:=Total/Count
  else
     result:=0;
end;

function TSeriesStatsTool.Skewness:Double;
begin
  with TSkewnessFunction.Create(nil) do
  try
    result:=Calculate(Series,-1,-1);
  finally
    Free;
  end;
end;

function TSeriesStatsTool.Kurtosis:Double;
begin
  with TKurtosisFunction.Create(nil) do
  try
    result:=Calculate(Series,-1,-1);
  finally
    Free;
  end;
end;

class function TSeriesStatsTool.LongDescription: String;
begin
  result:=TeeMsg_StatsToolDesc;
end;

// Removes (destroys) all series that have been created with the
// TSeriesStatsTool Editor dialog (if any):

procedure TSeriesStatsTool.FreeStatsSeries;
var t : Integer;
    tmp : TTeeFunction;
begin
  if Assigned(ParentChart) and Assigned(Series) then
  begin
    t:=0;

    while t<ParentChart.SeriesCount do
    begin
      tmp:=ParentChart[t].FunctionType;

      if Assigned(tmp) and (tmp.ParentSeries.DataSource=Series) then
         ParentChart[t].{$IFDEF AUTOREFCOUNT}DisposeOf{$ELSE}Free{$ENDIF}
      else
         Inc(t);
    end;
  end;
end;

initialization
  RegisterTeeTools([TSeriesStatsTool]);
finalization
  UnRegisterTeeTools([TSeriesStatsTool]);
end.
