YOUSEFUL / DELPHI / COMPONENTS

TAtlasReportDefinition

A serializable report definition: a detail projection or a grouped summary over a TAtlasTable, run into a table, an HTML page or a print document.

Non-visual component Palette: Atlas Analytics Unit Atlas.Reports · Inherits TComponent · Product Atlas Analytics

Description

A non-visual component on the Atlas Analytics palette page that describes a report and runs it over the current view of a TAtlasTable.

A definition has one of two shapes:

SetFilters adds conjunctive TAtlasFilter conditions that apply before grouping. Execute returns the result as a new table, RenderHTML as a self-contained HTML page, and PreparePrint as a paginated TAtlasPrintDocument. The work is done by TAtlasAnalytics, so filters and measures behave exactly as described there. The source table is never modified.

Field names are not checked when you define the report or load it from JSON; they are checked every time it runs, against the table you pass. Only Title and Subtitle are published, so a report dropped on a form stores just those two properties: set the fields, groups and filters in code or load them with LoadJSON. No property reads back the fields, dimensions or measures; ToJSON shows them.

The unit also declares function AtlasEscapeHTML(const Text: string): string;, which replaces &, <, >, " and ' with HTML entities. RenderHTML uses it for every title, caption and value.

Reports are deterministic. Not implemented: a visual band designer, XLSX or PDF export, scripting, a database query language and AI-assisted reporting (pending a provider decision; no service is called). The class has no locking; do not change the source table from another thread while a report runs.

Members

MemberKindWhat it does
CreateconstructorCreates an empty definition: Title is 'Atlas report', Subtitle is empty, and there are no fields, groups or filters, so Execute returns a copy of every column and row of the source's current view.
SelectFieldsmethodMakes the definition a detail report that returns the listed fields in this order; an empty array returns every field in source order.
GroupBymethodMakes the definition a summary report: the result has one column per dimension, then one ackNumber column per measure named by its caption, and one row per distinct combination of dimension values in first-seen order.
SetFiltersmethodReplaces the filters.
ExecutemethodRuns the definition over the current view of Source (the table's own filter and sort apply) and returns a new, unowned TAtlasTable that the caller must free.
RenderHTMLmethodRuns Execute and returns a complete HTML5 page with no external resources: Title in <title> and <h1>, Subtitle in a paragraph, a table with one <th scope="col"> per result column, numeric cells right-aligned with class="number", and a final N result rows paragraph.
PreparePrintmethodRuns Execute and returns a new paginated TAtlasPrintDocument that the caller must free.
ToJSONmethodReturns the definition as a version 1 JSON object with title, subtitle, fields, dimensions, measures (each field, caption, and kind as the ordinal of TAtlasMeasureKind) and filters (each field, value, and operator as the ordinal of TAtlasFilterOperator).
LoadJSONmethodReplaces the whole definition with one written by ToJSON, after validating all of it: the text must be a JSON object with version 1, string title and subtitle, arrays fields and dimensions of non-blank strings, measures objects with a string field, a non-blank caption and a kind from 0 to 4, and filters objects with string field and value and an operator from 0 to 6.
FilterspropertyA copy of the current filters.
TitlepropertyThe report heading: the HTML <title> and <h1>, and the title of the document from PreparePrint.
SubtitlepropertyA paragraph below the heading in the HTML page (line breaks are kept).

Constructors

Create constructor

constructor Create(AOwner: TComponent); override;

Creates an empty definition: Title is 'Atlas report', Subtitle is empty, and there are no fields, groups or filters, so Execute returns a copy of every column and row of the source's current view. Pass nil and free the definition yourself, or pass an owner such as a form.

pascal
Report := TAtlasReportDefinition.Create(nil);

Properties

Filters property

property Filters: TArray<TAtlasFilter> read GetFilters;

A copy of the current filters. Changing the returned array does not change the report; call SetFilters instead.

pascal
for Filter in Report.Filters do
  Writeln(Filter.Field, ' ', Filter.Value);

Title property

property Title: string read FTitle write FTitle;

The report heading: the HTML <title> and <h1>, and the title of the document from PreparePrint. Defaults to 'Atlas report'. Published, so it is stored with the form.

Subtitle property

property Subtitle: string read FSubtitle write FSubtitle;

A paragraph below the heading in the HTML page (line breaks are kept). Empty by default. Not used by PreparePrint. Published.

Methods

SelectFields method

procedure SelectFields(const Fields: array of string);

Makes the definition a detail report that returns the listed fields in this order; an empty array returns every field in source order. Clears any dimensions and measures set by GroupBy; filters are kept. Names match columns without regard to case and are checked when the report runs: an unknown name raises EAtlasAnalyticsError Unknown field: ..., and a field listed twice raises EAtlasDataError Duplicate column name: ....

pascal
Report.SelectFields(['Booked', 'Product', 'Revenue']);

GroupBy method

procedure GroupBy(const Dimensions: array of string; const Measures: array of TAtlasMeasure);

Makes the definition a summary report: the result has one column per dimension, then one ackNumber column per measure named by its caption, and one row per distinct combination of dimension values in first-seen order. Grouping uses the exact cell text, so East and east are separate groups. An empty Dimensions array gives one total row. Clears the field list set by SelectFields; filters are kept. Raises EAtlasAnalyticsError A grouped report needs a measure when Measures is empty, and leaves the definition unchanged. Dimension names and measure captions must all differ (compared without case), or running the report raises EAtlasDataError Duplicate column name: ....

pascal
Report.GroupBy(['Region'], [TAtlasMeasure.Create('Revenue', 'Revenue', amSum),
  TAtlasMeasure.Create('', 'Orders', amCount)]);

SetFilters method

procedure SetFilters(const Filters: array of TAtlasFilter);

Replaces the filters. A row is included only when every filter matches; in a summary report the filters apply before grouping. An empty array removes all filters. Filters are checked when the report runs: an unknown field or afContains on a non-text field raises EAtlasAnalyticsError, and a value that does not fit the column kind (for example lots for a number) raises EAtlasDataError.

pascal
Report.SetFilters([TAtlasFilter.Create('Region', 'West'),
  TAtlasFilter.Create('Revenue', '10000', afGreaterEqual)]);

Execute method

function Execute(Source: TAtlasTable): TAtlasTable;

Runs the definition over the current view of Source (the table's own filter and sort apply) and returns a new, unowned TAtlasTable that the caller must free. A detail report returns the selected columns of the matching rows in view order, with the source column names, kinds and widths. A summary report returns the dimension columns (named as you passed them) and one ackNumber column per measure. All result columns are read-only. Numbers are stored in invariant format, for example 12216.6666666667. Raises EAtlasAnalyticsError for a missing source, an unknown field, afContains on a non-text field or a numeric measure on a non-numeric field, and EAtlasDataError for filter values that do not fit their column and for duplicate result column names.

pascal
Summary := Report.Execute(Sales);
try
  Writeln(Summary.Cell(0, 0), ' ', Summary.Cell(1, 0));
finally
  Summary.Free;
end;

RenderHTML method

function RenderHTML(Source: TAtlasTable): string;

Runs Execute and returns a complete HTML5 page with no external resources: Title in <title> and <h1>, Subtitle in a paragraph, a table with one <th scope="col"> per result column, numeric cells right-aligned with class="number", and a final N result rows paragraph. Every title, caption and value is escaped. The inline style sheet repeats the table header on every printed page and avoids splitting rows across pages. Values are written as stored, without thousands separators or rounding. The page declares UTF-8, so write it with TEncoding.UTF8. Raises the same exceptions as Execute.

pascal
TFile.WriteAllText('report.html', Report.RenderHTML(Sales), TEncoding.UTF8);

PreparePrint method

function PreparePrint(Source: TAtlasTable): TAtlasPrintDocument;

Runs Execute and returns a new paginated TAtlasPrintDocument that the caller must free. Its Title is the report title and it is prepared with the default geometry of TAtlasPrintDocument.Prepare (794 by 1123 pixels, a 48-pixel margin, 28-pixel rows). Use the document's PageCount, Preview, RenderPage and Print. The subtitle is not printed. Raises the same exceptions as Execute. Output on a physical printer has not been certified.

pascal
Document := Report.PreparePrint(Sales);
try
  Writeln(Document.PageCount, ' pages');
finally
  Document.Free;
end;

ToJSON method

function ToJSON: string;

Returns the definition as a version 1 JSON object with title, subtitle, fields, dimensions, measures (each field, caption, and kind as the ordinal of TAtlasMeasureKind) and filters (each field, value, and operator as the ordinal of TAtlasFilterOperator). No data is included.

pascal
{"version":1,"title":"Sales by region","subtitle":"","fields":[],"dimensions":["Region"],"measures":[{"field":"Revenue","caption":"Revenue","kind":1}],"filters":[]}

LoadJSON method

procedure LoadJSON(const Text: string);

Replaces the whole definition with one written by ToJSON, after validating all of it: the text must be a JSON object with version 1, string title and subtitle, arrays fields and dimensions of non-blank strings, measures objects with a string field, a non-blank caption and a kind from 0 to 4, and filters objects with string field and value and an operator from 0 to 6. A definition cannot have both fields and measures, and dimensions need at least one measure. On any failure (including text that is not JSON) it raises EAtlasAnalyticsError and the current definition is unchanged. Field names are not checked against a table until the report runs.

pascal
Report.LoadJSON(TFile.ReadAllText('report.json', TEncoding.UTF8));

Examples

Grouped report to an HTML file

Totals and average sale per region, written as a page that opens in any browser.

pascal
uses System.SysUtils, System.IOUtils, Atlas.Data, Atlas.Analytics, Atlas.Reports;

procedure WriteRegionReport(Sales: TAtlasTable; const FileName: string);
var
  Report: TAtlasReportDefinition;
begin
  Report := TAtlasReportDefinition.Create(nil);
  try
    Report.Title := 'Sales by region';
    Report.Subtitle := 'Revenue and average sale per region';
    Report.GroupBy(['Region'], [
      TAtlasMeasure.Create('Revenue', 'Revenue', amSum),
      TAtlasMeasure.Create('Revenue', 'Average sale', amAverage)]);
    TFile.WriteAllText(FileName, Report.RenderHTML(Sales), TEncoding.UTF8);
  finally
    Report.Free;
  end;
end;

Filtered detail report

Three columns of the large West sales; the caller frees the returned table.

pascal
uses Atlas.Data, Atlas.Analytics, Atlas.Reports;

function LargeWestSales(Sales: TAtlasTable): TAtlasTable;
var
  Report: TAtlasReportDefinition;
begin
  Report := TAtlasReportDefinition.Create(nil);
  try
    Report.SelectFields(['Booked', 'Product', 'Revenue']);
    Report.SetFilters([
      TAtlasFilter.Create('Region', 'West'),
      TAtlasFilter.Create('Revenue', '10000', afGreaterEqual)]);
    Result := Report.Execute(Sales);
  finally
    Report.Free;
  end;
end;

Preview the first printed page

PreparePrint and Preview both return objects the caller frees.

pascal
uses Vcl.Graphics, Vcl.Imaging.pngimage, Atlas.Data, Atlas.Printing, Atlas.Reports;

procedure SavePreview(Report: TAtlasReportDefinition; Sales: TAtlasTable; const FileName: string);
var
  Document: TAtlasPrintDocument;
  Page: TBitmap;
  Png: TPngImage;
begin
  Document := Report.PreparePrint(Sales);
  try
    Page := Document.Preview(0);
    try
      Png := TPngImage.Create;
      try
        Png.Assign(Page);
        Png.SaveToFile(FileName);
      finally
        Png.Free;
      end;
    finally
      Page.Free;
    end;
  finally
    Document.Free;
  end;
end;

Keep a definition in a file

LoadJSON validates first, so a damaged file leaves the current definition in place.

pascal
uses System.SysUtils, System.IOUtils, Atlas.Analytics, Atlas.Reports;

procedure SaveDefinition(Report: TAtlasReportDefinition; const FileName: string);
begin
  TFile.WriteAllText(FileName, Report.ToJSON, TEncoding.UTF8);
end;

function LoadDefinition(Report: TAtlasReportDefinition; const FileName: string): Boolean;
begin
  Result := True;
  try
    Report.LoadJSON(TFile.ReadAllText(FileName, TEncoding.UTF8));
  except
    on EAtlasAnalyticsError do
      Result := False;
  end;
end;

TAtlasAnalytics · TAtlasMeasure · TAtlasFilter · TAtlasDashboard · TAtlasTable · TAtlasPrintDocument

Also in Atlas Analytics

TAtlasDashboard