admin管理员组

文章数量:1426929

I have a timeline in D3 with a highly modified drag/scroll pan/zoom. The zoom callbacks use the d3.event.transform objects generated by the zoom behavior.

I need to add a programmatic zoom that uses my existing callbacks. I have tried and tried to do this without doing so, but I haven't gotten it to work and it would be radically easier and faster to reuse the existing structure.

So the input is a new domain, i.e. [new Date(1800,0), new Date(2000,0)], and the output should be a new d3.event.transform that acts exactly like the output of a, say, mousewheel event.

Some example existing code:

this.xScale = d3.scaleTime()
  .domain(this.initialDateRange)
  .range([0, this.w]);

this.xScaleShadow = d3.scaleTime()
  .domain(this.xScale.domain())
  .range([0, this.w]);

this.zoomBehavior = d3.zoom()
  .extent([[0, 0], [this.w, this.h]])
  .on('zoom', this.zoomHandler.bind(this));

this.timelineSVG
  .call(zoomBehavior);

... 

function zoomHandler(transformEvent) {
  this.xScale.domain(transformEvent.rescaleX(this.xScaleShadow).domain());

  // update UI
  this.timeAxis.transformHandler(transformEvent);
  this.updateGraphics();
}

Example goal:

function zoomTo(extents){
  var newTransform = ?????(extents);

  zoomHandler(newTransform);
}

(Please don't mark as duplicate of this question, my question is more specific and refers to a much newer d3 API)

I have a timeline in D3 with a highly modified drag/scroll pan/zoom. The zoom callbacks use the d3.event.transform objects generated by the zoom behavior.

I need to add a programmatic zoom that uses my existing callbacks. I have tried and tried to do this without doing so, but I haven't gotten it to work and it would be radically easier and faster to reuse the existing structure.

So the input is a new domain, i.e. [new Date(1800,0), new Date(2000,0)], and the output should be a new d3.event.transform that acts exactly like the output of a, say, mousewheel event.

Some example existing code:

this.xScale = d3.scaleTime()
  .domain(this.initialDateRange)
  .range([0, this.w]);

this.xScaleShadow = d3.scaleTime()
  .domain(this.xScale.domain())
  .range([0, this.w]);

this.zoomBehavior = d3.zoom()
  .extent([[0, 0], [this.w, this.h]])
  .on('zoom', this.zoomHandler.bind(this));

this.timelineSVG
  .call(zoomBehavior);

... 

function zoomHandler(transformEvent) {
  this.xScale.domain(transformEvent.rescaleX(this.xScaleShadow).domain());

  // update UI
  this.timeAxis.transformHandler(transformEvent);
  this.updateGraphics();
}

Example goal:

function zoomTo(extents){
  var newTransform = ?????(extents);

  zoomHandler(newTransform);
}

(Please don't mark as duplicate of this question, my question is more specific and refers to a much newer d3 API)

Share Improve this question edited May 1, 2019 at 3:58 Gerardo Furtado 102k9 gold badges128 silver badges177 bronze badges asked Apr 30, 2019 at 19:26 T3db0tT3db0t 3,5514 gold badges33 silver badges45 bronze badges 2
  • Have you looked at brush and zoom examples? In these the brush is used to rescale the scale and then apply a new zoom by setting a new transform (with selection.call(zoom.transform, zoomTransform). Assuming I understand the question correctly, I'll fill an answer out later when I'm free later tonight. – Andrew Reid Commented Apr 30, 2019 at 19:49
  • @AndrewReid I have, but to be honest I simply don't understand the details of zoom.transform and other moving parts... – T3db0t Commented Apr 30, 2019 at 20:00
Add a ment  | 

1 Answer 1

Reset to default 5

Assuming I understand the problem:

Simply based on the title of your question, we can assign a zoom transform and trigger a zoom event programatically in d3v4 and d3v5 using zoom.transform, as below:

selection.call(zoom.transform, newTransform)

Where selection is the selection that the zoom was called on, zoom is the name of the zoom behavior object, zoom.transform is a function of the zoom object that sets a zoom transform that is applied on a selection (and emits start, zoom, and end events), while newTransform is a transformation that is provided to zoom.transform as a parameter (see selection.call() in the docs for more info on this pattern, but it is the same as zoom.transform(selection,newTransform)).

Below you can set a zoom on the rectangle by clicking the button: The zoom is applied not spatially but with color, but the principles are the same when zooming on data semantically or geometrically.

var scale = d3.scaleSqrt()
  .range(["red","blue","yellow"])
  .domain([1,40,1600]);
  
var zoom = d3.zoom()
  .on("zoom", zoomed)
  .scaleExtent([1,1600])
    

var rect = d3.select("svg")
  .append("rect")
  .attr("width", 400)
  .attr("height", 200)
  .attr("fill","red")
  .call(zoom);
  
// Call zoom.transform initially to trigger zoom (otherwise current zoom isn't shown to start). 
rect.call(zoom.transform, d3.zoomIdentity);

// Call zoom.transform to set k to 100 on button push:
d3.select("button").on("click", function() {
  var newTransform = d3.zoomIdentity.scale(100);
  rect.call(zoom.transform, newTransform);
})

// Zoom function:
function zoomed(){
  var k = d3.event.transform.k;
  rect.attr("fill", scale(k));
  d3.select("#currentZoom").text(k);
}
rect {
  cursor: pointer;
}
<script src="https://cdnjs.cloudflare./ajax/libs/d3/5.7.0/d3.min.js"></script>
<button>Trigger Zoom</button> <br />
<span> Current Zoom: </span><span id="currentZoom"></span><br />
<svg></svg>

If applying a zoom transform to a scale, we need to rescale based on the new extent. This is similar to the brush and zoom examples that exist, but I'll break it out in a bare bones example using only a scale and an axis (you can zoom on the scale itself with the mouse too):

var width = 400;
var height = 200;

var svg = d3.select("svg")
  .attr("width",width)
  .attr("height",height);
  
// The scale used to display the axis.
var scale = d3.scaleLinear()
  .range([0,width])
  .domain([0,100]);
  
// The reference scale
var shadowScale = scale.copy();

var axis = d3.axisBottom()
  .scale(scale);
  
var g = svg.append("g")
  .attr("transform","translate(0,50)")
  .call(axis);
  
// Standard zoom behavior:
var zoom = d3.zoom()
  .scaleExtent([1,10])
  .translateExtent([[0, 0], [width, height]])
  .on("zoom", zoomed);
 
// Rect to interface with mouse for zoom events.
var rect = svg.append("rect")
  .attr("width",width)
  .attr("height",height)
  .attr("fill","none")
  .call(zoom);
  
d3.select("#extent")
  .on("click", function() {
    // Redfine the scale based on extent
    var extent = [10,20];

    // Build a new zoom transform:
    var transform = d3.zoomIdentity
      .scale( width/ ( scale(extent[1]) - scale(extent[0]) ) ) // how wide is the full domain relative to the shown domain?
      .translate(-scale(extent[0]), 0);  // Shift the transform to account for starting value
      
    // Apply the new zoom transform:
    rect.call(zoom.transform, transform);

  })
  
d3.select("#reset")
  .on("click", function() {
    // Create an identity transform
    var transform = d3.zoomIdentity;
    
    // Apply the transform:
    rect.call(zoom.transform, transform);
  })

// Handle both regular and artificial zooms:  
function zoomed() {
  var t = d3.event.transform;
  scale.domain(t.rescaleX(shadowScale).domain());
  g.call(axis);
}
rect {
  pointer-events: all;
}
<script src="https://cdnjs.cloudflare./ajax/libs/d3/5.7.0/d3.min.js"></script>
<button id="extent">Zoom to extent 10-20</button><button id="reset">Reset</button><br />
<svg></svg>

Taking a look at the key part, when we want to zoom to a certain extent we can use something along the following lines:

d3.select("something")
  .on("click", function() {
    // Redfine the scale based on scaled extent we want to show
    var extent = [10,20];

    // Build a new zoom transform (using d3.zoomIdentity as a base)
    var transform = d3.zoomIdentity
      // how wide is the full domain relative to the shown domain?
      .scale( width/(scale(extent[1]) - scale(extent[0])) ) 
      // Shift the transform to account for starting value
      .translate(-scale(extent[0]), 0);  

    // Apply the new zoom transform:
    rect.call(zoom.transform, transform);

  })

Note that by using d3.zoomIdentity, we can take advantage of the identity transform (with its built in methods for rescaling) and modify its scale and transform to meet our needs.

本文标签: javascriptCreate artificial zoom transform eventStack Overflow