mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-08-15 06:19:24 +02:00
Fix: Adjust ER diagram row height calculation and layout rendering
This commit is contained in:
222
demos/er-multiline.html
Normal file
222
demos/er-multiline.html
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet" />
|
||||||
|
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Caveat:wght@400..700&family=Kalam:wght@300;400;700&family=Rubik+Mono+One&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&family=Rubik+Mono+One&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Recursive:wght@300..1000&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.recursive-500 {
|
||||||
|
font-family: 'Recursive', serif;
|
||||||
|
font-optical-sizing: auto;
|
||||||
|
font-weight: 500;
|
||||||
|
font-style: normal;
|
||||||
|
font-variation-settings:
|
||||||
|
'slnt' 0,
|
||||||
|
'CASL' 0,
|
||||||
|
'CRSV' 0.5,
|
||||||
|
'MONO' 0;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
/* background: rgb(221, 208, 208); */
|
||||||
|
/* background: #333; */
|
||||||
|
/* font-family: 'Arial'; */
|
||||||
|
font-family: 'Recursive', serif;
|
||||||
|
font-optical-sizing: auto;
|
||||||
|
font-weight: 500;
|
||||||
|
font-style: normal;
|
||||||
|
font-variation-settings:
|
||||||
|
'slnt' 0,
|
||||||
|
'CASL' 0,
|
||||||
|
'CRSV' 0.5,
|
||||||
|
'MONO' 0;
|
||||||
|
/* color: white; */
|
||||||
|
/* font-size: 18px !important; */
|
||||||
|
}
|
||||||
|
.gridify.tiny {
|
||||||
|
background-image:
|
||||||
|
linear-gradient(transparent 11px, rgba(220, 220, 200, 0.8) 12px, transparent 12px),
|
||||||
|
linear-gradient(90deg, transparent 11px, rgba(220, 220, 200, 0.8) 12px, transparent 12px);
|
||||||
|
background-size:
|
||||||
|
100% 12px,
|
||||||
|
12px 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gridify.dots {
|
||||||
|
background-image: radial-gradient(
|
||||||
|
circle at center,
|
||||||
|
rgba(220, 220, 200, 0.8) 1px,
|
||||||
|
transparent 1px
|
||||||
|
);
|
||||||
|
background-size: 24px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid2 {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid svg {
|
||||||
|
font-size: 16px !important;
|
||||||
|
font-family: 'Recursive', serif;
|
||||||
|
font-optical-sizing: auto;
|
||||||
|
font-weight: 500;
|
||||||
|
font-style: normal;
|
||||||
|
font-variation-settings:
|
||||||
|
'slnt' 0,
|
||||||
|
'CASL' 0,
|
||||||
|
'CRSV' 0.5,
|
||||||
|
'MONO' 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
width: 100%;
|
||||||
|
/*box-shadow: 4px 4px 0px 0px #0000000F;*/
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="gridify dots">
|
||||||
|
<div class="w-full h-64">
|
||||||
|
<pre id="diagram4" class="mermaid" style="background: rgb(255, 255, 255)">
|
||||||
|
erDiagram
|
||||||
|
CAR ||--o{ NAMED-DRIVER : allows
|
||||||
|
CAR ::: Pine {
|
||||||
|
string registrationNumber PK "Primary Key<br><strong>Unique registration number</strong>"
|
||||||
|
string make "Car make<br><strong>e.g., Toyota</strong>"
|
||||||
|
string model "Model of the car<br><strong>e.g., Corolla</strong>"
|
||||||
|
string[] parts "List of parts<br><strong>Stored as array</strong>"
|
||||||
|
}
|
||||||
|
PERSON ||--o{ NAMED-DRIVER : is
|
||||||
|
PERSON ::: someclass {
|
||||||
|
string driversLicense PK "The license #<br><strong>Primary Key</strong>"
|
||||||
|
string(99) firstName "Only 99 characters <br>are allowed <br> <strong>e.g., Smith</strong>"
|
||||||
|
string lastName "Last name of person<br><strong>e.g., Smith</strong>"
|
||||||
|
string phone UK "Unique phone number<br><strong>Used for contact</strong>"
|
||||||
|
int age "Age of the person<br><strong>Must be numeric</strong>"
|
||||||
|
}
|
||||||
|
NAMED-DRIVER {
|
||||||
|
string carRegistrationNumber PK, FK, UK, PK "Foreign key to CAR<br><strong>Also part of PK</strong>"
|
||||||
|
string driverLicence PK, FK "Foreign key to PERSON<br><strong>Also part of PK</strong>"
|
||||||
|
}
|
||||||
|
MANUFACTURER only one to zero or more CAR : makesx
|
||||||
|
</pre>
|
||||||
|
<hr />
|
||||||
|
<pre class="mermaid">
|
||||||
|
erDiagram
|
||||||
|
_**testẽζ➕Ø😀㌕ぼ**_ {
|
||||||
|
*__List~List~int~~sdfds__* **driversLicense** PK "***The l😀icense #***"
|
||||||
|
string last*Name*
|
||||||
|
string __phone__ UK
|
||||||
|
*string(99)~T~~~~~~* firstName "Only __99__ <br>characters are a<br>llowed dsfsdfsdfsdfs"
|
||||||
|
int _age_
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import mermaid from './mermaid.esm.mjs';
|
||||||
|
import layouts from './mermaid-layout-elk.esm.mjs';
|
||||||
|
|
||||||
|
const staticBellIconPack = {
|
||||||
|
prefix: 'fa6-regular',
|
||||||
|
icons: {
|
||||||
|
bell: {
|
||||||
|
body: '<path fill="currentColor" d="M224 0c-17.7 0-32 14.3-32 32v19.2C119 66 64 130.6 64 208v25.4c0 45.4-15.5 89.5-43.8 124.9L5.3 377c-5.8 7.2-6.9 17.1-2.9 25.4S14.8 416 24 416h400c9.2 0 17.6-5.3 21.6-13.6s2.9-18.2-2.9-25.4l-14.9-18.6c-28.3-35.5-43.8-79.6-43.8-125V208c0-77.4-55-142-128-156.8V32c0-17.7-14.3-32-32-32m0 96c61.9 0 112 50.1 112 112v25.4c0 47.9 13.9 94.6 39.7 134.6H72.3c25.8-40 39.7-86.7 39.7-134.6V208c0-61.9 50.1-112 112-112m64 352H160c0 17 6.7 33.3 18.7 45.3S207 512 224 512s33.3-6.7 45.3-18.7S288 465 288 448"/>',
|
||||||
|
width: 448,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
width: 512,
|
||||||
|
height: 512,
|
||||||
|
};
|
||||||
|
|
||||||
|
mermaid.registerIconPacks([
|
||||||
|
{
|
||||||
|
name: 'logos',
|
||||||
|
loader: () =>
|
||||||
|
fetch('https://unpkg.com/@iconify-json/logos@1/icons.json').then((res) => res.json()),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fa',
|
||||||
|
loader: () => staticBellIconPack,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
mermaid.registerLayoutLoaders(layouts);
|
||||||
|
mermaid.parseError = function (err, hash) {
|
||||||
|
console.error('Mermaid error: ', err);
|
||||||
|
};
|
||||||
|
window.callback = function () {
|
||||||
|
alert('A callback was triggered');
|
||||||
|
};
|
||||||
|
function callback() {
|
||||||
|
alert('It worked');
|
||||||
|
}
|
||||||
|
await mermaid.initialize({
|
||||||
|
startOnLoad: false,
|
||||||
|
|
||||||
|
theme: 'forest',
|
||||||
|
look: 'classic',
|
||||||
|
layout: 'dagre',
|
||||||
|
|
||||||
|
// theme: 'default',
|
||||||
|
// look: 'classic',
|
||||||
|
flowchart: { titleTopMargin: 10 },
|
||||||
|
fontFamily: 'Recursive',
|
||||||
|
sequence: {
|
||||||
|
actorFontFamily: 'courier',
|
||||||
|
noteFontFamily: 'courier',
|
||||||
|
messageFontFamily: 'courier',
|
||||||
|
},
|
||||||
|
kanban: {
|
||||||
|
htmlLabels: false,
|
||||||
|
},
|
||||||
|
fontSize: 16,
|
||||||
|
logLevel: 0,
|
||||||
|
securityLevel: 'loose',
|
||||||
|
callback,
|
||||||
|
});
|
||||||
|
// setTimeout(() => {
|
||||||
|
mermaid.init(undefined, document.querySelectorAll('.mermaid'));
|
||||||
|
// }, 1000);
|
||||||
|
mermaid.parseError = function (err, hash) {
|
||||||
|
console.error('In parse error:');
|
||||||
|
console.error(err);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@@ -89,6 +89,7 @@ export async function erBox<T extends SVGGraphicsElement>(parent: D3Selection<T>
|
|||||||
nameBBox.height += TEXT_PADDING;
|
nameBBox.height += TEXT_PADDING;
|
||||||
let yOffset = 0;
|
let yOffset = 0;
|
||||||
const yOffsets = [];
|
const yOffsets = [];
|
||||||
|
const rows = [];
|
||||||
let maxTypeWidth = 0;
|
let maxTypeWidth = 0;
|
||||||
let maxNameWidth = 0;
|
let maxNameWidth = 0;
|
||||||
let maxKeysWidth = 0;
|
let maxKeysWidth = 0;
|
||||||
@@ -137,12 +138,12 @@ export async function erBox<T extends SVGGraphicsElement>(parent: D3Selection<T>
|
|||||||
);
|
);
|
||||||
maxCommentWidth = Math.max(maxCommentWidth, commentBBox.width + PADDING);
|
maxCommentWidth = Math.max(maxCommentWidth, commentBBox.width + PADDING);
|
||||||
|
|
||||||
yOffset +=
|
const rowHeight =
|
||||||
Math.max(typeBBox.height, nameBBox.height, keysBBox.height, commentBBox.height) +
|
Math.max(typeBBox.height, nameBBox.height, keysBBox.height, commentBBox.height) +
|
||||||
TEXT_PADDING;
|
TEXT_PADDING;
|
||||||
yOffsets.push(yOffset);
|
rows.push({ yOffset, rowHeight });
|
||||||
|
yOffset += rowHeight;
|
||||||
}
|
}
|
||||||
yOffsets.pop();
|
|
||||||
let totalWidthSections = 4;
|
let totalWidthSections = 4;
|
||||||
|
|
||||||
if (maxKeysWidth <= PADDING) {
|
if (maxKeysWidth <= PADDING) {
|
||||||
@@ -185,8 +186,12 @@ export async function erBox<T extends SVGGraphicsElement>(parent: D3Selection<T>
|
|||||||
options.fillStyle = 'solid';
|
options.fillStyle = 'solid';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let totalShapeBBoxHeight = 0;
|
||||||
|
if (rows.length > 0) {
|
||||||
|
totalShapeBBoxHeight = rows.reduce((sum, row) => sum + (row?.rowHeight ?? 0), 0);
|
||||||
|
}
|
||||||
const w = Math.max(shapeBBox.width + PADDING * 2, node?.width || 0, maxWidth);
|
const w = Math.max(shapeBBox.width + PADDING * 2, node?.width || 0, maxWidth);
|
||||||
const h = Math.max(shapeBBox.height + (yOffsets[0] || yOffset) + TEXT_PADDING, node?.height || 0);
|
const h = Math.max((totalShapeBBoxHeight ?? 0) + nameBBox.height, node?.height || 0);
|
||||||
const x = -w / 2;
|
const x = -w / 2;
|
||||||
const y = -h / 2;
|
const y = -h / 2;
|
||||||
|
|
||||||
@@ -232,13 +237,10 @@ export async function erBox<T extends SVGGraphicsElement>(parent: D3Selection<T>
|
|||||||
|
|
||||||
yOffsets.push(0);
|
yOffsets.push(0);
|
||||||
// Draw row rects
|
// Draw row rects
|
||||||
for (const [i, yOffset] of yOffsets.entries()) {
|
for (const [i, row] of rows.entries()) {
|
||||||
if (i === 0 && yOffsets.length > 1) {
|
const contentRowIndex = i + 1; // Adjusted index to skip the header (name) row
|
||||||
continue;
|
const isEven = contentRowIndex % 2 === 0 && row.yOffset !== 0;
|
||||||
// Skip first row
|
const roughRect = rc.rectangle(x, nameBBox.height + y + row?.yOffset, w, row?.rowHeight, {
|
||||||
}
|
|
||||||
const isEven = i % 2 === 0 && yOffset !== 0;
|
|
||||||
const roughRect = rc.rectangle(x, nameBBox.height + y + yOffset, w, nameBBox.height, {
|
|
||||||
...options,
|
...options,
|
||||||
fill: isEven ? rowEven : rowOdd,
|
fill: isEven ? rowEven : rowOdd,
|
||||||
stroke: nodeBorder,
|
stroke: nodeBorder,
|
||||||
@@ -246,7 +248,7 @@ export async function erBox<T extends SVGGraphicsElement>(parent: D3Selection<T>
|
|||||||
shapeSvg
|
shapeSvg
|
||||||
.insert(() => roughRect, 'g.label')
|
.insert(() => roughRect, 'g.label')
|
||||||
.attr('style', cssStyles!.join(''))
|
.attr('style', cssStyles!.join(''))
|
||||||
.attr('class', `row-rect-${i % 2 === 0 ? 'even' : 'odd'}`);
|
.attr('class', `row-rect-${isEven ? 'even' : 'odd'}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw divider lines
|
// Draw divider lines
|
||||||
|
Reference in New Issue
Block a user