Position tooltip in center of bar

本小妞迷上赌 提交于 2020-02-02 16:26:46

问题


Can't find a way to position tooltip in center of bar like this (yeah, I know it is not exactly in center on this screenshot, but still):

If I use custom tooltip option, I can get only x and y positions of a caret on top of a bar. But can't get height/width of a bar.

Here is a part of an options object that I pass to Chart constructor:

const options = {
  tooltips: {
    enabled: false,
    custom: (tooltip) => {
      // Retrieving valuable props from tooltip (caretX, caretY)
      // and creating custom tooltip that is positioned
      // on top of a bar
    }
  }
  // other options
}

const chart = new Chart(ctx, {
  type: 'bar',
  data,
  options
})

回答1:


As you may already know, to position a custom tooltip at the center of a bar, you might need some of it­'s (bar) properties, such as - width, height, top and left position. But unfortunately, there is no straight-forward way of getting these properties, rather you need to calculate them yourself.

To obtain / calculate those properties, you can use the following function (can be named anything), which will return an object containing all these (width, height, top, left) properties of a particular bar, when hovered over.

function getBAR(chart) {
   const dataPoints = tooltipModel.dataPoints,
         datasetIndex = chart.data.datasets.length - 1,
         datasetMeta = chart.getDatasetMeta(datasetIndex),
         scaleBottom = chart.scales['y-axis-0'].bottom,
         bar = datasetMeta.data[dataPoints[0].index]._model,
         canvasPosition = chart.canvas.getBoundingClientRect(),
         paddingLeft = parseFloat(getComputedStyle(chart.canvas).paddingLeft),
         paddingTop = parseFloat(getComputedStyle(chart.canvas).paddingTop),
         scrollLeft = document.body.scrollLeft,
         scrollTop = document.body.scrollTop;

   return {
      top: bar.y + canvasPosition.top + paddingTop + scrollTop,
      left: bar.x - (bar.width / 2) + canvasPosition.left + paddingLeft + scrollLeft,
      width: bar.width,
      height: scaleBottom - bar.y
   }
}

Calculate Center Position

After retrieving the required properties, you can calculate center position of a bar as such :

𝚌𝚎𝚗𝚝𝚎𝚛𝚇  =  𝚋𝚊𝚛-𝚕𝚎𝚏𝚝 + (𝚋𝚊𝚛-𝚠𝚒𝚍𝚝𝚑 / 𝟸)

­

𝚌𝚎𝚗𝚝𝚎𝚛𝚈  =  𝚋𝚊𝚛-𝚝𝚘𝚙 + (𝚋𝚊𝚛-𝚑𝚎𝚒𝚐𝚑𝚝 / 𝟸)

then, create your custom tooltip element and position it accordingly.


ᴘʀᴇᴠɪᴇᴡ

ʟɪᴠᴇ ᴇxᴀᴍᴘʟᴇ ⧩

const chart = new Chart(ctx, {
   type: 'bar',
   data: {
      labels: ['Jan', 'Feb', 'Mar', 'Apr'],
      datasets: [{
         label: 'Revenue',
         data: [4, 2, 3, 3],
         backgroundColor: '#2d4e6d'
      }, {
         label: 'Expenses',
         data: [3, 3.5, 4, 1],
         backgroundColor: '#c06526'
      }, {
         label: 'Profit',
         data: [3, 2.5, 4, 2],
         backgroundColor: '#e0ecf0'
      }]
   },
   options: {
      scales: {
         xAxes: [{
            stacked: true
         }],
         yAxes: [{
            stacked: true,
            ticks: {
               beginAtZero: true
            }
         }]
      },
      tooltips: {
         enabled: false,
         custom: function(tooltipModel) {
         /*** jQuery IS USED FOR SIMPLICITY ***/
         
            /* TOOLTIP & CARET ELEMENT */
            let tooltip = $('#tooltip');
            let tooltipCaret = $('#tooltip-caret');

            /* CREATE TOOLTIP & CARET ELEMENT AT FIRST RENDER */
            if (!tooltip.length && !tooltipCaret.length) {
               tooltip = $('<div></div>').attr('id', 'tooltip');
               tooltipCaret = $('<div></div>').attr('id', 'tooltip-caret');
               $('body').append(tooltip, tooltipCaret);
            }

            /* HIDE IF NO TOOLTIP */
            if (!tooltipModel.opacity) {
               tooltip.hide();
               tooltipCaret.hide();
               return;
            }

            /* GET BAR PROPS (width, height, top, left) */
            const barWidth = getBAR(this._chart).width,
                  barHeight = getBAR(this._chart).height,
                  barTop = getBAR(this._chart).top,
                  barLeft = getBAR(this._chart).left;

            /* SET STYLE FOR TOOLTIP 
            	(these can also be set in separate css file) */
            tooltip.css({
               "display": "inline-block",
               "position": "absolute",
               "color": "rgba(255, 255, 255, 1)",
               "background": "rgba(0, 0, 0, 0.7)",
               "padding": "5px",
               "font": "12px Arial",
               "border-radius": "3px",
               "white-space": "nowrap",
               "pointerEvents": "none"
            });

            /* SET STYLE FOR TOOLTIP CARET 
            	(these can also be set in separate css file) */
            tooltipCaret.css({
               "display": "block",
               "position": "absolute",
               "width": 0,
               "pointerEvents": "none",
               "border-style": "solid",
               "border-width": "8px 10px 8px 0",
               "border-color": "transparent rgba(0, 0, 0, 0.7) transparent transparent"
            });

            /* ADD CONTENT IN TOOLTIP */
            tooltip.text('ChartJS');
            tooltip.append('<br><div class="color-box"></div><label style="display: block; margin: -16px 0 0 16px;"> Custom Tooltip<label>');

            /* POSITION TOOLTIP & CARET
            (position should be set after tooltip & caret is rendered) */
            const centerX = barLeft + (barWidth / 2),
                  centerY = barTop + (barHeight / 2)
            
            tooltip.css({
               "top": centerY - (tooltip.outerHeight() / 2) + 'px',
               "left": centerX + tooltipCaret.outerWidth() + 'px'
            });
            tooltipCaret.css({
               "top": centerY - (tooltipCaret.outerHeight() / 2) + 'px',
               "left": centerX + 'px'
            });

            /* FUNCTION TO GET BAR PROPS */
            function getBAR(chart) {
               const dataPoints = tooltipModel.dataPoints,
                     datasetIndex = chart.data.datasets.length - 1,
                     datasetMeta = chart.getDatasetMeta(datasetIndex),
                     scaleBottom = chart.scales['y-axis-0'].bottom,
                     bar = datasetMeta.data[dataPoints[0].index]._model,
                     canvasPosition = chart.canvas.getBoundingClientRect(),
                     paddingLeft = parseFloat(getComputedStyle(chart.canvas).paddingLeft),
                     paddingTop = parseFloat(getComputedStyle(chart.canvas).paddingTop),
                     scrollLeft = document.body.scrollLeft,
                     scrollTop = document.body.scrollTop;

               return {
                  top: bar.y + canvasPosition.top + paddingTop + scrollTop,
                  left: bar.x - (bar.width / 2) + canvasPosition.left + paddingLeft + scrollLeft,
                  width: bar.width,
                  height: scaleBottom - bar.y
               }
            }

         }
      }
   }
});
.color-box{width:12px;height:12px;background:#c06526;display:inline-block;margin-top:5px}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.min.js"></script>
<canvas id="ctx"></canvas>

UPDATE

IF you wish to position tooltip at the center of each bar segment then, use the following function :

function getBARSegment(chart) {
   const dataPoints = tooltipModel.dataPoints,
         bar = chart.active[dataPoints[0].datasetIndex]._model,
         canvasPosition = chart.canvas.getBoundingClientRect(),
         paddingLeft = parseFloat(getComputedStyle(chart.canvas).paddingLeft),
         paddingTop = parseFloat(getComputedStyle(chart.canvas).paddingTop),
         scrollLeft = document.body.scrollLeft,
         scrollTop = document.body.scrollTop;

   return {
      top: bar.y + canvasPosition.top + paddingTop + scrollTop,
      left: bar.x - (bar.width / 2) + canvasPosition.left + paddingLeft + scrollLeft,
      width: bar.width,
      height: bar.base - bar.y
   }
}

ᴘʀᴇᴠɪᴇᴡ

ʟɪᴠᴇ ᴇxᴀᴍᴘʟᴇ ⧩

const chart = new Chart(ctx, {
   type: 'bar',
   data: {
      labels: ['Jan', 'Feb', 'Mar', 'Apr'],
      datasets: [{
         label: 'Revenue',
         data: [4, 2, 3, 3],
         backgroundColor: '#2d4e6d'
      }, {
         label: 'Expenses',
         data: [3, 3.5, 4, 1],
         backgroundColor: '#c06526'
      }, {
         label: 'Profit',
         data: [3, 2.5, 4, 2],
         backgroundColor: '#e0ecf0'
      }]
   },
   options: {
      scales: {
         xAxes: [{
            stacked: true
         }],
         yAxes: [{
            stacked: true,
            ticks: {
               beginAtZero: true
            }
         }]
      },
      tooltips: {
         enabled: false,
         custom: function(tooltipModel) {
            /*** jQuery IS USED FOR SIMPLICITY ***/

            /* TOOLTIP & CARET ELEMENT */
            let tooltip = $('#tooltip');
            let tooltipCaret = $('#tooltip-caret');

            /* CREATE TOOLTIP & CARET ELEMENT AT FIRST RENDER */
            if (!tooltip.length && !tooltipCaret.length) {
               tooltip = $('<div></div>').attr('id', 'tooltip');
               tooltipCaret = $('<div></div>').attr('id', 'tooltip-caret');
               $('body').append(tooltip, tooltipCaret);
            }

            /* HIDE IF NO TOOLTIP */
            if (!tooltipModel.opacity) {
               tooltip.hide();
               tooltipCaret.hide();
               return;
            }

            /* GET BAR PROPS (width, height, top, left) */
            const barWidth = getBARSegment(this._chart).width,
                  barHeight = getBARSegment(this._chart).height,
                  barTop = getBARSegment(this._chart).top,
                  barLeft = getBARSegment(this._chart).left;

            /* SET STYLE FOR TOOLTIP 
            	(these can also be set in separate css file) */
            tooltip.css({
               "display": "inline-block",
               "position": "absolute",
               "color": "rgba(255, 255, 255, 1)",
               "background": "rgba(0, 0, 0, 0.7)",
               "padding": "5px",
               "font": "12px Arial",
               "border-radius": "3px",
               "white-space": "nowrap",
               "pointerEvents": "none"
            });

            /* SET STYLE FOR TOOLTIP CARET 
            	(these can also be set in separate css file) */
            tooltipCaret.css({
               "display": "block",
               "position": "absolute",
               "width": 0,
               "pointerEvents": "none",
               "border-style": "solid",
               "border-width": "8px 10px 8px 0",
               "border-color": "transparent rgba(0, 0, 0, 0.7) transparent transparent"
            });

            /* ADD CONTENT IN TOOLTIP */
            tooltip.text('ChartJS');
            tooltip.append('<br><div class="color-box"></div><label style="display: block; margin: -16px 0 0 16px;"> Custom Tooltip<label>');

            /* POSITION TOOLTIP & CARET
            (position should be set after tooltip & caret is rendered) */
            const centerX = barLeft + (barWidth / 2),
                  centerY = barTop + (barHeight / 2)

            tooltip.css({
               "top": centerY - (tooltip.outerHeight() / 2) + 'px',
               "left": centerX + tooltipCaret.outerWidth() + 'px'
            });
            tooltipCaret.css({
               "top": centerY - (tooltipCaret.outerHeight() / 2) + 'px',
               "left": centerX + 'px'
            });

            /* FUNCTION TO GET BAR PROPS */
            function getBARSegment(chart) {
               const dataPoints = tooltipModel.dataPoints,
                     bar = chart.active[dataPoints[0].datasetIndex]._model,
                     canvasPosition = chart.canvas.getBoundingClientRect(),
                     paddingLeft = parseFloat(getComputedStyle(chart.canvas).paddingLeft),
                     paddingTop = parseFloat(getComputedStyle(chart.canvas).paddingTop),
                     scrollLeft = document.body.scrollLeft,
                     scrollTop = document.body.scrollTop;

               return {
                  top: bar.y + canvasPosition.top + paddingTop + scrollTop,
                  left: bar.x - (bar.width / 2) + canvasPosition.left + paddingLeft + scrollLeft,
                  width: bar.width,
                  height: bar.base - bar.y
               }
            }

         }
      }
   }
});
.color-box{width:12px;height:12px;background:#c06526;display:inline-block;margin-top:5px}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="ctx"></canvas>



回答2:


You can use values from the datasets to workout the relative height of the item being hovered over and adjust the CSS accordingly.

The following is close to the centre, but is not the exact centre. My calculations need correcting if you want exactness.

Inside the custom tooltip function include the following:

// find relative proportion
var dataIndex = tooltip.dataPoints[0].index;
var datasetIndex = tooltip.dataPoints[0].datasetIndex;
var totalHeight = 0;

var thisHeight = this._chart.config.data.datasets[datasetIndex].data[dataIndex];

for (var i = 0; i <= datasetIndex; i++)
{
  totalHeight += this._chart.config.data.datasets[i].data[dataIndex];
}

var positionY = this._chart.canvas.offsetTop;
var positionX = this._chart.canvas.offsetLeft;
var chartHeight = this._chart.canvas.scrollHeight;
var tooltipHalfHeight = tooltip.height / 2;

// Display, position, and set styles for font
tooltipEl.style.left = positionX + tooltip.caretX + 'px';
tooltipEl.style.top = tooltip.caretY + ((chartHeight - tooltip.caretY - positionY) * (thisHeight / totalHeight / 2)) - tooltipHalfHeight + 'px';

<!DOCTYPE html>
<html>

<head>
  <link rel="stylesheet" href="style.css" />
  <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.bundle.min.js"></script>
  <script src="script.js"></script>
  <style>
		canvas{
			-moz-user-select: none;
			-webkit-user-select: none;
			-ms-user-select: none;
		}
		#chartjs-tooltip {
			opacity: 1;
			position: absolute;
			background: rgba(0, 0, 0, .7);
			color: white;
			border-radius: 3px;
			-webkit-transition: all .1s ease;
			transition: all .1s ease;
			pointer-events: none;
			/*-webkit-transform: translate(-50%, 0);
			transform: translate(-50%, 0);*/
		}
		.chartjs-tooltip-key {
			display: inline-block;
			width: 10px;
			height: 10px;
			margin-right: 10px;
		}
	</style>
</head>

<body>
  <div id="chartjs-tooltip">
			<table></table>
</div>
  <canvas id="myChart" width="400" height="400"></canvas>

  <script>
var customTooltips = function(tooltip) {
			// Tooltip Element
			var tooltipEl = document.getElementById('chartjs-tooltip');
			if (!tooltipEl) {
				tooltipEl = document.createElement('div');
				tooltipEl.id = 'chartjs-tooltip';
				tooltipEl.innerHTML = "<table></table>"
				this._chart.canvas.parentNode.appendChild(tooltipEl);
			}
			// Hide if no tooltip
			if (tooltip.opacity === 0) {
				tooltipEl.style.opacity = 0;
				return;
			}
			// Set caret Position
			tooltipEl.classList.remove('above', 'below', 'no-transform');
			if (tooltip.yAlign) {
				tooltipEl.classList.add(tooltip.yAlign);
			} else {
				tooltipEl.classList.add('no-transform');
			}
			function getBody(bodyItem) {
				return bodyItem.lines;
			}
			// Set Text
			if (tooltip.body) {
				var titleLines = tooltip.title || [];
				var bodyLines = tooltip.body.map(getBody);
				var innerHtml = '<thead>';
				titleLines.forEach(function(title) {
					innerHtml += '<tr><th>' + title + '</th></tr>';
				});
				innerHtml += '</thead><tbody>';
				bodyLines.forEach(function(body, i) {
					var colors = tooltip.labelColors[i];
					var style = 'background:' + colors.backgroundColor;
					style += '; border-color:' + colors.borderColor;
					style += '; border-width: 2px'; 
					var span = '<span class="chartjs-tooltip-key" style="' + style + '"></span>';
					innerHtml += '<tr><td>' + span + body + '</td></tr>';
				});
				innerHtml += '</tbody>';
				var tableRoot = tooltipEl.querySelector('table');
				tableRoot.innerHTML = innerHtml;
			}
			
			// find relative proportion
			var dataIndex = tooltip.dataPoints[0].index;
			var datasetIndex = tooltip.dataPoints[0].datasetIndex;
			var totalHeight = 0;
			
			var thisHeight = this._chart.config.data.datasets[datasetIndex].data[dataIndex];
			
			for (var i = 0; i <= datasetIndex; i++)
			{
  			  totalHeight += this._chart.config.data.datasets[i].data[dataIndex];
			}

			var positionY = this._chart.canvas.offsetTop;
			var positionX = this._chart.canvas.offsetLeft;
			var chartHeight = this._chart.canvas.scrollHeight;
			var tooltipHalfHeight = tooltip.height / 2;
			
			// Display, position, and set styles for font
			tooltipEl.style.opacity = 1;
			tooltipEl.style.left = positionX + tooltip.caretX + 'px';
			tooltipEl.style.top = tooltip.caretY + ((chartHeight - tooltip.caretY - positionY) * (thisHeight / totalHeight / 2)) - tooltipHalfHeight + 'px';
			tooltipEl.style.fontFamily = tooltip._fontFamily;
			tooltipEl.style.fontSize = tooltip.fontSize;
			tooltipEl.style.fontStyle = tooltip._fontStyle;
			tooltipEl.style.padding = tooltip.yPadding + 'px ' + tooltip.xPadding + 'px';
		};
		
		var ctx = document.getElementById("myChart").getContext('2d');
    var myChart = new Chart(ctx, {
      type: 'bar',
      data: {
        labels: ["This", "That", "Something else", "Important thing", "Oh really?", "What!!"],
        datasets: [{
          label: '# of Votes',
          data: [12, 19, 3, 5, 2, 3],
          backgroundColor: [
            'rgba(255, 99, 132, 0.2)',
            'rgba(54, 162, 235, 0.2)',
            'rgba(255, 206, 86, 0.2)',
            'rgba(75, 192, 192, 0.2)',
            'rgba(153, 102, 255, 0.2)',
            'rgba(255, 159, 64, 0.2)'
          ],
          borderColor: [
            'rgba(255,99,132,1)',
            'rgba(54, 162, 235, 1)',
            'rgba(255, 206, 86, 1)',
            'rgba(75, 192, 192, 1)',
            'rgba(153, 102, 255, 1)',
            'rgba(255, 159, 64, 1)'
          ],
          borderWidth: 1
        }, {
          data: [2, 5, 13, 5, 3, 4],
          backgroundColor: [
            'rgba(255, 206, 86, 0.2)',
            'rgba(75, 192, 192, 0.2)',
            'rgba(153, 102, 255, 0.2)',
            'rgba(255, 159, 64, 0.2)',
            'rgba(255, 99, 132, 0.2)',
            'rgba(54, 162, 235, 0.2)'
          ],
          borderColor: [
            'rgba(255, 206, 86, 1)',
            'rgba(75, 192, 192, 1)',
            'rgba(153, 102, 255, 1)',
            'rgba(255, 159, 64, 1)',
            'rgba(255,99,132,1)',
            'rgba(54, 162, 235, 1)'
          ],
          borderWidth: 1
        }]
      },
      options: {
        scales: {
          xAxes: [{
            stacked: true,
          }],
          yAxes: [{
            stacked: true
          }]
        },
        tooltips: {
          enabled: false,
          custom: customTooltips,
        }
      }
    });
  </script>
</body>

</html>

Plunker: http://plnkr.co/edit/f0EqpYe6zJMyIDxY4Xg9?p=preview




回答3:


Chartjs 2.8 allows you to add custom position modes for tooltips. With this you can create a center position option:

Chart.Tooltip.positioners.center = function (elements) {
    const { x, y, base } = elements[0]._model;
    const height = base - y;
    return { x, y: y + (height / 2) };
  };

See fiddle for working example: https://jsfiddle.net/astroash/wk5y0fqd/36/



来源:https://stackoverflow.com/questions/45415925/position-tooltip-in-center-of-bar

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!