Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/117 er diagram #1308

Merged
merged 16 commits into from
Mar 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions cypress/integration/rendering/erDiagram.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/* eslint-env jest */
import { imgSnapshotTest } from '../../helpers/util';

describe('Entity Relationship Diagram', () => {
it('should render a simple ER diagram', () => {
imgSnapshotTest(
`
erDiagram
CUSTOMER !-?< ORDER : places
ORDER !-!< LINE-ITEM : contains
`,
{logLevel : 1}
);
cy.get('svg');
});

it('should render an ER diagram with a recursive relationship', () => {
imgSnapshotTest(
`
erDiagram
CUSTOMER !-?< CUSTOMER : refers
CUSTOMER !-?< ORDER : places
ORDER !-!< LINE-ITEM : contains
`,
{logLevel : 1}
);
cy.get('svg');
});

it('should render an ER diagram with multiple relationships between the same two entities', () => {
imgSnapshotTest(
`
erDiagram
CUSTOMER !-!< ADDRESS : "invoiced at"
CUSTOMER !-!< ADDRESS : "receives goods at"
`,
{logLevel : 1}
);
cy.get('svg');
});

it('should render a cyclical ER diagram', () => {
imgSnapshotTest(
`
erDiagram
A !-!< B : likes
B !-!< C : likes
C !-!< A : likes
`,
{logLevel : 1}
);
cy.get('svg');

});

it('should render a not-so-simple ER diagram', () => {
imgSnapshotTest(
`
erDiagram
DELIVERY-ADDRESS !-?< ORDER : receives
CUSTOMER >!-!< DELIVERY-ADDRESS : has
CUSTOMER !-?< ORDER : places
CUSTOMER !-?< INVOICE : "liable for"
INVOICE !-!< ORDER : covers
ORDER !-!< ORDER-ITEM : includes
PRODUCT-CATEGORY !-!< PRODUCT : contains
PRODUCT !-?< ORDER-ITEM : "ordered in"
`,
{logLevel : 1}
);
cy.get('svg');
});

it('should render multiple ER diagrams', () => {
imgSnapshotTest(
[
`
erDiagram
CUSTOMER !-?< ORDER : places
ORDER !-!< LINE-ITEM : contains
`,
`
erDiagram
CUSTOMER !-?< ORDER : places
ORDER !-!< LINE-ITEM : contains
`
],
{logLevel : 1}
);
cy.get('svg');
});
});
31 changes: 31 additions & 0 deletions docs/entityRelationshipDiagram.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Entity Relationship Diagrams

> An entity–relationship 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.

Mermaid can render ER diagrams
```
erDiagram
CUSTOMER !-?< ORDER : places
ORDER !-!< LINE-ITEM : contains
```
```mermaid
erDiagram
CUSTOMER !-?< ORDER : places
ORDER !-!< LINE-ITEM : contains
```

Entity names are often capitalised, although there is no accepted standard on this, and it is not required in Mermaid.

Relationships between entities are represented by lines with end markers representing cardinality. Mermaid uses the most popular crow's foot notation. The crow's foot intuitively conveys the possibility of many instances of the entity that it connects to.

## Status

ER diagrams are a new feature in Mermaid and are **experimental**. There are likely to be a few bugs and constraints, and enhancements will be made in due course.

## Syntax

### Entities and Relationships

To be completed
83 changes: 83 additions & 0 deletions src/diagrams/er/erDb.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
*
*/
import { logger } from '../../logger';

let entities = {};
let relationships = [];
let title = '';

const Cardinality = {
ONLY_ONE_TO_ONE_OR_MORE: 'ONLY_ONE_TO_ONE_OR_MORE',
ONLY_ONE_TO_ZERO_OR_MORE: 'ONLY_ONE_TO_ZERO_OR_MORE',
ZERO_OR_ONE_TO_ZERO_OR_MORE: 'ZERO_OR_ONE_TO_ZERO_OR_MORE',
ZERO_OR_ONE_TO_ONE_OR_MORE: 'ZERO_OR_ONE_TO_ONE_OR_MORE',
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',
ONE_OR_MORE_TO_ZERO_OR_ONE: 'ONE_OR_MORE_TO_ZERO_OR_ONE',
ZERO_OR_ONE_TO_ONLY_ONE: 'ZERO_OR_ONE_TO_ONLY_ONE',
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) {
if (typeof entities[name] === 'undefined') {
entities[name] = name;
logger.debug('Added new entity :', name);
}
};

const getEntities = () => entities;

/**
* Add a relationship
* @param entA The first entity in the relationship
* @param rolA The role played by the first entity in relation to the second
* @param entB The second entity in the relationship
* @param card The cardinality of the relationship between the two entities
*/
const addRelationship = function(entA, rolA, entB, card) {
let rel = {
entityA: entA,
roleA: rolA,
entityB: entB,
cardinality: card
};

relationships.push(rel);
logger.debug('Added new relationship :', rel);
};

const getRelationships = () => relationships;

// Keep this - TODO: revisit...allow the diagram to have a title
const setTitle = function(txt) {
title = txt;
};

const getTitle = function() {
return title;
};

const clear = function() {
entities = {};
relationships = [];
title = '';
};

export default {
Cardinality,
addEntity,
getEntities,
addRelationship,
getRelationships,
clear,
setTitle,
getTitle
};
168 changes: 168 additions & 0 deletions src/diagrams/er/erMarkers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
const ERMarkers = {
ONLY_ONE_START: 'ONLY_ONE_START',
ONLY_ONE_END: 'ONLY_ONE_END',
ZERO_OR_ONE_START: 'ZERO_OR_ONE_START',
ZERO_OR_ONE_END: 'ZERO_OR_ONE_END',
ONE_OR_MORE_START: 'ONE_OR_MORE_START',
ONE_OR_MORE_END: 'ONE_OR_MORE_END',
ZERO_OR_MORE_START: 'ZERO_OR_MORE_START',
ZERO_OR_MORE_END: 'ZERO_OR_MORE_END'
};

/**
* Put the markers into the svg DOM for later use with edge paths
*/
const insertMarkers = function(elem, conf) {
let marker;

elem
.append('defs')
.append('marker')
.attr('id', ERMarkers.ONLY_ONE_START)
.attr('refX', 0)
.attr('refY', 9)
.attr('markerWidth', 18)
.attr('markerHeight', 18)
.attr('orient', 'auto')
.append('path')
.attr('stroke', conf.stroke)
.attr('fill', 'none')
.attr('d', 'M9,0 L9,18 M15,0 L15,18');

elem
.append('defs')
.append('marker')
.attr('id', ERMarkers.ONLY_ONE_END)
.attr('refX', 18)
.attr('refY', 9)
.attr('markerWidth', 18)
.attr('markerHeight', 18)
.attr('orient', 'auto')
.append('path')
.attr('stroke', conf.stroke)
.attr('fill', 'none')
.attr('d', 'M3,0 L3,18 M9,0 L9,18');

marker = elem
.append('defs')
.append('marker')
.attr('id', ERMarkers.ZERO_OR_ONE_START)
.attr('refX', 0)
.attr('refY', 9)
.attr('markerWidth', 30)
.attr('markerHeight', 18)
.attr('orient', 'auto');
marker
.append('circle')
.attr('stroke', conf.stroke)
.attr('fill', 'white')
.attr('cx', 21)
.attr('cy', 9)
.attr('r', 6);
marker
.append('path')
.attr('stroke', conf.stroke)
.attr('fill', 'none')
.attr('d', 'M9,0 L9,18');

marker = elem
.append('defs')
.append('marker')
.attr('id', ERMarkers.ZERO_OR_ONE_END)
.attr('refX', 30)
.attr('refY', 9)
.attr('markerWidth', 30)
.attr('markerHeight', 18)
.attr('orient', 'auto');
marker
.append('circle')
.attr('stroke', conf.stroke)
.attr('fill', 'white')
.attr('cx', 9)
.attr('cy', 9)
.attr('r', 6);
marker
.append('path')
.attr('stroke', conf.stroke)
.attr('fill', 'none')
.attr('d', 'M21,0 L21,18');

elem
.append('defs')
.append('marker')
.attr('id', ERMarkers.ONE_OR_MORE_START)
.attr('refX', 18)
.attr('refY', 18)
.attr('markerWidth', 45)
.attr('markerHeight', 36)
.attr('orient', 'auto')
.append('path')
.attr('stroke', conf.stroke)
.attr('fill', 'none')
.attr('d', 'M0,18 Q 18,0 36,18 Q 18,36 0,18 M42,9 L42,27');

elem
.append('defs')
.append('marker')
.attr('id', ERMarkers.ONE_OR_MORE_END)
.attr('refX', 27)
.attr('refY', 18)
.attr('markerWidth', 45)
.attr('markerHeight', 36)
.attr('orient', 'auto')
.append('path')
.attr('stroke', conf.stroke)
.attr('fill', 'none')
.attr('d', 'M3,9 L3,27 M9,18 Q27,0 45,18 Q27,36 9,18');

marker = elem
.append('defs')
.append('marker')
.attr('id', ERMarkers.ZERO_OR_MORE_START)
.attr('refX', 18)
.attr('refY', 18)
.attr('markerWidth', 57)
.attr('markerHeight', 36)
.attr('orient', 'auto');
marker
.append('circle')
.attr('stroke', conf.stroke)
.attr('fill', 'white')
.attr('cx', 48)
.attr('cy', 18)
.attr('r', 6);
marker
.append('path')
.attr('stroke', conf.stroke)
.attr('fill', 'none')
.attr('d', 'M0,18 Q18,0 36,18 Q18,36 0,18');

marker = elem
.append('defs')
.append('marker')
.attr('id', ERMarkers.ZERO_OR_MORE_END)
.attr('refX', 39)
.attr('refY', 18)
.attr('markerWidth', 57)
.attr('markerHeight', 36)
.attr('orient', 'auto');
marker
.append('circle')
.attr('stroke', conf.stroke)
.attr('fill', 'white')
.attr('cx', 9)
.attr('cy', 18)
.attr('r', 6);
marker
.append('path')
.attr('stroke', conf.stroke)
.attr('fill', 'none')
.attr('d', 'M21,18 Q39,0 57,18 Q39,36 21,18');

return;
};

export default {
ERMarkers,
insertMarkers
};
Loading