mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-28 07:03:17 +08:00
#945 Rendering of labels and new label positioning algorithm
This commit is contained in:
parent
13baa43081
commit
3cffd1e3ed
@ -13,4 +13,33 @@ describe('State diagram', () => {
|
|||||||
);
|
);
|
||||||
cy.get('svg');
|
cy.get('svg');
|
||||||
});
|
});
|
||||||
|
it('should render a simple state diagrams', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
stateDiagram
|
||||||
|
[*] --> State1
|
||||||
|
State1 --> State2
|
||||||
|
State1 --> State3
|
||||||
|
State1 --> [*]
|
||||||
|
`,
|
||||||
|
{ logLevel: 0 }
|
||||||
|
);
|
||||||
|
cy.get('svg');
|
||||||
|
});
|
||||||
|
it('should render a simple state diagrams with labels', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
stateDiagram
|
||||||
|
[*] --> State1
|
||||||
|
State1 --> State2 : Transition 1
|
||||||
|
State1 --> State3 : Transition 2
|
||||||
|
State1 --> State4 : Transition 3
|
||||||
|
State1 --> State5 : Transition 4
|
||||||
|
State2 --> State3 : Transition 5
|
||||||
|
State1 --> [*]
|
||||||
|
`,
|
||||||
|
{ logLevel: 0 }
|
||||||
|
);
|
||||||
|
cy.get('svg');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -106,7 +106,7 @@ line
|
|||||||
statement
|
statement
|
||||||
: idStatement DESCR
|
: idStatement DESCR
|
||||||
| idStatement '-->' idStatement {yy.addRelation($1, $3);}
|
| idStatement '-->' idStatement {yy.addRelation($1, $3);}
|
||||||
| idStatement '-->' idStatement DESCR
|
| idStatement '-->' idStatement DESCR {yy.addRelation($1, $3, $4.substr(1).trim());}
|
||||||
| HIDE_EMPTY
|
| HIDE_EMPTY
|
||||||
| scale WIDTH
|
| scale WIDTH
|
||||||
| COMPOSIT_STATE
|
| COMPOSIT_STATE
|
||||||
|
@ -41,7 +41,7 @@ export const getRelations = function() {
|
|||||||
return relations;
|
return relations;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addRelation = function(_id1, _id2) {
|
export const addRelation = function(_id1, _id2, title) {
|
||||||
let id1 = _id1;
|
let id1 = _id1;
|
||||||
let id2 = _id2;
|
let id2 = _id2;
|
||||||
let type1 = 'default';
|
let type1 = 'default';
|
||||||
@ -56,10 +56,10 @@ export const addRelation = function(_id1, _id2) {
|
|||||||
id2 = 'end' + startCnt;
|
id2 = 'end' + startCnt;
|
||||||
type2 = 'end';
|
type2 = 'end';
|
||||||
}
|
}
|
||||||
console.log(id1, id2);
|
console.log(id1, id2, title);
|
||||||
addState(id1, type1);
|
addState(id1, type1);
|
||||||
addState(id2, type2);
|
addState(id2, type2);
|
||||||
relations.push({ id1, id2 });
|
relations.push({ id1, id2, title });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addMember = function(className, member) {
|
export const addMember = function(className, member) {
|
||||||
|
@ -304,6 +304,24 @@ describe('state diagram, ', function() {
|
|||||||
note right of NotShooting : This is a note on a composite state
|
note right of NotShooting : This is a note on a composite state
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
parser.parse(str);
|
||||||
|
});
|
||||||
|
xit('should handle if statements', function() {
|
||||||
|
const str = `stateDiagram\n
|
||||||
|
[*] --> "Order Submitted"
|
||||||
|
if "Payment Accepted" then
|
||||||
|
-->[yes] "Pack products"
|
||||||
|
--> "Send parcel"
|
||||||
|
-right-> (*)
|
||||||
|
else
|
||||||
|
->[no] "Send error message"
|
||||||
|
-->[Cancel Order] [*]
|
||||||
|
endif
|
||||||
|
}
|
||||||
|
|
||||||
|
note right of NotShooting : This is a note on a composite state
|
||||||
|
`;
|
||||||
|
|
||||||
parser.parse(str);
|
parser.parse(str);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -4,6 +4,7 @@ import graphlib from 'graphlibrary';
|
|||||||
import { logger } from '../../logger';
|
import { logger } from '../../logger';
|
||||||
import stateDb from './stateDb';
|
import stateDb from './stateDb';
|
||||||
import { parser } from './parser/stateDiagram';
|
import { parser } from './parser/stateDiagram';
|
||||||
|
import utils from '../../utils';
|
||||||
|
|
||||||
parser.yy = stateDb;
|
parser.yy = stateDb;
|
||||||
|
|
||||||
@ -136,7 +137,7 @@ const insertMarkers = function(elem) {
|
|||||||
.attr('markerHeight', 28)
|
.attr('markerHeight', 28)
|
||||||
.attr('orient', 'auto')
|
.attr('orient', 'auto')
|
||||||
.append('path')
|
.append('path')
|
||||||
.attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z');
|
.attr('d', 'M 19,7 L9,13 L14,7 L9,1 Z');
|
||||||
};
|
};
|
||||||
const drawStart = function(elem, stateDef) {
|
const drawStart = function(elem, stateDef) {
|
||||||
logger.info('Rendering class ' + stateDef);
|
logger.info('Rendering class ' + stateDef);
|
||||||
@ -285,48 +286,67 @@ const drawEdge = function(elem, path, relation) {
|
|||||||
url = url.replace(/\)/g, '\\)');
|
url = url.replace(/\)/g, '\\)');
|
||||||
}
|
}
|
||||||
|
|
||||||
svgPath.attr(
|
// svgPath.attr(
|
||||||
'marker-start',
|
// 'marker-start',
|
||||||
'url(' + url + '#' + getRelationType(stateDb.relationType.DEPENDENCY) + 'Start' + ')'
|
// 'url(' + url + '#' + getRelationType(stateDb.relationType.DEPENDENCY) + 'Start' + ')'
|
||||||
);
|
// );
|
||||||
svgPath.attr(
|
svgPath.attr(
|
||||||
'marker-end',
|
'marker-end',
|
||||||
'url(' + url + '#' + getRelationType(stateDb.relationType.DEPENDENCY) + 'End' + ')'
|
'url(' + url + '#' + getRelationType(stateDb.relationType.DEPENDENCY) + 'End' + ')'
|
||||||
);
|
);
|
||||||
|
|
||||||
let x, y;
|
// Figure ou where to put the label given the points
|
||||||
const l = path.points.length;
|
// let x, y;
|
||||||
if (l % 2 !== 0 && l > 1) {
|
// const l = path.points.length;
|
||||||
const p1 = path.points[Math.floor(l / 2)];
|
// if (l % 2 !== 0 && l > 1) {
|
||||||
const p2 = path.points[Math.ceil(l / 2)];
|
// const p1 = path.points[Math.floor(l / 2)];
|
||||||
x = (p1.x + p2.x) / 2;
|
// const p2 = path.points[Math.ceil(l / 2)];
|
||||||
y = (p1.y + p2.y) / 2;
|
// x = (p1.x + p2.x) / 2;
|
||||||
} else {
|
// y = (p1.y + p2.y) / 2;
|
||||||
const p = path.points[Math.floor(l / 2)];
|
// } else {
|
||||||
x = p.x;
|
// const p = path.points[Math.floor(l / 2)];
|
||||||
y = p.y;
|
// x = p.x;
|
||||||
}
|
// y = p.y;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// console.log('calcLabelPosition', utils);
|
||||||
|
|
||||||
if (typeof relation.title !== 'undefined') {
|
if (typeof relation.title !== 'undefined') {
|
||||||
const g = elem.append('g').attr('class', 'classLabel');
|
const g = elem.append('g').attr('class', 'classLabel');
|
||||||
const label = g
|
const label = g
|
||||||
.append('text')
|
.append('text')
|
||||||
.attr('class', 'label')
|
.attr('class', 'label')
|
||||||
.attr('x', x)
|
|
||||||
.attr('y', y)
|
|
||||||
.attr('fill', 'red')
|
.attr('fill', 'red')
|
||||||
.attr('text-anchor', 'middle')
|
.attr('text-anchor', 'middle')
|
||||||
.text(relation.title);
|
.text(relation.title);
|
||||||
|
|
||||||
window.label = label;
|
const { x, y } = utils.calcLabelPosition(path.points);
|
||||||
const bounds = label.node().getBBox();
|
label.attr('x', x).attr('y', y);
|
||||||
|
|
||||||
|
const bounds = label.node().getBBox();
|
||||||
g.insert('rect', ':first-child')
|
g.insert('rect', ':first-child')
|
||||||
.attr('class', 'box')
|
.attr('class', 'box')
|
||||||
.attr('x', bounds.x - conf.padding / 2)
|
.attr('x', bounds.x - conf.padding / 2)
|
||||||
.attr('y', bounds.y - conf.padding / 2)
|
.attr('y', bounds.y - conf.padding / 2)
|
||||||
.attr('width', bounds.width + conf.padding)
|
.attr('width', bounds.width + conf.padding)
|
||||||
.attr('height', bounds.height + conf.padding);
|
.attr('height', bounds.height + conf.padding);
|
||||||
|
|
||||||
|
// Debug points
|
||||||
|
// path.points.forEach(point => {
|
||||||
|
// g.append('circle')
|
||||||
|
// .style('stroke', 'red')
|
||||||
|
// .style('fill', 'red')
|
||||||
|
// .attr('r', 1)
|
||||||
|
// .attr('cx', point.x)
|
||||||
|
// .attr('cy', point.y);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// g.append('circle')
|
||||||
|
// .style('stroke', 'blue')
|
||||||
|
// .style('fill', 'blue')
|
||||||
|
// .attr('r', 1)
|
||||||
|
// .attr('cx', x)
|
||||||
|
// .attr('cy', y);
|
||||||
}
|
}
|
||||||
|
|
||||||
edgeCount++;
|
edgeCount++;
|
||||||
@ -338,7 +358,7 @@ const drawEdge = function(elem, path, relation) {
|
|||||||
* @param {*} stateDef
|
* @param {*} stateDef
|
||||||
*/
|
*/
|
||||||
const drawState = function(elem, stateDef) {
|
const drawState = function(elem, stateDef) {
|
||||||
logger.info('Rendering class ' + stateDef);
|
// logger.info('Rendering class ' + stateDef);
|
||||||
|
|
||||||
const addTspan = function(textEl, txt, isFirst) {
|
const addTspan = function(textEl, txt, isFirst) {
|
||||||
const tSpan = textEl
|
const tSpan = textEl
|
||||||
@ -416,14 +436,11 @@ export const draw = function(text, id) {
|
|||||||
// metadata about the node. In this case we're going to add labels to each of
|
// metadata about the node. In this case we're going to add labels to each of
|
||||||
// our nodes.
|
// our nodes.
|
||||||
graph.setNode(node.id, node);
|
graph.setNode(node.id, node);
|
||||||
logger.info('Org height: ' + node.height);
|
// logger.info('Org height: ' + node.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
const relations = stateDb.getRelations();
|
const relations = stateDb.getRelations();
|
||||||
relations.forEach(function(relation) {
|
relations.forEach(function(relation) {
|
||||||
logger.info(
|
|
||||||
'tjoho' + getGraphId(relation.id1) + getGraphId(relation.id2) + JSON.stringify(relation)
|
|
||||||
);
|
|
||||||
graph.setEdge(getGraphId(relation.id1), getGraphId(relation.id2), {
|
graph.setEdge(getGraphId(relation.id1), getGraphId(relation.id2), {
|
||||||
relation: relation
|
relation: relation
|
||||||
});
|
});
|
||||||
|
50
src/utils.js
50
src/utils.js
@ -73,8 +73,56 @@ export const interpolateToCurve = (interpolate, defaultCurve) => {
|
|||||||
return d3[curveName] || defaultCurve;
|
return d3[curveName] || defaultCurve;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const distance = (p1, p2) =>
|
||||||
|
p1 && p2 ? Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)) : 0;
|
||||||
|
|
||||||
|
const traverseEdge = points => {
|
||||||
|
let prevPoint;
|
||||||
|
let totalDistance = 0;
|
||||||
|
|
||||||
|
points.forEach(point => {
|
||||||
|
totalDistance += distance(point, prevPoint);
|
||||||
|
prevPoint = point;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Traverse half of total distance along points
|
||||||
|
const distanceToLabel = totalDistance / 2;
|
||||||
|
|
||||||
|
let remainingDistance = distanceToLabel;
|
||||||
|
let center;
|
||||||
|
prevPoint = undefined;
|
||||||
|
points.forEach(point => {
|
||||||
|
if (prevPoint && !center) {
|
||||||
|
const vectorDistance = distance(point, prevPoint);
|
||||||
|
if (vectorDistance < remainingDistance) {
|
||||||
|
remainingDistance -= vectorDistance;
|
||||||
|
} else {
|
||||||
|
// The point is remainingDistance from prevPoint in the vector between prevPoint and point
|
||||||
|
// Calculate the coordinates
|
||||||
|
const distanceRatio = remainingDistance / vectorDistance;
|
||||||
|
if (distanceRatio <= 0) center = prevPoint;
|
||||||
|
if (distanceRatio >= 1) center = { x: point.x, y: point.y };
|
||||||
|
if (distanceRatio > 0 && distanceRatio < 1) {
|
||||||
|
center = {
|
||||||
|
x: (1 - distanceRatio) * prevPoint.x + distanceRatio * point.x,
|
||||||
|
y: (1 - distanceRatio) * prevPoint.y + distanceRatio * point.y
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prevPoint = point;
|
||||||
|
});
|
||||||
|
return center;
|
||||||
|
};
|
||||||
|
|
||||||
|
const calcLabelPosition = points => {
|
||||||
|
const p = traverseEdge(points);
|
||||||
|
return p;
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
detectType,
|
detectType,
|
||||||
isSubstringInArray,
|
isSubstringInArray,
|
||||||
interpolateToCurve
|
interpolateToCurve,
|
||||||
|
calcLabelPosition
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user