//
// Copyright © 2011-2025 Cambridge Intelligence Limited.
// All rights reserved.
//
// Sample Code
//
//! Drag nodes onto the chart to create a network.
import KeyLines from "keylines";
let chart;
let container;
const draggableArea = document.getElementsByClassName(
"chart-wrapper demo-cols"
)[0];
// used for item creation
let nodeIdCounter = 0;
let linkIdCounter = 0;
let dragIcon = "";
let clone;
let dragStartCoords;
function removePlusIcon(nodeId) {
if (nodeId) {
chart.setProperties({ id: nodeId, g: null });
}
}
function removeAllPlusIcons() {
chart.each({ type: "node" }, (node) => {
if (node.g) {
removePlusIcon(node.id);
}
});
}
// Glyphs
function addPlusIcon({ id }) {
removeAllPlusIcons();
const hoveringItem = chart.getItem(id);
// in case the cursor is hovering a node, add the icon
if (id && hoveringItem.type === "node") {
chart.setProperties({ id, g: [{ p: "ne", t: "+", c: "green" }] });
}
}
async function createLink(nodeId, newLinkId) {
// Define the style of the new link
const newLinkStyle = {
c: "rgb(255, 127, 127)",
w: 5,
a2: true, // link directed to the new node
};
// create the link and remove the + icon at the end in any case
const linkedItemId = await chart.createLink(nodeId, newLinkId, {
style: newLinkStyle,
});
removePlusIcon(linkedItemId);
}
function startLinkCreation({ id: nodeId, type, subItem }) {
// if the node has the plus icon on the top-right corner...
if (type === "node" && subItem.subId === "ne") {
const linkId = `link${++linkIdCounter}`;
// override the default node dragger with create-link
createLink(nodeId, linkId);
}
}
// place and setup the label box
function openLabelEditBox({ id }) {
const item = chart.getItem(id);
if (!item) return;
const editLabel = document.getElementById("editLabel");
const chartBox = document.getElementById("klchart").getBoundingClientRect();
let keyDownHandler;
function closeEdit() {
editLabel.style.display = "none";
chart.lock(false);
// detach event listeners from the box
editLabel.removeEventListener("keydown", keyDownHandler, false);
editLabel.removeEventListener("blur", closeEdit, false);
}
keyDownHandler = (evt) => {
if (evt.keyCode === 27) {
// esc
// exit without save
closeEdit();
}
if (evt.keyCode === 13) {
// return
// save
chart.setProperties({ id, t: editLabel.value });
// and exit
closeEdit();
}
};
const labelPosition = chart.labelPosition(id);
labelPosition.x1 += chartBox.left;
labelPosition.x2 += chartBox.left;
labelPosition.y1 += chartBox.top - 8;
labelPosition.y2 += chartBox.top - 8;
const labelWidth = labelPosition.x2 - labelPosition.x1;
const editBoxWidth = Math.max(labelWidth, 80);
const deltaWidth = editBoxWidth - labelWidth;
// Place the box just under the node
Object.assign(editLabel.style, {
fontSize: `${labelPosition.fs}px`,
top: `${labelPosition.y1}px`,
left: `${labelPosition.x1 - deltaWidth / 2}px`,
height: `${labelPosition.y2 - labelPosition.y1 + 4}px`,
width: `${editBoxWidth}px`,
});
// copy and paste the current label in the box
editLabel.value = item.t || "";
// listen for which key the user presses
editLabel.addEventListener("keydown", keyDownHandler, false);
// on blur, exit without save
editLabel.addEventListener("blur", closeEdit, false);
// show the box, focus and set it ready to be filled
editLabel.style.display = "block";
editLabel.focus();
editLabel.select();
// lock the chart
chart.lock(true);
}
function followMouse(evt) {
// The cloned icon has 'position: fixed'.
// On mousemove, we update its top and left CSS values
// by looking at the current mouse position.
clone.style.left = `${evt.clientX - clone.clientWidth / 2}px`;
clone.style.top = `${evt.clientY - clone.clientHeight / 2}px`;
}
function getIconColor(iconName) {
const icon = document.querySelector(`#dragdropTable i.fa-${iconName}`);
return getComputedStyle(icon).color;
}
function removeClone() {
clone.parentElement.removeChild(clone);
clone = null;
}
function createNode(mouseViewCoords) {
// mouseViewCoords gives the position of the
// mouseup event, relative to the top-left corner of the the page
const chartBox = document.getElementById("klchart").getBoundingClientRect();
const x = mouseViewCoords.x - chartBox.left;
const y = mouseViewCoords.y - chartBox.top;
const pos = chart.worldCoordinates(x, y);
const id = `node${++nodeIdCounter}`;
// create the node
chart.setItem({
type: "node",
id,
x: pos.x,
y: pos.y,
fi: {
t: `fas fa-${dragIcon}`,
c: getIconColor(dragIcon),
},
t: "New Item",
});
openLabelEditBox({ id });
removeClone();
}
function revertDrag() {
// dropped icon outside of chart, so we slide it back to initial position
clone.classList.add("transition");
clone.style.left = `${dragStartCoords.x}px`;
clone.style.top = `${dragStartCoords.y}px`;
// when the transition is over, remove the cloned fonticon
setTimeout(removeClone, 500);
}
function endDrag(evt) {
document.removeEventListener("mousemove", followMouse, false);
document.removeEventListener("mouseup", endDrag, false);
// check whether we have dropped the cloned icon within the chart area
const klRect = container.getBoundingClientRect();
const mouseViewCoords = {
x: evt.clientX,
y: evt.clientY,
};
const withinChartX =
mouseViewCoords.x >= 0 && mouseViewCoords.x <= klRect.width;
const withinChartY =
mouseViewCoords.y >= 0 && mouseViewCoords.y <= klRect.height;
const mouseIsOverChart = withinChartX && withinChartY;
if (mouseIsOverChart) {
createNode(mouseViewCoords);
} else {
revertDrag();
}
}
function startDrag(evt) {
if (!clone) {
const element = evt.target;
dragIcon = element.dataset.icon;
clone = element.cloneNode(true);
draggableArea.appendChild(clone);
// add id to apply CSS
clone.id = "icon-clone";
dragStartCoords = {
x: evt.clientX,
y: evt.clientY,
};
followMouse(evt);
document.addEventListener("mousemove", followMouse, false);
document.addEventListener("mouseup", endDrag, false);
}
}
function makeDraggable(element) {
element.addEventListener("mousedown", startDrag, false);
}
function setUpUI() {
container = document.getElementById("klchart");
chart.on("hover", addPlusIcon);
chart.on("drag-start", startLinkCreation);
chart.on("double-click", openLabelEditBox);
const draggables = document.querySelectorAll(".draggable");
draggables.forEach(makeDraggable);
const layoutButton = document.getElementById("layout");
layoutButton.addEventListener(
"click",
() => {
chart.layout("organic", { tightness: 2 });
},
false
);
const clearButton = document.getElementById("clear");
clearButton.addEventListener("click", chart.clear, false);
}
async function startKeyLines() {
const imageAlignment = {
"fa-female": { e: 1.2 },
"fa-male": { e: 1.2 },
"fa-money-bill-alt": { e: 1.125 },
};
const options = {
drag: {
links: false,
},
logo: { u: "/images/Logo.png" },
// quick hover refresh time to set the glyph icon
hover: 5,
iconFontFamily: "Font Awesome 5 Free",
imageAlignment,
};
// create the chart
chart = await KeyLines.create({ container: "klchart", options });
setUpUI();
}
function loadFontsAndStart() {
document.fonts.load('24px "Font Awesome 5 Free"').then(startKeyLines);
}
window.addEventListener("DOMContentLoaded", loadFontsAndStart);
<!DOCTYPE html>
<html lang="en" style="background-color: #2d383f;">
<head>
<meta charset="UTF-8">
<title>Drag and Drop 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/solid.css">
<link rel="stylesheet" type="text/css" href="/drawing.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="/drawing.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">
<input id="editLabel" type="text" name="label" style="display: none; position: fixed; max-height 0px;">
</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%;">Drag and Drop 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>
Drag the icons from the panel onto the chart.
Draw a new link by dragging from the plus icon of one node to another node.
To edit a label, double click on a node or link.
</p>
<p>This demo is not supported on touch devices.</p>
<div id="dragdropTable">
<table class="table table-bordered">
<tbody>
<tr>
<td><i class="fas fa-female draggable drag-drop-img" data-icon="female"></i></td>
<td><i class="fas fa-male draggable drag-drop-img" data-icon="male"></i></td>
</tr>
<tr>
<td><i class="fas fa-laptop draggable drag-drop-img" data-icon="laptop"></i></td>
<td><i class="fas fa-money-bill-alt draggable drag-drop-img" data-icon="money-bill-alt"></i></td>
</tr>
<tr>
<td><i class="fas fa-at draggable drag-drop-img" data-icon="at"></i></td>
<td><i class="fas fa-server draggable drag-drop-img" data-icon="server"></i></td>
</tr>
<tr>
<td><i class="fas fa-home draggable drag-drop-img" data-icon="home"></i></td>
<td><i class="fas fa-mobile-alt draggable drag-drop-img" data-icon="mobile-alt"></i></td>
</tr>
<tr>
<td><i class="fas fa-building draggable drag-drop-img" data-icon="building"></i></td>
<td><i class="fas fa-credit-card draggable drag-drop-img" data-icon="credit-card"></i></td>
</tr>
</tbody>
</table>
</div>
<fieldset>
<input class="btn" type="button" value="Run layout" id="layout">
<input class="btn" type="button" value="Clear" id="clear">
</fieldset>
</div>
</form>
</div>
</div>
</div>
</div>
<div id="moreParent">
<div id="moreContainer">
</div>
</div>
<div id="lazyScripts">
</div>
</body>
</html>
.cicontent fieldset {
margin: 0 auto;
max-width: 250px;
}
#clear {
float: right;
}
/* Standard Table Styling */
table {
max-width: 100%;
background-color: transparent;
border-collapse: collapse;
border-spacing: 0;
}
.table {
width: 100%;
margin-bottom: 20px;
}
.table-bordered {
border: 1px solid #ddd;
border-collapse: separate;
*border-collapse: collapse;
border-left: 0;
}
.table th, .table td {
line-height: 20px;
text-align: left;
vertical-align: top;
}
.table td { border-top: 1px solid #ddd; }
.table-condensed th, .table-condensed td {
padding: 4px 5px;
}
.table-bordered th, .table-bordered td {
border-left: 1px solid #ddd;
}
.table-bordered tbody:first-child tr:first-child td {
border-top: 0;
}
/**/
#dragdropTable tr {
height: 3rem;
}
#dragdropTable td {
height: 4rem;
position: relative;
text-align: center;
padding: 10px 16px;
}
.tab-content {
overflow: visible;
}
.table i.fas {
cursor: pointer;
width: 50%;
left: 0;
top: 10px;
position: absolute;
font-size: 2.5rem;
height: 2.5rem;
line-height: 2.5rem;
margin-left: 25%;
}
.drag-drop-img {
z-index: 26;
width: 60px;
height: 60px;
}
#icon-clone {
display: block;
position: fixed;
text-align: center;
z-index: 1000;
max-width: 50px;
max-height: 50px;
margin: 0;
pointer-events: none;
}
i.fas.transition {
transition: top 0.5s ease-in-out, left 0.5s ease-in-out;
}
i.fa-female,
i.fa-male {
font-size: 3rem;
}
i.fa-laptop {
color: #bc80bd;
font-size: 2.3rem;
height: 2.3rem;
}
i.fa-female {
color: #ff6347;
}
i.fa-male {
color: #00008b;
}
i.fa-money-bill-alt {
color: #006400;
}
i.fa-at {
color: #b3de69;
}
i.fa-server {
color: #fdb462;
}
i.fa-home {
color: #80b1d3;
}
i.fa-mobile-alt {
color: #c71585;
}
i.fa-building {
color: #b22222;
}
i.fa-credit-card {
color: #3cb371;
}
Loading source
