demo D288
AI Data Insights: Explain a Grid Figure in Plain Language in Angular
Ask this view a question in plain language from one console: type a question or tap a shortcut and read a few plain sentences built from the grid’s own computed figures, then filter the grid and ask again to watch the answer follow the rows on screen, with every figure reconciled against what the grid computed so an invented number never reaches the reading
createAI · ai.explain
Ask this view a question in plain language and read the answer in one place: type a question, or tap a shortcut, and get a few sentences that say what the numbers mean, built from the grid's own computed figures for the rows on screen. Filter the grid and ask again, and every figure is reconciled against what the grid computed, so an invented number never reaches the answer.
This is the Angular version. A standalone component takes the whole configuration through one config input, surfaces grid events as outputs, and exposes the live grid on a getter for anything the inputs do not cover.
The configuration
import { Component, ViewChild, ElementRef } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { createGrid } from '@toclocoinc/lattice-grid';
import '@toclocoinc/lattice-grid/css';
import { createAI } from '@toclocoinc/lattice-grid/modules/ai';
import { LatticeGridComponent, provideLattice } from '@toclocoinc/lattice-grid/angular';
@Component({
selector: 'app-root',
standalone: true,
imports: [LatticeGridComponent],
template:
'<lattice-grid [config]="grid" (grid-ready)="onGridReady()" style="display:block;height:420px"></lattice-grid>' +
'<div #insights style="margin-top:12px"></div>',
})
export class AppComponent {
@ViewChild(LatticeGridComponent) gridRef!: LatticeGridComponent;
@ViewChild('insights') insightsRef!: ElementRef<HTMLElement>;
grid = {
rowKey: 'id',
columns: [
{ field: 'region', title: 'Region', filter: { type: 'set' } },
{ field: 'stage', title: 'Stage', filter: { type: 'set' } },
{ field: 'amount', title: 'Amount', type: 'number',
format: { style: 'currency', currency: 'USD', decimals: 0 }, total: 'sum' },
{ field: 'probability', title: 'Probability', type: 'number',
format: { style: 'percent', decimals: 0 } },
],
rows: [/* sales opportunities: region, stage, amount, probability */],
};
// Your model, your key, your network call. The module makes none: it hands
// ask() the grid's computed figures and validates whatever text comes back.
private ask = async ({ system, messages }: any) => {
const reply = await (window as any).myProvider.chat({ system, messages });
return { text: reply.text };
};
// The grid is created once the view has rendered, so the live instance
// arrives on grid-ready rather than in ngAfterViewInit.
async onGridReady() {
const ai = createAI(this.gridRef.grid, { ask: this.ask });
// The insights panel over the current view: its button turns the grid's
// own computed figures into a plain-language reading.
ai.insights(this.insightsRef.nativeElement);
// Read one column's shape on demand: its spread, its typical value and
// its outliers, none of which the table prints. Every number in the
// reply is reconciled against a value the grid computed this render, so
// an ungrounded figure is stripped before it is shown.
const { text, flagged } = await ai.explain({ kind: 'column', colId: 'amount' });
console.log(text, flagged);
}
}
bootstrapApplication(AppComponent, {
providers: [provideLattice({ createGrid })],
});
Understand what your data is telling you, in a sentence you can trust
A table answers “what are the numbers”. It rarely answers “so what”. This turns the grid’s own figures into a short, plain-language reading of them: ask a question in plain language, or tap a shortcut, and get a few sentences that say what the numbers show, in words you can paste into an email. The prompt, the shortcuts and the answer all sit in one console above the grid, so asking and reading happen in the same place. The reading is built for a person who has thirty seconds, not thirty minutes.
The reason you can act on it is that it cannot make anything up. Every number in the sentence is reconciled against a figure the grid actually computed for what is on screen right now: the totals, the averages, the counts, the spread. Anything that does not trace back to a real computation is stripped before you ever see it, so the console only shows figures the grid stands behind. So the narrative is only ever a plain-language rendering of figures you could check yourself, never a guess dressed up as one.
It reads the current view, so it follows your filters. Narrow the grid to one region or one stage and ask again, and the answer is about that narrower set, because it is drawn from the same figures the grid is showing you.
This demo answers questions about your data. To reshape the view itself from a plain-English request, see the ask-your-data demo, the one that filters, sorts and groups the grid for you.
Your model, your key, nothing sent that you did not send
The grid ships no model and holds no key, and it makes no AI call of its own. You supply one small function, ask(), that wraps the model and key you already use. The grid hands it the figures it computed and the question being asked; what leaves the browser is only what your ask() sends, and you decide what that is. There is no separate service to buy and no data quietly leaving on the side: the plain-language layer is part of the grid you already have, and the privacy decision stays yours.
Because the whole thing is grounded in figures the grid computed in the browser, the sensitive part - the row values - never has to leave to get a useful reading. The figures are aggregates and profiles, not the underlying rows.
How do I explain a figure in plain language?
Load the AI module and call createAI(grid, { ask }), passing the live grid and your own model callback. Then reach for ai.explain({ kind: 'view' }, { question }) to answer a plain-language question about the current view, or point it at a single figure: a column with { kind: 'column', colId }, a headline number with { kind: 'kpi', facts: [...] }, or a chart datum the same way. Each call returns the answer text and the list of any figures the guard removed, so you can show both. The figures are computed for the current filtered view, so the answer tracks the rows on screen.
How does it stop the model inventing numbers?
The grid builds the set of figures it computed for the current render and checks the model’s reply against it. A number that matches a computed value, allowing for the rounding a model naturally does, is kept; a number that matches nothing is treated as ungrounded and taken out. That check is the whole point: it is what lets you trust a plain-language summary enough to forward it, because it can only ever repeat figures the grid stands behind.
Wire your own model in production
The demo on this page uses a stand-in ask() that runs in your browser with no key, so the page works offline: it reads the grid’s figures back and writes sentences around them. In a product you replace that one function with a call to the model you choose. Everything else on the page stays the same, and the grounding guarantee is the same whichever model you wire in, because the reconciliation happens on this side against the grid’s own numbers.