From 10551148bf0bfcbe7f3eaf4a995e22144e8eee48 Mon Sep 17 00:00:00 2001
From: Corson-Dosch <hcorson-dosch@usgs.gov>
Date: Fri, 16 Aug 2024 16:04:35 -0500
Subject: [PATCH 01/25] draft fish clmate vulnerability chart

---
 public/fish_as_food_climate_test.csv      |  13 +
 src/assets/content/ChartGrid.js           |   8 +-
 src/components/FishAsFoodLinkChartViz.vue | 344 ++++++++++++++++++++++
 src/components/Viz3Viz.vue                |  37 ---
 4 files changed, 361 insertions(+), 41 deletions(-)
 create mode 100644 public/fish_as_food_climate_test.csv
 create mode 100644 src/components/FishAsFoodLinkChartViz.vue
 delete mode 100644 src/components/Viz3Viz.vue

diff --git a/public/fish_as_food_climate_test.csv b/public/fish_as_food_climate_test.csv
new file mode 100644
index 0000000..e052fb1
--- /dev/null
+++ b/public/fish_as_food_climate_test.csv
@@ -0,0 +1,13 @@
+family,thermal_guild,species,cvi_2030,cvi_2075
+A,warm,species_1,0.319953022,0.35962678
+A,warm,species_2,0.224863212,0.194521316
+A,warm,species_3,0.835617044,0.810848349
+A,warm,species_4,0.68844979,0.080709824
+A,warm,species_5,0.506342706,0.733569142
+A,warm,species_6,0.313221865,0.058278019
+B,cool,species_7,0.484686402,0.646526582
+B,cool,species_8,0.943640492,0.319394434
+B,cool,species_9,0.894768952,0.914064253
+B,cool,species_10,0.069633857,0.332752341
+C,cold,species_11,0.953508638,0.178160176
+C,cold,species_12,0.092467137,0.585798787
diff --git a/src/assets/content/ChartGrid.js b/src/assets/content/ChartGrid.js
index 251d23a..61ff2c4 100644
--- a/src/assets/content/ChartGrid.js
+++ b/src/assets/content/ChartGrid.js
@@ -21,10 +21,10 @@ export default {
             description: 'Description or key takeaways from this Viz (Viz 2)'
         },       
         {
-            title: 'Viz 3',
-            project: 'Findex',
-            vizKey: 'Viz3',
-            vizRoute: 'viz-3',
+            title: 'Climate vulnerability of recreationally fished inland fish species',
+            project: 'Fish as Food',
+            vizKey: 'FishAsFoodLinkChart',
+            vizRoute: 'inland-rec-fish-climate',
             img_src: 'Placeholder_thumbnail.PNG',
             alt: '',
             description: 'Description or key takeaways from this Viz (Viz 3)'
diff --git a/src/components/FishAsFoodLinkChartViz.vue b/src/components/FishAsFoodLinkChartViz.vue
new file mode 100644
index 0000000..6321669
--- /dev/null
+++ b/src/components/FishAsFoodLinkChartViz.vue
@@ -0,0 +1,344 @@
+<template>
+    <!---VizSection-->
+    <VizSection
+        :figures="true"
+        :fig-caption="false"
+    >
+        <!-- HEADING -->
+        <template #heading>
+            <h2>
+            </h2>
+        </template>
+        <!-- FIGURES -->
+        <template #aboveExplanation>
+        </template>
+        <template #figures>
+            <div id="chart-container"></div>
+        </template>
+        <!-- FIGURE CAPTION -->
+        <template #figureCaption>
+        </template>
+        <!-- EXPLANATION -->
+        <template #belowExplanation>
+        </template>
+    </VizSection>
+</template>
+
+<script setup>
+    import { onMounted, ref } from "vue";
+    import * as d3 from 'd3';
+    import VizSection from '@/components/VizSection.vue';
+
+    // define props
+    defineProps({
+        text: { type: Object }
+    })
+
+    // global variables
+    const publicPath = import.meta.env.BASE_URL;
+    const dataFile = 'fish_as_food_climate_test.csv'
+    const data = ref();
+    let chartDimensions;
+    const chartTitle = 'Title of chart';
+    let chartBounds;
+    let xScale;
+    let yScale;
+    let widthScale;
+    let colorScale;
+
+
+    // Behavior on mounted (functions called here)
+    // Load data and then make chart
+    onMounted(async () => {
+        try {
+            await loadDatasets();
+            if (data.value.length > 0) {
+                initChart({
+                    width: 400,
+                    height: 300,
+                    margin: 10,
+                    marginBottom: 30,
+                    marginLeft: 50});
+                drawChart();
+            } else {
+                console.error('Error loading data');
+            }
+        } catch (error) {
+            console.error('Error during component mounting', error);
+        }
+    });
+
+    async function loadDatasets() {
+        try {
+            data.value = await loadData(dataFile);
+            console.log('data in');
+        } catch (error) {
+            console.error('Error loading datasets', error);
+        }
+    }
+
+    async function loadData(fileName) {
+        try {
+            const data = await d3.csv(publicPath + fileName);
+            return data;
+        } catch (error) {
+            console.error(`Error loading data from ${fileName}`, error);
+            return [];
+        }
+    }
+
+    function initChart({
+        width = 500, // outer width, in pixels
+        height = width, // outer height, in pixels
+        margin = 1, // default margins
+        marginTop = margin, // top margin, in pixels
+        marginBottom = margin, // left margin, in pixels
+        marginLeft = margin, // left margin, in pixels
+        marginRight = margin // right margin, in pixels
+    }) {
+        // set up global chart dimensions
+        chartDimensions = {
+            width,
+            height,
+            margin: {
+                top: marginTop,
+                right: marginRight,
+                bottom: marginBottom,
+                left: marginLeft
+            }
+        }
+        chartDimensions.boundedWidth = chartDimensions.width - chartDimensions.margin.left - chartDimensions.margin.right
+        chartDimensions.boundedHeight = chartDimensions.height - chartDimensions.margin.top - chartDimensions.margin.bottom
+
+        // draw canvas for chart
+        const chartSVG = d3.select("#chart-container")
+            .append("svg")
+                .attr("viewBox", [0, 0, (chartDimensions.width), (chartDimensions.height)].join(' '))
+                .attr("width", "100%")
+                .attr("height", "100%")
+                .attr("id", "chart-svg")
+
+        // assign role for accessibility
+        chartSVG.attr("role", "figure")
+            .append("title")
+            .text(chartTitle)
+
+        // Add group for bounds
+        chartBounds = chartSVG.append("g")
+            .attr("id", "chart-bounds")
+            .style("transform", `translate(${
+                chartDimensions.margin.left
+            }px, ${
+                chartDimensions.margin.top
+            }px)`)
+
+        // Initialize scales
+        initXScale()
+        initYScale()
+    }
+
+    function initXScale() {
+        // scale for the x axis (domain set in `drawChart()`)
+        xScale = d3.scaleLinear()
+            .range([0, chartDimensions.boundedWidth]);
+
+        // add group for x axis
+        const xAxis = chartBounds.append("g")
+            .attr("id", "x-axis")
+            .attr("class", "axis")
+            .attr("transform", `translate(0,${chartDimensions.boundedHeight})`)
+            .attr("aria-hidden", true) // hide from screen reader
+
+        // generate x axis
+        xAxis
+            .call(d3.axisBottom(xScale).tickSize(0).tickPadding(10))
+            // .select(".domain").remove() // remove axis line
+        
+        // add placeholder for x axis title (title text set in drawChart())
+        xAxis
+            .append("text")
+            .attr("class", "x-axis axis-title")
+            .attr("x", -chartDimensions.boundedWidth / 2)
+            .style("text-anchor", "middle")
+            .attr("role", "presentation")
+            .attr("aria-hidden", true)
+    }
+
+    function initYScale() {
+        // scale for y axis (domain set in `drawChart()`)
+        yScale = d3.scaleBand()
+            .range([chartDimensions.boundedHeight, 0])
+            .padding(0.1)
+
+        // add group for y axis
+        const yAxis = chartBounds.append("g")
+            .attr("id", "y-axis")
+            .attr("class", "axis")
+            .attr("aria-hidden", true)
+
+        // generate y axis
+        yAxis
+            .call(d3.axisLeft(yScale).tickSize(2))
+            // .select(".domain").remove() // remove axis line
+
+        // add placeholder for y axis title (title text set in drawChart())
+        yAxis
+            .append("text")
+            .attr("class", "y-axis axis-title")
+            .attr("x", -chartDimensions.boundedHeight / 2)
+            .attr("transform", "rotate(-90)")
+            .style("text-anchor", "middle")
+            .attr("role", "presentation")
+            .attr("aria-hidden", true)
+    }
+
+    function initWidthScale(low, high) {
+        widthScale = d3.scaleLinear()
+            .domain([0, 1])
+            .range([low, high]);
+    }
+
+    function initColorScale(data) {
+        colorScale = d3.scaleOrdinal()
+            .domain(data)
+            .range(data.map(item => getColor(item)));
+    }
+
+    function getColor(item) {
+        let itemColor;
+        switch(item) {
+            case 'warm':
+                itemColor = '#FF7256';
+                break;
+            case 'cool':
+                itemColor = '#5CACEE';
+                break;
+            case 'cold':
+                itemColor = '#36648B';
+                break;
+            default:
+                itemColor =  '#B3B3B3';
+        }
+        return itemColor;
+    }
+
+    function drawChart() {
+        // accessor functions
+        const yAccessor = d => d.species
+        const xAccessor = d => d.cvi
+        const x0Accessor = d => d.cvi_2030
+        const x1Accessor = d => d.cvi_2075
+        const widthAccessor = d => d.position
+        const colorAccessor = d => d.thermal_guild
+        const identifierAccessor = d => d.family + '_' + d.species
+
+        // set domain for xScale
+        xScale
+            .domain([0, d3.max([d3.max(data.value, x0Accessor), d3.max(data.value, x1Accessor)])])
+
+        // set domain for yScale
+        yScale
+            .domain(d3.union(data.value.map(yAccessor).sort(d3.ascending)))
+
+        // set up width scale
+        const radiusPosition0 = 1
+        const strokeRatio = 0.3
+        const strokeWidth1 = strokeRatio * yScale.bandwidth() / 2
+        const radiusPosition1 = (1 - strokeRatio) * yScale.bandwidth() / 2
+        initWidthScale(radiusPosition0, radiusPosition1)
+
+        // set up area function
+        const area = d3.area()
+            .x(d => xScale(xAccessor(d)))
+            .y0(d => yScale(yAccessor(d)) - widthScale(widthAccessor(d)))
+            .y1(d => yScale(yAccessor(d)) + widthScale(widthAccessor(d)));
+
+        // set up color scale
+        const colorCategories = Array.from(new Set(data.value.map(colorAccessor)))
+        initColorScale(colorCategories)
+
+        // set up area data
+        const areaCategories = Array.from(new Set(data.value.map(yAccessor)))
+        const areaData = areaCategories.map(areaCategory => {
+            return [
+                {
+                    species: areaCategory,
+                    cvi: data.value.filter(d => d.species === areaCategory)[0].cvi_2030,
+                    position: 0,
+                    thermal_guild: data.value.filter(d => d.species === areaCategory)[0].thermal_guild
+                },
+                {
+                    species: areaCategory,
+                    cvi: data.value.filter(d => d.species === areaCategory)[0].cvi_2075,
+                    position: 1,
+                    thermal_guild: data.value.filter(d => d.species === areaCategory)[0].thermal_guild
+                }
+            ]
+        })
+            
+        // draw chart
+        chartBounds.append("g")
+            .attr("id", "areas")
+            .selectAll('.area')
+                .data(areaData)
+                .enter()
+                .append('path')
+                    .attr("id", d => 'area-2030-' + identifierAccessor(d))
+                    .attr('class', "area")
+                    .attr('d', d => area(d))
+                    .attr('fill', d => colorScale(colorAccessor(d[0])))
+                    .style("opacity", 1);
+
+        chartBounds.append("g")
+            .attr("id", "points-2030")
+            .attr("class", "points points_2030")    
+            .selectAll('points')
+                .data(data.value)
+                .enter()
+                .append("circle")
+                    .attr("id", d => 'point-2030-' + identifierAccessor(d))
+                    .attr("class", "point")
+                    .attr("cx", d => xScale(x0Accessor(d)))
+                    .attr("cy", d => yScale(yAccessor(d)))
+                    .attr("r", radiusPosition0)
+                    .style("stroke", d => colorScale(colorAccessor(d)))
+                    .style("fill", "white")
+
+        chartBounds.append("g")
+            .attr("id", "points-2075")
+            .attr("class", "points points_2075")    
+            .selectAll('points')
+                .data(data.value)
+                .enter()
+                .append("circle")
+                    .attr("id", d => 'point-2075-' + identifierAccessor(d))
+                    .attr("class", "point")
+                    .attr("cx", d => xScale(x1Accessor(d)))
+                    .attr("cy", d => yScale(yAccessor(d)))
+                    .attr("r", radiusPosition1 - strokeWidth1 / 2)
+                    .style("stroke", d => colorScale(colorAccessor(d)))
+                    .style("stroke-width", strokeWidth1)
+                    .style("fill", "white")
+
+        // chartBounds.append("g")
+        //     .attr("id", "eyes-2075")
+        //     .attr("class", "eyes eyes_2075")    
+        //     .selectAll('eyes')
+        //         .data(data.value)
+        //         .enter()
+        //         .append("circle")
+        //             .attr("id", d => 'point-2075-' + identifierAccessor(d))
+        //             .attr("class", "point")
+        //             .attr("cx", d => {
+        //                 return x1Accessor(d) > x0Accessor(d) ? (xScale(x1Accessor(d)) + radiusPosition1 / 4) : (xScale(x1Accessor(d)) - radiusPosition1 / 4)
+        //             })
+        //             .attr("cy", d => yScale(yAccessor(d)) + radiusPosition1 / 4)
+        //             .attr("r", radiusPosition1 / 2)
+        //             .style("stroke", 'none')
+        //             .style("fill", "black")
+
+    }
+</script>
+
+<style>
+</style>
\ No newline at end of file
diff --git a/src/components/Viz3Viz.vue b/src/components/Viz3Viz.vue
deleted file mode 100644
index 5f149d4..0000000
--- a/src/components/Viz3Viz.vue
+++ /dev/null
@@ -1,37 +0,0 @@
-<template>
-    <!---VizSection-->
-    <VizSection
-        :figures="true"
-        :fig-caption="false"
-    >
-        <!-- HEADING -->
-        <template #heading>
-            <h2>
-                This is the Viz 3 viz!
-            </h2>
-        </template>
-        <!-- FIGURES -->
-        <template #aboveExplanation>
-        </template>
-        <template #figures>
-        </template>
-        <!-- FIGURE CAPTION -->
-        <template #figureCaption>
-        </template>
-        <!-- EXPLANATION -->
-        <template #belowExplanation>
-        </template>
-    </VizSection>
-</template>
-
-<script setup>
-    import VizSection from '@/components/VizSection.vue';
-
-    // define props
-    defineProps({
-        text: { type: Object }
-    })
-</script>
-
-<style>
-</style>
\ No newline at end of file
-- 
GitLab


From 1a7b8a945cfbc71ee95969fbed2a23ca22f07003 Mon Sep 17 00:00:00 2001
From: Corson-Dosch <hcorson-dosch@usgs.gov>
Date: Wed, 21 Aug 2024 00:16:14 -0500
Subject: [PATCH 02/25] udpate data

---
 public/fish_as_food_climate.csv      | 84 ++++++++++++++++++++++++++++
 public/fish_as_food_climate_test.csv | 26 ++++-----
 2 files changed, 97 insertions(+), 13 deletions(-)
 create mode 100644 public/fish_as_food_climate.csv

diff --git a/public/fish_as_food_climate.csv b/public/fish_as_food_climate.csv
new file mode 100644
index 0000000..74ed7c5
--- /dev/null
+++ b/public/fish_as_food_climate.csv
@@ -0,0 +1,84 @@
+family,thermal_guild,species,cvi_2030,cvi_2075,cvi_2030_family,cvi_2075_family
+Cyprinidae,warm,Albanian roach,0.10778174260000001,0.0941208422,0.10778174260000001,0.0941208422
+Cyprinidae,warm,Asp,0.013429605127960001,0.011727456938119999,0.013429605127960001,0.011727456938119999
+Cyprinidae,warm,Barbel,0.04705801130333334,0.039834021549,0.04705801130333334,0.039834021549
+Cyprinidae,warm,Bata,0.0055121400746,0.007563008347000001,0.0055121400746,0.007563008347000001
+Cyprinidae,warm,BeyÅŸehir bleak,7.267071073e-4,6.011377922000001e-4,7.267071073e-4,6.011377922000001e-4
+Cyprinidae,warm,Black bream,0.051246241799999995,0.061345053,0.051246241799999995,0.061345053
+Cyprinidae,warm,Black carp,0.051246241799999995,0.061345053,0.051246241799999995,0.061345053
+Cyprinidae,warm,Blue-finned mahseer,0.0224598681704,0.027820540914799997,0.0224598681704,0.027820540914799997
+Cyprinidae,warm,Catla,0.0220743179623,0.0302873745485,0.0220743179623,0.0302873745485
+Cyprinidae,warm,Chocolate mahseer,0.0028306322192,0.0034302133044,0.0028306322192,0.0034302133044
+Cyprinidae,warm,Chub,0.03216167369029999,0.026581131487899996,0.03216167369029999,0.026581131487899996
+Cyprinidae,warm,Common barbel,0.002834382148,0.0023992692516,0.002834382148,0.0023992692516
+Cyprinidae,warm,Common bleak,0.00854949538,0.007072209320000001,0.00854949538,0.007072209320000001
+Cyprinidae,warm,Common bream,0.04600620713676522,0.03642865846998261,0.04600620713676522,0.03642865846998261
+Cyprinidae,warm,Common carp,0.13811330662322036,0.1199294950838376,0.13811330662322036,0.1199294950838376
+Cyprinidae,warm,Common nase,0.026387573782000004,0.023642661213400005,0.026387573782000004,0.023642661213400005
+Cyprinidae,warm,Common roach,0.05312624687117,0.042622199202855,0.05312624687117,0.042622199202855
+Cyprinidae,warm,Crucian carp,0.116377215108,0.10105514804045,0.116377215108,0.10105514804045
+Cyprinidae,warm,Cyprinds,0.032788926016,0.028471980271733335,0.032788926016,0.028471980271733335
+Cyprinidae,warm,European carp,0.13040639766,0.11323726735275001,0.13040639766,0.11323726735275001
+Cyprinidae,warm,European chub,0.001917238372,0.001584568196,0.001917238372,0.001584568196
+Cyprinidae,warm,Ghonia,0.0018287941369,0.0025092223955,0.0018287941369,0.0025092223955
+Cyprinidae,warm,Golden mahseer,0.0034355641156000004,0.0039133067584,0.0034355641156000004,0.0039133067584
+Cyprinidae,warm,Goldfish,0.189583548,0.16462323395,0.189583548,0.16462323395
+Cyprinidae,warm,Grass carp,0.028623161305374997,0.034263768144375,0.028623161305374997,0.034263768144375
+Cyprinidae,warm,Gudgeon,0.0344069672688,0.030376054976399995,0.0344069672688,0.030376054976399995
+Cyprinidae,warm,Italian bleak,0.10233745969859999,0.0846543455604,0.10233745969859999,0.0846543455604
+Cyprinidae,warm,Kalibaus,0.0036833459377,0.0050537859515,0.0036833459377,0.0050537859515
+Cyprinidae,warm,Kutum,2.15702238e-4,1.9326426059999998e-4,2.15702238e-4,1.9326426059999998e-4
+Cyprinidae,warm,Labeo,0.017643999771499996,0.024208694942499998,0.017643999771499996,0.024208694942499998
+Cyprinidae,warm,Mrigal,0.019859158866899998,0.0272480347455,0.019859158866899998,0.0272480347455
+Cyprinidae,warm,Mullet,0.022048241277999998,0.018222534254,0.022048241277999998,0.018222534254
+Cyprinidae,warm,Orange River mudfish,0.01803036473,0.024738812350000004,0.01803036473,0.024738812350000004
+Cyprinidae,warm,Pale chub,0.0023231629616,0.0027809757360000003,0.0023231629616,0.0027809757360000003
+Cyprinidae,warm,Pearl mullet,0.1777012614733,0.14699587071620002,0.1777012614733,0.14699587071620002
+Cyprinidae,warm,Prussian carp,0.064146947634,0.055701447087225,0.064146947634,0.055701447087225
+Cyprinidae,warm,Prussian/Crucian carp,0.0216666912,0.018814083880000002,0.0216666912,0.018814083880000002
+Cyprinidae,warm,Razor fish (ziege),3.7723609910000005e-4,3.294229477e-4,3.7723609910000005e-4,3.294229477e-4
+Cyprinidae,warm,Red garra,0.017643999771499996,0.024208694942499998,0.017643999771499996,0.024208694942499998
+Cyprinidae,warm,Rohu,0.0017515211452,0.0024031989140000003,0.0017515211452,0.0024031989140000003
+Cyprinidae,warm,Rudd,0.010652671243479999,0.00881197281272,0.010652671243479999,0.00881197281272
+Cyprinidae,warm,Rui,0.0368077017131,0.0505025183545,0.0368077017131,0.0505025183545
+Cyprinidae,warm,Shabout,0.0011991616780000001,0.0010150754526,0.0011991616780000001,0.0010150754526
+Cyprinidae,warm,Silver bream,0.010815032339999999,0.008563564440000001,0.010815032339999999,0.008563564440000001
+Cyprinidae,warm,Silver carp,0.029425103982114283,0.035223745194000004,0.029425103982114283,0.035223745194000004
+Cyprinidae,warm,Tench,0.017184651140000003,0.015171401295,0.017184651140000003,0.015171401295
+Cyprinidae,warm,Transcau casian barb,0.014553462183,0.0123193248111,0.014553462183,0.0123193248111
+Cyprinidae,warm,Trout barb,0.004578617316,0.0038757426371999995,0.004578617316,0.0038757426371999995
+Cyprinidae,warm,Vimba bream,0.0212868941635,0.0185888663345,0.0212868941635,0.0185888663345
+Cyprinidae,warm,White Bream,0.043260129359999996,0.034254257760000004,0.043260129359999996,0.034254257760000004
+Percidae,cool,Eurasian Ruffe,0.00486753292,0.0043199098100000005,0.00486753292,0.0043199098100000005
+Percidae,cool,European perch,0.07259463927767063,0.06347059881833862,0.07259463927767063,0.06347059881833862
+Percidae,cool,Golden perch,0.0334276893426,0.0344360226346,0.0334276893426,0.0344360226346
+Percidae,cool,Redfin perch,0.0263136745872,0.0230064464784,0.0263136745872,0.0230064464784
+Percidae,cool,Volga pikeperch,8.01681972e-4,6.487112373e-4,8.01681972e-4,6.487112373e-4
+Percidae,cool,Walleye,0.10268783482319999,0.0983561899176,0.10268783482319999,0.0983561899176
+Percidae,cool,Yellow perch,0.0488076651,0.0456058989,0.0488076651,0.0456058989
+Percidae,cool,Zander (pikeperch),0.037962706268244786,0.032533836325300367,0.037962706268244786,0.032533836325300367
+Salmonidae,cold,Arctic Char,0.0356557719054,0.03500071147903334,0.0356557719054,0.03500071147903334
+Salmonidae,cold,Atlantic salmon,0.265177841604771,0.24011746662753772,0.265177841604771,0.24011746662753772
+Salmonidae,cold,Balkan trout,0.026710147320000002,0.023695444160000002,0.026710147320000002,0.023695444160000002
+Salmonidae,cold,Brook Trout,0.026461932224000003,0.024708687848000004,0.026461932224000003,0.024708687848000004
+Salmonidae,cold,Brown Trout,0.050732109690129576,0.04500611164287132,0.050732109690129576,0.04500611164287132
+Salmonidae,cold,Cherry trout,0.0029371095160000004,0.0027625745076,0.0029371095160000004,0.0027625745076
+Salmonidae,cold,Chinook Salmon (Quinnat Salmon),0.1600752957,0.15222991949999998,0.1600752957,0.15222991949999998
+Salmonidae,cold,Chum salmon,0.02885045315,0.0284171047,0.02885045315,0.0284171047
+Salmonidae,cold,Coho salmon,0.2150947458,0.20066481688,0.2150947458,0.20066481688
+Salmonidae,cold,Common huchen,5.120283490000001e-5,4.81393951e-5,5.120283490000001e-5,4.81393951e-5
+Salmonidae,cold,Dolly varden/Char,0.0034788601644,0.0033581483528,0.0034788601644,0.0033581483528
+Salmonidae,cold,European grayling,0.024065881522700002,0.0212009485031,0.024065881522700002,0.0212009485031
+Salmonidae,cold,Pink salmon,0.025550719900000005,0.02443988385,0.025550719900000005,0.02443988385
+Salmonidae,cold,Rainbow trout,0.06302419030074105,0.05967045472608766,0.06302419030074105,0.05967045472608766
+Salmonidae,cold,Salmon,0.06449020412644443,0.06255334611855555,0.06449020412644443,0.06255334611855555
+Salmonidae,cold,Sockeye salmon,0.029446763100000002,0.029350874649999997,0.029446763100000002,0.029350874649999997
+Salmonidae,cold,Steelhead,0.0049289009400000005,0.00466661704,0.0049289009400000005,0.00466661704
+Salmonidae,cold,Trout,0.0858357697255,0.08267910955158334,0.0858357697255,0.08267910955158334
+Salmonidae,cold,Vendace,0.04116622401,0.0383635935,0.04116622401,0.0383635935
+Salmonidae,cold,Whitefish,0.05007684609903145,0.04666757308221,0.05007684609903145,0.04666757308221
+Siluridae,warm,Boal,0.0124330956288,0.0105118070912,0.0124330956288,0.0105118070912
+Siluridae,warm,Catfish,0.033215868223999995,0.028083014042666665,0.033215868223999995,0.028083014042666665
+Siluridae,warm,European catfish,0.016684117296,0.014105917603999998,0.016684117296,0.014105917603999998
+Siluridae,warm,Far eastern catfish,0.0031082739072,0.0026279517728,0.0031082739072,0.0026279517728
+Siluridae,warm,Wels catfish,0.0333433019136,0.028190755380945457,0.0333433019136,0.028190755380945457
diff --git a/public/fish_as_food_climate_test.csv b/public/fish_as_food_climate_test.csv
index e052fb1..d6846eb 100644
--- a/public/fish_as_food_climate_test.csv
+++ b/public/fish_as_food_climate_test.csv
@@ -1,13 +1,13 @@
-family,thermal_guild,species,cvi_2030,cvi_2075
-A,warm,species_1,0.319953022,0.35962678
-A,warm,species_2,0.224863212,0.194521316
-A,warm,species_3,0.835617044,0.810848349
-A,warm,species_4,0.68844979,0.080709824
-A,warm,species_5,0.506342706,0.733569142
-A,warm,species_6,0.313221865,0.058278019
-B,cool,species_7,0.484686402,0.646526582
-B,cool,species_8,0.943640492,0.319394434
-B,cool,species_9,0.894768952,0.914064253
-B,cool,species_10,0.069633857,0.332752341
-C,cold,species_11,0.953508638,0.178160176
-C,cold,species_12,0.092467137,0.585798787
+family,thermal_guild,species,cvi_2030,cvi_2075,cvi_2030_family,cvi_2075_family
+A,warm,species_1,0.319953022,0.35962678,0.85151318,0.020097514
+A,warm,species_2,0.224863212,0.194521316,0.85151318,0.020097514
+A,warm,species_3,0.835617044,0.810848349,0.85151318,0.020097514
+A,warm,species_4,0.68844979,0.080709824,0.85151318,0.020097514
+A,warm,species_5,0.506342706,0.733569142,0.85151318,0.020097514
+A,warm,species_6,0.313221865,0.058278019,0.85151318,0.020097514
+B,cool,species_7,0.484686402,0.646526582,0.053519735,0.775921332
+B,cool,species_8,0.943640492,0.319394434,0.053519735,0.775921332
+B,cool,species_9,0.894768952,0.914064253,0.053519735,0.775921332
+B,cool,species_10,0.069633857,0.332752341,0.053519735,0.775921332
+C,cold,species_11,0.953508638,0.178160176,0.700810585,0.0658746
+C,cold,species_12,0.092467137,0.585798787,0.700810585,0.0658746
-- 
GitLab


From 4a79278ebb6ebf7c66968fa9bfef4d5d100701d1 Mon Sep 17 00:00:00 2001
From: Corson-Dosch <hcorson-dosch@usgs.gov>
Date: Wed, 21 Aug 2024 00:16:32 -0500
Subject: [PATCH 03/25] update chart styling, explore layout options

---
 src/components/FishAsFoodLinkChartViz.vue | 171 ++++++++++++++++++----
 1 file changed, 139 insertions(+), 32 deletions(-)

diff --git a/src/components/FishAsFoodLinkChartViz.vue b/src/components/FishAsFoodLinkChartViz.vue
index 6321669..642a0dc 100644
--- a/src/components/FishAsFoodLinkChartViz.vue
+++ b/src/components/FishAsFoodLinkChartViz.vue
@@ -6,14 +6,12 @@
     >
         <!-- HEADING -->
         <template #heading>
-            <h2>
-            </h2>
         </template>
         <!-- FIGURES -->
         <template #aboveExplanation>
         </template>
         <template #figures>
-            <div id="chart-container"></div>
+            <div id="chart-container" class="maxWidth" ref="chart"></div>
         </template>
         <!-- FIGURE CAPTION -->
         <template #figureCaption>
@@ -25,7 +23,7 @@
 </template>
 
 <script setup>
-    import { onMounted, ref } from "vue";
+    import { onMounted, ref, reactive } from "vue";
     import * as d3 from 'd3';
     import VizSection from '@/components/VizSection.vue';
 
@@ -36,15 +34,22 @@
 
     // global variables
     const publicPath = import.meta.env.BASE_URL;
-    const dataFile = 'fish_as_food_climate_test.csv'
+    const dataFile = 'fish_as_food_climate.csv' //'fish_as_food_climate_test.csv'
     const data = ref();
+    const chart = ref(null);
+    // const families = ref();
     let chartDimensions;
     const chartTitle = 'Title of chart';
+    let chartSVG;
     let chartBounds;
     let xScale;
+    let xAxis;
     let yScale;
+    let yAxis;
     let widthScale;
     let colorScale;
+    // Create a reactive object to track expanded families
+    // const expandedFamilies = reactive({});
 
 
     // Behavior on mounted (functions called here)
@@ -53,13 +58,20 @@
         try {
             await loadDatasets();
             if (data.value.length > 0) {
+                // families.value = Array.from(new Set(data.value.map(d => d.family)));
+                // // Initialize the expandedFamilies object
+                // families.value.forEach(family => {
+                //     expandedFamilies[family] = false;
+                // });
+                // expandedFamilies["A"] = !expandedFamilies["A"]
+                // console.log(expandedFamilies)
                 initChart({
-                    width: 400,
-                    height: 300,
-                    margin: 10,
+                    width: chart.value.offsetWidth,
+                    height: 1600,
+                    margin: 20,
                     marginBottom: 30,
-                    marginLeft: 50});
-                drawChart();
+                    marginLeft: 200});
+                drawChart(data.value);
             } else {
                 console.error('Error loading data');
             }
@@ -111,7 +123,7 @@
         chartDimensions.boundedHeight = chartDimensions.height - chartDimensions.margin.top - chartDimensions.margin.bottom
 
         // draw canvas for chart
-        const chartSVG = d3.select("#chart-container")
+        chartSVG = d3.select("#chart-container")
             .append("svg")
                 .attr("viewBox", [0, 0, (chartDimensions.width), (chartDimensions.height)].join(' '))
                 .attr("width", "100%")
@@ -135,6 +147,9 @@
         // Initialize scales
         initXScale()
         initYScale()
+
+        // Inititalize gradients
+        initGradients()
     }
 
     function initXScale() {
@@ -143,15 +158,15 @@
             .range([0, chartDimensions.boundedWidth]);
 
         // add group for x axis
-        const xAxis = chartBounds.append("g")
+        xAxis = chartBounds.append("g")
             .attr("id", "x-axis")
             .attr("class", "axis")
             .attr("transform", `translate(0,${chartDimensions.boundedHeight})`)
             .attr("aria-hidden", true) // hide from screen reader
 
         // generate x axis
-        xAxis
-            .call(d3.axisBottom(xScale).tickSize(0).tickPadding(10))
+        // xAxis
+        //     .call(d3.axisBottom(xScale).tickSize(0).tickPadding(10))
             // .select(".domain").remove() // remove axis line
         
         // add placeholder for x axis title (title text set in drawChart())
@@ -171,14 +186,14 @@
             .padding(0.1)
 
         // add group for y axis
-        const yAxis = chartBounds.append("g")
+        yAxis = chartBounds.append("g")
             .attr("id", "y-axis")
             .attr("class", "axis")
             .attr("aria-hidden", true)
 
         // generate y axis
-        yAxis
-            .call(d3.axisLeft(yScale).tickSize(2))
+        // yAxis
+        //     .call(d3.axisLeft(yScale).tickSize(2))
             // .select(".domain").remove() // remove axis line
 
         // add placeholder for y axis title (title text set in drawChart())
@@ -192,6 +207,43 @@
             .attr("aria-hidden", true)
     }
 
+    function initGradients() {
+        let colors = {warm: '#FF7256', cool: '#5CACEE', cold: '#36648B'}
+        Object.keys(colors).forEach(color => {
+            let lg_decreasing = chartSVG.append("defs").append("linearGradient")
+                .attr("id", color + "_gradient_decreasing")
+                .attr("x1", "0%")
+                .attr("x2", "100%")
+                .attr("y1", "0%")
+                .attr("y2", "0%");
+            lg_decreasing.append("stop")
+                .attr("offset", "40%")
+                .style("stop-color", colors[color])
+                .style("stop-opacity", 1)
+
+            lg_decreasing.append("stop")
+                .attr("offset", "100%")
+                .style("stop-color", colors[color])
+                .style("stop-opacity", 0)
+
+            let lg_increasing = chartSVG.append("defs").append("linearGradient")
+                .attr("id", color + "_gradient_increasing")
+                .attr("x1", "100%")
+                .attr("x2", "0%")
+                .attr("y1", "0%")
+                .attr("y2", "0%");
+            lg_increasing.append("stop")
+                .attr("offset", "40%")
+                .style("stop-color", colors[color])
+                .style("stop-opacity", 1)
+
+            lg_increasing.append("stop")
+                .attr("offset", "100%")
+                .style("stop-color", colors[color])
+                .style("stop-opacity", 0)
+        })
+    }
+
     function initWidthScale(low, high) {
         widthScale = d3.scaleLinear()
             .domain([0, 1])
@@ -222,23 +274,49 @@
         return itemColor;
     }
 
-    function drawChart() {
+    function drawChart(data) {
         // accessor functions
         const yAccessor = d => d.species
+        // const yAccessor_family = d => d.family
         const xAccessor = d => d.cvi
+        // const xAccessor_family = d => d.cvi_family
         const x0Accessor = d => d.cvi_2030
         const x1Accessor = d => d.cvi_2075
+        const x0Accessor_family = d => d.cvi_2030_family
+        const x1Accessor_family = d => d.cvi_2075_family
         const widthAccessor = d => d.position
         const colorAccessor = d => d.thermal_guild
         const identifierAccessor = d => d.family + '_' + d.species
 
+        // to get dynamic
+        // need key for data
+        // need enter update exit pattern with transitions
+        // need to transition chart height, yscale, yaxis
+
+
         // set domain for xScale
         xScale
-            .domain([0, d3.max([d3.max(data.value, x0Accessor), d3.max(data.value, x1Accessor)])])
+            .domain([0, 0.3]) // d3.max([d3.max(data, x0Accessor), d3.max(data, x1Accessor)])])
+        xAxis
+            .call(d3.axisBottom(xScale).tickSize(0).tickPadding(10))
 
+        xAxis
+            .selectAll("text")
+            .attr("class", "axis-text")
         // set domain for yScale
+        // let yDomain = []
+        // families.value.map(family => expandedFamilies[family] ? yDomain.push(data.filter(d => d.family === family).map(d => d.species)) : yDomain.push(family))
+        // yDomain = yDomain.flat()
+        // yScale
+        //     .domain(yDomain)
         yScale
-            .domain(d3.union(data.value.map(yAccessor).sort(d3.ascending)))
+            .domain(d3.union(data.map(yAccessor))) //.sort(d3.ascending)))
+        yAxis
+            .call(d3.axisLeft(yScale).tickSize(2))
+        
+        yAxis
+            .selectAll("text")
+            .attr("class", "axis-text")
 
         // set up width scale
         const radiusPosition0 = 1
@@ -252,26 +330,35 @@
             .x(d => xScale(xAccessor(d)))
             .y0(d => yScale(yAccessor(d)) - widthScale(widthAccessor(d)))
             .y1(d => yScale(yAccessor(d)) + widthScale(widthAccessor(d)));
+            // .x(d => expandedFamilies[d.family] ? xScale(xAccessor(d)) : xScale(xAccessor_family(d)))
+            // .y0(d => expandedFamilies[d.family] ? yScale(yAccessor(d)) - widthScale(widthAccessor(d)) : yScale(yAccessor_family(d)) - widthScale(widthAccessor(d)))
+            // .y1(d => expandedFamilies[d.family] ? yScale(yAccessor(d)) + widthScale(widthAccessor(d)) : yScale(yAccessor_family(d)) + widthScale(widthAccessor(d)));
 
         // set up color scale
-        const colorCategories = Array.from(new Set(data.value.map(colorAccessor)))
+        const colorCategories = Array.from(new Set(data.map(colorAccessor)))
         initColorScale(colorCategories)
 
         // set up area data
-        const areaCategories = Array.from(new Set(data.value.map(yAccessor)))
+        const areaCategories = Array.from(new Set(data.map(yAccessor)))
         const areaData = areaCategories.map(areaCategory => {
             return [
                 {
                     species: areaCategory,
-                    cvi: data.value.filter(d => d.species === areaCategory)[0].cvi_2030,
+                    family: data.filter(d => d.species === areaCategory)[0].family,
+                    cvi: x0Accessor(data.filter(d => d.species === areaCategory)[0]),
+                    cvi_decreasing: x0Accessor(data.filter(d => d.species === areaCategory)[0]) > x1Accessor(data.filter(d => d.species === areaCategory)[0]),
+                    cvi_family: x0Accessor_family(data.filter(d => d.species === areaCategory)[0]),
                     position: 0,
-                    thermal_guild: data.value.filter(d => d.species === areaCategory)[0].thermal_guild
+                    thermal_guild: colorAccessor(data.filter(d => d.species === areaCategory)[0])
                 },
                 {
                     species: areaCategory,
-                    cvi: data.value.filter(d => d.species === areaCategory)[0].cvi_2075,
+                    family: data.filter(d => d.species === areaCategory)[0].family,
+                    cvi: x1Accessor(data.filter(d => d.species === areaCategory)[0]),
+                    cvi_decreasing: x0Accessor(data.filter(d => d.species === areaCategory)[0]) > x1Accessor(data.filter(d => d.species === areaCategory)[0]),
+                    cvi_family: x1Accessor_family(data.filter(d => d.species === areaCategory)[0]),
                     position: 1,
-                    thermal_guild: data.value.filter(d => d.species === areaCategory)[0].thermal_guild
+                    thermal_guild: colorAccessor(data.filter(d => d.species === areaCategory)[0])
                 }
             ]
         })
@@ -286,14 +373,29 @@
                     .attr("id", d => 'area-2030-' + identifierAccessor(d))
                     .attr('class', "area")
                     .attr('d', d => area(d))
-                    .attr('fill', d => colorScale(colorAccessor(d[0])))
-                    .style("opacity", 1);
+                    .attr('fill', d => d[0].cvi_decreasing ? `url(#${d[0].thermal_guild}_gradient_decreasing)`: `url(#${d[0].thermal_guild}_gradient_increasing)`)//d => colorScale(colorAccessor(d[0])))
+                    .style("opacity", 1)
+                    // .on("click", function(event, d) {
+                    //     const clickedFamily = d[0].family
+                    //     expandedFamilies[clickedFamily] = !expandedFamilies[clickedFamily]
+                    //     let yDomain = []
+                    //     families.value.map(family => expandedFamilies[family] ? yDomain.push(data.filter(d => d.family === family).map(d => d.species)) : yDomain.push(family))
+                    //     yDomain = yDomain.flat()
+                    //     const yBandwidth = yScale.bandwidth()
+                    //     chartDimensions.boundedHeight = yBandwidth * yDomain.length;
+                    //     chartDimensions.height = chartDimensions.boundedHeight + chartDimensions.margin.top + chartDimensions.margin.bottom;
+                    //     chartSVG.attr("viewBox", [0, 0, (chartDimensions.width), (chartDimensions.height)].join(' '))
+                    //     yScale.range([chartDimensions.boundedHeight * 2, 0])
+                    //     yAxis
+                    //         .call(d3.axisLeft(yScale).tickSize(2))
+                    //     drawChart(data)
+                    // });
 
         chartBounds.append("g")
             .attr("id", "points-2030")
             .attr("class", "points points_2030")    
             .selectAll('points')
-                .data(data.value)
+                .data(data)
                 .enter()
                 .append("circle")
                     .attr("id", d => 'point-2030-' + identifierAccessor(d))
@@ -308,7 +410,7 @@
             .attr("id", "points-2075")
             .attr("class", "points points_2075")    
             .selectAll('points')
-                .data(data.value)
+                .data(data)
                 .enter()
                 .append("circle")
                     .attr("id", d => 'point-2075-' + identifierAccessor(d))
@@ -324,7 +426,7 @@
         //     .attr("id", "eyes-2075")
         //     .attr("class", "eyes eyes_2075")    
         //     .selectAll('eyes')
-        //         .data(data.value)
+        //         .data(data)
         //         .enter()
         //         .append("circle")
         //             .attr("id", d => 'point-2075-' + identifierAccessor(d))
@@ -340,5 +442,10 @@
     }
 </script>
 
-<style>
+<style lang="scss">
+    .axis-text {
+        font-size: 1.8rem;
+        font-family: var(--default-font);
+        user-select: none;
+    }
 </style>
\ No newline at end of file
-- 
GitLab


From 528e2461ecc566813505967efafaab744e95a272 Mon Sep 17 00:00:00 2001
From: Corson-Dosch <hcorson-dosch@usgs.gov>
Date: Wed, 21 Aug 2024 14:20:01 -0500
Subject: [PATCH 04/25] switch to percent change and add axis labels

---
 public/fish_as_food_climate.csv           | 166 +++++++++++-----------
 src/components/FishAsFoodLinkChartViz.vue |  99 +++++++++----
 2 files changed, 157 insertions(+), 108 deletions(-)

diff --git a/public/fish_as_food_climate.csv b/public/fish_as_food_climate.csv
index 74ed7c5..667f577 100644
--- a/public/fish_as_food_climate.csv
+++ b/public/fish_as_food_climate.csv
@@ -1,84 +1,84 @@
 family,thermal_guild,species,cvi_2030,cvi_2075,cvi_2030_family,cvi_2075_family
-Cyprinidae,warm,Albanian roach,0.10778174260000001,0.0941208422,0.10778174260000001,0.0941208422
-Cyprinidae,warm,Asp,0.013429605127960001,0.011727456938119999,0.013429605127960001,0.011727456938119999
-Cyprinidae,warm,Barbel,0.04705801130333334,0.039834021549,0.04705801130333334,0.039834021549
-Cyprinidae,warm,Bata,0.0055121400746,0.007563008347000001,0.0055121400746,0.007563008347000001
-Cyprinidae,warm,BeyÅŸehir bleak,7.267071073e-4,6.011377922000001e-4,7.267071073e-4,6.011377922000001e-4
-Cyprinidae,warm,Black bream,0.051246241799999995,0.061345053,0.051246241799999995,0.061345053
-Cyprinidae,warm,Black carp,0.051246241799999995,0.061345053,0.051246241799999995,0.061345053
-Cyprinidae,warm,Blue-finned mahseer,0.0224598681704,0.027820540914799997,0.0224598681704,0.027820540914799997
-Cyprinidae,warm,Catla,0.0220743179623,0.0302873745485,0.0220743179623,0.0302873745485
-Cyprinidae,warm,Chocolate mahseer,0.0028306322192,0.0034302133044,0.0028306322192,0.0034302133044
-Cyprinidae,warm,Chub,0.03216167369029999,0.026581131487899996,0.03216167369029999,0.026581131487899996
-Cyprinidae,warm,Common barbel,0.002834382148,0.0023992692516,0.002834382148,0.0023992692516
-Cyprinidae,warm,Common bleak,0.00854949538,0.007072209320000001,0.00854949538,0.007072209320000001
-Cyprinidae,warm,Common bream,0.04600620713676522,0.03642865846998261,0.04600620713676522,0.03642865846998261
-Cyprinidae,warm,Common carp,0.13811330662322036,0.1199294950838376,0.13811330662322036,0.1199294950838376
-Cyprinidae,warm,Common nase,0.026387573782000004,0.023642661213400005,0.026387573782000004,0.023642661213400005
-Cyprinidae,warm,Common roach,0.05312624687117,0.042622199202855,0.05312624687117,0.042622199202855
-Cyprinidae,warm,Crucian carp,0.116377215108,0.10105514804045,0.116377215108,0.10105514804045
-Cyprinidae,warm,Cyprinds,0.032788926016,0.028471980271733335,0.032788926016,0.028471980271733335
-Cyprinidae,warm,European carp,0.13040639766,0.11323726735275001,0.13040639766,0.11323726735275001
-Cyprinidae,warm,European chub,0.001917238372,0.001584568196,0.001917238372,0.001584568196
-Cyprinidae,warm,Ghonia,0.0018287941369,0.0025092223955,0.0018287941369,0.0025092223955
-Cyprinidae,warm,Golden mahseer,0.0034355641156000004,0.0039133067584,0.0034355641156000004,0.0039133067584
-Cyprinidae,warm,Goldfish,0.189583548,0.16462323395,0.189583548,0.16462323395
-Cyprinidae,warm,Grass carp,0.028623161305374997,0.034263768144375,0.028623161305374997,0.034263768144375
-Cyprinidae,warm,Gudgeon,0.0344069672688,0.030376054976399995,0.0344069672688,0.030376054976399995
-Cyprinidae,warm,Italian bleak,0.10233745969859999,0.0846543455604,0.10233745969859999,0.0846543455604
-Cyprinidae,warm,Kalibaus,0.0036833459377,0.0050537859515,0.0036833459377,0.0050537859515
-Cyprinidae,warm,Kutum,2.15702238e-4,1.9326426059999998e-4,2.15702238e-4,1.9326426059999998e-4
-Cyprinidae,warm,Labeo,0.017643999771499996,0.024208694942499998,0.017643999771499996,0.024208694942499998
-Cyprinidae,warm,Mrigal,0.019859158866899998,0.0272480347455,0.019859158866899998,0.0272480347455
-Cyprinidae,warm,Mullet,0.022048241277999998,0.018222534254,0.022048241277999998,0.018222534254
-Cyprinidae,warm,Orange River mudfish,0.01803036473,0.024738812350000004,0.01803036473,0.024738812350000004
-Cyprinidae,warm,Pale chub,0.0023231629616,0.0027809757360000003,0.0023231629616,0.0027809757360000003
-Cyprinidae,warm,Pearl mullet,0.1777012614733,0.14699587071620002,0.1777012614733,0.14699587071620002
-Cyprinidae,warm,Prussian carp,0.064146947634,0.055701447087225,0.064146947634,0.055701447087225
-Cyprinidae,warm,Prussian/Crucian carp,0.0216666912,0.018814083880000002,0.0216666912,0.018814083880000002
-Cyprinidae,warm,Razor fish (ziege),3.7723609910000005e-4,3.294229477e-4,3.7723609910000005e-4,3.294229477e-4
-Cyprinidae,warm,Red garra,0.017643999771499996,0.024208694942499998,0.017643999771499996,0.024208694942499998
-Cyprinidae,warm,Rohu,0.0017515211452,0.0024031989140000003,0.0017515211452,0.0024031989140000003
-Cyprinidae,warm,Rudd,0.010652671243479999,0.00881197281272,0.010652671243479999,0.00881197281272
-Cyprinidae,warm,Rui,0.0368077017131,0.0505025183545,0.0368077017131,0.0505025183545
-Cyprinidae,warm,Shabout,0.0011991616780000001,0.0010150754526,0.0011991616780000001,0.0010150754526
-Cyprinidae,warm,Silver bream,0.010815032339999999,0.008563564440000001,0.010815032339999999,0.008563564440000001
-Cyprinidae,warm,Silver carp,0.029425103982114283,0.035223745194000004,0.029425103982114283,0.035223745194000004
-Cyprinidae,warm,Tench,0.017184651140000003,0.015171401295,0.017184651140000003,0.015171401295
-Cyprinidae,warm,Transcau casian barb,0.014553462183,0.0123193248111,0.014553462183,0.0123193248111
-Cyprinidae,warm,Trout barb,0.004578617316,0.0038757426371999995,0.004578617316,0.0038757426371999995
-Cyprinidae,warm,Vimba bream,0.0212868941635,0.0185888663345,0.0212868941635,0.0185888663345
-Cyprinidae,warm,White Bream,0.043260129359999996,0.034254257760000004,0.043260129359999996,0.034254257760000004
-Percidae,cool,Eurasian Ruffe,0.00486753292,0.0043199098100000005,0.00486753292,0.0043199098100000005
-Percidae,cool,European perch,0.07259463927767063,0.06347059881833862,0.07259463927767063,0.06347059881833862
-Percidae,cool,Golden perch,0.0334276893426,0.0344360226346,0.0334276893426,0.0344360226346
-Percidae,cool,Redfin perch,0.0263136745872,0.0230064464784,0.0263136745872,0.0230064464784
-Percidae,cool,Volga pikeperch,8.01681972e-4,6.487112373e-4,8.01681972e-4,6.487112373e-4
-Percidae,cool,Walleye,0.10268783482319999,0.0983561899176,0.10268783482319999,0.0983561899176
-Percidae,cool,Yellow perch,0.0488076651,0.0456058989,0.0488076651,0.0456058989
-Percidae,cool,Zander (pikeperch),0.037962706268244786,0.032533836325300367,0.037962706268244786,0.032533836325300367
-Salmonidae,cold,Arctic Char,0.0356557719054,0.03500071147903334,0.0356557719054,0.03500071147903334
-Salmonidae,cold,Atlantic salmon,0.265177841604771,0.24011746662753772,0.265177841604771,0.24011746662753772
-Salmonidae,cold,Balkan trout,0.026710147320000002,0.023695444160000002,0.026710147320000002,0.023695444160000002
-Salmonidae,cold,Brook Trout,0.026461932224000003,0.024708687848000004,0.026461932224000003,0.024708687848000004
-Salmonidae,cold,Brown Trout,0.050732109690129576,0.04500611164287132,0.050732109690129576,0.04500611164287132
-Salmonidae,cold,Cherry trout,0.0029371095160000004,0.0027625745076,0.0029371095160000004,0.0027625745076
-Salmonidae,cold,Chinook Salmon (Quinnat Salmon),0.1600752957,0.15222991949999998,0.1600752957,0.15222991949999998
-Salmonidae,cold,Chum salmon,0.02885045315,0.0284171047,0.02885045315,0.0284171047
-Salmonidae,cold,Coho salmon,0.2150947458,0.20066481688,0.2150947458,0.20066481688
-Salmonidae,cold,Common huchen,5.120283490000001e-5,4.81393951e-5,5.120283490000001e-5,4.81393951e-5
-Salmonidae,cold,Dolly varden/Char,0.0034788601644,0.0033581483528,0.0034788601644,0.0033581483528
-Salmonidae,cold,European grayling,0.024065881522700002,0.0212009485031,0.024065881522700002,0.0212009485031
-Salmonidae,cold,Pink salmon,0.025550719900000005,0.02443988385,0.025550719900000005,0.02443988385
-Salmonidae,cold,Rainbow trout,0.06302419030074105,0.05967045472608766,0.06302419030074105,0.05967045472608766
-Salmonidae,cold,Salmon,0.06449020412644443,0.06255334611855555,0.06449020412644443,0.06255334611855555
-Salmonidae,cold,Sockeye salmon,0.029446763100000002,0.029350874649999997,0.029446763100000002,0.029350874649999997
-Salmonidae,cold,Steelhead,0.0049289009400000005,0.00466661704,0.0049289009400000005,0.00466661704
-Salmonidae,cold,Trout,0.0858357697255,0.08267910955158334,0.0858357697255,0.08267910955158334
-Salmonidae,cold,Vendace,0.04116622401,0.0383635935,0.04116622401,0.0383635935
-Salmonidae,cold,Whitefish,0.05007684609903145,0.04666757308221,0.05007684609903145,0.04666757308221
-Siluridae,warm,Boal,0.0124330956288,0.0105118070912,0.0124330956288,0.0105118070912
-Siluridae,warm,Catfish,0.033215868223999995,0.028083014042666665,0.033215868223999995,0.028083014042666665
-Siluridae,warm,European catfish,0.016684117296,0.014105917603999998,0.016684117296,0.014105917603999998
-Siluridae,warm,Far eastern catfish,0.0031082739072,0.0026279517728,0.0031082739072,0.0026279517728
-Siluridae,warm,Wels catfish,0.0333433019136,0.028190755380945457,0.0333433019136,0.028190755380945457
+Cyprinidae,warm,Albanian roach,0,-0.12674595966311653,0,-0.12674595966311653
+Cyprinidae,warm,Asp,0,-0.12674595966311658,0,-0.12674595966311658
+Cyprinidae,warm,Barbel,0,-0.15351243187409472,0,-0.15351243187409472
+Cyprinidae,warm,Bata,0,0.37206388891501935,0,0.37206388891501935
+Cyprinidae,warm,BeyÅŸehir bleak,0,-0.1727921934966878,0,-0.1727921934966878
+Cyprinidae,warm,Black bream,0,0.19706442551266273,0,0.19706442551266273
+Cyprinidae,warm,Black carp,0,0.19706442551266273,0,0.19706442551266273
+Cyprinidae,warm,Blue-finned mahseer,0,0.2386778365629439,0,0.2386778365629439
+Cyprinidae,warm,Catla,0,0.3720638889150193,0,0.3720638889150193
+Cyprinidae,warm,Chocolate mahseer,0,0.21181878773691584,0,0.21181878773691584
+Cyprinidae,warm,Chub,0,-0.17351529202546123,0,-0.17351529202546123
+Cyprinidae,warm,Common barbel,0,-0.15351243187409475,0,-0.15351243187409475
+Cyprinidae,warm,Common bleak,0,-0.1727921934966878,0,-0.1727921934966878
+Cyprinidae,warm,Common bream,0,-0.20817948843969888,0,-0.20817948843969888
+Cyprinidae,warm,Common carp,0,-0.13165865030651286,0,-0.13165865030651286
+Cyprinidae,warm,Common nase,0,-0.10402292348955595,0,-0.10402292348955595
+Cyprinidae,warm,Common roach,0,-0.19771860966928614,0,-0.19771860966928614
+Cyprinidae,warm,Crucian carp,0,-0.1316586503065129,0,-0.1316586503065129
+Cyprinidae,warm,Cyprinds,0,-0.13165865030651291,0,-0.13165865030651291
+Cyprinidae,warm,European carp,0,-0.13165865030651283,0,-0.13165865030651283
+Cyprinidae,warm,European chub,0,-0.17351529202546126,0,-0.17351529202546126
+Cyprinidae,warm,Ghonia,0,0.3720638889150193,0,0.3720638889150193
+Cyprinidae,warm,Golden mahseer,0,0.13905799068941688,0,0.13905799068941688
+Cyprinidae,warm,Goldfish,0,-0.13165865030651283,0,-0.13165865030651283
+Cyprinidae,warm,Grass carp,0,0.1970644255126627,0,0.1970644255126627
+Cyprinidae,warm,Gudgeon,0,-0.11715395492165936,0,-0.11715395492165936
+Cyprinidae,warm,Italian bleak,0,-0.17279219349668792,0,-0.17279219349668792
+Cyprinidae,warm,Kalibaus,0,0.3720638889150193,0,0.3720638889150193
+Cyprinidae,warm,Kutum,0,-0.10402292348955605,0,-0.10402292348955605
+Cyprinidae,warm,Labeo,0,0.37206388891501935,0,0.37206388891501935
+Cyprinidae,warm,Mrigal,0,0.3720638889150193,0,0.3720638889150193
+Cyprinidae,warm,Mullet,0,-0.1735152920254612,0,-0.1735152920254612
+Cyprinidae,warm,Orange River mudfish,0,0.3720638889150193,0,0.3720638889150193
+Cyprinidae,warm,Pale chub,0,0.19706442551266287,0,0.19706442551266287
+Cyprinidae,warm,Pearl mullet,0,-0.17279219349668795,0,-0.17279219349668795
+Cyprinidae,warm,Prussian carp,0,-0.13165865030651291,0,-0.13165865030651291
+Cyprinidae,warm,Prussian/Crucian carp,0,-0.13165865030651283,0,-0.13165865030651283
+Cyprinidae,warm,Razor fish (ziege),0,-0.1267459596631165,0,-0.1267459596631165
+Cyprinidae,warm,Red garra,0,0.37206388891501935,0,0.37206388891501935
+Cyprinidae,warm,Rohu,0,0.37206388891501935,0,0.37206388891501935
+Cyprinidae,warm,Rudd,0,-0.1727921934966879,0,-0.1727921934966879
+Cyprinidae,warm,Rui,0,0.37206388891501924,0,0.37206388891501924
+Cyprinidae,warm,Shabout,0,-0.15351243187409466,0,-0.15351243187409466
+Cyprinidae,warm,Silver bream,0,-0.20817948843969877,0,-0.20817948843969877
+Cyprinidae,warm,Silver carp,0,0.1970644255126629,0,0.1970644255126629
+Cyprinidae,warm,Tench,0,-0.11715395492165945,0,-0.11715395492165945
+Cyprinidae,warm,Transcau casian barb,0,-0.1535124318740947,0,-0.1535124318740947
+Cyprinidae,warm,Trout barb,0,-0.1535124318740947,0,-0.1535124318740947
+Cyprinidae,warm,Vimba bream,0,-0.12674595966311641,0,-0.12674595966311641
+Cyprinidae,warm,White Bream,0,-0.20817948843969877,0,-0.20817948843969877
+Percidae,cool,Eurasian Ruffe,0,-0.11250527094534767,0,-0.11250527094534767
+Percidae,cool,European perch,0,-0.12568476887712102,0,-0.12568476887712102
+Percidae,cool,Golden perch,0,0.030164612386623692,0,0.030164612386623692
+Percidae,cool,Redfin perch,0,-0.1256847688771208,0,-0.1256847688771208
+Percidae,cool,Volga pikeperch,0,-0.19081224231396332,0,-0.19081224231396332
+Percidae,cool,Walleye,0,-0.04218264912350984,0,-0.04218264912350984
+Percidae,cool,Yellow perch,0,-0.06559965926335624,0,-0.06559965926335624
+Percidae,cool,Zander (pikeperch),0,-0.1430053459462026,0,-0.1430053459462026
+Salmonidae,cold,Arctic Char,0,-0.018371792037054607,0,-0.018371792037054607
+Salmonidae,cold,Atlantic salmon,0,-0.09450403105167436,0,-0.09450403105167436
+Salmonidae,cold,Balkan trout,0,-0.11286733554414553,0,-0.11286733554414553
+Salmonidae,cold,Brook Trout,0,-0.06625534224631832,0,-0.06625534224631832
+Salmonidae,cold,Brown Trout,0,-0.11286733554414562,0,-0.11286733554414562
+Salmonidae,cold,Cherry trout,0,-0.05942407235726659,0,-0.05942407235726659
+Salmonidae,cold,Chinook Salmon (Quinnat Salmon),0,-0.04901053698319066,0,-0.04901053698319066
+Salmonidae,cold,Chum salmon,0,-0.015020507572166128,0,-0.015020507572166128
+Salmonidae,cold,Coho salmon,0,-0.06708638496180307,0,-0.06708638496180307
+Salmonidae,cold,Common huchen,0,-0.05982949588597892,0,-0.05982949588597892
+Salmonidae,cold,Dolly varden/Char,0,-0.034698667349516474,0,-0.034698667349516474
+Salmonidae,cold,European grayling,0,-0.1190454219139104,0,-0.1190454219139104
+Salmonidae,cold,Pink salmon,0,-0.04347572414192542,0,-0.04347572414192542
+Salmonidae,cold,Rainbow trout,0,-0.05321346547492189,0,-0.05321346547492189
+Salmonidae,cold,Salmon,0,-0.030033367611790023,0,-0.030033367611790023
+Salmonidae,cold,Sockeye salmon,0,-0.00325633244218971,0,-0.00325633244218971
+Salmonidae,cold,Steelhead,0,-0.05321346547492191,0,-0.05321346547492191
+Salmonidae,cold,Trout,0,-0.036775579505042726,0,-0.036775579505042726
+Salmonidae,cold,Vendace,0,-0.06808082541938247,0,-0.06808082541938247
+Salmonidae,cold,Whitefish,0,-0.06808082541938261,0,-0.06808082541938261
+Siluridae,warm,Boal,0,-0.15453018258377504,0,-0.15453018258377504
+Siluridae,warm,Catfish,0,-0.154530182583775,0,-0.154530182583775
+Siluridae,warm,European catfish,0,-0.1545301825837752,0,-0.1545301825837752
+Siluridae,warm,Far eastern catfish,0,-0.15453018258377504,0,-0.15453018258377504
+Siluridae,warm,Wels catfish,0,-0.15453018258377502,0,-0.15453018258377502
diff --git a/src/components/FishAsFoodLinkChartViz.vue b/src/components/FishAsFoodLinkChartViz.vue
index 642a0dc..aa884e6 100644
--- a/src/components/FishAsFoodLinkChartViz.vue
+++ b/src/components/FishAsFoodLinkChartViz.vue
@@ -23,7 +23,7 @@
 </template>
 
 <script setup>
-    import { onMounted, ref, reactive } from "vue";
+    import { onMounted, ref } from "vue"; //, reactive
     import * as d3 from 'd3';
     import VizSection from '@/components/VizSection.vue';
 
@@ -67,9 +67,9 @@
                 // console.log(expandedFamilies)
                 initChart({
                     width: chart.value.offsetWidth,
-                    height: 1600,
+                    height: 1800,
                     margin: 20,
-                    marginBottom: 30,
+                    marginBottom: 60,
                     marginLeft: 200});
                 drawChart(data.value);
             } else {
@@ -169,14 +169,14 @@
         //     .call(d3.axisBottom(xScale).tickSize(0).tickPadding(10))
             // .select(".domain").remove() // remove axis line
         
-        // add placeholder for x axis title (title text set in drawChart())
-        xAxis
-            .append("text")
-            .attr("class", "x-axis axis-title")
-            .attr("x", -chartDimensions.boundedWidth / 2)
-            .style("text-anchor", "middle")
-            .attr("role", "presentation")
-            .attr("aria-hidden", true)
+        // // add placeholder for x axis title (title text set in drawChart())
+        // xAxis
+        //     .append("text")
+        //     .attr("class", "x-axis axis-title")
+        //     .attr("x", -chartDimensions.boundedWidth / 2)
+        //     .style("text-anchor", "middle")
+        //     .attr("role", "presentation")
+        //     .attr("aria-hidden", true)
     }
 
     function initYScale() {
@@ -196,15 +196,15 @@
         //     .call(d3.axisLeft(yScale).tickSize(2))
             // .select(".domain").remove() // remove axis line
 
-        // add placeholder for y axis title (title text set in drawChart())
-        yAxis
-            .append("text")
-            .attr("class", "y-axis axis-title")
-            .attr("x", -chartDimensions.boundedHeight / 2)
-            .attr("transform", "rotate(-90)")
-            .style("text-anchor", "middle")
-            .attr("role", "presentation")
-            .attr("aria-hidden", true)
+        // // add placeholder for y axis title (title text set in drawChart())
+        // yAxis
+        //     .append("text")
+        //     .attr("class", "y-axis axis-title")
+        //     .attr("x", -chartDimensions.boundedHeight / 2)
+        //     .attr("transform", "rotate(-90)")
+        //     .style("text-anchor", "middle")
+        //     .attr("role", "presentation")
+        //     .attr("aria-hidden", true)
     }
 
     function initGradients() {
@@ -296,13 +296,45 @@
 
         // set domain for xScale
         xScale
-            .domain([0, 0.3]) // d3.max([d3.max(data, x0Accessor), d3.max(data, x1Accessor)])])
+            // .domain([0, 0.3]) // d3.max([d3.max(data, x0Accessor), d3.max(data, x1Accessor)])])
+            .domain([-0.4, 0.4])
+        console.log(xScale.domain())
+        
         xAxis
             .call(d3.axisBottom(xScale).tickSize(0).tickPadding(10))
 
         xAxis
             .selectAll("text")
             .attr("class", "axis-text")
+
+        const xAxisLabelYPosition = xAxis.select("text").attr('y')
+        const xAxisLabelDy = xAxis.select("text").attr('dy')
+        xAxis.append("text")
+            .attr("class", "x-axis axis-title")
+            .attr("x", chartDimensions.boundedWidth / 2)
+            .attr("y", xAxisLabelYPosition * 4)
+            .attr("dy", xAxisLabelDy)
+            .style("text-anchor", "middle")
+            .text('Percent change in harvest-weighted climate vulnerability, 2030-2075')
+        // xAxis.append("text")
+        //     .attr("class", "x-axis axis-title")
+        //     .attr("x", 0)
+        //     .attr("y", xAxisLabelYPosition * 4)
+        //     .attr("dy", xAxisLabelDy)
+        //     .style("text-anchor", "start")
+        //     .text('Less vulnerable')
+        // xAxis.append("text")
+        //     .attr("class", "x-axis axis-title")
+        //     .attr("x", chartDimensions.boundedWidth)
+        //     .attr("y", xAxisLabelYPosition * 4)
+        //     .attr("dy", xAxisLabelDy)
+        //     .style("text-anchor", "end")
+        //     .text('More vulnerable')
+
+        // Remove axix line and labels
+        // xAxis.select(".domain").remove()
+        // xAxis.call(d3.axisBottom(xScale).tickValues([]))
+
         // set domain for yScale
         // let yDomain = []
         // families.value.map(family => expandedFamilies[family] ? yDomain.push(data.filter(d => d.family === family).map(d => d.species)) : yDomain.push(family))
@@ -318,6 +350,16 @@
             .selectAll("text")
             .attr("class", "axis-text")
 
+        const yAxisLabelXPosition = yAxis.select("text").attr('x')
+        const yAxisLabelDx = yAxis.select("text").attr('dx')
+        yAxis.append("text")
+            .attr("class", "y-axis axis-title")
+            .attr("y", 0)
+            .attr("x", yAxisLabelXPosition)
+            .attr("dx", yAxisLabelDx)
+            .style("text-anchor", "end")
+            .text('Species')
+
         // set up width scale
         const radiusPosition0 = 1
         const strokeRatio = 0.3
@@ -328,8 +370,8 @@
         // set up area function
         const area = d3.area()
             .x(d => xScale(xAccessor(d)))
-            .y0(d => yScale(yAccessor(d)) - widthScale(widthAccessor(d)))
-            .y1(d => yScale(yAccessor(d)) + widthScale(widthAccessor(d)));
+            .y0(d => yScale(yAccessor(d)) + yScale.bandwidth() / 2 - widthScale(widthAccessor(d)))
+            .y1(d => yScale(yAccessor(d)) + yScale.bandwidth() / 2+ widthScale(widthAccessor(d)));
             // .x(d => expandedFamilies[d.family] ? xScale(xAccessor(d)) : xScale(xAccessor_family(d)))
             // .y0(d => expandedFamilies[d.family] ? yScale(yAccessor(d)) - widthScale(widthAccessor(d)) : yScale(yAccessor_family(d)) - widthScale(widthAccessor(d)))
             // .y1(d => expandedFamilies[d.family] ? yScale(yAccessor(d)) + widthScale(widthAccessor(d)) : yScale(yAccessor_family(d)) + widthScale(widthAccessor(d)));
@@ -401,7 +443,7 @@
                     .attr("id", d => 'point-2030-' + identifierAccessor(d))
                     .attr("class", "point")
                     .attr("cx", d => xScale(x0Accessor(d)))
-                    .attr("cy", d => yScale(yAccessor(d)))
+                    .attr("cy", d => yScale(yAccessor(d)) + yScale.bandwidth() / 2)
                     .attr("r", radiusPosition0)
                     .style("stroke", d => colorScale(colorAccessor(d)))
                     .style("fill", "white")
@@ -416,7 +458,7 @@
                     .attr("id", d => 'point-2075-' + identifierAccessor(d))
                     .attr("class", "point")
                     .attr("cx", d => xScale(x1Accessor(d)))
-                    .attr("cy", d => yScale(yAccessor(d)))
+                    .attr("cy", d => yScale(yAccessor(d)) + yScale.bandwidth() / 2)
                     .attr("r", radiusPosition1 - strokeWidth1 / 2)
                     .style("stroke", d => colorScale(colorAccessor(d)))
                     .style("stroke-width", strokeWidth1)
@@ -448,4 +490,11 @@
         font-family: var(--default-font);
         user-select: none;
     }
+    .axis-title {
+        font-size: 1.8rem;
+        font-family: var(--default-font);
+        font-weight: 900;
+        fill: var(--color-text);
+        user-select: none;
+    }
 </style>
\ No newline at end of file
-- 
GitLab


From 7e8348bd46d218a78c7f16c3905e93bf8de5a793 Mon Sep 17 00:00:00 2001
From: Corson-Dosch <hcorson-dosch@usgs.gov>
Date: Tue, 27 Aug 2024 10:23:12 -0500
Subject: [PATCH 05/25] add ref to track if scale is percent change

---
 public/fish_as_food_climate.csv           | 166 +++++++++++-----------
 src/components/FishAsFoodLinkChartViz.vue |  19 ++-
 2 files changed, 95 insertions(+), 90 deletions(-)

diff --git a/public/fish_as_food_climate.csv b/public/fish_as_food_climate.csv
index 667f577..74ed7c5 100644
--- a/public/fish_as_food_climate.csv
+++ b/public/fish_as_food_climate.csv
@@ -1,84 +1,84 @@
 family,thermal_guild,species,cvi_2030,cvi_2075,cvi_2030_family,cvi_2075_family
-Cyprinidae,warm,Albanian roach,0,-0.12674595966311653,0,-0.12674595966311653
-Cyprinidae,warm,Asp,0,-0.12674595966311658,0,-0.12674595966311658
-Cyprinidae,warm,Barbel,0,-0.15351243187409472,0,-0.15351243187409472
-Cyprinidae,warm,Bata,0,0.37206388891501935,0,0.37206388891501935
-Cyprinidae,warm,BeyÅŸehir bleak,0,-0.1727921934966878,0,-0.1727921934966878
-Cyprinidae,warm,Black bream,0,0.19706442551266273,0,0.19706442551266273
-Cyprinidae,warm,Black carp,0,0.19706442551266273,0,0.19706442551266273
-Cyprinidae,warm,Blue-finned mahseer,0,0.2386778365629439,0,0.2386778365629439
-Cyprinidae,warm,Catla,0,0.3720638889150193,0,0.3720638889150193
-Cyprinidae,warm,Chocolate mahseer,0,0.21181878773691584,0,0.21181878773691584
-Cyprinidae,warm,Chub,0,-0.17351529202546123,0,-0.17351529202546123
-Cyprinidae,warm,Common barbel,0,-0.15351243187409475,0,-0.15351243187409475
-Cyprinidae,warm,Common bleak,0,-0.1727921934966878,0,-0.1727921934966878
-Cyprinidae,warm,Common bream,0,-0.20817948843969888,0,-0.20817948843969888
-Cyprinidae,warm,Common carp,0,-0.13165865030651286,0,-0.13165865030651286
-Cyprinidae,warm,Common nase,0,-0.10402292348955595,0,-0.10402292348955595
-Cyprinidae,warm,Common roach,0,-0.19771860966928614,0,-0.19771860966928614
-Cyprinidae,warm,Crucian carp,0,-0.1316586503065129,0,-0.1316586503065129
-Cyprinidae,warm,Cyprinds,0,-0.13165865030651291,0,-0.13165865030651291
-Cyprinidae,warm,European carp,0,-0.13165865030651283,0,-0.13165865030651283
-Cyprinidae,warm,European chub,0,-0.17351529202546126,0,-0.17351529202546126
-Cyprinidae,warm,Ghonia,0,0.3720638889150193,0,0.3720638889150193
-Cyprinidae,warm,Golden mahseer,0,0.13905799068941688,0,0.13905799068941688
-Cyprinidae,warm,Goldfish,0,-0.13165865030651283,0,-0.13165865030651283
-Cyprinidae,warm,Grass carp,0,0.1970644255126627,0,0.1970644255126627
-Cyprinidae,warm,Gudgeon,0,-0.11715395492165936,0,-0.11715395492165936
-Cyprinidae,warm,Italian bleak,0,-0.17279219349668792,0,-0.17279219349668792
-Cyprinidae,warm,Kalibaus,0,0.3720638889150193,0,0.3720638889150193
-Cyprinidae,warm,Kutum,0,-0.10402292348955605,0,-0.10402292348955605
-Cyprinidae,warm,Labeo,0,0.37206388891501935,0,0.37206388891501935
-Cyprinidae,warm,Mrigal,0,0.3720638889150193,0,0.3720638889150193
-Cyprinidae,warm,Mullet,0,-0.1735152920254612,0,-0.1735152920254612
-Cyprinidae,warm,Orange River mudfish,0,0.3720638889150193,0,0.3720638889150193
-Cyprinidae,warm,Pale chub,0,0.19706442551266287,0,0.19706442551266287
-Cyprinidae,warm,Pearl mullet,0,-0.17279219349668795,0,-0.17279219349668795
-Cyprinidae,warm,Prussian carp,0,-0.13165865030651291,0,-0.13165865030651291
-Cyprinidae,warm,Prussian/Crucian carp,0,-0.13165865030651283,0,-0.13165865030651283
-Cyprinidae,warm,Razor fish (ziege),0,-0.1267459596631165,0,-0.1267459596631165
-Cyprinidae,warm,Red garra,0,0.37206388891501935,0,0.37206388891501935
-Cyprinidae,warm,Rohu,0,0.37206388891501935,0,0.37206388891501935
-Cyprinidae,warm,Rudd,0,-0.1727921934966879,0,-0.1727921934966879
-Cyprinidae,warm,Rui,0,0.37206388891501924,0,0.37206388891501924
-Cyprinidae,warm,Shabout,0,-0.15351243187409466,0,-0.15351243187409466
-Cyprinidae,warm,Silver bream,0,-0.20817948843969877,0,-0.20817948843969877
-Cyprinidae,warm,Silver carp,0,0.1970644255126629,0,0.1970644255126629
-Cyprinidae,warm,Tench,0,-0.11715395492165945,0,-0.11715395492165945
-Cyprinidae,warm,Transcau casian barb,0,-0.1535124318740947,0,-0.1535124318740947
-Cyprinidae,warm,Trout barb,0,-0.1535124318740947,0,-0.1535124318740947
-Cyprinidae,warm,Vimba bream,0,-0.12674595966311641,0,-0.12674595966311641
-Cyprinidae,warm,White Bream,0,-0.20817948843969877,0,-0.20817948843969877
-Percidae,cool,Eurasian Ruffe,0,-0.11250527094534767,0,-0.11250527094534767
-Percidae,cool,European perch,0,-0.12568476887712102,0,-0.12568476887712102
-Percidae,cool,Golden perch,0,0.030164612386623692,0,0.030164612386623692
-Percidae,cool,Redfin perch,0,-0.1256847688771208,0,-0.1256847688771208
-Percidae,cool,Volga pikeperch,0,-0.19081224231396332,0,-0.19081224231396332
-Percidae,cool,Walleye,0,-0.04218264912350984,0,-0.04218264912350984
-Percidae,cool,Yellow perch,0,-0.06559965926335624,0,-0.06559965926335624
-Percidae,cool,Zander (pikeperch),0,-0.1430053459462026,0,-0.1430053459462026
-Salmonidae,cold,Arctic Char,0,-0.018371792037054607,0,-0.018371792037054607
-Salmonidae,cold,Atlantic salmon,0,-0.09450403105167436,0,-0.09450403105167436
-Salmonidae,cold,Balkan trout,0,-0.11286733554414553,0,-0.11286733554414553
-Salmonidae,cold,Brook Trout,0,-0.06625534224631832,0,-0.06625534224631832
-Salmonidae,cold,Brown Trout,0,-0.11286733554414562,0,-0.11286733554414562
-Salmonidae,cold,Cherry trout,0,-0.05942407235726659,0,-0.05942407235726659
-Salmonidae,cold,Chinook Salmon (Quinnat Salmon),0,-0.04901053698319066,0,-0.04901053698319066
-Salmonidae,cold,Chum salmon,0,-0.015020507572166128,0,-0.015020507572166128
-Salmonidae,cold,Coho salmon,0,-0.06708638496180307,0,-0.06708638496180307
-Salmonidae,cold,Common huchen,0,-0.05982949588597892,0,-0.05982949588597892
-Salmonidae,cold,Dolly varden/Char,0,-0.034698667349516474,0,-0.034698667349516474
-Salmonidae,cold,European grayling,0,-0.1190454219139104,0,-0.1190454219139104
-Salmonidae,cold,Pink salmon,0,-0.04347572414192542,0,-0.04347572414192542
-Salmonidae,cold,Rainbow trout,0,-0.05321346547492189,0,-0.05321346547492189
-Salmonidae,cold,Salmon,0,-0.030033367611790023,0,-0.030033367611790023
-Salmonidae,cold,Sockeye salmon,0,-0.00325633244218971,0,-0.00325633244218971
-Salmonidae,cold,Steelhead,0,-0.05321346547492191,0,-0.05321346547492191
-Salmonidae,cold,Trout,0,-0.036775579505042726,0,-0.036775579505042726
-Salmonidae,cold,Vendace,0,-0.06808082541938247,0,-0.06808082541938247
-Salmonidae,cold,Whitefish,0,-0.06808082541938261,0,-0.06808082541938261
-Siluridae,warm,Boal,0,-0.15453018258377504,0,-0.15453018258377504
-Siluridae,warm,Catfish,0,-0.154530182583775,0,-0.154530182583775
-Siluridae,warm,European catfish,0,-0.1545301825837752,0,-0.1545301825837752
-Siluridae,warm,Far eastern catfish,0,-0.15453018258377504,0,-0.15453018258377504
-Siluridae,warm,Wels catfish,0,-0.15453018258377502,0,-0.15453018258377502
+Cyprinidae,warm,Albanian roach,0.10778174260000001,0.0941208422,0.10778174260000001,0.0941208422
+Cyprinidae,warm,Asp,0.013429605127960001,0.011727456938119999,0.013429605127960001,0.011727456938119999
+Cyprinidae,warm,Barbel,0.04705801130333334,0.039834021549,0.04705801130333334,0.039834021549
+Cyprinidae,warm,Bata,0.0055121400746,0.007563008347000001,0.0055121400746,0.007563008347000001
+Cyprinidae,warm,BeyÅŸehir bleak,7.267071073e-4,6.011377922000001e-4,7.267071073e-4,6.011377922000001e-4
+Cyprinidae,warm,Black bream,0.051246241799999995,0.061345053,0.051246241799999995,0.061345053
+Cyprinidae,warm,Black carp,0.051246241799999995,0.061345053,0.051246241799999995,0.061345053
+Cyprinidae,warm,Blue-finned mahseer,0.0224598681704,0.027820540914799997,0.0224598681704,0.027820540914799997
+Cyprinidae,warm,Catla,0.0220743179623,0.0302873745485,0.0220743179623,0.0302873745485
+Cyprinidae,warm,Chocolate mahseer,0.0028306322192,0.0034302133044,0.0028306322192,0.0034302133044
+Cyprinidae,warm,Chub,0.03216167369029999,0.026581131487899996,0.03216167369029999,0.026581131487899996
+Cyprinidae,warm,Common barbel,0.002834382148,0.0023992692516,0.002834382148,0.0023992692516
+Cyprinidae,warm,Common bleak,0.00854949538,0.007072209320000001,0.00854949538,0.007072209320000001
+Cyprinidae,warm,Common bream,0.04600620713676522,0.03642865846998261,0.04600620713676522,0.03642865846998261
+Cyprinidae,warm,Common carp,0.13811330662322036,0.1199294950838376,0.13811330662322036,0.1199294950838376
+Cyprinidae,warm,Common nase,0.026387573782000004,0.023642661213400005,0.026387573782000004,0.023642661213400005
+Cyprinidae,warm,Common roach,0.05312624687117,0.042622199202855,0.05312624687117,0.042622199202855
+Cyprinidae,warm,Crucian carp,0.116377215108,0.10105514804045,0.116377215108,0.10105514804045
+Cyprinidae,warm,Cyprinds,0.032788926016,0.028471980271733335,0.032788926016,0.028471980271733335
+Cyprinidae,warm,European carp,0.13040639766,0.11323726735275001,0.13040639766,0.11323726735275001
+Cyprinidae,warm,European chub,0.001917238372,0.001584568196,0.001917238372,0.001584568196
+Cyprinidae,warm,Ghonia,0.0018287941369,0.0025092223955,0.0018287941369,0.0025092223955
+Cyprinidae,warm,Golden mahseer,0.0034355641156000004,0.0039133067584,0.0034355641156000004,0.0039133067584
+Cyprinidae,warm,Goldfish,0.189583548,0.16462323395,0.189583548,0.16462323395
+Cyprinidae,warm,Grass carp,0.028623161305374997,0.034263768144375,0.028623161305374997,0.034263768144375
+Cyprinidae,warm,Gudgeon,0.0344069672688,0.030376054976399995,0.0344069672688,0.030376054976399995
+Cyprinidae,warm,Italian bleak,0.10233745969859999,0.0846543455604,0.10233745969859999,0.0846543455604
+Cyprinidae,warm,Kalibaus,0.0036833459377,0.0050537859515,0.0036833459377,0.0050537859515
+Cyprinidae,warm,Kutum,2.15702238e-4,1.9326426059999998e-4,2.15702238e-4,1.9326426059999998e-4
+Cyprinidae,warm,Labeo,0.017643999771499996,0.024208694942499998,0.017643999771499996,0.024208694942499998
+Cyprinidae,warm,Mrigal,0.019859158866899998,0.0272480347455,0.019859158866899998,0.0272480347455
+Cyprinidae,warm,Mullet,0.022048241277999998,0.018222534254,0.022048241277999998,0.018222534254
+Cyprinidae,warm,Orange River mudfish,0.01803036473,0.024738812350000004,0.01803036473,0.024738812350000004
+Cyprinidae,warm,Pale chub,0.0023231629616,0.0027809757360000003,0.0023231629616,0.0027809757360000003
+Cyprinidae,warm,Pearl mullet,0.1777012614733,0.14699587071620002,0.1777012614733,0.14699587071620002
+Cyprinidae,warm,Prussian carp,0.064146947634,0.055701447087225,0.064146947634,0.055701447087225
+Cyprinidae,warm,Prussian/Crucian carp,0.0216666912,0.018814083880000002,0.0216666912,0.018814083880000002
+Cyprinidae,warm,Razor fish (ziege),3.7723609910000005e-4,3.294229477e-4,3.7723609910000005e-4,3.294229477e-4
+Cyprinidae,warm,Red garra,0.017643999771499996,0.024208694942499998,0.017643999771499996,0.024208694942499998
+Cyprinidae,warm,Rohu,0.0017515211452,0.0024031989140000003,0.0017515211452,0.0024031989140000003
+Cyprinidae,warm,Rudd,0.010652671243479999,0.00881197281272,0.010652671243479999,0.00881197281272
+Cyprinidae,warm,Rui,0.0368077017131,0.0505025183545,0.0368077017131,0.0505025183545
+Cyprinidae,warm,Shabout,0.0011991616780000001,0.0010150754526,0.0011991616780000001,0.0010150754526
+Cyprinidae,warm,Silver bream,0.010815032339999999,0.008563564440000001,0.010815032339999999,0.008563564440000001
+Cyprinidae,warm,Silver carp,0.029425103982114283,0.035223745194000004,0.029425103982114283,0.035223745194000004
+Cyprinidae,warm,Tench,0.017184651140000003,0.015171401295,0.017184651140000003,0.015171401295
+Cyprinidae,warm,Transcau casian barb,0.014553462183,0.0123193248111,0.014553462183,0.0123193248111
+Cyprinidae,warm,Trout barb,0.004578617316,0.0038757426371999995,0.004578617316,0.0038757426371999995
+Cyprinidae,warm,Vimba bream,0.0212868941635,0.0185888663345,0.0212868941635,0.0185888663345
+Cyprinidae,warm,White Bream,0.043260129359999996,0.034254257760000004,0.043260129359999996,0.034254257760000004
+Percidae,cool,Eurasian Ruffe,0.00486753292,0.0043199098100000005,0.00486753292,0.0043199098100000005
+Percidae,cool,European perch,0.07259463927767063,0.06347059881833862,0.07259463927767063,0.06347059881833862
+Percidae,cool,Golden perch,0.0334276893426,0.0344360226346,0.0334276893426,0.0344360226346
+Percidae,cool,Redfin perch,0.0263136745872,0.0230064464784,0.0263136745872,0.0230064464784
+Percidae,cool,Volga pikeperch,8.01681972e-4,6.487112373e-4,8.01681972e-4,6.487112373e-4
+Percidae,cool,Walleye,0.10268783482319999,0.0983561899176,0.10268783482319999,0.0983561899176
+Percidae,cool,Yellow perch,0.0488076651,0.0456058989,0.0488076651,0.0456058989
+Percidae,cool,Zander (pikeperch),0.037962706268244786,0.032533836325300367,0.037962706268244786,0.032533836325300367
+Salmonidae,cold,Arctic Char,0.0356557719054,0.03500071147903334,0.0356557719054,0.03500071147903334
+Salmonidae,cold,Atlantic salmon,0.265177841604771,0.24011746662753772,0.265177841604771,0.24011746662753772
+Salmonidae,cold,Balkan trout,0.026710147320000002,0.023695444160000002,0.026710147320000002,0.023695444160000002
+Salmonidae,cold,Brook Trout,0.026461932224000003,0.024708687848000004,0.026461932224000003,0.024708687848000004
+Salmonidae,cold,Brown Trout,0.050732109690129576,0.04500611164287132,0.050732109690129576,0.04500611164287132
+Salmonidae,cold,Cherry trout,0.0029371095160000004,0.0027625745076,0.0029371095160000004,0.0027625745076
+Salmonidae,cold,Chinook Salmon (Quinnat Salmon),0.1600752957,0.15222991949999998,0.1600752957,0.15222991949999998
+Salmonidae,cold,Chum salmon,0.02885045315,0.0284171047,0.02885045315,0.0284171047
+Salmonidae,cold,Coho salmon,0.2150947458,0.20066481688,0.2150947458,0.20066481688
+Salmonidae,cold,Common huchen,5.120283490000001e-5,4.81393951e-5,5.120283490000001e-5,4.81393951e-5
+Salmonidae,cold,Dolly varden/Char,0.0034788601644,0.0033581483528,0.0034788601644,0.0033581483528
+Salmonidae,cold,European grayling,0.024065881522700002,0.0212009485031,0.024065881522700002,0.0212009485031
+Salmonidae,cold,Pink salmon,0.025550719900000005,0.02443988385,0.025550719900000005,0.02443988385
+Salmonidae,cold,Rainbow trout,0.06302419030074105,0.05967045472608766,0.06302419030074105,0.05967045472608766
+Salmonidae,cold,Salmon,0.06449020412644443,0.06255334611855555,0.06449020412644443,0.06255334611855555
+Salmonidae,cold,Sockeye salmon,0.029446763100000002,0.029350874649999997,0.029446763100000002,0.029350874649999997
+Salmonidae,cold,Steelhead,0.0049289009400000005,0.00466661704,0.0049289009400000005,0.00466661704
+Salmonidae,cold,Trout,0.0858357697255,0.08267910955158334,0.0858357697255,0.08267910955158334
+Salmonidae,cold,Vendace,0.04116622401,0.0383635935,0.04116622401,0.0383635935
+Salmonidae,cold,Whitefish,0.05007684609903145,0.04666757308221,0.05007684609903145,0.04666757308221
+Siluridae,warm,Boal,0.0124330956288,0.0105118070912,0.0124330956288,0.0105118070912
+Siluridae,warm,Catfish,0.033215868223999995,0.028083014042666665,0.033215868223999995,0.028083014042666665
+Siluridae,warm,European catfish,0.016684117296,0.014105917603999998,0.016684117296,0.014105917603999998
+Siluridae,warm,Far eastern catfish,0.0031082739072,0.0026279517728,0.0031082739072,0.0026279517728
+Siluridae,warm,Wels catfish,0.0333433019136,0.028190755380945457,0.0333433019136,0.028190755380945457
diff --git a/src/components/FishAsFoodLinkChartViz.vue b/src/components/FishAsFoodLinkChartViz.vue
index aa884e6..368c972 100644
--- a/src/components/FishAsFoodLinkChartViz.vue
+++ b/src/components/FishAsFoodLinkChartViz.vue
@@ -37,6 +37,7 @@
     const dataFile = 'fish_as_food_climate.csv' //'fish_as_food_climate_test.csv'
     const data = ref();
     const chart = ref(null);
+    const scalePercent = ref();
     // const families = ref();
     let chartDimensions;
     const chartTitle = 'Title of chart';
@@ -65,13 +66,17 @@
                 // });
                 // expandedFamilies["A"] = !expandedFamilies["A"]
                 // console.log(expandedFamilies)
+                // Set starting scale type
+                scalePercent.value = true;
+
                 initChart({
                     width: chart.value.offsetWidth,
                     height: 1800,
                     margin: 20,
                     marginBottom: 60,
                     marginLeft: 200});
-                drawChart(data.value);
+
+                drawChart(data.value, scalePercent.value);
             } else {
                 console.error('Error loading data');
             }
@@ -274,16 +279,16 @@
         return itemColor;
     }
 
-    function drawChart(data) {
+    function drawChart(data, scalePercent) {
         // accessor functions
         const yAccessor = d => d.species
         // const yAccessor_family = d => d.family
         const xAccessor = d => d.cvi
         // const xAccessor_family = d => d.cvi_family
-        const x0Accessor = d => d.cvi_2030
-        const x1Accessor = d => d.cvi_2075
-        const x0Accessor_family = d => d.cvi_2030_family
-        const x1Accessor_family = d => d.cvi_2075_family
+        const x0Accessor = d => scalePercent ? 0 : d.cvi_2030
+        const x1Accessor = d => scalePercent ? (d.cvi_2075 - d.cvi_2030)/ d.cvi_2030 : d.cvi_2075
+        const x0Accessor_family = d => scalePercent ? 0 : d.cvi_2030_family
+        const x1Accessor_family = d => scalePercent ? (d.cvi_2075_family - d.cvi_2030_family)/ d.cvi_2030_family : d.cvi_2075_family
         const widthAccessor = d => d.position
         const colorAccessor = d => d.thermal_guild
         const identifierAccessor = d => d.family + '_' + d.species
@@ -430,7 +435,7 @@
                     //     yScale.range([chartDimensions.boundedHeight * 2, 0])
                     //     yAxis
                     //         .call(d3.axisLeft(yScale).tickSize(2))
-                    //     drawChart(data)
+                    //     drawChart(data, scalePercent.value)
                     // });
 
         chartBounds.append("g")
-- 
GitLab


From d3e3ef88418c02b90d2d5008ce3c4b100a40b56c Mon Sep 17 00:00:00 2001
From: Corson-Dosch <hcorson-dosch@usgs.gov>
Date: Tue, 27 Aug 2024 14:07:54 -0500
Subject: [PATCH 06/25] start implementing enter update exit pattern

---
 src/components/FishAsFoodLinkChartViz.vue | 126 ++++++++++++++++++----
 1 file changed, 103 insertions(+), 23 deletions(-)

diff --git a/src/components/FishAsFoodLinkChartViz.vue b/src/components/FishAsFoodLinkChartViz.vue
index 368c972..66a47fb 100644
--- a/src/components/FishAsFoodLinkChartViz.vue
+++ b/src/components/FishAsFoodLinkChartViz.vue
@@ -12,6 +12,14 @@
         </template>
         <template #figures>
             <div id="chart-container" class="maxWidth" ref="chart"></div>
+            <button
+                  aria-pressed="!scalePercent"
+                  class="button"
+                  :text="scaleType"
+                  @click="toggleScale"
+                >
+                  {{ scaleType }}
+            </button>
         </template>
         <!-- FIGURE CAPTION -->
         <template #figureCaption>
@@ -23,7 +31,7 @@
 </template>
 
 <script setup>
-    import { onMounted, ref } from "vue"; //, reactive
+    import { computed, onMounted, ref } from "vue"; //, reactive
     import * as d3 from 'd3';
     import VizSection from '@/components/VizSection.vue';
 
@@ -37,8 +45,7 @@
     const dataFile = 'fish_as_food_climate.csv' //'fish_as_food_climate_test.csv'
     const data = ref();
     const chart = ref(null);
-    const scalePercent = ref();
-    // const families = ref();
+    const scalePercent = ref(false);
     let chartDimensions;
     const chartTitle = 'Title of chart';
     let chartSVG;
@@ -52,6 +59,11 @@
     // Create a reactive object to track expanded families
     // const expandedFamilies = reactive({});
 
+    // set up filtered chart data as computed property
+    const scaleType = computed(() => {
+        return scalePercent.value ? 'Percent change' : 'Change'
+    });
+
 
     // Behavior on mounted (functions called here)
     // Load data and then make chart
@@ -67,7 +79,7 @@
                 // expandedFamilies["A"] = !expandedFamilies["A"]
                 // console.log(expandedFamilies)
                 // Set starting scale type
-                scalePercent.value = true;
+                // scalePercent.value = true;
 
                 initChart({
                     width: chart.value.offsetWidth,
@@ -96,7 +108,13 @@
 
     async function loadData(fileName) {
         try {
-            const data = await d3.csv(publicPath + fileName);
+            const data = await d3.csv(publicPath + fileName, d => {
+                d.cvi_2030 = +d.cvi_2030;
+                d.cvi_2075 = +d.cvi_2075;
+                d.cvi_2030_family = +d.cvi_2030_family;
+                d.cvi_2075_family = +d.cvi_2075_family;
+                return d;
+            });
             return data;
         } catch (error) {
             console.error(`Error loading data from ${fileName}`, error);
@@ -104,6 +122,11 @@
         }
     }
 
+    function toggleScale() {
+        scalePercent.value = !scalePercent.value
+        drawChart(data.value, scalePercent.value)
+    }
+
     function initChart({
         width = 500, // outer width, in pixels
         height = width, // outer height, in pixels
@@ -291,7 +314,7 @@
         const x1Accessor_family = d => scalePercent ? (d.cvi_2075_family - d.cvi_2030_family)/ d.cvi_2030_family : d.cvi_2075_family
         const widthAccessor = d => d.position
         const colorAccessor = d => d.thermal_guild
-        const identifierAccessor = d => d.family + '_' + d.species
+        const identifierAccessor = d => d.family + '_' + d.species.replace(/ /g,"_");
 
         // to get dynamic
         // need key for data
@@ -300,13 +323,21 @@
 
 
         // set domain for xScale
-        xScale
-            // .domain([0, 0.3]) // d3.max([d3.max(data, x0Accessor), d3.max(data, x1Accessor)])])
-            .domain([-0.4, 0.4])
-        console.log(xScale.domain())
+        if (scalePercent) {
+            const maxVal = d3.max([Math.abs(d3.max(data, x1Accessor)), Math.abs(d3.min(data, x1Accessor))])
+            xScale
+                .domain([-maxVal, maxVal])
+                .nice()
+        } else {
+            const maxVal = d3.max([d3.min(data, x0Accessor), d3.max(data, x0Accessor), d3.min(data, x1Accessor), d3.max(data, x1Accessor)])
+            xScale
+                .domain([0, Math.round(maxVal * 10) / 10])
+                .nice()
+        }
         
-        xAxis
-            .call(d3.axisBottom(xScale).tickSize(0).tickPadding(10))
+        xAxis.transition()
+            .duration(500)
+            .call(d3.axisBottom(xScale).tickSize(0).tickPadding(10));
 
         xAxis
             .selectAll("text")
@@ -320,7 +351,7 @@
             .attr("y", xAxisLabelYPosition * 4)
             .attr("dy", xAxisLabelDy)
             .style("text-anchor", "middle")
-            .text('Percent change in harvest-weighted climate vulnerability, 2030-2075')
+            .text(scalePercent ? 'Percent change in harvest-weighted climate vulnerability, 2030-2075' : 'Change in harvest-weighted climate vulnerability, 2030-2075')
         // xAxis.append("text")
         //     .attr("class", "x-axis axis-title")
         //     .attr("x", 0)
@@ -411,17 +442,54 @@
         })
             
         // draw chart
-        chartBounds.append("g")
+        let areaGroups = chartBounds.append("g")
             .attr("id", "areas")
-            .selectAll('.area')
-                .data(areaData)
-                .enter()
-                .append('path')
-                    .attr("id", d => 'area-2030-' + identifierAccessor(d))
-                    .attr('class', "area")
-                    .attr('d', d => area(d))
-                    .attr('fill', d => d[0].cvi_decreasing ? `url(#${d[0].thermal_guild}_gradient_decreasing)`: `url(#${d[0].thermal_guild}_gradient_increasing)`)//d => colorScale(colorAccessor(d[0])))
-                    .style("opacity", 1)
+            .selectAll(".area")
+            .data(areaData, d => d[0].species)
+
+        const oldAreaGroups = areaGroups.exit()
+
+        oldAreaGroups.selectAll('path')
+            .transition(getExitTransition())
+            .style("opacity", 0)
+
+        oldAreaGroups.transition(getExitTransition()).remove()
+        
+        const newAreaGroups = areaGroups.enter().append("g")
+            .attr("class", d => "area " + d[0].species)
+            .attr("id", d => 'area-group-' + identifierAccessor(d[0]))
+
+        // append paths
+        newAreaGroups.append("path")
+            .append('path')
+            .attr("id", d => 'area-2030-' + identifierAccessor(d[0]))
+            .attr('d', d => area(d))
+            .attr('fill', d => d[0].cvi_decreasing ? `url(#${d[0].thermal_guild}_gradient_decreasing)`: `url(#${d[0].thermal_guild}_gradient_increasing)`)//d => colorScale(colorAccessor(d[0])))
+            .style("opacity", 1)
+
+        // update rectGroups to include new points
+        areaGroups = newAreaGroups.merge(areaGroups)
+
+        const areaPaths = areaGroups.select("path")
+
+        // Update bars based on data values
+        areaPaths.transition(getUpdateTransition())
+            .attr("id", d => 'area-2030-' + identifierAccessor(d[0]))
+            .attr('d', d => area(d))
+            .attr('fill', d => d[0].cvi_decreasing ? `url(#${d[0].thermal_guild}_gradient_decreasing)`: `url(#${d[0].thermal_guild}_gradient_increasing)`)//d => colorScale(colorAccessor(d[0])))
+            .style("opacity", 1)
+        
+        // chartBounds.append("g")
+        //     .attr("id", "areas")
+        //     .selectAll('.area')
+        //         .data(areaData, d => d[0].species)
+        //         .enter()
+        //         .append('path')
+        //             .attr("id", d => 'area-2030-' + identifierAccessor(d[0]))
+        //             .attr('class', "area")
+        //             .attr('d', d => area(d))
+        //             .attr('fill', d => d[0].cvi_decreasing ? `url(#${d[0].thermal_guild}_gradient_decreasing)`: `url(#${d[0].thermal_guild}_gradient_increasing)`)//d => colorScale(colorAccessor(d[0])))
+        //             .style("opacity", 1)
                     // .on("click", function(event, d) {
                     //     const clickedFamily = d[0].family
                     //     expandedFamilies[clickedFamily] = !expandedFamilies[clickedFamily]
@@ -438,6 +506,7 @@
                     //     drawChart(data, scalePercent.value)
                     // });
 
+
         chartBounds.append("g")
             .attr("id", "points-2030")
             .attr("class", "points points_2030")    
@@ -487,6 +556,17 @@
         //             .style("fill", "black")
 
     }
+
+    function getUpdateTransition () {
+      return d3.transition()
+        .duration(500)
+        .ease(d3.easeCubicInOut)
+    }
+    function getExitTransition() {
+      return d3.transition()
+        .duration(500)
+        .ease(d3.easeCubicInOut)
+    }
 </script>
 
 <style lang="scss">
-- 
GitLab


From c7fb294adeb08903da1ef8e1eae6bab5a59c915d Mon Sep 17 00:00:00 2001
From: Corson-Dosch <hcorson-dosch@usgs.gov>
Date: Tue, 27 Aug 2024 17:18:28 -0500
Subject: [PATCH 07/25] get enter update exit working for areas and points

---
 public/fish_as_food_climate.csv           | 128 ++++++++--------
 src/components/FishAsFoodLinkChartViz.vue | 178 +++++++++++++++++-----
 2 files changed, 200 insertions(+), 106 deletions(-)

diff --git a/public/fish_as_food_climate.csv b/public/fish_as_food_climate.csv
index 74ed7c5..a64a562 100644
--- a/public/fish_as_food_climate.csv
+++ b/public/fish_as_food_climate.csv
@@ -1,84 +1,84 @@
 family,thermal_guild,species,cvi_2030,cvi_2075,cvi_2030_family,cvi_2075_family
-Cyprinidae,warm,Albanian roach,0.10778174260000001,0.0941208422,0.10778174260000001,0.0941208422
+Cyprinidae,warm,Albanian roach,0.10778174260000001,0.094120842,0.10778174260000001,0.094120842
 Cyprinidae,warm,Asp,0.013429605127960001,0.011727456938119999,0.013429605127960001,0.011727456938119999
-Cyprinidae,warm,Barbel,0.04705801130333334,0.039834021549,0.04705801130333334,0.039834021549
-Cyprinidae,warm,Bata,0.0055121400746,0.007563008347000001,0.0055121400746,0.007563008347000001
+Cyprinidae,warm,Barbel,0.047058011,0.039834022,0.047058011,0.039834022
+Cyprinidae,warm,Bata,0.00551214,0.007563008,0.00551214,0.007563008
 Cyprinidae,warm,BeyÅŸehir bleak,7.267071073e-4,6.011377922000001e-4,7.267071073e-4,6.011377922000001e-4
 Cyprinidae,warm,Black bream,0.051246241799999995,0.061345053,0.051246241799999995,0.061345053
 Cyprinidae,warm,Black carp,0.051246241799999995,0.061345053,0.051246241799999995,0.061345053
-Cyprinidae,warm,Blue-finned mahseer,0.0224598681704,0.027820540914799997,0.0224598681704,0.027820540914799997
-Cyprinidae,warm,Catla,0.0220743179623,0.0302873745485,0.0220743179623,0.0302873745485
-Cyprinidae,warm,Chocolate mahseer,0.0028306322192,0.0034302133044,0.0028306322192,0.0034302133044
-Cyprinidae,warm,Chub,0.03216167369029999,0.026581131487899996,0.03216167369029999,0.026581131487899996
-Cyprinidae,warm,Common barbel,0.002834382148,0.0023992692516,0.002834382148,0.0023992692516
-Cyprinidae,warm,Common bleak,0.00854949538,0.007072209320000001,0.00854949538,0.007072209320000001
-Cyprinidae,warm,Common bream,0.04600620713676522,0.03642865846998261,0.04600620713676522,0.03642865846998261
+Cyprinidae,warm,Blue-finned mahseer,0.022459868,0.027820540914799997,0.022459868,0.027820540914799997
+Cyprinidae,warm,Catla,0.022074318,0.030287375,0.022074318,0.030287375
+Cyprinidae,warm,Chocolate mahseer,0.002830632,0.003430213,0.002830632,0.003430213
+Cyprinidae,warm,Chub,0.032161674,0.026581131487899996,0.032161674,0.026581131487899996
+Cyprinidae,warm,Common barbel,0.002834382,0.002399269,0.002834382,0.002399269
+Cyprinidae,warm,Common bleak,0.008549495,0.007072209,0.008549495,0.007072209
+Cyprinidae,warm,Common bream,0.046006207,0.036428658,0.046006207,0.036428658
 Cyprinidae,warm,Common carp,0.13811330662322036,0.1199294950838376,0.13811330662322036,0.1199294950838376
 Cyprinidae,warm,Common nase,0.026387573782000004,0.023642661213400005,0.026387573782000004,0.023642661213400005
-Cyprinidae,warm,Common roach,0.05312624687117,0.042622199202855,0.05312624687117,0.042622199202855
-Cyprinidae,warm,Crucian carp,0.116377215108,0.10105514804045,0.116377215108,0.10105514804045
-Cyprinidae,warm,Cyprinds,0.032788926016,0.028471980271733335,0.032788926016,0.028471980271733335
-Cyprinidae,warm,European carp,0.13040639766,0.11323726735275001,0.13040639766,0.11323726735275001
-Cyprinidae,warm,European chub,0.001917238372,0.001584568196,0.001917238372,0.001584568196
-Cyprinidae,warm,Ghonia,0.0018287941369,0.0025092223955,0.0018287941369,0.0025092223955
-Cyprinidae,warm,Golden mahseer,0.0034355641156000004,0.0039133067584,0.0034355641156000004,0.0039133067584
-Cyprinidae,warm,Goldfish,0.189583548,0.16462323395,0.189583548,0.16462323395
-Cyprinidae,warm,Grass carp,0.028623161305374997,0.034263768144375,0.028623161305374997,0.034263768144375
-Cyprinidae,warm,Gudgeon,0.0344069672688,0.030376054976399995,0.0344069672688,0.030376054976399995
-Cyprinidae,warm,Italian bleak,0.10233745969859999,0.0846543455604,0.10233745969859999,0.0846543455604
-Cyprinidae,warm,Kalibaus,0.0036833459377,0.0050537859515,0.0036833459377,0.0050537859515
+Cyprinidae,warm,Common roach,0.053126247,0.042622199,0.053126247,0.042622199
+Cyprinidae,warm,Crucian carp,0.116377215,0.101055148,0.116377215,0.101055148
+Cyprinidae,warm,Cyprinds,0.032788926,0.028471980271733335,0.032788926,0.028471980271733335
+Cyprinidae,warm,European carp,0.130406398,0.11323726735275001,0.130406398,0.11323726735275001
+Cyprinidae,warm,European chub,0.001917238,0.001584568,0.001917238,0.001584568
+Cyprinidae,warm,Ghonia,0.001828794,0.002509222,0.001828794,0.002509222
+Cyprinidae,warm,Golden mahseer,0.003435564,0.003913307,0.003435564,0.003913307
+Cyprinidae,warm,Goldfish,0.189583548,0.164623234,0.189583548,0.164623234
+Cyprinidae,warm,Grass carp,0.028623161305374997,0.034263768,0.028623161305374997,0.034263768
+Cyprinidae,warm,Gudgeon,0.034406967,0.030376054976399995,0.034406967,0.030376054976399995
+Cyprinidae,warm,Italian bleak,0.10233745969859999,0.084654346,0.10233745969859999,0.084654346
+Cyprinidae,warm,Kalibaus,0.003683346,0.005053786,0.003683346,0.005053786
 Cyprinidae,warm,Kutum,2.15702238e-4,1.9326426059999998e-4,2.15702238e-4,1.9326426059999998e-4
 Cyprinidae,warm,Labeo,0.017643999771499996,0.024208694942499998,0.017643999771499996,0.024208694942499998
-Cyprinidae,warm,Mrigal,0.019859158866899998,0.0272480347455,0.019859158866899998,0.0272480347455
-Cyprinidae,warm,Mullet,0.022048241277999998,0.018222534254,0.022048241277999998,0.018222534254
-Cyprinidae,warm,Orange River mudfish,0.01803036473,0.024738812350000004,0.01803036473,0.024738812350000004
-Cyprinidae,warm,Pale chub,0.0023231629616,0.0027809757360000003,0.0023231629616,0.0027809757360000003
-Cyprinidae,warm,Pearl mullet,0.1777012614733,0.14699587071620002,0.1777012614733,0.14699587071620002
-Cyprinidae,warm,Prussian carp,0.064146947634,0.055701447087225,0.064146947634,0.055701447087225
-Cyprinidae,warm,Prussian/Crucian carp,0.0216666912,0.018814083880000002,0.0216666912,0.018814083880000002
+Cyprinidae,warm,Mrigal,0.019859158866899998,0.027248035,0.019859158866899998,0.027248035
+Cyprinidae,warm,Mullet,0.022048241277999998,0.018222534,0.022048241277999998,0.018222534
+Cyprinidae,warm,Orange River mudfish,0.018030365,0.024738812350000004,0.018030365,0.024738812350000004
+Cyprinidae,warm,Pale chub,0.002323163,0.002780976,0.002323163,0.002780976
+Cyprinidae,warm,Pearl mullet,0.177701261,0.14699587071620002,0.177701261,0.14699587071620002
+Cyprinidae,warm,Prussian carp,0.064146948,0.055701447,0.064146948,0.055701447
+Cyprinidae,warm,Prussian/Crucian carp,0.021666691,0.018814083880000002,0.021666691,0.018814083880000002
 Cyprinidae,warm,Razor fish (ziege),3.7723609910000005e-4,3.294229477e-4,3.7723609910000005e-4,3.294229477e-4
 Cyprinidae,warm,Red garra,0.017643999771499996,0.024208694942499998,0.017643999771499996,0.024208694942499998
-Cyprinidae,warm,Rohu,0.0017515211452,0.0024031989140000003,0.0017515211452,0.0024031989140000003
-Cyprinidae,warm,Rudd,0.010652671243479999,0.00881197281272,0.010652671243479999,0.00881197281272
-Cyprinidae,warm,Rui,0.0368077017131,0.0505025183545,0.0368077017131,0.0505025183545
-Cyprinidae,warm,Shabout,0.0011991616780000001,0.0010150754526,0.0011991616780000001,0.0010150754526
-Cyprinidae,warm,Silver bream,0.010815032339999999,0.008563564440000001,0.010815032339999999,0.008563564440000001
+Cyprinidae,warm,Rohu,0.001751521,0.002403199,0.001751521,0.002403199
+Cyprinidae,warm,Rudd,0.010652671243479999,0.008811973,0.010652671243479999,0.008811973
+Cyprinidae,warm,Rui,0.036807702,0.050502518,0.036807702,0.050502518
+Cyprinidae,warm,Shabout,0.001199162,0.001015075,0.001199162,0.001015075
+Cyprinidae,warm,Silver bream,0.010815032339999999,0.008563564,0.010815032339999999,0.008563564
 Cyprinidae,warm,Silver carp,0.029425103982114283,0.035223745194000004,0.029425103982114283,0.035223745194000004
-Cyprinidae,warm,Tench,0.017184651140000003,0.015171401295,0.017184651140000003,0.015171401295
-Cyprinidae,warm,Transcau casian barb,0.014553462183,0.0123193248111,0.014553462183,0.0123193248111
-Cyprinidae,warm,Trout barb,0.004578617316,0.0038757426371999995,0.004578617316,0.0038757426371999995
-Cyprinidae,warm,Vimba bream,0.0212868941635,0.0185888663345,0.0212868941635,0.0185888663345
+Cyprinidae,warm,Tench,0.017184651140000003,0.015171401,0.017184651140000003,0.015171401
+Cyprinidae,warm,Transcau casian barb,0.014553462,0.012319325,0.014553462,0.012319325
+Cyprinidae,warm,Trout barb,0.004578617,0.003875743,0.004578617,0.003875743
+Cyprinidae,warm,Vimba bream,0.021286894,0.018588866,0.021286894,0.018588866
 Cyprinidae,warm,White Bream,0.043260129359999996,0.034254257760000004,0.043260129359999996,0.034254257760000004
-Percidae,cool,Eurasian Ruffe,0.00486753292,0.0043199098100000005,0.00486753292,0.0043199098100000005
-Percidae,cool,European perch,0.07259463927767063,0.06347059881833862,0.07259463927767063,0.06347059881833862
-Percidae,cool,Golden perch,0.0334276893426,0.0344360226346,0.0334276893426,0.0344360226346
-Percidae,cool,Redfin perch,0.0263136745872,0.0230064464784,0.0263136745872,0.0230064464784
+Percidae,cool,Eurasian Ruffe,0.004867533,0.00431991,0.004867533,0.00431991
+Percidae,cool,European perch,0.072594639,0.063470599,0.072594639,0.063470599
+Percidae,cool,Golden perch,0.033427689,0.034436023,0.033427689,0.034436023
+Percidae,cool,Redfin perch,0.026313675,0.023006446,0.026313675,0.023006446
 Percidae,cool,Volga pikeperch,8.01681972e-4,6.487112373e-4,8.01681972e-4,6.487112373e-4
-Percidae,cool,Walleye,0.10268783482319999,0.0983561899176,0.10268783482319999,0.0983561899176
-Percidae,cool,Yellow perch,0.0488076651,0.0456058989,0.0488076651,0.0456058989
+Percidae,cool,Walleye,0.10268783482319999,0.09835619,0.10268783482319999,0.09835619
+Percidae,cool,Yellow perch,0.048807665,0.045605899,0.048807665,0.045605899
 Percidae,cool,Zander (pikeperch),0.037962706268244786,0.032533836325300367,0.037962706268244786,0.032533836325300367
-Salmonidae,cold,Arctic Char,0.0356557719054,0.03500071147903334,0.0356557719054,0.03500071147903334
-Salmonidae,cold,Atlantic salmon,0.265177841604771,0.24011746662753772,0.265177841604771,0.24011746662753772
+Salmonidae,cold,Arctic Char,0.035655772,0.035000711,0.035655772,0.035000711
+Salmonidae,cold,Atlantic salmon,0.265177842,0.24011746662753772,0.265177842,0.24011746662753772
 Salmonidae,cold,Balkan trout,0.026710147320000002,0.023695444160000002,0.026710147320000002,0.023695444160000002
 Salmonidae,cold,Brook Trout,0.026461932224000003,0.024708687848000004,0.026461932224000003,0.024708687848000004
-Salmonidae,cold,Brown Trout,0.050732109690129576,0.04500611164287132,0.050732109690129576,0.04500611164287132
-Salmonidae,cold,Cherry trout,0.0029371095160000004,0.0027625745076,0.0029371095160000004,0.0027625745076
-Salmonidae,cold,Chinook Salmon (Quinnat Salmon),0.1600752957,0.15222991949999998,0.1600752957,0.15222991949999998
-Salmonidae,cold,Chum salmon,0.02885045315,0.0284171047,0.02885045315,0.0284171047
-Salmonidae,cold,Coho salmon,0.2150947458,0.20066481688,0.2150947458,0.20066481688
+Salmonidae,cold,Brown Trout,0.050732109690129576,0.045006112,0.050732109690129576,0.045006112
+Salmonidae,cold,Cherry trout,0.00293711,0.002762575,0.00293711,0.002762575
+Salmonidae,cold,Chinook Salmon (Quinnat Salmon),0.160075296,0.15222991949999998,0.160075296,0.15222991949999998
+Salmonidae,cold,Chum salmon,0.028850453,0.028417105,0.028850453,0.028417105
+Salmonidae,cold,Coho salmon,0.215094746,0.200664817,0.215094746,0.200664817
 Salmonidae,cold,Common huchen,5.120283490000001e-5,4.81393951e-5,5.120283490000001e-5,4.81393951e-5
-Salmonidae,cold,Dolly varden/Char,0.0034788601644,0.0033581483528,0.0034788601644,0.0033581483528
-Salmonidae,cold,European grayling,0.024065881522700002,0.0212009485031,0.024065881522700002,0.0212009485031
-Salmonidae,cold,Pink salmon,0.025550719900000005,0.02443988385,0.025550719900000005,0.02443988385
-Salmonidae,cold,Rainbow trout,0.06302419030074105,0.05967045472608766,0.06302419030074105,0.05967045472608766
-Salmonidae,cold,Salmon,0.06449020412644443,0.06255334611855555,0.06449020412644443,0.06255334611855555
+Salmonidae,cold,Dolly varden/Char,0.00347886,0.003358148,0.00347886,0.003358148
+Salmonidae,cold,European grayling,0.024065881522700002,0.021200949,0.024065881522700002,0.021200949
+Salmonidae,cold,Pink salmon,0.025550719900000005,0.024439884,0.025550719900000005,0.024439884
+Salmonidae,cold,Rainbow trout,0.06302419,0.059670455,0.06302419,0.059670455
+Salmonidae,cold,Salmon,0.064490204,0.062553346,0.064490204,0.062553346
 Salmonidae,cold,Sockeye salmon,0.029446763100000002,0.029350874649999997,0.029446763100000002,0.029350874649999997
-Salmonidae,cold,Steelhead,0.0049289009400000005,0.00466661704,0.0049289009400000005,0.00466661704
-Salmonidae,cold,Trout,0.0858357697255,0.08267910955158334,0.0858357697255,0.08267910955158334
-Salmonidae,cold,Vendace,0.04116622401,0.0383635935,0.04116622401,0.0383635935
-Salmonidae,cold,Whitefish,0.05007684609903145,0.04666757308221,0.05007684609903145,0.04666757308221
-Siluridae,warm,Boal,0.0124330956288,0.0105118070912,0.0124330956288,0.0105118070912
+Salmonidae,cold,Steelhead,0.004928901,0.004666617,0.004928901,0.004666617
+Salmonidae,cold,Trout,0.08583577,0.08267911,0.08583577,0.08267911
+Salmonidae,cold,Vendace,0.041166224,0.038363594,0.041166224,0.038363594
+Salmonidae,cold,Whitefish,0.050076846,0.046667573,0.050076846,0.046667573
+Siluridae,warm,Boal,0.012433096,0.010511807,0.012433096,0.010511807
 Siluridae,warm,Catfish,0.033215868223999995,0.028083014042666665,0.033215868223999995,0.028083014042666665
-Siluridae,warm,European catfish,0.016684117296,0.014105917603999998,0.016684117296,0.014105917603999998
-Siluridae,warm,Far eastern catfish,0.0031082739072,0.0026279517728,0.0031082739072,0.0026279517728
-Siluridae,warm,Wels catfish,0.0333433019136,0.028190755380945457,0.0333433019136,0.028190755380945457
+Siluridae,warm,European catfish,0.016684117,0.014105917603999998,0.016684117,0.014105917603999998
+Siluridae,warm,Far eastern catfish,0.003108274,0.002627952,0.003108274,0.002627952
+Siluridae,warm,Wels catfish,0.033343302,0.028190755380945457,0.033343302,0.028190755380945457
diff --git a/src/components/FishAsFoodLinkChartViz.vue b/src/components/FishAsFoodLinkChartViz.vue
index 66a47fb..550bb66 100644
--- a/src/components/FishAsFoodLinkChartViz.vue
+++ b/src/components/FishAsFoodLinkChartViz.vue
@@ -178,6 +178,14 @@
 
         // Inititalize gradients
         initGradients()
+
+        // Add groups for visual elements
+        chartBounds.append("g")
+            .attr("class", "areas")
+        chartBounds.append("g")
+            .attr("class", "points_2030")
+        chartBounds.append("g")
+            .attr("class", "points_2075")
     }
 
     function initXScale() {
@@ -335,8 +343,7 @@
                 .nice()
         }
         
-        xAxis.transition()
-            .duration(500)
+        xAxis.transition(getUpdateTransition())
             .call(d3.axisBottom(xScale).tickSize(0).tickPadding(10));
 
         xAxis
@@ -442,11 +449,11 @@
         })
             
         // draw chart
-        let areaGroups = chartBounds.append("g")
-            .attr("id", "areas")
+        // Enter-update-exit pattern for areas
+        let areaGroups = chartBounds.selectAll(".areas")
             .selectAll(".area")
             .data(areaData, d => d[0].species)
-
+        
         const oldAreaGroups = areaGroups.exit()
 
         oldAreaGroups.selectAll('path')
@@ -462,19 +469,19 @@
         // append paths
         newAreaGroups.append("path")
             .append('path')
-            .attr("id", d => 'area-2030-' + identifierAccessor(d[0]))
-            .attr('d', d => area(d))
+            .attr("id", d => 'area-' + identifierAccessor(d[0]))
+            .attr('d', null)
             .attr('fill', d => d[0].cvi_decreasing ? `url(#${d[0].thermal_guild}_gradient_decreasing)`: `url(#${d[0].thermal_guild}_gradient_increasing)`)//d => colorScale(colorAccessor(d[0])))
             .style("opacity", 1)
 
-        // update rectGroups to include new points
+        // update areaGroups to include new paths
         areaGroups = newAreaGroups.merge(areaGroups)
 
         const areaPaths = areaGroups.select("path")
 
-        // Update bars based on data values
+        // Update paths based on data values
         areaPaths.transition(getUpdateTransition())
-            .attr("id", d => 'area-2030-' + identifierAccessor(d[0]))
+            .attr("id", d => 'area-' + identifierAccessor(d[0]))
             .attr('d', d => area(d))
             .attr('fill', d => d[0].cvi_decreasing ? `url(#${d[0].thermal_guild}_gradient_decreasing)`: `url(#${d[0].thermal_guild}_gradient_increasing)`)//d => colorScale(colorAccessor(d[0])))
             .style("opacity", 1)
@@ -485,7 +492,7 @@
         //         .data(areaData, d => d[0].species)
         //         .enter()
         //         .append('path')
-        //             .attr("id", d => 'area-2030-' + identifierAccessor(d[0]))
+        //             .attr("id", d => 'area-' + identifierAccessor(d[0]))
         //             .attr('class', "area")
         //             .attr('d', d => area(d))
         //             .attr('fill', d => d[0].cvi_decreasing ? `url(#${d[0].thermal_guild}_gradient_decreasing)`: `url(#${d[0].thermal_guild}_gradient_increasing)`)//d => colorScale(colorAccessor(d[0])))
@@ -506,37 +513,124 @@
                     //     drawChart(data, scalePercent.value)
                     // });
 
+        // Enter-Update-Exit pattern for 2030 points
+        let pointGroups2030 = chartBounds.selectAll('.points_2030')
+            .selectAll(".point_2030")
+            .data(data, d => d.species)
+        
+        const oldPointGroups2030 = pointGroups2030.exit()
+
+        oldPointGroups2030.selectAll('circle')
+            .transition(getExitTransition())
+            .style("opacity", 0)
 
-        chartBounds.append("g")
-            .attr("id", "points-2030")
-            .attr("class", "points points_2030")    
-            .selectAll('points')
-                .data(data)
-                .enter()
-                .append("circle")
-                    .attr("id", d => 'point-2030-' + identifierAccessor(d))
-                    .attr("class", "point")
-                    .attr("cx", d => xScale(x0Accessor(d)))
-                    .attr("cy", d => yScale(yAccessor(d)) + yScale.bandwidth() / 2)
-                    .attr("r", radiusPosition0)
-                    .style("stroke", d => colorScale(colorAccessor(d)))
-                    .style("fill", "white")
+        oldPointGroups2030.transition(getExitTransition()).remove()
+        
+        const newPointGroups2030 = pointGroups2030.enter().append("g")
+            .attr("class", d => "point_2030 " + d.species)
+            .attr("id", d => 'point-2030-group-' + identifierAccessor(d))
+
+        // append points
+        newPointGroups2030
+            .append("circle")
+                .attr("id", d => 'point-2030-' + identifierAccessor(d))
+                .attr("class", "point_2030")
+                .attr("cx", d => xScale(x0Accessor(d)))
+                .attr("cy", d => yScale(yAccessor(d)) + yScale.bandwidth() / 2)
+                .attr("r", radiusPosition0)
+                .style("stroke", d => colorScale(colorAccessor(d)))
+                .style("fill", "white")
+
+        // update pointGroups2030 to include new points
+        pointGroups2030 = newPointGroups2030.merge(pointGroups2030)
+
+        const allPoints2030 = pointGroups2030.select("circle")
+
+        // Update points based on data values
+        allPoints2030.transition(getUpdateTransition())
+            .attr("id", d => 'point-2030-' + identifierAccessor(d))
+            .attr("class", "point_2030")
+            .attr("cx", d => xScale(x0Accessor(d)))
+            .attr("cy", d => yScale(yAccessor(d)) + yScale.bandwidth() / 2)
+            .attr("r", radiusPosition0)
+            .style("stroke", d => colorScale(colorAccessor(d)))
+            .style("fill", "white")
 
-        chartBounds.append("g")
-            .attr("id", "points-2075")
-            .attr("class", "points points_2075")    
-            .selectAll('points')
-                .data(data)
-                .enter()
-                .append("circle")
-                    .attr("id", d => 'point-2075-' + identifierAccessor(d))
-                    .attr("class", "point")
-                    .attr("cx", d => xScale(x1Accessor(d)))
-                    .attr("cy", d => yScale(yAccessor(d)) + yScale.bandwidth() / 2)
-                    .attr("r", radiusPosition1 - strokeWidth1 / 2)
-                    .style("stroke", d => colorScale(colorAccessor(d)))
-                    .style("stroke-width", strokeWidth1)
-                    .style("fill", "white")
+        // chartBounds.append("g")
+        //     .attr("id", "points-2030")
+        //     .attr("class", "points points_2030")    
+        //     .selectAll('points')
+        //         .data(data)
+        //         .enter()
+        //         .append("circle")
+        //             .attr("id", d => 'point-2030-' + identifierAccessor(d))
+        //             .attr("class", "point")
+        //             .attr("cx", d => xScale(x0Accessor(d)))
+        //             .attr("cy", d => yScale(yAccessor(d)) + yScale.bandwidth() / 2)
+        //             .attr("r", radiusPosition0)
+        //             .style("stroke", d => colorScale(colorAccessor(d)))
+        //             .style("fill", "white")
+
+        // Enter-Update-Exit pattern for 2075 points
+        let pointGroups2075 = chartBounds.selectAll('.points_2075')
+            .selectAll(".point_2075")
+            .data(data, d => d.species)
+        
+        const oldPointGroups2075 = pointGroups2075.exit()
+
+        oldPointGroups2075.selectAll('circle')
+            .transition(getExitTransition())
+            .style("opacity", 0)
+
+        oldPointGroups2075.transition(getExitTransition()).remove()
+        
+        const newPointGroups2075 = pointGroups2075.enter().append("g")
+            .attr("class", d => "point_2075 " + d.species)
+            .attr("id", d => 'point-2030-group-' + identifierAccessor(d))
+
+        // append points
+        newPointGroups2075
+            .append("circle")
+                .attr("id", d => 'point-2075-' + identifierAccessor(d))
+                .attr("class", "point_2075")
+                .attr("cx", d => xScale(x1Accessor(d)))
+                .attr("cy", d => yScale(yAccessor(d)) + yScale.bandwidth() / 2)
+                .attr("r", radiusPosition1 - strokeWidth1 / 2)
+                .style("stroke", d => colorScale(colorAccessor(d)))
+                .style("stroke-width", strokeWidth1)
+                .style("fill", "white")
+
+        // update pointGroups2075 to include new points
+        pointGroups2075 = newPointGroups2075.merge(pointGroups2075)
+
+        const allPoints2075 = pointGroups2075.select("circle")
+
+        // Update points based on data values
+        allPoints2075.transition(getUpdateTransition())
+            .attr("id", d => 'point-2075-' + identifierAccessor(d))
+            .attr("class", "point_2075")
+            .attr("cx", d => xScale(x1Accessor(d)))
+            .attr("cy", d => yScale(yAccessor(d)) + yScale.bandwidth() / 2)
+            .attr("r", radiusPosition1 - strokeWidth1 / 2)
+            .style("stroke", d => colorScale(colorAccessor(d)))
+            .style("stroke-width", strokeWidth1)
+            .style("fill", "white")
+
+        // chartBounds.append("g")
+        //     .attr("id", "points-2075")
+        //     .attr("class", "points points_2075")    
+        //     .selectAll('points')
+        //         .data(data)
+        //         .enter()
+        //         .append("circle")
+        //             .attr("id", d => 'point-2075-' + identifierAccessor(d))
+        //             .attr("class", "point")
+        //             .attr("cx", d => xScale(x1Accessor(d)))
+        //             .attr("cy", d => yScale(yAccessor(d)) + yScale.bandwidth() / 2)
+        //             .attr("r", radiusPosition1 - strokeWidth1 / 2)
+        //             .style("stroke", d => colorScale(colorAccessor(d)))
+        //             .style("stroke-width", strokeWidth1)
+        //             .style("fill", "white")
 
         // chartBounds.append("g")
         //     .attr("id", "eyes-2075")
@@ -559,12 +653,12 @@
 
     function getUpdateTransition () {
       return d3.transition()
-        .duration(500)
+        .duration(1500)
         .ease(d3.easeCubicInOut)
     }
     function getExitTransition() {
       return d3.transition()
-        .duration(500)
+        .duration(1500)
         .ease(d3.easeCubicInOut)
     }
 </script>
-- 
GitLab


From cdef8303e1e090c50f131f307009253bbccd6e16 Mon Sep 17 00:00:00 2001
From: Corson-Dosch <hcorson-dosch@usgs.gov>
Date: Tue, 27 Aug 2024 17:37:18 -0500
Subject: [PATCH 08/25] add more less vulnerable labels

---
 src/components/FishAsFoodLinkChartViz.vue | 38 ++++++++++++++---------
 1 file changed, 23 insertions(+), 15 deletions(-)

diff --git a/src/components/FishAsFoodLinkChartViz.vue b/src/components/FishAsFoodLinkChartViz.vue
index 550bb66..b9e9c92 100644
--- a/src/components/FishAsFoodLinkChartViz.vue
+++ b/src/components/FishAsFoodLinkChartViz.vue
@@ -85,7 +85,7 @@
                     width: chart.value.offsetWidth,
                     height: 1800,
                     margin: 20,
-                    marginBottom: 60,
+                    marginBottom: 80,
                     marginLeft: 200});
 
                 drawChart(data.value, scalePercent.value);
@@ -359,20 +359,20 @@
             .attr("dy", xAxisLabelDy)
             .style("text-anchor", "middle")
             .text(scalePercent ? 'Percent change in harvest-weighted climate vulnerability, 2030-2075' : 'Change in harvest-weighted climate vulnerability, 2030-2075')
-        // xAxis.append("text")
-        //     .attr("class", "x-axis axis-title")
-        //     .attr("x", 0)
-        //     .attr("y", xAxisLabelYPosition * 4)
-        //     .attr("dy", xAxisLabelDy)
-        //     .style("text-anchor", "start")
-        //     .text('Less vulnerable')
-        // xAxis.append("text")
-        //     .attr("class", "x-axis axis-title")
-        //     .attr("x", chartDimensions.boundedWidth)
-        //     .attr("y", xAxisLabelYPosition * 4)
-        //     .attr("dy", xAxisLabelDy)
-        //     .style("text-anchor", "end")
-        //     .text('More vulnerable')
+        xAxis.append("text")
+            .attr("class", "x-axis axis-subtitle")
+            .attr("x", 0)
+            .attr("y", xAxisLabelYPosition * 5.5)
+            .attr("dy", xAxisLabelDy)
+            .style("text-anchor", "start")
+            .text('Less vulnerable')
+        xAxis.append("text")
+            .attr("class", "x-axis axis-subtitle")
+            .attr("x", chartDimensions.boundedWidth)
+            .attr("y", xAxisLabelYPosition * 5.5)
+            .attr("dy", xAxisLabelDy)
+            .style("text-anchor", "end")
+            .text('More vulnerable')
 
         // Remove axix line and labels
         // xAxis.select(".domain").remove()
@@ -676,4 +676,12 @@
         fill: var(--color-text);
         user-select: none;
     }
+    .axis-subtitle {
+        font-size: 1.8rem;
+        font-family: var(--default-font);
+        font-weight: 400;
+        font-style: italic;
+        fill: var(--color-text);
+        user-select: none;
+    }
 </style>
\ No newline at end of file
-- 
GitLab


From 4db3ae1cc3069055181d20d68692e1d2ea4d634c Mon Sep 17 00:00:00 2001
From: Corson-Dosch <hcorson-dosch@usgs.gov>
Date: Wed, 28 Aug 2024 17:25:58 -0500
Subject: [PATCH 09/25] get family expansion working

---
 public/fish_as_food_climate.csv           | 224 ++++++++++++--------
 src/components/FishAsFoodLinkChartViz.vue | 243 +++++++++++++++-------
 2 files changed, 312 insertions(+), 155 deletions(-)

diff --git a/public/fish_as_food_climate.csv b/public/fish_as_food_climate.csv
index a64a562..ab5c337 100644
--- a/public/fish_as_food_climate.csv
+++ b/public/fish_as_food_climate.csv
@@ -1,84 +1,142 @@
 family,thermal_guild,species,cvi_2030,cvi_2075,cvi_2030_family,cvi_2075_family
-Cyprinidae,warm,Albanian roach,0.10778174260000001,0.094120842,0.10778174260000001,0.094120842
-Cyprinidae,warm,Asp,0.013429605127960001,0.011727456938119999,0.013429605127960001,0.011727456938119999
-Cyprinidae,warm,Barbel,0.047058011,0.039834022,0.047058011,0.039834022
-Cyprinidae,warm,Bata,0.00551214,0.007563008,0.00551214,0.007563008
-Cyprinidae,warm,BeyÅŸehir bleak,7.267071073e-4,6.011377922000001e-4,7.267071073e-4,6.011377922000001e-4
-Cyprinidae,warm,Black bream,0.051246241799999995,0.061345053,0.051246241799999995,0.061345053
-Cyprinidae,warm,Black carp,0.051246241799999995,0.061345053,0.051246241799999995,0.061345053
-Cyprinidae,warm,Blue-finned mahseer,0.022459868,0.027820540914799997,0.022459868,0.027820540914799997
-Cyprinidae,warm,Catla,0.022074318,0.030287375,0.022074318,0.030287375
-Cyprinidae,warm,Chocolate mahseer,0.002830632,0.003430213,0.002830632,0.003430213
-Cyprinidae,warm,Chub,0.032161674,0.026581131487899996,0.032161674,0.026581131487899996
-Cyprinidae,warm,Common barbel,0.002834382,0.002399269,0.002834382,0.002399269
-Cyprinidae,warm,Common bleak,0.008549495,0.007072209,0.008549495,0.007072209
-Cyprinidae,warm,Common bream,0.046006207,0.036428658,0.046006207,0.036428658
-Cyprinidae,warm,Common carp,0.13811330662322036,0.1199294950838376,0.13811330662322036,0.1199294950838376
-Cyprinidae,warm,Common nase,0.026387573782000004,0.023642661213400005,0.026387573782000004,0.023642661213400005
-Cyprinidae,warm,Common roach,0.053126247,0.042622199,0.053126247,0.042622199
-Cyprinidae,warm,Crucian carp,0.116377215,0.101055148,0.116377215,0.101055148
-Cyprinidae,warm,Cyprinds,0.032788926,0.028471980271733335,0.032788926,0.028471980271733335
-Cyprinidae,warm,European carp,0.130406398,0.11323726735275001,0.130406398,0.11323726735275001
-Cyprinidae,warm,European chub,0.001917238,0.001584568,0.001917238,0.001584568
-Cyprinidae,warm,Ghonia,0.001828794,0.002509222,0.001828794,0.002509222
-Cyprinidae,warm,Golden mahseer,0.003435564,0.003913307,0.003435564,0.003913307
-Cyprinidae,warm,Goldfish,0.189583548,0.164623234,0.189583548,0.164623234
-Cyprinidae,warm,Grass carp,0.028623161305374997,0.034263768,0.028623161305374997,0.034263768
-Cyprinidae,warm,Gudgeon,0.034406967,0.030376054976399995,0.034406967,0.030376054976399995
-Cyprinidae,warm,Italian bleak,0.10233745969859999,0.084654346,0.10233745969859999,0.084654346
-Cyprinidae,warm,Kalibaus,0.003683346,0.005053786,0.003683346,0.005053786
-Cyprinidae,warm,Kutum,2.15702238e-4,1.9326426059999998e-4,2.15702238e-4,1.9326426059999998e-4
-Cyprinidae,warm,Labeo,0.017643999771499996,0.024208694942499998,0.017643999771499996,0.024208694942499998
-Cyprinidae,warm,Mrigal,0.019859158866899998,0.027248035,0.019859158866899998,0.027248035
-Cyprinidae,warm,Mullet,0.022048241277999998,0.018222534,0.022048241277999998,0.018222534
-Cyprinidae,warm,Orange River mudfish,0.018030365,0.024738812350000004,0.018030365,0.024738812350000004
-Cyprinidae,warm,Pale chub,0.002323163,0.002780976,0.002323163,0.002780976
-Cyprinidae,warm,Pearl mullet,0.177701261,0.14699587071620002,0.177701261,0.14699587071620002
-Cyprinidae,warm,Prussian carp,0.064146948,0.055701447,0.064146948,0.055701447
-Cyprinidae,warm,Prussian/Crucian carp,0.021666691,0.018814083880000002,0.021666691,0.018814083880000002
-Cyprinidae,warm,Razor fish (ziege),3.7723609910000005e-4,3.294229477e-4,3.7723609910000005e-4,3.294229477e-4
-Cyprinidae,warm,Red garra,0.017643999771499996,0.024208694942499998,0.017643999771499996,0.024208694942499998
-Cyprinidae,warm,Rohu,0.001751521,0.002403199,0.001751521,0.002403199
-Cyprinidae,warm,Rudd,0.010652671243479999,0.008811973,0.010652671243479999,0.008811973
-Cyprinidae,warm,Rui,0.036807702,0.050502518,0.036807702,0.050502518
-Cyprinidae,warm,Shabout,0.001199162,0.001015075,0.001199162,0.001015075
-Cyprinidae,warm,Silver bream,0.010815032339999999,0.008563564,0.010815032339999999,0.008563564
-Cyprinidae,warm,Silver carp,0.029425103982114283,0.035223745194000004,0.029425103982114283,0.035223745194000004
-Cyprinidae,warm,Tench,0.017184651140000003,0.015171401,0.017184651140000003,0.015171401
-Cyprinidae,warm,Transcau casian barb,0.014553462,0.012319325,0.014553462,0.012319325
-Cyprinidae,warm,Trout barb,0.004578617,0.003875743,0.004578617,0.003875743
-Cyprinidae,warm,Vimba bream,0.021286894,0.018588866,0.021286894,0.018588866
-Cyprinidae,warm,White Bream,0.043260129359999996,0.034254257760000004,0.043260129359999996,0.034254257760000004
-Percidae,cool,Eurasian Ruffe,0.004867533,0.00431991,0.004867533,0.00431991
-Percidae,cool,European perch,0.072594639,0.063470599,0.072594639,0.063470599
-Percidae,cool,Golden perch,0.033427689,0.034436023,0.033427689,0.034436023
-Percidae,cool,Redfin perch,0.026313675,0.023006446,0.026313675,0.023006446
-Percidae,cool,Volga pikeperch,8.01681972e-4,6.487112373e-4,8.01681972e-4,6.487112373e-4
-Percidae,cool,Walleye,0.10268783482319999,0.09835619,0.10268783482319999,0.09835619
-Percidae,cool,Yellow perch,0.048807665,0.045605899,0.048807665,0.045605899
-Percidae,cool,Zander (pikeperch),0.037962706268244786,0.032533836325300367,0.037962706268244786,0.032533836325300367
-Salmonidae,cold,Arctic Char,0.035655772,0.035000711,0.035655772,0.035000711
-Salmonidae,cold,Atlantic salmon,0.265177842,0.24011746662753772,0.265177842,0.24011746662753772
-Salmonidae,cold,Balkan trout,0.026710147320000002,0.023695444160000002,0.026710147320000002,0.023695444160000002
-Salmonidae,cold,Brook Trout,0.026461932224000003,0.024708687848000004,0.026461932224000003,0.024708687848000004
-Salmonidae,cold,Brown Trout,0.050732109690129576,0.045006112,0.050732109690129576,0.045006112
-Salmonidae,cold,Cherry trout,0.00293711,0.002762575,0.00293711,0.002762575
-Salmonidae,cold,Chinook Salmon (Quinnat Salmon),0.160075296,0.15222991949999998,0.160075296,0.15222991949999998
-Salmonidae,cold,Chum salmon,0.028850453,0.028417105,0.028850453,0.028417105
-Salmonidae,cold,Coho salmon,0.215094746,0.200664817,0.215094746,0.200664817
-Salmonidae,cold,Common huchen,5.120283490000001e-5,4.81393951e-5,5.120283490000001e-5,4.81393951e-5
-Salmonidae,cold,Dolly varden/Char,0.00347886,0.003358148,0.00347886,0.003358148
-Salmonidae,cold,European grayling,0.024065881522700002,0.021200949,0.024065881522700002,0.021200949
-Salmonidae,cold,Pink salmon,0.025550719900000005,0.024439884,0.025550719900000005,0.024439884
-Salmonidae,cold,Rainbow trout,0.06302419,0.059670455,0.06302419,0.059670455
-Salmonidae,cold,Salmon,0.064490204,0.062553346,0.064490204,0.062553346
-Salmonidae,cold,Sockeye salmon,0.029446763100000002,0.029350874649999997,0.029446763100000002,0.029350874649999997
-Salmonidae,cold,Steelhead,0.004928901,0.004666617,0.004928901,0.004666617
-Salmonidae,cold,Trout,0.08583577,0.08267911,0.08583577,0.08267911
-Salmonidae,cold,Vendace,0.041166224,0.038363594,0.041166224,0.038363594
-Salmonidae,cold,Whitefish,0.050076846,0.046667573,0.050076846,0.046667573
-Siluridae,warm,Boal,0.012433096,0.010511807,0.012433096,0.010511807
-Siluridae,warm,Catfish,0.033215868223999995,0.028083014042666665,0.033215868223999995,0.028083014042666665
-Siluridae,warm,European catfish,0.016684117,0.014105917603999998,0.016684117,0.014105917603999998
-Siluridae,warm,Far eastern catfish,0.003108274,0.002627952,0.003108274,0.002627952
-Siluridae,warm,Wels catfish,0.033343302,0.028190755380945457,0.033343302,0.028190755380945457
+Alestidae,warm,Big-scale tetra,0.012274708350000002,0.0108627154,0.11286009582760641,0.10272450171172955
+Alestidae,warm,Nurse tetra,0.012274708350000002,0.0108627154,0.11286009582760641,0.10272450171172955
+Alestidae,warm,Tigerfish,0.17991702081267735,0.1639656925862159,0.11286009582760641,0.10272450171172955
+Anguillidae,warm,Australian eel,0.0019279156693999997,0.0018898418147999998,0.060088577503724185,0.05356143600511973
+Anguillidae,warm,European eel,0.07434074572260523,0.06609150511026632,0.060088577503724185,0.05356143600511973
+Anguillidae,warm,Japanese eel,0.0036558991076,0.0042168184693999996,0.060088577503724185,0.05356143600511973
+Centrarchidae,warm,Bass,0.0657804855974,0.062753579817,0.06571650285962258,0.06199132366577771
+Centrarchidae,warm,Black bass,0.06640203166666667,0.0643960009,0.06571650285962258,0.06199132366577771
+Centrarchidae,warm,Crappie,0.0699718436,0.06477207332000001,0.06571650285962258,0.06199132366577771
+Centrarchidae,warm,Largemouth bass,0.07146540516942362,0.06686251627901083,0.06571650285962258,0.06199132366577771
+Centrarchidae,warm,Sunfish,0.0673828433562,0.062317355349150005,0.06571650285962258,0.06199132366577771
+Centrarchidae,warm,"White bass, striped bass",0.0386972223,0.0352063888,0.06571650285962258,0.06199132366577771
+Characidae,warm,Dorado,0.012366997772518243,0.011679929906890194,0.03036102208400994,0.028133410406297642
+Characidae,warm,Goldenfish,0.09620422520000001,0.08895133840000001,0.03036102208400994,0.028133410406297642
+Characidae,warm,Macabil,0.0801381195916,0.0740964648872,0.03036102208400994,0.028133410406297642
+Characidae,warm,Sabaleta,0.012333875025147671,0.011404017743133585,0.03036102208400994,0.028133410406297642
+Characidae,warm,Sabalo del Patía,0.012333875025147671,0.011404017743133585,0.03036102208400994,0.028133410406297642
+Characidae,warm,South American trout,0.004810211260000001,0.00444756692,0.03036102208400994,0.028133410406297642
+Characidae,warm,"Yamú, Bocón, Sábalo",0.012333875025147671,0.011404017743133585,0.03036102208400994,0.028133410406297642
+Cichlidae,warm,Bay snook (Tenguayaca),0.052899941706100004,0.0543490282988,0.020603395776181064,0.020246516151158973
+Cichlidae,warm,"Mojarra Plateada, Tilapia",0.01490390997376282,0.01381896607637032,0.020603395776181064,0.020246516151158973
+Cichlidae,warm,Mozambique tilapia,0.00698411364,0.00638055316,0.020603395776181064,0.020246516151158973
+Cichlidae,warm,Oscar,0.008136825204802732,0.008359717025306638,0.020603395776181064,0.020246516151158973
+Cichlidae,warm,Pavón,0.008136825204802732,0.008359717025306638,0.020603395776181064,0.020246516151158973
+Cichlidae,warm,Pavón Cinchado - Pinta de Lapa,0.007861983512506034,0.008050564307370287,0.020603395776181064,0.020246516151158973
+Cichlidae,warm,Pavón Mariposo,0.008136825204802732,0.008359717025306638,0.020603395776181064,0.020246516151158973
+Cichlidae,warm,Peacock Bass,0.0317336183,0.0326028964,0.020603395776181064,0.020246516151158973
+Cichlidae,warm,"Speckled pavon, speckled peacock bass (percid)",0.030661735699999998,0.031397200800000004,0.020603395776181064,0.020246516151158973
+Cichlidae,warm,Tilapia,0.0344154895108,0.031549818668399995,0.020603395776181064,0.020246516151158973
+Cichlidae,warm,Tilapia Negra,0.008953991845795688,0.00818019635864715,0.020603395776181064,0.020246516151158973
+Clariidae,warm,African catfish,0.012784239967400001,0.0122263953392,0.027590318372466668,0.02638640551306667
+Clariidae,warm,African sharptooth catfish,0.0466578101,0.044621880800000006,0.027590318372466668,0.02638640551306667
+Clariidae,warm,North African catfish,0.02332890505,0.022310940400000003,0.027590318372466668,0.02638640551306667
+Cyprinidae,warm,Albanian roach,0.10778174260000001,0.0941208422,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Asp,0.013429605127960001,0.011727456938119999,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Barbel,0.04705801130333334,0.039834021549,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Bata,0.0055121400746,0.007563008347000001,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,BeyÅŸehir bleak,7.267071073e-4,6.011377922000001e-4,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Black bream,0.051246241799999995,0.061345053,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Black carp,0.051246241799999995,0.061345053,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Blue-finned mahseer,0.0224598681704,0.027820540914799997,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Catla,0.0220743179623,0.0302873745485,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Chocolate mahseer,0.0028306322192,0.0034302133044,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Chub,0.03216167369029999,0.026581131487899996,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Common barbel,0.002834382148,0.0023992692516,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Common bleak,0.00854949538,0.007072209320000001,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Common bream,0.04600620713676522,0.03642865846998261,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Common carp,0.13811330662322036,0.1199294950838376,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Common nase,0.026387573782000004,0.023642661213400005,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Common roach,0.05312624687117,0.042622199202855,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Crucian carp,0.116377215108,0.10105514804045,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Cyprinds,0.032788926016,0.028471980271733335,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,European carp,0.13040639766,0.11323726735275001,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,European chub,0.001917238372,0.001584568196,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Ghonia,0.0018287941369,0.0025092223955,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Golden mahseer,0.0034355641156000004,0.0039133067584,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Goldfish,0.189583548,0.16462323395,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Grass carp,0.028623161305374997,0.034263768144375,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Gudgeon,0.0344069672688,0.030376054976399995,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Italian bleak,0.10233745969859999,0.0846543455604,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Kalibaus,0.0036833459377,0.0050537859515,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Kutum,2.15702238e-4,1.9326426059999998e-4,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Labeo,0.017643999771499996,0.024208694942499998,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Mrigal,0.019859158866899998,0.0272480347455,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Mullet,0.022048241277999998,0.018222534254,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Orange River mudfish,0.01803036473,0.024738812350000004,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Pale chub,0.0023231629616,0.0027809757360000003,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Pearl mullet,0.1777012614733,0.14699587071620002,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Prussian carp,0.064146947634,0.055701447087225,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Prussian/Crucian carp,0.0216666912,0.018814083880000002,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Razor fish (ziege),3.7723609910000005e-4,3.294229477e-4,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Red garra,0.017643999771499996,0.024208694942499998,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Rohu,0.0017515211452,0.0024031989140000003,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Rudd,0.010652671243479999,0.00881197281272,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Rui,0.0368077017131,0.0505025183545,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Shabout,0.0011991616780000001,0.0010150754526,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Silver bream,0.010815032339999999,0.008563564440000001,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Silver carp,0.029425103982114283,0.035223745194000004,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Tench,0.017184651140000003,0.015171401295,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Transcau casian barb,0.014553462183,0.0123193248111,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Trout barb,0.004578617316,0.0038757426371999995,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,Vimba bream,0.0212868941635,0.0185888663345,0.05987770694593892,0.052805491729310206
+Cyprinidae,warm,White Bream,0.043260129359999996,0.034254257760000004,0.05987770694593892,0.052805491729310206
+Percidae,cool,Eurasian Ruffe,0.00486753292,0.0043199098100000005,0.053766659159614545,0.04716731515478815
+Percidae,cool,European perch,0.07259463927767063,0.06347059881833862,0.053766659159614545,0.04716731515478815
+Percidae,cool,Golden perch,0.0334276893426,0.0344360226346,0.053766659159614545,0.04716731515478815
+Percidae,cool,Redfin perch,0.0263136745872,0.0230064464784,0.053766659159614545,0.04716731515478815
+Percidae,cool,Volga pikeperch,8.01681972e-4,6.487112373e-4,0.053766659159614545,0.04716731515478815
+Percidae,cool,Walleye,0.10268783482319999,0.0983561899176,0.053766659159614545,0.04716731515478815
+Percidae,cool,Yellow perch,0.0488076651,0.0456058989,0.053766659159614545,0.04716731515478815
+Percidae,cool,Zander (pikeperch),0.037962706268244786,0.032533836325300367,0.053766659159614545,0.04716731515478815
+Pimelodidae,warm,"Amarillo, Toruno, Chontaduro",0.007525378768929754,0.007699380717640743,0.010981231987585237,0.011153188357481665
+Pimelodidae,warm,"Bagre, Bagre Rayado",0.007525378768929754,0.007699380717640743,0.010981231987585237,0.011153188357481665
+Pimelodidae,warm,Barbiancho,0.008066403358651704,0.00795324982019469,0.010981231987585237,0.011153188357481665
+Pimelodidae,warm,Barred sorubim,0.02347918176,0.02402206784,0.010981231987585237,0.011153188357481665
+Pimelodidae,warm,Blancopobre,0.007525378768929754,0.007699380717640743,0.010981231987585237,0.011153188357481665
+Pimelodidae,warm,Blanquillo,0.007525378768929754,0.007699380717640743,0.010981231987585237,0.011153188357481665
+Pimelodidae,warm,Cajaro,0.008066403358651704,0.00795324982019469,0.010981231987585237,0.011153188357481665
+Pimelodidae,warm,Cucharo,0.007525378768929754,0.007699380717640743,0.010981231987585237,0.011153188357481665
+Pimelodidae,warm,Flatwhiskered catfish,0.00629179462,0.006203534859999999,0.010981231987585237,0.011153188357481665
+Pimelodidae,warm,Gilded catfish,0.007525378768929754,0.007699380717640743,0.010981231987585237,0.011153188357481665
+Pimelodidae,warm,Kumakuma (catfish),0.0293489772,0.030027584800000003,0.010981231987585237,0.011153188357481665
+Pimelodidae,warm,Pati,0.00629179462,0.006203534859999999,0.010981231987585237,0.011153188357481665
+Pimelodidae,warm,"Pintadillo, Bagre, Bagre Rayado",0.007525378768929754,0.007699380717640743,0.010981231987585237,0.011153188357481665
+Pimelodidae,warm,Redtail catfish,0.00629179462,0.006203534859999999,0.010981231987585237,0.011153188357481665
+Pimelodidae,warm,South American catfish,0.0293489772,0.030027584800000003,0.010981231987585237,0.011153188357481665
+Pimelodidae,warm,Spotted sorubim,0.0293489772,0.030027584800000003,0.010981231987585237,0.011153188357481665
+Pimelodidae,warm,"Tigrito, tijero",0.007525378768929754,0.007699380717640743,0.010981231987585237,0.011153188357481665
+Pimelodidae,warm,"Valentón, Plumita",0.007525378768929754,0.007699380717640743,0.010981231987585237,0.011153188357481665
+Pimelodidae,warm,White catfish,0.00629179462,0.006203534859999999,0.010981231987585237,0.011153188357481665
+Pimelodidae,warm,Yellow catfish,0.009437691929999999,0.00930530229,0.010981231987585237,0.011153188357481665
+Salmonidae,cold,Arctic Char,0.0356557719054,0.03500071147903334,0.06944668713248901,0.06438766096040704
+Salmonidae,cold,Atlantic salmon,0.265177841604771,0.24011746662753772,0.06944668713248901,0.06438766096040704
+Salmonidae,cold,Balkan trout,0.026710147320000002,0.023695444160000002,0.06944668713248901,0.06438766096040704
+Salmonidae,cold,Brook Trout,0.026461932224000003,0.024708687848000004,0.06944668713248901,0.06438766096040704
+Salmonidae,cold,Brown Trout,0.050732109690129576,0.04500611164287132,0.06944668713248901,0.06438766096040704
+Salmonidae,cold,Cherry trout,0.0029371095160000004,0.0027625745076,0.06944668713248901,0.06438766096040704
+Salmonidae,cold,Chinook Salmon (Quinnat Salmon),0.1600752957,0.15222991949999998,0.06944668713248901,0.06438766096040704
+Salmonidae,cold,Chum salmon,0.02885045315,0.0284171047,0.06944668713248901,0.06438766096040704
+Salmonidae,cold,Coho salmon,0.2150947458,0.20066481688,0.06944668713248901,0.06438766096040704
+Salmonidae,cold,Common huchen,5.120283490000001e-5,4.81393951e-5,0.06944668713248901,0.06438766096040704
+Salmonidae,cold,Dolly varden/Char,0.0034788601644,0.0033581483528,0.06944668713248901,0.06438766096040704
+Salmonidae,cold,European grayling,0.024065881522700002,0.0212009485031,0.06944668713248901,0.06438766096040704
+Salmonidae,cold,Pink salmon,0.025550719900000005,0.02443988385,0.06944668713248901,0.06438766096040704
+Salmonidae,cold,Rainbow trout,0.06302419030074105,0.05967045472608766,0.06944668713248901,0.06438766096040704
+Salmonidae,cold,Salmon,0.06449020412644443,0.06255334611855555,0.06944668713248901,0.06438766096040704
+Salmonidae,cold,Sockeye salmon,0.029446763100000002,0.029350874649999997,0.06944668713248901,0.06438766096040704
+Salmonidae,cold,Steelhead,0.0049289009400000005,0.00466661704,0.06944668713248901,0.06438766096040704
+Salmonidae,cold,Trout,0.0858357697255,0.08267910955158334,0.06944668713248901,0.06438766096040704
+Salmonidae,cold,Vendace,0.04116622401,0.0383635935,0.06944668713248901,0.06438766096040704
+Salmonidae,cold,Whitefish,0.05007684609903145,0.04666757308221,0.06944668713248901,0.06438766096040704
+Serrasalmidae,warm,"Cachama, Morocoto",0.008435958589406153,0.008389041102228541,0.023958122394703078,0.02382487673111427
+Serrasalmidae,warm,"Cachama, Pacú",0.008435958589406153,0.008389041102228541,0.023958122394703078,0.02382487673111427
+Serrasalmidae,warm,"Cachara, Tambaqui",0.055930405450000005,0.05561934251,0.023958122394703078,0.02382487673111427
+Serrasalmidae,warm,"Cherna, Cachama Negra",0.008435958589406153,0.008389041102228541,0.023958122394703078,0.02382487673111427
+Serrasalmidae,warm,Pacu,0.031255226575,0.031081397285000006,0.023958122394703078,0.02382487673111427
+Siluridae,warm,Boal,0.0124330956288,0.0105118070912,0.0286296405472,0.024205496966133333
+Siluridae,warm,Catfish,0.033215868223999995,0.028083014042666665,0.0286296405472,0.024205496966133333
+Siluridae,warm,European catfish,0.016684117296,0.014105917603999998,0.0286296405472,0.024205496966133333
+Siluridae,warm,Far eastern catfish,0.0031082739072,0.0026279517728,0.0286296405472,0.024205496966133333
+Siluridae,warm,Wels catfish,0.0333433019136,0.028190755380945457,0.0286296405472,0.024205496966133333
diff --git a/src/components/FishAsFoodLinkChartViz.vue b/src/components/FishAsFoodLinkChartViz.vue
index b9e9c92..2846298 100644
--- a/src/components/FishAsFoodLinkChartViz.vue
+++ b/src/components/FishAsFoodLinkChartViz.vue
@@ -11,7 +11,6 @@
         <template #aboveExplanation>
         </template>
         <template #figures>
-            <div id="chart-container" class="maxWidth" ref="chart"></div>
             <button
                   aria-pressed="!scalePercent"
                   class="button"
@@ -20,6 +19,7 @@
                 >
                   {{ scaleType }}
             </button>
+            <div id="chart-container" class="maxWidth" ref="chart"></div>
         </template>
         <!-- FIGURE CAPTION -->
         <template #figureCaption>
@@ -31,7 +31,7 @@
 </template>
 
 <script setup>
-    import { computed, onMounted, ref } from "vue"; //, reactive
+    import { computed, onMounted, reactive, ref } from "vue"; //, reactive
     import * as d3 from 'd3';
     import VizSection from '@/components/VizSection.vue';
 
@@ -44,6 +44,7 @@
     const publicPath = import.meta.env.BASE_URL;
     const dataFile = 'fish_as_food_climate.csv' //'fish_as_food_climate_test.csv'
     const data = ref();
+    const families = ref();
     const chart = ref(null);
     const scalePercent = ref(false);
     let chartDimensions;
@@ -51,13 +52,13 @@
     let chartSVG;
     let chartBounds;
     let xScale;
-    let xAxis;
+    let xAxisBottom;
     let yScale;
     let yAxis;
     let widthScale;
     let colorScale;
     // Create a reactive object to track expanded families
-    // const expandedFamilies = reactive({});
+    const expandedFamilies = reactive({});
 
     // set up filtered chart data as computed property
     const scaleType = computed(() => {
@@ -71,15 +72,13 @@
         try {
             await loadDatasets();
             if (data.value.length > 0) {
-                // families.value = Array.from(new Set(data.value.map(d => d.family)));
-                // // Initialize the expandedFamilies object
-                // families.value.forEach(family => {
-                //     expandedFamilies[family] = false;
-                // });
-                // expandedFamilies["A"] = !expandedFamilies["A"]
-                // console.log(expandedFamilies)
-                // Set starting scale type
-                // scalePercent.value = true;
+                families.value = Array.from(new Set(data.value.map(d => d.family)));
+                // Initialize the expandedFamilies object
+                families.value.forEach(family => {
+                    expandedFamilies[family] = false;
+                });
+                // expandedFamilies["Cyprinidae"] = !expandedFamilies["Cyprinidae"]
+                expandedFamilies["Salmonidae"] = !expandedFamilies["Salmonidae"]
 
                 initChart({
                     width: chart.value.offsetWidth,
@@ -129,7 +128,7 @@
 
     function initChart({
         width = 500, // outer width, in pixels
-        height = width, // outer height, in pixels
+        // height = width, // outer height, in pixels
         margin = 1, // default margins
         marginTop = margin, // top margin, in pixels
         marginBottom = margin, // left margin, in pixels
@@ -139,7 +138,7 @@
         // set up global chart dimensions
         chartDimensions = {
             width,
-            height,
+            //height,
             margin: {
                 top: marginTop,
                 right: marginRight,
@@ -148,12 +147,12 @@
             }
         }
         chartDimensions.boundedWidth = chartDimensions.width - chartDimensions.margin.left - chartDimensions.margin.right
-        chartDimensions.boundedHeight = chartDimensions.height - chartDimensions.margin.top - chartDimensions.margin.bottom
+        //chartDimensions.boundedHeight = chartDimensions.height - chartDimensions.margin.top - chartDimensions.margin.bottom
 
         // draw canvas for chart
         chartSVG = d3.select("#chart-container")
             .append("svg")
-                .attr("viewBox", [0, 0, (chartDimensions.width), (chartDimensions.height)].join(' '))
+                //.attr("viewBox", [0, 0, (chartDimensions.width), (chartDimensions.height)].join(' '))
                 .attr("width", "100%")
                 .attr("height", "100%")
                 .attr("id", "chart-svg")
@@ -172,6 +171,10 @@
                 chartDimensions.margin.top
             }px)`)
 
+        // Initialize axes
+        initXAxis()
+        initYAxis()
+
         // Initialize scales
         initXScale()
         initYScale()
@@ -186,27 +189,30 @@
             .attr("class", "points_2030")
         chartBounds.append("g")
             .attr("class", "points_2075")
+        chartBounds.append("g")
+            .attr("class", "rects")
     }
 
     function initXScale() {
         // scale for the x axis (domain set in `drawChart()`)
         xScale = d3.scaleLinear()
             .range([0, chartDimensions.boundedWidth]);
-
+    }
+    function initXAxis() {
         // add group for x axis
-        xAxis = chartBounds.append("g")
+        xAxisBottom = chartBounds.append("g")
             .attr("id", "x-axis")
             .attr("class", "axis")
-            .attr("transform", `translate(0,${chartDimensions.boundedHeight})`)
+            //.attr("transform", `translate(0,${chartDimensions.boundedHeight})`)
             .attr("aria-hidden", true) // hide from screen reader
 
         // generate x axis
-        // xAxis
+        // xAxisBottom
         //     .call(d3.axisBottom(xScale).tickSize(0).tickPadding(10))
             // .select(".domain").remove() // remove axis line
         
         // // add placeholder for x axis title (title text set in drawChart())
-        // xAxis
+        // xAxisBottom
         //     .append("text")
         //     .attr("class", "x-axis axis-title")
         //     .attr("x", -chartDimensions.boundedWidth / 2)
@@ -218,9 +224,12 @@
     function initYScale() {
         // scale for y axis (domain set in `drawChart()`)
         yScale = d3.scaleBand()
-            .range([chartDimensions.boundedHeight, 0])
+            // .range([chartDimensions.boundedHeight, 0])
             .padding(0.1)
 
+
+    }
+    function initYAxis() {
         // add group for y axis
         yAxis = chartBounds.append("g")
             .attr("id", "y-axis")
@@ -312,7 +321,7 @@
 
     function drawChart(data, scalePercent) {
         // accessor functions
-        const yAccessor = d => d.species
+        const yAccessor = d => expandedFamilies[d.family] ? d.species : d.family //d.species
         // const yAccessor_family = d => d.family
         const xAccessor = d => d.cvi
         // const xAccessor_family = d => d.cvi_family
@@ -322,7 +331,7 @@
         const x1Accessor_family = d => scalePercent ? (d.cvi_2075_family - d.cvi_2030_family)/ d.cvi_2030_family : d.cvi_2075_family
         const widthAccessor = d => d.position
         const colorAccessor = d => d.thermal_guild
-        const identifierAccessor = d => d.family + '_' + d.species.replace(/ /g,"_");
+        const identifierAccessor = d => expandedFamilies[d.family] ? d.family + '_' + d.species.replace(/ /g,"_") : d.family;
 
         // to get dynamic
         // need key for data
@@ -343,65 +352,82 @@
                 .nice()
         }
         
-        xAxis.transition(getUpdateTransition())
+        xAxisBottom.transition(getUpdateTransition())
             .call(d3.axisBottom(xScale).tickSize(0).tickPadding(10));
 
-        xAxis
+        xAxisBottom
             .selectAll("text")
             .attr("class", "axis-text")
 
-        const xAxisLabelYPosition = xAxis.select("text").attr('y')
-        const xAxisLabelDy = xAxis.select("text").attr('dy')
-        xAxis.append("text")
+        const xAxisBottomLabelYPosition = xAxisBottom.select("text").attr('y')
+        const xAxisBottomLabelDy = xAxisBottom.select("text").attr('dy')
+        xAxisBottom.append("text")
             .attr("class", "x-axis axis-title")
             .attr("x", chartDimensions.boundedWidth / 2)
-            .attr("y", xAxisLabelYPosition * 4)
-            .attr("dy", xAxisLabelDy)
+            .attr("y", xAxisBottomLabelYPosition * 4)
+            .attr("dy", xAxisBottomLabelDy)
             .style("text-anchor", "middle")
             .text(scalePercent ? 'Percent change in harvest-weighted climate vulnerability, 2030-2075' : 'Change in harvest-weighted climate vulnerability, 2030-2075')
-        xAxis.append("text")
+        xAxisBottom.append("text")
             .attr("class", "x-axis axis-subtitle")
             .attr("x", 0)
-            .attr("y", xAxisLabelYPosition * 5.5)
-            .attr("dy", xAxisLabelDy)
+            .attr("y", xAxisBottomLabelYPosition * 5.5)
+            .attr("dy", xAxisBottomLabelDy)
             .style("text-anchor", "start")
             .text('Less vulnerable')
-        xAxis.append("text")
+        xAxisBottom.append("text")
             .attr("class", "x-axis axis-subtitle")
             .attr("x", chartDimensions.boundedWidth)
-            .attr("y", xAxisLabelYPosition * 5.5)
-            .attr("dy", xAxisLabelDy)
+            .attr("y", xAxisBottomLabelYPosition * 5.5)
+            .attr("dy", xAxisBottomLabelDy)
             .style("text-anchor", "end")
             .text('More vulnerable')
 
         // Remove axix line and labels
-        // xAxis.select(".domain").remove()
-        // xAxis.call(d3.axisBottom(xScale).tickValues([]))
+        // xAxisBottom.select(".domain").remove()
+        // xAxisBottom.call(d3.axisBottom(xScale).tickValues([]))
 
         // set domain for yScale
-        // let yDomain = []
-        // families.value.map(family => expandedFamilies[family] ? yDomain.push(data.filter(d => d.family === family).map(d => d.species)) : yDomain.push(family))
-        // yDomain = yDomain.flat()
-        // yScale
-        //     .domain(yDomain)
+        let yDomain = []
+        families.value.map(family => expandedFamilies[family] ? yDomain.push(data.filter(d => d.family === family).map(d => d.species)) : yDomain.push(family))
+        yDomain = yDomain.flat()
+        
+        const totalHeight = yDomain.length * 25;
+        chartDimensions.boundedHeight = totalHeight
+        chartDimensions.height = chartDimensions.boundedHeight + chartDimensions.margin.top + chartDimensions.margin.bottom
+        chartSVG
+            .transition(getUpdateTransition())
+            .attr("viewBox", [0, 0, (chartDimensions.width), (chartDimensions.height)].join(' '))
+        
+        // Set range and domain for y scale
         yScale
-            .domain(d3.union(data.map(yAccessor))) //.sort(d3.ascending)))
+            .range([chartDimensions.boundedHeight, 0])
+            .domain(yDomain)
+        //yScale
+        //    .domain(d3.union(data.map(yAccessor))) //.sort(d3.ascending)))
+        
+        // set y position for xAxisBottom
+        xAxisBottom
+            .transition(getUpdateTransition())
+            .attr("transform", `translate(0,${chartDimensions.boundedHeight})`)
+
         yAxis
+            .transition(getUpdateTransition())
             .call(d3.axisLeft(yScale).tickSize(2))
         
         yAxis
             .selectAll("text")
-            .attr("class", "axis-text")
+            .attr("class", d => expandedFamilies[d] === false ? "axis-text family" : "axis-text species")
 
         const yAxisLabelXPosition = yAxis.select("text").attr('x')
         const yAxisLabelDx = yAxis.select("text").attr('dx')
-        yAxis.append("text")
-            .attr("class", "y-axis axis-title")
-            .attr("y", 0)
-            .attr("x", yAxisLabelXPosition)
-            .attr("dx", yAxisLabelDx)
-            .style("text-anchor", "end")
-            .text('Species')
+        // yAxis.append("text")
+        //     .attr("class", "y-axis axis-title")
+        //     .attr("y", 0)
+        //     .attr("x", yAxisLabelXPosition)
+        //     .attr("dx", yAxisLabelDx)
+        //     .style("text-anchor", "end")
+        //     .text('Species')
 
         // set up width scale
         const radiusPosition0 = 1
@@ -424,26 +450,33 @@
         initColorScale(colorCategories)
 
         // set up area data
-        const areaCategories = Array.from(new Set(data.map(yAccessor)))
+        const areaCategories = Array.from(new Set(data.map(d => d.species))) //Array.from(new Set(data.map(yAccessor)))
         const areaData = areaCategories.map(areaCategory => {
+            const species = areaCategory //expandedFamilies[areaCategory] === false ? null : areaCategory;
+            const family = data.filter(d => d.species === species)[0].family //expandedFamilies[areaCategory] === false ? areaCategory : data.filter(d => d.species === species)[0].family;
+            const cvi_2030 = expandedFamilies[family] ? x0Accessor(data.filter(d => d.species === species)[0]) : x0Accessor_family(data.filter(d => d.family === family)[0]) //species ? x0Accessor(data.filter(d => d.species === species)[0]) : x0Accessor_family(data.filter(d => d.family === family)[0])
+            const cvi_2075 = expandedFamilies[family] ? x1Accessor(data.filter(d => d.species === species)[0]) : x1Accessor_family(data.filter(d => d.family === family)[0])
+            const cvi_decreasing = cvi_2030 > cvi_2075
+            const thermal_guild = colorAccessor(data.filter(d => d.species === species)[0]) //species ? colorAccessor(data.filter(d => d.species === species)[0]) : colorAccessor(data.filter(d => d.family === family)[0])
+
             return [
                 {
-                    species: areaCategory,
-                    family: data.filter(d => d.species === areaCategory)[0].family,
-                    cvi: x0Accessor(data.filter(d => d.species === areaCategory)[0]),
-                    cvi_decreasing: x0Accessor(data.filter(d => d.species === areaCategory)[0]) > x1Accessor(data.filter(d => d.species === areaCategory)[0]),
-                    cvi_family: x0Accessor_family(data.filter(d => d.species === areaCategory)[0]),
+                    species: species,
+                    family: family,
+                    cvi: cvi_2030,
+                    cvi_decreasing: cvi_decreasing,
+                    // cvi_family: x0Accessor_family(data.filter(d => d.species === areaCategory)[0]),
                     position: 0,
-                    thermal_guild: colorAccessor(data.filter(d => d.species === areaCategory)[0])
+                    thermal_guild: thermal_guild
                 },
                 {
-                    species: areaCategory,
-                    family: data.filter(d => d.species === areaCategory)[0].family,
-                    cvi: x1Accessor(data.filter(d => d.species === areaCategory)[0]),
-                    cvi_decreasing: x0Accessor(data.filter(d => d.species === areaCategory)[0]) > x1Accessor(data.filter(d => d.species === areaCategory)[0]),
-                    cvi_family: x1Accessor_family(data.filter(d => d.species === areaCategory)[0]),
+                    species: species,
+                    family: family,
+                    cvi: cvi_2075,
+                    cvi_decreasing: cvi_decreasing,
+                    // cvi_family: x1Accessor_family(data.filter(d => d.species === areaCategory)[0]),
                     position: 1,
-                    thermal_guild: colorAccessor(data.filter(d => d.species === areaCategory)[0])
+                    thermal_guild: thermal_guild
                 }
             ]
         })
@@ -452,7 +485,7 @@
         // Enter-update-exit pattern for areas
         let areaGroups = chartBounds.selectAll(".areas")
             .selectAll(".area")
-            .data(areaData, d => d[0].species)
+            .data(areaData, d => d[0].species + d[0].family)
         
         const oldAreaGroups = areaGroups.exit()
 
@@ -535,7 +568,7 @@
             .append("circle")
                 .attr("id", d => 'point-2030-' + identifierAccessor(d))
                 .attr("class", "point_2030")
-                .attr("cx", d => xScale(x0Accessor(d)))
+                .attr("cx", d => expandedFamilies[d.family] ? xScale(x0Accessor(d)) : xScale(x0Accessor_family(d)))
                 .attr("cy", d => yScale(yAccessor(d)) + yScale.bandwidth() / 2)
                 .attr("r", radiusPosition0)
                 .style("stroke", d => colorScale(colorAccessor(d)))
@@ -550,7 +583,7 @@
         allPoints2030.transition(getUpdateTransition())
             .attr("id", d => 'point-2030-' + identifierAccessor(d))
             .attr("class", "point_2030")
-            .attr("cx", d => xScale(x0Accessor(d)))
+            .attr("cx", d => expandedFamilies[d.family] ? xScale(x0Accessor(d)) : xScale(x0Accessor_family(d)))
             .attr("cy", d => yScale(yAccessor(d)) + yScale.bandwidth() / 2)
             .attr("r", radiusPosition0)
             .style("stroke", d => colorScale(colorAccessor(d)))
@@ -593,7 +626,7 @@
             .append("circle")
                 .attr("id", d => 'point-2075-' + identifierAccessor(d))
                 .attr("class", "point_2075")
-                .attr("cx", d => xScale(x1Accessor(d)))
+                .attr("cx", d => expandedFamilies[d.family] ? xScale(x1Accessor(d)) : xScale(x1Accessor_family(d)))
                 .attr("cy", d => yScale(yAccessor(d)) + yScale.bandwidth() / 2)
                 .attr("r", radiusPosition1 - strokeWidth1 / 2)
                 .style("stroke", d => colorScale(colorAccessor(d)))
@@ -609,7 +642,7 @@
         allPoints2075.transition(getUpdateTransition())
             .attr("id", d => 'point-2075-' + identifierAccessor(d))
             .attr("class", "point_2075")
-            .attr("cx", d => xScale(x1Accessor(d)))
+            .attr("cx", d => expandedFamilies[d.family] ? xScale(x1Accessor(d)) : xScale(x1Accessor_family(d)))
             .attr("cy", d => yScale(yAccessor(d)) + yScale.bandwidth() / 2)
             .attr("r", radiusPosition1 - strokeWidth1 / 2)
             .style("stroke", d => colorScale(colorAccessor(d)))
@@ -649,6 +682,65 @@
         //             .style("stroke", 'none')
         //             .style("fill", "black")
 
+        // Enter-Update-Exit pattern for overlay rectangles
+        let rectGroups = chartBounds.selectAll('.rects')
+            .selectAll(".rect")
+            .data(data, d => d.species)
+        
+        const oldRectGroups = rectGroups.exit()
+
+        oldRectGroups.selectAll('rect')
+            .transition(getExitTransition())
+            .style("opacity", 0)
+
+        oldRectGroups.transition(getExitTransition()).remove()
+        
+        const newRectGroups = rectGroups.enter().append("g")
+            .attr("class", d => "rect " + d.species)
+            .attr("id", d => 'rect-group-' + identifierAccessor(d))
+
+        // append points
+        newRectGroups
+            .append("rect")
+                .attr("id", d => 'rect-' + identifierAccessor(d))
+                .attr("class", "rect")
+                .attr("x", -chartDimensions.margin.left)
+                .attr("y", d => yScale(yAccessor(d)))
+                .attr("height", yScale.bandwidth())
+                .attr("width", chartDimensions.width)
+                .style("fill", "transparent")
+
+        // update pointGroups2075 to include new points
+        rectGroups = newRectGroups.merge(rectGroups)
+
+        const allRects = rectGroups.select("rect")
+
+        // Update points based on data values
+        allRects.transition(getUpdateTransition())
+            .attr("id", d => 'rect-' + identifierAccessor(d))
+            .attr("class", "rect")
+            .attr("x", -chartDimensions.margin.left)
+            .attr("y", d => yScale(yAccessor(d)))
+            .attr("height", yScale.bandwidth())
+            .attr("width", chartDimensions.width)
+            .style("fill", "transparent")
+        
+        allRects
+            .on("click", function(event, d) {
+                const clickedFamily = d.family
+                expandedFamilies[clickedFamily] = !expandedFamilies[clickedFamily]
+                // let yDomain = []
+                // families.value.map(family => expandedFamilies[family] ? yDomain.push(data.filter(d => d.family === family).map(d => d.species)) : yDomain.push(family))
+                // yDomain = yDomain.flat()
+                // const yBandwidth = yScale.bandwidth()
+                // chartDimensions.boundedHeight = yBandwidth * yDomain.length;
+                // chartDimensions.height = chartDimensions.boundedHeight + chartDimensions.margin.top + chartDimensions.margin.bottom;
+                // chartSVG.attr("viewBox", [0, 0, (chartDimensions.width), (chartDimensions.height)].join(' '))
+                // yScale.range([chartDimensions.boundedHeight * 2, 0])
+                // yAxis
+                //     .call(d3.axisLeft(yScale).tickSize(2))
+                drawChart(data, scalePercent)
+            });
     }
 
     function getUpdateTransition () {
@@ -668,6 +760,13 @@
         font-size: 1.8rem;
         font-family: var(--default-font);
         user-select: none;
+        
+    }
+    .species {
+        font-style: italic;
+    }
+    .family {
+        font-weight: 900;
     }
     .axis-title {
         font-size: 1.8rem;
-- 
GitLab


From a95b400caa40852514ad57fd87383a836fbd3b01 Mon Sep 17 00:00:00 2001
From: Corson-Dosch <hcorson-dosch@usgs.gov>
Date: Wed, 28 Aug 2024 17:29:17 -0500
Subject: [PATCH 10/25] Comment out unused variables

---
 src/components/FishAsFoodLinkChartViz.vue | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/components/FishAsFoodLinkChartViz.vue b/src/components/FishAsFoodLinkChartViz.vue
index 2846298..3c8c272 100644
--- a/src/components/FishAsFoodLinkChartViz.vue
+++ b/src/components/FishAsFoodLinkChartViz.vue
@@ -419,8 +419,8 @@
             .selectAll("text")
             .attr("class", d => expandedFamilies[d] === false ? "axis-text family" : "axis-text species")
 
-        const yAxisLabelXPosition = yAxis.select("text").attr('x')
-        const yAxisLabelDx = yAxis.select("text").attr('dx')
+        // const yAxisLabelXPosition = yAxis.select("text").attr('x')
+        // const yAxisLabelDx = yAxis.select("text").attr('dx')
         // yAxis.append("text")
         //     .attr("class", "y-axis axis-title")
         //     .attr("y", 0)
-- 
GitLab


From 8ab69ba67c1d78ede73dd4813903d7c1be2dc40f Mon Sep 17 00:00:00 2001
From: Corson-Dosch <hcorson-dosch@usgs.gov>
Date: Tue, 3 Sep 2024 14:21:06 -0500
Subject: [PATCH 11/25] add thumbnail and key message

---
 src/assets/content/ChartGrid.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/assets/content/ChartGrid.js b/src/assets/content/ChartGrid.js
index 0deb495..0ca0bf6 100644
--- a/src/assets/content/ChartGrid.js
+++ b/src/assets/content/ChartGrid.js
@@ -25,9 +25,9 @@ export default {
             project: 'Fish as Food',
             vizKey: 'FishAsFoodLinkChart',
             vizRoute: 'inland-rec-fish-climate',
-            img_src: 'Placeholder_thumbnail.PNG',
+            img_src: 'FishAsFoodLinkChart_thumbnail.png',
             alt: '',
-            description: 'Description or key takeaways from this Viz (Viz 3)'
+            description: '...and recreationally fished inland inland fish species are vulnerable'
         },         
         {
             title: 'Glacier/Topography D3 Cross-Section Scan',
-- 
GitLab


From b5492e13b09121f75155e3538445a42a6477666f Mon Sep 17 00:00:00 2001
From: Corson-Dosch <hcorson-dosch@usgs.gov>
Date: Tue, 3 Sep 2024 14:36:12 -0500
Subject: [PATCH 12/25] Add draft text, explainer, and prompt

---
 src/assets/text/text.js                   |  6 ++++++
 src/components/FishAsFoodLinkChartViz.vue | 26 +++++++++++++++--------
 2 files changed, 23 insertions(+), 9 deletions(-)

diff --git a/src/assets/text/text.js b/src/assets/text/text.js
index ec4aa29..e62ec9b 100644
--- a/src/assets/text/text.js
+++ b/src/assets/text/text.js
@@ -94,6 +94,12 @@ export default {
         FishAsFoodCirclePacking: {
             paragraph1: "Explore the global economic value of recreationally-fished inland fish species, in U.S. dollars. Click on the circles in the diagram to see the economic value of species within each fish family, and click on the nested circles to see the economic value of each species in the various countries where it is recreationally fished.",
             paragraph2: "The total economic value for each species in each country is calculated by multiplying the total kilograms of bimoass harvested for each species by the price per kilogram, in U.S. dollars. Species- and country-specific price data were collected from November 2021 to February 2022."
+        },
+        FishAsFoodLinkChart: {
+            paragraph1: 'Climate vulnerability index.',
+            explainerPart1: 'Showing the ',
+            explainerPart2: 'from 2030 to 2075 in harvest-weighted climate vulnerability under representative concentration pathway 4.5.',
+            prompt1: 'Click on the chart to show or hide <i>species</i> within each recreationally fished <b>family</b>'
         }
     }
 }
\ No newline at end of file
diff --git a/src/components/FishAsFoodLinkChartViz.vue b/src/components/FishAsFoodLinkChartViz.vue
index 3c8c272..cc51207 100644
--- a/src/components/FishAsFoodLinkChartViz.vue
+++ b/src/components/FishAsFoodLinkChartViz.vue
@@ -9,16 +9,24 @@
         </template>
         <!-- FIGURES -->
         <template #aboveExplanation>
+            <p v-html="text.paragraph1" />
+            <p>
+                {{ text.explainerPart1 }}
+                <span>
+                    <button
+                        aria-pressed="!scalePercent"
+                        class="button"
+                        :text="scaleType"
+                        @click="toggleScale"
+                        >
+                        {{ scaleType }}
+                    </button>
+                </span>
+                {{ text.explainerPart2 }}
+            </p>
+            <p v-html="text.prompt1" />
         </template>
         <template #figures>
-            <button
-                  aria-pressed="!scalePercent"
-                  class="button"
-                  :text="scaleType"
-                  @click="toggleScale"
-                >
-                  {{ scaleType }}
-            </button>
             <div id="chart-container" class="maxWidth" ref="chart"></div>
         </template>
         <!-- FIGURE CAPTION -->
@@ -62,7 +70,7 @@
 
     // set up filtered chart data as computed property
     const scaleType = computed(() => {
-        return scalePercent.value ? 'Percent change' : 'Change'
+        return scalePercent.value ? 'percent change' : 'change'
     });
 
 
-- 
GitLab


From af4f8b9df973a66f69dfc573def06c1d83a10577 Mon Sep 17 00:00:00 2001
From: Corson-Dosch <hcorson-dosch@usgs.gov>
Date: Tue, 3 Sep 2024 23:10:22 -0500
Subject: [PATCH 13/25] simplify species name

---
 public/fish_as_food_climate.csv | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/public/fish_as_food_climate.csv b/public/fish_as_food_climate.csv
index ab5c337..bb9e48f 100644
--- a/public/fish_as_food_climate.csv
+++ b/public/fish_as_food_climate.csv
@@ -26,7 +26,7 @@ Cichlidae,warm,Pavón,0.008136825204802732,0.008359717025306638,0.02060339577618
 Cichlidae,warm,Pavón Cinchado - Pinta de Lapa,0.007861983512506034,0.008050564307370287,0.020603395776181064,0.020246516151158973
 Cichlidae,warm,Pavón Mariposo,0.008136825204802732,0.008359717025306638,0.020603395776181064,0.020246516151158973
 Cichlidae,warm,Peacock Bass,0.0317336183,0.0326028964,0.020603395776181064,0.020246516151158973
-Cichlidae,warm,"Speckled pavon, speckled peacock bass (percid)",0.030661735699999998,0.031397200800000004,0.020603395776181064,0.020246516151158973
+Cichlidae,warm,Speckled pavon,0.030661735699999998,0.031397200800000004,0.020603395776181064,0.020246516151158973
 Cichlidae,warm,Tilapia,0.0344154895108,0.031549818668399995,0.020603395776181064,0.020246516151158973
 Cichlidae,warm,Tilapia Negra,0.008953991845795688,0.00818019635864715,0.020603395776181064,0.020246516151158973
 Clariidae,warm,African catfish,0.012784239967400001,0.0122263953392,0.027590318372466668,0.02638640551306667
-- 
GitLab


From 94cfca8b9caac35a31d63af21891e39cf8df9e19 Mon Sep 17 00:00:00 2001
From: Corson-Dosch <hcorson-dosch@usgs.gov>
Date: Tue, 3 Sep 2024 23:10:42 -0500
Subject: [PATCH 14/25] tweak prompt text

---
 src/assets/text/text.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/assets/text/text.js b/src/assets/text/text.js
index e62ec9b..42ae131 100644
--- a/src/assets/text/text.js
+++ b/src/assets/text/text.js
@@ -99,7 +99,7 @@ export default {
             paragraph1: 'Climate vulnerability index.',
             explainerPart1: 'Showing the ',
             explainerPart2: 'from 2030 to 2075 in harvest-weighted climate vulnerability under representative concentration pathway 4.5.',
-            prompt1: 'Click on the chart to show or hide <i>species</i> within each recreationally fished <b>family</b>'
+            prompt1: 'Click on the chart to show or hide data for <i>species</i> within each <b>family</b>'
         }
     }
 }
\ No newline at end of file
-- 
GitLab


From 95e1e672ac8a81a027b013b4d12c48cde068e919 Mon Sep 17 00:00:00 2001
From: Corson-Dosch <hcorson-dosch@usgs.gov>
Date: Tue, 3 Sep 2024 23:11:12 -0500
Subject: [PATCH 15/25] get family labels working, adjust chart appearance

---
 src/components/FishAsFoodLinkChartViz.vue | 90 +++++++++++++++++------
 1 file changed, 68 insertions(+), 22 deletions(-)

diff --git a/src/components/FishAsFoodLinkChartViz.vue b/src/components/FishAsFoodLinkChartViz.vue
index cc51207..247eff4 100644
--- a/src/components/FishAsFoodLinkChartViz.vue
+++ b/src/components/FishAsFoodLinkChartViz.vue
@@ -65,6 +65,7 @@
     let yAxis;
     let widthScale;
     let colorScale;
+    const transitionLength = 1500;
     // Create a reactive object to track expanded families
     const expandedFamilies = reactive({});
 
@@ -92,8 +93,8 @@
                     width: chart.value.offsetWidth,
                     height: 1800,
                     margin: 20,
-                    marginBottom: 80,
-                    marginLeft: 200});
+                    marginBottom: 95,
+                    marginLeft: 350});
 
                 drawChart(data.value, scalePercent.value);
             } else {
@@ -360,8 +361,16 @@
                 .nice()
         }
         
+        const xTickFormat = scalePercent ? d3.format("+.0%") : d3.format(".2f");
         xAxisBottom.transition(getUpdateTransition())
-            .call(d3.axisBottom(xScale).tickSize(0).tickPadding(10));
+            .call(d3.axisBottom(xScale).tickSize(5).tickPadding(10).tickFormat(xTickFormat))
+            .on("start", function(){
+                xAxisBottom.select(".domain").remove()
+                // xAxisBottom.selectAll("text")
+                //     .style("opacity", 0)
+            })
+            // .selectAll("text")
+            //     .style("opacity", 1);
 
         xAxisBottom
             .selectAll("text")
@@ -369,24 +378,25 @@
 
         const xAxisBottomLabelYPosition = xAxisBottom.select("text").attr('y')
         const xAxisBottomLabelDy = xAxisBottom.select("text").attr('dy')
+        const labelOffsetFactor = 5
         xAxisBottom.append("text")
             .attr("class", "x-axis axis-title")
             .attr("x", chartDimensions.boundedWidth / 2)
-            .attr("y", xAxisBottomLabelYPosition * 4)
+            .attr("y", xAxisBottomLabelYPosition * labelOffsetFactor / 1.5)
             .attr("dy", xAxisBottomLabelDy)
             .style("text-anchor", "middle")
             .text(scalePercent ? 'Percent change in harvest-weighted climate vulnerability, 2030-2075' : 'Change in harvest-weighted climate vulnerability, 2030-2075')
         xAxisBottom.append("text")
             .attr("class", "x-axis axis-subtitle")
             .attr("x", 0)
-            .attr("y", xAxisBottomLabelYPosition * 5.5)
+            .attr("y", xAxisBottomLabelYPosition * labelOffsetFactor)
             .attr("dy", xAxisBottomLabelDy)
             .style("text-anchor", "start")
             .text('Less vulnerable')
         xAxisBottom.append("text")
             .attr("class", "x-axis axis-subtitle")
             .attr("x", chartDimensions.boundedWidth)
-            .attr("y", xAxisBottomLabelYPosition * 5.5)
+            .attr("y", xAxisBottomLabelYPosition * labelOffsetFactor)
             .attr("dy", xAxisBottomLabelDy)
             .style("text-anchor", "end")
             .text('More vulnerable')
@@ -409,7 +419,7 @@
         
         // Set range and domain for y scale
         yScale
-            .range([chartDimensions.boundedHeight, 0])
+            .range([0, chartDimensions.boundedHeight])
             .domain(yDomain)
         //yScale
         //    .domain(d3.union(data.map(yAccessor))) //.sort(d3.ascending)))
@@ -419,23 +429,55 @@
             .transition(getUpdateTransition())
             .attr("transform", `translate(0,${chartDimensions.boundedHeight})`)
 
+            
+        const xBuffer = 10;
         yAxis
             .transition(getUpdateTransition())
-            .call(d3.axisLeft(yScale).tickSize(2))
-        
+            .call(d3.axisLeft(yScale).tickSize(- chartDimensions.boundedWidth))
+            .on("start", function(){
+                yAxis.select(".domain").remove();
+                yAxis
+                    .selectAll("text")
+                    .attr("x", -xBuffer)
+            })
+            // repeat text position b/c of weird quirk
+            .selectAll("text")
+                .attr("x", -xBuffer)
+
         yAxis
             .selectAll("text")
-            .attr("class", d => expandedFamilies[d] === false ? "axis-text family" : "axis-text species")
+            .attr("class", d => {
+                const current_data = data.filter(item => item.species === d)[0]
+                const current_family = current_data ? current_data.family : d
+                return expandedFamilies[d] === false ? "axis-text family " + current_family : "axis-text species family" + current_family
+            })
+        
+        // Add family labels for expanded families
+        families.value.forEach(family => {
+            if (expandedFamilies[family]) {
+                setTimeout(() => {
+                    const firstLabel = yAxis.select('.family' + family);
+                    const firstLabelWidth = firstLabel.node().getComputedTextLength()
+                    const firstLabelData = data.filter(d => d.family === family)[0]
+                    const firstLabelYPosition = yScale(yAccessor(firstLabelData))
+                    const yAxisLabelDy = yAxis.select("text").attr('dy')
+                    const yAxisLabelXPosition = yAxis.select("text").attr('x')
+                    const yAxisLabelDx = yAxis.select("text").attr('dx')
+                    yAxis.append("text")
+                        .attr("class", "y-axis axis-title familyTitle" + family)
+                        .attr("y", firstLabelYPosition + yScale.bandwidth() / 2)
+                        .attr("x", yAxisLabelXPosition - firstLabelWidth - xBuffer)
+                        .attr("dy", yAxisLabelDy)
+                        .attr("dx", yAxisLabelDx)
+                        .style("text-anchor", "end")
+                        .text(family)
+                        .style("opacity", 0)
+                        .transition(getUpdateTransition())
+                        .style("opacity", 1)
+                }, transitionLength * 1.1); // give the chart a little extra time to render
+            }
+        });
 
-        // const yAxisLabelXPosition = yAxis.select("text").attr('x')
-        // const yAxisLabelDx = yAxis.select("text").attr('dx')
-        // yAxis.append("text")
-        //     .attr("class", "y-axis axis-title")
-        //     .attr("y", 0)
-        //     .attr("x", yAxisLabelXPosition)
-        //     .attr("dx", yAxisLabelDx)
-        //     .style("text-anchor", "end")
-        //     .text('Species')
 
         // set up width scale
         const radiusPosition0 = 1
@@ -753,12 +795,12 @@
 
     function getUpdateTransition () {
       return d3.transition()
-        .duration(1500)
+        .duration(transitionLength)
         .ease(d3.easeCubicInOut)
     }
     function getExitTransition() {
       return d3.transition()
-        .duration(1500)
+        .duration(transitionLength)
         .ease(d3.easeCubicInOut)
     }
 </script>
@@ -768,7 +810,6 @@
         font-size: 1.8rem;
         font-family: var(--default-font);
         user-select: none;
-        
     }
     .species {
         font-style: italic;
@@ -791,4 +832,9 @@
         fill: var(--color-text);
         user-select: none;
     }
+    .tick line {
+        stroke: #0f0f0f;
+        stroke-width: 0.5px;
+        stroke-dasharray: 1, 2;
+    }
 </style>
\ No newline at end of file
-- 
GitLab


From 8faa2b5d7c2f953919957f87c69ebdd242b23b66 Mon Sep 17 00:00:00 2001
From: Corson-Dosch <hcorson-dosch@usgs.gov>
Date: Wed, 4 Sep 2024 09:14:07 -0500
Subject: [PATCH 16/25] add references

---
 src/assets/text/references.js | 54 +++++++++++++++++++++++++++++++++++
 1 file changed, 54 insertions(+)

diff --git a/src/assets/text/references.js b/src/assets/text/references.js
index 3818f74..788ce8d 100644
--- a/src/assets/text/references.js
+++ b/src/assets/text/references.js
@@ -167,6 +167,60 @@ export default {
       doi: "https://doi.org/10.1038/s43016-024-00961-8"
     },
   ],
+  FishAsFoodLinkChart: [
+    {
+      reference: "Reference 1.",
+      num: "1",
+      authors: "Lynch, A.J., Embke, H.S., Nyboer, E.A., Wood, L.E., Thorpe, A., Phang, S.C., Viana, D.F., & Golden, C.D.",
+      year: "2023",
+      title: "Global dataset of nutritional value, economic value, and climate vulnerability for species-specific recreational fisheries harvest for consumption",
+      journal: false,
+      journal_name: "",
+      journal_issue: "",
+      data_release: true,
+      link: "https://www.sciencebase.gov/catalog/item/644ae0e0d34e45f6ddccf773",
+      doi: "https://doi.org/10.5066/P9WO91SZ"
+    },
+    {
+      reference: "Reference 2.",
+      num: "2",
+      authors: "Lynch, A.J., Embke, H.S., Nyboer, E.A., Wood, L.E., Thorpe, A., Phang, S.C., Viana, D.F., Golden, C.D., Milardi, M., Arlinghaus, R., Baigun, C., Beard, T.D., Cooke, S.J., Cowx, I.G., Koehn, J.D., Lyach, R., Potts, W., Robertson, A.M., Schmidhuber, J., & Weyl, O.L.F.",
+      year: "2024",
+      title: "Inland recreational fisheries contribute nutritional benefits and economic value but are vulnerable to climate change",
+      link: "https://www.nature.com/articles/s43016-024-00961-8",
+      journal: true,
+      journal_name: "Nature Food",
+      journal_issue: "5",
+      data_release: false,
+      doi: "https://doi.org/10.1038/s43016-024-00961-8"
+    },
+    {
+      reference: "Reference 3.",
+      num: "3",
+      authors: "Nyboer, E. A., Embke, H. S., Robertson, A. M., Arlinghaus, R., Bower, S., Baigun, C., Beard, D., Cooke, S. J., Cowx, I. G., Koehn, J. D., Lyach, R., Milardi, M., Potts, W., & Lynch, A. J.",
+      year: "2021",
+      title: "Data for: Nyboer et al. 2021 Global assessment of marine and freshwater recreational fish reveals mismatch in climate change vulnerability and conservation effort",
+      link: "https://osf.io/keajr/",
+      journal: false,
+      journal_name: "",
+      journal_issue: "",
+      data_release: false,
+      doi: "https://doi.org/10.17605/osf.io/keajr"
+    },
+    {
+      reference: "Reference 4.",
+      num: "4",
+      authors: "Nyboer, E.A.,  Lin, H., Bennett, J.R., Gabriel, J, Twardek, W., Chhor, A.D., Daly, L., Dolson, S., Guitard, E., Holder, P., Mozzon, C.M., Trahan, A., Zimmermann, D., Kesner-Reyes, K., Garilao, C., Kaschner, K., & Cooke, S.J.",
+      year: "2021",
+      title: "Global assessment of marine and freshwater recreational fish reveals mismatch in climate change vulnerability and conservation effort",
+      link: "https://onlinelibrary.wiley.com/doi/10.1111/gcb.15768?af=R",
+      journal: true,
+      journal_name: "Global Change Biology",
+      journal_issue: "527, 19",
+      data_release: false,
+      doi: "https://doi.org/10.1111/gcb.15768"
+    }
+  ],
   fishasfood: [
     {
       reference: "Reference 1.",
-- 
GitLab


From dedfeb4ef6f2a94c88e0441ccb183c66eee6d35e Mon Sep 17 00:00:00 2001
From: Corson-Dosch <hcorson-dosch@usgs.gov>
Date: Wed, 4 Sep 2024 09:14:20 -0500
Subject: [PATCH 17/25] add authors

---
 src/assets/text/authors.js | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/src/assets/text/authors.js b/src/assets/text/authors.js
index fc83e21..e7796c6 100644
--- a/src/assets/text/authors.js
+++ b/src/assets/text/authors.js
@@ -208,4 +208,29 @@ export default {
       }
     ]
   },
+  FishAsFoodLinkChart: {
+    primaryAuthors: [
+      {
+        firstName: 'Hayley',
+        lastName: 'Corson-Dosch',
+        fullName: 'Hayley Corson-Dosch',
+        initials: 'HCD',
+        profile_link: 'https://www.usgs.gov/staff-profiles/hayley-corson-dosch',
+        
+        role: 'lead developer',
+        contribution: 'built this data visualization, and also co-led the design and development of the website'
+      },
+    ],
+    additionalAuthors: [
+      {
+        firstName: 'Maggie',
+        lastName: 'Jaenicke',
+        fullName: 'Maggie Jaenicke',
+        initials: 'MJ',
+        profile_link: 'https://www.usgs.gov/staff-profiles/margaret-maggie-jaenicke',
+        role: 'lead developer',
+        contribution: 'co-led the design and development of the website'
+      }
+    ]
+  },
 };
\ No newline at end of file
-- 
GitLab


From d01dd811c0938afeacef0f55ced83408adb991e5 Mon Sep 17 00:00:00 2001
From: Corson-Dosch <hcorson-dosch@usgs.gov>
Date: Wed, 4 Sep 2024 09:36:39 -0500
Subject: [PATCH 18/25] add placeholder color key

---
 src/assets/text/text.js                   |  2 +-
 src/components/FishAsFoodLinkChartViz.vue | 12 ++++++++++++
 2 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/src/assets/text/text.js b/src/assets/text/text.js
index 42ae131..71bd4b4 100644
--- a/src/assets/text/text.js
+++ b/src/assets/text/text.js
@@ -96,7 +96,7 @@ export default {
             paragraph2: "The total economic value for each species in each country is calculated by multiplying the total kilograms of bimoass harvested for each species by the price per kilogram, in U.S. dollars. Species- and country-specific price data were collected from November 2021 to February 2022."
         },
         FishAsFoodLinkChart: {
-            paragraph1: 'Climate vulnerability index.',
+            paragraph1: 'Climate vulnerability index. Climate vulnerability varies by family and species. <span class="warm"><b>warm</b></span>, <span class="cool"><b>cool</b></span>, or <span class="cold"><b>cold</b></span> thermal guilds.',
             explainerPart1: 'Showing the ',
             explainerPart2: 'from 2030 to 2075 in harvest-weighted climate vulnerability under representative concentration pathway 4.5.',
             prompt1: 'Click on the chart to show or hide data for <i>species</i> within each <b>family</b>'
diff --git a/src/components/FishAsFoodLinkChartViz.vue b/src/components/FishAsFoodLinkChartViz.vue
index 247eff4..31aca6f 100644
--- a/src/components/FishAsFoodLinkChartViz.vue
+++ b/src/components/FishAsFoodLinkChartViz.vue
@@ -805,6 +805,18 @@
     }
 </script>
 
+<style>
+    .warm {
+        color: #DB2500;
+    }
+    .cool {
+        color: #1474C2;
+    }
+    .cold {
+        color: #36648B;
+    }
+</style>
+
 <style lang="scss">
     .axis-text {
         font-size: 1.8rem;
-- 
GitLab


From 93650c26481ba3a5af413bd64d3a19a47e75db74 Mon Sep 17 00:00:00 2001
From: Corson-Dosch <hcorson-dosch@usgs.gov>
Date: Wed, 4 Sep 2024 13:02:11 -0500
Subject: [PATCH 19/25] add legend svg

---
 .../svgs/climate-vulnerability-legend.svg     | 75 +++++++++++++++++++
 src/components/FishAsFoodLinkChartViz.vue     |  2 +
 2 files changed, 77 insertions(+)
 create mode 100644 src/assets/svgs/climate-vulnerability-legend.svg

diff --git a/src/assets/svgs/climate-vulnerability-legend.svg b/src/assets/svgs/climate-vulnerability-legend.svg
new file mode 100644
index 0000000..2f93da6
--- /dev/null
+++ b/src/assets/svgs/climate-vulnerability-legend.svg
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg id="chart-svg" xmlns="http://www.w3.org/2000/svg" width="350" height="100" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 350 100">
+  <defs>
+    <style>
+      .cls-1 {
+        fill: url(#linear-gradient-2);
+      }
+
+      .cls-2 {
+        fill: #231f20;
+        font-family: SourceSansPro-Bold, 'Source Sans Pro';
+        font-size: 12px;
+        font-weight: 700;
+      }
+
+      .cls-3, .cls-4 {
+        stroke-width: .5px;
+      }
+
+      .cls-3, .cls-4, .cls-5, .cls-6 {
+        fill: #fff;
+        stroke-miterlimit: 10;
+      }
+
+      .cls-3, .cls-6 {
+        stroke: #f37359;
+      }
+
+      .cls-4, .cls-5 {
+        stroke: #66a9dd;
+      }
+
+      .cls-5, .cls-6 {
+        stroke-width: 4px;
+      }
+
+      .cls-7 {
+        fill: url(#linear-gradient);
+      }
+    </style>
+    <linearGradient id="linear-gradient" x1="152.4" y1="217.7" x2="162.2" y2="217.7" gradientTransform="translate(-4043 499) scale(26.7 -1.9)" gradientUnits="userSpaceOnUse">
+      <stop offset=".4" stop-color="#66a9dd"/>
+      <stop offset="1" stop-color="#66a9dd" stop-opacity="0"/>
+    </linearGradient>
+    <linearGradient id="linear-gradient-2" x1="172.4" y1="237.6" x2="162.6" y2="237.6" gradientTransform="translate(-183.8 496) scale(2.9 -1.9)" gradientUnits="userSpaceOnUse">
+      <stop offset=".4" stop-color="#f37359"/>
+      <stop offset="1" stop-color="#f37359" stop-opacity="0"/>
+    </linearGradient>
+  </defs>
+  <g id="legend-visuals">
+    <g id="area-group-Percidae">
+      <path id="area-Percidae" class="cls-7" d="M292.2,78.6L31.1,86.9v-18.9l261.1,8.3v2.4Z"/>
+    </g>
+    <g id="area-group-Pimelodidae">
+      <path id="area-Pimelodidae" class="cls-1" d="M292.2,37.1l28.6,8.3v-18.9l-28.6,8.3v2.4Z"/>
+    </g>
+    <g id="point-2030-group-Percidae">
+      <circle id="point-2030-Percidae_x5F_Zander_x5F__x28_pikeperch_x29_" class="cls-4" cx="292.2" cy="77.4" r="1.2"/>
+    </g>
+    <g id="point-2030-group-Pimelodidae">
+      <circle id="point-2030-Pimelodidae" class="cls-3" cx="292.2" cy="35.9" r="1.2"/>
+    </g>
+    <g id="point-2030-group-Percidae-2" data-name="point-2030-group-Percidae">
+      <circle id="point-2075-Percidae_x5F_Zander_x5F__x28_pikeperch_x29_" class="cls-5" cx="31.1" cy="77.4" r="7.4"/>
+    </g>
+    <g id="point-2030-group-Pimelodidae-2" data-name="point-2030-group-Pimelodidae">
+      <circle id="point-2075-Pimelodidae" class="cls-6" cx="320.7" cy="35.9" r="7.4"/>
+    </g>
+  </g>
+  <g id="legend-labels">
+    <text class="cls-2" transform="translate(279.4 60.5)"><tspan x="0" y="0">2030</tspan></text>
+    <text class="cls-2" transform="translate(18.3 60.5)"><tspan x="0" y="0">2075</tspan></text>
+    <text class="cls-2" transform="translate(308 19.3)"><tspan x="0" y="0">2075</tspan></text>
+  </g>
+</svg>
\ No newline at end of file
diff --git a/src/components/FishAsFoodLinkChartViz.vue b/src/components/FishAsFoodLinkChartViz.vue
index 31aca6f..515aae8 100644
--- a/src/components/FishAsFoodLinkChartViz.vue
+++ b/src/components/FishAsFoodLinkChartViz.vue
@@ -25,6 +25,7 @@
                 {{ text.explainerPart2 }}
             </p>
             <p v-html="text.prompt1" />
+            <legendSVG />
         </template>
         <template #figures>
             <div id="chart-container" class="maxWidth" ref="chart"></div>
@@ -42,6 +43,7 @@
     import { computed, onMounted, reactive, ref } from "vue"; //, reactive
     import * as d3 from 'd3';
     import VizSection from '@/components/VizSection.vue';
+    import legendSVG from '@/assets/svgs/climate-vulnerability-legend.svg'
 
     // define props
     defineProps({
-- 
GitLab


From fac0fa0a238aebaef617240eefccc1f8b228b607 Mon Sep 17 00:00:00 2001
From: Corson-Dosch <hcorson-dosch@usgs.gov>
Date: Wed, 4 Sep 2024 15:17:22 -0500
Subject: [PATCH 20/25] add data processing steps

---
 .gitignore                                    |   1 +
 fishAsFood/_targets.R                         |  14 ++++
 .../in/recfishfoods_families_thermal.xlsx     | Bin 0 -> 34307 bytes
 fishAsFood/src/data_utils.R                   |  68 ++++++++++++++++--
 4 files changed, 79 insertions(+), 4 deletions(-)
 create mode 100644 fishAsFood/in/recfishfoods_families_thermal.xlsx

diff --git a/.gitignore b/.gitignore
index f5faa2b..e90c346 100644
--- a/.gitignore
+++ b/.gitignore
@@ -52,6 +52,7 @@ examples/pydrograph/
 *.html
 beaufortSea/images/*
 !*/images/in/*
+!fishAsFood/in/recfishfoods_families_thermal.xlsx
 # allow empty files to be committed
 !*/in/.empty
 !*/out/.empty
diff --git a/fishAsFood/_targets.R b/fishAsFood/_targets.R
index 360eddd..d296258 100644
--- a/fishAsFood/_targets.R
+++ b/fishAsFood/_targets.R
@@ -26,6 +26,11 @@ p1 <- list(
       return(out_file)
     },
     format = 'file'
+  ),
+  tar_target(
+    p2_metadata_xlsx,
+    file.path('in', 'recfishfoods_families_thermal.xlsx'),
+    format = 'file'
   )
 )
 
@@ -42,6 +47,15 @@ p2 <- list(
       out_file = '../public/total_price.json'
     ),
     format = 'file'
+  ),
+  tar_target(
+    p2_climate_csv,
+    build_climate_csv(
+      data = p2_data,
+      metadata_file = p2_metadata_xlsx,
+      out_file = '../public/fish_as_food_climate.csv'
+    ),
+    format = 'file'
   )
 )
 
diff --git a/fishAsFood/in/recfishfoods_families_thermal.xlsx b/fishAsFood/in/recfishfoods_families_thermal.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..9a84bc9f920218d9d7d79f18a10ec5374aa18e68
GIT binary patch
literal 34307
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
a|1}~1Hg_VxjIUU`z#v<|6M_S;e)~VIDth<;

literal 0
HcmV?d00001

diff --git a/fishAsFood/src/data_utils.R b/fishAsFood/src/data_utils.R
index e134fbd..da0aefc 100644
--- a/fishAsFood/src/data_utils.R
+++ b/fishAsFood/src/data_utils.R
@@ -6,15 +6,19 @@ clean_input_data <- function(data_file) {
   data |>
     mutate(
       family = ifelse(family == 'Salmonindae', 'Salmonidae', family),
-      species_common = ifelse(species_common == 'Bey?ehir bleak', 
-                              'BeyÅŸehir bleak', 
-                              species_common)
+      species_common = case_when(
+        species_common == 'Bey?ehir bleak' ~ 'BeyÅŸehir bleak',
+        species_taxa_sci == 'Brachyplatystoma rousseauxii' ~ 'Gilded catfish',
+        species_common == 'Speckled pavon, speckled peacock bass (percid)' ~ 
+          'Speckled pavon',
+        TRUE ~ species_common
       )
+    )
 }
 
 build_nested_json <- function(data, focal_columns, out_file) {
   data_list <- data |>
-    filter(!is.na(!!rlang::sym(focal_columns[['value']]))) |>
+    filter(!is.na(focal_columns[['value']])) |>
     group_by(name = family) |>
     group_modify(~ {
       species_list <- .x |> 
@@ -32,5 +36,61 @@ build_nested_json <- function(data, focal_columns, out_file) {
   json_data <- paste0('{\n "name": "fish", "children":', json_data, '\n}')
   write(json_data, file = out_file)
   
+  return(out_file)
+}
+
+build_climate_csv <- function(data, metadata_file, out_file) {
+  # read in metadata
+  metadata <- read_xlsx(metadata_file)
+  
+  # filter out missing and uncertain data
+  data_subset <- data |>
+    filter(!is.na(species_common), !is.na(MCDM_VUL_2030_45)) |>
+    left_join(metadata) |>
+    filter(uncertainty_classification < 4)
+  
+  # get subset of better represented families with >2 species
+  focal_families <- data_subset |>
+    group_by(family, species_common) |>
+    count() |>
+    group_by(family) |>
+    count() |>
+    filter(n > 2) |>
+    pull(family)
+  
+  # filter to subset of families
+  data_subset <- data_subset |>
+    filter(family %in% focal_families)
+  
+  # aggregate to species level
+  data_species <- data_subset |>
+    group_by(family, species_common, thermal_guild, popular_y) |>
+    summarize(across(starts_with('MCDM'), 
+                     ~ first(.x)),
+              across(starts_with('weighted_MCDM'), 
+                     ~ mean(.x, na.rm = TRUE)))
+  
+  # sort aggregated data
+  subset_species <- pull(data_species, species_common) |> sort(decreasing = TRUE)
+  data_species <- mutate(data_species, species_common = 
+                           factor(species_common, levels = subset_species))
+  
+  # aggregate to family level
+  data_family <- data_subset |>
+    group_by(family, thermal_guild, popular_y) |>
+    summarize(across(starts_with('MCDM'), 
+                     ~ first(.x)),
+              across(starts_with('weighted_MCDM'), 
+                     ~ mean(.x, na.rm = TRUE)))
+  
+  data_species |>
+    select(family, thermal_guild, species = species_common, 
+           cvi_2030 = weighted_MCDM_VUL_2030_45, 
+           cvi_2075 = weighted_MCDM_VUL_2075_45) |>
+    left_join(select(data_family, family, thermal_guild, 
+                     cvi_2030_family = weighted_MCDM_VUL_2030_45,
+                     cvi_2075_family = weighted_MCDM_VUL_2075_45))|>
+    readr::write_csv(out_file)
+  
   return(out_file)
 }
\ No newline at end of file
-- 
GitLab


From e97fc7ee5ebfd40dad2f0edd900d886503e2c106 Mon Sep 17 00:00:00 2001
From: Corson-Dosch <hcorson-dosch@usgs.gov>
Date: Wed, 4 Sep 2024 15:18:01 -0500
Subject: [PATCH 21/25] clean up code and add comments

---
 src/assets/text/text.js                   |   2 +-
 src/components/FishAsFoodLinkChartViz.vue | 268 +++++++---------------
 2 files changed, 88 insertions(+), 182 deletions(-)

diff --git a/src/assets/text/text.js b/src/assets/text/text.js
index 71bd4b4..f81d30d 100644
--- a/src/assets/text/text.js
+++ b/src/assets/text/text.js
@@ -96,7 +96,7 @@ export default {
             paragraph2: "The total economic value for each species in each country is calculated by multiplying the total kilograms of bimoass harvested for each species by the price per kilogram, in U.S. dollars. Species- and country-specific price data were collected from November 2021 to February 2022."
         },
         FishAsFoodLinkChart: {
-            paragraph1: 'Climate vulnerability index. Climate vulnerability varies by family and species. <span class="warm"><b>warm</b></span>, <span class="cool"><b>cool</b></span>, or <span class="cold"><b>cold</b></span> thermal guilds.',
+            paragraph1: 'Climate vulnerability index. Climate vulnerability varies by family and species. <span class="warm-text"><b>warm</b></span>, <span class="cool-text"><b>cool</b></span>, or <span class="cold-text"><b>cold</b></span> thermal guilds.',
             explainerPart1: 'Showing the ',
             explainerPart2: 'from 2030 to 2075 in harvest-weighted climate vulnerability under representative concentration pathway 4.5.',
             prompt1: 'Click on the chart to show or hide data for <i>species</i> within each <b>family</b>'
diff --git a/src/components/FishAsFoodLinkChartViz.vue b/src/components/FishAsFoodLinkChartViz.vue
index 515aae8..f37a233 100644
--- a/src/components/FishAsFoodLinkChartViz.vue
+++ b/src/components/FishAsFoodLinkChartViz.vue
@@ -64,8 +64,10 @@
     let xScale;
     let xAxisBottom;
     let yScale;
+    const rowHeight = 25;
     let yAxis;
     let widthScale;
+    const colors = {warm: '#FF7256', cool: '#5CACEE', cold: '#36648B'}
     let colorScale;
     const transitionLength = 1500;
     // Create a reactive object to track expanded families
@@ -93,7 +95,6 @@
 
                 initChart({
                     width: chart.value.offsetWidth,
-                    height: 1800,
                     margin: 20,
                     marginBottom: 95,
                     marginLeft: 350});
@@ -139,7 +140,6 @@
 
     function initChart({
         width = 500, // outer width, in pixels
-        // height = width, // outer height, in pixels
         margin = 1, // default margins
         marginTop = margin, // top margin, in pixels
         marginBottom = margin, // left margin, in pixels
@@ -149,7 +149,6 @@
         // set up global chart dimensions
         chartDimensions = {
             width,
-            //height,
             margin: {
                 top: marginTop,
                 right: marginRight,
@@ -158,12 +157,11 @@
             }
         }
         chartDimensions.boundedWidth = chartDimensions.width - chartDimensions.margin.left - chartDimensions.margin.right
-        //chartDimensions.boundedHeight = chartDimensions.height - chartDimensions.margin.top - chartDimensions.margin.bottom
 
         // draw canvas for chart
         chartSVG = d3.select("#chart-container")
             .append("svg")
-                //.attr("viewBox", [0, 0, (chartDimensions.width), (chartDimensions.height)].join(' '))
+                // viewbox set on draw, since height is dynamic
                 .attr("width", "100%")
                 .attr("height", "100%")
                 .attr("id", "chart-svg")
@@ -211,17 +209,19 @@
     }
     function initXAxis() {
         // add group for x axis
+        // will translate in drawChart, since height dynamic
         xAxisBottom = chartBounds.append("g")
             .attr("id", "x-axis")
             .attr("class", "axis")
-            //.attr("transform", `translate(0,${chartDimensions.boundedHeight})`)
             .attr("aria-hidden", true) // hide from screen reader
 
+        // Not generating here, will generate in drawChart()
         // generate x axis
         // xAxisBottom
         //     .call(d3.axisBottom(xScale).tickSize(0).tickPadding(10))
             // .select(".domain").remove() // remove axis line
         
+        // Not adding x-axis title here
         // // add placeholder for x axis title (title text set in drawChart())
         // xAxisBottom
         //     .append("text")
@@ -233,12 +233,10 @@
     }
 
     function initYScale() {
-        // scale for y axis (domain set in `drawChart()`)
+        // scale for y axis (domain and range set in `drawChart()`)
         yScale = d3.scaleBand()
             // .range([chartDimensions.boundedHeight, 0])
             .padding(0.1)
-
-
     }
     function initYAxis() {
         // add group for y axis
@@ -247,11 +245,13 @@
             .attr("class", "axis")
             .attr("aria-hidden", true)
 
+        // Not generating here, will generate in drawChart()
         // generate y axis
         // yAxis
         //     .call(d3.axisLeft(yScale).tickSize(2))
             // .select(".domain").remove() // remove axis line
 
+        // Not adding y-axis title
         // // add placeholder for y axis title (title text set in drawChart())
         // yAxis
         //     .append("text")
@@ -264,7 +264,6 @@
     }
 
     function initGradients() {
-        let colors = {warm: '#FF7256', cool: '#5CACEE', cold: '#36648B'}
         Object.keys(colors).forEach(color => {
             let lg_decreasing = chartSVG.append("defs").append("linearGradient")
                 .attr("id", color + "_gradient_decreasing")
@@ -309,33 +308,15 @@
     function initColorScale(data) {
         colorScale = d3.scaleOrdinal()
             .domain(data)
-            .range(data.map(item => getColor(item)));
-    }
-
-    function getColor(item) {
-        let itemColor;
-        switch(item) {
-            case 'warm':
-                itemColor = '#FF7256';
-                break;
-            case 'cool':
-                itemColor = '#5CACEE';
-                break;
-            case 'cold':
-                itemColor = '#36648B';
-                break;
-            default:
-                itemColor =  '#B3B3B3';
-        }
-        return itemColor;
+            .range(data.map(item => colors[item]));
     }
 
     function drawChart(data, scalePercent) {
-        // accessor functions
+        ///////////////////////////////////////////
+        /////    SET UP ACCESSOR FUNCTIONS    /////
+        ///////////////////////////////////////////
         const yAccessor = d => expandedFamilies[d.family] ? d.species : d.family //d.species
-        // const yAccessor_family = d => d.family
         const xAccessor = d => d.cvi
-        // const xAccessor_family = d => d.cvi_family
         const x0Accessor = d => scalePercent ? 0 : d.cvi_2030
         const x1Accessor = d => scalePercent ? (d.cvi_2075 - d.cvi_2030)/ d.cvi_2030 : d.cvi_2075
         const x0Accessor_family = d => scalePercent ? 0 : d.cvi_2030_family
@@ -344,12 +325,9 @@
         const colorAccessor = d => d.thermal_guild
         const identifierAccessor = d => expandedFamilies[d.family] ? d.family + '_' + d.species.replace(/ /g,"_") : d.family;
 
-        // to get dynamic
-        // need key for data
-        // need enter update exit pattern with transitions
-        // need to transition chart height, yscale, yaxis
-
-
+        ////////////////////////////////
+        /////    SET UP X AXIS    /////
+        ///////////////////////////////
         // set domain for xScale
         if (scalePercent) {
             const maxVal = d3.max([Math.abs(d3.max(data, x1Accessor)), Math.abs(d3.min(data, x1Accessor))])
@@ -363,21 +341,22 @@
                 .nice()
         }
         
+        // set tick format for x-axis
         const xTickFormat = scalePercent ? d3.format("+.0%") : d3.format(".2f");
+        
+        // generate x-axis
         xAxisBottom.transition(getUpdateTransition())
             .call(d3.axisBottom(xScale).tickSize(5).tickPadding(10).tickFormat(xTickFormat))
             .on("start", function(){
                 xAxisBottom.select(".domain").remove()
-                // xAxisBottom.selectAll("text")
-                //     .style("opacity", 0)
             })
-            // .selectAll("text")
-            //     .style("opacity", 1);
 
+        // assign class to x-axis text for css styling
         xAxisBottom
             .selectAll("text")
             .attr("class", "axis-text")
 
+        // Add titles to x-Axis
         const xAxisBottomLabelYPosition = xAxisBottom.select("text").attr('y')
         const xAxisBottomLabelDy = xAxisBottom.select("text").attr('dy')
         const labelOffsetFactor = 5
@@ -403,35 +382,35 @@
             .style("text-anchor", "end")
             .text('More vulnerable')
 
-        // Remove axix line and labels
-        // xAxisBottom.select(".domain").remove()
-        // xAxisBottom.call(d3.axisBottom(xScale).tickValues([]))
-
+        //////////////////////////////////////////
+        /////    SET UP Y AXIS AND HEIGHT    /////
+        //////////////////////////////////////////
         // set domain for yScale
         let yDomain = []
         families.value.map(family => expandedFamilies[family] ? yDomain.push(data.filter(d => d.family === family).map(d => d.species)) : yDomain.push(family))
         yDomain = yDomain.flat()
         
-        const totalHeight = yDomain.length * 25;
+        // compute chart height
+        const totalHeight = yDomain.length * rowHeight;
         chartDimensions.boundedHeight = totalHeight
         chartDimensions.height = chartDimensions.boundedHeight + chartDimensions.margin.top + chartDimensions.margin.bottom
+        
+        // set viewbox for SVG
         chartSVG
             .transition(getUpdateTransition())
             .attr("viewBox", [0, 0, (chartDimensions.width), (chartDimensions.height)].join(' '))
         
-        // Set range and domain for y scale
-        yScale
-            .range([0, chartDimensions.boundedHeight])
-            .domain(yDomain)
-        //yScale
-        //    .domain(d3.union(data.map(yAccessor))) //.sort(d3.ascending)))
-        
         // set y position for xAxisBottom
         xAxisBottom
             .transition(getUpdateTransition())
             .attr("transform", `translate(0,${chartDimensions.boundedHeight})`)
 
-            
+        // Set range and domain for y scale
+        yScale
+            .range([0, chartDimensions.boundedHeight])
+            .domain(yDomain)
+                    
+        // generate y axis
         const xBuffer = 10;
         yAxis
             .transition(getUpdateTransition())
@@ -446,6 +425,7 @@
             .selectAll("text")
                 .attr("x", -xBuffer)
 
+        // assign class to y-axis labels
         yAxis
             .selectAll("text")
             .attr("class", d => {
@@ -454,62 +434,70 @@
                 return expandedFamilies[d] === false ? "axis-text family " + current_family : "axis-text species family" + current_family
             })
         
-        // Add family labels for expanded families
+        // Add family labels for expanded families, waiting until chart is rendered to determine x placement
+        // start by making sure all existing labels are removed
         families.value.forEach(family => {
             if (expandedFamilies[family]) {
                 setTimeout(() => {
-                    const firstLabel = yAxis.select('.family' + family);
-                    const firstLabelWidth = firstLabel.node().getComputedTextLength()
-                    const firstLabelData = data.filter(d => d.family === family)[0]
-                    const firstLabelYPosition = yScale(yAccessor(firstLabelData))
-                    const yAxisLabelDy = yAxis.select("text").attr('dy')
-                    const yAxisLabelXPosition = yAxis.select("text").attr('x')
-                    const yAxisLabelDx = yAxis.select("text").attr('dx')
-                    yAxis.append("text")
-                        .attr("class", "y-axis axis-title familyTitle" + family)
-                        .attr("y", firstLabelYPosition + yScale.bandwidth() / 2)
-                        .attr("x", yAxisLabelXPosition - firstLabelWidth - xBuffer)
-                        .attr("dy", yAxisLabelDy)
-                        .attr("dx", yAxisLabelDx)
-                        .style("text-anchor", "end")
-                        .text(family)
-                        .style("opacity", 0)
-                        .transition(getUpdateTransition())
-                        .style("opacity", 1)
+                    // confirm family is still expanded
+                    if (expandedFamilies[family]) {
+                        const firstLabel = yAxis.select('.family' + family);
+                        const firstLabelWidth = firstLabel.node().getComputedTextLength()
+                        const firstLabelData = data.filter(d => d.family === family)[0]
+                        const firstLabelYPosition = yScale(yAccessor(firstLabelData))
+                        const yAxisLabelDy = yAxis.select("text").attr('dy')
+                        const yAxisLabelXPosition = yAxis.select("text").attr('x')
+                        const yAxisLabelDx = yAxis.select("text").attr('dx')
+                        yAxis.append("text")
+                            .attr("class", "y-axis axis-title familyTitle" + family)
+                            .attr("y", firstLabelYPosition + yScale.bandwidth() / 2)
+                            .attr("x", yAxisLabelXPosition - firstLabelWidth - xBuffer)
+                            .attr("dy", yAxisLabelDy)
+                            .attr("dx", yAxisLabelDx)
+                            .style("text-anchor", "end")
+                            .text(family)
+                            .style("opacity", 0)
+                            .transition(getUpdateTransition())
+                            .style("opacity", 1)
+                    }
                 }, transitionLength * 1.1); // give the chart a little extra time to render
             }
         });
 
-
-        // set up width scale
+        ////////////////////////////////////
+        /////    SET UP WIDTH SCALE    /////
+        ////////////////////////////////////
         const radiusPosition0 = 1
         const strokeRatio = 0.3
         const strokeWidth1 = strokeRatio * yScale.bandwidth() / 2
         const radiusPosition1 = (1 - strokeRatio) * yScale.bandwidth() / 2
         initWidthScale(radiusPosition0, radiusPosition1)
 
-        // set up area function
+        /////////////////////////////////////
+        /////    SET UP AREA FUNCTION   /////
+        /////////////////////////////////////
         const area = d3.area()
             .x(d => xScale(xAccessor(d)))
             .y0(d => yScale(yAccessor(d)) + yScale.bandwidth() / 2 - widthScale(widthAccessor(d)))
             .y1(d => yScale(yAccessor(d)) + yScale.bandwidth() / 2+ widthScale(widthAccessor(d)));
-            // .x(d => expandedFamilies[d.family] ? xScale(xAccessor(d)) : xScale(xAccessor_family(d)))
-            // .y0(d => expandedFamilies[d.family] ? yScale(yAccessor(d)) - widthScale(widthAccessor(d)) : yScale(yAccessor_family(d)) - widthScale(widthAccessor(d)))
-            // .y1(d => expandedFamilies[d.family] ? yScale(yAccessor(d)) + widthScale(widthAccessor(d)) : yScale(yAccessor_family(d)) + widthScale(widthAccessor(d)));
 
-        // set up color scale
+        ///////////////////////////////////
+        /////    SET UP COLOR SCALE   /////
+        ///////////////////////////////////
         const colorCategories = Array.from(new Set(data.map(colorAccessor)))
         initColorScale(colorCategories)
 
-        // set up area data
-        const areaCategories = Array.from(new Set(data.map(d => d.species))) //Array.from(new Set(data.map(yAccessor)))
+        ///////////////////////////////////////
+        /////    SET UP DATA FOR AREAS    /////
+        ///////////////////////////////////////
+        const areaCategories = Array.from(new Set(data.map(d => d.species)))
         const areaData = areaCategories.map(areaCategory => {
-            const species = areaCategory //expandedFamilies[areaCategory] === false ? null : areaCategory;
-            const family = data.filter(d => d.species === species)[0].family //expandedFamilies[areaCategory] === false ? areaCategory : data.filter(d => d.species === species)[0].family;
-            const cvi_2030 = expandedFamilies[family] ? x0Accessor(data.filter(d => d.species === species)[0]) : x0Accessor_family(data.filter(d => d.family === family)[0]) //species ? x0Accessor(data.filter(d => d.species === species)[0]) : x0Accessor_family(data.filter(d => d.family === family)[0])
+            const species = areaCategory
+            const family = data.filter(d => d.species === species)[0].family
+            const cvi_2030 = expandedFamilies[family] ? x0Accessor(data.filter(d => d.species === species)[0]) : x0Accessor_family(data.filter(d => d.family === family)[0])
             const cvi_2075 = expandedFamilies[family] ? x1Accessor(data.filter(d => d.species === species)[0]) : x1Accessor_family(data.filter(d => d.family === family)[0])
             const cvi_decreasing = cvi_2030 > cvi_2075
-            const thermal_guild = colorAccessor(data.filter(d => d.species === species)[0]) //species ? colorAccessor(data.filter(d => d.species === species)[0]) : colorAccessor(data.filter(d => d.family === family)[0])
+            const thermal_guild = colorAccessor(data.filter(d => d.species === species)[0])
 
             return [
                 {
@@ -517,7 +505,6 @@
                     family: family,
                     cvi: cvi_2030,
                     cvi_decreasing: cvi_decreasing,
-                    // cvi_family: x0Accessor_family(data.filter(d => d.species === areaCategory)[0]),
                     position: 0,
                     thermal_guild: thermal_guild
                 },
@@ -526,13 +513,15 @@
                     family: family,
                     cvi: cvi_2075,
                     cvi_decreasing: cvi_decreasing,
-                    // cvi_family: x1Accessor_family(data.filter(d => d.species === areaCategory)[0]),
                     position: 1,
                     thermal_guild: thermal_guild
                 }
             ]
         })
-            
+        
+        ////////////////////////////////////
+        /////    ADD CHART ELEMENTS    /////
+        ////////////////////////////////////
         // draw chart
         // Enter-update-exit pattern for areas
         let areaGroups = chartBounds.selectAll(".areas")
@@ -570,33 +559,6 @@
             .attr('d', d => area(d))
             .attr('fill', d => d[0].cvi_decreasing ? `url(#${d[0].thermal_guild}_gradient_decreasing)`: `url(#${d[0].thermal_guild}_gradient_increasing)`)//d => colorScale(colorAccessor(d[0])))
             .style("opacity", 1)
-        
-        // chartBounds.append("g")
-        //     .attr("id", "areas")
-        //     .selectAll('.area')
-        //         .data(areaData, d => d[0].species)
-        //         .enter()
-        //         .append('path')
-        //             .attr("id", d => 'area-' + identifierAccessor(d[0]))
-        //             .attr('class', "area")
-        //             .attr('d', d => area(d))
-        //             .attr('fill', d => d[0].cvi_decreasing ? `url(#${d[0].thermal_guild}_gradient_decreasing)`: `url(#${d[0].thermal_guild}_gradient_increasing)`)//d => colorScale(colorAccessor(d[0])))
-        //             .style("opacity", 1)
-                    // .on("click", function(event, d) {
-                    //     const clickedFamily = d[0].family
-                    //     expandedFamilies[clickedFamily] = !expandedFamilies[clickedFamily]
-                    //     let yDomain = []
-                    //     families.value.map(family => expandedFamilies[family] ? yDomain.push(data.filter(d => d.family === family).map(d => d.species)) : yDomain.push(family))
-                    //     yDomain = yDomain.flat()
-                    //     const yBandwidth = yScale.bandwidth()
-                    //     chartDimensions.boundedHeight = yBandwidth * yDomain.length;
-                    //     chartDimensions.height = chartDimensions.boundedHeight + chartDimensions.margin.top + chartDimensions.margin.bottom;
-                    //     chartSVG.attr("viewBox", [0, 0, (chartDimensions.width), (chartDimensions.height)].join(' '))
-                    //     yScale.range([chartDimensions.boundedHeight * 2, 0])
-                    //     yAxis
-                    //         .call(d3.axisLeft(yScale).tickSize(2))
-                    //     drawChart(data, scalePercent.value)
-                    // });
 
         // Enter-Update-Exit pattern for 2030 points
         let pointGroups2030 = chartBounds.selectAll('.points_2030')
@@ -641,21 +603,6 @@
             .style("stroke", d => colorScale(colorAccessor(d)))
             .style("fill", "white")
 
-        // chartBounds.append("g")
-        //     .attr("id", "points-2030")
-        //     .attr("class", "points points_2030")    
-        //     .selectAll('points')
-        //         .data(data)
-        //         .enter()
-        //         .append("circle")
-        //             .attr("id", d => 'point-2030-' + identifierAccessor(d))
-        //             .attr("class", "point")
-        //             .attr("cx", d => xScale(x0Accessor(d)))
-        //             .attr("cy", d => yScale(yAccessor(d)) + yScale.bandwidth() / 2)
-        //             .attr("r", radiusPosition0)
-        //             .style("stroke", d => colorScale(colorAccessor(d)))
-        //             .style("fill", "white")
-
         // Enter-Update-Exit pattern for 2075 points
         let pointGroups2075 = chartBounds.selectAll('.points_2075')
             .selectAll(".point_2075")
@@ -701,39 +648,6 @@
             .style("stroke-width", strokeWidth1)
             .style("fill", "white")
 
-        // chartBounds.append("g")
-        //     .attr("id", "points-2075")
-        //     .attr("class", "points points_2075")    
-        //     .selectAll('points')
-        //         .data(data)
-        //         .enter()
-        //         .append("circle")
-        //             .attr("id", d => 'point-2075-' + identifierAccessor(d))
-        //             .attr("class", "point")
-        //             .attr("cx", d => xScale(x1Accessor(d)))
-        //             .attr("cy", d => yScale(yAccessor(d)) + yScale.bandwidth() / 2)
-        //             .attr("r", radiusPosition1 - strokeWidth1 / 2)
-        //             .style("stroke", d => colorScale(colorAccessor(d)))
-        //             .style("stroke-width", strokeWidth1)
-        //             .style("fill", "white")
-
-        // chartBounds.append("g")
-        //     .attr("id", "eyes-2075")
-        //     .attr("class", "eyes eyes_2075")    
-        //     .selectAll('eyes')
-        //         .data(data)
-        //         .enter()
-        //         .append("circle")
-        //             .attr("id", d => 'point-2075-' + identifierAccessor(d))
-        //             .attr("class", "point")
-        //             .attr("cx", d => {
-        //                 return x1Accessor(d) > x0Accessor(d) ? (xScale(x1Accessor(d)) + radiusPosition1 / 4) : (xScale(x1Accessor(d)) - radiusPosition1 / 4)
-        //             })
-        //             .attr("cy", d => yScale(yAccessor(d)) + radiusPosition1 / 4)
-        //             .attr("r", radiusPosition1 / 2)
-        //             .style("stroke", 'none')
-        //             .style("fill", "black")
-
         // Enter-Update-Exit pattern for overlay rectangles
         let rectGroups = chartBounds.selectAll('.rects')
             .selectAll(".rect")
@@ -751,7 +665,7 @@
             .attr("class", d => "rect " + d.species)
             .attr("id", d => 'rect-group-' + identifierAccessor(d))
 
-        // append points
+        // append rectangles
         newRectGroups
             .append("rect")
                 .attr("id", d => 'rect-' + identifierAccessor(d))
@@ -762,12 +676,12 @@
                 .attr("width", chartDimensions.width)
                 .style("fill", "transparent")
 
-        // update pointGroups2075 to include new points
+        // update rectGroups to include new rectangles
         rectGroups = newRectGroups.merge(rectGroups)
 
         const allRects = rectGroups.select("rect")
 
-        // Update points based on data values
+        // Update rectangles based on data values
         allRects.transition(getUpdateTransition())
             .attr("id", d => 'rect-' + identifierAccessor(d))
             .attr("class", "rect")
@@ -781,16 +695,8 @@
             .on("click", function(event, d) {
                 const clickedFamily = d.family
                 expandedFamilies[clickedFamily] = !expandedFamilies[clickedFamily]
-                // let yDomain = []
-                // families.value.map(family => expandedFamilies[family] ? yDomain.push(data.filter(d => d.family === family).map(d => d.species)) : yDomain.push(family))
-                // yDomain = yDomain.flat()
-                // const yBandwidth = yScale.bandwidth()
-                // chartDimensions.boundedHeight = yBandwidth * yDomain.length;
-                // chartDimensions.height = chartDimensions.boundedHeight + chartDimensions.margin.top + chartDimensions.margin.bottom;
-                // chartSVG.attr("viewBox", [0, 0, (chartDimensions.width), (chartDimensions.height)].join(' '))
-                // yScale.range([chartDimensions.boundedHeight * 2, 0])
-                // yAxis
-                //     .call(d3.axisLeft(yScale).tickSize(2))
+
+                // redraw chart
                 drawChart(data, scalePercent)
             });
     }
@@ -808,13 +714,13 @@
 </script>
 
 <style>
-    .warm {
+    .warm-text {
         color: #DB2500;
     }
-    .cool {
+    .cool-text {
         color: #1474C2;
     }
-    .cold {
+    .cold-text {
         color: #36648B;
     }
 </style>
-- 
GitLab


From 99490bdd6364927aaa022cc47f8b58538757ce2e Mon Sep 17 00:00:00 2001
From: Corson-Dosch <hcorson-dosch@usgs.gov>
Date: Wed, 4 Sep 2024 15:20:32 -0500
Subject: [PATCH 22/25] have chart collapsed by default

---
 src/components/FishAsFoodLinkChartViz.vue | 2 --
 1 file changed, 2 deletions(-)

diff --git a/src/components/FishAsFoodLinkChartViz.vue b/src/components/FishAsFoodLinkChartViz.vue
index f37a233..ff6d3bd 100644
--- a/src/components/FishAsFoodLinkChartViz.vue
+++ b/src/components/FishAsFoodLinkChartViz.vue
@@ -90,8 +90,6 @@
                 families.value.forEach(family => {
                     expandedFamilies[family] = false;
                 });
-                // expandedFamilies["Cyprinidae"] = !expandedFamilies["Cyprinidae"]
-                expandedFamilies["Salmonidae"] = !expandedFamilies["Salmonidae"]
 
                 initChart({
                     width: chart.value.offsetWidth,
-- 
GitLab


From b5373de2468c31a87bcd3d220238644761454ee0 Mon Sep 17 00:00:00 2001
From: Corson-Dosch <hcorson-dosch@usgs.gov>
Date: Wed, 4 Sep 2024 15:35:07 -0500
Subject: [PATCH 23/25] only transition x axis if not initial load

---
 src/components/FishAsFoodLinkChartViz.vue | 18 +++++++++++++-----
 1 file changed, 13 insertions(+), 5 deletions(-)

diff --git a/src/components/FishAsFoodLinkChartViz.vue b/src/components/FishAsFoodLinkChartViz.vue
index ff6d3bd..3b7f83d 100644
--- a/src/components/FishAsFoodLinkChartViz.vue
+++ b/src/components/FishAsFoodLinkChartViz.vue
@@ -52,6 +52,7 @@
 
     // global variables
     const publicPath = import.meta.env.BASE_URL;
+    const initialLoad = ref();
     const dataFile = 'fish_as_food_climate.csv' //'fish_as_food_climate_test.csv'
     const data = ref();
     const families = ref();
@@ -78,12 +79,13 @@
         return scalePercent.value ? 'percent change' : 'change'
     });
 
-
     // Behavior on mounted (functions called here)
     // Load data and then make chart
     onMounted(async () => {
         try {
             await loadDatasets();
+            initialLoad.value = true;
+
             if (data.value.length > 0) {
                 families.value = Array.from(new Set(data.value.map(d => d.family)));
                 // Initialize the expandedFamilies object
@@ -98,6 +100,7 @@
                     marginLeft: 350});
 
                 drawChart(data.value, scalePercent.value);
+                initialLoad.value = false;
             } else {
                 console.error('Error loading data');
             }
@@ -398,10 +401,15 @@
             .transition(getUpdateTransition())
             .attr("viewBox", [0, 0, (chartDimensions.width), (chartDimensions.height)].join(' '))
         
-        // set y position for xAxisBottom
-        xAxisBottom
-            .transition(getUpdateTransition())
-            .attr("transform", `translate(0,${chartDimensions.boundedHeight})`)
+        // set y position for xAxisBottom, transitioning it if NOT initial load
+        if (initialLoad.value) {
+            xAxisBottom
+                .attr("transform", `translate(0,${chartDimensions.boundedHeight})`)
+        } else {
+            xAxisBottom
+                .transition(getUpdateTransition())
+                .attr("transform", `translate(0,${chartDimensions.boundedHeight})`)
+        }
 
         // Set range and domain for y scale
         yScale
-- 
GitLab


From 70b335023a30e54eb6e6c458f63040f6c1496855 Mon Sep 17 00:00:00 2001
From: Corson-Dosch <hcorson-dosch@usgs.gov>
Date: Wed, 4 Sep 2024 15:41:01 -0500
Subject: [PATCH 24/25] bump up margin

---
 src/components/FishAsFoodLinkChartViz.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/FishAsFoodLinkChartViz.vue b/src/components/FishAsFoodLinkChartViz.vue
index 3b7f83d..11c0306 100644
--- a/src/components/FishAsFoodLinkChartViz.vue
+++ b/src/components/FishAsFoodLinkChartViz.vue
@@ -95,7 +95,7 @@
 
                 initChart({
                     width: chart.value.offsetWidth,
-                    margin: 20,
+                    margin: 25,
                     marginBottom: 95,
                     marginLeft: 350});
 
-- 
GitLab


From 489412ac42d81a6567b831482e370cd415118755 Mon Sep 17 00:00:00 2001
From: Corson-Dosch <hcorson-dosch@usgs.gov>
Date: Wed, 4 Sep 2024 23:01:12 -0500
Subject: [PATCH 25/25] simplify author statement

---
 src/assets/text/authors.js | 11 +----------
 1 file changed, 1 insertion(+), 10 deletions(-)

diff --git a/src/assets/text/authors.js b/src/assets/text/authors.js
index 5b66d38..253beb0 100644
--- a/src/assets/text/authors.js
+++ b/src/assets/text/authors.js
@@ -177,19 +177,10 @@ export default {
         profile_link: 'https://www.usgs.gov/staff-profiles/hayley-corson-dosch',
         
         role: 'lead developer',
-        contribution: 'built this data visualization, and also co-led the design and development of the website'
+        contribution: 'built this data visualization'
       },
     ],
     additionalAuthors: [
-      {
-        firstName: 'Maggie',
-        lastName: 'Jaenicke',
-        fullName: 'Maggie Jaenicke',
-        initials: 'MJ',
-        profile_link: 'https://www.usgs.gov/staff-profiles/margaret-maggie-jaenicke',
-        role: 'lead developer',
-        contribution: 'co-led the design and development of the website'
-      }
     ]
   },
 };
\ No newline at end of file
-- 
GitLab