Tooltips show in the wrong position - d3.js

I have three charts in different positions. The tooltips look fine for my first chart, but for the other two the tooltips show in the first chart.
I've tried BBbox and getboundindclientrect(), and none of them work for d3.select(this).
// This is my chart module, I'm struggling to get xPosition and yPosition right.
function chart(selection) {
innerWidth = width - margin.left - margin.right,
innerHeight = height - margin.top - margin.bottom,
selection.each(function (data) {
// Select the svg element, if it exists.
var svg = d3.select(this).selectAll("svg").data([data]);
// Otherwise, create the skeletal chart.
var svgEnter = svg.enter().append("svg");
var gEnter = svgEnter.append("g");
gEnter.append("g").attr("class", "x axis");
gEnter.append("g").attr("class", "y axis");
// Update the outer dimensions.
svg.merge(svgEnter).attr("width", width)
.attr("height", height);
// Update the inner dimensions.
var g = svg.merge(svgEnter).select("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
xScale.rangeRound([0, innerWidth])
.domain(data.map(xValue)); //xValue = function(d) { return d[0]; }
yScale.rangeRound([innerHeight, 0])
.domain([0, d3.max(data, yValue)]);
g.select(".x.axis")
.attr("transform", "translate(0," + innerHeight + ")")
.call(d3.axisBottom(xScale));
g.select(".y.axis")
.call(d3.axisLeft(yScale).ticks(10))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.text("Frequency");
var bars = g.selectAll(".bar")
.data(function (d) { return d; });
bars.enter().append("rect")
.attr("class", "bar")
.merge(bars)
.attr("x", X) // function X(d) {return xScale(xValue(d));}
.attr("y", Y)
.attr("width", xScale.bandwidth())
.attr("height", function(d) { return innerHeight - Y(d); })
.attr("fill", "rgb(0, 18, 65") // International Klein blue
.on("mouseover", function(d) {
//Get this bar's x/y values, then augment for the tooltip
var xPosition = parseFloat(d3.select(this).attr("x")) + xScale.bandwidth()/2; // + parseFloat(bars.node().getBBox().x);
var yPosition = parseFloat(d3.select(this).attr("y"))/2 + innerHeight/2;
//Update the tooltip position and value
d3.select("#tooltip")
.style("left", xPosition + "px")
.style("top", yPosition + "px")
.select("#value")
.text(yValue(d));
//Show the tooltip
d3.select("#tooltip").classed("hidden", false);
})
.on("mouseout", function() {
//Hide the tooltip
d3.select("#tooltip").classed("hidden", true);
})
.on("click", onClick);
bars.exit().remove();
});
}
I'd like to get the tooltips on top of my rectangles when I place the mouse over them.

Related

Tooltip for d3 v4 is not responding to the bar graph

I am hoping to make my bar changes color and display the year and value as the mouse hooves over it. I have the "mouseover, mouseout, and mousemove" but doesn't seem to work. Any help would be great. When I click on the bar, the key and value appear in the console. The values are nested. Thank you
Js:
//set the dimensions and margins of the graph
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 70
},
width = 600 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom,
padding = 15;
// Fomat timeStamp to year
var dateFormat = d3.timeFormat("%Y");
//append the svg object to the body of the page
var svg = d3.select("#graph").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 + ")");
// Get the data
d3.json("https://moto.data.socrata.com/resource/jfwn-iu5d.json?
$limit=500000",
function(data) {
// Objects
data.forEach(function(data) {
data.incident_description = data.incident_description;
data.incident_datetime = dateFormat(new Date(data.incident_datetime));
});
// Nest data by year of incident
var NestbyDate = d3.nest()
.key(function(d) {
return d.incident_datetime;
})
.key(function(d) {
return d.incident_description + " " + d.incident_datetime;
})
.rollup(function(leaves) {
return d3.sum(leaves, function(d) {
return (d.incident_description)
});
})
.entries(data);
var y_domain = d3.max(NestbyDate, function(d) {
return d.values.length;
});
console.log(NestbyDate) /
NestbyDate.sort((a, b) => a.key - b.key);
// set the ranges
var x = d3.scaleBand().domain(NestbyDate.map(d =>
d.key)).range([padding, width]);
var y = d3.scaleLinear().domain([0, y_domain]).range([height,
10]);
// Add the X Axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(6));
// Add the Y Axis
svg.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y));
// Text label for the x-axis
svg.append("text")
.attr("x", width / 2)
.attr("y", height + margin.top + 7)
.style("text-anchor", "center")
.text("Day Date Format")
.text("Year");
// Text Label for y-axis
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Number of crime incidents");
// Draw the bars
svg.selectAll("rect")
.data(NestbyDate)
.enter()
.append("rect")
.attr("class", "rect")
.attr("x", function(d) {
return x(d.key);
})
.attr("y", function(d) {
return y(d.values.length);
})
.attr("fill", "darkblue")
.attr("width", x.bandwidth())
.attr("height", function(d) {
return y(0) - y(d.values.length);
})
.on("mouseover", function() {
tooltip.style("display", null);
})
.on("mouseout", function() {
tooltip.style("display", "none");
})
.on("mousemove", function(d) {
console.log(d);
var xPosition = d3.mouse(this)[0] - 15;
var yPosition = d3.mouse(this)[1] - 55;
tooltip.attr("transform", "translate(" + xPosition +
"," + yPosition + ")");
tooltip.select("text").text(d.key +":" + y_domain);
});
// tooltips
var tooltip = svg.append("g")
.attr("class", "tooltip")
.style("display", "none");
tooltip.append("text")
.attr("dy", "1.2mm")
});
graph portion of html:
<div>
<h3> Number of crimes per year</h3>
<p>Below is a bar graph of the total number of crimes per year
from 2011 to 2018. The most crime incidents occur in 2017, with a total of
101,478 crimes.</p>
<div id="graph" class="responsive-plot"></div>
</div>
</div>

how to highlight a bar in stacked bar chart d3.js v4

I want to highlight a bar with April (value in x-axis) with a square box. but I am not getting a approach to do the same.
tried getting the co-ordinates of the respected bar, but unable to find a solution for the same
Unable to find the co-ordinates of the respective bar which I need to highlight.
what should be the approach for highlighting a bar with a square box in stacked bar chart d3.js v4
createStackedBarChart(130,300,10,60,20,45,"manager-line-graph-2");
function createStackedBarChart(height,width,top,right,bottom,left,id){
var margin = {top: top, right: right, bottom: bottom, left: left };
//console.log("margin"+margin);
var svg = d3.select("#"+id).append("svg"),
width = width - margin.left - margin.right,
height = height - margin.top - margin.bottom,
g = svg.attr("width", "100%")
.attr("height", height + margin.top + margin.bottom)
.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleBand()
.rangeRound([0, width])
.padding(0.2)
.align(5.0);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var z = d3.scaleOrdinal()
.range(["#0000FF", "#00FFFF", "#81F781", "#F3F781", "#FE2E2E"]);
var data = [
{"Months": "Feb","Installation": 5,"Product": 10,"Payment": 15,"Billing": 20,"Outage": 25},
{"Months": "March","Installation": 6,"Product": 8,"Payment": 9,"Billing": 15,"Outage": 18},
{"Months": "April","Installation": 9,"Product": 12,"Payment": 24,"Billing": 17,"Outage": 14},
{"Months": "May","Installation": 9,"Product": 12,"Payment": 14,"Billing": 17,"Outage": 14},
{"Months": "June","Installation": 9,"Product": 12,"Payment": 15,"Billing": 11,"Outage": 10}
];
// fix pre-processing
var keys = [];
for (key in data[0]){
if (key != "Months")
keys.push(key);
}
console.log("value of keys are " + keys);
data.forEach(function(d){
d.total = 0;
keys.forEach(function(k){
d.total += d[k];
})
});
//data.sort(function(a, b) {
//return b.total - a.total;
//});
x.domain(data.map(function(d) {
return d.Months;
}));
y.domain([0, d3.max(data, function(d) {
return d.total;
})]).nice();
z.domain(keys);
g.append("g")
.selectAll("g")
.data(d3.stack().keys(keys)(data))
.enter().append("g")
.attr("fill", function(d) {return z(d.key);})
.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("x", function(d) {
return x(d.data.Months);
})
.attr("y", function(d) {
return y(d[1]);
})
.attr("height", function(d) {
return y(d[0]) - y(d[1]);
})
.attr("width", x.bandwidth()-5);
g.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(5));
g.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y).ticks(5, "s"))
.append("text")
.attr("x", 2)
.attr("y", y(y.ticks(5).pop()))
.attr("dy", "0.32em")
.attr("fill", "#000")
.attr("font-weight", "bold")
.attr("text-anchor", "start")
var legend = g.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("text-anchor", "end")
.selectAll("g")
.data(keys.slice().reverse())
.enter().append("g")
.attr("transform", function(d, i) {
return "translate(0," + i * 20 + ")";
});
//legend.append("rect")
//.attr("x", width + 20)
//.attr("width", 10)
//.attr("height", 10)
//.attr("fill", z);
legend.append("circle")
.attr("r",5)
.attr("cx", width+30)
.attr("cy", 0)
.attr("fill",z);
legend.append("text")
.attr("x", width + 88)
.attr("y", 3.5)
.attr("dy", "0.12em")
.text(function(d) {
return`enter code here` d;
});
}
One solution I have used previously is to create an invisible layer of bars for the same data. Then use the .on("mouseover", function...); to make the bars for that data visible again by changing the styles or the fill opacity.
Here is a sample bl.ock with what I mean. It is a grouped bar chart with ordinal scale, but the same could be applied to your data with some tweaking.
https://bl.ocks.org/Coola85/b05339b65a7f9b082ca210d307a3e469
Update May 5, 2018: upon further reading I see your issue is different than the solution I suggested. This link might give you a good starting point of how to use an if statement to selectively highlight particular data.
so you could use the following style for your rect
.style ("fill", function(d) {
if (d.Months === "April") {return "red"} // <== Add these
else { return "black" }
})

Limitting x and y axis ticks in bar graph with d3.js

I am developing bar graph using d3.js integrating with angular js.I am new to d3.js. I dont know how we can limt the the no.of x and y axis ticks.
The working is given below
mainApp.directive('ngTest', function() {
return {
restrict: 'AE',
scope: {
data: '='
},
link: function (scope, element) {
var margin = {top: 20, right: 30, bottom: 30, left: 60},
width = 410 - margin.left - margin.right,
height = 230 - margin.top - margin.bottom;
var chart = d3.select(element[0])
.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 + ")");
var x = d3.scale.ordinal().rangeRoundBands([0, width], .1);
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<strong>Frequency:</strong> <span style='color:red'>" + d.value + "</span>";
});
chart.call(tip);
//Render graph based on 'data'
scope.render = function(data) {
var y = d3.scale.linear()
.range([height, 0])
.domain(d3.extent(data, function(d) { return d.value; }))
.nice();
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var yAxis = d3.svg.axis().scale(y).orient("left");
x.domain(data.map(function(d) { return d.name; }));
//Redraw the axes
chart.selectAll('g.axis').remove();
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (height) + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", function(d) {
return "rotate(-20)";
});
chart.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0-margin.left)
.attr("x",0-(height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Value");
chart.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", function(d) { return d.value < 0 ? "bar negative" : "bar positive"; })
.attr("x", function(d) { return x(d.name); })
.attr("y", height)
.attr("height", 0)
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
.transition().duration(2000)
.attr("y", function(d) {return y(Math.max(0, d.value)); })
.attr("height", function(d) {return Math.abs(y(d.value) - y(0)); })
// .attr("width", x.rangeBand());
.attr("width", Math.min.apply(null, [x.rangeBand()-2, 100]));
};
scope.$watch('data', function() {
scope.render(scope.data);
}, true);
}
};
});
The working example is given in following fiddle adderss
http://jsfiddle.net/HB7LU/9000/
Use ticks method of d3 axis. Since tick format of x axis is time, you might specify both a count and a tick format.
var xAxis = d3.svg.axis().scale(x).orient("bottom").ticks(d3.time.day, 2);
var yAxis = d3.svg.axis().scale(y).orient("left").ticks(5);
You can refer more about d3 svg axis from here and about time formats from here

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.js x-axis time scale

I'm trying to do a scatter plot in D3.js with date on the x-axis. The code below is based on the scatter plot example on the d3 site. I must be doing something wrong in the attr('cx'
area...
var data =[
{
"title":"SUNY GENESEO COLLEGE STADIUM PHASE 2",
"stage":"Biddate Set",
"naples_update_date":"2/9/2014",
"value":7500000,
"value_type":"Confirmed",
"ownership":"State",
"work_type":"Alteration",
"record_date":"1/21/2014",
"floors":null,
"floor_area":null,
"floor_units":"",
"land_area":null,
"land_units":"",
"structures":null,
"units":0,
"contract_type":"Open Bidding",
"address":"1 College Cir",
"city":"Geneseo",
"state":"NY",
"county":"Livingston",
"date":1390911781
},
{
"title":"KENTUCKY FAIR & EXPOSITION CENTER FREEDOM HALL-ROOFING",
"stage":"Post Bid Results Pending",
"naples_update_date":"2/10/2014",
"value":2662903,
"value_type":"Confirmed",
"ownership":"State",
"work_type":"Alteration",
"record_date":"10/29/2013",
"floors":2,
"floor_area":null,
"floor_units":"",
"land_area":null,
"land_units":"",
"structures":1,
"units":0,
"contract_type":"Open Bidding",
"address":"937 Phillips Ln",
"city":"Louisville",
"state":"KY",
"county":"Jefferson",
"date":1383132359
}
];
var format = d3.time.format("%d/%m/%Y");
var dateMin = format.parse("20/03/2001");
var dateMax = format.parse("7/02/2001");
var margin = {top: 20, right: 20, bottom: 30, left: 120},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var xValue = function(d) {
return format.parse(d.record_date);
}, // data -> value
xScale = d3.time.scale().domain([dateMin,dateMax]).range([0, width]), // value -> display
xMap = function(d) { return xScale(xValue(d));}, // data -> display
xAxis = d3.svg.axis().scale(xScale).orient("bottom");
var yValue = function(d) { return d.value;}, // data -> value
yScale = d3.scale.linear().range([height, 0]), // value -> display
yMap = function(d) { return yScale(yValue(d));}, // data -> display
yAxis = d3.svg.axis().scale(yScale).orient("left");
// setup fill color
var cValue = function(d) { return d.ownership;},
color = d3.scale.category10();
// add the graph canvas to the body of the webpage
var svg = d3.select("body").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 + ")");
// add the tooltip area to the webpage
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// don't want dots overlapping axis, so add in buffer to data domain
xScale.domain([d3.min(data, xValue)-1, d3.max(data, xValue)+1]);
yScale.domain([d3.min(data, yValue)-1, d3.max(data, yValue)+1]);
//x-axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", -6)
.style("text-anchor", "end")
.text("Date");
// y-axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Value");
// draw dots
svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", 3.5)
.attr("cx", xMap)
.attr("cy", yMap)
.style("fill", function(d) { return color(cValue(d));})
.on("mouseover", function(d) {
tooltip.transition()
.duration(200)
.style("opacity", 0.9);
tooltip.html(d.title + "<br/> (" + xValue(d) + ", " + yValue(d) + ")")
.style("left", (d3.event.pageX + 5) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
tooltip.transition()
.duration(500)
.style("opacity", 0);
})
.attr('data-title',function(e){
return e.title;
})
.attr('data-value',function(e){
return e.value;
})
.attr('data-date',function(e){
return e.record_date;
})
.attr('data-sqft',function(e){
return e.floor_area;
});
I've searched around and tried to follow the tips out there, making sure the dates for the .range() are objects of the same format at the dates inside attr(cx).
Demo: http://jsfiddle.net/EC6TL/
The problem was in line:
xScale.domain([d3.min(data, xValue) - 1, d3.max(data, xValue) + 1]);
You cannot add and subtract 1 from dates. :-)
Fix:
xScale.domain([d3.min(data, xValue), d3.max(data, xValue)]);

Resources