lazarus/components/tachart/taexpressionseries.pas

1009 lines
26 KiB
ObjectPascal

{
Function series with expression parser for TAChart.
*****************************************************************************
See the file COPYING.modifiedLGPL.txt, included in this distribution,
for details about the license.
*****************************************************************************
AUTHOR: Werner Pamler
NOTE:
Set/unset the define MATH_EXPRESSIONS to get/remove design-time access to
functions declared in the unit Math. For runtime-only access call
"ExtendExprBuiltIns()".
}
unit TAExpressionSeries;
{$MODE ObjFPC}{$H+}
{$WARN 6058 off : Call to subroutine "$1" marked as inline is not inlined}
interface
uses
Classes, Graphics, SysUtils, fpexprpars,
TAChartUtils, TADrawUtils, TAFuncSeries;
type
TExpressionSeries = class;
TChartExprParam = class(TCollectionItem)
private
FName: String;
FValue: Double;
procedure ParamChanged(ASender: TObject);
procedure SetName(const AValue: String);
procedure SetValue(const AValue: Double);
public
procedure Assign(Source: TPersistent); override;
published
property Name: String read FName write SetName;
property Value: Double read FValue write SetValue;
end;
TChartExprParams = class(TCollection)
private
FParser: TFpExpressionParser;
FOnChanged: TNotifyEvent;
function GetP(AIndex: Integer): TChartExprParam;
function GetValByName(AName: String): Double;
procedure SetP(AIndex: Integer; AValue: TChartExprParam);
procedure SetValByName(AName: String; AValue: Double);
protected
procedure Changed;
public
constructor Create(AParser: TFpExpressionParser; AOnChanged: TNotifyEvent);
function AddParam(const AName: String; const AValue: Double): TChartExprParam;
function FindParamByName(AName: String): TChartExprParam;
property Params[AIndex: Integer]: TChartExprParam read GetP write SetP; default;
property ValueByName[AName: String]: Double read GetValByName write SetValByName;
property OnChanged: TNotifyEvent read FOnChanged write FOnChanged;
end;
TDomainParts = array[0..4] of String;
// parts of a domain expression
// e.g. '1>= x >0' has the parts '1', '>=', 'x', '>', '0'
TChartDomainScanner = class
private
FSeries: TExpressionSeries;
FParser: TFpExpressionParser;
FExpression: String;
FEpsilon: Double;
function GetVariable: String;
protected
procedure Analyze(AList, ADomain: TIntervalList; const AParts: TDomainParts);
procedure ConvertToExclusions(AList, ADomain: TIntervalList);
procedure ExpressionError;
procedure ParseExpression(AList, ADomain: TIntervalList);
public
constructor Create(ASeries: TExpressionSeries);
procedure ExtractDomainExclusions(AList: TIntervalList);
property Epsilon: Double read FEpsilon write FEpsilon;
property Expression: String read FExpression write FExpression;
property Variable: String read GetVariable;
end;
TExpressionSeries = class(TCustomFuncSeries)
private
FDomain: String;
FDomainScanner: TChartDomainScanner;
FExpression: String;
FParams: TChartExprParams;
FParser: TFpExpressionParser;
FVariable: String;
FX: TFPExprIdentifierDef;
FDirty: Boolean;
function GetDomainEpsilon: Double;
procedure SetDomain(const AValue: String);
procedure SetDomainEpsilon(const AValue: Double);
procedure SetExpression(const AValue: string);
procedure SetParams(const AValue: TChartExprParams);
procedure SetVariable(const AValue: String);
protected
function DoCalculate(AX: Double): Double; override;
procedure GetBounds(var ABounds: TDoubleRect); override;
procedure OnChangedHandler(Sender: TObject);
procedure SetupParser;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Assign(ASource: TPersistent); override;
procedure Draw(ADrawer: IChartDrawer); override;
function IsEmpty: Boolean; override;
procedure RequestParserUpdate; inline;
published
property DomainEpsilon: Double read GetDomainEpsilon write SetDomainEpsilon;
property Params: TChartExprParams read FParams write SetParams;
property Variable: String read FVariable write SetVariable;
property Domain: String read FDomain write SetDomain;
property Expression: String read FExpression write SetExpression;
end;
TExpressionColorMapSeries = class(TCustomColorMapSeries)
private
FExpression: String;
FParams: TChartExprParams;
FParser: TFpExpressionParser;
FVarX: String;
FVarY: String;
FX: TFpExprIdentifierDef;
FY: TFpExprIdentifierDef;
FDirty: Boolean;
procedure SetExpression(const AValue: String);
procedure SetParams(const AValue: TChartExprParams);
procedure SetVarX(const AValue: String);
procedure SetVarY(const AValue: String);
protected
procedure OnChangedHandler(Sender: TObject);
procedure SetupParser;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Assign(ASource: TPersistent); override;
procedure Draw(ADrawer: IChartDrawer); override;
function FunctionValue(AX, AY: Double): Double; override;
function IsEmpty: Boolean; override;
procedure RequestParserUpdate; inline;
published
property Expression: String read FExpression write SetExpression;
property Params: TChartExprParams read FParams write SetParams;
property VariableNameX: String read FVarX write SetVarX;
property VariableNameY: String read FVarY write SetVarY;
end;
procedure ExtendExprBuiltins;
implementation
uses
Math,
TAGraph, TAChartStrConsts;
{ TChartExprParam }
procedure TChartExprParam.Assign(Source: TPersistent);
begin
if Source is TChartExprParam then begin
FName := TChartExprParam(Source).Name;
FValue := TChartExprParam(Source).Value;
end;
inherited Assign(Source);
end;
procedure TChartExprParam.ParamChanged(ASender: TObject);
begin
Unused(ASender);
TChartExprParams(Collection).Changed;
end;
procedure TChartExprParam.SetName(const AValue: String);
begin
if AValue = FName then exit;
FName := AValue;
ParamChanged(self);
end;
procedure TChartExprParam.SetValue(const AValue: Double);
begin
if AValue = FValue then exit;
FValue := AValue;
ParamChanged(self);
end;
{ TChartExprParams }
constructor TChartExprParams.Create(AParser: TFpExpressionParser;
AOnChanged: TNotifyEvent);
begin
inherited Create(TChartExprParam);
FParser := AParser;
FOnChanged := AOnChanged;
end;
function TChartExprParams.AddParam(const AName: String;
const AValue: Double): TChartExprParam;
begin
Result := Add as TChartExprParam;
Result.FName := AName;
Result.FValue := AValue;
Changed;
end;
procedure TChartExprParams.Changed;
begin
if Assigned(FOnChanged) then FOnChanged(self);
end;
function TChartExprParams.FindParamByName(AName: String): TChartExprParam;
var
i: Integer;
begin
for i:=0 to Count-1 do begin
Result := Params[i];
if Result.Name = AName then
exit;
end;
Result := nil;
end;
function TChartExprParams.GetP(AIndex: Integer): TChartExprParam;
begin
Result := TChartExprParam(Items[AIndex]);
end;
function TChartExprParams.GetValByName(AName: String): Double;
var
P : TChartExprParam;
begin
P := FindParamByName(AName);
if P = nil then
raise Exception.CreateFmt('Parameter "%s" not found.', [AName]);
Result := P.Value;
end;
procedure TChartExprParams.SetP(AIndex: Integer;
AValue: TChartExprParam);
begin
Items[AIndex] := AValue;
end;
procedure TChartExprParams.SetValByName(AName: String; AValue: Double);
var
P: TChartExprParam;
begin
P := FindParamByName(AName);
if P = nil then
raise Exception.CreateFmt('Parameter "%s" not found.', [AName]);
P.Value := AValue;
Changed;
end;
{ TChartDomainScanner }
constructor TChartDomainScanner.Create(ASeries: TExpressionSeries);
begin
FSeries := ASeries;
FParser := ASeries.FParser;
FEpsilon := DEFAULT_EPSILON;
end;
{ Analyzes the parts of the domain expression and extract the intervals on
which the function is defined (--> ADomain).
NOTE: Although supported by the scanner the method does not distinguish
between cases < and <=, as well as between > and >=. }
procedure TChartDomainScanner.Analyze(AList, ADomain: TIntervalList;
const AParts: TDomainParts);
var
SaveListEpsilon, SaveDomainEpsilon: Double;
a, b: Double;
begin
SaveListEpsilon := AList.Epsilon;
SaveDomainEpsilon := ADomain.Epsilon;
try
AList.Epsilon := FEpsilon; // list of excluded ranges should be widened by Epsilon
ADomain.Epsilon := -FEpsilon; // list of included ranges should be narrowed by Epsilon
// two-sided interval, e.g. "0 < x <= 1", or "2 > x >= 1"
if (AParts[2] = Variable) and (AParts[3] <> '') and (AParts[4] <> '') then
begin
FParser.Expression := AParts[0];
a := ArgToFloat(FParser.Evaluate);
FParser.Expression := AParts[4];
b := ArgToFloat(FParser.Evaluate);
if (AParts[1][1] = '<') and (AParts[3][1] = '<') and (a < b) then
ADomain.AddRange(a, b)
else
if (AParts[1][1] = '>') and (AParts[3][1] = '>') and (a > b) then
ADomain.AddRange(b, a);
end else
// one-sided interval, variable is at left
if (AParts[0] = Variable) and (AParts[3] = '') and (AParts[4] = '') then
begin
FParser.Expression := AParts[2];
a := ArgToFloat(FParser.Evaluate);
case AParts[1] of
'<>' : AList.AddPoint(a); // x <> a
'<', '<=' : ADomain.AddRange(-Infinity, a); // x < a, x <= a
'>', '>=' : ADomain.AddRange(a, Infinity); // x > a, x >= a
else Expressionerror;
end;
end else
// one-sided interval, variable is at right
if (AParts[2] = Variable) and (AParts[3] = '') and (AParts[4] = '') then
begin
FParser.Expression := AParts[0];
a := ArgToFloat(FParser.Evaluate);
case AParts[1] of
'<>' : AList.AddPoint(a); // a <> x
'<', '<=' : ADomain.AddRange(a, Infinity); // a < x, a <= x
'>', '>=' : ADomain.AddRange(-Infinity, a); // a > x, a >= x
else ExpressionError;
end;
end else
ExpressionError;
finally
AList.Epsilon := SaveListEpsilon;
ADomain.Epsilon := SaveDomainEpsilon;
end;
end;
{ Converts the intervals in ADomain on which the function is defined to
intervals in AList in which the function is NOT defined (= DomainExclusion) }
procedure TChartDomainScanner.ConvertToExclusions(AList, ADomain: TIntervalList);
var
SaveListEpsilon: Double;
a, b: Double;
i, j: Integer;
points: array of Double = nil;
begin
if ADomain.IntervalCount = 0 then
exit;
j := 0;
SetLength(points, ADomain.IntervalCount*2);
for i:=0 to ADomain.IntervalCount-1 do begin
if ADomain.Interval[i].FStart <> -Infinity then begin
points[j] := ADomain.Interval[i].FStart;
inc(j);
end;
if ADomain.Interval[i].FEnd <> +Infinity then begin
points[j] := ADomain.Interval[i].FEnd;
inc(j);
end;
end;
SetLength(points, j);
SaveListEpsilon := AList.Epsilon;
try
AList.Epsilon := 0; // provide direct ADomain to AList conversion - all the
// required epsilons have already been applied earlier,
// in the Analyze() call
// Case 1: domain extends to neg infinity
// -INF <---------|xxxxxxxx|------|xxxx> INF with - = allowed, x = forbidden
// 0 1 2
if ADomain.Interval[0].FStart = -Infinity then
j := 0
else
// Case 2: domain begins at finite value
// -INF <xxxxxxxxx|--------|xxxxxx|------>INF
// 0 1 2
begin
a := -Infinity;
b := points[0];
AList.AddRange(a, b);
j := 1;
end;
while j < Length(points) do begin
a := points[j];
if j = High(points) then
b := Infinity
else
b := points[j+1];
AList.AddRange(a, b);
inc(j, 2);
end;
finally
AList.Epsilon := SaveListEpsilon;
end;
end;
procedure TChartDomainScanner.ExpressionError;
begin
raise Exception.Create('Incorrect domain expression in "' + FExpression + '"');
end;
procedure TChartDomainScanner.ExtractDomainExclusions(AList: TIntervalList);
var
domains: TIntervalList;
savedExpr: String;
begin
Assert(AList <> nil);
AList.Clear;
if FExpression = '' then
exit;
savedExpr := FParser.Expression;
domains := TIntervalList.Create;
try
ParseExpression(AList, domains);
ConvertToExclusions(AList, domains);
finally
domains.Free;
FParser.Expression := savedExpr;
end;
end;
function TChartDomainScanner.GetVariable: String;
begin
Result := FSeries.Variable;
end;
{ Parses the expression string and creates an interval list with the
domain of the function, i.e. the intervals in which the function is DEFINED. }
procedure TChartDomainScanner.ParseExpression(AList, ADomain: TIntervalList);
var
i: Integer;
s: String;
parts: TDomainParts;
p: Integer; // 0=left, 1=operator left , 2=middle, 3=operator right, 4=right
// 0 <= x < 1
begin
for p := 0 to Length(parts)-1 do
parts[p] := '';
p := 0;
i := 1;
s := FExpression + ';'; // Simplify parsing...
while i <= Length(s) do begin
case s[i] of
' ': ;
';': begin
Analyze(AList, ADomain, parts);
for p := 0 to Length(parts)-1 do
parts[p] := '';
p := 0;
end;
'<': if (i < Length(FExpression)) then begin
inc(p);
case s[i+1] of
'=': begin parts[p] := '<='; inc(i); end;
'>': begin parts[p] := '<>'; inc(i); end;
else parts[p] := '<';
end;
inc(p);
end else
ExpressionError;
'>': if (i < Length(FExpression)) then begin
inc(p);
if s[i+1] = '=' then begin
parts[p] := '>=';
inc(i);
end else
parts[p] := '>';
inc(p);
end else
ExpressionError;
'=': ExpressionError;
else
parts[p] := Parts[p] + s[i];
end;
inc(i);
end;
end;
{ TExpressionSeries }
constructor TExpressionSeries.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FDirty := true;
FVariable := 'x';
FExpression := 'x';
FParser := TFpExpressionParser.Create(self);
FParser.BuiltIns := [bcMath];
FX := FParser.Identifiers.AddFloatVariable(FVariable, 0.0);
FDomainScanner := TChartDomainScanner.Create(self);
FParams := TChartExprParams.Create(FParser, @OnChangedHandler);
end;
destructor TExpressionSeries.Destroy;
begin
FX := nil;
FParams.Free;
FDomainScanner.Free;
inherited;
end;
procedure TExpressionSeries.Assign(ASource: TPersistent);
begin
if ASource is TExpressionSeries then begin
Domain := TExpressionSeries(ASource).Domain;
Expression := TExpressionSeries(ASource).Expression;
Params := TExpressionSeries(ASource).Params;
Variable := TExpressionSeries(ASource).Variable;
end;
inherited Assign(ASource);
end;
function TExpressionSeries.DoCalculate(AX: Double): Double;
var
res: TFPExpressionResult;
begin
Result := 0.0;
FX.AsFloat := AX;
try
res := FParser.Evaluate;
if res.ResultType=rtFloat then
Result := res.ResFloat
else
if res.ResultType=rtInteger then
Result := res.ResInteger;
except
end;
end;
procedure TExpressionSeries.Draw(ADrawer: IChartDrawer);
begin
if (not Active) then exit;
SetupParser;
inherited;
end;
procedure TExpressionSeries.GetBounds(var ABounds: TDoubleRect);
begin
SetupParser;
inherited;
end;
function TExpressionSeries.IsEmpty: Boolean;
begin
Result := FParser.Expression = '';
end;
procedure TExpressionSeries.OnChangedHandler(Sender: TObject);
begin
RequestParserUpdate;
UpdateParentChart;
end;
procedure TExpressionSeries.RequestParserUpdate;
begin
FDirty := true;
end;
procedure TExpressionSeries.SetDomain(const AValue: String);
begin
if FDomain = AValue then exit;
FDomain := AValue;
RequestParserUpdate;
UpdateParentChart;
end;
function TExpressionSeries.GetDomainEpsilon: Double;
begin
Result := FDomainScanner.Epsilon;
end;
procedure TExpressionSeries.SetDomainEpsilon(const AValue: Double);
begin
if FDomainScanner.Epsilon = abs(AValue) then exit;
FDomainScanner.Epsilon := abs(AValue);
RequestParserUpdate;
UpdateParentChart;
end;
procedure TExpressionSeries.SetExpression(const AValue: String);
begin
// if FParser.Expression = AValue then
// exit;
FExpression := AValue;
RequestParserUpdate;
UpdateParentChart;
end;
procedure TExpressionSeries.SetParams(const AValue: TChartExprParams);
begin
RequestParserUpdate;
FParams.Assign(AValue);
UpdateParentChart;
end;
procedure TExpressionSeries.SetupParser;
var
i: Integer;
p: TChartExprParam;
begin
if (not FDirty) then
exit;
FParser.Identifiers.Clear;
FX := FParser.Identifiers.AddFloatVariable(FVariable, 0.0);
for i:=0 to FParams.Count-1 do begin
p := FParams[i];
FParser.Identifiers.AddFloatVariable(p.Name, p.Value);
end;
FDomainScanner.Expression := FDomain;
FDomainScanner.ExtractDomainExclusions(DomainExclusions);
FParser.Expression := FExpression;
if not (FParser.ResultType in [rtInteger, rtFLoat]) then
raise EExprParser.CreateFmt(rsErrInvalidResultType, [ResultTypeName(FParser.ResultType)]);
FDirty := false;
end;
procedure TExpressionSeries.SetVariable(const AValue: String);
begin
if FVariable = AValue then exit;
FVariable := AValue;
RequestParserUpdate;
UpdateParentChart;
end;
{ TExpressionColorMapSeries }
constructor TExpressionColorMapSeries.Create(AOwner: TComponent);
begin
inherited;
FVarX := 'x';
FVarY := 'y';
FExpression := 'x^2 + y^2';
FParser := TFpExpressionParser.Create(self);
FParser.BuiltIns := [bcMath];
FX := FParser.Identifiers.AddFloatVariable(FVarX, 0.0);
FY := FParser.Identifiers.AddFloatVariable(FVarY, 0.0);
FParams := TChartExprParams.Create(FParser, @OnChangedHandler);
FParser.Expression := FExpression;
end;
destructor TExpressionColorMapSeries.Destroy;
begin
FX := nil;
FY := nil;
FParams.Free;
inherited;
end;
procedure TExpressionColorMapSeries.Assign(ASource: TPersistent);
begin
if ASource is TExpressionColorMapSeries then begin
Self.FExpression := TExpressionColorMapSeries(ASource).Expression;
Self.FParams := TExpressionColorMapSeries(ASource).Params;
Self.FVarX := TExpressionColorMapSeries(ASource).VariableNameX;
Self.FVarY := TExpressionColorMapSeries(ASource).VariableNameY;
end;
inherited Assign(ASource);
end;
procedure TExpressionColorMapSeries.Draw(ADrawer: IChartDrawer);
begin
if (not Active) then exit;
SetupParser;
inherited;
end;
function TExpressionColorMapSeries.FunctionValue(AX, AY: Double): Double;
var
res: TFPExpressionResult;
begin
Result := 0.0;
FX.AsFloat := AX;
FY.AsFloat := AY;
try
res := FParser.Evaluate;
if res.ResultType=rtFloat then
Result := res.ResFloat
else
if res.ResultType=rtInteger then
Result := res.ResInteger;
except
end;
end;
function TExpressionColorMapSeries.IsEmpty: Boolean;
begin
Result := FParser.Expression = '';
end;
procedure TExpressionColorMapSeries.OnChangedHandler(Sender: TObject);
begin
RequestParserUpdate;
UpdateParentChart;
end;
procedure TExpressionColorMapSeries.RequestParserUpdate;
begin
FDirty := true;
end;
procedure TExpressionColorMapSeries.SetExpression(const AValue: String);
begin
if FParser.Expression = AValue then
exit;
FExpression := AValue;
RequestParserUpdate;
UpdateParentChart;
end;
procedure TExpressionColorMapSeries.SetParams(const AValue: TChartExprParams);
begin
RequestParserUpdate;
FParams.Assign(AValue);
UpdateParentChart;
end;
procedure TExpressionColorMapSeries.SetupParser;
var
i: Integer;
p: TChartExprParam;
begin
if (not FDirty) then
exit;
FParser.Identifiers.Clear;
FX := FParser.Identifiers.AddFloatVariable(FVarX, 0.0);
FY := FParser.Identifiers.AddFloatVariable(FVarY, 0.0);
for i:=0 to FParams.Count-1 do begin
p := FParams[i];
FParser.Identifiers.AddFloatVariable(p.Name, p.Value);
end;
FParser.Expression := FExpression;
if not (FParser.ResultType in [rtInteger, rtFLoat]) then
raise EExprParser.CreateFmt(rsErrInvalidResultType, [ResultTypeName(FParser.ResultType)]);
FDirty := false;
end;
procedure TExpressionColorMapSeries.SetVarX(const AValue: String);
begin
if FVarX = AValue then exit;
FVarX := AValue;
RequestParserUpdate;
UpdateParentChart;
end;
procedure TExpressionColorMapSeries.SetVarY(const AValue: String);
begin
if FVarY = AValue then exit;
FVarY := AValue;
RequestParserUpdate;
UpdateParentChart;
end;
{ Additional functions for the parser }
procedure ExprDegToRad(var Result: TFPExpressionResult; const Args: TExprParameterArray);
var
x: Double;
begin
x := ArgToFloat(Args[0]);
Result.resFloat := degtorad(x);
end;
procedure ExprRadToDeg(var Result: TFPExpressionResult; const Args: TExprParameterArray);
var
x: Double;
begin
x := ArgToFloat(Args[0]);
Result.resFloat := radtodeg(x);
end;
procedure ExprTan(var Result: TFPExpressionResult; const Args: TExprParameterArray);
var
x: Double;
begin
x := ArgToFloat(Args[0]);
Result.resFloat := tan(x);
end;
procedure ExprCot(var Result: TFPExpressionResult; const Args: TExprParameterArray);
var
x: Double;
begin
x := ArgToFloat(Args[0]);
Result.resFloat := cot(x);
end;
procedure ExprArcsin(var Result: TFPExpressionResult; const Args: TExprParameterArray);
var
x: Double;
begin
x := ArgToFloat(Args[0]);
Result.resFloat := arcsin(x);
end;
procedure ExprArccos(var Result: TFPExpressionResult; const Args: TExprParameterArray);
var
x: Double;
begin
x := ArgToFloat(Args[0]);
Result.resFloat := arccos(x);
end;
procedure ExprArccot(Var Result: TFPExpressionResult; const Args: TExprParameterArray);
var
x: Double;
begin
x := ArgToFloat(Args[0]);
Result.resFloat := pi/2 - arctan(x);
end;
procedure ExprCosh(var Result: TFPExpressionResult; const Args: TExprParameterArray);
var
x: Double;
begin
x := ArgToFloat(Args[0]);
Result.resFloat := cosh(x);
end;
{ Hyperbolic cotangent coth(x); x <> 0 }
procedure ExprCoth(var Result: TFPExpressionResult; const Args: TExprParameterArray);
var
x: Double;
begin
x := ArgToFloat(Args[0]);
Result.resFloat := 1/tanh(x);
end;
procedure ExprSinh(var Result: TFPExpressionResult; const Args: TExprParameterArray);
var
x: Double;
begin
x := ArgToFloat(Args[0]);
Result.resFloat := sinh(x);
end;
procedure ExprTanh(var Result: TFPExpressionResult; const Args: TExprParameterArray);
var
x: Double;
begin
x := ArgToFloat(Args[0]);
Result.resFloat := tanh(x);
end;
procedure ExprArcosh(var Result: TFPExpressionResult; const Args: TExprParameterArray);
var
x: Double;
begin
x := ArgToFloat(Args[0]);
Result.resFloat := arcosh(x);
end;
procedure ExprArsinh(var Result: TFPExpressionResult; const Args: TExprParameterArray);
var
x: Double;
begin
x := ArgtoFloat(Args[0]);
Result.resFloat := arsinh(x);
end;
procedure ExprArtanh(var Result: TFPExpressionResult; const Args: TExprParameterArray);
var
x: Double;
begin
x := ArgToFloat(Args[0]);
Result.resFloat := artanh(x);
end;
procedure ExprArcoth(var Result: TFPExpressionResult; const Args: TExprParameterArray);
var
x: Double;
begin
x := ArgToFloat(Args[0]);
Result.resFloat := artanh(1.0/x);
end;
procedure ExprSinc(var Result: TFPExpressionResult; const Args: TExprParameterArray);
var
x: Double;
begin
x := ArgToFloat(Args[0]);
if x = 0 then
Result.ResFloat := 1.0
else
Result.resFloat := sin(x)/x;
end;
procedure ExprPower(var Result: TFPExpressionResult; const Args: TExprParameterArray);
var
x,y: Double;
begin
x := ArgToFloat(Args[0]);
y := ArgToFloat(Args[1]);
Result.resFloat := Power(x, y);
end;
procedure ExprHypot(var Result: TFPExpressionResult; const Args: TExprParameterArray);
var
x,y: Double;
begin
x := ArgToFloat(Args[0]);
y := ArgToFloat(Args[1]);
Result.resFloat := Hypot(x,y);
end;
procedure ExprLg(var Result: TFPExpressionResult; const Args: TExprParameterArray);
var
x: Double;
begin
x := ArgToFloat(Args[0]);
Result.resFloat := log10(x);
end;
procedure ExprLog10(var Result: TFPExpressionResult; const Args: TExprParameterArray);
var
x: Double;
begin
x := ArgToFloat(Args[0]);
Result.resFloat := log10(x);
end;
procedure ExprLog2(var Result: TFPExpressionResult; const Args: TExprParameterArray);
var
x: Double;
begin
x := ArgToFloat(Args[0]);
Result.resFloat := log2(x);
end;
procedure ExprMax(var Result: TFPExpressionResult; const Args: TExprParameterArray);
var
x1, x2: Double;
begin
x1 := ArgToFloat(Args[0]);
x2 := ArgToFloat(Args[1]);
Result.resFloat := Max(x1, x2);
end;
procedure ExprMin(var Result: TFPExpressionResult; const Args: TExprParameterArray);
var
x1, x2: Double;
begin
x1 := ArgToFloat(Args[0]);
x2 := ArgToFloat(Args[1]);
Result.resFloat := Min(x1, x2);
end;
procedure ExtendExprBuiltins;
begin
with BuiltinIdentifiers do begin
// Trigonometric and related
AddFunction(bcMath, 'degtorad', 'F', 'F', @ExprDegtorad);
AddFunction(bcMath, 'radtodeg', 'F', 'F', @ExprRadtodeg);
AddFunction(bcMath, 'tan', 'F', 'F', @ExprTan);
AddFunction(bcMath, 'cot', 'F', 'F', @ExprCot);
AddFunction(bcMath, 'arcsin', 'F', 'F', @ExprArcSin);
AddFunction(bcMath, 'arccos', 'F', 'F', @ExprArcCos);
AddFunction(bcMath, 'arccot', 'F', 'F', @ExprArcCot);
AddFunction(bcMath, 'cosh', 'F', 'F', @ExprCosh);
AddFunction(bcMath, 'coth', 'F', 'F', @ExprCoth);
AddFunction(bcMath, 'sinh', 'F', 'F', @ExprSinh);
AddFunction(bcMath, 'tanh', 'F', 'F', @ExprTanh);
AddFunction(bcMath, 'arcosh', 'F', 'F', @ExprArcosh);
AddFunction(bcMath, 'arsinh', 'F', 'F', @ExprArsinh);
AddFunction(bcMath, 'artanh', 'F', 'F', @ExprArtanh);
AddFunction(bcMath, 'arcoth', 'F', 'F', @ExprArcoth);
AddFunction(bcMath, 'sinc', 'F', 'F', @ExprSinc);
// Power
AddFunction(bcMath, 'power', 'F', 'FF', @ExprPower);
AddFunction(bcMath, 'hypot', 'F', 'FF', @ExprHypot);
// Logarithm
AddFunction(bcMath, 'lg', 'F', 'F', @ExprLog10);
AddFunction(bcMath, 'log10', 'F', 'F', @ExprLog10);
AddFunction(bcMath, 'log2', 'F', 'F', @ExprLog2);
end;
end;
initialization
{$IFDEF MATH_EXPRESSIONS}
ExtendExprBuiltins;
{$ENDIF}
RegisterSeriesClass(TExpressionSeries, @rsExpressionSeries);
RegisterSeriesClass(TExpressionColorMapSeries, @rsExpressionColorMapSeries);
end.