Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create artificial zoom transform event

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)

like image 259
T3db0t Avatar asked Apr 30 '19 19:04

T3db0t


People also ask

How do I create a zoom Conference?

Sign in to Zoom Events. In the top-right corner, click Create. Click Conference. Note: You will only see this message when you create a conference for the first time. You will see another pop-up message after clicking Got It; read the message and click Got it. Enter the required information for each section of the process to create a conference.

How do I change the size of a zoomed image?

Set the dimensions of the image – .zoomA { width: 600px; height: auto }. Take note, this can either be a fixed or a percentage value. The hover zoom is done with .zoomA:hover { transform: scale (1.2) } and .zoomA { transition: transform }. If you need a “larger zoom”, just change transform: scale (1.2) to however much is required.

How do I make a hover Zoom on a picture?

The hover zoom is done with .zoomA:hover { transform: scale (1.2) } and .zoomA { transition: transform }. If you need a “larger zoom”, just change transform: scale (1.2) to however much is required. The above hover zoom works, but it may cover surrounding text when the image “expands out of place”. So this is an alternative with a “bounding box”:

How to zoom in/out of an element in JavaScript?

if you scroll up, the targeted element is zoomed out. To do this, we are going to be using the transform property from CSS and dynamically target it via the scroll event in JavaScript. It’s pretty basic. Nothing much. Just a container div with the class zoom attached to it. You can call it whatever you want. Next up is your CSS.


1 Answers

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.com/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.com/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.

like image 146
Andrew Reid Avatar answered Oct 04 '22 20:10

Andrew Reid