-
Notifications
You must be signed in to change notification settings - Fork 7
Building Visualisations Guide
This comprehensive guide walks you through the process of building visualisations in Tupaia, from data sourcing to presentation configuration.
Building a visualisation in Tupaia involves three main components:
- Data Table - Where your data comes from (built-in tables or custom SQL)
- Report Transform Pipeline - How you fetch, transform, and format your data
- Dashboard Presentation - How the data is visualised to users
This guide synthesises information from multiple sources to provide a complete workflow for building visualisations.
Before building visualisations, familiarise yourself with:
- SQL basics (for custom data tables)
- JSON structure
- Your data schema (data elements, organisation units, entities)
- The type of visualisation you want to create (chart, matrix, or view)
All visualisations start with data. Tupaia provides both built-in data tables and the ability to create custom SQL data tables.
These are pre-configured data sources available in all reports:
| Data Table | Purpose | Key Parameters |
|---|---|---|
analytics |
Survey and indicator data |
dataElementCodes, organisationUnitCodes, period, aggregations
|
events |
Event-level survey data |
dataElementCodes, organisationUnitCodes, period
|
entities |
Organisation unit information |
entityCodes, type, filter
|
entity_relations |
Hierarchical relationships |
ancestorType, descendantType
|
entity_attributes |
Custom entity attributes |
entityCodes, filter
|
data_group_metadata |
Survey and indicator metadata |
dataGroupCodes, dataElementCodes
|
data_element_metadata |
Individual data element details | dataElementCodes |
survey_responses |
Raw survey response data |
surveyCodes, dataElementCodes
|
Example: Fetching analytics data
{
"transform": "fetchData",
"dataTableCode": "analytics",
"parameters": {
"dataElementCodes": ["BCD1", "BCD2"],
"organisationUnitCodes": ["TO", "FJ"],
"period": "2023"
}
}For more complex data requirements, you can create custom SQL data tables. These allow you to write custom queries and accept parameters.
Key characteristics:
- Use
:paramNamesyntax for named parameters - Always query from
transform_tablein SQL transforms - Can connect to external databases
- Support parameter validation (type, required, defaultValue)
For detailed information on creating custom data tables, see [Data Table Configuration.md](Data Table Configuration.md).
The transform pipeline is a sequence of transformations that fetch, manipulate, and format your data. Each transform is applied in order, with the output of one becoming the input to the next.
While transforms can be combined in many ways, a common pattern for entity-based visualisations follows this three-step structure:
1. Entity Relations Fetch (Recommended first)
- Fetches the hierarchical relationships between entities
- Determines which facilities/districts/countries to include
- Typically uses
entity_relationsdata table - Respects the
entityparameter from dashboard context
2. Entities Fetch (Recommended second)
- Fetches detailed information about the entities
- Provides entity names, codes, and types
- Uses output from entity relations
- Typically uses
entitiesdata table
3. Data Fetch (Recommended last)
- Fetches the actual survey/indicator data
- Uses entity codes from previous steps
- Typically uses
analyticsoreventsdata table
Example: Three-step entity-based transform
{
"transform": [
{
"transform": "fetchData",
"dataTableCode": "entity_relations",
"parameters": {
"descendantType": "facility"
}
},
{
"transform": "fetchData",
"dataTableCode": "entities",
"parameters": {
"entityCodes": "=$all.descendant"
}
},
{
"transform": "fetchData",
"dataTableCode": "analytics",
"parameters": {
"dataElementCodes": ["BCD1", "BCD2"],
"organisationUnitCodes": "=$all.code",
"aggregations": [{"type": "MOST_RECENT"}]
}
}
]
}Why this pattern works:
- Entity relations respects the user's selected entity (country, district, facility)
- Entity fetch provides human-readable names for the visualisation
- Data fetch uses the filtered entity list from previous steps
Alternative patterns:
- Skip entity relations if you know the exact entity codes
- Skip entity fetch if you don't need entity names
- Start directly with data fetch for simple queries
After fetching data, you typically need to transform it into the right shape for your visualisation.
| Transform | Purpose | When to Use |
|---|---|---|
insertColumns |
Add calculated or static columns | Adding derived values, categories, or labels |
updateColumns |
Modify existing column values | Transforming data, applying calculations |
excludeColumns |
Remove columns | Cleaning up unnecessary fields |
mergeRows |
Combine rows with same key | Aggregating data across columns |
sortRows |
Order rows | Controlling display order |
excludeRows |
Filter out rows | Removing unwanted data |
fillRows |
Fill missing rows | Ensuring complete data for all entities/periods |
insertRows |
Add new rows | Adding totals, averages, or summaries |
gatherColumns |
Pivot columns to rows | Converting wide data to long format |
orderColumns |
Reorder columns | Controlling column display order |
sql |
Custom SQL query | Complex transformations not possible with other functions |
Tupaia transforms support an expression language for calculations and references:
Field References:
-
=$columnName- Reference a column from the current row -
=$all.columnName- Reference a column from all rows (returns array)
Context Access:
-
=@params.paramName- Access dashboard parameters -
=@all.rows- Access all rows in the transform table
Functions:
- Math:
=sum($col1, $col2),=divide($numerator, $denominator) - Conditionals:
=if($value > 10, "High", "Low") - Strings:
=concat($firstName, " ", $lastName) - Dates:
=dateUtils.format($timestamp, "YYYY-MM-DD") - Comparisons:
=equals($status, "Active"),=greaterThan($value, 5)
Example: Adding calculated columns
{
"transform": "insertColumns",
"columns": {
"percentage": "=divide($numerator, $denominator) * 100",
"category": "=if($value > 50, 'High', 'Low')",
"label": "=concat($entityName, ' - ', $period)"
}
}For complete details on all transform functions, see [Report Building Configuration.md](Report Building Configuration.md).
The final transform structure depends on your visualisation type.
Charts expect data in rows format with specific column names:
Required columns:
-
name(string) - The label for each data point -
value(number) - The numeric value to display
Optional columns:
-
timestamp(number) - For time series charts - Any other columns for grouping or metadata
Example: Chart data format
[
{"name": "Category A", "value": 45},
{"name": "Category B", "value": 78},
{"name": "Category C", "value": 32}
]Transform to create chart data:
{
"transform": "updateColumns",
"columns": {
"name": "=$entityName",
"value": "=$BCD1"
}
}Matrices expect data with row and column structure:
Row format:
- Each row is an object with column keys as properties
- Special fields:
dataElement(row header),category/categoryId(row grouping)
Column format:
- Array of column objects with
key,title, and optionallyentityCode
Example: Matrix data format
{
"rows": [
{"dataElement": "Indicator 1", "TO": 45, "FJ": 67},
{"dataElement": "Indicator 2", "TO": 78, "FJ": 89}
],
"columns": [
{"key": "TO", "title": "Tonga"},
{"key": "FJ", "title": "Fiji"}
]
}Transform to create matrix:
{
"output": {
"type": "matrix",
"rowField": "dataElement",
"columns": [
{"key": "TO", "title": "Tonga"},
{"key": "FJ", "title": "Fiji"}
]
}
}Views expect simple rows format with flexible structure:
Format:
- Array of objects
- Each object represents one item to display
- Column names become display labels
Example: View data format
[
{"Facility": "Nuku'alofa Hospital", "Status": "Active", "Beds": 150},
{"Facility": "Vaiola Hospital", "Status": "Active", "Beds": 80}
]Let's build a complete example: A matrix showing facility indicators by district.
Goal: Display BCD1 and BCD2 indicators for all facilities in the selected district, with facilities as rows and indicators as columns.
{
"transform": [
{
"transform": "fetchData",
"dataTableCode": "entity_relations",
"parameters": {
"descendantType": "facility"
}
},
{
"transform": "fetchData",
"dataTableCode": "entities",
"parameters": {
"entityCodes": "=$all.descendant"
}
},
{
"transform": "fetchData",
"dataTableCode": "analytics",
"parameters": {
"dataElementCodes": ["BCD1", "BCD2"],
"organisationUnitCodes": "=$all.code",
"aggregations": [{"type": "MOST_RECENT"}]
}
},
{
"transform": "mergeRows",
"groupBy": ["organisationUnit"],
"using": "single"
},
{
"transform": "updateColumns",
"columns": {
"facilityName": "=$name",
"BCD1": "=$BCD1",
"BCD2": "=$BCD2"
}
},
{
"transform": "excludeColumns",
"columns": ["code", "name", "organisationUnit", "period"]
},
{
"transform": "sortRows",
"by": "facilityName"
}
],
"output": {
"type": "matrix",
"rowField": "facilityName",
"columns": [
{"key": "BCD1", "title": "Indicator 1"},
{"key": "BCD2", "title": "Indicator 2"}
]
}
}What this does:
- Fetches all facilities in the selected entity (respects dashboard context)
- Gets facility details (names, codes)
- Fetches BCD1 and BCD2 data for those facilities
- Merges rows so each facility has one row
- Renames columns for clarity
- Removes unnecessary columns
- Sorts facilities alphabetically
- Outputs as matrix with facilities as rows, indicators as columns
After building your report transform, you configure how it's presented to users.
Every dashboard item requires:
-
code- Unique identifier -
reportCode- Links to your report transform -
name- Display title -
config- Presentation configuration
Example: Basic chart presentation
{
"code": "FACILITY_CHART",
"reportCode": "FACILITY_REPORT",
"name": "Facility Indicators",
"config": {
"type": "chart",
"chartType": "bar",
"labelType": "fractionAndPercentage",
"valueType": "number"
}
}Entity Links (Clickable Entities): Make matrix cells or rows clickable to navigate to entity pages.
{
"type": "matrix",
"columns": [
{"entityCode": "TO", "entityLabel": "Tonga"},
{"entityCode": "FJ", "entityLabel": "Fiji"}
]
}DrillDown (Clickable Rows): Make matrix rows open enlarged view of another dashboard item.
{
"type": "matrix",
"drillDown": {
"itemCode": "DETAIL_MATRIX",
"parameterLink": "organisationUnitCode"
}
}Export Options: Allow users to export data.
{
"exportWithLabels": true,
"exportWithTableDisabled": false
}For complete details on presentation configuration, see [Dashboard Presentation Configuration.md](Dashboard Presentation Configuration.md).
Let's create a complete visualisation from start to finish.
Requirement: Show a time series chart of population growth for Pacific Island countries.
For this example, we'll use the built-in analytics data table which already contains our population data element.
File: report.json
{
"code": "POPULATION_GROWTH_REPORT",
"config": {
"transform": [
{
"transform": "fetchData",
"dataTableCode": "entity_relations",
"parameters": {
"descendantType": "country"
}
},
{
"transform": "fetchData",
"dataTableCode": "entities",
"parameters": {
"entityCodes": "=$all.descendant"
}
},
{
"transform": "fetchData",
"dataTableCode": "analytics",
"parameters": {
"dataElementCodes": ["POP_TOTAL"],
"organisationUnitCodes": "=$all.code",
"period": "RANGE(2018-01-01, 2023-12-31);YEARLY"
}
},
{
"transform": "updateColumns",
"columns": {
"name": "=concat($name, ' ', $period)",
"value": "=$value",
"timestamp": "=periodToTimestamp($period)"
}
},
{
"transform": "excludeColumns",
"columns": ["code", "period", "organisationUnit", "dataElement"]
},
{
"transform": "sortRows",
"by": "timestamp"
}
]
}
}File: dashboard_item.json
{
"code": "POPULATION_GROWTH_CHART",
"reportCode": "POPULATION_GROWTH_REPORT",
"name": "Population Growth (2018-2023)",
"config": {
"type": "chart",
"chartType": "line",
"valueType": "number",
"labelType": "fractionAndPercentage",
"periodGranularity": "year",
"chartConfig": {
"cartesian": {
"xAxisDomain": {
"min": {
"type": "number",
"value": "2018"
},
"max": {
"type": "number",
"value": "2023"
}
}
}
},
"exportWithLabels": true,
"presentationOptions": {
"hideAverage": true
}
}
}Add your dashboard item to the appropriate dashboard via the dashboard relation.
Result: Users see a line chart showing population growth over time for each country in the Pacific region.
- Fetch only the data you need (limit date ranges, entity codes, data elements)
- Use aggregations in
fetchDatarather than later transforms - Avoid unnecessary transforms (each step processes all rows)
- Use
excludeColumnsto remove fields you don't need
- Use descriptive column names
- Add comments in complex expressions
- Break complex calculations into multiple steps
- Follow the recommended entity relations → entities → data pattern
- Use
fillRowsto ensure complete data for all entities/periods - Handle missing values with
defaultValuein expressions - Validate data ranges with conditional expressions
- Use appropriate aggregations (MOST_RECENT, FINAL_EACH_YEAR, etc.)
- Provide clear, descriptive titles
- Use appropriate visualisation types for your data
- Enable exports when users need raw data
- Add entity links for navigation
- Use drillDown for detailed views
Show facility-level data for all facilities in selected district.
{
"transform": [
{"transform": "fetchData", "dataTableCode": "entity_relations", "parameters": {"descendantType": "facility"}},
{"transform": "fetchData", "dataTableCode": "entities", "parameters": {"entityCodes": "=$all.descendant"}},
{"transform": "fetchData", "dataTableCode": "analytics", "parameters": {"organisationUnitCodes": "=$all.code"}}
]
}Compare multiple countries across indicators.
{
"transform": [
{"transform": "fetchData", "dataTableCode": "analytics", "parameters": {"organisationUnitCodes": ["TO", "FJ", "VU"]}},
{"transform": "mergeRows", "groupBy": ["dataElement"], "using": "single"}
],
"output": {
"type": "matrix",
"rowField": "dataElement",
"columns": [
{"key": "TO", "title": "Tonga", "entityCode": "TO"},
{"key": "FJ", "title": "Fiji", "entityCode": "FJ"},
{"key": "VU", "title": "Vanuatu", "entityCode": "VU"}
]
}
}Show trends over time grouped by category.
{
"transform": [
{"transform": "fetchData", "dataTableCode": "analytics", "parameters": {"period": "RANGE(2020-01-01, 2023-12-31);MONTHLY"}},
{"transform": "insertColumns", "columns": {"category": "=if($value > 50, 'High', 'Low')"}},
{"transform": "updateColumns", "columns": {"name": "=concat($category, ' - ', $period)"}}
]
}Add totals or averages to matrix.
{
"transform": [
{"transform": "fetchData", "dataTableCode": "analytics"},
{"transform": "mergeRows", "groupBy": ["dataElement"], "using": "single"},
{"transform": "insertRows", "position": "end", "rows": [{"dataElement": "TOTAL", "value": "=sum($all.value)"}]}
]
}Possible causes:
- Entity parameter not passed correctly - check entity relations fetch
- Date range excludes available data - check period parameters
- Organisation unit codes don't exist - verify entity codes
- Data element codes incorrect - check spelling and case
Solution:
- Test each transform step individually
- Check intermediate data with
console.logequivalents - Verify parameter syntax (
=$columnvs$column)
Possible causes:
- Missing
mergeRowstransform - Incorrect
rowFieldin output configuration - Column keys don't match data keys
Solution:
- Ensure data is pivoted correctly with
mergeRows - Verify
rowFieldmatches actual column name - Check column
keymatches data object keys
Possible causes:
- Missing
=prefix - Incorrect column reference syntax
- Function name typo
- Column doesn't exist in current transform step
Solution:
- All expressions must start with
= - Use
=$columnnot$column - Verify column exists at that transform step
- Check function names in documentation
Possible causes:
-
parameterLinkfield doesn't exist in row data -
itemCodedoesn't exist - Wrong visualisation type (drillDown only works with matrix)
Solution:
- Verify field exists in transform output
- Check dashboard item code spelling
- Ensure
type: "matrix"in config
Possible causes:
- Missing
entityCodein column definition - Entity code doesn't exist in database
- Wrong format (should be object with
entityCodeandentityLabel)
Solution:
- Add
entityCodeto column objects - Verify entity codes are valid
- For row data, use
{"entityCode": "CODE", "entityLabel": "Name"}
- [Report Building Configuration.md](Report Building Configuration.md) - Complete reference for all transform functions, aliases, and expressions
- [Dashboard Presentation Configuration.md](Dashboard Presentation Configuration.md) - Complete reference for all visualisation types and configuration options
- [Data Table Configuration.md](Data Table Configuration.md) - Guide for creating custom SQL data tables
- Data Element - A specific data point tracked in Tupaia (e.g., "Number of beds", "Population")
- Organisation Unit - An entity in the hierarchy (country, district, facility)
- Entity - Generic term for organisation units
- Transform - A step in the data pipeline that modifies the data structure
- Aggregation - Combining multiple data points (e.g., MOST_RECENT, SUM, AVERAGE)
- Period - Time range or point for data (e.g., "2023", "2023Q1", "20230615")
- Matrix - Table visualisation with rows and columns
- DrillDown - Clicking a row to open detailed view
- Entity Link - Clickable entity that navigates to entity page
-
Expression - Dynamic calculation using
=syntax - Context - Runtime information (parameters, user, entity)