//
// Copyright © 2011-2025 Cambridge Intelligence Limited.
// All rights reserved.
//
// Sample Code
//
//! Use tooltips to display useful information about nodes and links.
import KeyLines from "keylines";
import { data } from "./tooltips-data.js";
// declaring global vars
let chart;
const nodeBaseSize = 26;
const tooltipContainer = document.querySelector("#tooltip-container");
const tooltip = {
itemId: null,
element: null,
};
// Create the HTML code that is going to fill the tooltip
const templateHtml = document.getElementById("tt_html").innerHTML;
function updateTooltipPosition() {
if (tooltip.itemId) {
const { itemId, element } = tooltip;
const item = chart.getItem(itemId);
const coordinates = chart.viewCoordinates(item.x, item.y);
const x = coordinates.x;
const y = coordinates.y;
const zoom = chart.viewOptions().zoom;
const arrowTipOffset = 20;
// get the size of the node on screen
const nodeSize = nodeBaseSize * (item.e || 1) * zoom;
element.style.opacity = 0;
// allow fade in and out to animate
element.style.transition = "opacity 0.3s ease";
// scale the size of the tooltip depending on zoom level
element.style.transform = `scale(${Math.max(0.75, Math.min(2, zoom))}`;
const top = y - element.clientHeight / 2;
element.style.left = `${x + arrowTipOffset + nodeSize}px`;
element.style.top = `${top}px`;
element.style.opacity = 1;
}
}
function closeTooltip() {
if (tooltip.element) {
tooltip.element.style.opacity = 0;
tooltip.itemId = null;
tooltip.element = null;
}
}
function handleTooltip({ id }) {
const item = chart.getItem(id);
if (item && item.type === "node") {
const html = templateHtml
.replace(/{{label}}/, item.t)
.replace(/{{gender}}/, item.d.g)
.replace(/{{name}}/, item.d.fn)
.replace(/{{surname}}/, item.d.ln);
// Add it to the DOM
tooltipContainer.innerHTML = html;
tooltip.element = document.getElementById("tooltip");
tooltip.itemId = id;
updateTooltipPosition();
} else if (tooltip.element) {
closeTooltip();
}
}
function chartLoaded() {
let { width, height } = chart.viewOptions();
chart.on("view-change", () => {
const { width: newWidth, height: newHeight } = chart.viewOptions();
if (width !== newWidth || height !== newHeight) {
closeTooltip();
width = newWidth;
height = newHeight;
}
updateTooltipPosition();
});
chart.on("drag-move", updateTooltipPosition);
chart.on("hover", handleTooltip);
chart.load(data);
chart.layout();
}
async function startKeyLines() {
const options = {
drag: {
links: false,
},
logo: { u: "/images/Logo.png" },
hover: 100,
iconFontFamily: "Font Awesome 5 Free",
imageAlignment: { ["far fa-user"]: { dy: -5, e: 0.9 } },
};
chart = await KeyLines.create({
container: "klchart",
options,
});
chartLoaded();
}
function loadKeyLines() {
document.fonts.load('24px "Font Awesome 5 Free"').then(startKeyLines);
}
window.addEventListener("DOMContentLoaded", loadKeyLines);
<!DOCTYPE html>
<html lang="en" style="background-color: #2d383f;">
<head>
<meta charset="UTF-8">
<title>Tooltips on Items</title>
<link rel="stylesheet" type="text/css" href="/css/keylines.css">
<link rel="stylesheet" type="text/css" href="/css/minimalsdk.css">
<link rel="stylesheet" type="text/css" href="/css/sdk-layout.css">
<link rel="stylesheet" type="text/css" href="/css/demo.css">
<link rel="stylesheet" type="text/css" href="/css/fontawesome5/fontawesome.css">
<link rel="stylesheet" type="text/css" href="/css/fontawesome5/regular.css">
<link rel="stylesheet" type="text/css" href="/tooltips.css">
<script src="/vendor/jquery.js" defer type="text/javascript"></script>
<script id="keylines" src="/public/keylines.umd.cjs" defer type="text/javascript"></script>
<script src="/tooltips.js" crossorigin="use-credentials" defer type="module"></script>
</head>
<body>
<div class="chart-wrapper demo-cols">
<div class="tab-content-panel flex1" id="lhs" data-tab-group="rhs">
<div class="toggle-content is-visible" id="lhsVisual" style="width:100%; height: 100%;">
<div class="klchart" id="klchart">
<div id="tooltip-container"></div>
</div>
</div>
</div>
<div class="rhs citext closed" id="demorhscontent">
<div class="title-bar">
<svg viewBox="0 0 360 60" style="max-width: 360px; max-height: 60px; font-size: 24px; font-family: Raleway; flex: 1;">
<text x="0" y="38" style="width: 100%;">Tooltips on Items</text>
</svg>
</div>
<div class="tab-content-panel" data-tab-group="rhs">
<div class="toggle-content is-visible tabcontent" id="controlsTab">
<form autocomplete="off" onsubmit="return false" id="rhsForm">
<div class="cicontent">
<p>Hover over the chart nodes (or tap on touch devices) to show tooltips with details.</p>
</div>
</form>
</div>
</div>
</div>
</div>
<div id="moreParent">
<div id="moreContainer">
<template id="tt_html">
<div class="popover right" id="tooltip" style="margin:0px; transform-origin:left;position: absolute; min-width: 70px; pointer-events: none;">
<div class="arrow"></div>
<h2 class="popover-title"><strong>{{label}}</strong></h2>
<div class="popover-content">
<table class="table-condensed">
<tbody>
<tr>
<td style="text-align: right;"><strong>Gender</strong></td>
<td>{{gender}}</td>
</tr>
<tr>
<td style="text-align: right;"><strong>Name</strong></td>
<td>{{name}}</td>
</tr>
<tr>
<td style="text-align: right;"><strong>Surname</strong></td>
<td>{{surname}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
</div>
</div>
<div id="lazyScripts">
</div>
</body>
</html>
#htmlModal {
-webkit-user-select: text;
-khtml-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
}
/* Tooltip styling */
.popover {
z-index: 1000;
background: white;
}
:fullscreen .popover {
margin-top: -70px;
}
#tooltip .popover-title {
font-size: 16px;
padding: 8px 14px;
margin: 0px;
line-height: 20px;
top: 0px;
}
#tooltip .popover-content {
font-size: 12px;
border: 1px solid gray;
}
#tooltip .popover-content .td {
border: 1px solid gray;
max-height: 20px;
}
#tooltip .arrow {
background-color: white;
border-bottom: 1px solid gray;
border-left: 1px solid gray;
transform: translate(-5px, 60px) rotateZ(45deg);
width: 10px;
height: 10px;
position: absolute;
}
.popover-content {
padding: 9px 14px;
}
.klchart {
overflow: hidden;
}
#tooltip {
background: white;
border: black;
transition: opacity 0.3s ease;
}
Loading source
