diff --git a/README.md b/README.md
index 90ae1ad4c..6d5d26463 100644
--- a/README.md
+++ b/README.md
@@ -164,6 +164,7 @@ Class01 <|-- AveryLongClass : Cool
Class09 --> C2 : Where am I?
Class09 --* C3
Class09 --|> Class07
+note "I love this diagram!\nDo you love it?"
Class07 : equals()
Class07 : Object[] elementData
Class01 : size()
@@ -174,6 +175,7 @@ class Class10 {
int id
size()
}
+note for Class10 "Cool class\nI said it's very cool class!"
```
### State diagram [docs - live editor]
diff --git a/cypress/integration/rendering/classDiagram-v2.spec.js b/cypress/integration/rendering/classDiagram-v2.spec.js
index d285a9237..e36693a65 100644
--- a/cypress/integration/rendering/classDiagram-v2.spec.js
+++ b/cypress/integration/rendering/classDiagram-v2.spec.js
@@ -478,4 +478,22 @@ describe('Class diagram V2', () => {
);
cy.get('svg');
});
+
+ it('18: should render a simple class diagram with notes', () => {
+ imgSnapshotTest(
+ `
+ classDiagram-v2
+ note "I love this diagram!\nDo you love it?"
+ class Class10 {
+ <>
+ int id
+ size()
+ }
+ note for Class10 "Cool class\nI said it's very cool class!"
+
+ `,
+ { logLevel: 1, flowchart: { htmlLabels: false } }
+ );
+ cy.get('svg');
+ });
});
diff --git a/cypress/integration/rendering/classDiagram.spec.js b/cypress/integration/rendering/classDiagram.spec.js
index 8cf410d05..16601652d 100644
--- a/cypress/integration/rendering/classDiagram.spec.js
+++ b/cypress/integration/rendering/classDiagram.spec.js
@@ -407,4 +407,21 @@ describe('Class diagram', () => {
// // expect(svg).to.not.have.attr('style');
// });
// });
+
+ it('19: should render a simple class diagram with notes', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ note "I love this diagram!\nDo you love it?"
+ class Class10 {
+ <>
+ int id
+ size()
+ }
+ note for Class10 "Cool class\nI said it's very cool class!"
+ `,
+ { logLevel: 1 }
+ );
+ cy.get('svg');
+ });
});
diff --git a/docs/classDiagram.md b/docs/classDiagram.md
index 60dc6c390..f89b4b002 100644
--- a/docs/classDiagram.md
+++ b/docs/classDiagram.md
@@ -11,7 +11,9 @@ Mermaid can render class diagrams.
```mermaid-example
classDiagram
+ note "From Duck till Zebra"
Animal <|-- Duck
+ note for Duck "can fly\ncan swim\ncan dive\ncan help in debugging"
Animal <|-- Fish
Animal <|-- Zebra
Animal : +int age
@@ -35,7 +37,9 @@ classDiagram
```mermaid
classDiagram
+ note "From Duck till Zebra"
Animal <|-- Duck
+ note for Duck "can fly\ncan swim\ncan dive\ncan help in debugging"
Animal <|-- Fish
Animal <|-- Zebra
Animal : +int age
@@ -549,6 +553,10 @@ You would define these actions on a separate line after all classes have been de
- (_optional_) tooltip is a string to be displayed when hovering over element (note: The styles of the tooltip are set by the class .mermaidTooltip.)
- note: callback function will be called with the nodeId as parameter.
+## Notes
+
+It is possible to add notes on digram using `note "line1\nline2"` or note for class using `note for class "line1\nline2"`
+
### Examples
_URL Link:_
diff --git a/packages/mermaid/src/diagrams/class/classDb.js b/packages/mermaid/src/diagrams/class/classDb.js
index 6c49381c4..83ef6072b 100644
--- a/packages/mermaid/src/diagrams/class/classDb.js
+++ b/packages/mermaid/src/diagrams/class/classDb.js
@@ -16,6 +16,7 @@ const MERMAID_DOM_ID_PREFIX = 'classid-';
let relations = [];
let classes = {};
+let notes = [];
let classCounter = 0;
let funs = [];
@@ -84,6 +85,7 @@ export const lookUpDomId = function (id) {
export const clear = function () {
relations = [];
classes = {};
+ notes = [];
funs = [];
funs.push(setupToolTips);
commonClear();
@@ -100,6 +102,10 @@ export const getRelations = function () {
return relations;
};
+export const getNotes = function () {
+ return notes;
+};
+
export const addRelation = function (relation) {
log.debug('Adding relation: ' + JSON.stringify(relation));
addClass(relation.id1);
@@ -170,6 +176,15 @@ export const addMembers = function (className, members) {
}
};
+export const addNote = function (text, className) {
+ const note = {
+ id: `note${notes.length}`,
+ class: className,
+ text: text,
+ };
+ notes.push(note);
+};
+
export const cleanupLabel = function (label) {
if (label.substring(0, 1) === ':') {
return common.sanitizeText(label.substr(1).trim(), configApi.getConfig());
@@ -375,7 +390,9 @@ export default {
clear,
getClass,
getClasses,
+ getNotes,
addAnnotation,
+ addNote,
getRelations,
addRelation,
getDirection,
diff --git a/packages/mermaid/src/diagrams/class/classDiagram.spec.js b/packages/mermaid/src/diagrams/class/classDiagram.spec.js
index 3f47701e6..04a8e9bf3 100644
--- a/packages/mermaid/src/diagrams/class/classDiagram.spec.js
+++ b/packages/mermaid/src/diagrams/class/classDiagram.spec.js
@@ -529,6 +529,16 @@ foo()
parser.parse(str);
});
+
+ it('should handle "note for"', function () {
+ const str = 'classDiagram\n' + 'Class11 <|.. Class12\n' + 'note for Class11 "test"\n';
+ parser.parse(str);
+ });
+
+ it('should handle "note"', function () {
+ const str = 'classDiagram\n' + 'note "test"\n';
+ parser.parse(str);
+ });
});
describe('when fetching data from a classDiagram graph it', function () {
diff --git a/packages/mermaid/src/diagrams/class/classRenderer-v2.js b/packages/mermaid/src/diagrams/class/classRenderer-v2.js
index aa3f87208..fbc2e4833 100644
--- a/packages/mermaid/src/diagrams/class/classRenderer-v2.js
+++ b/packages/mermaid/src/diagrams/class/classRenderer-v2.js
@@ -134,6 +134,99 @@ export const addClasses = function (classes, g, _id, diagObj) {
});
};
+/**
+ * Function that adds the additional vertices (notes) found during parsing to the graph to be rendered.
+ *
+ * @param {{text: string; class: string; placement: number}[]} notes
+ * Object containing the additional vertices (notes).
+ * @param {SVGGElement} g The graph that is to be drawn.
+ * @param {number} startEdgeId starting index for note edge
+ * @param classes
+ */
+export const addNotes = function (notes, g, startEdgeId, classes) {
+ log.info(notes);
+
+ // Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
+ notes.forEach(function (note, i) {
+ const vertex = note;
+
+ /**
+ * Variable for storing the classes for the vertex
+ *
+ * @type {string}
+ */
+ let cssNoteStr = '';
+
+ const styles = { labelStyle: '', style: '' };
+
+ // Use vertex id as text in the box if no text is provided by the graph definition
+ let vertexText = vertex.text;
+
+ let radious = 0;
+ let _shape = 'note';
+ // Add the node
+ g.setNode(vertex.id, {
+ labelStyle: styles.labelStyle,
+ shape: _shape,
+ labelText: sanitizeText(vertexText),
+ noteData: vertex,
+ rx: radious,
+ ry: radious,
+ class: cssNoteStr,
+ style: styles.style,
+ id: vertex.id,
+ domId: vertex.id,
+ tooltip: '',
+ type: 'note',
+ padding: getConfig().flowchart.padding,
+ });
+
+ log.info('setNode', {
+ labelStyle: styles.labelStyle,
+ shape: _shape,
+ labelText: vertexText,
+ rx: radious,
+ ry: radious,
+ style: styles.style,
+ id: vertex.id,
+ type: 'note',
+ padding: getConfig().flowchart.padding,
+ });
+
+ if (!vertex.class || !(vertex.class in classes)) {
+ return;
+ }
+ const edgeId = startEdgeId + i;
+ const edgeData = {};
+ //Set relationship style and line type
+ edgeData.classes = 'relation';
+ edgeData.pattern = 'dotted';
+
+ edgeData.id = `edgeNote${edgeId}`;
+ // Set link type for rendering
+ edgeData.arrowhead = 'none';
+
+ log.info(`Note edge: ${JSON.stringify(edgeData)}, ${JSON.stringify(vertex)}`);
+ //Set edge extra labels
+ edgeData.startLabelRight = '';
+ edgeData.endLabelLeft = '';
+
+ //Set relation arrow types
+ edgeData.arrowTypeStart = 'none';
+ edgeData.arrowTypeEnd = 'none';
+ let style = 'fill:none';
+ let labelStyle = '';
+
+ edgeData.style = style;
+ edgeData.labelStyle = labelStyle;
+
+ edgeData.curve = interpolateToCurve(conf.curve, curveLinear);
+
+ // Add the edge to the graph
+ g.setEdge(vertex.id, vertex.class, edgeData, edgeId);
+ });
+};
+
/**
* Add edges to graph based on parsed graph definition
*
@@ -305,10 +398,12 @@ export const draw = function (text, id, _version, diagObj) {
// Fetch the vertices/nodes and edges/links from the parsed graph definition
const classes = diagObj.db.getClasses();
const relations = diagObj.db.getRelations();
+ const notes = diagObj.db.getNotes();
log.info(relations);
addClasses(classes, g, id, diagObj);
addRelations(relations, g);
+ addNotes(notes, g, relations.length + 1, classes);
// Add custom shapes
// flowChartShapes.addToRenderV2(addShape);
diff --git a/packages/mermaid/src/diagrams/class/classRenderer.js b/packages/mermaid/src/diagrams/class/classRenderer.js
index b31ddf52d..23b586192 100644
--- a/packages/mermaid/src/diagrams/class/classRenderer.js
+++ b/packages/mermaid/src/diagrams/class/classRenderer.js
@@ -208,12 +208,42 @@ export const draw = function (text, id, _version, diagObj) {
);
});
+ const notes = diagObj.db.getNotes();
+ notes.forEach(function (note) {
+ log.debug(`Adding note: ${JSON.stringify(note)}`);
+ const node = svgDraw.drawNote(diagram, note, conf, diagObj);
+ idCache[node.id] = node;
+
+ // Add nodes to the graph. The first argument is the node id. The second is
+ // metadata about the node. In this case we're going to add labels to each of
+ // our nodes.
+ g.setNode(node.id, node);
+ if (note.class && note.class in classes) {
+ g.setEdge(
+ note.id,
+ getGraphId(note.class),
+ {
+ relation: {
+ id1: note.id,
+ id2: note.class,
+ relation: {
+ type1: 'none',
+ type2: 'none',
+ lineType: 10,
+ },
+ },
+ },
+ 'DEFAULT'
+ );
+ }
+ });
+
dagre.layout(g);
g.nodes().forEach(function (v) {
if (typeof v !== 'undefined' && typeof g.node(v) !== 'undefined') {
log.debug('Node ' + v + ': ' + JSON.stringify(g.node(v)));
root
- .select('#' + diagObj.db.lookUpDomId(v))
+ .select('#' + (diagObj.db.lookUpDomId(v) || v))
.attr(
'transform',
'translate(' +
diff --git a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison
index ba0e69fba..157e3d7d8 100644
--- a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison
+++ b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison
@@ -56,6 +56,8 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
"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");
@@ -263,6 +265,7 @@ statement
| annotationStatement
| clickStatement
| cssClassStatement
+ | noteStatement
| directive
| direction
| acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); }
@@ -300,6 +303,11 @@ relationStatement
| className STR relation STR className { $$ = {id1:$1, id2:$5, relation:$3, relationTitle1:$2, relationTitle2:$4} }
;
+noteStatement
+ : NOTE_FOR className noteText { yy.addNote($3, $2); }
+ | NOTE noteText { yy.addNote($2); }
+ ;
+
relation
: relationType lineType relationType { $$={type1:$1,type2:$3,lineType:$2}; }
| lineType relationType { $$={type1:'none',type2:$2,lineType:$1}; }
@@ -351,4 +359,6 @@ alphaNumToken : UNICODE_TEXT | NUM | ALPHA;
classLiteralName : BQUOTE_STR;
+noteText : STR;
+
%%
diff --git a/packages/mermaid/src/diagrams/class/styles.js b/packages/mermaid/src/diagrams/class/styles.js
index 9e7665c58..bb5580492 100644
--- a/packages/mermaid/src/diagrams/class/styles.js
+++ b/packages/mermaid/src/diagrams/class/styles.js
@@ -80,6 +80,10 @@ g.classGroup line {
stroke-dasharray: 3;
}
+.dotted-line{
+ stroke-dasharray: 1 2;
+}
+
#compositionStart, .composition {
fill: ${options.lineColor} !important;
stroke: ${options.lineColor} !important;
diff --git a/packages/mermaid/src/diagrams/class/svgDraw.js b/packages/mermaid/src/diagrams/class/svgDraw.js
index 4be4ff2e5..35f793460 100644
--- a/packages/mermaid/src/diagrams/class/svgDraw.js
+++ b/packages/mermaid/src/diagrams/class/svgDraw.js
@@ -9,13 +9,13 @@ export const drawEdge = function (elem, path, relation, conf, diagObj) {
switch (type) {
case diagObj.db.relationType.AGGREGATION:
return 'aggregation';
- case diagObj.db.EXTENSION:
+ case diagObj.db.relationType.EXTENSION:
return 'extension';
- case diagObj.db.COMPOSITION:
+ case diagObj.db.relationType.COMPOSITION:
return 'composition';
- case diagObj.db.DEPENDENCY:
+ case diagObj.db.relationType.DEPENDENCY:
return 'dependency';
- case diagObj.db.LOLLIPOP:
+ case diagObj.db.relationType.LOLLIPOP:
return 'lollipop';
}
};
@@ -55,6 +55,9 @@ export const drawEdge = function (elem, path, relation, conf, diagObj) {
if (relation.relation.lineType == 1) {
svgPath.attr('class', 'relation dashed-line');
}
+ if (relation.relation.lineType == 10) {
+ svgPath.attr('class', 'relation dotted-line');
+ }
if (relation.relation.type1 !== 'none') {
svgPath.attr(
'marker-start',
@@ -288,6 +291,69 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
return classInfo;
};
+/**
+ * Renders a note diagram
+ *
+ * @param {SVGSVGElement} elem The element to draw it into
+ * @param {{id: string; text: string; class: string;}} note
+ * @param conf
+ * @param diagObj
+ * @todo Add more information in the JSDOC here
+ */
+export const drawNote = function (elem, note, conf, diagObj) {
+ log.debug('Rendering note ', note, conf);
+
+ const id = note.id;
+ const noteInfo = {
+ id: id,
+ text: note.text,
+ width: 0,
+ height: 0,
+ };
+
+ // add class group
+ const g = elem.append('g').attr('id', id).attr('class', 'classGroup');
+
+ // add text
+ let text = g
+ .append('text')
+ .attr('y', conf.textHeight + conf.padding)
+ .attr('x', 0);
+
+ const lines = JSON.parse(`"${note.text}"`).split('\n');
+
+ lines.forEach(function (line) {
+ log.debug(`Adding line: ${line}`);
+ text.append('tspan').text(line).attr('class', 'title').attr('dy', conf.textHeight);
+ });
+
+ const noteBox = g.node().getBBox();
+
+ const rect = g
+ .insert('rect', ':first-child')
+ .attr('x', 0)
+ .attr('y', 0)
+ .attr('width', noteBox.width + 2 * conf.padding)
+ .attr(
+ 'height',
+ noteBox.height + lines.length * conf.textHeight + conf.padding + 0.5 * conf.dividerMargin
+ );
+
+ const rectWidth = rect.node().getBBox().width;
+
+ // Center title
+ // We subtract the width of each text element from the class box width and divide it by 2
+ text.node().childNodes.forEach(function (x) {
+ x.setAttribute('x', (rectWidth - x.getBBox().width) / 2);
+ });
+
+ noteInfo.width = rectWidth;
+ noteInfo.height =
+ noteBox.height + lines.length * conf.textHeight + conf.padding + 0.5 * conf.dividerMargin;
+
+ return noteInfo;
+};
+
export const parseMember = function (text) {
const fieldRegEx = /^(\+|-|~|#)?(\w+)(~\w+~|\[\])?\s+(\w+) *(\*|\$)?$/;
const methodRegEx = /^([+|\-|~|#])?(\w+) *\( *(.*)\) *(\*|\$)? *(\w*[~|[\]]*\s*\w*~?)$/;
@@ -439,5 +505,6 @@ const parseClassifier = function (classifier) {
export default {
drawClass,
drawEdge,
+ drawNote,
parseMember,
};
diff --git a/packages/mermaid/src/docs/classDiagram.md b/packages/mermaid/src/docs/classDiagram.md
index 362e90bc6..3ca564e55 100644
--- a/packages/mermaid/src/docs/classDiagram.md
+++ b/packages/mermaid/src/docs/classDiagram.md
@@ -9,7 +9,9 @@ Mermaid can render class diagrams.
```mermaid-example
classDiagram
+ note "From Duck till Zebra"
Animal <|-- Duck
+ note for Duck "can fly\ncan swim\ncan dive\ncan help in debugging"
Animal <|-- Fish
Animal <|-- Zebra
Animal : +int age
@@ -375,6 +377,10 @@ click className href "url" "tooltip"
- (_optional_) tooltip is a string to be displayed when hovering over element (note: The styles of the tooltip are set by the class .mermaidTooltip.)
- note: callback function will be called with the nodeId as parameter.
+## Notes
+
+It is possible to add notes on digram using `note "line1\nline2"` or note for class using `note for class "line1\nline2"`
+
### Examples
_URL Link:_
diff --git a/packages/mermaid/src/utils.ts b/packages/mermaid/src/utils.ts
index a38044bd6..9d282991a 100644
--- a/packages/mermaid/src/utils.ts
+++ b/packages/mermaid/src/utils.ts
@@ -322,15 +322,10 @@ function calcLabelPosition(points: Point[]): Point {
const calcCardinalityPosition = (isRelationTypePresent, points, initialPosition) => {
let prevPoint;
- log.info('our points', points);
+ log.info(`our points ${JSON.stringify(points)}`);
if (points[0] !== initialPosition) {
points = points.reverse();
}
- points.forEach((point) => {
- totalDistance += distance(point, prevPoint);
- prevPoint = point;
- });
-
// Traverse only 25 total distance along points to find cardinality point
const distanceToCardinalityPoint = 25;