MiniMap for d3.js可折叠树

时间:2015-06-02 10:23:50

标签: d3.js tree

我是d3.js的新手,我正在研究可折叠树的小地图。 在可折叠树小地图中总是有一次点击延迟。当用户单击第一个节点后跟第二个节点时,minimap会在用户单击第二个节点时显示第一个节点的图像。

有人可以帮帮我吗?

    d3.demo = {};

/** CANVAS **/
d3.demo.canvas = function(width,height) {

    "use strict";

    var width           = 500,
        height          = 500,
        zoomEnabled     = true,
        dragEnabled     = true,
        scale           = 1,
        translation     = [0,0],
        base            = null,
        wrapperBorder   = 2,
        minimap         = null,
        minimapPadding  = 20,
        minimapScale    = 0.25;

    function canvas(selection) {

        base = selection;

        var xScale = d3.scale.linear()
            .domain([-width / 2, width / 2])
            .range([0, width]);

        var yScale = d3.scale.linear()
            .domain([-height / 2, height / 2])
            .range([height, 0]);

        var zoomHandler = function(newScale) {
            if (!zoomEnabled) { return; }
            if (d3.event) {
                scale = d3.event.scale;
            } else {
                scale = newScale;
            }
            if (dragEnabled) {
                var tbound = -height * scale,
                    bbound = height  * scale,
                    lbound = -width  * scale,
                    rbound = width   * scale;
                // limit translation to thresholds
                translation = d3.event ? d3.event.translate : [0, 0];
                translation = [
                    Math.max(Math.min(translation[0], rbound), lbound),
                    Math.max(Math.min(translation[1], bbound), tbound)
                ];
            }

            d3.select(".panCanvas, .panCanvas .bg")
                .attr("transform", "translate(" + translation + ")" + " scale(" + scale + ")");

            minimap.scale(scale).render();
        }; // startoff zoomed in a bit to show pan/zoom rectangle

        var zoom = d3.behavior.zoom()
            .x(xScale)
            .y(yScale)
            .scaleExtent([0.5, 5])
            .on("zoom.canvas", zoomHandler);

        var svg = selection.append("svg")
            .attr("class", "svg canvas")
            .attr("width",  width  + (wrapperBorder*2) + minimapPadding*2 + (width*minimapScale))
            .attr("height", height + (wrapperBorder*2) + minimapPadding*2)
            .attr("shape-rendering", "auto");

        var svgDefs = svg.append("defs");

        svgDefs.append("clipPath")
            .attr("id", "wrapperClipPath")
            .attr("class", "wrapper clipPath")
            .append("rect")
            .attr("class", "background")
            .attr("width", width)
            .attr("height", height);

        svgDefs.append("clipPath")
            .attr("id", "minimapClipPath")
            .attr("class", "minimap clipPath")
            .attr("width", width)
            .attr("height", height)
            //.attr("transform", "translate(" + (width + minimapPadding) + "," + (minimapPadding/2) + ")")
            .append("rect")
            .attr("class", "background")
            .attr("width", width)
            .attr("height", height);

        var filter = svgDefs.append("svg:filter")
            .attr("id", "minimapDropShadow")
            .attr("x", "-20%")
            .attr("y", "-20%")
            .attr("width", "150%")
            .attr("height", "150%");

        filter.append("svg:feOffset")
            .attr("result", "offOut")
            .attr("in", "SourceGraphic")
            .attr("dx", "1")
            .attr("dy", "1");

        filter.append("svg:feColorMatrix")
            .attr("result", "matrixOut")
            .attr("in", "offOut")
            .attr("type", "matrix")
            .attr("values", "0.1 0 0 0 0 0 0.1 0 0 0 0 0 0.1 0 0 0 0 0 0.5 0");

        filter.append("svg:feGaussianBlur")
            .attr("result", "blurOut")
            .attr("in", "matrixOut")
            .attr("stdDeviation", "10");

        filter.append("svg:feBlend")
            .attr("in", "SourceGraphic")
            .attr("in2", "blurOut")
            .attr("mode", "normal");

        var minimapRadialFill = svgDefs.append("radialGradient")
            .attr({
                id:"minimapGradient",
                gradientUnits:"userSpaceOnUse",
                cx:"500",
                cy:"500",
                r:"400",
                fx:"500",
                fy:"500"
            });
        minimapRadialFill.append("stop")
            .attr("offset", "0%")
            .attr("stop-color", "#FFFFFF");
        minimapRadialFill.append("stop")
            .attr("offset", "40%")
            .attr("stop-color", "#EEEEEE");
        minimapRadialFill.append("stop")
            .attr("offset", "100%")
            .attr("stop-color", "#E0E0E0");

        var outerWrapper = svg.append("g")
            .attr("class", "wrapper outer")
            .attr("transform", "translate(0, " + minimapPadding + ")");

        outerWrapper.append("rect")
            .attr("class", "background")
            .attr("width", width + wrapperBorder*2)
            .attr("height", height + wrapperBorder*2);

        var innerWrapper = outerWrapper.append("g")
            .attr("class", "wrapper inner")
            .attr("clip-path", "url(#wrapperClipPath)")
            .attr("transform", "translate(" + (wrapperBorder) + "," + (wrapperBorder) + ")")
            .call(zoom);

        innerWrapper.append("rect")
            .attr("class", "background")
            .attr("width", width)
            .attr("height", height);

        var panCanvas = innerWrapper.append("g")
            .attr("class", "panCanvas")
            .attr("width", width)
            .attr("height", height)
            .attr("transform", "translate(0,0)");

        panCanvas.append("rect")
            .attr("class", "background")
            .attr("width", width)
            .attr("height", height);

        minimap = d3.demo.minimap()
            .zoom(zoom)
            .target(panCanvas)
            .minimapScale(minimapScale)
            .x(width + minimapPadding)
            .y(minimapPadding);

        svg.call(minimap);

        // startoff zoomed in a bit to show pan/zoom rectangle
        zoom.scale(1.75);
        zoomHandler(1.75);

        /** ADD SHAPE **/
        canvas.addItem = function(item) {
            panCanvas.node().appendChild(item.node());
            minimap.render();
        };




        canvas.loadTree = function (divID,treeData,height,width) {
                var totalNodes = 0;
                var maxLabelLength = 0;

                // Misc. variables
                var i = 0;
                var duration = 750;
                var root,
                rootNode;

                // size of the diagram
                var viewerWidth = width;
                var viewerHeight = height;

                var tree = d3.layout.tree()
                    .size([viewerHeight, viewerWidth]);

                // define a d3 diagonal projection for use by the node paths later on.
                var diagonal = d3.svg.diagonal()
                    .projection(function (d) {
                        return [d.y, d.x];
                    });

                // A recursive helper function for performing some setup by walking through all nodes

                function visit(parent, visitFn, childrenFn) {
                    if (!parent)
                        return;

                    visitFn(parent);

                    var children = childrenFn(parent);
                    if (children) {
                        var count = children.length;
                        for (var i = 0; i < count; i++) {
                            visit(children[i], visitFn, childrenFn);
                        }
                    }
                }

                // Call visit function to establish maxLabelLength
                visit(treeData, function (d) {
                    totalNodes++;
                    maxLabelLength = Math.max(d.name.length, maxLabelLength);

                }, function (d) {
                    return d.children && d.children.length > 0 ? d.children : null;
                });

                // sort the tree according to the node names

                function sortTree() {
                    tree.sort(function (a, b) {
                        return b.name.toLowerCase() < a.name.toLowerCase() ? 1 : -1;
                    });
                }
                // Sort the tree initially incase the JSON isn't in a sorted order.
                sortTree();

                // Define the zoom function for the zoomable tree

                /*function zoom() {
                    svgGroup.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
                }*/

                // define the zoomListener which calls the zoom function on the "zoom" event constrained within the scaleExtents
                //var zoomListener = d3.behavior.zoom().scaleExtent([0.1, 3]).on("zoom", zoom);

                // define the baseSvg, attaching a class for styling and the zoomListener
                var baseSvg =panCanvas.append("g");



                // Helper functions for collapsing and expanding nodes.

                function collapse(d) {
                    if (d.children) {
                        d._children = d.children;
                        d._children.forEach(collapse);
                        d.children = null;
                    }
                }

                function expand(d) {
                    if (d._children) {
                        d.children = d._children;
                        d.children.forEach(expand);
                        d._children = null;
                    }
                }

                // Function to center node when clicked/dropped so node doesn't get lost when collapsing/moving with large amount of children.


                // Toggle children function

                function toggleChildren(d) {
                    if (d.children) {
                        d._children = d.children;
                        d.children = null;
                        update(d);
                    minimap.render();
                        //centerNode(d);
                    } else if (d._children) {
                        d.children = d._children;
                        d._children = null;
                        update(d);
                        minimap.render();
                        //centerNode(d);
                    } else {
                        d.children = null;
                        var json = {
                            "useCase" : d.useCase,
                            "chartType" : d.chartType,
                            "type" : d.type,
                            "assetId" : d.assetId,
                            "name" : d.name,
                            "childQueriesWithDelim" : d.childQueriesWithDelim,
                            "imgSrc" : d.imgSrc
                        };
                        window.parameterJsonData = JSON.stringify(json);
                        window.getDataMethod();
                        window.setChildData = function (childData) {

                            var childObj = getObjects(childData, 'name', d.name);
                            if (childObj != null) {
                                var newnodes = tree.nodes(childObj[0].children).reverse();
                                d.children = newnodes[0];
                                update(d);
                                minimap.render();
                                //centerNode(d);

                            }
                        }

                    }

                }

                // Toggle children on click.

                function click(d) {
                    //if (d3.event.defaultPrevented)
                    //return; // click suppressed

                    $('#loading' + d.id).show();
                    toggleChildren(d);

                }

                function update(source) {
                    // Compute the new height, function counts total children of root node and sets tree height accordingly.
                    // This prevents the layout looking squashed when new nodes are made visible or looking sparse when nodes are removed
                    // This makes the layout more consistent.
                    $('#loading' + source.id).hide();
                    var levelWidth = [1];
                    var childCount = function (level, n) {

                        if (n.children && n.children.length > 0) {
                            if (levelWidth.length <= level + 1)
                                levelWidth.push(0);

                            levelWidth[level + 1] += n.children.length;
                            n.children.forEach(function (d) {
                                childCount(level + 1, d);
                            });
                        }
                    };
                    childCount(0, root);
                    var newHeight = d3.max(levelWidth) * 25; // 25 pixels per line
                    tree = tree.size([newHeight, viewerWidth]);

                    // Compute the new tree layout.
                    var nodes = tree.nodes(root).reverse(),
                    links = tree.links(nodes);

                    // Set widths between levels based on maxLabelLength.
                    nodes.forEach(function (d) {
                        //d.y = (d.depth * (maxLabelLength * 30)); //maxLabelLength * 10px
                        // alternatively to keep a fixed scale one can set a fixed depth per level
                        // Normalize for fixed-depth by commenting out below line
                        d.y = (d.depth * 150); //500px per level.
                    });

                    // Update the nodes…
                    var node = svgGroup.selectAll("g.node")
                        .data(nodes, function (d) {
                            return d.id || (d.id = ++i);
                        });

                    // Enter any new nodes at the parent's previous position.
                    var nodeEnter = node.enter().append("g")
                        .attr("class", "node")
                        .attr("transform", function (d) {
                            return "translate(" + source.y0 + "," + source.x0 + ")";
                        })
                        .on('click', click);

                    /*nodeEnter.append("circle")
                    .attr('class', 'nodeCircle')
                    .attr("r", 0)
                    .style("fill", function (d) {
                    return d.hasChild ? "lightsteelblue" : "#fff";
                    });*/

                    nodeEnter.append("svg:image")
                    .attr("class", "nodeCircle")
                    .attr("xlink:href", function (d) {
                        return d.imgSrc;
                    })
                    .attr("x", "-8px")
                    .attr("y", "-8px")
                    .attr("width", function (d) {
                        if (d.id == rootNode.id) {
                            return "40px";
                        } else {
                            return "16px";
                        }
                    })
                    .attr("height", function (d) {
                        if (d.id == rootNode.id) {
                            return "40px";
                        } else {
                            return "16px";
                        }
                    });
                    nodeEnter.append("foreignObject").attr("width", 100)
                    .attr("height", 100).attr("id", function (d) {
                        return "loading" + d.id;
                    }).style("display", "none")
                    .append("xhtml:div").html(
                        "<img src=\"d3/images/loading.gif\"/>");

                    nodeEnter.append("a")
                    .attr("xlink:href", function (d) {
                        return d.url;
                    })
                    .on("mousedown.zoom", function (d) {
                        if (d.url != null) {
                            disableDrag();
                        }
                    })
                    .append("text")
                    .attr("x", function (d) {
                        return d.hasChild ? -10 : 10;
                    })
                    .attr("dy", ".02em")
                    .attr('class', 'nodeText')
                    .attr("text-anchor", function (d) {
                        return d.hasChild ? "end" : "start";
                    })
                    .text(function (d) {
                        var name = d.name.substr(0, d.truncationLimit);
                        if (d.name != null && d.name.length > d.truncationLimit) {
                            name = name.concat("...");
                        }
                        return name;
                    })
                    .style("fill-opacity", 0)
                    .on("mouseover", function (d) {

                        var res = d.description ? d.description.split(",") : null;
                        var desc = "";
                        for (var i = 0; res != null && i < res.length; i++) {
                            desc = desc + '<div>' + res[i] + '</div>';
                        }

                        if (d.description == null) {
                            desc = '<div>Name : ' + d.name + '</div>';
                        }
                        tooltip.show([d3.event.clientX, d3.event.clientY], desc);

                    })
                    .on('mouseout', function () {
                        tooltip.cleanup()
                    });

                    /*nodeEnter.append("foreignObject")
                    .attr('x', 10)
                    .attr("width", 100)
                    .attr("height", 200)
                    .append("xhtml:p")
                    .attr('style', 'word-wrap: break-word; text-align:center;')
                    .append("xhtml:a")
                    .attr("xlink:href", function (d) {
                    return d.url;
                    })
                    .html(function (d) {
                    return d.name;
                    });*/

                    // Update the text to reflect whether node has children or not.
                    node.select('text')
                    .attr("x", function (d) {
                        return d.hasChild ? -10 : 10;
                    })
                    .attr("dy", ".02em")
                    .attr("text-anchor", function (d) {
                        return d.hasChild ? "end" : "start";
                    })
                    .text(function (d) {
                        var name = d.name.substr(0, d.truncationLimit);
                        if (d.name != null && d.name.length > d.truncationLimit) {
                            name = name.concat("...");
                        }
                        return name;
                    });

                    // Change the circle fill depending on whether it has children and is collapsed
                    /*node.select("circle.nodeCircle")
                    .attr("r", 4.5);*/

                    // Transition nodes to their new position.
                    var nodeUpdate = node.transition()
                        .duration(duration)
                        .attr("transform", function (d) {
                            return "translate(" + d.y + "," + d.x + ")";
                        });

                    // Fade the text in
                    nodeUpdate.select("text")
                    .style("fill-opacity", 1);

                    // Transition exiting nodes to the parent's new position.
                    var nodeExit = node.exit().transition()
                        .duration(duration)
                        .attr("transform", function (d) {
                            return "translate(" + source.y + "," + source.x + ")";
                        })
                        .remove();

                    /*nodeExit.select("circle")
                    .attr("r", 0);*/

                    nodeExit.select("text")
                    .style("fill-opacity", 0);

                    // Update the links…
                    var link = svgGroup.selectAll("path.link")
                        .data(links, function (d) {
                            return d.target.id;
                        });

                    // Enter any new links at the parent's previous position.
                    link.enter().insert("path", "g")
                    .attr("class", "link")
                    .attr("d", function (d) {
                        var o = {
                            x : source.x0,
                            y : source.y0
                        };
                        return diagonal({
                            source : o,
                            target : o
                        });
                    });

                    // Transition links to their new position.
                    link.transition()
                    .duration(duration)
                    .attr("d", diagonal);

                    // Transition exiting nodes to the parent's new position.
                    link.exit().transition()
                    .duration(duration)
                    .attr("d", function (d) {
                        var o = {
                            x : source.x,
                            y : source.y
                        };
                        return diagonal({
                            source : o,
                            target : o
                        });
                    })
                    .remove();

                    // Stash the old positions for transition.
                    nodes.forEach(function (d) {
                        d.x0 = d.x;
                        d.y0 = d.y;
                    });
                    //canvas.addItem(svgGroup);
                    minimap.render();
                }

                // Append a group which holds all nodes and which the zoom Listener can act upon.
                var svgGroup = baseSvg.append("g");

                // Define the root
                root = treeData;
                rootNode = treeData;
                root.x0 = viewerHeight / 2;
                root.y0 = 0;

                // Layout the tree initially and center on the root node.
                update(root);




                function disableDrag() {
                    baseSvg.on("mousedown.zoom", null);
                }

                function getObjects(obj, key, val) {
                    var objects = [];
                    for (var i in obj) {
                        if (!obj.hasOwnProperty(i))
                            continue;
                        if (typeof obj[i] == 'object') {
                            objects = objects.concat(getObjects(obj[i], key, val));
                        } else if (i == key && obj[key] == val) {
                            objects.push(obj);
                        }
                    }
                    return objects;
                }




            //d3.select(self.frameElement).style("height", _height + "px");
        }

        /** RENDER **/
        canvas.render = function() {
            svgDefs
                .select(".clipPath .background")
                .attr("width", width)
                .attr("height", height);

            svg
                .attr("width",  width  + (wrapperBorder*2) + minimapPadding*2 + (width*minimapScale))
                .attr("height", height + (wrapperBorder*2));

            outerWrapper
                .select(".background")
                .attr("width", width + wrapperBorder*2)
                .attr("height", height + wrapperBorder*2);

            innerWrapper
                .attr("transform", "translate(" + (wrapperBorder) + "," + (wrapperBorder) + ")")
                .select(".background")
                .attr("width", width)
                .attr("height", height);

            panCanvas
                .attr("width", width)
                .attr("height", height)
                .select(".background")
                .attr("width", width)
                .attr("height", height);

            minimap
                .x(width + minimapPadding)
                .y(minimapPadding)
                .render();
        };

        canvas.zoomEnabled = function(isEnabled) {
            if (!arguments.length) { return zoomEnabled }
            zoomEnabled = isEnabled;
        };

        canvas.dragEnabled = function(isEnabled) {
            if (!arguments.length) { return dragEnabled }
            dragEnabled = isEnabled;
        };

        canvas.reset = function() {
            d3.transition().duration(750).tween("zoom", function() {
                var ix = d3.interpolate(xScale.domain(), [-width  / 2, width  / 2]),
                    iy = d3.interpolate(yScale.domain(), [-height / 2, height / 2]),
                    iz = d3.interpolate(scale, 1);
                return function(t) {
                    zoom.scale(iz(t)).x(x.domain(ix(t))).y(y.domain(iy(t)));
                    zoomed(iz(t));
                };
            });
        };
    }


    //============================================================
    // Accessors
    //============================================================


    canvas.width = function(value) {
        if (!arguments.length) return width;
        width = parseInt(value, 10);
        return this;
    };

    canvas.height = function(value) {
        if (!arguments.length) return height;
        height = parseInt(value, 10);
        return this;
    };

    canvas.scale = function(value) {
        if (!arguments.length) { return scale; }
        scale = value;
        return this;
    };

    return canvas;
};




/** MINIMAP **/
d3.demo.minimap = function() {

    "use strict";

    var minimapScale    = 0.15,
        scale           = 1,
        zoom            = null,
        base            = null,
        target          = null,
        width           = 0,
        height          = 0,
        x               = 0,
        y               = 0,
        frameX          = 0,
        frameY          = 0;

    function minimap(selection) {

        base = selection;

        var container = selection.append("g")
            .attr("class", "minimap")
            .call(zoom);

        zoom.on("zoom.minimap", function() {
            scale = d3.event.scale;
        });


        minimap.node = container.node();

        var frame = container.append("g")
            .attr("class", "frame")

        frame.append("rect")
            .attr("class", "background")
            .attr("width", width)
            .attr("height", height)
            .attr("filter", "url(#minimapDropShadow)");

        var drag = d3.behavior.drag()
            .on("dragstart.minimap", function() {
                var frameTranslate = d3.demo.util.getXYFromTranslate(frame.attr("transform"));
                frameX = frameTranslate[0];
                frameY = frameTranslate[1];
            })
            .on("drag.minimap", function() {
                d3.event.sourceEvent.stopImmediatePropagation();
                frameX += d3.event.dx;
                frameY += d3.event.dy;
                frame.attr("transform", "translate(" + frameX + "," + frameY + ")");
                var translate =  [(-frameX*scale),(-frameY*scale)];
                target.attr("transform", "translate(" + translate + ")scale(" + scale + ")");
                zoom.translate(translate);
            });

        frame.call(drag);

        /** RENDER **/
        minimap.render = function() {
            scale = zoom.scale();
            container.attr("transform", "translate(" + x + "," + y + ")scale(" + minimapScale + ")");
            var node = target.node().cloneNode(true);
            node.removeAttribute("id");
            base.selectAll(".minimap .panCanvas").remove();
            minimap.node.appendChild(node);
            var targetTransform = d3.demo.util.getXYFromTranslate(target.attr("transform"));
            frame.attr("transform", "translate(" + (-targetTransform[0]/scale) + "," + (-targetTransform[1]/scale) + ")")
                .select(".background")
                .attr("width", width/scale)
                .attr("height", height/scale);
            frame.node().parentNode.appendChild(frame.node());
            d3.select(node).attr("transform", "translate(1,1)");
        };
    }


    //============================================================
    // Accessors
    //============================================================


    minimap.width = function(value) {
        if (!arguments.length) return width;
        width = parseInt(value, 10);
        return this;
    };


    minimap.height = function(value) {
        if (!arguments.length) return height;
        height = parseInt(value, 10);
        return this;
    };


    minimap.x = function(value) {
        if (!arguments.length) return x;
        x = parseInt(value, 10);
        return this;
    };


    minimap.y = function(value) {
        if (!arguments.length) return y;
        y = parseInt(value, 10);
        return this;
    };


    minimap.scale = function(value) {
        if (!arguments.length) { return scale; }
        scale = value;
        return this;
    };


    minimap.minimapScale = function(value) {
        if (!arguments.length) { return minimapScale; }
        minimapScale = value;
        return this;
    };


    minimap.zoom = function(value) {
        if (!arguments.length) return zoom;
        zoom = value;
        return this;
    };


    minimap.target = function(value) {
        if (!arguments.length) { return target; }
        target = value;
        width  = parseInt(target.attr("width"),  10);
        height = parseInt(target.attr("height"), 10);
        return this;
    };

    return minimap;
};




/** UTILS **/
d3.demo.util = {};
d3.demo.util.getXYFromTranslate = function(translateString) {
    var split = translateString.split(",");
    var x = split[0] ? ~~split[0].split("(")[1] : 0;
    var y = split[1] ? ~~split[1].split(")")[0] : 0;
    return [x, y];
};


/** RUN SCRIPT **/

treeChart= (function (divID, treeData, height, width) {
var canvasWidth = width;
var shapes = [];
var lastXY = 1;
var zoomEnabled = true;
var dragEnabled = true;

var canvas = d3.demo.canvas(width,height).width(width/2).height(height/2);
d3.select(divID).call(canvas);

canvas.loadTree(divID,treeData,height,width);





});

0 个答案:

没有答案