JavaScript is required

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() 会统一清理仍在使用的插件。

下一页:工具栏、缩略图与编辑套件。