Solved: jsfiddle
Issue #1: Have a grouped bar chart. I'd like the group to highlight if any bars in the group are moused-over.
Currently on mouseover sets all rects with class 'bar' to opacity 0.5 and then the specific rect to opacity 1. But how can I set the node or group of bars to opacity 1, so that they are highlighted against the others?
.on("mouseover", function(d, i, node) { //this is repeated should be in a function
d3.selectAll(".bar").transition()
.style("opacity", 0.3); //all groups given opacity 0
d3.select(this).transition()
.style("opacity", 1); //give opacity 1 to group on which it hovers.
return tooltip
.style("visibility", "visible")
.html("<span style=font-size:30px;> " + "name:" + d.name +
"</span>"
)
})
.on("mouseout", function(d) {
d3.selectAll(".bar").transition()
.style("opacity", 1);
return tooltip.style("visibility", "hidden");
})
Issue #2: Also I would like the bar's x axis labels to behave similarly. So that the names of all but the current bar would have opacity 0.5
I did try adding a clas of bar to the xAxis text, but doesn't work,
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.style("font", "20px times")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("class", "bar")
.attr("transform", "rotate(-65)");
I this try implementing ideas from this post
D3 Grouped Bar Chart - Selecting entire group?
but I haven't been able to get it to work.
My attempt to give a class of d.name + index to each bar in a group. But I can't select then, the return "." + d.name isn't working as I'd expect.
.attr("class", function(d, i) {
return d.name.replace(/\s/g, '') + i + " bar"
})
.on("mouseover", function(d, i, node) {
d3.selectAll(".bar").transition()
.style("opacity", 0.3);
d3.selectAll("." + d.name.replace(/\s/g) + i)
.transition()
.style("opacity", 1);
return tooltip
.style("visibility", "visible")
.html("<span style=font-size:30px;> " + "name:" + d.name +
"</span>");
})
The select should be,
d3.selectAll("." + d.name.replace(/\s/g, '') + i)
Actually each bar in each group could just be given a class of "group + index". There is no need for the regular expression.
Except for the text on the xAxis the highlighting is now working fine.
Any help would be greatly appreciated,
Thanks
you could base the opacity for all bars on its .name value (which is common attribute per group in your example), eg
.on("mouseover", function(d) {
let selectedName = d.name;
d3.selectAll(".bar")
.style("opacity", function(d) {
return d.name == selectedName ? 1 : 0.3;
})
//etc
This works jsFiddle
.on("mouseover", function(d, i, node) {
d3.selectAll(".bar").transition()
.style("opacity", 0.3);
d3.selectAll(".xAxisText").transition()
.style("fill", "lightgray")
.style("font-weight", "100"); //all groups given opacity 0
d3.selectAll(".groupText" + i)
.transition()
.style("font-weight", "900")
.style("fill", "black"); //give opacity 1 to group on which it hovers.
d3.selectAll(".group" + i)
.transition()
.style("opacity", 1);
return tooltip
.style("visibility", "visible")
.html("<span style=font-size:30px;> " + "name: " + d.name +
" (on blue bar)</span>");
})
.on("mouseout", function(d) {
d3.selectAll(".bar").transition()
.style("opacity", 1);
d3.selectAll(".xAxisText").transition()
.style("fill", "black")
.style("font-weight", "normal");
return tooltip.style("visibility", "hidden");
})
Related
I have a data object to show stacked bars with an initial animation that plays upon loading the page, essentially where fruits correspond to orchards:
[{Apple=1.0, Orange=2.0, Lettuce=1.0, orchard=小明, Blueberry=1.0}, {Apple=1.0, Orange=1.0, Lettuce=1.0, orchard=小陈, Blueberry=1.0}, {Apple=1.0, Orange=1.0, Lettuce=1.0, orchard=小虎, Blueberry=1.0}, {Orange=1.0, Lettuce=1.0, orchard=小桃, Blueberry=1.0, Apple=1.0}]
The below code works fine if each orchard includes every fruit-type, but upon let's say removing Apples from everyone except for 小明, the y0 values for multiple orchards become 'NaN'. I am asking for a way to remove NaN values or find a way to skip them in d3js so that other fruits can still be displayed.
My rectangle code can be found below:
var rect = groups.selectAll("rect")
.data(function(d) { return d; })
.enter()
.append("rect")
.attr("x", function(d) { return x(d.x); })
.attr("height", function(d) { return y(d.y0) - y(0); })
.attr("y", function(d) { return y(0); })
.attr("width", x.rangeBand())
.on("mouseover", function() { tooltip.style("display", null); })
.on("mouseout", function() { tooltip.style("display", "none"); })
.on("mousemove", function(d) {
var xPosition = d3.mouse(this)[0] - 15;
var yPosition = d3.mouse(this)[1] - 25;
tooltip.attr("transform", "translate(" + xPosition + "," + yPosition + ")");
tooltip.select("text").text(d.x + ": " + d.y);
});
svg.selectAll("rect")
.transition()
.duration(800)
.attr("y", function(d) { return y(d.y0 + d.y); })
.attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); })
.delay(function(d,i){console.log(i) ; return(i*90)});
I am curious about using something like
rect.filter(function(d) {
return d.y == 0;
})
.remove();
But this does not seem to work. Unfortunately I am using d3js v3.
The issue was solved by reformatting how my d3 viz read keys. In my answer to my other question you can see how the keys variable was reformatted.
I don't have my v3 version saved on-file, but it had something to do with how stackedLayout works in v3. Not sure, but if you have a similar issue, I suggest reformatting how your variables are stacked to follow the same philosophy of using
.data(d3.stack().keys(keys)(data))
I have a parallel coordinates (parallel axes) chart that is 90% working, but I'm having issues with entering new data. I am getting the error, "Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node."
I am copying the re-drawing of the axes from when the chart is initialized exactly, so that should be working fine. It seems like it's a problem with my identifier function when adding new data (potentially), although, my exit function works fine with it.
When I console log axes.enter(), the output is correct (correct element being added in correct spot). However, there's the insert error that seems to stop it from rendering.
function updateAxis(newFilters, selectedAxis, newData) {
dimensions = d3.keys(newData[0]).filter(function(d) {
return d != "PID" && d != "Age" && (y[d] = d3.scaleLinear()
.domain(d3.extent(data, function(p) { return +p[d]; }))
.range([height, 0]))
});
x.domain(dimensions);
var axes = d3.selectAll(".dimension")
.data(dimensions, d => d);
axes
.transition('update')
.duration(500)
.each(function(d) { d3.select(this).call(axis.scale(y[d])); })
.attr("transform", function(d, i) {
return "translate(" + x(d) + ")";
});
axes.selectAll("text")
.text(function(d) { return d; });
foreground
.transition('update')
.duration(500)
.attr("d", path);
background
.transition('update')
.duration(500)
.attr("d", path);
/********start axis enter() updates*************/
axes.enter().append("axes")
.attr("class", "axis")
.each(function(d) { d3.select(this).call(axis.scale(y[d])); })
.append("text")
.style("text-anchor", "end")
.attr("y", -9)
.text(function(d) { return d; })
.style("fill", "black");
axes.append("axes")
.attr("class", "dimension")
.attr("transform", function(d) { console.log ("axes.enter() transform: ", d); return "translate(" + x(d) + ")"; })
.call(d3.drag()
.subject(function(d) {
//console.log(d);
return {x: x(d)}; })
.on("start", function(d) {
dragging[d] = x(d);
background.attr("visibility", "hidden");
})
.on("drag", function(d) {
dragging[d] = Math.min(width, Math.max(0, d3.event.x));
foreground.attr("d", path);
dimensions.sort(function(a, b) { return position(a) - position(b); });
x.domain(dimensions);
axes.attr("transform", function(d) { return "translate(" + position(d) + ")"; })
})
.on("end", function(d) {
delete dragging[d];
transition(d3.select(this)).attr("transform", "translate(" + x(d) + ")");
transition(foreground).attr("d", path);
background
.attr("d", path)
.transition()
.delay(500)
.duration(0)
.attr("visibility", null);
}));
// Add and store a brush for each axis.
axes.append("axes")
.attr("class", "brush")
.each(function(d) {
d3.select(this).call(y[d].brush = d3.brushY()
.extent([[-10,0], [10,height]])
.on("brush", brush)
.on("end", brush)
)
})
.selectAll("rect")
.attr("x", -8)
.attr("width", 16);
/********end axis enter() updates*************/
axes.exit()
.transition('exit-transition')
.duration(500)
.attr("opacity", 0)
.remove();
};
Expected output is re-drawing the lines and adding in the path data (foreground and background). Foreground and background are added correctly, but the axes are not getting re-drawn correctly. So the foreground/background updates, there is space allocated in the visualization for the axis, all other axes are moved to match the new foreground/background, it's just the enter() axes themselves not being added back in. I suspect it's related to this "insert" error.
Unfortunately I can't share the entire visualization because the data I'm working on is protected, but the newData file is updating correctly and the dimensions re-add the axes in the correct place. Essentially, the axes is being added back in the order it was removed in (so it's being added back "in place" as much as possible).
I can't figure out what I'm doing wrong - appreciate all help/advice! Let me know if I need to add more details.
I have a script in which I make txts in the following way:
texts = svg.selectAll(null)
.data(data)
.enter()
.append('text')
.attr("text-anchor", "middle")
.text(function(d) {
return d.abbreviation;
})
.attr("pointer-events", "none")
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.attr("fill", "black");
texts.each(function(d) {
console.log(this.getComputedTextLength());
d.size = this.getComputedTextLength() / 2 ;
});
simulation.nodes(data).on("tick", function() {
circles.attr("cx", function(d) {
return d.x = Math.max(20, Math.min(width - 20, d.x));
})
.attr("cy", function(d) {
return d.y = Math.max(20, Math.min(height - 20, d.y));
})
texts.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
});
});
I am using property abbreviation in data to put text over the bubbles but I want to replace all spaces in input with new line character.
I tried some soutions like given at this link: How to linebreak an svg text in javascript?
but all texts go to left most corner or if I remove the .attr("x", 0) property from this, then the alignment of texts in not right. See picture below:
"State" should come directly below "Iowa"
Updated script:
texts = svg.selectAll(null)
.data(data)
.enter()
.append('text')
.attr("text-anchor", "middle")
.each(function (d) {
var arr = d.abbreviation.split(" ");
for (i = 0; i < arr.length; i++) {
d3.select(this).append("tspan")
.text(arr[i])
.attr("dy", i ? "1.2em" : 0)
.attr("text-anchor", "middle")
.attr("class", "tspan" + i);
}
})
.attr("pointer-events", "none")
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.attr("fill", "black");
What should I do to make the alignment right or is there any other way to do this?
There are different ways to fix this. An easy one is appending the <tspan> to a <g> element, setting all their x properties to 0 and text-anchor to middle.
Have a look at the demo:
var svg = d3.select("svg");
var data = [{
text: "some text"
}, {
text: "a longer text here"
}, {
text: "an even longer text here"
}, {
text: "short text"
}, {
text: "a long text"
}];
var texts = svg.selectAll(null)
.data(data)
.enter()
.append("g");
texts.append("text")
.attr("text-anchor", "middle")
.each(function(d) {
var arr = d.text.split(" ");
d3.select(this).selectAll(null)
.data(arr)
.enter()
.append("tspan")
.attr("text-anchor", "middle")
.attr("x", 0)
.attr("dy", function(d, i) {
return "1.2em"
})
.text(String)
})
var simulation = d3.forceSimulation(data)
.force("center", d3.forceCenter(200, 100))
.force("collide", d3.forceCollide(40))
.on("tick", tick);
function tick() {
texts.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"
})
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="400" height="250"></svg>
PS: Don't use that for loop inside the each. That's not idiomatic D3. Instead of that, just use an enter selection (refer to the demo to see how to do it).
I creating a stacked bar chart using this example. The chart works and renders but I can't add a mouseover label.
I tried this...
DATE.selectAll("rect")
.data(function(d) { return d.ages; })
.enter().append("rect")
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.y1); })
.attr("height", function(d) { return y(d.y0) - y(d.y1); })
.style("fill", function(d) { return color(d.name); });
.append("svg:title")
.text(functino(d){return "foo"});
But this after adding the .append("svg:title... the graph stops rendering. If I remove the .style("fill... line, the graph renders, however it's not stacked and there's no mouseover feature.
I have also tried using the tooltip route. (Source)
.on("mouseover", function() { tooltip.style("display", null); })
.on("mouseout", function() { tooltip.style("display", "none"); })
.on("mousemove", function(d) {
var xPosition = d3.mouse(this)[0] - 15;
var yPosition = d3.mouse(this)[1] - 25;
tooltip.attr("transform", "translate(" + xPosition + "," + yPosition + ")");
tooltip.select("text").text(d.y);
});
// Prep the tooltip bits, initial display is hidden
var tooltip = svg.append("g")
.attr("class", "tooltip")
.style("display", "none");
tooltip.append("rect")
.attr("width", 30)
.attr("height", 20)
.attr("fill", "white")
.style("opacity", 0.5);
tooltip.append("text")
.attr("x", 15)
.attr("dy", "1.2em")
.style("text-anchor", "middle")
.attr("font-size", "12px")
.attr("font-weight", "bold");
But still not luck. Is there a library I need to load? Not sure what's going on.
The graph stop rendering when you try to append the title because you have a typo: it's function, not functino.
Besides that, this is what you need to get the value of each stacked bar:
.append("title")
.text(function(d){
return d[1]-d[0]
});
Here is the demo: https://bl.ocks.org/anonymous/raw/886d1749c4e01e191b94df23d97dcaf7/
But I don't like <title>s. They are not very versatile. Thus, instead of creating another <text>, as the second code you linked does, I prefer creating a div:
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
Which we position and set the HTML text this way:
.on("mousemove", function(d) {
tooltip.html("Value: " + (d[1] - d[0]))
.style('top', d3.event.pageY - 10 + 'px')
.style('left', d3.event.pageX + 10 + 'px')
.style("opacity", 0.9);
}).on("mouseout", function() {
tooltip.style("opacity", 0)
});
And here is the demo: https://bl.ocks.org/anonymous/raw/f6294c4d8513dbbd8152770e0750efd9/
I am trying to add some text from the name field in my JSON file to each bubble in a cluster.
https://plnkr.co/edit/hwxxG34Z2wYZ0bc51Hgu?p=preview
I have added what I thought was correct attributes to the nodes with
node.append("text")
.text(function (d) {
return d.name;
})
.attr("dx", -10)
.attr("dy", "5em")
.text(function (d) {
return d.name
})
.style("stroke", "white");
function tick(e) {
node.each(cluster(10 * e.alpha * e.alpha))
.each(collide(.5))
.attr("transform", function (d) {
var k = "translate(" + d.x + "," + d.y + ")";
return k;
})
}
Everything works fine except the labels.
What am I missing here?
Thanks
Kev
For that you will need to make a group like this:
var node = svg.selectAll("g")
.data(nodes)
.enter().append("g").call(force.drag);//add drag to the group
To the group add circle.
var circles = node.append("circle")
.style("fill", function(d) {
return color(d.cluster);
})
To the group add text:
node.append("text")
.text(function(d) {
return d.name;
})
.attr("dx", -10)
.text(function(d) {
return d.name
})
.style("stroke", "white");
Add tween to the circle in group like this:
node.selectAll("circle").transition()
.duration(750)
.delay(function(d, i) {
return i * 5;
})
.attrTween("r", function(d) {
var i = d3.interpolate(0, d.radius);
return function(t) {
return d.radius = i(t);
};
});
Now the tick method will translate the group and with the group the circle and text will take its position.
working code here
The problem: a text SVG element cannot be child of a circle SVG element.
The solution is creating another selection for the texts:
var nodeText = svg.selectAll(".nodeText")
.data(nodes)
.enter().append("text")
.text(function (d) {
return d.name;
})
.attr("text-anchor", "middle")
.style("stroke", "white")
.call(force.drag);
Here is the plunker: https://plnkr.co/edit/qnx7CQox0ge89zBL9jxc?p=preview