Packages
Treemaps and heatmaps
The general-purpose charts in the family, on the same engine.
What Is in the Package
@ortex-charts/viz holds the general-purpose charts a financial data product needs
alongside its price charts: a category chart for bars and columns, a treemap, a heatmap, and
export helpers for PNG and CSV. They run on the same canvas layer, follow the same
theme tokens and share one animation frame with everything else on
the page.
npm install @ortex-charts/viz
Each component follows its container, is device-pixel-ratio aware, has an optional built-in tooltip, and is created and disposed the same way:
import { createTreemap, createHeatmap, createCategoryChart } from "@ortex-charts/viz";
const chart = createTreemap(el, { theme: "dark", data });
chart.applyOptions({ colorMode: "change" });
chart.setTheme("light");
chart.resize(900, 500);
chart.flush(); // draw synchronously
chart.remove();@ortex-charts/vizTreemap
A treemap sizes rectangles by value and colors them by change, which is the standard way to show a universe of instruments at once.
import { createTreemap, type TreemapNode } from "@ortex-charts/viz";
const data: TreemapNode = {
name: "S&P 500",
children: [
{
name: "Technology",
children: [
{ name: "AAPL", value: 3_100_000, change: 1.42 },
{ name: "MSFT", value: 2_900_000, change: -0.63 },
],
},
{
name: "Financials",
children: [{ name: "JPM", value: 620_000, change: 0.28 }],
},
],
};
const treemap = createTreemap(el, {
theme: "dark",
data,
colorMode: "change",
changeDomain: "auto",
valueFormat: { type: "compact", precision: 1 },
changeFormat: { type: "percent", precision: 2 },
headers: true,
breadcrumb: true,
drillDown: true,
onClick: (hit) => openInstrument(hit.data.name),
});
A node has name, an optional value for leaves, an optional change that drives the
diverging color scale, an optional explicit color inherited by descendants, and
children. Parents are sized by the sum of their leaves, so only the leaves need values.
| Option | Default | What it does |
|---|---|---|
colorMode |
"auto" |
auto uses an explicit color, else the change scale, else the group palette color. explicit, change and group force one of them. |
changeDomain |
"auto" |
Domain of the diverging scale; auto is symmetric about zero over the visible leaves. |
changeColors |
theme | [low, neutral, high]; defaults to theme.down, theme.grid, theme.up. |
valueFormat, changeFormat |
— | Number formatting for the labels and the tooltip. |
paddingInner, paddingOuter |
— | Gaps between and around rectangles. |
headers, headerHeight |
true |
Header strips above groups. |
breadcrumb |
true |
Path strip above the map while zoomed in. |
labels, minFontSize, maxFontSize |
true |
Names and values inside leaves when they fit. |
drillDown |
true |
Click a group to zoom in; click the breadcrumb to zoom out. |
treemap.setData(nextRoot);
treemap.zoomTo(node);
treemap.zoomOut(1);
treemap.currentRoot();
treemap.getPath(); // TreemapNode[] from the full root to the current one
treemap.getLayout(); // the laid-out tree in CSS pixels
layoutTreemap, treemapLeaves and findPath are exported for hosts that want the layout
without the canvas — for a server-rendered image, a test, or an accessible table
alternative.
Heatmap
A grid of rows by columns with a sequential or diverging color scale, which is what a returns calendar, a correlation matrix or a factor exposure grid wants.
import { createHeatmap } from "@ortex-charts/viz";
const heatmap = createHeatmap(el, {
theme: "light",
rows: ["2021", "2022", "2023", "2024", "2025"],
columns: ["Jan", "Feb", "Mar", "Apr", "May", "Jun"],
values: [
[1.2, -0.4, 2.8, 0.1, -1.9, 3.2],
[-2.1, 0.8, 1.1, NaN, 0.4, -0.7],
// …
],
scale: { type: "diverging", domain: "auto", center: 0 },
format: { type: "percent", precision: 1 },
cellLabels: true,
legend: true,
onClick: (hit) => drillInto(hit.rowLabel, hit.columnLabel),
});
values is row-major: values[row][column]. NaN is an empty cell and is drawn in
emptyColor rather than at the bottom of the scale, which matters for a calendar with
missing months.
| Option | Default | What it does |
|---|---|---|
scale.type |
"diverging" |
sequential or diverging. |
scale.domain |
"auto" |
[min, max], or derived from the finite values; symmetric about center when diverging. |
scale.center |
0 |
Center of a diverging scale. |
scale.colors |
theme | Two colors for sequential, three for diverging. |
format |
percent, 1 decimal | Cell and tooltip formatting. |
cellLabels |
true |
Print the value in the cell when it fits. |
cellGap |
1 |
Pixels between cells. |
rowLabels, columnLabels |
true |
Axis labels. |
columnLabelPosition |
"top" |
top or bottom. |
maxLabelWidth |
140 |
Cap on a label footprint in pixels. |
legend |
true |
Color bar under the grid. |
emptyColor |
a faint tint | Fill for NaN cells. |
heatmap.setData({ rows, columns, values });
heatmap.colorFor(1.4); // the color a value would get, for a host-drawn legend
resolveHeatmapDomain and createColorScale are exported for the same reason: a host that
wants to color a table cell to match the heatmap can use exactly the same scale.
Category Chart
Bars and columns over a categorical axis, stacked or grouped, horizontal or vertical.
import { createCategoryChart } from "@ortex-charts/viz";
const chart = createCategoryChart(el, {
theme: "dark",
orientation: "horizontal",
categories: ["Energy", "Financials", "Healthcare", "Technology", "Utilities"],
series: [
{ key: "long", title: "Long", color: "#22C08A", data: [12, 34, 21, 55, 8] },
{ key: "short", title: "Short", color: "#EF4E5A", data: [-4, -12, -6, -19, -2] },
],
stacked: true,
baseline: 0,
colorBySign: false,
valueLabels: true,
legend: true,
sortBy: "desc",
valueAxis: { position: "bottom", format: { type: "compact", precision: 1 }, gridlines: true, visible: true },
onClick: (hit) => filterBy(hit.category),
});
chart.setData({ categories, series });
orientation: "vertical" draws columns and "horizontal" draws bars. colorBySign colors
each bar by which side of baseline it falls on, using the theme up and down colors, which
is the fastest way to a readable contribution chart. sortBy orders categories by their
total across series without changing the underlying arrays, and CategoryHit.categoryIndex
is the index into your original categories, not into the displayed order.
Export
import { canvasToPNG, downloadPNG, downloadCSV, rowsToCSV, composeCanvases } from "@ortex-charts/viz";
await downloadPNG(chartCanvas, "sectors.png", { scale: 2, background: "#0E1218" });
const csv = rowsToCSV(rows, ["symbol", { key: "change", label: "1D %" }]);
downloadCSV(rows, "universe.csv", ["symbol", "value", "change"]);
const combined = composeCanvases([topCanvas, bottomCanvas], "#0E1218");
canvasToPNG returns a Blob for hosts that want to upload rather than download, and
composeCanvases stacks several canvases into one image, which is how a multi-pane
screenshot is assembled.
These helpers are in viz rather than in core because they are DOM download plumbing
rather than chart engine. A financial chart's own screenshot is
chart.takeScreenshot(), which returns a canvas you can pass straight to canvasToPNG.
Shared Options and Methods
Every component takes theme, width, height, tooltip and padding, and exposes:
component.root; // the container div it created
component.getTheme();
component.getOptions(); // readonly
component.setTheme(theme); // "dark", "light", or a Theme object
component.applyOptions(patch); // deeply partial
component.resize(width, height);
component.invalidate(); // schedule a redraw
component.flush(); // draw now
component.remove();
Leaving width and height unset makes the component follow its container, which is the
usual choice; setting them fixes the size, which is what an export path wants.
What Is Not Here
@ortex-charts/viz is three chart types, not a general charting library. There is no
scatter or bubble chart, no pie or donut, no radar, no sankey, no gauge, no geographic map
and no 3D. Apache ECharts has 20 or more chart types and is a reasonable thing to use
alongside this package if you need them.
What viz is for is the small set of general charts that have to look like the price chart,
share its theme, and cost nothing extra in the bundle because the engine is already there.