YOUSEFUL / DELPHI / SAMPLE PROJECTS

Plot to PNG

Console program Self-checking Product: Atlas Plot · Components: TAtlasPlot, TAtlasPlotSeries, TAtlasPlotAxis

A console program that creates a TAtlasPlot in code, adds a bar series and a line series with a trend line, and exports the chart as PNG, SVG and CSV without ever showing a window. It then verifies the files it wrote and exits with code 0.

What it demonstrates

Build and run

Open PlotToPng.dpr in Delphi 13; the IDE creates the project file. Add the Atlas Plot src folder to the project's search path, then build and run for Win32 or Win64. No packages need to be installed. The program needs no input. It checks its own results and stops with an error if any of them differs from the output below.

The program writes plot-output/orders.png, orders.svg and orders.csv in its current directory.

Expected output

Example
  ok: chart has two data series and one trend line
  ok: a trend line has exactly two points
Exporting to ...\plot-output
  ok: PNG file has content
  ok: PNG is 800 x 500
  ok: SVG root has the requested size
  ok: SVG contains the title
  ok: SVG shows category ticks from the point text
  ok: SVG clips the series to the plot area
  ok: CSV header is series,kind,x,y,text
  ok: CSV has one row per point
  ok: first row is the first order bar
  ok: numbers use a decimal point regardless of locale
  ok: the trend line is exported as a series
  ok: CSV import recreates all three series
  ok: series kinds survive the round trip
  ok: import and export are symmetric
  ok: a value beyond 1E100 is rejected and nothing changes
  ok: no partial files are left behind
PlotToPng: all checks passed

Adapting it

Replace the Months, OrderCounts and Revenue constants with your own data, or load a CSV file with Plot.DataFromCSV(TFile.ReadAllText(...)) instead of calling AddY. Change the extension in SaveToFile to .bmp or .emf for the other image formats, or call RenderTo(Printer.Canvas, ...) to print.

Project files

Download all 34 projects

The ZIP contains every sample project with its README. See getting started for the search path to add.

Source

PlotToPng.dpr

pascal
program PlotToPng;

// Atlas Plot quick start: build a chart in code, export it as PNG, SVG and CSV
// without showing a window, then verify the files.

{$APPTYPE CONSOLE}

uses
  System.SysUtils, System.Classes, System.IOUtils, Vcl.Graphics, Vcl.Imaging.pngimage,
  Atlas.Plot, Atlas.PlotSeries, Atlas.PlotAxis;

const
  Months: array[0..11] of string = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
    'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');
  OrderCounts: array[0..11] of Double = (42, 38, 51, 47, 55, 61, 58, 64, 70, 66, 73, 81);
  Revenue: array[0..11] of Double = (12.5, 11.2, 15.8, 14.1, 17.3, 19.0, 18.2, 20.6, 22.9, 21.4, 24.0, 27.5);

procedure Check(Condition: Boolean; const What: string);
begin
  if not Condition then raise Exception.Create('Check failed: ' + What);
  Writeln('  ok: ', What);
end;

procedure BuildChart(Plot: TAtlasPlot);
var
  Orders, Sales: TAtlasPlotSeries;
  I: Integer;
begin
  Plot.BeginUpdate;
  try
    Plot.Title := 'Orders and revenue, 2026';
    Plot.Footnote := 'Revenue in thousands';
    Plot.LegendPosition := lpBottom;
    Plot.ArgumentAxis.Title := 'Month';
    Plot.ValueAxis.Title := 'Count / thousands';
    Plot.ValueAxis.Grid := True;
    Orders := Plot.AddSeries('Orders', pkBar);
    Sales := Plot.AddSeries('Revenue', pkLine, clMaroon);
    Sales.LineWidth := 3;
    Sales.ShowPoints := True;
    Sales.Marks := pmValue;
    for I := 0 to High(Months) do
    begin
      // AddY numbers the argument 0, 1, 2, ... and the text becomes a category tick.
      Orders.AddY(OrderCounts[I], Months[I]);
      Sales.AddY(Revenue[I]);
    end;
    // A least-squares fit of the revenue; a snapshot that does not follow later edits.
    Plot.AddTrendLine(Sales).LineWidth := 1;
  finally
    Plot.EndUpdate;
  end;
end;

procedure VerifyPng(const FileName: string);
var Png: TPngImage;
begin
  Check(TFile.GetSize(FileName) > 1000, 'PNG file has content');
  Png := TPngImage.Create;
  try
    Png.LoadFromFile(FileName);
    Check((Png.Width = 800) and (Png.Height = 500), 'PNG is 800 x 500');
  finally
    Png.Free;
  end;
end;

procedure VerifySvg(const FileName: string);
var Svg: string;
begin
  Svg := TFile.ReadAllText(FileName, TEncoding.UTF8);
  Check(Pos('<svg xmlns="http://www.w3.org/2000/svg" width="800" height="500"', Svg) > 0, 'SVG root has the requested size');
  Check(Pos('>Orders and revenue, 2026<', Svg) > 0, 'SVG contains the title');
  Check((Pos('>Jan<', Svg) > 0) and (Pos('>Dec<', Svg) > 0), 'SVG shows category ticks from the point text');
  Check(Pos('<clipPath', Svg) > 0, 'SVG clips the series to the plot area');
end;

procedure VerifyCsv(const FileName: string; Plot: TAtlasPlot);
var
  Lines: TArray<string>;
  Copy: TAtlasPlot;
begin
  Lines := TFile.ReadAllLines(FileName, TEncoding.UTF8);
  Check(Lines[0] = 'series,kind,x,y,text', 'CSV header is series,kind,x,y,text');
  // 12 order rows, 12 revenue rows and the two-point trend line.
  Check(Length(Lines) = 1 + 12 + 12 + 2, 'CSV has one row per point');
  Check(Lines[1] = 'Orders,bar,0,42,Jan', 'first row is the first order bar');
  Check(Lines[13] = 'Revenue,line,0,12.5,', 'numbers use a decimal point regardless of locale');
  Check(Pos('Revenue trend,line,', Lines[25]) = 1, 'the trend line is exported as a series');
  Copy := TAtlasPlot.Create(nil);
  try
    Copy.DataFromCSV(TFile.ReadAllText(FileName, TEncoding.UTF8));
    Check(Copy.Series.Count = 3, 'CSV import recreates all three series');
    Check(Copy.Series.Find('orders').Kind = pkBar, 'series kinds survive the round trip');
    Check(Copy.DataToCSV = Plot.DataToCSV, 'import and export are symmetric');
  finally
    Copy.Free;
  end;
end;

procedure Run;
var
  Plot: TAtlasPlot;
  Folder: string;
begin
  Folder := TPath.Combine(GetCurrentDir, 'plot-output');
  TDirectory.CreateDirectory(Folder);
  Plot := TAtlasPlot.Create(nil);
  try
    BuildChart(Plot);
    Check(Plot.Series.Count = 3, 'chart has two data series and one trend line');
    Check(Plot.Series[2].Count = 2, 'a trend line has exactly two points');
    Writeln('Exporting to ', Folder);
    // The extension selects the format; width and height are in pixels.
    Plot.SaveToFile(TPath.Combine(Folder, 'orders.png'), 800, 500);
    Plot.SaveToFile(TPath.Combine(Folder, 'orders.svg'), 800, 500);
    Plot.SaveToFile(TPath.Combine(Folder, 'orders.csv'));
    VerifyPng(TPath.Combine(Folder, 'orders.png'));
    VerifySvg(TPath.Combine(Folder, 'orders.svg'));
    VerifyCsv(TPath.Combine(Folder, 'orders.csv'), Plot);
    // Invalid input raises EAtlasPlotError and leaves the chart unchanged.
    try
      Plot.Series[0].Add(12, 1E101);
      Check(False, 'a value beyond 1E100 must be rejected');
    except
      on EAtlasPlotError do Check(Plot.Series[0].Count = 12, 'a value beyond 1E100 is rejected and nothing changes');
    end;
    Check(Length(TDirectory.GetFiles(Folder, '*.part')) = 0, 'no partial files are left behind');
  finally
    Plot.Free;
  end;
end;

begin
  try
    Run;
    Writeln('PlotToPng: all checks passed');
  except
    on E: Exception do
    begin
      Writeln(E.ClassName, ': ', E.Message);
      ExitCode := 1;
    end;
  end;
end.