Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ChartJs custom tooltip position

there. I use ChartJS and customise tooltip, but have issue with position first and last tooltip's. Look:

enter image description here I suppose that in order to fix the problem, I need to use the https://www.chartjs.org/docs/latest/configuration/tooltip.html#position-modes but, I cannot understand what the formula should be.

CodePen example - https://codepen.io/anon/pen/JzRooy

<html>

<head>
	<title>Line Chart with Custom Tooltips</title>
	<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.bundle.js"></script>
	<script>
	window.chartColors = {
		red: 'rgb(255, 99, 132)',
		orange: 'rgb(255, 159, 64)',
		yellow: 'rgb(255, 205, 86)',
		green: 'rgb(75, 192, 192)',
		blue: 'rgb(54, 162, 235)',
		purple: 'rgb(153, 102, 255)',
		grey: 'rgb(231,233,237)'
	};

	window.randomScalingFactor = function() {
		return (Math.random() > 0.5 ? 1.0 : -1.0) * Math.round(Math.random() * 100);
	}
	</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>
		<canvas id="chart"/>
	<script>
		Chart.defaults.global.pointHitDetectionRadius = 1;

		var customTooltips = function(tooltip) {
			var tooltipEl = document.getElementById('chartjs-tooltip');
			if (!tooltipEl) {
				tooltipEl = document.createElement('div');
				tooltipEl.id = 'chartjs-tooltip';
				tooltipEl.innerHTML = "<div class='wrapper'></div>"
				document.body.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 = '';

				titleLines.forEach(function(title) {
					innerHtml += '<span style="margin-bottom: 10px;display: inline-block;">' + title + '</span>';
				});
				innerHtml += '<div style="display: flex;flex-direction: row;">';

				bodyLines.forEach(function(body, i) {
					var parts = body[0].split(':');
					innerHtml += '<div style="display: flex;flex-direction: column;margin-right: 10px;font-size: 12px;">';
					innerHtml += '<span>' + parts[0].trim() + '</span>';
					innerHtml += '<b>' + parts[1].trim() + '</b>';
					innerHtml += '</div>';
				});
				innerHtml += '</div>';

				var root = tooltipEl.querySelector('.wrapper');
				root.innerHTML = innerHtml;
			}

			var canvas = this._chart.canvas;
			tooltipEl.style.opacity = 1;
			tooltipEl.style.left = canvas.offsetLeft + tooltip.caretX + 'px';
			tooltipEl.style.top = canvas.offsetTop + tooltip.caretY + 'px';
			tooltipEl.style.fontFamily = tooltip._fontFamily;
			tooltipEl.style.fontSize = tooltip.fontSize;
			tooltipEl.style.fontStyle = tooltip._fontStyle;
			tooltipEl.style.padding = "10px";
			tooltipEl.style.border = "1px solid #B4B6C1";
			tooltipEl.style.backgroundColor = "#FFFFFF";
			tooltipEl.style.color = "#4C4F59";
			tooltipEl.style.fontFamily = '"open sans", "helvetica neue", "arial", "sans-serif"';
		};

		var lineChartData = {
			labels: ["January", "February", "March", "April", "May", "June", "July"],
			datasets: [{
				label: "My First dataset",
				borderColor: window.chartColors.red,
				pointBackgroundColor: window.chartColors.red,
				fill: false,
				data: [
					randomScalingFactor(),
					randomScalingFactor(),
					randomScalingFactor(),
					randomScalingFactor(),
					randomScalingFactor(),
					randomScalingFactor(),
					randomScalingFactor()
				]
			}, {
				label: "My Second dataset",
				borderColor: window.chartColors.blue,
				pointBackgroundColor: window.chartColors.blue,
				fill: false,
				data: [
					randomScalingFactor(),
					randomScalingFactor(),
					randomScalingFactor(),
					randomScalingFactor(),
					randomScalingFactor(),
					randomScalingFactor(),
					randomScalingFactor()
				]
			}]
		};

		window.onload = function() {
			var chartEl = document.getElementById("chart");
			window.myLine = new Chart(chartEl, {
				type: 'line',
				data: lineChartData,
				options: {
					title:{
						display:true,
						text:'Chart.js Line Chart - Custom Tooltips'
					},
					tooltips: {
						enabled: false,
						mode: 'nearest',
						position: 'average',
						intersect: false,
						custom: customTooltips
					}
				}
			});
		};
	</script>
</body>

</html>
like image 309
user2164613 Avatar asked Mar 04 '19 16:03

user2164613


3 Answers

New modes can be defined by adding functions to the Chart.Tooltip.positioners map (DOC). This function returns the x and y position for the tooltip.

You can add a custom one to adjust the x at an offset. One way to do this would be to be:

    //register custome positioner
Chart.Tooltip.positioners.custom = function(elements, position) {
    if (!elements.length) {
      return false;
    }
    var offset = 0;
    //adjust the offset left or right depending on the event position
    if (elements[0]._chart.width / 2 > position.x) {
      offset = 20;
    } else {
      offset = -20;
    }
    return {
      x: position.x + offset,
      y: position.y
    }
  }

Fiddle example that I created

I hope it helps.

like image 70
Edi Avatar answered Nov 05 '22 12:11

Edi


I did this: Subtract the pixels by way of centering it or putting it in the position.

tooltipEl.style.left = canvas.offsetLeft + tooltip.caretX - 55 + 'px';
like image 39
Donato Aquino Barrios Avatar answered Nov 05 '22 12:11

Donato Aquino Barrios


I had the same issue and I didn't find a good solution, so I had to dot it myself.

Actually, it's simple than I thought, wish it helps someone.

Demo: https://codepen.io/themustafaomar/pen/wvWZrod

 const labels = ["1 April","2 April","3 April","4 April","5 April","6 April","7 April","8 April","9 April","10 April","11 April","12 April","13 April","14 April","15 April","16 April","17 April","18 April","19 April","20 April","21 April","22 April","23 April","24 April","25 April","26 April","27 April","28 April","29 April","30 April","31 April"]
const data = [ 95, 57, 72, 54, 73, 53, 98, 75, 52, 93, 50, 65, 99, 67, 77, 61, 74, 65, 86, 92, 64, 89, 82, 62, 64, 89, 59, 75, 56, 63 ];

function customTooltips(tooltipModel) {
// Tooltip Element
var tooltipEl = document.getElementById("chartjs-tooltip");

const yAlign = tooltipModel.yAlign;
const xAlign = tooltipModel.xAlign;

// Create element on first render
if (!tooltipEl) {
  tooltipEl = document.createElement("div");
  tooltipEl.id = "chartjs-tooltip";
  tooltipEl.innerHTML = "<table></table>";
  document.body.appendChild(tooltipEl);
}

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

// Set caret Position
tooltipEl.classList.remove("top", "bottom", "center", "left", "right");
if (tooltipModel.yAlign || tooltipModel.xAlign) {
  tooltipEl.classList.add(tooltipModel.yAlign);
  tooltipEl.classList.add(tooltipModel.xAlign);
}

// Set Text
if (tooltipModel.body) {
  var titleLines = tooltipModel.title || [];
  var bodyLines = tooltipModel.body.map((bodyItem) => {
    return bodyItem.lines;
  });

  var innerHtml = "<thead>";

  titleLines.forEach(function (title) {
    innerHtml += '<tr><th><div class="mb-1">' + title + "</div></th></tr>";
  });
  innerHtml += "</thead><tbody>";

  bodyLines.forEach((body, i) => {
    var colors = tooltipModel.labelColors[i];
    // var style = 'background-color:' + colors.borderColor
    var style =
      "background-color:" + this._chart.data.datasets[i].borderColor;
    var value = tooltipModel.dataPoints[i].value;
    var label = this._chart.data.datasets[i].label;

    style += "; border-color:" + colors.borderColor;
    style += "; border-color:" + this._chart.data.datasets[i].borderColor;
    style += "; border-width: 2px";

    var span =
      '<span class="chartjs-tooltip-key" style="' + style + '"></span>';

    innerHtml += `<tr><td> ${span} $${value}K </td></tr>`;
  });
  innerHtml += "</tbody>";

  var tableRoot = tooltipEl.querySelector("table");
  tableRoot.innerHTML = innerHtml;
}

// Tooltip height and width
const { height, width } = tooltipEl.getBoundingClientRect();

// Chart canvas positions
const positionY = this._chart.canvas.offsetTop;
const positionX = this._chart.canvas.offsetLeft;

// Carets
const caretY = tooltipModel.caretY;
const caretX = tooltipModel.caretX;

// Final coordinates
let top = positionY + caretY - height;
let left = positionX + caretX - width / 2;
let space = 8; // This for making space between the caret and the element.

// yAlign could be: `top`, `bottom`, `center`
if (yAlign === "top") {
  top += height + space;
} else if (yAlign === "center") {
  top += height / 2;
} else if (yAlign === "bottom") {
  top -= space;
}
// xAlign could be: `left`, `center`, `right`
if (xAlign === "left") {
  left = left + width / 2 - tooltipModel.xPadding - space / 2;
  if (yAlign === "center") {
    left = left + space * 2;
  }
} else if (xAlign === "right") {
  left -= width / 2;
  if (yAlign === "center") {
    left = left - space;
  } else {
    left += space;
  }
}

// Display, position, and set styles for font
tooltipEl.style.opacity = 1;

// Left and right
tooltipEl.style.top = `${top}px`;
tooltipEl.style.left = `${left}px`;

// Font
tooltipEl.style.fontFamily = tooltipModel._bodyFontFamily;
tooltipEl.style.fontSize = tooltipModel.bodyFontSize + "px";
tooltipEl.style.fontStyle = tooltipModel._bodyFontStyle;

// Paddings
tooltipEl.style.padding =
  tooltipModel.yPadding + "px " + tooltipModel.xPadding + "px";
}

// Chart
new Chart("chart", {
type: "line",
data: {
  labels,
  datasets: [
    {
      label: "Custom tooltip demo",
      borderColor: "#f66",
      backgroundColor: "transparent",
      lineTension: 0,
      borderWidth: 1.5,
      pointRadius: 2,
      data
    }
  ]
},
options: {
  responsive: true,
  maintainAspectRatio: false,
  legend: { display: false },
  scales: {
    // YAxes
    yAxes: [{ display: false }],

    // XAxes
    xAxes: [
      {
        display: false,
        gridLines: { display: false },
        ticks: {
          padding: 20,
          autoSkipPadding: 30,
          maxRotation: 0
        }
      }
    ]
  },
  tooltips: {
    enabled: false,
    intersect: false,
    mode: "index",
    position: "average",
    custom: customTooltips
  }
}
});
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}

.chartjs-wrapper {
height: 90px;
width: 300px;
margin: 25px auto 0;
border: 1px solid #e6e6e6;
}

#chartjs-tooltip {
opacity: 1;
position: absolute;
color: #fff;
background-color: #000;
border-radius: 6px;
transition: all 0.25s ease-in-out;
pointer-events: none;
}

#chartjs-tooltip:after {
content: "";
display: block;
position: absolute;
margin: auto;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
border-width: 6px;
}

/* Top center */
#chartjs-tooltip.top.center:after {
border-bottom-color: #000;
top: -11px;
left: 0;
right: 0;
}
/* Top left */
#chartjs-tooltip.top.left:after {
border-bottom-color: #000;
left: 5px;
top: -11px;
}
/* Top right */
#chartjs-tooltip.top.right:after {
border-bottom-color: #000;
right: 5px;
top: -11px;
}

/* Bottom center */
#chartjs-tooltip.bottom.center:after {
border-top-color: #000;
bottom: -11px;
right: 0;
left: 0;
}
/* Bottom left */
#chartjs-tooltip.bottom.left:after {
border-top-color: #000;
bottom: -11px;
}
/* Bottom right */
#chartjs-tooltip.bottom.right:after {
border-top-color: #000;
bottom: -11px;
right: 5px;
}

/* Center left */
#chartjs-tooltip.center.left:after {
border-right-color: #000;
margin: auto;
left: -11px;
bottom: 0;
top: 0;
}
/* Center right */
#chartjs-tooltip.center.right:after {
border-left-color: #000;
margin: auto;
right: -11px;
bottom: 0;
top: 0;
}

.chartjs-tooltip-key {
display: inline-block;
border-radius: 50%;
width: 10px;
height: 10px;
margin-right: 7px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js"></script>
<div class="chartjs-wrapper">
  <canvas id="chart"></canvas>
</div>
like image 3
Mustafa Omar Avatar answered Nov 05 '22 12:11

Mustafa Omar