Recent

Author Topic: TAChart in command line app  (Read 6026 times)

JohnnieK

  • New Member
  • *
  • Posts: 19
TAChart in command line app
« on: February 10, 2017, 11:12:25 am »
Hi

I am trying to get a command line app using TAChart to run on a server without X or GTX (linux server). I have based my app on the nogui demo app, but when I try to run the app it complains "Gtk-WARNING **: cannot open display". So it seems like the app is somehow trying to still use GTK.
I then compiled the nogui demo and it gives the same error.
I can get it working using xvfb, but it is horribly slow. I have done a lot of reading and it seems like it is supposed to work, but I just cannot get it to work. I am using openSUSE Leap 64bit and Lazarus 1.6.2.

Any help will be appreciated.

wp

  • Hero Member
  • *****
  • Posts: 11923
Re: TAChart in command line app
« Reply #1 on: February 10, 2017, 11:31:05 am »
Could you try to post a stack trace?

JohnnieK

  • New Member
  • *
  • Posts: 19
Re: TAChart in command line app
« Reply #2 on: February 14, 2017, 07:24:16 am »
Hi

There is no stack trace. The only error displayed is the following line:
(noguidemo:15232): Gtk-WARNING **: cannot open display:

The app is compiled with debug info on.

Johan

Thaddy

  • Hero Member
  • *****
  • Posts: 14382
  • Sensorship about opinions does not belong here.
Re: TAChart in command line app
« Reply #3 on: February 14, 2017, 08:02:54 am »
Remove tachartlazaruspkg from the uses clause.
Also disable the buildintoolset!
Object Pascal programmers should get rid of their "component fetish" especially with the non-visuals.

JohnnieK

  • New Member
  • *
  • Posts: 19
Re: TAChart in command line app
« Reply #4 on: February 14, 2017, 12:24:05 pm »
Removing tachartlazaruspkg results in the compiler error: "Can't find unit ComponentEditors used by TATransformations" I had to add LCL as a requirement otherwise it did not find unit Interfaces.

Thaddy

  • Hero Member
  • *****
  • Posts: 14382
  • Sensorship about opinions does not belong here.
Re: TAChart in command line app
« Reply #5 on: February 14, 2017, 12:48:27 pm »
Try disabling the buildintoolset first. These contain all kind of visual editors and of course they rely on a window manager/widget set.
I succeeded on arm this morning, but I remember just doing these two. Maybe the other way around? I will check. I may have editted out more units...

As it stands, the example code as provided always relies on a widgetset installed. But it can be eliminated, because apart from the editors all other code does not rely on widget sets. (So it is a bug... but it can be done)
Object Pascal programmers should get rid of their "component fetish" especially with the non-visuals.

JohnnieK

  • New Member
  • *
  • Posts: 19
Re: TAChart in command line app
« Reply #6 on: February 14, 2017, 02:40:29 pm »
Can you explain by what is meant by "disabling the buildintoolset".

Thanx

wp

  • Hero Member
  • *****
  • Posts: 11923
Re: TAChart in command line app
« Reply #7 on: February 15, 2017, 05:00:04 pm »
My problem is that I don't have a suitable testing enviroment at hand, so this debugging session will be some kind of blind search.

Relying on what Thaddy wrote above I modified the nogui demo as follows (changes marked by "// wp"):
Code: Pascal  [Select][+][-]
  1. program noguidemo;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6.   {$IFDEF UNIX}{$IFDEF UseCThreads} cthreads, {$ENDIF}{$ENDIF}
  7.   Interfaces, Classes, //tachartlazaruspkg { you can add units after this },  
  8.                        // wp: REMOVE tachartlazaruspkg
  9.   FPCanvas, FPImage, FPImgCanv,
  10.   TAGraph, TASeries, TADrawerFPCanvas in '../../TADrawerFPCanvas.pas', TADrawerCanvas, TADrawUtils;
  11.  
  12. var
  13.   chart: TChart;
  14.   bs: TBarSeries;
  15.   img: TFPMemoryImage;
  16.   c: TFPImageCanvas;
  17.   d: IChartDrawer;
  18.  
  19. // wp: ADD THIS FUNCTION
  20. function MyInitBuiltinTools(AChart: TChart): TBasicChartToolset;
  21. begin
  22.   Result := nil;
  23. end;
  24.  
  25. begin
  26.   OnInitBuiltinTools := @MyInitBuiltinTools;   // wp: ADD THIS LINE TO DEACTIVATE CHARTTOOLS
  27.  
  28.   chart := TChart.Create(nil);
  29.   chart.LeftAxis.Marks.LabelFont.Name := 'Arial';
  30.   chart.LeftAxis.Marks.LabelFont.Size := 10;
  31.   chart.LeftAxis.Marks.LabelFont.Orientation := 450;
  32.   chart.LeftAxis.Marks.Frame.Visible := true;
  33.   chart.LeftAxis.Marks.Frame.Style := psSolid;
  34.   chart.LeftAxis.Marks.Frame.FPColor := colBlack;
  35.   chart.LeftAxis.Grid.FPColor := colDkGray;
  36.   chart.BottomAxis.Marks.Visible := false;
  37.   chart.BottomAxis.Grid.FPColor := colDkGray;
  38.   chart.Color := $FFA0A0;
  39.   chart.BackColor := $FFFFFF;
  40.   bs := TBarSeries.Create(nil);
  41.   chart.AddSeries(bs);
  42.   bs.AddXY(1, 10);
  43.   bs.AddXY(2, 7);
  44.   bs.AddXY(3,;
  45.   img := TFPMemoryImage.Create(chart.Width, chart.Height);
  46.   c := TFPImageCanvas.Create(img);
  47.   d := TFPCanvasDrawer.Create(c);
  48.   d.DoGetFontOrientation := @CanvasGetFontOrientationFunc;
  49.   chart.Draw(d, Rect(0, 0, chart.Width, chart.Height));
  50.   img.SaveToFile('test.png');
  51.   c.Free;
  52.   img.Free;
  53.   bs.Free;
  54.   chart.Free;
  55. end.

This code removes the initialization of the built-in chart toolset by overriding the OnInitBuiltinTools function pointer - this function is called when the chart is created. Unfortunately TChart assumes that the built-in toolset does exist and crashes, if it does not. So, you must patch also the sources of TAChart:
  • Navigate to the folder components/tachart of your Lazarus installation.
  • Find the file TAGraph.pas
  • Make a backup copy of it
  • Open TAGraph.pas in Lazarus
  • Find the implementation of the procedure TChart.Draw
  • In the var part, immediately below the procedure header, declare a variable tools
Code: Pascal  [Select][+][-]
  1. procedure TChart.Draw((ADrawer: IChartDrawer; const ARect: TRect);
  2. var
  3.   tools: TBasicChartToolset;   // wp: NEW
  4.   ldd: TChartLegendDrawingData;
  5.   s: TBasicChartSeries;
  • Scroll down a bit and find the line "GetToolset.Draw(self, ADrawer)".
  • Replace it by the lines
Code: Pascal  [Select][+][-]
  1.   tools := GetToolset;
  2.   if Assigned(tools) then tools.Draw(self, ADrawer);
  • Save and recompile the package TAChartLazarusPck.
  • Please report back if this fixes your isses, and I'll modify the TAChart repository in the same way as you did.
« Last Edit: February 15, 2017, 05:01:56 pm by wp »

JohnnieK

  • New Member
  • *
  • Posts: 19
Re: TAChart in command line app
« Reply #8 on: February 16, 2017, 07:52:35 am »
Hi wp

Thanx for helping out. I did the changes you suggested, but I still get the GTK error. I have pasted the relevant pieces of code:

noguidemo

Code: Pascal  [Select][+][-]
  1. program noguidemo;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6.   {$IFDEF UNIX}{$IFDEF UseCThreads} cthreads, {$ENDIF}{$ENDIF}
  7.   Interfaces, Classes,
  8.   FPCanvas, FPImage, FPImgCanv,
  9.   TAGraph, TASeries, TADrawerFPCanvas in '../../TADrawerFPCanvas.pas', TADrawerCanvas, TADrawUtils;
  10.  
  11.  
  12.  
  13.  
  14.  
  15. var
  16.   chart: TChart;
  17.   bs: TBarSeries;
  18.   img: TFPMemoryImage;
  19.   c: TFPImageCanvas;
  20.   d: IChartDrawer;
  21.  
  22.   // wp: ADD THIS FUNCTION
  23.   function MyInitBuiltinTools(AChart: TChart): TBasicChartToolset;
  24.   begin
  25.     Result := nil;
  26.   end;
  27.  
  28.  
  29. begin
  30.   OnInitBuiltinTools := @MyInitBuiltinTools;
  31.  
  32.   chart := TChart.Create(nil);
  33.   chart.LeftAxis.Marks.LabelFont.Name := 'Arial';
  34.   chart.LeftAxis.Marks.LabelFont.Size := 10;
  35.   chart.LeftAxis.Marks.LabelFont.Orientation := 450;
  36.   chart.LeftAxis.Marks.Frame.Visible := true;
  37.   chart.LeftAxis.Marks.Frame.Style := psSolid;
  38.   chart.LeftAxis.Marks.Frame.FPColor := colBlack;
  39.   chart.LeftAxis.Grid.FPColor := colDkGray;
  40.   chart.BottomAxis.Marks.Visible := false;
  41.   chart.BottomAxis.Grid.FPColor := colDkGray;
  42.   chart.Color := $FFA0A0;
  43.   chart.BackColor := $FFFFFF;
  44.   bs := TBarSeries.Create(nil);
  45.   chart.AddSeries(bs);
  46.   bs.AddXY(1, 10);
  47.   bs.AddXY(2, 7);
  48.   bs.AddXY(3, 8);
  49.   img := TFPMemoryImage.Create(chart.Width, chart.Height);
  50.   c := TFPImageCanvas.Create(img);
  51.   d := TFPCanvasDrawer.Create(c);
  52.   d.DoGetFontOrientation := @CanvasGetFontOrientationFunc;
  53.   chart.Draw(d, Rect(0, 0, chart.Width, chart.Height));
  54.   img.SaveToFile('test.png');
  55.   c.Free;
  56.   img.Free;
  57.   bs.Free;
  58.   chart.Free;
  59. end.
  60.  

TAGraph

Code: Pascal  [Select][+][-]
  1. procedure TChart.Draw(ADrawer: IChartDrawer; const ARect: TRect);
  2. var
  3.   tools: TBasicChartToolset;
  4.   ldd: TChartLegendDrawingData;
  5.   s: TBasicChartSeries;
  6. begin
  7.   Prepare;
  8.  
  9.   ADrawer.SetRightToLeft(BiDiMode <> bdLeftToRight);
  10.  
  11.   FClipRect := ARect;
  12.   with MarginsExternal do begin
  13.     FClipRect.Left += Left;
  14.     FClipRect.Top += Top;
  15.     FClipRect.Right -= Right;
  16.     FClipRect.Bottom -= Bottom;
  17.   end;
  18.  
  19.   with ClipRect do begin
  20.     FTitle.Measure(ADrawer, 1, Left, Right, Top);
  21.     FFoot.Measure(ADrawer, -1, Left, Right, Bottom);
  22.   end;
  23.  
  24.   ldd.FItems := nil;
  25.   if Legend.Visible then
  26.     ldd := PrepareLegend(ADrawer, FClipRect);
  27.  
  28.   try
  29.     PrepareAxis(ADrawer);
  30.     if Legend.Visible and not Legend.UseSidebar then
  31.       Legend.Prepare(ldd, FClipRect);
  32.     if (FPrevLogicalExtent <> FLogicalExtent) and Assigned(OnExtentChanging) then
  33.       OnExtentChanging(Self);
  34.     ADrawer.DrawingBegin(ARect);
  35.     ADrawer.SetAntialiasingMode(AntialiasingMode);
  36.     Clear(ADrawer, ARect);
  37.     FTitle.Draw(ADrawer);
  38.     FFoot.Draw(ADrawer);
  39.     DrawBackWall(ADrawer);
  40.     DisplaySeries(ADrawer);
  41.     if Legend.Visible then begin
  42.       if Assigned(FOnDrawLegend) then
  43.         FOnDrawlegend(Self, ldd.FDrawer, ldd.FItems, ldd.FItemSize, ldd.FBounds,
  44.           ldd.FColCount, ldd.FRowCount)
  45.       else
  46.         Legend.Draw(ldd);
  47.     end;
  48.   finally
  49.     ldd.FItems.Free;
  50.   end;
  51.   DrawReticule(ADrawer);
  52.   //GetToolset.Draw(Self, ADrawer);
  53.   tools := getToolSet;
  54.   if Assigned(tools) then tools.Draw(self, ADrawer);
  55.  
  56.   for s in Series do
  57.     s.AfterDraw;
  58.  
  59.   if Assigned(OnAfterDraw) then
  60.     OnAfterDraw(Self, ADrawer);
  61.   ADrawer.DrawingEnd;
  62.  
  63.   if FPrevLogicalExtent <> FLogicalExtent then begin
  64.     FExtentBroadcaster.Broadcast(Self);
  65.     if Assigned(OnExtentChanged) then
  66.       OnExtentChanged(Self);
  67.     FPrevLogicalExtent := FLogicalExtent;
  68.   end;
  69.  
  70.   // Undo changes made by the drawer (mainly for printing). The user may print
  71.   // something else after the chart and, for example, would not expect the font
  72.   // to be rotated (Fix for issue #0027163) or the pen to be in xor mode.
  73.   ADrawer.ResetFont;
  74.   ADrawer.SetXor(false);
  75.   ADrawer.PrepareSimplePen(clBlack);     // resets canvas pen mode to pmCopy
  76.   ADrawer.SetPenParams(psSolid, clDefault);
  77.   ADrawer.SetBrushParams(bsSolid, clWhite);
  78.   ADrawer.SetAntialiasingMode(amDontCare);
  79. end;
  80.  
  81.  
  82.  

Johan

wp

  • Hero Member
  • *****
  • Posts: 11923
Re: TAChart in command line app
« Reply #9 on: February 16, 2017, 09:36:17 am »
Thanks. Your code looks correct.

So, we must do another approach. Please make a backup copy the original TAGraph.pas. Then, in the TChart.Draw method add a "writeln()" after every code line, something like this:

Code: Pascal  [Select][+][-]
  1. procedure TChart.Draw(ADrawer: IChartDrawer; const ARect: TRect);
  2. var
  3.   tools: TBasicChartToolset;
  4.   ldd: TChartLegendDrawingData;
  5.   s: TBasicChartSeries;
  6. begin
  7.   WriteLn('ENTER Draw');
  8.   Prepare;
  9.   WriteLn('After Prepare');
  10.  
  11.   ADrawer.SetRightToLeft(BiDiMode <> bdLeftToRight);
  12.  
  13.   FClipRect := ARect;
  14.   with MarginsExternal do begin
  15.     FClipRect.Left += Left;
  16.     FClipRect.Top += Top;
  17.     FClipRect.Right -= Right;
  18.     FClipRect.Bottom -= Bottom;
  19.   end;
  20.  
  21.   with ClipRect do begin
  22.     FTitle.Measure(ADrawer, 1, Left, Right, Top);
  23.     WriteLn('After FTitle.Measure');
  24.     FFoot.Measure(ADrawer, -1, Left, Right, Bottom);
  25.     WriteLn('After FFoot.Measure');
  26.   end;
  27.  
  28.   ldd.FItems := nil;
  29.   if Legend.Visible then
  30.     ldd := PrepareLegend(ADrawer, FClipRect);
  31.   WriteLn('After PrepareLegend');
  32.  
  33.   try
  34.     PrepareAxis(ADrawer);
  35.     WriteLn('After PrepareAxis');
  36.     if Legend.Visible and not Legend.UseSidebar then
  37.       Legend.Prepare(ldd, FClipRect);
  38.       WriteLn('After Legend.Prepare');
  39.     if (FPrevLogicalExtent <> FLogicalExtent) and Assigned(OnExtentChanging) then
  40.       OnExtentChanging(Self);
  41.     WriteLn('After OnExtentChanging');
  42.     ADrawer.DrawingBegin(ARect);
  43.     WriteLn('After DrawingBegin');
  44.     ADrawer.SetAntialiasingMode(AntialiasingMode);
  45.     WriteLn('After SetAntialiasingMode');
  46.     Clear(ADrawer, ARect);
  47.     WriteLn('After Clear');
  48.     FTitle.Draw(ADrawer);
  49.     WriteLn('After FTitle.Draw');
  50.     FFoot.Draw(ADrawer);
  51.     WriteLn('After FFoot.Draw');
  52.     DrawBackWall(ADrawer);
  53.     WriteLn('After DrawBackwall'):
  54.     DisplaySeries(ADrawer);
  55.     WriteLn('After DisplaySeries');
  56.  
  57.   // ... etc

Then recompile the package TAChartLazarusPkg and compile & run the demo again. Does the first WriteLn message appear at all? If not the bug is outside the Draw method. If some WriteLn text does appear then report the last message, the next statement will be the faulty one.

After the test, restore the backup copy of TAGraph.pas and rebuild the package again.
« Last Edit: February 16, 2017, 11:19:10 am by wp »

JohnnieK

  • New Member
  • *
  • Posts: 19
Re: TAChart in command line app
« Reply #10 on: February 16, 2017, 12:02:25 pm »
Hi WP

I managed to get it working. In the project options under "Config and target" I had to change the Widgetset from GTK2 to nogui. That and the changes you gave me seemed to have done the trick. Thanx for all the help.

Johan

wp

  • Hero Member
  • *****
  • Posts: 11923
Re: TAChart in command line app
« Reply #11 on: February 16, 2017, 12:16:06 pm »
Thank you, that's the best solution. In r54167, I modified the nogui demo such that the nogui widgetset is active by default.

 

TinyPortal © 2005-2018