YOUSEFUL / DELPHI / SAMPLE PROJECTS
Grouped report to HTML
The Atlas Analytics quick start and the project of the
tutorial. A
console program builds a small sales table, defines a grouped
TAtlasReportDefinition, runs it, writes the HTML export next to the executable,
round-trips the definition through JSON and prepares a paginated print
document. No window is shown.
What it demonstrates
GroupBywith sum, average and count measures over one dimension, andExecutereturning a caller-ownedTAtlasTable.SetFiltersnarrowing the rows before grouping while the source table stays unchanged.RenderHTMLproducing a complete, escaped page (title, column headers, right-aligned numeric cells, a row count).ToJSON/LoadJSONround trip of the definition.PreparePrintreturning aTAtlasPrintDocumentwith one page.SelectFieldsturning the same definition into a detail report.
Build and run
Open GroupedReport.dpr in Delphi 13; the IDE creates the project file. Add the Atlas Analytics src folder and the Atlas VCL 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.
Expected output
Grouped report: 3 rows, 5 columns
East revenue 24400 units 20 average 12200 orders 2
West revenue 32100 units 27 average 16050 orders 2
North revenue 16800 units 14 average 8400 orders 2
Wrote ...\GroupedReport\sales-by-region.html (1453 characters)
Definition: {"version":1,"title":"Sales by region","subtitle":"Revenue, units and average sale per region","fields":[],"dimensions":["Region"],"measures":[{"field":"Revenue","caption":"Revenue","kind":1},{"field":"Units","caption":"Units","kind":1},{"field":"Revenue","caption":"Average sale","kind":4},{"field":"","caption":"Orders","kind":0}],"filters":[]}
Print document: 1 page, title "Sales by region"
Detail report: 3 Desktop rows
PASSOpen sales-by-region.html in a browser to see the report. The exit code is 0;
any mismatch prints Check failed: ... and exits with 1.
Adapting it
Change SalesTable to load your own data (ImportCSV accepts a CSV string
whose header matches the columns), then change the dimension and measures in
GroupBy. Every measure except amCount needs an ackNumber column, and every
result column (dimensions and measure captions) needs a distinct name.
Project files
The ZIP contains every sample project with its README. See getting started for the search path to add.
Source
GroupedReport.dpr
program GroupedReport;
// Atlas Analytics quick start: define a grouped report, run it over a
// TAtlasTable, export it to HTML and prepare a print document, all without
// a window. The program checks its own results and exits with code 1 on any
// mismatch.
{$APPTYPE CONSOLE}
uses
System.SysUtils, System.IOUtils,
Atlas.Data, Atlas.Analytics, Atlas.Reports, Atlas.Printing;
function SalesTable: TAtlasTable;
begin
Result := TAtlasTable.Create(nil);
try
Result.AddColumn('Region');
Result.AddColumn('Product');
Result.AddColumn('Revenue', ackNumber);
Result.AddColumn('Units', ackNumber);
Result.AddColumn('Booked', ackDate);
Result.ImportCSV(
'Region,Product,Revenue,Units,Booked' + sLineBreak +
'East,Desktop,18000,12,2026-09-01' + sLineBreak +
'West,Desktop,22500,15,2026-09-02' + sLineBreak +
'East,Service,6400,8,2026-09-03' + sLineBreak +
'North,Desktop,12000,8,2026-09-04' + sLineBreak +
'West,Service,9600,12,2026-09-05' + sLineBreak +
'North,Service,4800,6,2026-09-06');
except
Result.Free;
raise;
end;
end;
procedure Check(Condition: Boolean; const Message: string);
begin
if not Condition then
raise Exception.Create('Check failed: ' + Message);
end;
procedure Expect(const Expected, Actual, What: string);
begin
Check(Expected = Actual, What + ': expected "' + Expected + '", got "' + Actual + '"');
end;
var
Source, Summary, Detail: TAtlasTable;
Report: TAtlasReportDefinition;
Document: TAtlasPrintDocument;
HTML, FileName, JSON: string;
I: Integer;
begin
try
Source := SalesTable;
Report := TAtlasReportDefinition.Create(nil);
try
Report.Title := 'Sales by region';
Report.Subtitle := 'Revenue, units and average sale per region';
Report.GroupBy(['Region'], [
TAtlasMeasure.Create('Revenue', 'Revenue', amSum),
TAtlasMeasure.Create('Units', 'Units', amSum),
TAtlasMeasure.Create('Revenue', 'Average sale', amAverage),
TAtlasMeasure.Create('', 'Orders', amCount)]);
// 1. Execute returns a new table that the caller owns.
Summary := Report.Execute(Source);
try
Writeln('Grouped report: ', Summary.RowCount, ' rows, ', Summary.ColumnCount, ' columns');
for I := 0 to Summary.RowCount - 1 do
Writeln(' ', Summary.Cell(0, I):6, ' revenue ', Summary.Cell(1, I):6,
' units ', Summary.Cell(2, I):3, ' average ', Summary.Cell(3, I):6,
' orders ', Summary.Cell(4, I));
Check(Summary.RowCount = 3, 'three regions');
Expect('East', Summary.Cell(0, 0), 'groups keep first-seen order');
Expect('24400', Summary.Cell(1, 0), 'East revenue');
Expect('20', Summary.Cell(2, 0), 'East units');
Expect('12200', Summary.Cell(3, 0), 'East average sale');
Expect('2', Summary.Cell(4, 0), 'East orders');
Expect('32100', Summary.Cell(1, 1), 'West revenue');
Expect('16800', Summary.Cell(1, 2), 'North revenue');
finally
Summary.Free;
end;
// 2. Filters narrow the rows before they are grouped; the source is untouched.
Report.SetFilters([TAtlasFilter.Create('Revenue', '5000', afGreaterEqual)]);
Summary := Report.Execute(Source);
try
Expect('12000', Summary.Cell(1, 2), 'North revenue with filter');
Expect('1', Summary.Cell(4, 2), 'North orders with filter');
finally
Summary.Free;
end;
Check(Source.RowCount = 6, 'source table unchanged');
// 3. RenderHTML writes a complete, escaped, printable page.
Report.SetFilters([]);
HTML := Report.RenderHTML(Source);
FileName := TPath.Combine(TPath.GetDirectoryName(ParamStr(0)), 'sales-by-region.html');
TFile.WriteAllText(FileName, HTML, TEncoding.UTF8);
Check(Pos('<title>Sales by region</title>', HTML) > 0, 'HTML title');
Check(Pos('<th scope="col">Average sale</th>', HTML) > 0, 'HTML column header');
Check(Pos('<td class="number">24400</td>', HTML) > 0, 'HTML numeric cell');
Check(Pos('<p>3 result rows</p>', HTML) > 0, 'HTML row count');
Writeln('Wrote ', FileName, ' (', Length(HTML), ' characters)');
// 4. The definition round-trips through JSON.
JSON := Report.ToJSON;
Report.LoadJSON(JSON);
Expect(JSON, Report.ToJSON, 'JSON round trip');
Writeln('Definition: ', JSON);
// 5. PreparePrint returns a paginated document the caller owns.
Document := Report.PreparePrint(Source);
try
Check(Document.PageCount = 1, 'one printed page');
Writeln('Print document: ', Document.PageCount, ' page, title "', Document.Title, '"');
finally
Document.Free;
end;
// 6. SelectFields turns the same definition into a detail report.
Report.SelectFields(['Region', 'Product', 'Revenue']);
Report.SetFilters([TAtlasFilter.Create('Product', 'Desktop')]);
Detail := Report.Execute(Source);
try
Check(Detail.RowCount = 3, 'three Desktop rows');
Check(Detail.ColumnCount = 3, 'three selected columns');
Writeln('Detail report: ', Detail.RowCount, ' Desktop rows');
finally
Detail.Free;
end;
finally
Report.Free;
Source.Free;
end;
Writeln('PASS');
except
on E: Exception do
begin
Writeln(E.ClassName, ': ', E.Message);
ExitCode := 1;
end;
end;
end.