My quest for d3.js wisdom continues!
This time, I have added a guide line which is hovering in a vertical direction as a tool close to the pointer. The problem is that the line disturbs the mousemove functions since it adds an extra layer on top of the rest of the graph, which makes the the code run the mouseout event on sudden pointer movements. Is there a solution for this?
I have implemented the function in the following manner:
svg.on("mousemove", function(d) {
svg.select(".guideline").remove();
//svg.select(".valuelabel").remove();
svg.append("line")
.attr("class", "guideline")
.attr("x1", d3.mouse(this)[0]-3)
.attr("x2", d3.mouse(this)[0]-3)
.attr("y1", margin[0])
.attr("y2", height+margin[0])
.attr("opacity", originOpacity)
.attr("stroke", "#333");
});
And as an example of an event it is disturbing:
//Highlight each stack when hovering, and calculate y value for legend
stacks.on("mousemove", function(d) {
svg.select(".label").remove();
//Calculate the closest index when hovering
var perValue = width / data[0].data.length;
var index = Math.ceil((d3.mouse(this)[0]-margin[3]) / perValue - 0.5);
chart.selectAll(".valuelabel").each(function(data) {
if (data.name == d.name) {
d3.select(this).text(Math.round(data.data[index].y) + "%");
}
});
d3.select(this).attr("opacity", "1");
svg.selectAll("." + d3.select(this).attr("class")).attr("opacity", "1");
svg.append("text")
.attr("class", "label")
.attr("width", "100px")
.attr("height", "20px")
.attr("x", d3.mouse(this)[0] + 40)
.attr("y", d3.mouse(this)[1] - 5)
.text(d.group + ": " + d.name);
});
stacks.on("mouseout", function(d) {
groups.selectAll("." + d.name).text(d.name);
svg.select(".label").remove();
svg.selectAll("." + d3.select(this).attr("class")).attr("opacity", originOpacity);
});
Looks like you want pointer-events none on the guide line.
Related
I have a chart with zoom. I want to add a vertical line that will follow the mouse along the graph, and display the values at the point for all the lines of the graph.
Found an example d3.js v4, how do I have a line follow the mouse on hover, but also have a circle follow the path?
But when combining with my chart the following problems:
the line flickers or fades while moving the mouse
the zoom does not work with the mouse wheel (only works when the
mouse is in motion)
I understand that the problem is likely that when the cursor moves, it pulls a line, and is called mouseleave event for the zoom element.
I tried to move the line several pixels to the left or to the right, but this is not what I want, and it still does not work correctly.
I tried to create a line not in the mouseG element, as in the example, but on my own zoom element. The line is no longer displayed at all.
Here is my fiddle https://jsfiddle.net/zkdxrtuc/8/
Put the line group below the zoom rect.
Add second mouse event handlers to the zoom rect.
To show a line set opacity to 1, to hide set opacity to 0.
var mouseG = svg.append("g")
.attr("class", "mouse-over-effects");
svg.append("rect")
.attr("class", "zoom")
.attr("width", width)
.attr("height", height)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr('pointer-events', 'all')
.call(zoom);
function brushed() {
//...
}
function zoomed() {
//...
}
mouseG.append("path") // this is the black vertical line to follow mouse
.attr("class", "mouse-line")
.style("stroke", "black")
.style("stroke-width", "1px")
.style("opacity", "0");
// var lines = focus.selectAll('path');
// var mousePerLine = mouseG.selectAll('.mouse-per-line')
// .data(d3.range(lines.length))
// .enter()
// .append("g")
// .attr("class", "mouse-per-line")
// .attr('pointer-events', 'none');
// // the circle
// mousePerLine.append("circle")
// .attr("r", 7)
// .style("stroke", function(d) { return 'red'; })
// .style("fill", "none")
// .style("stroke-width", "1px")
// .style("opacity", "0");
function showLine(){
d3.select(".mouse-line").style("opacity", "1");
}
function hideLine(){
d3.select(".mouse-line").style("opacity", "0");
}
svg.select(".zoom")
.on('mouseenter.line', showLine)
.on('mouseleave.line', hideLine)
.on('mousemove.line', function() { // mouse moving over canvas
var mouse = d3.mouse(this);
//showLine();
// move the vertical line
d3.select(".mouse-line")
.attr("d", function() {
var d = "M" + (mouse[0] + margin.left) + "," + (height + margin.top);
d += " " + (mouse[0] + margin.left) + "," + margin.top;
return d;
});
// position the circle and text
});
I'm trying to create a line chart with a tooltip where the x-axis is a date.
I would like the line and tooltip to change after it is halfway (or similar) to the next tick on the x-axis.
Mainly I would like the behavior to be the same as this bl.ock: http://bl.ocks.org/wdickerson/64535aff478e8a9fd9d9facccfef8929
You can view the behavior I currently have on my bl.ock: https://bl.ocks.org/JulienAssouline/574a52ee2034bcdc1e56ed926f36dd52
It mostly works but the data only changes after it passes to the September month and it never reaches the October month.
I have tried to adapt my code to the bl.ock. The problem is the bl.ock displayed is using years and I am using a date format which seems to be my main problem.
Here is the main part of the code:
var tipBox = svg.append("rect")
.attr("width", width)
.attr("height", height)
.attr("opacity", 0)
.on("mousemove", drawTooltip)
.on("mouseout", removeTooltip)
function removeTooltip() {
if (tooltip) tooltip.style('display', 'none');
if (tooltipLine) tooltipLine.attr('stroke', 'none');
}
function drawTooltip(){
const line_hover = xScale.invert(d3.mouse(this)[0]);
// console.log(d3.mouse(this)[0])
console.log(xScale.invert(d3.mouse(this)[0]).getMonth())
console.log(Math.floor(xScale.invert(d3.mouse(this)[0])))
const date_hover = xScale.invert(d3.mouse(this)[0]).getMonth()
// yScale.invert(pos.y)
tooltipLine.attr("stroke", "grey")
.attr("x1", xScale(line_hover))
.attr("x2", xScale(line_hover))
.attr("y1", 0)
.attr("y2", height)
.attr("class", "line_hover")
.style('stroke-width', 1)
tooltip.html(date_hover)
.style("position", "absolute")
.style("background-color", "lightgrey")
.style('display', 'block')
.style('left', d3.event.pageX - 100+ "px")
.style('top', d3.event.pageY - 20+"px")
.selectAll()
.data(dataNest).enter()
.append('div')
.style('color', "black")
.html(function(e){ return e.key + ': ' + e.values.find(function(h){ return (h.Date.getMonth() + 0.5) == (date_hover + 0.5) }).randNumCol})
}
You can again view all of the code on my bl.ock: https://bl.ocks.org/JulienAssouline/574a52ee2034bcdc1e56ed926f36dd52
GetMonth will always give the month. Get the date and display based on the date. Not ideal, but works.
Example here
I am trying to understand the logic behind this chart : http://bl.ocks.org/d3noob/6eb506b129f585ce5c8a made by D3noob.
I have tried to add a focus line from the focus circle to right before the margin.
I found a partial solution which is :
// append the x line_up
focus.append("line")
.attr("class", "x_track_line_up")
.style("stroke", "black")
.style("stroke", "3,3")
.style("opacity", 0.9)
.attr("y1", 0)
.attr("y2", height);
//call the x line
focus.select(".x_track_line_up")
.attr("transform",
"translate(" + x(d.date) + "," +
y(d.close) + ")")
.attr("y2", -height - y(d.close));
The only problem is that this added line is also drawn in the top margin.
I believe that the issue here is the last line of this portion of code but I am not sure how to fix it.
You don't need the translate. Just do:
focus.select(".x_track_line_up")
.attr("x1", x(d.date))
.attr("x2", x(d.date))
.attr("y2", y(d.close));
Here is the updated bl.ocks: http://bl.ocks.org/anonymous/dd1bb9adda42e4bc9768e9a35432197a
How can I add a d3-tip / mouseover event AFTER a transition on a histogram / bar chart?
I create a bar chart / plot:
canvas.call(tip);
var sampleBars = canvas.selectAll(".sampleBar")
.data(data)
.enter().insert("rect", ".axis")
.attr("class", "sampleBar")
.attr("x", function(d) { return x(d.x) + 1; })
.attr("y", function(d) { return y(d.y); })
.attr("width", x(data[0].dx + data[0].x) - x(data[0].x) - 1)
.attr("height", 0)
.transition()
.duration(2500)
.delay(500)
.attr("height", function(d) { return height - y(d.y); });
I want to add:
sampleBars
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
And this is the tip:
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<span style='color:white'>" + d3.round((d.y * 100))
+ "%" + "</span>" + " infected"; })
So that the mouseover event occurs after the transition is complete. But I get the error:
Uncaught TypeError: undefined is not a function
The error is for the line:
.on('mouseover', tip.show)
I think there is a simple flaw in my logic. I seem to be confused about how these events or attributes interact with each other.
Again: I want to 'activate' the mouseover tip AFTER the transition is complete so that after the transition does its thing the tip will appear if the user puts their mouse over each bar. I have no problem creating the mouseover event and having it work on user mouseover to display the data I want, but I am having no luck with making this work with a transition of those bars.
Instead of adding/removing events, one approach is to simply set/unset the pointer-events attribute so that the events don't fire when you want them suppressed.
var myselection = d3.select('body')
.append('svg').attr({height: 200, width: 200})
.append('circle')
.attr( {cx: 100, cy: 100, r: 0})
.attr('pointer-events', 'none')
.on('mouseover', function() { console.log('mouseover'); })
.on('mouseout', function() { console.log('mouseout'); })
myselection
.transition().duration(4000).attr('r', 100)
.transition().attr('pointer-events', 'auto')
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
If you have the console window open you'll notice that mouse over/out logs nothing until the circle stops growing.
I have a set of graphs that can be dynamically added and removed from the page. Each one has an invisible 'rect' element appended to the base svg hosting each graph, and on that rect element I can append mouseover elements. However, these are all limited to the single svg/rect that the mouse is hovering over; I'd like to extend them to cover all visible graphs. Here's the main code affecting that:
var focus = svg.append('g') // An invisible layer over the top. Problem is, it only overlays on one graph at a time...
.style('display', 'none');
svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all")
.on("mouseover", function() { focus.style("display", null); })
.on("mouseout", function() { focus.style("display", "none"); })
.on("mousemove", mousemove);
// append the x line
focus.append("line")
.attr("class", "x")
.style("stroke", "blue")
.style("stroke-dasharray", "3,3")
.style("opacity", 0.5)
.attr("y1", 0)
.attr("y2", height);
function mousemove() {
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(dataset, x0, 1),
d0 = dataset[i - 1],
d1 = dataset[i],
d = x0 - d0.time > d1.time - x0 ? d1 : d0;
focus.select(".x")
.attr("transform", function() {
return "translate(" + x(d.time) + "," + rightDomain(symbol, d) + ")";
})
.attr("y2", height - y(d[symbol]));
}
All of this is inside a forEach() loop, where it loops over an array containing the names of the graphs to be shown, so multiple graphs (albeit in their separate svgs) show up.
I also have a plunker here: http://plnkr.co/edit/s4K84f5HGRjHFWMwiuIA?p=preview. I'm not sure why it's failing to work since I've copied and pasted my code, which I know works elsewhere.
Edit: I've managed to attach another svg element to the body but for some reason I can't get it to overlay on top of the existing svgs (the graphs). Here's my code (where I've tried several ways of adjusting the position):
var overlay = d3.select('html')
.append('div')
.attr('height', function() { return (symbols.length - 1) * 135 + 130; })
.attr('width', 1000)
.attr('z-index', 2)
//.attr('transform', 'translate(' + margin.left + ',' + extraBuffer/2 + ')');
//.attr('x', margin.left)
//.attr('y', extraBuffer/2);
.attr('position', 'absolute')
.attr('top', '20')
.attr('right', '40');
Looking at this in chrome devtools I always see it below existing graphs, even if I explicitly set its x/y values.