d3.event.y has strange values during drag behavior - d3.js

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
});

Related

d3.zoom unexpected behavior: what am I missing?

I've got a behavior with d3.zoom whose solution I'm sure is to be found in something I'm obviously missing, but I can't seem to make sense of it. I've reviewed and reviewed examples, and seem to be following them precisely, but something is causing this particular function to not behave.
The following, rather than zoom to focusElement as intended, flips between zooming away from it, and then back to it. The values of -focusBBox['x'], for example, flip between the following two values on subsequent executions. 2500 is svgWidth/2
-208.586669921875
2500
function focusObject(focusElement) {
var focus = document.getElementById(focusElement);
var focusBBox = focus.getBoundingClientRect();
gridGroup.transition().duration(750).call(zoom.transform,d3.zoomIdentity.translate(-svgWidth / 2, -svgHeight / 2).translate(-focusBBox['x'], -focusBBox['y']));
}
Can someone just please take a moment to give a kind virtual slap to point out what it is that I'm missing?
Aha! From a previous incarnation of this particular endeavor, I was using a fixed-position SVG to capture mouse events and applying my transformations to a child SVG. The fact that getBbox() returns local coordinates and getBoundingClientRect() returns coordinates from the outer SVG coord system thus mucked things up.
I've included the revised snippet below. Note that focusBBox2 uses getBBox rather than getBoundingClientRect(), and that grid is the parent SVG as distinct from gridGroup in the original post.
function focusObject(focusElement) {
var focus = document.getElementById(focusElement);
var focusBBox2 = focus.getBBox();
grid.transition()
.duration(750)
.call(zoom.transform,d3.zoomIdentity.translate(focusBBox2.x, focusBBox2.y));
}

Fixing zoom 'jump' when scaling map

I've been working with Jason Davies' Rotate the World and World Countries examples and incorporating a few other bits and pieces to learn about d3.
An example of what I have come up with so far is here.
The small selector in the top left corner will trigger a new 'trip' to be displayed on the globe and the centroid of the trip will be centered in view. This all works well, as does panning and zooming on the globe with the mouse.
However, there's an issue with the present implementation when one zooms in/out, alters the visible trip, then attempts to pan/zoom again: the zoom defaults back to the level it was before the trip change - resulting in a zoom 'jump' I'd like to remove [To see this behaviour, load my MNWE, zoom in with the mouse wheel perhaps 3 times, click the OK button to load 'J07', click on the globe holding the mouse button down and pan a bit - you'll see the jump I'm referring to.].
I'm pretty sure it's just me not updating the scale of my projection correctly, but I don't know enough to troubleshoot further. Jason has implemented a d3.geo.zoom function, which probably should be capable of fixing the issue. My attempt was to call it in my $("#sub").on("click" ... call via:
d3.select("#map").call(d3.geo.zoom().projection(proj).scale(a/2-10).on("zoom.redraw", function() {
d3.select(this).selectAll("path").attr("d", d3.geo.path().projection(proj));
}));
but that gave spurious results after the fact as well.
Lines 344 and 346:
var sc = d3.interpolate(proj.scale(), a / 2 - 10); //344
return function(i) {
proj.rotate(interp(i)).scale(sc(i)); //346
d3.select("#map").selectAll("path").attr("d", d3.geo.path().projection(proj));
//r.world();
};
are probably where I introduce the bug, as the rotation portion works without issue, it's only the scale additions that cause problems.
This can be solved by defining the zoom behavior in global scope.
m = d3.behavior.zoom();
Then update the zoom behavior with the scale in the transition as shown below this will stop the jump effect you have.
d3.transition().delay(250).duration(2250)
.tween("rotate", function() {
interp.source(proj.rotate()).target(coords).distance();
var sc = d3.interpolate(proj.scale(), a / 2 - 10);
return function(i) {
proj.rotate(interp(i)).scale(sc(i));
m.scale(sc(i));//update the zoom in the zoom behavior this will sop the jumping effect
d3.select("#map").selectAll("path").attr("d", d3.geo.path().projection(proj));
//r.world();
};
});
Working code here
Hope this helps.

wxToolBar changing device coordinates

Using the mouse I am drawing 2D shapes on the client area of a MDIChildFrame. Recently I have added a wxToolBar to the frame and when I now draw a shape on the client area it seems that the points have shifted by the size the toolbar. Imagine that with mouse I am clicking on (100,100) and drawing a line to (150,150); however, the line appears somewhere (75,75) to (125,125). By the way, wxMouseEvent GetPosition(); reports (100,100) to me.
Removing the toolbar fixes the problem however, I want to keep the toolbar for ease of tool selection.
I use the code:
m_ToolBar=new wxToolBar(this, wxID_ANY);
m_ToolBar->AddTool() //
m_ToolBar->Realize();
this->SetToolBar(m_ToolBar);
Any ideas will be appreciated.
You can always use wxWindow::GetClientAreaOrigin() to manually offset the coordinates by the toolbar height but normally this shouldn't be necessary, and if this doesn't happen with a "normal" frame but only happens with wxMDIChildFrame it would be a bug in wxWidgets that should be reported as usual.
It's also recommended to not draw over wxFrame itself but rather put a wxWindow into it and draw on it. This should also take care of your problem.

How to properly use setPatternOffset to fix NSColor colorWithPatternImage insanity

I'm trying to draw a tiled pattern inside a NSScrollView which is itself inside a resizable window (on Mac OS X). The code simply calls [NSColor colorWithPatternImage], CGContextSetFillColorWithColor, and CGContextFillRect.
The problem is that the pattern is drawn relative to the bottom-left corner of the window. This is documented behavior, but causes two unpleasant effects:
When the window is resized, the pattern scrolls up or down in a very surprising manner.
When the scroll view is scrolled, and then scrolled back, the newly drawn pattern doesn't line up with the scrolled (buffered) part of the pattern.
I'm able to mostly fix problem 2 by calling CGContextConvertPointToDeviceSpace, passing in 0,0, and seeing what I get back -- this tells me my scroll offset, which I can then use with CGContextSetPatternPhase to fix the problem. (Though it doesn't completely fix it -- when I scroll quickly, I still see mismatched patterns, for reasons I haven't sorted out yet.)
But addressing problem 1 is proving really thorny. From my drawing code, which only knows the CGContext, I can't find any way to get the window height. (The Device/User space conversion routines seem completely unaffected by window height.)
Short of adding a bunch of plumbing to all the drawing code to pass around a window reference, is there any way to figure out the correct offset so that my pattern will stay put when the window is resized, and scroll properly when the scrollview is scrolled?
Oops -- shortly after posting this, I found at least a partial answer: the current transformation matrix (from CGContextGetCTM) reflects both the window height, and the scroll offset.
(Or at least, it does in my app; I set the CTM very early in the pipeline to give me a top-down coordinate system so I can use much the same code on iOS.)
So, if I just call CGContextGetCTM, and then pass the .x0 and .y0 values of the result to CGContextSetPatternPhase, it mostly works.
I'm still running into some visual glitches when I scroll quickly horizontally, though not vertically -- a very odd effect, which suggests to me that it might be something in my code, or perhaps related to how wide the content area is. I'll dig further.

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.

Resources