4

So far I've used d3.svg.symbol() in a force-directed graph for distinguishing different node types from one another.

I'd now like to distinguish different node types by displaying node as a svg image. Following one of the examples I used the following code to display the node images:

    var node = svg.selectAll("image.node").data(json.nodes);
    var nodeEnter = node.enter().append("svg:g").append("svg:image")
    .attr("class", "node")
    .attr("xlink:href", function(d)
    {
      switch( d.source )
      {
        case "GEXP": return "img/node_gexp.svg";
        case "CNVR": return "img/node_cnvr.svg";
        case "METH": return "img/node_meth.svg";
        case "CLIN": return "img/node_clin.svg";
        case "GNAB": return "img/node_gnab.svg";
        case "MIRN": return "img/node_mirn.svg";
        case "SAMP": return "img/node_samp.svg";
        case "RPPA": return "img/node_rppa.svg";
      }
    })
    .attr("x", function(d) { return d.px; } )
    .attr("y", function(d) { return d.py; } )
    .attr("width", "50")
    .attr("height", "50")
    .attr("transform", "translate(-25,-25)")
    .on("click", function(d) { 
      console.log("nodeclick");
      } )
    .on("mouseover", fade(0.10) )
    .on("mouseout", fade(default_opacity) )
    .call(force.drag);

This does display the svg images but I've two problems:

1) I want to scale the node sizes based on an attribute. From what I understand this can be done by supplying the "scale" attribute

transform="scale(something)"

to a suitable place, like to the image tag itself or to the group containing the image:

    var node = svg.selectAll("image.node").data(json.nodes);
    var nodeEnter = node.enter()
    .append("svg:g")
    .attr("transform", function(d) 
    {
      var str = "scale(";
        if( d.gene_interesting_score && d.gene_interesting_score > 0 )
        {
          return str + ( (d.gene_interesting_score - minScore ) / ( maxScore - minScore ) ) + ")";
        }
        return str + 0.7 + ")";
    })
    .append("svg:image")
    ....

As it happens, the scale() transform displaces the images: they are no longer on the endpoint of the edge. How do I resize the images properly when initializing the graph, preferrably so that the controlling mechanism is within a single function (e.g. so that I don't have to control x,y,width,height separately)?

2) When the graph is zoomed, Chrome blurs the images whereas in Firefox the image remains crispy (picture). How can this blur be avoided?

Chrome above, FF below

Edit: Based on duopixel's suggestions, the code is now:

    var nodeGroup = svg.selectAll("image.node").data(json.nodes).enter()
    .append("svg:g")
    .attr("id", function(d) { return d.id; }) 
    .attr("class", "nodeGroup")
    .call(force.drag);

    var node = nodeGroup.append("svg:image")
    .attr("viewBox", "0 0 " + nodeImageW + " " + nodeImageH)
    .attr("class", "node")
    .attr("xlink:href", function(d)
    {
      switch( d.source )
      {
        case "GEXP": return "img/node_gexp.svg";
        case "CNVR": return "img/node_cnvr.svg";
        case "METH": return "img/node_meth.svg";
        case "CLIN": return "img/node_clin.svg";
        case "GNAB": return "img/node_gnab.svg";
        case "MIRN": return "img/node_mirn.svg";
        case "SAMP": return "img/node_samp.svg";
        case "RPPA": return "img/node_rppa.svg";
      }
    })
    .attr("width", nodeImageW)
    .attr("height", nodeImageH)
    .attr("transform", function(d) 
    {
      var matrix = "matrix(";
      var scale = 0.7; // sx & sy
      if( d.gene_interesting_score && d.gene_interesting_score > 0 )
      {
        scale = ( (d.gene_interesting_score - minScore ) / ( maxScore - minScore ) ) * 0.5 + 0.25; 
      }
      //console.log("id=" + d.id + ", score=" + scale );
      matrix += scale + ",0,0," + scale + "," + ( d.x - ( scale*nodeImageW/2 ) ) + "," + ( d.y - ( scale*nodeImageH/2 ) ) + ")";
      return matrix;
    })
    // .attr("transform", "translate(-25,-25)")
    .on("click", function(d) { 
      console.log("nodeclick");
      } )
    .on("mouseover", fade(0.10) )
    .on("mouseout", fade(node_def_opacity) );

It solves the problem #1, but not the second one: by selecting

var nodeImageH = 300;
var nodeImageW = 300;

The resulting svg image contains a lot empty space (seen by selecting the image with firebug's selection tool). The images were created in Inkscape, canvas size cropped to 50x50 which should be the correct viewing pixel size.

VividD
  • 10,456
  • 6
  • 64
  • 111
amergin
  • 962
  • 1
  • 12
  • 32

1 Answers1

3

#1 You need to set the transform origin. In SVG this means that you are going to have to use a transform matrix as answered here: https://stackoverflow.com/a/6714140/524555

#2 To solve the blurry scaling modify the viewBox, width, height of your svg files to start as large images (i.e. <svg viewBox="0 0 300 300" width="300" height="300">.

Community
  • 1
  • 1
methodofaction
  • 70,885
  • 21
  • 151
  • 164
  • Seems that #2 does not work or I'm doing something wrong, see above edit. – amergin Sep 06 '12 at 10:45
  • 1
    You can't assign a viewBox on an `image` element, you have to dig into the actual svg files and change the values there. Or just scale up the images in Inkscape (Webkit is essentially treating the svg file as if it were a bitmap, so you need a larger version if you want to keep things sharp). – methodofaction Sep 06 '12 at 10:52