HTML renderers, layers, and plugins
The HTML platform does not use framework slots. Use renderers for node and line internals, layers for background/canvas/view content, and plugins for reusable behavior.
Renderers
type RGHtmlRenderers = {
node?: RGHtmlRenderer<RGHtmlNodeRenderContext>;
line?: RGHtmlRenderer<RGHtmlLineRenderContext>;
lineLabel?: RGHtmlRenderer<RGHtmlLineRenderContext>;
nodeExpandButton?: RGHtmlRenderer<RGHtmlExpandRenderContext>;
};
Custom nodes
renderers: {
node({node, checked, graph}) {
const root = document.createElement('div');
root.className = checked ? 'service service-checked' : 'service';
const title = document.createElement('strong');
title.textContent = node.text || '';
root.append(title);
return root;
}
}
A renderer may return a Node, DocumentFragment, text, trustedHTML(), or an empty value. Plain strings are always text:
node() {
return '<strong>This remains text</strong>';
}
node() {
return trustedHTML('<strong>Trusted static content only</strong>');
}
Never pass user input to trustedHTML().
Custom lines
Create SVG namespace nodes and use pathInfo and lineConfig from the context:
const SVG_NS = 'http://www.w3.org/2000/svg';
function renderLine({pathInfo, lineConfig}) {
const path = document.createElementNS(SVG_NS, 'path');
path.setAttribute('d', pathInfo.pathData);
path.setAttribute('fill', 'none');
path.setAttribute('stroke', lineConfig.line.color || '#888');
path.setAttribute('stroke-width', String(lineConfig.line.lineWidth || 1));
return path;
}
lineLabel replaces label content only. nodeExpandButton receives expanded, position, and toggle(event); bind toggle from the custom button.
Lifecycle renderers
Reuse DOM for complex content that updates frequently:
const nodeRenderer = {
create(context) {
const element = document.createElement('article');
element.innerHTML = '<strong></strong><small></small>';
return element;
},
update(element, context) {
element.querySelector('strong').textContent = context.node.text || '';
element.querySelector('small').textContent = context.node.data?.status || '';
},
destroy(element) {
// Release renderer-owned listeners or external resources.
}
};
Call setRenderers(nextRenderers) to replace renderers at runtime.
Layers
Available layers are background, canvasBehind, canvasAbove, and view:
layers: {
view({element, graph}) {
const badge = document.createElement('div');
badge.className = 'graph-status';
badge.textContent = 'Online';
element.append(badge);
return () => badge.remove();
},
canvasAbove({element}) {
const overlay = document.createElement('div');
element.append(overlay);
return () => overlay.remove();
}
}
background and view do not scale with the canvas. canvasBehind and canvasAbove use canvas coordinates. A layer factory returns its cleanup function.
Plugins
const statusPlugin = ({graph, host, getLayerElement, subscribe}) => {
const output = document.createElement('output');
getLayerElement('view').append(output);
const stop = subscribe('selection', state => {
output.textContent = state.checkedNodeId || state.checkedLineId || 'none';
});
return () => {
stop();
output.remove();
};
};
const graph = await createRelationGraph('#graph', {
data,
plugins: [statusPlugin]
});
At runtime, call const unuse = graph.use(plugin). Plugins should return idempotent cleanup functions; graph.destroy() cleans up any plugin still in use.