TAB-separierte Textdatei in TAChart

Für Fragen von Einsteigern und Programmieranfängern...
Antworten
Theozh
Beiträge: 99
Registriert: So 1. Jul 2012, 10:56

TAB-separierte Textdatei in TAChart

Beitrag von Theozh »

eben mühe ich mich ab, simple ASCII Daten aus einer TAB-separierten Textdatei in einen TAChart zu bringen.
Die Dokumentation http://wiki.freepascal.org/TAChart ist dabei nur sehr begrenzt hilfreich.
Bevor ich nun anfange, die Textdatei zeilenweise in Strings einzulesen und von Hand zu zerstückeln und mit Chart1LineSeries1.AddXY(x,y) Punkt für Punkt meine Kurven zu zeichnen,
wollte ich die Spezialisten fragen ob es
1) eleganter geht, so dass man vielleicht mit Hilfe bereits existierender Prozeduren und Packages z.B. einfach Spalte 2 versus Spalte 4 plotten kann?
2) irgendwo einen verständlichen Beispielcode hat?
Danke für Hinweise!

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

Re: TAB-separierte Textdatei in TAChart

Beitrag von wp_xyz »

die Textdatei zeilenweise in Strings einzulesen und von Hand zu zerstückeln und mit Chart1LineSeries1.AddXY(x,y) Punkt für Punkt meine Kurven zu zeichnen
Genauso geht's.

Wenn du's vom Plotten her einfacher haben willst, musst du ein fertiges Plotprogramm verwenden, z.B. gnuplot, das du per Kommandozeilenscript steuern und dessen Output du dann als Bitmap in dein Programm einbinden kannst. Generell ist das aber wesentlich umständlicher als TAChart zu verwenden. Textzeilen an den Separatoren in Einzelstrings zu zerlegen ist wirklich keine Kunst. Sieh dir dazu vielleicht die Routine "LoadPopulationData" im Code-Teil meines Tutorials http://wiki.lazarus.freepascal.org/TACh ... hartSource an.

Theozh
Beiträge: 99
Registriert: So 1. Jul 2012, 10:56

Re: TAB-separierte Textdatei in TAChart

Beitrag von Theozh »

OK, danke wp_xyz, dann muss ich nicht weiter suchen.

Apropos gnuplot:
eine erste Übersicht über die Rohdaten wollte ich in TAChart realisieren. Für weitere Plots hatte ich vor, mit FreePascal den entsprechenden gnuplot-Code zu generieren und dann von FreePascal gnuplot aufzurufen. Es ist nicht nötig, dass das Bitmap in meinem Programm eingebunden und dargestellt wird. Wenn der Plot in einem separaten gnuplot-Fenster ist, ist das auch in Ordnung.
Gnuplot oder generell andere Programme mit Lazarus starten ist zwar ein anderes Thema, aber hast Du da evtl. schnell einen Tipp oder Link wie das geht und gibt es vielleicht noch Besonderheiten, die man ggf. speziell mit gnuplot beachten müsste?

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

Re: TAB-separierte Textdatei in TAChart

Beitrag von wp_xyz »

Ich habe mir, bevor ich es mit TAChart direkt hinbekommen hatte, Contour-Maps mit gnuplot erzeugen lassen. Das Problem war, dass ich es nicht geschafft hatte, Fehlermeldungen von gnuplot (falls das Script fehlerhaft war - das geht mit gnuplot recht leicht...) mitzuschneiden und im eigenen Programm darzustellen. Erst als ich im englischen Forum über den shellscriptrunner gestolpert bin (http://forum.lazarus.freepascal.org/ind ... 15.15.html), hatte ich eine Lösung, mit der ich zufrieden war.

Hier die wesentlichen Punkte aus meinem Programm (es kommen viele programminterne Variablen vor, aber ich denke, du bekommst eine Idee, wie es geht):

Code: Alles auswählen

 
uses
  ..., shellcommandrunner, ...
 
type
  TMainForm = class(TForm)
    procedure FormCloseQuery(Sender:TObject; var CanClose:boolean);
  private
    FTempPlotScriptFile : string; 
    FTempPlotOutput : string; 
 
    FCmdThread: TShellCommandRunnerThread;
    FCmdThreadErrMsg: String;
    FCmdThreadAborted: Boolean;
    FGnuplotOutput : string;
    FSynHighlighter : TSynGnuplotSyn;
  ....
    procedure CmdThreadOutputAvailable(const pBuffer: PByteArray;
      const pCount: integer);
    procedure CmdThreadTerminate(Sender: TObject);
    function  DoAnalyzeData(out ErrMsg:string) : boolean;
    function  DoExecGnuPlot(out ErrMsg:string) : boolean;
...
end;
 
procedure TMainForm.CmdThreadOutputAvailable(const pBuffer: PByteArray;
  const pCount: integer);
begin
  FCmdThreadErrMsg := FCmdThreadErrMsg + TShellCommandRunner.BufferToString(pBuffer,pCount)
end;
 
procedure TMainForm.CmdThreadTerminate(Sender: TObject);
begin
  if (FCmdThread.Runner.ExitStatus = 259) then begin
    FCmdThreadErrMsg := '';
    FCmdThreadAborted := true;
    // gnuplot is still running, the errmsg is the gnuplot prompt in this case
  end;
 
  if (FCmdThread.Runner.ExitStatus=0) and (FCmdThreadErrMsg = '') then
    PlotImage.Picture.LoadFromFile(FTempPlotOutput)
  else
    PlotImage.Picture.Clear;
  AcKillGnuPlot.Visible := false;
  FCmdThread := nil;
end;
 
procedure TMainForm.FormCloseQuery(Sender:TObject; var CanClose:boolean);
begin
  if Assigned(FCmdThread) then
    FCmdThread.Abort; 
  // ...
end;
 
function TMainForm.DoExecGnuPlot(out ErrMsg:string) : boolean;
var
  List: TStringList;
  crs: TCursor;
  res: integer;
begin
  result := false;
  ErrMsg := '';
 
  if (Settings.GnuPlotExe = '') then begin
    ErrMsg := 'Path to gnuplot binary is not specified. Please use settings dialog.';
    exit;
  end;
  if not FileExists(Settings.GnuPlotExe) then begin
    ErrMsg := Format('Gnuplot binary is not found, file "%s" does not exist. '+
      'Please use settings dialog.', [Settings.GnuPlotexe]);
    exit;
  end;
 
  if FCmdThread <> nil then begin
    ErrMsg := 'Gnuplot still running.';
    exit;
  end;
 
  crs := Screen.Cursor;
  Screen.Cursor := crHourglass;
 
  try
    ScriptMemo.Lines.SaveToFile(FTempPlotScriptFile);
 
    AcKillGnuplot.Visible := true;
 
    FCmdThreadErrMsg := '';
    FCmdThreadAborted := false;
    FCmdThread := TShellCommandRunnerThread.Create;
    // Note: the thread has FreeOnTerminate=true --> no Free here.
    // Also, see OnTerminate event handler for further actions.
    with FCmdThread do begin
      OnOutputAvailable := CmdThreadOutputAvailable;
      OnTerminate := CmdThreadTerminate;
      CommandLine := Format(
        '"%s" -e "set terminal %s size %d,%d truecolor enhanced" -e "set output ''%s''" "%s"',
        [
          Settings.GnuPlotExe,
          Settings.OutputFormat,
          Settings.OutputWidth,
          Settings.OutputHeight,
          UseUnixPathDelimiters(FTempPlotOutput),
          FTempPlotScriptFile
        ]
      );
      Start;
    end;
 
    // this is a replacement for "WaitFor" -- system remains responsive.
    while (FCmdThread <> nil) do
      Application.ProcessMessages;
 
    ErrMsg := FCmdThreadErrMsg;
    result := (ErrMsg = '') and (not FCmdThreadAborted);
  finally
    Screen.Cursor := crs;
  end;
end;
 

Theozh
Beiträge: 99
Registriert: So 1. Jul 2012, 10:56

Re: TAB-separierte Textdatei in TAChart

Beitrag von Theozh »

Danke für den Code, aber ich tue mich schwer, das nachzuvollziehen und zu reproduzieren.
Ich habe versucht alles entsprechend anzupassen und alles "Unnötige" wegzulassen, damit es beim Kompilieren keine Fehlermeldungen gibt.
Die zentrale Stelle ist doch:

Code: Alles auswählen

    FCmdThread := TShellCommandRunnerThread.Create;
    with FCmdThread do
    begin
      OnOutputAvailable := CmdThreadOutputAvailable;    // Fehlermeldung "wrong numbers of parameters"
      OnTerminate := CmdThreadTerminate;                // Fehlermeldung "wrong numbers of parameters"
      CommandLine := 'E:/Programs/gnuplot/bin/wgnuplot.exe';
      Start;
    end;
 
Die Fehlermeldung "wrong numbers of parameters" bekomme ich nicht weg. Welche Parameter soll ich bei

Code: Alles auswählen

OnOutputAvailable := CmdThreadOutputAvailable;
einsetzen?
Wenn ich diese und die nächste Zeile auskommentiere, läuft das Programm zwar, aber es hängt und im Taskmanager sind 50% CPU beansprucht. Kein GnuPlot-Fenster geht auf.
Was fehlt, was mache ich falsch?

Mit der anderen Variante, die in Tutorials beschrieben wird und den einfachen Zeilen

Code: Alles auswählen

procedure TForm1.Button2Click(Sender: TObject);
var Output: string;
begin
  Output:= '';
  RunCommand('E:/Programs/gnuplot/bin/wgnuplot.exe',[''],Output);
end;
 
geht wenigstens ein GnuPlot-Fenster auf. Aber das bringt mich auch nicht weiter, weil ich nicht weiss wie ich ein Kommando, z.B. "plot sin(x)" oder "load TestPlot.plt" übergeben kann.

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: TAB-separierte Textdatei in TAChart

Beitrag von mse »

Theozh hat geschrieben:

Code: Alles auswählen

    FCmdThread := TShellCommandRunnerThread.Create;
    with FCmdThread do
    begin
      OnOutputAvailable := CmdThreadOutputAvailable;    // Fehlermeldung "wrong numbers of parameters"
 
Im objfpc Modus ist zur Angabe einer Methodenadresse "@" notwendig.

Code: Alles auswählen

 
      OnOutputAvailable := @CmdThreadOutputAvailable; 
 

Theozh
Beiträge: 99
Registriert: So 1. Jul 2012, 10:56

Re: TAB-separierte Textdatei in TAChart

Beitrag von Theozh »

Danke, mse! Wieder etwas dazugelernt.
Aber das Programm "hängt" nach wie vor. Da liegt der Hund noch woanders begraben. Hmm, such, such...

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

Re: TAB-separierte Textdatei in TAChart

Beitrag von wp_xyz »

Sorry wegen des @ - ich hatte das Programm damals im Delphi-kompatiblen Modus geschrieben.

Das RunCommand kannte ich noch nicht, in welcher Unit steht es?

Schau dir wegen der Übergabe der gnuplot-Befehle das gnuplot-Manual an. Du kannst die Befehle direkt in der Kommandozeile angeben, jeweils hinter einen "-e" und mit " eingeschlossen, also z.B.

Code: Alles auswählen

 
RunCommand('E:/Programs/gnuplot/bin/wgnuplot.exe', ['-persist', '-e', '"plot sin(x)"'], Output);
 
wobei die Option "-persist" dafür sorgt, dass das gnuplot-Fenster direkt nach dem Aufruf nicht gleich wieder verschwindet.

Spätestens beim Ändern des Achsen-Labels a la

Code: Alles auswählen

set xlabel "x-Achse"
wirst du damit allerdings an die Grenzen stoßen, weil zuviele Anführungsstriche nötig sind.

Daher ist es ratsamer, alle gnuplot-Befehle in einer Script-Datei zusammenzufassen, z.B. "testplot.plt" mit folgendem Inhalt

Code: Alles auswählen

 
set xlabel "x-Achse"
set ylabel "y-Achse"
plot sin(x), cos(x)
 
und diese als letzten Parameter zu übergeben

Code: Alles auswählen

 
RunCommand('E:/Programs/gnuplot/bin/wgnuplot.exe', ['-persist', 'Testplot.plt'], Output);
 
Übrigens, wenn du nicht möchtest, dass das Eingabefester von wgnuplot zusammen mit dem Plot auf dem Bildschirm erscheint, solltest du statt wgnuplot das gnuplot-Programm selbst (ohne das "w") verwenden.

Theozh
Beiträge: 99
Registriert: So 1. Jul 2012, 10:56

Re: TAB-separierte Textdatei in TAChart

Beitrag von Theozh »

Danke, wp, für die nötige GnuPlot-Kommandozeile.
Die Funktion 'RunCommand' steht in der unit Process.
Die Parameter können auch am Stück folgendermaßen übergeben werden:

Code: Alles auswählen

RunCommand('E:/Programs/gnuplot/bin/wgnuplot.exe -persist Test.plt', Output);
Wenn in 'Test.plt' entsprechend korrekter GnuPlot-Code steht wird er ausgeführt und angezeigt :-).
Bei mir ist es allerdings so, dass bei 'gnuplot.exe' zusätzlich ein Konsolenfenster erscheint. Bei 'wgnuplot.exe' erscheint nur das Plot-Fenster (so wie ich es bevorzuge).
Der Nachteil mit 'RunCommand' ist, dass das eigene Programm "blockiert" ist. Erst wenn man das GnuPlot-Fenster schliesst, geht das eigene Programm wieder weiter.
Wenn ich Dich recht verstanden habe, kann man bei der Variante mit 'ShellCommandRunner' mit dem eigenen Programm noch agieren. Deshalb würde ich Deine Version gerne noch zum Laufen bringen.
Denn idealerweise würde ich das GnuPlot-Fenster mit dem eigenen Programm starten, auffrischen und beenden.

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

Re: TAB-separierte Textdatei in TAChart

Beitrag von wp_xyz »

dass bei 'gnuplot.exe' zusätzlich ein Konsolenfenster erscheint
Ja. Das bekommt man weg, indem man den Process mit der Option poNoConsole (siehe "Process.pp") startet. An die Prozess-Instanz kommst du allerdings bei der Verwendung von RunCommand nicht ran. Baue dir am besten das Kommando in deiner eigenen Unit nach und setze die entsprechenden Optionen.

Das große ABER: Leider ist damit das eigene Programm nicht mehr bedienbar...

Generell wirst du ohne einen eigenen Thread für den Prozess nicht glücklich werden. Das ist genau, was der ShellCommandRunner macht. Ich habe mein Programm auf das nötigste komprimiert und in einem separaten Thread veröffentlicht (http://www.lazarusforum.de/viewtopic.php?f=11&t=7139).

Antworten