Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

d3.js svg image: scale from center of rectangle

I'm using d3 to populate a cartesian plane with a bunch of svg:image elements spread out over different coordinates.

I'd like to add mouserover and mouseout logic that zooms the image the mouse is over in and lightens the opacity of the others. I'm filtering my selection on mouseover to only select the desired element and everything is working great, except my scaling logic doesn't seem to get the desired effect. The images expand downward and to the right rather than in the outward from the diagonal center.

Here's what I've tried:

  1. transform: scale(1.5) Which expands, but also totally shifts the image's position
  2. transform: translate(-(width/2), -(height/2)) combined with scale, which does the same but from a different starting position
  3. Changing the x and y coords to ones adjusted for half widths and heights, which has the same effect.

Is there no text-anchor equivalent for image elements with which I could set an "anchor point" to scale from? I'm not sure what the html svg parlance is, but I guess I'm thinking of something similar to the anchor points a lot of vector editors have.

Current approach, mouseover handler:

  function fade(dir){
    return function(d){
      var others = svg.selectAll("image.movie_cover")
        .filter(function(g,i){
          return g != d
        })
        .transition().duration(800)
        .style("opacity",.3);

      var single = svg.selectAll("image.movie_cover")
        .filter(function(g,i){
          return g === d;
        })
        .transition().duration(900)
        .attr("transform", "translate(-40,-40) scale(1.4)")

        var title = keys[coords.indexOf(d)];
        var url = "/static/eshk/"+hash+"_images/" + title  + ".jpg";

        tt.transition()        
                .duration(200)      
                .style("opacity", .9);      
        tt.html(title)  
              .style("left", (d3.event.pageX) + "px")     
              .style("top", (d3.event.pageY - 28) + "px"); 
    }
 }

Using this method, the images move inconsistent distances despite all being the same size.

like image 901
DeaconDesperado Avatar asked Jan 31 '14 16:01

DeaconDesperado


2 Answers

Set up: A 50 x 50 box at 200, 200. It needs to transition to a 100 x 100. It is 50 larger and wider, so needs to move back and up 25, eg 175, 175. Replace hard coded values with functions that look up the current width on mouse hover to calculate the exact values.

d3.select('svg').append('rect');
rect = d3.select('rect');
rect.attr({
    height: 50,
    width: 50,
    x: 200,
    y: 200,
    color: 'steelblue'
})
.transition()
.attr({
    width: 100,
    height: 100,
    x: 175,
    y: 175
});
like image 63
David Souther Avatar answered Nov 14 '22 16:11

David Souther


This could also be done without modifying width or position attributes:

                    images.on("mouseover", function(d, i) {
                    var selection = d3.select(this);
                    var offsetX = parseFloat(selection.attr("x"))+
                                  parseFloat(selection.attr("width")/2.0);
                    var offsetY = parseFloat(selection.attr("y"))+
                                  parseFloat(selection.attr("height")/2.0);
                    selection.attr({
                        transform:"translate("+offsetX+ ","+offsetY+") "+
                                  "scale(1.2) "+
                                  "translate(-"+offsetX+",-"+offsetY+ ")"
                    });
                });

And on mouse out, you'd just set the transform to null to remove it.

Basically, this is just translating the center point to the origin, scaling around that, and translating back to the correct position. Remember that transforms are applied in reverse order (right to left).

I think you were on the right track with the use of translate with scale, but translating back and forth from the origin is what allows it to work while remaining centered at the original location.

like image 42
user1417684 Avatar answered Nov 14 '22 17:11

user1417684