Related
I am trying to have a tooltip for each square on my heatmap visualization. However, I am not able to position it next to the cursor correctly. This is what happens:
My code is this:
const margin = {
top: 20,
right: 20,
bottom: 30,
left: 150,
};
const width = 960 - margin.left - margin.right;
const height = 2500 - margin.top - margin.bottom;
let chart = d3
.select("#chart2")
.append("div")
// Set id to chartArea
.attr("id", "chartArea")
.classed("chart", true)
.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 + ")");
// Make sure to create a separate SVG for the XAxis
let axis = d3
.select("#chart2")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", 40)
.append("g")
.attr("transform", "translate(" + margin.left + ", 0)");
// Load the data
d3.csv("https://raw.githubusercontent.com/thedivtagguy/files/main/land_use.csv").then(function(data) {
// console.log(data);
const years = Array.from(new Set(data.map((d) => d.year)));
const countries = Array.from(new Set(data.map((d) => d.entity)));
countries.reverse();
const x = d3.scaleBand().range([0, width]).domain(years).padding(0.01);
// create a tooltip
var tooltip = d3
// Select all boxes in the chart
.select("#chart2")
.append("div")
.classed("tooltip", true)
.style("opacity", 0)
.attr("class", "tooltip")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "2px")
.style("border-radius", "5px")
.style("padding", "5px");
const mouseover = function(event, d) {
tooltip.style("opacity", 1);
d3.select(this).style("stroke", "black").style("opacity", 1);
};
const mousemove = function(event, d) {
tooltip
.html("The exact value of<br>this cell is: " + d.value)
// Position the tooltip next to the cursor
.style("left", event.x + "px")
.style("top", event.y / 2 + "px");
};
const mouseleave = function(event, d) {
tooltip.style("opacity", 0);
d3.select(this).style("stroke", "none").style("opacity", 0.8);
};
const y = d3.scaleBand().range([height, 0]).domain(countries).padding(0.01);
// Only 10 years
axis
.call(d3.axisBottom(x).tickValues(years.filter((d, i) => !(i % 10))))
.selectAll("text")
.style("color", "black")
.style("position", "fixed")
.attr("transform", "translate(-10,10)rotate(-45)")
.style("text-anchor", "end");
chart
.append("g")
.call(d3.axisLeft(y))
.selectAll("text")
.style("color", "black")
.attr("transform", "translate(-10,0)")
.style("text-anchor", "end");
const colorScale = d3
.scaleSequential()
.domain([0, d3.max(data, (d) => d.change)])
.interpolator(d3.interpolateInferno);
// add the squares
chart
.selectAll()
.data(data, function(d) {
return d.year + ":" + d.entity;
})
.join("rect")
// Add id-s
.attr("id", function(d) {
return d.year + ":" + d.entity;
})
.attr("x", function(d) {
return x(d.year);
})
.attr("y", function(d) {
return y(d.entity);
})
.attr("width", x.bandwidth())
.attr("height", y.bandwidth())
.style("fill", function(d) {
return colorScale(d.change);
console.log(d.change);
})
.style("stroke-width", 4)
.style("stroke", "none")
.style("opacity", 0.8)
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseleave", mouseleave);
});
#chart2 .chart {
width: 960px;
max-height: 900px;
overflow-y: scroll;
overflow-x: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<div id="chart2" class="mx-auto"></div>
I think I have some idea of why this is happening. Since I am appending the tooltip iv to chart2 div like this, all positioning is happening relative to that div, which means it'll inevitably be below the entire chart and not on top of it:
// create a tooltip
var tooltip = d3
// Select all boxes in the chart
.select("#chart2")
.append("div")
.classed("tooltip", true)
.style("opacity", 0)
.attr("class", "tooltip")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "2px")
.style("border-radius", "5px")
.style("padding", "5px");
const mousemove = function(event, d) {
tooltip
.html("The exact value of<br>this cell is: " + d.value)
// Position the tooltip next to the cursor
.style("left", event.x + "px")
.style("top", event.y / 2 + "px");
};
But how else would I do this? I have tried trying to select by the hovered-on rect element but that hasn't been working. I am trying to do something like this.
How can I fix this?
A live version can be found here
Just add in the mousemove function .style("position","absolute"); and change top style to .style("top", event.y + "px")
const margin = {
top: 20,
right: 20,
bottom: 30,
left: 150,
};
const width = 960 - margin.left - margin.right;
const height = 2500 - margin.top - margin.bottom;
let chart = d3
.select("#chart2")
.append("div")
// Set id to chartArea
.attr("id", "chartArea")
.classed("chart", true)
.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 + ")");
// Make sure to create a separate SVG for the XAxis
let axis = d3
.select("#chart2")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", 40)
.append("g")
.attr("transform", "translate(" + margin.left + ", 0)");
// Load the data
d3.csv("https://raw.githubusercontent.com/thedivtagguy/files/main/land_use.csv").then(function(data) {
// console.log(data);
const years = Array.from(new Set(data.map((d) => d.year)));
const countries = Array.from(new Set(data.map((d) => d.entity)));
countries.reverse();
const x = d3.scaleBand().range([0, width]).domain(years).padding(0.01);
// create a tooltip
var tooltip = d3
// Select all boxes in the chart
.select("#chart2")
.append("div")
.classed("tooltip", true)
.style("opacity", 0)
.attr("class", "tooltip")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "2px")
.style("border-radius", "5px")
.style("padding", "5px");
const mouseover = function(event, d) {
tooltip.style("opacity", 1);
d3.select(this).style("stroke", "black").style("opacity", 1);
};
const mousemove = function(event, d) {
tooltip
.html("The exact value of<br>this cell is: " + d.value)
// Position the tooltip next to the cursor
.style("left", event.x + "px")
.style("top", event.y + "px")
.style("position","absolute");
};
const mouseleave = function(event, d) {
tooltip.style("opacity", 0);
d3.select(this).style("stroke", "none").style("opacity", 0.8);
};
const y = d3.scaleBand().range([height, 0]).domain(countries).padding(0.01);
// Only 10 years
axis
.call(d3.axisBottom(x).tickValues(years.filter((d, i) => !(i % 10))))
.selectAll("text")
.style("color", "black")
.style("position", "fixed")
.attr("transform", "translate(-10,10)rotate(-45)")
.style("text-anchor", "end");
chart
.append("g")
.call(d3.axisLeft(y))
.selectAll("text")
.style("color", "black")
.attr("transform", "translate(-10,0)")
.style("text-anchor", "end");
const colorScale = d3
.scaleSequential()
.domain([0, d3.max(data, (d) => d.change)])
.interpolator(d3.interpolateInferno);
// add the squares
chart
.selectAll()
.data(data, function(d) {
return d.year + ":" + d.entity;
})
.join("rect")
// Add id-s
.attr("id", function(d) {
return d.year + ":" + d.entity;
})
.attr("x", function(d) {
return x(d.year);
})
.attr("y", function(d) {
return y(d.entity);
})
.attr("width", x.bandwidth())
.attr("height", y.bandwidth())
.style("fill", function(d) {
return colorScale(d.change);
console.log(d.change);
})
.style("stroke-width", 4)
.style("stroke", "none")
.style("opacity", 0.8)
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseleave", mouseleave);
});
#chart2 .chart {
width: 960px;
max-height: 900px;
overflow-y: scroll;
overflow-x: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<div id="chart2" class="mx-auto"></div>
I try to build a tooltip like it is explained here:
D3 Tooltip Example
But I want to have a div as a tooltip.
Now I have the problem to position the div to the chat line.
My code:
var div = d3.select("#chart").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var path = svg.append("path") // Add the line path.
.data(data)
.attr("class", "line")
.attr("d", line(data));
var focus = svg.append("g")
.attr("class", "focus")
.style("display", "none");
focus.append("text")
.attr("x", 9)
.attr("dy", ".35em");
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function() {
focus.style("display", null);
})
.on("mouseout", function(d) {
div.transition()
.duration(50)
.style("opacity", 1e-6);
})
.on("mousemove", mousemove);
function mousemove() {
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.date > d1.date - x0 ? d1 : d0;
//move focus around
focus.attr("transform", "translate(" + x(d.date) + "," + y(d.equity) + ")");
div.transition()
.duration(50)
.style("opacity", .9);
div.html("<strong><table><tr><th>Datum: </th><th>" + formatTime(d.date) + "</th></tr><tr><th>Equity:</th><th>" + Euro(d.equity) + "</th></tr></table></strong>")
// .style("left", (d3.event.pageX) + "px")
// .style("top", (d3.event.pageY - 1100) + "px");
;
}
The full example on Fiddle
Is it possible to position the div relatively to the svg line, where the mouse is?
Changing the div position using left and top is best for HTML tooltips: https://jsfiddle.net/da3nx51L/
div.transition()
.duration(50)
.style('left', d3.event.clientX + "px")
.style('top', d3.event.clientY + "px")
.style('display', 'inline-block')
.style("opacity", .9);
uncomment the last two lines, and remove the -1100 from the top
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY) + "px");
I am trying to make a visualization with D3.js. In my visualization, I have a stack of color blocks. I noticed that there are thin grey edges between my color blocks, which I don't want to have. I am not sure where the problem comes from. Any help is appreciated.
My code:
var svg = d3.select(container).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 background = svg.append("rect")
//.style("stroke-width", "2px")
.attr("width", width)
.attr("height", height);
var x = d3.scale.ordinal()
.domain(d3.range(numcols))
.rangeBands([0, width]);
var y = d3.scale.ordinal()
.domain(d3.range(numrows))
.rangeBands([0, height]);
var colorMap = d3.scale.linear()
.domain([minValue,maxValue])
.range([startColor, endColor]);
var row = svg.selectAll(".row")
.data(dataValues)
.enter().append("g")
.attr("class", "row")
.attr("transform", function(d, i) { return "translate(0," + y(i) + ")"; });
var cell = row.selectAll(".cell")
.data(function(d) { return d; })
.enter().append("g")
.attr("class", "cell")
.attr("transform", function(d, i) { return "translate(" + x(i) + ", 0)"; });
cell.append('rect')
.attr("width", x.rangeBand())
.attr("height", y.rangeBand());
row.selectAll(".cell")
.data(function(d, i) { return dataValues[i]; })
.style("fill", colorMap);
if(highlightCellOnHover){
cell
.on("mouseover", function(d) {
d3.select(this).style("fill", highlightCellColor);
})
.on("mouseout", function() {
d3.select(this).style("fill", colorMap);
});
}
I want to create two 1D frequency distribution charts (see this example) and put them one next to other (in a single row).
Here is my fiddle.
The problem is that only the first 1D chart appears, while the second one is invisible.
Should I create a separate svg element for each bar or can I add different bars to the same svg?
var bar1 = svg1.selectAll(".bar")
.data(data1)
.enter().append("g")
.attr("class", "bar1")
.attr("transform", function(d) { return "translate(" + x(d.x) + ",0)"; });
var bar2 = svg2.selectAll(".bar")
.data(data2)
.enter().append("g")
.attr("class", "bar1")
.attr("transform", function(d) { return "translate(" + x(d.x) + ",0)"; });
This line:
.attr("transform", "translate(" + 100+margin.left + "," + margin.top + ")");
is causing you troubles. JavaScript parses this left to right and it treates the whole thing as string concatenation, so it evaluates to:
<g transform="translate(10030,10)">
What you probably meant is:
.attr("transform", "translate(" + (100+margin.left) + "," + margin.top + ")");
That will evaluate (100+margin.left) as a numerical expression before the string concatenation.
Cleaned up example:
var values1 = [48,119,92,53,58,84,56,54,141,176,23,78,55,32,53,71,45,85,41,74,80]
var values2 = [18,19,12,13,18,14,16,14,14,16,23,18,15,12,13,11,15,15,11,14,8]
// A formatter for counts.
var formatCount = d3.format(",.0f");
var margin = {top: 10, right: 30, bottom: 30, left: 30},
width = 300 - margin.left - margin.right,
height = 100 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([0, 150])
.range([0, width]);
// Generate a histogram using sixty uniformly-spaced bins.
var data1 = d3.layout.histogram()
.bins(x.ticks(60))
(values1);
var data2 = d3.layout.histogram()
.bins(x.ticks(60))
(values2);
var opacity = d3.scale.linear()
.domain([0, d3.max(data1, function(d) { return d.y; })])
.range(["white", "blue"]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
<!-- The 1st 1D histogram -->
var svg1 = d3.select("body").append("svg").style('float','left')
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var bar1 = svg1.selectAll(".bar")
.data(data1)
.enter().append("g")
.attr("class", "bar1")
.attr("transform", function(d) { return "translate(" + x(d.x) + ",0)"; });
bar1.append("rect")
.attr("x", 0)
.attr("width", x(data1[0].dx) )
.attr("height", 50)
.style("fill", function(d){ return opacity(d.y)})
svg1.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
<!-- The 2nd 1D histogram -->
var svg2 = d3.select("body").append('div').append("svg").style('float','left')
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var bar2 = svg2.selectAll(".bar")
.data(data2)
.enter().append("g")
.attr("class", "bar2")
.attr("transform", function(d) { return "translate(" + x(d.x) + ",0)"; });
bar2.append("rect")
.attr("x", 0)
.attr("width", x(data2[0].dx) )
.attr("height", 50)
.style("fill", function(d){ return opacity(d.y)})
svg2.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
body {
font: 10px sans-serif;
}
.bar rect {
fill: steelblue;
shape-rendering: crispEdges;
}
.bar text {
fill: #fff;
}
.axis path, .axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
<body></body>
<script src="https://d3js.org/d3.v3.min.js"></script>
hi this maybe very easy for you guys i just need to draw a vertical line that represents the current date in my d3 gantt chart. i already figure out the values for my y i just having trouble in the value in my X because im using a time.scale in my x-axis. ill paste the codes that draws my gantt chart and the part where i draw my vertical line is located at the very bottom
initTimeDomain(tasks);
initAxis();
var numFormat = d3.format(",.0f");
var dateFormat = d3.time.format("%Y-%b-%d");
var parseDate = d3.time.format("%Y-%b-%d").parse;
var svg = d3.select("#gantt_chart")
.append("svg")
.attr("class", "chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("class", "gantt-chart")
.attr("width", width + margin.left + margin.right)
.attr("height", (height + margin.top + margin.bottom) / tasks[tasks.length - 1].endDate)
.attr("transform", "translate(" + margin.left + ", " + margin.top + ")");
//this is the x-axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0, " + (height - margin.top - margin.bottom) + ")")
.transition()
.call(xAxis)
.selectAll("text")
.style("text-anchor","end")
//.attr("dx", 35)
//.attr("dy", 5);
.attr("dx", "-.8em")
.attr("dy", -10)
.attr("transform", function(d){return "rotate(-90)"});
//this is the y-axis
svg.append("g").attr("class", "y axis").transition().call(yAxis);
//this is the actual gantt
svg.selectAll(".chart")
.data(tasks, keyFunction).enter()
.append("rect")
.attr("rx", 0)
.attr("ry", 0)
.attr("class", function(d){
if(d.status > 70)
{
return "bar-failed";
}
else if (d.status >= 51 && d.status <= 70){
return "bar-killed";
}
else{
return "bar-running";
}
})
.attr("y", 0)
.attr("transform", rectTransform)
.attr("height", function(d) { return y.rangeBand(); })
.attr("width", function(d) {
return (x(d.endDate) - x(d.startDate));
})
.on("mouseover", function(d){
div.transition()
.duration(200)
.style("opacity", .9);
div.html("HandlerID: " + d.taskName + "<br>" + "startDate: " + dateFormat(d.startDate) + "<br/>" +
"endDate: " + dateFormat(d.endDate) + "<br/>" + "% Insertions: " + d3.round(d.status,2) + "%" + "<br/>" +
"Insertions: " + numFormat(d.insertions) )
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout",function(d){
div.transition()
.duration(500)
.style("opacity", 0);
});
var today = new Date();
var dd = today.getDate();
var mm = today.getMonth()+1; //January is 0!
var yyyy = today.getFullYear();
if(dd<10) {
dd='0'+dd
}
if(mm<10) {
mm='0'+mm
}
today = yyyy+'-'+mm+'-'+dd;
today = parseDate(today);
//document.write(today);
svg.append("line")
.attr("x1", today)
.attr("y1", 0)
.attr("x2", today)
.attr("y2", height - margin.top - margin.bottom)
.style("stroke-width", 2)
.style("stroke", "red")
.style("fill", "none");
Just get the date today. No need to get its date, month, and year because it will return a string.
All you have to do is put the date in your x variable to jive with its domain
var today = new Date();
var dd = today.getDate(); //<<===== no need
var mm = today.getMonth()+1; //January is 0! //<<===== no need
var yyyy = today.getFullYear(); //<<===== no need
svg.append("line")
.attr("x1", x(today)) //<<== change your code here
.attr("y1", 0)
.attr("x2", x(today)) //<<== and here
.attr("y2", height - margin.top - margin.bottom)
.style("stroke-width", 2)
.style("stroke", "red")
.style("fill", "none");