Write YAML, get Highcharts.
DSL Documentation · Demo Notebook · GitHub
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 -- the TREndence Visualization Language -- a custom DSL designed to make chart creation as simple as writing a few lines of YAML. A language humans easily can use and robots love.
AI-First Design
TREVL is built for humans and machines. Every feature is designed so that LLMs can generate, validate, and iterate on visualizations autonomously:
Trevl.schema_reference-- compact reference (~1500 tokens) optimized for system promptsTrevl.validate(yaml)-- structured error feedback for self-correction loopsTrevl.examples-- 10 annotated examples for few-shot learningTrevl::DataSource.for("name").field_names("endpoint")-- discover available data fieldsllms.txt-- machine-readable reference in the repo root- JSON Schema -- formal validation for editors, CI, and AI agents
# 1. Discover: what fields does this endpoint return?
Trevl::DataSource.for("myapi").field_names("salary")
# => ["q10", "q50", "q90"]
# 2. Reference: get the compact TREVL spec for a system prompt
Trevl.schema_reference
# => "# TREVL — TREndence Visualization Language\n..."
# 3. Examples: few-shot learning material
Trevl.examples.first
# => {name: "bar_chart", description: "Simple bar chart...", yaml: "..."}
# 4. Validate: catch errors before rendering
Trevl.validate(yaml).errors
# => ["[my_chart] Missing required field(s): api, highchartsData (at )"]
# 5. Render: YAML → Highcharts JSON
Trevl.render(yaml)
Features
- Declarative YAML -- define charts, scores, tables, and filters without writing JavaScript
- Pluggable data sources -- REST APIs, CubeJS, static/in-memory data, or build your own
- Computed fields -- per-row JavaScript transformations via ExecJS
- Postprocess -- full-dataset transforms (sort, filter, aggregate) in JavaScript
- Template inheritance -- share chart styles with deep merge
- JSON Schema validation -- catch errors before rendering
- iRuby notebooks -- render interactive Highcharts directly in Jupyter
- Fully offline -- bundled Highcharts JS, no CDN needed
- Standalone -- no Rails, no framework dependencies
Installation
gem "trevl", github: "trendence/trevl"
Prerequisites: Ruby >= 3.1, Node.js (brew install node) for computed fields.
Quick Start
require "trevl"
# 1. Register a data source
Trevl::DataSource.register("demo", Trevl::DataSource::Static.new(
data: {
"pirates" => {
"data" => [
{"name" => "Guybrush", "barrels" => 12},
{"name" => "LeChuck", "barrels" => 47},
{"name" => "Elaine", "barrels" => 3},
{"name" => "Stan", "barrels" => 28},
{"name" => "Murray", "barrels" => 0}
]
}
}
))
# 2. Render
results = Trevl.render(<<~YAML)
components:
- id: rum_consumption
type: chart
api: demo
highchartsData:
chart:
type: bar
title:
text: Annual Rum Consumption by Pirate (Barrels)
colors: ["#8B4513"]
series:
- name: Rum (barrels)
data:
x: "$pirates.data.name"
y: "$pirates.data.barrels"
YAML
# 3. Done -- results.first["highchartsData"] is ready for Highcharts
Examples Gallery
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
See examples/ for the YAML source of each chart.
HTML Export
Render TREVL to an HTML file:
html = Trevl.render_to_html(yaml, width: 1000, height: 500)
File.write("chart.html", html)
The output is a complete HTML document that pulls Highcharts from the CDN. Point highcharts_path at a local copy (see Highcharts) and it is embedded inline instead, which makes the file self-contained and usable offline. Open it in any browser, or take a screenshot for AI agents:
# Grover gem (Puppeteer wrapper)
Grover.new(html).to_png
# Ferrum (Chrome DevTools Protocol)
browser = Ferrum::Browser.new
browser.content = html
browser.screenshot(path: "chart.png")
# Playwright MCP (for AI agents)
# browser_navigate → browser_take_screenshot
iRuby Notebooks
require "trevl"
require "trevl/notebook"
nb = Trevl::Notebook.new
nb.chart(<<~YAML, data: {"salary" => {"data" => [...]}})
components:
- id: chart
type: chart
api: static
highchartsData:
chart:
type: column
series:
- data:
x: "$salary.data.level"
y: "$salary.data.value"
YAML
Highcharts is bundled -- charts render offline. See notebooks/demo.ipynb for 5 working examples.
Data Sources
Static (in-memory)
Trevl::DataSource.register("mydata", Trevl::DataSource::Static.new(
data: {"endpoint" => {"data" => [...], "meta" => {...}}}
))
REST API
Trevl::DataSource.register("myapi", Trevl::DataSource::Api.new(
base_url: "https://api.example.com/v1",
auth: Trevl::Auth::BearerToken.new(ENV["API_TOKEN"])
))
CubeJS
Trevl::DataSource.register("cube", Trevl::DataSource::Cube.new(
url: "https://cube.example.com/cubejs-api/v1",
token: ENV["CUBE_TOKEN"]
))
Custom
class MySource < Trevl::DataSource::Base
def fetch(endpoint, params = {}, resource: nil)
{"data" => MyDB.query(endpoint, params), "meta" => {}}
end
end
Trevl::DataSource.register("db", MySource.new)
Per-render injection
Instead of registering globally, pass data directly to a render call. Per-render data takes precedence over the registry and never touches shared state — the right choice when the data differs per request (e.g. web apps serving concurrent users).
For inline rows, pass the raw hash as data: — it answers any api: name in
the document, and components may omit api: entirely:
Trevl.render(yaml, data: {"scores" => rows_for_this_request})
Trevl.render_to_html(yaml, data: {"scores" => rows_for_this_request})
For full control (multiple sources, API/Cube instances), pass data_sources:
— entries win over data: for their name:
source = Trevl::DataSource::Api.new(base_url: "https://api.example.com/v1")
Trevl.render(yaml, data_sources: {"mydata" => source})
Validation
Validate TREVL YAML before rendering — catch errors early, not at render time.
result = Trevl.validate(<<~YAML)
components:
- id: my_chart
type: chart
YAML
result.valid? # => false
result.errors # => ["[my_chart] Missing required field(s): api, highchartsData (at )"]
Powered by JSON Schema (draft 2020-12) -- covers all 5 component types with conditional validation. The schema files work standalone in VS Code, CI pipelines, or any JSON Schema-compatible tool.
Ideal for AI agents: generate TREVL, validate, self-correct, render.
Full docs: trevl.trendence.com/validation
YAML Reference
Component Types
| Type | Description |
|---|---|
chart |
Highcharts visualization (bar, column, line, pie, ...) |
score |
Single KPI value with optional unit |
table |
Data table with column definitions |
text |
Static text / HTML content |
filter |
Filter options bound to data |
Variable References
"$endpoint.data.fieldName" # data row field
"$endpoint.meta.fieldName" # metadata field
"$resource.endpoint.data.fieldName" # with resource qualifier
"$computedFieldName" # computed field shorthand
Computed Fields
Per-row JavaScript expressions:
computed:
- name: color
arguments:
val: "$salary.data.value"
code: 'val > 60000 ? "#003F85" : "#ccc"'
Postprocess
Full-dataset JavaScript transforms:
postprocess: |
$result = $result
.sort((a, b) => b.value - a.value)
.slice(0, 10);
Templates
Trevl.template_store.register("blue_bar", {
"highchartsData" => {
"chart" => {"type" => "bar"},
"colors" => ["#003F85"]
}
})
- id: my_chart
template: blue_bar
highchartsData:
title:
text: My Chart
Deep merge: component overrides template at the same path.
Auth
# Bearer token
auth = Trevl::Auth::BearerToken.new("token")
# Custom -- any object with #apply(headers, url:)
class MyAuth
def apply(headers, url: nil)
headers["Authorization"] = "Bearer #{fetch_token}"
end
end
Configuration
Trevl.configure do |c|
c.logger = Logger.new($stdout, level: :info)
c.template_store = my_custom_store
end
Highcharts
TREVL produces Highcharts configuration; it does not ship Highcharts. Highcharts is commercial software by Highsoft and is deliberately not bundled here, so using it requires your own licence.
Trevl.render returns plain configuration hashes and never touches Highcharts at all.
Only the HTML export and the notebook display load it, and by default they reference
the CDN:
Trevl.configure do |c|
c.highcharts_url = "https://code.highcharts.com/11.4.0/highcharts.js" # default
c.highcharts_modules = ["https://code.highcharts.com/11.4.0/highcharts-more.js"]
end
Set highcharts_path to a local file and it gets inlined instead of linked, which is
what you want for offline use or air-gapped rendering:
Trevl.configure do |c|
c.highcharts_path = "/opt/highcharts/highcharts.js"
end
highcharts_modules accepts URLs and local paths under the same rule: a path is
inlined, a URL is referenced.
In a Rails app
If your app already renders charts, it almost certainly ships Highcharts through the
asset pipeline. In that case, do nothing: pass the hash from Trevl.render to your
existing frontend and let the bundle you already load draw it.
components = Trevl.render(yaml, data: {"rows" => rows})
# hand components.first["highchartsData"] to your Stimulus controller
Only the server-side HTML export needs its own copy. The leanest way to give it one is to reuse the file the asset pipeline already has:
# config/initializers/trevl.rb
Trevl.configure do |c|
local = Rails.root.join("vendor/javascript/highcharts.js")
c.highcharts_path = local if local.exist?
end
Without that initializer the export falls back to the CDN, which is fine for anything that renders in a browser with network access.
Development
bin/setup # install dependencies
bundle exec rspec # 140 specs
bundle exec standardrb # lint
bin/console # interactive console
Language Specification
The full TREVL v3.0 specification lives at trevl.trendence.com -- covering component schemas, query definitions, filter operators, computed fields, postprocess patterns, template inheritance, and data source integration.
License
MIT -- see LICENSE.
Highcharts is not covered by that licence and is not distributed with this project. Using it requires a licence from Highsoft; see NOTICE and highcharts.com/license.





