[Chartjs]-ChartJS โ€“ Custom tooltip with icon

4๐Ÿ‘

โœ…

Chart.js provides for โ€œExternal (Custom) Tooltipsโ€ that can be built using whatever HTML you choose:

options: {
  tooltips: {
    // Disable the on-canvas tooltip
    enabled: false,
    custom: function(tooltipModel) {
      // your custom tooltip code ...

Multiple examples are provided on the Chart.js samples page:

Based on your comment, hereโ€™s a quick example of loading an image in a tooltip using the code in the documentation:

const tooltip = document.getElementById("tooltip");

new Chart(document.getElementById("chart"), {
  type: "bar",
  data: {
    labels: ["A", "B", "C"],
    datasets: [{
      label: "Series 1",
      data: [1, 4, 2]
    }]
  },
  options: {
    scales: {
      yAxes: [{
        ticks: {
          beginAtZero: true
        }
      }]
    },
    tooltips: {
      enabled: false,
      custom: function(tooltipModel) {
        // Hide if no tooltip
        if (tooltipModel.opacity === 0) {
          tooltip.style.opacity = 0;
          return;
        }

        // show the tooltip.
        tooltip.style.opacity = 1;

        // create the img element and append it to the tooltip element.
        const img = document.createElement('img');
        img.src = "https://www.gravatar.com/avatar/6fcc51ca5e7029116a383e7aeb0bbaa0?s=32&d=identicon&r=PG&f=1";
        tooltip.innerHTML = "";
        tooltip.appendChild(img);

        // move the tooltip to the 'correct' position.
        const position = this._chart.canvas.getBoundingClientRect();
        tooltip.style.left = position.left + window.pageXOffset + tooltipModel.caretX + 'px';
        tooltip.style.top = position.top + window.pageYOffset + tooltipModel.caretY + 'px';
      }
    }
  }
});
#tooltip {
  opacity: 0;
  position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="chart"></canvas>
<div id="tooltip"></div>

2๐Ÿ‘

This is not a complete answer, it is something to get you started.

You can create a custom tooltip using the renderer. You can also use the document like they did at the bottom here https://www.chartjs.org/docs/latest/configuration/tooltip.html but it may not work with server side generated applications.

The thisAsThat is a good utility function => it allows you to refer to the Chart.js object as that and allows you to refer to the class as this.

At the bottom of this page of the link I provided, it shows how to make a custom tooltip. Take your time in going through it. Basically everywhere they use the document, you can use the renderer. To position and style the tooltip it is up to you and you may have to do calculations.

constructor(private renderer: Renderer2, @Inject(DOCUMENT) private document: Document) {}

private thisAsThat(callBack: Function) {
  const self = this;
  return function () {
      return callBack.apply(self, [this].concat(Array.prototype.slice.call(arguments)));
  }; 
}

options: {
  tooltips: {
    enabled: false,
    custom: this.thisAsThat((that, tooltipModel: any) => {
      // maybe you need chartPosition
      const chartPosition = that._chart.canvas.getBoundingClientRect();
      const tooltipEl = this.renderer.createElement('div');
      const image = this.renderer.createElement('img');
      // Pretty sure it is setProperty, can also give setAttribute a try as well
      this.renderer.setProperty(image, 'src', 'src of image here');
      this.renderer.setProperty(image, 'alt', 'Your alt');
      this.renderer.appendChild(tooltipEl, image);
      // Can also add a class as well.
      this.renderer.setStyle(tooltipEl, 'background', 'black');
      // this should add your tooltip at the end of the DOM right before the body tag
      this.renderer.appendChild(this.document.body, tooltipEl);
   })
  }
}

1๐Ÿ‘

In V3 the tooltip config has been moved to the options.plugins.tooltip namespace, also the custom option has been renamed to external. Since it renders just html you can pass anything to it you want like so:

<body>
    <canvas id="chartJSContainer" width="600" height="400"></canvas>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.6.0/chart.js"></script>
</body>
const getOrCreateTooltip = (chart) => {
  let tooltipEl = chart.canvas.parentNode.querySelector('div');

  if (!tooltipEl) {
    tooltipEl = document.createElement('div');
    tooltipEl.style.background = 'rgba(0, 0, 0, 0.7)';
    tooltipEl.style.borderRadius = '3px';
    tooltipEl.style.color = 'white';
    tooltipEl.style.opacity = 1;
    tooltipEl.style.pointerEvents = 'none';
    tooltipEl.style.position = 'absolute';
    tooltipEl.style.transform = 'translate(-50%, 0)';
    tooltipEl.style.transition = 'all .1s ease';

    const table = document.createElement('table');
    table.style.margin = '0px';

    tooltipEl.appendChild(table);
    chart.canvas.parentNode.appendChild(tooltipEl);
  }

  return tooltipEl;
};

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

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

  // Set Text
  if (tooltip.body) {
    const titleLines = tooltip.title || [];
    const bodyLines = tooltip.body.map(b => b.lines);

    const tableHead = document.createElement('thead');

    titleLines.forEach(title => {
      const tr = document.createElement('tr');
      tr.style.borderWidth = 0;

      const th = document.createElement('th');
      th.style.borderWidth = 0;
      const text = document.createTextNode(title);
      th.appendChild(text);

            // THIS BLOCK ADDED
      const imageTh = document.createElement('th');
      th.style.borderWidth = 0;
      const image = document.createElement('img');
      image.style = 'width:20px'
      image.src = context.tooltip.dataPoints[0].dataset.url;
      imageTh.appendChild(image);

      tr.appendChild(th);
      tr.appendChild(imageTh);
      tableHead.appendChild(tr);
    });

    const tableBody = document.createElement('tbody');
    bodyLines.forEach((body, i) => {
      const colors = tooltip.labelColors[i];

      const span = document.createElement('span');
      span.style.background = colors.backgroundColor;
      span.style.borderColor = colors.borderColor;
      span.style.borderWidth = '2px';
      span.style.marginRight = '10px';
      span.style.height = '10px';
      span.style.width = '10px';
      span.style.display = 'inline-block';

      const tr = document.createElement('tr');
      tr.style.backgroundColor = 'inherit';
      tr.style.borderWidth = 0;

      const td = document.createElement('td');
      td.style.borderWidth = 0;

      const text = document.createTextNode(body);

      td.appendChild(span);
      td.appendChild(text);
      tr.appendChild(td);
      tableBody.appendChild(tr);
    });

    const tableRoot = tooltipEl.querySelector('table');

    // Remove old children
    if(tableRoot && tableRoot.firstChild) {
    while (tableRoot.firstChild) {
      tableRoot.firstChild.remove();
    }
    }
    

    // Add new children
    tableRoot.appendChild(tableHead);
    tableRoot.appendChild(tableBody);
  }

  const {
    offsetLeft: positionX,
    offsetTop: positionY
  } = chart.canvas;

  // 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';
  tooltipEl.style.font = tooltip.options.bodyFont.string;
  tooltipEl.style.padding = tooltip.options.padding + 'px ' + tooltip.options.padding + 'px';
};

const options = {
  type: 'line',
  data: {
    labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
    datasets: [{
        label: '# of Votes',
        data: [12, 19, 3, 5, 2, 3],
        borderColor: 'pink',
        backgroundColor: 'pink',
        url: 'https://www.chartjs.org/img/chartjs-logo.svg'
      },
      {
        label: '# of Points',
        data: [7, 11, 5, 8, 3, 7],
        borderColor: 'orange',
        backgroundColor: 'orange',
        url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/ef/Stack_Overflow_icon.svg/512px-Stack_Overflow_icon.svg.png'
      }
    ]
  },
  options: {
    plugins: {
      tooltip: {
        enabled: false,
        position: 'nearest',
        external: externalTooltipHandler
      }
    }
  }
}

const ctx = document.getElementById('chartJSContainer').getContext('2d');
const chart = new Chart(ctx, options);

Fiddle link since stack sandbox didnt like the table: https://jsfiddle.net/Leelenaleee/xhrs2wvc/19/

Leave a comment