A Data Engine loads, processes, and caches data for Studio widgets.
Studio creates one automatically when you pass data sources via the data property, but you can create the built-in engine yourself to share it across instances, or cache it across a Studio instance's lifecycle.
Built-in Engine Copy Link
When you pass data sources directly to Studio, it creates a built-in Data Engine behind the scenes. Creating the engine externally with createDataEngine(data) gives you two benefits:
- Sharing - multiple Studio instances can point at the same engine, so they share a single copy of the data.
- Caching across lifecycles - the engine survives when Studio is destroyed and recreated, so data doesn't need to be re-fetched or reprocessed on remount.
"use client";
import type {
AgDataEngine,
AgDataSourcesDefinition,
AgReportState,
} from "ag-studio";
import { createDataEngine, enableStudioDevValidations } from "ag-studio";
import type { AgStudioRef } from "ag-studio-react";
import { AgStudio } from "ag-studio-react";
import React, {
StrictMode,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { createRoot } from "react-dom/client";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableStudioDevValidations();
}
const StudioExample = () => {
const studioRef = useRef<AgStudioRef>(null);
const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
const studioStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
const [data, setData] = useState<AgDataSourcesDefinition | AgDataEngine>();
const [created, setCreated] = useState<boolean>(true);
const initialState = useMemo<AgReportState>(
() => ({
pages: [
{
id: "page1",
widgets: {
"1": {
type: "grid",
dataMapping: {
cols: [
{ id: "medals.country" },
{ id: "medals.sport" },
{ id: "medals.gold", aggregation: "sum" },
{ id: "medals.silver", aggregation: "sum" },
{ id: "medals.bronze", aggregation: "sum" },
{ id: "medals.total", aggregation: "sum" },
],
},
},
"2": {
type: "column-chart-grouped",
dataMapping: {
categoryKey: [{ id: "medals.country" }],
valueKey: [
{ id: "medals.gold", aggregation: "sum" },
{ id: "medals.silver", aggregation: "sum" },
{ id: "medals.bronze", aggregation: "sum" },
],
tooltipKey: [],
},
},
},
widgetLayout: {
"1": {
xTrack: 0,
yTrack: 0,
xSpan: 24,
ySpan: 16,
},
"2": {
xTrack: 0,
yTrack: 16,
xSpan: 24,
ySpan: 16,
},
},
},
],
selectedPageId: "page1",
panels: {
filters: {
collapsed: true,
},
edit: {
collapsed: true,
},
},
}),
[],
);
useEffect(() => {
fetch("https://www.ag-grid.com/studio/archive/3.0.0/example-assets/olympic-winners.json")
.then((resp) => resp.json())
.then((data: any[]) =>
setData(createDataEngine({ sources: [{ id: "medals", data }] })),
);
}, []);
const recreate = useCallback(() => {
setCreated((currentCreated) => !currentCreated);
}, []);
return (
<div style={containerStyle}>
<div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
<div className="example-controls">
<div className="controls-row">
<button onClick={recreate}>
{created
? "Destroy Studio Instance"
: "Recreate with Data Engine"}
</button>
</div>
</div>
{created && (
<AgStudio
ref={studioRef}
style={studioStyle}
className="my-studio-container"
data={data}
initialState={initialState}
mode={"edit"}
/>
)}
{!created && (
<div className="my-studio-container">No current Studio instance</div>
)}
</div>
</div>
);
};
const root = createRoot(document.getElementById("root")!);
root.render(
<StrictMode>
<StudioExample />
</StrictMode>,
);
.my-studio-container {
color: var(--main-fg);
}
const dataEngine = createDataEngine({
sources: [{
id: 'medals',
data: [
{
year: 2000,
sport: 'Swimming',
country: 'United States',
// ... other fields
},
// ... other rows
],
}],
});const data = useMemo(() => {
return dataEngine;
}, []);
<AgStudio data={data} />See Loading Data for the full range of data loading patterns.
createDataEngine(data) accepts a data object of type AgDataSourcesDefinition.
One or more data sources.
|
When using multiple related tables, this describes the fields that link the tables together.
|
Expression field definitions for calculated columns.
|
Overrides to existing formats, or additional custom formats.
|
AI-facing overview of the entire dataset: what it contains, what it's for, domain quirks.
|
Named time dimensions (calendars) that supply date fragments and a continuous date spine.
|
Additional date-fragment bucket definitions to register alongside the built-in set (year, quarter, month, week, day, monthOfYear, dayOfWeek, …). Use this to add project-specific groupings such as weekend, dayOfMonth, or hour that the built-in registry does not include. Provide via createBuckets so type-level registry inference works correctly.
|
Engine-wide behavioural options, such as fan-out detection policy.
|
Embedding Single Widgets Copy Link
A widget cannot be used on its own outside of Studio. To place an individual widget in your own application, run a Studio instance that shows a single widget filling the canvas, with the panels hidden. Several such instances can share one engine, so the data is loaded once.
"use client";
import type { AgDataEngine, AgReportState, AgWidgetState } from "ag-studio";
import {
createDataEngine,
enableStudioDevValidations,
studioTheme,
} from "ag-studio";
import { AgStudio } from "ag-studio-react";
import React, { StrictMode, useEffect, useMemo, useState } from "react";
import { createRoot } from "react-dom/client";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableStudioDevValidations();
}
// A single full-canvas widget, no panels - a Studio instance acting as one embeddable widget.
function singleWidgetState(id: string, widget: AgWidgetState): AgReportState {
return {
pages: [
{
id: "page1",
widgets: { [id]: widget },
widgetLayout: { [id]: { xTrack: 0, yTrack: 0, xSpan: 1, ySpan: 1 } },
},
],
selectedPageId: "page1",
};
}
const StudioExample = () => {
const [dataEngine, setDataEngine] = useState<AgDataEngine>();
// Remove the spacing around the canvas so the widget fills its instance edge to edge.
const theme = useMemo(
() => studioTheme.withParams({ studioWrapperSpacing: 0 }),
[],
);
const gridState = useMemo<AgReportState>(
() =>
singleWidgetState("1", {
type: "grid",
dataMapping: {
cols: [
{ id: "medals.country" },
{ id: "medals.gold", aggregation: "sum" },
{ id: "medals.silver", aggregation: "sum" },
{ id: "medals.bronze", aggregation: "sum" },
],
},
}),
[],
);
const chartState = useMemo<AgReportState>(
() =>
singleWidgetState("2", {
type: "column-chart-grouped",
dataMapping: {
categoryKey: [{ id: "medals.country" }],
valueKey: [
{ id: "medals.gold", aggregation: "sum" },
{ id: "medals.silver", aggregation: "sum" },
{ id: "medals.bronze", aggregation: "sum" },
],
tooltipKey: [],
},
}),
[],
);
useEffect(() => {
fetch("https://www.ag-grid.com/studio/archive/3.0.0/example-assets/olympic-winners.json")
.then((resp) => resp.json())
// One engine, shared by both instances - the data is loaded and cached once.
.then((data: any[]) =>
setDataEngine(createDataEngine({ sources: [{ id: "medals", data }] })),
);
}, []);
return (
<div className="single-widgets">
<AgStudio
className="single-widget"
mode="view"
panels={{}}
layout={{
minWidth: 300,
height: 300,
columns: 1,
rowHeight: 300,
pagePadding: 0,
widgetPadding: 0,
}}
theme={theme}
initialState={gridState}
data={dataEngine}
/>
<div className="app-area">Your Application</div>
<AgStudio
className="single-widget"
mode="view"
panels={{}}
layout={{
minWidth: 300,
height: 300,
columns: 1,
rowHeight: 300,
pagePadding: 0,
widgetPadding: 0,
}}
theme={theme}
initialState={chartState}
data={dataEngine}
/>
</div>
);
};
const root = createRoot(document.getElementById("root")!);
root.render(
<StrictMode>
<StudioExample />
</StrictMode>,
);
.single-widgets {
display: flex;
flex-direction: row;
align-items: stretch;
justify-content: center;
gap: 12px;
height: 300px;
color: var(--main-fg);
}
.single-widget {
flex: 1 1 0;
min-width: 0;
max-width: 360px;
height: 300px;
}
.app-area {
display: flex;
align-items: center;
justify-content: center;
flex: 0 0 160px;
text-align: center;
border: 1px dashed var(--chart-border);
border-radius: 6px;
}
To show a single widget, give the report a one-cell layout and hide the panels:
const [mode, setMode] = useState('view');
const panels = {};
const layout = { columns: 1, height: 300, rowHeight: 300, pagePadding: 0, widgetPadding: 0 };
<AgStudio
mode={mode}
panels={panels}
layout={layout}
/>Each instance is independent. Panels belong to a single instance, so one panel cannot control several instances. Only the data engine is shared.
Custom Engines Copy Link
Sharing and caching, as described above, are features of the built-in engine only. For larger datasets, or when you want to replace query execution with a backend you already own, see the Custom Engine guide in the Server-Side Data section.