TAChart in command line app lazarus / 55-TAChart ---------------------------------------------------------------------- JohnnieK · 2017-02-10 10:12 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 · 2017-02-10 10:31 Could you try to post a stack trace? ---------------------------------------------------------------------- JohnnieK · 2017-02-14 06:24 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 · 2017-02-14 07:02 Remove tachartlazaruspkg from the uses clause. Also disable the buildintoolset! ---------------------------------------------------------------------- JohnnieK · 2017-02-14 11:24 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 · 2017-02-14 11:48 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) ---------------------------------------------------------------------- JohnnieK · 2017-02-14 13:40 Can you explain by what is meant by "disabling the buildintoolset". Thanx ---------------------------------------------------------------------- wp · 2017-02-15 16:00 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program noguidemo; {$mode objfpc}{$H+} uses  {$IFDEF UNIX}{$IFDEF UseCThreads} cthreads, {$ENDIF}{$ENDIF}  Interfaces, Classes, //tachartlazaruspkg { you can add units after this },                          // wp: REMOVE tachartlazaruspkg  FPCanvas, FPImage, FPImgCanv,  TAGraph, TASeries, TADrawerFPCanvas in '../../TADrawerFPCanvas.pas', TADrawerCanvas, TADrawUtils; var  chart: TChart;  bs: TBarSeries;  img: TFPMemoryImage;  c: TFPImageCanvas;  d: IChartDrawer; // wp: ADD THIS FUNCTIONfunction MyInitBuiltinTools(AChart: TChart): TBasicChartToolset;begin  Result := nil;end; begin  OnInitBuiltinTools := @MyInitBuiltinTools;   // wp: ADD THIS LINE TO DEACTIVATE CHARTTOOLS   chart := TChart.Create(nil);  chart.LeftAxis.Marks.LabelFont.Name := 'Arial';  chart.LeftAxis.Marks.LabelFont.Size := 10;  chart.LeftAxis.Marks.LabelFont.Orientation := 450;  chart.LeftAxis.Marks.Frame.Visible := true;  chart.LeftAxis.Marks.Frame.Style := psSolid;  chart.LeftAxis.Marks.Frame.FPColor := colBlack;  chart.LeftAxis.Grid.FPColor := colDkGray;  chart.BottomAxis.Marks.Visible := false;  chart.BottomAxis.Grid.FPColor := colDkGray;  chart.Color := $FFA0A0;  chart.BackColor := $FFFFFF;  bs := TBarSeries.Create(nil);  chart.AddSeries(bs);  bs.AddXY(1, 10);  bs.AddXY(2, 7);  bs.AddXY(3,;  img := TFPMemoryImage.Create(chart.Width, chart.Height);  c := TFPImageCanvas.Create(img);  d := TFPCanvasDrawer.Create(c);  d.DoGetFontOrientation := @CanvasGetFontOrientationFunc;  chart.Draw(d, Rect(0, 0, chart.Width, chart.Height));  img.SaveToFile('test.png');  c.Free;  img.Free;  bs.Free;  chart.Free;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.DrawIn the var part, immediately below the procedure header, declare a variable toolsCode: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TChart.Draw((ADrawer: IChartDrawer; const ARect: TRect);var  tools: TBasicChartToolset;   // wp: NEW   ldd: TChartLegendDrawingData;  s: TBasicChartSeries;Scroll down a bit and find the line "GetToolset.Draw(self, ADrawer)".Replace it by the linesCode: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   tools := GetToolset;  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. ---------------------------------------------------------------------- JohnnieK · 2017-02-16 06:52 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program noguidemo; {$mode objfpc}{$H+} uses  {$IFDEF UNIX}{$IFDEF UseCThreads} cthreads, {$ENDIF}{$ENDIF}  Interfaces, Classes,  FPCanvas, FPImage, FPImgCanv,  TAGraph, TASeries, TADrawerFPCanvas in '../../TADrawerFPCanvas.pas', TADrawerCanvas, TADrawUtils;     var  chart: TChart;  bs: TBarSeries;  img: TFPMemoryImage;  c: TFPImageCanvas;  d: IChartDrawer;   // wp: ADD THIS FUNCTION  function MyInitBuiltinTools(AChart: TChart): TBasicChartToolset;  begin    Result := nil;  end;  begin  OnInitBuiltinTools := @MyInitBuiltinTools;   chart := TChart.Create(nil);  chart.LeftAxis.Marks.LabelFont.Name := 'Arial';  chart.LeftAxis.Marks.LabelFont.Size := 10;  chart.LeftAxis.Marks.LabelFont.Orientation := 450;  chart.LeftAxis.Marks.Frame.Visible := true;  chart.LeftAxis.Marks.Frame.Style := psSolid;  chart.LeftAxis.Marks.Frame.FPColor := colBlack;  chart.LeftAxis.Grid.FPColor := colDkGray;  chart.BottomAxis.Marks.Visible := false;  chart.BottomAxis.Grid.FPColor := colDkGray;  chart.Color := $FFA0A0;  chart.BackColor := $FFFFFF;  bs := TBarSeries.Create(nil);  chart.AddSeries(bs);  bs.AddXY(1, 10);  bs.AddXY(2, 7);  bs.AddXY(3, 8);  img := TFPMemoryImage.Create(chart.Width, chart.Height);  c := TFPImageCanvas.Create(img);  d := TFPCanvasDrawer.Create(c);  d.DoGetFontOrientation := @CanvasGetFontOrientationFunc;  chart.Draw(d, Rect(0, 0, chart.Width, chart.Height));  img.SaveToFile('test.png');  c.Free;  img.Free;  bs.Free;  chart.Free;end.  TAGraph Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TChart.Draw(ADrawer: IChartDrawer; const ARect: TRect);var  tools: TBasicChartToolset;  ldd: TChartLegendDrawingData;  s: TBasicChartSeries;begin  Prepare;   ADrawer.SetRightToLeft(BiDiMode <> bdLeftToRight);   FClipRect := ARect;  with MarginsExternal do begin  FClipRect.Left += Left;    FClipRect.Top += Top;    FClipRect.Right -= Right;    FClipRect.Bottom -= Bottom;  end;   with ClipRect do begin    FTitle.Measure(ADrawer, 1, Left, Right, Top);    FFoot.Measure(ADrawer, -1, Left, Right, Bottom);  end;   ldd.FItems := nil;  if Legend.Visible then    ldd := PrepareLegend(ADrawer, FClipRect);   try    PrepareAxis(ADrawer);    if Legend.Visible and not Legend.UseSidebar then      Legend.Prepare(ldd, FClipRect);    if (FPrevLogicalExtent <> FLogicalExtent) and Assigned(OnExtentChanging) then      OnExtentChanging(Self);    ADrawer.DrawingBegin(ARect);    ADrawer.SetAntialiasingMode(AntialiasingMode);    Clear(ADrawer, ARect);    FTitle.Draw(ADrawer);    FFoot.Draw(ADrawer);    DrawBackWall(ADrawer);    DisplaySeries(ADrawer);    if Legend.Visible then begin      if Assigned(FOnDrawLegend) then        FOnDrawlegend(Self, ldd.FDrawer, ldd.FItems, ldd.FItemSize, ldd.FBounds,          ldd.FColCount, ldd.FRowCount)      else        Legend.Draw(ldd);    end;  finally    ldd.FItems.Free;  end;  DrawReticule(ADrawer);  //GetToolset.Draw(Self, ADrawer);  tools := getToolSet;  if Assigned(tools) then tools.Draw(self, ADrawer);   for s in Series do    s.AfterDraw;   if Assigned(OnAfterDraw) then    OnAfterDraw(Self, ADrawer);  ADrawer.DrawingEnd;   if FPrevLogicalExtent <> FLogicalExtent then begin    FExtentBroadcaster.Broadcast(Self);    if Assigned(OnExtentChanged) then      OnExtentChanged(Self);    FPrevLogicalExtent := FLogicalExtent;  end;   // Undo changes made by the drawer (mainly for printing). The user may print  // something else after the chart and, for example, would not expect the font  // to be rotated (Fix for issue #0027163) or the pen to be in xor mode.  ADrawer.ResetFont;  ADrawer.SetXor(false);  ADrawer.PrepareSimplePen(clBlack);     // resets canvas pen mode to pmCopy  ADrawer.SetPenParams(psSolid, clDefault);  ADrawer.SetBrushParams(bsSolid, clWhite);  ADrawer.SetAntialiasingMode(amDontCare);end;    Johan ---------------------------------------------------------------------- wp · 2017-02-16 08:36 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TChart.Draw(ADrawer: IChartDrawer; const ARect: TRect);var  tools: TBasicChartToolset;  ldd: TChartLegendDrawingData;  s: TBasicChartSeries;begin  WriteLn('ENTER Draw');  Prepare;  WriteLn('After Prepare');   ADrawer.SetRightToLeft(BiDiMode <> bdLeftToRight);   FClipRect := ARect;  with MarginsExternal do begin  FClipRect.Left += Left;    FClipRect.Top += Top;    FClipRect.Right -= Right;    FClipRect.Bottom -= Bottom;  end;   with ClipRect do begin    FTitle.Measure(ADrawer, 1, Left, Right, Top);    WriteLn('After FTitle.Measure');    FFoot.Measure(ADrawer, -1, Left, Right, Bottom);    WriteLn('After FFoot.Measure');  end;   ldd.FItems := nil;  if Legend.Visible then    ldd := PrepareLegend(ADrawer, FClipRect);  WriteLn('After PrepareLegend');   try    PrepareAxis(ADrawer);    WriteLn('After PrepareAxis');    if Legend.Visible and not Legend.UseSidebar then      Legend.Prepare(ldd, FClipRect);      WriteLn('After Legend.Prepare');    if (FPrevLogicalExtent <> FLogicalExtent) and Assigned(OnExtentChanging) then      OnExtentChanging(Self);    WriteLn('After OnExtentChanging');    ADrawer.DrawingBegin(ARect);    WriteLn('After DrawingBegin');    ADrawer.SetAntialiasingMode(AntialiasingMode);    WriteLn('After SetAntialiasingMode');    Clear(ADrawer, ARect);    WriteLn('After Clear');    FTitle.Draw(ADrawer);    WriteLn('After FTitle.Draw');    FFoot.Draw(ADrawer);    WriteLn('After FFoot.Draw');    DrawBackWall(ADrawer);    WriteLn('After DrawBackwall'):    DisplaySeries(ADrawer);    WriteLn('After DisplaySeries');   // ... 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. ---------------------------------------------------------------------- JohnnieK · 2017-02-16 11:02 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 · 2017-02-16 11:16 Thank you, that's the best solution. In r54167, I modified the nogui demo such that the nogui widgetset is active by default.