We have a heatmap for which I am trying to create a tooltip. The example we were using when building mine had d3.mouse which is deprecated in the D3 version we use with React. We saw different alternatives such as d3.pointer(), but we haven't managed to get it working.
I have attached below the small part of the code where the d3.mouse would have gone. Is there any way to position the tooltip with respect to the mouse without using the deprecated d3.mouse?
svg.selectAll('rect')
.data(data, d => {return (d.group+':'+d.variable)})
.enter()
.append("rect")
.attr("x", d => { return x(d.group) })
.attr("y", d => { return y(d.variable) })
.attr("width", x.bandwidth() )
.attr("height", y.bandwidth() )
.style("fill", function(d) { return myColor(d.value)} )
.on("mouseover", (d,i,e) => {
tooltip.html("The value is : " + i.value)
.style('opacity', .9)
.style("left", (d3.mouse(this)[0]+70) + "px")
.style("top", (d3.mouse(this)[1]) + "px")
})
.on("mouseout", () => {
tooltip.style('opacity', 0)
.style('left', '0px');
})
Related
I built a basic heatmap in html using d3, and the tooltips show properly. I've got the same code working in React, and the heatmap is working, and the mouseover, mousemove, and mouseleave functions are working (and the data is printing into the console accurately), but no matter what i try, I can't get the tooltip to actually show up. Any help would be appreciated
Below is my code-
import rd3 from 'react-d3-library';
import * as d3 from 'd3';
import React from 'react';
import data2 from '../data/hmap_current_year.csv';
const node = document.createElement('div');
// set the dimensions and margins of the graph
const margin = { top: 30, right: 60, bottom: 29, left: 60 },
width = 1400 - margin.left - margin.right,
height = 630 - margin.top - margin.bottom;
// append the svg object to the body of the page
const svg = d3
.select(node)
.append("svg")
//add mobile-friendly
.attr("viewBox", `0 0 ${width + margin.left + margin.right} ${height + margin.top + margin.bottom}`)
// .attr("width", width + margin.left + margin.right)
// .attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
d3.csv(data2).then(function (data) {
const var0 = data.map(function (d0) {
return {
month: d3.timeParse("%Y-%m-%d")(d0.time).getMonth(),
day: d3.timeParse("%Y-%m-%d")(d0.time).getDate(),
value: d0.tmed
}
})
const myGroups = Array.from({ length: 31 }, (_, i) => i + 1)
const myMonths = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
]
// Build X scales and axis:
const x = d3.scaleBand()
.range([0, width])
.domain(myGroups)
.padding(0.01);
svg.append("g")
.attr("transform", `translate(0, ${height})`)
.call(d3.axisBottom(x))
.call(g => g.select(".domain").remove());
svg.append("g")
.call(d3.axisTop(x))
.call(g => g.select(".domain").remove());
// Build X scales and axis:
const y = d3.scaleBand()
.range([0, height])
.domain(myMonths)
.padding(0.01);
svg.append("g")
.call(d3.axisLeft(y))
.call(g => g.select(".domain").remove());
svg.append("g")
.call(d3.axisRight(y))
.attr("transform", `translate(${width}, 0)`)
.call(g => g.select(".domain").remove());
// Build color scale
const myColor = d3.scaleQuantile()
.domain([0, 5, 20, 40, 60, 80, 95, 100])
.range([
"#08306B",
"#2171B5",
"#6BAED6",
"#FFFFFF",
"#FCBBA1",
"#FA6A4A",
"#CB181D"
]);
// create a tooltip
const tooltip = d3.select(node)
.append("div")
.style("opacity", 0)
.attr("class", "tooltip")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "2px")
.style("border-radius", "5px")
.style("padding", "5px")
.style("margin-bottom", '51px')
// Three function that change the tooltip when user hover / move / leave a cell
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)
//accessing values correctly
console.log(d.value, 'HELLO')
.style("left", (event.x) / 2 + "px")
.style("top", (event.y) / 2 + "px")
};
const mouseleave = function (event, d) {
tooltip
.style("opacity", 0)
d3.select(this)
.style("stroke", "none")
.style("opacity", 1)
}
svg.selectAll()
.data(var0)
.join("rect")
.attr("x", function (d) { return x(d.day) })
.attr("y", function (d) { return y(myMonths[d.month]) })
.attr("width", x.bandwidth())
.attr("height", y.bandwidth())
.style("fill", function (d) { return myColor(d.value) })
.style("stroke", "none")
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseleave", mouseleave)
});
const RD3Component = rd3.Component;
class HeatmapInteractive extends React.Component {
constructor(props) {
super(props);
this.state = {d3: ''}
}
componentDidMount() {
this.setState({d3: node});
}
render() {
return (
<div>
<RD3Component data={this.state.d3}/>
</div>
)
}
}
export default HeatmapInteractive;
here you have done perfectly for defining the tooltip initially
const tooltip = d3.select(node)
.append("div")
.style("opacity", 0)
.attr("class", "tooltip")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "2px")
.style("border-radius", "5px")
.style("padding", "5px")
.style("margin-bottom", '51px')
but while hovering you dont have to select any node by using d3.select(this) in mouseover and mouseleave state
you've written this
const mouseover = function (event, d) {
tooltip
.style("opacity", 1)
d3.select(this)
.style("stroke", "black")
.style("opacity", 1)
}
you can simply remove the d3.select(node) from chaining
Insted of doing like that you can simple change only opacity in mouseover
const mouseover = function(event, d) {
tooltip.style("opacity", 1)
}
if it's mouseleave just reverse
const mouseleave = function(event, d) {
tooltip.style("opacity", 0)
}
if it's a mousemove then you have to add html and postion properties for dynamic float of tooltip according to the mouse values like
Note one more thing dont use console in middle of chaining that may break the chaining, if you have tested with it then have to remove otherwise it won't work
const mousemove = function (event, d) {
tooltip
.html("The exact value of<br>this cell is: " + d.value)
.style("opacity", 1)
.style("left", (event.x) + 20 + "px")
.style("top", (event.y) + 20 + "px")
};
i hope this answer will get clarifies you
I am creating grouped bar chart using D3 V5 in react.I am able to display y axis but not ticks and text.but in case of x-axis it's completely invisible. i have added d3.min.js to index.html file, but nothing works. any help is appreciated
here I am attaching my code
DrawChart = (data) => {
var w = 450, h = 300, p = 100;
var x0 = d3.scaleBand().range([0, w]).padding(0.4);
var x1 = d3.scaleBand();
var y = d3.scaleLinear().range([h, 0]);
var color = d3.scaleOrdinal().range(["#a85db3", "#95f578"]);
const svg = d3.select("div#predicative")
.append("svg").attr("width", w).attr("height", h)
.attr("padding", p).style("margin-left", 30)
.style("margin-top", 20).style("margin-bottom", 10);
var ageNames = d3.keys(data[0]).filter(function (key) { return key !== "dept"; });
data.forEach(function (d) {
d.ages = ageNames.map(function (name) { return { name: name, value: +d[name] }; });
});
x0.domain(data.map(function (d) { return d.dept; }));
x1.domain(ageNames).rangeRound([0, x0.bandwidth()]);
y.domain([0, (d3.max(data, function (d) { return d3.max(d.ages, function (d) { return d.value; }); })) + 10]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + h + ")")
.call(d3.axisBottom(x0)
.tickSize(-w, 0, 0)
.tickFormat(''));
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(y))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Time");
svg:not(:root){
overflow: visible;
}
I just started with D3 this week.My project is written in Angular2 and TypeScript. I created a dynamic graph which getting values from user inputs. It is filled with gradient colors. I will like to outline or add border or stroke to my links whichever is a correct way of doing it. I was able to add "stroke" and "stroke-width" for my marker. I'm having problems with doing the same with my links.
Here is my marker code:
svg.selectAll('marker')
.data(links)
.enter().append('svg:marker')
.attr('id', function (d) {
return 'end-' + d.target;
})
.attr('orient', 'auto')
.attr('refX', .1)
.attr('refY', 0)
.attr("viewBox", "0 -5 10 10")
.attr("stroke", "black")
.style("stroke-width", .1)
.attr("fill", (d) => {
return color(results.nodes[d.target].value);
})
Here is my links code:
var link = svg.append("g")
.attr("class", "links")
.selectAll("path")
.data(links)
.enter().append('path')
.attr("stroke", "black")
.style("stroke-width", .1)
.attr("d", (d) => {
return this.makeLinks(d, results.nodes);
})
// .attr("fill", "black")
// .style("stroke-width", .1)
.style("stroke", (d, i) => {
return "url(" + window.location + "#linear-gradient-" + i + ")";
})
// .attr("fill", "black")
// .style("stroke-width", .1)
.style("stroke-width", (d) => {
return results.nodes[d.target].displaySize;
})
.attr("stroke", "black")
.style("stroke-width", .1)
.attr('marker-end', (d) => {
return this.getEndMarker(d, results.nodes);
})
;
SO community,
I'm making a D3 histogram as an Angular directive and I want it to be able to change/update accordingly as the data it reads in changes. In other words, I am using Angular to watch the changes in data and (hope to) redraw the histogram every time the data is changed.
This might mostly be a question about D3's updating and binding of data because the $watchCollection seems to work fine. Even though I have went through this tutorial on adding elements to a d3 chart, I still cannot apply it on my histogram. I think the way the elements in my histogram are nested is really confusing me...
Context: Ideally this histogram will read from an array to which data returned from several Ajax calls will get stored in. So every time a new set of data arrive, the histogram will grow itself another bar. That's why I would love to know how to update the chart as well as the x-axis properly.
Thank you! :)
The JS fiddle is here: http://jsfiddle.net/santina/wrtenjny/1/
Code for just the d3 part is here, largely taken from mbostock's sortable bar chart.
// Aesthetic settings
var margin = {top: 20, right: 50, bottom: 20, left: 50},
width = document.getElementById('performance').clientWidth - margin.left - margin.right ||
940 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
barColor = "steelblue",
axisColor = "whitesmoke",
axisLabelColor = "grey",
yText = "# QUERIES",
xText = "BEACON IDs";
// Inputs to the d3 graph
var data = scope[attrs.data];
// A formatter for counts.
var formatCount = d3.format(",.0f");
// Set the scale, separate the first bar by a bar width from y-axis
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1, 1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(formatCount);
// Initialize histogram
var svg = d3.select(".histogram-chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
function drawAxis(){
data.forEach(function(d) {
d.nqueries = +d.nqueries;
});
x.domain(data.map(function(d) { return d.name; }));
y.domain([0, d3.max(data, function(d) { return d.nqueries; })]);
// Draw x-axis
svg.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("y", 6)
.attr("dy", "-0.71em")
.attr("x", width )
.style("text-anchor", "end")
.style("fill", axisLabelColor)
.text(xText);
// 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")
.style("fill", axisLabelColor)
.text(yText);
// Change axis color
d3.selectAll("path").attr("fill", axisColor);
}
function updateAxis(){
console.log(data);
data.forEach(function(d) {
d.nqueries = +d.nqueries;
});
x.domain(data.map(function(d) { return d.name; }));
y.domain([0, d3.max(data, function(d) { return d.nqueries; })]);
svg.selectAll("g.y_axis").call(yAxis);
svg.selectAll("g.x_axis").call(xAxis);
}
function drawHistogram(){
drawAxis();
var bar = svg.selectAll(".bar")
.data(data)
.enter().append("g")
.attr("class", "barInfo");
bar.append("rect")
.attr("class", "bar")
.attr("x", function(d){ return x(d.name) })
.attr("width", x.rangeBand())
.attr("y", function(d){ return y(d.nqueries) })
.attr("height", function(d) { return height - y(d.nqueries); })
.attr("fill", barColor);
bar.append("text")
.attr("y", function(d){ return y(d.nqueries) })
.attr("x", function(d){ return x(d.name) })
.attr("dy", "-1px")
.attr("dx", x.rangeBand()/2 )
.attr("text-anchor", "middle")
.attr("class", "numberLabel")
.text(function(d) { return formatCount(d.nqueries); });
}
// Doesn't work :(
function updateHistogram(){
console.log("updating");
// Redefine scale and update axis
updateAxis();
// Select
var bar = svg.selectAll(".barInfo").data(data);
// Update - rect
var rects = bar.selectAll("rect")
.attr("class", "bar")
.attr("x", function(d){ return x(d.name) })
.attr("width", x.rangeBand());
// Update
var texts = bar.selectAll("text")
.attr("x", function(d){ return x(d.name) })
.attr("dx", x.rangeBand()/2 );
// Enter
bar.enter().append("g")
.attr("class", "bar").selectAll("rect").append("rect")
.attr("class", "bar")
.attr("x", function(d){ return x(d.name) })
.attr("width", x.rangeBand())
.attr("y", function(d){ return y(d.nqueries) })
.attr("height", function(d) { return height - y(d.nqueries); })
.attr("fill", barColor);
bar.enter().append("g")
.attr("class", "bar").selectAll("text").append("text")
.attr("y", function(d){ return y(d.nqueries) })
.attr("x", function(d){ return x(d.name) })
.attr("dy", "-1px")
.attr("dx", x.rangeBand()/2 )
.attr("text-anchor", "middle")
.attr("class", "numberLabel")
.text(function(d) { return formatCount(d.nqueries); });
}
drawHistogram();
First, you got the wrong class selector when you update your axis:
svg.selectAll("g.y-axis").call(yAxis); //<-- dash not underscore
svg.selectAll("g.x-axis").call(xAxis);
Second, you were close on your update, but we can clean it up a bit:
// select on what you originally binded data to
var bar = svg.selectAll(".barInfo").data(data);
// for data entering
var bEnter = bar.enter().append("g")
.attr("class", "barInfo");
// append a rect
bEnter.append("rect")
.attr("class", "bar");
// and the text elements
bEnter.append("text")
.attr("class","numberLabel");
// now we can update everybody together
bar.select("rect")
.attr("x", function(d){ return x(d.name) })
.attr("width", x.rangeBand())
.attr("y", function(d){ return y(d.nqueries) })
.attr("height", function(d) { return height - y(d.nqueries); })
.attr("fill", barColor);
bar.select("text")
.attr("y", function(d){ return y(d.nqueries) })
.attr("x", function(d){ return x(d.name) })
.attr("dy", "-1px")
.attr("dx", x.rangeBand()/2 )
.attr("text-anchor", "middle")
.attr("class", "numberLabel")
.text(function(d) { return formatCount(d.nqueries); });
Udpated example here.
EDITS
Opps, I'm not selecting correctly on my updates.
bar.selectAll("rect")
Should be:
bar.select("rect")
This fixes both the updates and the sorting...
Updated fiddle.
Also notice that I collapsed your code further. With your angular watch you really don't need a seperate draw and update function, one can do both.
I am using d3.js integrating with angularJs. But it is not working and got an error when calling tip.show and tip.hide.
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 + ")");
chart.call(tip);
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)
.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())
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
The complete working example is given in fiddle
http://jsfiddle.net/HB7LU/8984/
You are binding the mouse listeners on each transition. Bind the listeners to the selection
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', tipX.show) //Moved up
.on('mouseout', tipX.hide) //Moved up
.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());
Also update this code, since d is an object here.
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
//Changed d to d.value
return "<strong>Frequency:</strong> <span style='color:red'>" + d.value + "</span>";
});
Here is the updated fiddle
I think you can draw a div to do the trick.
I'm not sure if it is a good way.
var div = d3.select('body').append('div')
.attr('class', 'tooltip')
.style('opacity', 0);
fiddle