Skip to content
Snippets Groups Projects
graph.js 14.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • const highlightNodes = new Set();
    const highlightLinks = new Set();
    let hoverNode = null;
    
    Matthias Konitzny's avatar
    Matthias Konitzny committed
    let Graph = null;
    let firstTick = true;
    
    
    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');
    
    Matthias Konitzny's avatar
    Matthias Konitzny committed
    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()]})
    
    Matthias Konitzny's avatar
    Matthias Konitzny committed
        (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's avatar
    Matthias Konitzny committed
            .onNodeHover(nodeHover)
            .onLinkHover(linkHover)
    
            .linkColor(linkColor)
            .linkOpacity(0.8)
    
            .nodeThreeObjectExtend(false)
            .nodeThreeObject(drawNode)
            .onEngineTick(loadComponents)
    
            .height(getHeight());
    
        //Graph.renderer().sortObjects = true;
    
    Matthias Konitzny's avatar
    Matthias Konitzny committed
    function loadComponents() {
        if (firstTick) {
    
            mapEdgeColors();
    
    Matthias Konitzny's avatar
    Matthias Konitzny committed
            updateLinks();
            add_background();
    
            add_fullscreen_mode_button();
    
            window.onresize = resize_graph();
    
    Matthias Konitzny's avatar
    Matthias Konitzny committed
            firstTick = false;
        }
    }
    
    function resize_graph() {
        Graph.width(getWidth());
        Graph.height(getHeight());
    }
    
    
    function getWidth() {
    
        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]
        }
    }
    
    
    Matthias Konitzny's avatar
    Matthias Konitzny committed
    
    function getLinkClasses() {
        const linkClasses = [];
        Graph.graphData().links.forEach(link => linkClasses.push(link.type));
        return [... new Set(linkClasses)];
    }
    
        const domNode = document.getElementById('3d-graph');
    
        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);
    
    
    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>&#10006;</p>';
        close.id = 'infoOverlayCloseButton';
    
        close.className = 'close-button';
    
        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 linkType = linkClasses[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);
    
        jQuery('#infoOverlayCloseButton').click(function () {
            jQuery('#infoOverlay').slideUp('fast');
        });
    
        //sceneNode.appendChild(overlayNode);
    
    }
    
    
    function createLinkOverlay() {
        const sceneNode = getCanvasDivNode();
    
        const overlayNode = document.createElement('div');
    
    Matthias Konitzny's avatar
    Matthias Konitzny committed
        //overlayNode.innerText = 'Hello there!';
    
        sceneNode.insertBefore(overlayNode, sceneNode.childNodes[2])
    
    Matthias Konitzny's avatar
    Matthias Konitzny committed
    
        const linkClasses = getLinkClasses();
    
        const chars = Math.max.apply(Math, linkClasses.map(function (c) { return c.length; }));
    
    Matthias Konitzny's avatar
    Matthias Konitzny committed
    
        for (let i=0; i<linkClasses.length; i++) {
            const relation = document.createElement('div');
            relation.className = 'relation';
            relation.innerHTML = "<p>" + linkClasses[i] + "</p>";
    
            overlayNode.appendChild(relation);
    
    Matthias Konitzny's avatar
    Matthias Konitzny committed
    
            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);
    
    }
    
    function fullscreen_mode(){
    
        if(getCanvasDivNode().requestFullscreen) {
            getCanvasDivNode().requestFullscreen().catch();
        }
    }
    
    function resize_canvas() {
        if(document.fullscreenElement == getCanvasDivNode()) {
    
            Graph.height(screen.height);
            Graph.width(screen.width);
    
            Graph.height(window.innerHeight - 200);
    
    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);
    }
    
    Matthias Konitzny's avatar
    Matthias Konitzny committed
        const gData = Graph.graphData()
    
        // 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)
    }
    
    
    
    
    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;
    
                    if(link.source == node){
    
                        node2 = link.target;
    
                    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);
    
    function focusOnNodeId(id) {
        const gData = Graph.graphData();
    
        gData.nodes.forEach(node => {
            if (node.id === id) {
                handleNodeClick(node);
            }
        })
    }
    
    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", "");
        }
    
        // Show the current tab, and add an "active" class to the button that opened the tab
        document.getElementById(tab.id + "_content").style.display = "block";
        event.currentTarget.className += " active";
    }