Configurable extents for d3.behavior.zoom. #345

Closed
wants to merge 50 commits into
from

Conversation

6 participants
Collaborator

jasondavies commented Oct 20, 2011

This is my take on the matter. I'm open to suggestions for API improvements!

I believe this also fixes #341 as the domain is no longer stashed in the scale, although I haven't tested modifying the scale domain elsewhere.

See also: #247.

larskotthoff and others added some commits Oct 3, 2011

fix bug that causes chord layout to break in Opera
The chord layout uses an assigment like x += y to specify the end angle of a
chord segment. This seems to return the new value in most browsers, but the old
value (i.e. x - y) in Opera.
Add configurable origin to d3.behavior.zoom.
This defaults to storing the absolute scale and transform across
multiple zooms.  However, it can be modified to retrieve state from
outside the behaviour e.g. if the offsets have been restricted in some
way, the true values should be returned in a custom offset accessor.

The offset accessor is called on "mousedown" or "touchstart", and so has
access to the current mouse position.
Add configurable extents to d3.behavior.zoom.
The extents parameter is a 3-element array of ranges, corresponding to
the ranges allowed in pixel-space at zoom-level 0.  Each range is a
two-element array.  For example, to restrict zooming and panning to a
rectangle of width w and height h, with minimum zoom level 0, use:

    .extents([[0, w], [0, h], [0, Infinity]])

jasondavies and others added some commits Oct 20, 2011

Fix two sorting bugs in chord layout.
We still weren't sorting subgroups correctly. Also, we now sort chords by their
average value, rather than the source value, which works well with one-sided
chords (where either the source or target value is zero).
Use changedTouches instead of touches.
This is because the "touchend" event removes the touch from
event.touches on iOS, but it can still be accessed via
event.changedTouches.
Optional touch array argument for d3.svg.touches.
By default, we probably want to use event.touches, but for "touchend"
events it's useful to retrieve the positions of event.changedTouches.
Better handling of "touchend".
We can't check for existence of event.touches to decide whether to use
it or not for touchend, as it always exists, just not containing the
touch that just ended.
Simplify touchend handling.
We only care about changed touches for d3.behavior.drag.
Namespaces for d3.dispatch events.
Fixes #294. This isn't backwards-compatible, but d3.dispatch is considered an
internal API so this doesn't require a major version bump.
Contributor

stepheneb commented Oct 31, 2011

This app: http://stepheneb.github.com/avalanche2d-js/avalanche2d.html now uses your zoom-extent branch and a bug I was having with the scale being cached is fixed.

See: stepheneb/avalanche2d-js@86a8aa8 for more details about what behavior in my app was fixed by this pull request.

mbostock and others added some commits Oct 31, 2011

First cut at a brush component.
The d3.svg.brush component allows one- or two-dimensional rectangular brushing.
A future commit will allow the brushed region to be resized by grabbing an edge,
and also provide some way of reporting the selection (duh)!
Add resize handles.
These aren't yet hooked up to interaction, but they show the right cursor!
Query the brush selection.
You can now query the brush for its selection. This commit also includes a new
d3.random.normal for generating random numbers with a normal distribution. This
is useful for jittering points for display.
Brush refinements.
The brush component now reports brushstart and brushend events at the start and
end of a brushing gesture, which is useful for some types of interaction. You
can now set the brush extent programmatically, as well. Note, however, that
you'll probably want to redraw the brush after setting the extent.
Use the altKey to preserve the brush center.
A somewhat hidden feature, but useful!
Merge pull request #359 from jasondavies/brush
Remove stray global in brush example.
Update SPLOM example to use d3.svg.brush.
This required a couple core changes. First, the brush shouldn't notify listeners
when redrawing, because this commonly causes an infinite loop if one brush
triggers a change in another brush (as in a scatterplot matrix, where only one
brush is active at a given time). I suppose an alternative implementation might
use just a single brush, and assign the axes dynmically; I might try that in a
future commit. Second, I added a clear convenience method to reset a brush.
Add parallel coordinates example.
This includes a d3.extent convenience method for [d3.min, d3.max], and fixes the
brush component such that the resizers are hidden when the extent is empty.
Invoke accessor once per value in d3.extent.
This allows the use of nondeterministic accessors. Also add a test.
Merge remote-tracking branch 'mbostock/brush' into extent
Conflicts:
	d3.js
	d3.min.js
	src/core/extent.js
	test/core/extent-test.js
Merge pull request #363 from jasondavies/extent
Use single loop for d3.extent.
Owner

mbostock commented Nov 4, 2011

I think I'll probably rename "extents" to "extent" for consistency with d3.svg.brush (and org.polymaps.map, long before). It's multi-dimensional, but it's still a single extent. :)

Collaborator

jasondavies commented Nov 4, 2011

Good point. :)

Fix range generation for ordinal scales.
Using d3.range with a floating point step is a bit sketchy, because there's a
chance that too many or too few elements could be generated. Instead, we now we
generate an integer range and scale it accordingly.
Owner

mbostock commented Nov 4, 2011

Can you give me an example of how the origin feature is used? I don't see it used in any of our existing examples.

Owner

mbostock commented Nov 4, 2011

And a few more questions:

  • Why is the default extent for x & y [Infinity, -Infinity] rather than [-Infinity, Infinity]?
  • Why is the extent set to Object if the input extent x is null?
  • Do we need to support origin as a function, or would a constant be sufficient (as with extent)?
Collaborator

jasondavies commented Nov 4, 2011

Hmm, it's quite likely we don't need origin any more. The origin accessor was an implementation of my idea in this comment. Essentially it provided a way to retrieve the element's current position on dragstart. Previously the zoom behaviour stored the origin internally, but if you clamped it externally, the origin would have the wrong position (which matters when zooming).

  • Yes, this really should be [-∞, ∞] but I think I got a bit muddled when clamping the zoom range, so this needs to be corrected.
  • I think this is left over from when I allowed a function to be set as the extent, again it needs correcting I think!
  • It's possible we don't need it at all, if we just set it to [0, 0] to start with as before, then the new extent clamping should keep it in check.
Fix silly bug.
This was due to mishandling infinities in the clamping function, so the
default extent was "backwards".
Collaborator

jasondavies commented Nov 4, 2011

Fixed backwards infinities in @14b8d48.

jasondavies and others added some commits Nov 4, 2011

Remove unnecessary origin accessor from zoom.
I don't think this is needed any more, as we now clamp the extent in the
behaviour itself.
Owner

mbostock commented Nov 4, 2011

Re-merged to my zoom branch.

Collaborator

jasondavies commented Nov 4, 2011

I've corrected the other issues too, I think, apologies if I've caused any merge conflicts!

Owner

mbostock commented Nov 4, 2011

OK, merged into 2.5.0!

@mbostock mbostock closed this Nov 4, 2011

Collaborator

jasondavies commented Nov 4, 2011

Can you include @070b463ac097079f468253ed17b8d35a5643e163 too, I think it got missed out. Thanks!

Owner

mbostock commented Nov 4, 2011

Yup, it's in.

Collaborator

jasondavies commented Nov 4, 2011

Ah, it's there now, thanks. :)

I'm having a little trouble understanding why the extents clamping is written the way it it. I might not understand the expected syntax? Assuming that .extent([a,b],[c,d],[e,f]) is meant to limit a<=x<=b, c<=y<=d, e<=zoom scale<=f, then shouldn't the function d3_behavior_zoomExtentClamp read:

function d3_behavior_zoomExtentClamp(x, i, k) {
  var range = d3_behavior_zoomExtent[i],
      r0 = range[0],
      r1 = range[1];
  return arguments.length === 3
      ? Math.max((r0 === -Infinity ? -Infinity : r0),
                 Math.min(r1 === Infinity ? Infinity : r1, x ))*k
      : Math.max(r0, Math.min(r1, x));

The change is to the return value of the 3 argument version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment