add implementation/realization edge type, fix arrow heads to be hollow

This commit is contained in:
Justin Greywolf
2023-07-02 17:44:23 -07:00
parent 8066d94c1d
commit 6d0794130c
6 changed files with 93 additions and 4 deletions

View File

@@ -469,6 +469,7 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph
default: default:
strokeClasses = ''; strokeClasses = '';
} }
switch (edge.pattern) { switch (edge.pattern) {
case 'solid': case 'solid':
strokeClasses += ' edge-pattern-solid'; strokeClasses += ' edge-pattern-solid';
@@ -533,6 +534,9 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph
case 'extension': case 'extension':
svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-extensionStart' + ')'); svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-extensionStart' + ')');
break; break;
case 'realization':
svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-realizationStart' + ')');
break;
case 'composition': case 'composition':
svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-compositionStart' + ')'); svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-compositionStart' + ')');
break; break;
@@ -563,6 +567,9 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph
case 'extension': case 'extension':
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-extensionEnd' + ')'); svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-extensionEnd' + ')');
break; break;
case 'realization':
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-realizationEnd' + ')');
break;
case 'composition': case 'composition':
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-compositionEnd' + ')'); svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-compositionEnd' + ')');
break; break;

View File

@@ -2,6 +2,43 @@
import { log } from '../logger.js'; import { log } from '../logger.js';
const getSvgParent = (elem) => {
let container = elem;
// the intent here is to find the first parent element that is NOT part of the SVG element
// I know there has to be a better way, but could not find one that worked
// tried using checking if elem was instanceof SVGGraphicsElement or SVGElement, but it failed to detect correctly
if (container._groups) {
container = container._groups[0][0];
}
if (container.tagName.toLowerCase() === 'g') {
container = container.parentElement;
}
if (container.localName.toLowerCase() === 'svg') {
container = container.parentElement;
}
return container;
};
const getBackgroundColor = (elem) => {
let parent = getSvgParent(elem);
let backgroundColor;
while (parent && parent.tagName.toLowerCase() !== 'body') {
if(parent instanceof Element) {
const computedStyle = getComputedStyle(parent);
backgroundColor = computedStyle.backgroundColor;
if (backgroundColor !== 'rgba(0, 0, 0, 0)') {
break;
}
parent = parent.parentNode;
}
}
return backgroundColor === 'rgba(0, 0, 0, 0)' ? 'white' : backgroundColor;
};
// Only add the number of markers that the diagram needs // Only add the number of markers that the diagram needs
const insertMarkers = (elem, markerArray, type, id) => { const insertMarkers = (elem, markerArray, type, id) => {
markerArray.forEach((markerName) => { markerArray.forEach((markerName) => {
@@ -11,6 +48,8 @@ const insertMarkers = (elem, markerArray, type, id) => {
const extension = (elem, type, id) => { const extension = (elem, type, id) => {
log.trace('Making markers for ', id); log.trace('Making markers for ', id);
let backgroundColor = getBackgroundColor(elem);
elem elem
.append('defs') .append('defs')
.append('marker') .append('marker')
@@ -22,7 +61,8 @@ const extension = (elem, type, id) => {
.attr('markerHeight', 240) .attr('markerHeight', 240)
.attr('orient', 'auto') .attr('orient', 'auto')
.append('path') .append('path')
.attr('d', 'M 1,7 L18,13 V 1 Z'); .attr('d', 'M 1,7 L18,13 V 2 Z')
.attr('fill', backgroundColor);
elem elem
.append('defs') .append('defs')
@@ -35,7 +75,41 @@ const extension = (elem, type, id) => {
.attr('markerHeight', 28) .attr('markerHeight', 28)
.attr('orient', 'auto') .attr('orient', 'auto')
.append('path') .append('path')
.attr('d', 'M 1,1 V 13 L18,7 Z'); // this is actual shape for arrowhead .attr('d', 'M 1,1 V 13 L18,7 Z')
.attr('fill', backgroundColor); // this is actual shape for arrowhead
};
const realization = (elem, type, id) => {
log.trace('Making markers for ', id);
let backgroundColor = getBackgroundColor(elem);
elem
.append('defs')
.append('marker')
.attr('id', type + '-realizationStart')
.attr('class', 'marker realization ' + type)
.attr('refX', 0)
.attr('refY', 7)
.attr('markerWidth', 190)
.attr('markerHeight', 240)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M 1,7 L18,13 V 2 Z')
.attr('fill', backgroundColor);
elem
.append('defs')
.append('marker')
.attr('id', type + '-realizationEnd')
.attr('class', 'marker realization ' + type)
.attr('refX', 19)
.attr('refY', 7)
.attr('markerWidth', 20)
.attr('markerHeight', 28)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M 1,1 V 13 L18,7 Z')
.attr('fill', backgroundColor); // this is actual shape for arrowhead
}; };
const composition = (elem, type) => { const composition = (elem, type) => {
@@ -265,6 +339,7 @@ const barb = (elem, type) => {
// TODO rename the class diagram markers to something shape descriptive and semantic free // TODO rename the class diagram markers to something shape descriptive and semantic free
const markers = { const markers = {
extension, extension,
realization,
composition, composition,
aggregation, aggregation,
dependency, dependency,

View File

@@ -362,6 +362,7 @@ export const relationType = {
COMPOSITION: 2, COMPOSITION: 2,
DEPENDENCY: 3, DEPENDENCY: 3,
LOLLIPOP: 4, LOLLIPOP: 4,
REALIZATION: 5,
}; };
const setupToolTips = function (element: Element) { const setupToolTips = function (element: Element) {

View File

@@ -361,7 +361,7 @@ export const draw = async function (text: string, id: string, _version: string,
await render( await render(
element, element,
g, g,
['aggregation', 'extension', 'composition', 'dependency', 'lollipop'], ['aggregation', 'extension', 'realization', 'composition', 'dependency', 'lollipop'],
'classDiagram', 'classDiagram',
id id
); );
@@ -413,6 +413,9 @@ function getArrowMarker(type: number) {
case 4: case 4:
marker = 'lollipop'; marker = 'lollipop';
break; break;
case 5:
marker = 'realization';
break;
default: default:
marker = 'none'; marker = 'none';
} }

View File

@@ -124,7 +124,7 @@ Function arguments are optional: 'call <callback_name>()' simply executes 'callb
<*>"_top" return 'LINK_TARGET'; <*>"_top" return 'LINK_TARGET';
<*>\s*\<\| return 'EXTENSION'; <*>\s*\<\| return 'EXTENSION';
<*>\s*\|\> return 'EXTENSION'; <*>\s*\|\> return 'REALIZATION';
<*>\s*\> return 'DEPENDENCY'; <*>\s*\> return 'DEPENDENCY';
<*>\s*\< return 'DEPENDENCY'; <*>\s*\< return 'DEPENDENCY';
<*>\s*\* return 'COMPOSITION'; <*>\s*\* return 'COMPOSITION';
@@ -370,6 +370,7 @@ relation
relationType relationType
: AGGREGATION { $$=yy.relationType.AGGREGATION;} : AGGREGATION { $$=yy.relationType.AGGREGATION;}
| EXTENSION { $$=yy.relationType.EXTENSION;} | EXTENSION { $$=yy.relationType.EXTENSION;}
| REALIZATION { $$=yy.relationType.REALIZATION;}
| COMPOSITION { $$=yy.relationType.COMPOSITION;} | COMPOSITION { $$=yy.relationType.COMPOSITION;}
| DEPENDENCY { $$=yy.relationType.DEPENDENCY;} | DEPENDENCY { $$=yy.relationType.DEPENDENCY;}
| LOLLIPOP { $$=yy.relationType.LOLLIPOP;} | LOLLIPOP { $$=yy.relationType.LOLLIPOP;}

View File

@@ -9,6 +9,8 @@ export const drawEdge = function (elem, path, relation, conf, diagObj) {
switch (type) { switch (type) {
case diagObj.db.relationType.AGGREGATION: case diagObj.db.relationType.AGGREGATION:
return 'aggregation'; return 'aggregation';
case diagObj.db.relationType.REALIZATION:
return 'realization';
case diagObj.db.relationType.EXTENSION: case diagObj.db.relationType.EXTENSION:
return 'extension'; return 'extension';
case diagObj.db.relationType.COMPOSITION: case diagObj.db.relationType.COMPOSITION: