HTML Renderer、Layer 与 Plugin
HTML 平台不使用框架插槽。自定义节点和连线内部内容使用 renderer;添加到背景、画布或视图上的内容使用 layer;可复用行为使用 plugin。
Renderer
type RGHtmlRenderers = {
node?: RGHtmlRenderer<RGHtmlNodeRenderContext>;
line?: RGHtmlRenderer<RGHtmlLineRenderContext>;
lineLabel?: RGHtmlRenderer<RGHtmlLineRenderContext>;
nodeExpandButton?: RGHtmlRenderer<RGHtmlExpandRenderContext>;
};
自定义节点
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;
}
}
renderer 可以返回 Node、DocumentFragment、文本、trustedHTML() 或空值。普通字符串永远作为文本处理:
node() {
return '<strong>这是文本,不会被解析</strong>';
}
node() {
return trustedHTML('<strong>只用于可信静态内容</strong>');
}
不要把用户输入传给 trustedHTML()。
自定义连线
line renderer 应创建 SVG namespace 节点,并使用上下文中的 pathInfo 和 lineConfig:
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 只替换文字内容。nodeExpandButton 上下文提供 expanded、position 和 toggle(event),自定义按钮必须自行绑定 toggle。
生命周期 renderer
频繁更新的复杂内容可以复用 DOM:
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) {
// 释放 renderer 自己创建的监听器或外部资源
}
};
运行时调用 setRenderers(nextRenderers) 可以整体替换 renderer。
Layer
可用层是 background、canvasBehind、canvasAbove、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 和 view 不随画布缩放;canvasBehind、canvasAbove 位于画布坐标系中。Layer factory 返回清理函数。
Plugin
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]
});
运行时也可以调用 const unuse = graph.use(plugin)。插件应返回幂等清理函数;graph.destroy() 会统一清理仍在使用的插件。
下一页:工具栏、缩略图与编辑套件。