Komponente plotfunction

Rund um die LCL und andere Komponenten
Antworten
rds
Beiträge: 17
Registriert: Di 4. Nov 2014, 18:32

Komponente plotfunction

Beitrag von rds »

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.

wp_xyz
Beiträge: 5189
Registriert: Fr 8. Apr 2011, 09:01

Re: Komponente plotfunction

Beitrag von wp_xyz »

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.

rds
Beiträge: 17
Registriert: Di 4. Nov 2014, 18:32

Re: Komponente plotfunction

Beitrag von rds »

Danke für den Hinweis. Ich werd's ausprobieren.

rds
Beiträge: 17
Registriert: Di 4. Nov 2014, 18:32

Re: Komponente plotfunction

Beitrag von rds »

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:

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;
Der Compiler bemängelt die Codezeile: FParser.Identifiers[0] := AX;
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

wp_xyz
Beiträge: 5189
Registriert: Fr 8. Apr 2011, 09:01

Re: Komponente plotfunction

Beitrag von wp_xyz »

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:

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;
...
 
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.

rds
Beiträge: 17
Registriert: Di 4. Nov 2014, 18:32

Re: Komponente plotfunction

Beitrag von rds »

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. :D

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;
 
Vielleicht gibt es hier auch noch eine Lösung.
Zuletzt geändert von Lori am Do 13. Nov 2014, 18:43, insgesamt 1-mal geändert.
Grund: Highlighter

wp_xyz
Beiträge: 5189
Registriert: Fr 8. Apr 2011, 09:01

Re: Komponente plotfunction

Beitrag von wp_xyz »

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:

Code: Alles auswählen

 
function IsNumber(AValue: TExprFloat): Boolean;
begin
  result := not (IsNaN(AValue) or IsInfinite(AValue) or IsInfinite(-AValue));
end;;
 
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:

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;
 

rds
Beiträge: 17
Registriert: Di 4. Nov 2014, 18:32

Re: Komponente plotfunction

Beitrag von rds »

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.

rds
Beiträge: 17
Registriert: Di 4. Nov 2014, 18:32

Re: Komponente plotfunction

Beitrag von rds »

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. :D

wp_xyz
Beiträge: 5189
Registriert: Fr 8. Apr 2011, 09:01

Re: Komponente plotfunction

Beitrag von wp_xyz »

...und die Funktion muss eine alleinstehende Funktion sein, also keine Methode...
Obwohl... Wenn ich mir den Quelltext von fpexprpars anschaue, dann könnte eine Methode doch funktionieren - das hab ich noch nie getestet.

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
    ...
 
Wie war bei dir die User-Funktion deklariert?

Eine Frage an die Experten: Warum geht das oben eigentlich ohne "overload"?

rds
Beiträge: 17
Registriert: Di 4. Nov 2014, 18:32

Re: Komponente plotfunction

Beitrag von rds »

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.

wp_xyz
Beiträge: 5189
Registriert: Fr 8. Apr 2011, 09:01

Re: Komponente plotfunction

Beitrag von wp_xyz »

Wenn ich das richtig verstehe, hattest du folgendes

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;
 
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.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;
 

rds
Beiträge: 17
Registriert: Di 4. Nov 2014, 18:32

Re: Komponente plotfunction

Beitrag von rds »

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.

Antworten