D3-Transform: an extension for the transform attribute

1,049 views
Skip to first unread message

Erik Cunningham

unread,
Apr 8, 2013, 7:24:07 PM4/8/13
to d3...@googlegroups.com
Hello all!

I'd like to announce and bring your attention to a d3 extension: d3-transform. It's a simple API wrapping the creation and composition of functions to set the "transform" SVG attribute.  You can get the source here, and the README has some examples that I'll go over below:

https://github.com/trinary/d3-transform

I found myself doing a lot of clumsy string manipulation to set transform attributes nearly every time I used d3, so I wrote an initial implementation of a transform API that added translate(), rotate(), etc functions to d3.selection.  However, there were a number of drawbacks to this approach.  More work would be required to use these functions in transitions, composition and reuse wouldn't be possible, and so on.

After discussing it a bit in the #d3js IRC channel, Spiros Eliopoulos (https://twitter.com/seliopou, https://github.com/seliopou) wrote an implementation of the same idea as a separate object, d3.svg.transform.

Creating a transform function looks like this (from the README):

var transform = d3.svg.transform()
    .translate(function(d) { return [20, d.size * 10] })
    .rotate(40)
    .scale(function(d) { return d.size + 2 });

One can then use the resulting function to set the transform attribute:

var svg = d3.select('svg.example1').selectAll('g')
    .data([{ size: 5 }, { size: 10 }])
    .enter()
    .append('g')
    .attr('transform', transform);

Resulting in two "g" elements with a transform attribute like you'd expect:

<svg>
  <g transform="translate(20,50) rotate(40) scale(7)"></g>
  <g transform="translate(20,100) rotate(40) scale(12)"></g>
</svg>

For unchanging values, you can just pass in the parameters of each transform operation as arguments to the corresponding function.  For dynamic values, pass in a function that returns an array of parameters to the transform operation.  You can reuse it, pass it around, and compose it with other d3.svg.transform objects by passing an existing transformation into the d3.svg.transform() function:

var transform1 = d3.svg.transform()
  .translate(10,20);

var transform2 = d3.svg.transform(transform1)
  .scale(function(d) { return [d.size];})

transform2 takes the existing operations from transform1 and appends a data-dependent scale operation.

This lets you keep different parts of a transformation separate and re-useable, and extend them to add further transformation operations.

I think this adds a nice interface to what is very frequently an awkward but necessary step in constructing an SVG visualization, so I'd love for people to try it out.  Feedback and comments are much appreciated.  If you spot something you want changed or added, open an issue on GitHub! I've also added it to NPM for convenience, but I'm an NPM newbie so let me know if that can be done better.  Thanks!

https://github.com/trinary/d3-transform
https://npmjs.org/package/d3-transform

--Erik (https://twitter.com/trinary, https://github.com/trinary)

Scott Murray

unread,
Apr 8, 2013, 7:25:46 PM4/8/13
to d3...@googlegroups.com
How awesome is that?! I love it. Thanks for this!

Scott

marc fawzi

unread,
Apr 8, 2013, 8:07:22 PM4/8/13
to d3...@googlegroups.com
I could get carried away (it won't work) and insert .transitions()'s in that chain:

var transform = d3.svg.transform()
    .translate(function(d) { return [20, d.size * 10] })
    .transition()
     .duration(750)

    .rotate(40)
    .scale(function(d) { return d.size + 2 });


which won't work ... but why couldn't you simply split a transform with .transition() in it into multiple transform statement such that the effect of passing the transform above to this:

var svg = d3.select('svg.example1').selectAll('g')
    .data([{ size: 5 }, { size: 10 }])
    .enter()
    .append('g')
    .attr('transform', transform);


would be this:


var svg = d3.select('svg.example1').selectAll('g')
    .data([{ size: 5 }, { size: 10 }])
    .enter()
    .append('g')
    .attr('transform', transform[0]);
    .transition()

    .duration(750)
    .attr('transform', transform[1])

If you can make it do that (which I believe is doable) then it would pack more power

I haven't thought about the bad consequences of doing so, but that's what I'd want to do with it to make it fun


On Mon, Apr 8, 2013 at 4:25 PM, Scott Murray <s...@alignedleft.com> wrote:
How awesome is that?!  I love it.  Thanks for this!

        Scott

--
You received this message because you are subscribed to the Google Groups "d3-js" group.
To unsubscribe from this group and stop receiving emails from it, send an email to d3-js+un...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.



Spiros Eliopoulos

unread,
Apr 8, 2013, 8:45:10 PM4/8/13
to d3...@googlegroups.com
Not exactly a response to your question but here's how you could accomplish what you want to do without changing the API:

var t1 = d3.svg.transform()
    .translate(function(d) { return [20, d.size * 10] });
    
var t2 = d3.svg.transform(t1)
    .rotate(40)
    .scale(function(d) { return d.size + 2 });
    
d3.select('svg.example1'). selectAll('g')
    .data([{ size: 5 }, { size: 10 }])
  .enter().append('g')
    .attr('transform', t1)
  .transition()
    .duration(750)
    .attr('transform', t2);

Note that when you create t2, you pass t1 into d3.svg.transform(). This will transition _in_ the rotate and scale while _preserving_ the translation. If you wanted to transition in the rotate and scale while transitioning _out_ the translation, you'd omit t1 as an argument to d3.svg.transform() when you create t2.

Hope that helps.

-Spiros E.

marc fawzi

unread,
Apr 8, 2013, 9:13:59 PM4/8/13
to d3...@googlegroups.com
yes obvious from the initial description

keep it coming and hope to meet you guys on IRC

Erik Cunningham

unread,
Apr 9, 2013, 2:08:36 AM4/9/13
to d3...@googlegroups.com
Come on by any time.  Freenode, #d3.js. :)
Reply all
Reply to author
Forward
0 new messages