Skip to content

用 G6 浏览单张图 ​

kgdiff 展示的是一张图两个版本之间的变化,没有单独查看一张图的模式,这是有意为之:它的颜色只用来表示变化,单独一张图画出来会是一整片灰色;而浏览一张图需要的布局、筛选、样式、从某个节点逐步展开,正是图可视化库擅长的事。

不过 kgdiff 的解析器在这里同样用得上。本页用 @kgdiff/formats 读取文件(kgdiff 能读的格式都可以),再交给 AntV G6 v5 绘制。

安装 ​

sh
npm install @antv/g6 @kgdiff/core @kgdiff/formats

画出一张图 ​

html
<div id="graph" style="height: 640px"></div>
ts
import { Graph } from '@antv/g6';
import { normalize } from '@kgdiff/core';
import { compactIri, parseGraph } from '@kgdiff/formats';

const file = 'ontology.ttl';
const parsed = parseGraph(await (await fetch(file)).text(), { filename: file });
const graph = normalize(parsed.graph);
const { prefixes, predicateLabels } = parsed;

const g6 = new Graph({
  container: 'graph',
  autoFit: 'view',
  data: {
    nodes: [...graph.nodes.values()].map((node) => ({
      id: node.id,
      data: { type: node.types[0] ?? '' },
      style: { labelText: node.label === node.id ? compactIri(node.id, prefixes) : node.label },
    })),
    edges: [...graph.edges.values()].map((edge) => ({
      id: edge.key, // 唯一,同一对节点之间有多条关系时也不会重复
      source: edge.source,
      target: edge.target,
      style: {
        labelText: Object.hasOwn(predicateLabels, edge.predicate)
          ? predicateLabels[edge.predicate]
          : edge.label,
      },
    })),
  },
  node: { palette: { type: 'group', field: 'type' } }, // 每种类型一种颜色
  edge: { style: { endArrow: true } },
  layout: {
    type: 'd3-force',
    animation: false, // 一次算出最终位置
    link: { distance: 100 },
    manyBody: { strength: -300 },
    collide: { radius: 40 },
    x: { strength: 0.05 }, // 让没有关系的节点留在附近
    y: { strength: 0.05 },
  },
  behaviors: ['drag-canvas', 'zoom-canvas', 'drag-element'],
});
await g6.render();

G6 画在这个容器里,容器需要有尺寸。拖动画布可以平移,滚动滚轮可以缩放,拖动节点可以移动它。

这段代码处理了什么 ​

  • 任何格式。 parseGraph 会判断格式,并按 kgdiff 的方式读取:Turtle、N-Triples、N-Quads、TriG、RDF/XML、JSON-LD、OBO 和常见的 JSON 图结构;标签按你偏好的语言选取,OWL 存在量词约束转为关系,空白节点得到稳定的 id(见格式)。传入 rdf: { languages: ['zh', 'en'] } 会优先使用中文标签。
  • 每条关系的两端都有节点。 文件经常指向一些自己并未描述的 IRI(例如 rdfs:subClassOf owl:Thing)。解析时这类关系会保留下来,但 G6 不接受指向不存在节点的关系(报错 Node not found)。@kgdiff/core 的 normalize 会把这些端点补成节点,合并重复声明的节点,并保证每个节点都有标签。
  • 关系的 id 不重复。 不指定 id 时,G6 用 source-target 给关系命名,同一对节点之间的两条关系就会冲突(报错 Edge already exists)。edge.key 在整张图里是唯一的。
  • 名称可读。 没有标签的节点以 IRI 作标签,并用文件声明的前缀缩写。关系显示文件给它起的名字(RDF 属性自己的 rdfs:label、OBO [Typedef] 的名称),没有的话显示谓词的可读形式,例如 subClassOf、part of。Object.hasOwn 防止名为 constructor 的关系读到内置属性。
  • 按类型着色。 色板按节点的第一个类型着色。kgdiff 自己从不这样用颜色:在它的视觉语言(英文)里,颜色表示变化。
  • 布局快。 animation: false 一次算出最终位置,而不是把布局的 300 次迭代逐帧画出来;x / y 两个力把没有关系的节点留在附近,免得它们越飘越远,让整张图在自适应时被缩得很小。

更进一步 ​

其余都是 G6 的能力:其他布局(层次、辐射、环形……)、提示框、筛选、样式和交互,见 G6 文档。G6 会画出交给它的每个元素,所以对于有几万个术语的本体,请只画其中一部分,例如一个分支或某个术语的邻域:graph.outgoing.get(id) 和 graph.incoming.get(id) 给出一个节点的关系 key,graph.edges.get(key) 给出关系本身。

图变了的时候 ​

审阅两个版本之间的变化,是 kgdiff 的事:

  • 命令行:用于终端、CI 和自包含的 HTML 报告;
  • <kgdiff-viewer> Web 组件:在你自己的页面里展示差异;
  • @kgdiff/g6:在 G6 图里按 kgdiff 的视觉语言绘制差异,和你已有的 G6 视图放在一起。

基于 MIT 许可证发布。示例数据采用 CC0-1.0。