mirror of
				https://github.com/mermaid-js/mermaid.git
				synced 2025-10-30 18:34:09 +01:00 
			
		
		
		
	Fix: Fixed Connection gaps in flowchart shapes
This commit is contained in:
		| @@ -25,6 +25,7 @@ export async function question<T extends SVGGraphicsElement>(parent: D3Selection | ||||
|   const w = bbox.width + node.padding; | ||||
|   const h = bbox.height + node.padding; | ||||
|   const s = w + h; | ||||
|   const adjustment = 0.5; | ||||
|  | ||||
|   const points = [ | ||||
|     { x: s / 2, y: 0 }, | ||||
| @@ -45,13 +46,14 @@ export async function question<T extends SVGGraphicsElement>(parent: D3Selection | ||||
|  | ||||
|     polygon = shapeSvg | ||||
|       .insert(() => roughNode, ':first-child') | ||||
|       .attr('transform', `translate(${-s / 2}, ${s / 2})`); | ||||
|       .attr('transform', `translate(${-s / 2 + adjustment}, ${s / 2})`); | ||||
|  | ||||
|     if (cssStyles) { | ||||
|       polygon.attr('style', cssStyles); | ||||
|     } | ||||
|   } else { | ||||
|     polygon = insertPolygonShape(shapeSvg, s, s, points); | ||||
|     polygon.attr('transform', `translate(${-s / 2 + adjustment}, ${s / 2})`); | ||||
|   } | ||||
|  | ||||
|   if (nodeStyles) { | ||||
|   | ||||
| @@ -1,18 +1,160 @@ | ||||
| import type { Node, RectOptions } from '../../types.js'; | ||||
| import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js'; | ||||
| import intersect from '../intersect/index.js'; | ||||
| import type { Node } from '../../types.js'; | ||||
| import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; | ||||
| import rough from 'roughjs'; | ||||
| import type { D3Selection } from '../../../types.js'; | ||||
| import { drawRect } from './drawRect.js'; | ||||
|  | ||||
| /** | ||||
|  * Generates evenly spaced points along an elliptical arc connecting two points. | ||||
|  * | ||||
|  * @param x1 - x-coordinate of the start point of the arc | ||||
|  * @param y1 - y-coordinate of the start point of the arc | ||||
|  * @param x2 - x-coordinate of the end point of the arc | ||||
|  * @param y2 - y-coordinate of the end point of the arc | ||||
|  * @param rx - horizontal radius of the ellipse | ||||
|  * @param ry - vertical radius of the ellipse | ||||
|  * @param clockwise - direction of the arc; true for clockwise, false for counterclockwise | ||||
|  * @returns Array of points `{ x, y }` along the elliptical arc | ||||
|  * | ||||
|  * @throws Error if the given radii are too small to draw an arc between the points | ||||
|  */ | ||||
| export function generateArcPoints( | ||||
|   x1: number, | ||||
|   y1: number, | ||||
|   x2: number, | ||||
|   y2: number, | ||||
|   rx: number, | ||||
|   ry: number, | ||||
|   clockwise: boolean | ||||
| ) { | ||||
|   const numPoints = 20; | ||||
|   // Calculate midpoint | ||||
|   const midX = (x1 + x2) / 2; | ||||
|   const midY = (y1 + y2) / 2; | ||||
|  | ||||
|   // Calculate the angle of the line connecting the points | ||||
|   const angle = Math.atan2(y2 - y1, x2 - x1); | ||||
|  | ||||
|   // Calculate transformed coordinates for the ellipse | ||||
|   const dx = (x2 - x1) / 2; | ||||
|   const dy = (y2 - y1) / 2; | ||||
|  | ||||
|   // Scale to unit circle | ||||
|   const transformedX = dx / rx; | ||||
|   const transformedY = dy / ry; | ||||
|  | ||||
|   // Calculate the distance between points on the unit circle | ||||
|   const distance = Math.sqrt(transformedX ** 2 + transformedY ** 2); | ||||
|  | ||||
|   // Check if the ellipse can be drawn with the given radii | ||||
|   if (distance > 1) { | ||||
|     throw new Error('The given radii are too small to create an arc between the points.'); | ||||
|   } | ||||
|  | ||||
|   // Calculate the distance from the midpoint to the center of the ellipse | ||||
|   const scaledCenterDistance = Math.sqrt(1 - distance ** 2); | ||||
|  | ||||
|   // Calculate the center of the ellipse | ||||
|   const centerX = midX + scaledCenterDistance * ry * Math.sin(angle) * (clockwise ? -1 : 1); | ||||
|   const centerY = midY - scaledCenterDistance * rx * Math.cos(angle) * (clockwise ? -1 : 1); | ||||
|  | ||||
|   // Calculate the start and end angles on the ellipse | ||||
|   const startAngle = Math.atan2((y1 - centerY) / ry, (x1 - centerX) / rx); | ||||
|   const endAngle = Math.atan2((y2 - centerY) / ry, (x2 - centerX) / rx); | ||||
|  | ||||
|   // Adjust angles for clockwise/counterclockwise | ||||
|   let angleRange = endAngle - startAngle; | ||||
|   if (clockwise && angleRange < 0) { | ||||
|     angleRange += 2 * Math.PI; | ||||
|   } | ||||
|   if (!clockwise && angleRange > 0) { | ||||
|     angleRange -= 2 * Math.PI; | ||||
|   } | ||||
|  | ||||
|   // Generate points | ||||
|   const points = []; | ||||
|   for (let i = 0; i < numPoints; i++) { | ||||
|     const t = i / (numPoints - 1); | ||||
|     const angle = startAngle + t * angleRange; | ||||
|     const x = centerX + rx * Math.cos(angle); | ||||
|     const y = centerY + ry * Math.sin(angle); | ||||
|     points.push({ x, y }); | ||||
|   } | ||||
|  | ||||
|   return points; | ||||
| } | ||||
|  | ||||
| export async function roundedRect<T extends SVGGraphicsElement>( | ||||
|   parent: D3Selection<T>, | ||||
|   node: Node | ||||
| ) { | ||||
|   const options = { | ||||
|     rx: 5, | ||||
|     ry: 5, | ||||
|     classes: '', | ||||
|     labelPaddingX: (node?.padding || 0) * 1, | ||||
|     labelPaddingY: (node?.padding || 0) * 1, | ||||
|   } as RectOptions; | ||||
|   const { labelStyles, nodeStyles } = styles2String(node); | ||||
|   node.labelStyle = labelStyles; | ||||
|   const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node)); | ||||
|  | ||||
|   return drawRect(parent, node, options); | ||||
|   const labelPaddingX = node?.padding ?? 0; | ||||
|   const labelPaddingY = node?.padding ?? 0; | ||||
|  | ||||
|   const w = (node?.width ? node?.width : bbox.width) + labelPaddingX * 2; | ||||
|   const h = (node?.height ? node?.height : bbox.height) + labelPaddingY * 2; | ||||
|   const radius = 5; | ||||
|   const taper = 5; // Taper width for the rounded corners | ||||
|   const { cssStyles } = node; | ||||
|   // @ts-expect-error -- Passing a D3.Selection seems to work for some reason | ||||
|   const rc = rough.svg(shapeSvg); | ||||
|   const options = userNodeOverrides(node, {}); | ||||
|  | ||||
|   if (node.look !== 'handDrawn') { | ||||
|     options.roughness = 0; | ||||
|     options.fillStyle = 'solid'; | ||||
|   } | ||||
|  | ||||
|   const points = [ | ||||
|     // Top edge (left to right) | ||||
|     { x: -w / 2 + taper, y: -h / 2 }, // Top-left corner start (1) | ||||
|     { x: w / 2 - taper, y: -h / 2 }, // Top-right corner start (2) | ||||
|  | ||||
|     ...generateArcPoints(w / 2 - taper, -h / 2, w / 2, -h / 2 + taper, radius, radius, true), // Top-left arc (2 to 3) | ||||
|  | ||||
|     // Right edge (top to bottom) | ||||
|     { x: w / 2, y: -h / 2 + taper }, // Top-right taper point (3) | ||||
|     { x: w / 2, y: h / 2 - taper }, // Bottom-right taper point (4) | ||||
|  | ||||
|     ...generateArcPoints(w / 2, h / 2 - taper, w / 2 - taper, h / 2, radius, radius, true), // Top-left arc (4 to 5) | ||||
|  | ||||
|     // Bottom edge (right to left) | ||||
|     { x: w / 2 - taper, y: h / 2 }, // Bottom-right corner start (5) | ||||
|     { x: -w / 2 + taper, y: h / 2 }, // Bottom-left corner start (6) | ||||
|  | ||||
|     ...generateArcPoints(-w / 2 + taper, h / 2, -w / 2, h / 2 - taper, radius, radius, true), // Top-left arc (4 to 5) | ||||
|  | ||||
|     // Left edge (bottom to top) | ||||
|     { x: -w / 2, y: h / 2 - taper }, // Bottom-left taper point (7) | ||||
|     { x: -w / 2, y: -h / 2 + taper }, // Top-left taper point (8) | ||||
|     ...generateArcPoints(-w / 2, -h / 2 + taper, -w / 2 + taper, -h / 2, radius, radius, true), // Top-left arc (4 to 5) | ||||
|   ]; | ||||
|  | ||||
|   const pathData = createPathFromPoints(points); | ||||
|   const shapeNode = rc.path(pathData, options); | ||||
|  | ||||
|   const polygon = shapeSvg.insert(() => shapeNode, ':first-child'); | ||||
|   polygon.attr('class', 'basic label-container outer-path'); | ||||
|  | ||||
|   if (cssStyles && node.look !== 'handDrawn') { | ||||
|     polygon.selectChildren('path').attr('style', cssStyles); | ||||
|   } | ||||
|  | ||||
|   if (nodeStyles && node.look !== 'handDrawn') { | ||||
|     polygon.selectChildren('path').attr('style', nodeStyles); | ||||
|   } | ||||
|  | ||||
|   updateNodeBounds(node, polygon); | ||||
|  | ||||
|   node.intersect = function (point) { | ||||
|     const pos = intersect.polygon(node, points, point); | ||||
|     return pos; | ||||
|   }; | ||||
|  | ||||
|   return shapeSvg; | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,15 @@ | ||||
| import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js'; | ||||
| import { | ||||
|   labelHelper, | ||||
|   updateNodeBounds, | ||||
|   getNodeClasses, | ||||
|   generateCirclePoints, | ||||
|   createPathFromPoints, | ||||
| } from './util.js'; | ||||
| import intersect from '../intersect/index.js'; | ||||
| import type { Node } from '../../types.js'; | ||||
| import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; | ||||
| import rough from 'roughjs'; | ||||
| import { createRoundedRectPathD } from './roundedRectPath.js'; | ||||
| import type { D3Selection } from '../../../types.js'; | ||||
| import { handleUndefinedAttr } from '../../../utils.js'; | ||||
|  | ||||
| export const createStadiumPathD = ( | ||||
|   x: number, | ||||
| @@ -60,36 +64,44 @@ export async function stadium<T extends SVGGraphicsElement>(parent: D3Selection< | ||||
|   const h = bbox.height + node.padding; | ||||
|   const w = bbox.width + h / 4 + node.padding; | ||||
|  | ||||
|   let rect; | ||||
|   const radius = h / 2; | ||||
|   const { cssStyles } = node; | ||||
|   if (node.look === 'handDrawn') { | ||||
|   // @ts-expect-error -- Passing a D3.Selection seems to work for some reason | ||||
|   const rc = rough.svg(shapeSvg); | ||||
|   const options = userNodeOverrides(node, {}); | ||||
|  | ||||
|     const pathData = createRoundedRectPathD(-w / 2, -h / 2, w, h, h / 2); | ||||
|     const roughNode = rc.path(pathData, options); | ||||
|  | ||||
|     rect = shapeSvg.insert(() => roughNode, ':first-child'); | ||||
|     rect.attr('class', 'basic label-container').attr('style', handleUndefinedAttr(cssStyles)); | ||||
|   } else { | ||||
|     rect = shapeSvg.insert('rect', ':first-child'); | ||||
|  | ||||
|     rect | ||||
|       .attr('class', 'basic label-container') | ||||
|       .attr('style', nodeStyles) | ||||
|       .attr('rx', h / 2) | ||||
|       .attr('ry', h / 2) | ||||
|       .attr('x', -w / 2) | ||||
|       .attr('y', -h / 2) | ||||
|       .attr('width', w) | ||||
|       .attr('height', h); | ||||
|   if (node.look !== 'handDrawn') { | ||||
|     options.roughness = 0; | ||||
|     options.fillStyle = 'solid'; | ||||
|   } | ||||
|  | ||||
|   updateNodeBounds(node, rect); | ||||
|   const points = [ | ||||
|     { x: -w / 2 + radius, y: -h / 2 }, | ||||
|     { x: w / 2 - radius, y: -h / 2 }, | ||||
|     ...generateCirclePoints(-w / 2 + radius, 0, radius, 50, 90, 270), | ||||
|     { x: w / 2 - radius, y: h / 2 }, | ||||
|     ...generateCirclePoints(w / 2 - radius, 0, radius, 50, 270, 450), | ||||
|   ]; | ||||
|  | ||||
|   const pathData = createPathFromPoints(points); | ||||
|   const shapeNode = rc.path(pathData, options); | ||||
|  | ||||
|   const polygon = shapeSvg.insert(() => shapeNode, ':first-child'); | ||||
|   polygon.attr('class', 'basic label-container outer-path'); | ||||
|  | ||||
|   if (cssStyles && node.look !== 'handDrawn') { | ||||
|     polygon.selectChildren('path').attr('style', cssStyles); | ||||
|   } | ||||
|  | ||||
|   if (nodeStyles && node.look !== 'handDrawn') { | ||||
|     polygon.selectChildren('path').attr('style', nodeStyles); | ||||
|   } | ||||
|  | ||||
|   updateNodeBounds(node, polygon); | ||||
|  | ||||
|   node.intersect = function (point) { | ||||
|     return intersect.rect(node, point); | ||||
|     const pos = intersect.polygon(node, points, point); | ||||
|     return pos; | ||||
|   }; | ||||
|  | ||||
|   return shapeSvg; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 omkarht
					omkarht