D3.js Interactive Legend Toggling - javascript

I have a line graph I've made from nested data. The user can pick which keys will be graphed. Each key value has 4 child values associated with it. Every key has the same child value names. Each child value has it's own line. So, if the user picks 2 keys to be graphed, 8 lines will be on the graph. 4 for one key and 4 for the other.
I've made a legend that will display the child name and the pattern of it's line. I would like to be able to click the name of the child value in the legend and all child values with that name will be hidden, even if they have a different key.
Here is how I nested:
var servers = d3.nest()
.key(function (d) { return d.serverName; })
.entries(data)
How I defined lines:
var lineGen = d3.svg.line()
.x(function (d) {
return x(timeFormat.parse(d.metricDate));
})
.y(function (d) {
return y(d.ServerLogon);
})
.interpolate("linear")
var lineGen2 = d3.svg.line()
.x(function (d) {
return x(timeFormat.parse(d.metricDate));
})
.y(function (d) {
return y1(d.Processor);
})
.interpolate("linear");
var lineGen3 = d3.svg.line()
.x(function (d) {
return x(timeFormat.parse(d.metricDate));
})
.y(function (d) {
return y2(d.AdminLogon);
})
.interpolate("linear");
var lineGen4 = d3.svg.line()
.x(function (d) {
return x(timeFormat.parse(d.metricDate));
})
.y(function (d) {
return y3(d.ServerExport);
})
.interpolate("linear");
How I append lines, text and line pattern for legend:
servers.forEach(function (d, i) {
visObjectLoc.append("svg:path")
.attr("class", "line")
.attr("fill", "none")
.attr("stroke-dasharray", ("15, 3"))
.attr("d", lineGen2(d.values))
.attr("id", "Processor")
.style("stroke", function () {
return d.color = color(d.key);
});
visObjectLoc.append("text")
.attr("x", WIDTH / 4)
.attr("y", 34)
.style("text-anchor", "middle")
.style("font-size", "18px")
.text("Processor")
.on("click", function () {
var active = d.active ? false : true,
newOpacity = active ? 0 : 1;
d3.select("#Processor")
.transition().duration(100)
.style("opacity", newOpacity);
d.active = active;
});
visObjectLoc.append("line")
.attr("x1", 455)
.attr("x2", 370)
.attr("y1", 44)
.attr("y2", 44)
.attr("stroke-width", 2)
.attr("stroke", "black")
.attr("stroke-dasharray", ("15, 3"))
});
So far, the onClicks are only hiding the first key's child line. I've tried giving each child line for each server the same ID. I'm thinking I'm not looping through the keys correctly or maybe each line isn't getting the ID it needs. Any suggestions are welcome and appreciated.

IDs are unique. If you want to select a group of things, give them a class. If you could put the project in a JSFiddle I could test it, but it might be as simple as changing the line in the servers.forEach(... in the following way:
.attr("id", "Processor")
to
.attr("class", "Processor")
and then in the onClick callback:
d3.selectAll(".Processor")
.transition().duration(100)
.style("opacity", newOpacity);
...

Related

How can I change the attribute of the circle node in d3.js?

The following code displays a graph based on http://my-neo4j-movies-app.herokuapp.com/
Now I want to make the individual nodes react to click events, similar to http://bl.ocks.org/d3noob/5141528, and increase thier size with help of the click() function, but the attribute doesn’t change. If I remove the two lines
.append("circle")
.attr("r", 5)
in the variable definition of 'node' the code breaks.
<script type="text/javascript">
var width = screen.width, height = screen.height;
var force = d3.layout.force().charge(-200).linkDistance(30)
.size([width, height]);
var svg = d3.select("#graph").append("svg")
.attr("width", "100%").attr("height", "100%")
.attr("pointer-events", "all");
d3.json("/graph", function(error, graph) {
if (error) return;
force.nodes(graph.nodes).links(graph.links).start();
var link = svg.selectAll(".link")
.data(graph.links).enter()
.append("line").attr("class", "link");
var node = svg.selectAll(".node")
.data(graph.nodes).enter()
.append("circle")
.attr("r", 5)
.attr("class", function (d) { return "node "+d.label })
.on("click", click)
.call(force.drag);
// add the nodes
node.append("circle").attr("r", 5);
// html title attribute
node.append("title").text(function (d) { return d.title; });
// force feed algo ticks
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
// action to take on mouse click
function click() {
console.log(d3.select(this));
d3.select(this).select(".circle")
.attr("r", 26)
.style("fill", "lightsteelblue");
}
});
In your second example the "node" is a g element with circle and text like this:
<g>
<circle />
<text>some text</text>
</g>
So this code in the click:
d3.select(this).select(".circle")
is saying select the g and grab the first thing with class circle (the circle).
Your code though, the node is just a circle. So, in the click, just do this:
d3.select(this)
.attr("r", 26)
.style("fill", "lightsteelblue");

Force-Layout links all being removed when updating with new data (d3 v 4.0)

This is my first question here so bear with me...
I've made a Force-Directed graph using the d3 inbuilt force layout. This works find on the initial load, but when I try to reload with new data (using onchange with a select element), all the links disappear from the force layout.
The data seems to be updating correctly, in that the correct links are still present in the data, I just can't understand why the links aren't coming through as lines in the svg element.
I've made a js fiddle to show how I'm doing it:
https://jsfiddle.net/onlyskin/f5a4uxje/
(run and then select either 'size' or 'colour' from the dropdown - the expected behaviour is for the appropriate nodes and links to disappear).
The update function looks like this:
function update() {
var pointGroup = nodeGroup.selectAll('.node')
.data(data.nodes, function(d) { return d ? d.id : this.id; });
pointGroup.exit().remove();
var newNodeGroups = pointGroup.enter().append('g')
.attr('class', 'node');
newNodeGroups.append('circle')
.attr('r', function(d) { return circleRadius[d.type] });
newNodeGroups.append('text')
.text(function(d) { return d.id; })
.attr('class', function(d) { return d.type; });
var allLinks = linkGroup.selectAll('line')
.data(data.links, function(d) { return d.source+'-'+d.target; });
allLinks.exit().remove();
newLinks = allLinks.enter().append('line')
.attr('class', 'link');
var circles = nodeGroup.selectAll('circle');
var text = nodeGroup.selectAll('text');
var lines = linkGroup.selectAll('line');
function ticked() {
lines
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
circles
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
text
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
}
simulation
.on('tick', ticked)
.restart();
};

Circles on top of bar chart d3.js

I am making grouped bar chart based on Mike Bostock's tutorial.
I can't figure out how to put circles on top of my bars to act as tooltip when hovering, just like in this tutorial except it's on bars and not on a line.
I tried appending the circles like this :
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 5)
.attr("cx", function(d) { return x1(d.name); })
.attr("cy", function(d) { return y(d.value); })
});
But I get NaN values. I am very confused about which variable I should use to get the right cx and cy.
Here is my code.
Any ideas ?
Thank you
You will get NaN values since your data join is not correct, you are trying to get values that are not currently present in your data. In order to get those values you would need to make a reference to data.years.
Here is my approach:
// Inheriting data from parent node and setting it up,
// add year to each object so we can make use for our
// mouse interactions.
year.selectAll('.gender-circles')
.data(function(data) {
return data.years.map(function(d) {
d.year = data.year;
return d;
})
})
.enter().append('circle')
.attr("class", function(d) {
return "gender-circles gender-circles-" + d.year;
})
.attr("r", 10)
.attr('cx', function(d) {
console.log(d)
return x1(d.name) + 6.5;
})
.attr('cy', function(d) {
return y(d.value) - 15;
})
.style('display', 'none'); // default display
// ....
// Using an invisible rect for mouseover interactions
year.selectAll('.gender-rect-interaction')
.data(function(d) { // Inheriting data from parent node and setting it up
return [d];
})
.enter().append('rect')
.attr("width", x0.rangeBand()) // full width of x0 rangeband
.attr("x", function(d) {
return 0;
})
.attr("y", function(d) {
return 0;
})
.attr("height", function(d) { // full height
return height;
})
.style('opacity', 0) // invisible!
.on('mousemove', function(d) { // show all our circles by class
d3.selectAll('.gender-circles-' + d.year)
.style('display', 'block');
})
.on('mouseout', function(d) { // hide all our circles by class
d3.selectAll('.gender-circles-' + d.year)
.style('display', 'none');
});
Working plnkr: https://plnkr.co/edit/oH4KXdxdIW82nLGv46NI?p=preview

Can't append text to d3.js force layout nodes

OBJECTIVE: append text to each node in a d3 force layout
BUG: text is appended to the object (I think, see console) but not displayed on screen
Here's the jsfiddle.
node.append("svg:text")
.text(function (d) { return d.name; }) // note that this works for
// storing the name as the id, as seen by selecting that element by
// it's ID in the CSS (see red-stroked node)
.style("fill", "#555")
.style("font-family", "Arial")
.style("font-size", 12);
I'd be so grateful for any thoughts.
You can't add svg text to a svg circle. You should first create an svg g object (g stands for group) for each node, and than add a circle and a text for each g element, like in this code:
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("g");
var circle = node.append("circle")
.attr("class", "node")
.attr("id", function (d) { return d.name; })
.attr("r", 5)
.style("fill", function (d) {
return color(d.group);
});
var label = node.append("svg:text")
.text(function (d) { return d.name; })
.style("text-anchor", "middle")
.style("fill", "#555")
.style("font-family", "Arial")
.style("font-size", 12);
Of course, tick function should be updated accordingly: (also css a little bit)
circle.attr("cx", function (d) {
return d.x;
})
.attr("cy", function (d) {
return d.y;
});
label.attr("x", function (d) {
return d.x;
})
.attr("y", function (d) {
return d.y - 10;
});
Here is jsfiddle.

Converting only certain nodes in D3 Sankey chart from rectangle to circle

I would like to reproduce the process from D3 Sankey chart using circle node instead of rectangle node, however, I would like to select only certain nodes to change from rectangles to circles.
For example, in this jsfiddle used in the example, how would you only select Node 4 and Node 7 to be converted to a circle?
I updated your fiddle.
Basically you just need some way to select the nodes that you want to make different. I used unique classname so that you can style them with CSS as well. I didn't feel like writing the code to select just 4 and 7 (I'm lazy) so I just selected all of the even nodes instead.
// add in the nodes
var node = svg.append("g").selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", function (d, i) { return i % 2 ? "node rect" : "node circle";
})
Then you can use that to select the nodes and add circles instead of rectangles.
svg.selectAll(".node.circle").append("circle")
.attr("r", sankey.nodeWidth() / 2)
.attr("cy", function(d) { return d.dy/2; })
.attr("cx", sankey.nodeWidth() / 2)
.style("fill", function (d) {
There is also another similar approach, illustrated in the following jsfiddle.
I started from this fiddle (from another SO question that you merntioned)), where all nodes had already been converted to circles:
Then I modified existing and added some new code that involves filtering during creation of circles:
// add the circles for "node4" and "node7"
node
.filter(function(d){ return (d.name == "node4") || (d.name == "node7"); })
.append("circle")
.attr("cx", sankey.nodeWidth()/2)
.attr("cy", function (d) {
return d.dy/2;
})
.attr("r", function (d) {
return Math.sqrt(d.dy);
})
.style("fill", function (d) {
return d.color = color(d.name.replace(/ .*/, ""));
})
.style("fill-opacity", ".9")
.style("shape-rendering", "crispEdges")
.style("stroke", function (d) {
return d3.rgb(d.color).darker(2);
})
.append("title")
.text(function (d) {
return d.name + "\n" + format(d.value);
});
// add the rectangles for the rest of the nodes
node
.filter(function(d){ return !((d.name == "node4") || (d.name == "node7")); })
.append("rect")
.attr("y", function (d) {
return d.dy/2 - Math.sqrt(d.dy)/2;
})
.attr("height", function (d) {
return Math.sqrt(d.dy);
})
.attr("width", sankey.nodeWidth())
.style("fill", function (d) {
return d.color = color(d.name.replace(/ .*/, ""));
})
.style("fill-opacity", ".9")
.style("shape-rendering", "crispEdges")
.style("stroke", function (d) {
return d3.rgb(d.color).darker(2);
})
.append("title")
.text(function (d) {
return d.name + "\n" + format(d.value);
});
Similar code had to be modified for accurate positioning text beside rectangles.
I believe the final result looks natural, even though it lost some of the qualities of the original Sankey (like wider connections).

Categories