mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-24 01:39:53 +02:00
Initial work on updating to new renderer
This commit is contained in:
32
cypress/platform/yari.html
Normal file
32
cypress/platform/yari.html
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<pre id="diagram" class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
class Square~Shape~{
|
||||||
|
int id
|
||||||
|
List~int~ position
|
||||||
|
setPoints(List~int~ points)
|
||||||
|
getPoints() List~int~
|
||||||
|
}
|
||||||
|
|
||||||
|
Square : -List~string~ messages
|
||||||
|
Square : +setMessages(List~string~ messages)
|
||||||
|
Square : +getMessages() List~string~
|
||||||
|
Square : +getDistanceMatrix() List~List~int~~
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<div id="d2" class="mermaid">classDiagram class Duck{ +String beakColor +swim() +quack() }</div>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import mermaid from '/mermaid.esm.mjs';
|
||||||
|
mermaid.parseError = function (err, hash) {
|
||||||
|
console.error('Mermaid error: ', err);
|
||||||
|
};
|
||||||
|
mermaid.initialize();
|
||||||
|
mermaid.parseError = function (err, hash) {
|
||||||
|
console.error('In parse error:');
|
||||||
|
console.error(err);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@@ -80,6 +80,8 @@ export const addClass = function (_id: string) {
|
|||||||
id: name,
|
id: name,
|
||||||
type: type,
|
type: type,
|
||||||
label: name,
|
label: name,
|
||||||
|
text: `<strong>${name}${type ? `<${type}>` : ''}</strong>`,
|
||||||
|
shape: 'classBox',
|
||||||
cssClasses: [],
|
cssClasses: [],
|
||||||
methods: [],
|
methods: [],
|
||||||
members: [],
|
members: [],
|
||||||
@@ -472,6 +474,11 @@ export const setCssStyle = function (id: string, styles: string[]) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getData = () => {
|
||||||
|
const config = getConfig();
|
||||||
|
return { nodes: classes, edges: relations, other: {}, config, direction: getDirection() };
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
setAccTitle,
|
setAccTitle,
|
||||||
getAccTitle,
|
getAccTitle,
|
||||||
@@ -509,4 +516,5 @@ export default {
|
|||||||
getNamespace,
|
getNamespace,
|
||||||
getNamespaces,
|
getNamespaces,
|
||||||
setCssStyle,
|
setCssStyle,
|
||||||
|
getData,
|
||||||
};
|
};
|
||||||
|
@@ -3,7 +3,7 @@ import type { DiagramDefinition } from '../../diagram-api/types.js';
|
|||||||
import parser from './parser/classDiagram.jison';
|
import parser from './parser/classDiagram.jison';
|
||||||
import db from './classDb.js';
|
import db from './classDb.js';
|
||||||
import styles from './styles.js';
|
import styles from './styles.js';
|
||||||
import renderer from './classRenderer-v2.js';
|
import renderer from './classRenderer-v3-unified.js';
|
||||||
|
|
||||||
export const diagram: DiagramDefinition = {
|
export const diagram: DiagramDefinition = {
|
||||||
parser,
|
parser,
|
||||||
|
@@ -0,0 +1,87 @@
|
|||||||
|
import { getConfig } from '../../diagram-api/diagramAPI.js';
|
||||||
|
import type { DiagramStyleClassDef } from '../../diagram-api/types.js';
|
||||||
|
import { log } from '../../logger.js';
|
||||||
|
import { getDiagramElements } from '../../rendering-util/insertElementsForSize.js';
|
||||||
|
import { render } from '../../rendering-util/render.js';
|
||||||
|
import { setupViewPortForSVG } from '../../rendering-util/setupViewPortForSVG.js';
|
||||||
|
import type { LayoutData } from '../../rendering-util/types.js';
|
||||||
|
import utils from '../../utils.js';
|
||||||
|
// import { CSS_DIAGRAM, DEFAULT_NESTED_DOC_DIR } from './stateCommon.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the direction from the statement items.
|
||||||
|
* Look through all of the documents (docs) in the parsedItems
|
||||||
|
* Because is a _document_ direction, the default direction is not necessarily the same as the overall default _diagram_ direction.
|
||||||
|
* @param parsedItem - the parsed statement item to look through
|
||||||
|
* @param defaultDir - the direction to use if none is found
|
||||||
|
* @returns The direction to use
|
||||||
|
*/
|
||||||
|
export const getDir = (parsedItem: any, defaultDir = DEFAULT_NESTED_DOC_DIR) => {
|
||||||
|
if (!parsedItem.doc) {
|
||||||
|
return defaultDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dir = defaultDir;
|
||||||
|
|
||||||
|
for (const parsedItemDoc of parsedItem.doc) {
|
||||||
|
if (parsedItemDoc.stmt === 'dir') {
|
||||||
|
dir = parsedItemDoc.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dir;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getClasses = function (
|
||||||
|
text: string,
|
||||||
|
diagramObj: any
|
||||||
|
): Map<string, DiagramStyleClassDef> {
|
||||||
|
// diagramObj.db.extract(diagramObj.db.getRootDocV2());
|
||||||
|
return diagramObj.db.getClasses();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const draw = async function (text: string, id: string, _version: string, diag: any) {
|
||||||
|
log.info('REF0:');
|
||||||
|
log.info('Drawing class diagram (v3)', id);
|
||||||
|
const { securityLevel, state: conf, layout } = getConfig();
|
||||||
|
// Extracting the data from the parsed structure into a more usable form
|
||||||
|
// Not related to the refactoring, but this is the first step in the rendering process
|
||||||
|
// diag.db.extract(diag.db.getRootDocV2());
|
||||||
|
|
||||||
|
//const DIR = getDir(diag.db.getRootDocV2());
|
||||||
|
|
||||||
|
// The getData method provided in all supported diagrams is used to extract the data from the parsed structure
|
||||||
|
// into the Layout data format
|
||||||
|
const data4Layout = diag.db.getData() as LayoutData;
|
||||||
|
|
||||||
|
// Create the root SVG - the element is the div containing the SVG element
|
||||||
|
const { element, svg } = getDiagramElements(id, securityLevel);
|
||||||
|
|
||||||
|
data4Layout.type = diag.type;
|
||||||
|
data4Layout.layoutAlgorithm = layout;
|
||||||
|
|
||||||
|
// TODO: Should we move these two to baseConfig? These types are not there in StateConfig.
|
||||||
|
|
||||||
|
data4Layout.nodeSpacing = conf?.nodeSpacing || 50;
|
||||||
|
data4Layout.rankSpacing = conf?.rankSpacing || 50;
|
||||||
|
data4Layout.markers = ['barb'];
|
||||||
|
data4Layout.diagramId = id;
|
||||||
|
await render(data4Layout, svg, element);
|
||||||
|
const padding = 8;
|
||||||
|
utils.insertTitle(
|
||||||
|
element,
|
||||||
|
'statediagramTitleText',
|
||||||
|
conf?.titleTopMargin ?? 25,
|
||||||
|
diag.db.getDiagramTitle()
|
||||||
|
);
|
||||||
|
|
||||||
|
const CSS_DIAGRAM = 'classDiagram';
|
||||||
|
|
||||||
|
setupViewPortForSVG(svg, padding, CSS_DIAGRAM, conf?.useMaxWidth ?? true);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getClasses,
|
||||||
|
draw,
|
||||||
|
getDir,
|
||||||
|
};
|
@@ -5,6 +5,7 @@ export interface ClassNode {
|
|||||||
id: string;
|
id: string;
|
||||||
type: string;
|
type: string;
|
||||||
label: string;
|
label: string;
|
||||||
|
text: string;
|
||||||
cssClasses: string[];
|
cssClasses: string[];
|
||||||
methods: ClassMember[];
|
methods: ClassMember[];
|
||||||
members: ClassMember[];
|
members: ClassMember[];
|
||||||
@@ -30,6 +31,7 @@ export class ClassMember {
|
|||||||
cssStyle!: string;
|
cssStyle!: string;
|
||||||
memberType!: 'method' | 'attribute';
|
memberType!: 'method' | 'attribute';
|
||||||
visibility!: Visibility;
|
visibility!: Visibility;
|
||||||
|
text: string;
|
||||||
/**
|
/**
|
||||||
* denote if static or to determine which css class to apply to the node
|
* denote if static or to determine which css class to apply to the node
|
||||||
* @defaultValue ''
|
* @defaultValue ''
|
||||||
@@ -50,6 +52,7 @@ export class ClassMember {
|
|||||||
this.memberType = memberType;
|
this.memberType = memberType;
|
||||||
this.visibility = '';
|
this.visibility = '';
|
||||||
this.classifier = '';
|
this.classifier = '';
|
||||||
|
this.text = '';
|
||||||
const sanitizedInput = sanitizeText(input, getConfig());
|
const sanitizedInput = sanitizeText(input, getConfig());
|
||||||
this.parseMember(sanitizedInput);
|
this.parseMember(sanitizedInput);
|
||||||
}
|
}
|
||||||
@@ -118,6 +121,21 @@ export class ClassMember {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.classifier = potentialClassifier;
|
this.classifier = potentialClassifier;
|
||||||
|
this.text = `${this.visibility}${this.id}${this.memberType === 'method' ? `(${this.parameters})${this.returnType ? ' : ' + this.returnType : ''}` : ''}`; //.replaceAll('~', '<');
|
||||||
|
const combinedText = `${this.visibility}${this.id}${this.memberType === 'method' ? `(${this.parameters})${this.returnType ? ' : ' + this.returnType : ''}` : ''}`;
|
||||||
|
if (combinedText.includes('~')) {
|
||||||
|
let count = (combinedText.match(/~/g) ?? []).length;
|
||||||
|
|
||||||
|
// Replace all '~' with '>'
|
||||||
|
let replacedRaw = combinedText.replaceAll('~', '>');
|
||||||
|
|
||||||
|
// Replace the first half of '>' with '<'
|
||||||
|
while (count > 0) {
|
||||||
|
replacedRaw = replacedRaw.replace('>', '<');
|
||||||
|
count -= 2; // Each iteration replaces one '>' with '<', so reduce count by 2
|
||||||
|
}
|
||||||
|
this.text = replacedRaw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parseClassifier() {
|
parseClassifier() {
|
||||||
|
@@ -22,6 +22,7 @@ import { lean_left } from './shapes/leanLeft.js';
|
|||||||
import { trapezoid } from './shapes/trapezoid.js';
|
import { trapezoid } from './shapes/trapezoid.js';
|
||||||
import { inv_trapezoid } from './shapes/invertedTrapezoid.js';
|
import { inv_trapezoid } from './shapes/invertedTrapezoid.js';
|
||||||
import { labelRect } from './shapes/labelRect.js';
|
import { labelRect } from './shapes/labelRect.js';
|
||||||
|
import { classBox } from './shapes/classBox.js';
|
||||||
|
|
||||||
const shapes = {
|
const shapes = {
|
||||||
state,
|
state,
|
||||||
@@ -47,6 +48,7 @@ const shapes = {
|
|||||||
trapezoid,
|
trapezoid,
|
||||||
inv_trapezoid,
|
inv_trapezoid,
|
||||||
labelRect,
|
labelRect,
|
||||||
|
classBox,
|
||||||
};
|
};
|
||||||
|
|
||||||
const nodeElems = new Map();
|
const nodeElems = new Map();
|
||||||
|
@@ -0,0 +1,139 @@
|
|||||||
|
import { getNodeClasses } from './util.js';
|
||||||
|
import { getConfig } from '$root/diagram-api/diagramAPI.js';
|
||||||
|
import { createText } from '../../createText.js';
|
||||||
|
import { select } from 'd3';
|
||||||
|
import type { Node } from '$root/rendering-util/types.d.ts';
|
||||||
|
import { evaluate } from '$root/diagrams/common/common.js';
|
||||||
|
import { calculateTextDimensions, calculateTextWidth } from '$root/utils.js';
|
||||||
|
import { styles2String } from '$root/rendering-util/rendering-elements/shapes/handDrawnShapeStyles.js';
|
||||||
|
|
||||||
|
export const classBox = async (parent: SVGAElement, node: Node): Promise<SVGAElement> => {
|
||||||
|
const { labelStyles, nodeStyles } = styles2String(node);
|
||||||
|
node.labelStyle = labelStyles;
|
||||||
|
|
||||||
|
const mainGroup = parent
|
||||||
|
.insert('g')
|
||||||
|
.attr('class', getNodeClasses(node))
|
||||||
|
.attr('style', nodeStyles)
|
||||||
|
.attr('id', node.domId || node.id);
|
||||||
|
let labelGroup = null;
|
||||||
|
let membersGroup = null;
|
||||||
|
let methodsGroup = null;
|
||||||
|
|
||||||
|
let labelGroupHeight = 0;
|
||||||
|
let membersGroupHeight = 0;
|
||||||
|
|
||||||
|
const config = getConfig();
|
||||||
|
|
||||||
|
const PADDING = config.class.padding;
|
||||||
|
const GAP = PADDING;
|
||||||
|
|
||||||
|
if (node.label) {
|
||||||
|
labelGroup = mainGroup.insert('g').attr('class', 'label-group');
|
||||||
|
// TODO: Add Padding
|
||||||
|
await helper(labelGroup, node, 0);
|
||||||
|
const labelGroupBBox = labelGroup.node().getBBox();
|
||||||
|
labelGroupHeight = labelGroupBBox.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.members) {
|
||||||
|
membersGroup = mainGroup.insert('g').attr('class', 'members-group');
|
||||||
|
let yOffset = 0;
|
||||||
|
for (const member of node.members) {
|
||||||
|
await helper(membersGroup, member, yOffset);
|
||||||
|
yOffset += calculateTextDimensions(member.text, config).height;
|
||||||
|
}
|
||||||
|
membersGroupHeight = membersGroup.node().getBBox().height;
|
||||||
|
membersGroup.attr('transform', `translate(0, ${labelGroupHeight + GAP * 3})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.methods) {
|
||||||
|
methodsGroup = mainGroup.insert('g').attr('class', 'methods-group');
|
||||||
|
let methodsYOffset = 0;
|
||||||
|
for (const method of node.methods) {
|
||||||
|
await helper(methodsGroup, method, methodsYOffset);
|
||||||
|
methodsYOffset += calculateTextDimensions(method.text, config).height;
|
||||||
|
}
|
||||||
|
// TODO: Update transform
|
||||||
|
methodsGroup.attr(
|
||||||
|
'transform',
|
||||||
|
`translate(0, ${labelGroupHeight + (membersGroupHeight ? membersGroupHeight + GAP * 5 : GAP * 3)})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mainGroupBBox = mainGroup.node().getBBox();
|
||||||
|
const labelGroupBBox = labelGroup.node().getBBox();
|
||||||
|
// Center label
|
||||||
|
labelGroup.attr(
|
||||||
|
'transform',
|
||||||
|
`translate(${mainGroupBBox.width / 2 - labelGroupBBox.width / 2}, 0)`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Insert the rectangle around the main group
|
||||||
|
mainGroup
|
||||||
|
.insert('rect', ':first-child')
|
||||||
|
.attr('class', 'basic label-container')
|
||||||
|
.attr('style', nodeStyles)
|
||||||
|
.attr('data-id', 'abc')
|
||||||
|
.attr('data-et', 'node')
|
||||||
|
.attr('x', mainGroupBBox.x - PADDING)
|
||||||
|
.attr('y', mainGroupBBox.y - PADDING)
|
||||||
|
.attr('width', mainGroupBBox.width + 2 * PADDING)
|
||||||
|
.attr('height', mainGroupBBox.height + 2 * PADDING);
|
||||||
|
|
||||||
|
// Render separating lines.
|
||||||
|
if (node.label) {
|
||||||
|
mainGroup
|
||||||
|
.insert('line')
|
||||||
|
.attr('x1', 0 - PADDING)
|
||||||
|
.attr('y1', labelGroupHeight - GAP)
|
||||||
|
.attr('x2', mainGroupBBox.width + PADDING)
|
||||||
|
.attr('y2', labelGroupHeight - GAP)
|
||||||
|
.attr('class', 'divider');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.members.length > 0 && node.methods.length > 0) {
|
||||||
|
mainGroup
|
||||||
|
.insert('line')
|
||||||
|
.attr('x1', 0 - PADDING)
|
||||||
|
.attr('y1', labelGroupHeight + membersGroupHeight + GAP * 2)
|
||||||
|
.attr('x2', mainGroupBBox.width + PADDING)
|
||||||
|
.attr('y2', labelGroupHeight + membersGroupHeight + GAP * 2)
|
||||||
|
.attr('class', 'divider');
|
||||||
|
}
|
||||||
|
|
||||||
|
return mainGroup;
|
||||||
|
};
|
||||||
|
|
||||||
|
const helper = async (parentGroup, node, yOffset) => {
|
||||||
|
const textEl = parentGroup.insert('g').attr('class', 'label').attr('style', node.labelStyle);
|
||||||
|
const textContent = node.text;
|
||||||
|
const config = getConfig();
|
||||||
|
const text = await createText(
|
||||||
|
textEl,
|
||||||
|
textContent,
|
||||||
|
{
|
||||||
|
width: calculateTextWidth(textContent, config),
|
||||||
|
classes: 'markdown-node-label',
|
||||||
|
style: node.labelStyle,
|
||||||
|
useHtmlLabels: true,
|
||||||
|
},
|
||||||
|
config
|
||||||
|
);
|
||||||
|
|
||||||
|
let bbox = text.getBBox();
|
||||||
|
|
||||||
|
if (evaluate(config.flowchart.htmlLabels)) {
|
||||||
|
const div = text.children[0];
|
||||||
|
const dv = select(text);
|
||||||
|
|
||||||
|
bbox = div.getBoundingClientRect();
|
||||||
|
dv.attr('width', bbox.width);
|
||||||
|
dv.attr('height', bbox.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
textEl.attr('transform', 'translate(' + 0 + ', ' + (-bbox.height / 2 + yOffset) + ')');
|
||||||
|
if (node.centerLabel) {
|
||||||
|
textEl.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')');
|
||||||
|
}
|
||||||
|
};
|
Reference in New Issue
Block a user