Tables in the built-in engine can change in place. The application applies a transaction to add, update or remove rows, and every widget that reads the table shows the change, with no reload.
import {
AgReportState,
AgStudioApi,
AgStudioProperties,
createStudio,
enableStudioDevValidations,
} from "ag-studio";
import {
createRandomTransaction,
getInitialTrades,
tradeFields,
} from "./data.ts";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableStudioDevValidations();
}
const initialState: AgReportState = {
pages: [
{
id: "page1",
widgets: {
table: {
type: "grid",
dataMapping: {
cols: [
{ id: "trades.desk" },
{ id: "trades.quantity", aggregation: "sum" },
],
},
format: { title: { enabled: true, text: "Table" } },
},
chart: {
type: "column-chart-grouped",
dataMapping: {
categoryKey: [{ id: "trades.desk" }],
valueKey: [{ id: "trades.quantity", aggregation: "sum" }],
tooltipKey: [],
},
format: { title: { enabled: true, text: "Quantity by Desk" } },
},
},
widgetLayout: {
table: { xTrack: 0, yTrack: 0, xSpan: 24, ySpan: 12 },
chart: { xTrack: 0, yTrack: 12, xSpan: 24, ySpan: 16 },
},
},
],
selectedPageId: "page1",
panels: {
filters: {
collapsed: true,
},
edit: {
collapsed: true,
},
},
};
const studioProperties: AgStudioProperties = {
mode: "edit",
initialState,
data: {
sources: [
{
id: "trades",
transactions: "all",
primaryKeyField: "id",
data: getInitialTrades(),
fields: tradeFields,
},
],
},
};
let studioApi: AgStudioApi;
let updateTimer: ReturnType<typeof setInterval> | undefined;
function applyRandomChange() {
const engine = studioApi.getDataEngine();
engine?.applyTransaction?.(createRandomTransaction());
}
function startUpdates() {
if (updateTimer == null) {
updateTimer = setInterval(applyRandomChange, 100);
}
}
function stopUpdates() {
clearInterval(updateTimer);
updateTimer = undefined;
}
// setup Studio after the page has finished loading
const studioDiv = document.querySelector<HTMLElement>("#myStudio")!;
studioApi = createStudio(studioDiv, studioProperties);
if (typeof window !== "undefined") {
// Attach external event handlers to window so they can be called from index.html
(<any>window).startUpdates = startUpdates;
(<any>window).stopUpdates = stopUpdates;
}
In the example above, Start Updates applies a transaction to the trades table every 100 milliseconds. The table and the chart show each change as it lands.
Accepting Transactions Copy Link
A table accepts transactions only when its data source allows them. Set transactions on a sync or columnar data source:
'none', the default, accepts no transactions.'add'accepts added rows only.'all'also accepts updated and removed rows. It requiresprimaryKeyField, which names the field that identifies each row.
const studioProperties = {
data: {
sources: [{
id: 'trades',
transactions: 'all',
primaryKeyField: 'id',
data: [
{ id: 1, desk: 'Rates', quantity: 100 },
// ... other rows
],
}],
},
// other studio properties ...
}Tables from an async data source accept no transactions. See Async Data Sources.
Applying a Transaction Copy Link
Get the engine with api.getDataEngine() once the studioReady event has fired, and call applyTransaction.
const engine = api.getDataEngine();
const position = await engine.applyTransaction({
tableId: 'trades',
remove: [2],
update: [{ id: 1, desk: 'Rates', quantity: 120 }],
add: [{ id: 3, desk: 'Credit', quantity: 10 }],
});- Rows are identified by the value of their
primaryKeyField. - The parts apply in the order
remove,update,add. To change a row's key, remove the row and add it again under the new key. - Each row in
updatereplaces the whole row, so give every field the row keeps. - If any part is invalid, such as a key the table does not hold, the engine rejects the whole transaction and the table does not change.
The promise resolves once the change is committed, so a query made after that sees the new rows. It resolves with the table's new position: a number that goes up with each change to the table.
The table to change.
|
Rows to add, as row objects or as columns. Accepted by a table whose transactions is 'add' or 'all'. On a table with a primaryKeyField, each row must carry a key no other row holds once remove has applied.
|
Replacement rows, as row objects or as columns. Each is a whole row carrying the key of the row it replaces, and every field the row should keep. Requires a table whose transactions is 'all'.
|
Keys of the rows to remove. Requires a table whose transactions is 'all'.
|
Changing Several Tables Together Copy Link
Pass an array of transactions to change several tables as one change. Either every transaction applies or none does, and no query result reflects some of the tables changed and not others. The promise resolves with each changed table's new position, by table id.
const positions = await engine.applyTransaction([
{ tableId: 'trades', add: [{ id: 7, desk: 'FX', quantity: 5 }] },
{ tableId: 'desks', add: [{ desk: 'FX', region: 'AMER' }] },
]);
// positions.trades and positions.desks are the two tables' new positions Frequent Changes Copy Link
Call applyTransaction for each change as it arrives. Transactions apply in the order they are called. Transactions called while an earlier one is still being applied are applied together, and resolve with the same position.
A widget does not run its queries once for each transaction. Changes that land while its queries run are shown together, by the next run.
What Widgets Show Copy Link
When a table changes, only the widgets with a query that reads it run their queries again. Filters and cross filters apply to the new data as they did to the old.
- Tables and Pivot Tables update in place on the next frame after their queries have run. Selected rows and open row groups stay as they were. They can also animate changed values, using the Highlight Changes toggle on the Format tab.
- Charts redraw on the next frame after their queries have run.
- Other widgets refresh at most once a second, with the latest data. The states between two refreshes are not shown.
When two of a widget's queries read the same table, the widget never shows one query's data from before a change to that table beside the other's from after it. A widget in a hidden browser tab updates when the tab is shown again.
Each widget is kept up to date on its own, so while the data is changing two widgets can briefly show it at different points. Once the data stops changing, every widget shows the latest data within about a second.
A Custom Widget chooses how it is kept up to date.
Transactions, Data Updates and Reloads Copy Link
| Transaction | New data property | api.reload() | |
|---|---|---|---|
| Changes | Rows of the tables named, in place | The data of the sync data sources passed | Nothing in place: async data sources are fetched again |
| Cached results | Kept for the tables not changed | Cleared | Cleared |
| Widgets | Those that read a changed table run their queries again | Every widget loads its data again | Every widget loads its data again |
See Reloading Sync Data and Reloading Async Data.
Async Data Sources Copy Link
A table from an async data source accepts no transactions. When its data changes, call api.reload() to show the change.
An async data source's getData must not run queries on the engine it supplies data to, directly or through Studio, and must not wait for anything that is itself waiting for a transaction. A transaction to a table waits for the queries reading it, and a query can be waiting for getData, so such a wait can hold up every update on the dashboard.