D3 force layout - legend interactivity on/off - javascript

I have a force layout with a legend, and I’ve appended checkboxes to each role in the legend.
I’m trying to follow this example(https://jsfiddle.net/zhanghuancs/cuYu8/) and add interactivity to the legend, but when I uncheck any of the checkboxes, all the links disappear instead of the links and nodes related to the role.
I’m creating the legend using this loop,
var col = color(d.group);
// adding code to display legend
// as nodes are filled
if(!(d.role in roles)){
legend.append("li")
.html(d.role)
.style("color", col)
//add checkbox
.append("input")
.attr("type", "checkbox")
.attr("id", function (d){
return "chk_" + d;
})
//check box
.property("checked", true)
.on("click", function (d){
//click event
var visibility = this.checked? "visible" : "hidden";
filterLegend(d,visibility);
})
roles[d.role] = true;
}
return col; })
Here’s a fiddle of my graph (https://jsfiddle.net/gbhrea/077mb7o1/1/), using a lot of data so just used a small sample for the fiddle. (graph won’t show on fiddle but will keep the link anyways so that full code can be seen)
Here is the code for changing visibility
function filterLegend(aType, aVisibility){
//change vis of link
link.style("visibility", function (o) {
var lOriginalVisibility = $(this).css("visibility");
return o.type === aType ? aVisibility : lOriginalVisibility;
});
//change vis of node
//if links of node invisible, node should be too
node.style("visibility", function (o, i) {
var lHideNode = true;
link.each(function (d, i) {
if (d.source === o || d.target === o) {
if ($(this).css("visibility") === "visible") {
lHideNode = false;
}
}
})
});
} //end filter
To be clear, what I want to achieve is - When I uncheck a role, e.g Actor, all nodes and links disappear. Any pointers would be greatly appreciated :)

There are a few mistakes in your code. First, you're not binding any data to the legend elements so .on("click", function (d) { }) won't work because d isn't defined there. You do have d.role there though, so that's what you should use to pass on to functions.
Similarly, your filterLegend() function references undefined things (.type) -- this is why everything disappears at the moment. You're passing in something undefined and comparing it to something undefined, which gives true. Furthermore, links don't have the node information directly, but under .source and .target, so you need to compare to .source.role and .target.role.
For the nodes on the other hand, it's much easier than what your current code is trying to do -- there's no need to iterate over the links. You can use the code you have for the links here, except for comparing to the existing .role attribute instead of the non-existing .type.
Complete demo here.

Related

Update the heatmap based on the selected data

I created an heatmap and some sparklines following this example.
In the example the user could click on the labels of the rows and those of the columns, in my case I kept only the possibility to click on the columns labels.
That example was perfect for my data, only I need to update the heatmap based on the radio buttons selection.
The first radio buttons allow you to choose the type of area (A or B).
The second group of radio buttons allow you to choose the daily data to be displayed.
However, the data are "incomplete": not all the months have daily data but only April and December.
So if you select the April or December radio button, the daily data on the heatmap are shown, otherwise the monthly ones are shown.
The example works but it is very primitive because the heatmap is deleted and recreated every time.
// Return to the initial order when the user clicks on the button
d3.select('#initialOrder').on('click', function() {
var trans = heat.transition().duration(1000);
var sortedYear = Array.from(Array(numYLabels).keys());
trans.selectAll('.cell')
.attr('y', function(d) {
var row = parseInt(d3.select(this).attr('data-r'));
return sortedYear.indexOf(row)*cellSize;
});
trans.selectAll('.rowLabel')
.attr('y', function(d, k) {
return sortedYear.indexOf(k) * cellSize;
});
sortedYear.forEach(function(d) {
d3.select('#data-svg-' + d).raise();
});
});
// Area radio button change selection
d3.selectAll('input[name=area-rb]').on('change', function() {
areaSelected = d3.select('input[name="area-rb"]:checked').property("value");
console.log('areaSelected:', areaSelected);
d3.select('#heatmapSvg').remove();
d3.selectAll('.data-svg').remove();
createHeatmap();
});
// Month radio button change selection
d3.selectAll('input[name=month-rb]').on('change', function() {
monthSelected = d3.select('input[name="month-rb"]:checked').property("value");
console.log('monthSelected:', monthSelected);
if(avaibleDayData.includes(monthSelected)) {
d3.select('#heatmapSvg').remove();
d3.selectAll('.data-svg').remove();
createHeatmap();
}
else {
monthSelected = 'nothing';
d3.select('#heatmapSvg').remove();
d3.selectAll('.data-svg').remove();
createHeatmap();
}
});
I found this example that allow to update the heatmap but I can't able to adapt the code.
In the example, data changes only value and not "shape". That is, the number of labels remains the same. In my case, the situation is a bit more complicated.
I create a Plunker with the code.
This is a good example where a LOT of d3's enter/update/exit selection can be implemented.
Yeah, I agree: recreating a chart on every change isn't a good solution.
Okay, so here's a fork of your Plunkr using enter/update/merge/exit methods.
http://plnkr.co/edit/2v8YQoZSClhKpW2U1pwi?p=preview
A preview of how I got the merge selection done: Let's take rowLabels for example:
// make region labels
var rowLabels = rowLabelGroup
.selectAll('text.rowLabel')
.data(yLabelsNames);
// ENTER SELECTION FOR COL LABELS
// ENTER + UPDATE
// After merging the entered elements with the update selection,
// apply operations to both.
rowLabels
.enter().append('text').attr('class', 'rowLabel mono')
.attr('font-weight', 'normal')
.style('text-anchor', 'end')
.on('mouseover', function(d) {
d3.select(this).attr('font-weight', 'bold').style('fill', 'red');
})
.on('mouseout', function(d) {
d3.select(this).attr('font-weight', 'normal').style('fill', 'black');
})
.attr('x', 0)
.attr('y', function(d, i) {
return i * cellSize;
})
.attr('transform', function(d, i) {
return 'translate(-3, 11)';
})
.merge(rowLabels)
.attr('name', function(d) {
return d;
})
.text(function(d) {
return d;
})
.attr('id', function(d) {
return 'rowLabel_' + yLabelsNames.indexOf(d);
})
.attr('label-r', function(d) {
return yLabelsNames.indexOf(d);
});
// exit/remove elements which are no longer in use
rowLabels.exit().remove();
Similarly, these methods have been applied to colLabels, cells and sparkLineSvgs as you can notice in the code.
And regarding appending of the SVGs, I've moved that code to outside the updateHeatmap function. And yes, btw, I've changed the name of the function from createHeatmap to updateHeatmap.
And I did encounter an issue while hovering over for the tooltip i.e. the tooltip flickered a lot. To counter that, I've added pointer-events:none to .d3-tip tooltip.
Go through the code and let me know if I've missed on anything or if you face issue understanding any part.
Hope it helps.

style text based on the context of the text in d3

I wondering is there is a way to style text in d3 based on the context of the text itself?
I have this chunk of d3 code that loop through a list of items and append them to the DOM on mouseover, so the list is updating a lot as the mouse moves along the chart. I'd like to color the p tag with the class coeff-direction as red or green based on the text inside saying "Avoid" or "Target" but what I have currently only colors everything red? Is there anyway to appropriately access the text so my condition will work?
d.features.forEach(function(m){
d3.select("#showdata")
.style("display", null)
.append("p")
.attr("class", "coeff-direction")
.text(m.coeffDirection + ' ')
.style("color", function(d) {
if ("Avoid") {
return "red"
} else {
return "Green"
}
})
.append("span")
.attr("class", 'feature-description')
.text(m.featureDescription)
})
Here is a JSFiddle with all the code: JSFiddle
The problem is that nothing is getting passed in the color function. You just need to check against the same variable that you are using the set the text - m.coeffDirection. So your if block needs to be replaced with:
if (m.coeffDirection === "Avoid") {
return "red"
} else {
return "Green"
}
Find a working JSFiddle here.
Your code only ever evaluates to true, as the string "Avoid" is always true.
if ("Avoid") {
return "red"
} else {
return "Green"
}
Perhaps you meant to check the argument d for d === "Avoid" ?
Example:
if (d === "Avoid") {
return "red";
} else {
return "green";
}

D3 treemap colors mix up

I have made a D3 treemap based on this example: http://bl.ocks.org/mbostock/4063582. My version also shows the children after clicking the treemap or button.
The size/count changefunction doesn't work as it should: I get double nodes, the backgroundcolors mix up and there are divs without a backgroundcolor.
color = d3.scale.ordinal()
.domain(colorDomainArr) //colorDomainArr = [0,1,2,3]
.range([
"#b1e1d7", "#c1e7df", "#d1ede6", "#e1f3ee", //lichtblauw
"#688ea3", "#87a4b5", "#a5bac7", "#c3d0d9", //donkerblauw
"#cbb84c", "#d7c671", "#e3d494", //"#eee2b7", //geel
"#b9c2c1", "#ced4d3", "#e3e6e6", //"#f8f8f8", //grijs
]);
It used to work, but not after I fixed another similar colorproblem.
I haven't changed the change() function.
I've tried adding
node.style("background", function(d) {
return d.children && d.depth != ondiepst && d.depth != ondiepst-1 ? color(d.name) : null
})
Color is also done by
d3.selectAll(".BG").style("background", function(d) { return color(d.name) });
In the first view everything goes well
Click on 'Laat alles zien'-button: Colors mix up
Click on 'count' radio button: no background color on some nodes
Click on 'Originele weergave'-button: it seems like everything is well, but we see the 'size' version instead of the 'count' version.
Click on 'size' radio button: three/four times double nodes instead of original view
The jsfiddle doesn't work as it does locally, but it shows my problem and code.
https://jsfiddle.net/zjx967g4/

d3j : Remove a data set

I want to remove a curve when a checkbox is unchecked. I tried this without success:
function drawdata(fileno,draw){
var name="circle"+fileno;
if (draw) {
console.log("Drawing file "+fileno+ " ("+files[fileno]+")");
svg.selectAll(name) .data(datasets[fileno]) .enter() .append("circle")
.attr('cx',function(a){ return xscales[xval](a[xval]); })
.attr('cy',function(a){ return yscales[yval](a[yval]); })
.attr('r',3)
.style("opacity", 1)
;
} else {
console.log("removing file "+fileno);
svg.selectAll(name).data(datasets[fileno]).exit().remove();
}
}
The first part work well (ie if I check a checkbox the file is plotted) but when I uncheck it, it is not removed from the svg. I guess I do not understand the exit well. Can someone point out what is wrong? Thanks.
Sample jsfiddle code:
http://jsfiddle.net/datavis/530h1qLw/11/#&togetherjs=Rkz5QyDgJm
Update:
With the addition of the fiddle, it's pretty easy to see what is going on. When you do svg.selectAll(name) it's returning an empty selection. The reason for that is because there are no elements of circleN. circleN is not a valid DOM element, and none of them exist anyway, so the selection will fail.
If you make the following changes to your cds function you will see that your code now works:
function cds(el) {
name = "circle" + el.id;
if (el.checked) {
svg.selectAll(name)
.data(dataset[el.id])
.enter().append("circle")
.attr('cx', function (a) { return xscale(a[0]); })
.attr('cy', function (a) { return yscale(a[1]); })
.attr('r', 3)
.attr('class', name)
.style("opacity", 1);
} else {
svg.selectAll("circle." + name).remove();
}
}
There are two changes of note in this function:
.attr('class', name) being added to the code creating your new DOM elements. This will add a class to the circle, making the selection easier when it comes time to remove them.
Updated the line that removes them to svg.selectAll("circle." + name).remove(); which will remove the circles when you uncheck the radio boxes.

jsnetworkx altering node colors, accessing neighbors

I have drawn a graph with jsNetworkx which is a JavaScript version of Networkx. This port doesn't quite have all the features of Networkx, yet.
My work: http://jsfiddle.net/mrcactu5/VGpRJ/4/
I would like to be able to highlight a node and its neighbors upon mouseover and change their color to #FEFEFE.
I plan to make adding event handlers to the visualization easier in the future, but for now, you have to implement such things yourself with D3.
The basic idea is the following: Assign each SVG node element a unique ID based on the node name. Then, on mouseover (which you bind with D3), get all the neighbors of a node and use the names to find the corresponding SVG elements and change their style.
Example:
jsnx.draw(G3, {
// ...
node_attr: {
r: 8,
title: function(d) { return d.label;},
id: function(d) {
return 'node-' + d.node; // assign unique ID
}
}
// ...
}, true);
// helper method to find and style SVG node elements
function highlight_nodes(nodes, on) {
nodes.forEach(function(n) {
d3.select('#node-' + n).style('fill', function(d) {
return on ? '#EEE' : d.data.color;
});
});
}
// bind event handlers
d3.selectAll('.node').on('mouseover', function(d) {
highlight_nodes(d.G.neighbors(d.node).concat(d.node), true);
});
d3.selectAll('.node').on('mouseout', function(d) {
highlight_nodes(d.G.neighbors(d.node).concat(d.node), false);
});
DEMO

Categories

Resources