How to attach multiple react-spring springs on a single component? - react-spring

I'm trying to learn how to use react-spring. Let's say that I have three divs to animate.
<a.div style={trans1}>
<a.div style={trans2}>
<a.div style={trans3}>
and trans1 has the following configuration…
const [clicked, toggle] = useState(null)
const { x } = useSpring({
from: { x: 0 },
x: clicked ? 1 : 0,
config: { duration: 500 },
})
const trans1 = {
transform: x
.interpolate({
range: [0, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 1],
output: [1, 0.97, 0.9, 1.1, 0.9, 1.1, 1.03, 1],
})
.interpolate((x) => `scale(${x})`),
}
What's the best way to implement the same type of animation on the second and third divs without duplicating all that code? How do I make multiple instances of the same spring for use on multiple Dom objects without triggering them all at the same time? I certainly don't want to duplicate a full set of code for each item, right?
Do I need to create a function that accepts a parameter that can switch the arguments in the config on the fly? 🤷🏽‍♂️
Any help is appreciated.
Here's a live example: https://codesandbox.io/s/optimistic-bassi-brnle
How do I make the left & right sides animate one at a time without creating duplicate code?

The first possibility would be to separate the style and give it to more than one div. Its drawback is, that they would behave exactly the same at the same time.
const style = {
opacity: x.interpolate({ range: [0, 1], output: [0.3, 1] }),
transform: x
.interpolate({
range: [0, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 1],
output: [1, 0.97, 0.9, 1.1, 0.9, 1.1, 1.03, 1]
})
.interpolate(x => `scale(${x})`)
};
return (
<div onClick={() => toggle(!state)}>
<animated.div
style={style}>
click
</animated.div>
<animated.div
style={style}>
click
</animated.div>
</div>
)
The second option is, that you create a new component with the click and spring logic. This way you write the logic once and you can use it multiple time. I introduced a text attribute also to make different text for the component.
const AnimText = ({text}) => {
const [state, toggle] = useState(true)
const { x } = useSpring({ from: { x: 0 }, x: state ? 1 : 0, config: { duration: 1000 } })
return (
<div onClick={() => toggle(!state)}>
<animated.div
style={{
opacity: x.interpolate({ range: [0, 1], output: [0.3, 1] }),
transform: x
.interpolate({
range: [0, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 1],
output: [1, 0.97, 0.9, 1.1, 0.9, 1.1, 1.03, 1]
})
.interpolate(x => `scale(${x})`)
}}>
{text}
</animated.div>
</div>
)
}
function Demo() {
return (
<div>
<AnimText text={'click1'}/>
<AnimText text={'click2'}/>
<AnimText text={'click3'}/>
</div>
)
}
here is the example: https://codesandbox.io/s/divine-water-n1b6x

Related

unable to access reactive nested object returned by useFetch in <script setup> but i am able to access in the html

So I am trying to create a flow where there is a form, when they user modifies the form useFetch is triggered and the results on the page update. things are mostly working well i am able to access the currentPokemon variable within the html and reactivity is working great. The issue is that I am trying to access parts of currentPokemon from within the so that the chart i define will also be reactive.
The graph_data show in the below is in the currentPokemon object, but i cant access it from within <script setup>
<script setup>
import { ref } from 'vue'
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend
} from 'chart.js'
import { Scatter } from 'vue-chartjs'
ChartJS.register(LinearScale, PointElement, LineElement, Tooltip, Legend)
const units = ref('METRIC')
const friction = ref(1)
const payload = ref(1)
const distance = ref(1)
const time = ref(1)
const gearRatio = ref(1)
const motionProfile = ref(0.25)
const conservatism = ref(1.5)
const stage = ref('DEFAULT')
const options = {
responsive: true,
maintainAspectRatio: true
}
const { data: currentPokemon } = await useFetch(`http://localhost:3000/`, {
method: "POST",
body: {
unit_system: units,
motion_profile: motionProfile,
move_time_sec: time,
move_distance_deg: distance,
friction: friction,
payload: payload,
gear_ratio: gearRatio,
safety_factor: conservatism,
be_stage: stage,
manufacturers: ["Bosch"],
motor: "DEFAULT"
}
})
console.log(typeof(currentPokemon))
console.log(currentPokemon)
console.log(currentPokemon.graph_data)
const test = ref(currentPokemon.value)
console.log("test")
console.log(typeof(test))
console.log(test.graph_data)
const data = ref({
datasets: [
{
label: 'Accel Profile 0.25 User',
borderColor: 'red',
backgroundColor: 'red',
borderWidth: 1,
pointRadius: 0,
pointHoverRadius: 5,
// tension: 0,
showLine: true,
fill: false,
data: [
{x: user_graph.x[0], y: user_graph.y[0]},
{x: user_graph.x[1], y: user_graph.y[1]},
{x: user_graph.x[2], y: user_graph.y[2]},
{x: user_graph.x[3], y: user_graph.y[3]}
]
}
]
})
here is the console output
✔ Vite server hmr 6 files in 54.932ms 13:56:25
object 13:56:25
[Vue warn]: Unhandled error during execution of setup function 13:56:25
at <App>
RefImpl { 13:56:25
__v_isShallow: false,
dep: undefined,
__v_isRef: true,
_rawValue: {
motormatch: {
'1': [Object],
'3': [Object],
'5': [Object],
'7': [Object],
'10': [Object]
},
stage_details: { DEFAULT: [Object] },
motioncalcs: {
'0.5': [Object],
'0.33': [Object],
'0.1': [Object],
'0.25 User': [Object]
},
graph_data: {
xlabel: 'Time (s)',
ylabel: 'Velocity (deg/s)',
title: 'Move Profiles',
profiles: [Array]
}
},
_value: {
motormatch: {
'1': [Object],
'3': [Object],
'5': [Object],
'7': [Object],
'10': [Object]
},
stage_details: { DEFAULT: [Object] },
motioncalcs: {
'0.5': [Object],
'0.33': [Object],
'0.1': [Object],
'0.25 User': [Object]
},
graph_data: {
xlabel: 'Time (s)',
ylabel: 'Velocity (deg/s)',
title: 'Move Profiles',
profiles: [Array]
}
}
}
undefined 13:56:25
test 13:56:25
object 13:56:25
undefined
Again the goal is to have the paramater passed to the graph be reactive based on the value of currentPokemon.graph_data. I have tried using let to declare new variables and access graph_data that way, which did allow me to get the value of graph_data but it was no longer reactive.
console.log(typeof(currentPokemon))
let res_data1 = currentPokemon.value
let graph_data1 = res_data1.graph_data
let user_graph = graph_data1.profiles[3]
console.log(user_graph)
addition note, in html {{ currentPokemon.graph_data }} works perfectly and is reactive...
Whenever you use ref or computed the way you access values template vs script differs, in script you must use the value key accessor, in template you omit it because in templates the value key accessor is injected at compilation run-time.
<script setup>
const myVar = ref([]) // Array
console.log(myVar.value.length) // Outputs 0
myVar.value.push('hello')
console.log(myVar.value.length) // Outputs 1
const count = computed(() => myvar.value.length)
myVar.value.push('world')
console.log(count.value) // Outputs 2
</script>
<template>
<div>Total Items: {{ myVar.length }}</div>
<div>Counter: {{ count.length }}</div>
</template>
However it is not the same case when working with nested reactive reference objects which is what you want to use if you are altering inner values of a nested object where you want to react to those nested alterations:
<script setup>
const myObject = reference({
myData: [] // Array
})
console.log(myObject.myData.length) // Outputs 0
myObject.myData.push('hello')
console.log(myObject.myData.length) // Outputs 1
</script>
<template>
<div>Total Items: {{ myObject.myData.length }}</div>
</template>
Not sure if this is the best way to do this... I tried using reactive and a few other methods but this finally worked for me
/ Data pased to scatter plot
const data = computed({
get() {
let res_data1 = currentPokemon.value
let user_graph = res_data1.graph_data.profiles[3]
let dataset = {datasets: [
{label: 'Accel Profile 0.5',
fill: false,
borderColor: 'blue',
backgroundColor: 'blue',
borderWidth: 1,
// pointBackgroundColor: ['#000', '#00bcd6', '#d300d6'],
// pointBorderColor: ['#000', '#00bcd6', '#d300d6'],
pointRadius: 0,
pointHoverRadius: 5,
fill: false,
// tension: 0,
showLine: true,
data: [
{x: 0, y: 0},
{x: 0.5, y: 2},
{x: 0.5, y: 2},
{x: 1, y: 0}]},
{label: 'Accel Profile 0.33',
fill: false,
borderColor: 'orange',
backgroundColor: 'orange',
borderWidth: 1,
pointRadius: 0,
pointHoverRadius: 5,
// tension: 0,
showLine: true,
data: [
{x: 0, y: 0},
{x: 0.33, y: 1.49},
{x: 0.66, y: 1.49},
{x: 1, y: 0}]
},
{
label: 'Accel Profile 0.1',
borderColor: 'green',
backgroundColor: 'green',
borderWidth: 1,
pointRadius: 0,
pointHoverRadius: 5,
// tension: 0,
showLine: true,
fill: false,
data: [
{x: 0, y: 0},
{x: 0.1, y: 1.11},
{x: 0.9, y: 1.11},
{x: 1, y: 0}]
},
{
label: 'Accel Profile 0.25 User',
borderColor: 'red',
backgroundColor: 'red',
borderWidth: 1,
pointRadius: 0,
pointHoverRadius: 5,
// tension: 0,
showLine: true,
fill: false,
data: [
{x: user_graph.x[0], y: user_graph.y[0]},
{x: user_graph.x[1], y: user_graph.y[1]},
{x: user_graph.x[2], y: user_graph.y[2]},
{x: user_graph.x[3], y: user_graph.y[3]}
]
}
]}
return dataset
},
// setter
set(newValue) {
// Note: we are using destructuring assignment syntax here.
[firstName.value, lastName.value] = newValue.split(' ')
}
})

How to exclude code from running inside requestAnimationFrame

Hi all, in a nextjs app Im using requestAnimationFrame and matter.js to animate shapes created using paper.js. The shapes are created with a javascript Class inside a forEach loop. Each shape (RegularPolygon) has a random number of sides. When I run the code, Matter.js takes care of the physics but the logic to create the random sides also animates creating a cool (but undesirable strobing effect) of continuously randomising the number of sides of the RegularPolygon. I'm looking for a way of creating the shapes with random sides, once, and then animating them. Thanks in advance.
link to netlify site
import { Engine, Render, World, Bodies } from 'matter-js';
import { useEffect, useRef } from 'react';
import { paper } from 'paper';
const random = (min, max) => {
return Math.random() * min + Math.random() * max + min;
};
function Canvas(props) {
const scene = useRef();
const engine = useRef(Engine.create());
const paperRef = useRef();
const raf = useRef();
console.log(random);
useEffect(() => {
const time = 0;
window.addEventListener('load', () => {
paper.setup(paperRef.current);
const cw = document.body.clientWidth;
const ch = document.body.clientHeight;
const render = Render.create({
element: scene.current,
engine: engine.current,
options: {
width: cw,
height: ch,
wireframes: false,
background: 'transparent',
},
});
World.add(engine.current.world, [
Bodies.rectangle(cw / 2, -10, cw, 20, {
isStatic: true,
}),
Bodies.rectangle(-10, ch / 2, 20, ch, {
isStatic: true,
fillStyle: '#F35e66',
}),
Bodies.rectangle(cw / 2, ch + 10, cw, 20, {
isStatic: true,
fillStyle: '#F35e66',
}),
Bodies.rectangle(cw + 10, ch / 2, 20, ch, {
isStatic: true,
fillStyle: '#F35e66',
}),
]);
Engine.run(engine.current);
Render.run(render);
const balls = [];
for (let i = 0; i < 40; i++) {
balls.push(
Bodies.circle(Math.random() * cw, 0, 80, {
density: Math.random(),
friction: 0.01,
frictionAir: 0.00001,
restitution: 0.8,
render: {
fillStyle: '#F35e66',
strokeStyle: 'black',
lineWidth: 1,
},
})
);
}
var ball = Bodies.circle(0, 0, 20, {
density: 0.04,
friction: 0.01,
frictionAir: 0.00001,
restitution: 0.8,
render: {
fillStyle: '#F35e66',
strokeStyle: 'black',
lineWidth: 1,
},
});
for (let i = 0; i < balls.length; i++) {
World.add(engine.current.world, [balls[i]]);
}
const shapes = [];
for (let i = 0; i < balls.length; i++) {
shapes.push(
new paper.Path.RegularPolygon({
position: new paper.Point([
balls[i].position.x,
balls[i].position.y,
]),
sides: Math.floor(random(0, 8)),
radius: 100,
fillColor: 'tomato',
})
);
}
class Shape {
constructor(x, y, angle) {
this.angle = angle;
this.x = x;
this.y = y;
this.draw();
this.shape = new paper.Path.RegularPolygon({
position: new paper.Point([this.x, this.y]),
sides: Math.floor(random(0, 8)),
radius: 100,
fillColor: 'tomato',
});
}
draw() {
// this.shape.rotate(this.angle);
}
}
console.log(ball);
const callback = () => {
// paper.view.update();
paper.project.clear();
balls.forEach((ball) => {
new Shape(ball.position.x, ball.position.y, ball.angle);
// let shape = new paper.Path.RegularPolygon({
// position: new paper.Point([
// ball.position.x,
// ball.position.y,
// ]),
// sides: Math.floor(Math.random() * 8),
// radius: 100,
// fillColor: 'tomato',
// });
// shape.rotate(ball.angle);
});
const shape = new paper.Path.RegularPolygon({
position: new paper.Point([
ball.position.x,
ball.position.y,
]),
sides: 5,
radius: 100,
fillColor: 'tomato',
});
shape.rotate(ball.angle);
raf.current = requestAnimationFrame(callback);
};
raf.current = requestAnimationFrame(callback);
return () => {
cancelAnimationFrame(raf.current);
Render.stop(render);
World.clear(engine.current.world);
Engine.clear(engine.current);
render.canvas.remove();
render.canvas = null;
render.context = null;
render.textures = {};
};
});
}, []);
return (
<div className=''>
<div className='paper'>
<canvas
resize='true'
ref={paperRef}
style={{
width: '100%',
height: '100%',
position: 'fixed',
top: 0,
left: 0,
}}
/>
</div>
<div
ref={scene}
style={{
display: 'none',
width: '100%',
height: '100%',
position: 'fixed',
top: 0,
left: 0,
}}
/>
</div>
);
}
export default Canvas;

In LightningChartJS, is there a way to do a drag zoom where the zoom for X and Y is always equal?

My problem is when I drag rectangle zoom for example (gyazo gif), the X axis is wider than the Y. In effect, the X axis is zoomed in more details than the Y and the visual of the graph looks different from the original graph.
https://gyazo.com/749db917465a8037b7c5f21792f572ce
I am looking for a way where if i zoom rectangle drag, the function is similar to zooming via mousewheel where x and y zoom feels equal.
here you will find an example how you can perform custom zoom rect instead of default and keep the ratio of the axes.
// Extract required parts from LightningChartJS.
const {
ColorRGBA,
ColorHEX,
emptyFill,
SolidFill,
SolidLine,
translatePoint,
_point,
lightningChart
} = lcjs;
// Import data-generator from 'xydata'-library.
const {
createProgressiveTraceGenerator
} = xydata
const chart = lightningChart()
.ChartXY()
// Disable default chart interactions with left mouse button.
// .setMouseInteractionRectangleFit(false)
.setMouseInteractionRectangleZoom(false)
.setTitleFillStyle(emptyFill)
// generate data and creating the series
const series = chart.addLineSeries().setStrokeStyle(
new SolidLine({
fillStyle: new SolidFill({ color: ColorHEX('#fff') }),
thickness: 2,
}),
)
// generate data and create series
createProgressiveTraceGenerator()
.setNumberOfPoints(200)
.generate()
.toPromise()
.then((data) => {
return series.add(data)
})
// create zooming rect and dispose it
const rect = chart
.addRectangleSeries()
.add({
x: 0,
y: 0,
width: 0,
height: 0,
})
.setFillStyle(new SolidFill({ color: ColorRGBA(255, 255, 255, 30) }))
.setStrokeStyle(
new SolidLine({
thickness: 2,
fillStyle: new SolidFill({ color: ColorRGBA(255, 255, 255, 255) }),
}),
)
.dispose()
// om Mouse drag restor rectange and set position and coordinates
chart.onSeriesBackgroundMouseDrag((obj, event, button, startLocation, delta) => {
if (button !== 0) return
const startLocationOnScale = translatePoint(
chart.engine.clientLocation2Engine(startLocation.x, startLocation.y),
chart.engine.scale,
series.scale,
)
const curLocationOnScale = translatePoint(chart.engine.clientLocation2Engine(event.x, event.y), chart.engine.scale, series.scale)
const x = Math.abs(series.getBoundaries().min.x) + Math.abs(series.getBoundaries().max.x)
const y = Math.abs(series.getBoundaries().min.y) + Math.abs(series.getBoundaries().max.y)
const ratio = x / y
const width = Math.abs(curLocationOnScale.x - startLocationOnScale.x)
const height = Math.abs(curLocationOnScale.y - startLocationOnScale.y)
const heightDirection = curLocationOnScale.y - startLocationOnScale.y // check direction of rect
// check for mouse direction to prevet fit and zoom conflict
if (curLocationOnScale.x > startLocationOnScale.x) {
rect.setDimensions({
x: startLocationOnScale.x,
y: startLocationOnScale.y,
width: width > height * ratio ? width : height * ratio,
height: width > height * ratio ? (heightDirection > 0 ? width : -width) / ratio : heightDirection,
}).restore()
} else {
// prevent phantom rectangle if you change zoom to fit during the dragging
rect.setDimensions({
x: 0,
y: 0,
width: 0,
height: 0,
}).dispose()
}
})
// on mouse drag stop dispose rect and zoom relative to its dimensions
chart.onSeriesBackgroundMouseDragStop((_, event, button, startLocation) => {
if (button !== 0) return
const rectZooom = rect.getDimensionsPositionAndSize()
if (rectZooom.width !== 0) {
chart.getDefaultAxisX().setInterval(rectZooom.x, (rectZooom.x + rectZooom.width), true, true)
if(rectZooom.height > 0){
chart.getDefaultAxisY().setInterval(rectZooom.y, (rectZooom.y + Math.abs(rectZooom.height)), true, true)
}else{
chart.getDefaultAxisY().setInterval((rectZooom.y - Math.abs(rectZooom.height)),rectZooom.y, true, true)
}
}
rect.setDimensions({
x: 0,
y: 0,
width: 0,
height: 0,
}).dispose()
})
<script src="https://unpkg.com/#arction/xydata#1.4.0/dist/xydata.iife.js"></script>
<script src="https://unpkg.com/#arction/lcjs#3.0.0/dist/lcjs.iife.js"></script>

Scoping in r2d3 visualization - d3.selectAll vs svg.selectAll

Why does d3.selectAll('rect')... not work as seen in the script below in the mouseover function, but svg.selectAll('rect')... does?
svg is the special pre-set selector from r2d3.
Additionally, I have noticed that running e.g. d3.selectAll('rect').remove() from the browser console does not work when running the visualization from Rstudio.
This is from the r2d3 example, as sample.js:
// !preview r2d3 data=c(0.3, 0.6, 0.8, 0.95, 0.40, 0.20)
var barHeight = Math.floor(height / data.length);
svg.selectAll('rect')
.data(data)
.enter().append('rect')
.attr('width', function(d) { return d * width; })
.attr('height', barHeight)
.attr('y', function(d, i) { return i * barHeight; })
.attr('fill', 'steelblue')
.on('mouseover', function(d){
d3.select(this).attr('fill', 'red')
//svg.selectAll('rect').attr('fill', 'red')
d3.selectAll('rect').attr('fill', 'red')
})
Run from R via r2d3::r2d3("sample.js", data=c(0.3, 0.6, 0.8, 0.95, 0.40, 0.20))
By default r2d3 places the visualization in a shadow DOM. Both d3.select and d3.selectAll start their search at the root element of the DOM, but do not venture into a shadow DOM's child nodes.
Neither select nor selectAll will cross into a shadow tree from the current tree being searched. As svg is a selection of an element within the shadow DOM, the rectangles can be found with svg.selectAll("rect") but not d3.selectAll("rect") (the rects aren't "shadowed" relative to the SVG).
The easiest solution is to not create a shadow DOM by setting the r2d3 shadow option to false.
eg (using the base example from r2d3's documentation):
r2d3(options(r2d3.shadow = FALSE), data=c(0.3, 0.6, 0.8, 0.95, 0.40, 0.20), script = "barchart.js")
Of course in doing so you lose the encapsulation offered by the shadow root, but this might be preferable or neutral depending on the situation.
I've found that sometimes the r2d3.shadow option doesn't always work well, so here's another possible solution. The parent of the event target will be the svg/g that you've placed it in.
// !preview r2d3 data=c(0.3, 0.6, 0.8, 0.95, 0.40, 0.20)
var barHeight = Math.floor(height / data.length);
svg.selectAll('rect')
.data(data)
.enter().append('rect')
.attr('width', function(d) { return d * width; })
.attr('height', barHeight)
.attr('y', function(d, i) { return i * barHeight; })
.attr('fill', 'steelblue')
.on('mouseover', function(d){
const parent = d3.select(d.target.parentElement); // this is the original svg
//svg.selectAll('rect').attr('fill', 'red')
parent.selectAll('rect').attr('fill', 'blue')
d3.select(this).attr('fill', 'red')
})

what does interpolation really do

beginner here, i'm having trouble understanding interpolation with usespring in React spring library , im trying to make an element go back and forth with the translate css property, now what i do understand is that passing in a range emulates css key frames and the output is the values the element should have at that point of the animation
so i did something like,
const {xyz} = useSpring({
from: {xyz: [0, 0, 0]},
to: {xyz: [0, 200, 0]},
})
<animated.div className={classes.down} style={{
transform: xyz
.interpolate(([y]) => (
{range: [0, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 1],
output: [180, 220, 180, 220, 180, 220, 180, 200]}
))
.interpolate((x, y, z)=> translate(${x}px, ${y}px, ${z}px))
}}>
<Typography variant="body2" className={classes.downText}>
Scroll Down
</Typography>
<DownArrow className={classes.downIcon}/>
</animated.div>
which dosen't work
There is a lot of problem here.
First of all in the useSpring if you want to change only the y then you can eliminate the x and z.
Secondly the range not worked for me with arrow function parameter.
Finally you must use translate3d instead of translate and the backtick is missing for the template string.
Something like this:
const { y } = useSpring({
from: { y: 0 },
to: { y: 1 }
});
return (
<div className="App">
<animated.div
style={{
transform: y
.interpolate({
range: [0, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 1],
output: [180, 220, 180, 220, 180, 220, 180, 200]
})
.interpolate(y => `translate3d(0px, ${y}px, 0px)`)
}}
>
Scroll Down
</animated.div>
</div>
);
https://codesandbox.io/s/gracious-diffie-rgz06?file=/src/App.js:135-613

Resources