diff --git a/src/components/BeeswarmChart.vue b/src/components/BeeswarmChart.vue index 96ac51d71d71374c7eca7c604d5f7dd77fc4bb50..682eb1db802c3865a74c80e42a7640bad86ed67f 100644 --- a/src/components/BeeswarmChart.vue +++ b/src/components/BeeswarmChart.vue @@ -1,73 +1,75 @@ <template> <section id="beeswarm"> <div id="text1" class="text-container"> - <p v-html="t('text.components.chartText.bubbleCheckbox')"></p> - <!-- Render the translated labels within the span elements --> - <p> - <span - :class="['highlight', 'Demographiccharacteristics', { checked: isChecked.Demographiccharacteristics }]" - @click="toggleCategory('Demographiccharacteristics')" - > - {{ t('text.components.chartText.bubbleLabels.Demographiccharacteristics') }} - </span>, - <span - :class="['highlight', 'Health', { checked: isChecked.Health }]" - @click="toggleCategory('Health')" - > - {{ t('text.components.chartText.bubbleLabels.Health') }} - </span>, - <span - :class="['highlight', 'Livingconditions', { checked: isChecked.Livingconditions }]" - @click="toggleCategory('Livingconditions')" - > - {{ t('text.components.chartText.bubbleLabels.Livingconditions') }} - </span>, - <span - :class="['highlight', 'Socioeconomicstatus', { checked: isChecked.Socioeconomicstatus }]" - @click="toggleCategory('Socioeconomicstatus')" - > - {{ t('text.components.chartText.bubbleLabels.Socioeconomicstatus') }} - </span>, - <span - :class="['highlight', 'Riskperception', { checked: isChecked.Riskperception }]" - @click="toggleCategory('Riskperception')" - > - {{ t('text.components.chartText.bubbleLabels.Riskperception') }} - </span>, - <span - :class="['highlight', 'Landtenure', { checked: isChecked.Landtenure }]" - @click="toggleCategory('Landtenure')" - > - {{ t('text.components.chartText.bubbleLabels.Landtenure') }} - </span>, & - <span - :class="['highlight', 'Exposure', { checked: isChecked.Exposure }]" - @click="toggleCategory('Exposure')" - > - {{ t('text.components.chartText.bubbleLabels.Exposure') }} - </span> - - <span v-html="t('text.components.chartText.bubbleCheckboxEnd')" /> - </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> + <p v-html="bubbleCheckboxText"></p> + <!-- Render the translated labels within the span elements --> + <p> + <span + :class="['highlight', 'Demographiccharacteristics', { checked: isChecked.Demographiccharacteristics }]" + @click="toggleCategory('Demographiccharacteristics')" + > + {{ demographicText }} + </span>, + <span + :class="['highlight', 'Health', { checked: isChecked.Health }]" + @click="toggleCategory('Health')" + > + {{ healthText }} + </span>, + <span + :class="['highlight', 'Livingconditions', { checked: isChecked.Livingconditions }]" + @click="toggleCategory('Livingconditions')" + > + {{ livingConditionsText }} + </span>, + <span + :class="['highlight', 'Socioeconomicstatus', { checked: isChecked.Socioeconomicstatus }]" + @click="toggleCategory('Socioeconomicstatus')" + > + {{ socioeconomicStatusText }} + </span>, + <span + :class="['highlight', 'Riskperception', { checked: isChecked.Riskperception }]" + @click="toggleCategory('Riskperception')" + > + {{ riskPerceptionText }} + </span>, + <span + :class="['highlight', 'Landtenure', { checked: isChecked.Landtenure }]" + @click="toggleCategory('Landtenure')" + > + {{ landTenureText }} + </span>, & + <span + :class="['highlight', 'Exposure', { checked: isChecked.Exposure }]" + @click="toggleCategory('Exposure')" + > + {{ exposureText }} + </span> + + <span v-html="bubbleCheckboxEndText" /> + </p> </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 id="beeswarm-chart-container"></div> <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> </section> </template> + <script setup> -import { onMounted, ref } from "vue"; +import { ref, computed, onMounted } from "vue"; import * as d3 from 'd3'; import { useI18n } from 'vue-i18n'; -const { t } = useI18n(); +const { t, locale } = useI18n(); const userLang = navigator.language || navigator.userLanguage; const isSpanish = userLang.startsWith('es'); @@ -101,6 +103,35 @@ const dimensionColors = { 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'; // bar chart patterning and fills @@ -333,25 +364,9 @@ function handleMouseOver(event, d) { /// Select the tooltip element const tooltip = d3.select('#tooltip'); - const tooltipTemplate = "{determinant} {appeared} {count} {studyLabel}" - - function getTranslatedDeterminant(d) { - 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); + + // Generate the tooltip text using the computed property + const tooltipText = generateTooltipText(d).value; // Update the tooltip with the constructed text tooltip.html('') diff --git a/src/components/InteractiveDendrogram.vue b/src/components/InteractiveDendrogram.vue index dab70432f706f848b646d2bbec1a86328dde9233..5ef6741076a7d3cbbe1e064adf1b7a736aee0633 100644 --- a/src/components/InteractiveDendrogram.vue +++ b/src/components/InteractiveDendrogram.vue @@ -46,12 +46,12 @@ </template> <script setup> -import { onMounted, ref } from 'vue'; +import { computed, onMounted, ref, watch } from 'vue'; import { isMobile } from 'mobile-device-detect'; import * as d3 from 'd3'; import { useI18n } from 'vue-i18n'; -const { t } = useI18n(); +const { t, locale } = useI18n(); // global variables const publicPath = import.meta.env.BASE_URL; @@ -85,7 +85,6 @@ let linkWidthScale, nodeRadiusScale; onMounted(() => { const userLang = navigator.language || navigator.userLanguage; - const isSpanish = userLang.startsWith('es'); // Check if the language is Spanish loadData().then(() => { if (data.value.length > 0) { @@ -97,10 +96,10 @@ onMounted(() => { .domain([0, d3.max(data.value, d => d.evidence_val)]) .range([2, 9]); - transformData(isSpanish) + transformData() .then((result) => { calculateTextSize(); - createDendrogram(result, isSpanish); + createDendrogram(result); }) .catch((error) => { console.error('Error transforming data:', error); @@ -109,6 +108,8 @@ onMounted(() => { }); }) + + async function loadData() { try { const jsonData = await d3.json(publicPath + 'indicator_uncertainty.json'); @@ -130,8 +131,10 @@ function calculateTextSize() { } } -function transformData(isSpanish) { +async function transformData() { return new Promise((resolve) => { + const isSpanish = locale.value === 'es'; // Determine if the current locale is Spanish + const nestedData = data.value.reduce((acc, item) => { const dimensionName = isSpanish ? item.dimension_es : item.dimension; let dimensionObj = acc.find((d) => d.name === dimensionName); @@ -151,7 +154,7 @@ function transformData(isSpanish) { determinantObj.children.push({ name: indicatorName, evidence_val: item.evidence_val, - level_agreement: item.level_agreement + level_agreement: item.level_agreement, }); return acc; @@ -167,7 +170,7 @@ function transformData(isSpanish) { return node.totalEvidence; }; - nestedData.forEach(dimension => calculateTotalEvidence(dimension)); + nestedData.forEach((dimension) => calculateTotalEvidence(dimension)); const result = { name: ' ', children: nestedData }; resolve(result); @@ -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 height = 800; const marginTop = 40; @@ -237,7 +247,7 @@ root.descendants().forEach(d => { .attr('cursor', 'pointer') .attr('pointer-events', 'all'); - function update(event, source) { + function update(event, source) { const duration = 1200; const nodes = root.descendants().reverse(); const links = root.links().filter(link => link.source.depth !== 0); @@ -327,8 +337,8 @@ root.descendants().forEach(d => { nodeEnter.append('circle') .filter(d => d.depth !== 1) .attr('r', d => getNodeRadius(d)) - .attr('fill', d => getNodeColor(d, isSpanish)) - .attr('stroke', d => getNodeColor(d, isSpanish)) + .attr('fill', d => getNodeColor(d)) + .attr('stroke', d => getNodeColor(d)) .attr('stroke-width', 2); const wrapWidth = mobileView ? 300 : 300; @@ -344,10 +354,10 @@ root.descendants().forEach(d => { }) .attr('text-anchor', d => d.depth === 1 ? 'end' : 'start') .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 wrapAfterSecondWord(textSelection, isSpanish) { + function wrapAfterSecondWord(textSelection) { textSelection.each(function() { const textElement = d3.select(this); const text = textElement.text(); // Get the original text @@ -356,7 +366,7 @@ root.descendants().forEach(d => { const words = text.split(/\s+/); // 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 (words.length > breakAfter) { @@ -374,11 +384,11 @@ root.descendants().forEach(d => { .attr('dy', '1.2em') .text(words.slice(breakAfter).join(' ')); } - }); - } + }); + } textElements.filter(d => d.depth === 1) - .call(wrapAfterSecondWord, isSpanish) + .call(wrapAfterSecondWord) .clone(true).lower() .attr('stroke-linejoin', 'round') .attr('stroke-width', 3) @@ -427,12 +437,12 @@ root.descendants().forEach(d => { return diagonal({ source: o, target: o }); }) .attr('stroke-width', getLinkWidth) - .attr('stroke', d => getLinkColor(d, isSpanish)); + .attr('stroke', d => getLinkColor(d)); link.merge(linkEnter).transition(transition) .attr('d', diagonal) .attr('stroke-width', getLinkWidth) - .attr('stroke', d => getLinkColor(d, isSpanish)); + .attr('stroke', d => getLinkColor(d)); link.exit().transition(transition).remove() .attr('d', () => { @@ -491,42 +501,38 @@ function wrap(text, width) { }); } -function getNodeColor(d, isSpanish) { - // Choose the correct color map based on the language - const colorMap = isSpanish ? dimensionColors_es : dimensionColors; +function getNodeColor(d) { + // Choose the correct color map based on the current locale + const colorMap = locale.value === 'es' ? dimensionColors_es : dimensionColors; + let normalizedDimension; if (d.depth === 1) { - const normalizedDimension = normalizeDimensionName(d.data.name); - return colorMap[normalizedDimension] || 'transaprent'; // Fallback to grey if not found + normalizedDimension = normalizeDimensionName(d.data.name); } else if (d.parent && d.parent.depth === 1) { - const normalizedDimension = normalizeDimensionName(d.parent.data.name); - return colorMap[normalizedDimension] || '#ccc'; + normalizedDimension = normalizeDimensionName(d.parent.data.name); } else if (d.parent && d.parent.parent && d.parent.parent.depth === 1) { - const normalizedDimension = normalizeDimensionName(d.parent.parent.data.name); - return colorMap[normalizedDimension] || '#ccc'; - } else { - return 'transparent'; // Fallback color + normalizedDimension = normalizeDimensionName(d.parent.parent.data.name); } + + // Return the color from the map, defaulting to '#ccc' if not found + return colorMap[normalizedDimension] || '#ccc'; } -function getLinkColor(d, isSpanish) { - const colorMap = isSpanish ? dimensionColors_es : dimensionColors; +function getLinkColor(d) { + const colorMap = locale.value === 'es' ? dimensionColors_es : dimensionColors; + let normalizedDimension; if (d.target.depth === 3) { - const normalizedDimension = normalizeDimensionName(d.target.parent.parent.data.name); - return colorMap[normalizedDimension] || '#ccc'; + normalizedDimension = normalizeDimensionName(d.target.parent.parent.data.name); } else if (d.target.depth === 2) { - const normalizedDimension = normalizeDimensionName(d.target.parent.data.name); - return colorMap[normalizedDimension] || '#ccc'; + normalizedDimension = normalizeDimensionName(d.target.parent.data.name); } else if (d.target.depth === 1) { - const normalizedDimension = normalizeDimensionName(d.target.data.name); - return colorMap[normalizedDimension] || '#ccc'; - } else { - return 'red'; // Fallback color for other cases + normalizedDimension = normalizeDimensionName(d.target.data.name); } -} - + // Return the color from the map, defaulting to '#ccc' if not found + return colorMap[normalizedDimension] || '#ccc'; +} function getLinkWidth(d) { if (d.target.depth === 3) { diff --git a/src/components/LanguageButton.vue b/src/components/LanguageButton.vue index 50b493accf1ef427b55b766790c508fccd8c3912..87e8321de2bf1207233b88e43887ca8a52540efa 100644 --- a/src/components/LanguageButton.vue +++ b/src/components/LanguageButton.vue @@ -10,7 +10,7 @@ <script setup> -import { ref } from 'vue'; +import { ref, watch } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRouter, useRoute } from 'vue-router'; @@ -21,7 +21,7 @@ const route = useRoute(); // Create a reactive variable to track the selected language const selectedLanguage = ref(locale.value); -function handleLanguageChange(event) { +function handleLanguageChange() { switchLanguage(selectedLanguage.value); } @@ -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>