Library / SDK
spiermar/d3-flame-graph avatar
spiermar/d3-flame-graph

d3-flame-graph: rendering flame graphs from hierarchical JSON inside D3

A D3.js plugin that produces flame graphs from hierarchical data.

979 stars125 forksJavaScriptApache-2.0

At a glance

What is it?
d3-flame-graph is a D3 plugin that draws flame graphs from a JSON hierarchy of name, value and children, with sizing options, tooltips and zoom. It renders data you already have, it does not collect profiles, and the current version is a breaking change from the D3 v3 based releases.
Who is it for?
Adopt d3-flame-graph when you already have profile data in JSON, or can convert it with the burn CLI, and you want the graph inside a page you control, styled with your own CSS. Do not expect it to profile anything, since it is a renderer, and check whether your data uses self values before trusting frame widths, because selfValue defaults to false.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 80 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A renderer, not a profiler

d3-flame-graph is a D3.js plugin that produces flame graphs from hierarchical data. That first sentence sets the scope precisely. The library takes data that describes a hierarchy of stack frames and draws the familiar flame graph on top of it.

The README points newcomers at Brendan Gregg's post on flame graphs for the background, and quotes his summary of what they are for: a visualization of profiled software that lets the most frequent code paths be identified quickly and accurately. It also notes that his own open source programs create interactive SVGs, which is the alternative this library exists alongside rather than against.

So the audience is anyone who has a profile and wants to present it inside their own web interface rather than opening a standalone SVG file. A dashboard of a continuous profiling system, an internal tool that shows where a request spent its time, a documentation page with an embedded example. The library handles the drawing, and the surrounding application handles everything else.

What the library does not do is collect, parse or sample anything. If you do not yet have the data, this is the wrong dependency and no API call will produce a profile for you.

The input contract is name, value and children

The input is a plain hierarchical data structure in JSON. Each node has a name, a value and an optional array of children:

js
{
  "name": "<name>",
  "value": <value>,
  "children": [
    <Object>
  ]
}

That is the whole contract, and it is a useful one because it is the shape most profilers can produce or convert to. The README points at the burn CLI tool, which can convert multiple file formats into this hierarchical data structure, so the practical path for a given profiler is usually a conversion step rather than a custom adapter.

What the format does not specify is where the value comes from, and that is where the most common confusion starts. The plugin offers a switch for it, selfValue, and its meaning is worth reading closely. If selfValue is true, the plugin assumes the node value in the input callgraph represents only the internal node value, the self value, and not the sum of all children. If it is false, it assumes the value already includes the children values too. The default is false, which matches the behaviour of 1.x.

If you feed it a profile where the values are self values while the plugin assumes they are totals, every frame is drawn too wide and the picture is wrong in a way that looks plausible. That is the first thing to check when a graph looks off.

Adding the CDN build to a page

The quickest start is the jsdelivr CDN, where you reference the hosted CSS and JS files. The example loads D3 v7 from d3js.org, loads the plugin from jsdelivr, creates the chart with a width of 960, then fetches a JSON file and binds it to a div:

html
    <script
        type="text/javascript"
        src="https://cdn.jsdelivr.net/npm/[email protected]/dist/d3-flamegraph.umd.min.js"
    >
    <script type="text/javascript">
        var chart = flamegraph().width(960);

        d3.json("data.json")
            .then((data) => {
                d3.select("#chart").datum(data).call(chart);
            })
            .catch((error) => {
                return console.warn(error);
            });
    </script>

Three details are worth copying exactly. The CDN URL pins the package version at 5.0.0, so the page does not drift when a new release lands. The chart is configured through a chain of setter calls on a `flamegraph()` factory, and the width call returns a configured chart. And the binding follows the D3 idiom of selecting the element, attaching the data as the datum, and calling the chart with the selection, so the same object is the handle you keep for later updates.

The error handling in the example is deliberately minimal, logging a warning if the JSON fetch fails. For a dashboard that reloads on an interval, you would want to decide yourself what a failed fetch means for the previous picture.

Installing from npm and using the local build

The package is on npm, and the README asks you to have Node and npm installed. The install itself is one command:

code
$ npm install d3-flame-graph --save

The page then loads the plugin from node_modules rather than from a CDN, and the rest of the wiring is the same:

html
    <script
        type="text/javascript"
        src="node_modules/d3-flame-graph/dist/d3-flamegraph.umd.js"
    >
    <script type="text/javascript">
        var chart = flamegraph().width(960);

        d3.json("data.json")
            .then((data) => {
                d3.select("#chart").datum(data).call(chart);
            })
            .catch((error) => {
                return console.warn(error);
            });
    </script>

The package metadata explains what you are actually installing. The published files are the dist directory, the main entry is the UMD build at ./dist/d3-flamegraph.umd.js and the module entry is the ES build at ./dist/d3-flamegraph.es.js, with an exports map that resolves import to the ES file and require to the UMD file. The runtime dependencies are the D3 v3 era modules taken one by one rather than the whole library: d3-hierarchy, d3-selection, d3-transition, d3-scale, d3-array, d3-dispatch, d3-ease and d3-format, all at version 3 or 4.

That dependency list is the clearest statement of what the plugin is. It is a rendering component with a layout engine, and it does not pull in the whole of D3.

The d3.hierarchy breaking change and the node shape

Internally, the input data is transformed into a d3 hierarchy, and functions like onClick, label and zoom expose individual entries as hierarchy nodes that wrap the original data and add more properties:

code
{
  "data": <original user-provided object>,
  "parent": <another hierarchy node>,
  "children": [
    <hierarchy node>
  ],
  "x1": <double>,  // x2 - x1 is the size of this node, as a fraction of the root.
  "x2": <double>
}

The x1 and x2 values are the geometry the plugin computes, and their difference is the width of that frame as a fraction of the root, which is why a frame's size on screen is proportional to its share of the total.

The important caveat is the version break. This is a breaking change from previous versions of d3-flame-graph, which were based on version 3 of the d3 library, and the README points at the d3-hierarchy documentation. Any code you wrote against the old releases that reached into node internals, or that relied on the v3 selection and event model, needs review. The current dependency on d3-hierarchy at version 3 is the concrete expression of that move.

For most users the visible effect is smaller than it sounds. If your callbacks receive the node and read d.data.name, the shape of the code changes little. If you built custom interactions on the old node structure, the migration is the work.

Sizing, tooltips and the frame size floor

The API is a set of chainable setters on the chart, each of which returns the current value when called without an argument. That is the pattern throughout, and it makes the chart object easy to inspect as well as configure.

Width defaults to 960 pixels. Height defaults to the number of cell rows times the cell height, and a minimum height can override that if it is higher than the calculated value. Cell height defaults to 18 pixels. Title is empty by default and is displayed on top of the graph. The transition duration defaults to 750 milliseconds, with a separate setter for the easing function.

One option deserves a separate mention because it decides whether small frames are drawn at all. minFrameSize sets the minimum size in pixels for a frame to be displayed, and it defaults to 0. With the default, frames narrower than a pixel are still rendered as marks, which is how you get the dense slivers along the bottom of a flame graph. Raising it removes that clutter at the cost of hiding the deepest, narrowest frames.

Tooltips are a two method contract. The function you pass must implement `.show(d)` and `.hide()`, called when the tooltip should become visible and hidden, with show taking the flame graph frame as its single argument. The package includes a ready made implementation, `flamegraph.tooltip.defaultFlamegraphTooltip()`, and the README shows how to configure its text:

js
var tip = flamegraph.tooltip
    .defaultFlamegraphTooltip()
    .text((d) => "name: " + d.data.name + ", value: " + d.data.value);
flamegraph.tooltip(tip);

Because the tooltip is a plain object with a text method, you can supply your own implementation instead, which is how you would add a link to source or a filter action.

Where d3-flame-graph stops

The boundaries are clear enough to state plainly.

First, the library is a renderer. The README does not document any profiling, sampling or format parsing beyond the JSON hierarchy, and the recommended path for other formats is the separate burn CLI. If your profile arrives as a collapsed stack file, a pprof output or a custom trace format, you have a conversion step in front of you.

Second, the data has to be a tree. The plugin consumes hierarchical data, and the interactive behaviour of a flame graph, zooming into a frame and reading the stack below it, depends on that structure being present rather than flattened.

Third, the packaging story is unusual. The repository publishes no GitHub releases, so the versioned artefacts you consume come from npm, where the package is at version 5.0.0. The CDN example pins that version explicitly, which is the safe pattern, but it also means a page left pointing at an old version stays on it. The last push was on 2026-08-04, and the repository is not archived, so work continues on the default branch, which is master.

The alternative approach is worth comparing on its own terms. The interactive SVG approach produces a self contained file that can be attached to a bug report, served from a bucket, or opened offline by someone who will never run your application. This plugin produces a graph inside a live page, which is what you want when the data changes while the reader is watching, or when the graph sits inside a product. The trade is between a shareable artefact and a live component, not between good and bad tools.

Editorial conclusion

Adopt d3-flame-graph when you already have profile data in JSON, or can convert it with the burn CLI, and you want the graph inside a page you control, styled with your own CSS. Do not expect it to profile anything, since it is a renderer, and check whether your data uses self values before trusting frame widths, because selfValue defaults to false. Before upgrading from a 1.x release, plan for the d3-hierarchy breaking change, note the licence is Apache-2.0, and remember the repository publishes no GitHub releases while the last push was on 2026-08-04.

Frequently asked questions

How do I install d3-flame-graph?

Install it from npm with npm install d3-flame-graph --save, or reference the hosted build from jsdelivr. The CDN example pins the version at 5.0.0 in the script URL.

What data format does d3-flame-graph expect?

A JSON hierarchy where each node has a name, a value and an optional children array. The burn CLI tool can convert multiple file formats into this structure.

What does the selfValue option change in d3-flame-graph?

With selfValue set to true the plugin treats each node value as the internal value only rather than the sum of its children, and with false it assumes the value already includes the children. It defaults to false, matching 1.x behaviour.

How do I add a tooltip to a d3-flame-graph chart?

Pass a function that implements .show(d) and .hide() to the tooltip setter. The package includes flamegraph.tooltip.defaultFlamegraphTooltip(), whose text method you can chain to customise the content.

Is d3-flame-graph compatible with the older 1.x releases?

No. The current version is a breaking change from releases based on D3 version 3, because the data is now transformed into a d3 hierarchy and exposed as hierarchy nodes.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. README
  4. spiermar/d3-flame-graph on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/spiermar-d3-flame-graph.svg)](https://hysenlabs.com/projects/spiermar-d3-flame-graph)

Was this useful?