Newer
Older
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<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>
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
</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();
} catch (error) {
console.error('Error during component mounting', error);
}
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
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("text-anchor", "start")
.attr("x", 10)
.attr("y", 20)
.text("High level of Agreement");
.attr("text-anchor", "start")
.attr("x", 10)
// 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)
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
.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();
$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;
}
.highlight {
color: white;
padding: 0.25px 5px;
border-radius: 10px;
white-space: nowrap;
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:not(.checked) {
color: $Landtenure;
border-color: $Landtenure;
.highlight.livingConditions {
background-color: $Livingconditions;
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
.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;