{****************************************************}
{ TeeChart Functions                                 }
{ Copyright (c) 1996-2020 by Steema Software         }
{ TModeTeeFunction Copyright 2000-2020 by Tom Deprez }
{ All Rights Reserved                                }
{****************************************************}
unit TeeFunci;
{$I TeeDefs.inc}

interface

Uses
  Classes,

  {$IFDEF D21}
  System.Generics.Collections, 
  {$ENDIF}

  {$IFDEF FMX}
  FMXTee.Engine
  {$ELSE}
  TeEngine
  {$ENDIF}
  ;

type
  TAddTeeFunction=class(TTeeFunction)
  protected
    Function CalculateValue(const Values:TChartValues): Double; override;
    procedure PrepareForGallery(const Chart:TCustomAxisPanel); override;
  end;

  TManySeriesTeeFunction=class(TTeeFunction)
  end;

  TSubtractTeeFunction=class(TManySeriesTeeFunction)
  protected
    Function CalculateValue(const Values:TChartValues): Double; override;
    procedure PrepareForGallery(const Chart:TCustomAxisPanel); override;
  end;

  TMultiplyTeeFunction=class(TManySeriesTeeFunction)
  protected
    Function CalculateValue(const Values:TChartValues): Double; override;
    procedure PrepareForGallery(const Chart:TCustomAxisPanel); override;
  end;

  TDivideTeeFunction=class(TManySeriesTeeFunction)
  protected
    Function CalculateValue(const Values:TChartValues): Double; override;
    procedure PrepareForGallery(const Chart:TCustomAxisPanel); override;
  end;

  THighTeeFunction=class(TTeeFunction)
  protected
    Function CalculateValue(const Values:TChartValues): Double; override;
  public
    Function Calculate(const SourceSeries:TChartSeries; FirstIndex,LastIndex:Integer):Double; override;
  end;

  TLowTeeFunction=class(TTeeFunction)
  protected
    Function CalculateValue(const Values:TChartValues): Double; override;
  public
    Function Calculate(const SourceSeries:TChartSeries; FirstIndex,LastIndex:Integer):Double; override;
  end;

  TAverageTeeFunction=class(TTeeFunction)
  protected
    Function CalculateValue(const Values:TChartValues): Double; override;
    class Function GetEditorClass:String; override;
  public
    Function Calculate(const SourceSeries:TChartSeries; FirstIndex,LastIndex:Integer):Double; override;
  end;

  TCustomTeeFunction=class;

  TCalculateEvent=procedure(Sender:TCustomTeeFunction; const x:Double; var y:Double) of object;

  TCustomTeeFunction=class(TTeeFunction)
  private
    FCalculate : TCalculateEvent;
    FNumPoints : Integer;
    FStartX    : Double;
    FX         : Double;

    procedure GalleryCalculate(Sender: TCustomTeeFunction; const x: Double; var y: Double);
  protected
    class Function GetEditorClass:String; override;
    procedure PrepareForGallery(const Chart:TCustomAxisPanel); override;
  public
    ValueIndex:Integer;

    Constructor Create(AOwner:TComponent); override;
    procedure AddPoints(Source:TChartSeries); override;

    property X:Double read FX write FX;
  published
    property NumPoints:Integer read FNumPoints write FNumPoints;
    property StartX:Double read FStartX write FStartX;

    property OnCalculate:TCalculateEvent read FCalculate write FCalculate;
  end;

  TCustomSortedFunction=class(TTeeFunction)
  protected
    Procedure AddValue(const Value:Double; const Values:TChartValues; Index:Integer); override;
  end;

  TMedianTeeFunction=class(TCustomSortedFunction)
  protected
    Function CalculateValue(const Values:TChartValues):Double; override;
  end;

  TModeTeeFunction=class(TCustomSortedFunction) // Tom Deprez
  protected
    function CalculateValue(const Values:TChartValues):Double; override;
  end;

  TSubsetTeeFunction=class(TTeeFunction)
  private
    FEnd   : TChartValue;
    FStart : TChartValue;

    procedure SetEnd(Const Value:TChartValue);
    procedure SetStart(Const Value:TChartValue);
  protected
    class Function GetEditorClass:String; override;
    procedure PrepareForGallery(const Chart:TCustomAxisPanel); override;
  public
    Constructor Create(AOwner:TComponent); override;

    procedure AddPoints(Source:TChartSeries); override;
  published
    property EndValue:TChartValue read FEnd write SetEnd;
    property StartValue:TChartValue read FStart write SetStart;
  end;

  TParetoTeeFunction=class(TTeeFunction)
  protected
    procedure PrepareForGallery(const Chart:TCustomAxisPanel); override;
  public
    { Public declarations }
    Constructor Create(AOwner: TComponent); override;

    Function Calculate(const Series:TChartSeries; First,Last:Integer):Double; override;
  published
    { Published declarations }
    property Period;
  end;

implementation

Uses SysUtils,
     
     {$IFDEF FMX}
     FMXTee.Canvas, FMXTee.Procs, FMXTee.Constants, FMXTee.Chart
     {$ELSE}
     TeCanvas, Chart, TeeProcs, TeeConst
     {$ENDIF}
     ;

{ Add }
Function TAddTeeFunction.CalculateValue(const Values:TChartValues): Double;
var t,
    H : TeeNativeInt;
begin
  result:=0;  // Values.Sum or TTeeMath.Sum(Values)

  H:=High(Values);

  if H>=0 then
  begin
    result:=Values[0];

    for t:=1 to H do
        result:=result+Values[t];
  end;
end;

{ ManySeriesFunction }

{ Subtract }

Function TSubtractTeeFunction.CalculateValue(const Values:TChartValues): Double;
var t,
    H : TeeNativeInt;
begin
  result:=0; // Values.Subtract or TTeeMath.Subtract(Values)

  H:=High(Values);

  if H>=0 then
  begin
    result:=Values[0];

    for t:=1 to H do
        result:=result-Values[t];
  end;
end;

{ Multiply }

Function TMultiplyTeeFunction.CalculateValue(const Values:TChartValues): Double;
var t,
    H : TeeNativeInt;
begin
  result:=0; // Values.Multiply or TTeeMath.Multiply(Values)

  H:=High(Values);

  if H>=0 then
  begin
    result:=Values[0];

    for t:=1 to H do
        result:=result*Values[t];  // <-- Potential overflow for big quantities !
  end;
end;

{ Divide }

Function TDivideTeeFunction.CalculateValue(const Values:TChartValues): Double;
var t,
    H : TeeNativeInt;
begin
  result:=0; // Values.Divide or TTeeMath.Divide(Values)

  H:=High(Values);

  if H>=0 then
  begin
    result:=Values[0];

    for t:=1 to H do
        if Values[t]<>0 then // Avoid Divide By Zero
           result:=result/Values[t];
  end;
end;

{ High }

Function THighTeeFunction.Calculate(const SourceSeries:TChartSeries; FirstIndex,LastIndex:Integer):Double;
begin
  if (FirstIndex=TeeAllValues) and IncludeNulls then
     result:=ValueList(SourceSeries).MaxValue
  else
     result:=inherited Calculate(SourceSeries,FirstIndex,LastIndex);
end;

Function THighTeeFunction.CalculateValue(const Values:TChartValues): Double;
var t,
    H : TeeNativeInt;
begin
  result:=0; // Values.Maximum or TTeeMath.Maximum(Values)

  H:=High(Values);

  if H>=0 then
  begin
    result:=Values[0];

    for t:=1 to H do
        if Values[t]>result then
           result:=Values[t];
  end;
end;

{ Low }

Function TLowTeeFunction.Calculate(const SourceSeries:TChartSeries; FirstIndex,LastIndex:Integer):Double;
begin
  if (FirstIndex=TeeAllValues) and IncludeNulls then
     result:=ValueList(SourceSeries).MinValue
  else
     result:=inherited Calculate(SourceSeries,FirstIndex,LastIndex);
end;

Function TLowTeeFunction.CalculateValue(const Values:TChartValues): Double;
var t,
    H : TeeNativeInt;
begin
  result:=0; // Values.Minimum or TTeeMath.Minimum(Values)

  H:=High(Values);

  if H>=0 then
  begin
    result:=Values[0];

    for t:=1 to H do
        if Values[t]<result then
           result:=Values[t];
  end;
end;

{ Average }

Function TAverageTeeFunction.Calculate(const SourceSeries:TChartSeries; FirstIndex,LastIndex:Integer):Double;
begin
  if (FirstIndex=TeeAllValues) and IncludeNulls then
    With SourceSeries do
    begin
      if Count>0 then result:=ValueList(SourceSeries).Total/Count
                 else result:=0;
    end
  else
    result:=inherited Calculate(SourceSeries,FirstIndex,LastIndex);
end;

Function TAverageTeeFunction.CalculateValue(const Values:TChartValues): Double;
var t,
    H : TeeNativeInt;
begin
  result:=0; // Values.Mean or TTeeMath.Mean(Values)

  H:=High(Values);

  if H>=0 then
  begin
    for t:=0 to H do
        result:=result+Values[t];

    result:=result/Succ(H);
  end;
end;

class function TAverageTeeFunction.GetEditorClass: String;
begin
  result:='TAverageFuncEditor'; // Do not localize
end;

{ TCustomTeeFunction }

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

  NoSourceRequired:=True;
  Period:=1;
  FNumPoints:=100;
  ValueIndex:=-1;
end;

procedure TCustomTeeFunction.AddPoints(Source: TChartSeries);
var x : Double;
    y : Double;
    t : Integer;
begin
  if Assigned(FCalculate) then
  begin
    ParentSeries.Clear;

    x:=FStartX;
    y:=0;

    for t:=1 to FNumPoints do
    begin
      FX:=x;
      ValueIndex:=t-1;

      FCalculate(Self,x,y);

      if ParentSeries.YMandatory then ParentSeries.AddXY(FX,y)
                                 else ParentSeries.AddXY(y,FX);
      x:=x+Period;
    end;

    ValueIndex:=-1;
  end;
end;

class function TCustomTeeFunction.GetEditorClass: String;
begin
  result:='TCustomFunctionEditor'; // Do not localize
end;

procedure TCustomTeeFunction.GalleryCalculate(Sender: TCustomTeeFunction;
  const x: Double; var y: Double);
var Angle : Double;
begin
  Angle:=x*TeePiStep;
  y:=Sqr(Sin(Angle) + Cos(Angle));
end;

procedure TCustomTeeFunction.PrepareForGallery(const Chart:TCustomAxisPanel);
begin
  inherited;

  TCustomChart(Chart).Legend.Hide;
  TCustomChart(Chart).Title.Caption:='y = Sqr(Sin(x) + Cos(x))';

  NumPoints:=360;
  FCalculate:=GalleryCalculate;
  ReCalculate;

  ParentSeries.GetVertAxis.Title.Caption:='Y';
  ParentSeries.GetVertAxis.Title.Angle:=0;
  ParentSeries.GetHorizAxis.Title.Caption:='X';

  Chart[0].Visible:=False;
end;

{ TCustomSortedFunction }

procedure TCustomSortedFunction.AddValue(const Value:Double; const Values:TChartValues; Index:Integer);
var tmpOk : Boolean;
    tt    : Integer;
    ttt   : Integer;
begin
  tmpOk:=False;
  for tt:=0 to Index do
  if Values[tt]>Value then
  begin
    for ttt:=Index downto tt+1 do Values[ttt]:=Values[ttt-1];

    Values[tt]:=Value;
    tmpOk:=True;
    break;
  end;

  if not tmpOk then Values[Index]:=Value;
end;

{ TMedianFunction }

Function TMedianTeeFunction.CalculateValue(const Values:TChartValues):Double;
var tmpMiddle,
    tmpCount  : TeeNativeInt;
begin
  tmpCount:=Length(Values);

  if tmpCount=1 then
     result:=Values[0]
  else
  begin
    tmpMiddle:=tmpCount div 2;
    result:=Values[tmpMiddle];

    if not Odd(tmpCount) then
       result:=( result+Values[tmpMiddle-1] ) * 0.5;
  end;
end;

{ TModeTeeFunction }

function TModeTeeFunction.CalculateValue(const Values:TChartValues):Double;
var Freq,
    HiFreq : Integer;

    t,
    H      : TeeNativeInt;
begin
  H:=High(Values);

  if H>-1 then result:=Values[0]
          else result:=0;

  if H>0 then
  begin
    Freq:=1;
    HiFreq:=0;

    for t:=1 to H do
    begin
      if Values[t-1]=Values[t] then
         Inc(Freq)
      else
      begin
        if Freq>HiFreq then
        begin
          result:=Values[t-1];
          HiFreq:=Freq;
        end;

        Freq:=1;
      end;
    end;

    if Freq>HiFreq then
       result:=Values[H];
  end;
end;

{ TSubsetFunction }

Constructor TSubsetTeeFunction.Create(AOwner:TComponent);
begin
  inherited;
  CanUsePeriod:=False;
end;

procedure TSubsetTeeFunction.AddPoints(Source:TChartSeries);
var t         : Integer;
    tmp       : TChartValueList;
    tmpValue  : TChartValue;
    tmpSource : String;
begin
  ParentSeries.Clear;

  tmp:=Source.MandatoryValueList;
  tmpSource:=ParentSeries.MandatoryValueList.ValueSource;

  if tmpSource<>'' then
     for t:=0 to Source.ValuesList.Count-1 do
         if Source.ValuesList.ValueList[t].Name=tmpSource then
         begin
           tmp:=Source.ValuesList[t];
           break;
         end;

  if Assigned(tmp) then
     for t:=0 to Source.Count-1 do
     begin
       tmpValue:=Source.NotMandatoryValueList.Value[t];

       if (tmpValue>=FStart) and (tmpValue<=FEnd) then
          if ParentSeries.YMandatory then
             ParentSeries.AddXY(tmpValue,tmp.Value[t])
          else
             ParentSeries.AddXY(tmp.Value[t],tmpValue);
     end;
end;

class function TSubsetTeeFunction.GetEditorClass: String;
begin
  result:='TSubsetFuncEditor';
end;

procedure TSubsetTeeFunction.SetEnd(const Value: TChartValue);
begin
  if FEnd<>Value then
  begin
    FEnd:=Value;
    ReCalculate;
  end;
end;

procedure TSubsetTeeFunction.SetStart(const Value: TChartValue);
begin
  if FStart<>Value then
  begin
    FStart:=Value;
    ReCalculate;
  end;
end;

procedure TSubsetTeeFunction.PrepareForGallery(const Chart:TCustomAxisPanel);
var tmp : Integer;
    tmpSeries : TChartSeries;
begin
  inherited;

  tmpSeries:=ParentSeries;
  ChangeSeriesType(tmpSeries,TeeSeriesTypes.Find('TLineSeries'));

  tmp:=(ParentSeries.DataSource as TChartSeries).Count;

  FStart:=Round(tmp*0.25);
  FEnd:=Round(tmp*0.75);

  ReCalculate;
end;

{ TAddTeeFunction }

procedure TAddTeeFunction.PrepareForGallery(const Chart: TCustomAxisPanel);
begin
  inherited;
  ParentSeries.VertAxis:=aRightAxis;
end;

{ TDivideTeeFunction }

procedure TDivideTeeFunction.PrepareForGallery(const Chart: TCustomAxisPanel);
begin
  inherited;
  ParentSeries.VertAxis:=aRightAxis;
end;

{ TSubtractTeeFunction }

procedure TSubtractTeeFunction.PrepareForGallery(const Chart: TCustomAxisPanel);
begin
  inherited;
  ParentSeries.VertAxis:=aRightAxis;
end;

{ TMultiplyTeeFunction }

procedure TMultiplyTeeFunction.PrepareForGallery(const Chart: TCustomAxisPanel);
begin
  inherited;
  ParentSeries.VertAxis:=aRightAxis;
  ParentSeries.GetVertAxis.AxisValuesFormat:='1E+1';
end;

{ TParetoFunction }

Constructor TParetoTeeFunction.Create(AOwner: TComponent);
Begin
  inherited;
  InternalSetPeriod(1);
end;

procedure TParetoTeeFunction.PrepareForGallery(const Chart:TCustomAxisPanel);
var tmp : TChartSeries;
    tmpClass : TChartSeriesClass;
begin
  inherited;

  tmp:=Chart[0];

  tmpClass:=TeeSeriesTypes.Find('TBarSeries');

  if Assigned(tmpClass) then
     ChangeSeriesType(tmp,tmpClass);

  tmp.XValues.Order:=loNone;
  tmp.YValues.Order:=loDescending;
  tmp.CheckOrder;

  ParentSeries.VertAxis:=aRightAxis;
  ParentSeries.GetVertAxis.AxisValuesFormat:='0%';
end;

Function TParetoTeeFunction.Calculate(const Series:TChartSeries; First,Last:Integer):Double;
var t : Integer;
    tmpAbs,
    tmpAcum : Double;
    tmpList : TChartValueList;
begin
  tmpList:=ValueList(Series);

  tmpAcum:=0;

  if First>-1 then
  for t:=0 to First do
      tmpAcum:=tmpAcum+Abs(tmpList[t]);

  tmpAbs:=tmpList.TotalABS;

  if tmpAbs=0 then
     result:=0
  else
     result:=100*tmpAcum/tmpAbs;
end;

initialization
  RegisterTeeBasicFunction( TAddTeeFunction,      @TeeMsg_FunctionAdd );
  RegisterTeeBasicFunction( TSubtractTeeFunction, @TeeMsg_FunctionSubtract );
  RegisterTeeBasicFunction( TMultiplyTeeFunction, @TeeMsg_FunctionMultiply );
  RegisterTeeBasicFunction( TDivideTeeFunction,   @TeeMsg_FunctionDivide );
  RegisterTeeBasicFunction( THighTeeFunction,     @TeeMsg_FunctionHigh );
  RegisterTeeBasicFunction( TLowTeeFunction,      @TeeMsg_FunctionLow );
  RegisterTeeBasicFunction( TAverageTeeFunction,  @TeeMsg_FunctionAverage );
  RegisterTeeBasicFunction( TCustomTeeFunction,   @TeeMsg_FunctionCustom);
  RegisterTeeBasicFunction( TMedianTeeFunction,   @TeeMsg_FunctionMedian);
  RegisterTeeBasicFunction( TModeTeeFunction,     @TeeMsg_FunctionMode);
  RegisterTeeBasicFunction( TSubsetTeeFunction,   @TeeMsg_FunctionSubset);
  RegisterTeeBasicFunction( TParetoTeeFunction,   @TeeMsg_FunctionPareto);
end.
