From 4a431078d550e415816b17de54d28a02edb70791 Mon Sep 17 00:00:00 2001
From: Cee <cnell@usgs.gov>
Date: Tue, 8 Oct 2024 14:17:58 -0700
Subject: [PATCH 1/5] preserve positions

---
 src/components/BeaufortSeaTimelineViz.vue | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/src/components/BeaufortSeaTimelineViz.vue b/src/components/BeaufortSeaTimelineViz.vue
index b6ac839..aa6504f 100644
--- a/src/components/BeaufortSeaTimelineViz.vue
+++ b/src/components/BeaufortSeaTimelineViz.vue
@@ -199,9 +199,7 @@
         
         const nodes = data.map((d) => ({
             ...d,
-            radius: sizeScale(parseFloat(d.pct_abundance)),
-            x: bubbleChartDimensions.boundedWidth / 2,
-            y: bubbleChartDimensions.boundedHeight / 2
+            radius: sizeScale(parseFloat(d.pct_abundance))
         }));
        
         // set up nodes
@@ -221,6 +219,7 @@
         const newNodeGroups = nodeGroups.enter().append("g")
             .attr("class", "node")
             .attr("id", d => "group_" + d.species_id)
+            .attr("transform", d => `translate(${d.x || bubbleChartDimensions.boundedWidth / 2}, ${d.y || bubbleChartDimensions.boundedHeight / 2})`);
 
         newNodeGroups.append("circle")
             .attr("id", d => d.species_id)
-- 
GitLab


From 9517e836b619af1455421ae1c43fc748ade2bcc3 Mon Sep 17 00:00:00 2001
From: Cee <cnell@usgs.gov>
Date: Tue, 8 Oct 2024 14:35:18 -0700
Subject: [PATCH 2/5] tweak forces

---
 src/components/BeaufortSeaTimelineViz.vue | 50 +++++------------------
 1 file changed, 11 insertions(+), 39 deletions(-)

diff --git a/src/components/BeaufortSeaTimelineViz.vue b/src/components/BeaufortSeaTimelineViz.vue
index aa6504f..78298b5 100644
--- a/src/components/BeaufortSeaTimelineViz.vue
+++ b/src/components/BeaufortSeaTimelineViz.vue
@@ -229,60 +229,32 @@
             .attr("r", 0) //instantiate w/ radius = 0
 
         //update nodeGroups to include new nodes
-        nodeGroups = newNodeGroups.merge(nodeGroups)
+        nodeGroups = newNodeGroups.merge(nodeGroups);
 
-        const nodeGroupCircle = nodeGroups.select("circle")
-        
-        nodeGroupCircle
+        nodeGroups.select("circle")
             .transition(getUpdateTransition())
-            .attr("r", d => d.radius)
+            .attr("r", d => d.radius);
 
         function ticked() {
-            nodeGroupCircle
-                // .transition(getUpdateTransition()) // BREAKS d3 force
-                // .attr("r", d => d.radius)
-                .attr("cx", (d) => d.x)
-                .attr("cy", (d) => d.y);
+            nodeGroups
+                .transition(getUpdateTransition())
+                .attr("transform", d => `translate(${d.x}, ${d.y})`);
         }
 
         // set up d3 force simulation
         if (simulation.value) {
-            // simulation.value.stop()
             simulation.value
                 .nodes(nodes)
                 .alpha(0.9)
-                .restart()
-                // .force("x", d3.forceX(bubbleChartDimensions.boundedWidth / 2).strength(0.05))
-                // .force("y", d3.forceY(bubbleChartDimensions.boundedHeight / 2).strength(0.05))
-                // .force("center", d3.forceCenter(bubbleChartDimensions.boundedWidth / 2, bubbleChartDimensions.boundedHeight / 2))
-                // .force(
-                //     "collide",
-                //     d3.forceCollide()
-                //         .radius((d) => d.radius + 2)
-                //         .iterations(1)
-                // )
-                // .force('charge', d3.forceManyBody().strength(0.01))
-                // .alphaMin(0.01)
-                // .alpha(0)
-                // .alphaDecay(0.005)
-                .velocityDecay(0.9)
-                .on("tick", ticked);
+                .restart();
         } else {
             simulation.value = d3.forceSimulation();
             simulation.value
                 .nodes(nodes)
-                .force("x", d3.forceX(bubbleChartDimensions.boundedWidth / 2).strength(0.05))
-                .force("y", d3.forceY(bubbleChartDimensions.boundedHeight / 2).strength(0.05))
-                // .force("center", d3.forceCenter(bubbleChartDimensions.boundedWidth / 2, bubbleChartDimensions.boundedHeight / 2))
-                .force(
-                    "collide",
-                    d3.forceCollide()
-                        .radius((d) => d.radius + 2)
-                        .iterations(1)
-                )
-                .force('charge', d3.forceManyBody().strength(0.01))
-                // .alphaMin(0.01)
-                // .alphaDecay(0.005)
+                .force("x", d3.forceX(bubbleChartDimensions.boundedWidth / 2).strength(0.2))
+                .force("y", d3.forceY(bubbleChartDimensions.boundedHeight / 2).strength(0.2))
+                .force("collide", d3.forceCollide().radius(d => d.radius + 2).iterations(1))
+                .force('charge', d3.forceManyBody().strength(-5))
                 .velocityDecay(0.9)
                 .on("tick", ticked);
         }
-- 
GitLab


From c5835fdad3c077062706c6768dd3f352e26f5a9e Mon Sep 17 00:00:00 2001
From: Cee <cnell@usgs.gov>
Date: Tue, 8 Oct 2024 14:44:48 -0700
Subject: [PATCH 3/5] rm dup selectall

---
 src/components/BeaufortSeaTimelineViz.vue | 20 ++++++++------------
 1 file changed, 8 insertions(+), 12 deletions(-)

diff --git a/src/components/BeaufortSeaTimelineViz.vue b/src/components/BeaufortSeaTimelineViz.vue
index 78298b5..07adb22 100644
--- a/src/components/BeaufortSeaTimelineViz.vue
+++ b/src/components/BeaufortSeaTimelineViz.vue
@@ -203,20 +203,18 @@
         }));
        
         // set up nodes
-        let nodeGroups = bubbleChartBounds.selectAll('.nodes')
+        let nodeGroups = bubbleChartBounds
             .selectAll(".node")
             .data(nodes, d => d.species_id)
 
         const oldNodeGroups = nodeGroups.exit()
 
-        oldNodeGroups.selectAll("circle")
-            .attr("r", 0); //radius to 0
-
-        // Remove old nodes
-        oldNodeGroups.transition(getExitTransition()).remove()
+        oldNodeGroups.selectAll("circle").attr("r", 0); //radius to 0
+        oldNodeGroups.transition(getExitTransition()).remove() // Remove old nodes
 
         // Append new nodes
-        const newNodeGroups = nodeGroups.enter().append("g")
+        const newNodeGroups = nodeGroups.enter()
+            .append("g")
             .attr("class", "node")
             .attr("id", d => "group_" + d.species_id)
             .attr("transform", d => `translate(${d.x || bubbleChartDimensions.boundedWidth / 2}, ${d.y || bubbleChartDimensions.boundedHeight / 2})`);
@@ -237,7 +235,6 @@
 
         function ticked() {
             nodeGroups
-                .transition(getUpdateTransition())
                 .attr("transform", d => `translate(${d.x}, ${d.y})`);
         }
 
@@ -248,9 +245,8 @@
                 .alpha(0.9)
                 .restart();
         } else {
-            simulation.value = d3.forceSimulation();
-            simulation.value
-                .nodes(nodes)
+            simulation.value = d3.forceSimulation(nodes)
+                .force("center", d3.forceCenter(bubbleChartDimensions.boundedWidth / 2, bubbleChartDimensions.boundedHeight / 2))
                 .force("x", d3.forceX(bubbleChartDimensions.boundedWidth / 2).strength(0.2))
                 .force("y", d3.forceY(bubbleChartDimensions.boundedHeight / 2).strength(0.2))
                 .force("collide", d3.forceCollide().radius(d => d.radius + 2).iterations(1))
@@ -263,7 +259,7 @@
     // define transitions
     function getUpdateTransition() {
       return d3.transition()
-        .duration(2000)
+        .duration(500)
         .ease(d3.easeCubicInOut)
     }
     function getExitTransition() {
-- 
GitLab


From 9fb19999b7a10e9d4c821978ded8ee4fb4531131 Mon Sep 17 00:00:00 2001
From: Cee <cnell@usgs.gov>
Date: Tue, 8 Oct 2024 14:58:11 -0700
Subject: [PATCH 4/5] separate simulation from chart fxn

---
 src/components/BeaufortSeaTimelineViz.vue | 45 ++++++++++++-----------
 1 file changed, 24 insertions(+), 21 deletions(-)

diff --git a/src/components/BeaufortSeaTimelineViz.vue b/src/components/BeaufortSeaTimelineViz.vue
index 07adb22..faf4ef8 100644
--- a/src/components/BeaufortSeaTimelineViz.vue
+++ b/src/components/BeaufortSeaTimelineViz.vue
@@ -207,10 +207,11 @@
             .selectAll(".node")
             .data(nodes, d => d.species_id)
 
-        const oldNodeGroups = nodeGroups.exit()
-
-        oldNodeGroups.selectAll("circle").attr("r", 0); //radius to 0
-        oldNodeGroups.transition(getExitTransition()).remove() // Remove old nodes
+        // Handle exit
+        nodeGroups.exit().selectAll("circle")
+            .transition(getExitTransition())
+            .attr("r", 0) //radius to 0
+            .remove() // Remove old nodes
 
         // Append new nodes
         const newNodeGroups = nodeGroups.enter()
@@ -225,6 +226,8 @@
             .attr("stroke-width", 0.5)
             .attr("fill", d => d.hexcode)
             .attr("r", 0) //instantiate w/ radius = 0
+            .transition(getUpdateTransition()) // Transition to final size
+            .attr("r", d => d.radius);
 
         //update nodeGroups to include new nodes
         nodeGroups = newNodeGroups.merge(nodeGroups);
@@ -233,29 +236,29 @@
             .transition(getUpdateTransition())
             .attr("r", d => d.radius);
 
+        // Update the force simulation
+        updateSimulation(nodes, nodeGroups);
+    }
+    function updateSimulation(nodes, nodeGroups) {
         function ticked() {
-            nodeGroups
-                .attr("transform", d => `translate(${d.x}, ${d.y})`);
+            nodeGroups.attr("transform", d => `translate(${d.x}, ${d.y})`);
         }
 
-        // set up d3 force simulation
         if (simulation.value) {
-            simulation.value
-                .nodes(nodes)
-                .alpha(0.9)
-                .restart();
-        } else {
-            simulation.value = d3.forceSimulation(nodes)
-                .force("center", d3.forceCenter(bubbleChartDimensions.boundedWidth / 2, bubbleChartDimensions.boundedHeight / 2))
-                .force("x", d3.forceX(bubbleChartDimensions.boundedWidth / 2).strength(0.2))
-                .force("y", d3.forceY(bubbleChartDimensions.boundedHeight / 2).strength(0.2))
-                .force("collide", d3.forceCollide().radius(d => d.radius + 2).iterations(1))
-                .force('charge', d3.forceManyBody().strength(-5))
-                .velocityDecay(0.9)
-                .on("tick", ticked);
+            // Clear forces and reset simulation for new nodes
+            simulation.value.stop();
         }
-    }
 
+        // Create a new force simulation
+        simulation.value = d3.forceSimulation(nodes)
+            .force("center", d3.forceCenter(bubbleChartDimensions.boundedWidth / 2, bubbleChartDimensions.boundedHeight / 2))
+            .force("x", d3.forceX(bubbleChartDimensions.boundedWidth / 2).strength(0.3)) // Pull toward the center on x-axis
+            .force("y", d3.forceY(bubbleChartDimensions.boundedHeight / 2).strength(0.3)) // Pull toward the center on y-axis
+            .force("collide", d3.forceCollide(d => d.radius + 2).strength(1))  // Ensure no overlap
+            .force("charge", d3.forceManyBody().strength(-15))  // Weak repulsive force to spread nodes slightly
+            .velocityDecay(0.8)
+            .on("tick", ticked); // Update positions during simulation
+    }
     // define transitions
     function getUpdateTransition() {
       return d3.transition()
-- 
GitLab


From 521c6c64a1d07edf62bc88c1db581ba7eff68ffd Mon Sep 17 00:00:00 2001
From: Cee <cnell@usgs.gov>
Date: Tue, 8 Oct 2024 16:21:15 -0700
Subject: [PATCH 5/5] make exiting circles invisible but still present

---
 src/components/BeaufortSeaTimelineViz.vue | 88 ++++++++++++-----------
 1 file changed, 47 insertions(+), 41 deletions(-)

diff --git a/src/components/BeaufortSeaTimelineViz.vue b/src/components/BeaufortSeaTimelineViz.vue
index faf4ef8..721f6fc 100644
--- a/src/components/BeaufortSeaTimelineViz.vue
+++ b/src/components/BeaufortSeaTimelineViz.vue
@@ -183,91 +183,97 @@
             .attr("class", "nodes")
     }
 
-    function drawBubbleChart(data, {
-        decade = 200
-    }) {
-        // Set radius based on data values across all decades
+    function drawBubbleChart(data, { decade = 200 }) {
         const sizeScale = d3.scaleSqrt()
-            .domain([
-                d3.min(data, (d) => parseFloat(d.pct_abundance)),
-                d3.max(data, (d) => parseFloat(d.pct_abundance))
-            ])
+            .domain([d3.min(data, d => parseFloat(d.pct_abundance)), d3.max(data, d => parseFloat(d.pct_abundance))])
             .range([4, chart.value.offsetWidth / 7]);
 
-        // filter data to current decade and filter out decades where species is not present
+        // Filter data for the selected decade
         data = data.filter(d => d.decade === decade && d.pct_abundance > 0);
-        
-        const nodes = data.map((d) => ({
+
+        const nodes = data.map(d => ({
             ...d,
-            radius: sizeScale(parseFloat(d.pct_abundance))
+            radius: sizeScale(parseFloat(d.pct_abundance)),
+            x: d.x || Math.random() * bubbleChartDimensions.boundedWidth, // Retain previous position if available
+            y: d.y || Math.random() * bubbleChartDimensions.boundedHeight
         }));
-       
-        // set up nodes
-        let nodeGroups = bubbleChartBounds
-            .selectAll(".node")
-            .data(nodes, d => d.species_id)
 
-        // Handle exit
-        nodeGroups.exit().selectAll("circle")
+        // Join data to nodes, keyed by species_id
+        let nodeGroups = bubbleChartBounds.selectAll(".node")
+            .data(nodes, d => d.species_id);  // Ensure the key is species_id
+
+        // Handle exit (hide old nodes instead of removing them)
+        nodeGroups.exit().select("circle")
             .transition(getExitTransition())
-            .attr("r", 0) //radius to 0
-            .remove() // Remove old nodes
+            .attr("r", 0)  // Shrink radius
+            .style("opacity", 0)  // Set opacity to 0 (hide)
+            .on("end", function() {
+                d3.select(this).attr("visibility", "hidden");  // Hide from view but keep in DOM
+            });
 
-        // Append new nodes
+        // Handle enter (new nodes)
         const newNodeGroups = nodeGroups.enter()
             .append("g")
             .attr("class", "node")
             .attr("id", d => "group_" + d.species_id)
-            .attr("transform", d => `translate(${d.x || bubbleChartDimensions.boundedWidth / 2}, ${d.y || bubbleChartDimensions.boundedHeight / 2})`);
+            .attr("transform", d => `translate(${d.x}, ${d.y})`);
 
         newNodeGroups.append("circle")
             .attr("id", d => d.species_id)
             .attr("stroke", "#000000")
             .attr("stroke-width", 0.5)
             .attr("fill", d => d.hexcode)
-            .attr("r", 0) //instantiate w/ radius = 0
-            .transition(getUpdateTransition()) // Transition to final size
+            .attr("r", 0)  // Start new circles at radius 0
+            .transition(getUpdateTransition())  // Transition to final size
             .attr("r", d => d.radius);
 
-        //update nodeGroups to include new nodes
+        // Merge enter and update selections
         nodeGroups = newNodeGroups.merge(nodeGroups);
 
+        // Handle updates (transitioning circles based on new data)
         nodeGroups.select("circle")
+            .attr("visibility", "visible")  // Make re-entered nodes visible again
             .transition(getUpdateTransition())
-            .attr("r", d => d.radius);
+            .attr("r", d => d.radius)
+            .style("opacity", 1);  // Ensure opacity is set back to 1
 
-        // Update the force simulation
+        // Update the force simulation with the full set of nodes
         updateSimulation(nodes, nodeGroups);
     }
+
     function updateSimulation(nodes, nodeGroups) {
         function ticked() {
             nodeGroups.attr("transform", d => `translate(${d.x}, ${d.y})`);
         }
 
-        if (simulation.value) {
-            // Clear forces and reset simulation for new nodes
-            simulation.value.stop();
+        if (!simulation.value) {
+            simulation.value = d3.forceSimulation();
         }
 
-        // Create a new force simulation
-        simulation.value = d3.forceSimulation(nodes)
+        // Restart the simulation and reapply forces
+        simulation.value.nodes(nodes)
             .force("center", d3.forceCenter(bubbleChartDimensions.boundedWidth / 2, bubbleChartDimensions.boundedHeight / 2))
-            .force("x", d3.forceX(bubbleChartDimensions.boundedWidth / 2).strength(0.3)) // Pull toward the center on x-axis
-            .force("y", d3.forceY(bubbleChartDimensions.boundedHeight / 2).strength(0.3)) // Pull toward the center on y-axis
-            .force("collide", d3.forceCollide(d => d.radius + 2).strength(1))  // Ensure no overlap
-            .force("charge", d3.forceManyBody().strength(-15))  // Weak repulsive force to spread nodes slightly
-            .velocityDecay(0.8)
-            .on("tick", ticked); // Update positions during simulation
+            .force("x", d3.forceX(bubbleChartDimensions.boundedWidth / 2).strength(0.3))  // Pull toward center on x-axis
+            .force("y", d3.forceY(bubbleChartDimensions.boundedHeight / 2).strength(0.3))  // Pull toward center on y-axis
+            .force("collide", d3.forceCollide(d => d.radius + 2).strength(1))  // Prevent overlap
+            .force("charge", d3.forceManyBody().strength(-15))  // Slight repulsion to spread nodes slightly
+            .on("tick", ticked);  // Call tick function on each simulation iteration
+
+        // Restart the simulation with an alpha of 0.7 to ensure proper positioning
+        simulation.value.alpha(0.7).restart();
     }
+
+
+
     // define transitions
     function getUpdateTransition() {
       return d3.transition()
-        .duration(500)
+        .duration(700)
         .ease(d3.easeCubicInOut)
     }
     function getExitTransition() {
       return d3.transition()
-        .duration(500)
+        .duration(700)
         .ease(d3.easeCubicInOut)
     }
 
-- 
GitLab