Cannot pan SVG when the cursor is on the svg itself in D3.js - d3.js

I've made a simple Zoom & Pan notebook in ObservableHQ.com with D3.js I have two questions:
https://observablehq.com/d/c63434913a56fbb2
If the curson is on the black square (i.e. SVG), it dosen't click and drag anymore! How to allow drag at all time?
How to disable zoom function when mouse is scroling and keep only panning?
Thank you very much!
I'tried different code snnipets, but the simpler one I found don't behave like I would like.

You’re using d3.drag and d3.zoom, which both include functionality to let you drag stuff around. d3.drag lets you drag individual elements with finer control (like rearranging circles); d3.zoom only lets you drag the whole scene. They can be combined, but you only need one or the other here.
In your notebook, the d3.zoom piece is working, but doing more than you want it to (zooming and panning when you only want panning); the d3.drag piece is broken.
You call d3.zoom on the whole SVG, and d3.drag on just the group g. The group contains the bigger black-stroked empty square and the smaller black-filled square; if you click anywhere on that, the d3.drag code runs before the d3.zoom. (It's not just on the black-filled square; if you zoom in, you can see that dragging directly on the outer stroke also doesn't work.)
But the d3.drag code is throwing an error (as you can see in the browser console), because it's trying to set d.x and d.y when d doesn't exist, so nothing happens when you drag. And it wouldn't work anyway, because it's trying to set the cx and cy attributes, which the SVG group element doesn't have; it was probably originally written for an SVG circle element. For a group element, the dragged event should be setting a transform on the group instead, which your d3.zoom code is already doing.
Using d3.zoom
In this approach, you can click and drag anywhere on the SVG. If you don’t want it to zoom when you scroll, you can make scaleExtent only allow a scale of 1:
svg.call(
d3.zoom()
.extent([[0, 0], [w, w]])
.scaleExtent([1, 1])
.on("zoom", function(event) {
g.attr("transform", event.transform);
})
);
Here's a suggestion you can merge into your notebook to use the d3.zoom approach (you should only merge one or the other!): https://observablehq.com/compare/c63434913a56fbb2...a3c5334fa206bb61
Using d3.drag
In this approach, you can only click and drag on the group (where it has stroke or fill). You can give the group a datum with x and y values of 0 to start with, and use a transform instead of setting cx and cy:
const g = svg.append("g")
.datum({x: 0, y: 0})
.call(d3.drag().on("drag", function(event, d) {
d3.select(this)
.attr("transform", `translate(${d.x = event.x}, ${d.y = event.y})`);
}));
If you wanted to be able to click and drag anywhere, you could add an invisible rectangle for pointer capture, as described here.
Here's a suggestion you can merge into your notebook to use the d3.drag approach (you should only merge one or the other!): https://observablehq.com/compare/c63434913a56fbb2...3650eb69db864a42

Related

D3.js: Mouse over between grid lines

I am creating a line chart with grid lines. Here's the similar example: http://bl.ocks.org/hunzy/11110940
I need to change the background of vertical space between X(2) to X(4), X(8) to X(10) and X(12) to X(14) on mouse hover. But I am not able to understand how to use D3 to reference those spaces between the grid lines.
There is nothing there to click on/hover over. The normal axis/grid creates lines, not rectangles. You would need to change the default behave of the axis objects to create "invisible, but clickable" rectangles in order to be able to attach a mouse event to those spaces.
I don't know if this is the recommended approach but it seems like it could work. After the axis has been created:
something
.attr('class','xaxis')
.call(xAxis)
You could select the ticks with something like this:
d3.select(svg).select('.xaxis g.tick').each(function(){
// this.transform will be "translate(X,Y)"
})
In the function you can query the existing properties of the g elements and extract the transform attribute which will contain the X and Y offset for the "tick". This can be used to determine one dimension of your rectangle objects. The other dimension is determined by the size of the other axis or something like that.

d3.event.y has strange values during drag behavior

I'm creating an example illustrating a layout with resizeable cells using the D3 drag behaviour and CSS {display: table} styles. It works fine for dragging horizontally, but not vertically. For vertical resizing, d3.event.y is providing values that do not make sense to me.
Here is a fiddle showing the working horizontal drag and the broken vertical drag. Take a look at the console output while dragging to see that the values returned by d3.event match the values returned by d3.mouse() for the horizontal drag, but they diverge for the vertical drag.
I can fix the behaviour by using the d3.mouse() y-coordinate instead of the d3.event y-coordinate. To see this, comment out the "DOESN'T WORK" line and uncomment the "WORKS" line. However, I don't understand why I need to do this, and it seems less general in that I have to assume a mouse input instead of using the more generic d3.event.
Is this a bug, or am I failing to understand something here?
Note that this question seems to be hitting the same issue, but using HTML tables instead of CSS tables. I thought it would be helpful to document that this problem is occurring in both contexts.
Also note that commenting out the two lines that actually do the vertical resizing, commented with "RESIZE CELLS", makes the d3.event work correctly. Of course, the table doesn't get resized then. This suggests that it is something about the act of resizing the divs that is leading d3.event astray.
Alright, I think I've figured out the issue. If you look at the code for drag behavior, you'll notice in dragstart that the value used to calculate the mouse offset is based off this.parentNode. In short, it uses this.parentNode as a reference point, and assumes that it's going to be stable for the duration of the drag. You're modifying the parent nodes during the drag, so its reference point gets, to put it technically, pretty borked. In this case, using d3.mouse is your best bet, since d3.event.y is only going to be reliable as long as the parent node stays in place.
The reason this only happens in the y direction for you is that the x position of all the rows, which are the parent nodes here, stay constant, whereas the y component changes during the drag.
The relevant code sections:
parent = that.parentNode,
function moved() {
var position1 = position(parent, dragId), dx, dy;
//...
dispatch({
type: "drag",
x: position1[0] + dragOffset[0],
y: position1[1] + dragOffset[1],
dx: dx,
dy: dy
});

How to achieve magnifying square effect for D3 charts?

Take a look at this jsfiddle. Magnifying on hover works well for text and images, but I would like to have the same effect for the chart.
I would like the solution, if possible, to be applicable to any D3 SVG-based chart.
The example uses jquery plugin AnythingZoomer, it looked to me as a good starting point, however, you don't need to stick to it, you can use anything else if you wish.
I am aware of D3 fisheye pluging, this is related, but not quite what I want.
You can do this by not explicitly declaring width and height in the SVG (which is overwritten by CSS anyway), using the viewBox attribute, and then allowing AnythingZoom to clone the content of your original chart.
Demo (Fragile): http://jsfiddle.net/H9psX/ http://jsfiddle.net/H9psX/38/
Changes
var svg = d3.select("#small-chart").append("svg")
// .attr("width", diameter + 300)
// .attr("height", diameter)
.attr('viewBox', "0 0 " + 225 + " " + 225);
// ...
$("#zoom3").anythingZoomer({
clone: true
});
Separation of concerns
Since you are drawing in SVG using D3 (where you need to know the width and height for the pack layout) and using a jquery plugin which zooms by setting classes and absolute positioning, you have to share the coordinates (the 225px magic number) in CSS and in JS.
Ideally, you would want to keep the magic number at only one place. To do that you can declare the value only in CSS and then read them in your JS after creating your SVG element.

d3 center bar chart's x-axis on arbitrary value

I have a bar chart that is wider than the svg element but, with panning, you're able to drag left and right. The x-axis is time based (I use d3.time.scale()).
After building the chart, I'd like to be able to pan to a specific point on the x-axis. For example, the user may have already panned to a certain point and shut down their session - I'd like to put them back where they were when they return.
I've been looking at doing something like:
d3.selectAll('rect')
.attr('transform', 'translate(' + savedXaxisLocation + ',0)';
Is that the right idea? I'm assuming I also need to do that to the x axis itself?
As you can tell I'm feeling my way around this - I'd be happy to include any other code or screenshots if y'all feel it relevant.
In general, you would have a top-level g element that contains everything else and would translate that. This saves you from having to translate all the elements individually. When you do that, you may want to apply a clipPath to hide the elements that have been panned out of view.
This example should help you to get a better idea of what you can do and how.

D3 Scroll a node into view

I'm using a D3 tree. Similar to: http://bl.ocks.org/mbostock/4063570
However, this particular diagram is bigger than the browser window.
Given that i know exactly which one of the JSON element's that i want to reveal.
How could i scroll that element into view within the svg canvas?
The easiest way to achieve this is probably to wrap what you want to appear in a g element and use a transition on the translation.
groupToShow.attr("transform", "translate(1000,1000)")
.transition()
.duration(1000)
.attr("transform", "translate(0,0)");
Adapt the values for translation and duration as needed.

Resources