mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-12-24 05:06:28 +01:00
Compare commits
3 Commits
refactor/s
...
fix/treema
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7b2fe3b95 | ||
|
|
365c1b1062 | ||
|
|
fd142d0915 |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'mermaid': patch
|
||||
---
|
||||
|
||||
revert: restore original hexagon and roundedRect implementations
|
||||
5
.changeset/legal-apples-wink.md
Normal file
5
.changeset/legal-apples-wink.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'mermaid': patch
|
||||
---
|
||||
|
||||
fix: Ensure treemap labels render correctly in large nested diagrams
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'mermaid': patch
|
||||
---
|
||||
|
||||
chore: restore original hexagon and roundedRect implementations
|
||||
@@ -468,4 +468,65 @@ classDef highlight fill:#f39c12,color:#000,stroke:#e67e22,stroke-width:2px;
|
||||
);
|
||||
});
|
||||
*/
|
||||
|
||||
it('14: should render labels in complex treemap with many nested blocks', () => {
|
||||
imgSnapshotTest(
|
||||
`treemap-beta
|
||||
"🔴 High Activity (Top 50%)":::redContainer
|
||||
"packages/app (1115)": 15:::redleaf
|
||||
"packages/app/src (844)": 12:::redleaf
|
||||
"packages/app/src/lib (707)": 11:::redleaf
|
||||
"packages/app/src/routes (353)": 7:::redleaf
|
||||
"packages/app/tests (277)": 6:::redleaf
|
||||
"packages/app/tests/e2e (245)": 6:::redleaf
|
||||
"packages/app/tests/common (48)": 4:::redleaf
|
||||
"packages/llm-prompts (29)": 3:::redleaf
|
||||
"packages/emails (26)": 3:::redleaf
|
||||
"packages/app/tests/api (23)": 3:::redleaf
|
||||
"packages/emails/src (18)": 3:::redleaf
|
||||
"packages/emails/src/emails (17)": 3:::redleaf
|
||||
"packages/app/static (15)": 3:::redleaf
|
||||
"packages/llm-prompts/diagram-chat (15)": 3:::redleaf
|
||||
"packages/app/prisma (11)": 3:::redleaf
|
||||
"packages/app/prisma/migrations (11)": 3:::redleaf
|
||||
"packages/llm-prompts/shared (9)": 3:::redleaf
|
||||
"packages/app/static/icons (8)": 3:::redleaf
|
||||
"packages/emails/src/components (8)": 3:::redleaf
|
||||
"packages/app-observability (8)": 3:::redleaf
|
||||
"packages/mermaid-pre-render-server (8)": 3:::redleaf
|
||||
"🟠 Medium Activity (35%)":::orangeContainer
|
||||
"packages/app/tests/mobile (6)": 3:::orangeleaf
|
||||
"packages/llm-prompts/diagram-chat/tests (6)": 3:::orangeleaf
|
||||
"packages/eslint-plugin (6)": 3:::orangeleaf
|
||||
"packages/eslint-plugin/src (5)": 3:::orangeleaf
|
||||
"packages/eslint-plugin/src/rules (5)": 3:::orangeleaf
|
||||
"packages/app/static/img (4)": 3:::orangeleaf
|
||||
"packages/llm-prompts/tests (4)": 3:::orangeleaf
|
||||
"packages/llm-prompts/common (3)": 3:::orangeleaf
|
||||
"packages/mermaid-pre-render-server/tests (3)": 3:::orangeleaf
|
||||
"packages/mermaid-pre-render-server/tests/e2e (3)": 3:::orangeleaf
|
||||
"packages/app/tests/seed (2)": 3:::orangeleaf
|
||||
"packages/llm-prompts/shared/patches (2)": 3:::orangeleaf
|
||||
"packages/mermaid-pre-render-server/src (2)": 3:::orangeleaf
|
||||
"packages/app/scripts (1)": 3:::orangeleaf
|
||||
"🟡 Low Activity (15%)":::yellowContainer
|
||||
"packages/llm-prompts/regenerate-diagram (1)": 3:::yellowleaf
|
||||
"packages/llm-prompts/repair-diagram (1)": 3:::yellowleaf
|
||||
"packages/app-buildship (1)": 3:::yellowleaf
|
||||
"packages/app-buildship/src (1)": 3:::yellowleaf
|
||||
"packages/app-buildship/src/cleanupAssets (1)": 3:::yellowleaf
|
||||
"packages/icons (1)": 3:::yellowleaf
|
||||
|
||||
classDef root fill:#F5BA71
|
||||
classDef redContainer fill:#F54927
|
||||
classDef orangeContainer fill:#DE8118
|
||||
classDef yellowContainer fill:#F5E514
|
||||
|
||||
classDef redleaf fill:#F54927,stroke:none,color:#FFFFFF
|
||||
classDef orangeleaf fill:#DE8118,stroke:none,color:#FFFFFF
|
||||
classDef yellowleaf fill:#F5E514,stroke:none,color:#333333
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -224,6 +224,7 @@ const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => {
|
||||
.attr('dominant-baseline', 'middle')
|
||||
.text((d) => (d.depth === 0 ? '' : d.data.name)) // Skip label for root section
|
||||
.attr('font-weight', 'bold')
|
||||
.attr('clip-path', (_d, i) => `url(#clip-section-${id}-${i})`) // Apply clip-path to prevent overflow
|
||||
.attr('style', (d) => {
|
||||
// Hide the label for the root section
|
||||
if (d.depth === 0) {
|
||||
@@ -309,6 +310,17 @@ const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => {
|
||||
|
||||
// Draw the leaf nodes
|
||||
const leafNodes = treemapData.leaves();
|
||||
|
||||
const isComplexTreemap = leafNodes.length > 20;
|
||||
|
||||
const baseLabelFontSize = isComplexTreemap ? 16 : 38;
|
||||
const baseValueFontSize = isComplexTreemap ? 14 : 28;
|
||||
const minLabelFontSize = isComplexTreemap ? 4 : 8;
|
||||
const minValueFontSize = isComplexTreemap ? 4 : 6;
|
||||
const labelPadding = isComplexTreemap ? 2 : 4;
|
||||
const minDisplayThreshold = isComplexTreemap ? 8 : 10;
|
||||
const spacingBetweenLabelAndValue = isComplexTreemap ? 1 : 2;
|
||||
|
||||
const cell = g
|
||||
.selectAll('.treemapLeafGroup')
|
||||
.data(leafNodes)
|
||||
@@ -359,7 +371,7 @@ const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => {
|
||||
// .style('fill', (d) => colorScaleLabel(d.data.name))
|
||||
.attr('style', (d) => {
|
||||
const labelStyles =
|
||||
'text-anchor: middle; dominant-baseline: middle; font-size: 38px;fill:' +
|
||||
`text-anchor: middle; dominant-baseline: middle; font-size: ${baseLabelFontSize}px;fill:` +
|
||||
colorScaleLabel(d.data.name) +
|
||||
';';
|
||||
const styles = styles2String({ cssCompiledStyles: d.data.cssCompiledStyles } as Node);
|
||||
@@ -374,21 +386,16 @@ const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => {
|
||||
const nodeHeight = d.y1 - d.y0;
|
||||
const textNode = self.node()!;
|
||||
|
||||
const padding = 4;
|
||||
const availableWidth = nodeWidth - 2 * padding;
|
||||
const availableHeight = nodeHeight - 2 * padding;
|
||||
const availableWidth = nodeWidth - 2 * labelPadding;
|
||||
const availableHeight = nodeHeight - 2 * labelPadding;
|
||||
|
||||
if (availableWidth < 10 || availableHeight < 10) {
|
||||
if (availableWidth < minDisplayThreshold || availableHeight < minDisplayThreshold) {
|
||||
self.style('display', 'none');
|
||||
return;
|
||||
}
|
||||
|
||||
let currentLabelFontSize = parseInt(self.style('font-size'), 10);
|
||||
const minLabelFontSize = 8;
|
||||
const originalValueRelFontSize = 28; // Original font size of value, for max cap
|
||||
const valueScaleFactor = 0.6; // Value font size as a factor of label font size
|
||||
const minValueFontSize = 6;
|
||||
const spacingBetweenLabelAndValue = 2;
|
||||
|
||||
// 1. Adjust label font size to fit width
|
||||
while (
|
||||
@@ -402,7 +409,7 @@ const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => {
|
||||
// 2. Adjust both label and prospective value font size to fit combined height
|
||||
let prospectiveValueFontSize = Math.max(
|
||||
minValueFontSize,
|
||||
Math.min(originalValueRelFontSize, Math.round(currentLabelFontSize * valueScaleFactor))
|
||||
Math.min(baseValueFontSize, Math.round(currentLabelFontSize * valueScaleFactor))
|
||||
);
|
||||
let combinedHeight =
|
||||
currentLabelFontSize + spacingBetweenLabelAndValue + prospectiveValueFontSize;
|
||||
@@ -411,7 +418,7 @@ const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => {
|
||||
currentLabelFontSize--;
|
||||
prospectiveValueFontSize = Math.max(
|
||||
minValueFontSize,
|
||||
Math.min(originalValueRelFontSize, Math.round(currentLabelFontSize * valueScaleFactor))
|
||||
Math.min(baseValueFontSize, Math.round(currentLabelFontSize * valueScaleFactor))
|
||||
);
|
||||
if (
|
||||
prospectiveValueFontSize < minValueFontSize &&
|
||||
@@ -432,13 +439,18 @@ const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => {
|
||||
self.style('font-size', `${currentLabelFontSize}px`);
|
||||
|
||||
// 3. Final visibility check for the label
|
||||
if (
|
||||
textNode.getComputedTextLength() > availableWidth ||
|
||||
currentLabelFontSize < minLabelFontSize ||
|
||||
availableHeight < currentLabelFontSize
|
||||
) {
|
||||
self.style('display', 'none');
|
||||
// If label is hidden, value will be hidden by its own .each() loop
|
||||
if (isComplexTreemap) {
|
||||
if (currentLabelFontSize < minLabelFontSize || availableHeight < minLabelFontSize) {
|
||||
self.style('display', 'none');
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
textNode.getComputedTextLength() > availableWidth ||
|
||||
currentLabelFontSize < minLabelFontSize ||
|
||||
availableHeight < currentLabelFontSize
|
||||
) {
|
||||
self.style('display', 'none');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -454,7 +466,7 @@ const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => {
|
||||
})
|
||||
.attr('style', (d) => {
|
||||
const labelStyles =
|
||||
'text-anchor: middle; dominant-baseline: hanging; font-size: 28px;fill:' +
|
||||
`text-anchor: middle; dominant-baseline: hanging; font-size: ${baseValueFontSize}px;fill:` +
|
||||
colorScaleLabel(d.data.name) +
|
||||
';';
|
||||
const styles = styles2String({ cssCompiledStyles: d.data.cssCompiledStyles } as Node);
|
||||
@@ -481,14 +493,11 @@ const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => {
|
||||
}
|
||||
|
||||
const finalLabelFontSize = parseFloat(labelElement.style('font-size'));
|
||||
const originalValueFontSize = 28; // From initial style setting
|
||||
const valueScaleFactor = 0.6;
|
||||
const minValueFontSize = 6;
|
||||
const spacingBetweenLabelAndValue = 2;
|
||||
|
||||
const actualValueFontSize = Math.max(
|
||||
minValueFontSize,
|
||||
Math.min(originalValueFontSize, Math.round(finalLabelFontSize * valueScaleFactor))
|
||||
Math.min(baseValueFontSize, Math.round(finalLabelFontSize * valueScaleFactor))
|
||||
);
|
||||
valueTextElement.style('font-size', `${actualValueFontSize}px`);
|
||||
|
||||
@@ -500,7 +509,7 @@ const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => {
|
||||
const nodeTotalHeight = d.y1 - d.y0;
|
||||
const cellBottomPadding = 4;
|
||||
const maxValueBottomY = nodeTotalHeight - cellBottomPadding;
|
||||
const availableWidthForValue = nodeWidth - 2 * 4; // padding for value text
|
||||
const availableWidthForValue = nodeWidth - 2 * labelPadding;
|
||||
|
||||
if (
|
||||
valueTextElement.node()!.getComputedTextLength() > availableWidthForValue ||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.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 { insertPolygonShape } from './insertPolygonShape.js';
|
||||
import type { D3Selection } from '../../../types.js';
|
||||
|
||||
export const createHexagonPathD = (
|
||||
@@ -29,42 +28,50 @@ export async function hexagon<T extends SVGGraphicsElement>(parent: D3Selection<
|
||||
node.labelStyle = labelStyles;
|
||||
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
|
||||
|
||||
const f = 4;
|
||||
const h = bbox.height + node.padding;
|
||||
const m = h / f;
|
||||
const w = bbox.width + 2 * m + node.padding;
|
||||
const points = [
|
||||
{ x: m, y: 0 },
|
||||
{ x: w - m, y: 0 },
|
||||
{ x: w, y: -h / 2 },
|
||||
{ x: w - m, y: -h },
|
||||
{ x: m, y: -h },
|
||||
{ x: 0, y: -h / 2 },
|
||||
];
|
||||
|
||||
let polygon: D3Selection<SVGGElement> | Awaited<ReturnType<typeof insertPolygonShape>>;
|
||||
const h = bbox.height + (node.padding ?? 0);
|
||||
const w = bbox.width + (node.padding ?? 0) * 2.5;
|
||||
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') {
|
||||
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
|
||||
const rc = rough.svg(shapeSvg);
|
||||
const options = userNodeOverrides(node, {});
|
||||
const pathData = createHexagonPathD(0, 0, w, h, m);
|
||||
const roughNode = rc.path(pathData, options);
|
||||
|
||||
polygon = shapeSvg
|
||||
.insert(() => roughNode, ':first-child')
|
||||
.attr('transform', `translate(${-w / 2}, ${h / 2})`);
|
||||
|
||||
if (cssStyles) {
|
||||
polygon.attr('style', cssStyles);
|
||||
}
|
||||
} else {
|
||||
polygon = insertPolygonShape(shapeSvg, w, h, points);
|
||||
if (node.look !== 'handDrawn') {
|
||||
options.roughness = 0;
|
||||
options.fillStyle = 'solid';
|
||||
}
|
||||
|
||||
if (nodeStyles) {
|
||||
polygon.attr('style', nodeStyles);
|
||||
let halfWidth = w / 2;
|
||||
const m = halfWidth / 6; // Margin for label
|
||||
halfWidth = halfWidth + m; // Adjusted half width for hexagon
|
||||
|
||||
const halfHeight = h / 2;
|
||||
|
||||
const fixedLength = halfHeight / 2;
|
||||
const deducedWidth = halfWidth - fixedLength;
|
||||
|
||||
const points = [
|
||||
{ x: -deducedWidth, y: -halfHeight },
|
||||
{ x: 0, y: -halfHeight },
|
||||
{ x: deducedWidth, y: -halfHeight },
|
||||
{ x: halfWidth, y: 0 },
|
||||
{ x: deducedWidth, y: halfHeight },
|
||||
{ x: 0, y: halfHeight },
|
||||
{ x: -deducedWidth, y: halfHeight },
|
||||
{ x: -halfWidth, y: 0 },
|
||||
];
|
||||
|
||||
const pathData = createPathFromPoints(points);
|
||||
const shapeNode = rc.path(pathData, options);
|
||||
|
||||
const polygon = shapeSvg.insert(() => shapeNode, ':first-child');
|
||||
polygon.attr('class', 'basic label-container');
|
||||
|
||||
if (cssStyles && node.look !== 'handDrawn') {
|
||||
polygon.selectChildren('path').attr('style', cssStyles);
|
||||
}
|
||||
|
||||
if (nodeStyles && node.look !== 'handDrawn') {
|
||||
polygon.selectChildren('path').attr('style', nodeStyles);
|
||||
}
|
||||
|
||||
node.width = w;
|
||||
|
||||
@@ -1,18 +1,161 @@
|
||||
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 = node.radius || 5;
|
||||
const taper = node.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.stroke) {
|
||||
options.stroke = node.stroke;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user