chartjs / Chart.js

Simple HTML5 Charts using the <canvas> tag
https://www.chartjs.org/
MIT License
64.77k stars 11.92k forks source link

When defining an External implementation for Tooltip, chart.canvas.offsetLeft doesn't work when chart in a table #11830

Open ldhasson opened 4 months ago

ldhasson commented 4 months ago

Expected behavior

No matter where a chart is positioned, correct coordinates should be available for the tooltip implementation (with respect to the viewport?).

Current behavior

chart.canvas.offsetLeft and chart.canvas.offsetTop etc... have values representing the relative positioning of the chart within the table element.

Reproducible sample

https://www.chartjs.org/dist/master/chart.umd.js

Optional extra steps/info to reproduce

<script src="https://npmcdn.com/chart.js@latest/dist/chart.umd.js"></script>

<TABLE>
   <TR><TD><CANVAS id="chart1" height="200px" width="500px"></TD></TR>
   <TR><TD><CANVAS id="chart2" height="200px" width="500px"></TD></TR>
</TABLE>
.myChartDiv {
  max-width: 600px;
  max-height: 400px;
}

.chartTooltip {
   position: absolute;
   background-color: #345;
   font-size:80%;
   box-shadow: 3px 3px 4px 0px #aaa;
   opacity: 1;
   border-radius: 5px 5px 5px 5px; 
   pointer-events: none;
   transform: translate(-50%, 0);
   transition: all .1s ease;
   z-index: 9999;   
   color: #CCC;
}
function getOrCreateTooltip(chart)
 {
   let tooltipEl = document.getElementById('chartjs-tooltip');
   if (!tooltipEl)
    {
      tooltipEl = document.createElement('div');
      tooltipEl.id = 'chartjs-tooltip';
      tooltipEl.className="chartTooltip";
      chart.canvas.parentNode.appendChild(tooltipEl);
    }
   return tooltipEl;
 };

function tooltipExternal(context)
    {
       // Tooltip Element
       const {chart, tooltip} = context;
       const tooltipEl = getOrCreateTooltip(chart);

       // Hide if no tooltip
       if (tooltip.opacity === 0)
        return tooltipEl.style.opacity = 0;

       // table
       tooltipEl.innerHTML = '<H2>Hello!</H2>';

//       console.log("chart.canvas.getBoundingClientRect(): ", chart.canvas.getBoundingClientRect());
//       console.log("chart.canvas.offsetst: ", [chart.canvas.offsetLeft, chart.canvas.offsetTop]);
//       console.log("tooltip.carets: ", [tooltip.caretX, tooltip.caretY]);
//       console.log("window.scrolls: ", [window.scrollX, window.scrollY]);
       const positionX = chart.canvas.offsetLeft;
       const positionY = chart.canvas.offsetTop;

       // Display, position, and set styles for font
       tooltipEl.style.opacity = 1;
       tooltipEl.style.left = (positionX + tooltip.caretX) + 'px';
       tooltipEl.style.top = (positionY + tooltip.caretY) + 'px';
   };

var ctx = document.getElementById("chart1");
var myChart = new Chart(ctx, {
    type: 'line',
    data: {
        labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
        datasets: [{
            label: '# of Votes',
            data: [12, 19, 3, 5, 2, 3, 5, 10, 8]
        }]
    },
    options: {
       plugins: {
          tooltip: {
              enabled: false
             ,borderColor: '#CCC'
             ,external: tooltipExternal
           }
       }
    }
});

var ctx = document.getElementById("chart2");
var myChart = new Chart(ctx, {
    type: 'line',
    data: {
        labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
        datasets: [{
            label: '# of Votes',
            data: [12, 19, 3, 5, 2, 3, 5, 10, 8]
        }]
    },
    options: {
       plugins: {
          tooltip: {
              enabled: false
             ,borderColor: '#CCC'
             ,external: tooltipExternal
           }
       }
    }
});

Possible solution

No response

Context

We are trying to use ChartJS in an environment where lots of HTML tables are used. We can figure out some work arounds, but painful. In general though, for tooltips, we should be able to get viewport-relative coordinates.

chart.js version

v4.2.1

Browser name and version

Firefox and Chrome (latest) on Windows 11

Link to your project

No response

LeeLenaleee commented 4 months ago

As I can read in the documentation about the offsetTop property, it returns the offset to the closest anchor element. In this case its the TD. Because of this in both the charts it will return 1 as the offset. To get the accurate position you will need to use getBoundingClientRect on the client. If you use that you get the real position and then you can feed the y and x coordinates of those to the position of the tooltip and that will work fine.

https://codepen.io/leelenaleee/pen/qBzEpvK

ldhasson commented 3 months ago

Agreed, but the documentation and sample are not so clear about this. This works for a general tooltip DIV attached at the document level and with position:relative. The positioning then works anywhere everywhere all at once.