Nine D3 chart types, a map and a cloud
Charts
Each chart is a page of its own, addressed by URL, drawing from the same pipeline as the table it came from. A chart page takes the view it was opened from — the source, joins, filters, calculated fields and time range — so the chart and the table behind it always agree.
The chart pages
| Route | Draws | Its own parameters |
|---|---|---|
/graph/line |
Zoomable line chart with brush-to-zoom, reference lines for the minimum, average and maximum, and a multi-series legend. | x, y, series, span, agg |
/bar |
Bar chart, horizontal or vertical, sorted by value or label, with a top-N tail merged into one bar. | cat, val, orient=h, sort, max |
/pie |
A pie or donut; clicking a slice or legend entry opens the rows behind it. | cat, val, donut=1 |
/scatter |
Scatter plot, with bubbles sized by a numeric column and coloured by a category. | x, y, series, size, scheme |
/heatmap |
Grid with two categorical axes; each cell is a sum or a row count, in a named colour scheme. | x, y, val, scheme, show_val=1 |
/stacked |
Stacked bars: one bar per category, split by a series column. | cat, series, val, scheme |
/combo |
Bars and a line on one pair of axes, each with its own measure column. | cat, bar_val, line_val |
/treemap |
Tiles sized by a measure, grouped by a label column, with a top-N tail merged. | group, size, topn, scheme |
/histogram |
Histogram with server-side binning, so the counts are the data's counts rather than a client's sample. | col, bins (1–200, default 10) |
/wordcloud |
Word cloud of a text column. Each row contributes its words once, so a word's size is the number of rows using it, not the times one row repeats it. | col, topn, minlen, stop |
/map |
OpenStreetMap map with clustered markers, an optional heat layer, bubbles and colour rules. | see Maps |
/pivot |
Crosstab of rows against columns with one or more measures, subtotals and a grand total. | see Pivot |
/stats |
One row per column: count, nulls, distinct values, first, last and, for numeric columns, min, max, range, mean, median, a chosen percentile and the sample standard deviation. | l, f, p |
/insights |
Automatic observations: outliers by the Tukey fences, period-over-period deltas, top movers and seasonality. No model and no network call. | l, f |
Common parameters
Every data page and chart takes the same selection of the pipeline:
l=<spec>-
The left source:
table:namefrom the data directory,path:/absolute/file.csv, a remote path/../host/dir/file.csv,url:,redis:tablename|redis://host:port, ormariadb:server/table. j=<spec>|<leftkey>|<rightkey>- A left join, repeatable and chainable.
c=name:expression- A calculated field, added as a virtual column before any aggregation.
f=<col>:<op>:<value>- A filter, repeatable, applied after joins and before the chart aggregates.
span=1h&tcol=at- Bucket the dimension into time buckets before drawing.
range=last_30_days&rcol=date- A window resolved when the request arrives, so a link keeps its meaning tomorrow. See relative time ranges.
cs=,by=,order=- A cross-row plan: a running total, rank, z-score and so on, written beside each row. See cross-row columns.
back=,back2=-
Where the breadcrumb goes. Only a same-site path or an
http(s)://URL is followed.
Maps
GET /map plots the filtered view with
HTML::OSM and Leaflet. A row's position comes from a place column
that is geocoded locally (loc=, one lookup per distinct value,
remembered in the model store and shared by every dataset that names it) or
from coordinate columns (lat= and lon=). Rows
sharing a position become one marker.
popup=<col>- A column shown in the marker popup; repeatable.
max=N,zoom=N- How many markers to draw (default 2,000) and the initial zoom; by default the map fits its markers.
tiles=name- A named tile source from the
tile_sourcesconfiguration key, for a tile server on your own network. heat=1,weight=<col>- A heat layer over the markers, optionally weighted by a numeric column.
size=<col>- Markers become circles with a radius from a numeric column.
color=<col>withcolor_rule=op:value:colour-
Colour markers by thresholds, tried in order, first match wins; the
operators are
gt,ge,lt,le,eqandne. Unmatched values takecolor_default=, shown in the legend as "Other". series=<col>withscheme=- Colour by category from a named palette instead of by rules.
Markers are clustered, always, so a dense map draws a count at low zoom and
splits it as it is zoomed in. The Nominatim search box is switched off on
every map page, so nothing typed on a map is sent anywhere. An unknown
tiles=, an unknown scheme=, an unknown column,
color= together with series=, or a transposed
latitude and longitude pair is a 400 rather than a silently different map.
Resizing and exporting
Every chart can be resized by dragging its bottom-right grip, or from the keyboard: the grip takes focus, the arrow keys resize the chart, Shift takes bigger steps, Home and End go to the narrowest and widest it can be, and the size is announced as it changes.
Each chart page carries Copy link and Export SVG, Export PNG and Export PDF. The PDF is a one-page document the size of the chart, holding a high-resolution picture, so it needs no PDF library on the server. Saved files carry "Database::BI" at the bottom right, and use the light colours even when the reader has chosen the dark theme.
A chart is drawn in the browser, so what an export contains can only be seen in a browser. That is also why a chart on a canvas can be filtered again: it is a live snippet rather than a picture.
Time buckets
The aggregating routes bucket the dimension they already take, so a daily series is a request rather than a calculated field:
GET /bar?l=table:sales&cat=at&val=amount&span=1h
GET /graph?l=table:sales&x=at&y=amount&span=1h&agg=sum&series=region
GET /pivot?l=table:sales&rows=at&m=amount:sum:total&span=1mon
span= takes a count and
s, m, h, d,
w, mon, q or y. Labels
are ISO at the bucket's own resolution, so they sort chronologically as
strings. A value that cannot be read as a time lands in one visible
"(unparseable)" bucket rather than disappearing.