import { logger } from '../logger'; // eslint-disable-line import createLabel from './createLabel'; import { line, curveBasis, select } from 'd3'; import { getConfig } from '../config'; import utils from '../utils'; // import { calcLabelPosition } from '../utils'; let edgeLabels = {}; let terminalLabels = {}; export const clear = () => { edgeLabels = {}; terminalLabels = {}; }; export const insertEdgeLabel = (elem, edge) => { // Create the actual text element const labelElement = createLabel(edge.label, edge.labelStyle); // Create outer g, edgeLabel, this will be positioned after graph layout const edgeLabel = elem.insert('g').attr('class', 'edgeLabel'); // Create inner g, label, this will be positioned now for centering the text const label = edgeLabel.insert('g').attr('class', 'label'); label.node().appendChild(labelElement); // Center the label let bbox = labelElement.getBBox(); if (getConfig().flowchart.htmlLabels) { const div = labelElement.children[0]; const dv = select(labelElement); bbox = div.getBoundingClientRect(); dv.attr('width', bbox.width); dv.attr('height', bbox.height); } label.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')'); // Make element accessible by id for positioning edgeLabels[edge.id] = edgeLabel; // Update the abstract data of the edge with the new information about its width and height edge.width = bbox.width; edge.height = bbox.height; if (edge.startLabelLeft) { // Create the actual text element const startLabelElement = createLabel(edge.startLabelLeft, edge.labelStyle); const startEdgeLabelLeft = elem.insert('g').attr('class', 'edgeTerminals'); const inner = startEdgeLabelLeft.insert('g').attr('class', 'inner'); inner.node().appendChild(startLabelElement); const slBox = startLabelElement.getBBox(); inner.attr('transform', 'translate(' + -slBox.width / 2 + ', ' + -slBox.height / 2 + ')'); if (!terminalLabels[edge.id]) { terminalLabels[edge.id] = {}; } terminalLabels[edge.id].startLeft = startEdgeLabelLeft; } if (edge.startLabelRight) { // Create the actual text element const startLabelElement = createLabel(edge.startLabelRight, edge.labelStyle); const startEdgeLabelRight = elem.insert('g').attr('class', 'edgeTerminals'); const inner = startEdgeLabelRight.insert('g').attr('class', 'inner'); startEdgeLabelRight.node().appendChild(startLabelElement); inner.node().appendChild(startLabelElement); const slBox = startLabelElement.getBBox(); inner.attr('transform', 'translate(' + -slBox.width / 2 + ', ' + -slBox.height / 2 + ')'); if (!terminalLabels[edge.id]) { terminalLabels[edge.id] = {}; } terminalLabels[edge.id].startRight = startEdgeLabelRight; } if (edge.endLabelLeft) { // Create the actual text element const endLabelElement = createLabel(edge.endLabelLeft, edge.labelStyle); const endEdgeLabelLeft = elem.insert('g').attr('class', 'edgeTerminals'); const inner = endEdgeLabelLeft.insert('g').attr('class', 'inner'); inner.node().appendChild(endLabelElement); const slBox = endLabelElement.getBBox(); inner.attr('transform', 'translate(' + -slBox.width / 2 + ', ' + -slBox.height / 2 + ')'); endEdgeLabelLeft.node().appendChild(endLabelElement); if (!terminalLabels[edge.id]) { terminalLabels[edge.id] = {}; } terminalLabels[edge.id].endLeft = endEdgeLabelLeft; } if (edge.endLabelRight) { // Create the actual text element const endLabelElement = createLabel(edge.endLabelRight, edge.labelStyle); const endEdgeLabelRight = elem.insert('g').attr('class', 'edgeTerminals'); const inner = endEdgeLabelRight.insert('g').attr('class', 'inner'); inner.node().appendChild(endLabelElement); const slBox = endLabelElement.getBBox(); inner.attr('transform', 'translate(' + -slBox.width / 2 + ', ' + -slBox.height / 2 + ')'); endEdgeLabelRight.node().appendChild(endLabelElement); if (!terminalLabels[edge.id]) { terminalLabels[edge.id] = {}; } terminalLabels[edge.id].endRight = endEdgeLabelRight; } }; export const positionEdgeLabel = (edge, paths) => { logger.info('Moving label', edge.id, edge.label, edgeLabels[edge.id]); let path = paths.updatedPath ? paths.updatedPath : paths.originalPath; if (edge.label) { const el = edgeLabels[edge.id]; let x = edge.x; let y = edge.y; if (path) { // // debugger; const pos = utils.calcLabelPosition(path); logger.info('Moving label from (', x, ',', y, ') to (', pos.x, ',', pos.y, ')'); // x = pos.x; // y = pos.y; } el.attr('transform', 'translate(' + x + ', ' + y + ')'); } //let path = paths.updatedPath ? paths.updatedPath : paths.originalPath; if (edge.startLabelLeft) { const el = terminalLabels[edge.id].startLeft; let x = edge.x; let y = edge.y; if (path) { // debugger; const pos = utils.calcTerminalLabelPosition(0, 'start_left', path); x = pos.x; y = pos.y; } el.attr('transform', 'translate(' + x + ', ' + y + ')'); } if (edge.startLabelRight) { const el = terminalLabels[edge.id].startRight; let x = edge.x; let y = edge.y; if (path) { // debugger; const pos = utils.calcTerminalLabelPosition(0, 'start_right', path); x = pos.x; y = pos.y; } el.attr('transform', 'translate(' + x + ', ' + y + ')'); } if (edge.endLabelLeft) { const el = terminalLabels[edge.id].endLeft; let x = edge.x; let y = edge.y; if (path) { // debugger; const pos = utils.calcTerminalLabelPosition(0, 'end_left', path); x = pos.x; y = pos.y; } el.attr('transform', 'translate(' + x + ', ' + y + ')'); } if (edge.endLabelRight) { const el = terminalLabels[edge.id].endRight; let x = edge.x; let y = edge.y; if (path) { // debugger; const pos = utils.calcTerminalLabelPosition(0, 'end_right', path); x = pos.x; y = pos.y; } el.attr('transform', 'translate(' + x + ', ' + y + ')'); } }; // const getRelationType = function(type) { // switch (type) { // case stateDb.relationType.AGGREGATION: // return 'aggregation'; // case stateDb.relationType.EXTENSION: // return 'extension'; // case stateDb.relationType.COMPOSITION: // return 'composition'; // case stateDb.relationType.DEPENDENCY: // return 'dependency'; // } // }; const outsideNode = (node, point) => { // logger.warn('Checking bounds ', node, point); const x = node.x; const y = node.y; const dx = Math.abs(point.x - x); const dy = Math.abs(point.y - y); const w = node.width / 2; const h = node.height / 2; if (dx >= w || dy >= h) { return true; } return false; }; export const intersection = (node, outsidePoint, insidePoint) => { logger.warn('intersection calc o:', outsidePoint, ' i:', insidePoint, node); const x = node.x; const y = node.y; const dx = Math.abs(x - insidePoint.x); const w = node.width / 2; let r = insidePoint.x < outsidePoint.x ? w - dx : w + dx; const h = node.height / 2; const edges = { x1: x - w, x2: x + w, y1: y - h, y2: y + h }; if ( outsidePoint.x === edges.x1 || outsidePoint.x === edges.x2 || outsidePoint.y === edges.y1 || outsidePoint.y === edges.y2 ) { logger.warn('calc equals on edge'); return outsidePoint; } const Q = Math.abs(outsidePoint.y - insidePoint.y); const R = Math.abs(outsidePoint.x - insidePoint.x); // log.warn(); if (Math.abs(y - outsidePoint.y) * w > Math.abs(x - outsidePoint.x) * h) { // eslint-disable-line // Intersection is top or bottom of rect. // let q = insidePoint.y < outsidePoint.y ? outsidePoint.y - h - y : y - h - outsidePoint.y; let q = insidePoint.y < outsidePoint.y ? outsidePoint.y - h - y : y - h - outsidePoint.y; r = (R * q) / Q; const res = { x: insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : insidePoint.x - r, y: outsidePoint.y + q }; logger.warn(`topp/bott calc, Q ${Q}, q ${q}, R ${R}, r ${r}`, res); return res; } else { // Intersection onn sides of rect // q = (Q * r) / R; // q = 2; // r = (R * q) / Q; if (insidePoint.x < outsidePoint.x) { r = outsidePoint.x - w - x; } else { // r = outsidePoint.x - w - x; r = x - w - outsidePoint.x; } let q = (q = (Q * r) / R); logger.warn(`sides calc, Q ${Q}, q ${q}, R ${R}, r ${r}`, { x: insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : insidePoint.x + dx - w, y: insidePoint.y < outsidePoint.y ? insidePoint.y + q : insidePoint.y - q }); return { x: insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : insidePoint.x + dx - w, y: insidePoint.y < outsidePoint.y ? insidePoint.y + q : insidePoint.y - q }; } }; //(edgePaths, e, edge, clusterDb, diagramtype, graph) export const insertEdge = function(elem, e, edge, clusterDb, diagramType, graph) { let points = edge.points; let pointsHasChanged = false; const tail = graph.node(e.v); var head = graph.node(e.w); if (head.intersect && tail.intersect) { points = points.slice(1, edge.points.length - 1); points.unshift(tail.intersect(points[0])); logger.info( 'Last point', points[points.length - 1], head, head.intersect(points[points.length - 1]) ); points.push(head.intersect(points[points.length - 1])); } if (edge.toCluster) { logger.trace('edge', edge); logger.trace('to cluster', clusterDb[edge.toCluster]); points = []; let lastPointOutside; let isInside = false; edge.points.forEach(point => { const node = clusterDb[edge.toCluster].node; if (!outsideNode(node, point) && !isInside) { logger.trace('inside', edge.toCluster, point, lastPointOutside); // First point inside the rect const inter = intersection(node, lastPointOutside, point); let pointPresent = false; points.forEach(p => { pointPresent = pointPresent || (p.x === inter.x && p.y === inter.y); }); // if (!pointPresent) { if (!points.find(e => e.x === inter.x && e.y === inter.y)) { points.push(inter); } else { logger.warn('no intersect', inter, points); } isInside = true; } else { if (!isInside) points.push(point); } lastPointOutside = point; }); pointsHasChanged = true; } if (edge.fromCluster) { logger.trace('edge', edge); logger.warn('from cluster', clusterDb[edge.fromCluster]); const updatedPoints = []; let lastPointOutside; let isInside = false; for (let i = points.length - 1; i >= 0; i--) { const point = points[i]; const node = clusterDb[edge.fromCluster].node; if (!outsideNode(node, point) && !isInside) { logger.warn('inside', edge.fromCluster, point, node); // First point inside the rect const insterection = intersection(node, lastPointOutside, point); // logger.trace('intersect', intersection(node, lastPointOutside, point)); updatedPoints.unshift(insterection); // points.push(insterection); isInside = true; } else { // at the outside logger.trace('Outside point', point); if (!isInside) updatedPoints.unshift(point); } lastPointOutside = point; } points = updatedPoints; pointsHasChanged = true; } // The data for our line const lineData = points.filter(p => !Number.isNaN(p.y)); // This is the accessor function we talked about above const lineFunction = line() .x(function(d) { return d.x; }) .y(function(d) { return d.y; }) .curve(curveBasis); // Contruct stroke classes based on properties let strokeClasses; switch (edge.thickness) { case 'normal': strokeClasses = 'edge-thickness-normal'; break; case 'thick': strokeClasses = 'edge-thickness-thick'; break; default: strokeClasses = ''; } switch (edge.pattern) { case 'solid': strokeClasses += ' edge-pattern-solid'; break; case 'dotted': strokeClasses += ' edge-pattern-dotted'; break; case 'dashed': strokeClasses += ' edge-pattern-dashed'; break; } const svgPath = elem .append('path') .attr('d', lineFunction(lineData)) .attr('id', edge.id) .attr('class', ' ' + strokeClasses + (edge.classes ? ' ' + edge.classes : '')) .attr('style', edge.style); // DEBUG code, adds a red circle at each edge coordinate // edge.points.forEach(point => { // elem // .append('circle') // .style('stroke', 'red') // .style('fill', 'red') // .attr('r', 1) // .attr('cx', point.x) // .attr('cy', point.y); // }); let url = ''; if (getConfig().state.arrowMarkerAbsolute) { url = window.location.protocol + '//' + window.location.host + window.location.pathname + window.location.search; url = url.replace(/\(/g, '\\('); url = url.replace(/\)/g, '\\)'); } logger.info('arrowTypeStart', edge.arrowTypeStart); logger.info('arrowTypeEnd', edge.arrowTypeEnd); switch (edge.arrowTypeStart) { case 'arrow_cross': svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-crossStart' + ')'); break; case 'arrow_point': svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-pointStart' + ')'); break; case 'arrow_barb': svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-barbStart' + ')'); break; case 'arrow_circle': svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-circleStart' + ')'); break; case 'aggregation': svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-aggregationStart' + ')'); break; case 'extension': svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-extensionStart' + ')'); break; case 'composition': svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-compositionStart' + ')'); break; case 'dependency': svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-dependencyStart' + ')'); break; default: } switch (edge.arrowTypeEnd) { case 'arrow_cross': svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-crossEnd' + ')'); break; case 'arrow_point': svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-pointEnd' + ')'); break; case 'arrow_barb': svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-barbEnd' + ')'); break; case 'arrow_circle': svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-circleEnd' + ')'); break; case 'aggregation': svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-aggregationEnd' + ')'); break; case 'extension': svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-extensionEnd' + ')'); break; case 'composition': svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-compositionEnd' + ')'); break; case 'dependency': svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-dependencyEnd' + ')'); break; default: } let paths = {}; if (pointsHasChanged) { paths.updatedPath = points; } paths.originalPath = edge.points; return paths; };