Giter Club home page Giter Club logo

d3-zoom's Introduction

d3-zoom

The zoom behavior implemented by d3-zoom is a convenient but flexible abstraction for enabling pan-and-zoom on selections. It is agnostic about the DOM, so you can use it with SVG, HTML or Canvas.

Resources

d3-zoom's People

Contributors

cambecc avatar dakkaron avatar fidelthomet avatar fil avatar herst avatar mbostock avatar mccaskillj avatar ondras avatar robinhouston avatar swordensen avatar testower avatar tmcw avatar trysound avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

d3-zoom's Issues

Simultaneous zoom gestures on different elements.

With touch, it should be possible to have concurrent zoom gestures on different elements. So, we shouldn’t assume that they are all acting on the same element (and transform). However, it should be safe to assume that we receive separate touchmove events for each zooming element.

Ability to set X & Y transform scale

With zoom.x() and zoom.y() removed, there doesn't appear to be an elegant way to zoom a single axis while leaving the opposite axis's domain untouched.

I'd like to propose the idea of extending the transform methods to support 1 or 2 scales, replacing the single k value with kx and ky values and extending the transform.scale method to accept an optional second parameter. By default, when called with a single parameter, kx and ky would be assigned with the same scale, resulting in the same functionality as today. However, users would have more control over their transforms through the added ability to compress and expand axises as needed.

I'm happy to work on this functionality but wanted to get a feel for it's perceived viability before I dove in.

Slowdown due to many zoom events per frame

A simple benchmark shows that mouse events happen more often than screen refreshes. I.e. zoom() handler is called 3 to 120 times more frequently than requestAnimationFrame() handler on different hardware. So heavy work (DOM and other painting) should be avoided in .zoom() handlers and performed in requestAnimationFrame instead (or using a 16ms timer as a poor man substitute if rAF is not available but the browser is otherwise capable enough).

What do you think? Can such workflow be supported by library code? Does it have direct support in D3 now?

In multiplayer games and other realtime interactions where latency is important it is nice to get all raw input early, to feed into predictive models and/or send to game server. But in simple animations and visualization controls it is seldom necessary. So one approach for D3 could be to modify zoom events so they are fired only once per frame. E.g.

function onDomMouseMove() { lastCoords = event.transform; zoomHappened = true }
function onAnimationFrame() { if (zoomHappened) fireAllZoomHandlers(lastCoords) }

I modified heavily your gradient panning gist to demonstrate the advantages:

https://github.com/streamcode9/svg-time-series/tree/626ddf9bed104bb457217f2183084b91a4b4261f/benchmarks/d3-myaxis

The code is rather dirty proof-of-concept, and I also fixed extra attribute modifications in axis by copy-pasting axis module in my code. But even zoom event filtering alone index.js#L252-293 shows improvement in FPS on slow devices.

My tests show that in panning lastCoords approach works better than firstCoords, as the observed distance between mouse pointer and object being dragged is smaller.

d3.zoomTransform(node)

I can't seem to figure out how to use this function, everything I have tried has returned the identity matrix and never the current transform that is being applied.

var zoom = d3.zoom()
             .scaleExtent([0.3,2])
             .on("zoom", zoomed);


var svg = d3.select("body")
            .append("svg")
              .attr("width", viewerWidth)
              .attr("height", viewerHeight)
            .call(zoom)
            .append("g");

function zoomed() {
  var t = d3.event.transform;
  svg.attr("transform", "translate(" + t.x + "," + t.y + ") scale(" + t.k + ")");
}

If i zoom or pane on the page and then typed3.zoomTransform(svg) all i get back is the identity matrix, I tried d3.zoomTransform(zoom) and d3.zoomTransform(zoom,svg) and all of them give me the same identity matrix...
If I look at svg in the console the __zoom parameter is not there.
Any suggestions on this? Thank you

Tests?

Much of the code is covered in the examples, but perhaps this module would benefit from unit tests?

View state should be accessed per-element?

I feel like this is a source of confusion with D3 3.x, in that in 3.x, the zoom behavior stores state internally on the behavior (view = {x: 0, y: 0, k: 1}) and on each selected element (this.__chart__ = view), though the latter only occurs when you apply a transition or call zoom.event. Related d3/d3#1515.

Is the zoom behavior intended to be applied to a selection of multiple elements, or just a single element? If you apply it to multiple elements, presumably you’d want their views to be independent, which necessitates storing the view state on each element independently, rather than storing it inside the behavior.

The d3-drag API seems more reasonable in this regard, in that it has accessors for determining the state of the targeted element at the beginning of a drag gesture. Could d3-zoom do something similar, where you have accessors for determining state (scale, translate) at the beginning of a zoom gesture?

I’m not totally sure how this would extend to zoom transitions. The idea would be that the zoom behavior would report the new state, but not directly modify the data or the DOM. Though presumably you could always use d3.interpolateZoom to do zoom transitions.

Let the zoom behavior choose the appropriate transition duration?

It might be nice if the zoom behavior set the transition duration by scaling the duration computed by d3.interpolateZoom. You’d need to have a multipler, though, like maybe you specify the duration as 250ms, but it means that’s the duration for a 2x zoom in-place, and it’s scaled accordingly for other transitions.

However, we’d need an option to disable this, since it’s equally valid for someone to specify the transition’s duration explicitly. Also, we might want to disambiguate between setting the duration of the dblclick transition and setting the duration of other zoom transitions.

zoomTransform not retrieving value set via zoom.transform

From the documentation, it seems that zoom.transform should be used to set the transform value on a selection, like this:

var transform = d3.zoomIdentity
  .scale(2)
  .translate(3, 4);

var zoom = d3.zoom();

var svg = d3.select("body").append("svg");

svg.call(zoom.transform, transform);

Also from the documentation, it seems that zoomTransform should be used to retrieve the value set via zoom.transform, like this:

var retrievedTransform = d3.zoomTransform(svg);

However, the retrieved transform ends up as the identity transform, rather than the transform previously set.

// Prints "transform = translate(6,8) scale(2)", as expected.
console.log("transform = " + transform);

// Prints "retrievedTransform = translate(0,0) scale(1)".
// This is not expected, it should be the "translate(6,8) scale(2)".
console.log("retrievedTransform = " + retrievedTransform);

Here's a block that reproduces the behavior.

Am I misunderstanding the API or might this be a bug?

Error when trying to implement zoom in application

I am trying to port existing working code from d3 version 3.5 to 4.0. I am getting the error Uncaught TypeError: Cannot read property 'button' of null (zoom.js:82) when trying to implement zooming in a webpacked React application. I have written a simple example which demonstrates the issue:

import { zoom } from 'd3-zoom';
import { select, event } from 'd3-selection';

function zoomed() {
  console.log(event);
}

function selectSvgRef(ref) {
  const svg = select(ref);

  svg.append('rect')
    .attr('width', 960)
    .attr('height', 500)
    .style('pointer-events', 'all')
    .style('fill', 'steelblue')
    .call(zoom().on('zoom', zoomed))
}

class ZoomBox extends React.Component {
  render() {
    return (
      <svg ref={selectSvgRef} width={960} height={500} >
      </svg>
    );
  }
}

Clicking or scrolling on the svg rect causes the error to be thrown. It is entirely possible that I am messing up the new syntax. Can you please help?

Stack trace:

defaultFilter @ d3-zoom.js:82
wheeled @ d3-zoom.js:261
(anonymous function) @ d3-selection.js:97

edit: fixed code snippet

Behaviour changed?

This is not a request.
When zoom is constrained by translateExtent(), dragging out of the extent may cause scale-up to keep to conform to translateExtent.
For example, try drag the cross section chart of a railroad route.( http://www.chunichi.co.jp/ee/feature/chuo/ ).

This is just a little enquiry. That the dragging near the extent border should not trigger scaling-up is a design ?

emulate double click with double touch/tap events and link their handlers

Hi, i'm trying to define a zoom behavior mostly for panning, where a double click on the background would unzoom and return at scale 1 instead of zooming (i use zoom scales on other objects than the background). This works very fine with the desktop version through .on("dblclick.zoom") handler.

 mysvg.selectAll("image.bg")
    .call(zoom)
    .on("dblclick.zoom", autoRescale)

Good so far!

However i have an issue i can't solve on mobile with the touch events. I would like to emulate the same doubleclick behavior when the user makes a double tap.

If i add a handler .on("touchstart.zoom", myhandler) i manage to emulate the double tap correctly (through my own timer). But then i lose all the gestures handled by d3 such as standard panning and pinch zoom and i'd definitely want to keep those.

I went a bit in the code and noticed the touchstart() inner function calls dblclicked() inner function regardless of my own handler that will be ignored. Maybe there could be a solution here?

I haven't found any workaround. I tried the dirty way reading the internal touchstart.zoom callback function and calling it again but it has side effects. I also tried capturing the dispatch events such as zoomstart but i couldn't solve this. Any suggestion?

Also i'm a bit lost with the documentation, between these 2 pages:

What is the good API reference? For instance the events are called zoomstart, zoom, zoomend in the main page but start, zoom, end in the zoom package (without prefix). The filter function described in the package does not seem to be available on the zoom behavior but maybe i missed something here. The documentation in the zoom package is more detailed but the API reference matches the version i'm using (d3.min 3.5.16). A bit confused where to go here.

Many thanks for this awesome library!

Zoom allow horizontal scrolling, or wheel on drag

Zooming leverages the vertical aspect of the mouse wheel as shown in http://bl.ocks.org/mbostock/34f08d5e11952a80609169b7917d4172, but can zoom also allow for horizontal movement on a zoom that doesn't use it's Y scale (timelines, eg.)? Instead of having to drag horizontally, it would be much quicker to scroll left and right (iMovie editor section, e.g.). If scrolling is not an option, at least extending the drag so that it can be given the momentum effect the 'wheel' provides would provide for a better experience in many of the cases I've come across.

The zoom event has a deltaX on it, which I am trying to use in place of the zoom effect when the scrolling is primarily horizontal (abs(deltaX) >= abs(deltaY)). For an example of how this works, simply scroll your browser on a page that isn't overflowing horizontally. The page vertically scrolls up and down without any horizontal shift until ample horizontal action is detected and the page budges left and right.

Below is is the pattern I've been trying to follow, but I run into a lot of issues (trying to set the zoom retriggers the zoom event, setting the offset on the event transform doesn't persist, locking the scaling seems problematic, nested elements don't respond to the horizontal zoom and instead the 'back' on browser history is triggered). Has any consideration been given to this sort of fx.

var zoom_direction,
      zoom = d3.zoom()
            .extent([[0, 0], [width, container_height]])
            .translateExtent([[0, 0], [width, container_height]])
            .on('zoom', zoomed)
            .on('start', stashZoomTransform)
            .on('end', () => zoom_direction = undefined);

function zoomed() {
            let t = d3.event.transform;
            if (!zoom_direction) {
                  zoom_direction; // set to 'horizontal' if deltaX is more significant then deltaY
            }
            // if zoom_direction is horizontal
                // lock or reset scaling to the stashed zoomTransform 
                // Set transform offset according to deltaX 
                // Reapply new transform to node
}

I tried all day to come up with a solution, but haven't managed it. Any insights would be appreciated.

Extent accessor failure

const width = 960;
const height = 430;

const svg = d3.select('body').append('svg')
    .attr('width', width)
    .attr('height', height);
const zoom = d3.zoom();
const handler = svg.append('rect')
    .attr('x', 0)
    .attr('y', 0)
    .attr('width', width)
    .attr('height', height)
    .attr('fill', 'transparent')
    .attr('stroke', '#000')
    .call(zoom);

console.log(zoom.extent()());
// Error: d3-zoom.v1.js:92 Uncaught TypeError: Cannot read property 'clientWidth' of undefined

How to reset zoom?

Here's my reset function:

function reset() {
  var t = d3.zoomIdentity.translate(0,0).scale(1)
  chart_svg.call(zoomListener.transform, t)
}

But the resulting transform property applied to my chart is translate (-xxxxx,-xxx) scale (1).

Option to disable double-click and double-tap behavior

I require d3-zoom to not zoom in on a double-click or double-tap since it is interfering with my use case. The use case is as follows: Given a graph, the zoom behavior is programmatically changed when the user clicks on a node in such a way that the node appears on the center of the screen. When clicked on the background, the zoom is reset.

Transform jumps when sharing zoom between components

I've got two components sharing the same zoom. Depending on which component I use the scroll wheel on, I get different zoom levels because the __zoom transform in each component is not kept in sync.

Expected behaviour:

  • Zoom in on component A, observe component B also zooming to the same scale.
  • Zoom in on component B, observe component A also zooming in further to the same scale as B.

Actual behaviour:

  • Zoom in on component A, observe component B also zooming to the same scale.
  • Zoom in on component B, components A and B jump back out to the original zoom level before linked zoom commences.

I've worked around the problem by copying d3.event.transform into each component's __zoom member inside the corresponding component's zoom event listener. It doesn't seem right though, I shouldn't be fiddling __zoom directly. I don't want to kick off a full zoom.transform inside the zoom handlers.

Is this how it's supposed to work? Am I expected to do this transform sync myself? If so, what's the right way to do it?

Combining programmatic and interactive control?

With the recent change to distinguish the “target” view from the “current” displayed view, how would you implement external controls for zooming, such as plus and minus buttons? How do you inspect the current displayed view of a given element (aside from element.__zoom, obviously)?

Represent view as [cx, cy, width]?

Currently the view is represented as an object {scale: k, translate: [x, y]}. However, d3.interpolateZoom expects an object [cx, cy, width]. The latter seems a bit awkward in terms of mapping to a Canvas or SVG transform, but maybe it’s worth considering it anyway for consistency.

Not Compatible with Brushing

Creating a brush and zoom chart like this one http://bl.ocks.org/nnattawat/9689303 in version 4 becomes extremely difficult. An infinite loop occurs:

  • A zoom event is only able to move the brush using "brush.move". This triggers a brush event.
  • The brush event must update the zoom scale and translate which it cannot do except by calling "zoom.transform".
  • This triggers a zoom event...

The zoom could be calculated and set to the node manually but the documentation says this is bad practice. Is there be a way to update the zoom without triggering an event? Previously in V3 you could reset the scale, I believe.

Having difficulties using d3-zoom with d3 v4.0

I am using webpack and trying add zoom to an svg with the following code:

import d3 from 'd3';
import d3Zoom from 'd3-zoom';
Object.assign(d3, { zoom: d3Zoom.zoom, zoomTransform: d3Zoom.zoomTransform });

d3.select('svg').call(d3.zoom().on("zoom", ()=> {
   console.log('zooming')
}));

But I am getting this error:

Uncaught TypeError: Cannot read property 'button' of null.

from:

// d3-zoom/build/d3-zoom.js, v:`0.1.0`
function defaultFilter() {
   return !d3Selection.event.button;
}

Are there any examples to integrate zoom with existing d3 graphs? I am trying to do pan and zoom. Thanks.

How to constrain the transform during zooming?

Like this: https://bl.ocks.org/mbostock/4987520

Calling zoom.transform during a zoom event isn’t great because it would cause another zoom event to be dispatched, leading to a potential infinite loop. And d3.zoomTransform only provides a way to retrieve the zoom transform, not to set it. I suppose d3.zoomTransform(node, transform) could work, though, but I’m not wild about having another way to set the transform.

Better default zoom.size?

How about instead of requiring people to set zoom.size explicitly (which they’ll probably forget to do, because it’s currently only used for d3.interpolateZoom and zoom transitions), it could be inferred from the element itself using element.clientWidth and element.clientHeight.

function size(node) {
  node = node.ownerSVGElement || node;
  return [node.clientWidth, node.clientHeight];
}

Add zoom.filter.

We should have something like drag.filter for ignoring initiating input events, and in particular it should default to ignoring mousedown on secondary buttons.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.