#945 Rendering of labels and new label positioning algorithm

This commit is contained in:
Knut Sveidqvist 2019-09-28 13:31:10 +02:00
parent 13baa43081
commit 3cffd1e3ed
6 changed files with 143 additions and 31 deletions

View File

@ -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');
});
}); });

View File

@ -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

View File

@ -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) {

View File

@ -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);
}); });
}); });

View File

@ -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
}); });

View File

@ -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
}; };