我是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);
});