I need to compare the values of two AnimatedInterpolation objects in react-native. I want to know which AnimatedInterpolation has the smallest decimal value.
Interpolation documentation
Unfortunately, Math.min(value1, value2) doesn't work.
How can I get the float value of an AnimatedInterpolation object ?
I have tried the following :
interpolateOpacity = () => {
let xOpacity = this.state.pan.x.interpolate({
inputRange: this.props.inputOpacityRangeX,
outputRange: this.props.outputOpacityRangeX
});
let yOpacity = this.state.pan.y.interpolate({
inputRange: this.props.inputOpacityRangeY,
outputRange: this.props.outputOpacityRangeY
});
return Math.min(xOpacity, yOpacity) // NOT WORKING
};
Debugger:
The way I would do this would be to compare the input values.
If your mapping for the output range isn't significantly complicated, you should be able to compare the input ranges like this by accessing this.state.pan.x._value and this.state.pan.y._value, or better practice would be to set up a listener as described here.
I don't think there is a way to compare the interpolated values in real-time.
You could probably using something like this.state.pan.y.__getValue(). If that isn't effective, I'd recommend adding a listener to the animated value and using that value instead like so:
this.state.pan = new Animated.Value({ x: 0, y: 0 });
this.panValue = { x: 0, y: 0 };
this.state.pan.addListener((value) =>
this.panValue = value); // value is an object with x and y props
Also, don't forget to set and reset the values and offsets like so:
onPanResponderGrant: () => {
this.state.pan.setOffset({
x: this.panValue.x,
y: this.panValue.y
});
this.state.pan.setValue(0);
},
onPanResponderMove: (evt, { dx, dy }) => {
this.state.pan.setValue({ x: dx, y: dy});
}
Related
I am developing an app where I can add multiple images, which can be dragged accross the whole screen. This works till a certain extend.
But what I am trying to do is saving the image, position (x, y) to firestore. I haven't been able to find some sample code that does that.
Anyone an idea?
Managed to find a code snippet for dragging and dropping a html object. According to this discussion, you can use element.getBoundingClientRect() method and have the shape's positions logged. Should look something like this:
var rect = element.getBoundingClientRect();
console.log(rect.top, rect.right, rect.bottom, rect.left);
From there, for the positions returned. You can follow this documentation on adding data to a collection in Firestore.
I managed to find the solution, as I use panresponder. I get the x and y values and add them to firestore.
This is part of the code that I use:
const pan = useState(new Animated.ValueXY({x: xval, y: yval}))[0];
const panResponder = useState(
PanResponder.create({
onMoveShouldSetPanResponder: () => true,
onPanResponderGrant: () => {
pan.setOffset({
x: pan.x._value,
y: pan.y._value
});
pan.setValue({x: xval, y: yval});
},
onPanResponderMove: Animated.event(
[
null,
{ dx: pan.x, dy: pan.y }
],
{useNativeDriver: false}
),
onPanResponderRelease: () => {
pan.flattenOffset();
stagesRef.doc(parent).collection('stageimages').doc(id)
.update({
x: pan.x._value,
y: pan.y._value,
}).then(function() {
});
}
})
)[0];
The stagesRef is the base for the firestore.
The xval and yval are values coming from anther file.
This is my initial dataset:
arr1 = [{
url: ['https://example.com/A.jpg?', 'https://example.com/B.jpg?', 'https://example.com/C.jpg?'],
width: ['w=300', 'w=400', 'w=500'],
type: [-1, 1, 2]
}];
By filtering with type: n => n > 0 and passing the result through the arr1, I would like to produce arr2 with Ramda. If nth value is excluded as the result of the filter, then nth value in another arrays are also excluded.
arr2 = [{
url: ['https://example.com/B.jpg?', 'https://example.com/C.jpg?'],
width: ['w=400', 'w=500'],
type: [1, 2]
}];
I tried the code below, but not working...
const isgt0 = n => n > 0 ;
const arr2 = R.applySpec({
url : arr1,
width : arr1,
type : R.filter(isgt0),
});
console.log(arr2(arr1));
Once I get the desired object, I intend to R.transpose the array to generate an URL like: [https://example.com/B.jpg?w=400, https://example.com/C.jpg?w=500]
The main steps are:
Get the arrays of the values with R.props:
[-1, 1, 2]
['w=300', 'w=400', 'w=500']
['https://example.com/A.jpg?', 'https://example.com/B.jpg?', 'https://example.com/C.jpg?']
Transpose them to arrays of items with the same index:
[-1, 'w=300', 'https://example.com/A.jpg?']
[1, 'w=400', 'https://example.com/B.jpg?']
[1, 'w=500', 'https://example.com/C.jpg?']
Filter by index 0 (the original type), transpose back, and then reconstruct the object using R.applySpec.
const { pipe, props, transpose, filter, propSatisfies, gt, __, tranpose, applySpec, nth, map } = R
const filterProps = pipe(
props(['type', 'width', 'url']), // get an array of property
transpose, // convert to arrays of all property values with the same index
filter(propSatisfies(gt(__, 0), 0)), // filter by the type (index 0)
transpose, // convert back to arrays of each type
applySpec({ // reconstruct the object
type: nth(0),
width: nth(1),
url: nth(2),
})
)
const data = [
{
type: [-1, 1, 2],
width: ['w=300', 'w=400', 'w=500'],
url: [
'https://example.com/A.jpg?',
'https://example.com/B.jpg?',
'https://example.com/C.jpg?',
],
}
]
const result = map(filterProps, data)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js" integrity="sha512-3sdB9mAxNh2MIo6YkY05uY1qjkywAlDfCf5u1cSotv6k9CZUSyHVf4BJSpTYgla+YHLaHG8LUpqV7MHctlYzlw==" crossorigin="anonymous"></script>
Another way to think about it more generically is to filter using a configuration object that holds the tests to apply for various properties. Here it is only type, but it's easy enough to imagine others.
My solution for this problem is configured with this object:
{
type: n => n > 0
}
This solutions uses many Ramda functions, but also uses Array.prototype.filter to have access to the index parameter of filter. We could choose R.addIndex instead, but I would only bother if I was trying to make it point-free, which doesn't seem worthwhile here. This is what it might look like:
const filterOnProps = (config) => (obj) => {
const test = allPass (map(([k, v]) => (i) => v (obj [k] [i]), toPairs (config)))
const indices = filter (test) (range (0, values (obj) [0] .length))
return map(a => a .filter ((_, i) => contains (i, indices)), obj)
}
const transform = map (filterOnProps ({type: n => n > 0}))
const arr1 = [{url: ['https://example.com/A.jpg?', 'https://example.com/B.jpg?', 'https://example.com/C.jpg?'], width: ['w=300', 'w=400', 'w=500'], type: [-1, 1, 2]}]
console .log (transform (arr1))
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>
<script> const {allPass, map, toPairs, filter, range, values, contains} = R </script>
With obj in scope, we create test, which will be somewhat equivalent to
allPass([
i => obj['type'][i] > 0
])
If we had more conditions in the original configuration object, they would also be in this list.
Then we filter the indices, to see on which ones the record passes this test.
Finally we map over our object, filtering each array to keep only those where the index is in the list.
While this should work, and is reasonably generic, it points to a problem with your data structure. I would suggest that as much as possible, you shy away from situations where structures are dependent on shared indices. To my mind the only reasonable use of that is for a relatively compact serialization format. On deserialization, I would immediately rehydrate that to something more useful, perhaps something like
const data = [
{url: 'https://example.com/A.jpg?', width: 'w=300', type: -1},
{url: 'https://example.com/B.jpg?', width: 'w=400', type: 1},
{url: 'https://example.com/C.jpg?', width: 'w=500', type: 2}
]
This structure is much easier to work with. For example, data.filter(({type}) => type > 0) would be the equivalent to the work above, if you started with this structure.
This might help a bit
const gte1 = R.filter(R.gte(R.__, 1));
const fn = R.map(
R.evolve({
type: gte1,
}),
);
// =====
const data = [
{
type: [-1, 1, 2],
width: ['w=300', 'w=400', 'w=500'],
url: [
'https://example.com/A.jpg?',
'https://example.com/B.jpg?',
'https://example.com/C.jpg?',
],
}
];
console.log(
fn(data),
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous"></script>
I've created some arcs using deck.gl. When you click on different points/polygons, different arcs appear between countries. When doing this, I want the map to zoom to the bounds of those arcs.
For clarity, here is an example: When clicking on Glasgow, I'd want to zoom to the arc shown (as tightly as possible):
It appears that with WebMercatorViewport, you can call fitBounds
(see: https://deck.gl/docs/api-reference/core/web-mercator-viewport#webmercatorviewport)
It's not clear to me how this gets used, though. I've tried to find examples, but have come up short. How can I add this to what I have?
Here is the code for the arcs:
fetch('countries.json')
.then(res => res.json())
.then(data => {
console.log('data',data)
const inFlowColors = [
[0, 55, 255]
];
const outFlowColors = [
[255, 200, 0]
];
const countyLayer = new deck.GeoJsonLayer({
id: 'geojson',
data: data,
stroked: true,
filled: true,
autoHighlight: true,
lineWidthScale: 20,
lineWidthMinPixels: 1,
pointRadiusMinPixels: 10,
opacity:.5,
getFillColor: () => [0, 0, 0],
getLineColor: () => [0,0,0],
getLineWidth: 1,
onClick: info => updateLayers(info.object),
pickable: true
});
const deckgl = new deck.DeckGL({
mapboxApiAccessToken: 'pk.eyJ1IjoidWJlcmRhdGEiLCJhIjoiY2pudzRtaWloMDAzcTN2bzN1aXdxZHB5bSJ9.2bkj3IiRC8wj3jLThvDGdA',
mapStyle: 'mapbox://styles/mapbox/light-v9',
initialViewState: {
longitude: -19.903283,
latitude: 36.371449,
zoom: 1.5,
maxZoom: 15,
pitch: 0,
bearing: 0
},
controller: true,
layers: []
});
updateLayers(
data.features.find(f => f.properties.name == 'United States' )
);
function updateLayers(selectedFeature) {
const {exports, centroid, top_exports, export_value} = selectedFeature.properties;
const arcs = Object.keys(exports).map(toId => {
const f = data.features[toId];
return {
source: centroid,
target: f.properties.centroid,
value: exports[toId],
top_exports: top_exports[toId],
export_value: export_value[toId]
};
});
arcs.forEach(a => {
a.vol = a.value;
});
const arcLayer = new deck.ArcLayer({
id: 'arc',
data: arcs,
getSourcePosition: d => d.source,
getTargetPosition: d => d.target,
getSourceColor: d => [0, 55, 255],
getTargetColor: d => [255, 200, 0],
getHeight: 0,
getWidth: d => d.vol
});
deckgl.setProps({
layers: [countyLayer, arcLayer]
});
}
});
Here it is as a Plunker:
https://plnkr.co/edit/4L7HUYuQFM19m9rI
I try to make it simple, starting from a raw implementation with ReactJs then try to translate into vanilla.
In ReactJS I will do something like that.
Import LinearInterpolator and WebMercatorViewport from react-map-gl:
import {LinearInterpolator, WebMercatorViewport} from 'react-map-gl';
Then I define an useEffect for viewport:
const [viewport, setViewport] = useState({
latitude: 37.7577,
longitude: -122.4376,
zoom: 11,
bearing: 0,
pitch: 0
});
Then I will define a layer to show:
const layerGeoJson = new GeoJsonLayer({
id: 'geojson',
data: someData,
...
pickable: true,
onClick: onClickGeoJson,
});
Now we need to define onClickGeoJson:
const onClickGeoJson = useCallback((event) => {
const feature = event.features[0];
const [minLng, minLat, maxLng, maxLat] = bbox(feature); // Turf.js
const viewportWebMercator = new WebMercatorViewport(viewport);
const {longitude, latitude, zoom} = viewport.fitBounds([[minLng, minLat], [maxLng, maxLat]], {
padding: 20
});
viewportWebMercator = {
...viewport,
longitude,
latitude,
zoom,
transitionInterpolator: new LinearInterpolator({
around: [event.offsetCenter.x, event.offsetCenter.y]
}),
transitionDuration: 1500,
};
setViewport(viewportWebMercator);
}, []);
First issue: in this way we are fitting on point or polygon clicked, but what you want is fitting arcs. I think the only way to overcome this kind of issue is to add a reference inside your polygon about bounds of arcs. You can precompute bounds for each feature and storage them inside your geojson (the elements clicked), or you can storage just a reference in feature.properties to point another object where you have your bounds (you can also compute them on the fly).
const dataWithComputeBounds = {
'firstPoint': bounds_arc_computed,
'secondPoint': bounds_arc_computed,
....
}
bounds_arc_computed need to be an object
bounds_arc_computed = {
minLng, minLat, maxLng, maxLat,
}
then on onClick function just take the reference
const { minLng, minLat, maxLng, maxLat} = dataWithComputedBounds[event.features[0].properties.reference];
const viewportWebMercator = new WebMercatorViewport(viewport);
...
Now just define our main element:
return (
<DeckGL
layers={[layerGeoJson]}
initialViewState={INITIAL_VIEW_STATE}
controller
>
<StaticMap
reuseMaps
mapStyle={mapStyle}
preventStyleDiffing
mapboxApiAccessToken={YOUR_TOKEN}
/>
</DeckGL>
);
At this point we are pretty close to what you already linked (https://codepen.io/vis-gl/pen/pKvrGP), but you need to use deckgl.setProps() onClick function instead of setViewport to change your viewport.
Does it make sense to you?
It is possible to get the current animated value using ._value while nativeDriver is false.
But is it possible to get current value when nativeDriver is turned to true ?
Do we have any work arounds ?
use addListener like so:
this._animatedValue = new Animated.Value(0);
this._animatedValue.addListener(({value}) => this._value = value);
Animated.timing(this._animatedValue, {
toValue: 100,
duration: 500
}).start();
I figured out a workaround for scenarios like this, where the native driver spoils our offset and gives a jump in our animation towards the end.
Create a pseudo animated value (which will not be used for any animation) and keep updating it alongside your original animated value. Finally when your animation is over (like decay, spring, etc.) set the offsets and current angle with the pseudo value as below:
const angle = useRef(new Animated.Value(0)).current; //original value used in animation; will run on native side
const psuedoAngle = useRef(new Animated.Value(0)).current; //only to track the value on JS side
//using PanResponder
...
onPanResponderMove: (evt, gestureState) => {
angle.setValue(some_value);
psuedoAngle.setValue(same_value);
},
onPanResponderRelease: (evt, {vx, vy}) => {
// calculate your velocity
...
const decay1 = Animated.decay(angle,{
velocity: velocity,
deceleration: 0.997,
useNativeDriver: true
}
);
const decay2 = Animated.decay(psuedoAngle,{
velocity: velocity,
deceleration: 0.997,
useNativeDriver: false //this will keep your pseudo value intact
}
);
//set offset and current angle in callback as below
Animated.parallel([decay1,decay2]).start(() => {
psuedoAngle.flattenOffset();
angle.setOffset(psuedoAngle._value); //correct value passed to original value
psuedoAngle.setOffset(psuedoAngle._value);
angle.setValue(0);
psuedoAngle.setValue(0);
});
}
I have the following code to animate in React Native
Animated.timing(
this.state.absoluteChangeX,
{toValue: 0},
).start(function() {
this.lastX = 0;
this.lastY = 0;
});
Pretty simple, but whenever it's triggered, I receive the error:
singleValue.stopTracking is not a function
Here's where the error originates:
/react-native/Libraries/Animates/src/AnimtaedImplementation.js
var timing = function(
value: AnimatedValue | AnimatedValueXY,
config: TimingAnimationConfig,
): CompositeAnimation {
return maybeVectorAnim(value, config, timing) || {
start: function(callback?: ?EndCallback): void {
var singleValue: any = value;
var singleConfig: any = config;
singleValue.stopTracking(); // <--------------- HERE!!!
if (config.toValue instanceof Animated) {
singleValue.track(new AnimatedTracking(
singleValue,
config.toValue,
TimingAnimation,
singleConfig,
callback
));
} else {
singleValue.animate(new TimingAnimation(singleConfig), callback);
}
},
stop: function(): void {
value.stopAnimation();
},
};
};
I'm not extremely versed in typeScript, but var singleValue: any means that "singleValue" could be any type. In my case, it's a number. Since numbers don't have methods, it would make sense that this would error.
Am I doing something wrong?
The value you wish to animate must be an instance of Animated.Value, or one of its subtypes. When you initialize your state, it should look something like this:
getInitialState() {
return { absoluteChangeX: new Animated.Value(0) };
}
The fact that the type declaration in the framework method is any is just a lack of constraint, not an explicit invitation to pass any value into it.
See the Animated docs for more examples.
I run into this issue sometimes (React hooks instead) when I forget to set my variable to the .current of the ref:
function MyComponent() {
const animatedValue = useRef(new Animated.Value(0.0)).current; // Notice the .current
}
This may not necessarily answer the original question, but developers who encounter this error while using React hooks may end up here so maybe it will help someone.
I ran into this issue because I used the animated value (2) instead of the object (1):
const animatedValue = useRef(new Animated.Value(0.0)).current; // (1)
const transform = animatedValue.interpolate({
inputRange: [0.0, 1.0],
outputRange: [0, 100]
}); // (2)
Animated.timing(animatedValue, { // USE animatedValue, NOT transform HERE!
toValue: 1.0,
duration: 3000,
});
Hope this can help anyone that was new to React Native Animation (like me :) )...