paper-burner/js/chatbot/utils/drawio-academic-enhancer.js

376 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// js/chatbot/utils/drawio-academic-enhancer.js
/**
* Draw.io 学术增强工具
*
* 🎓 专为学术论文配图设计,不同于通用美化工具
*
* 核心理念:
* 1. 学术规范 - 符合 IEEE/ACM/Nature 等期刊标准
* 2. 语义理解 - 根据内容自动分类和配色
* 3. 渐进增强 - 三级优化,用户可选
* 4. 可读性优先 - 黑白打印也清晰
*
* @version 1.0.0
* @date 2025-01-15
*/
/**
* 解析样式字符串为对象
*/
function parseStyle(styleString) {
const style = {};
if (!styleString) return style;
styleString.split(';').forEach(pair => {
const [key, value] = pair.split('=');
if (key && key.trim()) {
style[key.trim()] = value || '';
}
});
return style;
}
/**
* 样式对象转回字符串
*/
function styleToString(styleObj) {
return Object.entries(styleObj)
.map(([k, v]) => v ? `${k}=${v}` : k)
.join(';');
}
/**
* 🎯 Level 1: 学术基础优化
*
* 重点:清晰度和规范性
* - 统一线宽(易于打印)
* - 连接线标签背景(黑白打印可辨认)
* - 字体大小规范化(符合学术期刊要求)
*/
function academicBaselineOptimization(xmlDoc) {
console.log('[AcademicEnhancer] 🎓 Level 1: 学术基础优化');
let optimized = 0;
// 1. 优化连接线
const edges = xmlDoc.querySelectorAll('mxCell[edge="1"]');
edges.forEach(edge => {
const style = parseStyle(edge.getAttribute('style') || '');
// ✅ 统一线宽为 2px学术标准
if (!style.strokeWidth || style.strokeWidth === '1') {
style.strokeWidth = '2';
optimized++;
}
// ✅ 连接线标签添加白色背景(关键改进!)
const hasLabel = edge.getAttribute('value');
if (hasLabel) {
style.labelBackgroundColor = '#ffffff';
style.labelBorderColor = '#d0d0d0';
style.labelPadding = '4';
optimized++;
}
// ✅ 圆角转弯(更专业)
if (style.edgeStyle === 'orthogonalEdgeStyle') {
style.rounded = '1';
style.arcSize = '6'; // 小圆角,不夸张
}
// ✅ 统一箭头样式(学术标准:实心块状箭头)
if (!style.endArrow) {
style.endArrow = 'block';
style.endFill = '1';
style.endSize = '6';
}
edge.setAttribute('style', styleToString(style));
});
// 2. 规范化节点字体
const vertices = xmlDoc.querySelectorAll('mxCell[vertex="1"]');
vertices.forEach(vertex => {
const style = parseStyle(vertex.getAttribute('style') || '');
// ✅ 统一字体大小(学术可读性)
if (!style.fontSize || parseInt(style.fontSize) < 11) {
style.fontSize = '12'; // 默认 12pt
optimized++;
}
// ✅ 启用 HTML 模式(支持换行)
if (!style.html) {
style.html = '1';
}
vertex.setAttribute('style', styleToString(style));
});
console.log(`[AcademicEnhancer] ✅ Level 1 完成: ${optimized} 处优化`);
return optimized;
}
/**
* 🎨 Level 2: 语义感知配色
*
* 重点:自动识别节点类型并配色
* - 输入节点 → 蓝色系
* - 处理节点 → 绿色系
* - 输出节点 → 橙色系
* - 决策节点 → 黄色系
* - 数据存储 → 灰色系
*/
function semanticColorization(xmlDoc) {
console.log('[AcademicEnhancer] 🎨 Level 2: 语义感知配色');
// 学术配色方案(色盲友好 + 黑白打印可辨)
const colorSchemes = {
input: {
fill: '#dae8fc',
stroke: '#6c8ebf',
keywords: ['输入', 'input', '数据', 'data', '采集', 'collect', '读取', 'read']
},
process: {
fill: '#d5e8d4',
stroke: '#82b366',
keywords: ['处理', 'process', '计算', 'compute', '分析', 'analyze', '算法', 'algorithm']
},
output: {
fill: '#ffe6cc',
stroke: '#d79b00',
keywords: ['输出', 'output', '结果', 'result', '生成', 'generate', '显示', 'display']
},
decision: {
fill: '#fff2cc',
stroke: '#d6b656',
keywords: ['判断', 'decision', '选择', 'choose', '是否', 'if', '条件', 'condition']
},
storage: {
fill: '#f5f5f5',
stroke: '#666666',
keywords: ['存储', 'storage', '数据库', 'database', '缓存', 'cache', '保存', 'save']
}
};
let colorized = 0;
const vertices = xmlDoc.querySelectorAll('mxCell[vertex="1"]');
vertices.forEach(vertex => {
const value = (vertex.getAttribute('value') || '').toLowerCase();
const style = parseStyle(vertex.getAttribute('style') || '');
// 跳过已经有明确配色的节点
if (style.fillColor && style.fillColor !== '#ffffff') {
return;
}
// 语义匹配
for (const [type, scheme] of Object.entries(colorSchemes)) {
const matched = scheme.keywords.some(keyword => value.includes(keyword));
if (matched) {
style.fillColor = scheme.fill;
style.strokeColor = scheme.stroke;
vertex.setAttribute('style', styleToString(style));
colorized++;
break;
}
}
});
console.log(`[AcademicEnhancer] ✅ Level 2 完成: ${colorized} 个节点智能配色`);
return colorized;
}
/**
* 📐 Level 3: 学术规范增强
*
* 重点:符合学术期刊投稿标准
* - 子图编号 (a), (b), (c)
* - 图例自动生成
* - 统一对齐网格线
* - 添加比例尺/单位标注
*/
function academicStandardEnhancement(xmlDoc) {
console.log('[AcademicEnhancer] 📐 Level 3: 学术规范增强');
let enhanced = 0;
// 1. 自动添加子图编号(学术论文标准)
const vertices = Array.from(xmlDoc.querySelectorAll('mxCell[vertex="1"]'));
// 只对"主要节点"添加编号(不是标题、不是注释)
const mainNodes = vertices.filter(v => {
const style = parseStyle(v.getAttribute('style') || '');
const value = v.getAttribute('value') || '';
// 排除标题样式节点
if (style.fontSize && parseInt(style.fontSize) > 16) return false;
// 排除纯文本节点
if (style.shape === 'text' || !style.shape) return false;
// 排除空节点
if (!value.trim()) return false;
return true;
});
// 按 Y 坐标排序(从上到下)
mainNodes.sort((a, b) => {
const geoA = a.querySelector('mxGeometry');
const geoB = b.querySelector('mxGeometry');
if (!geoA || !geoB) return 0;
const yA = parseFloat(geoA.getAttribute('y')) || 0;
const yB = parseFloat(geoB.getAttribute('y')) || 0;
return yA - yB;
});
// 添加编号(如果节点数量合理)
if (mainNodes.length >= 3 && mainNodes.length <= 10) {
mainNodes.forEach((node, index) => {
const label = String.fromCharCode(97 + index); // a, b, c...
const currentValue = node.getAttribute('value') || '';
// 检查是否已有编号
if (!currentValue.match(/^\([a-z]\)/)) {
node.setAttribute('value', `(${label}) ${currentValue}`);
enhanced++;
}
});
}
// 2. 生成图例(如果使用了多种颜色)
const usedColors = new Set();
vertices.forEach(v => {
const style = parseStyle(v.getAttribute('style') || '');
if (style.fillColor && style.fillColor !== '#ffffff') {
usedColors.add(style.fillColor);
}
});
// 如果使用了 3 种以上颜色,生成图例
if (usedColors.size >= 3) {
console.log('[AcademicEnhancer] 检测到多色配色方案,建议手动添加图例');
// 注:自动生成图例会干扰布局,这里只做提示
}
console.log(`[AcademicEnhancer] ✅ Level 3 完成: ${enhanced} 处学术规范增强`);
return enhanced;
}
/**
* 🔍 自动检测图表类型
*
* 根据节点和连接的特征判断图表类型:
* - flowchart: 流程图(有决策节点、线性流程)
* - architecture: 架构图(层次分明、模块化)
* - network: 网络图(节点相互连接)
* - sequence: 序列图(时间顺序)
*/
function detectDiagramType(xmlDoc) {
const vertices = xmlDoc.querySelectorAll('mxCell[vertex="1"]');
const edges = xmlDoc.querySelectorAll('mxCell[edge="1"]');
if (vertices.length === 0) return 'unknown';
// 特征检测
let hasDecisionShape = false;
let hasLayeredLayout = false;
let avgConnectionsPerNode = edges.length / vertices.length;
vertices.forEach(v => {
const style = parseStyle(v.getAttribute('style') || '');
if (style.shape === 'rhombus' || style.shape === 'diamond') {
hasDecisionShape = true;
}
});
// 检测是否有明显的层次结构Y 坐标相近的节点成组)
const yGroups = new Map();
vertices.forEach(v => {
const geo = v.querySelector('mxGeometry');
if (!geo) return;
const y = Math.round(parseFloat(geo.getAttribute('y') || 0) / 50) * 50; // 按 50px 分组
yGroups.set(y, (yGroups.get(y) || 0) + 1);
});
hasLayeredLayout = yGroups.size >= 3 && Array.from(yGroups.values()).some(count => count >= 2);
// 判断类型
if (hasDecisionShape) return 'flowchart';
if (hasLayeredLayout) return 'architecture';
if (avgConnectionsPerNode > 2) return 'network';
if (avgConnectionsPerNode <= 1.5) return 'sequence';
return 'generic';
}
/**
* 主优化函数:学术增强
*
* @param {string} xmlString - 原始 XML
* @param {Object} options - 优化选项
* @param {number} options.level - 优化级别 1-3
* @param {boolean} options.autoDetect - 是否自动检测图表类型
* @returns {string} 优化后的 XML
*/
function enhanceAcademicDiagram(xmlString, options = {}) {
const defaultOptions = {
level: 2, // 默认 Level 2基础 + 配色)
autoDetect: true // 自动检测图表类型
};
const opts = { ...defaultOptions, ...options };
try {
console.log('[AcademicEnhancer] 🎓 开始学术增强...');
// 解析 XML
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlString, 'text/xml');
const parserError = xmlDoc.querySelector('parsererror');
if (parserError) {
throw new Error('XML 解析失败');
}
// 自动检测图表类型
if (opts.autoDetect) {
const diagramType = detectDiagramType(xmlDoc);
console.log(`[AcademicEnhancer] 📊 检测到图表类型: ${diagramType}`);
}
let totalEnhanced = 0;
// Level 1: 基础优化(总是执行)
totalEnhanced += academicBaselineOptimization(xmlDoc);
// Level 2: 语义配色
if (opts.level >= 2) {
totalEnhanced += semanticColorization(xmlDoc);
}
// Level 3: 学术规范
if (opts.level >= 3) {
totalEnhanced += academicStandardEnhancement(xmlDoc);
}
console.log(`[AcademicEnhancer] ✅ 学术增强完成,共 ${totalEnhanced} 处改进`);
// 序列化回 XML
const serializer = new XMLSerializer();
return serializer.serializeToString(xmlDoc);
} catch (error) {
console.error('[AcademicEnhancer] ❌ 学术增强失败:', error);
return xmlString; // 失败时返回原始 XML
}
}
// 导出到全局
window.DrawioAcademicEnhancer = {
enhanceAcademicDiagram,
detectDiagramType
};