From 5a02e4d28adcecd0ef271b62b4c0be32f080c9bd Mon Sep 17 00:00:00 2001
From: Corson-Dosch <hcorson-dosch@usgs.gov>
Date: Thu, 20 Feb 2025 15:54:57 -0600
Subject: [PATCH] Fix wrapping fxn and add sankey subtitles

---
 src/components/ThreatSankeyViz.vue | 70 +++++++++++++++++++++++-------
 1 file changed, 54 insertions(+), 16 deletions(-)

diff --git a/src/components/ThreatSankeyViz.vue b/src/components/ThreatSankeyViz.vue
index 4709d9a..81c8f2d 100644
--- a/src/components/ThreatSankeyViz.vue
+++ b/src/components/ThreatSankeyViz.vue
@@ -71,7 +71,7 @@
                     margin: 10,
                     marginLeft: mobileView ? 80: 150,
                     marginRight: mobileView ? 125: 250,
-                    marginTop: 30,
+                    marginTop: mobileView ? 60 : 50,
                     containerId: 'threat-container'
                 });
                 createSankey({
@@ -160,26 +160,57 @@
             .attr('id', 'text_group')
 
         // add titles
-        svg.append("text")
+        const subtitle = mobileView ? "High to low" : "Ranked high to low"
+        const leftTitle = svg.append("text")
             .attr("class", "axis-title")
             .attr("x", chartDimensions.margin.left - labelBuffer + nodeWidth) // match spacing between sankey and labels
-            .attr("y", chartDimensions.margin.top / 2)
+            .attr("y", 0)
             .attr("dx", "0em")
             .attr("dy", "0em")
-            .attr("data-width", chartDimensions.margin.left)
+            .attr("dominant-baseline", "hanging")
+            .attr("text-width", chartDimensions.margin.left)
             .style("text-anchor", "end")
             .text("Threat Categories")
-            .call(d => mobileView ? wrap(d) : d)
+            .call(d => mobileView ? wrap(d, {shift: false}) : d)
+
+        const leftTitleLength = leftTitle.node().getComputedTextLength()
 
         svg.append("text")
+            .attr("class", "axis-text axis-value axis-notation")
+            .attr("x", chartDimensions.margin.left - labelBuffer + nodeWidth) // match spacing between sankey and labels
+            .attr("y", 0)
+            .attr("dx", "0em")
+            .attr("dy", leftTitleLength > chartDimensions.margin.left ? "2.8em" : "1.5em")
+            .attr("dominant-baseline", "hanging")
+            .attr("text-width", chartDimensions.margin.left)
+            .attr("text-anchor", "end")
+            .text(subtitle)
+            .call(d => wrap(d, {shift: false}))
+
+        const rightTitle = svg.append("text")
             .attr("class", "axis-title")
             .attr("x", chartDimensions.width - chartDimensions.margin.right + labelBuffer - nodeWidth) // match spacing between sankey and labels
-            .attr("y", chartDimensions.margin.top / 2)
+            .attr("y", 0)
             .attr("dx", "0em")
             .attr("dy", "0em")
-            .attr("data-width", chartDimensions.margin.right)
+            .attr("dominant-baseline", "hanging")
+            .attr("text-width", chartDimensions.margin.right)
             .style("text-anchor", "start")
             .text("Threats")
+
+        const rightTitleLength = rightTitle.node().getComputedTextLength()
+
+        svg.append("text")
+            .attr("class", "axis-text axis-value axis-notation")
+            .attr("x", chartDimensions.width - chartDimensions.margin.right + labelBuffer - nodeWidth) // match spacing between sankey and labels
+            .attr("y", 0)
+            .attr("dx", "0em")
+            .attr("dy", rightTitleLength > chartDimensions.margin.right ? "2.8em" : "1.5em")
+            .attr("dominant-baseline", "hanging")
+            .attr("text-width", chartDimensions.margin.right)
+            .attr("text-anchor", "start")
+            .text(subtitle)
+            .call(d => wrap(d, {shift: false}))
     };
 
     function createSankey({
@@ -278,12 +309,13 @@
                             .attr("class", d => d.x0 < chartDimensions.boundedWidth / 2 ? "axis-text left" : "axis-text right")
                             .attr("x", d => d.x0 < chartDimensions.boundedWidth / 2 ? d.x1 : d.x0) //checks for right-most labels
                             .attr("y", d => (d.y1 + d.y0) / 2)
-                            .attr("dy", "0.35em")
+                            .attr("dy", "0em")
                             .attr("dx", d => d.x0 < chartDimensions.boundedWidth / 2 ? -labelBuffer : labelBuffer)
+                            .attr("dominant-baseline", "central")
                             .attr("text-anchor", d => d.x0 < chartDimensions.boundedWidth / 2 ? "end" : "start") //checks for right-most labels
-                            .attr("data-width", d => d.x0 < chartDimensions.boundedWidth / 2 ? chartDimensions.margin.left : chartDimensions.margin.right)
+                            .attr("text-width", d => d.x0 < chartDimensions.boundedWidth / 2 ? chartDimensions.margin.left : chartDimensions.margin.right)
                             .text(d => d.name)
-                            .call(d => mobileView ? wrap(d) : d)
+                            .call(d => mobileView ? wrap(d, {shift: true}) : d)
                         // .append("tspan")
                         //     .attr("fill-opacity", 0.7)
                         //     .text(d => ` ${d.value.toLocaleString()}`)
@@ -349,7 +381,9 @@
     };
 
     // https://gist.github.com/mbostock/7555321
-    function wrap(text) {
+    function wrap(text, {
+        shift = false
+    }) {
         text.each(function() {
             var text = d3.select(this),
             words = text.text().split(/\s|-+/).reverse(),
@@ -357,12 +391,13 @@
             line = [],
             lineNumber = 0,
             lineHeight = 1.1, // ems
-            width = text.attr("data-width"),
+            width = text.attr("text-width"),
+            baseline = text.attr("dominant-baseline"),
             x = text.attr("x"),
             y = text.attr("y"),
             dy = parseFloat(text.attr("dy")),
             dx = parseFloat(text.attr("dx")),
-            tspan = text.text(null).append("tspan").attr("y", y).attr("dy", dy + "em");
+            tspan = text.text(null).append("tspan").attr("y", y).attr("dy", dy + "em").attr("dominant-baseline", baseline);;
             
             console.log(text.attr("dy"))
 
@@ -373,13 +408,13 @@
                 line.pop();
                 tspan.text(line.join(" "));
                 line = [word];
-                tspan = text.append("tspan").attr("x", x).attr("y", y).attr("dx", dx).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
+                tspan = text.append("tspan").attr("x", x).attr("y", y).attr("dx", dx).attr("dy", ++lineNumber * lineHeight + dy + "em").attr("dominant-baseline", baseline).text(word);
                 }
             }
 
             // https://stackoverflow.com/questions/60558291/wrapping-and-vertically-centering-text-using-d3-js
-            if (lineNumber > 0) {
-                const startDy = -(lineNumber * (lineHeight / 2)) * 0.5; // *0.5 for vertically-centered labels
+            if (lineNumber > 0  && shift) {
+                const startDy = -(lineNumber * (lineHeight / 2));
                 text
                     .selectAll("tspan")
                     .attr("dy", (d, i) => startDy + lineHeight * i + "em");
@@ -411,4 +446,7 @@
             font-size: 1.6rem;
         }
     }
+    .axis-notation {
+        font-style: italic;
+    }
 </style>
\ No newline at end of file
-- 
GitLab