Merge branch 'develop' into feature/1295_generic_rendering_engine

This commit is contained in:
Knut Sveidqvist 2020-03-30 19:15:17 +02:00
commit a5af248715
11 changed files with 297 additions and 266 deletions

View File

@ -6,8 +6,8 @@ describe('Entity Relationship Diagram', () => {
imgSnapshotTest( imgSnapshotTest(
` `
erDiagram erDiagram
CUSTOMER !-?< ORDER : places CUSTOMER ||--o{ ORDER : places
ORDER !-!< LINE-ITEM : contains ORDER ||--|{ LINE-ITEM : contains
`, `,
{logLevel : 1} {logLevel : 1}
); );
@ -18,9 +18,9 @@ describe('Entity Relationship Diagram', () => {
imgSnapshotTest( imgSnapshotTest(
` `
erDiagram erDiagram
CUSTOMER !-?< CUSTOMER : refers CUSTOMER ||..o{ CUSTOMER : refers
CUSTOMER !-?< ORDER : places CUSTOMER ||--o{ ORDER : places
ORDER !-!< LINE-ITEM : contains ORDER ||--|{ LINE-ITEM : contains
`, `,
{logLevel : 1} {logLevel : 1}
); );
@ -31,8 +31,8 @@ describe('Entity Relationship Diagram', () => {
imgSnapshotTest( imgSnapshotTest(
` `
erDiagram erDiagram
CUSTOMER !-!< ADDRESS : "invoiced at" CUSTOMER ||--|{ ADDRESS : "invoiced at"
CUSTOMER !-!< ADDRESS : "receives goods at" CUSTOMER ||--|{ ADDRESS : "receives goods at"
`, `,
{logLevel : 1} {logLevel : 1}
); );
@ -43,28 +43,27 @@ describe('Entity Relationship Diagram', () => {
imgSnapshotTest( imgSnapshotTest(
` `
erDiagram erDiagram
A !-!< B : likes A ||--|{ B : likes
B !-!< C : likes B ||--|{ C : likes
C !-!< A : likes C ||--|{ A : likes
`, `,
{logLevel : 1} {logLevel : 1}
); );
cy.get('svg'); cy.get('svg');
}); });
it('should render a not-so-simple ER diagram', () => { it('should render a not-so-simple ER diagram', () => {
imgSnapshotTest( imgSnapshotTest(
` `
erDiagram erDiagram
DELIVERY-ADDRESS !-?< ORDER : receives CUSTOMER }|..|{ DELIVERY-ADDRESS : has
CUSTOMER >!-!< DELIVERY-ADDRESS : has CUSTOMER ||--o{ ORDER : places
CUSTOMER !-?< ORDER : places CUSTOMER ||--o{ INVOICE : "liable for"
CUSTOMER !-?< INVOICE : "liable for" DELIVERY-ADDRESS ||--o{ ORDER : receives
INVOICE !-!< ORDER : covers INVOICE ||--|{ ORDER : covers
ORDER !-!< ORDER-ITEM : includes ORDER ||--|{ ORDER-ITEM : includes
PRODUCT-CATEGORY !-!< PRODUCT : contains PRODUCT-CATEGORY ||--|{ PRODUCT : contains
PRODUCT !-?< ORDER-ITEM : "ordered in" PRODUCT ||--o{ ORDER-ITEM : "ordered in"
`, `,
{logLevel : 1} {logLevel : 1}
); );
@ -76,13 +75,13 @@ describe('Entity Relationship Diagram', () => {
[ [
` `
erDiagram erDiagram
CUSTOMER !-?< ORDER : places CUSTOMER ||--o{ ORDER : places
ORDER !-!< LINE-ITEM : contains ORDER ||--|{ LINE-ITEM : contains
`, `,
` `
erDiagram erDiagram
CUSTOMER !-?< ORDER : places CUSTOMER ||--o{ ORDER : places
ORDER !-!< LINE-ITEM : contains ORDER ||--|{ LINE-ITEM : contains
` `
], ],
{logLevel : 1} {logLevel : 1}

View File

@ -2,18 +2,20 @@
> An entityrelationship model (or ER model) describes interrelated things of interest in a specific domain of knowledge. A basic ER model is composed of entity types (which classify the things of interest) and specifies relationships that can exist between entities (instances of those entity types). Wikipedia. > An entityrelationship model (or ER model) describes interrelated things of interest in a specific domain of knowledge. A basic ER model is composed of entity types (which classify the things of interest) and specifies relationships that can exist between entities (instances of those entity types). Wikipedia.
Note that practitioners of ER modelling almost always refer to entity types simply as entities. For example the CUSTOMER entity type would be referred to simply as the CUSTOMER entity. This is so common it would be inadvisable to do anything else, but technically an entity is an abstract instance of an entity type, and this is what an ER diagram shows - abstract instances, and the relationships between them. This is why entities are always named using singular nouns. Note that practitioners of ER modelling almost always refer to entity types simply as entities. For example the CUSTOMER entity type would be referred to simply as the CUSTOMER entity. This is so common it would be inadvisable to do anything else, but technically an entity is an abstract *instance* of an entity type, and this is what an ER diagram shows - abstract instances, and the relationships between them. This is why entities are always named using singular nouns.
Mermaid can render ER diagrams Mermaid can render ER diagrams
``` ```
erDiagram erDiagram
CUSTOMER !-?< ORDER : places CUSTOMER ||--o{ ORDER : places
ORDER !-!< LINE-ITEM : contains ORDER ||--|{ LINE-ITEM : contains
CUSTOMER }|..|{ : DELIVERY-ADDRESS : uses
``` ```
```mermaid ```mermaid
erDiagram erDiagram
CUSTOMER !-?< ORDER : places CUSTOMER ||--o{ ORDER : places
ORDER !-!< LINE-ITEM : contains ORDER ||--|{ LINE-ITEM : contains
CUSTOMER }|..|{ : DELIVERY-ADDRESS : uses
``` ```
Entity names are often capitalised, although there is no accepted standard on this, and it is not required in Mermaid. Entity names are often capitalised, although there is no accepted standard on this, and it is not required in Mermaid.
@ -28,4 +30,51 @@ ER diagrams are a new feature in Mermaid and are **experimental**. There are li
### Entities and Relationships ### Entities and Relationships
To be completed Mermaid syntax for ER diagrams is compatible with PlantUML, with an extension to label the relationship. Each statement consists of the following parts, all of which are mandatory:
```
<first-entity> <relationship> <second-entity> : <relationship-label>
```
Where:
- `first-entity` is the name of an entity. Names must begin with an alphabetic character and may also contain digits and hyphens
- `relationship` describes the way that both entities inter-relate. See below.
- `second-entity` is the name of the other entity
- `relationship-label` describes the relationship from the perspective of the first entity.
For example:
```
PROPERTY ||--|{ ROOM : contains
```
This statement can be read as *a property contains one or more rooms, and a room is part of one and only one property*. You can see that the label here is from the first entity's perspective: a property contains a room, but a room does not contain a property. When considered from the perspective of the second entity, the equivalent label is usually very easy to infer. (Some ER diagrams label relationships from both perspectives, but this is not supported here, and is usually superfluous).
### Relationship Syntax
The `relationship` part of each statement can be broken down into three sub-components:
- the cardinality of the first entity with respect to the second,
- whether the relationship confers identity on a 'child' entity
- the cardinality of the second entity with respect to the first
Cardinality is a property that describes how many elements of another entity can be related to the entity in question. In the above example a `PROPERTY` can have one or more `ROOM` instances associated to it, whereas a `ROOM` can only be associated with one `PROPERTY`. In each cardinality marker there are two characters. The outermost character represents a maximum value, and the innermost character represents a minimum value. The table below summarises possible cardinalities.
| Value (left) | Value (right) | Meaning |
|:------------:|:-------------:|--------------------------------------------------------|
| `|o` | `o|` | Zero or one |
| `||` | `||` | Exactly one |
| `}o` | `o{` | Zero or more (no upper limit) |
| `}|` | `|{` | One or more (no upper limit) |
### Identification
Relationships may be classified as either *identifying* or *non-identifying* and these are rendered with either solid or dashed lines respectively. This is relevant when one of the entities in question can not have independent existence without the other. For example a firm that insures people to drive cars might need to store data on `NAMED-DRIVER`s. In modelling this we might start out by observing that a `CAR` can be driven by many `PERSON` instances, and a `PERSON` can drive many `CAR`s - both entities can exist without the other, so this is a non-identifying relationship that we might specify in Mermaid as: `PERSON }|..|{ CAR : "driver"`. Note the two dots in the middle of the relationship that will result in a dashed line being drawn between the two entities. But when this many-to-many relationship is resolved into two one-to-many relationships, we observe that a `NAMED-DRIVER` cannot exist without both a `PERSON` and a `CAR` - the relationships become identifying and would be specified using hyphens, which translate to a solid line:
```
CAR ||--o{ NAMED-DRIVER : allows
PERSON ||--o{ NAMED-DRIVER : is
```
### Other Things
- If you want the relationship label to be more than one word, you must use double quotes around the phrase
- If you don't want a label at all on a relationship, you must use an empty double-quoted string

View File

@ -441,10 +441,10 @@ Examples of tooltip usage below:
``` ```
<script> <script>
var callback = function(){ var callback = function(){
alert('A callback was triggered'); alert('A callback was triggered');
} }
<script> </script>
``` ```
``` ```
@ -464,28 +464,30 @@ graph LR;
``` ```
> **Success** The tooltip functionality and the ability to link to urls are available from version 0.5.2. > **Success** The tooltip functionality and the ability to link to urls are available from version 0.5.2.
?> Due to limitations with how Docsify handles JavaScript callback functions, an alternate working demo for the above code can be viewed at [this jsfiddle](https://jsfiddle.net/s37cjoau/3/).
Beginners tip, a full example using interactive links in a html context: Beginners tip, a full example using interactive links in a html context:
``` ```
<body> <body>
<div class="mermaid"> <div class="mermaid">
graph LR; graph LR;
A-->B; A-->B;
click A callback "Tooltip" click A callback "Tooltip"
click B "http://www.github.com" "This is a link" click B "http://www.github.com" "This is a link"
</div> </div>
<script> <script>
var callback = function(){ var callback = function(){
alert('A callback was triggered'); alert('A callback was triggered');
} }
var config = { var config = {
startOnLoad:true, startOnLoad:true,
flowchart:{ flowchart:{
useMaxWidth:true, useMaxWidth:true,
htmlLabels:true, htmlLabels:true,
curve:'cardinal', curve:'cardinal',
}, },
securityLevel:'loose', securityLevel:'loose',
}; };
mermaid.initialize(config); mermaid.initialize(config);

View File

@ -174,6 +174,14 @@ margin around notes.
Space between messages. Space between messages.
**Default value 35**. **Default value 35**.
### messageAlign
Multiline message alignment. Possible values are:
- left
- center **default**
- right
### mirrorActors ### mirrorActors
mirror actors under diagram. mirror actors under diagram.

View File

@ -8,22 +8,15 @@ let relationships = [];
let title = ''; let title = '';
const Cardinality = { const Cardinality = {
ONLY_ONE_TO_ONE_OR_MORE: 'ONLY_ONE_TO_ONE_OR_MORE', ZERO_OR_ONE: 'ZERO_OR_ONE',
ONLY_ONE_TO_ZERO_OR_MORE: 'ONLY_ONE_TO_ZERO_OR_MORE', ZERO_OR_MORE: 'ZERO_OR_MORE',
ZERO_OR_ONE_TO_ZERO_OR_MORE: 'ZERO_OR_ONE_TO_ZERO_OR_MORE', ONE_OR_MORE: 'ONE_OR_MORE',
ZERO_OR_ONE_TO_ONE_OR_MORE: 'ZERO_OR_ONE_TO_ONE_OR_MORE', ONLY_ONE: 'ONLY_ONE'
ONE_OR_MORE_TO_ONLY_ONE: 'ONE_OR_MORE_TO_ONLY_ONE', };
ZERO_OR_MORE_TO_ONLY_ONE: 'ZERO_OR_MORE_TO_ONLY_ONE',
ZERO_OR_MORE_TO_ZERO_OR_ONE: 'ZERO_OR_MORE_TO_ZERO_OR_ONE', const Identification = {
ONE_OR_MORE_TO_ZERO_OR_ONE: 'ONE_OR_MORE_TO_ZERO_OR_ONE', NON_IDENTIFYING: 'NON_IDENTIFYING',
ZERO_OR_ONE_TO_ONLY_ONE: 'ZERO_OR_ONE_TO_ONLY_ONE', IDENTIFYING: 'IDENTIFYING'
ONLY_ONE_TO_ONLY_ONE: 'ONLY_ONE_TO_ONLY_ONE',
ONLY_ONE_TO_ZERO_OR_ONE: 'ONLY_ONE_TO_ZERO_OR_ONE',
ZERO_OR_ONE_TO_ZERO_OR_ONE: 'ZERO_OR_ONE_TO_ZERO_OR_ONE',
ZERO_OR_MORE_TO_ZERO_OR_MORE: 'ZERO_OR_MORE_TO_ZERO_OR_MORE',
ZERO_OR_MORE_TO_ONE_OR_MORE: 'ZERO_OR_MORE_TO_ONE_OR_MORE',
ONE_OR_MORE_TO_ZERO_OR_MORE: 'ONE_OR_MORE_TO_ZERO_OR_MORE',
ONE_OR_MORE_TO_ONE_OR_MORE: 'ONE_OR_MORE_TO_ONE_OR_MORE'
}; };
const addEntity = function(name) { const addEntity = function(name) {
@ -40,14 +33,14 @@ const getEntities = () => entities;
* @param entA The first entity in the relationship * @param entA The first entity in the relationship
* @param rolA The role played by the first entity in relation to the second * @param rolA The role played by the first entity in relation to the second
* @param entB The second entity in the relationship * @param entB The second entity in the relationship
* @param card The cardinality of the relationship between the two entities * @param rSpec The details of the relationship between the two entities
*/ */
const addRelationship = function(entA, rolA, entB, card) { const addRelationship = function(entA, rolA, entB, rSpec) {
let rel = { let rel = {
entityA: entA, entityA: entA,
roleA: rolA, roleA: rolA,
entityB: entB, entityB: entB,
cardinality: card relSpec: rSpec
}; };
relationships.push(rel); relationships.push(rel);
@ -73,6 +66,7 @@ const clear = function() {
export default { export default {
Cardinality, Cardinality,
Identification,
addEntity, addEntity,
getEntities, getEntities,
addRelationship, addRelationship,

View File

@ -25,7 +25,7 @@ export const setConf = function(cnf) {
* Use D3 to construct the svg elements for the entities * Use D3 to construct the svg elements for the entities
* @param svgNode the svg node that contains the diagram * @param svgNode the svg node that contains the diagram
* @param entities The entities to be drawn * @param entities The entities to be drawn
* @param g The graph that contains the vertex and edge definitions post-layout * @param graph The graph that contains the vertex and edge definitions post-layout
* @return The first entity that was inserted * @return The first entity that was inserted
*/ */
const drawEntities = function(svgNode, entities, graph) { const drawEntities = function(svgNode, entities, graph) {
@ -63,7 +63,7 @@ const drawEntities = function(svgNode, entities, graph) {
const rectNode = groupNode const rectNode = groupNode
.insert('rect', '#' + textId) .insert('rect', '#' + textId)
.attr('fill', conf.fill) .attr('fill', conf.fill)
.attr('fill-opacity', conf.fillOpacity) .attr('fill-opacity', '100%')
.attr('stroke', conf.stroke) .attr('stroke', conf.stroke)
.attr('x', 0) .attr('x', 0)
.attr('y', 0) .attr('y', 0)
@ -124,6 +124,7 @@ let relCnt = 0;
* @param svg the svg node * @param svg the svg node
* @param rel the relationship to draw in the svg * @param rel the relationship to draw in the svg
* @param g the graph containing the edge information * @param g the graph containing the edge information
* @param insert the insertion point in the svg DOM (because relationships have markers that need to sit 'behind' opaque entity boxes)
*/ */
const drawRelationshipFromLayout = function(svg, rel, g, insert) { const drawRelationshipFromLayout = function(svg, rel, g, insert) {
relCnt++; relCnt++;
@ -149,6 +150,11 @@ const drawRelationshipFromLayout = function(svg, rel, g, insert) {
.attr('stroke', conf.stroke) .attr('stroke', conf.stroke)
.attr('fill', 'none'); .attr('fill', 'none');
// ...and with dashes if necessary
if (rel.relSpec.relType === erDb.Identification.NON_IDENTIFYING) {
svgPath.attr('stroke-dasharray', '8,8');
}
// TODO: Understand this better // TODO: Understand this better
let url = ''; let url = '';
if (conf.arrowMarkerAbsolute) { if (conf.arrowMarkerAbsolute) {
@ -164,106 +170,44 @@ const drawRelationshipFromLayout = function(svg, rel, g, insert) {
// Decide which start and end markers it needs. It may be possible to be more concise here // Decide which start and end markers it needs. It may be possible to be more concise here
// by reversing a start marker to make an end marker...but this will do for now // by reversing a start marker to make an end marker...but this will do for now
switch (rel.cardinality) {
case erDb.Cardinality.ONLY_ONE_TO_ONE_OR_MORE: // Note that the 'A' entity's marker is at the end of the relationship and the 'B' entity's marker is at the start
svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_START + ')'); switch (rel.relSpec.cardA) {
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_END + ')'); case erDb.Cardinality.ZERO_OR_ONE:
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_END + ')');
break; break;
case erDb.Cardinality.ONLY_ONE_TO_ZERO_OR_MORE: case erDb.Cardinality.ZERO_OR_MORE:
svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_START + ')');
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_END + ')'); svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_END + ')');
break; break;
case erDb.Cardinality.ZERO_OR_ONE_TO_ZERO_OR_MORE: case erDb.Cardinality.ONE_OR_MORE:
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_END + ')');
break;
case erDb.Cardinality.ONLY_ONE:
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_END + ')');
break;
}
switch (rel.relSpec.cardB) {
case erDb.Cardinality.ZERO_OR_ONE:
svgPath.attr( svgPath.attr(
'marker-start', 'marker-start',
'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_START + ')' 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_START + ')'
); );
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_END + ')');
break; break;
case erDb.Cardinality.ZERO_OR_ONE_TO_ONE_OR_MORE: case erDb.Cardinality.ZERO_OR_MORE:
svgPath.attr(
'marker-start',
'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_START + ')'
);
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_END + ')');
break;
case erDb.Cardinality.ONE_OR_MORE_TO_ONLY_ONE:
svgPath.attr(
'marker-start',
'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_START + ')'
);
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_END + ')');
break;
case erDb.Cardinality.ZERO_OR_MORE_TO_ONLY_ONE:
svgPath.attr( svgPath.attr(
'marker-start', 'marker-start',
'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_START + ')' 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_START + ')'
); );
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_END + ')');
break; break;
case erDb.Cardinality.ZERO_OR_MORE_TO_ZERO_OR_ONE: case erDb.Cardinality.ONE_OR_MORE:
svgPath.attr(
'marker-start',
'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_START + ')'
);
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_END + ')');
break;
case erDb.Cardinality.ONE_OR_MORE_TO_ZERO_OR_ONE:
svgPath.attr( svgPath.attr(
'marker-start', 'marker-start',
'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_START + ')' 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_START + ')'
); );
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_END + ')');
break; break;
case erDb.Cardinality.ZERO_OR_ONE_TO_ONLY_ONE: case erDb.Cardinality.ONLY_ONE:
svgPath.attr(
'marker-start',
'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_START + ')'
);
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_END + ')');
break;
case erDb.Cardinality.ONLY_ONE_TO_ONLY_ONE:
svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_START + ')'); svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_START + ')');
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_END + ')');
break;
case erDb.Cardinality.ONLY_ONE_TO_ZERO_OR_ONE:
svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_START + ')');
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_END + ')');
break;
case erDb.Cardinality.ZERO_OR_ONE_TO_ZERO_OR_ONE:
svgPath.attr(
'marker-start',
'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_START + ')'
);
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_ONE_END + ')');
break;
case erDb.Cardinality.ZERO_OR_MORE_TO_ZERO_OR_MORE:
svgPath.attr(
'marker-start',
'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_START + ')'
);
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_END + ')');
break;
case erDb.Cardinality.ZERO_OR_MORE_TO_ONE_OR_MORE:
svgPath.attr(
'marker-start',
'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_START + ')'
);
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_END + ')');
break;
case erDb.Cardinality.ONE_OR_MORE_TO_ZERO_OR_MORE:
svgPath.attr(
'marker-start',
'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_START + ')'
);
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ZERO_OR_MORE_END + ')');
break;
case erDb.Cardinality.ONE_OR_MORE_TO_ONE_OR_MORE:
svgPath.attr(
'marker-start',
'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_START + ')'
);
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONE_OR_MORE_END + ')');
break; break;
} }
@ -289,7 +233,7 @@ const drawRelationshipFromLayout = function(svg, rel, g, insert) {
// Figure out how big the opaque 'container' rectangle needs to be // Figure out how big the opaque 'container' rectangle needs to be
const labelBBox = labelNode.node().getBBox(); const labelBBox = labelNode.node().getBBox();
// Insert the opaque rectangle in front of the text label // Insert the opaque rectangle before the text label
svg svg
.insert('rect', '#' + labelId) .insert('rect', '#' + labelId)
.attr('x', labelPoint.x - labelBBox.width / 2) .attr('x', labelPoint.x - labelBBox.width / 2)
@ -384,28 +328,16 @@ export const draw = function(text, id) {
drawRelationshipFromLayout(svg, rel, g, firstEntity); drawRelationshipFromLayout(svg, rel, g, firstEntity);
}); });
const padding = 8; // TODO: move this to config const padding = conf.diagramPadding;
const svgBounds = svg.node().getBBox(); const svgBounds = svg.node().getBBox();
const width = svgBounds.width + padding * 4; const width = svgBounds.width + padding * 2;
const height = svgBounds.height + padding * 4; const height = svgBounds.height + padding * 2;
logger.debug(
`new ViewBox 0 0 ${width} ${height}`,
`translate(${padding - g._label.marginx}, ${padding - g._label.marginy})`
);
if (conf.useMaxWidth) { svg.attr('height', height);
svg.attr('width', '100%'); svg.attr('width', '100%');
svg.attr('style', `max-width: ${width}px;`); svg.attr('style', `max-width: ${width}px;`);
} else { svg.attr('viewBox', `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`);
svg.attr('height', height);
svg.attr('width', width);
}
svg.attr('viewBox', `0 0 ${width} ${height}`);
svg
.select('g')
.attr('transform', `translate(${padding - g._label.marginx}, ${padding - svgBounds.y})`);
}; // draw }; // draw
export default { export default {

View File

@ -10,27 +10,21 @@
<string>["] { this.popState(); } <string>["] { this.popState(); }
<string>[^"]* { return 'STR'; } <string>[^"]* { return 'STR'; }
"erDiagram" return 'ER_DIAGRAM'; "erDiagram" return 'ER_DIAGRAM';
\|o return 'ZERO_OR_ONE';
\}o return 'ZERO_OR_MORE';
\}\| return 'ONE_OR_MORE';
\|\| return 'ONLY_ONE';
o\| return 'ZERO_OR_ONE';
o\{ return 'ZERO_OR_MORE';
\|\{ return 'ONE_OR_MORE';
\.\. return 'NON_IDENTIFYING';
\-\- return 'IDENTIFYING';
\.\- return 'NON_IDENTIFYING';
\-\. return 'NON_IDENTIFYING';
[A-Za-z][A-Za-z0-9\-]* return 'ALPHANUM'; [A-Za-z][A-Za-z0-9\-]* return 'ALPHANUM';
\>\?\-\?\< return 'ZERO_OR_MORE_TO_ZERO_OR_MORE';
\>\?\-\!\< return 'ZERO_OR_MORE_TO_ONE_OR_MORE';
\>\!\-\!\< return 'ONE_OR_MORE_TO_ONE_OR_MORE';
\>\!\-\?\< return 'ONE_OR_MORE_TO_ZERO_OR_MORE';
\!\-\!\< return 'ONLY_ONE_TO_ONE_OR_MORE';
\!\-\?\< return 'ONLY_ONE_TO_ZERO_OR_MORE';
\?\-\?\< return 'ZERO_OR_ONE_TO_ZERO_OR_MORE';
\?\-\!\< return 'ZERO_OR_ONE_TO_ONE_OR_MORE';
\>\!\-\! return 'ONE_OR_MORE_TO_ONLY_ONE';
\>\?\-\! return 'ZERO_OR_MORE_TO_ONLY_ONE';
\>\?\-\? return 'ZERO_OR_MORE_TO_ZERO_OR_ONE';
\>\!\-\? return 'ONE_OR_MORE_TO_ZERO_OR_ONE';
\?\-\! return 'ZERO_OR_ONE_TO_ONLY_ONE';
\!\-\! return 'ONLY_ONE_TO_ONLY_ONE';
\!\-\? return 'ONLY_ONE_TO_ZERO_OR_ONE';
\?\-\? return 'ZERO_OR_ONE_TO_ZERO_OR_ONE';
. return yytext[0]; . return yytext[0];
<<EOF>> return 'EOF'; <<EOF>> return 'EOF';
/lex /lex
%start start %start start
@ -46,8 +40,8 @@ document
; ;
statement statement
: entityName relationship entityName ':' role : entityName relSpec entityName ':' role
{ {
yy.addEntity($1); yy.addEntity($1);
yy.addEntity($3); yy.addEntity($3);
yy.addRelationship($1, $5, $3, $2); yy.addRelationship($1, $5, $3, $2);
@ -55,26 +49,27 @@ statement
}; };
entityName entityName
: 'ALPHANUM' { $$ = $1; } : 'ALPHANUM' { $$ = $1; /*console.log('Entity: ' + $1);*/ }
; ;
relationship relSpec
: 'ONLY_ONE_TO_ONE_OR_MORE' { $$ = yy.Cardinality.ONLY_ONE_TO_ONE_OR_MORE; } : cardinality relType cardinality
| 'ONLY_ONE_TO_ZERO_OR_MORE' { $$ = yy.Cardinality.ONLY_ONE_TO_ZERO_OR_MORE; } {
| 'ZERO_OR_ONE_TO_ZERO_OR_MORE' { $$ = yy.Cardinality.ZERO_OR_ONE_TO_ZERO_OR_MORE; } $$ = { cardA: $3, relType: $2, cardB: $1 };
| 'ZERO_OR_ONE_TO_ONE_OR_MORE' { $$ = yy.Cardinality.ZERO_OR_ONE_TO_ONE_OR_MORE; } /*console.log('relSpec: ' + $3 + $2 + $1);*/
| 'ONE_OR_MORE_TO_ONLY_ONE' { $$ = yy.Cardinality.ONE_OR_MORE_TO_ONLY_ONE; } }
| 'ZERO_OR_MORE_TO_ONLY_ONE' { $$ = yy.Cardinality.ZERO_OR_MORE_TO_ONLY_ONE; } ;
| 'ZERO_OR_MORE_TO_ZERO_OR_ONE' { $$ = yy.Cardinality.ZERO_OR_MORE_TO_ZERO_OR_ONE; }
| 'ONE_OR_MORE_TO_ZERO_OR_ONE' { $$ = yy.Cardinality.ONE_OR_MORE_TO_ZERO_OR_ONE; } cardinality
| 'ZERO_OR_ONE_TO_ONLY_ONE' { $$ = yy.Cardinality.ZERO_OR_ONE_TO_ONLY_ONE; } : 'ZERO_OR_ONE' { $$ = yy.Cardinality.ZERO_OR_ONE; }
| 'ONLY_ONE_TO_ONLY_ONE' { $$ = yy.Cardinality.ONLY_ONE_TO_ONLY_ONE; } | 'ZERO_OR_MORE' { $$ = yy.Cardinality.ZERO_OR_MORE; }
| 'ONLY_ONE_TO_ZERO_OR_ONE' { $$ = yy.Cardinality.ONLY_ONE_TO_ZERO_OR_ONE; } | 'ONE_OR_MORE' { $$ = yy.Cardinality.ONE_OR_MORE; }
| 'ZERO_OR_ONE_TO_ZERO_OR_ONE' { $$ = yy.Cardinality.ZERO_OR_ONE_TO_ZERO_OR_ONE; } | 'ONLY_ONE' { $$ = yy.Cardinality.ONLY_ONE; }
| 'ZERO_OR_MORE_TO_ZERO_OR_MORE' { $$ = yy.Cardinality.ZERO_OR_MORE_TO_ZERO_OR_MORE; } ;
| 'ZERO_OR_MORE_TO_ONE_OR_MORE' { $$ = yy.Cardinality.ZERO_OR_MORE_TO_ONE_OR_MORE; }
| 'ONE_OR_MORE_TO_ONE_OR_MORE' { $$ = yy.Cardinality.ONE_OR_MORE_TO_ONE_OR_MORE; } relType
| 'ONE_OR_MORE_TO_ZERO_OR_MORE' { $$ = yy.Cardinality.ONE_OR_MORE_TO_ZERO_OR_MORE; } : 'NON_IDENTIFYING' { $$ = yy.Identification.NON_IDENTIFYING; }
| 'IDENTIFYING' { $$ = yy.Identification.IDENTIFYING; }
; ;
role role

View File

@ -15,7 +15,7 @@ describe('when parsing ER diagram it...', function() {
}); });
it('should associate two entities correctly', function() { it('should associate two entities correctly', function() {
erDiagram.parser.parse('erDiagram\nCAR !-?< DRIVER : "insured for"'); erDiagram.parser.parse('erDiagram\nCAR ||--o{ DRIVER : "insured for"');
const entities = erDb.getEntities(); const entities = erDb.getEntities();
const relationships = erDb.getRelationships(); const relationships = erDb.getRelationships();
const carEntity = entities.CAR; const carEntity = entities.CAR;
@ -24,12 +24,14 @@ describe('when parsing ER diagram it...', function() {
expect(carEntity).toBe('CAR'); expect(carEntity).toBe('CAR');
expect(driverEntity).toBe('DRIVER'); expect(driverEntity).toBe('DRIVER');
expect(relationships.length).toBe(1); expect(relationships.length).toBe(1);
expect(relationships[0].cardinality).toBe(erDb.Cardinality.ONLY_ONE_TO_ZERO_OR_MORE); expect(relationships[0].relSpec.cardA).toBe(erDb.Cardinality.ZERO_OR_MORE);
expect(relationships[0].relSpec.cardB).toBe(erDb.Cardinality.ONLY_ONE);
expect(relationships[0].relSpec.relType).toBe(erDb.Identification.IDENTIFYING);
}); });
it('should not create duplicate entities', function() { it('should not create duplicate entities', function() {
const line1 = 'CAR !-?< DRIVER : "insured for"'; const line1 = 'CAR ||--o{ DRIVER : "insured for"';
const line2 = 'DRIVER !-! LICENSE : has'; const line2 = 'DRIVER ||--|| LICENSE : has';
erDiagram.parser.parse(`erDiagram\n${line1}\n${line2}`); erDiagram.parser.parse(`erDiagram\n${line1}\n${line2}`);
const entities = erDb.getEntities(); const entities = erDb.getEntities();
@ -38,7 +40,7 @@ describe('when parsing ER diagram it...', function() {
it('should create the role specified', function() { it('should create the role specified', function() {
const teacherRole = 'is teacher of'; const teacherRole = 'is teacher of';
const line1 = `TEACHER >?-?< STUDENT : "${teacherRole}"`; const line1 = `TEACHER }o--o{ STUDENT : "${teacherRole}"`;
erDiagram.parser.parse(`erDiagram\n${line1}`); erDiagram.parser.parse(`erDiagram\n${line1}`);
const rels = erDb.getRelationships(); const rels = erDb.getRelationships();
@ -46,13 +48,13 @@ describe('when parsing ER diagram it...', function() {
}); });
it('should allow recursive relationships', function() { it('should allow recursive relationships', function() {
erDiagram.parser.parse('erDiagram\nNODE !-?< NODE : "leads to"'); erDiagram.parser.parse('erDiagram\nNODE ||--o{ NODE : "leads to"');
expect(Object.keys(erDb.getEntities()).length).toBe(1); expect(Object.keys(erDb.getEntities()).length).toBe(1);
}); });
it('should allow more than one relationship between the same two entities', function() { it('should allow more than one relationship between the same two entities', function() {
const line1 = 'CAR !-?< PERSON : "insured for"'; const line1 = 'CAR ||--o{ PERSON : "insured for"';
const line2 = 'CAR >?-! PERSON : "owned by"'; const line2 = 'CAR }o--|| PERSON : "owned by"';
erDiagram.parser.parse(`erDiagram\n${line1}\n${line2}`); erDiagram.parser.parse(`erDiagram\n${line1}\n${line2}`);
const entities = erDb.getEntities(); const entities = erDb.getEntities();
const rels = erDb.getRelationships(); const rels = erDb.getRelationships();
@ -69,149 +71,178 @@ describe('when parsing ER diagram it...', function() {
/* TODO */ /* TODO */
}); });
it('should handle only-one-to-one-or-more relationships', function() { it('should handle only-one-to-one-or-more relationships', function() {
erDiagram.parser.parse('erDiagram\nA !-!< B : has'); erDiagram.parser.parse('erDiagram\nA ||--|{ B : has');
const rels = erDb.getRelationships(); const rels = erDb.getRelationships();
expect(Object.keys(erDb.getEntities()).length).toBe(2); expect(Object.keys(erDb.getEntities()).length).toBe(2);
expect(rels.length).toBe(1); expect(rels.length).toBe(1);
expect(rels[0].cardinality).toBe(erDb.Cardinality.ONLY_ONE_TO_ONE_OR_MORE); expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ONE_OR_MORE);
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ONLY_ONE);
}); });
it('should handle only-one-to-zero-or-more relationships', function() { it('should handle only-one-to-zero-or-more relationships', function() {
erDiagram.parser.parse('erDiagram\nA !-?< B : has'); erDiagram.parser.parse('erDiagram\nA ||..o{ B : has');
const rels = erDb.getRelationships(); const rels = erDb.getRelationships();
expect(Object.keys(erDb.getEntities()).length).toBe(2); expect(Object.keys(erDb.getEntities()).length).toBe(2);
expect(rels.length).toBe(1); expect(rels.length).toBe(1);
expect(rels[0].cardinality).toBe(erDb.Cardinality.ONLY_ONE_TO_ZERO_OR_MORE); expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ZERO_OR_MORE);
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ONLY_ONE);
}); });
it('should handle zero-or-one-to-zero-or-more relationships', function() { it('should handle zero-or-one-to-zero-or-more relationships', function() {
erDiagram.parser.parse('erDiagram\nA ?-?< B : has'); erDiagram.parser.parse('erDiagram\nA |o..o{ B : has');
const rels = erDb.getRelationships(); const rels = erDb.getRelationships();
expect(Object.keys(erDb.getEntities()).length).toBe(2); expect(Object.keys(erDb.getEntities()).length).toBe(2);
expect(rels.length).toBe(1); expect(rels.length).toBe(1);
expect(rels[0].cardinality).toBe(erDb.Cardinality.ZERO_OR_ONE_TO_ZERO_OR_MORE); expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ZERO_OR_MORE);
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_ONE);
}); });
it('should handle zero-or-one-to-one-or-more relationships', function() { it('should handle zero-or-one-to-one-or-more relationships', function() {
erDiagram.parser.parse('erDiagram\nA ?-!< B : has'); erDiagram.parser.parse('erDiagram\nA |o--|{ B : has');
const rels = erDb.getRelationships(); const rels = erDb.getRelationships();
expect(Object.keys(erDb.getEntities()).length).toBe(2); expect(Object.keys(erDb.getEntities()).length).toBe(2);
expect(rels.length).toBe(1); expect(rels.length).toBe(1);
expect(rels[0].cardinality).toBe(erDb.Cardinality.ZERO_OR_ONE_TO_ONE_OR_MORE); expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ONE_OR_MORE);
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_ONE);
}); });
it('should handle one-or-more-to-only-one relationships', function() { it('should handle one-or-more-to-only-one relationships', function() {
erDiagram.parser.parse('erDiagram\nA >!-! B : has'); erDiagram.parser.parse('erDiagram\nA }|--|| B : has');
const rels = erDb.getRelationships(); const rels = erDb.getRelationships();
expect(Object.keys(erDb.getEntities()).length).toBe(2); expect(Object.keys(erDb.getEntities()).length).toBe(2);
expect(rels.length).toBe(1); expect(rels.length).toBe(1);
expect(rels[0].cardinality).toBe(erDb.Cardinality.ONE_OR_MORE_TO_ONLY_ONE); expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ONLY_ONE);
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ONE_OR_MORE);
}); });
it('should handle zero-or-more-to-only-one relationships', function() { it('should handle zero-or-more-to-only-one relationships', function() {
erDiagram.parser.parse('erDiagram\nA >?-! B : has'); erDiagram.parser.parse('erDiagram\nA }o--|| B : has');
const rels = erDb.getRelationships(); const rels = erDb.getRelationships();
expect(Object.keys(erDb.getEntities()).length).toBe(2); expect(Object.keys(erDb.getEntities()).length).toBe(2);
expect(rels.length).toBe(1); expect(rels.length).toBe(1);
expect(rels[0].cardinality).toBe(erDb.Cardinality.ZERO_OR_MORE_TO_ONLY_ONE); expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ONLY_ONE);
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_MORE);
}); });
it('should handle zero-or-more-to-zero-or-one relationships', function() { it('should handle zero-or-more-to-zero-or-one relationships', function() {
erDiagram.parser.parse('erDiagram\nA >?-? B : has'); erDiagram.parser.parse('erDiagram\nA }o..o| B : has');
const rels = erDb.getRelationships(); const rels = erDb.getRelationships();
expect(Object.keys(erDb.getEntities()).length).toBe(2); expect(Object.keys(erDb.getEntities()).length).toBe(2);
expect(rels.length).toBe(1); expect(rels.length).toBe(1);
expect(rels[0].cardinality).toBe(erDb.Cardinality.ZERO_OR_MORE_TO_ZERO_OR_ONE); expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ZERO_OR_ONE);
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_MORE);
}); });
it('should handle one-or-more-to-zero-or-one relationships', function() { it('should handle one-or-more-to-zero-or-one relationships', function() {
erDiagram.parser.parse('erDiagram\nA >!-? B : has'); erDiagram.parser.parse('erDiagram\nA }|..o| B : has');
const rels = erDb.getRelationships(); const rels = erDb.getRelationships();
expect(Object.keys(erDb.getEntities()).length).toBe(2); expect(Object.keys(erDb.getEntities()).length).toBe(2);
expect(rels.length).toBe(1); expect(rels.length).toBe(1);
expect(rels[0].cardinality).toBe(erDb.Cardinality.ONE_OR_MORE_TO_ZERO_OR_ONE); expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ZERO_OR_ONE);
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ONE_OR_MORE);
}); });
it('should handle zero-or-one-to-only-one relationships', function() { it('should handle zero-or-one-to-only-one relationships', function() {
erDiagram.parser.parse('erDiagram\nA ?-! B : has'); erDiagram.parser.parse('erDiagram\nA |o..|| B : has');
const rels = erDb.getRelationships(); const rels = erDb.getRelationships();
expect(Object.keys(erDb.getEntities()).length).toBe(2); expect(Object.keys(erDb.getEntities()).length).toBe(2);
expect(rels.length).toBe(1); expect(rels.length).toBe(1);
expect(rels[0].cardinality).toBe(erDb.Cardinality.ZERO_OR_ONE_TO_ONLY_ONE); expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ONLY_ONE);
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_ONE);
}); });
it('should handle only-one-to-only-one relationships', function() { it('should handle only-one-to-only-one relationships', function() {
erDiagram.parser.parse('erDiagram\nA !-! B : has'); erDiagram.parser.parse('erDiagram\nA ||..|| B : has');
const rels = erDb.getRelationships(); const rels = erDb.getRelationships();
expect(Object.keys(erDb.getEntities()).length).toBe(2); expect(Object.keys(erDb.getEntities()).length).toBe(2);
expect(rels.length).toBe(1); expect(rels.length).toBe(1);
expect(rels[0].cardinality).toBe(erDb.Cardinality.ONLY_ONE_TO_ONLY_ONE); expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ONLY_ONE);
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ONLY_ONE);
}); });
it('should handle only-one-to-zero-or-one relationships', function() { it('should handle only-one-to-zero-or-one relationships', function() {
erDiagram.parser.parse('erDiagram\nA !-? B : has'); erDiagram.parser.parse('erDiagram\nA ||--o| B : has');
const rels = erDb.getRelationships(); const rels = erDb.getRelationships();
expect(Object.keys(erDb.getEntities()).length).toBe(2); expect(Object.keys(erDb.getEntities()).length).toBe(2);
expect(rels.length).toBe(1); expect(rels.length).toBe(1);
expect(rels[0].cardinality).toBe(erDb.Cardinality.ONLY_ONE_TO_ZERO_OR_ONE); expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ZERO_OR_ONE);
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ONLY_ONE);
}); });
it('should handle zero-or-one-to-zero-or-one relationships', function() { it('should handle zero-or-one-to-zero-or-one relationships', function() {
erDiagram.parser.parse('erDiagram\nA ?-? B : has'); erDiagram.parser.parse('erDiagram\nA |o..o| B : has');
const rels = erDb.getRelationships(); const rels = erDb.getRelationships();
expect(Object.keys(erDb.getEntities()).length).toBe(2); expect(Object.keys(erDb.getEntities()).length).toBe(2);
expect(rels.length).toBe(1); expect(rels.length).toBe(1);
expect(rels[0].cardinality).toBe(erDb.Cardinality.ZERO_OR_ONE_TO_ZERO_OR_ONE); expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ZERO_OR_ONE);
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_ONE);
}); });
it('should handle zero-or-more-to-zero-or-more relationships', function() { it('should handle zero-or-more-to-zero-or-more relationships', function() {
erDiagram.parser.parse('erDiagram\nA >?-?< B : has'); erDiagram.parser.parse('erDiagram\nA }o--o{ B : has');
const rels = erDb.getRelationships(); const rels = erDb.getRelationships();
expect(Object.keys(erDb.getEntities()).length).toBe(2); expect(Object.keys(erDb.getEntities()).length).toBe(2);
expect(rels.length).toBe(1); expect(rels.length).toBe(1);
expect(rels[0].cardinality).toBe(erDb.Cardinality.ZERO_OR_MORE_TO_ZERO_OR_MORE); expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ZERO_OR_MORE);
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_MORE);
}); });
it('should handle one-or-more-to-one-or-more relationships', function() { it('should handle one-or-more-to-one-or-more relationships', function() {
erDiagram.parser.parse('erDiagram\nA >!-!< B : has'); erDiagram.parser.parse('erDiagram\nA }|..|{ B : has');
const rels = erDb.getRelationships(); const rels = erDb.getRelationships();
expect(Object.keys(erDb.getEntities()).length).toBe(2); expect(Object.keys(erDb.getEntities()).length).toBe(2);
expect(rels.length).toBe(1); expect(rels.length).toBe(1);
expect(rels[0].cardinality).toBe(erDb.Cardinality.ONE_OR_MORE_TO_ONE_OR_MORE); expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ONE_OR_MORE);
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ONE_OR_MORE);
}); });
it('should handle zero-or-more-to-one-or-more relationships', function() { it('should handle zero-or-more-to-one-or-more relationships', function() {
erDiagram.parser.parse('erDiagram\nA >?-!< B : has'); erDiagram.parser.parse('erDiagram\nA }o--|{ B : has');
const rels = erDb.getRelationships(); const rels = erDb.getRelationships();
expect(Object.keys(erDb.getEntities()).length).toBe(2); expect(Object.keys(erDb.getEntities()).length).toBe(2);
expect(rels.length).toBe(1); expect(rels.length).toBe(1);
expect(rels[0].cardinality).toBe(erDb.Cardinality.ZERO_OR_MORE_TO_ONE_OR_MORE); expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ONE_OR_MORE);
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_MORE);
}); });
it('should handle one-or-more-to-zero-or-more relationships', function() { it('should handle one-or-more-to-zero-or-more relationships', function() {
erDiagram.parser.parse('erDiagram\nA >!-?< B : has'); erDiagram.parser.parse('erDiagram\nA }|..o{ B : has');
const rels = erDb.getRelationships(); const rels = erDb.getRelationships();
expect(Object.keys(erDb.getEntities()).length).toBe(2); expect(Object.keys(erDb.getEntities()).length).toBe(2);
expect(rels.length).toBe(1); expect(rels.length).toBe(1);
expect(rels[0].cardinality).toBe(erDb.Cardinality.ONE_OR_MORE_TO_ZERO_OR_MORE); expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ZERO_OR_MORE);
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ONE_OR_MORE);
});
it('should represent identifying relationships properly', function() {
erDiagram.parser.parse('erDiagram\nHOUSE ||--|{ ROOM : contains');
const rels = erDb.getRelationships();
expect(rels[0].relSpec.relType).toBe(erDb.Identification.IDENTIFYING);
});
it('should represent non-identifying relationships properly', function() {
erDiagram.parser.parse('erDiagram\n PERSON ||..o{ POSSESSION : owns');
const rels = erDb.getRelationships();
expect(rels[0].relSpec.relType).toBe(erDb.Identification.NON_IDENTIFYING);
}); });
it('should not accept a syntax error', function() { it('should not accept a syntax error', function() {

View File

@ -24,6 +24,8 @@ const conf = {
noteMargin: 10, noteMargin: 10,
// Space between messages // Space between messages
messageMargin: 35, messageMargin: 35,
// Multiline message alignment
messageAlign: 'center',
// mirror actors under diagram // mirror actors under diagram
mirrorActors: false, mirrorActors: false,
// Depending on css styling this might need adjustment // Depending on css styling this might need adjustment
@ -230,26 +232,38 @@ const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceInde
const g = elem.append('g'); const g = elem.append('g');
const txtCenter = startx + (stopx - startx) / 2; const txtCenter = startx + (stopx - startx) / 2;
let textElem; let textElems = [];
let counterBreaklines = 0; let counterBreaklines = 0;
let breaklineOffset = 17; let breaklineOffset = 17;
const breaklines = msg.message.split(/<br\s*\/?>/gi); const breaklines = msg.message.split(/<br\s*\/?>/gi);
for (const breakline of breaklines) { for (const breakline of breaklines) {
textElem = g textElems.push(
.append('text') // text label for the x axis g
.attr('x', txtCenter) .append('text') // text label for the x axis
.attr('y', verticalPos - 7 + counterBreaklines * breaklineOffset) .attr('x', txtCenter)
.style('text-anchor', 'middle') .attr('y', verticalPos - 7 + counterBreaklines * breaklineOffset)
.attr('class', 'messageText') .style('text-anchor', 'middle')
.text(breakline.trim()); .attr('class', 'messageText')
.text(breakline.trim())
);
counterBreaklines++; counterBreaklines++;
} }
const offsetLineCounter = counterBreaklines - 1; const offsetLineCounter = counterBreaklines - 1;
const totalOffset = offsetLineCounter * breaklineOffset; const totalOffset = offsetLineCounter * breaklineOffset;
bounds.bumpVerticalPos(totalOffset); let textWidths = textElems.map(function(textElem) {
return (textElem._groups || textElem)[0][0].getBBox().width;
});
let textWidth = Math.max(...textWidths);
for (const textElem of textElems) {
if (conf.messageAlign === 'left') {
textElem.attr('x', txtCenter - textWidth / 2).style('text-anchor', 'start');
} else if (conf.messageAlign === 'right') {
textElem.attr('x', txtCenter + textWidth / 2).style('text-anchor', 'end');
}
}
let textWidth = (textElem._groups || textElem)[0][0].getBBox().width; bounds.bumpVerticalPos(totalOffset);
let line; let line;
if (startx === stopx) { if (startx === stopx) {

View File

@ -233,6 +233,14 @@ const config = {
*/ */
messageMargin: 35, messageMargin: 35,
/**
* Multiline message alignment. Possible values are:
* * left
* * center **default**
* * right
*/
messageAlign: 'center',
/** /**
* mirror actors under diagram. * mirror actors under diagram.
* **Default value true**. * **Default value true**.
@ -356,6 +364,11 @@ const config = {
* The object containing configurations specific for entity relationship diagrams * The object containing configurations specific for entity relationship diagrams
*/ */
er: { er: {
/**
* The amount of padding around the diagram as a whole so that embedded diagrams have margins, expressed in pixels
*/
diagramPadding: 20,
/** /**
* Directional bias for layout of entities. Can be either 'TB', 'BT', 'LR', or 'RL', * Directional bias for layout of entities. Can be either 'TB', 'BT', 'LR', or 'RL',
* where T = top, B = bottom, L = left, and R = right. * where T = top, B = bottom, L = left, and R = right.
@ -363,17 +376,17 @@ const config = {
layoutDirection: 'TB', layoutDirection: 'TB',
/** /**
* The mimimum width of an entity box * The mimimum width of an entity box, expressed in pixels
*/ */
minEntityWidth: 100, minEntityWidth: 100,
/** /**
* The minimum height of an entity box * The minimum height of an entity box, expressed in pixels
*/ */
minEntityHeight: 75, minEntityHeight: 75,
/** /**
* The minimum internal padding between the text in an entity box and the enclosing box borders * The minimum internal padding between the text in an entity box and the enclosing box borders, expressed in pixels
*/ */
entityPadding: 15, entityPadding: 15,
@ -387,13 +400,6 @@ const config = {
*/ */
fill: 'honeydew', fill: 'honeydew',
/**
* Opacity of entity boxes - if you want to see how the crows feet
* retain their elegant joins to the boxes regardless of the angle of incidence
* then override this to something less than 100%
*/
fillOpacity: '100%',
/** /**
* Font size * Font size
*/ */
@ -822,6 +828,7 @@ export default mermaidAPI;
* boxTextMargin:5, * boxTextMargin:5,
* noteMargin:10, * noteMargin:10,
* messageMargin:35, * messageMargin:35,
* messageAlign:'center',
* mirrorActors:true, * mirrorActors:true,
* bottomMarginAdj:1, * bottomMarginAdj:1,
* useMaxWidth:true, * useMaxWidth:true,

View File

@ -1681,9 +1681,9 @@ acorn-walk@^6.0.1:
integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA== integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==
acorn@^5.2.1, acorn@^5.5.3: acorn@^5.2.1, acorn@^5.5.3:
version "5.7.3" version "5.7.4"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e"
integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==
acorn@^6.0.1, acorn@^6.2.1: acorn@^6.0.1, acorn@^6.2.1:
version "6.4.0" version "6.4.0"