Skip to content

Instantly share code, notes, and snippets.

@neisdev
Created October 14, 2025 05:14
Show Gist options
  • Select an option

  • Save neisdev/5fb123d056f0891c900a4b6efecba780 to your computer and use it in GitHub Desktop.

Select an option

Save neisdev/5fb123d056f0891c900a4b6efecba780 to your computer and use it in GitHub Desktop.
Entity Relationship Diagram ERD

Entity Relationship Diagram ERD

Sometimes you need the simplicity, this pen draw an ERD with only a few parameters, clean and easy to use, was developed prompting ChatGPT

A Pen by Ronald Avendaño on CodePen.

License.

<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Entity Relationship Diagram ERD</title>
</head>
<body>
<p>This is a single ERD using canvas and javascript aided with ChatGPT, no libraries needed</p>
<canvas id="canvas" width="1000" height="1000"></canvas>
</body>
</html>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let selectedTable = null;
let offsetX = 0;
let offsetY = 0;
// Definición de tablas y relaciones
const entities = {
projects: {
x: 50,
y: 100,
title: "Projects",
fields: ["ProjectID (PK)", "ProjectName", "StartDate", "EndDate"],
headerBackgroundColor: 'lightblue',
headerBorderColor: 'black',
headerFont: '16px Arial',
headerPadding: 10,
fieldsBackgroundColor: 'white',
fieldsBorderColor: 'black',
fieldsFont: '12px Arial'
},
tasks: {
x: 400,
y: 100,
title: "Tasks",
fields: ["TaskID (PK)", "ProjectID (FK)", "TaskName", "StartDate", "EndDate"],
headerBackgroundColor: 'lightgreen',
headerBorderColor: 'black',
headerFont: '16px Arial',
headerPadding: 10,
fieldsBackgroundColor: 'white',
fieldsBorderColor: 'black',
fieldsFont: '12px Arial'
},
employees: {
x: 50,
y: 300,
title: "Employees",
fields: ["EmployeeID (PK)", "FirstName", "LastName", "Position"],
headerBackgroundColor: 'lightyellow',
headerBorderColor: 'black',
headerFont: '16px Arial',
headerPadding: 10,
fieldsBackgroundColor: 'white',
fieldsBorderColor: 'black',
fieldsFont: '12px Arial'
},
assignments: {
x: 750,
y: 300,
title: "Assignments",
fields: ["AssignmentID (PK)", "TaskID (FK)", "EmployeeID (FK)"],
headerBackgroundColor: 'lightblue',
headerBorderColor: 'black',
headerFont: '16px Arial',
headerPadding: 10,
fieldsBackgroundColor: 'white',
fieldsBorderColor: 'black',
fieldsFont: '12px Arial'
}
};
const relationships = [
{ from: entities.projects, to: entities.tasks, label: "0 or 1" },
{ from: entities.employees, to: entities.assignments, label: "1 :. 1" },
{ from: entities.tasks, to: entities.assignments, label: "1 - N" }
];
// Dibuja las tablas en su posición actual
function drawTables() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Dibuja relaciones
ctx.strokeStyle = 'gray';
ctx.lineWidth = 2;
relationships.forEach(relationship => {
drawRelationship(relationship);
});
// Dibuja tablas
Object.values(entities).forEach(entity => {
drawTable(entity);
});
}
// Dibuja una tabla con campos
function drawTable(table) {
const tableWidth = 200;
const cellHeight = 20;
const tableHeight = 100 + table.fields.length * cellHeight;
// Dibuja el rectángulo de la tabla
ctx.fillStyle = table.fieldsBackgroundColor;
ctx.fillRect(table.x, table.y, tableWidth, tableHeight);
ctx.strokeStyle = table.fieldsBorderColor;
ctx.strokeRect(table.x, table.y, tableWidth, tableHeight);
// Dibuja el encabezado de la tabla
ctx.fillStyle = table.headerBackgroundColor;
ctx.strokeStyle = table.headerBorderColor;
ctx.lineWidth = 2;
ctx.fillRect(table.x, table.y, tableWidth, table.headerPadding * 2);
ctx.strokeRect(table.x, table.y, tableWidth, table.headerPadding * 2);
// Dibuja el título de la tabla
ctx.fillStyle = 'black';
ctx.font = table.headerFont;
ctx.fillText(table.title, table.x + table.headerPadding, table.y + table.headerPadding * 1.5);
// Dibuja los campos de la tabla
ctx.font = table.fieldsFont;
for (let i = 0; i < table.fields.length; i++) {
ctx.fillText(table.fields[i], table.x + table.headerPadding, table.y + table.headerPadding * 3.5 + i * cellHeight);
}
}
// Dibuja una relación entre dos tablas con líneas angulares redondeadas y notación de cardinalidad
function drawRelationship(relationship) {
const startX = relationship.from.x + 200;
const startY = relationship.from.y + 50;
const endX = relationship.to.x;
const endY = relationship.to.y + 50;
// Puntos de control para curvas
const controlPointX1 = startX + 100;
const controlPointY1 = startY;
const controlPointX2 = endX - 100;
const controlPointY2 = endY;
// Dibuja la línea de relación
ctx.beginPath();
ctx.moveTo(startX, startY);
ctx.lineTo(controlPointX1, controlPointY1);
ctx.quadraticCurveTo(startX, startY, controlPointX1, controlPointY1);
ctx.lineTo(controlPointX2, controlPointY2);
ctx.quadraticCurveTo(endX, endY, controlPointX2, controlPointY2);
ctx.lineTo(endX, endY);
ctx.stroke();
// Dibuja la notación de cardinalidad
const labelX = (startX + endX) / 2;
const labelY = (startY + endY) / 2;
ctx.fillStyle = 'black';
ctx.font = '12px Arial';
ctx.fillText(relationship.label, labelX, labelY);
// Dibuja la terminación de la relación
if (relationship.label === "1 - N") {
ctx.beginPath();
ctx.moveTo(startX + 10, startY - 8);
ctx.lineTo(startX + 10, startY + 8);
ctx.moveTo(endX - 10, endY);
ctx.lineTo(endX, endY + 5);
ctx.moveTo(endX - 10, endY);
ctx.lineTo(endX, endY - 5);
ctx.stroke();
} else if(relationship.label === "1 - 1") {
ctx.beginPath();
ctx.moveTo(startX + 10, startY - 8);
ctx.lineTo(startX + 10, startY + 8);
ctx.moveTo(endX - 10, endY - 8);
ctx.lineTo(endX - 10, endY + 8);
ctx.stroke();
} else if(relationship.label === "N - N") {
ctx.beginPath();
ctx.moveTo(startX + 10, startY);
ctx.lineTo(startX, startY - 5);
ctx.moveTo(startX + 10, startY);
ctx.lineTo(startX, startY + 5);
ctx.moveTo(endX - 10, endY);
ctx.lineTo(endX, endY + 5);
ctx.moveTo(endX - 10, endY);
ctx.lineTo(endX, endY - 5);
ctx.stroke();
} else if(relationship.label === "1 or N"){
ctx.beginPath();
ctx.moveTo(startX + 10, startY - 8);
ctx.lineTo(startX + 10, startY + 8);
ctx.moveTo(endX - 10, endY - 8);
ctx.lineTo(endX - 10, endY + 8);
ctx.moveTo(endX - 10, endY);
ctx.lineTo(endX, endY + 5);
ctx.moveTo(endX - 10, endY);
ctx.lineTo(endX, endY - 5);
ctx.stroke();
} else if(relationship.label === "0 or 1"){
ctx.beginPath();
ctx.moveTo(startX + 10, startY - 8);
ctx.lineTo(startX + 10, startY + 8);
ctx.moveTo(endX - 6, endY - 8);
ctx.lineTo(endX - 6, endY + 8);
ctx.stroke();
ctx.beginPath();
ctx.arc(endX - 15, endY, 5, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
} else if(relationship.label === "0 or N"){
ctx.beginPath();
ctx.moveTo(startX + 10, startY - 8);
ctx.lineTo(startX + 10, startY + 8);
ctx.moveTo(endX - 10, endY);
ctx.moveTo(endX - 10, endY);
ctx.lineTo(endX, endY + 5);
ctx.moveTo(endX - 10, endY);
ctx.lineTo(endX, endY - 5);
ctx.stroke();
//ctx.fillStyle = 'white';
ctx.beginPath();
ctx.arc(endX - 15, endY, 5, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
} else if(relationship.label === "1 :. 1"){
ctx.beginPath();
ctx.moveTo(endX - 10, endY - 8);
ctx.lineTo(endX - 10, endY + 8);
ctx.moveTo(endX - 14, endY - 8);
ctx.lineTo(endX - 14, endY + 8);
ctx.stroke();
}
}
// Evento de clic del ratón para seleccionar y mover tablas
canvas.addEventListener('mousedown', (e) => {
const mouseX = e.clientX - canvas.getBoundingClientRect().left;
const mouseY = e.clientY - canvas.getBoundingClientRect().top;
// Verifica si se hizo clic en una tabla
selectedTable = Object.values(entities).find(entity => {
return (
mouseX >= entity.x &&
mouseX <= entity.x + 200 &&
mouseY >= entity.y &&
mouseY <= entity.y + (100 + entity.fields.length * 20)
);
});
if (selectedTable) {
offsetX = mouseX - selectedTable.x;
offsetY = mouseY - selectedTable.y;
}
});
// Evento de movimiento del ratón para arrastrar la tabla seleccionada
canvas.addEventListener('mousemove', (e) => {
if (selectedTable) {
const mouseX = e.clientX - canvas.getBoundingClientRect().left;
const mouseY = e.clientY - canvas.getBoundingClientRect().top;
selectedTable.x = mouseX - offsetX;
selectedTable.y = mouseY - offsetY;
drawTables();
}
});
// Evento de liberación del botón del ratón para soltar la tabla seleccionada
canvas.addEventListener('mouseup', () => {
selectedTable = null;
});
// Dibuja las tablas iniciales
drawTables();
body {
margin: 0;
overflow: hidden;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment