Komponente plotfunction
Komponente plotfunction
Hallo allerseits,
ich bin neu in diesem Forum und habe eine Frage zur Komponente plotfunction. Ich bin interessiert an dieser Komponente weil sie erlaubt, zur Laufzeit eine math. Funktion zu parsen und anschließend zu plotten. Ich habe die Demo-Anwendung exprdemo (aus components/plotfunction/demo/expression) ausgeführt. Es klappt im Prinzip alles, bis auf die Skalierung. Wenn ich mir f(x) =x² zeichnen lasse, geht die Funktion nicht furch Null, genausowenig wenn ich mit die Gerade f(x) = x zeichnen lasse. Sie geht ebenfalls nicht durch den Koordinatenursprung. Kann mir jemand auf die Sprünge helfen, wie ich das abstellen kann. Ansonsten wäre plotfunction genau das, was ich brauche.
Ich habe Lazarus 1.2.6 unter Windows 7 installiert.
Vielen Dank.
ich bin neu in diesem Forum und habe eine Frage zur Komponente plotfunction. Ich bin interessiert an dieser Komponente weil sie erlaubt, zur Laufzeit eine math. Funktion zu parsen und anschließend zu plotten. Ich habe die Demo-Anwendung exprdemo (aus components/plotfunction/demo/expression) ausgeführt. Es klappt im Prinzip alles, bis auf die Skalierung. Wenn ich mir f(x) =x² zeichnen lasse, geht die Funktion nicht furch Null, genausowenig wenn ich mit die Gerade f(x) = x zeichnen lasse. Sie geht ebenfalls nicht durch den Koordinatenursprung. Kann mir jemand auf die Sprünge helfen, wie ich das abstellen kann. Ansonsten wäre plotfunction genau das, was ich brauche.
Ich habe Lazarus 1.2.6 unter Windows 7 installiert.
Vielen Dank.
Re: Komponente plotfunction
Ich habe's gerade ausprobiert, das Ding scheint wirklich Probleme zu haben. Du kannst einen Bug-Report schreiben, damit sich jemand darum kümmert (http://bugs.freepascal.org/my_view_page.php).
Als Alternative würde ich TAChart nehmen und mit fpexprparser kombinieren; über beide gibt es Tutorials im wiki: Du musst dazu allerdings ein bisschen Code schreiben - hält sich aber in Grenzen, ich kann dir gerne helfen.
Als Alternative würde ich TAChart nehmen und mit fpexprparser kombinieren; über beide gibt es Tutorials im wiki: Du musst dazu allerdings ein bisschen Code schreiben - hält sich aber in Grenzen, ich kann dir gerne helfen.
Re: Komponente plotfunction
Danke für den Hinweis. Ich werd's ausprobieren.
Re: Komponente plotfunction
Hallo,
habe jetzt versucht, fpexprpars und TAChart zu kombinieren. Der Parser-Teil funktioniert.
das Problem ist die Darstellung über das OnCalculate event der TFuncSeries. Ich bin entsprechend einer hier gefundenen Anleitung vorgegangen:
Der Compiler bemängelt die Codezeile: FParser.Identifiers[0] := AX;
Error: Incompatible type for arg no. 2: Got "Double", expected "TFPExprIdentifierDef"
habe jetzt versucht, fpexprpars und TAChart zu kombinieren. Der Parser-Teil funktioniert.
das Problem ist die Darstellung über das OnCalculate event der TFuncSeries. Ich bin entsprechend einer hier gefundenen Anleitung vorgegangen:
Code: Alles auswählen
procedure TForm1.Chart1FuncSeries1Calculate(const AX: Double; out AY: Double);
var
res: TFpExpressionResult;
begin
FParser.Identifiers[0] := AX;
res := FParser.Evaluate;
case res of
rtInteger: AY := res.ResInteger;
rtFloat: AY := res.ResFloat;
end;
end;
Error: Incompatible type for arg no. 2: Got "Double", expected "TFPExprIdentifierDef"
Zuletzt geändert von Lori am Do 13. Nov 2014, 18:43, insgesamt 1-mal geändert.
Grund: Highlighter
Grund: Highlighter
Re: Komponente plotfunction
Die Fehlermeldung ist (ausnahmsweise) sehr aussagekräftig: du hast eine Double-Zahl angegeben, der Parser will aber ein TFpExpressionResult. Dieses wiederum hat eine Eigenschaft AsFloat (die du in Lese-Richtung bereits verwendest), die eine Zahl in ein TFPExpressionResult umwandelt:
Das nächste Problem gibt es in der Zeile "case res of". Das Ergebnis der Rechnung, also res, ist wieder ein TFPExpressionResult. In der Case-Anweisung willst du den Ergebnis-Typ abfragen, also muss es "case res.ResultType of" heißen.
Code: Alles auswählen
procedure TForm1.Chart1FuncSeries1Calculate(const AX: Double; out AY: Double);
var
res: TFpExpressionResult;
begin
FParser.Identifiers[0].AsFloat := AX;
res := FParser.Evaluate;
...
Re: Komponente plotfunction
Vielen Dank, jetzt bin ich schon sehr viel weiter.
Der Plot meiner Parabel mit FParser.Expression := 'x*x'; klappt jetzt einwandfrei und sogar richtig skaliert.
Ein weiteres Problem besteht noch beim Versuch dem Parser eine eigendefinierte Funktion beizubringen:
Der Compiler beanstandet die Zeile:
....
Identifiers.AddFunction('tan', 'F', 'F', @ExprTan);
Meldung: Error: Incompatible type for arg no. 4: Got "<address of procedure(var TFPExpressionResult,const TExprParameterArray) is nested;Register>", expected "<procedure variable type of procedure(var TFPExpressionResult,const TExprParameterArray) of object;Register>"
In der Prozedur ExprTan kennt er die Funktion IsNumber() nicht und auch nicht die Variable NaN.
Vielleicht gibt es hier auch noch eine Lösung.
Der Plot meiner Parabel mit FParser.Expression := 'x*x'; klappt jetzt einwandfrei und sogar richtig skaliert.

Ein weiteres Problem besteht noch beim Versuch dem Parser eine eigendefinierte Funktion beizubringen:
Der Compiler beanstandet die Zeile:
....
Identifiers.AddFunction('tan', 'F', 'F', @ExprTan);
Meldung: Error: Incompatible type for arg no. 4: Got "<address of procedure(var TFPExpressionResult,const TExprParameterArray) is nested;Register>", expected "<procedure variable type of procedure(var TFPExpressionResult,const TExprParameterArray) of object;Register>"
In der Prozedur ExprTan kennt er die Funktion IsNumber() nicht und auch nicht die Variable NaN.
Code: Alles auswählen
Procedure ExprTan(Var Result: TFPExpressionResult; Const Args: TExprParameterArray);
var
x: Double;
begin
x := ArgToFloat(Args[0]);
if IsNumber(x) and ((frac(x - 0.5) / pi) <> 0.0) then
Result.resFloat := tan(x)
else
Result.resFloat := NaN;
end;
Zuletzt geändert von Lori am Do 13. Nov 2014, 18:43, insgesamt 1-mal geändert.
Grund: Highlighter
Grund: Highlighter
Re: Komponente plotfunction
Arbeitest du im Delphi-Modus? Dann musst du im AddFunction das @ vor dem Funktionsnamen weglassen. Und die Funktion muss eine alleinstehende Funktion sein, also keine Methode und auch nicht lokal innerhalb einer anderen Funktion deklariert.
Du scheinst den Code in einem älteren Posting von mir gefunden zu haben (http://forum.lazarus.freepascal.org/ind ... #msg111866), dort ist auch die Funktion IsNumber aufgeführt:
In den Posting ist auch eine geänderte Version des Parser, in dem ich die nervigen Exceptions beim Auftreten eines Fehlers herausgeworfen und durch Fehlerwerte (NaN) ersetzt habe.
NaN ("not a number") ist in der Unit Math deklariert.
Aber - und jetzt kommt das ABER: TFunctionSeries kommt mit NaN-Werten überhaupt nicht klar. Also wenn du mit Division durch 0, eingeschränkten Definitionsbereichen rechnen musst, ist es besser statt einer TFunctionSeries eine normale TLineSeries zu verwenden. TLineSeries lässt NaN-Werte in der Kurve aus, ohne abzustürzen. Du musst dann nach jeder Änderung der Funktion oder ihrer Parameter alle darzustellenden Werte auf einmal berechnen und der LineSeries übergeben:
Du scheinst den Code in einem älteren Posting von mir gefunden zu haben (http://forum.lazarus.freepascal.org/ind ... #msg111866), dort ist auch die Funktion IsNumber aufgeführt:
Code: Alles auswählen
function IsNumber(AValue: TExprFloat): Boolean;
begin
result := not (IsNaN(AValue) or IsInfinite(AValue) or IsInfinite(-AValue));
end;;
NaN ("not a number") ist in der Unit Math deklariert.
Aber - und jetzt kommt das ABER: TFunctionSeries kommt mit NaN-Werten überhaupt nicht klar. Also wenn du mit Division durch 0, eingeschränkten Definitionsbereichen rechnen musst, ist es besser statt einer TFunctionSeries eine normale TLineSeries zu verwenden. TLineSeries lässt NaN-Werte in der Kurve aus, ohne abzustürzen. Du musst dann nach jeder Änderung der Funktion oder ihrer Parameter alle darzustellenden Werte auf einmal berechnen und der LineSeries übergeben:
Code: Alles auswählen
procedure TForm1.UpdateSeries(ASeries: TLineseries; xStart, xEnd: Double);
{ Berechnet die in den Parser geladene Funktion mit den aktuellen Parameters komplett neu, zwischen xStart und xEnd).}
{ nicht getestet... }
var
i: Integer;
dx, y: double;
res : TFPExpressionResult;
begin
ASeries.Clear; // bisherige Daten in der Lineseries löschen
dx := Chart1.XImageToScreen(1); // Äquivalent zu 1 Pixel in Einheiten auf der x-Achse
x := xStart; // erster Punkt
while (x <= xEnd) do begin
FParser.Identifiers[0].AsFloat := x;
res := FParser.Evaluate;
case res.ResultType of
rtInteger: y := res.AsInteger;
rtFloat: y := rs.AsFloat;
end;
ASeries.AddXY(x, y); // Werte-Paar in Series eintragen
x := x + dx; // nächster Punkt
end;
end;
Re: Komponente plotfunction
Nein , kein DELPHI-Mode, sondern {$mode objfpc}{$H+}
der Fehler bei
...
Identifiers.AddFunction('tan', 'F', 'F', @ExprTan);
besteht noch: Error: Incompatible type for arg no. 4: Got "<address of procedure(var TFPExpressionResult,const TExprParameterArray) is nested;Register>", expected "<procedure variable type of procedure(var TFPExpressionResult,const TExprParameterArray) of object;Register>"
Die anderen Fehler sind nach Einbinden der unit math sowie der Funktion IsNumber() geheilt.
der Fehler bei
...
Identifiers.AddFunction('tan', 'F', 'F', @ExprTan);
besteht noch: Error: Incompatible type for arg no. 4: Got "<address of procedure(var TFPExpressionResult,const TExprParameterArray) is nested;Register>", expected "<procedure variable type of procedure(var TFPExpressionResult,const TExprParameterArray) of object;Register>"
Die anderen Fehler sind nach Einbinden der unit math sowie der Funktion IsNumber() geheilt.
Re: Komponente plotfunction
Problem ist jetzt gelöst.
Der entscheidende Hinweis:
.... und die Funktion muss eine alleinstehende Funktion sein, also keine Methode und auch nicht lokal innerhalb einer anderen Funktion deklariert.
Jetzt klappt alles.
Nochmals vielen Dank.
Der entscheidende Hinweis:
.... und die Funktion muss eine alleinstehende Funktion sein, also keine Methode und auch nicht lokal innerhalb einer anderen Funktion deklariert.
Jetzt klappt alles.
Nochmals vielen Dank.

Re: Komponente plotfunction
Obwohl... Wenn ich mir den Quelltext von fpexprpars anschaue, dann könnte eine Methode doch funktionieren - das hab ich noch nie getestet....und die Funktion muss eine alleinstehende Funktion sein, also keine Methode...
Code: Alles auswählen
TFPExprFunctionCallBack = Procedure (Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
TFPExprFunctionEvent = Procedure (Var Result : TFPExpressionResult; Const Args : TExprParameterArray) of object; // <-- Methode!
TFPExprIdentifierDefs = Class(TCollection)
...
Function AddFunction(Const AName : ShortString; Const AResultType : Char;
Const AParamTypes : String; ACallBack : TFPExprFunctionCallBack) : TFPExprIdentifierDef;
Function AddFunction(Const AName : ShortString; Const AResultType : Char;
Const AParamTypes : String; ACallBack : TFPExprFunctionEvent) : TFPExprIdentifierDef; // <-- Methode
...
Eine Frage an die Experten: Warum geht das oben eigentlich ohne "overload"?
Re: Komponente plotfunction
Nachdem ich die User-Funktion: Procedure ExprTan(Var Result: TFPExpressionResult; Const Args: TExprParameterArray);
außerhalb der Methoden der Klasse TForm definiert hatte, hat's geklappt.
Zunächst hatte ich die User-Funktion innerhalb einer Methode von TForm stehen: in einer OnClick-Ereignisprozedur, dort habe ich auch den Parser initialisiert und die zu parsenden Variablen festgelegt.
außerhalb der Methoden der Klasse TForm definiert hatte, hat's geklappt.
Zunächst hatte ich die User-Funktion innerhalb einer Methode von TForm stehen: in einer OnClick-Ereignisprozedur, dort habe ich auch den Parser initialisiert und die zu parsenden Variablen festgelegt.
Re: Komponente plotfunction
Wenn ich das richtig verstehe, hattest du folgendes
Ja - das funktioniert nicht. Ist ExprTan dagegen als reguläre Methode irgendeiner Klasse, also z.B. des Formulars, definiert, dann sollte es funktionieren, und natürlich auch als "alleinstehende" Funktion (wie ist dafür der richtige Begriff?)
Code: Alles auswählen
procedure TForm1.Button1Click(Sender: TObject);
Procedure ExprTan(Var Result: TFPExpressionResult; Const Args: TExprParameterArray);
var
x: Double;
begin
x := ArgToFloat(Args[0]);
if IsNumber(x) and ((frac(x - 0.5) / pi) <> 0.0) then
Result.resFloat := tan(x)
else
Result.resFloat := NaN;
end;
begin
...
end;
Code: Alles auswählen
Procedure TForm1.ExprTan(Var Result: TFPExpressionResult; Const Args: TExprParameterArray);
var
x: Double;
begin
x := ArgToFloat(Args[0]);
if IsNumber(x) and ((frac(x - 0.5) / pi) <> 0.0) then
Result.resFloat := tan(x)
else
Result.resFloat := NaN;
end;
Re: Komponente plotfunction
Ja, so hatte ich das.
Inzwischen aber als alleinstehende Prozedur außerhalb des Formulars, als
Procedure ExprTan(Var Result: TFPExpressionResult; Const Args: TExprParameterArray);
Die Variante:
Procedure TForm1.ExprTan(Var Result: TFPExpressionResult; Const Args: TExprParameterArray);
funktioniert aber auch, wie ich gerade ausprobiert habe.
Inzwischen aber als alleinstehende Prozedur außerhalb des Formulars, als
Procedure ExprTan(Var Result: TFPExpressionResult; Const Args: TExprParameterArray);
Die Variante:
Procedure TForm1.ExprTan(Var Result: TFPExpressionResult; Const Args: TExprParameterArray);
funktioniert aber auch, wie ich gerade ausprobiert habe.