diff --git a/cypress/integration/rendering/classDiagram.spec.js b/cypress/integration/rendering/classDiagram.spec.js index 29c4aba86..b2a0f6e0a 100644 --- a/cypress/integration/rendering/classDiagram.spec.js +++ b/cypress/integration/rendering/classDiagram.spec.js @@ -163,4 +163,28 @@ describe('Class diagram', () => { ); cy.get('svg'); }); + + it('5: should render a simple class diagram with abstract method', () => { + imgSnapshotTest( + ` + classDiagram + Class01 <|-- AveryLongClass : Cool + Class01 : |someMethod() + `, + {} + ); + cy.get('svg'); + }); + + it('6: should render a simple class diagram with static method', () => { + imgSnapshotTest( + ` + classDiagram + Class01 <|-- AveryLongClass : Cool + Class01 : $someMethod() + `, + {} + ); + cy.get('svg'); + }); }); diff --git a/docs/classDiagram.md b/docs/classDiagram.md index 6e838c6cd..c86685baf 100644 --- a/docs/classDiagram.md +++ b/docs/classDiagram.md @@ -105,17 +105,10 @@ Naming convention: a class name should be composed of alphanumeric (unicode allo UML provides mechanisms to represent class members, such as attributes and methods, and additional information about them. -#### Visibility -To specify the visibility of a class member (i.e. any attribute or method), these notations may be placed before the member's name, but is it optional: +Mermaid distinguishes between attributes and functions/methods based on if the **parenthesis** `()` are present or not. The ones with `()` are treated as functions/methods, and others as attributes. -- `+` Public -- `-` Private -- `#` Protected -- `~` Package -Mermaid distinguishes between attributes and functions/methods based on if the **parenthesis** `()` are present or not. The one with `()` are treated as functions/methods, and others as attributes. - -There are two ways to define the members of a class, and regardless of the whichever syntax is used to define the members, the output will still be same. The two different ways are : +There are two ways to define the members of a class, and regardless of whichever syntax is used to define the members, the output will still be same. The two different ways are : - Associate a member of a class using **:** (colon) followed by member name, useful to define one member at a time. For example: ``` @@ -125,7 +118,7 @@ There are two ways to define the members of a class, and regardless of the which BankAccount : +deposit(amount) BankAccount : +withdrawl(amount) ``` - ```mermaid + ``` mermaid classDiagram class BankAccount BankAccount : +String owner @@ -150,7 +143,22 @@ class BankAccount{ +BigDecimal balance +deposit(amount) +withdrawl(amount) -}``` +} +``` + + +#### Visibility +To specify the visibility of a class member (i.e. any attribute or method), these notations may be placed before the member's name, but it is optional: + +- `+` Public +- `-` Private +- `#` Protected +- `~` Package + +>_note_ you can also add additional _classifers_ to a method definition using the following notations (similar to visibliity above): +> - `|` Abstract e.g.: `|someAbstractMethod()` +> - `$` Static e.g.: `$someStaticMethod()` + ## Defining Relationship A relationship is a general term covering the specific types of logical connections found on class and object diagrams. diff --git a/src/diagrams/class/classDiagram.spec.js b/src/diagrams/class/classDiagram.spec.js index a1089d3d7..7c9252dc7 100644 --- a/src/diagrams/class/classDiagram.spec.js +++ b/src/diagrams/class/classDiagram.spec.js @@ -400,5 +400,27 @@ describe('class diagram, ', function () { 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()'); + }); }); }); diff --git a/src/diagrams/class/classRenderer.js b/src/diagrams/class/classRenderer.js index 9814237c4..78a4c4332 100644 --- a/src/diagrams/class/classRenderer.js +++ b/src/diagrams/class/classRenderer.js @@ -281,10 +281,30 @@ const drawClass = function(elem, classDef) { logger.info('Rendering class ' + classDef); const addTspan = function(textEl, txt, isFirst) { + let displayText = txt; + let cssStyle = ''; + let classifier = txt.substring(0 , 1); + + switch (classifier) { + case '|': + cssStyle = 'font-style:italic;'; + displayText = txt.substring(1); + break; + case '$': + cssStyle = 'text-decoration:underline;'; + displayText = txt.substring(1); + break; + } + const tSpan = textEl .append('tspan') .attr('x', conf.padding) - .text(txt); + .text(displayText); + + if (cssStyle !== '') { + tSpan.attr('style', cssStyle); + } + if (!isFirst) { tSpan.attr('dy', conf.textHeight); }