Trendence is a Berlin-based HR data and analytics company. We believe data visualization should be modern, AI-ready, and accessible. That’s why we’ve been building TREVL — a custom DSL (domain-specific language) designed to make chart creation as simple as writing a few lines of YAML. A language humans easily can use and robots love.
💡 The idea
You describe a visualization in YAML. The engine fetches data, transforms it, and produces a Highcharts chart. You never write JavaScript for the chart itself — you declare what you want, and TREVL figures out the rest.
A TREVL component answers three questions:
- What data? — Queries (Cube) or api-parameters (TIE API) define what to fetch
- How to transform? — Computed fields and postprocess shape the raw data
- How to display? — Highcharts configuration controls the visual output
⚙️ Rendering pipeline
The TREVL engine takes YAML input and produces Highcharts JSON output.
Your app / notebook
-> Trevl::Renderer.render(component)
-> resolves template (merges shared config with component)
-> DataSource.for(api).fetch(endpoint, params)
-> runs computed fields via ExecJS (JavaScript, per row)
-> runs postprocess via ExecJS (JavaScript, full dataset)
-> builds Highcharts JSON
-> returns {type, highchartsData, warnings}
The renderer supports pluggable data sources through a registry:
- REST APIs — any JSON API via
DataSource::Api - CubeJS — analytical queries via
DataSource::Cube - Static data — in-memory data for notebooks and testing
- Custom — register your own with
Trevl::DataSource.register("name", MySource)
We use Cube.js as a semantic data layer for analytical queries, but the architecture is fully pluggable — connect any data API as a TREVL data source.
🔗 How variables work
TREVL uses $-prefixed references to bind data at render time. Think of them as placeholders that get replaced with real values when the chart is built.
Cube references
$Cube.field — A value from the query result (dimension or measure)
y: "$hrmOptionDistributionAcademics.weightedShare"
TIE API references
$endpoint.data.field — A value from a TIE API row
y: "$salary.data.q50"
$endpoint.meta.field — A value from TIE API response metadata
total: "$vacancies-count.meta.total_results"
$resource.endpoint.data.field — Resource-qualified reference for non-default resources
y: "$surveys.option-distribution.data.share"
See TIE API for the full reference syntax.
Computed fields and parameters
$computedFieldName — The result of a computed field you defined
name: "$name" # where 'name' was defined as: code: '"Zielgruppe"'
color: "$color" # where 'color' was defined as: code: '"#003F85"'
$param_name — A runtime parameter from the user’s filter selection
parameter: "$param_geschlecht_zielgruppe"
Resolution priority:
- Computed field results (highest)
- Query result / TIE API data fields
- Parameters (lowest)
🗂️ Cube models
TREVL queries reference CubeJS models. In HRM, each target group has its own Cube model with identical structure:
| Cube Model | Target Group |
|---|---|
hrmOptionDistributionAcademics |
Akademiker_innen |
hrmOptionDistributionSkilledworkers |
Fachkräfte |
hrmOptionDistributionGraduates |
Studierende |
hrmOptionDistributionPupils |
Schüler_innen |
This means a chart for Fachkräfte looks exactly like one for Akademiker_innen — only the Cube model name changes. Templates leverage this by parameterizing the model through the suffix convention (see Templates).