YOUSEFUL / DELPHI / SAMPLE PROJECTS

Virtual rows and a chart companion

VCL application Product: Atlas Virtual Grid · Components: TAtlasVirtualGrid, TAtlasVirtualData, TAtlasPlotData

A VCL window with two pages, built entirely in code (TForm.CreateNew, no .dfm). It goes further than the quick start.

One million on-demand rows. A TAtlasVirtualData with RowCount = 1000000 computes every cell in OnGetCell. Nothing is stored except the cells the user edits, which OnSetCell keeps in a dictionary. The page shows:

Chart companion. A TAtlasPlot with two bar series, Plan and Actual, is linked to a TAtlasPlotData. The grid below the chart shows one row per quarter and one column per series, with sum totals. Editing a value moves the bar. Clicking a bar selects its cell (RowOfPoint), and moving in the table shows the chart argument of the row (ArgumentOfRow). The Quarter column is read-only, and a blank value is refused with "A series value must be a number".

Build and run

Open VirtualAndPlotGrid.dpr in Delphi 13; the IDE creates the project file. Add the Atlas Virtual Grid src folder and 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.

What you should see

Adapting it

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

VirtualAndPlotGrid.dpr

pascal
program VirtualAndPlotGrid;

// Atlas Virtual Grid, deeper sample: a million on-demand rows with bands, frozen
// columns, validated in-place editing and conditional colours, and a chart shown
// and edited as a table. The form is built in code; see VirtualPlotForm.pas.

uses
  Vcl.Forms,
  VirtualPlotForm in 'VirtualPlotForm.pas';

var
  MainForm: TVirtualPlotForm;

begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TVirtualPlotForm, MainForm);
  Application.Run;
end.

VirtualPlotForm.pas

pascal
unit VirtualPlotForm;

// Two pages. "One million rows": a TAtlasVirtualData that computes its cells on
// demand and stores only the cells the user edits, shown with bands, two frozen
// columns, validated editing and conditional colours. "Chart companion": a
// TAtlasPlot shown and edited as a table through TAtlasPlotData.

interface

uses
  System.SysUtils, System.Classes, System.Math, System.Diagnostics, System.UITypes,
  System.Generics.Collections, Vcl.Forms, Vcl.Controls, Vcl.StdCtrls, Vcl.ExtCtrls,
  Vcl.ComCtrls, Vcl.Graphics, Atlas.Plot, Atlas.PlotSeries, Atlas.VGridData,
  Atlas.VGridPlot, Atlas.VGrid;

const
  RowTotal = 1000000;
  // Data columns of the on-demand source.
  ColRow = 0; ColSensor = 1; ColReading = 2; ColTaken = 3; ColChecked = 4; ColNote = 5;

type
  TVirtualPlotForm = class(TForm)
  private
    FPages: TPageControl;
    FStatus: TLabel;
    FFilter: TEdit;
    FReadings: TAtlasVirtualData;
    // Only the cells the user changed are stored; everything else is computed.
    FEdits: TDictionary<Int64, TAtlasCell>;
    FGrid: TAtlasVirtualGrid;
    FChart: TAtlasPlot;
    FChartData: TAtlasPlotData;
    FChartGrid: TAtlasVirtualGrid;
    function AddPage(const Caption: string): TTabSheet;
    procedure AddButton(Parent: TWinControl; X: Integer; const Caption: string; Handler: TNotifyEvent);
    procedure BuildReadings(Page: TTabSheet);
    procedure BuildChart(Page: TTabSheet);
    procedure GetReading(Sender: TObject; Column, Row: Integer; var Cell: TAtlasCell);
    procedure SetReading(Sender: TObject; Column, Row: Integer; const Cell: TAtlasCell);
    procedure StyleReading(Sender: TObject; Column: TAtlasGridColumn; DataRow: Integer;
      const Cell: TAtlasCell; var Background, TextColor: TColor);
    procedure EditRejected(Sender: TObject; const Message: string);
    procedure ApplyFilter(Sender: TObject);
    procedure GoToLast(Sender: TObject);
    procedure ChartRowChanged(Sender: TObject);
    procedure PointClicked(Sender: TObject; Series: TAtlasPlotSeries; PointIndex: Integer);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;

implementation

function CellKey(Column, Row: Integer): Int64;
begin
  Result := Int64(Row) * 8 + Column;
end;

constructor TVirtualPlotForm.Create(AOwner: TComponent);
begin
  inherited CreateNew(AOwner);
  Caption := 'Atlas Virtual Grid: on-demand rows and a chart companion';
  Width := 1000;
  Height := 640;
  Position := poScreenCenter;
  FEdits := TDictionary<Int64, TAtlasCell>.Create;
  FStatus := TLabel.Create(Self);
  FStatus.Parent := Self;
  FStatus.Align := alBottom;
  FStatus.AlignWithMargins := True;
  FStatus.Caption := 'F2, Enter, double-click or type to edit; Esc cancels. Click a header to sort.';
  FPages := TPageControl.Create(Self);
  FPages.Parent := Self;
  FPages.Align := alClient;
  BuildReadings(AddPage('One million on-demand rows'));
  BuildChart(AddPage('Chart companion'));
end;

destructor TVirtualPlotForm.Destroy;
begin
  // The grids and sources are owned by the form and go first; the edits they read go last.
  inherited;
  FEdits.Free;
end;

function TVirtualPlotForm.AddPage(const Caption: string): TTabSheet;
begin
  Result := TTabSheet.Create(Self);
  Result.PageControl := FPages;
  Result.Caption := Caption;
end;

procedure TVirtualPlotForm.AddButton(Parent: TWinControl; X: Integer; const Caption: string; Handler: TNotifyEvent);
var Button: TButton;
begin
  Button := TButton.Create(Self);
  Button.Parent := Parent;
  Button.SetBounds(X, 5, 88, 26);
  Button.Caption := Caption;
  Button.OnClick := Handler;
end;

procedure TVirtualPlotForm.BuildReadings(Page: TTabSheet);
var Bar: TPanel;
begin
  FReadings := TAtlasVirtualData.Create(Self);
  FReadings.AddColumn('Row', ckNumber);
  FReadings.AddColumn('Sensor');
  FReadings.AddColumn('Reading', ckNumber);
  FReadings.AddColumn('Taken', ckDateTime);
  FReadings.AddColumn('Checked', ckBoolean);
  FReadings.AddColumn('Note');
  FReadings.OnGetCell := GetReading;
  FReadings.OnSetCell := SetReading;           // without it every column is read-only
  FReadings.RowCount := RowTotal;

  Bar := TPanel.Create(Self);
  Bar.Parent := Page;
  Bar.Align := alTop;
  Bar.Height := 36;
  Bar.BevelOuter := bvNone;
  FFilter := TEdit.Create(Self);
  FFilter.Parent := Bar;
  FFilter.SetBounds(8, 6, 200, 24);
  FFilter.TextHint := 'Text to look for';
  AddButton(Bar, 216, 'Filter', ApplyFilter);
  AddButton(Bar, 312, 'Last row', GoToLast);

  FGrid := TAtlasVirtualGrid.Create(Self);
  FGrid.Parent := Page;
  FGrid.Align := alClient;
  FGrid.Data := FReadings;                     // one grid column per data column
  FGrid.FrozenColumns := 2;                    // Row and Sensor stay put when scrolling sideways
  FGrid.Columns[ColRow].Width := 80;
  FGrid.Columns[ColRow].Format := '#,##0';
  FGrid.Columns[ColRow].ReadOnly := True;
  FGrid.Columns[ColSensor].ReadOnly := True;
  FGrid.Columns[ColReading].Format := '0.00';
  FGrid.Columns[ColTaken].Width := 140;
  FGrid.Columns[ColTaken].Format := 'yyyy-mm-dd hh:nn';
  FGrid.Columns[ColTaken].ReadOnly := True;
  FGrid.Columns[ColNote].Width := 640;         // wider than the window, so the grid scrolls sideways
  FGrid.Columns[ColRow].Band := 'Source';
  FGrid.Columns[ColSensor].Band := 'Source';
  FGrid.Columns[ColReading].Band := 'Measurement';
  FGrid.Columns[ColTaken].Band := 'Measurement';
  FGrid.Columns[ColChecked].Band := 'Review';
  FGrid.Columns[ColNote].Band := 'Review';
  FGrid.OnCellStyle := StyleReading;
  FGrid.OnEditError := EditRejected;
end;

procedure TVirtualPlotForm.GetReading(Sender: TObject; Column, Row: Integer; var Cell: TAtlasCell);
begin
  // Cell arrives as a null of the column's kind. An edited value wins over the computed one.
  if FEdits.TryGetValue(CellKey(Column, Row), Cell) then Exit;
  case Column of
    ColRow: Cell := TAtlasCell.OfNumber(Row + 1);
    ColSensor: Cell := TAtlasCell.OfText('Sensor ' + Chr(Ord('A') + Row mod 8));
    ColReading: Cell := TAtlasCell.OfNumber(RoundTo(50 + 60 * Sin(Row / 40) + (Row mod 7) * 3, -2));
    ColTaken: Cell := TAtlasCell.OfDateTime(EncodeDate(2026, 1, 1) + Row / 1440);
    ColChecked: Cell := TAtlasCell.OfBoolean(Row mod 5 = 0);
  end;                                         // ColNote stays null until edited
end;

procedure TVirtualPlotForm.SetReading(Sender: TObject; Column, Row: Integer; const Cell: TAtlasCell);
begin
  // The grid has already parsed the text for the column's kind. Raising
  // EAtlasGridError rejects the value: the editor stays open and OnEditError says why.
  case Column of
    ColReading:
      if Cell.IsNull then raise EAtlasGridError.Create('A reading cannot be blank')
      else if (Cell.Number < -50) or (Cell.Number > 150) then
        raise EAtlasGridError.Create('A reading must lie between -50 and 150');
    ColNote:
      if Length(Cell.Text) > 40 then raise EAtlasGridError.Create('A note holds at most 40 characters');
  end;
  FEdits.AddOrSetValue(CellKey(Column, Row), Cell);
  FStatus.Caption := Format('Row %d changed; %d edited cells held in memory', [Row + 1, FEdits.Count]);
end;

procedure TVirtualPlotForm.StyleReading(Sender: TObject; Column: TAtlasGridColumn; DataRow: Integer;
  const Cell: TAtlasCell; var Background, TextColor: TColor);
begin
  // Selection is painted over these backgrounds, so the current row stays visible.
  if FEdits.ContainsKey(CellKey(Column.DataIndex, DataRow)) then Background := $00C8F0FF
  else if (Column.DataIndex = ColReading) and not Cell.IsNull and (Cell.Number > 100) then Background := $00D8D8FF;
  if (Column.DataIndex = ColReading) and not Cell.IsNull and (Cell.Number < 0) then TextColor := clBlue;
end;

procedure TVirtualPlotForm.EditRejected(Sender: TObject; const Message: string);
begin
  FStatus.Caption := 'Not accepted: ' + Message;
end;

procedure TVirtualPlotForm.ApplyFilter(Sender: TObject);
var Watch: TStopwatch;
begin
  // A filter reads every row once; on a million computed rows that takes a moment.
  Screen.Cursor := crHourGlass;
  try
    Watch := TStopwatch.StartNew;
    FGrid.FilterText := FFilter.Text;
    FStatus.Caption := Format('%s of %s rows match "%s" (%d ms)', [FormatFloat('#,##0', FGrid.RowCount),
      FormatFloat('#,##0', FReadings.RowCount), FGrid.FilterText, Watch.ElapsedMilliseconds]);
  finally
    Screen.Cursor := crDefault;
  end;
end;

procedure TVirtualPlotForm.GoToLast(Sender: TObject);
begin
  if FGrid.RowCount = 0 then Exit;
  FGrid.SelectCell(ColReading, FGrid.RowCount - 1);   // scrolls the row into view
  if FGrid.CanFocus then FGrid.SetFocus;
end;

procedure TVirtualPlotForm.BuildChart(Page: TTabSheet);
const Quarters: array[0..3] of string = ('Q1', 'Q2', 'Q3', 'Q4');
var Plan, Actual: TAtlasPlotSeries; I: Integer;
begin
  FChart := TAtlasPlot.Create(Self);
  FChart.Parent := Page;
  FChart.Align := alClient;
  FChart.Title := 'Edit a value in the table and the bar follows; click a bar to find its cell';
  FChart.OnPointClick := PointClicked;
  Plan := FChart.AddSeries('Plan', pkBar);
  Actual := FChart.AddSeries('Actual', pkBar);
  for I := 0 to High(Quarters) do
  begin
    Plan.AddY(100 + I * 20, Quarters[I]);
    Actual.AddY(90 + I * 25 - (I mod 2) * 15, Quarters[I]);
  end;
  FChartData := TAtlasPlotData.Create(Self);
  FChartData.Plot := FChart;                   // column 0: the point labels; then one column per series
  FChartGrid := TAtlasVirtualGrid.Create(Self);
  FChartGrid.Parent := Page;
  FChartGrid.Align := alBottom;
  FChartGrid.Height := 170;
  FChartGrid.Data := FChartData;
  FChartGrid.Columns[0].Title := 'Quarter';
  FChartGrid.Columns[1].Format := '#,##0.0';
  FChartGrid.Columns[1].Total := tkSum;
  FChartGrid.Columns[2].Format := '#,##0.0';
  FChartGrid.Columns[2].Total := tkSum;
  FChartGrid.OnEditError := EditRejected;
  FChartGrid.OnSelectionChange := ChartRowChanged;
end;

procedure TVirtualPlotForm.ChartRowChanged(Sender: TObject);
var DataRow: Integer;
begin
  DataRow := FChartGrid.DataRowOf(FChartGrid.Row);
  if DataRow >= 0 then
    FStatus.Caption := Format('%s sits at argument %g on the chart',
      [FChartGrid.CellText(0, FChartGrid.Row), FChartData.ArgumentOfRow(DataRow)]);
end;

procedure TVirtualPlotForm.PointClicked(Sender: TObject; Series: TAtlasPlotSeries; PointIndex: Integer);
var Row: Integer;
begin
  // The adapter gives the data row; the grid turns it into the row as shown (it may be sorted).
  Row := FChartGrid.RowOfDataRow(FChartData.RowOfPoint(Series, PointIndex));
  if Row >= 0 then FChartGrid.SelectCell(Series.Index + 1, Row);
end;

end.