In React Native I draw my charts with d3 and ART libraries.
export default class DevTest extends React.Component {
static defaultProps = {
width: 300,
height: 150,
}
mainScaleX = d3Scale.scaleTime()
.domain(d3Array.extent(chartDataTo, data => data.date))
.range([0, this.props.width])
mainScaleY = d3Scale.scaleLinear()
.domain(d3Array.extent(chartDataTo, data => data.value))
.range([this.props.height, 0])
rawChartValue = d3Shape.line()
.x(data => this.mainScaleX(data.date))
.y(data => this.mainScaleY(data.value))
render(){
return (
<View>
<Surface width = {this.props.width} height = {this.props.height}>
<Group x = {0} y = {0}>
<Shape
d = {this.rawChartValue(chartData)}
stroke = "#000"
strokeWidth = {1}
/>
</Group>
</Surface>
</View>
)
}
}
Is there any way to implement d3 svg-like zoom behavior, but without DOM mutation. For example, using some d3 pure functions with just x and y values as input to rescale mainScaleX and mainScaleY?
You can use PanResponder from react-native and zoomIdentity from d3-zoom for that purpose.
Code example looks sometihng like this
componentWillMount() {
this._panResponder = PanResponder.create({
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onPanResponderMove: (event, { dx, dy }) => {
const zoomIdentity = d3Zoom.zoomIdentity.translate(dx, dy)
this._onDrag(zoomIdentity)
},
});
}
_onDrag(zoomIdentity){
this.setState({
rescaledX: zoomIdentity.rescaleX(this.state.mainScaleX),
rescaledY: zoomIdentity.rescaleY(this.state.mainScaleY)
})
}
And you pass your rescaled{X, Y} to a chart component
render(){
return (
<View {...this._panResponder.panHandlers}>
<Component
{...this.props}
scaleX = {this.state.rescaledX}
scaleY = {this.state.rescaledY}
/>
</View>
)
}
Related
I'm trying to make a sphere that follows my cursor. Most of it is done, however, the sphere severely warps on the edges of my screen.
Middle of screen:
Edge of screen:
I have the default camera (fov changed) setup but I feel like it's the wrong one
Canvas:
const Canvas: NextPage<CanvasProps> = ({ children }) => {
const camera = { fov: 60, near: 0.1, far: 1000, position: [0, 0, 5] }
return (
<ThreeCanvas className="bg-gray-900" camera={camera}>
{children}
<Mouse />
</ThreeCanvas>
)
}
Mouse.tsx:
const Mouse: NextPage<MouseProps> = ({}) => {
const [active, setActive] = useState(false)
const { viewport } = useThree()
const { scale } = useSpring({ scale: active ? 0.7 : 1 })
useEffect(() => {
if (active) {
setTimeout(() => setActive(false), 200)
}
}, [active])
const ref = useRef()
useFrame(({ mouse }) => {
const x = (mouse.x * viewport.width) / 2
const y = (mouse.y * viewport.height) / 2
if (typeof ref !== undefined) {
// #ts-ignore
ref!.current.position.set(x, y, 0)
// #ts-ignore
ref!.current.rotation.set(-y, x, 0)
}
})
return (
<>
<ambientLight />
<pointLight position={[10, 10, 10]} />
<animated.mesh scale={scale} onClick={() => setActive(!active)} ref={ref}>
<sphereGeometry args={[0.15, 32, 32]} />
<meshStandardMaterial color="beige" transparent />
</animated.mesh>
</>
)
}
I am trying to use react-native-camera package to create an app to take pictures and record videos.
I took the example code and converted it to functional component, since its necessary for my app. I am able to take pictures and store in local cache, but function for recording video is not working, and not showing any output when I console.log it. Do I need to implement stop recording also? I am new to react native and and I didn't find reference for functional implementation anywhere properly. I am confused regarding this. Below is the code, with two buttons, one for picture and one for video
...
import { RNCamera } from "react-native-camera";
const App = () => {
let [flash, setFlash] = useState("off");
let [zoom, setZoom] = useState(0);
let [autoFocus, setAutoFocus] = useState("on");
let [depth, setDepth] = useState(0);
let [type, setType] = useState("back");
let [permission, setPermission] = useState("undetermined");
let [isRecording, setIsRecording] = useState("false");
let [recordingOptions, setRecordingOptions] = useState({
mute: false,
maxDuration: 10,
quality: RNCamera.Constants.VideoQuality["360p"],
});
let cameraRef = useRef(null);
useEffect(() => {
Permissions.check("photo").then((response) => {
// Response is one of: 'authorized', 'denied', 'restricted', or 'undetermined'
setPermission(response);
});
}, []);
const toggleFlash = () => {
setFlash(flashModeOrder[flash]);
};
const zoomOut = () => {
setZoom(zoom - 0.1 < 0 ? 0 : zoom - 0.1);
};
const zoomIn = () => {
setZoom(zoom + 0.1 > 1 ? 1 : zoom + 0.1);
};
const takePicture = async () => {
if (cameraRef) {
const options = { quality: 0.5, base64: true };
const data = await cameraRef.current.takePictureAsync(options);
console.log(data.uri);
}
};
const takeVideo = async () => {
if (cameraRef && !isRecording) {
try {
console.log(recordingOptions);
const recordoptions = {
mute: false,
maxDuration: 10,
quality: RNCamera.Constants.VideoQuality["360p"],
};
const promise = cameraRef.current.recordAsync(recordOptions);
if (promise) {
setIsRecording(true);
const data = await promise;
console.log("takeVideo", data.uri);
}
} catch (e) {
console.error(e);
}
}
};
return (
<View style={styles.container}>
<RNCamera
ref={cameraRef}
style={styles.preview}
type={type}
flashMode={flash}
/>
<View style={{ flex: 0, flexDirection: "row", justifyContent: "center" }}>
<TouchableOpacity onPress={takePicture} style={styles.capture}>
<Text style={{ fontSize: 14 }}> TAKE PICTURE </Text>
</TouchableOpacity>
</View>
<View style={{ flex: 0, flexDirection: "row", justifyContent: "center" }}>
<TouchableOpacity onPress={takeVideo} style={styles.capture}>
<Text style={{ fontSize: 14 }}> TAKE VIDEO </Text>
</TouchableOpacity>
</View>
</View>
);
};
export default App;
Refactor these lines
const promise = cameraRef.current.recordAsync(recordOptions);
if (promise) {
setIsRecording(true);
const data = await promise;
console.log("takeVideo", data.uri);
}
To
setIsRecording(true);
const data = await cameraRef.current.recordAsync(recordOptions);
console.log(data);
I'm using react native v0.49 and I'm trying to implement custom transition when navigate to other page.
what I'm trying to do is to make transition only for one scene from scene 2 to scene3. but not for all the app.
this example I found it's for all web so I want to make just for one screen and for all the app because if I do that way it will effect for all the app and it's not what I'm looking for. any idea?
class SceneOne extends Component {
render() {
return (
<View>
<Text>{'Scene One'}</Text>
</View>
)
}
}
class SceneTwo extends Component {
render() {
return (
<View>
<Text>{'Scene Two'}</Text>
</View>
)
}
}
let AppScenes = {
SceneOne: {
screen: SceneOne
},
SceneTwo: {
screen: SceneTwo
},
SceneThree: {
screen: SceneTwo
},
}
let MyTransition = (index, position) => {
const inputRange = [index - 1, index, index + 1];
const opacity = position.interpolate({
inputRange,
outputRange: [.8, 1, 1],
});
const scaleY = position.interpolate({
inputRange,
outputRange: ([0.8, 1, 1]),
});
return {
opacity,
transform: [
{scaleY}
]
};
};
let TransitionConfiguration = () => {
return {
// Define scene interpolation, eq. custom transition
screenInterpolator: (sceneProps) => {
const {position, scene} = sceneProps;
const {index} = scene;
return MyTransition(index, position);
}
}
};
class App extends Component {
return (
<View>
<AppNavigator />
</View>
)
}
Here's an example of how we do it, you can add your own transitions to make it your own. Our goal was simply to expose the baked-in transition configurations to have more control over the animations. Our transition configuration: https://gist.github.com/jasongaare/db0c928673aec0fba7b4c8d1c456efb6
Then, in your StackNavigator, add that config like so:
StackNavigator(
{
LoginScreen: { screen: LoginScreen },
HomeScreen: { screen: HomeScreen },
},
{
stateName: 'MainStack',
initialRouteName: 'HomeScreen',
initialRouteParams: { transition: 'fade' },
transitionConfig: TransitionConfig,
}
);
Finally, when you navigate, just add your params when you navigate:
this.props.navigation.navigate('HomeScreen', { transition: 'vertical' })
I'm facing a task where I want to place a draggable marker on a background image and afterwards get the coordinates of the marker within the background image.
I've followed this neat tutorial to make a draggable marker using Animated.Image, PanResponder and Animated.ValueXY. The problem is that I cannot figure out how to limit the draggable view to only move around within the boundaries of its parent (the background image).
Any help is very much appreciated :)
Best regards
Jens
Here is one way to do it using the react-native-gesture-responder.
import React, { Component } from 'react'
import {
StyleSheet,
Animated,
View,
} from 'react-native'
import { createResponder } from 'react-native-gesture-responder'
const styles = StyleSheet.create({
container: {
height: '100%',
width: '100%',
},
draggable: {
height: 50,
width: 50,
},
})
export default class WorldMap extends Component {
constructor(props) {
super(props)
this.state = {
x: new Animated.Value(0),
y: new Animated.Value(0),
}
}
componentWillMount() {
this.Responder = createResponder({
onStartShouldSetResponder: () => true,
onStartShouldSetResponderCapture: () => true,
onMoveShouldSetResponder: () => true,
onMoveShouldSetResponderCapture: () => true,
onResponderMove: (evt, gestureState) => {
this.pan(gestureState)
},
onPanResponderTerminationRequest: () => true,
})
}
pan = (gestureState) => {
const { x, y } = this.state
const maxX = 250
const minX = 0
const maxY = 250
const minY = 0
const xDiff = gestureState.moveX - gestureState.previousMoveX
const yDiff = gestureState.moveY - gestureState.previousMoveY
let newX = x._value + xDiff
let newY = y._value + yDiff
if (newX < minX) {
newX = minX
} else if (newX > maxX) {
newX = maxX
}
if (newY < minY) {
newY = minY
} else if (newY > maxY) {
newY = maxY
}
x.setValue(newX)
y.setValue(newY)
}
render() {
const {
x, y,
} = this.state
const imageStyle = { left: x, top: y }
return (
<View
style={styles.container}
>
<Animated.Image
source={require('./img.png')}
{...this.Responder}
resizeMode={'contain'}
style={[styles.draggable, imageStyle]}
/>
</View>
)
}
}
I accomplished this another way using only the react-native PanResponder and Animated libraries. It took a number of steps to accomplish and was difficult to figure out based on the docs, however, it is working well on both platforms and seems decently performant.
The first step was to find the height, width, x, and y of the parent element (which in my case was a View). View takes an onLayout prop. onLayout={this.onLayoutContainer}
Here is the function where I get the size of the parent and then setState to the values, so I have it available in the next function.
` onLayoutContainer = async (e) => {
await this.setState({
width: e.nativeEvent.layout.width,
height: e.nativeEvent.layout.height,
x: e.nativeEvent.layout.x,
y: e.nativeEvent.layout.y
})
this.initiateAnimator()
}`
At this point, I had the parent size and position on the screen, so I did some math and initiated a new Animated.ValueXY. I set the x and y of the initial position I wanted my image offset by, and used the known values to center my image in the element.
I continued setting up my panResponder with the appropriate values, however ultimately found that I had to interpolate the x and y values to provide boundaries it could operate in, plus 'clamp' the animation to not go outside of those boundaries. The entire function looks like this:
` initiateAnimator = () => {
this.animatedValue = new Animated.ValueXY({x: this.state.width/2 - 50, y: ((this.state.height + this.state.y ) / 2) - 75 })
this.value = {x: this.state.width/2 - 50, y: ((this.state.height + this.state.y ) / 2) - 75 }
this.animatedValue.addListener((value) => this.value = value)
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: ( event, gestureState ) => true,
onMoveShouldSetPanResponder: (event, gestureState) => true,
onPanResponderGrant: ( event, gestureState) => {
this.animatedValue.setOffset({
x: this.value.x,
y: this.value.y
})
},
onPanResponderMove: Animated.event([ null, { dx: this.animatedValue.x, dy: this.animatedValue.y}]),
})
boundX = this.animatedValue.x.interpolate({
inputRange: [-10, deviceWidth - 100],
outputRange: [-10, deviceWidth - 100],
extrapolate: 'clamp'
})
boundY = this.animatedValue.y.interpolate({
inputRange: [-10, this.state.height - 90],
outputRange: [-10, this.state.height - 90],
extrapolate: 'clamp'
})
}`
The important variables here are boundX and boundY, as they are the interpolated values that will not go outside of the desired area. I then set up my Animated.Image with these values, which looks like this:
` <Animated.Image
{...this.panResponder.panHandlers}
style={{
transform: [{translateX: boundX}, {translateY: boundY}],
height: 100,
width: 100,
}}
resizeMode='contain'
source={eventEditing.resource.img_path}
/>`
The last thing I had to make sure, was that all of the values were available to the animation before it tried to render, so I put a conditional in my render method to check first for this.state.width, otherwise, render a dummy view in the meantime. All of this together allowed me to accomplish the desired result, but like I said it seems overly verbose/involved to accomplish something that seems so simple - 'stay within my parent.'
I am trying to change the opacity of an element, per mouseEnter and mouseLeave events:
import React, { Component, PropTypes } from 'react';
import d3 from 'd3';
import Bar from './Bar';
import lodash from 'lodash';
export default class DataSeries extends Component {
static propTypes = {
availableHeight: PropTypes.number,
data: PropTypes.array,
height: PropTypes.number,
offset: PropTypes.number,
width: PropTypes.number
}
constructor() {
super();
this.state = ({
opacity: 1
});
}
onMouseEnterHandler() {
this.setState({ opacity: 0.5 });
}
onMouseLeaveHandler() {
this.setState({ opacity: 1 });
}
render() {
const props = this.props;
const yScale = d3.scale.linear()
.domain([0, d3.max(this.props.data)])
.range([0, this.props.height]);
const xScale = d3.scale.ordinal()
.domain(d3.range(this.props.data.length))
.rangeRoundBands([0, this.props.width], 0.05);
const colors = d3.scale.linear()
.domain([0, this.props.data.length])
.range(['#FFB832', '#C61C6F']);
const bars = lodash.map(this.props.data, function(point, index) {
return (
<Bar height={ yScale(point) } width={ xScale.rangeBand() }
offset={ xScale(index) } availableHeight={ props.height }
color={ colors(point) } key={ index }
onMouseEnter={ () => this.onMouseEnterHandler() }
onMouseLeave={ () => this.onMouseLeaveHandler() }
style = { { opacity: this.state.opacity } } <-- error points here
/>
);
});
return (
<g>{ bars }</g>
);
}
}
DataSeries.defaultPropTypes = {
title: '',
data: []
};
I am receiving the error:
TypeError: Cannot read property 'state' of undefined(…)
I see one issue is 'this' needs to be passed in. The other change is probably optional.
let vm = this;
let bars = lodash.map(this.props.data, function(point, index, vm) {
let opacity = { opacity: vm.state.opacity };
return (
<Bar height={ yScale(point) } width={ xScale.rangeBand() }
offset={ xScale(index) } availableHeight={ props.height }
color={ colors(point) } key={ index }
onMouseEnter={ vm.onMouseEnterHandler }
onMouseLeave={ vm.onMouseLeaveHandler }
style = { opacity }
/>
);
});