Skip to content

Instantly share code, notes, and snippets.

@vidarh
Created August 19, 2024 12:31
Show Gist options
  • Save vidarh/b54c7c7be878cf0a95c4c878a06f287e to your computer and use it in GitHub Desktop.
Save vidarh/b54c7c7be878cf0a95c4c878a06f287e to your computer and use it in GitHub Desktop.
Simple example of an ER diagram with some embellishments (gradients, fixes to connector routing) using JointJS written entirely by Claude
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ER Diagram with JointJS, Metallic Entities, and Drop Shadows (Fixed)</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.4.0/backbone-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jointjs/3.5.5/joint.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jointjs/3.5.5/joint.min.css" />
<style>
#er-diagram {
width: 800px;
height: 600px;
border: 1px solid #ccc;
}
</style>
</head>
<body>
<h1>ER Diagram with JointJS, Metallic Entities, and Drop Shadows (Fixed)</h1>
<div id="er-diagram"></div>
<script>
var graph = new joint.dia.Graph();
var paper = new joint.dia.Paper({
el: document.getElementById('er-diagram'),
model: graph,
width: 800,
height: 600,
gridSize: 1,
drawGrid: true,
background: {
color: 'rgba(0, 255, 0, 0.1)'
},
interactive: true
});
// Define gradient and filter
var gradient = '<linearGradient id="metallic" x1="0%" y1="0%" x2="100%" y2="100%">' +
'<stop offset="0%" style="stop-color:rgb(180,180,180);stop-opacity:1" />' +
'<stop offset="100%" style="stop-color:rgb(220,220,220);stop-opacity:1" />' +
'</linearGradient>';
var filter = '<filter id="dropShadow" height="130%">' +
'<feGaussianBlur in="SourceAlpha" stdDeviation="3"/> ' +
'<feOffset dx="2" dy="2" result="offsetblur"/> ' +
'<feComponentTransfer>' +
'<feFuncA type="linear" slope="0.2"/> ' +
'</feComponentTransfer>' +
'<feMerge> ' +
'<feMergeNode/> ' +
'<feMergeNode in="SourceGraphic"/> ' +
'</feMerge>' +
'</filter>';
// Add defs to SVG
var defs = V('defs');
defs.append(V(gradient));
defs.append(V(filter));
V(paper.svg).prepend(defs);
// Custom shape for ER entities
joint.shapes.er = {};
joint.shapes.er.Entity = joint.shapes.standard.Rectangle.define('er.Entity', {
attrs: {
body: {
fill: 'url(#metallic)',
stroke: 'black',
strokeWidth: 2,
filter: 'url(#dropShadow)'
},
title: {
text: 'Entity',
fill: 'black',
fontSize: 14,
fontWeight: 'bold',
textVerticalAnchor: 'middle',
textAnchor: 'middle',
refX: '50%',
refY: 15
},
separator: {
stroke: 'black',
strokeWidth: 1
},
columnsNames: {
text: '',
fill: 'black',
fontSize: 12,
textVerticalAnchor: 'top',
textAnchor: 'start',
refX: 5,
refY: 35
},
columnsTypes: {
text: '',
fill: 'black',
fontSize: 12,
textVerticalAnchor: 'top',
textAnchor: 'start',
refX: 100,
refY: 35
}
}
}, {
markup: [{
tagName: 'rect',
selector: 'body'
}, {
tagName: 'text',
selector: 'title'
}, {
tagName: 'line',
selector: 'separator'
}, {
tagName: 'text',
selector: 'columnsNames'
}, {
tagName: 'text',
selector: 'columnsTypes'
}]
});
// Function to create an entity with columns
function createEntity(x, y, title, columns) {
var names = [];
var types = [];
columns.forEach(function(column) {
var parts = column.split(':');
names.push(parts[0].trim());
types.push(parts[1].trim());
});
return new joint.shapes.er.Entity({
position: { x: x, y: y },
size: { width: 200, height: 40 + columns.length * 20 },
attrs: {
title: { text: title },
separator: { x1: 0, y1: 30, x2: 200, y2: 30 },
columnsNames: { text: names.join('\n') },
columnsTypes: { text: types.join('\n') }
}
});
}
// Create entities
var user = createEntity(100, 100, 'User', [
'id: int (PK)',
'username: varchar(50)',
'email: varchar(100)',
'created_at: timestamp'
]);
var post = createEntity(400, 100, 'Post', [
'id: int (PK)',
'title: varchar(200)',
'content: text',
'user_id: int (FK)',
'published_at: timestamp'
]);
// Add entities to the graph
graph.addCells([user, post]);
// Custom router function
function customRouter(vertices, args, linkView) {
var sourcePoint = args.sourcePoint || linkView.sourcePoint;
var targetPoint = args.targetPoint || linkView.targetPoint;
if (!sourcePoint || !targetPoint) {
return vertices;
}
var midY = (sourcePoint.y + targetPoint.y) / 2;
return [
sourcePoint,
{ x: sourcePoint.x + 20, y: sourcePoint.y },
{ x: sourcePoint.x + 20, y: midY },
{ x: targetPoint.x - 20, y: midY },
{ x: targetPoint.x - 20, y: targetPoint.y },
targetPoint
];
}
// Create a custom link
var link = new joint.shapes.standard.Link({
source: { id: user.id },
target: { id: post.id },
router: customRouter,
connector: { name: 'rounded' },
attrs: {
line: {
stroke: 'black',
strokeWidth: 2
}
},
labels: [{
position: 0.5,
attrs: {
text: {
text: 'creates',
fill: 'black'
}
}
}]
});
// Add link to the graph
graph.addCell(link);
// Function to update link when entities move
function updateLinkEndpoints() {
var userPos = user.position();
var postPos = post.position();
link.set({
source: { x: userPos.x + 200, y: userPos.y + user.size().height / 2 },
target: { x: postPos.x, y: postPos.y + post.size().height / 2 }
});
}
// Initial update of link endpoints
updateLinkEndpoints();
// Listen for changes in element position
graph.on('change:position', function(element) {
updateLinkEndpoints();
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment