#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');
});
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
: idStatement DESCR
| idStatement '-->' idStatement {yy.addRelation($1, $3);}
| idStatement '-->' idStatement DESCR
| idStatement '-->' idStatement DESCR {yy.addRelation($1, $3, $4.substr(1).trim());}
| HIDE_EMPTY
| scale WIDTH
| COMPOSIT_STATE

View File

@ -41,7 +41,7 @@ export const getRelations = function() {
return relations;
};
export const addRelation = function(_id1, _id2) {
export const addRelation = function(_id1, _id2, title) {
let id1 = _id1;
let id2 = _id2;
let type1 = 'default';
@ -56,10 +56,10 @@ export const addRelation = function(_id1, _id2) {
id2 = 'end' + startCnt;
type2 = 'end';
}
console.log(id1, id2);
console.log(id1, id2, title);
addState(id1, type1);
addState(id2, type2);
relations.push({ id1, id2 });
relations.push({ id1, id2, title });
};
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
`;
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);
});
});

View File

@ -4,6 +4,7 @@ import graphlib from 'graphlibrary';
import { logger } from '../../logger';
import stateDb from './stateDb';
import { parser } from './parser/stateDiagram';
import utils from '../../utils';
parser.yy = stateDb;
@ -136,7 +137,7 @@ const insertMarkers = function(elem) {
.attr('markerHeight', 28)
.attr('orient', 'auto')
.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) {
logger.info('Rendering class ' + stateDef);
@ -285,48 +286,67 @@ const drawEdge = function(elem, path, relation) {
url = url.replace(/\)/g, '\\)');
}
svgPath.attr(
'marker-start',
'url(' + url + '#' + getRelationType(stateDb.relationType.DEPENDENCY) + 'Start' + ')'
);
// svgPath.attr(
// 'marker-start',
// 'url(' + url + '#' + getRelationType(stateDb.relationType.DEPENDENCY) + 'Start' + ')'
// );
svgPath.attr(
'marker-end',
'url(' + url + '#' + getRelationType(stateDb.relationType.DEPENDENCY) + 'End' + ')'
);
let x, y;
const l = path.points.length;
if (l % 2 !== 0 && l > 1) {
const p1 = path.points[Math.floor(l / 2)];
const p2 = path.points[Math.ceil(l / 2)];
x = (p1.x + p2.x) / 2;
y = (p1.y + p2.y) / 2;
} else {
const p = path.points[Math.floor(l / 2)];
x = p.x;
y = p.y;
}
// Figure ou where to put the label given the points
// let x, y;
// const l = path.points.length;
// if (l % 2 !== 0 && l > 1) {
// const p1 = path.points[Math.floor(l / 2)];
// const p2 = path.points[Math.ceil(l / 2)];
// x = (p1.x + p2.x) / 2;
// y = (p1.y + p2.y) / 2;
// } else {
// const p = path.points[Math.floor(l / 2)];
// x = p.x;
// y = p.y;
// }
// console.log('calcLabelPosition', utils);
if (typeof relation.title !== 'undefined') {
const g = elem.append('g').attr('class', 'classLabel');
const label = g
.append('text')
.attr('class', 'label')
.attr('x', x)
.attr('y', y)
.attr('fill', 'red')
.attr('text-anchor', 'middle')
.text(relation.title);
window.label = label;
const bounds = label.node().getBBox();
const { x, y } = utils.calcLabelPosition(path.points);
label.attr('x', x).attr('y', y);
const bounds = label.node().getBBox();
g.insert('rect', ':first-child')
.attr('class', 'box')
.attr('x', bounds.x - conf.padding / 2)
.attr('y', bounds.y - conf.padding / 2)
.attr('width', bounds.width + 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++;
@ -338,7 +358,7 @@ const drawEdge = function(elem, path, relation) {
* @param {*} stateDef
*/
const drawState = function(elem, stateDef) {
logger.info('Rendering class ' + stateDef);
// logger.info('Rendering class ' + stateDef);
const addTspan = function(textEl, txt, isFirst) {
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
// our nodes.
graph.setNode(node.id, node);
logger.info('Org height: ' + node.height);
// logger.info('Org height: ' + node.height);
}
const relations = stateDb.getRelations();
relations.forEach(function(relation) {
logger.info(
'tjoho' + getGraphId(relation.id1) + getGraphId(relation.id2) + JSON.stringify(relation)
);
graph.setEdge(getGraphId(relation.id1), getGraphId(relation.id2), {
relation: relation
});

View File

@ -73,8 +73,56 @@ export const interpolateToCurve = (interpolate, 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 {
detectType,
isSubstringInArray,
interpolateToCurve
interpolateToCurve,
calcLabelPosition
};