Skip to content
Snippets Groups Projects
BeeswarmChart.vue 18 KiB
Newer Older
  • Learn to ignore specific revisions
  • Cee Nell's avatar
    Cee Nell committed
    <template>
    
    Cee Nell's avatar
    Cee Nell committed
      <section id="beeswarm">
        <div id="text1" class="text-container">
          <p>
    
            Everyone needs access to clean water. The gap between water security and <span class="tooltip-group"><span class="tooltip-span"> water insecurity </span><span class="tooltiptext" > Populations cannot maintain access to adequate quantities of water at an acceptable quality to sustain livelihoods, development, and human and ecosystem health.</span></span> can be the difference between a daydream and a nightmare. People may be more vulnerable to water insecurity due to  
    
    Cee Nell's avatar
    Cee Nell committed
            <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).
    
    Cee Nell's avatar
    Cee Nell committed
          </p>
        </div>
        <div id="beeswarm-chart-container">
        </div>
        <div id="text2" class="text-container tooltip-width">
    
    Cee Nell's avatar
    Cee Nell committed
          <div id="tooltip" class="tooltip">Interact with the chart to explore evidence<br>for social vulnerability determinants.</div>
    
    Azadpour, Elmera's avatar
    Azadpour, Elmera committed
          <em>Many social vulnerability determinants have been studied. Some show positive <span class="legend-box positive"></span> relationships with water insecurity, some negative <span class="legend-box negative"></span>, and others unknown <span class="legend-box unknown"></span> <a href='https://www.sciencebase.gov/catalog/item/63f79d49d34e4f7eda456572' target='_blank'>(Hines and others, 2023)</a>. The y-axis of the chart represents the level of agreement among models where increased <b>consensus</b> indicates a majority of models using the selected determinant recorded the same direction of influence on conditions of water insecurity and <b>inconclusive</b> indicates models using the selected determinant did not record the same direction of influence on conditions of water insecurity.</em>
    
    Cee Nell's avatar
    Cee Nell committed
        </div>
    
    Cee Nell's avatar
    Cee Nell committed
      </section>
    </template>
    
    <script setup>
    import { onMounted, ref } from "vue";
    import * as d3 from 'd3';
    
    Cee Nell's avatar
    Cee Nell committed
    import { isMobile } from 'mobile-device-detect';
    
    Cee Nell's avatar
    Cee Nell committed
    
    // 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 = 600;
    const width = 800;
    
    Cee Nell's avatar
    Cee Nell committed
    const margin = { top: 40, right: 20, bottom: 40, left: 40 };
    
    Cee Nell's avatar
    Cee Nell committed
    
    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"
    };
    
    const patternId = 'pattern-stripe';
    
    // bar chart patterning and fills
    const legendData = [
    
    Cee Nell's avatar
    Cee Nell committed
      { name: 'Positive', color: dimensionColors['Demographiccharacteristics'] },
      { name: 'Negative', color: 'white', stroke: dimensionColors['Demographiccharacteristics'] },
      { name: 'Unknown', pattern: true, color: `url(#pattern-stripe)`, stroke: dimensionColors['Demographiccharacteristics'] }
    ];
    
    // create legend for stack bar charts
    function createPattern() {
      const svgDefs = d3.select('body')
    
    Cee Nell's avatar
    Cee Nell committed
        .append('svg')
    
    Cee Nell's avatar
    Cee Nell committed
        .attr('width', 0)
        .attr('height', 0)
        .append('defs');
    
    Cee Nell's avatar
    Cee Nell committed
    
    
    Cee Nell's avatar
    Cee Nell committed
      const pattern = svgDefs.append('pattern')
    
        .attr('id', patternId)
    
    Cee Nell's avatar
    Cee Nell committed
        .attr('patternUnits', 'userSpaceOnUse')
        .attr('width', 8)
        .attr('height', 8);
    
      pattern.append('rect')
        .attr('width', 8)
        .attr('height', 8)
        .attr('fill', 'white');
    
      pattern.append('path')
        .attr('d', 'M-2,2 l4,-4 M0,8 l8,-8 M6,10 l4,-4')
        .attr('stroke', dimensionColors['Demographiccharacteristics'])
        .attr('stroke-width', 3);
    
    }
    function createLegend() {
      const keyW = 65;
      const keyH = 12;
    
      createPattern();
    
    Cee Nell's avatar
    Cee Nell committed
    
      legendData.forEach(d => {
        const legend = d3.select(`.${d.name.toLowerCase()}`)
          .append('svg')
          .attr('width', keyW + 5)
          .attr('height', keyH);
    
        legend.append('rect')
          .attr('x', 4)
          .attr('y', 0)
          .attr('width', keyW)
          .attr('height', keyH)
          .style('fill', d.pattern ? d.color : d.color)
          .style('stroke', d.stroke ? d.stroke : 'none')
    
          .style('stroke-width', d.stroke ? 2 : 0);
    
    Cee Nell's avatar
    Cee Nell committed
      });
    
    Cee Nell's avatar
    Cee Nell committed
    }
    
    Cee Nell's avatar
    Cee Nell committed
    
    // 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();
    
          createLegend();
    
    Cee Nell's avatar
    Cee Nell committed
        } else {
          console.error('Error loading data');
    
    Cee Nell's avatar
    Cee Nell committed
        }
    
    Cee Nell's avatar
    Cee Nell committed
      } catch (error) {
        console.error('Error during component mounting', error);
    
    Cee Nell's avatar
    Cee Nell committed
    });
    
    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);
    
    Cee Nell's avatar
    Cee Nell committed
    }
    
    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() {
    
    Cee Nell's avatar
    Cee Nell committed
      // Remove any existing SVG element
      d3.select('#beeswarm-chart-container').select('svg').remove();
      
      // Get dynamic dimensions to draw chart
      const containerWidth = document.getElementById('beeswarm-chart-container').offsetWidth;
    
    Cee Nell's avatar
    Cee Nell committed
      const containerHeight = window.innerWidth <= 700 ? window.innerHeight * 0.5 : 600;
    
      const margin = window.innerWidth <= 700 ? { top: 40, right: 10, bottom: 20, left: 10 } : { top: 40, right: 20, bottom: 40, left: 40 };
    
    Cee Nell's avatar
    Cee Nell committed
      const width = containerWidth - margin.left - margin.right;
      const height = containerHeight - margin.top - margin.bottom;
      
    
    Cee Nell's avatar
    Cee Nell committed
      svg = d3
        .select('#beeswarm-chart-container')
        .append('svg')
        .attr('class', 'beeswarmSvg')
    
    Cee Nell's avatar
    Cee Nell committed
        .attr('viewBox', `0 0 ${containerWidth} ${containerHeight}`)
        .attr('preserveAspectRatio', 'xMidYMid meet')
        .style('width', '100%')
        .style('height', 'auto');
    
    Cee Nell's avatar
    Cee Nell committed
    
      const yScale = d3.scaleLinear()
        .domain([40, d3.max(data.value, d => d.level_agreement)])
    
    Cee Nell's avatar
    Cee Nell committed
        .range([containerHeight - margin.bottom, margin.top]);
    
    Cee Nell's avatar
    Cee Nell committed
    
      // 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(window.innerWidth <= 700 ? [8, 40] : [10, 70]);
    
    Cee Nell's avatar
    Cee Nell committed
    
      const yAxis = svg.append('g')
    
    Cee Nell's avatar
    Cee Nell committed
        .attr("transform", "translate(50, 0)")
    
    Cee Nell's avatar
    Cee Nell committed
        .call(d3.axisLeft(yScale).ticks(5))
        .attr("stroke-width", 2)
    
    Cee Nell's avatar
    Cee Nell committed
        .attr("font-size", 18);
    
    Cee Nell's avatar
    Cee Nell committed
    
      // Add label to y axis
      svg.append('text')
    
        .attr("class", "yLabel")
        .attr("text-anchor", "left")
    
    Cee Nell's avatar
    Cee Nell committed
        .attr("font-weight", 700)
    
    Cee Nell's avatar
    Cee Nell committed
        .attr("transform", `translate(${margin.left}, ${margin.top / 2})`)
    
    Cee Nell's avatar
    Cee Nell committed
        .text("Consensus");
    
    Cee Nell's avatar
    Cee Nell committed
      svg.append('text')
        .attr("class", "yLabel")
        .attr("text-anchor", "left")
        .attr("font-weight", 700)
    
    Cee Nell's avatar
    Cee Nell committed
        .attr("transform", `translate(${margin.left}, ${containerHeight - (margin.bottom / 2) + 10})`)
    
    Cee Nell's avatar
    Cee Nell committed
        .text("Inconclusive");
    
    Cee Nell's avatar
    Cee Nell committed
      // Set forces
    
    Cee Nell's avatar
    Cee Nell committed
      const forceY = d3.forceY(d => yScale(d.level_agreement)).strength(0.9);
      const forceX = d3.forceX(margin.left + (width / 2)).strength(0.1);
    
    Cee Nell's avatar
    Cee Nell committed
      const forceCollide = d3.forceCollide(d => radiusScale(d.evidence_val) + 2).iterations(20);
      const forceManyBody = d3.forceManyBody().strength(1);
    
      const bubbles = svg
    
    Cee Nell's avatar
    Cee Nell committed
        .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(' ', '')])
        .on('mouseover', handleMouseOver)
        .on('mouseout', handleMouseOut);
    
    Cee Nell's avatar
    Cee Nell committed
    
      // 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.75)
        .alphaDecay(0.03);
    
      function ticked() {
        bubbles
          .attr("cx", d => Math.max(margin.left + radiusScale(d.evidence_val), Math.min(width - margin.right - radiusScale(d.evidence_val), d.x)))
    
    Cee Nell's avatar
    Cee Nell committed
          .attr("cy", d => Math.max(radiusScale(d.evidence_val), Math.min(containerHeight - radiusScale(d.evidence_val), d.y)));
    
      //createLegend(); // add legend to caption
    
    Cee Nell's avatar
    Cee Nell committed
    }
    
    function toggleCategory(category) {
      isChecked.value[category] = !isChecked.value[category];
      console.log(`Category toggled: ${category}, new value: ${isChecked.value[category]}`);
      updateChart();
    }
    
    
    Cee Nell's avatar
    Cee Nell committed
    function updateChart() {
    
    Cee Nell's avatar
    Cee Nell committed
      // Adjust height based on screen size
      const isMobile = window.innerWidth <= 700;
      const containerHeight = isMobile ? window.innerHeight * 0.50 : height;
    
    Cee Nell's avatar
    Cee Nell committed
      // Set the y scale
      const yScale = d3.scaleLinear()
        .domain([40, d3.max(data.value, d => d.level_agreement)])
    
    Cee Nell's avatar
    Cee Nell committed
        .range([containerHeight - margin.bottom, margin.top]);
    
    Cee Nell's avatar
    Cee Nell committed
      // Set the radius scale 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(window.innerWidth <= 700 ? [8, 40] : [10, 70]);
    
    Cee Nell's avatar
    Cee Nell committed
      // Filter data based on active categories
      const activeCategories = Object.keys(isChecked.value).filter(category => isChecked.value[category]);
      console.log('Active categories:', activeCategories);
    
      // Select all bubbles and bind data
      const bubbles = svg.selectAll(".bubble")
        .data(data.value, d => d.id);
    
      // Update existing bubbles
      bubbles
        .attr('r', d => radiusScale(d.evidence_val))
    
        .style('fill', d => activeCategories.includes(d.dimension.replace(' ', '')) ? dimensionColors[d.dimension.replace(' ', '')] : 'rgba(217, 217, 217, 0.95)');
    
    Cee Nell's avatar
    Cee Nell committed
    
      // Add new bubbles
      bubbles.enter()
        .append('circle')
        .attr('class', 'bubble')
        .attr('r', d => radiusScale(d.evidence_val))
    
        .style('fill', d => activeCategories.includes(d.dimension.replace(' ', '')) ? dimensionColors[d.dimension.replace(' ', '')] : 'rgba(217, 217, 217, 0.95)')
    
    Cee Nell's avatar
    Cee Nell committed
        .attr('cx', d => d.x)  // Use existing x position
        .attr('cy', d => d.y)
    
    Cee Nell's avatar
    Cee Nell committed
        .on('mouseover', handleMouseOver)
        .on('mouseout', handleMouseOut)
    
    Cee Nell's avatar
    Cee Nell committed
        .merge(bubbles);
    
      // Ensure all bubbles are handled correctly
      bubbles.exit().remove();
    }
    
    Cee Nell's avatar
    Cee Nell committed
    
    
    function updatePatternColor(color) {
      d3.select(`#${patternId} path`)
        .attr('stroke', color);
    }
    
    
    Cee Nell's avatar
    Cee Nell committed
    // Tooltip functions
    
    Cee Nell's avatar
    Cee Nell committed
    function handleMouseOver(event, d) {
    
      const activeCategories = Object.keys(isChecked.value).filter(category => isChecked.value[category]);
    
      // Check if the category of the data point is toggled
      if (!activeCategories.includes(d.dimension.replace(' ', ''))) {
        return;  // Do nothing if the category is untoggled
      }
    
    
      // Add text to tooltip
    
    Cee Nell's avatar
    Cee Nell committed
      const tooltip = d3.select('#tooltip');
      tooltip.html('')
        .append('div')
    
        .html(`<strong>${d.determinant_wrapped}</strong> appeared in ${d.evidence_val} ${d.evidence_val === 1 ? 'study' : 'studies'}.`);
    
    Cee Nell's avatar
    Cee Nell committed
    
    
      // Define categories in stacked bar chart
    
    Cee Nell's avatar
    Cee Nell committed
      const barData = [
        { name: 'positive', value: d.pos_related_total, stroke: dimensionColors[d.dimension.replace(' ', '')], fill: dimensionColors[d.dimension.replace(' ', '')] },
        { name: 'negative', value: d.neg_related_total, stroke: dimensionColors[d.dimension.replace(' ', '')], fill: 'white' },
    
    Cee Nell's avatar
    Cee Nell committed
        { name: 'unknown', value: d.unk_direction_total, pattern: true, stroke: dimensionColors[d.dimension.replace(' ', '')], fill: `url(#pattern-stripe)` }
    
    Cee Nell's avatar
    Cee Nell committed
      ];
    
      // Set dimensions for the bar chart
    
    Cee Nell's avatar
    Cee Nell committed
      const barWidth = 200;
      const barHeight = 15;
    
    Cee Nell's avatar
    Cee Nell committed
    
      // Create an SVG element for the bar chart
      const svgBar = tooltip.append('svg')
        .attr('width', barWidth + 10)
        .attr('height', barHeight + 10);
    
      const g = svgBar.append('g')
        .attr('transform', 'translate(8, 8)');
    
      // Create a scale for the x-axis
      const xBar = d3.scaleLinear()
        .domain([0, d3.sum(barData, d => d.value)])
        .range([0, barWidth]);
    
      // Create groups for each bar segment
      const barGroups = g.selectAll('g')
        .data(barData)
        .enter()
        .append('g');
    
    
      // Add the rectangles for the bars
    
    Cee Nell's avatar
    Cee Nell committed
      barGroups.append('rect')
        .attr('x', (d, i) => i > 0 ? xBar(d3.sum(barData.slice(0, i), d => d.value)) : 0)
        .attr('y', 0)
        .attr('width', d => xBar(d.value))
        .attr('height', barHeight)
    
    Cee Nell's avatar
    Cee Nell committed
        .style('fill', d => d.pattern ? `url(#${patternId})` : d.fill)
    
    Cee Nell's avatar
    Cee Nell committed
        .style('stroke', d => d.stroke ? d.stroke : 'none');
    
    
      updatePatternColor(dimensionColors[d.dimension.replace(' ', '')]);
    
      // add emphasis to bubbles
      d3.select(this)
        .attr('stroke', dimensionColors[d.dimension.replace(' ', '')])
        .attr('stroke-width', 8);
      
      // update colors in legend
      d3.select('#text2').select('.positive').select('rect')
    
    Cee Nell's avatar
    Cee Nell committed
        .style('fill', dimensionColors[d.dimension.replace(' ', '')]);
    
    
    Cee Nell's avatar
    Cee Nell committed
      d3.select('#text2').select('.negative').select('rect')
        .style('fill', 'white')
    
    Cee Nell's avatar
    Cee Nell committed
        .style('stroke', dimensionColors[d.dimension.replace(' ', '')]);
    
    
    Cee Nell's avatar
    Cee Nell committed
      d3.select('#text2').select('.unknown').select('rect')
        .style('fill', `url(#pattern-stripe)`)
    
    Cee Nell's avatar
    Cee Nell committed
        .style('stroke', dimensionColors[d.dimension.replace(' ', '')]);
    
    
        d3.select('#text2').select(`#${patternId} path`)
        //.style('fill', `url(#pattern-stripe)`)
        .attr('stroke', dimensionColors[d.dimension.replace(' ', '')]);
    
    Cee Nell's avatar
    Cee Nell committed
    }
    
    function handleMouseOut() {
    
    Cee Nell's avatar
    Cee Nell committed
      const tooltip = d3.select('#tooltip');
    
      
      // add starting text
    
    Cee Nell's avatar
    Cee Nell committed
      tooltip.html('Interact with the chart to explore evidence for<br>social vulnerability determinants.');
    
      
      // remove emphasis on bubbles
    
    Cee Nell's avatar
    Cee Nell committed
      d3.select(this)
        .attr('stroke', null)
        .attr('stroke-width', null);
    
      
      /* d3.select('#text2').select('.positive').select('rect')
        .style('fill', dimensionColors['Demographiccharacteristics']);
    
      d3.select('#text2').select('.negative').select('rect')
        .style('fill', 'white')
        .style('stroke', dimensionColors['Demographiccharacteristics']); */
    
      /* d3.select('#text2').select('.unknown').select('rect')
        .style('fill', `url(#pattern-stripe)`)
        .style('stroke', dimensionColors[d.dimension.replace(' ', '')]); */
    
    Cee Nell's avatar
    Cee Nell committed
    }
    
    Cee Nell's avatar
    Cee Nell committed
    </script>
    
    <style scoped lang="scss">
    $Demographiccharacteristics: #092836;
    $Landtenure: #1b695e;
    $Livingconditions: #7a5195;
    $Socioeconomicstatus: #2a468f;
    $Health: #ef5675;
    $Riskperception: #ff764a;
    $Exposure: #ffa600;
    $ThemeGrey: rgba(217, 217, 217, 0.95);
    
    #beeswarm-chart-container {
    
    Cee Nell's avatar
    Cee Nell committed
      text-align: center;
      position: relative;
    
    Cee Nell's avatar
    Cee Nell committed
      max-width: 800px;
      margin: auto;
    
    }
    
    #beeswarm-chart-container svg {
    
    Cee Nell's avatar
    Cee Nell committed
      //width: 90vw;
      max-width: 700px;
    
    Cee Nell's avatar
    Cee Nell committed
      max-height: 100%;
      height: auto; /* Maintain aspect ratio */
    
    Cee Nell's avatar
    Cee Nell committed
      margin: auto;
    
    Cee Nell's avatar
    Cee Nell committed
      display: inline-block;
    
    Cee Nell's avatar
    Cee Nell committed
      font-weight: bold;
    }
    
    .tooltip-width {
      width: 100%; 
      margin: 0 auto;
      display: flex;
      flex-direction: column;
      align-items: center;
    }
    
    .tooltip {
      opacity: 1;
      background: $ThemeGrey;
      padding: 8px;
      border-radius: 5px;
      pointer-events: none; 
      width: 100%; 
    
    Cee Nell's avatar
    Cee Nell committed
      height: 70px;
    
    Cee Nell's avatar
    Cee Nell committed
      text-align: center;
    
    Cee Nell's avatar
    Cee Nell committed
    }
    
    .hidden {
      display: none;
    
    }
    
    .highlight {
      color: white;
      padding: 0.25px 5px;
      border-radius: 10px;
      white-space: nowrap;
      font-weight: bold;
      cursor: pointer;
      transition: all 0.1s;
    
      &.Demographiccharacteristics {
        background-color: $Demographiccharacteristics;
    
    
      &.Landtenure {
        background-color: $Landtenure;
    
    
      &.Livingconditions {
        background-color: $Livingconditions;
    
    
      &.Socioeconomicstatus {
        background-color: $Socioeconomicstatus;
    
    
      &.Health {
        background-color: $Health;
    
    
      &.Riskperception {
        background-color: $Riskperception;
    
    
      &.Exposure {
        background-color: $Exposure;
    
    
      &:not(.checked) {
        color: black;
        background-color: $ThemeGrey;
    
    Cee Nell's avatar
    Cee Nell committed
    @media (max-width: 700px) {
        .yLabel {
          font-size: 12px; /* Adjusted font size for mobile devices */
        }
      
        #beeswarm-chart-container {
          width: 100vw; /* Make the chart container take up 100% of the viewport width on mobile */
          height: auto; /* Make the chart container take up the full height of the screen */
        }
      
        #beeswarm-chart-container svg {
          height: 100%; /* Make the SVG fill the container height */
        }
      
        #mobile-tooltip {
          display: block; /* Show the mobile tooltip only on mobile devices */
          margin: 10px;
        }
      }
    
    
    
    Cee Nell's avatar
    Cee Nell committed
    </style>
    
    <style lang="scss">
    .tooltip-span {
      position: relative;
      cursor: pointer;
      display: inline-block;
      border-bottom: 1px dotted rgba(54, 54, 54, 0.8);
      z-index: 10;
    }
    
    .tooltiptext {
      visibility: hidden;
      width: 200px;
      background-color: #555;
      color: #fff;
      text-align: center;
      border-radius: 6px;
      padding: 5px;
      position: absolute;
      z-index: 1;
      margin-left: -170px;
      margin-top: 2rem;
      opacity: 0;
      transition: opacity 0.3s;
    }
    
    .tooltip-group:hover .tooltiptext {
      visibility: visible;
      opacity: 1;
    }
    </style>