Proposal: Transition reselection.

331 views
Skip to first unread message

Mike Bostock

unread,
Oct 5, 2012, 5:03:39 PM10/5/12
to d3...@googlegroups.com
Please give feedback on this pull request:

https://github.com/mbostock/d3/pull/844

This commit allows you to reselect elements with scheduled transitions
and redefine associated tweens; this enables "post-selection" to
customize the behavior of reusable components undergoing transitions,
such as an axis. This commit also makes it much easier to sequence
transitions.

Previously, a transition's tweens were stored privately by the
transition and could only be accessed through the transition. This
made it impossible to modify transitions created by components: the
transition is not accessible externally, and cannot be reselected from
the document. Consider the following snippet:

g.select(".x.axis")
.call(xAxis)
.selectAll("text")
.attr("dy", null);

If `g` is a selection, then this code alters the appearance of the
axis as expected. However, if `g` is a transition, then
transition.selectAll creates a new concurrent transition, and now
multiple tweens compete to set the "dy" attribute. Oy!

Under the new design, an element's scheduled tweens are stored
semi-privately on the node (in the existing node.__transition__).
Transition parameters can thus be reselected and modified by
transitions that share the same id. If you now reselect a
transitioning element, you modify the transition rather creating a
competing transition; this should be less surprising and allow greater
control.

As a side-effect of this change, it is no longer possible to schedule
concurrent transitions on the same element, even with the same id:
only one transition may be active on a given element at any time.
(Note that you can still schedule multiple future transitions on the
same element, and concurrent transitions on different elements.) For
example, you could previously schedule overlapping transitions with
different easing functions, delays or durations, provided you were
careful to avoid conflict. This seems like a relatively obscure
use-case compared to modifying a transition, so I believe this is a
reasonable change.

This commit also changes transition.transition, such that the returned
transition starts at the end of the originating transition, rather
than overlapping. This makes it much easier to schedule sequenced
transitions without the complexity of transition.each("end") and
d3.select(this).

Also, transitions are now simply arrays of nodes, consistent with selections!

Here's a code snippet showing sequenced transitions:

https://github.com/mbostock/d3/blob/223d6aa7153736b7904e1c60e0057440caa92c8b/examples/stream/stack.js#L78-86

Mike

marc fawzi

unread,
Oct 5, 2012, 5:09:58 PM10/5/12
to d3...@googlegroups.com
<<
For
example, you could previously schedule overlapping transitions with
different easing functions, delays or durations, provided you were
careful to avoid conflict. This seems like a relatively obscure
use-case compared to modifying a transition, so I believe this is a
reasonable change.
>>

I'm using that to dynamically/conditionally rescale a moving sparkline
based on min/max of dataset in view while also moving the line to the
left (scrolling)

So if this breaks it, how would we go about doing this when we need to
apply rescale and movement in overlapping transitions?

graph.selectAll("path")
.data([viewData]) // set the new data
.attr("transform", "translate(" + x(1) + ")")
.transition()
.duration(1000)
.attr("d", lineAdj)
.transition()
.ease("linear")
.duration(1000)
.attr("transform", "translate(" + x(0) + ")")e

Mike Bostock

unread,
Oct 5, 2012, 5:15:11 PM10/5/12
to d3...@googlegroups.com
> So if this breaks it, how would we go about doing this when we need to
> apply rescale and movement in overlapping transitions?

In your case, I'd recommend one transition on a parent <g> element to
translate the sparkline (or sparklines), and a child transition on the
path elements to change the path data.

Alternatively you could use a single transition with linear easing. If
you wanted, you could define a custom attr tween to add cubic easing
to just the d attribute.

Mike

marc fawzi

unread,
Oct 5, 2012, 5:23:30 PM10/5/12
to d3...@googlegroups.com
The single transition with linear easing worked great. Just tried it.
No idea why I thought I needed two transitions.

Thanks Mike, and the proposal sounds like it will refine things.

marc fawzi

unread,
Oct 5, 2012, 5:26:41 PM10/5/12
to d3...@googlegroups.com
Deleted duplicate form github commit thread

I assume .each("end", function(d) { d3.select(this) ..}) will still work, right?

Jason Davies

unread,
Oct 5, 2012, 5:40:54 PM10/5/12
to d3...@googlegroups.com
On Fri, Oct 05, 2012 at 02:03:39PM -0700, Mike Bostock wrote:
> g.select(".x.axis")
> .call(xAxis)
> .selectAll("text")
> .attr("dy", null);
>
> If `g` is a selection, then this code alters the appearance of the
> axis as expected. However, if `g` is a transition, then
> transition.selectAll creates a new concurrent transition, and now
> multiple tweens compete to set the "dy" attribute. Oy!

Excellent! So the snippet above is an example of overriding the default
d3.svg.axis "dy" attribute on the text elements, which was previously
impossible to do properly if there was a transition occurring, but will
work with this proposal.

This will make using transitions with reusable charts and components
much easier.

> As a side-effect of this change, it is no longer possible to schedule
> concurrent transitions on the same element, even with the same id:
> only one transition may be active on a given element at any time.
> (Note that you can still schedule multiple future transitions on the
> same element, and concurrent transitions on different elements.) For
> example, you could previously schedule overlapping transitions with
> different easing functions, delays or durations, provided you were
> careful to avoid conflict. This seems like a relatively obscure
> use-case compared to modifying a transition, so I believe this is a
> reasonable change.

I think it's quite rare for this to really be necessary. In d3-parsets
[1], the ribbon paths can transition independently in both x- and
y-directions, for example if you drag a category it will animate all the
other categories bouncing into their new positions, and if you quickly
reorder the dimensions too, they will simultaneously animate into their
new y-positions. Even this wouldn't be affected though, since these
independent transitions are scheduled on different elements.

> This commit also changes transition.transition, such that the returned
> transition starts at the end of the originating transition, rather
> than overlapping. This makes it much easier to schedule sequenced
> transitions without the complexity of transition.each("end") and
> d3.select(this).

Nice! Less typing. :)

[1]: http://www.jasondavies.com/parallel-sets/

--
Jason Davies, http://www.jasondavies.com/

Mike Bostock

unread,
Oct 5, 2012, 5:45:12 PM10/5/12
to d3...@googlegroups.com
> I assume .each("end", function(d) { d3.select(this) ..}) will still work, right?

Yep!

Mike

Bob Monteverde

unread,
Oct 5, 2012, 6:57:10 PM10/5/12
to d3...@googlegroups.com
I need to mess around with this a bit, but sounds awesome.

Bob

Scott Cameron

unread,
Oct 9, 2012, 1:20:37 PM10/9/12
to d3...@googlegroups.com
Sounds awesome.  This has potential to significantly clean up some of my transition handling code.  Very nice improvement!

  

Mike Bostock

unread,
Oct 13, 2012, 3:18:23 PM10/13/12
to d3...@googlegroups.com
Here's a nice demo showing how you can now apply staggered delays to
ordinal axis transitions:

http://bl.ocks.org/3885705

By reselecting transitioning elements, you can change the delay,
duration and even easing.

Related: I'm rebuilding the "introductory" examples for D3 and porting
them to http://bl.ocks.org. The current examples in the git repository
are a mix of manual tests, demonstrations of functionality, and
flexing (e.g., showreel). I'd like introductory examples to be as
simple as possible while still representative of real-world usage. So,
for example, I've replaced synthetic data (such as a sine wave) with
real data loaded from a file, e.g.,

http://bl.ocks.org/3883245

If there's something (simple) that you want to see included in these
pedagogical examples, please let me know!

Mike

Kai Chang

unread,
Oct 15, 2012, 11:55:56 AM10/15/12
to d3...@googlegroups.com
I've been using this type of chart recently:

http://bl.ocks.org/3884955

One issue I've hit is label crowding:

http://bl.ocks.org/3891711

I was going to look into a 1-D force layout after initial placement,
but if you have a good solution, it's a nice way to label line charts.

Also how do you upload pngs to gist? I've got thumbnails ready to go.

Mike Bostock

unread,
Oct 15, 2012, 12:22:09 PM10/15/12
to d3...@googlegroups.com
> I was going to look into a 1-D force layout after initial placement,
> but if you have a good solution, it's a nice way to label line charts.

I've solved that problem in the past using constraints relaxation;
I'll make a demo if I have time. The strategy is to detect overlapping
labels and then push them apart slightly, and then iterate a few
times.

> Also how do you upload pngs to gist? I've got thumbnails ready to go.

Use the git interface rather than the web interface. This script might help you:

https://gist.github.com/3883098

Mike

Ziggy Jonsson

unread,
Oct 15, 2012, 4:18:59 PM10/15/12
to d3...@googlegroups.com, kai.s...@gmail.com
While not a perfect solution, the force_labels plugin (https://github.com/d3/d3-plugins/tree/master/force_labels)  might help reducing label crowding.  
Simply define a label force with the appropriate parameters and  .call(labelForce.update) on whatever svg objects you want as anchors. 
The .__data__ attribute will subsequently contain an updated labelPos attribute for the corresponding label.  
Positioning of the anchor is assumed to be center of the SVG bounding box of each supplied anchor object..

Here is a quick example:

(To keep all labels in-line I fix the x value at each iteration)

Best regards,
Z

Kai Chang

unread,
Oct 15, 2012, 4:26:03 PM10/15/12
to d3...@googlegroups.com
I knew I had stolen the idea from somewhere. I merged force_labels 2
months ago and totally forgot about it. Thanks Ziggy!

Ziggy Jonsson

unread,
Oct 15, 2012, 11:55:42 PM10/15/12
to d3...@googlegroups.com, kai.s...@gmail.com
If anything was "stolen", it was from Mike :) The plugin is just a quick boilerplate wrapper around the force layout (responsible for the results).
I'm sure that more simple constraints relaxations as Mike suggested might be better and more lightweight in many instances, but the force layout has proven to be extremely flexible in my experience.

The only "issue" I have had, is that I'm using the same .__data__ object for both the anchor and the label (the magic of the plugin), and therefore it's not obvious how to set the anchor charges to zero (when needed) as .charge(function(...)) is based on the data object not the svg.

Scott Cameron

unread,
Oct 16, 2012, 2:07:04 PM10/16/12
to d3...@googlegroups.com

Hi Mike,

One additional question about the proposal that just occurred to me.  Does this change the behavior of interrupting/clobbering transitions at all?  For example, if I'm running a transition on set of elements and then I use a completely new selection to select those elements and create a new transition (assume conflicting delay, duration and so on), will it still clobber the original transition and prevent that transition's remove() and each("end",...) from being run?

My impression from your description is that transition modification is only possible from the original transition instance (with the same id) but I wanted to make sure.

Cheers,
scott

Mike Bostock

unread,
Oct 16, 2012, 2:35:22 PM10/16/12
to d3...@googlegroups.com
> My impression from your description is that transition modification is only
> possible from the original transition instance (with the same id) but I
> wanted to make sure.

Your interpretation is correct; this behavior is unchanged.

Mike

Mike Bostock

unread,
Oct 24, 2012, 4:09:10 PM10/24/12
to d3...@googlegroups.com
I've written a new tutorial that covers many heretofore undocumented
(or tersely documented) aspects of D3's transition system:

http://bost.ocks.org/mike/transition/

This tutorial covers many of the changes to transitions in the
forthcoming 3.0 release, and hopefully it will help shed some light on
how to implement transitions. Let me know if I can clarify anything
further.

Cheers,
Mike

Peter Rust

unread,
Oct 25, 2012, 2:48:08 PM10/25/12
to d3...@googlegroups.com
Excellent tutorial!

One errata:

> Transitions have a four-phrase life cycle
should be "four-phase".

Also, regarding "JavaScript formats small numbers in exponential notation, which unfortunately is not supported by CSS" -- would it be possible for d3 to (performantly) detect when this happens and convert the number to something that CSS can handle? It would be great if this problem were encapsulated by d3, so users didn't have to stumble into it and then try to remember to always use 1e-6 instead of 0.

Kai Chang

unread,
Nov 10, 2012, 7:25:09 PM11/10/12
to d3...@googlegroups.com
This thread got a bit off-topic, but here is an example of 1-D
constraint relaxation to fix label crowding on the line chart posted
earlier.

http://bl.ocks.org/4053096

Solution based on the Dorling Cartogram example:
http://d3-example.herokuapp.com/examples/cartogram/dorling.html

This doesn't require the force layout, it just recursively calls
relax() until no labels need to be adjusted.
Reply all
Reply to author
Forward
0 new messages