Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save JimmyCheng/b5a7a48e51d0b5d62aee718e10039e16 to your computer and use it in GitHub Desktop.
Save JimmyCheng/b5a7a48e51d0b5d62aee718e10039e16 to your computer and use it in GitHub Desktop.
This is test to see how difficult it would be to add multiple arrows to vis.js timeline view. Can be previewed with https://gistpreview.github.io/?b5a7a48e51d0b5d62aee718e10039e16
<!DOCTYPE html>
<html>
<head>
<title>Timeline | Group example, with an arrow</title>
<style>
body,
html {
font-family: arial, sans-serif;
font-size: 11pt;
}
#visualization {
box-sizing: border-box;
width: 100%;
height: 300px;
}
</style>
<!-- note: moment.js must be loaded before vis.js, else vis.js uses its embedded version of moment.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.8.4/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis.min.js"></script>
<link
href="https://cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis.min.css"
rel="stylesheet"
type="text/css"
/>
</head>
<body>
<p>
This is the same example which is in
"vis/examples/timeline/groups/groups.html" but with multiple added arrows defined in the dependencies.
The original discussion thread is https://github.com/almende/vis/issues/1699.
</p>
<div id="visualization"></div>
<script>
const getItemPos = function(item) {
left_x = item.left;
top_y = item.parent.top + item.parent.height - item.top - item.height;
return {
left: left_x,
top: top_y,
right: left_x + item.width,
bottom: top_y + item.height,
mid_x: left_x + item.width / 2,
mid_y: top_y + item.height / 2,
width: item.width,
height: item.height
};
};
const drawArrows = function(i, j, index) {
console.log("read dependencyPath", dependencyPath);
var item_i = getItemPos(timeline.itemSet.items[i]);
var item_j = getItemPos(timeline.itemSet.items[j]);
if (item_j.mid_x < item_i.mid_x) [item_i, item_j] = [item_j, item_i]; // As demo, we put an arrow between item 0 and item1, from the one that is more on left to the one more on right.
var curveLen = item_i.height * 2; // Length of straight Bezier segment out of the item.
item_j.left -= 10; // Space for the arrowhead.
dependencyPath[index].setAttribute(
"d",
"M " +
item_i.right +
" " +
item_i.mid_y +
" C " +
(item_i.right + curveLen) +
" " +
item_i.mid_y +
" " +
(item_j.left - curveLen) +
" " +
item_j.mid_y +
" " +
item_j.left +
" " +
item_j.mid_y
);
};
const dependency = [[1, 2], [3, 5], [6, 7], [3, 8]];
const drawDependencies = dependency => {
dependency.map((dep, index) => drawArrows(...dep, index));
};
const options = {
groupOrder: "content", // groupOrder can be a property name or a sorting function
onInitialDrawComplete: function() {
drawDependencies(dependency);
timeline.on("changed", () => {
drawDependencies(dependency);
}); //NOTE: We hijack the on "changed" event to draw the arrow.
}
};
// Generate some
var now = moment()
.minutes(0)
.seconds(0)
.milliseconds(0);
var names = ["John", "Alston", "Lee", "Grant"];
var itemCount = 20;
// create a data set with groups
var groups = new vis.DataSet();
for (var g = 0; g < names.length; g++) {
groups.add({ id: g, content: names[g] });
}
// create a dataset with items
var items = new vis.DataSet();
for (var i = 0; i < itemCount; i++) {
var start = now.clone().add(Math.random() * 200, "hours");
var group = Math.floor(Math.random() * names.length);
items.add({
id: i,
group: group,
content:
"item " +
i +
' <span style="color:#97B0F8;">(' +
names[group] +
")</span>",
start: start,
type: "box"
});
}
// Create visualization.
const container = document.getElementById("visualization");
const timeline = new vis.Timeline(container, items, groups, options);
// Create SVG layer on top of timeline "center" div.
svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.style.position = "absolute";
svg.style.top = "0px";
svg.style.height = "100%";
svg.style.width = "100%";
svg.style.display = "block";
svg.style.zIndex = "1"; // Should it be above or below? (1 for above, -1 for below)
svg.style.pointerEvents = "none"; // To click through, if we decide to put it above other elements.
timeline.dom.center.appendChild(this.svg);
// Add arrowhead definition to SVG.
var arrowHead = document.createElementNS(
"http://www.w3.org/2000/svg",
"marker"
);
arrowHead.setAttribute("id", "arrowhead0");
arrowHead.setAttribute("viewBox", "-10 -5 10 10");
arrowHead.setAttribute("refX", "-7");
arrowHead.setAttribute("refY", "0");
arrowHead.setAttribute("markerUnits", "strokeWidth");
arrowHead.setAttribute("markerWidth", "3");
arrowHead.setAttribute("markerHeight", "3");
arrowHead.setAttribute("orient", "auto");
var arrowHeadPath = document.createElementNS(
"http://www.w3.org/2000/svg",
"path"
);
arrowHeadPath.setAttribute("d", "M 0 0 L -10 -5 L -7.5 0 L -10 5 z");
arrowHeadPath.style.fill = "#F00";
arrowHead.appendChild(arrowHeadPath);
svg.appendChild(arrowHead);
// Add empty path (for now); it will be dynamically modified.
const dependencyPath = [];
for (let i = 0; i < dependency.length; i++) {
const somePath = document.createElementNS(
"http://www.w3.org/2000/svg",
"path"
);
somePath.setAttribute("d", "M 0 0");
somePath.setAttribute("marker-end", "url(#arrowhead0)");
somePath.style.stroke = "#F00";
somePath.style.strokeWidth = "3px";
somePath.style.fill = "none";
dependencyPath.push(somePath);
console.log("add somepath to dependencyPath", somePath);
svg.appendChild(somePath);
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment