Newer
Older
const highlightNodes = new Set();
const highlightLinks = new Set();
let hoverNode = null;
const colorPallette = ['rgb(104, 169, 77)', 'rgb(102, 75, 154)', 'rgb(41, 171, 226)', 'rgb(224, 133, 35)',
'rgb(214, 207, 126)', 'rgb(239, 65, 35)', 'rgb(255, 255, 255)'];
const edgeColors = {};
var graph_element = document.getElementById('3d-graph');
loadGraph();
async function loadGraph() {
const dataUrl = plugin_path + 'datasets/aud1.json'
const gData = await fetch(dataUrl).then(res => res.json())
Graph = ForceGraph3D({extraRenderers: [new THREE.CSS2DRenderer(), new THREE.CSS3DRenderer()]})
(document.getElementById('3d-graph'))
.graphData(gData)
.nodeLabel('id')
.nodeAutoColorBy('group')
.nodeColor(node => highlightNodes.has(node) ? node === hoverNode ? 'rgb(255,0,0,1)' : 'rgba(255,160,0,0.8)' : 'rgba(0,255,255,0.6)')
.linkWidth(link => highlightLinks.has(link) ? 2 : 0.8)

Matthias Konitzny
committed
.onNodeClick(handleNodeClick)
.onNodeHover(nodeHover)
.onLinkHover(linkHover)
.nodeThreeObjectExtend(false)
.nodeThreeObject(drawNode)
.onEngineTick(loadComponents)
.width(getWidth())
//Graph.renderer().sortObjects = true;

Matthias Konitzny
committed
createLinkOverlay();
createInfoOverlay();
add_fullscreen_Listener();
function resize_graph() {
Graph.width(getWidth());
Graph.height(getHeight());
}
return document.getElementById('3d-graph').offsetWidth;
function getHeight() {
return window.innerHeight - 200
}
function mapEdgeColors() {
const linkClasses = getLinkClasses()
for (let i = 0; i < linkClasses.length; i++) {
edgeColors[linkClasses[i]] = colorPallette[i % colorPallette.length]
}
}
function getLinkClasses() {
const linkClasses = [];
Graph.graphData().links.forEach(link => linkClasses.push(link.type));
return [... new Set(linkClasses)];
}

Matthias Konitzny
committed
function getCanvasDivNode() {
const domNode = document.getElementById('3d-graph');

Matthias Konitzny
committed
return domNode.firstChild.firstChild.firstChild;
}
function drawNode(node) {
// Draw node as label + image
const nodeDiv = document.createElement('div');
group = new THREE.Group();
const labelDiv = document.createElement('div')
labelDiv.textContent = node.name;
labelDiv.style.color = node.color;
labelDiv.className = 'node-label';
nodeDiv.appendChild(labelDiv);
const cssobj = new THREE.CSS3DSprite(nodeDiv);
cssobj.scale.set(0.25, 0.25, 0.25);
cssobj.position.set(0, -6, 0);
group.add(cssobj)
// Draw node circle image
const textureLoader = new THREE.TextureLoader();
const imageAlpha = textureLoader.load(plugin_path + 'datasets/images/alpha.png');
let imageTexture = null;
if ('image' in node) {
imageTexture = textureLoader.load(plugin_path + 'datasets/images/' + node.image);
} else {
imageTexture = textureLoader.load(plugin_path + 'datasets/images/default.jpg');
}
const material = new THREE.SpriteMaterial({map: imageTexture, alphaMap: imageAlpha, transparent: true,
alphaTest: 0.2, depthWrite:false, depthTest: false});
const sprite = new THREE.Sprite(material);
sprite.renderOrder = 999; // This may not be optimal. But it allows us to render the sprite on top of everything else.
if ('image' in node) {
sprite.scale.set(20, 20);
} else {
sprite.scale.set(5, 5);
group.add(sprite)

Matthias Konitzny
committed
function createInfoOverlay() {
const sceneNode = getCanvasDivNode();
const overlayNode = document.createElement('div');
overlayNode.id = 'infoOverlay'
overlayNode.className = 'detail-view';
//overlayNode.innerText = 'Hello there!';
sceneNode.insertBefore(overlayNode, sceneNode.childNodes[2]);
const close = document.createElement('div');
close.innerHTML = '<p>✖</p>';
close.id = 'infoOverlayCloseButton';
close.className = 'close-button';

Matthias Konitzny
committed
overlayNode.appendChild(close);
const topArea = document.createElement('div');
topArea.className = 'detail-view-top-area';
overlayNode.appendChild(topArea);
const nodeImage = document.createElement('img');
nodeImage.id = 'infoOverlayImage';
nodeImage.src = plugin_path + 'datasets/images/default.jpg';
nodeImage.className = 'detail-view-image';
topArea.appendChild(nodeImage);
const headline = document.createElement('h2');
headline.id = 'infoOverlayHeadline';
headline.innerText = 'Default Text';
headline.className = 'detail-view-headline';
topArea.appendChild(headline);
const textArea = document.createElement('div');
textArea.className = 'detail-view-text-area';
overlayNode.appendChild(textArea);
const description = document.createElement('p');
description.id = 'infoOverlayDescription';
description.innerText = 'Default Text'
description.setAttribute("overflow-y", "scroll");
textArea.appendChild(description);
const link_container = document.createElement('div');
const links = document.createElement('div');
const linkClasses = getLinkClasses();
link_container.id = 'link_container';
link_container.className = 'bottom container';
links.className = 'tab';
links.id = 'links';
for(i = 0; i < linkClasses.length; i++){
const linkType = linkClasses[i];
const tablink = document.createElement('button');
tablink.className = 'tablinks';
tablink.id = linkType + "_id";
tablink.innerHTML = linkType[0] + linkType[1] + linkType[2];
tablink.style = "background-color: " + edgeColors[linkClasses[i]];
tablink.setAttribute("onclick", "openTab(event, " + linkType + "_id)"); //unusual function call
links.appendChild(tablink);
}
link_container.appendChild(links);
for(i = 0; i < linkClasses.length; i++){
const tabcontent = document.createElement('div');
tabcontent.className = 'tabcontent';
tabcontent.id = linkType + "_id_content";
tabcontent.style = "background-color: " + edgeColors[linkClasses[i]];
link_container.appendChild(tabcontent);
overlayNode.appendChild(link_container);

Matthias Konitzny
committed
jQuery('#infoOverlayCloseButton').click(function () {
jQuery('#infoOverlay').slideUp('fast');
});
//sceneNode.appendChild(overlayNode);
}
function createLinkOverlay() {
const sceneNode = getCanvasDivNode();
const overlayNode = document.createElement('div');

Matthias Konitzny
committed
overlayNode.className = 'link-overlay';
sceneNode.insertBefore(overlayNode, sceneNode.childNodes[2])
const chars = Math.max.apply(Math, linkClasses.map(function (c) { return c.length; }));
for (let i=0; i<linkClasses.length; i++) {
const relation = document.createElement('div');
relation.className = 'relation';
relation.innerHTML = "<p>" + linkClasses[i] + "</p>";
const colorStrip = document.createElement('div');
colorStrip.className = 'rel-container';
colorStrip.style = "background-color: " + edgeColors[linkClasses[i]] + "; width: " + 10 * chars + "px;";
relation.appendChild(colorStrip);
function add_fullscreen_mode_button() {
const sceneNode = document.getElementById('3d-graph');
const overlayNode = document.createElement('button');
overlayNode.className = 'fullscreen_button';
overlayNode.innerText = 'fullscreen';
overlayNode.addEventListener("click", fullscreen_mode);
sceneNode.appendChild(overlayNode);
if(getCanvasDivNode().requestFullscreen) {
getCanvasDivNode().requestFullscreen().catch();
}
}
function resize_canvas() {
if(document.fullscreenElement == getCanvasDivNode()) {
Graph.height(screen.height);
Graph.width(screen.width);
} else {
Graph.height(window.innerHeight - 200);
Graph.width(getWidth());
function add_fullscreen_Listener() {
getCanvasDivNode().addEventListener("fullscreenchange", resize_canvas);
}
function add_background() {
const sphereGeometry = new THREE.SphereGeometry(20000, 32, 32);
//const planeGeometry = new THREE.PlaneGeometry(1000, 1000, 1, 1);
const loader = new THREE.TextureLoader();
//const planeMaterial = new THREE.MeshLambertMaterial({color: 0xFF0000, side: THREE.DoubleSide}); //THREE.BackSide
const planeMaterial = new THREE.MeshBasicMaterial({map: loader.load(plugin_path + 'backgrounds/background_3.jpg'), side: THREE.DoubleSide}); //THREE.BackSide
const mesh = new THREE.Mesh(sphereGeometry, planeMaterial);
mesh.position.set(0, 0, 0);
//mesh.rotation.set(0.5 * Math.PI, 0, 0);
Graph.scene().add(mesh);
}
function updateLinks() {
// cross-link node objects
gData.links.forEach(link => {
const a = link.source;
const b = link.target;
if (!a.neighbors) a.neighbors = [];
if (!b.neighbors) b.neighbors = [];
a.neighbors.push(b);
b.neighbors.push(a);
if (!a.links) a.links = [];
if (!b.links) b.links = [];
a.links.push(link);
b.links.push(link);
});
Graph.graphData(gData)
}

Matthias Konitzny
committed
function handleNodeClick(node) {
focusOnNode(node);
updateInfoOverlay(node);
}
function updateInfoOverlay(node) {
jQuery('#infoOverlayHeadline').text(node.name);
if ('image' in node) {
jQuery('#infoOverlayImage').attr('src', plugin_path + 'datasets/images/' + node.image);
} else {
jQuery('#infoOverlayImage').attr('src', plugin_path + 'datasets/images/default.jpg');
}
if ('description' in node) {
jQuery('#infoOverlayDescription').text(node.description);
} else {
jQuery('#infoOverlayDescription').text('Default Text');
}
updateTabs(node);
jQuery('#infoOverlay').slideDown('fast');
}
function updateTabs(node){
//tabs of the links to other nodes:
//delete old nodes from tabcontent
tabcontent = document.getElementsByClassName("tabcontent");
for (i = 0; i < tabcontent.length; i++) {
while (tabcontent[i].firstChild){
tabcontent[i].removeChild(tabcontent[i].lastChild);
}
}
//add new nodes to tabcontent
for (i = 0; i < tabcontent.length; i++) { //for every type of link
node.links.forEach(link => { //and for every link
if(link.type + '_id_content' == tabcontent[i].id){ //is checked if the type is equal
const linkButton = document.createElement('div'); //and if so a new element is created
linkButton.className = "link img";
var node2;
else if(link.target == node){
node2 = link.source;
}
linkButton.id = "linkButton_" + node2.id;
const nodeID = node2.id;
const linkImage = document.createElement('img');
const linkTitle = document.createElement('div');
if ('image' in node2) {
linkImage.src = plugin_path + 'datasets/images/' + node2.image;
} else if(!('image' in node2) && ('id' in node2)) {
linkImage.src = plugin_path + 'datasets/images/default.jpg';
linkTitle.className = "link title";
linkTitle.innerHTML = node2.name;
//linkButton.appendChild(linkTitle);
linkButton.appendChild(linkImage);
linkButton.addEventListener('click', function (){focusOnNodeId(nodeID)});
tabcontent[i].appendChild(linkButton);

Matthias Konitzny
committed
}
function focusOnNodeId(id) {
const gData = Graph.graphData();
gData.nodes.forEach(node => {
if (node.id === id) {
handleNodeClick(node);
}
})
}

Matthias Konitzny
committed
function focusOnNode(node) {
// Aim at node from outside it
const distance = 250;
const distRatio = 1 + distance/Math.hypot(node.x, node.y, node.z);
Graph.cameraPosition(
{ x: node.x * distRatio, y: node.y * distRatio, z: node.z * distRatio }, // new position
node, // lookAt ({ x, y, z })
1000 // ms transition duration
);
}
function nodeHover(node) {
// no state change
if ((!node && !highlightNodes.size) || (node && hoverNode === node)) return;
highlightNodes.clear();
highlightLinks.clear();
if (node) {
highlightNodes.add(node);
node.neighbors.forEach(neighbor => highlightNodes.add(neighbor));
node.links.forEach(link => highlightLinks.add(link));
}
hoverNode = node || null;
updateHighlight();
}
function linkHover(link) {
highlightNodes.clear();
highlightLinks.clear();
if (link) {
highlightLinks.add(link);
highlightNodes.add(link.source);
highlightNodes.add(link.target);
}
updateHighlight();
}
function linkColor(link) {
if ('type' in link) {
return edgeColors[link.type]
}
return 'rgb(255, 255, 255)'
}
function updateHighlight() {
// trigger update of highlighted objects in scene
Graph
.nodeColor(Graph.nodeColor())
.linkWidth(Graph.linkWidth())
.linkDirectionalParticles(Graph.linkDirectionalParticles());
}
//is used in createInfoOverlay, as an unusual function call to open link-tabs
function openTab (event, tab) {
var i, tabcontent, tablinks;
// Get all elements with class="tabcontent" and hide them
tabcontent = document.getElementsByClassName("tabcontent");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
}
// Get all elements with class="tablinks" and remove the class "active"
tablinks = document.getElementsByClassName("tablinks");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}