问题
I have a solid d3.js graph that works well for the most part. There are 4 node types:
Agent,Customer,Phone,ID_Card
There are checkboxes that handle the opacity of the nodes and links. If Agent and Customer are checked then the 2 nodes and all possible links between them are opaque. And rest of the graph has
opacity(0)
Also, there is a Search feature , which lets the user search through the graph with the below node attribute
d.id
A node for which the input ID matches , will stay opaque and the rest of the graph will fade out for a few seconds
Below is the HTML
<label>
<input type="checkbox" name="checkb" value="Agent"> type=Agent</label>
<br />
<label>
<input type="checkbox" name="checkb" value="Customer"> type=Customer</label>
<br />
<label>
<input type="checkbox" name="checkb" value="Phone"> type=Phone</label>
<br />
<label>
<input type="checkbox" name="checkb" value="ID_Card"> type=ID_Card</label>
<br />
<input type="text" id="node" />
<button id="search">
Search_Node
</button>
</div>
Below is the javascript code that ensures that all the filters work well together in tandem.
For instance , if Customer and Phone are checked and if the Search feature is applied to search either a Customer or a Phone , the remaining two nodes that were unchecked will still remain at
opacity(0)
And the search will be applied only to the sub graph.
d3.selectAll("input[name=checkb]").on("change", function() {
function getCheckedBoxes(chkboxName) {
var checkboxes = document.getElementsByName(chkboxName);
var checkboxesChecked = [];
for (var i = 0; i < checkboxes.length; i++) {
if (checkboxes[i].checked) {
checkboxesChecked.push(checkboxes[i].defaultValue);
}
}
return checkboxesChecked.length > 0 ? checkboxesChecked : " ";
}
var checkedBoxes = getCheckedBoxes("checkb");
node.style("opacity", 1);
link.style("opacity", 1);
node.filter(function(d) {
return checkedBoxes.indexOf(d.type) === -1;
})
.style("opacity", "0");
link.filter(function(d) {
return checkedBoxes.indexOf(d.source.type) === -1 ||
checkedBoxes.indexOf(d.target.type) === -1;
})
.style("opacity", "0");
link.filter(function(d) {
return checkedBoxes.indexOf(d.source.type) > -1 &&
checkedBoxes.indexOf(d.target.type) > -1;
})
.style("opacity", "1");
});
// get the checkboxes
var typeAgentChk = document.querySelectorAll('input[value="Agent"]')[0];
var typeCustomerChk = document.querySelectorAll('input[value="Customer"]')[0];
var typePhoneChk = document.querySelectorAll('input[value="Phone"]')[0];
var typeIDCardChk = document.querySelectorAll('input[value="ID_Card"]')[0];
var checkBoxes = [typeAgentChk, typeCustomerChk, typePhoneChk, typeIDCardChk];
var nodeTypes = ["Agent", "Customer", "Phone", "ID_Card"];
var filterByCheckBox = function(el) {
if (el.checked) {
// filter
filterBy("type", el.value, false);
} else {
removeFilterFor("type", el.value);
}
}
var removeFilterFor = function(attribute, value) {
var node = svg.selectAll(".node");
var link = svg.selectAll(".link");
var selected = node.filter(function(d) { // filter by attribute/value
return d[attribute] == value;
})
.style("opacity", 0)
.each(function(d) {
var index = filtered.indexOf(d.id);
if (index > -1) {
filtered.splice(index, 1);
}
});
}
var filterBy = function(attribute, value, restoreAll, highlightSelected) {
// get nodes and links - could be moved outside for efficiency
var node = svg.selectAll(".node");
var link = svg.selectAll(".link");
if (value == "none" || value == null) { // do we need this?
node.style("stroke", "white").style("stroke-width", "1");
} else {
var selected = node.filter(function(d) { // filter by attribute/value
return d[attribute] == value;
});
// get any nodes we have filtered before
var alreadyFiltered = node.filter(function(d) {
return filtered.indexOf(d.id) > -1;
})
var alreadyFilteredLinks = link.filter(function(d) {
return filtered.indexOf(d.source.id) > -1 && filtered.indexOf(d.target.id) > -1;
})
// hide all
node.style("opacity", 0);
link.style("opacity", 0);
if (highlightSelected) {
svg.selectAll(".node, .link").transition()
.duration(1000)
.style("opacity", 0);
}
selected.style("opacity", 1)
.each(function(d) { // save the id's in the filtered list
if (filtered.indexOf(d.id) == -1) {
filtered.push(d.id);
}
});
// but the already filtered and the currently selected
alreadyFiltered.transition()
.duration(3000)
.style("opacity", 1);
alreadyFilteredLinks.transition()
.duration(3000)
.style("opacity", 1);
if (restoreAll) { // restore all
filtered = [];
svg.selectAll(".node, .link").transition()
.duration(5000)
.style("opacity", 1);
}
}
}
for (let i = 0; i < checkBoxes.length; i++) {
checkBoxes[i].checked = true;
//filterByCheckBox(checkBoxes[i]); // add all ids in filtered
checkBoxes[i].addEventListener("click", function() {
filterByCheckBox(checkBoxes[i])
});
}
searchBtn.addEventListener("click", function() {
// get value to filter with
var selectedVal = document.getElementById('node').value;
if (nodeTypes.indexOf(selectedVal) > -1) { // we filter by type
filterBy("type", selectedVal, false);
} else {
console.log("filterBy", "id", selectedVal)
filterBy("id", selectedVal, false, true);
}
});
This seems to be working as expected for the most part. The unchecking of checkboxes works fine and seems to make the unchecked nodes at
opacity(0)
But when those nodes are checked back again , it does not seem to work well.
For instance, if Customer and Phone remained check and the other two remain unchecked, only Customer and Phone are visible. But if the Agent node is re-checked again , even ID_Card node becomes visible though it is still unchecked.
This holds true during checking back of any node. Even the unchecked node becomes visible.
Below is the fiddle
来源:https://stackoverflow.com/questions/41558545/d3-js-opacity-filters-partially-working