Created
August 19, 2024 12:31
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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