Skip to content

Commit

Permalink
Add support for extrapolation
Browse files Browse the repository at this point in the history
Summary:
Adds support for the `extrapolate` parameter on the native interpolation node. This is pretty much a 1 to 1 port of the JS implementation.

**Test plan**
Tested by adding the `extrapolate` parameter in the native animated UIExplorer example.
Closes #9366

Differential Revision: D3824154

fbshipit-source-id: 2ef593af827a8bd3d7b8ab2d53abbdc9516c6022
  • Loading branch information
janicduplessis authored and Facebook Github Bot 5 committed Sep 6, 2016
1 parent fab0ff3 commit 6d978c3
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 28 deletions.
9 changes: 7 additions & 2 deletions Libraries/Animated/src/AnimatedImplementation.js
Expand Up @@ -1071,11 +1071,16 @@ class AnimatedInterpolation extends AnimatedWithChildren {
}

__getNativeConfig(): any {
NativeAnimatedHelper.validateInterpolation(this._config);
if (__DEV__) {
NativeAnimatedHelper.validateInterpolation(this._config);
}

return {
...this._config,
inputRange: this._config.inputRange,
// Only the `outputRange` can contain strings so we don't need to tranform `inputRange` here
outputRange: this.__transformDataType(this._config.outputRange),
extrapolateLeft: this._config.extrapolateLeft || this._config.extrapolate || 'extend',
extrapolateRight: this._config.extrapolateRight || this._config.extrapolate || 'extend',
type: 'interpolation',
};
}
Expand Down
3 changes: 3 additions & 0 deletions Libraries/Animated/src/NativeAnimatedHelper.js
Expand Up @@ -135,6 +135,9 @@ function validateInterpolation(config: Object): void {
var SUPPORTED_INTERPOLATION_PARAMS = {
inputRange: true,
outputRange: true,
extrapolate: true,
extrapolateRight: true,
extrapolateLeft: true,
};
for (var key in config) {
if (!SUPPORTED_INTERPOLATION_PARAMS.hasOwnProperty(key)) {
Expand Down
@@ -1,5 +1,6 @@
package com.facebook.react.animated;

import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;

Expand All @@ -12,6 +13,10 @@
*/
/*package*/ class InterpolationAnimatedNode extends ValueAnimatedNode {

public static final String EXTRAPOLATE_TYPE_IDENTITY = "identity";
public static final String EXTRAPOLATE_TYPE_CLAMP = "clamp";
public static final String EXTRAPOLATE_TYPE_EXTEND = "extend";

private static double[] fromDoubleArray(ReadableArray ary) {
double[] res = new double[ary.size()];
for (int i = 0; i < res.length; i++) {
Expand All @@ -25,19 +30,62 @@ private static double interpolate(
double inputMin,
double inputMax,
double outputMin,
double outputMax) {
double outputMax,
String extrapolateLeft,
String extrapolateRight) {
double result = value;

// Extrapolate
if (result < inputMin) {
switch (extrapolateLeft) {
case EXTRAPOLATE_TYPE_IDENTITY:
return result;
case EXTRAPOLATE_TYPE_CLAMP:
result = inputMin;
break;
case EXTRAPOLATE_TYPE_EXTEND:
break;
default:
throw new JSApplicationIllegalArgumentException(
"Invalid extrapolation type " + extrapolateLeft + "for left extrapolation");
}
}

if (result > inputMax) {
switch (extrapolateRight) {
case EXTRAPOLATE_TYPE_IDENTITY:
return result;
case EXTRAPOLATE_TYPE_CLAMP:
result = inputMax;
break;
case EXTRAPOLATE_TYPE_EXTEND:
break;
default:
throw new JSApplicationIllegalArgumentException(
"Invalid extrapolation type " + extrapolateRight + "for right extrapolation");
}
}

return outputMin + (outputMax - outputMin) *
(value - inputMin) / (inputMax - inputMin);
(result - inputMin) / (inputMax - inputMin);
}

/*package*/ static double interpolate(double value, double[] inputRange, double[] outputRange) {
/*package*/ static double interpolate(
double value,
double[] inputRange,
double[] outputRange,
String extrapolateLeft,
String extrapolateRight
) {
int rangeIndex = findRangeIndex(value, inputRange);
return interpolate(
value,
inputRange[rangeIndex],
inputRange[rangeIndex + 1],
outputRange[rangeIndex],
outputRange[rangeIndex + 1]);
outputRange[rangeIndex + 1],
extrapolateLeft,
extrapolateRight);
}

private static int findRangeIndex(double value, double[] ranges) {
Expand All @@ -52,11 +100,15 @@ private static int findRangeIndex(double value, double[] ranges) {

private final double mInputRange[];
private final double mOutputRange[];
private final String mExtrapolateLeft;
private final String mExtrapolateRight;
private @Nullable ValueAnimatedNode mParent;

public InterpolationAnimatedNode(ReadableMap config) {
mInputRange = fromDoubleArray(config.getArray("inputRange"));
mOutputRange = fromDoubleArray(config.getArray("outputRange"));
mExtrapolateLeft = config.getString("extrapolateLeft");
mExtrapolateRight = config.getString("extrapolateRight");
}

@Override
Expand Down Expand Up @@ -84,6 +136,6 @@ public void update() {
throw new IllegalStateException("Trying to update interpolation node that has not been " +
"attached to the parent");
}
mValue = interpolate(mParent.mValue, mInputRange, mOutputRange);
mValue = interpolate(mParent.mValue, mInputRange, mOutputRange, mExtrapolateLeft, mExtrapolateRight);
}
}
Expand Up @@ -12,53 +12,102 @@
@RunWith(RobolectricTestRunner.class)
public class NativeAnimatedInterpolationTest {

private double simpleInterpolation(double value, double[] input, double[] output) {
return InterpolationAnimatedNode.interpolate(
value,
input,
output,
InterpolationAnimatedNode.EXTRAPOLATE_TYPE_EXTEND,
InterpolationAnimatedNode.EXTRAPOLATE_TYPE_EXTEND
);
}

@Test
public void testSimpleOneToOneMapping() {
double[] input = new double[] {0d, 1d};
double[] output = new double[] {0d, 1d};
assertThat(InterpolationAnimatedNode.interpolate(0, input, output)).isEqualTo(0);
assertThat(InterpolationAnimatedNode.interpolate(0.5, input, output)).isEqualTo(0.5);
assertThat(InterpolationAnimatedNode.interpolate(0.8, input, output)).isEqualTo(0.8);
assertThat(InterpolationAnimatedNode.interpolate(1, input, output)).isEqualTo(1);
assertThat(simpleInterpolation(0, input, output)).isEqualTo(0);
assertThat(simpleInterpolation(0.5, input, output)).isEqualTo(0.5);
assertThat(simpleInterpolation(0.8, input, output)).isEqualTo(0.8);
assertThat(simpleInterpolation(1, input, output)).isEqualTo(1);
}

@Test
public void testWiderOutputRange() {
double[] input = new double[] {0d, 1d};
double[] output = new double[] {100d, 200d};
assertThat(InterpolationAnimatedNode.interpolate(0, input, output)).isEqualTo(100);
assertThat(InterpolationAnimatedNode.interpolate(0.5, input, output)).isEqualTo(150);
assertThat(InterpolationAnimatedNode.interpolate(0.8, input, output)).isEqualTo(180);
assertThat(InterpolationAnimatedNode.interpolate(1, input, output)).isEqualTo(200);
assertThat(simpleInterpolation(0, input, output)).isEqualTo(100);
assertThat(simpleInterpolation(0.5, input, output)).isEqualTo(150);
assertThat(simpleInterpolation(0.8, input, output)).isEqualTo(180);
assertThat(simpleInterpolation(1, input, output)).isEqualTo(200);
}

@Test
public void testWiderInputRange() {
double[] input = new double[] {2000d, 3000d};
double[] output = new double[] {1d, 2d};
assertThat(InterpolationAnimatedNode.interpolate(2000, input, output)).isEqualTo(1);
assertThat(InterpolationAnimatedNode.interpolate(2250, input, output)).isEqualTo(1.25);
assertThat(InterpolationAnimatedNode.interpolate(2800, input, output)).isEqualTo(1.8);
assertThat(InterpolationAnimatedNode.interpolate(3000, input, output)).isEqualTo(2);
assertThat(simpleInterpolation(2000, input, output)).isEqualTo(1);
assertThat(simpleInterpolation(2250, input, output)).isEqualTo(1.25);
assertThat(simpleInterpolation(2800, input, output)).isEqualTo(1.8);
assertThat(simpleInterpolation(3000, input, output)).isEqualTo(2);
}

@Test
public void testManySegments() {
double[] input = new double[] {-1d, 1d, 5d};
double[] output = new double[] {0, 10d, 20d};
assertThat(InterpolationAnimatedNode.interpolate(-1, input, output)).isEqualTo(0);
assertThat(InterpolationAnimatedNode.interpolate(0, input, output)).isEqualTo(5);
assertThat(InterpolationAnimatedNode.interpolate(1, input, output)).isEqualTo(10);
assertThat(InterpolationAnimatedNode.interpolate(2, input, output)).isEqualTo(12.5);
assertThat(InterpolationAnimatedNode.interpolate(5, input, output)).isEqualTo(20);
assertThat(simpleInterpolation(-1, input, output)).isEqualTo(0);
assertThat(simpleInterpolation(0, input, output)).isEqualTo(5);
assertThat(simpleInterpolation(1, input, output)).isEqualTo(10);
assertThat(simpleInterpolation(2, input, output)).isEqualTo(12.5);
assertThat(simpleInterpolation(5, input, output)).isEqualTo(20);
}

@Test
public void testExtrapolate() {
public void testExtendExtrapolate() {
double[] input = new double[] {10d, 20d};
double[] output = new double[] {0d, 1d};
assertThat(InterpolationAnimatedNode.interpolate(30d, input, output)).isEqualTo(2);
assertThat(InterpolationAnimatedNode.interpolate(5d, input, output)).isEqualTo(-0.5);
assertThat(simpleInterpolation(30d, input, output)).isEqualTo(2);
assertThat(simpleInterpolation(5d, input, output)).isEqualTo(-0.5);
}

@Test
public void testClampExtrapolate() {
double[] input = new double[] {10d, 20d};
double[] output = new double[] {0d, 1d};
assertThat(InterpolationAnimatedNode.interpolate(
30d,
input,
output,
InterpolationAnimatedNode.EXTRAPOLATE_TYPE_CLAMP,
InterpolationAnimatedNode.EXTRAPOLATE_TYPE_CLAMP
)).isEqualTo(1);
assertThat(InterpolationAnimatedNode.interpolate(
5d,
input,
output,
InterpolationAnimatedNode.EXTRAPOLATE_TYPE_CLAMP,
InterpolationAnimatedNode.EXTRAPOLATE_TYPE_CLAMP
)).isEqualTo(0);
}

@Test
public void testIdentityExtrapolate() {
double[] input = new double[] {10d, 20d};
double[] output = new double[] {0d, 1d};
assertThat(InterpolationAnimatedNode.interpolate(
30d,
input,
output,
InterpolationAnimatedNode.EXTRAPOLATE_TYPE_IDENTITY,
InterpolationAnimatedNode.EXTRAPOLATE_TYPE_IDENTITY
)).isEqualTo(30);
assertThat(InterpolationAnimatedNode.interpolate(
5d,
input,
output,
InterpolationAnimatedNode.EXTRAPOLATE_TYPE_IDENTITY,
InterpolationAnimatedNode.EXTRAPOLATE_TYPE_IDENTITY
)).isEqualTo(5);
}
}
Expand Up @@ -652,7 +652,11 @@ public void testInterpolationNode() {
"inputRange",
JavaOnlyArray.of(10d, 20d),
"outputRange",
JavaOnlyArray.of(0d, 1d)));
JavaOnlyArray.of(0d, 1d),
"extrapolateLeft",
"extend",
"extrapolateRight",
"extend"));

mNativeAnimatedNodesManager.createAnimatedNode(
3,
Expand Down

0 comments on commit 6d978c3

Please sign in to comment.