mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-28 07:03:17 +08:00
Merge pull request #973 from chris579/feature/963_class_annotations
Class diagram: annotations support
This commit is contained in:
commit
4ce523f33b
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,6 +2,7 @@
|
||||
|
||||
node_modules/
|
||||
coverage/
|
||||
.idea/
|
||||
|
||||
dist/*.js
|
||||
dist/*.map
|
||||
|
@ -86,6 +86,7 @@ Future task2 : des4, after des3, 5d
|
||||
```
|
||||
classDiagram
|
||||
Class01 <|-- AveryLongClass : Cool
|
||||
<<interface>> Class01
|
||||
Class03 *-- Class04
|
||||
Class05 o-- Class06
|
||||
Class07 .. Class08
|
||||
@ -98,6 +99,11 @@ Class01 : size()
|
||||
Class01 : int chimp
|
||||
Class01 : int gorilla
|
||||
Class08 <--> C2: Cool label
|
||||
class Class10 {
|
||||
<<service>>
|
||||
int id
|
||||
size()
|
||||
}
|
||||
```
|
||||
![Class diagram](./img/class.png)
|
||||
|
||||
|
@ -1,12 +1,13 @@
|
||||
/* eslint-env jest */
|
||||
import { imgSnapshotTest } from '../../helpers/util';
|
||||
|
||||
describe('Sequencediagram', () => {
|
||||
it('should render a simple class diagrams', () => {
|
||||
describe('Class diagram', () => {
|
||||
it('should render a simple class diagram', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
classDiagram
|
||||
Class01 <|-- AveryLongClass : Cool
|
||||
<<interface>> Class01
|
||||
Class03 *-- Class04
|
||||
Class05 o-- Class06
|
||||
Class07 .. Class08
|
||||
@ -19,6 +20,11 @@ describe('Sequencediagram', () => {
|
||||
Class01 : int chimp
|
||||
Class01 : int gorilla
|
||||
Class08 <--> C2: Cool label
|
||||
class Class10 {
|
||||
<<service>>
|
||||
int id
|
||||
test()
|
||||
}
|
||||
`,
|
||||
{}
|
||||
);
|
||||
|
@ -1,50 +1,55 @@
|
||||
import { Base64 } from 'js-base64'
|
||||
import mermaid2 from '../../src/mermaid'
|
||||
import { Base64 } from 'js-base64';
|
||||
import mermaid2 from '../../src/mermaid';
|
||||
|
||||
/**
|
||||
* ##contentLoaded
|
||||
* Callback function that is called when page is loaded. This functions fetches configuration for mermaid rendering and
|
||||
* calls init for rendering the mermaid diagrams on the page.
|
||||
*/
|
||||
const contentLoaded = function () {
|
||||
let pos = document.location.href.indexOf('?graph=')
|
||||
const contentLoaded = function() {
|
||||
let pos = document.location.href.indexOf('?graph=');
|
||||
if (pos > 0) {
|
||||
pos = pos + 7
|
||||
const graphBase64 = document.location.href.substr(pos)
|
||||
const graphObj = JSON.parse(Base64.decode(graphBase64))
|
||||
pos = pos + 7;
|
||||
const graphBase64 = document.location.href.substr(pos);
|
||||
const graphObj = JSON.parse(Base64.decode(graphBase64));
|
||||
// const graph = 'hello'
|
||||
console.log(graphObj)
|
||||
const div = document.createElement('div')
|
||||
div.id = 'block'
|
||||
div.className = 'mermaid'
|
||||
div.innerHTML = graphObj.code
|
||||
document.getElementsByTagName('body')[0].appendChild(div)
|
||||
global.mermaid.initialize(graphObj.mermaid)
|
||||
console.log(graphObj);
|
||||
const div = document.createElement('div');
|
||||
div.id = 'block';
|
||||
div.className = 'mermaid';
|
||||
div.innerHTML = graphObj.code;
|
||||
document.getElementsByTagName('body')[0].appendChild(div);
|
||||
global.mermaid.initialize(graphObj.mermaid);
|
||||
// console.log('graphObj.mermaid', graphObj.mermaid)
|
||||
global.mermaid.init()
|
||||
global.mermaid.init();
|
||||
}
|
||||
}
|
||||
const contentLoadedApi = function () {
|
||||
let pos = document.location.href.indexOf('?graph=')
|
||||
};
|
||||
const contentLoadedApi = function() {
|
||||
let pos = document.location.href.indexOf('?graph=');
|
||||
if (pos > 0) {
|
||||
pos = pos + 7
|
||||
const graphBase64 = document.location.href.substr(pos)
|
||||
const graphObj = JSON.parse(Base64.decode(graphBase64))
|
||||
pos = pos + 7;
|
||||
const graphBase64 = document.location.href.substr(pos);
|
||||
const graphObj = JSON.parse(Base64.decode(graphBase64));
|
||||
// const graph = 'hello'
|
||||
const div = document.createElement('div')
|
||||
div.id = 'block'
|
||||
div.className = 'mermaid'
|
||||
const div = document.createElement('div');
|
||||
div.id = 'block';
|
||||
div.className = 'mermaid';
|
||||
// div.innerHTML = graphObj.code
|
||||
document.getElementsByTagName('body')[0].appendChild(div)
|
||||
global.mermaid.initialize(graphObj.mermaid)
|
||||
document.getElementsByTagName('body')[0].appendChild(div);
|
||||
global.mermaid.initialize(graphObj.mermaid);
|
||||
|
||||
mermaid2.render('newid', graphObj.code, (svgCode, bindFunctions) => {
|
||||
div.innerHTML = svgCode
|
||||
mermaid2.render(
|
||||
'newid',
|
||||
graphObj.code,
|
||||
(svgCode, bindFunctions) => {
|
||||
div.innerHTML = svgCode;
|
||||
|
||||
bindFunctions(div)
|
||||
}, div)
|
||||
bindFunctions(div);
|
||||
},
|
||||
div
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof document !== 'undefined') {
|
||||
/*!
|
||||
@ -52,15 +57,15 @@ if (typeof document !== 'undefined') {
|
||||
*/
|
||||
window.addEventListener(
|
||||
'load',
|
||||
function () {
|
||||
function() {
|
||||
if (this.location.href.match('xss.html')) {
|
||||
this.console.log('Using api')
|
||||
contentLoadedApi()
|
||||
this.console.log('Using api');
|
||||
contentLoadedApi();
|
||||
} else {
|
||||
this.console.log('Not using api')
|
||||
contentLoaded()
|
||||
this.console.log('Not using api');
|
||||
contentLoaded();
|
||||
}
|
||||
},
|
||||
false
|
||||
)
|
||||
);
|
||||
}
|
||||
|
8
dist/index.html
vendored
8
dist/index.html
vendored
@ -399,6 +399,7 @@ merge newbranch
|
||||
<div class="mermaid">
|
||||
classDiagram
|
||||
Class01 <|-- AveryLongClass : Cool
|
||||
<<interface>> Class01
|
||||
Class03 "0" *-- "0..n" Class04
|
||||
Class05 "1" o-- "many" Class06
|
||||
Class07 .. Class08
|
||||
@ -411,6 +412,11 @@ Class01 : size()
|
||||
Class01 : int chimp
|
||||
Class01 : int gorilla
|
||||
Class08 <--> C2: Cool label
|
||||
class Class10 {
|
||||
<<service>>
|
||||
int id
|
||||
size()
|
||||
}
|
||||
</div>
|
||||
<script src="./mermaid.js"></script>
|
||||
<script>
|
||||
@ -443,7 +449,7 @@ Class08 <--> C2: Cool label
|
||||
const testLineEndings = (test, input) => {
|
||||
try {
|
||||
mermaid.render(test, input, () => {});
|
||||
} catch (err) {
|
||||
} catch (err) {
|
||||
console.error("Error in %s:\n\n%s", test, err);
|
||||
}
|
||||
};
|
||||
|
BIN
img/class.png
BIN
img/class.png
Binary file not shown.
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 141 KiB |
@ -6,18 +6,18 @@ let classes = {};
|
||||
/**
|
||||
* Function called by parser when a node definition has been found.
|
||||
* @param id
|
||||
* @param text
|
||||
* @param type
|
||||
* @param style
|
||||
* @public
|
||||
*/
|
||||
export const addClass = function(id) {
|
||||
if (typeof classes[id] === 'undefined') {
|
||||
classes[id] = {
|
||||
id: id,
|
||||
methods: [],
|
||||
members: []
|
||||
};
|
||||
}
|
||||
// Only add class if not exists
|
||||
if (typeof classes[id] !== 'undefined') return;
|
||||
|
||||
classes[id] = {
|
||||
id: id,
|
||||
methods: [],
|
||||
members: [],
|
||||
annotations: []
|
||||
};
|
||||
};
|
||||
|
||||
export const clear = function() {
|
||||
@ -43,13 +43,39 @@ export const addRelation = function(relation) {
|
||||
relations.push(relation);
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds an annotation to the specified class
|
||||
* Annotations mark special properties of the given type (like 'interface' or 'service')
|
||||
* @param className The class name
|
||||
* @param annotation The name of the annotation without any brackets
|
||||
* @public
|
||||
*/
|
||||
export const addAnnotation = function(className, annotation) {
|
||||
classes[className].annotations.push(annotation);
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a member to the specified class
|
||||
* @param className The class name
|
||||
* @param member The full name of the member.
|
||||
* If the member is enclosed in <<brackets>> it is treated as an annotation
|
||||
* If the member is ending with a closing bracket ) it is treated as a method
|
||||
* Otherwise the member will be treated as a normal property
|
||||
* @public
|
||||
*/
|
||||
export const addMember = function(className, member) {
|
||||
const theClass = classes[className];
|
||||
if (typeof member === 'string') {
|
||||
if (member.substr(-1) === ')') {
|
||||
theClass.methods.push(member);
|
||||
// Member can contain white spaces, we trim them out
|
||||
const memberString = member.trim();
|
||||
|
||||
if (memberString.startsWith('<<') && memberString.endsWith('>>')) {
|
||||
// Remove leading and trailing brackets
|
||||
theClass.annotations.push(memberString.substring(2, memberString.length - 2));
|
||||
} else if (memberString.endsWith(')')) {
|
||||
theClass.methods.push(memberString);
|
||||
} else {
|
||||
theClass.members.push(member);
|
||||
theClass.members.push(memberString);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -85,6 +111,7 @@ export default {
|
||||
clear,
|
||||
getClass,
|
||||
getClasses,
|
||||
addAnnotation,
|
||||
getRelations,
|
||||
addRelation,
|
||||
addMember,
|
||||
|
@ -207,5 +207,60 @@ describe('class diagram, ', function() {
|
||||
expect(relations[3].relation.type2).toBe('none');
|
||||
expect(relations[3].relation.lineType).toBe(classDb.lineType.DOTTED_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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -255,15 +255,34 @@ const drawClass = function(elem, classDef) {
|
||||
height: 0
|
||||
};
|
||||
|
||||
// add class group
|
||||
const g = elem
|
||||
.append('g')
|
||||
.attr('id', id)
|
||||
.attr('class', 'classGroup');
|
||||
|
||||
// add title
|
||||
const title = g
|
||||
.append('text')
|
||||
.attr('x', conf.padding)
|
||||
.attr('y', conf.textHeight + conf.padding)
|
||||
.text(classDef.id);
|
||||
.attr('x', 0);
|
||||
|
||||
// add annotations
|
||||
let isFirst = true;
|
||||
classDef.annotations.forEach(function(member) {
|
||||
const titleText2 = title.append('tspan').text('«' + member + '»');
|
||||
if (!isFirst) titleText2.attr('dy', conf.textHeight);
|
||||
isFirst = false;
|
||||
});
|
||||
|
||||
// add class title
|
||||
const classTitle = title
|
||||
.append('tspan')
|
||||
.text(classDef.id)
|
||||
.attr('class', 'title');
|
||||
|
||||
// If class has annotations the title needs to have an offset of the text height
|
||||
if (!isFirst) classTitle.attr('dy', conf.textHeight);
|
||||
|
||||
const titleHeight = title.node().getBBox().height;
|
||||
|
||||
@ -280,7 +299,7 @@ const drawClass = function(elem, classDef) {
|
||||
.attr('fill', 'white')
|
||||
.attr('class', 'classText');
|
||||
|
||||
let isFirst = true;
|
||||
isFirst = true;
|
||||
classDef.members.forEach(function(member) {
|
||||
addTspan(members, member, isFirst);
|
||||
isFirst = false;
|
||||
@ -309,16 +328,25 @@ const drawClass = function(elem, classDef) {
|
||||
});
|
||||
|
||||
const classBox = g.node().getBBox();
|
||||
g.insert('rect', ':first-child')
|
||||
const rect = g
|
||||
.insert('rect', ':first-child')
|
||||
.attr('x', 0)
|
||||
.attr('y', 0)
|
||||
.attr('width', classBox.width + 2 * conf.padding)
|
||||
.attr('height', classBox.height + conf.padding + 0.5 * conf.dividerMargin);
|
||||
|
||||
membersLine.attr('x2', classBox.width + 2 * conf.padding);
|
||||
methodsLine.attr('x2', classBox.width + 2 * conf.padding);
|
||||
const rectWidth = rect.node().getBBox().width;
|
||||
|
||||
classInfo.width = classBox.width + 2 * conf.padding;
|
||||
// Center title
|
||||
// We subtract the width of each text element from the class box width and divide it by 2
|
||||
title.node().childNodes.forEach(function(x) {
|
||||
x.setAttribute('x', (rectWidth - x.getBBox().width) / 2);
|
||||
});
|
||||
|
||||
membersLine.attr('x2', rectWidth);
|
||||
methodsLine.attr('x2', rectWidth);
|
||||
|
||||
classInfo.width = rectWidth;
|
||||
classInfo.height = classBox.height + conf.padding + 0.5 * conf.dividerMargin;
|
||||
|
||||
idCache[id] = classInfo;
|
||||
|
@ -21,6 +21,8 @@
|
||||
|
||||
|
||||
"class" return 'CLASS';
|
||||
"<<" return 'ANNOTATION_START';
|
||||
">>" return 'ANNOTATION_END';
|
||||
["] this.begin("string");
|
||||
<string>["] this.popState();
|
||||
<string>[^"]* return "STR";
|
||||
@ -131,7 +133,6 @@ statements
|
||||
| statement NEWLINE statements
|
||||
;
|
||||
|
||||
|
||||
className
|
||||
: alphaNumToken className { $$=$1+$2; }
|
||||
| alphaNumToken { $$=$1; }
|
||||
@ -142,6 +143,7 @@ statement
|
||||
| relationStatement LABEL { $1.title = yy.cleanupLabel($2); yy.addRelation($1); }
|
||||
| classStatement
|
||||
| methodStatement
|
||||
| annotationStatement
|
||||
;
|
||||
|
||||
classStatement
|
||||
@ -149,6 +151,10 @@ classStatement
|
||||
| CLASS className STRUCT_START members STRUCT_STOP {/*console.log($2,JSON.stringify($4));*/yy.addClass($2);yy.addMembers($2,$4);}
|
||||
;
|
||||
|
||||
annotationStatement
|
||||
: ANNOTATION_START alphaNumToken ANNOTATION_END className { yy.addAnnotation($4,$2); }
|
||||
;
|
||||
|
||||
members
|
||||
: MEMBER { $$ = [$1]; }
|
||||
| MEMBER members { $2.push($1);$$=$2;}
|
||||
|
@ -3,6 +3,10 @@ g.classGroup text {
|
||||
stroke: none;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-size: 10px;
|
||||
|
||||
.title {
|
||||
font-weight: bolder;
|
||||
}
|
||||
}
|
||||
|
||||
g.classGroup rect {
|
||||
|
Loading…
x
Reference in New Issue
Block a user