almende / vis

⚠️ This project is not maintained anymore! Please go to https://github.com/visjs
7.85k stars 1.48k forks source link

Implementing minimap for vis network graph #1618

Open hcrobison opened 8 years ago

hcrobison commented 8 years ago

I have been playing with vis network library and was able to get pretty cool graphs going. Thank you for the library. Right now, I am experimenting creating a minimap for the graph. I was able to create a mini representation of the canvas by creating an image using canvas’s toDataURL(). Based on the scale difference, I calculated the viewport (blue rectangle) size in the minimap when the larger view is zoomed in or out. This is all good until I need to take the mouse pointer position during the zoom into consideration to determine the viewport’s correct location within the minimap. Any suggestions on this? The other problem I am encountering is that when the small viewport is moved, I need to move the larger view to the correct location. I am doing this using the moveTo function and calculating the offset by considering the scale. However, I can get the larger graph to shift to the right location, but only for some graphs. For others, I can’t. Am I going down the right path? Do you see another way of implementing the minimap?

image

josdejong commented 8 years ago

Interesting idea to create a minimap using toDataURL. Makes sense to use this method. The only other way I can think of is simply drawing twice: one time for the full graph, and once in the miniature, which is much more expensive I expect. You indeed have to get moving/zooming working in the minimap. As for the zooming around the current mouse position, I guess you could use the DOMtoCanvas method to get the right coordinates from the mouse position?

CIII commented 8 years ago

@hcrobison could you include code?

hcrobison commented 8 years ago

Ok, I find it a bit hard to describe my problem but I will try. The main graph is in a 1200px by 600px div and the minimap is in a 250px by 125px div. Within the minimap, there is a draggable div that is used like a viewfinder - when it moves, the large graph should show an enlarged version of what's inside the draggable div in the minimap.

<div id="mynetwork"></div>
<div id="miniMap">
    <div id="dragHandle" draggable="true" ondragstart="drag(event)"></div>
</div>

To make the minimap function, I need to consider the following:

When the graph stabilizes, I call network.fit() before drawing the minimap to ensure the entire graph is cloned to a small image.

        function drawMinimap() {
            lastScale = network.getScale();
            var canvases = document.getElementsByTagName("canvas");
            if (canvases.length == 1) {
                // get the canvas element
                var oldCanvas = canvases[0];
                // clone the canvas
                var newCanvas = cloneCanvas(oldCanvas);
                // remove previously drawn image in case the minimap image is 
                // created multiple times.
                var miniMap = document.getElementById("miniMap");
                if (miniMap.childElementCount > 1) {
                    miniMap.removeChild(miniMap.children[1]);
                }
                newCanvas.style.position = "relative";
                miniMap.appendChild(newCanvas);

                // set the initial draggable area to be slightly smaller than 
                // the minimap div
                var square = document.getElementById("dragHandle");
                square.style.visibility = 'visible';
                square.style.width = '236px';
                square.style.height = '113px';
                square.style.left = '5px';
                square.style.top = '4px';

            }
        }

        function cloneCanvas(oldCanvas) {
            var image,
                dataURI = oldCanvas.toDataURL();
            image = document.createElement('img');
            image.src = dataURI;
            image.style.width = "250px";
            image.style.height = "auto";
            image.ondrop = function () { drop(event); };
            image.ondragover = function () { allowDrop(event); };
            return image;
        }

Two things happen in zooming: positioning and sizing. The position of the draggable div is adjusted based on the cursor position when zooming occurs. The size of the draggable div is calculated based on the network scale. When changing the size of the draggable area, I have to make sure 1) it does not exceeds the size of the minimap div (250X125), and 2) it also should not become invisibly small. The sizing seems to be working fine but unfortunately, positioning is not calculated quite right and I am hoping you can see where the problem is from the code.

            network.on('zoom', function (params) {
                var networkDiv = document.getElementById("mynetwork");
                var miniMap = document.getElementById("miniMap");
                var square = document.getElementById("dragHandle");
                var ratio = params.scale / lastScale;
                // zoomFactor is meant to be used to adjust the ratio I am using to calculate the new 
                // draggable area size. I am not sure if it is needed. Currently, I am using zoomFactor 1.
                var factoredRatio = ratio > 1 ? ratio / zoomFactor : ratio * zoomFactor;
                var changeSquareSize = false;
                var largeViewCenter = network.getViewPosition();

                // POSITION: first move the drag handle to the location relative to the current cursor
                // location in the larger view
                var squareLeft = parseFloat(square.style.left.replace("px", ""));
                var squareTop = parseFloat(square.style.top.replace("px", ""));
                var changeInX = largeViewCenter.x - centerX;
                var changeInY = largeViewCenter.y - centerY;
                var xChangeRatio = changeInX / (centerX * 2);
                var yChangeRatio = changeInY / (centerY * 2);
                square.style.left = (squareLeft + xChangeRatio * 250) + "px";
                square.style.top = (squareTop + yChangeRatio * 125) + "px";

                // if both zoom stop scales are not set, set changeSquareSize to true
                if (upperZoomStopScale == null && lowerZoomStopScale == null) {
                    changeSquareSize = true;
                } else {
                    // if either or both zoom stop scales are set, need to consider
                    // based on whether the user is zooming in or zooming out
                    if (params.direction == '+') {
                        if (upperZoomStopScale == null && lowerZoomStopScale != null) {
                            if (params.scale < lowerZoomStopScale) {
                                changeSquareSize = true;
                            }
                        } else if (upperZoomStopScale != null && lowerZoomStopScale == null) {
                            if (params.scale > upperZoomStopScale) {
                                changeSquareSize = true;
                            }
                        } else {
                            if (params.scale < lowerZoomStopScale && params.scale > upperZoomStopScale) {
                                changeSquareSize = true;
                            }
                        }
                    } else {
                        if (upperZoomStopScale == null && lowerZoomStopScale != null) {
                            if (params.scale <= lowerZoomStopScale) {
                                changeSquareSize = true;
                            }
                        } else if (upperZoomStopScale != null && lowerZoomStopScale == null) {
                            if (params.scale >= upperZoomStopScale) {
                                changeSquareSize = true;
                            }
                        } else {
                            if (params.scale <= lowerZoomStopScale && params.scale >= upperZoomStopScale) {
                                changeSquareSize = true;
                            }
                        }
                    }
                }

                // SIZE
                if (changeSquareSize == true) {
                    var squareWidth = parseFloat(square.style.width.replace("px", ""));
                    var newSquareWidth = squareWidth / factoredRatio;
                    var widthDiff = newSquareWidth - squareWidth;
                    var squareHeight = parseFloat(square.style.height.replace("px", ""));
                    var newSquareHeight = squareHeight / factoredRatio;
                    var heightDiff = newSquareHeight - squareHeight;

                    if (newSquareWidth >= minWidth && newSquareWidth <= maxWidth && lastScale != params.scale) {
                        square.style.width = newSquareWidth + "px";
                        square.style.height = newSquareHeight + "px";
                        square.style.left = (parseFloat(square.style.left.replace("px", "")) - widthDiff / 2) + "px";
                        square.style.top = (parseFloat(square.style.top.replace("px", "")) - heightDiff / 2) + "px";
                    } else if (lastScale == params.scale) {
                        if (params.direction == '+') {
                            upperZoomStopScale = lastScale;
                        } else {
                            lowerZoomStopScale = lastScale;
                        }
                    } else {
                        if (newSquareWidth < minWidth) {
                            lowerZoomStopScale = lastScale;
                        }
                        else if (newSquareWidth > maxWidth) {
                            upperZoomStopScale = lastScale;
                        }
                    }
                } else {
                    if (params.direction == '+' && lowerZoomStopScale != null && params.scale > lowerZoomStopScale) {
                        // set the smallest draggable area to 20px in width
                        square.style.width = '20px';
                        square.style.height = 113 * 20 / 236 + 'px';
                        square.style.left = (250 - 20 - 4) / 2 + 'px';
                        square.style.top = (125 - 113 * 20 / 236 - 4) / 2 + 'px';
                    } else if (params.direction == '-' && upperZoomStopScale != null && params.scale <= upperZoomStopScale) {
                        // set the biggest draggable area
                        square.style.width = '236px';
                        square.style.height = '113px';
                        square.style.left = '5px';
                        square.style.top = '4px';
                    }
                }

                lastScale = params.scale;

            });

Based on the distance the draggable div moved, I am trying to calculate the offset the large graph should be moved, considering the current network scale of the graph and the scale difference between the large div (1200X600) and minimap div (250X125). Again, my calculation is not quite right. It works perfectly for some graphs but off for others. It seems if the graph's initial network scale is 1, the move is perfect. Otherwise, no. I am hoping you can spot the problem from the code.

        function drop(ev) {

            ev.preventDefault();

            var offset = ev.dataTransfer.getData("text/plain").split(',');
            var square = document.getElementById("dragHandle");
            var oldLeft = square.style.left.replace("px", ""); 
            var oldTop = square.style.top.replace("px", ""); 
            var newLeft = (ev.clientX + parseFloat(offset[0]));
            var newTop = (ev.clientY + parseFloat(offset[1]));
            var ratio = 1200 / 250;

            // move the square
            square.style.left = newLeft + 'px';
            square.style.top = newTop + 'px';

            // handle the larger view move
            var scale = network.getScale();
            var options = {
                offset: { x: (newLeft - oldLeft) * ratio * scale * -1, y: (newTop - oldTop) * ratio * scale * -1 },
                animation: false,
            }
            network.moveTo(options);

            return false;
        }

I have not yet coded this.

CIII commented 8 years ago

Thanks what does your drag function look like? @hcrobison

hcrobison commented 8 years ago

Drag function below. Currently, I am not using the last two params passed over to drop.

    function drag(ev) {
        var style = window.getComputedStyle(ev.target, null);
        ev.dataTransfer.setData("text/plain",
        (parseFloat(style.getPropertyValue("left")) - ev.clientX) + ',' + (parseFloat(style.getPropertyValue("top")) - ev.clientY)
        + ',' + parseFloat(style.getPropertyValue("width")) + ',' + parseFloat(style.getPropertyValue("height")));
    }
CIII commented 8 years ago

@hcrobison the code appears to be very incomplete. I can't recreate your issue because there seems to be some missing code (from copying and pasting this)

CIII commented 8 years ago

can you paste in your allowDrop method or your complete source file?

hcrobison commented 8 years ago

@CIII I am sorry about this. Here is the allowDrop code, which does nothing much.

        function allowDrop(ev) {
            ev.preventDefault();
        }

I am using vis.js with SignalR and .Net MVC framework, so I was trying to sift through and only include the code related to the issue at hand. I will try to create a cleaner code and upload that for you. Thanks.

CIII commented 8 years ago

thanks @hcrobison I'll lookout for it.

hcrobison commented 8 years ago

How is this set of code?

<html>
<head>
    <script type="text/javascript" src="vis.js"></script>
    <link href="vis.css" rel="stylesheet" type="text/css" />

    <style type="text/css">
        #mynetwork {
            float: left;
            width: 1200px;
            height: 600px;
            border: 1px solid lightgray;
        }

        #miniMap {
            position: relative;
            float: left;
            width: 250px;
            height: 125px;
            border: 1px solid lightgray;
        }

        #dragHandle {
            visibility: hidden;
            position: absolute;
            border: 2px solid blue;
            z-index: 10;
        }
    </style>
</head>
<body>
    <div id="mynetwork"></div>
    <div id="miniMap">
        <div id="dragHandle" draggable="true" ondragstart="drag(event)"></div>
    </div>
    <pre id="eventSpan"></pre>
</body>

    <script type="text/javascript">
    // create an array with nodes
    var nodes = [];
    var edges = [];
    var numberOfASAs = 1;
    var numberOfAssets = 10;
    var numberToDivide = 1;
    var counter = 0;
    var id = counter + 1;
    var parent = id;
    var level2Nodes = [];
    var DIR = 'img/refresh-cl/';
    var groups = [];
    var container = document.getElementById('mynetwork');
    var lastScale = null;
    var upperZoomStopScale = null;
    var lowerZoomStopScale = null;
    var network;
    var zoomFactor = 1;
    var minWidth = 20;
    var maxWidth = 236;
    var centerX;
    var centerY;
    var physicsThreshold = 1000;

    // build data
    nodes.push({
        id: id,
        label: "ASM",
        title: "ASM<br>10.10.11.1<br>group: " + 20,
        value: 30,
        group: 20,
        level: 0,
        image: DIR + 'ASM-small.png',
        shape: 'image'
    });
    counter = counter + 1;

    for (var i = 1; i <= numberOfASAs; i++) {
        id = counter + 1;
        nodes.push({
            id: id,
            label: "ASA" + i,
            title: "ASA" + i + "<br>10.10.11." + i + "<br>group: " + (20 + i),
            value: 20,
            group: 20 + i,
            level: 1,
            image: DIR + 'ASA-small.png',
            shape: 'image'
        });
        level2Nodes.push(nodes[nodes.length - 1]);

        edges.push({
            from: 1,
            to: id,
            arrows: 'from'
        });
        counter = counter + 1;
        parent = id;

        for (var j = 0; j < numberOfAssets; j++) {
            id = counter + 1;
            nodes.push({
                id: id,
                label: "Asset" + id,
                title: "Asset" + id + "<br>10.10." + i + "." + j + "<br>group: " + (i * 1000 + j % numberToDivide),
                value: 10,
                group: i * 1000 + j % numberToDivide,
                level: j % numberToDivide + 2,
                image: DIR + 'Endpoint-Client-border.png',
                shape: 'image'
            });
            edges.push({
                from: parent,
                to: id,
                arrows: 'from'
            });
            counter = counter + 1;
            groups.push(i * 1000 + j % numberToDivide);
        }
    }

    // add one special edge
    edges.push({
        from: 2,
        to: 1,
        arrows: 'from'
    });

    var nodesDataset = new vis.DataSet(nodes);
    var edgesDataset = new vis.DataSet(edges);

    allNodes = nodes;

    // create a network
    var data = {
        nodes: nodesDataset,
        edges: edgesDataset
    };
    var options;
    if (nodesDataset.length > physicsThreshold) {
        options = {
            physics: false,
        };
    }
    else {
        options = {
            physics: {
                stabilization: false,
            },
        };
    }
    options.nodes = {
        shape: 'dot',
        scaling: {
            min: 10,
            max: 30,
            label: {
                min: 8,
                max: 30,
                drawThreshold: 12,
                maxVisible: 20
            }
        },
        font: {
            size: 12,
            face: 'Tahoma'
        }
    };
    options.edges = {
        width: 0.15,
        color: { inherit: 'from' },
        smooth: false,
    };
    options.interaction = {
        hideEdgesOnDrag: true
    };
    options.layout = {
        hierarchical: {
            direction: 'UD'
        }
    };
    options.interaction = {
        navigationButtons: true,
        keyboard: true
    };

    var network = new vis.Network(container, data, options);

    // set the first initial zoom level
    network.once('initRedraw', function () {
    });

    network.on("zoom", function (params) {
        // two things I am attempting here when a user zooms in or out.
        // 1. resize the drag handle on the minimap
        // 2. When zoom out, we cluster the nodes automatically -- not currently used
        var networkDiv = document.getElementById("mynetwork");
        var miniMap = document.getElementById("miniMap");
        var square = document.getElementById("dragHandle");
        var ratio = params.scale / lastScale;
        var factoredRatio = ratio > 1 ? ratio / zoomFactor : ratio * zoomFactor;
        var changeSquareSize = false;
        var largeViewCenter = network.getViewPosition();

        // first move the drag handle to the location relative to the current cursor
        // location in the larger view
        var squareLeft = parseFloat(square.style.left.replace("px", ""));
        var squareTop = parseFloat(square.style.top.replace("px", ""));
        var changeInX = largeViewCenter.x - centerX;
        var changeInY = largeViewCenter.y - centerY;
        var xChangeRatio = changeInX / (centerX * 2);
        var yChangeRatio = changeInY / (centerY * 2);
        square.style.left = (squareLeft + xChangeRatio * 250) + "px";
        square.style.top = (squareTop + yChangeRatio * 125) + "px";

        // if both zoom stop scales are not set, set changeSquareSize to true
        if (upperZoomStopScale == null && lowerZoomStopScale == null) {
            changeSquareSize = true;
        } else {
            // if either or both zoom stop scales are set, need to consider
            // based on whether the user is zooming in or zooming out
            if (params.direction == '+') {
                if (upperZoomStopScale == null && lowerZoomStopScale != null) {
                    if (params.scale < lowerZoomStopScale) {
                        changeSquareSize = true;
                    }
                } else if (upperZoomStopScale != null && lowerZoomStopScale == null) {
                    if (params.scale > upperZoomStopScale) {
                        changeSquareSize = true;
                    }
                } else {
                    if (params.scale < lowerZoomStopScale && params.scale > upperZoomStopScale) {
                        changeSquareSize = true;
                    }
                }
            } else {
                if (upperZoomStopScale == null && lowerZoomStopScale != null) {
                    if (params.scale <= lowerZoomStopScale) {
                        changeSquareSize = true;
                    }
                } else if (upperZoomStopScale != null && lowerZoomStopScale == null) {
                    if (params.scale >= upperZoomStopScale) {
                        changeSquareSize = true;
                    }
                } else {
                    if (params.scale <= lowerZoomStopScale && params.scale >= upperZoomStopScale) {
                        changeSquareSize = true;
                    }
                }
            }
        }

        if (params.direction == '+') {
        } else {
            //if (params.scale < clusterZoomLevel * clusterFactor) {
            //    makeCluster(network);
            //}
        }

        console.log("current scale: " + params.scale);
        console.log("last scale: " + lastScale);
        console.log("lower stop scale: " + lowerZoomStopScale);
        console.log("upper stop scale: " + upperZoomStopScale);
        console.log("zoom: " + params.direction);
        console.log("center canvas unit: " + JSON.stringify(largeViewCenter));
        console.log("center dom unit: " + JSON.stringify(network.canvasToDOM(largeViewCenter)));

        if (changeSquareSize == true) {
            var squareWidth = parseFloat(square.style.width.replace("px", ""));
            var newSquareWidth = squareWidth / factoredRatio;
            var widthDiff = newSquareWidth - squareWidth;
            var squareHeight = parseFloat(square.style.height.replace("px", ""));
            var newSquareHeight = squareHeight / factoredRatio;
            var heightDiff = newSquareHeight - squareHeight;

            //console.log("square width current: " + squareWidth);
            //console.log("square width new: " + newSquareWidth);
            //console.log("square height current: " + squareHeight);
            //console.log("square height new: " + newSquareHeight);
            //console.log("ratio: " + factoredRatio);

            if (newSquareWidth >= minWidth && newSquareWidth <= maxWidth && lastScale != params.scale) {
                square.style.width = newSquareWidth + "px";
                square.style.height = newSquareHeight + "px";
                square.style.left = (parseFloat(square.style.left.replace("px", "")) - widthDiff / 2) + "px";
                square.style.top = (parseFloat(square.style.top.replace("px", "")) - heightDiff / 2) + "px";
            } else if (lastScale == params.scale) {
                if (params.direction == '+') {
                    upperZoomStopScale = lastScale;
                    //console.log("set upper stop scale to: " + upperZoomStopScale);
                } else {
                    lowerZoomStopScale = lastScale;
                    //console.log("set lower stop scale to: " + lowerZoomStopScale);
                }
            } else {
                if (newSquareWidth < minWidth) {
                    lowerZoomStopScale = lastScale;
                    //console.log("set lower stop scale to: " + lowerZoomStopScale);
                }
                else if (newSquareWidth > maxWidth) {
                    upperZoomStopScale = lastScale;
                    //console.log("set upper stop scale to: " + upperZoomStopScale);
                }
            }
        } else {
            if (params.direction == '+' && lowerZoomStopScale != null && params.scale > lowerZoomStopScale) {
                square.style.width = '20px';
                square.style.height = 113 * 20 / 236 + 'px';
                square.style.left = (250 - 20 - 4) / 2 + 'px';
                square.style.top = (125 - 113 * 20 / 236 - 4) / 2 + 'px';
            } else if (params.direction == '-' && upperZoomStopScale != null && params.scale <= upperZoomStopScale) {
                square.style.width = '236px';
                square.style.height = '113px';
                square.style.left = '5px';
                square.style.top = '4px';
            }
        }

        lastScale = params.scale;

    });
    network.on("selectNode", function (params) {
    });
    network.on("beforeDrawing", function (ctx) {

    });
    network.once("afterDrawing", function (ctx) {
        // move the top 2 level nodes when physics is not used
        var nodeId = nodes.length;
        var nodePosition = network.getPositions([nodeId]);

        var mynetwork = document.getElementById('mynetwork');
        if (allNodes[0].x != nodePosition[nodeId].x / 2) {
            allNodes[0].x = nodePosition[nodeId].x / 2;
            var updateArray = [];
            updateArray.push(allNodes[0]);

            var spacing = nodePosition[nodeId].x / (level2Nodes.length + 1);
            for (var i = 0; i < level2Nodes.length; i++) {
                level2Nodes[i].x = spacing * (i + 1);
                updateArray.push(level2Nodes[i]);
            }
            nodesDataset.update(updateArray);
        }

        // minimap
        if (nodesDataset.length >= physicsThreshold) {
            network.fit();
            setTimeout(drawMinimap, 1000);
        }
        var largeViewCenter = network.getViewPosition();
        centerX = largeViewCenter.x;
        centerY = largeViewCenter.y;
        console.log("after drawing - center canvas unit: " + JSON.stringify(largeViewCenter));
        console.log("after drawing - center dom unit: " + JSON.stringify(network.canvasToDOM(largeViewCenter)));
    });

    network.on("stabilized", function (params) {
        network.fit();
        setTimeout(drawMinimap, 1000);
        //network.stopSimulation();
    });

    function drawMinimap() {
        lastScale = network.getScale();
        var canvases = document.getElementsByTagName("canvas");
        if (canvases.length == 1) {
            // get the canvas element
            var oldCanvas = canvases[0];
            // clone the canvas
            var newCanvas = cloneCanvas(oldCanvas);
            // remove previously drawn image in case the minimap image is 
            // created multiple times.
            var miniMap = document.getElementById("miniMap");
            if (miniMap.childElementCount > 1) {
                miniMap.removeChild(miniMap.children[1]);
            }
            newCanvas.style.position = "relative";
            miniMap.appendChild(newCanvas);

            // set the initial draggable area to be slightly smaller than 
            // the minimap div
            var square = document.getElementById("dragHandle");
            square.style.visibility = 'visible';
            square.style.width = '236px';
            square.style.height = '113px';
            square.style.left = '5px';
            square.style.top = '4px';

        }
    }

    function cloneCanvas(oldCanvas) {
        // cloning using image. somehow, the faster option of using
        // drawImage does not work for the canvas done with vis

        //create a new canvas
        //var newCanvas = oldCanvas.cloneNode();
        //var context = newCanvas.getContext('2d');

        ////apply the old canvas to the new one
        //context.drawImage(oldCanvas, 1300, 0);

        var image,
            dataURI = oldCanvas.toDataURL();
        image = document.createElement('img');
        image.src = dataURI;
        image.style.width = "250px";
        image.style.height = "auto";
        image.ondrop = function () { drop(event); };
        image.ondragover = function () { allowDrop(event); };
        return image;

        //return the new canvas
        //return newCanvas;
    }

    function allowDrop(ev) {
        ev.preventDefault();
    }

    function drag(ev) {
        //ev.dataTransfer.setData("text", ev.target.id);
        //ev.dataTransfer.effectAllowed = 'move';
        var style = window.getComputedStyle(ev.target, null);
        ev.dataTransfer.setData("text/plain",
        (parseFloat(style.getPropertyValue("left")) - ev.clientX) + ',' + (parseFloat(style.getPropertyValue("top")) - ev.clientY)
        + ',' + parseFloat(style.getPropertyValue("width")) + ',' + parseFloat(style.getPropertyValue("height")));
    }

    function drop(ev) {

        ev.preventDefault();

        var offset = ev.dataTransfer.getData("text/plain").split(',');
        var square = document.getElementById("dragHandle");
        var oldLeft = square.style.left.replace("px", "");
        var oldTop = square.style.top.replace("px", "");
        var newLeft = (ev.clientX + parseFloat(offset[0]));
        var newTop = (ev.clientY + parseFloat(offset[1]));
        //var handleWidth = parseFloat(offset[2]);
        //var handleHeight = parseFloat(offset[3]);
        var ratio = 1200 / 250;

        // move the square
        square.style.left = newLeft + 'px';
        square.style.top = newTop + 'px';

        console.log("new left: " + newLeft);
        console.log("old left: " + oldLeft);
        console.log("new top: " + newTop);
        console.log("old top: " + oldTop);
        console.log("distance moved horizontally in minimap: " + (newLeft - oldLeft));
        console.log("distance moved vertically in minimap: " + (newTop - oldTop));

        // handle the larger view move
        var scale = network.getScale();
        var options = {
            offset: { x: (newLeft - oldLeft) * ratio * scale * -1, y: (newTop - oldTop) * ratio * scale * -1 },
            animation: false,
        }
        network.moveTo(options);

        return false;
    }

    function dragStartHandler(event) {
        if (event.target instanceof HTMLLIElement) {
            // use the element's data-value="" attribute as the value to be moving:
            event.dataTransfer.setData(internalDNDType, event.target.dataset.value);
            event.dataTransfer.effectAllowed = 'move'; // only allow moves
        } else {
            event.preventDefault(); // don't allow selection to be dragged
        }
    }
    </script>

</html>
hcrobison commented 8 years ago

Since I wrote you last time, I have made more progress in my calculations. I found that my primary problem was that I didn't have the correct canvas width and height in canvas unit. Previously, I calculated the canvas width and height by multiplying getViewPosition().x and .y by 2. This was not accurate. I now calculate the canvas width and height by getting the difference between DOMtoCanvas(beginning position) and DOMtoCanvas(ending position). This brought my calculation a lot closer.

       var bpos = network.DOMtoCanvas({ x: 0, y: 0 });
        var epos = network.DOMtoCanvas({ x: 1200, y: 600 });
        canvasWidth = epos.x - bpos.x;//centerX * 2;
        canvasHeight = epos.y - bpos.y;//centerY * 2;
hcrobison commented 8 years ago

My newest problem is the navigation buttons. I couldn't find any specific events fired when these buttons are clicked. Do they exist? In particular, the zoom extend button.

hcrobison commented 8 years ago

I have got the minimap working nearly perfectly now. I am closing this issue I will open a different issue about the events for navigation buttons.

CIII commented 8 years ago

@hcrobison can you post what solved the issue?

hcrobison commented 8 years ago

The solution was what I said earlier - " I found that my primary problem was that I didn't have the correct canvas width and height in canvas unit. Previously, I calculated the canvas width and height by multiplying getViewPosition().x and .y by 2. This was not accurate. I now calculate the canvas width and height by getting the difference between DOMtoCanvas(beginning position) and DOMtoCanvas(ending position). This brought my calculation a lot closer.

   var bpos = network.DOMtoCanvas({ x: 0, y: 0 });
    var epos = network.DOMtoCanvas({ x: 1200, y: 600 });
    canvasWidth = epos.x - bpos.x;//centerX * 2;
    canvasHeight = epos.y - bpos.y;//centerY * 2;

"

CIII commented 8 years ago

Yea I've been tweaking with it because it wasn't 100% perfect...depending on the canvas position it seems to get out of wack after a while.

mojoaxel commented 8 years ago

I really would like to see this as a official example or even part of the core code. I'm reopening this and marking it as enhancement.

Everybody please feel free to open a pull request ;-)

ghost commented 7 years ago

hi, I am very interested to know if you have realized minimap.

wimrijnders commented 7 years ago

Closed due to inactivity. Feel free to reopen if this is still relevant.

piwidev789 commented 6 years ago

I am also very interested in this feature

savkelita commented 5 years ago

Hi everyone. I have made a basic implementation of minimap on the following fiddle/gitrepo: Example: https://jsfiddle.net/savke/m476zwns/ Repo: https://github.com/savkelita/vis-network-minimap

Any suggestions are welcome.

Cheers!