当前位置: 首页 > news >正文

从零到一:用JointJS复刻一个简易的“逻辑门”模拟器(含完整源码)

从零构建逻辑门模拟器:JointJS实战与可视化电路设计

在数字电路设计与计算机科学教育中,逻辑门是最基础的构建模块。传统教学往往依赖抽象符号或静态图示,而通过JointJS构建交互式逻辑门模拟器,不仅能直观展示与门、或门、非门的工作原理,还能让学习者通过拖拽连接亲自搭建电路。本文将完整演示如何利用JointJS的joint.shapes.devs.Model创建带端口的可交互逻辑门元件,实现信号传递的动态模拟,并最终导出为可持久化的JSON数据。

1. 环境准备与JointJS核心概念

1.1 技术栈选型分析

JointJS作为基于SVG的图形库,其核心优势在于:

  • 元素-连接模型:所有图形由ElementLink两类对象构成
  • 端口系统:通过ports定义输入输出接口
  • 事件驱动:支持拖拽、连线、点击等交互事件的监听处理
  • 序列化能力:整个图形可导出为JSON格式

与其他可视化库(如D3.js或GoJS)相比,JointJS特别适合需要复杂连接关系的场景。下表对比了常见图形库特性:

特性JointJSD3.jsGoJS
预置图形元素
连接线管理
端口系统
序列化支持
学习曲线中等陡峭平缓

1.2 项目初始化

通过npm安装依赖:

npm install jointjs jquery backbone lodash

基础HTML结构:

<!DOCTYPE html> <html> <head> <link rel="stylesheet" href="node_modules/jointjs/dist/joint.min.css"> </head> <body> <div id="canvas"></div> <script src="node_modules/jquery/dist/jquery.min.js"></script> <script src="node_modules/lodash/lodash.min.js"></script> <script src="node_modules/backbone/backbone-min.js"></script> <script src="node_modules/jointjs/dist/joint.min.js"></script> <script src="app.js"></script> </body> </html>

2. 构建逻辑门元件库

2.1 定义基础元件模板

使用joint.shapes.devs.Model创建可复用的逻辑门模板:

const gateTemplate = new joint.shapes.devs.Model({ size: { width: 60, height: 80 }, ports: { groups: { 'in': { attrs: { '.port-body': { magnet: 'passive', width: 10, height: 10 } }, position: { name: 'left' } }, 'out': { attrs: { '.port-body': { magnet: 'active', width: 10, height: 10 } }, position: { name: 'right' } } } }, attrs: { '.body': { stroke: '#333', fill: '#FFF' }, '.label': { text: 'GATE', fontSize: 12 } } });

2.2 实现具体逻辑门类型

基于模板创建三种基本逻辑门:

与门(AND)实现

function createANDGate(x, y) { return gateTemplate.clone().set({ position: { x, y }, inPorts: ['A', 'B'], outPorts: ['Q'], attrs: { '.label': { text: 'AND' }, '.body': { d: 'M 0 0 L 60 0 L 60 80 L 30 80 L 0 40 Z' } } }); }

或门(OR)实现

function createORGate(x, y) { return gateTemplate.clone().set({ position: { x, y }, inPorts: ['A', 'B'], outPorts: ['Q'], attrs: { '.label': { text: 'OR' }, '.body': { d: 'M 0 20 Q 30 -20 60 20 L 60 60 Q 30 100 0 60 Z' } } }); }

非门(NOT)实现

function createNOTGate(x, y) { return gateTemplate.clone().set({ position: { x, y }, inPorts: ['A'], outPorts: ['Q'], attrs: { '.label': { text: 'NOT' }, '.body': { d: 'M 0 20 L 40 20 L 60 40 L 40 60 L 0 60 Z' } } }); }

3. 信号传递与交互逻辑

3.1 连接线行为配置

设置连线样式与交互规则:

const paper = new joint.dia.Paper({ el: document.getElementById('canvas'), model: graph, width: 800, height: 600, defaultLink: new joint.shapes.standard.Link({ attrs: { line: { stroke: '#555', strokeWidth: 2, targetMarker: { 'type': 'path', 'd': 'M 10 -5 0 0 10 5 Z' } } }, router: { name: 'manhattan' } }), validateConnection: function(cellViewS, magnetS, cellViewT, magnetT) { // 只允许输出端口连接到输入端口 return magnetS.getAttribute('port-group') === 'out' && magnetT.getAttribute('port-group') === 'in'; } });

3.2 实时信号计算

实现逻辑门运算核心逻辑:

function calculateOutput(gate, inputValues) { switch(gate.attr('.label/text')) { case 'AND': return inputValues.every(v => v === 1) ? 1 : 0; case 'OR': return inputValues.some(v => v === 1) ? 1 : 0; case 'NOT': return inputValues[0] === 1 ? 0 : 1; default: return 0; } } // 更新所有连接元件状态 function updateCircuit() { graph.getElements().forEach(gate => { const inputs = graph.getConnectedLinks(gate, { inbound: true }); const inputValues = inputs.map(link => link.get('source').value || 0); const outputValue = calculateOutput(gate, inputValues); // 更新输出端口值 gate.set('outValue', outputValue); // 传播到下游元件 graph.getConnectedLinks(gate, { outbound: true }).forEach(link => { link.set('target').value = outputValue; link.attr('line/stroke', outputValue ? '#F00' : '#555'); }); }); }

4. 高级功能实现

4.1 状态持久化与恢复

导出当前电路为JSON:

function exportCircuit() { return JSON.stringify(graph.toJSON()); }

从JSON导入电路:

function importCircuit(json) { graph.clear(); graph.fromJSON(JSON.parse(json)); }

4.2 动态元件创建界面

创建可拖拽的元件面板:

const palette = new joint.ui.Palette({ el: document.getElementById('palette'), groups: { gates: { label: '逻辑门', index: 1 } }, groupsCollapsible: true }); palette.addGroup('gates', [ { type: 'AND', template: createANDGate(0, 0), label: '与门(AND)' }, { type: 'OR', template: createORGate(0, 0), label: '或门(OR)' }, { type: 'NOT', template: createNOTGate(0, 0), label: '非门(NOT)' } ]); // 启用拖拽创建 palette.on('element:dragstart', function(elementView) { elementView.model.position(0, 0); // 重置位置 });

5. 教学应用场景扩展

5.1 真值表可视化

自动生成当前电路真值表:

function generateTruthTable() { const inputs = []; const outputs = []; // 识别所有输入输出端口 graph.getElements().forEach(gate => { if (gate.get('inPorts').length > 0 && graph.getConnectedLinks(gate, { inbound: true }).length === 0) { inputs.push(gate); } if (gate.get('outPorts').length > 0 && graph.getConnectedLinks(gate, { outbound: true }).length === 0) { outputs.push(gate); } }); // 生成所有可能的输入组合 const combinations = Math.pow(2, inputs.length); const table = []; for (let i = 0; i < combinations; i++) { const row = {}; // 设置输入值 inputs.forEach((input, idx) => { const value = (i >> idx) & 1; input.set('inValue', value); row[input.attr('.label/text')] = value; }); // 计算输出 updateCircuit(); // 记录输出值 outputs.forEach(output => { row[output.attr('.label/text')] = output.get('outValue'); }); table.push(row); } return table; }

5.2 电路验证模式

添加自动验证功能:

function verifyCircuit(expectedLogic) { const truthTable = generateTruthTable(); return truthTable.every(row => { const inputs = Object.keys(row) .filter(k => ['AND','OR','NOT'].includes(k)) .map(k => row[k]); const output = row['OUTPUT']; return expectedLogic(inputs) === output; }); } // 示例:验证一个AND-OR组合电路 const isValid = verifyCircuit(inputs => { return (inputs[0] && inputs[1]) || inputs[2]; });

6. 性能优化与实践建议

6.1 大规模电路处理

当元件数量超过100个时,可采取以下优化措施:

  1. 批量操作:使用graph.startBatch()graph.stopBatch()包裹多次更新
  2. 虚拟渲染:对不可见区域启用延迟渲染
  3. 简化样式:减少复杂SVG滤镜和渐变的使用
// 批量操作示例 graph.startBatch('complex_update'); // 执行多个元素修改 elements.forEach(el => el.set('position', randomPosition())); graph.stopBatch('complex_update');

6.2 调试技巧

常见问题排查方法:

问题现象可能原因解决方案
连线无法连接端口magnet属性未正确设置检查port-group和magnet配置
元素显示不全画布尺寸不足调用paper.scaleContentToFit()
性能明显下降频繁触发全局更新使用增量更新代替全量计算
导入后图形错位坐标系统不一致检查JSON中的position单位

6.3 扩展方向

基于当前项目可进一步开发:

  1. 复合逻辑门:将常用电路组合保存为可复用的自定义元件
  2. 时序电路:添加时钟信号支持触发器、计数器等元件
  3. 教学关卡:设计渐进式电路搭建挑战任务
  4. 多人协作:通过WebSocket实现实时协作编辑
// 自定义复合元件示例 joint.shapes.logic.CompoundGate = joint.shapes.devs.Model.extend({ defaults: joint.util.deepSupplement({ type: 'logic.CompoundGate', // 自定义属性和端口 }, joint.shapes.devs.Model.prototype.defaults) });

在开发过程中,特别注意保持代码模块化,将图形定义、业务逻辑和交互控制分离。例如,可以将所有元件模板集中在components.js中,电路计算逻辑放在simulator.js中,而界面交互则通过controller.js来管理。

http://www.jsqmd.com/news/872734/

相关文章:

  • 手把手教你用Python+UiAutomator2写一个抖音自动点赞脚本(附完整源码)
  • 2026年海南实业公司注册代办哪家强?本土合规财税机构TOP5权威实力测评 - 速递信息
  • 授权服务器搭建与授权码模式实战:信任链构建指南
  • 别再死记硬背WideDeep了!用TensorFlow 2.x手把手复现Google Play的推荐模型(附源码)
  • 紫桐冰酒:冰雪中的甜蜜艺术,匠心铸就东方冰酒典范 - 速递信息
  • VideoDownloadHelper:打破视频下载壁垒的智能解析引擎
  • ArcGIS Pro插件开发避坑指南:从DAML配置到图标路径的那些‘坑’
  • 超像素如何让Transformer更聪明?拆解SPIN论文里的ISPA与SPCA模块设计思路
  • 互联网软件企业的新建软件系统的缺陷密度
  • modAL贝叶斯优化:终极超参数调优实战指南
  • 2026年海南公司注册代办选择指南怎么选?合规高效服务商TOP10权威排行名单发布 - 速递信息
  • 从零实现神经网络:用XOR手撕反向传播与梯度计算
  • Frida内存漫游:无符号环境下定位X-Gorgon加密逻辑
  • Frida版本匹配实战指南:解决PC端与手机端不兼容问题
  • 别再死记硬背了!深入解析51单片机生成正弦波的查表法与延时技巧
  • Phyphox磁力计避坑指南:为什么你测的地磁场总不准?从校准到环境干扰的5个关键点
  • 紫桐载誉!斩获2026中国欧洲葡萄酒与白酒国际大奖赛双金奖 - 速递信息
  • 边缘多模态AI驱动的文档重构技术
  • Unity MCP协议实战:自然语言驱动UI动画生成
  • 告别盲测!用CANoe回放功能搭建你的车载网络“时光机”
  • 日志规范化与结构化输出:构建可观测的 AI 后端系统
  • LLM服务中的KV缓存碳排放优化与GreenCache框架
  • 5 月 23 日合肥实时金价,皖城出金,本地人专属避坑攻略 - 资讯纵览
  • 合肥 GEO 优化服务商精选|合肥豆包搜索优化优质机构推荐 - 行业深度观察C
  • 初创团队如何利用Taotoken统一管理多项目的AI模型调用
  • STM32驱动ST7735S屏幕避坑指南:从SPI时序到字库显示(附代码)
  • 事件相机与3D高斯飞溅技术在自动驾驶与无人机避障中的应用
  • 嵌入式C语言寄存器优化技巧与编译器原理
  • Java漏洞修复不是升级依赖:JVM类加载隔离与可验证补丁交付
  • 优化缺陷密度,核心是从“事后救火”转向“全程预防”