Hiding text elements in D3 chord diagram - d3.js

Similar to this example:
I have a function that highlights the chords linked to the selected group and hides all unrelated chords:
function fade() {
return function(d, i) {
svg.selectAll("path.chord")
.filter(function(d) {
return d.source.index != i && d.target.index != i;
})
.style("visibility", "hidden");
};
}
I was trying to extend this so that it not only hides the chords, but also the text labels for groups that are not connected to the currently selected group.
Is it possible to combine these two operations into a single function that I pass to .on("mouseover",[function])?

You can select with a logical OR like this:
svg.selectAll("path.chord, text.groupLabel")
see the documentation on selections for more details

Related

Changing colour of bar on selecting and unselecting bar in composite chart

https://blockbuilder.org/ninjakx/63295ea0a8052716644738d37d390e52
1)
When I click on focus ordinal bar((c2 of composite chart) it should keep the selected one as red and other as grey but it doesn't.
2)
When I click on pie chart I get red bars along with unfiltered bar(grey). Here clicking on red bar should filter other graphs it's doing that as you can see my table and pie chart is getting updated but When I click on gray bar data is also getting filtered but for pie chart it just add grey slices.
Line no. 284-324:
chart_11.fadeDeselectedArea = function (brushSelection) {
var _chart = this;
var bars = _chart.chartBodyG().selectAll('rect.bar');
if (chart_11Filter.length) {
bars.classed(dc.constants.SELECTED_CLASS, function (d) {
return chart_11Filter.includes(d.data.key);
});
bars.classed(dc.constants.DESELECTED_CLASS, function (d) {
return !chart_11Filter.includes(d.data.key);
});
} else {
bars.classed(dc.constants.SELECTED_CLASS, false);
bars.classed(dc.constants.DESELECTED_CLASS, false);
}
};
chart_11.on('pretransition', function(chart) {
chart.selectAll('rect.bar').on('click.ordinal-select', function(d) {
var i = chart_11Filter.indexOf(d.data.key);
if(i >= 0)
chart_11Filter.splice(i, 1);
else
chart_11Filter.push(d.data.key);
chart.applyFilter();
chart.redrawGroup();
});
});
If I use the above code then I get these things So I can think of these solutions.
I can change the colour of c2 bar on clicking by using the above code and applying it to c2.
also for the second graph I can use css to disable on clicking them or I
can make the filter to return none.
But when I tried the above solutions It didn't work. Problems were still the same.
If I make this function applicable only for c2 by replacing chart_11 with c2:
chart_11.fadeDeselectedArea = function (brushSelection) {
.
.
.
.
chart_11.on('pretransition', function(chart) {
.
.
.
I get this:
Edit:
chart_11.on('pretransition', function(chart) {
chart.selectAll('rect.bar').on('click', null);
If I add this I will be able to disable clicking all bar. I have to make it only for C1.
chart_11.on('pretransition', function(chart) {
// chart.selectAll('rect.bar').on('click', null);
chart.selectAll('rect.bar').on('click.ordinal-select', function(d) {
In this function my 2nd issue can be solved I guess. This function has to be customized. Accessing the child C2 and select its rect.bar and filter.
But unable to write the code for it.
This is getting to be a very hacky solution, combining two already hacky customizations of dc.js.
However, you weren't very far off; it is just a matter of restricting behaviors to c2 and cleaning out some irrelevant code.
I removed hide_second_chart because that's not necessary here, and removed the filterHandler for the same reason.
As you pointed out, fadeDeselectedArea has to be overridden on the parent; for some reason it doesn't fire on the children.
But this selection was empty, so nothing happened:
var bars = _chart.chartBodyG().selectAll('rect.bar');
I changed it to
var bars = c2.selectAll('rect.bar');
Also, the click handler should be specific to the second child, so this
chart_11.on('pretransition', function(chart) {
chart.selectAll('rect.bar').on('click.ordinal-select', function(d) {
becomes
c2.on('pretransition.click-handler', function(chart) {
chart.selectAll('.sub._1 rect.bar').on('click.ordinal-select', function(d) {
.sub._1 is CSS that will select only the second child chart.
We can use similar CSS to disable hover behaviors on the first child chart:
.dc-chart .sub._0 rect.bar:hover {
fill-opacity: 1;
}
.dc-chart .sub._0 rect.bar {
cursor: pointer;
}
Enable filterAll, as discussed in Unable to reset the focus ordinal bar chart:
chart_11.filterAll = function() {
chart_11Filter = [];
chart_11.filter(null);
};
Finally, it is confusing if the unfiltered chart is not the same color as deselected bars, so we change grey to #ccc:
.colors('#ccc')
Working fork of your block.
Hopefully the range/focus part still works, because otherwise this is making things much more complicated than they need to be!

dc.js exclude the brushed area and highlight rest

I'm not data-viz expert or d3, I have found plenty of examples to how to build brushing and zoom for example Mike.
They all have shown how to filter to the brushed area but I want to achieve to reverse of that effect, how?
Can someone through me ideas how to achieve it?
I don't know why I assumed you meant a bar chart when you linked to an area chart. You can ignore the highlighting section and skip to filtering if you're interested in doing this with line charts. There is no highlighting of line chart, just the brush itself.
Highlighting the bars in reverse
This isn't all that hard, but it's somewhat messy because we replace an undocumented function in the chart. Like most things in dc.js, if there isn't an option, you can usually replace the functionality (or add or change stuff once the chart has rendered/drawn).
Here there's a specific, public function which fades the deselected areas. It's called fadeDeselectedArea. (Actually it both fades and un-fades when the chart is ordinal, but we'll ignore that part.)
The original function looks like this:
_chart.fadeDeselectedArea = function () {
var bars = _chart.chartBodyG().selectAll('rect.bar');
var extent = _chart.brush().extent();
if (_chart.isOrdinal()) {
if (_chart.hasFilter()) {
bars.classed(dc.constants.SELECTED_CLASS, function (d) {
return _chart.hasFilter(d.x);
});
bars.classed(dc.constants.DESELECTED_CLASS, function (d) {
return !_chart.hasFilter(d.x);
});
} else {
bars.classed(dc.constants.SELECTED_CLASS, false);
bars.classed(dc.constants.DESELECTED_CLASS, false);
}
} else {
if (!_chart.brushIsEmpty(extent)) {
var start = extent[0];
var end = extent[1];
bars.classed(dc.constants.DESELECTED_CLASS, function (d) {
return d.x < start || d.x >= end;
});
} else {
bars.classed(dc.constants.DESELECTED_CLASS, false);
}
}
};
source link
We'll ignore the ordinal part because that's only individual selection, not brushed selection. Here is the reverse of the second part:
spendHistChart.fadeDeselectedArea = function () {
var _chart = this;
var bars = _chart.chartBodyG().selectAll('rect.bar');
var extent = _chart.brush().extent();
// only covering the non-ordinal (ranged brush) case here...
if (!_chart.brushIsEmpty(extent)) {
var start = extent[0];
var end = extent[1];
bars.classed(dc.constants.DESELECTED_CLASS, function (d) {
return d.x >= start && d.x < end;
});
} else {
bars.classed(dc.constants.DESELECTED_CLASS, false);
}
};
Creating a variable _chart is just to keep the code the same as much as possible. You can see that d.x >= start && d.x < end is exactly the opposite of d.x < start || d.x >= end
Reversing the filtering
We'll need to add a filterHandler to the chart in order to reverse the filtering. Again, we'll base it off the default behavior, but here there's a legitimate customization point so we don't have to replace a function, just supply one:
spendHistChart.filterHandler(function(dimension, filters) {
if(filters.length === 0)
dimension.filter(null);
else {
// assume one RangedFilter but apply in reverse
// this is less efficient than filterRange but it shouldn't
// matter much unless the data is huge
var filter = filters[0];
dimension.filterFunction(function(d) {
return !filter.isFiltered(d);
})
}
});
Again, we cut out the cases we don't care about. There is no reason to be general about something that has a specific purpose and it will only cause maintenance problems. The only two cases we care about are no filter and one range filter.
Here the RangedFilter already supplies a filter function, so we can just call it and not (!) the result. This will be slightly less efficient than the filterRange but crossfilter has no native support for multiple ranges (or the inverse of a range).
That's it! Fiddle here: http://jsfiddle.net/gordonwoodhull/46snsbc2/8/

d3 append invisible if data is not same as previous

d3 newb is trying something else:
I want to add a date label to my bar chart only if the date is not the same as in the previous bar.
bar.append("text")
.attr("visibility", function(d,i){
if(d.Datum == data[i-1].Datum) return "hidden"})
.attr("x", padding)
.attr("y", barHeight-2)
.attr("fill", "Gainsboro")
.text(function(d){ return d.Datum})
So I thought I add a visibility to my text label. however I cannot access the previous date from the data-object... probably this is an easy one for someone not newbie as me...
data example (before CSV import)
Datum,Name,Kategorie,Betrag
01/10/15,,Lohn,1586.7
02/10/15,,lunch,-4.55
So assuming that after d3 parses the text file you are left with data like:
var data = [
{
Datum: "01/10/15",
Name: "",
Kategorie: "Lohn",
Betrag: 1586.7
},{
...
}
];
I'd pre-process the data to contain a bool about whether or not it is the first instance of that date:
data.forEach(function(d,i){
d.isFirstInstanceOfDate = (i === 0 || d.Dataum !== data[i-1].Datum);
});
Then assuming that bar is a selection of gs elements (which already contain a rect), I'd filter them and only append the text on the first instance:
bar
.filter(function(d){
return d.isFirstInstanceOfDate
})
.append('text')
...
The index starts from 0. By subtracting 1 from it the first time you get an error. You need to check whether i > 0 so that you don't do out of bounds.
For example:
.style('visibility', function (d, i) {
if (i > 0) {
if (d.datetime === data[i - 1].datetime) {
return 'hidden';
}
}
return 'visible';
});
Here are two sample fiddles:
http://jsfiddle.net/00drii/y079vw7e/
http://jsfiddle.net/00drii/dmhs0gza/

apply several mouseover events to neighboring (connected) nodes

I have a network diagram (force-directed graph), a scatterplot, and a table that are all interconnected (see jsFiddle). I have the interconnections working the way I want them for mouseover events. I would like to modify my code so that when I mouseover a node in the network diagram, not only is the moused-over node highlighted (and its connections in the scatterplot and table), but its immediate neighbor nodes are also highlighted (as well as their connections in the scatterplot and table).
I looked at the information in Highlight selected node, its links, and its children in a D3 force directed graph for help. Somewhere along the way (not exactly sure where) I found an example of a function that helps define the connected nodes, isConnected().
function isConnected(a, b) {
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
}
I'd like to incorporate this function into my mouseover events, perhaps with an if() statement, so that I can do all of the "highlighting" that I want. But, I'm new to D3 and js and am not sure how to set it up.
Below is the snippet of code (from the jsFiddle) that I would like to modify. I would appreciate any suggestions or pointers to other examples.
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", function(d) { return "node " + d.name + " " + d.location; })
.call(force.drag)
.on("mouseover", function(d) {
// I would like to insert an if statement to do all of these things to the connected nodes
// if(isConnected(d, o)) {
d3.select(this).select("circle").style("stroke-width", 6);
d3.select(this).select("circle").style("stroke", "orange");
d3.select(this).select("text").style("font", "20px sans-serif");
d3.selectAll("rect." + d.location).style("stroke-width", 6);
d3.selectAll("rect." + d.location).style("stroke", "orange");
d3.selectAll("text." + d.location).style("font", "20px sans-serif");
d3.selectAll("tr." + d.name).style("background-color", "orange");
//}
})
.on("mouseout", function(d) {
// if(isConnected(d, o)) {
d3.select(this).select("circle").style("stroke-width", 1.5);
d3.select(this).select("circle").style("stroke", "gray");
d3.select(this).select("text").style("font", "12px sans-serif");
d3.selectAll("rect." + d.location).style("stroke-width", 1.5);
d3.selectAll("rect." + d.location).style("stroke", "gray");
d3.selectAll("text." + d.location).style("font", "12px sans-serif");
d3.selectAll("tr." + d.name).style("background-color", "white");
//}
});
In another scenario I would put my visual objects into a graph data structure and navigate that to efficiently update the appropriate items. But this is d3, but so we will do the same thing but instead of a graph data structure that we create we will use d3 selections (which can be like graphs but for this they will look a lot more like arrays). Algorithmically this approach will not be as efficient, but our graphs are small.
So working backwards I will want a selection that includes only the picked node's neighboring
nodes. I will do this by selecting all the circles and then using the d3 selection filter method to reduce that to only those circles that are neighbors.
Of course then I need the list of neighbors, but a few nice js array methods make short work of that. The final relevant code (in mouseover) is not even that long - but I've added a bunch of comments:
// Figure out the neighboring node id's with brute strength because the graph is small
var nodeNeighbors = graph.links.filter(function(link) {
// Filter the list of links to only those links that have our target
// node as a source or target
return link.source.index === d.index || link.target.index === d.index;})
.map(function(link) {
// Map the list of links to a simple array of the neighboring indices - this is
// technically not required but makes the code below simpler because we can use
// indexOf instead of iterating and searching ourselves.
return link.source.index === d.index ? link.target.index : link.source.index; });
// Reset all circles - we will do this in mouseout also
svg.selectAll('circle').style('stroke', 'gray');
// now we select the neighboring circles and apply whatever style we want.
// Note that we could also filter a selection of links in this way if we want to
// Highlight those as well
svg.selectAll('circle').filter(function(node) {
// I filter the selection of all circles to only those that hold a node with an
// index in my listg of neighbors
return nodeNeighbors.indexOf(node.index) > -1;
})
.style('stroke', 'orange');
You can also try the fiddle
I think the important d3 concept relevant here is that when you associate data with an element (usually using the data() or datum() methods on selections) then that data sticks with that element and any future selections will always use it.
To link other aspects you can pull those attributes in a similar way and link them through d3. For example for the location rectangles you could add to mouseover:
var nodeLocations = graph.links.filter(function(link) {
return link.source.index === d.index || link.target.index === d.index;})
.map(function(link) {
return link.source.index === d.index ? link.target.location : link.source.location; });
d3.selectAll("rect").filter(function(node) { return nodeLocations.indexOf(node.location) > -1; }) .style("stroke", "cyan");
This thing I built does that with the Ego Network feature:
https://gist.github.com/emeeks/4588962
Add a .on("mouseover", findEgo) to your nodes and the following should work, as long as you have some kind of identifying uid attribute, which you could generate when you load the nodes if one isn't handy. It's a bit of overkill, since it allows for n-degree ego networks, and creates an aggregated table for other network analysis functions, but the basic functionality will give you what you want and you or other users might find that aspect useful:
function findEgo(d) {
var computedEgoArray = findEgoNetwork(d.id, 1, false,"individual");
d3.selectAll("circle.node").style("fill", function(p) {return p.id == d.id ? "purple" : computedEgoArray.indexOf(p.id) > -1 ? "blue" : "pink"})
}
function findEgoNetwork(searchNode, egoNetworkDegree, isDirected, searchType) {
var egoNetwork = {};
for (x in nodes) {
if (nodes[x].id == searchNode || searchType == "aggregate") {
egoNetwork[nodes[x].id] = [nodes[x].id];
var z = 0;
while (z < egoNetworkDegree) {
var thisEgoRing = egoNetwork[nodes[x].id].slice(0);
for (y in links) {
if (thisEgoRing.indexOf(links[y].source.id) > -1 && thisEgoRing.indexOf(links[y].target.id) == -1) {
egoNetwork[nodes[x].id].push(links[y].target.id)
}
else if (isDirected == false && thisEgoRing.indexOf(links[y].source.id) == -1 && thisEgoRing.indexOf(links[y].target.id) > -1) {
egoNetwork[nodes[x].id].push(links[y].source.id)
}
}
z++;
}
}
}
if (searchType == "aggregate") {
//if it's checking the entire network, pass back the entire object of arrays
return egoNetwork;
}
else {
//Otherwise only give back the array that corresponds with the search node
return egoNetwork[searchNode];
}
}

d3js highlighting connected nodes

I want to list all the nodes that are connected to a node that I rollover with a mouse in a text field, been trying to use the filter function and it sort of does what I need since it affects the stroke width of the linked objects but I'd like to also output the node names too.
.on("mouseover", function(da) {
link.filter(function(db) { return da.group == db.groupColor; })
.transition()
.style("stroke-width", 8);
selectedText.text("Currently Selected: "+da.name+"is connecting to sensors: "+link.filter(function(db) { return da.group == db.groupColor; }) );
right now I am getting an output of "bike brian is connected to sensors: [object svgline element]"
but what I want is to be able to return all the names of the other nodes... does that make sense? so that it might say "bike brian is connected to sensors: accelerometer, gps, etc..."
Assuming your links are using standard network syntax, you could do the following:
var filteredLinks = link.filter(function(db) { return da.group == db.groupColor; });
var sensorList = '';
for (x in filteredLinks) {
if (filteredLinks[x]) {
filteredLinks[x].source.id == originalSensorID ? sensorList += filteredLinks[x].target.id : sensorList += filteredLinks[x].source.id
}
}
You'll probably want to add commas and spaces to the sensorList variable. It's a bit cumbersome, but I wasn't able to make the .each() method work with a filter, so it's the best I can come up with.

Resources