@superlinearspace/supertopo
v0.0.1
Published
> 基于 Cytoscape.js 的拓扑图渲染组件
Readme
SuperTopo
基于 Cytoscape.js 的拓扑图渲染组件
一个轻量级的拓扑图库,同时支持 React/Next.js 和原生 HTML/JS。
功能特性
- ✅ Clos 布局 - 自动为 Clos 拓扑网络计算最佳布局位置
- ✅ Cytoscape.js 渲染 - 利用 Cytoscape.js 强大的可视化能力
- ✅ TypeScript 支持 - 完整的类型定义
- ✅ 性能优化 - 支持大规模拓扑图渲染
- ✅ 双模式支持 - React 组件 + Vanilla JS(独立文件)
- React 版本: 用于 Next.js/React 项目
- Vanilla 版本: 用于原生 HTML/JS 项目(包含所有依赖,无需构建工具)
安装
pnpm add @repo/supertopo使用方式
SuperTopo 提供两种使用方式:
方式 1: React/Next.js(推荐用于 React 项目)
import { SuperTopo } from "@repo/supertopo"
export default function Page() {
const graphData = {
nodes: [
{ data: { id: "spine0", label: "Spine 0", type: "spine", size: 40, color: "#3B82F6" } },
{ data: { id: "leaf0", label: "Leaf 0", type: "leaf", size: 35, color: "#0D9488" } },
{ data: { id: "server0", label: "Server 0", type: "server", size: 25, color: "#F97316" } },
],
edges: [
{ data: { source: "spine0", target: "leaf0" } },
{ data: { source: "leaf0", target: "server0" } },
],
}
return (
<div>
<h1>Network Topology</h1>
<SuperTopo
data={graphData}
layout="clos"
height={600}
/>
</div>
)
}方式 2: Vanilla JS(用于原生 HTML/JS 项目)
步骤 1: 构建 Vanilla 版本
cd packages/supertopo
pnpm build步骤 2: 复制生成的文件
cp dist/supertopo-vanilla.js /path/to/your/project/static/步骤 3: 在 HTML 中使用
<!DOCTYPE html>
<html>
<head>
<title>Network Topology</title>
<script src="/static/supertopo-vanilla.js"></script>
<style>
#topology { width: 100%; height: 600px; border: 1px solid #ccc; }
</style>
</head>
<body>
<div id="topology"></div>
<script>
const topo = new SuperTopo({
container: '#topology',
nodes: [
{ id: 'spine0', label: 'Spine 0', type: 'spine', size: 40, color: '#3B82F6' },
{ id: 'leaf0', label: 'Leaf 0', type: 'leaf', size: 35, color: '#0D9488' },
{ id: 'server0', label: 'Server 0', type: 'server', size: 25, color: '#F97316' }
],
edges: [
{ source: 'spine0', target: 'leaf0' },
{ source: 'leaf0', target: 'server0' }
],
layout: 'clos',
fit: true
});
</script>
</body>
</html>Vanilla API 详情:
container- 容器选择器(如#topology)或 DOM 元素nodes- 节点数组(简化格式或完整格式)edges- 边数组(简化格式或完整格式)layout- 布局算法名称(clos,random,grid)layoutOptions- 布局选项fit- 是否自动适应视图(默认 true)events- 事件处理器
Vanilla JS 示例(在 FastAPI 中)
<!-- templates/topology.html -->
<!DOCTYPE html>
<html>
<head>
<title>Network Topology</title>
<script src="/static/supertopo-vanilla.js"></script>
<style>
#topology { width: 100%; height: 600px; border: 1px solid #ddd; }
</style>
</head>
<body>
<h1>NUNet Topology</h1>
<div id="topology"></div>
<script>
const topo = new SuperTopo({
container: '#topology',
nodes: {{ nodes|tojson|safe }},
edges: {{ edges|tojson|safe }},
layout: 'clos',
layoutOptions: {
suScope: {
include: (node) => String(node.id).toLowerCase().includes('gpu')
}
},
events: {
click: (evt) => console.log('Clicked:', evt.target.id())
},
fit: true
});
</script>
</body>
</html># FastAPI router
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
router = APIRouter()
templates = Jinja2Templates(directory="app/templates")
@router.get("/", response_class=HTMLResponse)
async def topology_page(request: Request):
# 假设这是从数据库获取的拓扑数据
nodes = [
{'id': 'spine0', 'label': 'Spine 0', 'type': 'spine', 'size': 40, 'color': '#3B82F6'},
{'id': 'leaf0', 'label': 'Leaf 0', 'type': 'leaf', 'size': 35, 'color': '#0D9488'},
{'id': 'server0', 'label': 'Server 0', 'type': 'server', 'size': 25, 'color': '#F97316'},
]
edges = [
{'source': 'spine0', 'target': 'leaf0'},
{'source': 'leaf0', 'target': 'server0'},
]
return templates.TemplateResponse("topology.html", {
"request": request,
"nodes": nodes,
"edges": edges
})API 参考
React 版本 API
SuperTopo Props
| Prop | 类型 | 默认值 | 描述 |
|------|------|---------|------|
| data | GraphData | - | 图数据(节点和边) |
| layout | string | "clos" | 布局算法名称 |
| height | string \| number | "600px" | 画布高度 |
| width | string \| number | "100%" | 画布宽度 |
| layoutOptions | Record<string, unknown> | - | 布局选项 |
Vanilla 版本 API
构造函数选项
| 选项 | 类型 | 默认值 | 描述 |
|------|------|---------|------|
| container | string \| HTMLElement | - | 容器(必需) |
| nodes | Array | [] | 节点数组(简化格式或完整格式) |
| edges | Array | [] | 边数组(简化格式或完整格式) |
| layout | string | - | 布局算法名称 |
| layoutOptions | Object | - | 布局选项 |
| fit | boolean | true | 是否自动适应视图 |
| padding | number | 10 | 适应视图的内边距 |
| events | Object | - | 事件处理器 |
实例方法
// 应用布局
topo.applyLayout('clos', options);
// 适应视图
topo.fit(padding);
// 获取节点数据
const nodeData = topo.getNode('spine0');
// 获取边数据
const edgeData = topo.getEdge('edge0');
// 设置节点属性
topo.setNodeAttribute('spine0', 'label', 'New Label');
// 销毁实例
topo.destroy();图数据格式
interface NodeData {
id: string | number
label?: string
type?: string
size?: number
color?: string
}
interface EdgeData {
source: string | number
target: string | number
id?: string
label?: string
}
interface TopoNode {
group?: "nodes"
data: NodeData
position?: { x: number; y: number }
}
interface TopoEdge {
group?: "edges"
data: EdgeData
}
interface GraphData {
nodes: TopoNode[]
edges: TopoEdge[]
}注意:在实际使用中,可以只传入 data 对象,Cytoscape 会自动将其转换为完整的元素定义。因此,为了简化使用,示例代码中直接使用了简化的格式。
简化格式(Vanilla JS 推荐):
{
nodes: [
{ id: 'spine0', label: 'Spine 0', type: 'spine' },
{ id: 'leaf0', label: 'Leaf 0', type: 'leaf' },
],
edges: [
{ source: 'spine0', target: 'leaf0' }
]
}完整格式:
{
nodes: [
{ data: { id: 'spine0', label: 'Spine 0', type: 'spine' } },
],
edges: [
{ data: { source: 'spine0', target: 'leaf0' } }
]
}布局选项
Clos 布局支持的选项:
| 选项 | 类型 | 默认值 | 描述 |
|------|------|---------|------|
| devicesPerRow | number | 8 | 每行最大设备数 |
| horizontalSpacingFactor | number | 2.0 | SU 内横向间距倍数 |
| spineToLeafSpacingFactor | number | 8.0 | Spine 到 Leaf 的间距倍数 |
| suVerticalSpacingFactor | number | 4.0 | SU 内纵向间距倍数 |
| suSpacingFactor | number | 2.0 | SU 之间间距倍数 |
| spineCoverageFactor | number | 0.8 | Spine 覆盖宽度比例 |
| scaleFactor | number | 0.5 | 整体缩放因子 |
| suScope | SUScope | - | SU 范围配置 |
SU 范围配置 (suScope)
suScope 用于控制哪些服务器参与 SU 分组判断。回调函数接收 Cytoscape 节点对象(Vanilla 版本通过 node.id() 等方法访问)。
Vanilla JS 示例:
layoutOptions: {
suScope: {
// 只考虑满足条件的服务器参与分组
include: (node) => String(node.id()).toLowerCase().includes('gpu'),
// 排除满足条件的服务器
exclude: (node) => node.data('type') === 'switch'
}
}常用方法:
node.id()- 获取节点IDnode.data('label')- 获取节点标签node.data('type')- 获取节点类型node.data('size')- 获取节点大小
节点类型
SuperTopo 通过节点的 type 属性自动识别节点类型:
- spine - 脊柱交换机
- leaf - 叶子交换机
- 其他 - 服务器节点
示例
快速体验(独立 HTML 示例)
构建 Vanilla 版本:
cd packages/supertopo pnpm build在浏览器中打开独立示例:
open examples/simple-standalone.html # 或在 Windows/Linux 中使用浏览器打开
这个示例包含:
- 2 个 Spine 节点
- 2 个 Leaf 节点
- 4 个 Server 节点
- 完整的 Clos 布局
- 点击事件交互
FastAPI 集成示例
完整的 FastAPI 模板示例位于 examples/fastapi-topology.html,包含:
- 完整的页面样式
- 节点统计显示
- 图例说明
- 错误处理
- 事件处理
开发
构建
# 开发模式(监听文件变化)
pnpm dev
# 生产构建
pnpm build输出文件
构建后会在 dist/ 目录生成:
supertopo-vanilla.js- Vanilla 版本(包含所有依赖,可直接在浏览器中使用)supertopo-vanilla.js.map- Source Map(用于调试)
许可证
MIT
