因此,我一直在尝试构建类似于Patrick Brockmann's "CollapsibleTree Search"的D3.js节点树。我的目标是当用户在下拉输入搜索中搜索一个词时,而不是使用所选词的突出显示路径展开树时,我的目标是隔离该词及其父项。基本上显示一条直线。此后,如果您在直线上选择一个节点,则树会展开并且路径仍会突出显示(当树被展开并单击红色节点时,会删除突出显示)。无论如何,问题在于在此过程中的某些节点上,有少量节点被抛出窗口之外(Y值以千为单位)。这是我的Bl.ocks "Organization Node Tree With Search and Child/Sibling Hiding"
的链接要重现该问题,请执行以下步骤(我在代码中添加了console.logs,以显示某些输出不相同): 1.载入页面 2.单击“ Analytics”(console.log) 3.搜索并选择“ AggregateExpression” 4.单击“查询” 5.单击“ Analytics”(console.log)
如果两次单击“ Analytics”都比较console.logs,则数组将不同。一个是3,一个是6。几乎所有的东西都可以工作,除非您只是在树上单击并进行一些搜索,否则会有几个节点从页面飞出。
这里是JavaScript的示例。它仍然需要重构,但是再次,它在大多数情况下可以起作用:
//===============================================
function select2DataCollectName(d) {
if (d.children)
d.children.forEach(select2DataCollectName);
else if (d._children)
d._children.forEach(select2DataCollectName);
select2Data.push(d.name);
}
//===============================================
function searchTree(d) {
if (d.children)
d.children.forEach(searchTree);
else if (d._children)
d._children.forEach(searchTree);
var searchFieldValue = eval(searchField);
if (searchFieldValue && searchFieldValue.match(searchText)) {
// Walk parent chain
var ancestors = [];
var parent = d;
while (typeof (parent) !== "undefined") {
ancestors.push(parent);
//console.log(parent);
parent.class = "found";
parent = parent.parent;
}
//console.log(ancestors);
}
}
//===============================================
function clearAll(d) {
d.class = "";
if (d.children)
d.children.forEach(clearAll);
else if (d._children)
d._children.forEach(clearAll);
}
//===============================================
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
function expandEverything(d) {
var children = (d.children) ? d.children : d._children;
if (d._children) {
d.children = d._children;
d._children = null;
}
if (children)
children.forEach(expandEverything);
}
function expandUp() {
expandEverything(root);
update(root);
}
function collapseDown() {
root.children.forEach(collapse);
collapse(root);
update(root);
}
//===============================================
function collapseAllNotFound2(d) {
if (d.children) {
if (d.class !== "found") {
d._children = d.children;
d._children.forEach(collapseAllNotFound);
d.children = null;
} else
d.children.forEach(collapseAllNotFound);
}
}
//===============================================
var childrenToRemove = [];
function collapseAllNotFound(d) {
if (d.children) {
if (d.class !== "found") {
d._children = d.children;
d._children.forEach(collapseAllNotFound);
d.children = null;
childrenToRemove.push(d);
} else {
d.children.forEach(collapseAllNotFound);
}
}
else {
if (d.class !== "found" && d.parent.class === "found") {
childrenToRemove.push(d);
}
}
}
function resetTree(d) {
if (d.children) {
if (d._children) {
d._children = d._children.concat(d.children);
}
else {
d._children = d.children
}
d.children = undefined;
d._children.forEach(resetTree);
}
else if (d._children) {
d._children.forEach(resetTree);
}
}
//===============================================
function expandAll(d) {
if (d._children) {
d.children = d._children;
d.children.forEach(expandAll);
d._children = null;
} else if (d.children)
d.children.forEach(expandAll);
}
//Toggle children on click.
function toggle(d) {
var circleTest = $(childrenToRemove).hasClass("found");
childrenToRemove = [];
if (d.class === "found") {
clearAll(root);
expandAll(root);
update(root);
searchField = "d.name";
searchText = $(".select2-chosen").text();
searchTree(root);
root.children.forEach(collapseAllNotFound2);
update(root);
childrenToRemove.class = "";
d.class = "";
parent.class = "";
d.parent.class = "";
parent.parent.class = "";
d.children.class = "";
d3.selectAll("circle").attr("class", "");
} else if (d.class !== "found") {
console.log(d);
console.log(d._children);
d.children = null;
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
clearAll(root);
update(d);
$("#searchName").select2("val", "");
}
}
// Focus on the section above
//=============================================================================================================================================
//===============================================
$("#searchName").on("select2-selecting", function (e) {
clearAll(root);
expandAll(root);
update(root);
searchField = "d.name";
searchText = e.object.text;
searchTree(root);
root.children.forEach(collapseAllNotFound);
for (var index in childrenToRemove) {
var d = childrenToRemove[index];
if (d.parent.children) {
d.parent.children.splice(d.parent.children.indexOf(d), 1);
}
if (d.parent._children) {
d.parent._children.push(d);
}
else {
d.parent._children = [d];
}
}
childrenToRemove = [];
update(root);
resetTree(root);
});
//===============================================
var margin = { top: 20, right: 120, bottom: 20, left: 120 },
width = 960 - margin.right - margin.left,
height = 800 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function (d) { return [d.y, d.x]; });
var svg = d3.select("body").append("svg")
.attr("id", "test-id")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.json("flare-long.json", function (error, flare) {
root = flare;
root.x0 = height / 2;
root.y0 = 0;
select2Data = [];
select2DataCollectName(root);
select2DataObject = [];
select2Data.sort(function (a, b) {
if (a > b) return 1; // sort
if (a < b) return -1;
return 0;
})
.filter(function (item, i, ar) {
return ar.indexOf(item) === i;
}) // remove duplicate items
.filter(function (item, i, ar) {
select2DataObject.push({
"id": i,
"text": item
});
});
$("#searchName").select2({
data: select2DataObject,
containerCssClass: "search"
});
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
root.children.forEach(collapse);
root.children.forEach(function (d) { d.hidden = false; });
root.hidden = false;
update(root);
});
d3.select(self.frameElement).style("height", "800px");
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).filter(function (d) { return !d.hidden; }).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function (d) { d.y = d.depth * 180; });
// Update the nodes…
var node = svg.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", toggle);
nodeEnter.append("circle")
.attr("r", 1e-6)
.style("fill", function (d) { return d._children ? "lightsteelblue" : "#fff"; });
// Possible use in the future. Needs adjustable width based on text.
// nodeEnter.append("rect")
// .attr("rx", 6)
// .attr("ry", 6)
// .attr("x", function (d) { return d.children || d._children ? -140 : -10; })
// .attr('y', -10)
// .attr("width", 150)
// .attr("height", 20)
// .style("fill", "#fff")
// .style("opacity", 0.2)
// .style("border-radius", "5px");
nodeEnter.append("text")
.attr("x", function (d) { return d.children || d._children ? -10 : 10; })
.attr("dy", ".35em")
.attr("text-anchor", function (d) { return d.children || d._children ? "end" : "start"; })
.text(function (d) { return d.name; })
.style("fill-opacity", 1e-6);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function (d) { return "translate(" + d.y + "," + d.x + ")"; });
nodeUpdate.select("circle")
.attr("r", 4.5)
.style("fill", function (d) {
if (d.class === "found") {
return "#ff4136"; //red
} else if (d._children) {
return "lightsteelblue";
} else {
return "#fff";
}
})
.style("stroke", function (d) {
if (d.class === "found") {
return "#ff4136"; //red
}
});
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", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = svg.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)
.style("stroke", function (d) {
if (d.target.class === "found") {
return "#ff4136";
}
});
// 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;
});
}
如果您有任何建议或想法,请告诉我。预先感谢。