v1.4.0
LAYOUT

Porphyry.js

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.0 Zero dependencies SVG-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 = new Porphyry('#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.

{
  topic: "Root Subject",           // required — label text
  url:   "https://example.com",   // optional — makes node clickable
  direction: "left",              // optional — "left"|"right", auto otherwise
  children: [
    {
      topic: "First branch",
      children: [
        { topic: "Leaf node" },
        { topic: "Linked leaf", url: "https://..." }
      ]
    },
    { topic: "Second branch" }
  ]
}

Node fields

FieldTypeDescription
topicstringNode label. Long text wraps automatically within the configured max width.
urlstring?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.
childrenNode[]?Child nodes. Omit or leave empty for leaf nodes.

Constructor

new Porphyry(selector, options)
ParameterTypeDescription
selectorstring | ElementA CSS selector string or a DOM element to render into.
optionsobject?Optional configuration. See Options reference below.

Options Reference

Layout

OptionDefaultDescription
layout"auto"Direction mode. One of "auto", "left", "right", "down", "up". See Layout Modes below.
fitPadding64Pixels of padding around the graph when auto-fitting to the container.
lineHeight1.45Line height multiplier for wrapped text.

Spacing (horizontal layouts)

OptionDefaultDescription
branchSpacingX220Horizontal gap (px) between the center node and depth-1 branches. Scales down automatically for deep trees.
subSpacingX170Horizontal gap (px) between sub-levels (depth ≥ 2). Also auto-scales.
verticalSpacing50Minimum vertical gap (px) between sibling nodes.

Spacing (vertical layouts)

OptionDefaultDescription
verticalSpacingY60Vertical gap (px) between depth levels in up/down layouts.
horizontalSpacing30Horizontal gap (px) between sibling subtrees in vertical layouts.

Center node

OptionDefaultDescription
center.fontSize17
center.paddingX / paddingY28 / 16
center.maxWidth240Max node width (px) before text wraps to next line.
center.radius12Corner radius of the center node rectangle.
center.fill"#1A1F2E"Background color.
center.color"#FFFFFF"Text color.

Branch nodes (depth 1)

OptionDefaultDescription
branch.fontSize14
branch.paddingX / paddingY18 / 10
branch.maxWidth200Max node width (px) before text wraps.
branch.color"#FFFFFF"Text color (fill comes from the color palette).

Leaf nodes (depth ≥ 2)

OptionDefaultDescription
leaf.fontSize13
leaf.paddingX / paddingY10 / 7
leaf.maxWidth170Max node width (px) before text wraps.
leaf.color"#2D3748"Text color.

Colors & edges

OptionDefaultDescription
colors10-color paletteArray of hex color strings. Each root branch is assigned one in order; descendants inherit it.
edgeWidth.root2.5Stroke width of edges leaving the center node.
edgeWidth.branch2Stroke width of depth-1 → depth-2 edges.
edgeWidth.leaf1.5Stroke width of deeper edges.
edgeOpacity0.85Global opacity of all edges.

Interactions

All interactions are off by default for clean embedding. Opt in explicitly to what you need.

OptionDefaultDescription
interactions.panfalseEnable drag-to-pan (mouse + touch).
interactions.zoomfalseEnable scroll-wheel zoom and pinch-to-zoom.
interactions.hudfalseInject a zoom HUD (−, level %, +, fit) into the bottom-right of the container.
interactions.tipsfalseInject a hint bar at the bottom-center describing active interactions.
minZoom0.08Minimum zoom scale (when zoom is enabled).
maxZoom4Maximum zoom scale (when zoom is enabled).
zoomSensitivity0.12Scroll-wheel zoom speed per tick.

Layout Modes

Set via options.layout at init time, or by mutating instance.options.layout before calling render() again.

ValueDescription
"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

MethodDescription
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 = new Porphyry('#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 depthFactorbranchSpacingXsubSpacingX
≤ 21.00220 px170 px
30.83183 px141 px
40.63138 px107 px
50.50110 px85 px
≥ 60.4599 px77 px

The floor is 45 % of the configured defaults. Vertical layouts are unaffected.