AG Studio: Build dashboards using native components in web apps. Join us for a webinar on 28th July at 2pm UTC+1 Register

JavaScript Embedded AnalyticsData Overview

Version 2.1.2

Data is provided to Studio using the data property.

Data is retrieved from data sources. A data source represents one or more tables of data.

When multiple tables are provided, Relationships describe how the tables are linked.

There are two main types of data source:

  • Sync Data - for data already loaded in the application.
  • Async Data - for data lazily loaded on demand.

See Sharing & Caching Data to reuse one data source across multiple instances of Studio.

The example above demonstrates configuring a single Sync Data.

const studioProperties = {
    data: {
        sources: [{
            id: 'medals',
            data: [
                {
                    year: 2000,
                    sport: 'Swimming',
                    country: 'United States',
                    // ... other fields
                },
                // ... other rows
            ],
        }],
    },

    // other studio properties ...
}

Relationships Copy Link

When multiple tables are provided, if there are relationships between the data, then these should be provided alongside the data sources. This will allow fields from different tables to be displayed together in the same widget.

The example above demonstrates two tables, Medals and Capitals, linked together by country. Both tables are Sync Data.

const studioProperties = {
    data: {
        sources: [{
            id: 'medals',
            data: [
                {
                    year: 2000,
                    sport: 'Swimming',
                    country: 'United States',
                    // ... other fields
                },
                // ... other rows
            ],
        }, {
            id: 'capitals',
            data: [
                {
                    country: 'United States',
                    capital: 'Washington, D.C.',
                    // ... other fields
                },
                // ... other rows
            ],
        }],
        relationships: [
            {
                id: 'medals-capitals',
                source: {
                    tableId: 'medals',
                    fieldId: 'country',
                },
                target: {
                    tableId: 'capitals',
                    fieldId: 'country',
                },
                type: 'many-to-one'
            },
        ],
    },

    // other studio properties ...
}

Normal relationships are defined using the AgDataRelationDefinition interface:

string
ID of the relationship.
sourceCopy Link
AgRelationField
Source field.
targetCopy Link
AgRelationField
Target field.
AgRelationType
The cardinality of the relationship from the source field to the target field.
acceptFanoutCopy Link
boolean
Accept row duplication a query genuinely introduces by joining through this relationship - e.g. a many-to-many relationship onto a pre-aggregated table with no finer-grained key available. When true, a query whose only path to a shared dimension crosses this relationship executes via ordinary join-through instead of being rejected; downstream aggregates must account for the duplication.

Joining Dates Copy Link

By default date fields are joined the same way as any other field, where matching rows are linked by exact date value.

It is also possible to join date fields of different granularities, or to override the granularity at which they are joined.

This is done by using a ::unit field ID in the relationship, Studio derives a bucketed column from the base date field and uses it as the join key.

The following units are supported. The value column shows the integer each unit produces, which is what the join key is compared against.

UnitValueExample
yearCalendar year2024
quarter1-41 = Jan-Mar
2 = Apr-Jun
3 = Jul-Sep
4 = Oct-Dec
month1-121 = January
12 = December
monthOfQuarter1-3Position within the quarter
week1-53ISO 8601 week number
weekOfMonth1-5Position within the month
day / dayOfMonth1-31Day of the month
dayOfWeek1-71 = Monday
7 = Sunday (ISO 8601)
weekend0 or 10 = weekday
1 = weekend
hour0-23UTC hour
timeOfDay0-30 = Night
1 = Morning
2 = Afternoon
3 = Evening
minute0-59UTC minute
second0-59UTC second

Both sides of the relationship can use ::unit. When they do, both must declare the same unit. Using different units on each side is a configuration error.

A plain date column can also appear on one side without a ::unit suffix. Studio extracts the same unit from it automatically, so both keys are comparable integers.

// Valid: both sides extract the same unit
{
    id: 'orders-targets',
    source: { tableId: 'orders', fieldId: 'order_date::quarter' },
    target: { tableId: 'targets', fieldId: 'target_date::quarter' },
    type: 'many-to-one',
}

// Valid: one side declares the common unit; the plain date side inherits it
{
    id: 'orders-targets',
    source: { tableId: 'orders', fieldId: 'order_date' },
    target: { tableId: 'targets', fieldId: 'target_date::quarter' },
    type: 'many-to-one',
}

// Error: mismatched units
{
    id: 'orders-targets',
    source: { tableId: 'orders', fieldId: 'order_date::month' },
    target: { tableId: 'targets', fieldId: 'target_date::year' },
    type: 'many-to-one',
}

The example above demonstrates joining at different granularities.

Modelling Data Copy Link

A dashboard's queries mostly do two things: filter/group by an attribute, and summarise a measure against it. Splitting tables along that line - tables you group by, and tables you summarise - is the basis of a data model that suits Studio well.

Star Schema Copy Link

A star schema puts this into practice: one fact table - the table holding your measures - related to several dimension tables, each by its own many-to-one relationship. It's named for the shape: the fact table sits at the centre with its dimensions radiating out around it.

productsorderItemscustomersregions many-to-onemany-to-onemany-to-one

The example above groups the orderItems fact table by two of its dimensions, products and regions, in a single widget - each dimension contributes its own many-to-one relationship, with no join between dimensions themselves.

This application of denormalised dimension tables produces fewer joins per query, leading to faster query times. This is why the star schema is the standard recommendation across the BI and analytics industry for read-heavy workloads such as dashboards and reporting.

Galaxy Schema Copy Link

Sometimes the data behind a dashboard arrives at more than one grain - daily order items and individual shipments, for example.

Avoid joining tables like these to each other directly. Instead, give each fact its own star, reusing the same dimension tables where they apply - a conformed dimension is a dimension table shared by more than one fact's star.

Multiple stars linked by conformed dimensions like this form a galaxy schema (also called a fact constellation). Joining the two facts directly can multiply rows on both sides, inflating measures in a way that's easy to miss.

The example below extends the star above with a second fact table: shipments (one row per shipment, a coarser grain than orderItems). Both facts relate to products and regions - now conformed dimensions, shared across both stars - while customers still relates to orderItems only:

productsorderItemscustomersshipmentsregions many-to-onemany-to-onemany-to-onemany-to-onemany-to-one
const studioProperties = {
    data: {
        sources: [
            { id: 'products', data: [/* ... */] },
            { id: 'customers', data: [/* ... */] },
            { id: 'regions', data: [/* ... */] },
            { id: 'orderItems', data: [/* ... */] },
            { id: 'shipments', data: [/* ... */] },
        ],
        relationships: [
            {
                id: 'order-items-products',
                source: { tableId: 'orderItems', fieldId: 'productId' },
                target: { tableId: 'products', fieldId: 'id' },
                type: 'many-to-one',
            },
            {
                id: 'order-items-customers',
                source: { tableId: 'orderItems', fieldId: 'customerId' },
                target: { tableId: 'customers', fieldId: 'id' },
                type: 'many-to-one',
            },
            {
                id: 'order-items-regions',
                source: { tableId: 'orderItems', fieldId: 'regionId' },
                target: { tableId: 'regions', fieldId: 'id' },
                type: 'many-to-one',
            },
            {
                id: 'shipments-products',
                source: { tableId: 'shipments', fieldId: 'productId' },
                target: { tableId: 'products', fieldId: 'id' },
                type: 'many-to-one',
            },
            {
                id: 'shipments-regions',
                source: { tableId: 'shipments', fieldId: 'regionId' },
                target: { tableId: 'regions', fieldId: 'id' },
                type: 'many-to-one',
            },
        ],
    },

    // other studio properties ...
}

Keep each fact's measure in its own widget, both grouped by products.name: one widget for sum(orderItems.netSales), another for count(shipments.id). Both widgets agree on which products they're showing, since they share the same products dimension.

The example above shows each fact as its own widget, both grouped by products.name.

Putting sum(orderItems.netSales) and count(shipments.id) in the same widget is safe: Studio detects that both measures come from facts sharing the products dimension and aggregates each fact independently before combining the results - the same handling described for calendars, not a special case limited to them.

Fan-out is still worth designing around, though. Joining two facts directly instead of through a shared dimension, or relating a dimension to a fact through a many-to-many relationship, are both shapes Studio can't safely combine automatically. See Fan-out Detection below for how Studio reports and controls these cases.

The ::unit override is intended for date fields, and only changes the granularity of a single join.

Snowflake Schema Copy Link

A dimension table can itself be normalised into further tables - splitting products into products and categories, for example, so each category's name is stored once rather than repeated on every product row. Following a dimension's relationships out another hop like this is called a snowflake schema, named for the way its dimensions branch further outward than a star's.

productscategoriesorderItemscustomersregions many-to-onemany-to-onemany-to-onemany-to-one

The example above groups orderItems.netSales by categories.name and products.name together. categories is not a dimension of orderItems directly - the join reaches it by following products' own relationship out one more hop.

Snowflaking trades query speed for storage: each extra hop is another join a query must perform. Prefer denormalising a dimension - keeping category as a plain column on products - unless the normalised attribute is large, changes independently of products, or is shared by dimensions outside this star.

Fan-out Detection Copy Link

Fan-out is row duplication introduced by a join: a shape where a single fact row can match more than one row on the other side, so a measure summed across the join counts some rows more than once. Two relationship shapes trigger it:

  • Two fact tables joined directly, rather than through a shared conformed dimension (see Galaxy Schema above).
  • A dimension related to a fact through a many-to-many relationship, so a fact row can attribute to more than one dimension member and vice versa.
orderItemstags many-to-many

In the shape above, an order item tagged with three tags contributes to sum(orderItems.netSales) three times over once grouped by tags.name - once per tag - rather than once.

Fan-out is almost never what you want. Inflated measures are easy to miss and hard to spot in review, since the numbers still look plausible - just wrong. Prefer remodelling the relationship (adding a conformed dimension, or normalising a many-to-many join through a bridge table) over accepting the duplication.

By default, Studio detects both shapes and reports a warning through the same collector used for other query validation issues, then runs the query anyway.

Accept the duplication for a specific relationship with acceptFanout: true, silencing its warning regardless of the data.options.fanout policy below:

{
    id: 'order-items-tags',
    source: { tableId: 'orderItems', fieldId: 'tagId' },
    target: { tableId: 'tags', fieldId: 'id' },
    type: 'many-to-many',
    acceptFanout: true,
}

Or control fan-out detection for the whole data model with data.options.fanout:

const studioProperties = {
    data: {
        sources: [/* ... */],
        options: {
            fanout: {
                warning: true, // default - report a warning for any unaccepted fan-out finding
                execute: 'allow', // default - run the query anyway; set to 'prevent' to block it instead
            },
        },
    },

    // other studio properties ...
}
const studioProperties = {
    data: {
        sources: [/* ... */],
        options: {
            fanout: false, // shorthand for { warning: false, execute: 'allow' } - never warn, never block
        },
    },

    // other studio properties ...
}

Set acceptFanout on a relationship only once you've confirmed the duplication is what you want - for example, a many-to-many relationship onto a table that's already pre-aggregated to a grain with no finer key available. Downstream aggregates still need to account for the duplication; Studio just stops warning about it.

Data API Copy Link

Properties available on the AgDataSourcesDefinition<TRegistry extends AgBaseRegistry = AgDefaultRegistry> interface.

sourcesCopy Link
AgDataSource<TRegistry>[]
One or more data sources.
relationshipsCopy Link
AgRelationDefinition[]
When using multiple related tables, this describes the fields that link the tables together.
expressionsCopy Link
AgExpressionFieldDefinition<TRegistry, AgFormat<TRegistry>, any>[]
Expression field definitions for calculated columns.
formatsCopy Link
TRegistry["formats"]
Overrides to existing formats, or additional custom formats.
descriptionCopy Link
string
AI-facing overview of the entire dataset: what it contains, what it's for, domain quirks.
calendarsCopy Link
AgCalendar[]
Named time dimensions (calendars) that supply date fragments and a continuous date spine.
bucketsCopy Link
TRegistry["buckets"]
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.
optionsCopy Link
AgDataSourcesOptions
Engine-wide behavioural options, such as fan-out detection policy.