<template> <section id="beeswarm"> <div id="text1" class="text-container"> <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')" > demographic characteristics </span>, <span :class="['highlight', 'health', { checked: isChecked.health }]" @click="toggleCategory('health')" > health </span>, <span :class="['highlight', 'livingConditions', { checked: isChecked.livingConditions }]" @click="toggleCategory('livingConditions')" > living conditions </span>, <span :class="['highlight', 'socioeconomicStatus', { checked: isChecked.socioeconomicStatus }]" @click="toggleCategory('socioeconomicStatus')" > socioeconomic status </span>, <span :class="['highlight', 'riskPerception', { checked: isChecked.riskPerception }]" @click="toggleCategory('riskPerception')" > risk perception </span>, <span :class="['highlight', 'landTenure', { checked: isChecked.landTenure }]" @click="toggleCategory('landTenure')" > land tenure </span>, and <span :class="['highlight', 'exposureToStressors', { checked: isChecked.exposureToStressors }]" @click="toggleCategory('exposureToStressors')" > exposure to stressors </span> (like drought or pollution). </p> </div> <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 []; } } 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); } } function toggleCategory(category) { isChecked.value[category] = !isChecked.value[category]; updateChart(); } 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 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.exposureToStressors { background-color: $Exposure; } .highlight.exposureToStressors:not(.checked) { color: $Exposure; border-color: $Exposure; } </style>