D3.js是一个用于数据可视化的JavaScript库,广泛应用于Web端的数据交互式图形展示

中文文档:入门 | D3 中文网

一、D3.js核心特点

1、核心思想

将数据绑定到DOM元素,通过数据动态生成/修改可视化图形。

2、应用场景
  • 交互式图表:如动态条形图、散点图、桑基图等。
  • 地理信息可视化:通过地理投影(如墨卡托投影)绘制地图。
  • 实时数据展示:如股票行情、监控仪表盘。
  • 复杂网络图:力导向布局可直观展示社交网络或拓扑关系。

二、首先要弄明白“力导向图布局”

力导向图布局(各节点之间有力,正值表示斥力、负值表示吸引力)

官网实例:力导向图 / D3 |观察 — Force-directed graph / D3 | Observable

在这里插入图片描述

官方代码+代码解释:

chart = {// 1、设置宽度和高度const width = 928;const height = 600;// color 是一个颜色映射函数,根据节点的 group 属性分配颜色(如不同类别的节点显示不同颜色)。const color = d3.scaleOrdinal(d3.schemeCategory10);// 2、数据准备// 连接数据和节点数据const links = data.links.map(d => ({...d}));const nodes = data.nodes.map(d => ({...d}));// 3、创建一个力模拟,作用于节点数据。const simulation = d3.forceSimulation(nodes).force("link", d3.forceLink(links).id(d => d.id)) //定义连接力,使连接的节点保持一定距离,指定如何从节点数据中获取唯一标识符(用于匹配 links 中的 source 和 target)。.force("charge", d3.forceManyBody()) //定义节点间的斥力(负值表示排斥,正值表示吸引).force("center", d3.forceCenter(width / 2, height / 2)) //将整个图居中显示。.on("tick", ticked); //每次模拟更新时,调用 ticked 函数更新节点和边的位置// 4、创建 SVG 容器const svg = d3.create("svg").attr("width", width).attr("height", height).attr("viewBox", [0, 0, width, height]).attr("style", "max-width: 100%; height: auto;");// 5、在每个节点之间绘制连接线(Links)const link = svg.append("g") .attr("stroke", "#999") // 线条颜色.attr("stroke-opacity", 0.6) //透明度.selectAll().data(links).join("line")  // 为每条连接创建 `<line>` 元素.attr("stroke-width", d => Math.sqrt(d.value)); // 线条宽度(基于 `value` 属性)// 6、绘制节点const node = svg.append("g") .attr("stroke", "#fff") // 节点边框颜色 .attr("stroke-width", 1.5)  // 边框宽度.selectAll().data(nodes).join("circle") // 为每个节点创建 `<circle>` 元素.attr("r", 5) // 节点半径.attr("fill", d => color(d.group)); // 节点颜色(按 `group` 分组)node.append("title").text(d => d.id); // 鼠标悬停时显示节点 ID// 7、拖拽交互行为(拖曳前中后调用的方法,在最下面)node.call(d3.drag().on("start", dragstarted) .on("drag", dragged) .on("end", dragended));// 8、更新节点和连接位置(ticked 函数)function ticked() {link.attr("x1", d => d.source.x).attr("y1", d => d.source.y).attr("x2", d => d.target.x).attr("y2", d => d.target.y);node.attr("cx", d => d.x).attr("cy", d => d.y);}// 重新激活模拟(alphaTarget 控制模拟的冷却速度)function dragstarted(event) {if (!event.active) simulation.alphaTarget(0.3).restart();event.subject.fx = event.subject.x;event.subject.fy = event.subject.y;}// 更新被拖拽节点的固定位置(fx, fy)。function dragged(event) {event.subject.fx = event.x;event.subject.fy = event.y;}// 取消固定位置,让节点恢复自由运动function dragended(event) {if (!event.active) simulation.alphaTarget(0);event.subject.fx = null;event.subject.fy = null;}// 当图表需要销毁时(如重新渲染),停止力模拟以释放资源。invalidation.then(() => simulation.stop());return svg.node();
}

总结:

  • 输入数据nodes(节点列表)和 links(连接列表)。
  • 力模拟:通过物理模型计算节点位置(斥力、连接力、中心力)。
  • 渲染:使用 SVG 绘制节点(圆形)和连接(线条)。
  • 交互:支持拖拽节点,悬停显示信息。
  • 动态更新:每次模拟迭代后更新视图。

交互过程:拖动Drag、缩放Zoom、点击Click

官网:Force-directed graph / D3 | Observable

在这里插入图片描述

三、实现一个网络拓扑图

工作流程图:

在这里插入图片描述

使用流程:

1、从npm安装d3
npm install d3
2、将 d3 加载到应用中
import * as d3 from "d3";
3、创建一个SVG容器
  • 选中 #chart 容器,创建 SVG 元素。

    const svg = d3.select('#chart').append('svg');
    
  • 设置svg宽高

  • 建一个 <g> 分组元素作为所有图表内容(节点和连接线)的统一容器(并设置为拖放/平移),添加到svg

  • 在统一容器下面创建两个 <g> 组,分别用于渲染节点和边,添加到container

注:<g> 是 SVG 的一个核心元素,表示 分组(Group)。它的作用类似于 HTML 中的 <div>,主要用于结构化组织多个图形元素,并可以对这些元素统一应用属性或变换

比如我可以创建一个组,设置这个组里面所有边都是一个颜色。

使用组的优点:统一属性设置、代码可读性高、减少重复操作、对组绑定事件

类比解释

概念类比说明
zoom遥控器接收用户输入(按钮/滚轮),发出控制信号
container电视屏幕接收遥控器信号,实际改变显示内容
e.transform遥控信号包含"音量调高"或"频道切换"等具体指令
子元素电视画面内容自动跟随屏幕的变化,无需直接处理信号
4、定义数据类型

网络拓扑图由节点、边、环组成,前端需从后端获取数据

let nodesData = []
let edgesData = []
let hoopsData = []

定义数据类型存储节点、边、环的数据。

  • 节点:图中的基本实体
  • 边:连接两个节点之间的线
  • 环:图中首尾相连的路径
5、创建一个力导向布局
const simulation = d3.forceSimulation(nodes) // 初始化模拟,传入节点数组.force('link', // 添加"连接力"(使连接的节点保持特定距离)d3.forceLink() // 创建连接力.id((d) => d.node_id) // 指定如何从节点数据中获取唯一标识符).force('collision', // 添加"碰撞力"(防止节点重叠)d3.forceCollide().radius(50) // 设置碰撞检测半径(节点间距至少为50) ).velocityDecay(0.5) // 设置速度衰减系数(类似摩擦力,0-1)// 0.5表示每帧速度衰减50%,值越小移动越"滑",越大越"顿"
6、节点和边的渲染

节点:

  • 用 显示节点图标(通过 getImg 获取)。

  • 显示节点名称,超长自动截断。

    在这里插入图片描述

  node = node.data(nodes).join((enter) => {const g = enter.append('g').attr('class', 'node').attr('id', (d) => `node-${d.node_id}`).attr('transform', `translate(200, 200)`);g.append("image").attr("xlink:href", (d) => {return getImg(d.node_type).img}).attr("x", -(NODE_HEIGHT / 2)) // 图片宽度的一半,用于居中.attr("y", -(NODE_HEIGHT / 2)) // 图片高度的一半,用于居中.attr("width", NODE_HEIGHT) // 图片宽度.attr("height", NODE_HEIGHT); // 图片高度// 添加名称g.append('text').style('fill', '#000').attr('class', 'node-text').text(d => {return d.node_name.length > 15 ? d.node_name.slice(0, 15) + '...' : d.node_name}).style('text-anchor', 'middle').attr('dx', 0).style('font-size', NODE_FONT_SIZE).attr('y', NODE_WIDTH - 5);if (atlasKey.value == '1' && checked.value == 1) {g.append('text').style('fill', "#000").attr('class', 'node-text').text(d => {return d.ext_prop['alarm.ne_name']}).style('text-anchor', 'middle').attr('dx', 0).style('font-size', NODE_FONT_SIZE).attr('y', NODE_WIDTH + 20);}return g;},(exit) => exit.remove()).on('click', clickNode).call(drag(simulation));

边:用 画线,可以选择虚线或者实线。

在这里插入图片描述

link = link.data(links).join(enter => {const g = enter.append('g').attr('id', d => getLinkIds(d, 'line')).attr('class', d => 'top line')if (atlasKey.value == '1') {// 根因告警 虚线g.append('path').attr('id', d => getLinkIds(d)).style('stroke', d => getLinkColor(d)).attr('fill', 'none').attr('stroke-width', '2px').attr('stroke-dasharray', '10 5 5 10')} else {// 网络拓扑 实线g.append('path').attr('id', d => getLinkIds(d)).style('stroke', d => getLinkColor(d)).attr('fill', 'none').attr('stroke-width', '2px').attr('transform', d => `translate(${calculateMidPoint(d)})`)}// 边上的图标g.append('image').attr('class', d => {if (!(d?.ext_prop?.alarmIdList?.length > 0)) {return 'hide'}}).attr('xlink:href', d => d.isAlarmRoot ? icon09 : icon08).attr('width', NODE_WIDTH).attr('height', NODE_HEIGHT).attr('x', -10).attr('y', -10);return g}).on('click', clickLink)
7、设置交互逻辑
  • 1. 缩放和平移

const chartEl = document.querySelector('#chart');
const svg = d3.select('#chart').append('svg');
const width = chartEl.offsetWidth;
const height = chartEl.clientHeight
const zoom = d3.zoom().on('zoom', handleZoom);
const container = svg.append('g').attr('class', 'container');function handleZoom(e) {container.attr('transform', e.transform);
}svg.attr('width', width).attr('height', height).call(zoom);
  • 2. 点击节点/边弹出详细信息

async function clickNode(e) {tooltipNode.visible = falseconst node = e.target.__data__;getAlarmList(true, node, nodesData); // 这里会弹出详细信息e.stopPropagation();
}async function clickLink(e) {tooltipNode.visible = falseconst node = e.target.__data__;getAlarmList(false, node, nodesData); // 这里会弹出详细信息e.stopPropagation();
}
  • 3. 拖拽节点,线条和图片实时更新

const drag = () => {function dragstarted(event, d) {if (!event.active) simulation.alphaTarget(0.3).restart();d.x = d.x;d.y = d.y;}function dragged(event, d) {d.fx = event.x;d.fy = event.y;// 更新线条updateLinks();// 更新图像位置updateImages();}function dragended(event, d) {if (!event.active) simulation.alphaTarget(0);// d.fx = null;// d.fy = null;}return d3.drag().on('start', dragstarted).on('drag', dragged).on('end', dragended);
};
  • 4. 线条和图片实时更新的方法

function updateLinks() {link.attr('d', d => {const dx = d.target.x - d.source.x,dy = d.target.y - d.source.y,dr = Math.sqrt(dx * dx + dy * dy);return `M ${d.source.x},${d.source.y} A ${dr},${dr} 0 0,1 ${d.target.x},${d.target.y}`;});
}function updateImages() {link.select('image').attr('x', d => {const midX = (d.source.x + d.target.x) / 2;return midX - 20 / 2; // 调整水平位置,使图片居中}).attr('y', d => {const midY = (d.source.y + d.target.y) / 2;return midY - 20 / 2; // 调整垂直位置,使图片居中});
}
  • 5. 节点和边绑定交互

node = node.data(nodes).join(// ...enter逻辑).on('click', clickNode).call(drag(simulation));link = link.data(links).join(// ...enter逻辑).on('click', clickLink)

四、参考力导向图组件

力导向图形组件 / D3 |观察 — Force-directed graph component / D3 | Observable

官方代码+中文注释

function ForceGraph({nodes, // 节点对象数组,通常格式如 [{id}, …]links // 连接线对象数组,通常格式如 [{source, target}, …]
}, {nodeId = d => d.id, // 从节点数据中获取唯一标识符的函数nodeGroup, // 从节点数据中获取分组信息的函数nodeGroups, // 节点分组的可选值数组nodeTitle, // 节点标题文本nodeFill = "currentColor", // 节点填充颜色nodeStroke = "#fff", // 节点边框颜色nodeStrokeWidth = 1.5, // 节点边框宽度(像素)nodeStrokeOpacity = 1, // 节点边框透明度nodeRadius = 5, // 节点半径(像素)nodeStrength, // 节点间作用力强度linkSource = ({source}) => source, // 从连接线数据中获取源节点linkTarget = ({target}) => target, // 从连接线数据中获取目标节点linkStroke = "#999", // 连接线颜色linkStrokeOpacity = 0.6, // 连接线透明度linkStrokeWidth = 1.5, // 连接线宽度(像素)linkStrokeLinecap = "round", // 连接线端点样式linkStrength, // 连接线作用力强度colors = d3.schemeTableau10, // 颜色方案,用于节点分组width = 640, // 画布宽度(像素)height = 400, // 画布高度(像素)invalidation // 当此Promise完成时停止模拟
} = {}) {// 数据处理const N = d3.map(nodes, nodeId).map(intern); // 节点ID数组const R = typeof nodeRadius !== "function" ? null : d3.map(nodes, nodeRadius); // 节点半径数组const LS = d3.map(links, linkSource).map(intern); // 连接线源节点数组const LT = d3.map(links, linkTarget).map(intern); // 连接线目标节点数组if (nodeTitle === undefined) nodeTitle = (_, i) => N[i]; // 默认使用节点ID作为标题const T = nodeTitle == null ? null : d3.map(nodes, nodeTitle); // 节点标题数组const G = nodeGroup == null ? null : d3.map(nodes, nodeGroup).map(intern); // 节点分组数组const W = typeof linkStrokeWidth !== "function" ? null : d3.map(links, linkStrokeWidth); // 连接线宽度数组const L = typeof linkStroke !== "function" ? null : d3.map(links, linkStroke); // 连接线颜色数组// 将输入数据转换为可修改对象供模拟使用nodes = d3.map(nodes, (_, i) => ({id: N[i]}));links = d3.map(links, (_, i) => ({source: LS[i], target: LT[i]}));// 计算默认分组if (G && nodeGroups === undefined) nodeGroups = d3.sort(G);// 创建比例尺const color = nodeGroup == null ? null : d3.scaleOrdinal(nodeGroups, colors);// 创建作用力模型const forceNode = d3.forceManyBody(); // 节点间作用力const forceLink = d3.forceLink(links).id(({index: i}) => N[i]); // 连接线作用力if (nodeStrength !== undefined) forceNode.strength(nodeStrength); // 设置节点作用力强度if (linkStrength !== undefined) forceLink.strength(linkStrength); // 设置连接线作用力强度// 创建力导向模拟const simulation = d3.forceSimulation(nodes).force("link", forceLink) // 添加连接线作用力.force("charge", forceNode) // 添加节点间作用力.force("center", d3.forceCenter()) // 添加向中心的作用力.on("tick", ticked); // 设置每帧更新回调// 创建SVG画布const svg = d3.create("svg").attr("width", width) // 设置宽度.attr("height", height) // 设置高度.attr("viewBox", [-width / 2, -height / 2, width, height]) // 设置视图框.attr("style", "max-width: 100%; height: auto; height: intrinsic;"); // 响应式样式// 创建连接线组const link = svg.append("g").attr("stroke", typeof linkStroke !== "function" ? linkStroke : null) // 连接线颜色.attr("stroke-opacity", linkStrokeOpacity) // 连接线透明度.attr("stroke-width", typeof linkStrokeWidth !== "function" ? linkStrokeWidth : null) // 连接线宽度.attr("stroke-linecap", linkStrokeLinecap) // 连接线端点样式.selectAll("line").data(links).join("line");// 创建节点组const node = svg.append("g").attr("fill", nodeFill) // 节点填充色.attr("stroke", nodeStroke) // 节点边框色.attr("stroke-opacity", nodeStrokeOpacity) // 节点边框透明度.attr("stroke-width", nodeStrokeWidth) // 节点边框宽度.selectAll("circle").data(nodes).join("circle").attr("r", nodeRadius) // 节点半径.call(drag(simulation)); // 添加拖拽交互// 设置动态样式if (W) link.attr("stroke-width", ({index: i}) => W[i]); // 动态连接线宽度if (L) link.attr("stroke", ({index: i}) => L[i]); // 动态连接线颜色if (G) node.attr("fill", ({index: i}) => color(G[i])); // 按分组设置节点颜色if (R) node.attr("r", ({index: i}) => R[i]); // 动态节点半径if (T) node.append("title").text(({index: i}) => T[i]); // 添加节点提示文本if (invalidation != null) invalidation.then(() => simulation.stop()); // 设置模拟停止条件// 辅助函数:确保值为可比较的原始值function intern(value) {return value !== null && typeof value === "object" ? value.valueOf() : value;}// 模拟更新时的回调函数function ticked() {// 更新连接线位置link.attr("x1", d => d.source.x).attr("y1", d => d.source.y).attr("x2", d => d.target.x).attr("y2", d => d.target.y);// 更新节点位置node.attr("cx", d => d.x).attr("cy", d => d.y);}// 拖拽交互函数function drag(simulation) {    // 拖拽开始function dragstarted(event) {if (!event.active) simulation.alphaTarget(0.3).restart(); // 激活模拟event.subject.fx = event.subject.x; // 固定节点x坐标event.subject.fy = event.subject.y; // 固定节点y坐标}// 拖拽过程中function dragged(event) {event.subject.fx = event.x; // 更新固定x坐标event.subject.fy = event.y; // 更新固定y坐标}// 拖拽结束function dragended(event) {if (!event.active) simulation.alphaTarget(0); // 停止模拟event.subject.fx = null; // 释放x坐标event.subject.fy = null; // 释放y坐标}return d3.drag().on("start", dragstarted).on("drag", dragged).on("end", dragended);}// 返回SVG元素和比例尺return Object.assign(svg.node(), {scales: {color}});
}

代码结构分析

1. 初始化与数据处理
const N = d3.map(nodes, nodeId).map(intern); // 节点ID
const R = typeof nodeRadius !== "function" ? null : d3.map(nodes, nodeRadius); // 节点半径
const LS = d3.map(links, linkSource).map(intern); // 连接源节点
const LT = d3.map(links, linkTarget).map(intern); // 连接目标节点
2. 力模拟设置
const simulation = d3.forceSimulation(nodes).force("link", forceLink) // 连接力.force("charge", forceNode) // 节点间斥力.force("center", d3.forceCenter()) // 向心力.on("tick", ticked); // 每帧更新
  • 创建力模拟系统,包含三种力:
    1. forceLink: 保持连接长度的力
    2. forceNode: 节点间斥力(避免重叠)
    3. forceCenter: 将图形居中
3. SVG元素创建
const svg = d3.create("svg")...; // 创建SVG画布const link = svg.append("g")...; // 创建连接线组
const node = svg.append("g")...; // 创建节点组
  • 创建SVG容器和分组
  • 绑定数据到DOM元素
4. 交互功能
function drag(simulation) {// 拖拽事件处理function dragstarted(event) {...}function dragged(event) {...}function dragended(event) {...}return d3.drag().on("start", dragstarted).on("drag", dragged).on("end", dragended);
}
  • 实现节点拖拽功能
  • 拖拽时临时固定节点位置
  • 释放后恢复物理模拟
5. 更新函数
function ticked() {link.attr("x1", d => d.source.x).attr("y1", d => d.source.y).attr("x2", d => d.target.x).attr("y2", d => d.target.y);node.attr("cx", d => d.x).attr("cy", d => d.y);
}
  • 每次模拟"tick"时更新节点和连接线位置
  • 将模拟中的坐标同步到SVG元素

项目代码

import * as d3 from 'd3';
import {getLinkIds, linkArc, registerDefs, getLinkColor, getImg} from '@/pages/chart_model/chart/summary/utils';
import {NODE_WIDTH,NODE_HEIGHT,NODE_FONT_SIZE,} from '@/pages/chart_model/chart/summary/config';
import {tooltipNode, setToolTipNode} from '@/pages/chart_model/store/tool-node';
import {getSceneKey} from "@/pages/chart_model/chart/sceneCommon";import icon08 from '@/assets/chart_model/node2/icon08.png'
import icon09 from '@/assets/chart_model/node2/icon09da.png'import {setCoordinate,
} from "@/pages/chart_model/js/index_default2";
import {adjustCoordinates,calculateMidPoint,filterDataHandle, getAlarmList,setLinkisRoot
} from "@/pages/chart_model/js/index_default";
window.d3 = d3let graph = null;
let nodesData = []
let edgesData = []
let hoopsData = []export default (state) => {let nodes = [];let links = [];const chartEl = document.querySelector('#chart');const svg = d3.select('#chart').append('svg');const width = chartEl.offsetWidth;const height = chartEl.clientHeightconst zoom = d3.zoom().on('zoom', handleZoom);const container = svg.append('g').attr('class', 'container');function handleZoom(e) {container.attr('transform', e.transform);}svg.attr('width', width).attr('height', height).call(zoom);async function clickNode(e) {tooltipNode.visible = falseconst node = e.target.__data__;getAlarmList(true, node, nodesData);e.stopPropagation();}async function clickLink(e) {tooltipNode.visible = falseconst node = e.target.__data__;getAlarmList(false, node,nodesData);e.stopPropagation();}const drag = () => {function dragstarted(event, d) {if (!event.active) simulation.alphaTarget(0.3).restart();d.x = d.x;d.y = d.y;}function dragged(event, d) {d.fx = event.x;d.fy = event.y;// 更新线条updateLinks();// 更新图像位置updateImages();}function dragended(event, d) {if (!event.active) simulation.alphaTarget(0);}return d3.drag().on('start', dragstarted).on('drag', dragged).on('end', dragended);};let link = container.append('g').attr('class', 'lines').selectAll('g');let node = container.append('g').attr('class', 'nodes').selectAll('g');const simulation = d3.forceSimulation(nodes).force('link', d3.forceLink().id((d) => d.node_id)).force('collision', d3.forceCollide().radius(50)).velocityDecay(0.5);function ticked(d) {link.selectAll('path').attr('d', linkArc);node.attr('transform', (d) => `translate(${d.x},${d.y})`);}// 更新线条的方法function updateLinks() {link.attr('d', d => {const dx = d.target.x - d.source.x,dy = d.target.y - d.source.y,dr = Math.sqrt(dx * dx + dy * dy);return `M ${d.source.x},${d.source.y} A ${dr},${dr} 0 0,1 ${d.target.x},${d.target.y}`;});}// 更新图像位置的方法function updateImages() {link.select('image').attr('x', d => {const midX = (d.source.x + d.target.x) / 2;return midX - 20 / 2; // 调整水平位置,使图片居中}).attr('y', d => {const midY = (d.source.y + d.target.y) / 2;return midY - 20 / 2; // 调整垂直位置,使图片居中});}graph = {name: 'summary',nodes,links,graph,appendData(data, alarmOrReason, atlasKey, value1, nodeIdInhoop) {simulation.stop();let filterDataHandle4 = filterDataHandle(data, alarmOrReason,atlasKey, value1, nodeIdInhoop, state);setCoordinate(filterDataHandle4.nodes, filterDataHandle4.links, atlasKey, alarmOrReason, state, state.sceneKey)nodes = filterDataHandle4.nodes;links = filterDataHandle4.links;adjustCoordinates(nodes)setLinkisRoot(links, nodesData)this.restart(atlasKey, alarmOrReason);},restart(atlasKey = 1, checked) {node = node.data(nodes).join((enter) => {const g = enter.append('g').attr('class', 'node').attr('id', (d) => `node-${d.node_id}`).attr('transform', `translate(200, 200)`);g.append("image").attr("xlink:href", (d) => {return getImg(d.node_type).img}).attr("x", -(NODE_HEIGHT / 2)) // 图片宽度的一半,用于居中.attr("y", -(NODE_HEIGHT / 2)) // 图片高度的一半,用于居中.attr("width", NODE_HEIGHT) // 图片宽度.attr("height", NODE_HEIGHT); // 图片高度// 添加名称g.append('text')// .style('fill', '#fff').style('fill', '#000').attr('class', 'node-text').text(d => {return d.node_name.length > 15 ? d.node_name.slice(0, 15) + '...' : d.node_name}).style('text-anchor', 'middle').attr('dx', 0).style('font-size', NODE_FONT_SIZE).attr('y', NODE_WIDTH - 5);if (atlasKey.value == '1' && checked.value == 1) {g.append('text')// .style('fill', '#fff').style('fill', "#000").attr('class', 'node-text').text(d => {return d.ext_prop['alarm.ne_name']}).style('text-anchor', 'middle').attr('dx', 0).style('font-size', NODE_FONT_SIZE).attr('y', NODE_WIDTH + 20);}return g;},(exit) => exit.remove()).on('click', clickNode).call(drag(simulation));link = link.data(links).join(enter => {const g = enter.append('g').attr('id', d => getLinkIds(d, 'line')).attr('class', d => 'top line')if (atlasKey.value == '1') {// 根因告警 虚线g.append('path').attr('id', d => getLinkIds(d)).style('stroke', d => getLinkColor(d)).attr('fill', 'none').attr('stroke-width', '2px').attr('stroke-dasharray', '10 5 5 10')} else {// 网络拓扑 实现 后续带颜色g.append('path').attr('id', d => getLinkIds(d)).style('stroke', d => getLinkColor(d)).attr('fill', 'none').attr('stroke-width', '2px').attr('transform', d => `translate(${calculateMidPoint(d)})`) // 计算中点位置}g.append('image').attr('class', d => {if (!(d?.ext_prop?.alarmIdList?.length > 0)) {return 'hide'}}).attr('xlink:href', d => d.isAlarmRoot ? icon09 : icon08).attr('width', NODE_WIDTH) // 图片宽度.attr('height', NODE_HEIGHT) // 图片高度.attr('x', -10) // 调整水平位置,使图片居中.attr('y', -10); // 调整垂直位置,使图片居中return g}).on('click', clickLink)simulation.nodes(nodes);simulation.force('link').links(links);simulation.alpha(1).restart();simulation.on('tick', ticked)graph.resetGraphLink()},resetGraph() {simulation.stop()},setData(nodes, links, hoops) {nodesData = nodesedgesData = linkshoopsData = hoops},resetGraphLink() {updateLinks()updateImages()},resetSvg(width,height) {svg.attr("width", width).attr("height", height);}};return graph;
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/916439.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/916439.shtml
英文地址,请注明出处:http://en.pswp.cn/news/916439.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Zookeeper的分布式事务与原子性:深入解析与实践指南

引言在分布式系统架构中&#xff0c;事务管理和原子性保证一直是极具挑战性的核心问题。作为分布式协调服务的标杆&#xff0c;Apache Zookeeper提供了一套独特而强大的机制来处理分布式环境下的原子操作。本文将深入探讨Zookeeper如何实现分布式事务的原子性保证&#xff0c;分…

Lua(迭代器)

Lua 迭代器基础概念Lua 迭代器是一种允许遍历集合&#xff08;如数组、表&#xff09;元素的机制。迭代器通常由两个部分组成&#xff1a;迭代函数和状态控制变量。每次调用迭代函数会返回集合中的下一个元素。泛型 for 循环Lua 提供了泛型 for 循环来简化迭代器的使用。语法如…

发布 VS Code 扩展的流程:以颜色主题为例

发布 VS Code 扩展的流程&#xff1a;以颜色主题为例 引言&#xff1a;您的 VS Code 扩展在市场中的旅程 Visual Studio Code (VS Code) 的强大扩展性是其广受欢迎的核心原因之一&#xff0c;它允许开发者通过添加语言支持、调试器和各种开发工具来定制和增强其集成开发环境&…

C++ 多线程(一)

C 多线程&#xff08;一&#xff09;1.std中的thread API 介绍开启一个线程获取线程信息API交换两个线程2.向线程里传递参数的方法第一种方式&#xff08;在创建线程的构造函数后携带参数&#xff09;第二种方式&#xff08;Lambda&#xff09;第三种方式&#xff08;成员函数&…

自动驾驶训练-tub详解

在 Donkeycar 的环境里&#xff0c;“tub” 是一个很关键的术语&#xff0c;它代表的是存储训练数据的目录。这些数据主要来源于自动驾驶模型训练期间收集的图像和控制指令。 Tub 的构成 一个标准的 tub 目录包含以下两类文件&#xff1a; JSON 记录文件&#xff1a;其命名格式…

CVPR多模态破题密钥:跨模对齐,信息串供

关注gongzhonghao【CVPR顶会精选】当今数字化时代&#xff0c;多模态技术正迅速改变我们与信息互动的方式。多模态被定义为在特定语境中多种符号资源的共存与协同。这种技术通过整合不同模态的数据&#xff0c;如文本、图像、音频等&#xff0c;为用户提供更丰富、更自然的交互…

小米路由器3G R3G 刷入Breed和OpenWrt 插入可共享网络的usb随身WiFi

小米 R3G 参数&#xff08;以下加黑加粗需要特别关注&#xff0c;灰常详细&#xff09; 市面上有R3G和R3Gv2两种型号, 注意区分, 后者是缩水版, 没有USB口. 内存只有128M, Flash只有16M. 这里描述的只适用于R3G. 就是这样 操作步骤开始&#xff0c;&#xff0c;注&#xff1a…

SpringBoot实现Serverless:手撸一个本地函数计算引擎

前言 最近突然冒出一个想法&#xff1a;能不能用SpringBoot自己实现一个类似AWS Lambda或阿里云函数计算的执行引擎&#xff1f; 说干就干&#xff0c;于是从零开始设计了一套基于SpringBoot的Serverless执行框架。 这套框架支持函数动态加载、按需执行、资源隔离&#xff0c;甚…

Java排序算法之<插入排序>

目录 1、插入排序 2、流程介绍 3、java实现 4、性能介绍 前言 在 Java 中&#xff0c; 冒泡排序&#xff08;Bubble Sort&#xff09; 和 选择排序&#xff08;Selection Sort&#xff09; 之后&#xff0c;下一个性能更好的排序算法通常是 插入排序&#xff08;Insertion …

《计算机网络》实验报告七 HTTP协议分析与测量

目 录 1、实验目的 2、实验环境 3、实验内容 4、实验结果与分析 4.1 使用tcpdump命令抓包 4.2 HTTP字段分析 5、实验小结 5.1 问题与解决办法&#xff1a; 5.2 心得体会&#xff1a; 1、实验目的 1、了解HTTP协议及其报文结构 2、了解HTTP操作过程&#xff1a;TCP三次…

面试实战,问题十三,Redis在Java项目中的作用及使用场景详解,怎么回答

Redis在Java项目中的作用及使用场景详解&#xff08;面试要点&#xff09; 一、Redis的核心作用高性能缓存层 原理&#xff1a;Redis基于内存操作&#xff08;引用[2]&#xff09;&#xff0c;采用单线程模型避免线程切换开销&#xff0c;配合IO多路复用实现高吞吐&#xff08;…

Python - 100天从新手到大师 - Day6

引言 这里主要是依托于 jackfrued 仓库 Python-100-Days 进行学习&#xff0c;记录自己的学习过程和心得体会。 1 文件读写和异常处理 实际开发中常常会遇到对数据进行持久化的场景&#xff0c;所谓持久化是指将数据从无法长久保存数据的存储介质&#xff08;通常是内存&…

IP--MGER综合实验报告

一、实验目的完成网络设备&#xff08;路由器 R1-R5、PC1-PC4&#xff09;的 IP 地址规划与配置&#xff0c;确保接口通信基础正常。配置链路层协议及认证&#xff1a;R1 与 R5 采用 PPP 的 PAP 认证&#xff08;R5 为主认证方&#xff09;&#xff0c;R2 与 R5 采用 PPP 的 CH…

window的WSL怎么一键重置

之前用WSL来在windows和服务器之间传输数据&#xff0c;所以有很多数据缓存&#xff0c;但是现在找不到他们的路径&#xff0c;所以想直接重置 首先使用spacesniffer看一下C盘的情况&#xff1a;看起来&#xff0c;这个WSL真的占用了很多空间&#xff0c;但是我又不知道该怎么删…

卷积神经网络研讨

卷积操作原理: 特征向量与遍历:假设已知特征向量(如蓝天白云、绿油油草地特征),在输入图像的各个区域进行遍历,通过计算内积判断该区域是否有想要的特征。 内积计算特征:内积为 0 表示两个向量垂直,关系不好,无想要的特征;夹角越小,内积越大,代表区域中有想要的特征…

【EWARM】EWARM(IAR)的安装过程以及GD32的IAR工程模板搭建

一、简介 IAR官网 EWARM&#xff0c;即 IAR Embedded Workbench for ARM&#xff0c;是由 IAR Systems 开发的一款专门用于 ARM 微处理器软件开发的集成开发环境。以下是具体介绍&#xff1a; 功能特性&#xff1a; 完整工具链支持&#xff1a;集成了高级编辑器、全面的编译…

【工程化】浅谈前端构建工具

一、前端构建工具概述​ 前端构建工具是辅助开发者将源代码转换为浏览器可直接运行的静态资源的工具集合。随着前端技术的发展&#xff0c;源代码往往包含浏览器无法直接解析的语法&#xff08;如 TypeScript、Sass&#xff09;、模块化规范&#xff08;如 ES Modules、Common…

数据取证:Elcomsoft Password Digger,解密 macOS (OS X) 钥匙串信息

Elcomsoft Password Digger&#xff08;EPD&#xff09;是一款在 Windows 平台上使用的工具&#xff0c;用于解密存储在 macOS 钥匙串中的信息。该工具可以将加密的钥匙串内容导出到一个纯文本 XML 文件中&#xff0c;方便查看和分析。一键字典构建功能可以将钥匙串中的所有密码…

2.JVM跨平台原理(字节码机制)

目录引言一、跨平台就跟国际语言翻译似的二、字节码和 JVM 到底是啥玩意儿三、解决 “语言不通” 这个老难题四、实现 “一次编写&#xff0c;到处运行” 就这四步五、字节码技术给世界带来的大改变总结引言 咱平常是不是老纳闷儿&#xff0c;为啥同一个 Java 程序&#xff0c…

06-ES6

微任务&宏任务JS是单线程执行。所有要执行的任务都要排队。所有的同步任务会在主线程上排队&#xff0c;等待执行。异步任务&#xff1a;不会进入主线程&#xff0c;而是会进入任务队列。等到主线程上的任务执行完成之后&#xff0c;通知任务队列&#xff0c;执行异步任务。…