Help with my first "micro-library": optimising a custom directive #10766
Unanswered
serman
asked this question in
Help/Questions
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Hello!
Context
I work developing chart and data viz stuff. In the old times, before reactive frameworks, we used to do everything with the D3.js library.
Nowadays I use d3 to help with the maths, scales etc.. However I create SVG charts using a declarative syntax in VUE.
I am happy with that but one important piece is missing. The chart animations. In D3 they have this fantastic transition() function which animates the different properties from the SVG as data changes.
To do that in VUE I could use https://motion.vueuse.org but it seems that it is restricted to some attributes. I tried to add functionalities there but the code is too complex for me to understand.
So I am writing a small library to achieve similar results using a custom directive. Before releasing it, I would like a second opinion about the unorthodox methods I am using. So any comments are welcome.
The "library"
The syntax I am using in the directive is
In this case, I am animating the width property. When it starts the base value is 0 and then it will grow to the value returned by widthScale(bar.count)
this is the easy case: We have an init value, and then we will use a "tween function" to go from init to target in the amount of time defined in the "transition options object"
This is the part of the code that animates that transition within the library
The library uses d3 functions to do all the maths in the transitions: "timer" and "interpolate"
This function is called in the mount and updated hook.
VUE stuff
vnode.el._timer = timer((elapsed) => ...
I am not sure this is a good practice but I have tried other ways and it doesn't workI need to save the successive value of each attribute to animate from existing value to new value. And I am using vnode.el._dataset for that too.
el.setAttribute(bindingKey, func(interpolator(te(normalizedTime))));
Is it correct, What is the difference in this case between that and using vnode.el.setAttribute...A complex case.
When animating some charts like the Pie chart, it has to work differently.
It is not a direct transition between init and target because what we are transitioning is the "d" attribute of the SVG (each arc of the pie chart). D3 cannot interpolate directly between two different path. On each frame of the transition the new shape is generated using what D3 calls a "generator function" that receives the interpolated data ( angles in this case) as inputs.
https://observablehq.com/@d3/pie-chart-update?collection=@d3/d3-transition
So the transition will be between arcGenerator(initangles) and arcGenerator(endAngles)
To do that we need to also pass the generator function to the custom directive.
Optimising
There is a problem with this approach:
The updated hook is not only called when the data in the binding param changes, it is called each time the component containing the SVG is updated. For instance, I show a line following the mouse in the same svg: with each mouse movement the updated hook is called. A lot of unnecessary calls triggering animations.
My first idea was to compare the new binding.value data with the old one to check for changes and do nothing if both are the same. That could work for the simple scenario, but not when I am also passing a function. Because functions could be different and I cannot compare functions.
So I am using this "hack". I am enclosing all the "animatable block" whitin a group with a v-memo directive like:
<g v-memo="[slices, arcGenerator]">
arcGenerator is a function returned from a computed property. In this way, this block is not updated unless there is an update in the slices or arcGenerator reactive values.
So the code block for the sectors of the animated pie chart looks like this:
You need to explicitly list all the reactive dependencies in v-memo, but it works. Any drawbacks to this approach?
I hope somebody has the patience to read all of this. Any tip or comment is welcome.
Beta Was this translation helpful? Give feedback.
All reactions