Can't add title to stacked bar graph on D3 - d3.js

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/

Related

remove or skip null values in d3js stacked bar chart

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))

d3 grouped bar chart highlight group on mouseover

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");
})

d3.js Bar Chart facing issue with tooltip

I have the following d3.js script to create bar chart which works fine.
I added functionality to show tool tip (not sure whether i added it at right place or not) which works fine but it has created an issue with existing mouseout event.
Issue:
The issue is that the following code is not working anymore. When i mouse over it does not turn into grey.
.on('mouseout', function (d) {
d3.select(this)
.attr('fill', 'blue');
However, if i comment the following lines than above mouseout event works perfect.
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
Complete Script
var jsonData = #Html.Raw(Json.Encode(Model));
data = jsonData;
InitChart();
function InitChart() {
var barData = data;
var vis = d3.select('#SummaryChart'),
WIDTH = 500,
HEIGHT = 375,
MARGINS = {
top: 20,
right: 20,
bottom: 20,
left: 150
},
xRange = d3.scale.ordinal().rangeRoundBands([MARGINS.left, WIDTH - MARGINS.right], 0.1).domain(barData.map(function (d) {
return d.Date;
})),
yRange = d3.scale.linear().range([HEIGHT - MARGINS.top, MARGINS.bottom]).domain([0,
d3.max(barData, function (d) {
return d.Duration;
})
]),
xAxis = d3.svg.axis()
.scale(xRange)
.tickSize(0)
.tickSubdivide(true),
yAxis = d3.svg.axis()
.scale(yRange)
.tickSize(0)
.orient("left")
.tickSubdivide(true);
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<strong>Duration:</strong> <span style='color:red'>" + d.Duration + "</span>";
})
vis.call(tip);
vis.append('svg:g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + (HEIGHT - MARGINS.bottom) + ')')
.call(xAxis);
vis.append('svg:g')
.attr('class', 'y axis')
.attr('transform', 'translate(' + (MARGINS.left) + ',0)')
.call(yAxis);
vis.append("text")
.attr("class", "x label")
.attr("text-anchor", "end")
.attr("font-size", "20px")
.attr("x", WIDTH)
.attr("y", HEIGHT + 20)
.text("Time");
vis.append("text")
.attr("class", "y label")
.attr("text-anchor", "end")
.attr("font-size", "20px")
.attr("y", 100)
.attr("x",-100)
.attr("dy", ".75em")
.attr("transform", "rotate(-90)")
.text("Hours:");
vis.selectAll('rect')
.data(barData)
.enter()
.append('rect')
.attr('x', function (d) {
return xRange(d.Date);
})
.attr('y', function (d) {
return yRange(d.Duration);
})
.attr('width', xRange.rangeBand())
.attr('height', function (d) {
return ((HEIGHT - MARGINS.bottom) - yRange(d.Duration));
})
.attr('fill', 'blue')
.on('mouseover', function (d) {
d3.select(this)
.attr('fill', 'grey');
})
.on('mouseout', function (d) {
d3.select(this)
.attr('fill', 'blue');
})
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
}
</script>
Can someone point out what is wrong and how i cam make it working with both tooltip and on mouse turning into grey working both together?
Unlike jQuery, D3 allows only a single callback per action. Therefore if you attach two .on('mouseout') callbacks, only the last one will execute. See:
d3.select('div')
.on('mouseout', function() {console.log('A')})
.on('mouseout', function() {console.log('B')})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div style='width:250px;height:250px;background:red'></div>
You have two ways around this. One, as suggested in comments, would be to call both attr and tooltip in the callback, as this:
.on('mouseout', function (d) {
d3.select(this).attr('fill', 'blue');
tip.hide();
}
Second would be to use the dot . notation, as described in the API, second paragraph
If an event listener was already registered for the same type on the selected element, the existing listener is removed before the new listener is added. To register multiple listeners for the same event type, the type may be followed by an optional namespace, such as "click.foo" and "click.bar".
So in your case
.on('mouseout.attr', function (d) {
d3.select(this)
.attr('fill', 'blue');
})
.on('mouseout.tip', tip.hide)
Working example:
d3.select('div')
.on('mouseout.logA', function() {console.log('A')})
.on('mouseout.logB', function() {console.log('B')})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div style='width:250px;height:250px;background:red'></div>

How do I make a tooltip show the amount in D3 grouped bar charts?

I have 5 years of data for a grouped bar chart that includes two variables per year. When I mouseover each bar, I want it to show the specific value for that bar. But I'm not sure how to style the tooltip at the very bottom of my code to show the actually amount in my CSV when I mouse over the bar.
I want the specific dollar amount in my CSV to show up where I have "amount here" written below. I'm able to get the dollar sign in that text to show up, just not pull any data from my CSV.
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 600 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var x1 = d3.scale.ordinal();
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.ordinal()
.range(["#98abc5", "#7b6888"]);
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(".2s"));
var svg = d3.select(".chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
$(document).ready(function() {
});
d3.csv("data/data.csv", function(error, data) {
var restAmt = d3.keys(data[0]).filter(function(key) { return key !== "year"; });
data.forEach(function(d) {
d.totalrest = restAmt.map(function(name) { return {name: name, value: +d[name]}; });
});
x0.domain(data.map(function(d) { return d.year; }));
x1.domain(restAmt).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(data, function(d) { return d3.max(d.totalrest, function(d) { return d.value; }); })]);
var year = svg.selectAll(".year")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x0(d.year) + ",0)"; });
//draw X axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
//draw Y axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Millions of Dollars");
year.selectAll("rest")
.data(function(d) { return d.totalrest; })
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) { return x1(d.name); })
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.style("fill", function(d) { return color(d.name); })
.on("mouseover", function(){return tooltip.style("visibility", "visible"); })
.on("mousemove", function(){return tooltip.style("top", (event.pageY-120)+"px").style("left",(event.pageX-120)+"px"); })
.on("mouseout", function(){return tooltip.style("visibility", "hidden");} )
var tooltip = d3.select(".chart")
.append("g")
.style("position", "absolute")
.style("z-index", "0")
.style("visibility", "hidden")
.text(function(){
return '$'+"amount here"
})
For one thing, see if you can create a minimal complete verifiable example. I couldn't manage to get you code running so I'm guessing here.
Just by looking though, when you set the text on the tooltip, see if data is bound such that you can just pass it in.
.text(function(d){
return '$'+d // or d.whatever
})
If that fails, you should be able to pull the same trick with .on just above, and pass that data to a function that creates the tooltip.
That being said, I get the sense that you may be hiding and showing every tooltip. If that's the case, and the data are bound to the tooltips, you can create a tooltip function that takes in indx and then call .style("visibility", function(d,i){return i === indx ? null : "hidden") which will unhide only the tooltip of indx. You can run the function with -1 at the start of execution to hide all tooltips. Alternatively, just create the tooltip when you need it rather than toggle its visibility.
here just guess your amount field in csv is called amount, use d.amount as an example below:
You can add parameter d in 'mouseover' event, and bind the amout value into tooltip element as html element:
var div = d3.select('body').append('div')
.attr('class', 'tooltip')
.style('opacity', 0);
year.selectAll("rest")
...
.on('mouseover', function(d) {
div.transition()
.duration(200)
.style('opacity', .9);
div.html('<h3>' + d.amount + '</h3>' + '<p>' + d.amount + '</p>')
.style('left', (d3.event.pageX) + 'px')
.style('top', (d3.event.pageY - 28) + 'px');
})

D3 drag error 'Cannot read property x of undefined'

I'm trying to get drag functionality to work on D3, and have copied the code directly from the developer's example.
However it seems the origin (what is being clicked) is not being passed correctly into the variable d, which leads to the error: 'Cannot read property 'x' of undefined'
The relevant code:
var drag = d3.behavior.drag()
.on("drag", function(d,i) {
d.x += d3.event.dx
d.y += d3.event.dy
d3.select(this).attr("transform", function(d,i){
return "translate(" + [ d.x,d.y ] + ")"
})
});
var svg = d3.select("body").append("svg")
.attr("width", 1000)
.attr("height", 300);
var group = svg.append("svg:g")
.attr("transform", "translate(10, 10)")
.attr("id", "group");
var rect1 = group.append("svg:rect")
.attr("rx", 6)
.attr("ry", 6)
.attr("x", 5/2)
.attr("y", 5/2)
.attr("id", "rect")
.attr("width", 250)
.attr("height", 125)
.style("fill", 'white')
.style("stroke", d3.scale.category20c())
.style('stroke-width', 5)
.call(drag);
Usually, in D3 you create elements out of some sort of datasets. In your case you have just one (perhaps, one day you'll want more than that). Here's how you can do it:
var data = [{x: 2.5, y: 2.5}], // here's a dataset that has one item in it
rects = group.selectAll('rect').data(data) // do a data join on 'rect' nodes
.enter().append('rect') // for all new items append new nodes with the following attributes:
.attr('x', function (d) { return d.x; })
.attr('y', function (d) { return d.y; })
... // other attributes here to modify
.call(drag);
As for the 'drag' event handler:
var drag = d3.behavior.drag()
.on('drag', function (d) {
d.x += d3.event.dx;
d.y += d3.event.dy;
d3.select(this)
.attr('transform', 'translate(' + d.x + ',' + d.y + ')');
});
Oleg's got it, I just wanted to mention one other thing you might do in your case.
Since you only have a single rect, you can bind data directly to it with .datum() and not bother with computing a join or having an enter selection:
var rect1 = svg.append('rect')
.datum([{x: 2.5, y: 2.5}])
.attr('x', function (d) { return d.x; })
.attr('y', function (d) { return d.y; })
//... other attributes here
.call(drag);

Resources