---
title: "applyGraphDiff"
description: "把 SerializedGraph 增量同步到 graphology 实例，替代 clear() 加 import()，保留已有节点的布局坐标。"
canonical_url: "https://sigma.mhaibaraai.cn/docs/utils/apply-graph-diff"
---
# applyGraphDiff

> 把 SerializedGraph 增量同步到 graphology 实例，替代 clear() 加 import()，保留已有节点的布局坐标。

## 用法

`SigmaGraph` 的 `data` 通道内部就走这个函数：换掉整份数据只会重建差异部分，其余节点不重建、布局不丢、相机不跳。自己持有 graphology 实例（`graph` 通道）时直接调用：

```vue [ApplyGraphDiffExample.vue]
<script setup lang="ts">
import Graph from 'graphology'
import type { SerializedGraph } from 'graphology-types'

const { data } = await useFetch('/api/data.json')

const payload = data.value as unknown as SerializedGraph
const graph = new Graph(payload.options)
graph.import(payload)

const prune = shallowRef(true)
const log = shallowRef<string[]>([])

const patch: SerializedGraph = {
  attributes: {},
  options: {},
  nodes: [
    { key: '11.0', attributes: { label: 'Valjean（已改名）', size: 45, color: '#e11d48' } },
    { key: '48.0', attributes: { label: 'Gavroche（已改色）', size: 30, color: '#f59e0b' } },
    { key: 'newcomer', attributes: { label: '新增节点', x: 0, y: 260, size: 24, color: '#a855f7' } }
  ],
  edges: [
    { source: '11.0', target: '48.0' },
    { source: '11.0', target: 'newcomer' },
    { source: '11.0', target: '尚未加载的节点' }
  ]
}

function record(prefix: string) {
  const { x, y } = graph.getNodeAttributes('11.0')
  log.value = [
    `${prefix} · Valjean (${(x as number).toFixed(1)}, ${(y as number).toFixed(1)}) · 节点 ${graph.order} · 边 ${graph.size}`,
    ...log.value
  ].slice(0, 3)
}

function shuffle() {
  graph.forEachNode((node) => {
    graph.mergeNodeAttributes(node, { x: (Math.random() - 0.5) * 700, y: (Math.random() - 0.5) * 700 })
  })
  record('已打乱坐标')
}

function sync() {
  applyGraphDiff(graph, patch, { prune: prune.value })
  record(prune.value ? '已同步（prune 开）' : '已同步（prune 关）')
}

async function restore() {
  applyGraphDiff(graph, await $fetch('/api/data.json') as unknown as SerializedGraph)
  record('已还原')
}
</script>

<template>
  <SigmaGraph :graph="graph">
    <SigmaControls>
      <div class="flex gap-1">
        <UButton size="xs" color="neutral" label="打乱坐标" @click="shuffle" />
        <UButton size="xs" color="neutral" label="同步不带坐标的数据" @click="sync" />
        <UButton size="xs" color="neutral" variant="ghost" label="还原" @click="restore" />
      </div>

      <UButton
        size="xs"
        :color="prune ? 'primary' : 'neutral'"
        :label="`prune ${prune ? '开' : '关'}`"
        class="self-start"
        @click="prune = !prune"
      />

      <div v-if="log.length" class="bg-accented p-2 text-muted text-xs font-mono">
        <p v-for="(line, index) in log" :key="`${line}-${index}`">
          {{ line }}
        </p>
      </div>
    </SigmaControls>
  </SigmaGraph>
</template>
```

```ts
watch(data, next => applyGraphDiff(graph.value, next))
```

`next` 必须是 graphology 的 `SerializedGraph`（`graph.export()` 的产物）。形状不对时立刻抛 `TypeError` 并点名缺失字段，不会拖到逐条边失败才报。

> [!NOTE]
> 
> graphology 实例本身就是可变数据结构，这里直接 mutation 是有意为之，也是本库不可变原则的唯一例外。

### preservePositions

节点属性按新数据整体替换，唯一例外是 `x` / `y`：新数据显式给出时以新值为准，未给出则沿用图上现有坐标。布局跑完后坐标只存在于图上、不在服务端返回的数据里，关掉这个开关，每次数据刷新整张图都会跳回初始坐标。

### prune

默认移除新数据中不存在的节点与边。做「概览 + 邻域按需展开」时必须关掉，否则每展开一个节点就会把其余部分清空：

```ts
applyGraphDiff(graph, await $fetch(`/api/graph/nodes/${id}/neighbors`), { prune: false })
```

增量合入时边可能指向尚未加载的节点，函数会跳过它们并在开发环境给出警告，而不是抛出难以定位的 `NotFoundGraphError`。

### 多重图的边身份

多重图上无 `key` 的边一律新增：按端点匹配会把三条 `a → b` 压成一条。代价是每次全量同步边 key 都会重新生成，需要稳定边身份、或用 `prune: false` 增量合入时，请在数据里显式给出 `key`。

## API

### applyGraphDiff()

`applyGraphDiff(graph: Graph, next: SerializedGraph, options?: ApplyGraphDiffOptions): void`

#### Parameters

**graph** (`Graph`) *required*: 目标 graphology 实例，就地修改。

**next** (`SerializedGraph`) *required*: 新数据，类型来自 graphology-types。

**options** (`ApplyGraphDiffOptions`): 行为选项。默认 true —— 已存在节点若在新数据中未显式给出 x / y，保留图上现有坐标。默认 true —— 移除新数据中不存在的节点与边。增量合入局部数据时应关闭。

类型从根出口取：

```ts
import type { ApplyGraphDiffOptions } from '@movk/sigma'
```


## Sitemap

See the full [sitemap](https://sigma.mhaibaraai.cn/sitemap.md) for all pages.
