diff --git a/src/components/BeeswarmChart.vue b/src/components/BeeswarmChart.vue index 796e63b5249c44d7ac9e78f78648a0120e123fda..d015a3201a098435ab33618e5236ab32ec5829ce 100644 --- a/src/components/BeeswarmChart.vue +++ b/src/components/BeeswarmChart.vue @@ -4,44 +4,44 @@ <p> Everyone needs access to clean water. Water insecurity is influenced by a number of social vulnerability indicators. This includes <span - :class="['highlight', 'demographicCharacteristics', { checked: isChecked.demographicCharacteristics }]" - @click="toggleCategory('demographicCharacteristics')" + :class="['highlight', 'Demographiccharacteristics', { checked: isChecked.Demographiccharacteristics }]" + @click="toggleCategory('Demographiccharacteristics')" > demographic characteristics </span>, <span - :class="['highlight', 'health', { checked: isChecked.health }]" - @click="toggleCategory('health')" + :class="['highlight', 'Health', { checked: isChecked.Health }]" + @click="toggleCategory('Health')" > health </span>, <span - :class="['highlight', 'livingConditions', { checked: isChecked.livingConditions }]" - @click="toggleCategory('livingConditions')" + :class="['highlight', 'Livingconditions', { checked: isChecked.Livingconditions }]" + @click="toggleCategory('Livingconditions')" > living conditions </span>, <span - :class="['highlight', 'socioeconomicStatus', { checked: isChecked.socioeconomicStatus }]" - @click="toggleCategory('socioeconomicStatus')" + :class="['highlight', 'Socioeconomicstatus', { checked: isChecked.Socioeconomicstatus }]" + @click="toggleCategory('Socioeconomicstatus')" > socioeconomic status </span>, <span - :class="['highlight', 'riskPerception', { checked: isChecked.riskPerception }]" - @click="toggleCategory('riskPerception')" + :class="['highlight', 'Riskperception', { checked: isChecked.Riskperception }]" + @click="toggleCategory('Riskperception')" > risk perception </span>, <span - :class="['highlight', 'landTenure', { checked: isChecked.landTenure }]" - @click="toggleCategory('landTenure')" + :class="['highlight', 'Landtenure', { checked: isChecked.Landtenure }]" + @click="toggleCategory('Landtenure')" > land tenure </span>, and <span - :class="['highlight', 'exposureToStressors', { checked: isChecked.exposureToStressors }]" - @click="toggleCategory('exposureToStressors')" + :class="['highlight', 'Exposure', { checked: isChecked.Exposure }]" + @click="toggleCategory('Exposure')" > exposure to stressors </span> (like drought or pollution). @@ -50,297 +50,303 @@ <div id="beeswarm-chart-container"></div> </section> </template> - - <script setup> - import { onMounted, ref } from "vue"; - import * as d3 from 'd3'; - - // Global variables - const publicPath = import.meta.env.BASE_URL; - const dataSet1 = ref([]); - const dataSet2 = ref([]); - const selectedDataSet = ref('dataSet1'); - const data = ref([]); - let simulation; - - // Set up SVG - let svg; - const height = 800; - const width = 800; - const margin = { top: 30, right: 20, bottom: 20, left: 30 }; - - const isChecked = ref({ - demographicCharacteristics: true, - health: true, - livingConditions: true, - socioeconomicStatus: true, - riskPerception: true, - landTenure: true, - exposureToStressors: true - }); - - // Set colors for bubble charts - const dimensionColors = { - Demographiccharacteristics: "#092836", - Landtenure: "#1b695e", - Livingconditions: "#7a5195", - Socioeconomicstatus: "#2a468f", - Health: "#ef5675", - Riskperception: "#ff764a", - Exposure: "#ffa600" - }; - - // Load data and then make chart - onMounted(async () => { - try { - await loadDatasets(); - data.value = selectedDataSet.value === 'dataSet1' ? dataSet1.value : dataSet2.value; - if (data.value.length > 0) { - createBeeswarmChart(); - } else { - console.error('Error loading data'); - } - } catch (error) { - console.error('Error during component mounting', error); - } - }); - - async function loadDatasets() { - try { - dataSet1.value = await loadData('determinant_uncertainty.csv'); - dataSet2.value = await loadData('indicator_uncertainty.csv'); - } catch (error) { - console.error('Error loading datasets', error); - } - } - - async function loadData(fileName) { - try { - const data = await d3.csv(publicPath + fileName, d => { - d.level_agreement = +(+d.level_agreement).toFixed(2); - d.evidence_val = +d.evidence_val; - d.sig_value = +d.sig_value; - return d; - }); - return data; - } catch (error) { - console.error(`Error loading data from ${fileName}`, error); - return []; + + <script setup> + import { onMounted, ref } from "vue"; + import * as d3 from 'd3'; + + // Global variables + const publicPath = import.meta.env.BASE_URL; + const dataSet1 = ref([]); + const dataSet2 = ref([]); + const selectedDataSet = ref('dataSet1'); + const data = ref([]); + let simulation; + + // Set up SVG + let svg; + const height = 800; + const width = 800; + const margin = { top: 30, right: 20, bottom: 20, left: 30 }; + + const isChecked = ref({ + Demographiccharacteristics: true, + Health: true, + Livingconditions: true, + Socioeconomicstatus: true, + Riskperception: true, + Landtenure: true, + Exposure: true + }); + + // Set colors for bubble charts + const dimensionColors = { + Demographiccharacteristics: "#092836", + Landtenure: "#1b695e", + Livingconditions: "#7a5195", + Socioeconomicstatus: "#2a468f", + Health: "#ef5675", + Riskperception: "#ff764a", + Exposure: "#ffa600" + }; + + // Load data and then make chart + onMounted(async () => { + try { + await loadDatasets(); + data.value = selectedDataSet.value === 'dataSet1' ? dataSet1.value : dataSet2.value; + if (data.value.length > 0) { + createBeeswarmChart(); + } else { + console.error('Error loading data'); } + } catch (error) { + console.error('Error during component mounting', error); } - - function createBeeswarmChart() { - svg = d3 - .select('#beeswarm-chart-container') - .append('svg') - .attr('class', 'beeswarmSvg') - .attr('width', width) - .attr('height', height); - - const yScale = d3.scaleLinear() - .domain([40, d3.max(data.value, d => d.level_agreement)]) - .range([margin.bottom + height, margin.top]); - - // Set radius based on evidence value - const radiusScale = d3.scaleLinear() - .domain([d3.min(data.value, d => d.evidence_val), d3.max(data.value, d => d.evidence_val)]) - .range([10, 90]); - - const yAxis = svg.append('g') - .attr("transform", "translate(50, 0)") - .call(d3.axisLeft(yScale)); - - // Add label to y axis - svg.append('text') - .attr("class", "yLabel") - .attr("text-anchor", "start") - .attr("x", 10) - .attr("y", 20) - .text("High level of Agreement"); - - svg.append('text') - .attr("class", "yLabel") - .attr("text-anchor", "start") - .attr("x", 10) - .attr("y", height - 50) - .text("Inconclusive"); - - // Set forces - const forceY = d3.forceY(d => yScale(d.level_agreement)).strength(1); - const forceX = d3.forceX(width / 1.5).strength(0.1); - const forceCollide = d3.forceCollide(d => radiusScale(d.evidence_val) + 2); - const forceManyBody = d3.forceManyBody().strength(-15); - - const bubbles = svg - .selectAll('.bubble') - .data(data.value) - .enter() - .append('circle') - .attr('class', 'bubble') - .attr('r', d => radiusScale(d.evidence_val)) - .style('fill', d => dimensionColors[d.dimension.replace(' ', '')]); - - // Run simulation - simulation = d3.forceSimulation() - .force('x', forceX) - .force('y', forceY) - .force('collide', forceCollide) - .force('charge', forceManyBody) - .nodes(data.value) - .on('tick', ticked) - .alpha(0.2); - - function ticked() { - bubbles - .attr("cx", d => d.x) - .attr("cy", d => d.y); - } + }); + + async function loadDatasets() { + try { + dataSet1.value = await loadData('determinant_uncertainty.csv'); + dataSet2.value = await loadData('indicator_uncertainty.csv'); + console.log('data in') + } catch (error) { + console.error('Error loading datasets', error); } - - function toggleCategory(category) { - isChecked.value[category] = !isChecked.value[category]; - updateChart(); + } + + async function loadData(fileName) { + try { + const data = await d3.csv(publicPath + fileName, d => { + d.level_agreement = +(+d.level_agreement).toFixed(2); + d.evidence_val = +d.evidence_val; + d.sig_value = +d.sig_value; + return d; + }); + return data; + } catch (error) { + console.error(`Error loading data from ${fileName}`, error); + return []; } + } + + function createBeeswarmChart() { + svg = d3 + .select('#beeswarm-chart-container') + .append('svg') + .attr('class', 'beeswarmSvg') + .attr('width', width) + .attr('height', height); + + const yScale = d3.scaleLinear() + .domain([40, d3.max(data.value, d => d.level_agreement)]) + .range([margin.bottom + height, margin.top]); - function updateChart() { - const yScale = d3.scaleLinear() - .domain([30, d3.max(data.value, d => d.level_agreement)]) - .range([margin.bottom + height, margin.top]); - - // Set radius based on evidence value - const radiusScale = d3.scaleLinear() - .domain([d3.min(data.value, d => d.evidence_val), d3.max(data.value, d => d.evidence_val)]) - .range([10, 90]); - - // Filter data based on active categories - const activeCategories = Object.keys(isChecked.value).filter(category => isChecked.value[category]); - const dataPoints = data.value.filter(d => activeCategories.includes(d.dimension.replace(' ', ''))); - - // Update existing bubbles and add new bubbles - const bubbles = svg.selectAll(".bubble") - .data(dataPoints, d => d.id); - - // Remove old bubbles - bubbles.exit().remove(); - - // Update existing bubbles + // Set radius based on evidence value + const radiusScale = d3.scaleLinear() + .domain([d3.min(data.value, d => d.evidence_val), d3.max(data.value, d => d.evidence_val)]) + .range([10, 90]); + + const yAxis = svg.append('g') + .attr("transform", "translate(50, 0)") + .call(d3.axisLeft(yScale)); + + // Add label to y axis + svg.append('text') + .attr("class", "yLabel") + .attr("text-anchor", "start") + .attr("x", 10) + .attr("y", 20) + .text("High level of Agreement"); + + svg.append('text') + .attr("class", "yLabel") + .attr("text-anchor", "start") + .attr("x", 10) + .attr("y", height - 50) + .text("Inconclusive"); + + // Set forces + const forceY = d3.forceY(d => yScale(d.level_agreement)).strength(1); + const forceX = d3.forceX(width / 1.5).strength(0.1); + const forceCollide = d3.forceCollide(d => radiusScale(d.evidence_val) + 2); + const forceManyBody = d3.forceManyBody().strength(-15); + + const bubbles = svg + .selectAll('.bubble') + .data(data.value) + .enter() + .append('circle') + .attr('class', 'bubble') + .attr('r', d => radiusScale(d.evidence_val)) + .style('fill', d => dimensionColors[d.dimension.replace(' ', '')]); + + // Run simulation + simulation = d3.forceSimulation() + .force('x', forceX) + .force('y', forceY) + .force('collide', forceCollide) + .force('charge', forceManyBody) + .nodes(data.value) + .on('tick', ticked) + .alpha(0.2); + + function ticked() { bubbles - .attr('r', d => radiusScale(d.evidence_val)) - .attr('cx', d => d.x) - .attr('cy', d => d.y) - .style('fill', d => dimensionColors[d.dimension.replace(' ', '')]); - - // Add new bubbles - bubbles.enter() - .append('circle') - .attr('class', 'bubble') - .attr('r', d => radiusScale(d.evidence_val)) - .attr('cx', () => Math.random() * (width - margin.left - margin.right) + margin.left) - .attr('cy', d => yScale(d.level_agreement)) - .style('fill', d => dimensionColors[d.dimension.replace(' ', '')]); - - // Restart simulation with new data - simulation.nodes(dataPoints).alpha(0.2).restart(); + .attr("cx", d => d.x) + .attr("cy", d => d.y); } -</script> - - -<style scoped lang="scss"> -$switchWidth: 12rem; -$Demographiccharacteristics: #092836; -$Landtenure: #1b695e; -$Livingconditions: #7a5195; -$Socioeconomicstatus: #2a468f; -$Health: #ef5675; -$Riskperception: #ff764a; -$Exposure: #ffa600; - -#beeswarm-chart-container { - text-align: center; - position: relative; -} - -#beeswarm-chart-container svg { - max-width: 100%; - max-height: 100%; - height: auto; /* Maintain aspect ratio */ - display: inline-block; -} - -.bubble { - stroke: black; - stroke-width: 2px; - fill-opacity: 0.8; -} - -.chart-text { - user-select: none; -} -.yLabel { - font-weight: bold; -} - -.highlight { - color: white; - padding: 0.25px 5px; - border-radius: 10px; - white-space: nowrap; - font-weight: bold; - cursor: pointer; /* Add cursor pointer for better UX */ - transition: all 0.1s; /* Smooth transition for background color and border */ -} - -.highlight:not(.checked) { - background-color: white; - border: 2px solid; -} -.highlight.demographicCharacteristics { - background-color: $Demographiccharacteristics; -} -.highlight.demographicCharacteristics:not(.checked) { - color: $Demographiccharacteristics; - border-color: $Demographiccharacteristics; -} -.highlight.landTenure { - background-color: $Landtenure; -} -.highlight.landTenure:not(.checked) { - color: $Landtenure; - border-color: $Landtenure; -} -.highlight.livingConditions { - background-color: $Livingconditions; -} -.highlight.livingConditions:not(.checked) { - color: $Livingconditions; - border-color: $Livingconditions; -} -.highlight.socioeconomicStatus { - background-color: $Socioeconomicstatus; -} -.highlight.socioeconomicStatus:not(.checked) { - color: $Socioeconomicstatus; - border-color: $Socioeconomicstatus; -} -.highlight.health { - background-color: $Health; -} -.highlight.health:not(.checked) { - color: $Health; - border-color: $Health; -} -.highlight.riskPerception { - background-color: $Riskperception; -} -.highlight.riskPerception:not(.checked) { - color: $Riskperception; - border-color: $Riskperception; -} -.highlight.exposureToStressors { - background-color: $Exposure; -} -.highlight.exposureToStressors:not(.checked) { - color: $Exposure; - border-color: $Exposure; -} -</style> + } + + function toggleCategory(category) { + console.log(`Toggle category called for: ${category}`); + isChecked.value[category] = !isChecked.value[category]; + console.log(`Category toggled: ${category}, new value: ${isChecked.value[category]}`); + updateChart(); + } + + function updateChart() { + console.log('Update chart called'); + const yScale = d3.scaleLinear() + .domain([30, d3.max(data.value, d => d.level_agreement)]) + .range([margin.bottom + height, margin.top]); + + // Set radius based on evidence value + const radiusScale = d3.scaleLinear() + .domain([d3.min(data.value, d => d.evidence_val), d3.max(data.value, d => d.evidence_val)]) + .range([10, 90]); + + // Filter data based on active categories + const activeCategories = Object.keys(isChecked.value).filter(category => isChecked.value[category]); + const dataPoints = data.value.filter(d => activeCategories.includes(d.dimension.replace(' ', ''))); + console.log('Active categories:', activeCategories); + console.log('Filtered data points:', dataPoints); + + // Update existing bubbles and add new bubbles + const bubbles = svg.selectAll(".bubble") + .data(dataPoints, d => d.id); + + // Remove old bubbles + bubbles.exit().remove(); + + // Update existing bubbles + bubbles + .attr('r', d => radiusScale(d.evidence_val)) + .attr('cx', d => d.x) + .attr('cy', d => d.y) + .style('fill', d => dimensionColors[d.dimension.replace(' ', '')]); + + // Add new bubbles + bubbles.enter() + .append('circle') + .attr('class', 'bubble') + .attr('r', d => radiusScale(d.evidence_val)) + .attr('cx', () => Math.random() * (width - margin.left - margin.right) + margin.left) + .attr('cy', d => yScale(d.level_agreement)) + .style('fill', d => dimensionColors[d.dimension.replace(' ', '')]); + + // Restart simulation with new data + simulation.nodes(dataPoints).alpha(0.2).restart(); + } + </script> + + <style scoped lang="scss"> + $switchWidth: 12rem; + $Demographiccharacteristics: #092836; + $Landtenure: #1b695e; + $Livingconditions: #7a5195; + $Socioeconomicstatus: #2a468f; + $Health: #ef5675; + $Riskperception: #ff764a; + $Exposure: #ffa600; + + #beeswarm-chart-container { + text-align: center; + position: relative; + } + + #beeswarm-chart-container svg { + max-width: 100%; + max-height: 100%; + height: auto; /* Maintain aspect ratio */ + display: inline-block; + } + + .bubble { + stroke: black; + stroke-width: 2px; + fill-opacity: 0.8; + } + + .chart-text { + user-select: none; + } + .yLabel { + font-weight: bold; + } + + .highlight { + color: white; + padding: 0.25px 5px; + border-radius: 10px; + white-space: nowrap; + font-weight: bold; + cursor: pointer; /* Add cursor pointer for better UX */ + transition: all 0.1s; /* Smooth transition for background color and border */ + } + + .highlight:not(.checked) { + background-color: white; + border: 2px solid; + } + .highlight.Demographiccharacteristics { + background-color: $Demographiccharacteristics; + } + .highlight.Demographiccharacteristics:not(.checked) { + color: $Demographiccharacteristics; + border-color: $Demographiccharacteristics; + } + .highlight.Landtenure { + background-color: $Landtenure; + } + .highlight.Landtenure:not(.checked) { + color: $Landtenure; + border-color: $Landtenure; + } + .highlight.Livingconditions { + background-color: $Livingconditions; + } + .highlight.Livingconditions:not(.checked) { + color: $Livingconditions; + border-color: $Livingconditions; + } + .highlight.Socioeconomicstatus { + background-color: $Socioeconomicstatus; + } + .highlight.Socioeconomicstatus:not(.checked) { + color: $Socioeconomicstatus; + border-color: $Socioeconomicstatus; + } + .highlight.Health { + background-color: $Health; + } + .highlight.Health:not(.checked) { + color: $Health; + border-color: $Health; + } + .highlight.Riskperception { + background-color: $Riskperception; + } + .highlight.Riskperception:not(.checked) { + color: $Riskperception; + border-color: $Riskperception; + } + .highlight.Exposure { + background-color: $Exposure; + } + .highlight.Exposure:not(.checked) { + color: $Exposure; + border-color: $Exposure; + } + </style> + \ No newline at end of file