unkleho / d3-render

Declarative and reusable D3. Replace select, append, data and more with one function.
MIT License
177 stars 11 forks source link

Fine grained durations, custom transitions and discussion #2

Open stardisblue opened 4 years ago

stardisblue commented 4 years ago

Hi,

I'm experimenting with this amazing library and I wanted to talk about some features that I would like to be present.

Most of them concern transitions.

1. Different transition durations per attributes

Let's imagine that we wish the width to change in 100ms and the height in 1000ms (maybe allow this behaviour with delay but that may be too much)

const datum = {
    append: "rect",
-   width: {enter: 100, exit: 0},
+   width: {enter: 100, exit: 0, duration: 100},
    height: 100,
    duration: 1000 // default duration
};

2. Pass transition object instead of duration, delay and ease.

I found myself using the same values in different components, which boils down to something like

const t = {duration: 250, delay: 10, ease: d3.easeQuadInOut};
// later
const ExampleComponent = (c) => ({
    append: "g",
    // ...
    ...t,
    ...c
})

I feel that it could be more esthetic to directly pass a d3.transition object

const transition = d3.transition()
    .duration(250)
    .delay(10)
    .ease(d3.easeQuadInOut);

// or
// const transition = {duration: 250, delay: 10, ease: d3.easeQuadInOut};

// ...later
const ExampleComponent = (c) => ({
    append: "g",
    // ...
    transition,
    ...c
})

Which might also allow for transition chaining shenanigans.

3. Datum components

When using d3-render, I cannot interact the with the component I have currently selected:

const $svg = d3.create('svg')
    .attr('width', "100")
    .attr('height', '100')
    .attr('viewBox', [0, 0, 50, 50]);

render($svg, [/*data*/]);

If I want to animate $svg's attributes, i need to use $svg.transtion()..., where something like this is waay better.

// by the power of d3-render
render(d3.create('svg'), {
    width: 100, 
    height, 100, 
    viewBox: [0, 0, 50, 50], 
    duration: 100, 
    children: [/*data*/]
})

4. Self controlled components

This is placed last, because it goes against the data->render flow. Because it might be an interesting case.

I wish my components had a react-like state/props approach. Where I could have some data which is passed down by data and the rest is self managed.

working example : https://observablehq.com/@stardisblue/d3-render-self-controlled-components

In this example, the components are setup using call. But call is not triggered during "update" state. So I cannot do a state/props setup (as in react for example). This is more a defaultstate/state approach.

Conclusion

This issue might be arguably the worst issue github as ever seen, as it presents no errors to fix and proposes way too much features. I was tempted to make several issues out of this one, but that felt like spam. So here are several features that I will be more that happy to see included. I can pull request you some of them but I feel like these [propositions] need an Okay from your side.

unkleho commented 4 years ago

Hi Stardisblue,

Thanks for these suggestions - it is early days for the library so I'm totally up for general discussions.

1. Different transition durations per attributes

I've been thinking about this feature too and I'm glad you raised it. I like the API you have proposed, plus delay and ease could work too:

const datum = {
  append: "rect",
  width: {enter: 100, exit: 0, duration: 100, delay: 100, ease: d3.easeQuadInOut},
  height: 100,
  duration: 1000 // default duration
};

Would it be crazy to also have different enter/exit transitions per attribute too?

const datum = {
  append: "rect",
  width: {enter: { value: 100, duration: 100 }, exit: { value: 0, duration: 200 },
  height: 100,
  duration: 1000 // default duration
};

This feature is certainly worthy of a separate issue for discussion. Feel free to create one if you'd like.

2. Pass transition object instead of duration, delay and ease.

Yes this is a tough one. I did originally think it would be nice to group those in a transition object, but having it flat along with everything else also makes it a bit easier to get started.

Perhaps it is just me, but I quite like destructuring objects like in your example. It also makes overriding the transition settings a bit easier than passing d3.transition, eg:

const t = {duration: 250, delay: 10, ease: d3.easeQuadInOut};

const ExampleComponent = (c) => ({
  append: "g",
  ...t,
  ...c
})

render('svg', [ExampleComponent({
  r: 100,
  // Overrides duration: 250 only
  duration: 500
})]

Not having funky chaining shenanigans is the tradeoff here, but perhaps we can do it by passing an array of transition objects to each attribute. I'll sketch something up in another issue.

3. Datum components

Hmm, this is an interesting one. By design, D3 Render needs a mount point, some div or svg, but can only change the elements inside. Perhaps if you would like control of the svg element, could you try this?:

render('#container', [{
  append: 'svg',
  width: 100, 
  height, 100, 
  viewBox: [0, 0, 50, 50], 
  duration: 100, 
  children: [/*data*/]
}])

4. Self controlled components

This is seriously cool what you have done there. I'll have to dig a bit deeper into your code, but it is definitely a something I'd be keen to look into. A React like props/state rendering system would make interaction really nice.

Thanks again for your feedback.

ms007 commented 4 years ago

Different enter/exit transitions would be very useful for my use-case.

I need to have a different transition duration on enter and exit.

const datum = {
  append: "circle",
  r: {enter: { value: 100, duration: 400 }, exit: { value: 0, duration: 50 },
  cx: 100,
  cy: 100,
  duration: 400 // default duration
};
ms007 commented 4 years ago

I like destructuring objects like in your example as well.

But Passing a d3.transition object would have the benefit of being able to use the other functions on d3 transitions as well like for example being able to pass custom functionality on transition.end when all the transitions have finished.

https://github.com/d3/d3-transition/blob/master/README.md#transition_end

Or how could an api for such use case look like in d3-render?

      svg
        .transition()
        .duration(600)
        .attr('width', width)
        .attr('height', height)
        .end()
        .then(() => {
          this.width = width;
          this.height = height;
        });