<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', 'Exposure', { checked: isChecked.Exposure }]" @click="toggleCategory('Exposure')" > 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, 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); } }); 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); } } 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) { 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>