diff --git a/app/components/RuleRelationsDisplay/subcomponents/RuleNodesGroup.ts b/app/components/RuleRelationsDisplay/subcomponents/RuleNodesGroup.ts index 7336eda..37bd433 100644 --- a/app/components/RuleRelationsDisplay/subcomponents/RuleNodesGroup.ts +++ b/app/components/RuleRelationsDisplay/subcomponents/RuleNodesGroup.ts @@ -41,6 +41,19 @@ export const RuleNodesGroup = ({ }; const createNodeGroup = () => { + //Drop Shadow on node text + const defs = containerGroup.append("defs"); + defs + .append("filter") + .attr("id", "drop-shadow") + .attr("height", "130%") + .append("feDropShadow") + .attr("dx", "0") + .attr("dy", "1") + .attr("stdDeviation", "2") + .attr("flood-color", "rgba(0,0,0,0.3)"); + + // Nodes with aria labels const nodeGroup = containerGroup .append("g") .selectAll("g") @@ -51,25 +64,67 @@ export const RuleNodesGroup = ({ .attr("aria-label", (d) => `Rule: ${d.name}`) .call(d3.drag().on("start", dragstarted).on("drag", dragged).on("end", dragended)); - // Add circles - nodeGroup - .append("circle") - .attr("r", (d) => d.radius) - .attr("fill", "#69b3a2"); - - // Add labels + // Text labels on nodes nodeGroup .append("text") - .text((d) => d.label || d.name) - .attr("font-size", "12px") - .attr("dx", 12) - .attr("dy", 4) - .attr("cursor", "pointer") - .attr("role", "link") - .style("text-decoration", "underline") - .style("fill", "#0066cc") - .style("font-weight", "500") - .style("text-shadow", "0 0 3px white") + .each(function (d) { + const text = d3.select(this); + const words = (d.label || d.name).split(/\s+/); + let line = ""; + let lineNumber = 0; + const lineHeight = 1.1; + const y = 4; + const dy = 0; + const dx = d.radius + 8; + const padding = 6; + + const textGroup = d3.select(this.parentNode as Element).insert("g", "text"); + + text + .attr("font-size", "12px") + .attr("dx", dx) + .attr("dy", dy) + .attr("cursor", "pointer") + .attr("role", "link") + .style("fill", "#0066cc") + .style("font-weight", "500"); + + const tspans: d3.Selection[] = []; + + for (let i = 0; i < words.length; i++) { + let word = words[i]; + if (line.length > 0) { + line = `${line} ${word}`; + } else { + line = word; + } + + if (line.length > 20 || i === words.length - 1) { + const tspan = text + .append("tspan") + .attr("x", dx) + .attr("dx", lineNumber === 0 ? 0 : dx) + .attr("dy", lineNumber ? `${lineHeight}em` : y) + .text(line); + + tspans.push(tspan); + line = ""; + lineNumber++; + } + } + + const bbox = (this as SVGTextElement).getBBox(); + textGroup + .insert("rect", "text") + .attr("x", bbox.x - padding) + .attr("y", bbox.y - padding) + .attr("width", bbox.width + padding * 2) + .attr("height", bbox.height + padding * 2) + .attr("fill", "white") + .attr("rx", 6) + .attr("ry", 6) + .style("filter", "url(#drop-shadow)"); + }) .on("mouseover", function () { d3.select(this).style("fill", "#003366"); }) @@ -77,6 +132,12 @@ export const RuleNodesGroup = ({ d3.select(this).style("fill", "#0066cc"); }); + // Circles for nodes + nodeGroup + .append("circle") + .attr("r", (d) => d.radius) + .attr("fill", "#69b3a2"); + return nodeGroup; };