Studio supports a variety of data types. Instead of being defined directly on fields, data types are defined on Formats. A Format controls formatting along with other behaviour.
const fields = [
{
id: 'athlete',
format: 'textFormat' // textFormat uses the `string` data type
},
// ... other fields
];When using Sync Data and not providing fields, the format is inferred from the data.
import { Component } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { AgStudio } from "ag-studio-angular";
import {
AgDataEngine,
AgDataSourcesDefinition,
AgFieldDefinition,
AgReportState,
AgStudioApi,
AgStudioApiReadyEvent,
AgStudioMode,
AgStudioProperties,
enableStudioDevValidations,
} from "ag-studio";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableStudioDevValidations();
}
@Component({
selector: "my-app",
standalone: true,
imports: [AgStudio],
template: `<div style="display: flex; flex-direction: column; height: 100%">
<ag-studio
style="width: 100%; height: 100%;"
class="my-studio-container"
[initialState]="initialState"
[mode]="mode"
[data]="data"
(apiReady)="onApiReady($event)"
/>
</div> `,
})
export class AppComponent {
initialState: AgReportState = {
pages: [
{
id: "a",
widgets: {
"1": {
type: "grid",
dataMapping: {
cols: [
{ id: "dataTypes.string" },
{ id: "dataTypes.integer" },
{ id: "dataTypes.decimal" },
{ id: "dataTypes.boolean" },
{ id: "dataTypes.date" },
{ id: "dataTypes.datetime" },
],
},
},
},
widgetLayout: {
"1": {
xTrack: 0,
yTrack: 0,
xSpan: 24,
ySpan: 25,
},
},
},
],
selectedPageId: "a",
panels: {
filters: {
collapsed: true,
},
edit: {
collapsed: true,
},
},
};
mode: AgStudioMode = "edit";
data!: AgDataSourcesDefinition | AgDataEngine;
constructor(private http: HttpClient) {}
onApiReady(params: AgStudioApiReadyEvent) {
this.http
.get("https://www.ag-grid.com/studio/example-assets/olympic-winners.json")
.subscribe(
(data) =>
(this.data = {
sources: [
{
id: "dataTypes",
data: data.slice(0, 100).map((row: any) => {
const datetime = new Date(row.date);
datetime.setHours(Math.floor(window.agRandom() * 24));
datetime.setMinutes(Math.floor(window.agRandom() * 60));
datetime.setSeconds(Math.floor(window.agRandom() * 60));
return {
string: row.athlete,
integer: row.total,
decimal: row.total * window.agRandom(),
boolean: !!row.gold,
date: row.date,
datetime,
};
}),
fields,
},
],
}),
);
}
}
const fields: AgFieldDefinition[] = [
{
id: "string",
format: "textFormat",
},
{
id: "integer",
format: "integerFormat",
},
{
id: "decimal",
format: "decimalFormat",
},
{
id: "boolean",
format: "booleanFormat",
},
{
id: "date",
format: "dateFormat",
},
{
id: "datetime",
format: "dateTimeFormat",
},
];
import '@angular/compiler';
import { provideHttpClient } from '@angular/common/http';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component.ts';
const app = bootstrapApplication(AppComponent, {
providers: [provideHttpClient()],
});
Data Types Copy Link
Each of the data types are described in the table below. The input type is the JavaScript type that is supported in the source data. The default Format is the Format that will be used when inferring fields.
| Data Type | Input Type | Default Format |
|---|---|---|
string | string | textFormat |
number | number | integerFormat / decimalFormat |
boolean | boolean | booleanFormat |
date | Date | string | number | dateFormat |
datetime | Date | string | number | dateTimeFormat |
For date and datetime, the string value is expected to be in ISO-8601 format, and the number value is Unix epoch.
See Formatting for how each data type is displayed and how to customise the display format.
Dictionary Columns Copy Link
Some data stores a code rather than a label, such as an ISO country code instead of a country name. In Studio, the mapping from code to label is a separate table joined to the table holding the codes, and the user-facing column comes from that dictionary table.
This is not the same as formatting the code for display. Because the label is a real field, you can group, sort, filter and aggregate by it, put it on a chart axis, and use it in an expression. A value formatter only changes what is displayed, so the engine still groups, sorts and filters on the raw code. The mapping is also data, so you can update it without changing any code.
The example below joins a medals table to a countries dictionary. The medals table stores an ISO country code, and the grid shows the country name that the dictionary maps it to.
import { Component } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { AgStudio } from "ag-studio-angular";
import {
AgDataEngine,
AgDataSourcesDefinition,
AgReportState,
AgStudioApi,
AgStudioApiReadyEvent,
AgStudioMode,
AgStudioProperties,
enableStudioDevValidations,
} from "ag-studio";
import { getData } from "./data.ts";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableStudioDevValidations();
}
@Component({
selector: "my-app",
standalone: true,
imports: [AgStudio],
template: `<div style="display: flex; flex-direction: column; height: 100%">
<ag-studio
style="width: 100%; height: 100%;"
class="my-studio-container"
[initialState]="initialState"
[mode]="mode"
[data]="data"
(apiReady)="onApiReady($event)"
/>
</div> `,
})
export class AppComponent {
initialState: AgReportState = {
pages: [
{
id: "page1",
widgets: {
"1": {
type: "grid",
dataMapping: {
cols: [
{ id: "countries.countryName" },
{ id: "medals.sport" },
{ id: "medals.gold", aggregation: "sum" },
{ id: "medals.silver", aggregation: "sum" },
{ id: "medals.bronze", aggregation: "sum" },
{ id: "medals.total", aggregation: "sum" },
],
},
},
},
widgetLayout: {
"1": {
xTrack: 0,
yTrack: 0,
xSpan: 24,
ySpan: 25,
},
},
},
],
selectedPageId: "page1",
panels: {
filters: {
collapsed: true,
},
edit: {
collapsed: true,
},
},
};
mode: AgStudioMode = "edit";
data!: AgDataSourcesDefinition | AgDataEngine;
constructor(private http: HttpClient) {}
onApiReady(params: AgStudioApiReadyEvent) {
this.http
.get("https://www.ag-grid.com/studio/example-assets/olympic-winners.json")
.subscribe((data) => (this.data = getData(data)));
}
}
import '@angular/compiler';
import { provideHttpClient } from '@angular/common/http';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component.ts';
const app = bootstrapApplication(AppComponent, {
providers: [provideHttpClient()],
});
import type { AgDataSourcesDefinition, AgFieldDefinition } from 'ag-studio';
interface CountryDictionaryRow {
countryCode: string;
countryName: string;
}
interface RawMedalRow {
country: string;
}
// The dictionary table: one row per ISO country code, mapping it to the name shown to the user.
// `countryName` matches the medals dataset's own country string exactly, so every fact row finds
// a code and no country is left blank.
const countryDictionary: CountryDictionaryRow[] = [
{ countryCode: 'AFG', countryName: 'Afghanistan' },
{ countryCode: 'DZA', countryName: 'Algeria' },
{ countryCode: 'ARG', countryName: 'Argentina' },
{ countryCode: 'ARM', countryName: 'Armenia' },
{ countryCode: 'AUS', countryName: 'Australia' },
{ countryCode: 'AUT', countryName: 'Austria' },
{ countryCode: 'AZE', countryName: 'Azerbaijan' },
{ countryCode: 'BHS', countryName: 'Bahamas' },
{ countryCode: 'BHR', countryName: 'Bahrain' },
{ countryCode: 'BRB', countryName: 'Barbados' },
{ countryCode: 'BLR', countryName: 'Belarus' },
{ countryCode: 'BEL', countryName: 'Belgium' },
{ countryCode: 'BWA', countryName: 'Botswana' },
{ countryCode: 'BRA', countryName: 'Brazil' },
{ countryCode: 'BGR', countryName: 'Bulgaria' },
{ countryCode: 'CMR', countryName: 'Cameroon' },
{ countryCode: 'CAN', countryName: 'Canada' },
{ countryCode: 'CHL', countryName: 'Chile' },
{ countryCode: 'CHN', countryName: 'China' },
{ countryCode: 'TPE', countryName: 'Chinese Taipei' },
{ countryCode: 'COL', countryName: 'Colombia' },
{ countryCode: 'CRI', countryName: 'Costa Rica' },
{ countryCode: 'HRV', countryName: 'Croatia' },
{ countryCode: 'CUB', countryName: 'Cuba' },
{ countryCode: 'CYP', countryName: 'Cyprus' },
{ countryCode: 'CZE', countryName: 'Czech Republic' },
{ countryCode: 'DNK', countryName: 'Denmark' },
{ countryCode: 'DOM', countryName: 'Dominican Republic' },
{ countryCode: 'ECU', countryName: 'Ecuador' },
{ countryCode: 'EGY', countryName: 'Egypt' },
{ countryCode: 'ERI', countryName: 'Eritrea' },
{ countryCode: 'EST', countryName: 'Estonia' },
{ countryCode: 'ETH', countryName: 'Ethiopia' },
{ countryCode: 'FIN', countryName: 'Finland' },
{ countryCode: 'FRA', countryName: 'France' },
{ countryCode: 'GAB', countryName: 'Gabon' },
{ countryCode: 'GEO', countryName: 'Georgia' },
{ countryCode: 'DEU', countryName: 'Germany' },
{ countryCode: 'GBR', countryName: 'Great Britain' },
{ countryCode: 'GRC', countryName: 'Greece' },
{ countryCode: 'GRD', countryName: 'Grenada' },
{ countryCode: 'GTM', countryName: 'Guatemala' },
{ countryCode: 'HKG', countryName: 'Hong Kong' },
{ countryCode: 'HUN', countryName: 'Hungary' },
{ countryCode: 'ISL', countryName: 'Iceland' },
{ countryCode: 'IND', countryName: 'India' },
{ countryCode: 'IDN', countryName: 'Indonesia' },
{ countryCode: 'IRN', countryName: 'Iran' },
{ countryCode: 'IRL', countryName: 'Ireland' },
{ countryCode: 'ISR', countryName: 'Israel' },
{ countryCode: 'ITA', countryName: 'Italy' },
{ countryCode: 'JAM', countryName: 'Jamaica' },
{ countryCode: 'JPN', countryName: 'Japan' },
{ countryCode: 'KAZ', countryName: 'Kazakhstan' },
{ countryCode: 'KEN', countryName: 'Kenya' },
{ countryCode: 'KWT', countryName: 'Kuwait' },
{ countryCode: 'KGZ', countryName: 'Kyrgyzstan' },
{ countryCode: 'LVA', countryName: 'Latvia' },
{ countryCode: 'LTU', countryName: 'Lithuania' },
{ countryCode: 'MKD', countryName: 'Macedonia' },
{ countryCode: 'MYS', countryName: 'Malaysia' },
{ countryCode: 'MUS', countryName: 'Mauritius' },
{ countryCode: 'MEX', countryName: 'Mexico' },
{ countryCode: 'MDA', countryName: 'Moldova' },
{ countryCode: 'MNG', countryName: 'Mongolia' },
{ countryCode: 'MNE', countryName: 'Montenegro' },
{ countryCode: 'MAR', countryName: 'Morocco' },
{ countryCode: 'MOZ', countryName: 'Mozambique' },
{ countryCode: 'NLD', countryName: 'Netherlands' },
{ countryCode: 'NZL', countryName: 'New Zealand' },
{ countryCode: 'NGA', countryName: 'Nigeria' },
{ countryCode: 'PRK', countryName: 'North Korea' },
{ countryCode: 'NOR', countryName: 'Norway' },
{ countryCode: 'PAN', countryName: 'Panama' },
{ countryCode: 'PRY', countryName: 'Paraguay' },
{ countryCode: 'POL', countryName: 'Poland' },
{ countryCode: 'PRT', countryName: 'Portugal' },
{ countryCode: 'PRI', countryName: 'Puerto Rico' },
{ countryCode: 'QAT', countryName: 'Qatar' },
{ countryCode: 'ROU', countryName: 'Romania' },
{ countryCode: 'RUS', countryName: 'Russia' },
{ countryCode: 'SAU', countryName: 'Saudi Arabia' },
{ countryCode: 'SRB', countryName: 'Serbia' },
{ countryCode: 'SCG', countryName: 'Serbia and Montenegro' },
{ countryCode: 'SGP', countryName: 'Singapore' },
{ countryCode: 'SVK', countryName: 'Slovakia' },
{ countryCode: 'SVN', countryName: 'Slovenia' },
{ countryCode: 'ZAF', countryName: 'South Africa' },
{ countryCode: 'KOR', countryName: 'South Korea' },
{ countryCode: 'ESP', countryName: 'Spain' },
{ countryCode: 'LKA', countryName: 'Sri Lanka' },
{ countryCode: 'SDN', countryName: 'Sudan' },
{ countryCode: 'SWE', countryName: 'Sweden' },
{ countryCode: 'CHE', countryName: 'Switzerland' },
{ countryCode: 'SYR', countryName: 'Syria' },
{ countryCode: 'TJK', countryName: 'Tajikistan' },
{ countryCode: 'THA', countryName: 'Thailand' },
{ countryCode: 'TGO', countryName: 'Togo' },
{ countryCode: 'TTO', countryName: 'Trinidad and Tobago' },
{ countryCode: 'TUN', countryName: 'Tunisia' },
{ countryCode: 'TUR', countryName: 'Turkey' },
{ countryCode: 'UGA', countryName: 'Uganda' },
{ countryCode: 'UKR', countryName: 'Ukraine' },
{ countryCode: 'ARE', countryName: 'United Arab Emirates' },
{ countryCode: 'USA', countryName: 'United States' },
{ countryCode: 'URY', countryName: 'Uruguay' },
{ countryCode: 'UZB', countryName: 'Uzbekistan' },
{ countryCode: 'VEN', countryName: 'Venezuela' },
{ countryCode: 'VNM', countryName: 'Vietnam' },
{ countryCode: 'ZWE', countryName: 'Zimbabwe' },
];
// The fact table stores the country code, not the country name, so the code is hidden from the
// field panel. It is still available as the join key.
const medalsFields: AgFieldDefinition[] = [
{ id: 'athlete', format: 'textFormat' },
{ id: 'age', format: 'integerFormat' },
{ id: 'countryCode', format: 'textFormat', hide: true },
{ id: 'year', format: 'integerFormat' },
{ id: 'date', format: 'dateFormat' },
{ id: 'sport', format: 'textFormat' },
{ id: 'gold', format: 'integerFormat' },
{ id: 'silver', format: 'integerFormat' },
{ id: 'bronze', format: 'integerFormat' },
{ id: 'total', format: 'integerFormat' },
];
const countriesFields: AgFieldDefinition[] = [
{ id: 'countryCode', format: 'textFormat', hide: true },
{ id: 'countryName', format: 'textFormat', name: 'Country' },
];
const codeByCountryName = new Map(countryDictionary.map((row) => [row.countryName, row.countryCode]));
export function getData(medals: RawMedalRow[]): AgDataSourcesDefinition {
return {
sources: [
{
id: 'medals',
name: 'Medals',
// The medals asset ships country names, so the codes are applied here.
// Production data would already carry the code.
data: medals.map(({ country, ...rest }) => ({
...rest,
countryCode: codeByCountryName.get(country),
})),
fields: medalsFields,
},
{ id: 'countries', name: 'Countries', data: countryDictionary, fields: countriesFields },
],
relationships: [
{
id: 'medals-countries',
source: {
tableId: 'medals',
fieldId: 'countryCode',
},
target: {
tableId: 'countries',
fieldId: 'countryCode',
},
type: 'many-to-one',
},
],
};
}
The medals table joins many-to-one to the countries table on the code. The code itself is hidden on both tables, so it does not appear in the field panel. A hidden field can still be used as a join key.
<ag-studio
[sources]="sources"
[relationships]="relationships"
/* other studio properties ... */ />
this.sources = [{
id: 'medals',
data: [
{
sport: 'Swimming',
countryCode: 'USA',
// ... other fields
},
// ... other rows
],
fields: [
{ id: 'countryCode', format: 'textFormat', hide: true },
// ... other fields
],
}, {
id: 'countries',
data: [
{ countryCode: 'USA', countryName: 'United States' },
// ... one row per code
],
fields: [
{ id: 'countryCode', format: 'textFormat', hide: true },
{ id: 'countryName', format: 'textFormat', name: 'Country' },
],
}];
this.relationships = [
{
id: 'medals-countries',
source: {
tableId: 'medals',
fieldId: 'countryCode',
},
target: {
tableId: 'countries',
fieldId: 'countryCode',
},
type: 'many-to-one',
},
];Give the dictionary exactly one row per code. A duplicate code turns the join into a one-to-many, which inflates aggregated values. A code with no matching dictionary row leaves the label blank, as described in Null and Undefined Values.
See Relationships for how relationships are defined, and Star Schema for modelling several dictionary tables around one table of facts.
Null and Undefined Values Copy Link
A field value can be missing - either null or undefined. AG Studio treats both the same way by default, across every data type.
Display Copy Link
A missing value is never passed to a Format's value formatter. Instead, the field shows that Format's blankValue:
| Data Type | Default Format | Blank display |
|---|---|---|
string | textFormat | (Blanks) |
number | integerFormat / decimalFormat | N/A |
boolean | booleanFormat | (empty) |
date / datetime | dateFormat / dateTimeFormat | (empty) |
Sorting Copy Link
A missing value sorts as the smallest value by default, ahead of every other value in the field's data type, following the ascending-sort convention used by leading analytics and BI tools.
Filtering Copy Link
A missing value is excluded from filter results by default - for example, an equals or between condition never matches it.
Aggregation Copy Link
sum, avg, min, and max exclude missing values from the calculation. If every value in a group is missing, the result is null rather than 0 - this is standard practice across analytical engines, since a missing result and a true zero mean different things and collapsing them would hide information. count also excludes missing values.
countd (distinct count) counts a missing value as a distinct value by default, consistent with standard distinct-count semantics in analytical engines. first and last return the first or last non-missing value in sort order by default. Grouping treats a missing value as its own group rather than dropping those rows.