Difference between d3.event.x and d3.mouse(this) [duplicate] - d3.js

When an event is in play, d3.event.x gives the position of the x coordinate of the mouse click, but relative to the entire HTML doc. I tried using jQuery's $('svg').position() to get the actual position of the svg but this return blatantly fallacious values.
Is there some easy way to find the position of an svg relative to the page that I am overlooking? I am using Chrome, by the way, in case the jQuery problem is an obscure browser error.
EDIT: I checked this in firefox and $('svg').position() returns the correct coordinates. ?!?

Instead of using d3.event, which is the browser's native event, use d3.mouse to get the coordinates relative to some container. For example:
var svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 500);
var rect = svg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.on("mousemove", mousemove);
function mousemove(d, i) {
console.log(d3.mouse(this));
}

Related

Append new shapes to existing shapes within SVG document D3

In D3 I'm used to creating visuals from a blank div. However I'm trying to get my head around the following.
I have an svg document (https://raw.githubusercontent.com/gangrel11/samplefiles/main/d3%20task1.svg) which is just 3 rectangles.
What I'm trying to do is append 3 new shapes (circles) to each one of the existing rectangles so that they appear in the centre of each one.
This is where I got to:
const svgUrl = "https://raw.githubusercontent.com/gangrel11/samplefiles/main/d3%20task1.svg"
d3.xml(svgUrl).then(render);
function render(svg) {
// add svg
d3.select("body").node().append(svg.documentElement)
var svg = d3.select("body").append("svg");
svg.selectAll("rect")
.append("rect")
.attr("width", 20)
.attr("height", 20)
.attr("x", 20)
.attr("y", 10)
.attr("fill", "red")
}
Here is my jsfiddle
You have a few problems:
You are adding another svg element instead of using the existing one.
My suggestion is changing:
d3.select("body").append("svg");
to
d3.select("body").select("svg #layer1");
notice that I also targeted the g element #layer1 that get transformed.
you try to append rect element to rect element but svg doesn't know how to draw rect inside rect - this syntax is invalid.
instead, you can target each element and use his position using the .each method and append them after all the existing rects.
code:
function render(svg) {
// add svg
d3.select("body").node().append(svg.documentElement)
var svg = d3.select("body").select("svg #layer1");
svg.selectAll("rect")
.each(function (rect){
const {x, y} = this.getBoundingClientRect();
svg.append("rect")
.attr("width", 20)
.attr("height", 20)
.attr("x", this.getAttribute('x'))
.attr("y", this.getAttribute('y'))
.attr("fill", "red");
});
}

Capturing mouseover events on two overlapping elements

So I have a d3 chart with a rect overlay to hold crosshair elements on mouseover events. Under the overlay I have other rects displaying data that have mouseover event handlers also, but The overlay is blocking mouseover events form triggeron the children rects below.
let chartWindow = svg
.append("g");
/* this holds axis groups, and cadlestick group*/
let candleStickWindow = chartWindow.append("g")
//this event never fires
.on('mousemove', ()=>console.log('mouse move'));
let candlesCrosshairWindow = chartWindow
.append("rect")
.attr("class", "overlay")
.attr("height", innerHeight)
.attr("width", innerWidth)
.on("mouseover", function() {
crosshair.style("display", null);
})
.on("mouseout", function() {
crosshair.style("display", "none");
removeAllAxisAnnotations();
})
.on("mousemove", mousemove);
The CrosshairWindow has CSS property pointer-events: all. If I remove that, I get my events to fire on the candleStickWindow but not the CrosshairWindow. How can I get mouse events onto both of the elements??
Thanks for any help!
Update
I changed the crosshair rect element to be on the bottom and it kinda works, the candlestick bars mouseover event works but it blocks the crosshair from working.
One solution that comes to mind might use event bubbling which, however, only works if the events can bubble up along the same DOM sub-tree. If, in your DOM structure, the crosshairs rectangle and the other elements do not share a common ancestor to which you could reasonably attach such listener, you need to either rethink your DOM or resort to some other solution. For this answer I will lay out an alternative approach which is more generally applicable.
You can position your full-size rect at the very bottom of your SVG and have its pointer-events set to all. That way you can easily attach a mousemove handler to it to control your crosshairs' movements spanning the entire viewport. As you have noticed yourself, however, this does not work if there are elements above which have listeners for that particular event type attached to them. Because in that case, once the event has reached its target, there is no way propagating it further to the underlying rectangle for handling the crosshairs component. The work-around is easy, though, since you can clone the event and dispatch that new one directly to your rectangle.
Cloning the event is done by using the MouseEvent() constructor passing in the event's details from the d3.event reference:
new MouseEvent(d3.event.type, d3.event)
You can then dispatch the newly created event object to your crosshairs rect element by using the .dispatchEvent() method of the EventTarget interface which is implemented by SVGRectElement:
.dispatchEvent(new MouseEvent(d3.event.type, d3.event));
For lack of a full example in your question I set up a working demo myself illustrating the approach. You can drag around the blue circle which is a boiled down version of your crosshairs component. Notice, how the circle can be seamlessly moved around even when under the orange rectangles. To demonstrate the event handlers attached to those small rectangles they will transition to green and back to orange when entering or leaving them with the mouse pointer.
const width = 500;
const height = 500;
const radius = 10;
const orange = d3.hsl("orange");
const steelblue = d3.hsl("steelblue");
const limegreen = d3.hsl("limegreen");
const svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
const target = svg.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height)
.attr("fill", "none")
.attr("pointer-events", "all")
.on("mousemove", () => {
circle.attr("cx", d3.event.clientX - radius);
circle.attr("cy", d3.event.clientY - radius);
});
const circle = svg.append("circle")
.attr("r", radius)
.attr("fill", steelblue)
.attr("pointer-events", "none");
const rect = svg.selectAll(null)
.data(d3.range(3).map(d => [Math.random() * width, Math.random() * height]))
.enter().append("rect")
.attr("x", d => d[0])
.attr("y", d => d[1])
.attr("width", 50)
.attr("height", 50)
.attr("fill", orange)
.attr("opacity", 0.5)
.on("mouseover", function() {
d3.select(this).transition().attr("fill", limegreen);
})
.on("mousemove", function() {
target.node().dispatchEvent(new MouseEvent(d3.event.type, d3.event));
})
.on("mouseout", function() {
d3.select(this).transition().attr("fill", orange);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

round SVG images on Joomla site do not get displayed in Firefox

I try to display round images in a svg. The purpose of it is to display round profile pictures within a graph (like in Gmail). The square images of the people are stored in a spritesheet (profiles.png) and I use d3.js for dynamically generating the svg.
This is the part of the code for the round images
var imgurl = '../../../images/profiles.png';
var svggraph = d3.select("#graph")
.append("svg:svg")
.attr("width", 300)
.attr("height", 300);
var imgRadius = 40;
var profilePos = 1;
var numProfiles = 3;
svggraph.append("defs")
.append("pattern")
.attr("id", "patternId")
.attr("height", 1)
.attr("width", 1)
.attr("x", "0")
.attr("y", "0").append("svg:image")
.attr("xlink:href", imgurl)
.attr("x", -profilePos*2*imgRadius)
.attr("y", 0)
.attr("width", numProfiles*2*imgRadius)
.attr("height", 2*imgRadius);
svggraph.append("circle")
.attr("r", imgRadius)
.attr("cx", imgRadius)
.attr("cy", imgRadius)
.attr("fill", "url(#patternId)");
// to test the imgurl
svggraph.append("svg:image")
.attr("id", "testImage")
.attr("xlink:href", imgurl)
.attr("x", 0)
.attr("y", 100)
.attr("width", 150)
.attr("height", 50);
It worked when I tested it in a plain test HTML, it also worked when I integrated it in my Joomla component in Firefox and Chrome. But if there is a GET query in the url (basically as soon as there is a ? even with no parameters), the round image is not displayed anymore. The svg tags in the "live source code" in the developer tools looks the same if there is a "?" in the url or not. It is always displays correct (with or without query url) in Chrome.
So in summary:
in Chrome -> always works
in FF "plain" HTML with and without ? in url -> works
in FF within Joomla without ? in url -> works
in FF within Joomla with ? in url -> round image not displayed
The test image (#testImage) is always displayed, so the url to the image is apperently correct.
I also tried the same but with clipPath instead of using a pattern, with the same result (not displayed in FF if ? in url and Joomla).
I already did an extensive google search, but the best I could find was this post with the same problem but not an solution.
https://forum.joomla.org/viewtopic.php?t=912784
I don't know what else I could try to fix this.
I had the same issue happening to me for my Joomla 3.6.4 site in Firefox. I resolved it by removing the <base> tag from my template. I added this code to my index.php file in my template:
$doc = JFactory::getDocument();
unset($doc->base);

D3 zoom pan stutter

I'm experiencing 'stutter' with the D3 drag behavior.
Seems to be a similar problem to "Stuttering" drag when using d3.behavior.drag() and transform
However the solution does not seem to work for the zoom behavior.
Here is an example of the issue: (try dragging the rectangle)
http://jsfiddle.net/EMNGq/109/
blocks = [
{ x: 0, y: 0 }
];
var translate_var = [0,0];
zoom_var = d3.behavior.zoom()
.on("zoom", function(d) {
d.x = d3.event.x;
d.y = d3.event.y;
draw();
});
svg = d3.select("body")
.append("svg:svg")
.attr("width", 600)
.attr("height", 600);
function draw() {
g = svg.selectAll("g")
.data(blocks);
gEnter = g.enter().append("g")
.call(zoom_var);
g.attr("transform", function(d) { return "translate("+translate_var[0]+","+translate_var[1]+")"; });
gEnter.append("rect")
.attr("height", 100)
.attr("width", 100);
}
draw()
You zoom in or drag the element, but then translate the same element. Because the translation is relative, it results in this stuttering.
As stated in the documentation for Zoom Behavior:
This behavior automatically creates event listeners to handle zooming and panning gestures on a container element. Both mouse and touch events are supported.
Contrast it to the documentation for Drag Behavior:
This behavior automatically creates event listeners to handle drag gestures on an element. Both mouse events and touch events are supported.
Your solution is inverse to the similar question. Call your zoom function on the container.
svg = d3.select("body")
.append("svg:svg")
.attr("width", 600)
.attr("height", 600)
.call(zoom_var);
Here's the demo.
You might also be interested in the actual zoom. To do that simply add the scale to your transform rule. Here's the demo with zoom enabled.

How can I add a duration histogram's time data to its bars?

I am working with numerous D3 duration histograms, each with a different duration. I would like to tie in the starting time of the time range each bar represents into a div id inside each bar's HTML. This information would be accessed via jQuery.
Say for example there's a bar at 00:10. I would like to add "00:10" into the bar's div.
What I can't figure out is how to get a hold of these times. Any suggestions on how to do this?
To add an attribute simply use selection.attr() for example:
bar.append("rect")
.attr("x", 1)
.attr("width", x(data[0].dx) - 1)
.attr("height", function(d) { return height - y(d.y); })
.attr("data-time", function(d){return formatMinutes(d.x)})
But, if you simply want to access an API on click, look into d3's selection.on():
bar.append("rect")
.attr("x", 1)
.attr("width", x(data[0].dx) - 1)
.attr("height", function(d) { return height - y(d.y); })
.on("click", function(d){
accessAPI(formatMinutes(d.x))
})
eg. http://jsfiddle.net/3saQW/
I recommend learning about how d3.js binds data. It may even be that jQuery isn't required. This is a good tutorial series: http://alignedleft.com/tutorials/d3/binding-data/

Resources