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:

  1. What data? — Queries (Cube) or api-parameters (TIE API) define what to fetch
  2. How to transform? — Computed fields and postprocess shape the raw data
  3. How to display? — Highcharts configuration controls the visual output

⚙️ Rendering pipeline

There are two rendering paths. Both take the same YAML input and produce the same Highcharts JSON output.

x-middle (production)

The original renderer. A TypeScript service that sits between the Trendence App and CubeJS.

Trendence App
  -> sends TREVL YAML to x-middle API
  -> x-middle resolves template (merges shared config with component)
  -> x-middle queries CubeJS (one query per context)
  -> x-middle runs computed fields (JavaScript, per row)
  -> x-middle runs postprocess (JavaScript, full dataset)
  -> x-middle assembles Highcharts JSON
  -> App frontend renders chart with Highcharts library

Trevl::Renderer (v3, Ruby)

The new in-process renderer. Runs inside the Rails app — no external service needed. 🚀

Rails app
  -> Trevl::Renderer.render(component)
  -> resolves template
  -> DataSource.for(api).fetch(endpoint, params)
  -> runs computed fields via ExecJS
  -> runs postprocess via ExecJS
  -> builds Highcharts JSON
  -> returns {type, highchartsData, warnings}

The Ruby renderer supports pluggable data sources through a registry:

We started out using Cube.js as a semantic data layer to express data queries. Cube remains the primary data source for our HRM product, but the architecture is designed to be pluggable — the goal is to be able to connect any suitable data API as a TREVL data source.

Note: Cube and TIE are currently proprietary integrations within the Trendence platform. We’re working towards opening the data source interface so you can plug in your own APIs.

🔗 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:

  1. Computed field results (highest)
  2. Query result / TIE API data fields
  3. 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).