<!DOCTYPE html>
<html>
<head>
<title>ER图生成器(最终版)</title>
<style>
.container { margin: 20px; }
input, button { margin: 5px; }
svg { border: 1px solid #ccc; margin: 10px; }
input { width: 300px; }
</style>
</head>
<body>
<div class="container">
<div>
<label>实体名称:</label>
<input type="text" id="entityName" placeholder="例如:User" value="User">
</div>
<div>
<label>属性列表(逗号分隔):</label>
<input type="text" id="attributes" placeholder="例如:id*,name,age" value="id*,username,password,email,created_at,updated_at,profile,status,role,department,phone,address">
<small>(带*表示主键)</small>
</div>
<button onclick="generateER()">生成ER图</button>
</div>
<svg id="erDiagram" width="1000" height="800"></svg>
<script>
// 几何计算函数
function getRectIntersection(centerX, centerY, rectWidth, rectHeight, targetX, targetY) {
const dx = targetX - centerX;
const dy = targetY - centerY;
if (dx === 0 && dy === 0) return { x: centerX, y: centerY };
let tx = Infinity;
let ty = Infinity;
if (dx !== 0) {
const edgeX = dx > 0 ? rectWidth / 2 : -rectWidth / 2;
tx = edgeX / dx;
}
if (dy !== 0) {
const edgeY = dy > 0 ? rectHeight / 2 : -rectHeight / 2;
ty = edgeY / dy;
}
const t = Math.min(tx, ty);
return {
x: centerX + dx * t,
y: centerY + dy * t
};
}
function getEllipseIntersection(startX, startY, ellipseX, ellipseY, rx, ry) {
const deltaX = startX - ellipseX;
const deltaY = startY - ellipseY;
const a = (deltaX * deltaX) / (rx * rx) + (deltaY * deltaY) / (ry * ry);
if (a === 0) return { x: ellipseX, y: ellipseY };
const t = 1 - 1 / Math.sqrt(a);
return {
x: startX + (ellipseX - startX) * t,
y: startY + (ellipseY - startY) * t
};
}
function generateER() {
const svg = document.getElementById('erDiagram');
svg.innerHTML = '';
const entityName = document.getElementById('entityName').value.trim();
const attributes = document.getElementById('attributes').value
.split(',')
.filter(attr => attr.trim())
.map(attr => ({
name: attr.replace('*', '').trim(),
isPrimary: attr.includes('*')
}));
if (!entityName || attributes.length === 0) {
alert("请填写实体名称和至少一个属性!");
return;
}
// 核心配置参数
const config = {
centerX: 500,
centerY: 400,
rectWidth: 160,
rectHeight: 80,
innerRadius: 200,
outerRadius: 320,
minSplitCount: 8,
ellipseRX: 60,
ellipseRY: 35
};
// 智能分圈系统
const shouldSplit = attributes.length > config.minSplitCount;
const groups = shouldSplit ? [
{
attributes: attributes.slice(0, Math.ceil(attributes.length * 0.6)),
radius: config.innerRadius,
angleOffset: 0
},
{
attributes: attributes.slice(Math.ceil(attributes.length * 0.6)),
radius: config.outerRadius,
angleOffset: Math.PI / (attributes.length / 2)
}
] : [{
attributes: attributes,
radius: config.innerRadius,
angleOffset: 0
}];
// 绘制实体
const entityRect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
entityRect.setAttribute("x", config.centerX - config.rectWidth/2);
entityRect.setAttribute("y", config.centerY - config.rectHeight/2);
entityRect.setAttribute("width", config.rectWidth);
entityRect.setAttribute("height", config.rectHeight);
entityRect.setAttribute("fill", "#f8f9fa");
entityRect.setAttribute("stroke", "#333");
entityRect.setAttribute("stroke-width", 2);
svg.appendChild(entityRect);
// 实体名称
const entityText = document.createElementNS("http://www.w3.org/2000/svg", "text");
entityText.setAttribute("x", config.centerX);
entityText.setAttribute("y", config.centerY);
entityText.setAttribute("text-anchor", "middle");
entityText.setAttribute("dominant-baseline", "middle");
entityText.setAttribute("font-weight", "bold");
entityText.setAttribute("font-size", "18");
entityText.textContent = entityName;
svg.appendChild(entityText);
// 绘制属性和连线
groups.forEach(group => {
const angleStep = (2 * Math.PI) / group.attributes.length;
group.attributes.forEach((attr, index) => {
const angle = angleStep * index + group.angleOffset;
const attrX = config.centerX + group.radius * Math.cos(angle);
const attrY = config.centerY + group.radius * Math.sin(angle);
// 计算连接点
const startPoint = getRectIntersection(
config.centerX, config.centerY,
config.rectWidth, config.rectHeight,
attrX, attrY
);
const endPoint = getEllipseIntersection(
startPoint.x, startPoint.y,
attrX, attrY,
config.ellipseRX, config.ellipseRY
);
// 绘制连接线
const line = document.createElementNS("http://www.w3.org/2000/svg", "line");
line.setAttribute("x1", startPoint.x);
line.setAttribute("y1", startPoint.y);
line.setAttribute("x2", endPoint.x);
line.setAttribute("y2", endPoint.y);
line.setAttribute("stroke", "#666");
line.setAttribute("stroke-width", 1.5);
svg.appendChild(line);
// 绘制属性椭圆
const ellipse = document.createElementNS("http://www.w3.org/2000/svg", "ellipse");
ellipse.setAttribute("cx", attrX);
ellipse.setAttribute("cy", attrY);
ellipse.setAttribute("rx", config.ellipseRX);
ellipse.setAttribute("ry", config.ellipseRY);
ellipse.setAttribute("fill", "#fff");
ellipse.setAttribute("stroke", "#333");
ellipse.setAttribute("stroke-width", 1.2);
svg.appendChild(ellipse);
// 属性文字
const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
text.setAttribute("x", attrX);
text.setAttribute("y", attrY);
text.setAttribute("text-anchor", "middle");
text.setAttribute("dominant-baseline", "middle");
text.setAttribute("font-size", "14");
if(attr.isPrimary) {
text.innerHTML = `<tspan style="text-decoration: underline">${attr.name}</tspan>`;
} else {
text.textContent = attr.name;
}
svg.appendChild(text);
});
});
}
</script>
</body>
</html>
自动根据名称生成er图
最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。