-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Additional chart examples #5
Comments
@techniq I recently got into vx, but would need some input in order to figure out how to make it fast. Generally the native prop mutates dom properties directly in the dom and can thereby skip render phases. Component props on the other hand will call render on every change. D3 writes to the dom, VX calls the component tree and react renders to the dom, which sometimes isn’t fast enough for animation purposes. What we would need is something that exposes VX’es data calculation, like so:
Thereby we could render the svg once, pass an animated template to the path, which gets the actual path data from VX. Would that be possible somehow? |
@drcmda For the collapsable tree, this should definitely be possible. My biggest confusion with it is how to express a single element's new styles/state. If you look at my example at <NodeGroup
data={nodes}
keyAccessor={d => d.data.name}
start={node => {
const parentTopLeft = getTopLeft(node.parent || { x: 0, y: 0 }, layout, orientation);
return {
top: parentTopLeft.top,
left: parentTopLeft.left,
opacity: 0
};
}}
enter={node => {
const topLeft = getTopLeft(node, layout, orientation);
return {
top: [topLeft.top],
left: [topLeft.left],
opacity: [1]
};
}}
update={node => {
const topLeft = getTopLeft(node, layout, orientation);
return {
top: [topLeft.top],
left: [topLeft.left],
opacity: [1]
};
}}
leave={node => {
const collapsedParent = findCollapsedParent(node.parent);
const collapsedParentPrevPos = {
x: collapsedParent.data.x0,
y: collapsedParent.data.y0,
}
const topLeft = getTopLeft(collapsedParentPrevPos, layout, orientation);
return {
top: [topLeft.top],
left: [topLeft.left],
opacity: [0]
};
}}
> It seems like I should use <Transition
keys={items.map(item => item.key)}
from={{ opacity: 0, height: 0 }}
enter={{ opacity: 1, height: 20 }}
leave={{ opacity: 0, height: 0 }}>
{items.map(item => styles => <li style={styles}>{item.text}</li>)}
</Transition> As for the links, currently you pass an object to it's The performance of the collapsable tree hasn't been as bad as the zoomable sunburst, but I think it would be better to focus on the simpler tree first, then look at how to improve the sunburst. |
@techniq sorry for the delay, vacations... yep, the link you sent that builds the path, if we manage to make this exposable i think it's possible to make vx as fast as plain d3. Without this part, no matter what, it will always be on the slow side because React has to re-render. I think this would be the important bit to tackle first. Maybe with a very simplistic example first (perhaps the area-graph). This would set us up to make everything else fast afterwards. As for the tree, i've never done anything big in d3 before, so it's hard for me to understand what's going on or what would be needed. Transition is a very basic primitive by design, i wanted to take out some of the complexity. If it needs more, like an updater, we could extend it - all this stuff is possible with animated. |
@drcmda No worries, and I'm under a few deadlines and just looking at this as I get time 😄 The Link components are currently just convenient wrappers to:
These 2 could be split (or least be able to import the parts separately), as we would need to wrap the path data in a template like you mentioned: I don't see any changes needed for react-spring regarding this, if One change that would be very useful for me is if you could pass a function to the lifecycle props of <Transition
keys={items.map(item => item.key)}
from={item => ({ opacity: 0, height: 0 })}
enter={item => ({ opacity: 1, height: 20 })}
leave={item => ({ opacity: 0, height: 0 })}>
{items.map(item => styles => <li style={styles}>{item.text}</li>)}
</Transition> I think this would be enough for me to use react-spring to replicate the collapsible tree. For the zoomable sunburst example, I currently use For example, this is the applicable part using react-move and d3-interpolate: <Animate
start={() => {
this.xScale.domain(xDomain).range(xRange);
this.yScale.domain(yDomain).range(yRange);
}}
update={() => {
const xd = interpolate(this.xScale.domain(), xDomain);
const yd = interpolate(this.yScale.domain(), yDomain);
const yr = interpolate(this.yScale.range(), yRange);
return {
unused: t => {
this.xScale.domain(xd(t));
this.yScale.domain(yd(t)).range(yr(t));
},
timing: {
duration: 800
}
}
}}
> |
@techniq So basically it's just a 0-1 toggle. I was confused about how you pass a zero object in there. So that would be a simple reset spring: https://codesandbox.io/s/nww6yxo0jl <Spring reset from={{ t: 0 }} to={{ t: 1 }}>
{({ t }) => {
this.xScale.domain(xd(t))
this.yScale.domain(yd(t)).range(yr(t))
return (
<Group top={height / 2} left={width / 2}> It still re-renders frame by frame of course, but the implementation is more logical to me. To make Transition read functions for each item so that you can give them individual values, that shouldn't be hard at all. Are you interested in lending a hand? I imagine we could really do something awesome here, VX could basically reach native speeds if react-spring (and vx on the exposing functions side) would be a little bit more flexible. |
@drcmda Nice! Pulling the const xd = interpolate(this.xScale.domain(), xDomain)
const yd = interpolate(this.yScale.domain(), yDomain)
const yr = interpolate(this.yScale.range(), yRange)
return (
<svg width={width} height={height}>
<Partition top={margin.top} left={margin.left} root={root}>
{({ data }) => (
<Spring reset from={{ t: 0 }} to={{ t: 1 }}>
{({ t }) => {
this.xScale.domain(xd(t))
this.yScale.domain(yd(t)).range(yr(t))
return ( |
Yeah, i just updated it : D Looks ways smoother like that. That was me having no idea what this d3 stuff is actually doing. |
I'm assuming we could use Animated to do the interpolation of the values as well... |
First draft that renders the chart natively: https://codesandbox.io/s/nww6yxo0jl I added an "interpolate" function that takes an animated value and calls you back with its actual value, which you can then pass to vx/d3/... to obtain the end-result that's going to be written into the path. .map((node, i) => (
<animated.path
d={interpolate(t, t => this.calculate(t, xd, yd, yr, node))} If you profile the component, it renders only once, that is, all 250 or so nodes render once. React is not involved any longer in the animation. I still see ripped frames, but that's perhaps something else, maybe domain/range are expensive - who knows. Does this go into the direction you want it to? |
@drcmda performance wise this looks great. I've noticed a little jank/stutter towards, primarily on mobile, but it is vast improvement over what I had. Regarding the api, I was thinking/hoping I could get rid of the use of Instead of the current... render() {
const { root, width, height, margin = { top: 0, left: 0, right: 0, bottom: 0 } } = this.props
const { xDomain, yDomain, yRange } = this.state
if (width < 10) return null
const xd = d3interpolate(this.xScale.domain(), xDomain)
const yd = d3interpolate(this.yScale.domain(), yDomain)
const yr = d3interpolate(this.yScale.range(), yRange)
return (
<svg width={width} height={height}>
<Partition top={margin.top} left={margin.left} root={root}>
{({ data }) => (
<Spring native reset from={{ t: 0 }} to={{ t: 1 }} config={{ tension: 200, friction: 50 }}>
{({ t }) => {
t.addListener(({ value }) => {
this.xScale.domain(xd(value))
this.yScale.domain(yd(value)).range(yr(value))
})
return (
<Group top={height / 2} left={width / 2}>
{data
.descendants()
.map((node, i) => (
<animated.path
d={interpolate(t, () => this.arc(node))}
stroke="#fff"
fill={color((node.children ? node.data : node.parent.data).name)}
fillRule="evenodd"
onClick={() => this.handleClick(node)}
key={`node-${i}`}
/>
))}
</Group>
)
}}
</Spring>
)}
</Partition>
</svg>
)
} Possibly to something like: render() {
const { root, width, height, margin = { top: 0, left: 0, right: 0, bottom: 0 } } = this.props
if (width < 10) return null
return (
<svg width={width} height={height}>
<Partition top={margin.top} left={margin.left} root={root}>
{({ data }) => (
<Spring
native
reset
from={{ xd: this.xScale.domain(), yd: this.xScale.domain(), yr: this.yScale.range() }}
to={{ xd: this.state.xDomain, yd: this.state.yDomain, yr: this.state.xRange }}
onFrame={({ xd, yd, yr }) => {
this.xScale.domain(xd)
this.yScale.domain(yd).range(yr)
}}
config={{ tension: 200, friction: 50 }}>
{({ t }) => {
return (
<Group top={height / 2} left={width / 2}>
{data
.descendants()
.map((node, i) => (
<animated.path
d={interpolate(t, () => this.arc(node))}
stroke="#fff"
fill={color((node.children ? node.data : node.parent.data).name)}
fillRule="evenodd"
onClick={() => this.handleClick(node)}
key={`node-${i}`}
/>
))}
</Group>
)
}}
</Spring>
)}
</Partition>
</svg>
)
} I don't know about the name Just a thought (and I'm likely overlooking something). |
Looks reasonable. I added it as You can certainly animate array values like in your last example, though i copied your code and it did nothing. Probably still something to do with the the domain stuff. Just be aware that |
My code was "pseudo" to express the idea (I didn't expect it to work) :). The idea is to encapsulate the tweening of the domain/range values as much as possible (to keep the api clean). While on the topic of api, could <Transition
keys={items.map(item => item.key)}
from={item => ({ opacity: 0, height: 0 })}
enter={item => ({ opacity: 1, height: 20 })}
leave={item => ({ opacity: 0, height: 0 })}>
{items.map(item => styles => <li style={styles}>{item.text}</li>)}
</Transition> |
Having functions is no problem, it's live under <Transition
keys={items.map(item => item.key)}
from={key => ({ opacity: 0, height: 0 })}
enter={key => ({ opacity: 1, height: 20 })}
leave={key => ({ opacity: 0, height: 0 })}>
{items.map(item => styles => <li style={styles}>{item.text}</li>)}
</Transition> Can you give it a try and see if it's fine? |
Another quick question, the <Transition
keys={items.map(item => item.key)}
from={key => ({ opacity: 0, height: 0 })}
enter={key => ({ opacity: 1, height: 20 })}
leave={key => ({ opacity: 0, height: 0 })}>
{items.map(item => styles => <li style={styles}>{item.text}</li>)}
</Transition> For instance, could I peel off the props and pass them as other props? <Transition
keys={items.map(item => item.key)}
from={key => ({ x: 0, y: 0 })}
enter={key => ({ x: 1, y: 20 })}
leave={key => ({ x: 0, y: 0 })}>
{items.map(item => styles => <rect x={styles.x} y={styles.y}>{item.text}</rect>)}
</Transition> Once again, arbitrary, just understanding what is possible. I plan to look at this more tonight (need to get back to my other priorities for now). If you get add Thanks again for all your help. |
Correct. If you're feeding regular styles and/or props, you can use
It's in the latest beta. I'll publish all this stuff officially (we've worked out reset, interpolate, onUpdate and functional styles so far) when your examples run. |
Awesome. I hadn't seen any commits/branches on Github so I wasn't sure what was pushed, but I figured out you were pushing npm betas from your local working copy. I'll hope to get to it this evening (if all goes well, but it might be another night or two as my kids' have practices and I'm under some work deadlines). Once again, thanks for the awesome work 😄. All of this looks very promising. Btw, a few other animation patterns I might look into after we get some VX patterns working are some of these react-flip-move examples I made: and some of the examples for react-morph. Not that we need one library too rule them all, but it seems from all of your examples, the few components can go a long way. |
@drcmda I spent a little time on it tonight, but ran into a few issues. See
Anyways, I didn't have a bunch of time tonight and likely overlooking something, but wanted to give you an update. Speaking of |
Live in beta.7 Should fix all your issues. update => onUpdate, keys can return the actual objects if you supply an The reason i went for edit I'm a little confused for what you need update. onUpdate isn't meant to return animated props, it's basically just a callback. from/enter/leave should know the positions where nodes should spring to. It seems to work: https://codesandbox.io/s/9jrjqvq954 only that after the second time when new nodes come up, the y offset seems off for some reason. |
@drcmda thanks. I just got a moment to look at this again. Being able to pass all the nodes to Regarding Here is the comparison between the react-move ( react-movereact-spring*note: only the nodes are using react-spring, the links are not (was waiting until I extracted the path generators) |
You don't need to set keys because each element is wrapped in a spring internally, Transition sets keys by itself. items/keyAccessor pair and keys is fine with me. I'll add it in the next beta. As for update, it just dawns me that this isn't the same as onUpdate (the frame-by-frame callback) at all. If i understood what it needs to do i can add it. react-move says:
But ... don't enter/leave already describe it? I am confused as to where the difference is and what exactly triggers it, or in other words, what constitutes an update. For instance, when T is clicked, it updates, but how does Transition know that? Do you trigger it elsewhere? |
Ok, looks like "update" means all children that neither were added nor removed. I made a quick test and it looks like that's it: https://codesandbox.io/s/9jrjqvq954 There's some cleaning-up to do with the naming of it all, but i'll do that tomorrow. |
@drcmda I was just writing something up, but yes 😄 , and thank you. Btw, the |
@drcmda that's still using react-spring for the links as well 😁. I have a deliverable due Monday that I need to focus on, but I hope to get around to getting the path generators exported this weekend possibly (and see if @hshoff can cut a release afterwards). not going to lie, I'm having trouble telling the exact improvement between react-move and spring based on the performance charts (especially with different zoom / selection). native shows |
the overall amount is just for the time it took for the snapshot. I only recorded half a sec for the first and a bit more for the second. The important part is the timeline: react is totally out of the loop except the first render and the forceupdates, all frames are around 15ms, so it runs smooth. In the other react is taking a large chunk of the main thread, causing frames to blow up to 50ms, which rips into a smooth animation. |
@drcmda thanks for the explantation :) |
Links are updated, too. Don't know how to do it natively here, it uses VX props. I'll take a nap. Thanks for all the input and work! |
@drcmda hope it's not 4:30am there (based on your github profile). Have a good rest :) |
@drcmda I'm not sure if I'll get time to tackle airbnb/visx#263 this weekend with other commitments, but it doesn't look like you'll need it (other than for better path performance on the tree example). Btw, regarding examples, here's some ideas / suggestions:
I've been collecting a lot of examples/inspiration but thought these might be a good start. I hope over the coming month or so to start building out some visuals using react-spring and vx that I'll share. Lastly, regarding the react-flip-move / react-morph examples mentioned above, it might be a cool new component that uses the FLIP technique to calculate the from/to states automatically (using Thanks again for all your work on react-spring. With your upcoming release, it definitely looks like a great foundation to use with |
@drcmda btw, at this point I'm cool with closing this issue and creating other issues for say the |
I collected some demos, three of them have vx stuff in them: https://o48nx3n19z.codesandbox.io/ I think it's fine to get the idea across. All these are under /examples as well. Flip move and react morph is very interesting as well, it would be nice to have an easy primitive for that, a new topic to flesh this out would be great. Overall i think we've made quite some progress. The biggest questionmark for me atm is how to promote it. I feel like barely anyone has even heard of it or knows it exists. Would be great if it had more contributors so it could grow into something that really covers ground, and animated underneath is maybe the perfect base to make this happen. There are so many things to explore, from primitives to react-native to a truly native web-driver (web-animations). |
I don't have much outreach myself, but I'll see what I can do about promoting it. Harrison Shoff (works at Airbnb, creator of VX and Airbnb style guide) and Elijah Meeks (works at Netflix, does a lot of work in d3, including https://github.com/emeeks/semiotic and a few books) among others hang out in the VX Slack channel and we've had some discussions about animation / etc. I think they'll be very receptive to promoting it. |
That would be awesome! I'm always open to suggestions should they or you have specific animation related wishes - so it could benefit VX as well. |
This thread is very helpful. Hey, @techniq your experiments with these different APIs is really interesting work. I found this because I was thinking about trying react-spring for a new project. I'm a little hazy on the outcome here in this thread. Did you find that you were able to handle the "d3 style" updates using react-spring? Were there things you couldn't do? Might need to just dig in here, but it would be great to hear your thoughts. One of the key pieces is how interrupts get handled, not sure if you got into that at all. @drcmda, this idea of porting over the native animated library is really great. Don't understand it completely, but it's clever and performs really well. I'm wondering if it would make sense to offer the animated components as a standalone package. Would that make any sense or is everything in this repo pretty tightly bound together? Probably just need to dig into the code a bit, but just wanted to see what you thought about this. |
@sghall I was using vx and had great success with it and react-spring. I haven't looked at them in probably a year though, especially with the later versions of react-spring / hooks, but they paired very well when I was. Here are two good examples
I hope to have to get back into this in the next few months (a few features planned) and coincidentally someone was asking me about this today (after many many months). In fact you were in that discussion which might be what triggered your comment :) |
First, let me say this library looks great.
I am one of the contributors to vx and I am attempting to port a few of my animation examples from react-move to use react-spring. I've had performance issues with react-move on occasion and wanted to see how well react-spring performed, especially when leveraging the
native
prop.I'm currently struggling with how my current react-move implementations port over to react-spring (and if they even can) and was hoping you could give some guidance (or show me how it's done 😄).
Collapsable tree
The first is a collapsable tree that animates the nodes (and links between them). When expanding a node, the child nodes will "zoom out" from their parent, and when collapsing, the child nodes will "zoom in" into their parent's previous location. In react-move, within each lifecycle hook (
start
,enter
,update
,leave
) you pass a function that takes in a node instance and you return an object (as opposed to just setting an object, like react-spring does).(see
NodesMove.js
in codesanbox, but the gist is...)I'm not sure if using
<Transition>
can work in this situation, or if I'm missing something. Also, the location of a node's previous parent isn't always correct based on the order you close/open nodes, but it works OK.Zoomable sunburst / partion
In these examples, I tween the domain and ranges of the x and y scales to collapse/hide the sunburst / partition pieces. I'm especially having difficulty wrapping my head around how to use
Transition
/Spring
to replicate this.Also, my react-move example was never as smooth as the original d3 example so I have high hopes this will work better with react-spring.
Any help you could provide on how to replicate these examples would be much appreciated.
The text was updated successfully, but these errors were encountered: