Add interpolate function from kmagiera with example. [WIP] (#11)

Interpolate function from https://github.com/kmagiera/react-native-reanimated/issues/10#issuecomment-392066106 with extrapolation handling, examples, and documentation.

Closes #10 .
This commit is contained in:
Dylan Vann
2018-06-01 02:14:44 -04:00
committed by Krzysztof Magiera
parent 8d3ece3356
commit b9ba694ace
13 changed files with 766 additions and 23 deletions

View File

@@ -1,11 +1,12 @@
import React from 'react';
import { Text, View, FlatList, StyleSheet, YellowBox } from 'react-native';
import { StackNavigator } from 'react-navigation';
import { createStackNavigator } from 'react-navigation';
import { RectButton, ScrollView } from 'react-native-gesture-handler';
import Snappable from './snappable';
import ImageViewer from './imageViewer';
import Test from './test';
import Interpolate from './src/interpolate';
YellowBox.ignoreWarnings([
'Warning: isMounted(...) is deprecated',
@@ -18,11 +19,12 @@ const SCREENS = {
Snappable: { screen: Snappable, title: 'Snappable' },
Test: { screen: Test, title: 'Test' },
ImageViewer: { screen: ImageViewer, title: 'Image Viewer' },
Interpolate: { screen: Interpolate, title: 'Interpolate' },
};
class MainScreen extends React.Component {
static navigationOptions = {
title: '🎬 Reanimated Demo',
title: '🎬 Reanimated Examples',
};
render() {
const data = Object.keys(SCREENS).map(key => ({ key }));
@@ -57,7 +59,7 @@ class MainScreenItem extends React.Component {
}
}
const ExampleApp = StackNavigator(
const ExampleApp = createStackNavigator(
{
Main: { screen: MainScreen },
...SCREENS,

View File

@@ -420,6 +420,9 @@ class Viewer extends Component {
}
export default class Example extends Component {
static navigationOptions = {
title: 'Image Viewer Example',
};
render() {
return (
<View style={styles.container}>

View File

@@ -12,7 +12,7 @@
"react-native": "0.55.3",
"react-native-gesture-handler": "^1.0.0",
"react-native-reanimated": "file:../",
"react-navigation": "^1.5.11"
"react-navigation": "^2.0.4"
},
"devDependencies": {
"babel-jest": "22.4.3",

View File

@@ -128,6 +128,9 @@ class Snappable extends Component {
}
export default class Example extends Component {
static navigationOptions = {
title: 'Snappable Example',
};
render() {
return (
<View style={styles.container}>

25
Example/src/Box.js Normal file
View File

@@ -0,0 +1,25 @@
import React from 'react';
import { StyleSheet } from 'react-native';
import Animated, { Easing } from 'react-native-reanimated';
/**
* Needs to be a class component for react-native-gesture-handler to put a ref on it.
*/
export default class Box extends React.Component {
render() {
const { style, ...props } = this.props;
return <Animated.View style={[styles.box, style]} {...props} />;
}
}
const BOX_SIZE = 44;
const styles = StyleSheet.create({
box: {
width: BOX_SIZE,
height: BOX_SIZE,
alignSelf: 'center',
backgroundColor: 'blue',
margin: 10,
},
});

14
Example/src/Row.js Normal file
View File

@@ -0,0 +1,14 @@
import React from 'react';
import { View, StyleSheet } from 'react-native';
const Row = ({ style, ...props }) => (
<View style={[styles.style, style]} {...props} pointerEvents="box-none" />
);
const styles = StyleSheet.create({
style: {
height: 64,
},
});
export default Row;

View File

@@ -0,0 +1,233 @@
import React, { Component } from 'react';
import { StyleSheet, View } from 'react-native';
import Animated, { Easing } from 'react-native-reanimated';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
import Box from '../Box';
import Row from '../Row';
const {
set,
cond,
sub,
eq,
and,
add,
call,
multiply,
lessThan,
startClock,
stopClock,
clockRunning,
block,
timing,
debug,
spring,
Value,
Clock,
event,
interpolate,
defined,
} = Animated;
function runSpring(clock, value, velocity, dest) {
const state = {
finished: new Value(0),
velocity: new Value(0),
position: new Value(0),
time: new Value(0),
};
const config = {
damping: 7,
mass: 1,
stiffness: 121.6,
overshootClamping: false,
restSpeedThreshold: 0.001,
restDisplacementThreshold: 0.001,
toValue: new Value(0),
};
return [
cond(clockRunning(clock), 0, [
set(state.finished, 0),
set(state.velocity, velocity),
set(state.position, value),
set(config.toValue, dest),
startClock(clock),
]),
cond(state.finished, stopClock(clock)),
state.position,
];
}
function runTiming(clock, value, dest) {
const state = {
finished: new Value(1),
position: new Value(value),
time: new Value(0),
frameTime: new Value(0),
};
const config = {
duration: 500,
toValue: new Value(0),
easing: Easing.inOut(Easing.ease),
};
const reset = [
set(state.finished, 0),
set(state.time, 0),
set(state.frameTime, 0),
];
return block([
cond(and(state.finished, eq(state.position, value)), [
...reset,
set(config.toValue, dest),
]),
cond(and(state.finished, eq(state.position, dest)), [
...reset,
set(config.toValue, value),
]),
cond(clockRunning(clock), 0, startClock(clock)),
timing(clock, state, config),
state.position,
]);
}
const getAnimation = (min, max) => {
const clock = new Clock();
const state = {
finished: new Value(1),
position: new Value(min),
time: new Value(0),
frameTime: new Value(0),
};
const config = {
duration: 500,
toValue: new Value(0),
easing: Easing.inOut(Easing.ease),
};
const reset = [
set(state.finished, 0),
set(state.time, 0),
set(state.frameTime, 0),
];
return block([
cond(and(state.finished, eq(state.position, min)), [
...reset,
set(config.toValue, max),
]),
cond(and(state.finished, eq(state.position, max)), [
...reset,
set(config.toValue, min),
]),
cond(clockRunning(clock), 0, startClock(clock)),
timing(clock, state, config),
state.position,
]);
};
export default class AnimatedBounds extends Component {
constructor(props) {
super(props);
const TOSS_SEC = 0.2;
const dragX = new Value(0);
const state = new Value(-1);
const dragVX = new Value(0);
const transX = new Value();
const prevDragX = new Value(0);
const clock = new Clock();
this._onGestureEvent = event([
{ nativeEvent: { translationX: dragX, velocityX: dragVX, state: state } },
]);
const snapPoint = cond(
lessThan(add(transX, multiply(TOSS_SEC, dragVX)), 0),
-100,
100
);
this._transX = cond(
eq(state, State.ACTIVE),
[
stopClock(clock),
set(transX, add(transX, sub(dragX, prevDragX))),
set(prevDragX, dragX),
transX,
],
[
set(prevDragX, 0),
set(
transX,
cond(defined(transX), runSpring(clock, transX, dragVX, snapPoint), 0)
),
]
);
this._transX = interpolate(this._transX, {
inputRange: [-100, 100],
outputRange: [-100, 100],
extrapolate: 'clamp',
});
const min = getAnimation(-100, -50);
const max = getAnimation(100, 50);
this._transXA = interpolate(this._transX, {
inputRange: [-100, 100],
outputRange: [min, max],
extrapolate: 'clamp',
});
this.min = min;
this.max = max;
}
render() {
return (
<View style={styles.container}>
<Row>
<Animated.View
style={[styles.line, { transform: [{ translateX: -100 }] }]}
/>
<Animated.View
style={[styles.line, { transform: [{ translateX: 100 }] }]}
/>
<PanGestureHandler
maxPointers={1}
onGestureEvent={this._onGestureEvent}
onHandlerStateChange={this._onGestureEvent}>
<Box style={{ transform: [{ translateX: this._transX }] }} />
</PanGestureHandler>
</Row>
<Row>
<Animated.View
style={[styles.line, { transform: [{ translateX: this.min }] }]}
/>
<Animated.View
style={[styles.line, { transform: [{ translateX: this.max }] }]}
/>
<Box style={{ transform: [{ translateX: this._transXA }] }} />
</Row>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
line: {
position: 'absolute',
alignSelf: 'center',
backgroundColor: 'red',
height: 64,
width: 1,
},
});

View File

@@ -0,0 +1,81 @@
import React, { Component } from 'react';
import { StyleSheet, View } from 'react-native';
import Animated, { Easing } from 'react-native-reanimated';
import Box from '../Box';
import Row from '../Row';
const {
set,
cond,
eq,
and,
add,
call,
multiply,
lessThan,
startClock,
stopClock,
clockRunning,
block,
timing,
debug,
spring,
Value,
Clock,
event,
interpolate,
} = Animated;
function runTiming(clock, value, dest) {
const state = {
finished: new Value(1),
position: new Value(value),
time: new Value(0),
frameTime: new Value(0),
};
const config = {
duration: 500,
toValue: new Value(0),
easing: Easing.inOut(Easing.ease),
};
const reset = [
set(state.finished, 0),
set(state.time, 0),
set(state.frameTime, 0),
];
return block([
cond(and(state.finished, eq(state.position, value)), [
...reset,
set(config.toValue, dest),
]),
cond(and(state.finished, eq(state.position, dest)), [
...reset,
set(config.toValue, value),
]),
cond(clockRunning(clock), 0, startClock(clock)),
timing(clock, state, config),
state.position,
]);
}
export default class Basic extends Component {
constructor(props) {
super(props);
const clock = new Clock();
const base = runTiming(clock, -1, 1);
this._transX = interpolate(base, {
inputRange: [-1, 1],
outputRange: [-100, 100],
});
}
render() {
return (
<Row>
<Box style={{ transform: [{ translateX: this._transX }] }} />
</Row>
);
}
}

View File

@@ -0,0 +1,176 @@
import React, { Component } from 'react';
import { StyleSheet, View } from 'react-native';
import Animated, { Easing } from 'react-native-reanimated';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
import Box from '../Box';
import Row from '../Row';
const {
set,
cond,
sub,
eq,
and,
add,
call,
multiply,
lessThan,
startClock,
stopClock,
clockRunning,
block,
timing,
debug,
spring,
Value,
Clock,
event,
interpolate,
defined,
} = Animated;
function runSpring(clock, value, velocity, dest) {
const state = {
finished: new Value(0),
velocity: new Value(0),
position: new Value(0),
time: new Value(0),
};
const config = {
damping: 7,
mass: 1,
stiffness: 121.6,
overshootClamping: false,
restSpeedThreshold: 0.001,
restDisplacementThreshold: 0.001,
toValue: new Value(0),
};
return [
cond(clockRunning(clock), 0, [
set(state.finished, 0),
set(state.velocity, velocity),
set(state.position, value),
set(config.toValue, dest),
startClock(clock),
]),
cond(state.finished, stopClock(clock)),
state.position,
];
}
function runTiming(clock, value, dest) {
const state = {
finished: new Value(1),
position: new Value(value),
time: new Value(0),
frameTime: new Value(0),
};
const config = {
duration: 500,
toValue: new Value(0),
easing: Easing.inOut(Easing.ease),
};
const reset = [
set(state.finished, 0),
set(state.time, 0),
set(state.frameTime, 0),
];
return block([
cond(and(state.finished, eq(state.position, value)), [
...reset,
set(config.toValue, dest),
]),
cond(and(state.finished, eq(state.position, dest)), [
...reset,
set(config.toValue, value),
]),
cond(clockRunning(clock), 0, startClock(clock)),
timing(clock, state, config),
state.position,
]);
}
export default class WithDrag extends Component {
constructor(props) {
super(props);
const TOSS_SEC = 0.2;
const dragX = new Value(0);
const state = new Value(-1);
const dragVX = new Value(0);
const transX = new Value();
const prevDragX = new Value(0);
const clock = new Clock();
this._onGestureEvent = event([
{ nativeEvent: { translationX: dragX, velocityX: dragVX, state: state } },
]);
const snapPoint = cond(
lessThan(add(transX, multiply(TOSS_SEC, dragVX)), 0),
-100,
100
);
this._transX = cond(
eq(state, State.ACTIVE),
[
stopClock(clock),
set(transX, add(transX, sub(dragX, prevDragX))),
set(prevDragX, dragX),
transX,
],
[
set(prevDragX, 0),
set(
transX,
cond(defined(transX), runSpring(clock, transX, dragVX, snapPoint), 0)
),
]
);
this._transXA = interpolate(this._transX, {
inputRange: [-120, 120],
outputRange: [-100, 100],
});
this._transXB = interpolate(this._transX, {
inputRange: [-120, -60, 60, 120],
outputRange: [-60, -10, 10, 60],
});
}
render() {
return (
<View style={styles.container}>
<Row>
<PanGestureHandler
maxPointers={1}
onGestureEvent={this._onGestureEvent}
onHandlerStateChange={this._onGestureEvent}>
<Box style={{ transform: [{ translateX: this._transX }] }} />
</PanGestureHandler>
</Row>
<Row>
<Box style={{ transform: [{ translateX: this._transXA }] }} />
</Row>
<Row>
<Box style={{ transform: [{ translateX: this._transXB }] }} />
</Row>
</View>
);
}
}
const BOX_SIZE = 44;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});

View File

@@ -0,0 +1,69 @@
import React from 'react';
import { Text, View, FlatList, StyleSheet, YellowBox } from 'react-native';
import { StackNavigator } from 'react-navigation';
import { RectButton, ScrollView } from 'react-native-gesture-handler';
import Basic from './Basic';
import WithDrag from './WithDrag';
import AnimatedBounds from './AnimatedBounds';
const examples = [Basic, WithDrag, AnimatedBounds].map(v => ({
key: v.displayName,
title: v.displayName,
Component: v,
}));
class Item extends React.Component {
render() {
const item = this.props.item;
const Comp = this.props.item.Component;
return (
<View style={styles.button}>
<Text>{item.title}</Text>
<Comp />
</View>
);
}
}
class MainScreen extends React.Component {
static navigationOptions = {
title: 'Interpolation Examples',
};
renderItem = props => <Item {...props} />;
render() {
return (
<FlatList
style={styles.list}
data={examples}
ItemSeparatorComponent={ItemSeparator}
renderItem={this.renderItem}
/>
);
}
}
const ItemSeparator = () => <View style={styles.separator} />;
const styles = StyleSheet.create({
list: {
backgroundColor: '#EFEFF4',
},
separator: {
height: 1,
backgroundColor: '#DBDBE0',
},
buttonText: {
backgroundColor: 'transparent',
},
button: {
flex: 1,
flexDirection: 'column',
alignItems: 'center',
backgroundColor: '#fff',
padding: 20,
},
});
export default MainScreen;

View File

@@ -1657,6 +1657,13 @@ create-react-class@^15.6.3:
loose-envify "^1.3.1"
object-assign "^4.1.1"
create-react-context@^0.2.1:
version "0.2.2"
resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.2.2.tgz#9836542f9aaa22868cd7d4a6f82667df38019dca"
dependencies:
fbjs "^0.8.0"
gud "^1.0.0"
cross-spawn@^5.0.1, cross-spawn@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
@@ -2089,7 +2096,7 @@ fbjs-scripts@^0.8.1:
semver "^5.1.0"
through2 "^2.0.0"
fbjs@^0.8.14, fbjs@^0.8.16, fbjs@^0.8.9:
fbjs@^0.8.0, fbjs@^0.8.14, fbjs@^0.8.16, fbjs@^0.8.9:
version "0.8.16"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db"
dependencies:
@@ -2323,6 +2330,10 @@ growly@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
gud@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0"
handlebars@^4.0.3:
version "4.0.11"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc"
@@ -2408,7 +2419,7 @@ hoek@4.x.x:
version "4.2.1"
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb"
hoist-non-react-statics@^2.2.0, hoist-non-react-statics@^2.3.1:
hoist-non-react-statics@^2.2.0, hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.0.tgz#d2ca2dfc19c5a91c5a6615ce8e564ef0347e2a40"
@@ -4160,9 +4171,9 @@ react-is@^16.3.1:
version "16.3.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.3.2.tgz#f4d3d0e2f5fbb6ac46450641eb2e25bf05d36b22"
react-lifecycles-compat@^1.0.2:
version "1.1.4"
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-1.1.4.tgz#fc005c72849b7ed364de20a0f64ff58ebdc2009a"
react-lifecycles-compat@^3, react-lifecycles-compat@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
react-native-dismiss-keyboard@1.0.0:
version "1.0.0"
@@ -4197,9 +4208,21 @@ react-native-safe-area-view@^0.7.0:
dependencies:
hoist-non-react-statics "^2.3.1"
"react-native-tab-view@github:react-navigation/react-native-tab-view":
version "0.0.74"
resolved "https://codeload.github.com/react-navigation/react-native-tab-view/tar.gz/36ebd834d78b841fc19778c966465d02fd1213bb"
react-native-safe-area-view@^0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/react-native-safe-area-view/-/react-native-safe-area-view-0.8.0.tgz#22d78cb8e8658d04a10cd53c1546e0bc86cb7aea"
dependencies:
hoist-non-react-statics "^2.3.1"
react-native-tab-view@^0.0.77:
version "0.0.77"
resolved "https://registry.yarnpkg.com/react-native-tab-view/-/react-native-tab-view-0.0.77.tgz#11ceb8e7c23100d07e628dc151b57797524d00d4"
dependencies:
prop-types "^15.6.0"
react-native-tab-view@~0.0.78:
version "0.0.78"
resolved "https://registry.yarnpkg.com/react-native-tab-view/-/react-native-tab-view-0.0.78.tgz#9b90730d89cbd34a03f0e0ab10e74ca7af945560"
dependencies:
prop-types "^15.6.0"
@@ -4267,18 +4290,36 @@ react-native@0.55.3:
xmldoc "^0.4.0"
yargs "^9.0.0"
react-navigation@^1.5.11:
version "1.5.11"
resolved "https://registry.yarnpkg.com/react-navigation/-/react-navigation-1.5.11.tgz#fba6ab45d7b9987c1763a5aaac53b4f6d62b7f5c"
react-navigation-deprecated-tab-navigator@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/react-navigation-deprecated-tab-navigator/-/react-navigation-deprecated-tab-navigator-1.3.0.tgz#015dcae1e977b984ca7e99245261c15439026bb7"
dependencies:
react-native-tab-view "^0.0.77"
react-navigation-tabs@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/react-navigation-tabs/-/react-navigation-tabs-0.3.0.tgz#b1fe7ef1c665dd8928fafcc8622616e220ae5efa"
dependencies:
hoist-non-react-statics "^2.5.0"
prop-types "^15.6.0"
react-lifecycles-compat "^3.0.4"
react-native-safe-area-view "^0.7.0"
react-native-tab-view "~0.0.78"
react-navigation@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/react-navigation/-/react-navigation-2.0.4.tgz#efc09de09799c2117a54002a6ea0e9b80e92828d"
dependencies:
clamp "^1.0.1"
create-react-context "^0.2.1"
hoist-non-react-statics "^2.2.0"
path-to-regexp "^1.7.0"
prop-types "^15.5.10"
react-lifecycles-compat "^1.0.2"
react-lifecycles-compat "^3"
react-native-drawer-layout-polyfill "^1.3.2"
react-native-safe-area-view "^0.7.0"
react-native-tab-view "github:react-navigation/react-native-tab-view"
react-native-safe-area-view "^0.8.0"
react-navigation-deprecated-tab-navigator "1.3.0"
react-navigation-tabs "0.3.0"
react-proxy@^1.1.7:
version "1.1.8"

View File

@@ -70,7 +70,6 @@ All the functionality that missing elements provide in Animated can be already a
- [ ] value tracking (can be achieved in different way, reanimated also allows for tracking all the animation parameters not only destination params)
- [ ] animation delays
- [ ] animation staggering
- [ ] interpolate method
## Clocks
@@ -422,6 +421,29 @@ Returns an accumulated value of the given node. This node stores a sum of all ev
Works the same way as with the original Animated library.
---
### `interpolate`
```js
interpolate(node, {
// Input range for the interpolation. Should be monotonically increasing.
inputRange: [nodeOrValue...],
// Output range for the interpolation, should be the same length as the input range.
outputRange: [nodeOrValue...],
// Sets the left and right extrapolate modes.
extrapolate?: Extrapolate.EXTEND | Extrapolate.CLAMP | Extrapolate.IDENTITY,
// Set the left extrapolate mode, the behavior if the input is less than the first value in inputRange.
extrapolateLeft?: Extrapolate.EXTEND | Extrapolate.CLAMP | Extrapolate.IDENTITY,
// Set the right extrapolate mode, the behavior if the input is greater than the last value in inputRange.
extrapolateRight?: Extrapolate.EXTEND | Extrapolate.CLAMP | Extrapolate.IDENTITY,
})
Extrapolate.EXTEND; // Will extend the range linearly.
Extrapolate.CLAMP; // Will clamp the input value to the range.
Extrapolate.IDENTITY; // Will return the input value if the input value is out of range.
```
Maps an input value within a range to an output value within a range. Also supports different types of extrapolation for when the value falls outside the range.
<!-- Anims -->
---
@@ -434,7 +456,7 @@ decay(clock, { finished, velocity, position, time }, { deceleration })
Updates `position` and `velocity` nodes by running a single step of animation each time this node evaluates. State variable `finished` is set to `1` when the animation gets to the final point (that is the velocity drops under the level of significance). The `time` state node is populated automatically by this node and refers to the last clock time this node got evaluated. It is expected to be reset each time we want to restart the animation. Decay animation can be configured using `deceleration` config param and it controls how fast the animation decelerates. The value should be between `0` and `1` but only values that are close to `1` would yield meaningful results.
---
#### `timing`
### `timing`
```js
timing(clock, { finished, position, frameTime, time }, { toValue, duration, easing })
@@ -444,7 +466,7 @@ Updates `position` node by running timing based animation from a given position
The `frameTime` node will also get updated and represents the progress of animation in milliseconds (how long the animation has lasted so far). Similarly to the `time` node that just indicates the last clock time the animation node has been evaluated. Both of these variables are expected to be reset before restarting the animation. Finally `finished` node will be set to `1` when the position reaches the final value or when `frameTime` exceeds `duration`.
---
#### `spring`
### `spring`
```js
spring(clock, { finished, position, velocity, time }, { damping, mass, stiffness, overshootClamping, restSpeedThreshold, restDisplacementThreshold, toValue })

View File

@@ -7,13 +7,14 @@ import {
sub,
set,
add,
divide,
} from './base';
import AnimatedValue from './core/AnimatedValue';
import { adapt } from './utils';
export function abs(a) {
export const abs = function(a) {
return cond(lessThan(a, 0), multiply(-1, a), a);
}
};
export const min = function(a, b) {
a = adapt(a);
@@ -49,3 +50,76 @@ export const diffClamp = function(a, minVal, maxVal) {
min(max(add(cond(defined(value), value, a), diff(a)), minVal), maxVal)
);
};
const interpolateInternalSingle = function(
value,
inputRange,
outputRange,
offset
) {
const inS = inputRange[offset];
const inE = inputRange[offset + 1];
const outS = outputRange[offset];
const outE = outputRange[offset + 1];
const progress = divide(sub(value, inS), sub(inE, inS));
return add(outS, multiply(progress, sub(outE, outS)));
};
const interpolateInternal = function(
value,
inputRange,
outputRange,
offset = 0
) {
if (inputRange.length - offset === 2) {
return interpolateInternalSingle(value, inputRange, outputRange, offset);
}
return cond(
lessThan(value, inputRange[offset + 1]),
interpolateInternalSingle(value, inputRange, outputRange, offset),
interpolateInternal(value, inputRange, outputRange, offset + 1)
);
};
export const Extrapolate = {
EXTEND: 'EXTEND',
CLAMP: 'CLAMP',
IDENTITY: 'IDENTITY',
};
export const interpolate = function(value, config) {
const {
inputRange,
outputRange,
extrapolate = Extrapolate.EXTEND,
extrapolateLeft,
extrapolateRight,
} = config;
const left = extrapolateLeft || extrapolate;
const right = extrapolateRight || extrapolate;
let output = interpolateInternal(value, inputRange, outputRange);
if (left === Extrapolate.EXTEND) {
} else if (left === Extrapolate.CLAMP) {
output = cond(lessThan(value, inputRange[0]), outputRange[0], output);
} else if (left === Extrapolate.IDENTITY) {
output = cond(lessThan(value, inputRange[0]), value, output);
}
if (right === Extrapolate.EXTEND) {
} else if (right === Extrapolate.CLAMP) {
output = cond(
greaterThan(value, inputRange[inputRange.length - 1]),
outputRange[outputRange.length - 1],
output
);
} else if (right === Extrapolate.IDENTITY) {
output = cond(
greaterThan(value, inputRange[inputRange.length - 1]),
value,
output
);
}
return output;
};