A lightweight, zero-dependency JavaScript library for rendering interactive mind maps as SVG. Named after Porphyry of Tyre, the ancient philosopher who introduced the hierarchical tree of categories.
v1.4.0Zero dependenciesSVG-based
Quick Start
Drop a single script tag on the page. No bundler, no npm, no build step required.
<!-- 1. Include the library --><script src="porphyry.js"></script><!-- 2. Give it a container --><div id="map" style="width:100%;height:500px"></div><!-- 3. Initialize and render --><script>const map = newPorphyry('#map');
map.render({
topic: 'My Topic',
children: [
{ topic: 'Branch A' },
{ topic: 'Branch B', children: [
{ topic: 'Sub-node' }
]}
]
});
</script>
The container must have an explicit width and height — Porphyry fills 100% of it. Set these via CSS or inline styles.
Data Format
Porphyry accepts a plain JSON object. Every node has a topic and an optional children array. Nesting can go as deep as needed.
Node label. Long text wraps automatically within the configured max width.
url
string?
If present, the node becomes clickable and opens this URL in a new tab. A small ↗ icon appears inside the node.
direction
"left"|"right"?
Pin a root-level child to a specific side. Only respected on direct children of the root in horizontal layouts. Ignored in vertical layouts.
children
Node[]?
Child nodes. Omit or leave empty for leaf nodes.
Constructor
newPorphyry(selector, options)
Parameter
Type
Description
selector
string | Element
A CSS selector string or a DOM element to render into.
options
object?
Optional configuration. See Options reference below.
Options Reference
Layout
Option
Default
Description
layout
"auto"
Direction mode. One of "auto", "left", "right", "down", "up". See Layout Modes below.
fitPadding
64
Pixels of padding around the graph when auto-fitting to the container.
lineHeight
1.45
Line height multiplier for wrapped text.
Spacing (horizontal layouts)
Option
Default
Description
branchSpacingX
220
Horizontal gap (px) between the center node and depth-1 branches. Scales down automatically for deep trees.
subSpacingX
170
Horizontal gap (px) between sub-levels (depth ≥ 2). Also auto-scales.
verticalSpacing
50
Minimum vertical gap (px) between sibling nodes.
Spacing (vertical layouts)
Option
Default
Description
verticalSpacingY
60
Vertical gap (px) between depth levels in up/down layouts.
horizontalSpacing
30
Horizontal gap (px) between sibling subtrees in vertical layouts.
Center node
Option
Default
Description
center.fontSize
17
center.paddingX / paddingY
28 / 16
center.maxWidth
240
Max node width (px) before text wraps to next line.
center.radius
12
Corner radius of the center node rectangle.
center.fill
"#1A1F2E"
Background color.
center.color
"#FFFFFF"
Text color.
Branch nodes (depth 1)
Option
Default
Description
branch.fontSize
14
branch.paddingX / paddingY
18 / 10
branch.maxWidth
200
Max node width (px) before text wraps.
branch.color
"#FFFFFF"
Text color (fill comes from the color palette).
Leaf nodes (depth ≥ 2)
Option
Default
Description
leaf.fontSize
13
leaf.paddingX / paddingY
10 / 7
leaf.maxWidth
170
Max node width (px) before text wraps.
leaf.color
"#2D3748"
Text color.
Colors & edges
Option
Default
Description
colors
10-color palette
Array of hex color strings. Each root branch is assigned one in order; descendants inherit it.
edgeWidth.root
2.5
Stroke width of edges leaving the center node.
edgeWidth.branch
2
Stroke width of depth-1 → depth-2 edges.
edgeWidth.leaf
1.5
Stroke width of deeper edges.
edgeOpacity
0.85
Global opacity of all edges.
Interactions
All interactions are off by default for clean embedding. Opt in explicitly to what you need.
Option
Default
Description
interactions.pan
false
Enable drag-to-pan (mouse + touch).
interactions.zoom
false
Enable scroll-wheel zoom and pinch-to-zoom.
interactions.hud
false
Inject a zoom HUD (−, level %, +, fit) into the bottom-right of the container.
interactions.tips
false
Inject a hint bar at the bottom-center describing active interactions.
minZoom
0.08
Minimum zoom scale (when zoom is enabled).
maxZoom
4
Maximum zoom scale (when zoom is enabled).
zoomSensitivity
0.12
Scroll-wheel zoom speed per tick.
Layout Modes
Set via options.layout at init time, or by mutating instance.options.layout before calling render() again.
Value
Description
"auto"
Branches are distributed evenly left and right of the center node. Explicit direction fields in node data are honoured first; the remainder are balanced.
"left"
All branches grow to the left. Node direction fields are ignored.
"right"
All branches grow to the right. Node direction fields are ignored.
"down"
Tree grows downward. Siblings spread horizontally. All nodes use the outlined button style.
"up"
Tree grows upward. Siblings spread horizontally. All nodes use the outlined button style.
In "down" and "up" modes all nodes — including the center and branches — use an outlined button style (white fill, colored border) instead of the solid-fill styles used in horizontal layouts.
Methods
Method
Description
render(data)
Parse data, lay out the tree, and draw it. Clears any previous render. Automatically calls fit() after the first paint.
fit()
Scale and translate the graph so it fits neatly inside the container, respecting fitPadding.
reset()
Reset pan and zoom to 1:1, centered.
_rebindInteractions()
Call after mutating options.interactions at runtime. Strips old event listeners, re-attaches new ones, and updates cursor and tips text.
const map = newPorphyry('#map', {
layout: 'auto',
interactions: { pan: true, zoom: true, hud: true },
colors: ['#E05C5C', '#4A90D9', '#4CAF82'],
branchSpacingX: 200,
});
map.render(myData);
// Later — switch to vertical layout and re-render
map.options.layout = 'down';
map.render(myData);
// Toggle pan off at runtime
map.options.interactions.pan = false;
map._rebindInteractions();
Node Links
Any node can carry an optional url field. When present:
A small ↗ external-link icon appears inside the node on the right side.
The cursor changes to a pointer on hover.
Clicking opens the URL in a new tab (noopener noreferrer).
A drag that moves more than 5 px never triggers the link, so panning over linked nodes is safe.
{ topic: "React", url: "https://react.dev" }
Text Wrapping
Long node labels wrap automatically. Each depth level has a configurable maxWidth. Text is measured with a hidden Canvas element before layout so node dimensions are always exact — the layout engine never needs to re-run after drawing.
Words are greedily packed onto lines.
A single word wider than maxWidth gets its own line rather than being clipped.
Multi-line branch nodes (depth 1) switch from a pill shape to a rounded rectangle (rx=10) automatically.
All vertical spacing accounts for the actual wrapped height, so nodes never overlap.
Adaptive Column Spacing
In horizontal layouts, column gaps scale down automatically as the tree gets deeper, keeping wide maps readable without manual tuning.
Max depth
Factor
branchSpacingX
subSpacingX
≤ 2
1.00
220 px
170 px
3
0.83
183 px
141 px
4
0.63
138 px
107 px
5
0.50
110 px
85 px
≥ 6
0.45
99 px
77 px
The floor is 45 % of the configured defaults. Vertical layouts are unaffected.