mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-25 02:09:50 +02:00
Merge branch 'develop' of https://github.com/mermaid-js/mermaid into develop
This commit is contained in:
14
README.md
14
README.md
@@ -165,6 +165,13 @@ class Class10 {
|
||||
int id
|
||||
size()
|
||||
}
|
||||
namespace Namespace01 {
|
||||
class Class11
|
||||
class Class12 {
|
||||
int id
|
||||
size()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
@@ -184,6 +191,13 @@ class Class10 {
|
||||
int id
|
||||
size()
|
||||
}
|
||||
namespace Namespace01 {
|
||||
class Class11
|
||||
class Class12 {
|
||||
int id
|
||||
size()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### State diagram [<a href="https://mermaid-js.github.io/mermaid/#/stateDiagram">docs</a> - <a href="https://mermaid.live/edit#pako:eNpdkEFvgzAMhf8K8nEqpYSNthx22Xbcqcexg0sCiZQQlDhIFeK_L8A6TfXp6fOz9ewJGssFVOAJSbwr7ByadGR1n8T6evpO0vQ1uZDSekOrXGFsPqJPO6q-2-imH8f_0TeHXm50lfelsAMjnEHFY6xpMdRAUhhRQxUlFy0GTTXU_RytYeAx-AdXZB1ULWovdoCB7OXWN1CRC-Ju-r3uz6UtchGHJqDbsPygU57iysb2reoWHpyOWBINvsqypb3vFMlw3TfWZF5xiY7keC6zkpUnZIUojwW-FAVvrvn51LLnvOXHQ84Q5nn-AVtLcwk">live editor</a>]
|
||||
|
@@ -90,8 +90,12 @@
|
||||
"sidharth",
|
||||
"sidharthv",
|
||||
"sphinxcontrib",
|
||||
"startx",
|
||||
"starty",
|
||||
"statediagram",
|
||||
"steph",
|
||||
"stopx",
|
||||
"stopy",
|
||||
"stylis",
|
||||
"substate",
|
||||
"sveidqvist",
|
||||
@@ -104,6 +108,7 @@
|
||||
"tuleap",
|
||||
"ugge",
|
||||
"unist",
|
||||
"valign",
|
||||
"verdana",
|
||||
"viewports",
|
||||
"vinod",
|
||||
|
@@ -548,4 +548,18 @@ class C13["With Città foreign language"]
|
||||
`
|
||||
);
|
||||
});
|
||||
it('should add classes namespaces', function () {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
classDiagram
|
||||
namespace Namespace1 {
|
||||
class C1
|
||||
class C2
|
||||
}
|
||||
C1 --> C2
|
||||
class C3
|
||||
class C4
|
||||
`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@@ -421,6 +421,34 @@ And `Link` can be one of:
|
||||
| -- | Solid |
|
||||
| .. | Dashed |
|
||||
|
||||
## Define Namespace
|
||||
|
||||
A namespace groups classes.
|
||||
|
||||
Code:
|
||||
|
||||
```mermaid-example
|
||||
classDiagram
|
||||
namespace BaseShapes {
|
||||
class Triangle
|
||||
class Rectangle {
|
||||
double width
|
||||
double height
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
namespace BaseShapes {
|
||||
class Triangle
|
||||
class Rectangle {
|
||||
double width
|
||||
double height
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Cardinality / Multiplicity on relations
|
||||
|
||||
Multiplicity or cardinality in class diagrams indicates the number of instances of one class that can be linked to an instance of the other class. For example, each company will have one or more employees (not zero), and each employee currently works for zero or one companies.
|
||||
|
@@ -1,28 +1,9 @@
|
||||
import common from '../common/common.js';
|
||||
import * as svgDrawCommon from '../common/svgDrawCommon';
|
||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||
|
||||
export const drawRect = function (elem, rectData) {
|
||||
const rectElem = elem.append('rect');
|
||||
rectElem.attr('x', rectData.x);
|
||||
rectElem.attr('y', rectData.y);
|
||||
rectElem.attr('fill', rectData.fill);
|
||||
rectElem.attr('stroke', rectData.stroke);
|
||||
rectElem.attr('width', rectData.width);
|
||||
rectElem.attr('height', rectData.height);
|
||||
rectElem.attr('rx', rectData.rx);
|
||||
rectElem.attr('ry', rectData.ry);
|
||||
|
||||
if (rectData.attrs !== 'undefined' && rectData.attrs !== null) {
|
||||
for (let attrKey in rectData.attrs) {
|
||||
rectElem.attr(attrKey, rectData.attrs[attrKey]);
|
||||
}
|
||||
}
|
||||
|
||||
if (rectData.class !== 'undefined') {
|
||||
rectElem.attr('class', rectData.class);
|
||||
}
|
||||
|
||||
return rectElem;
|
||||
return svgDrawCommon.drawRect(elem, rectData);
|
||||
};
|
||||
|
||||
export const drawImage = function (elem, width, height, x, y, link) {
|
||||
@@ -236,7 +217,8 @@ export const drawC4Shape = function (elem, c4Shape, conf) {
|
||||
|
||||
// <rect fill="#08427B" height="119.2188" rx="2.5" ry="2.5" stroke="#073B6F" stroke-width="0.5" width="110" x="120" y="7"/>
|
||||
// draw rect of c4Shape
|
||||
const rect = getNoteRect();
|
||||
const rect = svgDrawCommon.getNoteRect();
|
||||
|
||||
switch (c4Shape.typeC4Shape.text) {
|
||||
case 'person':
|
||||
case 'external_person':
|
||||
@@ -479,6 +461,7 @@ export const insertArrowHead = function (elem) {
|
||||
.append('path')
|
||||
.attr('d', 'M 0 0 L 10 5 L 0 10 z'); // this is actual shape for arrowhead
|
||||
};
|
||||
|
||||
export const insertArrowEnd = function (elem) {
|
||||
elem
|
||||
.append('defs')
|
||||
@@ -493,6 +476,7 @@ export const insertArrowEnd = function (elem) {
|
||||
.append('path')
|
||||
.attr('d', 'M 10 0 L 0 5 L 10 10 z'); // this is actual shape for arrowhead
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup arrow head and define the marker. The result is appended to the svg.
|
||||
*
|
||||
@@ -511,6 +495,7 @@ export const insertArrowFilledHead = function (elem) {
|
||||
.append('path')
|
||||
.attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z');
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup node number. The result is appended to the svg.
|
||||
*
|
||||
@@ -532,6 +517,7 @@ export const insertDynamicNumber = function (elem) {
|
||||
.attr('r', 6);
|
||||
// .style("fill", '#f00');
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup arrow head and define the marker. The result is appended to the svg.
|
||||
*
|
||||
@@ -568,20 +554,6 @@ export const insertArrowCrossHead = function (elem) {
|
||||
// this is actual shape for arrowhead
|
||||
};
|
||||
|
||||
export const getNoteRect = function () {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
fill: '#EDF2AE',
|
||||
stroke: '#666',
|
||||
width: 100,
|
||||
anchor: 'start',
|
||||
height: 100,
|
||||
rx: 0,
|
||||
ry: 0,
|
||||
};
|
||||
};
|
||||
|
||||
const getC4ShapeFont = (cnf, typeC4Shape) => {
|
||||
return {
|
||||
fontFamily: cnf[typeC4Shape + 'FontFamily'],
|
||||
@@ -714,6 +686,4 @@ export default {
|
||||
insertDatabaseIcon,
|
||||
insertComputerIcon,
|
||||
insertClockIcon,
|
||||
getNoteRect,
|
||||
sanitizeUrl, // TODO why is this exported?
|
||||
};
|
||||
|
@@ -14,7 +14,14 @@ import {
|
||||
setDiagramTitle,
|
||||
getDiagramTitle,
|
||||
} from '../../commonDb.js';
|
||||
import { ClassRelation, ClassNode, ClassNote, ClassMap } from './classTypes.js';
|
||||
import {
|
||||
ClassRelation,
|
||||
ClassNode,
|
||||
ClassNote,
|
||||
ClassMap,
|
||||
NamespaceMap,
|
||||
NamespaceNode,
|
||||
} from './classTypes.js';
|
||||
|
||||
const MERMAID_DOM_ID_PREFIX = 'classId-';
|
||||
|
||||
@@ -22,6 +29,8 @@ let relations: ClassRelation[] = [];
|
||||
let classes: ClassMap = {};
|
||||
let notes: ClassNote[] = [];
|
||||
let classCounter = 0;
|
||||
let namespaces: NamespaceMap = {};
|
||||
let namespaceCounter = 0;
|
||||
|
||||
let functions: any[] = [];
|
||||
|
||||
@@ -100,6 +109,8 @@ export const clear = function () {
|
||||
notes = [];
|
||||
functions = [];
|
||||
functions.push(setupToolTips);
|
||||
namespaces = {};
|
||||
namespaceCounter = 0;
|
||||
commonClear();
|
||||
};
|
||||
|
||||
@@ -237,7 +248,11 @@ const setTooltip = function (ids: string, tooltip?: string) {
|
||||
});
|
||||
};
|
||||
|
||||
export const getTooltip = function (id: string) {
|
||||
export const getTooltip = function (id: string, namespace?: string) {
|
||||
if (namespace) {
|
||||
return namespaces[namespace].classes[id].tooltip;
|
||||
}
|
||||
|
||||
return classes[id].tooltip;
|
||||
};
|
||||
/**
|
||||
@@ -395,6 +410,52 @@ const setDirection = (dir: string) => {
|
||||
direction = dir;
|
||||
};
|
||||
|
||||
/**
|
||||
* Function called by parser when a namespace definition has been found.
|
||||
*
|
||||
* @param id - Id of the namespace to add
|
||||
* @public
|
||||
*/
|
||||
export const addNamespace = function (id: string) {
|
||||
if (namespaces[id] !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
namespaces[id] = {
|
||||
id: id,
|
||||
classes: {},
|
||||
children: {},
|
||||
domId: MERMAID_DOM_ID_PREFIX + id + '-' + namespaceCounter,
|
||||
} as NamespaceNode;
|
||||
|
||||
namespaceCounter++;
|
||||
};
|
||||
|
||||
const getNamespace = function (name: string): NamespaceNode {
|
||||
return namespaces[name];
|
||||
};
|
||||
|
||||
const getNamespaces = function (): NamespaceMap {
|
||||
return namespaces;
|
||||
};
|
||||
|
||||
/**
|
||||
* Function called by parser when a namespace definition has been found.
|
||||
*
|
||||
* @param id - Id of the namespace to add
|
||||
* @param classNames - Ids of the class to add
|
||||
* @public
|
||||
*/
|
||||
export const addClassesToNamespace = function (id: string, classNames: string[]) {
|
||||
if (namespaces[id] !== undefined) {
|
||||
classNames.map((className) => {
|
||||
namespaces[id].classes[className] = classes[className];
|
||||
delete classes[className];
|
||||
classCounter--;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
parseDirective,
|
||||
setAccTitle,
|
||||
@@ -428,4 +489,8 @@ export default {
|
||||
setDiagramTitle,
|
||||
getDiagramTitle,
|
||||
setClassLabel,
|
||||
addNamespace,
|
||||
addClassesToNamespace,
|
||||
getNamespace,
|
||||
getNamespaces,
|
||||
};
|
||||
|
@@ -816,6 +816,37 @@ describe('given a class diagram with generics, ', function () {
|
||||
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle "namespace"', function () {
|
||||
const str = `classDiagram
|
||||
namespace Namespace1 { class Class1 }
|
||||
namespace Namespace2 { class Class1
|
||||
}
|
||||
namespace Namespace3 {
|
||||
class Class1 {
|
||||
int : test
|
||||
string : foo
|
||||
test()
|
||||
foo()
|
||||
}
|
||||
}
|
||||
namespace Namespace4 {
|
||||
class Class1 {
|
||||
int : test
|
||||
string : foo
|
||||
test()
|
||||
foo()
|
||||
}
|
||||
class Class2 {
|
||||
int : test
|
||||
string : foo
|
||||
test()
|
||||
foo()
|
||||
}
|
||||
}
|
||||
`;
|
||||
parser.parse(str);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when parsing invalid generic classes', function () {
|
||||
@@ -1051,5 +1082,487 @@ describe('given a class diagram with relationships, ', function () {
|
||||
expect(relations[3].relation.type2).toBe('none');
|
||||
expect(relations[3].relation.lineType).toBe(classDb.lineType.DOTTED_LINE);
|
||||
});
|
||||
|
||||
it('should handle generic class with relation definitions', function () {
|
||||
const str = 'classDiagram\n' + 'Class01~T~ <|-- Class02';
|
||||
|
||||
parser.parse(str);
|
||||
|
||||
const relations = parser.yy.getRelations();
|
||||
|
||||
expect(parser.yy.getClass('Class01').id).toBe('Class01');
|
||||
expect(parser.yy.getClass('Class01').type).toBe('T');
|
||||
expect(parser.yy.getClass('Class02').id).toBe('Class02');
|
||||
expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION);
|
||||
expect(relations[0].relation.type2).toBe('none');
|
||||
expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE);
|
||||
});
|
||||
|
||||
it('should handle class annotations', function () {
|
||||
const str = 'classDiagram\n' + 'class Class1\n' + '<<interface>> Class1';
|
||||
parser.parse(str);
|
||||
|
||||
const testClass = parser.yy.getClass('Class1');
|
||||
expect(testClass.annotations.length).toBe(1);
|
||||
expect(testClass.members.length).toBe(0);
|
||||
expect(testClass.methods.length).toBe(0);
|
||||
expect(testClass.annotations[0]).toBe('interface');
|
||||
});
|
||||
|
||||
it('should handle class annotations with members and methods', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1\n' +
|
||||
'Class1 : int test\n' +
|
||||
'Class1 : test()\n' +
|
||||
'<<interface>> Class1';
|
||||
parser.parse(str);
|
||||
|
||||
const testClass = parser.yy.getClass('Class1');
|
||||
expect(testClass.annotations.length).toBe(1);
|
||||
expect(testClass.members.length).toBe(1);
|
||||
expect(testClass.methods.length).toBe(1);
|
||||
expect(testClass.annotations[0]).toBe('interface');
|
||||
});
|
||||
|
||||
it('should handle class annotations in brackets', function () {
|
||||
const str = 'classDiagram\n' + 'class Class1 {\n' + '<<interface>>\n' + '}';
|
||||
parser.parse(str);
|
||||
|
||||
const testClass = parser.yy.getClass('Class1');
|
||||
expect(testClass.annotations.length).toBe(1);
|
||||
expect(testClass.members.length).toBe(0);
|
||||
expect(testClass.methods.length).toBe(0);
|
||||
expect(testClass.annotations[0]).toBe('interface');
|
||||
});
|
||||
|
||||
it('should handle class annotations in brackets with members and methods', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1 {\n' +
|
||||
'<<interface>>\n' +
|
||||
'int : test\n' +
|
||||
'test()\n' +
|
||||
'}';
|
||||
parser.parse(str);
|
||||
|
||||
const testClass = parser.yy.getClass('Class1');
|
||||
expect(testClass.annotations.length).toBe(1);
|
||||
expect(testClass.members.length).toBe(1);
|
||||
expect(testClass.methods.length).toBe(1);
|
||||
expect(testClass.annotations[0]).toBe('interface');
|
||||
});
|
||||
|
||||
it('should add bracket members in right order', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1 {\n' +
|
||||
'int : test\n' +
|
||||
'string : foo\n' +
|
||||
'test()\n' +
|
||||
'foo()\n' +
|
||||
'}';
|
||||
parser.parse(str);
|
||||
|
||||
const testClass = parser.yy.getClass('Class1');
|
||||
expect(testClass.members.length).toBe(2);
|
||||
expect(testClass.methods.length).toBe(2);
|
||||
expect(testClass.members[0]).toBe('int : test');
|
||||
expect(testClass.members[1]).toBe('string : foo');
|
||||
expect(testClass.methods[0]).toBe('test()');
|
||||
expect(testClass.methods[1]).toBe('foo()');
|
||||
});
|
||||
|
||||
it('should handle abstract methods', function () {
|
||||
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()*';
|
||||
parser.parse(str);
|
||||
|
||||
const testClass = parser.yy.getClass('Class1');
|
||||
expect(testClass.annotations.length).toBe(0);
|
||||
expect(testClass.members.length).toBe(0);
|
||||
expect(testClass.methods.length).toBe(1);
|
||||
expect(testClass.methods[0]).toBe('someMethod()*');
|
||||
});
|
||||
|
||||
it('should handle static methods', function () {
|
||||
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()$';
|
||||
parser.parse(str);
|
||||
|
||||
const testClass = parser.yy.getClass('Class1');
|
||||
expect(testClass.annotations.length).toBe(0);
|
||||
expect(testClass.members.length).toBe(0);
|
||||
expect(testClass.methods.length).toBe(1);
|
||||
expect(testClass.methods[0]).toBe('someMethod()$');
|
||||
});
|
||||
|
||||
it('should associate link and css appropriately', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1\n' +
|
||||
'Class1 : someMethod()\n' +
|
||||
'link Class1 "google.com"';
|
||||
parser.parse(str);
|
||||
|
||||
const testClass = parser.yy.getClass('Class1');
|
||||
expect(testClass.link).toBe('google.com');
|
||||
expect(testClass.cssClasses.length).toBe(1);
|
||||
expect(testClass.cssClasses[0]).toBe('clickable');
|
||||
});
|
||||
|
||||
it('should associate click and href link and css appropriately', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1\n' +
|
||||
'Class1 : someMethod()\n' +
|
||||
'click Class1 href "google.com"';
|
||||
parser.parse(str);
|
||||
|
||||
const testClass = parser.yy.getClass('Class1');
|
||||
expect(testClass.link).toBe('google.com');
|
||||
expect(testClass.cssClasses.length).toBe(1);
|
||||
expect(testClass.cssClasses[0]).toBe('clickable');
|
||||
});
|
||||
|
||||
it('should associate link with tooltip', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1\n' +
|
||||
'Class1 : someMethod()\n' +
|
||||
'link Class1 "google.com" "A tooltip"';
|
||||
parser.parse(str);
|
||||
|
||||
const testClass = parser.yy.getClass('Class1');
|
||||
expect(testClass.link).toBe('google.com');
|
||||
expect(testClass.tooltip).toBe('A tooltip');
|
||||
expect(testClass.cssClasses.length).toBe(1);
|
||||
expect(testClass.cssClasses[0]).toBe('clickable');
|
||||
});
|
||||
|
||||
it('should associate click and href link with tooltip', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1\n' +
|
||||
'Class1 : someMethod()\n' +
|
||||
'click Class1 href "google.com" "A tooltip"';
|
||||
parser.parse(str);
|
||||
|
||||
const testClass = parser.yy.getClass('Class1');
|
||||
expect(testClass.link).toBe('google.com');
|
||||
expect(testClass.tooltip).toBe('A tooltip');
|
||||
expect(testClass.cssClasses.length).toBe(1);
|
||||
expect(testClass.cssClasses[0]).toBe('clickable');
|
||||
});
|
||||
|
||||
it('should associate click and href link with tooltip and target appropriately', function () {
|
||||
spyOn(classDb, 'setLink');
|
||||
spyOn(classDb, 'setTooltip');
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1\n' +
|
||||
'Class1 : someMethod()\n' +
|
||||
'click Class1 href "google.com" "A tooltip" _self';
|
||||
parser.parse(str);
|
||||
|
||||
expect(classDb.setLink).toHaveBeenCalledWith('Class1', 'google.com', '_self');
|
||||
expect(classDb.setTooltip).toHaveBeenCalledWith('Class1', 'A tooltip');
|
||||
});
|
||||
|
||||
it('should associate click and href link appropriately', function () {
|
||||
spyOn(classDb, 'setLink');
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1\n' +
|
||||
'Class1 : someMethod()\n' +
|
||||
'click Class1 href "google.com"';
|
||||
parser.parse(str);
|
||||
|
||||
expect(classDb.setLink).toHaveBeenCalledWith('Class1', 'google.com');
|
||||
});
|
||||
|
||||
it('should associate click and href link with target appropriately', function () {
|
||||
spyOn(classDb, 'setLink');
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1\n' +
|
||||
'Class1 : someMethod()\n' +
|
||||
'click Class1 href "google.com" _self';
|
||||
parser.parse(str);
|
||||
|
||||
expect(classDb.setLink).toHaveBeenCalledWith('Class1', 'google.com', '_self');
|
||||
});
|
||||
|
||||
it('should associate link appropriately', function () {
|
||||
spyOn(classDb, 'setLink');
|
||||
spyOn(classDb, 'setTooltip');
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1\n' +
|
||||
'Class1 : someMethod()\n' +
|
||||
'link Class1 "google.com" "A tooltip" _self';
|
||||
parser.parse(str);
|
||||
|
||||
expect(classDb.setLink).toHaveBeenCalledWith('Class1', 'google.com', '_self');
|
||||
expect(classDb.setTooltip).toHaveBeenCalledWith('Class1', 'A tooltip');
|
||||
});
|
||||
|
||||
it('should associate callback appropriately', function () {
|
||||
spyOn(classDb, 'setClickEvent');
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1\n' +
|
||||
'Class1 : someMethod()\n' +
|
||||
'callback Class1 "functionCall"';
|
||||
parser.parse(str);
|
||||
|
||||
expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall');
|
||||
});
|
||||
|
||||
it('should associate click and call callback appropriately', function () {
|
||||
spyOn(classDb, 'setClickEvent');
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1\n' +
|
||||
'Class1 : someMethod()\n' +
|
||||
'click Class1 call functionCall()';
|
||||
parser.parse(str);
|
||||
|
||||
expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall');
|
||||
});
|
||||
|
||||
it('should associate callback appropriately with an arbitrary number of args', function () {
|
||||
spyOn(classDb, 'setClickEvent');
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1\n' +
|
||||
'Class1 : someMethod()\n' +
|
||||
'click Class1 call functionCall("test0", test1, test2)';
|
||||
parser.parse(str);
|
||||
|
||||
expect(classDb.setClickEvent).toHaveBeenCalledWith(
|
||||
'Class1',
|
||||
'functionCall',
|
||||
'"test0", test1, test2'
|
||||
);
|
||||
});
|
||||
|
||||
it('should associate callback with tooltip', function () {
|
||||
spyOn(classDb, 'setClickEvent');
|
||||
spyOn(classDb, 'setTooltip');
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1\n' +
|
||||
'Class1 : someMethod()\n' +
|
||||
'click Class1 call functionCall() "A tooltip"';
|
||||
parser.parse(str);
|
||||
|
||||
expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall');
|
||||
expect(classDb.setTooltip).toHaveBeenCalledWith('Class1', 'A tooltip');
|
||||
});
|
||||
|
||||
it('should add classes namespaces', function () {
|
||||
const str = `classDiagram
|
||||
namespace Namespace1 {
|
||||
class Class1 {
|
||||
int : test
|
||||
string : foo
|
||||
test()
|
||||
foo()
|
||||
}
|
||||
class Class2
|
||||
}`;
|
||||
parser.parse(str);
|
||||
|
||||
const testNamespace = parser.yy.getNamespace('Namespace1');
|
||||
expect(Object.keys(testNamespace.classes).length).toBe(2);
|
||||
expect(Object.keys(testNamespace.children).length).toBe(0);
|
||||
expect(testNamespace.classes['Class1'].id).toBe('Class1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when parsing classDiagram with text labels', () => {
|
||||
beforeEach(function () {
|
||||
parser.yy = classDb;
|
||||
parser.yy.clear();
|
||||
});
|
||||
|
||||
it('should parse a class with a text label', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["Class 1 with text label"]
|
||||
C1 --> C2
|
||||
`);
|
||||
const c1 = classDb.getClass('C1');
|
||||
expect(c1.label).toBe('Class 1 with text label');
|
||||
const c2 = classDb.getClass('C2');
|
||||
expect(c2.label).toBe('C2');
|
||||
});
|
||||
|
||||
it('should parse two classes with text labels', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["Class 1 with text label"]
|
||||
class C2["Class 2 with chars @?"]
|
||||
C1 --> C2
|
||||
`);
|
||||
const c1 = classDb.getClass('C1');
|
||||
expect(c1.label).toBe('Class 1 with text label');
|
||||
const c2 = classDb.getClass('C2');
|
||||
expect(c2.label).toBe('Class 2 with chars @?');
|
||||
});
|
||||
|
||||
it('should parse a class with a text label and members', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["Class 1 with text label"] {
|
||||
+member1
|
||||
}
|
||||
C1 --> C2
|
||||
`);
|
||||
const c1 = classDb.getClass('C1');
|
||||
expect(c1.label).toBe('Class 1 with text label');
|
||||
expect(c1.members.length).toBe(1);
|
||||
expect(c1.members[0]).toBe('+member1');
|
||||
|
||||
const c2 = classDb.getClass('C2');
|
||||
expect(c2.label).toBe('C2');
|
||||
});
|
||||
|
||||
it('should parse a class with a text label, members and annotation', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["Class 1 with text label"] {
|
||||
<<interface>>
|
||||
+member1
|
||||
}
|
||||
C1 --> C2
|
||||
`);
|
||||
const c1 = classDb.getClass('C1');
|
||||
expect(c1.label).toBe('Class 1 with text label');
|
||||
expect(c1.members.length).toBe(1);
|
||||
expect(c1.members[0]).toBe('+member1');
|
||||
expect(c1.annotations.length).toBe(1);
|
||||
expect(c1.annotations[0]).toBe('interface');
|
||||
|
||||
const c2 = classDb.getClass('C2');
|
||||
expect(c2.label).toBe('C2');
|
||||
});
|
||||
|
||||
it('should parse a class with text label and css class shorthand', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["Class 1 with text label"]:::styleClass {
|
||||
+member1
|
||||
}
|
||||
C1 --> C2
|
||||
`);
|
||||
|
||||
const c1 = classDb.getClass('C1');
|
||||
expect(c1.label).toBe('Class 1 with text label');
|
||||
expect(c1.cssClasses.length).toBe(1);
|
||||
expect(c1.members[0]).toBe('+member1');
|
||||
expect(c1.cssClasses[0]).toBe('styleClass');
|
||||
});
|
||||
|
||||
it('should parse a class with text label and css class', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["Class 1 with text label"] {
|
||||
+member1
|
||||
}
|
||||
C1 --> C2
|
||||
cssClass "C1" styleClass
|
||||
`);
|
||||
|
||||
const c1 = classDb.getClass('C1');
|
||||
expect(c1.label).toBe('Class 1 with text label');
|
||||
expect(c1.cssClasses.length).toBe(1);
|
||||
expect(c1.members[0]).toBe('+member1');
|
||||
expect(c1.cssClasses[0]).toBe('styleClass');
|
||||
});
|
||||
|
||||
it('should parse two classes with text labels and css classes', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["Class 1 with text label"] {
|
||||
+member1
|
||||
}
|
||||
class C2["Long long long long long long long long long long label"]
|
||||
C1 --> C2
|
||||
cssClass "C1,C2" styleClass
|
||||
`);
|
||||
|
||||
const c1 = classDb.getClass('C1');
|
||||
expect(c1.label).toBe('Class 1 with text label');
|
||||
expect(c1.cssClasses.length).toBe(1);
|
||||
expect(c1.cssClasses[0]).toBe('styleClass');
|
||||
|
||||
const c2 = classDb.getClass('C2');
|
||||
expect(c2.label).toBe('Long long long long long long long long long long label');
|
||||
expect(c2.cssClasses.length).toBe(1);
|
||||
expect(c2.cssClasses[0]).toBe('styleClass');
|
||||
});
|
||||
|
||||
it('should parse two classes with text labels and css class shorthands', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["Class 1 with text label"]:::styleClass1 {
|
||||
+member1
|
||||
}
|
||||
class C2["Class 2 !@#$%^&*() label"]:::styleClass2
|
||||
C1 --> C2
|
||||
`);
|
||||
|
||||
const c1 = classDb.getClass('C1');
|
||||
expect(c1.label).toBe('Class 1 with text label');
|
||||
expect(c1.cssClasses.length).toBe(1);
|
||||
expect(c1.cssClasses[0]).toBe('styleClass1');
|
||||
|
||||
const c2 = classDb.getClass('C2');
|
||||
expect(c2.label).toBe('Class 2 !@#$%^&*() label');
|
||||
expect(c2.cssClasses.length).toBe(1);
|
||||
expect(c2.cssClasses[0]).toBe('styleClass2');
|
||||
});
|
||||
|
||||
it('should parse multiple classes with same text labels', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["Class with text label"]
|
||||
class C2["Class with text label"]
|
||||
class C3["Class with text label"]
|
||||
C1 --> C2
|
||||
C3 ..> C2
|
||||
`);
|
||||
|
||||
const c1 = classDb.getClass('C1');
|
||||
expect(c1.label).toBe('Class with text label');
|
||||
|
||||
const c2 = classDb.getClass('C2');
|
||||
expect(c2.label).toBe('Class with text label');
|
||||
|
||||
const c3 = classDb.getClass('C3');
|
||||
expect(c3.label).toBe('Class with text label');
|
||||
});
|
||||
|
||||
it('should parse classes with different text labels', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["OneWord"]
|
||||
class C2["With, Comma"]
|
||||
class C3["With (Brackets)"]
|
||||
class C4["With [Brackets]"]
|
||||
class C5["With {Brackets}"]
|
||||
class C6[" "]
|
||||
class C7["With 1 number"]
|
||||
class C8["With . period..."]
|
||||
class C9["With - dash"]
|
||||
class C10["With _ underscore"]
|
||||
class C11["With ' single quote"]
|
||||
class C12["With ~!@#$%^&*()_+=-/?"]
|
||||
class C13["With Città foreign language"]
|
||||
`);
|
||||
expect(classDb.getClass('C1').label).toBe('OneWord');
|
||||
expect(classDb.getClass('C2').label).toBe('With, Comma');
|
||||
expect(classDb.getClass('C3').label).toBe('With (Brackets)');
|
||||
expect(classDb.getClass('C4').label).toBe('With [Brackets]');
|
||||
expect(classDb.getClass('C5').label).toBe('With {Brackets}');
|
||||
expect(classDb.getClass('C6').label).toBe(' ');
|
||||
expect(classDb.getClass('C7').label).toBe('With 1 number');
|
||||
expect(classDb.getClass('C8').label).toBe('With . period...');
|
||||
expect(classDb.getClass('C9').label).toBe('With - dash');
|
||||
expect(classDb.getClass('C10').label).toBe('With _ underscore');
|
||||
expect(classDb.getClass('C11').label).toBe("With ' single quote");
|
||||
expect(classDb.getClass('C12').label).toBe('With ~!@#$%^&*()_+=-/?');
|
||||
expect(classDb.getClass('C13').label).toBe('With Città foreign language');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -8,7 +8,7 @@ import utils from '../../utils.js';
|
||||
import { interpolateToCurve, getStylesFromArray } from '../../utils.js';
|
||||
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
|
||||
import common from '../common/common.js';
|
||||
import { ClassRelation, ClassNote, ClassMap, EdgeData } from './classTypes.js';
|
||||
import { ClassRelation, ClassNote, ClassMap, EdgeData, NamespaceMap } from './classTypes.js';
|
||||
|
||||
const sanitizeText = (txt: string) => common.sanitizeText(txt, getConfig());
|
||||
|
||||
@@ -19,6 +19,59 @@ let conf = {
|
||||
curve: undefined,
|
||||
};
|
||||
|
||||
interface RectParameters {
|
||||
id: string;
|
||||
shape: 'rect';
|
||||
labelStyle: string;
|
||||
domId: string;
|
||||
labelText: string;
|
||||
padding: number | undefined;
|
||||
style?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that adds the vertices found during parsing to the graph to be rendered.
|
||||
*
|
||||
* @param namespaces - Object containing the vertices.
|
||||
* @param g - The graph that is to be drawn.
|
||||
* @param _id - id of the graph
|
||||
* @param diagObj - The diagram object
|
||||
*/
|
||||
export const addNamespaces = function (
|
||||
namespaces: NamespaceMap,
|
||||
g: graphlib.Graph,
|
||||
_id: string,
|
||||
diagObj: any
|
||||
) {
|
||||
const keys = Object.keys(namespaces);
|
||||
log.info('keys:', keys);
|
||||
log.info(namespaces);
|
||||
|
||||
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
|
||||
keys.forEach(function (id) {
|
||||
const vertex = namespaces[id];
|
||||
|
||||
// parent node must be one of [rect, roundedWithTitle, noteGroup, divider]
|
||||
const shape = 'rect';
|
||||
|
||||
const node: RectParameters = {
|
||||
shape: shape,
|
||||
id: vertex.id,
|
||||
domId: vertex.domId,
|
||||
labelText: sanitizeText(vertex.id),
|
||||
labelStyle: '',
|
||||
style: 'fill: none; stroke: black',
|
||||
// TODO V10: Flowchart ? Keeping flowchart for backwards compatibility. Remove in next major release
|
||||
padding: getConfig().flowchart?.padding ?? getConfig().class?.padding,
|
||||
};
|
||||
|
||||
g.setNode(vertex.id, node);
|
||||
addClasses(vertex.classes, g, _id, diagObj, vertex.id);
|
||||
|
||||
log.info('setNode', node);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Function that adds the vertices found during parsing to the graph to be rendered.
|
||||
*
|
||||
@@ -26,12 +79,14 @@ let conf = {
|
||||
* @param g - The graph that is to be drawn.
|
||||
* @param _id - id of the graph
|
||||
* @param diagObj - The diagram object
|
||||
* @param parent - id of the parent namespace, if it exists
|
||||
*/
|
||||
export const addClasses = function (
|
||||
classes: ClassMap,
|
||||
g: graphlib.Graph,
|
||||
_id: string,
|
||||
diagObj: any
|
||||
diagObj: any,
|
||||
parent?: string
|
||||
) {
|
||||
const keys = Object.keys(classes);
|
||||
log.info('keys:', keys);
|
||||
@@ -55,6 +110,7 @@ export const addClasses = function (
|
||||
const vertexText = vertex.label ?? vertex.id;
|
||||
const radius = 0;
|
||||
const shape = 'class_box';
|
||||
|
||||
// Add the node
|
||||
const node = {
|
||||
labelStyle: styles.labelStyle,
|
||||
@@ -67,7 +123,7 @@ export const addClasses = function (
|
||||
style: styles.style,
|
||||
id: vertex.id,
|
||||
domId: vertex.domId,
|
||||
tooltip: diagObj.db.getTooltip(vertex.id) || '',
|
||||
tooltip: diagObj.db.getTooltip(vertex.id, parent) || '',
|
||||
haveCallback: vertex.haveCallback,
|
||||
link: vertex.link,
|
||||
width: vertex.type === 'group' ? 500 : undefined,
|
||||
@@ -76,6 +132,11 @@ export const addClasses = function (
|
||||
padding: getConfig().flowchart?.padding ?? getConfig().class?.padding,
|
||||
};
|
||||
g.setNode(vertex.id, node);
|
||||
|
||||
if (parent) {
|
||||
g.setParent(vertex.id, parent);
|
||||
}
|
||||
|
||||
log.info('setNode', node);
|
||||
});
|
||||
};
|
||||
@@ -275,10 +336,12 @@ export const draw = async function (text: string, id: string, _version: string,
|
||||
});
|
||||
|
||||
// Fetch the vertices/nodes and edges/links from the parsed graph definition
|
||||
const namespaces: NamespaceMap = diagObj.db.getNamespaces();
|
||||
const classes: ClassMap = diagObj.db.getClasses();
|
||||
const relations: ClassRelation[] = diagObj.db.getRelations();
|
||||
const notes: ClassNote[] = diagObj.db.getNotes();
|
||||
log.info(relations);
|
||||
addNamespaces(namespaces, g, id, diagObj);
|
||||
addClasses(classes, g, id, diagObj);
|
||||
addRelations(relations, g);
|
||||
addNotes(notes, g, relations.length + 1, classes);
|
||||
|
@@ -52,4 +52,13 @@ export type ClassRelation = {
|
||||
lineType: number;
|
||||
};
|
||||
};
|
||||
|
||||
export interface NamespaceNode {
|
||||
id: string;
|
||||
domId: string;
|
||||
classes: ClassMap;
|
||||
children: NamespaceMap;
|
||||
}
|
||||
|
||||
export type ClassMap = Record<string, ClassNode>;
|
||||
export type NamespaceMap = Record<string, NamespaceNode>;
|
||||
|
@@ -19,6 +19,10 @@
|
||||
%x acc_title
|
||||
%x acc_descr
|
||||
%x acc_descr_multiline
|
||||
%x class
|
||||
%x class-body
|
||||
%x namespace
|
||||
%x namespace-body
|
||||
%%
|
||||
\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
|
||||
.*direction\s+TB[^\n]* return 'direction_tb';
|
||||
@@ -41,35 +45,41 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
|
||||
|
||||
\s*(\r?\n)+ return 'NEWLINE';
|
||||
\s+ /* skip whitespace */
|
||||
|
||||
"classDiagram-v2" return 'CLASS_DIAGRAM';
|
||||
"classDiagram" return 'CLASS_DIAGRAM';
|
||||
[{] { this.begin("struct"); /*console.log('Starting struct');*/ return 'STRUCT_START';}
|
||||
<INITIAL,struct>"[*]" { /*console.log('EDGE_STATE=',yytext);*/ return 'EDGE_STATE';}
|
||||
<struct><<EOF>> return "EOF_IN_STRUCT";
|
||||
<struct>[{] return "OPEN_IN_STRUCT";
|
||||
<struct>[}] { /*console.log('Ending struct');*/this.popState(); return 'STRUCT_STOP';}}
|
||||
<struct>[\n] /* nothing */
|
||||
<struct>[^{}\n]* { /*console.log('lex-member: ' + yytext);*/ return "MEMBER";}
|
||||
"[*]" return 'EDGE_STATE';
|
||||
|
||||
"class" return 'CLASS';
|
||||
"cssClass" return 'CSSCLASS';
|
||||
"callback" return 'CALLBACK';
|
||||
"link" return 'LINK';
|
||||
"click" return 'CLICK';
|
||||
"note for" return 'NOTE_FOR';
|
||||
"note" return 'NOTE';
|
||||
"<<" return 'ANNOTATION_START';
|
||||
">>" return 'ANNOTATION_END';
|
||||
[~] this.begin("generic");
|
||||
<generic>[~] this.popState();
|
||||
<generic>[^~]* return "GENERICTYPE";
|
||||
["] this.begin("string");
|
||||
<string>["] this.popState();
|
||||
<string>[^"]* return "STR";
|
||||
<INITIAL,namespace>"namespace" { this.begin('namespace'); return 'NAMESPACE'; }
|
||||
<namespace>\s*(\r?\n)+ { this.popState(); return 'NEWLINE'; }
|
||||
<namespace>\s+ /* skip whitespace */
|
||||
<namespace>[{] { this.begin("namespace-body"); return 'STRUCT_START';}
|
||||
<namespace-body>[}] { this.popState(); return 'STRUCT_STOP'; }
|
||||
<namespace-body><<EOF>> return "EOF_IN_STRUCT";
|
||||
<namespace-body>\s*(\r?\n)+ return 'NEWLINE';
|
||||
<namespace-body>\s+ /* skip whitespace */
|
||||
<namespace-body>"[*]" return 'EDGE_STATE';
|
||||
|
||||
[`] this.begin("bqstring");
|
||||
<bqstring>[`] this.popState();
|
||||
<bqstring>[^`]+ return "BQUOTE_STR";
|
||||
<INITIAL,namespace-body>"class" { this.begin('class'); return 'CLASS';}
|
||||
<class>\s*(\r?\n)+ { this.popState(); return 'NEWLINE'; }
|
||||
<class>\s+ /* skip whitespace */
|
||||
<class>[}] { this.popState(); this.popState(); return 'STRUCT_STOP';}
|
||||
<class>[{] { this.begin("class-body"); return 'STRUCT_START';}
|
||||
<class-body>[}] { this.popState(); return 'STRUCT_STOP'; }
|
||||
<class-body><<EOF>> return "EOF_IN_STRUCT";
|
||||
<class-body>"[*]" { return 'EDGE_STATE';}
|
||||
<class-body>[{] return "OPEN_IN_STRUCT";
|
||||
<class-body>[\n] /* nothing */
|
||||
<class-body>[^{}\n]* { return "MEMBER";}
|
||||
|
||||
<*>"cssClass" return 'CSSCLASS';
|
||||
<*>"callback" return 'CALLBACK';
|
||||
<*>"link" return 'LINK';
|
||||
<*>"click" return 'CLICK';
|
||||
<*>"note for" return 'NOTE_FOR';
|
||||
<*>"note" return 'NOTE';
|
||||
<*>"<<" return 'ANNOTATION_START';
|
||||
<*>">>" return 'ANNOTATION_END';
|
||||
|
||||
/*
|
||||
---interactivity command---
|
||||
@@ -77,7 +87,7 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
|
||||
line was introduced with 'click'.
|
||||
'href "<link>"' attaches the specified link to the node that was specified by 'click'.
|
||||
*/
|
||||
"href"[\s]+["] this.begin("href");
|
||||
<*>"href"[\s]+["] this.begin("href");
|
||||
<href>["] this.popState();
|
||||
<href>[^"]* return 'HREF';
|
||||
|
||||
@@ -89,41 +99,53 @@ the line was introduced with 'click'.
|
||||
arguments to the node that was specified by 'click'.
|
||||
Function arguments are optional: 'call <callback_name>()' simply executes 'callback_name' without any arguments.
|
||||
*/
|
||||
"call"[\s]+ this.begin("callback_name");
|
||||
<*>"call"[\s]+ this.begin("callback_name");
|
||||
<callback_name>\([\s]*\) this.popState();
|
||||
<callback_name>\( this.popState(); this.begin("callback_args");
|
||||
<callback_name>[^(]* return 'CALLBACK_NAME';
|
||||
<callback_args>\) this.popState();
|
||||
<callback_args>[^)]* return 'CALLBACK_ARGS';
|
||||
|
||||
"_self" return 'LINK_TARGET';
|
||||
"_blank" return 'LINK_TARGET';
|
||||
"_parent" return 'LINK_TARGET';
|
||||
"_top" return 'LINK_TARGET';
|
||||
<generic>[~] this.popState();
|
||||
<generic>[^~]* return "GENERICTYPE";
|
||||
<*>[~] this.begin("generic");
|
||||
|
||||
\s*\<\| return 'EXTENSION';
|
||||
\s*\|\> return 'EXTENSION';
|
||||
\s*\> return 'DEPENDENCY';
|
||||
\s*\< return 'DEPENDENCY';
|
||||
\s*\* return 'COMPOSITION';
|
||||
\s*o return 'AGGREGATION';
|
||||
\s*\(\) return 'LOLLIPOP';
|
||||
\-\- return 'LINE';
|
||||
\.\. return 'DOTTED_LINE';
|
||||
":"{1}[^:\n;]+ return 'LABEL';
|
||||
":"{3} return 'STYLE_SEPARATOR';
|
||||
\- return 'MINUS';
|
||||
"." return 'DOT';
|
||||
\+ return 'PLUS';
|
||||
\% return 'PCT';
|
||||
"=" return 'EQUALS';
|
||||
\= return 'EQUALS';
|
||||
\w+ return 'ALPHA';
|
||||
"[" return 'SQS';
|
||||
"]" return 'SQE';
|
||||
[!"#$%&'*+,-.`?\\/] return 'PUNCTUATION';
|
||||
[0-9]+ return 'NUM';
|
||||
[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|
|
||||
<string>["] this.popState();
|
||||
<string>[^"]* return "STR";
|
||||
<*>["] this.begin("string");
|
||||
|
||||
<bqstring>[`] this.popState();
|
||||
<bqstring>[^`]+ return "BQUOTE_STR";
|
||||
<*>[`] this.begin("bqstring");
|
||||
|
||||
<*>"_self" return 'LINK_TARGET';
|
||||
<*>"_blank" return 'LINK_TARGET';
|
||||
<*>"_parent" return 'LINK_TARGET';
|
||||
<*>"_top" return 'LINK_TARGET';
|
||||
|
||||
<*>\s*\<\| return 'EXTENSION';
|
||||
<*>\s*\|\> return 'EXTENSION';
|
||||
<*>\s*\> return 'DEPENDENCY';
|
||||
<*>\s*\< return 'DEPENDENCY';
|
||||
<*>\s*\* return 'COMPOSITION';
|
||||
<*>\s*o return 'AGGREGATION';
|
||||
<*>\s*\(\) return 'LOLLIPOP';
|
||||
<*>\-\- return 'LINE';
|
||||
<*>\.\. return 'DOTTED_LINE';
|
||||
<*>":"{1}[^:\n;]+ return 'LABEL';
|
||||
<*>":"{3} return 'STYLE_SEPARATOR';
|
||||
<*>\- return 'MINUS';
|
||||
<*>"." return 'DOT';
|
||||
<*>\+ return 'PLUS';
|
||||
<*>\% return 'PCT';
|
||||
<*>"=" return 'EQUALS';
|
||||
<*>\= return 'EQUALS';
|
||||
<*>\w+ return 'ALPHA';
|
||||
<*>"[" return 'SQS';
|
||||
<*>"]" return 'SQE';
|
||||
<*>[!"#$%&'*+,-.`?\\/] return 'PUNCTUATION';
|
||||
<*>[0-9]+ return 'NUM';
|
||||
<*>[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|
|
||||
[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|
|
||||
[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|
|
||||
[\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|
|
||||
@@ -184,9 +206,9 @@ Function arguments are optional: 'call <callback_name>()' simply executes 'callb
|
||||
[\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|
|
||||
[\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|
|
||||
[\uFFD2-\uFFD7\uFFDA-\uFFDC]
|
||||
return 'UNICODE_TEXT';
|
||||
\s return 'SPACE';
|
||||
<<EOF>> return 'EOF';
|
||||
return 'UNICODE_TEXT';
|
||||
<*>\s return 'SPACE';
|
||||
<*><<EOF>> return 'EOF';
|
||||
|
||||
/lex
|
||||
|
||||
@@ -254,6 +276,11 @@ classLabel
|
||||
: SQS STR SQE { $$=$2; }
|
||||
;
|
||||
|
||||
namespaceName
|
||||
: alphaNumToken { $$=$1; }
|
||||
| alphaNumToken namespaceName { $$=$1+$2; }
|
||||
;
|
||||
|
||||
className
|
||||
: alphaNumToken { $$=$1; }
|
||||
| classLiteralName { $$=$1; }
|
||||
@@ -265,6 +292,7 @@ className
|
||||
statement
|
||||
: relationStatement { yy.addRelation($1); }
|
||||
| relationStatement LABEL { $1.title = yy.cleanupLabel($2); yy.addRelation($1); }
|
||||
| namespaceStatement
|
||||
| classStatement
|
||||
| methodStatement
|
||||
| annotationStatement
|
||||
@@ -277,6 +305,21 @@ statement
|
||||
| acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); }
|
||||
;
|
||||
|
||||
namespaceStatement
|
||||
: namespaceIdentifier STRUCT_START classStatements STRUCT_STOP {yy.addClassesToNamespace($1, $3);}
|
||||
| namespaceIdentifier STRUCT_START NEWLINE classStatements STRUCT_STOP {yy.addClassesToNamespace($1, $4);}
|
||||
;
|
||||
|
||||
namespaceIdentifier
|
||||
: NAMESPACE namespaceName {$$=$2; yy.addNamespace($2);}
|
||||
;
|
||||
|
||||
classStatements
|
||||
: classStatement {$$=[$1]}
|
||||
| classStatement NEWLINE {$$=[$1]}
|
||||
| classStatement NEWLINE classStatements {$3.unshift($1); $$=$3}
|
||||
;
|
||||
|
||||
classStatement
|
||||
: classIdentifier
|
||||
| classIdentifier STYLE_SEPARATOR alphaNumToken {yy.setCssClass($1, $3);}
|
||||
|
@@ -486,6 +486,7 @@ const buildLegacyDisplay = function (text) {
|
||||
cssStyle,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a <tspan> for a member in a diagram
|
||||
*
|
||||
|
114
packages/mermaid/src/diagrams/common/svgDrawCommon.js
Normal file
114
packages/mermaid/src/diagrams/common/svgDrawCommon.js
Normal file
@@ -0,0 +1,114 @@
|
||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||
|
||||
export const drawRect = function (elem, rectData) {
|
||||
const rectElem = elem.append('rect');
|
||||
rectElem.attr('x', rectData.x);
|
||||
rectElem.attr('y', rectData.y);
|
||||
rectElem.attr('fill', rectData.fill);
|
||||
rectElem.attr('stroke', rectData.stroke);
|
||||
rectElem.attr('width', rectData.width);
|
||||
rectElem.attr('height', rectData.height);
|
||||
rectElem.attr('rx', rectData.rx);
|
||||
rectElem.attr('ry', rectData.ry);
|
||||
|
||||
if (rectData.attrs !== 'undefined' && rectData.attrs !== null) {
|
||||
for (let attrKey in rectData.attrs) {
|
||||
rectElem.attr(attrKey, rectData.attrs[attrKey]);
|
||||
}
|
||||
}
|
||||
|
||||
if (rectData.class !== 'undefined') {
|
||||
rectElem.attr('class', rectData.class);
|
||||
}
|
||||
|
||||
return rectElem;
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws a background rectangle
|
||||
*
|
||||
* @param {any} elem Diagram (reference for bounds)
|
||||
* @param {any} bounds Shape of the rectangle
|
||||
*/
|
||||
export const drawBackgroundRect = function (elem, bounds) {
|
||||
const rectElem = drawRect(elem, {
|
||||
x: bounds.startx,
|
||||
y: bounds.starty,
|
||||
width: bounds.stopx - bounds.startx,
|
||||
height: bounds.stopy - bounds.starty,
|
||||
fill: bounds.fill,
|
||||
stroke: bounds.stroke,
|
||||
class: 'rect',
|
||||
});
|
||||
rectElem.lower();
|
||||
};
|
||||
|
||||
export const drawText = function (elem, textData) {
|
||||
// Remove and ignore br:s
|
||||
const nText = textData.text.replace(/<br\s*\/?>/gi, ' ');
|
||||
|
||||
const textElem = elem.append('text');
|
||||
textElem.attr('x', textData.x);
|
||||
textElem.attr('y', textData.y);
|
||||
textElem.attr('class', 'legend');
|
||||
|
||||
textElem.style('text-anchor', textData.anchor);
|
||||
|
||||
if (textData.class !== undefined) {
|
||||
textElem.attr('class', textData.class);
|
||||
}
|
||||
|
||||
const span = textElem.append('tspan');
|
||||
span.attr('x', textData.x + textData.textMargin * 2);
|
||||
span.text(nText);
|
||||
|
||||
return textElem;
|
||||
};
|
||||
|
||||
export const drawImage = function (elem, x, y, link) {
|
||||
const imageElem = elem.append('image');
|
||||
imageElem.attr('x', x);
|
||||
imageElem.attr('y', y);
|
||||
var sanitizedLink = sanitizeUrl(link);
|
||||
imageElem.attr('xlink:href', sanitizedLink);
|
||||
};
|
||||
|
||||
export const drawEmbeddedImage = function (elem, x, y, link) {
|
||||
const imageElem = elem.append('use');
|
||||
imageElem.attr('x', x);
|
||||
imageElem.attr('y', y);
|
||||
const sanitizedLink = sanitizeUrl(link);
|
||||
imageElem.attr('xlink:href', '#' + sanitizedLink);
|
||||
};
|
||||
|
||||
export const getNoteRect = function () {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 100,
|
||||
fill: '#EDF2AE',
|
||||
stroke: '#666',
|
||||
anchor: 'start',
|
||||
rx: 0,
|
||||
ry: 0,
|
||||
};
|
||||
};
|
||||
|
||||
export const getTextObj = function () {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 100,
|
||||
fill: undefined,
|
||||
anchor: undefined,
|
||||
'text-anchor': 'start',
|
||||
style: '#666',
|
||||
textMargin: 0,
|
||||
rx: 0,
|
||||
ry: 0,
|
||||
tspan: true,
|
||||
valign: undefined,
|
||||
};
|
||||
};
|
@@ -70,6 +70,7 @@ const defaultBkg = function (elem, node, section) {
|
||||
.attr('x2', node.width)
|
||||
.attr('y2', node.height);
|
||||
};
|
||||
|
||||
const rectBkg = function (elem, node) {
|
||||
elem
|
||||
.append('rect')
|
||||
@@ -78,6 +79,7 @@ const rectBkg = function (elem, node) {
|
||||
.attr('height', node.height)
|
||||
.attr('width', node.width);
|
||||
};
|
||||
|
||||
const cloudBkg = function (elem, node) {
|
||||
const w = node.width;
|
||||
const h = node.height;
|
||||
@@ -108,6 +110,7 @@ const cloudBkg = function (elem, node) {
|
||||
H0 V0 Z`
|
||||
);
|
||||
};
|
||||
|
||||
const bangBkg = function (elem, node) {
|
||||
const w = node.width;
|
||||
const h = node.height;
|
||||
@@ -139,6 +142,7 @@ const bangBkg = function (elem, node) {
|
||||
H0 V0 Z`
|
||||
);
|
||||
};
|
||||
|
||||
const circleBkg = function (elem, node) {
|
||||
elem
|
||||
.append('circle')
|
||||
|
@@ -3,6 +3,7 @@ import { select, selectAll } from 'd3';
|
||||
import svgDraw, { drawText, fixLifeLineHeights } from './svgDraw.js';
|
||||
import { log } from '../../logger.js';
|
||||
import common from '../common/common.js';
|
||||
import * as svgDrawCommon from '../common/svgDrawCommon';
|
||||
import * as configApi from '../../config.js';
|
||||
import assignWithDepth from '../../assignWithDepth.js';
|
||||
import utils from '../../utils.js';
|
||||
@@ -225,7 +226,7 @@ const drawNote = function (elem: any, noteModel: NoteModel) {
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
noteModel.height = conf.boxMargin;
|
||||
noteModel.starty = bounds.getVerticalPos();
|
||||
const rect = svgDraw.getNoteRect();
|
||||
const rect = svgDrawCommon.getNoteRect();
|
||||
rect.x = noteModel.startx;
|
||||
rect.y = noteModel.starty;
|
||||
rect.width = noteModel.width || conf.width;
|
||||
@@ -233,7 +234,7 @@ const drawNote = function (elem: any, noteModel: NoteModel) {
|
||||
|
||||
const g = elem.append('g');
|
||||
const rectElem = svgDraw.drawRect(g, rect);
|
||||
const textObj = svgDraw.getTextObj();
|
||||
const textObj = svgDrawCommon.getTextObj();
|
||||
textObj.x = noteModel.startx;
|
||||
textObj.y = noteModel.starty;
|
||||
textObj.width = rect.width;
|
||||
@@ -347,7 +348,7 @@ function boundMessage(_diagram, msgModel): number {
|
||||
const drawMessage = function (diagram, msgModel, lineStartY: number, diagObj: Diagram) {
|
||||
const { startx, stopx, starty, message, type, sequenceIndex, sequenceVisible } = msgModel;
|
||||
const textDims = utils.calculateTextDimensions(message, messageFont(conf));
|
||||
const textObj = svgDraw.getTextObj();
|
||||
const textObj = svgDrawCommon.getTextObj();
|
||||
textObj.x = startx;
|
||||
textObj.y = starty + 10;
|
||||
textObj.width = stopx - startx;
|
||||
|
@@ -1,33 +1,13 @@
|
||||
import common from '../common/common.js';
|
||||
import * as svgDrawCommon from '../common/svgDrawCommon';
|
||||
import { addFunction } from '../../interactionDb.js';
|
||||
import { parseFontSize } from '../../utils.js';
|
||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||
|
||||
export const drawRect = function (elem, rectData) {
|
||||
const rectElem = elem.append('rect');
|
||||
rectElem.attr('x', rectData.x);
|
||||
rectElem.attr('y', rectData.y);
|
||||
rectElem.attr('fill', rectData.fill);
|
||||
rectElem.attr('stroke', rectData.stroke);
|
||||
rectElem.attr('width', rectData.width);
|
||||
rectElem.attr('height', rectData.height);
|
||||
rectElem.attr('rx', rectData.rx);
|
||||
rectElem.attr('ry', rectData.ry);
|
||||
|
||||
if (rectData.class !== undefined) {
|
||||
rectElem.attr('class', rectData.class);
|
||||
}
|
||||
|
||||
return rectElem;
|
||||
return svgDrawCommon.drawRect(elem, rectData);
|
||||
};
|
||||
|
||||
// const sanitizeUrl = function (s) {
|
||||
// return s
|
||||
// .replace(/&/g, '&')
|
||||
// .replace(/</g, '<')
|
||||
// .replace(/javascript:/g, '');
|
||||
// };
|
||||
|
||||
const addPopupInteraction = (id, actorCnt) => {
|
||||
addFunction(() => {
|
||||
const arr = document.querySelectorAll(id);
|
||||
@@ -43,6 +23,7 @@ const addPopupInteraction = (id, actorCnt) => {
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const drawPopup = function (elem, actor, minMenuWidth, textAttrs, forceMenus) {
|
||||
if (actor.links === undefined || actor.links === null || Object.keys(actor.links).length === 0) {
|
||||
return { height: 0, width: 0 };
|
||||
@@ -107,22 +88,6 @@ export const drawPopup = function (elem, actor, minMenuWidth, textAttrs, forceMe
|
||||
return { height: rectData.height + linkY, width: menuWidth };
|
||||
};
|
||||
|
||||
export const drawImage = function (elem, x, y, link) {
|
||||
const imageElem = elem.append('image');
|
||||
imageElem.attr('x', x);
|
||||
imageElem.attr('y', y);
|
||||
var sanitizedLink = sanitizeUrl(link);
|
||||
imageElem.attr('xlink:href', sanitizedLink);
|
||||
};
|
||||
|
||||
export const drawEmbeddedImage = function (elem, x, y, link) {
|
||||
const imageElem = elem.append('use');
|
||||
imageElem.attr('x', x);
|
||||
imageElem.attr('y', y);
|
||||
var sanitizedLink = sanitizeUrl(link);
|
||||
imageElem.attr('xlink:href', '#' + sanitizedLink);
|
||||
};
|
||||
|
||||
export const popupMenu = function (popid) {
|
||||
return (
|
||||
"var pu = document.getElementById('" +
|
||||
@@ -152,9 +117,10 @@ const popupMenuDownFunc = function (popupId) {
|
||||
pu.style.display = 'none';
|
||||
}
|
||||
};
|
||||
|
||||
export const drawText = function (elem, textData) {
|
||||
let prevTextHeight = 0,
|
||||
textHeight = 0;
|
||||
let prevTextHeight = 0;
|
||||
let textHeight = 0;
|
||||
const lines = textData.text.split(common.lineBreakRegex);
|
||||
|
||||
const [_textFontSize, _textFontSizePx] = parseFontSize(textData.fontSize);
|
||||
@@ -188,6 +154,7 @@ export const drawText = function (elem, textData) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
textData.anchor !== undefined &&
|
||||
textData.textMargin !== undefined &&
|
||||
@@ -217,6 +184,7 @@ export const drawText = function (elem, textData) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (let [i, line] of lines.entries()) {
|
||||
if (
|
||||
textData.textMargin !== undefined &&
|
||||
@@ -371,7 +339,7 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
|
||||
}
|
||||
}
|
||||
|
||||
const rect = getNoteRect();
|
||||
const rect = svgDrawCommon.getNoteRect();
|
||||
var cssclass = 'actor';
|
||||
if (actor.properties != null && actor.properties['class']) {
|
||||
cssclass = actor.properties['class'];
|
||||
@@ -391,9 +359,9 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
|
||||
if (actor.properties != null && actor.properties['icon']) {
|
||||
const iconSrc = actor.properties['icon'].trim();
|
||||
if (iconSrc.charAt(0) === '@') {
|
||||
drawEmbeddedImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc.substr(1));
|
||||
svgDrawCommon.drawEmbeddedImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc.substr(1));
|
||||
} else {
|
||||
drawImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc);
|
||||
svgDrawCommon.drawImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,7 +406,7 @@ const drawActorTypeActor = function (elem, actor, conf, isFooter) {
|
||||
const actElem = elem.append('g');
|
||||
actElem.attr('class', 'actor-man');
|
||||
|
||||
const rect = getNoteRect();
|
||||
const rect = svgDrawCommon.getNoteRect();
|
||||
rect.x = actor.x;
|
||||
rect.y = actor.y;
|
||||
rect.fill = '#eaeaea';
|
||||
@@ -447,7 +415,6 @@ const drawActorTypeActor = function (elem, actor, conf, isFooter) {
|
||||
rect.class = 'actor';
|
||||
rect.rx = 3;
|
||||
rect.ry = 3;
|
||||
// drawRect(actElem, rect);
|
||||
|
||||
actElem
|
||||
.append('line')
|
||||
@@ -532,6 +499,7 @@ export const drawBox = function (elem, box, conf) {
|
||||
export const anchorElement = function (elem) {
|
||||
return elem.append('g');
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws an activation in the diagram
|
||||
*
|
||||
@@ -542,7 +510,7 @@ export const anchorElement = function (elem) {
|
||||
* @param {any} actorActivations - Number of activations on the actor.
|
||||
*/
|
||||
export const drawActivation = function (elem, bounds, verticalPos, conf, actorActivations) {
|
||||
const rect = getNoteRect();
|
||||
const rect = svgDrawCommon.getNoteRect();
|
||||
const g = bounds.anchored;
|
||||
rect.x = bounds.startx;
|
||||
rect.y = bounds.starty;
|
||||
@@ -594,7 +562,7 @@ export const drawLoop = function (elem, loopModel, labelText, conf) {
|
||||
});
|
||||
}
|
||||
|
||||
let txt = getTextObj();
|
||||
let txt = svgDrawCommon.getTextObj();
|
||||
txt.text = labelText;
|
||||
txt.x = loopModel.startx;
|
||||
txt.y = loopModel.starty;
|
||||
@@ -610,7 +578,7 @@ export const drawLoop = function (elem, loopModel, labelText, conf) {
|
||||
txt.class = 'labelText';
|
||||
|
||||
drawLabel(g, txt);
|
||||
txt = getTextObj();
|
||||
txt = svgDrawCommon.getTextObj();
|
||||
txt.text = loopModel.title;
|
||||
txt.x = loopModel.startx + labelBoxWidth / 2 + (loopModel.stopx - loopModel.startx) / 2;
|
||||
txt.y = loopModel.starty + boxMargin + boxTextMargin;
|
||||
@@ -661,16 +629,7 @@ export const drawLoop = function (elem, loopModel, labelText, conf) {
|
||||
* @param {any} bounds Shape of the rectangle
|
||||
*/
|
||||
export const drawBackgroundRect = function (elem, bounds) {
|
||||
const rectElem = drawRect(elem, {
|
||||
x: bounds.startx,
|
||||
y: bounds.starty,
|
||||
width: bounds.stopx - bounds.startx,
|
||||
height: bounds.stopy - bounds.starty,
|
||||
fill: bounds.fill,
|
||||
stroke: bounds.stroke,
|
||||
class: 'rect',
|
||||
});
|
||||
rectElem.lower();
|
||||
svgDrawCommon.drawBackgroundRect(elem, bounds);
|
||||
};
|
||||
|
||||
export const insertDatabaseIcon = function (elem) {
|
||||
@@ -737,6 +696,7 @@ export const insertArrowHead = function (elem) {
|
||||
.append('path')
|
||||
.attr('d', 'M 0 0 L 10 5 L 0 10 z'); // this is actual shape for arrowhead
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup arrow head and define the marker. The result is appended to the svg.
|
||||
*
|
||||
@@ -755,6 +715,7 @@ export const insertArrowFilledHead = function (elem) {
|
||||
.append('path')
|
||||
.attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z');
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup node number. The result is appended to the svg.
|
||||
*
|
||||
@@ -776,6 +737,7 @@ export const insertSequenceNumber = function (elem) {
|
||||
.attr('r', 6);
|
||||
// .style("fill", '#f00');
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup cross head and define the marker. The result is appended to the svg.
|
||||
*
|
||||
@@ -802,37 +764,6 @@ export const insertArrowCrossHead = function (elem) {
|
||||
// this is actual shape for arrowhead
|
||||
};
|
||||
|
||||
export const getTextObj = function () {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
fill: undefined,
|
||||
anchor: undefined,
|
||||
style: '#666',
|
||||
width: undefined,
|
||||
height: undefined,
|
||||
textMargin: 0,
|
||||
rx: 0,
|
||||
ry: 0,
|
||||
tspan: true,
|
||||
valign: undefined,
|
||||
};
|
||||
};
|
||||
|
||||
export const getNoteRect = function () {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
fill: '#EDF2AE',
|
||||
stroke: '#666',
|
||||
width: 100,
|
||||
anchor: 'start',
|
||||
height: 100,
|
||||
rx: 0,
|
||||
ry: 0,
|
||||
};
|
||||
};
|
||||
|
||||
const _drawTextCandidateFunc = (function () {
|
||||
/**
|
||||
* @param {any} content
|
||||
@@ -1062,8 +993,6 @@ export default {
|
||||
drawActor,
|
||||
drawBox,
|
||||
drawPopup,
|
||||
drawImage,
|
||||
drawEmbeddedImage,
|
||||
anchorElement,
|
||||
drawActivation,
|
||||
drawLoop,
|
||||
@@ -1075,8 +1004,6 @@ export default {
|
||||
insertDatabaseIcon,
|
||||
insertComputerIcon,
|
||||
insertClockIcon,
|
||||
getTextObj,
|
||||
getNoteRect,
|
||||
popupMenu,
|
||||
popdownMenu,
|
||||
fixLifeLineHeights,
|
||||
|
@@ -174,16 +174,4 @@ describe('svgDraw', function () {
|
||||
expect(rect.lower).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
describe('sanitizeUrl', function () {
|
||||
it('should sanitize malicious urls', function () {
|
||||
const maliciousStr = 'javascript:script:alert(1)';
|
||||
const result = svgDraw.sanitizeUrl(maliciousStr);
|
||||
expect(result).not.toContain('javascript:alert(1)');
|
||||
});
|
||||
it('should not sanitize non dangerous urls', function () {
|
||||
const maliciousStr = 'javajavascript:script:alert(1)';
|
||||
const result = svgDraw.sanitizeUrl(maliciousStr);
|
||||
expect(result).not.toContain('javascript:alert(1)');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,21 +1,8 @@
|
||||
import { arc as d3arc } from 'd3';
|
||||
import * as svgDrawCommon from '../common/svgDrawCommon';
|
||||
|
||||
export const drawRect = function (elem, rectData) {
|
||||
const rectElem = elem.append('rect');
|
||||
rectElem.attr('x', rectData.x);
|
||||
rectElem.attr('y', rectData.y);
|
||||
rectElem.attr('fill', rectData.fill);
|
||||
rectElem.attr('stroke', rectData.stroke);
|
||||
rectElem.attr('width', rectData.width);
|
||||
rectElem.attr('height', rectData.height);
|
||||
rectElem.attr('rx', rectData.rx);
|
||||
rectElem.attr('ry', rectData.ry);
|
||||
|
||||
if (rectData.class !== undefined) {
|
||||
rectElem.attr('class', rectData.class);
|
||||
}
|
||||
|
||||
return rectElem;
|
||||
return svgDrawCommon.drawRect(elem, rectData);
|
||||
};
|
||||
|
||||
export const drawFace = function (element, faceData) {
|
||||
@@ -128,25 +115,7 @@ export const drawCircle = function (element, circleData) {
|
||||
};
|
||||
|
||||
export const drawText = function (elem, textData) {
|
||||
// Remove and ignore br:s
|
||||
const nText = textData.text.replace(/<br\s*\/?>/gi, ' ');
|
||||
|
||||
const textElem = elem.append('text');
|
||||
textElem.attr('x', textData.x);
|
||||
textElem.attr('y', textData.y);
|
||||
textElem.attr('class', 'legend');
|
||||
|
||||
textElem.style('text-anchor', textData.anchor);
|
||||
|
||||
if (textData.class !== undefined) {
|
||||
textElem.attr('class', textData.class);
|
||||
}
|
||||
|
||||
const span = textElem.append('tspan');
|
||||
span.attr('x', textData.x + textData.textMargin * 2);
|
||||
span.text(nText);
|
||||
|
||||
return textElem;
|
||||
return svgDrawCommon.drawText(elem, textData);
|
||||
};
|
||||
|
||||
export const drawLabel = function (elem, txtObject) {
|
||||
@@ -192,7 +161,7 @@ export const drawLabel = function (elem, txtObject) {
|
||||
export const drawSection = function (elem, section, conf) {
|
||||
const g = elem.append('g');
|
||||
|
||||
const rect = getNoteRect();
|
||||
const rect = svgDrawCommon.getNoteRect();
|
||||
rect.x = section.x;
|
||||
rect.y = section.y;
|
||||
rect.fill = section.fill;
|
||||
@@ -249,7 +218,7 @@ export const drawTask = function (elem, task, conf) {
|
||||
score: task.score,
|
||||
});
|
||||
|
||||
const rect = getNoteRect();
|
||||
const rect = svgDrawCommon.getNoteRect();
|
||||
rect.x = task.x;
|
||||
rect.y = task.y;
|
||||
rect.fill = task.fill;
|
||||
@@ -298,41 +267,7 @@ export const drawTask = function (elem, task, conf) {
|
||||
* @param {any} bounds The bounds of the drawing
|
||||
*/
|
||||
export const drawBackgroundRect = function (elem, bounds) {
|
||||
const rectElem = drawRect(elem, {
|
||||
x: bounds.startx,
|
||||
y: bounds.starty,
|
||||
width: bounds.stopx - bounds.startx,
|
||||
height: bounds.stopy - bounds.starty,
|
||||
fill: bounds.fill,
|
||||
class: 'rect',
|
||||
});
|
||||
rectElem.lower();
|
||||
};
|
||||
|
||||
export const getTextObj = function () {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
fill: undefined,
|
||||
'text-anchor': 'start',
|
||||
width: 100,
|
||||
height: 100,
|
||||
textMargin: 0,
|
||||
rx: 0,
|
||||
ry: 0,
|
||||
};
|
||||
};
|
||||
|
||||
export const getNoteRect = function () {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
anchor: 'start',
|
||||
height: 100,
|
||||
rx: 0,
|
||||
ry: 0,
|
||||
};
|
||||
svgDrawCommon.drawBackgroundRect(elem, bounds);
|
||||
};
|
||||
|
||||
const _drawTextCandidateFunc = (function () {
|
||||
@@ -475,7 +410,5 @@ export default {
|
||||
drawLabel,
|
||||
drawTask,
|
||||
drawBackgroundRect,
|
||||
getTextObj,
|
||||
getNoteRect,
|
||||
initGraphics,
|
||||
};
|
||||
|
@@ -277,6 +277,23 @@ And `Link` can be one of:
|
||||
| -- | Solid |
|
||||
| .. | Dashed |
|
||||
|
||||
## Define Namespace
|
||||
|
||||
A namespace groups classes.
|
||||
|
||||
Code:
|
||||
|
||||
```mermaid-example
|
||||
classDiagram
|
||||
namespace BaseShapes {
|
||||
class Triangle
|
||||
class Rectangle {
|
||||
double width
|
||||
double height
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Cardinality / Multiplicity on relations
|
||||
|
||||
Multiplicity or cardinality in class diagrams indicates the number of instances of one class that can be linked to an instance of the other class. For example, each company will have one or more employees (not zero), and each employee currently works for zero or one companies.
|
||||
|
Reference in New Issue
Block a user