Legend transition not working properly - d3.js

I have a graph with a legend, and the legend is always the same. The only thing that changes with the transitions is the size of the legend tiles, that are the same as this example.
When I update my graph, its size changes, and so does the legend's. Here is what I have for the legend :
var couleurs = ["#ffffb2", "#fed976", "#feb24c", "#fd8d3c", "#fc4e2a", "#e31a1c", "#b10026"];
var legende = ["0-15", "15-30", "30-45", "45-60", "60-75", "75-90", "90-100"];
canevas.append("g").selectAll(".legende").data(legende).enter()
.append("rect")
.attr("class", "legende")
.attr("width", cellPosX / 7)
.attr("height", largeurCellule / 2)
.attr("x", (d, i) => i * (cellPosX / 7))
.attr("y", cellPosY + 10)
.attr("fill", ((d, i) => couleurs[i]));
canevas.selectAll(".legende").data(legende).transition()
.duration(transitionTime)
.attr("width", cellPosX / 7)
.attr("x", (d, i) => i * (cellPosY / 7))
.attr("y", cellPosY + 10)
.attr("fill", ((d, i) => couleurs[i]));
canevas.selectAll(".legende").data(legende).exit()
.remove();
This works fine, except that when the graph is updated, for a while there are 2 legends at the same time. One that goes from the old position to the new one, which is expected, but there is also one that instantly appears to the new position. Here is a very low fps gif that I quickly made to show an example.
How would I go about having the legend going from its initial position to the other one, without it also appearing instantly at the new position?

Related

Add text in rect svg and append it to arc in donut chart

I wanted to add labels to each arc in donut chart. I've added by taking the centroid of each arc and adding, but somehow it is not adding in correct position. I can't figure it out so I need some help regarding it. I've added my code in codepen. The link is here.
My donut should look like this.
Sample code is:
svg.selectAll(".dataText")
.data(data_ready)
.enter()
.each(function (d) {
var centroid = arc.centroid(d);
d3.select(this)
.append('rect')
.attr("class", "dataBG_" + d.data.value.label)
.attr('x', (centroid[0]) - 28)
.attr('y', (centroid[1]) - 5)
.attr('rx', '10px')
.attr('ry', '10px')
.attr("width", 50)
.attr("height", 20)
.style('fill', d.data.value.color)
.style("opacity", 1.0);
d3.select(this)
.append('text')
.attr("class", "dataText_" + d.data.value.label)
.style('fill', 'white')
.style("font-size", "11px")
.attr("dx", (centroid[0]) - 7)
.attr("dy", centroid[1] + 7)
.text(Math.round((d.data.value.value)) + "%");
});
Thanks in advance.
The difference between the "bad" state on codepen and the desired state is that in the one you don't like, you take the centroid and then you center your text on it. The centroid of a thick arc is the midpoint of the arc that runs from the midpoint of one line-segment cap to the other. This is roughly "center of mass" of the shape if it had some finite thickness and were a physical object. I don't think it's what you want. What you want is the midpoint of the outer arc. There's no function to generate it, but it's easy enough to calculate. Also, I think you want to justify your text differently for arcs whose text-anchor point is on the left hand of the chart from those on the right half. I'm going copy your code and modify it, with comments explaining.
// for some reason I couldn't get Math.Pi to work in d3.js, so
// I'm just going to calculate it once here in the one-shot setup
var piValue = Math.acos(-1);
// also, I'm noting the inner radius here and calculating the
// the outer radius (this is similar to what you do in codepen.)
var innerRadius = 40
var thickness = 30
var outerRadius = innerRadius + thickness
svg.selectAll(".dataText")
.data(data_ready)
.enter()
.each(function (d) {
// I'm renaming "centroid" to "anchor - just a
// point that relates to where you want to put
// the label, regardless of what it means geometrically.
// no more call to arc.centroid
// var centroid = arc.centroid(d);
// calculate the angle halfway between startAngle and
// endAngle. We can just average them because the convention
// seems to be that angles always increase, even if you
// if you pass the 2*pi/0 angle, and that endAngle
// is always greater than startAngle. I subtract piValue
// before dividing by 2 because in "real" trigonometry, the
// convention is that a ray that points in the 0 valued
// angles are measured against the positive x-axis, which
// is angle 0. In D3.pie conventions, the 0-angle points upward
// along the y-axis. Subtracting pi/2 to all angles before
// doing any trigonometry fixes that, because x and y
// are handled normally.
var bisectAngle = (d.startAngle + d.endAngle - piValue) / 2.0
var anchor = [ outerRadius * Math.cos(bisectAngle), outerRadius * Math.sin(bisectAngle) ];
d3.select(this)
.append('rect')
.attr("class", "dataBG_" + d.data.value.label)
// now if you stopped and didn't change anything more, you'd
// have something kind of close to what you want, but to get
// it closer, you want the labels to "swing out" from the
// from the circle - to the left on the left half of the
// the chart and to the right on the right half. So, I'm
// replacing your code with fixed offsets to code that is
// sensitive to which side we're on. You probably also want
// to replace the constants with something related to the
// the dynamic size of the label background, but I leave
// that as an "exercise for the reader".
// .attr('x', anchor[0] - 28)
// .attr('y', anchor[1] - 5)
.attr('x', anchor[0] < 0 ? anchor[0] - 48 : anchor[0] - 2)
.attr('y', anchor[1] - 10
.attr('rx', '10px')
.attr('ry', '10px')
.attr("width", 50)
.attr("height", 20)
.style('fill', d.data.value.color)
.style("opacity", 1.0);
d3.select(this)
.append('text')
.attr("class", "dataText_" + d.data.value.label)
.style('fill', 'white')
.style("font-size", "11px")
// changing the text centering code to match the box
// box-centering code above. Again, rather than constants,
// you're probably going to want something a that
// that adjusts to the size of the background box
// .attr("dx", anchor[0] - 7)
// .attr("dy", anchor[1] + 7)
.attr("dx", anchor[0] < 0 ? anchor[0] - 28 : anchor[0] + 14)
.attr("dy", anchor[1] + 4)
.text(Math.round((d.data.value.value)) + "%");
});
I tested. this code on your codepen example. I apologize if I affected your example for everyone - I'm not familiar with codepen and I don't know the collaboration rules. This is all just meant by way of suggestion, it can be made a lot more efficient with a few tweaks, but I wanted to keep it parallel to make it clear what I was changing and why. Hope this gives you some good ideas.

d3 force directed graph nodes stay at fixed position after filter

In my d3 force directed scatter plot i try to make points disappear and re-appear by clicking on a legend key. After clicking the legend key, i would like the remaining points to regroup and not to stay fixed in the same position, leaving blank spaces (screenshots). When clicking again on the legend, they should fly in again.
I tried to remove the fill of the circles upon clicking on a legend key, which is working, but obviouly does not make the force do its work..
My code on blockbuilder.org: http://blockbuilder.org/dwoltjer/04a84646720e1f82c16536d5ef9848e8
You can treat the filtered data as new data and apply the update, enter and exit pattern:
var node = svg.selectAll(".dot")
.data(data);
node.exit().remove();
node.enter().append("circle")
.attr("class", "dot")
.attr("r", radius)
......
The click event for legend:
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color)
.on("click", function (d) {
visible[d] = !visible[d];
var newdata = data.filter(function(e) { return visible[e.bank];});
DrawNode(newdata);
});
Here is the update blocks
Simply deleting the nodes should be enough to make the force rearrange itself and group the nodes again. But, you will want to save the nodes to bring them back (possibly using a temporary array).
However, if you want the nodes to fly off screen (and back on), then what I'd do (using V4) is move the nodes to a new forcePoint that's way off screen:
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color)
.on("click", function (d) {
node.filter(function () {
return this.dataset.bank === d;
})
position
.force('x', d3.forceX(width/2).strength(20))
.force('y', d3.forceY(height*2).strength(20));//should be twice the height of the svg, so way off the y axis. Or whichever direction you choose.
});

d3 version 4 zoom behaviour on g element

The attached fiddle shows that on zoom the blue rectangles resize with the scale as expected but the yellow rectangles don't! The main difference is that the yellow rectangles were added to a 'g' element with text included. Any ideas why?
https://jsfiddle.net/sjp700/u6rj20jc/1/
var group = svg.selectAll(".rectangle")
.data(data);
gEnter = group.enter()
.append("g")
.attr("class", "rectangle")
.attr("fill", "yellow")
.attr("transform", function (d) { return "translate(" + x(d.start) + "," + y(d.finish) + ")"; });
gEnter.append("rect")
.attr("class", "rectband")
.merge(gEnter)
.attr("width", 50)
.attr("height", 18)
//.attr("rx", 10)
//.attr("ry", 10)
.style("opacity", .5) // set the element opacity
.style("stroke", "black");
Your yellow rectangles and text is not contained in an element that the zoom is applied to. Simple fix is to append them to gMain (which is the element on which the zoom is applied):
var group = gMain
.selectAll(".rectangle")
.data(data);
Updated fiddle here.

Add custom html to nodes in d3 js tree

I'm trying to build a tree like d3 js tree. I need to add a div and 2 or 3 buttons in that div for each node of the tree. Clicking on that node button should show a popup.
I'm trying for this kind of functionality
There are other plugins similar to this. But i need this in d3 js tree as its navigation and animations are smooth.
I have done this:
Use base example from D3 tree web page.
Added more SVG elements in the nodes
Added a "popup" menu when you click the node text (Add, Remove, Edit, Move) to perform this simple operations on the node.
In my experience, it is better to use SVG elements instead of a DIV (You can display buttons as images or shapes, and text as svg:text.
Here is some code:
function clickLabel(d) {
// this removes the popup if it was displayed on another node beforehand
// is=2 identifies markers...
d3.selectAll("[marker='2']").remove();
// Every node has an ID, and I add shapes to it
d3.select("[id_node='" + d.id + "']")
.append("image")
.attr("marker", 2)
.attr("xlink:href", "/Content/delete_item.png")
.attr("x", 0)
.attr("y", -50)
.attr("height", 32)
.attr("width", 32)
.on("click", removeItem);
d3.select("[id_node='" + d.id + "']")
.append("image")
.attr("marker", 2)
.attr("xlink:href", "/Content/edit.png")
.attr("x", -50)
.attr("y", -30)
.attr("height", 32)
.attr("width", 32)
.on("click", editItem);
d3.select("[id_node='" + d.id + "']")
.append("image")
.attr("marker", 2)
.attr("xlink:href", "/Content/add_item.png")
.attr("x", 20)
.attr("y", 10)
.attr("height", 32)
.attr("width", 32)
.on("click", addItem);
d3.select("[id_node='" + d.id + "']")
.append("image")
.attr("marker", 2)
.attr("xlink:href", "/Content/next_item.png")
.attr("x", -30)
.attr("y", 20)
.attr("height", 32)
.attr("width", 32)
.on("click", moveItem);
// Stop events or else it gets de-selected
event.stopPropagation();
}
Hope this helps!
You can use .append("svg:foreignObject") to add custom html to nodes in d3 js tree, for example jsfiddle example

D3.js ignores duplicate values

I'm exploring D3.js. I primarily used their tutorial to get what I have. But I have made some adjustments.
What I'm doing is counting the number of active & inactive items in a specific table. It then displays a graph with those values. Most everything works fines. I have 2 issues with it though:
It doesn't update automatically with my AJAX call when items are deleted. Only updates when items are added. But I'm not concerned about this for this post.
My primary issue: duplicate values aren't being treated as individual numbers. Instead it sees [10,10] and outputs it as a single bar in the graph as 10 (instead of 2 bars).
Looking at the D3 docs, it would seem the issue lies with .data. Documentation mentions that it joins data.
$(document).on("DOMSubtreeModified DOMNodeRemoved", ".newsfeed", function() {
var columns = ['created','deleted'];
var data = [numN, numD];
var y = d3.scale.linear()
.domain([0, d3.max(data)])
.range([0, 420]);
var x = d3.scale.ordinal()
.domain(columns)
.rangeBands([0, 120]);
chart.selectAll("rect")
.data(data)
//.enter().append("rect")
.attr("x", x)
.attr("height", y)
.attr("width", x.rangeBand())
.attr("rx", 10)
.attr("ry", 10);
chart.selectAll("text")
.data(data)
//.enter().append("text")
.attr("y", y)
.attr("x", function(d) {
return x(d) + x.rangeBand() / 2;
}) //offset
.attr("dy", -10) // padding-right
.attr("dx", ".35em") // vertical-align: middle
.attr("text-anchor", "end") // text-align: right
.text(String);
});
How can I make each value to display? If I pass in two different values, the chart displays as it should.
Your problem is at .attr("x", x). So the way you're doing it assigns the same x coordinate for both rects.
So try offsetting x coordinate.
.attr("x", function(d, i) { x + i * width_of_your_rect); })

Resources