Skip to content
Snippets Groups Projects
Commit d8d2605f authored by Azadpour, Elmera's avatar Azadpour, Elmera
Browse files

Merge branch 'main' of...

Merge branch 'main' of https://code.usgs.gov/wma/vizlab/vulnerability-indicators into edit-code-json
parents c1ba6504 606f23c2
No related branches found
No related tags found
1 merge request!79edit links for E. Martinez review
<template> <template>
<section id="beeswarm"> <section id="beeswarm">
<div id="text1" class="text-container"> <div id="text1" class="text-container">
<p v-html="t('text.components.chartText.bubbleCheckbox')"></p> <p v-html="bubbleCheckboxText"></p>
<!-- Render the translated labels within the span elements --> <!-- Render the translated labels within the span elements -->
<p> <p>
<span <span
:class="['highlight', 'Demographiccharacteristics', { checked: isChecked.Demographiccharacteristics }]" :class="['highlight', 'Demographiccharacteristics', { checked: isChecked.Demographiccharacteristics }]"
@click="toggleCategory('Demographiccharacteristics')" @click="toggleCategory('Demographiccharacteristics')"
> >
{{ t('text.components.chartText.bubbleLabels.Demographiccharacteristics') }} {{ demographicText }}
</span>, </span>,
<span <span
:class="['highlight', 'Health', { checked: isChecked.Health }]" :class="['highlight', 'Health', { checked: isChecked.Health }]"
@click="toggleCategory('Health')" @click="toggleCategory('Health')"
> >
{{ t('text.components.chartText.bubbleLabels.Health') }} {{ healthText }}
</span>, </span>,
<span <span
:class="['highlight', 'Livingconditions', { checked: isChecked.Livingconditions }]" :class="['highlight', 'Livingconditions', { checked: isChecked.Livingconditions }]"
@click="toggleCategory('Livingconditions')" @click="toggleCategory('Livingconditions')"
> >
{{ t('text.components.chartText.bubbleLabels.Livingconditions') }} {{ livingConditionsText }}
</span>, </span>,
<span <span
:class="['highlight', 'Socioeconomicstatus', { checked: isChecked.Socioeconomicstatus }]" :class="['highlight', 'Socioeconomicstatus', { checked: isChecked.Socioeconomicstatus }]"
@click="toggleCategory('Socioeconomicstatus')" @click="toggleCategory('Socioeconomicstatus')"
> >
{{ t('text.components.chartText.bubbleLabels.Socioeconomicstatus') }} {{ socioeconomicStatusText }}
</span>, </span>,
<span <span
:class="['highlight', 'Riskperception', { checked: isChecked.Riskperception }]" :class="['highlight', 'Riskperception', { checked: isChecked.Riskperception }]"
@click="toggleCategory('Riskperception')" @click="toggleCategory('Riskperception')"
> >
{{ t('text.components.chartText.bubbleLabels.Riskperception') }} {{ riskPerceptionText }}
</span>, </span>,
<span <span
:class="['highlight', 'Landtenure', { checked: isChecked.Landtenure }]" :class="['highlight', 'Landtenure', { checked: isChecked.Landtenure }]"
@click="toggleCategory('Landtenure')" @click="toggleCategory('Landtenure')"
> >
{{ t('text.components.chartText.bubbleLabels.Landtenure') }} {{ landTenureText }}
</span>, & </span>, &
<span <span
:class="['highlight', 'Exposure', { checked: isChecked.Exposure }]" :class="['highlight', 'Exposure', { checked: isChecked.Exposure }]"
@click="toggleCategory('Exposure')" @click="toggleCategory('Exposure')"
> >
{{ t('text.components.chartText.bubbleLabels.Exposure') }} {{ exposureText }}
</span> </span>
&nbsp; &nbsp;
<span v-html="t('text.components.chartText.bubbleCheckboxEnd')" /> <span v-html="bubbleCheckboxEndText" />
</p> </p>
</div>
<div id="text2" class="text-container tooltip-width">
<div id="tooltip" class="tooltip"><p v-html="t('text.components.chartText.bubbleText')"></p></div>
<em><p v-html="t('text.components.chartText.bubbleLegend')"></p></em>
</div> </div>
<div id="beeswarm-chart-container"> <div id="text2" class="text-container tooltip-width">
<div id="tooltip" class="tooltip">
<p v-html="bubbleTooltipText"></p>
</div>
<em><p v-html="bubbleLegendText"></p></em>
</div> </div>
<div id="beeswarm-chart-container"></div>
<div id="text2" class="text-container tooltip-width"> <div id="text2" class="text-container tooltip-width">
<em><p v-html="t('text.components.chartText.bubbleYaxis')"></p></em> <em><p v-html="bubbleYAxisText"></p></em>
</div> </div>
</section> </section>
</template> </template>
<script setup> <script setup>
import { onMounted, ref } from "vue"; import { ref, computed, onMounted } from "vue";
import * as d3 from 'd3'; import * as d3 from 'd3';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
const { t } = useI18n(); const { t, locale } = useI18n();
const userLang = navigator.language || navigator.userLanguage; const userLang = navigator.language || navigator.userLanguage;
const isSpanish = userLang.startsWith('es'); const isSpanish = userLang.startsWith('es');
...@@ -101,6 +103,35 @@ const dimensionColors = { ...@@ -101,6 +103,35 @@ const dimensionColors = {
Exposure: "#FFA600" Exposure: "#FFA600"
}; };
// Computed properties for translations
const bubbleCheckboxText = computed(() => t('text.components.chartText.bubbleCheckbox'));
const bubbleCheckboxEndText = computed(() => t('text.components.chartText.bubbleCheckboxEnd'));
const bubbleTooltipText = computed(() => t('text.components.chartText.bubbleText'));
const bubbleLegendText = computed(() => t('text.components.chartText.bubbleLegend'));
const bubbleYAxisText = computed(() => t('text.components.chartText.bubbleYaxis'));
// Labels for each category
const demographicText = computed(() => t('text.components.chartText.bubbleLabels.Demographiccharacteristics'));
const healthText = computed(() => t('text.components.chartText.bubbleLabels.Health'));
const livingConditionsText = computed(() => t('text.components.chartText.bubbleLabels.Livingconditions'));
const socioeconomicStatusText = computed(() => t('text.components.chartText.bubbleLabels.Socioeconomicstatus'));
const riskPerceptionText = computed(() => t('text.components.chartText.bubbleLabels.Riskperception'));
const landTenureText = computed(() => t('text.components.chartText.bubbleLabels.Landtenure'));
const exposureText = computed(() => t('text.components.chartText.bubbleLabels.Exposure'));
// Computed function for generating tooltip text
const generateTooltipText = (dataPoint) => {
return computed(() => {
const translatedDeterminant = locale.value === 'es' ? dataPoint.determinant_es : dataPoint.determinant_wrapped;
const determinant = `<strong>${translatedDeterminant}</strong>`;
const count = dataPoint.evidence_val;
const studyLabel = dataPoint.evidence_val === 1 ? t('text.components.chartText.singleStudy') : t('text.components.chartText.multipleStudies');
const appeared = t('text.components.chartText.appeared');
return `${determinant} ${appeared} ${count} ${studyLabel}`;
});
};
const patternId = 'pattern-stripe'; const patternId = 'pattern-stripe';
// bar chart patterning and fills // bar chart patterning and fills
...@@ -333,25 +364,9 @@ function handleMouseOver(event, d) { ...@@ -333,25 +364,9 @@ function handleMouseOver(event, d) {
/// Select the tooltip element /// Select the tooltip element
const tooltip = d3.select('#tooltip'); const tooltip = d3.select('#tooltip');
const tooltipTemplate = "{determinant} {appeared} {count} {studyLabel}"
// Generate the tooltip text using the computed property
function getTranslatedDeterminant(d) { const tooltipText = generateTooltipText(d).value;
return isSpanish ? d.determinant_es : d.determinant_wrapped;
}
const translatedDeterminant = getTranslatedDeterminant(d);
// Dynamic values
const determinant = `<strong>${translatedDeterminant}</strong>`;
const count = d.evidence_val;
const studyLabel = d.evidence_val === 1 ? t('text.components.chartText.singleStudy') : t('text.components.chartText.multipleStudies');
const appeared = t('text.components.chartText.appeared');
// Directly construct the tooltip text using a template literal
const tooltipText = tooltipTemplate
.replace('{determinant}', determinant)
.replace('{appeared}', appeared)
.replace('{count}', count)
.replace('{studyLabel}', studyLabel);
// Update the tooltip with the constructed text // Update the tooltip with the constructed text
tooltip.html('') tooltip.html('')
......
...@@ -46,12 +46,12 @@ ...@@ -46,12 +46,12 @@
</template> </template>
<script setup> <script setup>
import { onMounted, ref } from 'vue'; import { computed, onMounted, ref, watch } from 'vue';
import { isMobile } from 'mobile-device-detect'; import { isMobile } from 'mobile-device-detect';
import * as d3 from 'd3'; import * as d3 from 'd3';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
const { t } = useI18n(); const { t, locale } = useI18n();
// global variables // global variables
const publicPath = import.meta.env.BASE_URL; const publicPath = import.meta.env.BASE_URL;
...@@ -85,7 +85,6 @@ let linkWidthScale, nodeRadiusScale; ...@@ -85,7 +85,6 @@ let linkWidthScale, nodeRadiusScale;
onMounted(() => { onMounted(() => {
const userLang = navigator.language || navigator.userLanguage; const userLang = navigator.language || navigator.userLanguage;
const isSpanish = userLang.startsWith('es'); // Check if the language is Spanish
loadData().then(() => { loadData().then(() => {
if (data.value.length > 0) { if (data.value.length > 0) {
...@@ -97,10 +96,10 @@ onMounted(() => { ...@@ -97,10 +96,10 @@ onMounted(() => {
.domain([0, d3.max(data.value, d => d.evidence_val)]) .domain([0, d3.max(data.value, d => d.evidence_val)])
.range([2, 9]); .range([2, 9]);
transformData(isSpanish) transformData()
.then((result) => { .then((result) => {
calculateTextSize(); calculateTextSize();
createDendrogram(result, isSpanish); createDendrogram(result);
}) })
.catch((error) => { .catch((error) => {
console.error('Error transforming data:', error); console.error('Error transforming data:', error);
...@@ -109,6 +108,8 @@ onMounted(() => { ...@@ -109,6 +108,8 @@ onMounted(() => {
}); });
}) })
async function loadData() { async function loadData() {
try { try {
const jsonData = await d3.json(publicPath + 'indicator_uncertainty.json'); const jsonData = await d3.json(publicPath + 'indicator_uncertainty.json');
...@@ -130,8 +131,10 @@ function calculateTextSize() { ...@@ -130,8 +131,10 @@ function calculateTextSize() {
} }
} }
function transformData(isSpanish) { async function transformData() {
return new Promise((resolve) => { return new Promise((resolve) => {
const isSpanish = locale.value === 'es'; // Determine if the current locale is Spanish
const nestedData = data.value.reduce((acc, item) => { const nestedData = data.value.reduce((acc, item) => {
const dimensionName = isSpanish ? item.dimension_es : item.dimension; const dimensionName = isSpanish ? item.dimension_es : item.dimension;
let dimensionObj = acc.find((d) => d.name === dimensionName); let dimensionObj = acc.find((d) => d.name === dimensionName);
...@@ -151,7 +154,7 @@ function transformData(isSpanish) { ...@@ -151,7 +154,7 @@ function transformData(isSpanish) {
determinantObj.children.push({ determinantObj.children.push({
name: indicatorName, name: indicatorName,
evidence_val: item.evidence_val, evidence_val: item.evidence_val,
level_agreement: item.level_agreement level_agreement: item.level_agreement,
}); });
return acc; return acc;
...@@ -167,7 +170,7 @@ function transformData(isSpanish) { ...@@ -167,7 +170,7 @@ function transformData(isSpanish) {
return node.totalEvidence; return node.totalEvidence;
}; };
nestedData.forEach(dimension => calculateTotalEvidence(dimension)); nestedData.forEach((dimension) => calculateTotalEvidence(dimension));
const result = { name: ' ', children: nestedData }; const result = { name: ' ', children: nestedData };
resolve(result); resolve(result);
...@@ -175,8 +178,15 @@ function transformData(isSpanish) { ...@@ -175,8 +178,15 @@ function transformData(isSpanish) {
} }
watch(() => locale.value, async () => {
// Clear the existing SVG before creating a new dendrogram
d3.select('#dendrogram-container').select('svg').remove();
const result = await transformData();
createDendrogram(result);
});
function createDendrogram(result, isSpanish) { function createDendrogram(result) {
const width = 1000; const width = 1000;
const height = 800; const height = 800;
const marginTop = 40; const marginTop = 40;
...@@ -237,7 +247,7 @@ root.descendants().forEach(d => { ...@@ -237,7 +247,7 @@ root.descendants().forEach(d => {
.attr('cursor', 'pointer') .attr('cursor', 'pointer')
.attr('pointer-events', 'all'); .attr('pointer-events', 'all');
function update(event, source) { function update(event, source) {
const duration = 1200; const duration = 1200;
const nodes = root.descendants().reverse(); const nodes = root.descendants().reverse();
const links = root.links().filter(link => link.source.depth !== 0); const links = root.links().filter(link => link.source.depth !== 0);
...@@ -327,8 +337,8 @@ root.descendants().forEach(d => { ...@@ -327,8 +337,8 @@ root.descendants().forEach(d => {
nodeEnter.append('circle') nodeEnter.append('circle')
.filter(d => d.depth !== 1) .filter(d => d.depth !== 1)
.attr('r', d => getNodeRadius(d)) .attr('r', d => getNodeRadius(d))
.attr('fill', d => getNodeColor(d, isSpanish)) .attr('fill', d => getNodeColor(d))
.attr('stroke', d => getNodeColor(d, isSpanish)) .attr('stroke', d => getNodeColor(d))
.attr('stroke-width', 2); .attr('stroke-width', 2);
const wrapWidth = mobileView ? 300 : 300; const wrapWidth = mobileView ? 300 : 300;
...@@ -344,10 +354,10 @@ root.descendants().forEach(d => { ...@@ -344,10 +354,10 @@ root.descendants().forEach(d => {
}) })
.attr('text-anchor', d => d.depth === 1 ? 'end' : 'start') .attr('text-anchor', d => d.depth === 1 ? 'end' : 'start')
.text(d => d.depth === 3 ? `${d.data.name} (${d.data.evidence_val})` : d.data.name) .text(d => d.depth === 3 ? `${d.data.name} (${d.data.evidence_val})` : d.data.name)
.style('fill', d => getNodeColor(d, isSpanish)); .style('fill', d => getNodeColor(d));
// function to add line break to dimension labels after 2nd word if spanish, after first word if english // function to add line break to dimension labels after 2nd word if spanish, after first word if english
function wrapAfterSecondWord(textSelection, isSpanish) { function wrapAfterSecondWord(textSelection) {
textSelection.each(function() { textSelection.each(function() {
const textElement = d3.select(this); const textElement = d3.select(this);
const text = textElement.text(); // Get the original text const text = textElement.text(); // Get the original text
...@@ -356,7 +366,7 @@ root.descendants().forEach(d => { ...@@ -356,7 +366,7 @@ root.descendants().forEach(d => {
const words = text.split(/\s+/); const words = text.split(/\s+/);
// For English: Break after 1st word, for Spanish: Break after 2nd word // For English: Break after 1st word, for Spanish: Break after 2nd word
const breakAfter = isSpanish ? 2 : 1; const breakAfter = locale.value === 'es' ? 2 : 1;
// If there are more than 2 words, wrap after the second word // If there are more than 2 words, wrap after the second word
if (words.length > breakAfter) { if (words.length > breakAfter) {
...@@ -374,11 +384,11 @@ root.descendants().forEach(d => { ...@@ -374,11 +384,11 @@ root.descendants().forEach(d => {
.attr('dy', '1.2em') .attr('dy', '1.2em')
.text(words.slice(breakAfter).join(' ')); .text(words.slice(breakAfter).join(' '));
} }
}); });
} }
textElements.filter(d => d.depth === 1) textElements.filter(d => d.depth === 1)
.call(wrapAfterSecondWord, isSpanish) .call(wrapAfterSecondWord)
.clone(true).lower() .clone(true).lower()
.attr('stroke-linejoin', 'round') .attr('stroke-linejoin', 'round')
.attr('stroke-width', 3) .attr('stroke-width', 3)
...@@ -427,12 +437,12 @@ root.descendants().forEach(d => { ...@@ -427,12 +437,12 @@ root.descendants().forEach(d => {
return diagonal({ source: o, target: o }); return diagonal({ source: o, target: o });
}) })
.attr('stroke-width', getLinkWidth) .attr('stroke-width', getLinkWidth)
.attr('stroke', d => getLinkColor(d, isSpanish)); .attr('stroke', d => getLinkColor(d));
link.merge(linkEnter).transition(transition) link.merge(linkEnter).transition(transition)
.attr('d', diagonal) .attr('d', diagonal)
.attr('stroke-width', getLinkWidth) .attr('stroke-width', getLinkWidth)
.attr('stroke', d => getLinkColor(d, isSpanish)); .attr('stroke', d => getLinkColor(d));
link.exit().transition(transition).remove() link.exit().transition(transition).remove()
.attr('d', () => { .attr('d', () => {
...@@ -491,42 +501,38 @@ function wrap(text, width) { ...@@ -491,42 +501,38 @@ function wrap(text, width) {
}); });
} }
function getNodeColor(d, isSpanish) { function getNodeColor(d) {
// Choose the correct color map based on the language // Choose the correct color map based on the current locale
const colorMap = isSpanish ? dimensionColors_es : dimensionColors; const colorMap = locale.value === 'es' ? dimensionColors_es : dimensionColors;
let normalizedDimension;
if (d.depth === 1) { if (d.depth === 1) {
const normalizedDimension = normalizeDimensionName(d.data.name); normalizedDimension = normalizeDimensionName(d.data.name);
return colorMap[normalizedDimension] || 'transaprent'; // Fallback to grey if not found
} else if (d.parent && d.parent.depth === 1) { } else if (d.parent && d.parent.depth === 1) {
const normalizedDimension = normalizeDimensionName(d.parent.data.name); normalizedDimension = normalizeDimensionName(d.parent.data.name);
return colorMap[normalizedDimension] || '#ccc';
} else if (d.parent && d.parent.parent && d.parent.parent.depth === 1) { } else if (d.parent && d.parent.parent && d.parent.parent.depth === 1) {
const normalizedDimension = normalizeDimensionName(d.parent.parent.data.name); normalizedDimension = normalizeDimensionName(d.parent.parent.data.name);
return colorMap[normalizedDimension] || '#ccc';
} else {
return 'transparent'; // Fallback color
} }
// Return the color from the map, defaulting to '#ccc' if not found
return colorMap[normalizedDimension] || '#ccc';
} }
function getLinkColor(d, isSpanish) { function getLinkColor(d) {
const colorMap = isSpanish ? dimensionColors_es : dimensionColors; const colorMap = locale.value === 'es' ? dimensionColors_es : dimensionColors;
let normalizedDimension;
if (d.target.depth === 3) { if (d.target.depth === 3) {
const normalizedDimension = normalizeDimensionName(d.target.parent.parent.data.name); normalizedDimension = normalizeDimensionName(d.target.parent.parent.data.name);
return colorMap[normalizedDimension] || '#ccc';
} else if (d.target.depth === 2) { } else if (d.target.depth === 2) {
const normalizedDimension = normalizeDimensionName(d.target.parent.data.name); normalizedDimension = normalizeDimensionName(d.target.parent.data.name);
return colorMap[normalizedDimension] || '#ccc';
} else if (d.target.depth === 1) { } else if (d.target.depth === 1) {
const normalizedDimension = normalizeDimensionName(d.target.data.name); normalizedDimension = normalizeDimensionName(d.target.data.name);
return colorMap[normalizedDimension] || '#ccc';
} else {
return 'red'; // Fallback color for other cases
} }
}
// Return the color from the map, defaulting to '#ccc' if not found
return colorMap[normalizedDimension] || '#ccc';
}
function getLinkWidth(d) { function getLinkWidth(d) {
if (d.target.depth === 3) { if (d.target.depth === 3) {
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter, useRoute } from 'vue-router'; import { useRouter, useRoute } from 'vue-router';
...@@ -21,7 +21,7 @@ const route = useRoute(); ...@@ -21,7 +21,7 @@ const route = useRoute();
// Create a reactive variable to track the selected language // Create a reactive variable to track the selected language
const selectedLanguage = ref(locale.value); const selectedLanguage = ref(locale.value);
function handleLanguageChange(event) { function handleLanguageChange() {
switchLanguage(selectedLanguage.value); switchLanguage(selectedLanguage.value);
} }
...@@ -38,6 +38,18 @@ function switchLanguage(lang) { ...@@ -38,6 +38,18 @@ function switchLanguage(lang) {
}); });
} }
} }
// Watch for changes in route language parameter to sync with selectedLanguage
watch(
() => route.params.lang,
(newLang) => {
if (newLang && locale.value !== newLang) {
locale.value = newLang;
selectedLanguage.value = newLang;
}
},
{ immediate: true }
);
</script> </script>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment