---
title: "SigmaGraph"
description: "根组件。创建 Sigma 实例与 graphology 图，下发上下文，透传配置，转发全部 sigma 事件。"
canonical_url: "https://sigma.mhaibaraai.cn/docs/components/graph"
---
# SigmaGraph

> 根组件。创建 Sigma 实例与 graphology 图，下发上下文，透传配置，转发全部 sigma 事件。

## 用法

`SigmaGraph` 在客户端创建 Sigma 实例与 graphology 图，把上下文下发给子树。所有 composable 都要在它的默认插槽内调用。

### `data`

数据有两条互斥通道。传 `data` 是托管的一条：库把新旧两份数据交给 [`applyGraphDiff`](https://sigma.mhaibaraai.cn/docs/utils/apply-graph-diff) 算增量，只改动真正变了的节点与边，画面不会整体闪一下。增量的匹配口径由 `diffOptions` 调。

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

const data = ref<SerializedGraph>({
  attributes: {},
  options: { type: 'mixed', multi: false, allowSelfLoops: true },
  nodes: [
    { key: 'a', attributes: { label: 'A', x: 0, y: 0, size: 20, color: '#e22653' } },
    { key: 'b', attributes: { label: 'B', x: 100, y: -100, size: 40, color: '#e28b53' } },
    { key: 'c', attributes: { label: 'C', x: 300, y: -200, size: 20, color: '#9be225' } },
    { key: 'd', attributes: { label: 'D', x: 100, y: -300, size: 20, color: '#53a4e2' } },
    { key: 'e', attributes: { label: 'E', x: 300, y: -400, size: 40, color: '#7553e2' } },
    { key: 'f', attributes: { label: 'F', x: 400, y: -500, size: 20, color: '#e253d5' } }
  ],
  edges: [
    { source: 'a', target: 'b', attributes: { size: 10 } },
    { source: 'b', target: 'c', attributes: { size: 10 } },
    { source: 'b', target: 'd', attributes: { size: 10 } },
    { source: 'c', target: 'b', attributes: { size: 10 } },
    { source: 'c', target: 'e', attributes: { size: 10 } },
    { source: 'd', target: 'c', attributes: { size: 10 } },
    { source: 'd', target: 'e', attributes: { size: 10 } },
    { source: 'e', target: 'd', attributes: { size: 10 } },
    { source: 'f', target: 'e', attributes: { size: 10 } }
  ]
})

const seq = ref(0)

function add() {
  const n = ++seq.value
  const angle = n * 1.2
  const key = `n${n}`

  data.value = {
    ...data.value,
    nodes: [...data.value.nodes, { key, attributes: { label: `新增 ${n}`, x: Math.cos(angle) * 180, y: Math.sin(angle) * 180, size: 8, color: '#a855f7' } }],
    edges: [...data.value.edges, { source: 'a', target: key }]
  }
}

function remove() {
  const key = `n${seq.value--}`

  data.value = {
    ...data.value,
    nodes: data.value.nodes.filter(node => node.key !== key),
    edges: data.value.edges.filter(edge => edge.target !== key)
  }
}
</script>

<template>
  <SigmaGraph :data="data" :settings="{ renderEdgeLabels: true }">
    <SigmaControls>
      <UButton label="新增节点" @click="add" />
      <UButton :disabled="seq === 0" label="移除最后一个" @click="remove" />
    </SigmaControls>
  </SigmaGraph>
</template>
```

> [!TIP]
> 
> 组件本身不带高度。画布用 
> 
> height: 100%
> 
>  占满容器，所以外层必须给出确定的高度，否则整张图不可见。

### `graph`

传 `graph` 是自持的一条：库完全不碰数据，只负责渲染与生命周期。图可以用 graphology 生态的任何方式操作，sigma 订阅了图事件，自己就会重绘。

```vue [GraphExternalExample.vue]
<script setup lang="ts">
import Graph from 'graphology'

const NODES = [
  { key: 'a', x: 0, y: 0, size: 20, label: 'A', color: '#e22653' },
  { key: 'b', x: 100, y: -100, size: 40, label: 'B', color: '#e28b53' },
  { key: 'c', x: 300, y: -200, size: 20, label: 'C', color: '#9be225' },
  { key: 'd', x: 100, y: -300, size: 20, label: 'D', color: '#53a4e2' },
  { key: 'e', x: 300, y: -400, size: 40, label: 'E', color: '#7553e2' },
  { key: 'f', x: 400, y: -500, size: 20, label: 'F', color: '#e253d5' }
]

const EDGES: [string, string][] = [
  ['a', 'b'],
  ['b', 'c'],
  ['b', 'd'],
  ['c', 'b'],
  ['c', 'e'],
  ['d', 'c'],
  ['d', 'e'],
  ['e', 'd'],
  ['f', 'e']
]

const graph = new Graph()

for (const { key, ...attributes } of NODES) {
  graph.addNode(key, attributes)
}
for (const [source, target] of EDGES) {
  graph.addEdge(source, target, { size: 10 })
}

const seq = shallowRef(0)

// 直接调 graphology 的 API：组件不参与，sigma 订阅了图事件自己重绘
function add() {
  const n = ++seq.value
  const angle = n * 1.2
  const key = `n${n}`

  graph.addNode(key, { label: `新增 ${n}`, x: Math.cos(angle) * 180, y: Math.sin(angle) * 180 - 250, size: 12, color: '#a855f7' })
  graph.addEdge('a', key, { size: 10 })
}

function remove() {
  graph.dropNode(`n${seq.value--}`)
}
</script>

<template>
  <SigmaGraph :graph="graph">
    <SigmaControls>
      <UButton label="graph.addNode()" @click="add" />
      <UButton :disabled="seq === 0" label="graph.dropNode()" @click="remove" />
    </SigmaControls>
  </SigmaGraph>
</template>
```

> [!WARNING]
> 
> 这条通道下 
> 
> data
> 
>  与 
> 
> diffOptions
> 
>  都不生效，两者同时传时开发环境会打一行警告。适合数据本来就由别处（Pinia、自己的 diff 逻辑、上游算法库）掌控的场景。

### `settings`

`settings` 整体交给 sigma，不逐字段枚举也不过滤未知键，而且可以热更新——引用变了就调 `setSettings()`，不重建实例。v4 的 `Settings` 只管行为与性能，视觉规则在 `styles` 里。下面这个示例传入一个库不认识的配置键，再从 `sigma.getSettings()` 原样读回：

```vue [GraphSettingsExample.vue]
<script setup lang="ts">
import type Sigma from 'sigma'
import Graph from 'graphology'

const graph = new Graph()

// Grid layout with varying sizes
const COLS = 8
const ROWS = 6
const SPACING = 100
const COLORS = ['#e22653', '#277da1', '#33cc33', '#ff9900', '#9b59b6', '#1abc9c']

for (let row = 0; row < ROWS; row++) {
  for (let col = 0; col < COLS; col++) {
    const id = `${row}-${col}`
    graph.addNode(id, {
      x: (col - (COLS - 1) / 2) * SPACING,
      y: ((ROWS - 1) / 2 - row) * SPACING,
      color: COLORS[(row + col) % COLORS.length]
    })

    if (col > 0) graph.addEdge(`${row}-${col - 1}`, id)
    if (row > 0) graph.addEdge(`${row - 1}-${col}`, id)
  }
}

const settings = {
  renderEdgeLabels: true,
  labelRenderedSizeThreshold: 0,
  futureUnknownSetting: '库不认识的键'
}

const readBack = shallowRef('')

function onReady(instance: Sigma) {
  const all = instance.getSettings() as unknown as Record<string, unknown>
  readBack.value = String(all.futureUnknownSetting)
}
</script>

<template>
  <SigmaGraph :graph="graph" :settings="settings" @ready="onReady">
    <SigmaControls>
      <div class="bg-accented p-2">
        futureUnknownSetting: {{ readBack }}
      </div>
    </SigmaControls>
  </SigmaGraph>
</template>
```

> [!NOTE]
> 
> 模块选项里的全局 
> 
> settings
> 
>  与组件级 
> 
> settings
> 
>  深度合并后一起透传，组件级优先。

> [!TIP]
> 
> renderEdgeLabels
> 
>  只是开关。边标签实际画不画还要过 sigma 的锚点启发式：某端节点被 hover 或 highlight，或者两端节点标签都正在显示。所以 
> 
> renderLabels: false
> 
>  会让边标签只在悬浮时冒出来。要脱开这条规则，把锚点换成全部节点（
> 
> edgeLabelAnchors: 'allNodes'
> 
> ），或者在 
> 
> styles.edges
> 
>  上写 
> 
> labelVisibility: 'visible'
> 
>  强制常显。

### 事件

`emits` 覆盖 sigma 事件全集，payload 类型与上游一致。指针事件分节点、边、画布、节点标签、边标签五组，此外还有节点拖拽、渲染生命周期（`beforeRender`、`afterRender`、`webglContextLost` 等），以及组件自己补的 `ready` 与 `update:graph`：

```vue [GraphEventsExample.vue]
<script setup lang="ts">
const log = shallowRef<string[]>([])

function push(line: string) {
  log.value = [line, ...log.value].slice(0, 5)
}

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

<template>
  <SigmaGraph
    :data="data"
    :settings="{ renderEdgeLabels: true, enableEdgeEvents: true }"
    @click-node="({ node }) => push(`clickNode ${node}`)"
    @enter-node="({ node }) => push(`enterNode ${node}`)"
    @click-edge="({ edge }) => push(`clickEdge ${edge}`)"
    @click-stage="() => push('clickStage')"
    @ready="() => push('ready')"
  >
    <SigmaControls>
      <div class="bg-accented p-2">
        <span class="text-muted text-xs">最近 5 条事件</span>
        <ul class="list-none text-muted text-xs font-mono">
          <li v-for="(line, index) in log" :key="`${line}-${index}`">
            {{ line }}
          </li>
        </ul>
      </div>
    </SigmaControls>
  </SigmaGraph>
</template>
```

> [!WARNING]
> 
> 其中四组要先在 
> 
> settings
> 
>  里开对应开关，四个开关的默认值都是 
> 
> false
> 
> ，这是 sigma 的默认行为，不是本库的限制：边事件要 
> 
> enableEdgeEvents
> 
> ，标签事件要 
> 
> nodeLabelEvents
> 
>  与 
> 
> edgeLabelEvents
> 
> ，拖拽事件要 
> 
> enableNodeDrag
> 
> 。

### `styles`

`styles` 是 v4 的声明式视觉层，与 `settings` 分工明确：`settings` 只管行为与性能，视觉规则一律写在 `styles` 里。每条规则有三种取值形式——常量、`{ attribute, dict, defaultValue }` 分类映射、`{ attribute, min, max, minValue, maxValue }` 数值映射。

```vue [GraphStylesExample.vue]
<script setup lang="ts">
import type { StylesDeclaration } from 'sigma/types'

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

const styles = computed<StylesDeclaration>(() => ({
  nodes: {
    label: { attribute: 'label' },
    color: { attribute: 'cluster', dict: dataset.value?.clusterColors ?? {}, defaultValue: '#999' },
    size: {
      attribute: 'score',
      min: 10,
      max: 50,
      minValue: dataset.value?.scoreExtent[0],
      maxValue: dataset.value?.scoreExtent[1]
    }
  },
  edges: { color: '#ccc', size: 5 }
}))
</script>

<template>
  <SigmaGraph :data="dataset!.data" :styles="styles" />
</template>
```

> [!NOTE]
> 
> styles
> 
>  只在构造 sigma 实例时读取，引用变了会重建实例并打一行警告。示例用 
> 
> await useFetch
> 
>  让数据先于挂载就绪，规则包在 
> 
> computed
> 
>  里因而只求值一次；直接在模板上写对象字面量，父组件每重渲染一次就会重建一次实例。

### `stylesBase`

sigma 拿到 `styles.nodes` 时是**整体替换**而非合并，所以库默认把用户规则合成在一套基础规则之后。`stylesBase` 控制用哪一套：`'default'`（默认，含深度层）、`'depthless'`（不声明深度层，自定义 `primitives.depthLayers` 时用）、`'none'`（不带任何基础规则，全部交给用户）。

```vue [GraphStylesBaseExample.vue]
<script setup lang="ts">
import type { SigmaStyles } from '@movk/sigma'

const { data } = await useFetch('/api/data.json')
const styles: SigmaStyles = {
  nodes: [
    {
      size: attributes => (attributes.size as number) / 2,
      cursor: 'grab'
    }
  ]
}
</script>

<template>
  <SigmaGraph :data="data" :styles="styles" />
</template>
```

> [!NOTE]
> 
> 切到 
> 
> none
> 
>  就能看到坍塌：示例只声明了 
> 
> size
> 
>  与 
> 
> cursor
> 
> ，基础规则一撤，标签绑定与悬浮反馈跟着一起没了。合成顺序与丢失清单见 
> 
> composeStyles
> 
> 。

### `primitives`

`primitives` 是渲染原语的注册表：节点形状与图层、边路径与端点、深度层。注册进来之后才能在 `styles` 里按名字引用。下面这个示例手写一个心形的 SDF 函数，与内置的 `circle` 一起注册，再由 `styles` 按节点的 `shape` 属性选形状：

```vue [GraphPrimitivesShapeExample.vue]
<script setup lang="ts">
import type { SDFShape } from 'sigma/rendering'
import type { SerializedGraph } from 'graphology-types'

// heart SDF 改自 Inigo Quilez 的 2D distance functions
// 尖端在原点，整体向上延伸到约 y=1，所以要下移 0.5 让心形居中于节点原点，再缩小 0.7 留出抗锯齿边距
const HEART_SCALE = 0.7
const HEART_Y_CENTER = 0.5

function sdfHeart(): SDFShape {
  const glsl = /* glsl */ `
float dot2_heart(vec2 v) { return dot(v, v); }

float sdf_heart(vec2 uv, float size) {
  float scale = ${HEART_SCALE.toFixed(4)};
  vec2 p = uv / (size * scale);
  p.y += ${HEART_Y_CENTER.toFixed(4)};
  p.x = abs(p.x);

  float d;
  if (p.y + p.x > 1.0) {
    d = sqrt(dot2_heart(p - vec2(0.25, 0.75))) - sqrt(2.0) / 4.0;
  } else {
    d = sqrt(min(dot2_heart(p - vec2(0.0, 1.0)), dot2_heart(p - 0.5 * max(p.x + p.y, 0.0)))) * sign(p.x - p.y);
  }

  return d * size * scale;
}
`

  return {
    name: 'heart',
    glsl,
    uniforms: [],
    inradiusFactor: 0.5
  }
}

// 内置工厂只能在这里按需加载：sigma 在模块顶层就读 WebGL2RenderingContext
const primitives = defineSigmaPrimitives(async () => {
  const { sdfCircle, layerFill } = await import('sigma/rendering')

  return {
    nodes: {
      shapes: [sdfCircle(), sdfHeart()],
      layers: [layerFill()]
    }
  }
})

const styles = {
  nodes: {
    shape: { attribute: 'shape', defaultValue: 'circle' }
  }
}

const data: SerializedGraph = {
  attributes: {},
  options: { type: 'mixed', multi: false, allowSelfLoops: true },
  nodes: [
    { key: 'a', attributes: { label: 'A', x: 0, y: 0, size: 20, color: '#e22653' } },
    { key: 'b', attributes: { label: 'B', x: 100, y: -100, size: 40, color: '#e28b53', shape: 'heart' } },
    { key: 'c', attributes: { label: 'C', x: 300, y: -200, size: 20, color: '#9be225', shape: 'heart' } },
    { key: 'd', attributes: { label: 'D', x: 100, y: -300, size: 20, color: '#53a4e2', shape: 'heart' } },
    { key: 'e', attributes: { label: 'E', x: 300, y: -400, size: 40, color: '#7553e2', shape: 'heart' } },
    { key: 'f', attributes: { label: 'F', x: 400, y: -500, size: 20, color: '#e253d5', shape: 'heart' } }
  ],
  edges: [
    { source: 'a', target: 'b', attributes: { size: 10 } },
    { source: 'b', target: 'c', attributes: { size: 10 } },
    { source: 'b', target: 'd', attributes: { size: 10 } },
    { source: 'c', target: 'e', attributes: { size: 10 } },
    { source: 'd', target: 'e', attributes: { size: 10 } },
    { source: 'f', target: 'e', attributes: { size: 10 } }
  ]
}
</script>

<template>
  <SigmaGraph :data="data" :primitives="primitives" :styles="styles" />
</template>
```

> [!WARNING]
> 
> 取用 sigma 内置的形状与层工厂时必须包在 
> 
> defineSigmaPrimitives()
> 
>  里延迟加载。sigma 在模块顶层就读 
> 
> WebGL2RenderingContext
> 
> ，静态导入会让 SSR 直接 
> 
> ReferenceError
> 
> 。内置工厂的全集见 
> 
> defineSigmaPrimitives
> 
> 。

> [!NOTE]
> 
> primitives
> 
>  与 
> 
> styles
> 
>  一样只在构造时读取，同样应该提到 setup 顶层保持引用稳定。

### `customNodeState`

`customNodeState` / `customEdgeState` / `customGraphState` 声明自定义状态标志位的默认值，键名不能与 `BaseNodeState` 等内置状态冲突。状态写进 sigma 实例，`styles` 里有两种消费方式：布尔标志位用 `whenState`，多取值的标志位用 `matchState` 按取值分支；两者都表达不了的组合判断走 `when` 回调，`state` 与 `graphState` 都在参数里。把状态形状传给 `SigmaStyles<NS, ES, GS>`，规则里读到的就是精确类型而非 `unknown`：

```ts
interface NodeState { isActive: boolean }
interface GraphState { hasActiveSubgraph: boolean }

const styles: SigmaStyles<NodeState, object, GraphState> = {
  nodes: [
    { whenState: 'isActive', then: { depth: 'topNodes' } },
    { when: (_attributes, state, graphState) => graphState.hasActiveSubgraph && !state.isActive, then: { color: '#f6f6f6' } }
  ]
}
```

悬停节点时把它与邻居标为活跃、其余元素刷灰并清空标签，三个标志位各用上一次。

```vue [GraphStateExample.vue]
<script setup lang="ts">
import type { SigmaStyles } from '@movk/sigma'

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

const customNodeState = { isActive: false }
const customEdgeState = { isActive: false }
const customGraphState = { hasActiveSubgraph: false }

// 布尔标志位用 whenState 直接消费；跨状态的组合判断走 when，后两个参数分别是元素自身的
const styles: SigmaStyles<typeof customNodeState, typeof customEdgeState, typeof customGraphState> = {
  nodes: [
    {
      whenState: 'isActive',
      then: { depth: 'topNodes', labelVisibility: 'visible' }
    },
    {
      when: (_attributes, state, graphState) => graphState.hasActiveSubgraph && !state.isActive,
      then: { color: '#f6f6f6', label: '' }
    }
  ],
  edges: [
    {
      whenState: 'isActive',
      then: { depth: 'topEdges' }
    },
    {
      when: (_attributes, state, graphState) => graphState.hasActiveSubgraph && !state.isActive,
      then: { color: '#f6f6f6' }
    }
  ]
}

/** 悬停节点把它与邻居标为活跃，图级标志位告诉规则此刻有活跃子图，外观全部交给 styles */
function setActiveSubgraph(node?: string) {
  const sigma = graphRef.value?.sigma
  const graph = graphRef.value?.graph
  if (!sigma || !graph) {
    return
  }

  const subgraph = node ? new Set([node, ...graph.neighbors(node)]) : undefined
  const isActive = (key: string) => subgraph?.has(key) ?? false

  sigma.setNodesState(graph.filterNodes(key => isActive(key)), { isActive: true })
  sigma.setNodesState(graph.filterNodes(key => !isActive(key)), { isActive: false })

  // 两端都在子图里才算活跃的边，否则边界上会挂出半截高亮的连线
  sigma.setEdgesState(graph.filterEdges((_key, _attributes, source, target) => isActive(source) && isActive(target)), { isActive: true })
  sigma.setEdgesState(graph.filterEdges((_key, _attributes, source, target) => !isActive(source) || !isActive(target)), { isActive: false })

  sigma.setGraphState({ hasActiveSubgraph: subgraph !== undefined })
}
</script>

<template>
  <SigmaGraph
    ref="graph"
    :data="data"
    :styles="styles"
    :custom-node-state="customNodeState"
    :custom-edge-state="customEdgeState"
    :custom-graph-state="customGraphState"
    @enter-node="({ node }) => setActiveSubgraph(node)"
    @leave-node="() => setActiveSubgraph()"
  />
</template>
```

> [!NOTE]
> 
> 状态存在 sigma 内部，不污染 graphology 的属性，导出图数据时不会把 UI 状态一并带走。读写状态的 composable 见 
> 
> useSigmaState
> 
> 。

### `nodeReducer` / `edgeReducer`

两个 reducer 原样透传给 sigma 的构造函数，是逐帧按外部状态计算显示数据、`styles` 静态映射表达不了时的逃生舱。签名与 sigma v4 一致：`(key, data, attributes, state, graphState, graph)`。常规视觉映射一律写 `styles`：

```vue [GraphReducerExample.vue]
<script setup lang="ts">
import type { EdgeReducer, NodeReducer } from 'sigma/types'

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

const DIM_OPACITY = 0.12

const query = shallowRef('')
const hoveredNode = shallowRef<string>()

const keyword = computed(() => query.value.trim().toLowerCase())

function matches(label: unknown): boolean {
  return String(label ?? '').toLowerCase().includes(keyword.value)
}

// 悬停优先，没有悬停时才看搜索词；两个条件都不成立就原样返回，省掉每帧的无谓拷贝
const nodeReducer: NodeReducer = (key, displayData, attributes, _state, _graphState, graph) => {
  const hovered = hoveredNode.value

  if (hovered) {
    return key === hovered || graph.areNeighbors(key, hovered)
      ? displayData
      : { ...displayData, opacity: DIM_OPACITY, labelVisibility: 'hidden' }
  }

  if (!keyword.value) {
    return displayData
  }

  return matches(attributes.label)
    ? { ...displayData, labelVisibility: 'visible' }
    : { ...displayData, opacity: DIM_OPACITY, labelVisibility: 'hidden' }
}

// 边跟着两端走：一端还在焦点里就保留，否则一起淡出
const edgeReducer: EdgeReducer = (key, displayData, _attributes, _state, _graphState, graph) => {
  const [source, target] = graph.extremities(key)
  const hovered = hoveredNode.value

  if (hovered) {
    return source === hovered || target === hovered ? displayData : { ...displayData, opacity: DIM_OPACITY }
  }

  if (!keyword.value) {
    return displayData
  }

  const hit = matches(graph.getNodeAttribute(source, 'label')) || matches(graph.getNodeAttribute(target, 'label'))
  return hit ? displayData : { ...displayData, opacity: DIM_OPACITY }
}

watch([keyword, hoveredNode], () => graphRef.value?.sigma?.refresh())
</script>

<template>
  <SigmaGraph
    ref="graph"
    :data="data"
    :node-reducer="nodeReducer"
    :edge-reducer="edgeReducer"
    @enter-node="({ node }) => (hoveredNode = node)"
    @leave-node="() => (hoveredNode = undefined)"
  >
    <SigmaControls>
      <UInput v-model="query" placeholder="搜索节点标签，如 Jean" />
    </SigmaControls>
  </SigmaGraph>
</template>
```

> [!WARNING]
> 
> reducer 只在每帧求值，sigma 不订阅它读到的 ref。示例里的搜索词变了要自己调一次 
> 
> refresh()
> 
> ，否则画面不会动——悬停之所以看着「自动生效」，是 sigma 因 hover 状态变化顺带重绘了。reducer 动到 
> 
> labelVisibility
> 
>  或 
> 
> visibility
> 
>  时不要传 
> 
> skipIndexation
> 
> ，标签栅格必须重建。

> [!NOTE]
> 
> reducer 只能走这两个 props，构造后无法通过 
> 
> sigma.setSetting('nodeReducer', fn)
> 
>  更新——v4 里它已不是 
> 
> Settings
> 
>  的键。库内规则只在用户 reducer 之后叠加，不会顶掉你的声明。

### `labelAtlas`

节点标签走 SDF 渲染：字形先按一个**源字号**烘进图集纹理，再按 `styles` 里的 `labelSize` 缩放绘制。这个 prop 调的是前者的两个参数——`fontSize` 与 `maxTextureSize`，改它不会让标签变大变小。

上游按 `64 × devicePixelRatio` 烘节点标签的字形。2 倍屏上一个字形连同 buffer 占约 144px，2048² 的图集一页只排得下约 190 个——Latin 字母表绰绰有余，中文字形集则轻易溢出。溢出之后图集翻页，而渲染程序只上传第一页，症状是**超出第一页的字形不显示**。sigma `4.0.0-beta.5` 及更早版本在翻页那一步还会把第一页截成 1px 宽，节点标签会整体消失，`4.0.0-beta.6` 已修复这一截断，只上传第一页的限制仍在。边标签的图集不带这个系数所以照常显示，1 倍屏也看不出问题，控制台一行报错都没有。上游追踪见 [jacomyal/sigma.js#1552](https://github.com/jacomyal/sigma.js/issues/1552) 与 [#1554](https://github.com/jacomyal/sigma.js/pull/1554)。

`fontSize` 的默认值因此定在 `64`，不随 `devicePixelRatio` 放大。SDF 存的是矢量距离场，源字号减半在 12~24px 的显示字号上看不出差别。只有确定标签全是 Latin、且 `labelSize` 开得很大时，才值得调回 `64 × devicePixelRatio` 换更锐利的边缘——这个字段是绝对字号不是倍数，2 倍屏该写 `128`，3 倍屏写 `192`。

一页装得下多少字形约等于 `floor(maxTextureSize / (fontSize + 18))²`：

| `fontSize` / `maxTextureSize` | 约可容字形 | 内存 |
| --- | --- | --- |
| `64` / `2048`（默认） | 576 | 约 16MB |
| `48` / `2048` | 960 | 约 16MB |
| `64` / `4096` | 2400 | 约 64MB |

这是保守下界——图集按实际字形宽度做 shelf 排布，Latin 比 CJK 窄，真实容量更高。中文图谱的不重复字数仍可能超，超了就调大 `maxTextureSize`（容量按边长平方增长，离屏 canvas 与纹理的占用同比增长；超出本机 GL 的 `MAX_TEXTURE_SIZE` 会被夹回去）或调小 `fontSize`。

> [!NOTE]
> 
> 容量再大也可能被超，所以开发环境下图集一翻页就会打一行告警，指明该调哪个字段。生产环境不打。

## API

### Props

```ts
/**
 * Props for the SigmaGraph component
 */
interface SigmaGraphProps {
  /**
   * 图数据，经 `applyGraphDiff` 增量同步。
   * 传了 `graph` 时本项失效，两者互斥
   */
  data?: SerializedGraph | undefined;
  /**
   * 外部 graphology 实例。传入后本组件完全不碰数据，只负责渲染与生命周期，
   * 可自由使用 graphology 生态的任何方式操作图。省略则内部创建一个并经 `update:graph` 回传
   */
  graph?: default<Attributes, Attributes, Attributes> | undefined;
  /**
   * 声明式视觉规则，排在基础规则之后因而覆盖它们。
   * sigma 只在构造时读取，变更会重建实例
   */
  styles?: SigmaStyles<NoInfer<NS>, NoInfer<ES>, NoInfer<GS>> | undefined;
  /**
   * 与哪一套基础规则合成。sigma 拿到 `styles.nodes` 时是整体替换而非合并，
   * 不合成就会丢掉标签绑定、`isHidden` 可见性与悬浮反馈
   * @default 'default'
   */
  stylesBase?: "default" | "depthless" | "none" | undefined;
  /**
   * 渲染原语：节点形状、边路径、端点、深度层。同样只在构造时读取。
   * 取用 sigma 内置的形状与层工厂时须包在 `defineSigmaPrimitives()` 里延迟加载
   */
  primitives?: PrimitivesDeclaration | SigmaLazyPrimitives | undefined;
  /**
   * sigma 行为与性能配置，整体透传，可热更新
   */
  settings?: Partial<Settings> | undefined;
  /**
   * 逐帧计算显示数据的逃生舱，styles 表达不了时才用。
   * 常规视觉映射一律写 `styles`
   */
  nodeReducer?: (key: string, data: NodeDisplayData, attrs: Attributes, state: BaseNodeState, graphState: BaseGraphState, graph: AbstractGraph<...>): Partial<...> | undefined;
  /**
   * 边侧的同名逃生舱
   */
  edgeReducer?: (key: string, data: EdgeDisplayData, attrs: Attributes, state: BaseEdgeState, graphState: BaseGraphState, graph: AbstractGraph<...>): Partial<...> | undefined;
  /**
   * 自定义节点状态标志位的默认值，键名不能与 `BaseNodeState` 冲突
   */
  customNodeState?: ForbidBaseKeys<BaseNodeState, NS> | undefined;
  /**
   * 自定义边状态标志位的默认值
   */
  customEdgeState?: ForbidBaseKeys<BaseEdgeState, ES> | undefined;
  /**
   * 自定义图级状态标志位的默认值
   */
  customGraphState?: ForbidBaseKeys<BaseGraphState, GS> | undefined;
  /**
   * 节点标签 SDF 字形图集的参数，调的是烘进纹理的源字形，不是标签显示字号（后者在 `styles` 的 `labelSize`）。
   * 
   * 上游按 `64 × devicePixelRatio` 生成字形，2 倍屏上 2048² 的图集一页只装得下约 190 个，
   * 中文字形集溢出后超出第一页的字形不会显示，故 `fontSize` 压回 64；字形集更大时再调 `maxTextureSize`。
   * 与 `primitives` 一样只在构造时读取，挂载后改不生效
   */
  labelAtlas?: SigmaLabelAtlasOptions | undefined;
  /**
   * 实例 id，登记后可经 `useSigmaById(id)` 在组件树之外访问
   */
  id?: string | undefined;
  /**
   * `applyGraphDiff` 的行为选项
   */
  diffOptions?: ApplyGraphDiffOptions | undefined;
}
```

### Emits

```ts
/**
 * Emitted events for the SigmaGraph component
 */
interface SigmaGraphEmits {
  /**
   * 单击节点
   */
  clickNode: (payload: [payload: SigmaNodeEventPayload]) => void;
  /**
   * 双击节点，默认会触发相机缩放，可在 payload 上 `preventSigmaDefault()` 阻止
   */
  doubleClickNode: (payload: [payload: SigmaNodeEventPayload]) => void;
  /**
   * 右键点击节点
   */
  rightClickNode: (payload: [payload: SigmaNodeEventPayload]) => void;
  /**
   * 在节点上滚动滚轮
   */
  wheelNode: (payload: [payload: SigmaNodeEventPayload]) => void;
  /**
   * 在节点上按下指针
   */
  downNode: (payload: [payload: SigmaNodeEventPayload]) => void;
  /**
   * 在节点上松开指针
   */
  upNode: (payload: [payload: SigmaNodeEventPayload]) => void;
  /**
   * 指针进入节点
   */
  enterNode: (payload: [payload: SigmaNodeEventPayload]) => void;
  /**
   * 指针离开节点
   */
  leaveNode: (payload: [payload: SigmaNodeEventPayload]) => void;
  /**
   * 单击边，需先开启 `enableEdgeEvents`
   */
  clickEdge: (payload: [payload: SigmaEdgeEventPayload]) => void;
  /**
   * 双击边
   */
  doubleClickEdge: (payload: [payload: SigmaEdgeEventPayload]) => void;
  /**
   * 右键点击边
   */
  rightClickEdge: (payload: [payload: SigmaEdgeEventPayload]) => void;
  /**
   * 在边上滚动滚轮
   */
  wheelEdge: (payload: [payload: SigmaEdgeEventPayload]) => void;
  /**
   * 在边上按下指针
   */
  downEdge: (payload: [payload: SigmaEdgeEventPayload]) => void;
  /**
   * 在边上松开指针
   */
  upEdge: (payload: [payload: SigmaEdgeEventPayload]) => void;
  /**
   * 指针进入边
   */
  enterEdge: (payload: [payload: SigmaEdgeEventPayload]) => void;
  /**
   * 指针离开边
   */
  leaveEdge: (payload: [payload: SigmaEdgeEventPayload]) => void;
  /**
   * 单击节点标签，需先配置 `nodeLabelEvents`
   */
  clickNodeLabel: (payload: [payload: SigmaNodeLabelEventPayload]) => void;
  /**
   * 双击节点标签
   */
  doubleClickNodeLabel: (payload: [payload: SigmaNodeLabelEventPayload]) => void;
  /**
   * 右键点击节点标签
   */
  rightClickNodeLabel: (payload: [payload: SigmaNodeLabelEventPayload]) => void;
  /**
   * 在节点标签上滚动滚轮
   */
  wheelNodeLabel: (payload: [payload: SigmaNodeLabelEventPayload]) => void;
  /**
   * 在节点标签上按下指针
   */
  downNodeLabel: (payload: [payload: SigmaNodeLabelEventPayload]) => void;
  /**
   * 在节点标签上松开指针
   */
  upNodeLabel: (payload: [payload: SigmaNodeLabelEventPayload]) => void;
  /**
   * 指针进入节点标签
   */
  enterNodeLabel: (payload: [payload: SigmaNodeLabelEventPayload]) => void;
  /**
   * 指针离开节点标签
   */
  leaveNodeLabel: (payload: [payload: SigmaNodeLabelEventPayload]) => void;
  /**
   * 单击边标签，需先配置 `edgeLabelEvents`
   */
  clickEdgeLabel: (payload: [payload: SigmaEdgeLabelEventPayload]) => void;
  /**
   * 双击边标签
   */
  doubleClickEdgeLabel: (payload: [payload: SigmaEdgeLabelEventPayload]) => void;
  /**
   * 右键点击边标签
   */
  rightClickEdgeLabel: (payload: [payload: SigmaEdgeLabelEventPayload]) => void;
  /**
   * 在边标签上滚动滚轮
   */
  wheelEdgeLabel: (payload: [payload: SigmaEdgeLabelEventPayload]) => void;
  /**
   * 在边标签上按下指针
   */
  downEdgeLabel: (payload: [payload: SigmaEdgeLabelEventPayload]) => void;
  /**
   * 在边标签上松开指针
   */
  upEdgeLabel: (payload: [payload: SigmaEdgeLabelEventPayload]) => void;
  /**
   * 指针进入边标签
   */
  enterEdgeLabel: (payload: [payload: SigmaEdgeLabelEventPayload]) => void;
  /**
   * 指针离开边标签
   */
  leaveEdgeLabel: (payload: [payload: SigmaEdgeLabelEventPayload]) => void;
  /**
   * 单击空白画布
   */
  clickStage: (payload: [payload: SigmaEventPayload]) => void;
  /**
   * 双击空白画布，默认会触发相机缩放
   */
  doubleClickStage: (payload: [payload: SigmaEventPayload]) => void;
  /**
   * 右键点击空白画布
   */
  rightClickStage: (payload: [payload: SigmaEventPayload]) => void;
  /**
   * 在空白画布上滚动滚轮，默认会触发相机缩放
   */
  wheelStage: (payload: [payload: SigmaEventPayload]) => void;
  /**
   * 在空白画布上按下指针
   */
  downStage: (payload: [payload: SigmaEventPayload]) => void;
  /**
   * 在空白画布上松开指针
   */
  upStage: (payload: [payload: SigmaEventPayload]) => void;
  /**
   * 指针进入画布区域
   */
  enterStage: (payload: [payload: SigmaEventPayload]) => void;
  /**
   * 指针离开画布区域
   */
  leaveStage: (payload: [payload: SigmaEventPayload]) => void;
  /**
   * 指针在画布上移动，坐标随动，用于自绘跟随层
   */
  moveBody: (payload: [payload: SigmaEventPayload]) => void;
  /**
   * 开始拖拽节点，需先开启 `enableNodeDrag`
   */
  nodeDragStart: (payload: [payload: SigmaNodeDragEventPayload]) => void;
  /**
   * 拖拽节点过程中
   */
  nodeDrag: (payload: [payload: SigmaNodeDragMovePayload]) => void;
  /**
   * 结束拖拽节点
   */
  nodeDragEnd: (payload: [payload: SigmaNodeDragEventPayload]) => void;
  /**
   * 每帧清空画布前
   */
  beforeClear: (payload: []) => void;
  /**
   * 每帧清空画布后
   */
  afterClear: (payload: []) => void;
  /**
   * 图数据进入渲染管线处理前
   */
  beforeProcess: (payload: []) => void;
  /**
   * 图数据处理完成、即将绘制前
   */
  afterProcess: (payload: []) => void;
  /**
   * 每帧绘制前
   */
  beforeRender: (payload: []) => void;
  /**
   * 每帧绘制后
   */
  afterRender: (payload: []) => void;
  /**
   * 每帧纹理上传完成后
   */
  afterTexturesUpload: (payload: []) => void;
  /**
   * 浏览器回收了 WebGL 上下文，画布此刻是空的
   */
  webglContextLost: (payload: []) => void;
  /**
   * 上下文已恢复，sigma 重建了渲染资源并自动重绘
   */
  webglContextRestored: (payload: []) => void;
  /**
   * 容器尺寸变化、画布已重新适配
   */
  resize: (payload: []) => void;
  /**
   * 实例被销毁
   */
  kill: (payload: []) => void;
  /**
   * 实例创建完成，payload 是原生 `Sigma` 对象
   */
  ready: (payload: [sigma: default<Attributes, Attributes, Attributes, {}, {}, {}, PrimitivesDeclaration>]) => void;
  /**
   * 内部创建的 graphology 实例回传，仅在未传 `graph` 时触发
   */
  update:graph: (payload: [graph: default<Attributes, Attributes, Attributes>]) => void;
}
```

### Slots

```ts
/**
 * Slots for the SigmaGraph component
 */
interface SigmaGraphSlots {
  default(): any;
}
```

### Expose

通过 [`useTemplateRef`](https://vuejs.org/api/composition-api-helpers.html#usetemplateref) 访问类型化的组件实例。

| Name | Type |
| --- | --- |
| `sigma` | `ShallowRef<Sigma<Attributes, Attributes, Attributes, NS, ES, GS> \| null>` <br> 原生 sigma 实例。SSR 期与挂载完成前为 `null`。声明了 `custom*State` 时状态形状会带到这里，`setNodesState()` 等方法认得那些自定义标志位。 |
| `graph` | `ShallowRef<Graph>` <br> 原生 graphology 实例。 |

> [!TIP]
> 
> 子树内更常用的是 
> 
> useSigma()
> 
> ，它拿到的是同一对实例，不必往上传 ref。一页里放多张图时给每个实例一个 
> 
> id
> 
> ，组件树之外就能经 
> 
> useSigmaById
> 
>  取用。


## Sitemap

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