Skip to content

Commit

Permalink
Layouts work nicely now and relationships draw more or less ok
Browse files Browse the repository at this point in the history
  • Loading branch information
dosaki committed Jul 7, 2018
1 parent bf78784 commit 9512e12
Show file tree
Hide file tree
Showing 15 changed files with 188 additions and 56 deletions.
53 changes: 41 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
# Text 2 ER Diagram
Currently **Work In Progress**!
Don't use this yet.
Currently **Work In Progress** but I guess it's ok to use.

## To do:
- [x] Draw tables
- [ ] Draw relationships
- [x] Draw relationships
- [ ] Support colours
- [ ] Probably better tests
- [ ] Support custom dimensions / positions
- [x] Support custom positions
- [ ] Support custom table dimensions

Issues:
- [x] Fix dimension calculations

# Usage

As a CLI tool it automatically converts the SVG into a PNG. In addition it will print the SVG code to the terminal.
As a CLI tool, it automatically converts the ERD into an SVG and then into a PNG. In addition it will print the SVG code to the terminal.

Usage:
```
./src/t2erd.js -i=/path/to/input.erd -o=/path/to/ouput.png
```
Expand All @@ -26,11 +27,16 @@ const svgString = t2erd(diagramTextHere);
```

# Syntax
*Borrowed some of the syntax from https://github.com/BurntSushi/erd*
## Comments
Comments start with `#` and are ignored by the parser.
```
# Comment
```
Anything to the right of a `#` is also a comment
```
Foo # Comment
```

## Tables
Table definitions start with a name between square brackets (`[Table Name]`) and end when the file ends or when another table definition starts.
Expand All @@ -43,26 +49,46 @@ column
+foreign key
```

You can optionally specify an alias for the table name (to make it easier for layout definition) by adding any text after a ` - ` (`space` `dash` `space`).
```
[Table1Name] - t1
*primary key
column
+foreign key
```

## Table Relationships

Table relationships are declared with a table name followed by the cardinality a double dash, the other table cardinality and the table it links to.
```
table1 *--1 table2
```
Supported cardinality characters are:
* `*` for many
* `1` for one

They can be declared anywhere even in the middle of table column definitions.

These can be declared anywhere even in the middle of table column definitions.

*Borrowed some of the syntax from https://github.com/BurntSushi/erd*
## Layouts
Any line starting with a `|` is added to a layout in order of appearance:
```
|p| |
|u|i|
```
Where `|` is the divider between the tables and between a set of `|` is a table name or alias.

They can be interrupted by other definitions.

## Example
```
# Users table that only contains login info
[user]
[user] - u
*id
username
hashed_password
# This one contains other information about the user
[user_info]
[user_info] - i
*id
+user_id
email
Expand All @@ -73,7 +99,7 @@ registered_date
user_info 1--1 user
# Posts (a thread starts on a post without a parent)
[post]
[post] - p
*id
+poster
+parent
Expand All @@ -82,4 +108,7 @@ timestamp
post *--1 post
post *--1 user
|p| |
|u|i|
```
Binary file modified diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"scripts": {
"start": "node --inspect=3001 ./src/t2erd.js -i=test/resources/diagram.erd -o=diagram.png",
"start": "node --inspect=3001 ./src/t2erd.js -i=test/resources/diagram2.erd -o=diagram2.png",
"test": "mocha"
},
"dependencies": {
Expand Down
28 changes: 15 additions & 13 deletions src/centredMatrix/centredMatrix.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const CentredMatrix = function(centreItem) {
let _items = {};

const itemUUID = (item) => {
if(!item.__uuid){
if(!!item && !item.__uuid){
item.__uuid = !!item.name ? uuidv5(item.name, _uuid_namespace) : uuidv4();
}
return item.__uuid
Expand Down Expand Up @@ -178,7 +178,7 @@ const CentredMatrix = function(centreItem) {
};

_self.includes = (item) => {
if(!item.__uuid){
if(!item || !item.__uuid){
return false;
}
return _self.items().includes(item.__uuid);
Expand All @@ -196,17 +196,19 @@ const CentredMatrix = function(centreItem) {
};

_self.push = (x, y, item) => {
fixMatrix(x, y);
_items[itemUUID(item)] = {
x: x,
y: y,
item: item
};
let existingItem = _self.get(x, y);
_positions[actual('y', y)][actual('x', x)] = item;

if (!isEmpty(existingItem)) {
delete _items[existingItem.__uuid];
if(!!item){
fixMatrix(x, y);
_items[itemUUID(item)] = {
x: x,
y: y,
item: item
};
let existingItem = _self.get(x, y);
_positions[actual('y', y)][actual('x', x)] = item;

if (!isEmpty(existingItem)) {
delete _items[existingItem.__uuid];
}
}
};

Expand Down
25 changes: 21 additions & 4 deletions src/drawer/diagram.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
const svg = require('svg-builder');

const paramUtils = require('../utils/param_utils.js');
const geometryUtils = require('../utils/geometry_utils.js');
const DiagramTable = require('./diagramTable.js');
const DiagramLayout = require('./diagramLayout.js');

const Diagram = function(parser, params){
const _self = this;
let _properties = {};
let _labelFonts = {};
let _layout = null;
_self.svg = null;
_self.tables = [];
_self.connections = [];
Expand Down Expand Up @@ -61,14 +63,14 @@ const Diagram = function(parser, params){
_self.tables.push(new DiagramTable(table, parser.relationships, _properties.tableOptions, _labelFonts));
});
_self.connections = parser.relationships;
_layout = new DiagramLayout(_self.tables, {layoutDefinition: parser.layoutDefinition});
};

const init = function (parser, params) {
preInit(parser, params);
const layout = new DiagramLayout(_self.tables);
_self.svg = setupDrawing(layout, _properties);
calculatePositions(layout, _properties, _self.svg);
draw(layout, _properties, _self.svg);
_self.svg = setupDrawing(_layout, _properties);
calculatePositions(_layout, _properties, _self.svg);
draw(_layout, _properties, _self.svg);
};

const calculateMaxTableDimensions = (tableOptions, padding) => {
Expand Down Expand Up @@ -138,6 +140,21 @@ const Diagram = function(parser, params){
const t1pos = table1.getClosestPerimeterCoordinate(table2.getLeveledCentrePosition());
const t2pos = table2.getClosestPerimeterCoordinate(table1.getLeveledCentrePosition());

const slope = geometryUtils.slope(t1pos, t2pos);
if(geometryUtils.orientation.isMostlyDiagonal(t1pos, t2pos, 0.8)){
//This is left here for debugging purposes.
}
else if(geometryUtils.orientation.isMostlyVertical(t1pos, t2pos, 2)){
const xAverage = (t1pos.x + t2pos.x)/2;
t1pos.x = xAverage;
t2pos.x = xAverage;
}
else if(geometryUtils.orientation.isMostlyHorizontal(t1pos, t2pos, 0.5)){
const yAverage = (t1pos.y + t2pos.y)/2;
t1pos.y = yAverage;
t2pos.y = yAverage;
}

const textSize = 16;
const t1size = connection.t1Cardinality == "*" ? textSize*1.5: textSize;
const t2size = connection.t2Cardinality == "*" ? textSize*1.5 : textSize;
Expand Down
25 changes: 20 additions & 5 deletions src/drawer/diagramLayout.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
const CentredMatrix = require('../centredMatrix/centredMatrix.js')

const DiagramLayout = function(tables, params){
//TODO: Use params to let people specify their own layout
const _self = this;
const _layout = new CentredMatrix(null);
let _tables = [];
Expand Down Expand Up @@ -46,12 +45,28 @@ const DiagramLayout = function(tables, params){
}
};

const init = (tables) => {
const init = (tables, params) => {
_tables = tables;
resolveRelativePositions(_tables);
if(!!params && !!params.layoutDefinition && params.layoutDefinition.length > 0){
params.layoutDefinition.forEach((row, yind) => {
row.forEach((nameOrAlias, xind) => {
const table = _tables.filter((table) => {
return table.name === nameOrAlias || table.alias === nameOrAlias;
});
if(table.length > 1){
throw new Error(`Duplicate table name or alias reference: "${nameOrAlias}"!`)
}
else if(table.length === 1){
_layout.push(xind, yind, table[0]);
}
});
});
}
else {
resolveRelativePositions(_tables);
}
};


_self.calculateDimensions = (maxTableDimensions) => {
return {
width: _layout.getWidth() * maxTableDimensions.width,
Expand Down Expand Up @@ -87,7 +102,7 @@ const DiagramLayout = function(tables, params){
return _tables;
}

init(tables);
init(tables, params);
};

module.exports = DiagramLayout;
1 change: 1 addition & 0 deletions src/drawer/diagramTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const DiagramTable = function(table, relationships, tableOptions, fontOptions){
_options = tableOptions;
_fonts = fontOptions;
_self.name = table.name;
_self.alias = table.alias;
_self.dimensions = calculateDimensions();
_self.position = {
x: !!tableOptions.x && !paramUtils.isAuto(tableOptions.x) ? tableOptions.x : null,
Expand Down
2 changes: 1 addition & 1 deletion src/parser/column.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const parserUtils = require("../utils/parser_utils.js");
const parserUtils = require("./parser_utils.js");

const Column = function(line, table){
const _self = this;
Expand Down
15 changes: 12 additions & 3 deletions src/parser/parser.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const parseUtils = require("../utils/parser_utils.js");
const parseUtils = require("./parser_utils.js");
const Table = require("./table.js")
const Relationship = require("./relationship.js")

Expand All @@ -8,20 +8,25 @@ const Parser = function(text){
let currentTable = null;
_self.tables = [];
_self.relationships = [];
_self.layoutDefinition = []

const parse = function(text) {
const lines = text.split('\n');

lines.forEach(function(rawLine){
const line = rawLine.trim();
if(!parseUtils.isCommentLine(line)){
const lineWithComments = rawLine.trim();
if(!parseUtils.isCommentLine(lineWithComments)){
const line = parseUtils.stripComments(lineWithComments);
if(parseUtils.isTableNameDefinition(line)){
currentTable = new Table(line);
_self.tables.push(currentTable);
}
else if(parseUtils.isRelationship(line)){
_self.relationships.push(new Relationship(line));
}
else if(parseUtils.isLayoutLine(line)){
_self.layoutDefinition.push(line.slice(1,-1).trim().split(parseUtils.constants.LAYOUT_LINE))
}
else if(parseUtils.isColumn(line, currentTable)){
currentTable.addColumn(line)
}
Expand All @@ -36,6 +41,10 @@ const Parser = function(text){
parse(text);
};

_self.layoutIsDefined = () => {
return _self.layoutLines.length > 0;
}

init(text);
};

Expand Down
20 changes: 18 additions & 2 deletions src/utils/parser_utils.js → src/parser/parser_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ const KEY_INDICATOR = {
primary: '*',
foreign: '+'
}
const LAYOUT_LINE = "|";
const TABLE_ALIAS_SEPARATOR = " - ";

const isTableNameDefinition = (rawLine) => {
const line = rawLine.trim();
return line.startsWith(TABLE_CHARACTER.start) && line.endsWith(TABLE_CHARACTER.end);
const splitDefinition = line.split(TABLE_ALIAS_SEPARATOR);
return splitDefinition[0].startsWith(TABLE_CHARACTER.start) && splitDefinition[0].endsWith(TABLE_CHARACTER.end);
}

const findRelationshipLine = (rawLine) => {
Expand All @@ -26,6 +29,11 @@ const isCommentLine = (rawLine) => {
return (COMMENT_CHARACTER === line[0]) || (line === "");
}

const isLayoutLine = (rawLine) => {
const line = rawLine.trim();
return LAYOUT_LINE === line[0];
}

const isRelationship = (rawLine) => {
const line = rawLine.trim();
const relationshipLine = findRelationshipLine(rawLine);
Expand All @@ -40,16 +48,24 @@ const isColumn = (rawLine, currentTable) => {
&& currentTable !== null;
}

const stripComments = (rawLine) => {
return rawLine.split(COMMENT_CHARACTER)[0].trim()
}


module.exports.isTableNameDefinition = isTableNameDefinition;
module.exports.findRelationshipLine = findRelationshipLine;
module.exports.isCommentLine = isCommentLine;
module.exports.isRelationship = isRelationship;
module.exports.isColumn = isColumn;
module.exports.isLayoutLine = isLayoutLine;
module.exports.stripComments = stripComments;
module.exports.constants = {
COMMENT_CHARACTER: COMMENT_CHARACTER,
RELATIONSHIP_LINE: RELATIONSHIP_LINE,
RELATIONSHIP_LINE_REGEX: RELATIONSHIP_LINE_REGEX,
TABLE_CHARACTER: TABLE_CHARACTER,
KEY_INDICATOR: KEY_INDICATOR
KEY_INDICATOR: KEY_INDICATOR,
LAYOUT_LINE,
TABLE_ALIAS_SEPARATOR
};
2 changes: 1 addition & 1 deletion src/parser/relationship.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const parserUtils = require("../utils/parser_utils.js");
const parserUtils = require("./parser_utils.js");

const Relationship = function(line){
const _self = this;
Expand Down
Loading

0 comments on commit 9512e12

Please sign in to comment.