How to center an SVG in React Native? - react-native

I am struggling to align an SVG in React Native.
So far I have the following, however the SVG renders in the top-left of the component. I've been battling this for an hour or two.
const Like = () => (
<View
style={{
height: 45,
width: 45,
flex: 1,
borderWidth: 1,
justifyContent: 'center',
alignItems: 'center',
}}>
<Svg width={45} height={45} viewBox="0 0 25 25">
<Path
d="M5.5 11a.344.344 0 0 1-.28-.126l-3.823-4.32c-.016 0-.041-.02-.078-.062-.033-.042-.067-.08-.095-.113L.88 5.902c-.143-.2-.28-.435-.407-.704a4.44 4.44 0 0 1-.323-.853C.05 4.01 0 3.675 0 3.34 0 2.303.256 1.482.774.88 1.273.293 1.997 0 2.944 0c.243 0 .5.05.771.15.274.1.517.235.732.403.2.15.393.318.58.502.17.15.328.31.473.477.142-.167.3-.327.47-.477.187-.184.381-.352.583-.502.215-.168.458-.302.73-.402.271-.1.53-.151.773-.151.944 0 1.669.293 2.17.879.515.603.774 1.424.774 2.461 0 1.038-.466 2.118-1.397 3.24l-3.824 4.294A.351.351 0 0 1 5.5 11"
fill="#f26aa3"
/>
</Svg>
</View>
);

You can import your image file directly.
Example:
import Image from '../assets/image.svg';
The style for image wrapper is
const styles = StyleSheet.create({
image: {
alignItems: 'center',
justifyContent: 'center',
},
});
In Return,
<View style={styles.image}>
<Image width={100} height={100} />
</View>

Path components do not have an adjustable x and y coordinate property, so you can't just provide these properties to center it. Instead, what we can do is create a definition that contains your Path component. Items declared inside of Defs tags are invisible until "used". Then we can utilize the Use component to use the defined component and provide x and y coordinates either as pixel values or percentages.
So, in your context you would end up with something like this:
import { Svg, Path, Defs, Use } from "react-native-svg";
const Like = () => (
<View
style={{
height: 45,
width: 45,
flex: 1,
borderWidth: 1,
justifyContent: 'center',
alignItems: 'center',
}}>
<Svg width={45} height={45} viewBox="0 0 25 25">
<Use href="likeButton" x="50%" y="50%" />
<Defs>
<Path
id="likeIcon"
d="M5.5 11a.344.344 0 0 1-.28-.126l-3.823-4.32c-.016 0-.041-.02-.078-.062-.033-.042-.067-.08-.095-.113L.88 5.902c-.143-.2-.28-.435-.407-.704a4.44 4.44 0 0 1-.323-.853C.05 4.01 0 3.675 0 3.34 0 2.303.256 1.482.774.88 1.273.293 1.997 0 2.944 0c.243 0 .5.05.771.15.274.1.517.235.732.403.2.15.393.318.58.502.17.15.328.31.473.477.142-.167.3-.327.47-.477.187-.184.381-.352.583-.502.215-.168.458-.302.73-.402.271-.1.53-.151.773-.151.944 0 1.669.293 2.17.879.515.603.774 1.424.774 2.461 0 1.038-.466 2.118-1.397 3.24l-3.824 4.294A.351.351 0 0 1 5.5 11"
fill="#f26aa3"
/>
</Defs>
</Svg>
</View>
);

Related

Space-evenly display text in React Native

I want to display my text with even amount of space per line of text displayed similar to how eBooks display texts. I could only find resources regarding displaying many <View>s in space-evenly or space-between using flexbox justifyContent.
I have a <Text>a lot of text inside....</Text> and I want the text inside should be evenly spaced. How could I space my words inside the <Text>?
const TextDisplay = () => {
const part = part0004_split_010;
const text_example =
"fdkfjldjsfljsldfjlskdjflsdjf alkjgdlkjg lkdjgflkdjgkld kdjfauieghjknf uio 23jo1j kldanglk3tlk jasdn lskj jie hgihigehi anvmcnvmcn ,zmnmx nmf ei nvi nie 2 i2j42in42i n kd nk nskan lk lkdas j92al klkjr1l2or a;z;z;z; 9r3ur9j 39 091 jr1 j a,c n kn oian iong oien voin ow ni ei nwoinq ei n93 n99";
return (
<View style={styles.container}>
<ScrollView style={styles.scrollview}>
<Text style={styles.text_style}>{text_example}</Text>
</ScrollView>
</View>
);
};
export default TextDisplay;
const styles = StyleSheet.create({
container: {
flex: 1,
borderColor: "red",
borderWidth: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "grey",
width: "80%",
},
scrollview: {
flex: 1,
justifyContent: 'space-evenly'
},
text_style: {
fontSize: 20,
color: "black",
},
});
I tried using justifyContent: 'space-evenly' on my scrollView but it doesn't change anything
You can use textAlign:"justify" in the styles of a <Text> to make the text justified.

React-native nested linear gradient shift on both platform

Component with nested gradient
<View style={styles.sectionContainer}>
<Text style={{
flexDirection: 'row',
alignItems: 'center',
fontSize: 24,
}}>
First
<LinearGradientText
textComponent={Text}
showGradient
>
<Text>second</Text>
</LinearGradientText>
third
</Text>
</View>
Component with Linear Gradient
export const LinearGradientText: React.FC\<Props\> = props =\> {
const TextComponent = props.textComponent ?? Text;
return (
\<View\>
\<MaskedView
maskElement={
\<TextComponent {...props} style={\[props.style, {fontSize: 24}\]} /\>
}
\\>
\<LinearGradient
colors={\[
props?.firstColor ?? 'pink',
props?.secondColor ?? 'blue',
\]}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
style={\[props.style\]}
\\>
\<TextComponent
{...props}
style={\[props.style, { opacity: 0, fontSize: 24 }\]}
/\>
\</LinearGradient\>
\</MaskedView\>
\</View\>
);
};
I can't resolve the problem with gradient text shifting, but I need to use gradient text in nested text, for an example "Good enough, [[username]]".
It works perfectly only for plain text, any tips how can I achieve this?

React Native - different behavior when turn on debugger (React Developer Tools)

I have a wheel that has 8 arcs, when I turn on debugger the arcs rendered correctly:
But when I turn the debugger off, the arcs position is just messed up:
I tried to build a release version and still got the wrong result. Here is my code:
import * as d3Shape from 'd3-shape'
import React, {forwardRef, useEffect, useImperativeHandle, useRef, useState} from 'react'
import {Animated, Dimensions, Image, TouchableOpacity, View} from 'react-native'
import Svg, {G, Path} from 'react-native-svg'
import Images from '~/styles/Images'
import ImageReward from './components/ImageReward'
import {styles} from './styles'
...
const renderPaths = () => {
let paths: any[] = []
for (let i = 0; i < _wheelPaths.length; i++) {
const arc = _wheelPaths[i]
const [x, y] = arc.centroid
const number = arc.value.toString()
paths.push({
index: i,
body: (
<G key={`arc-${i}`}>
<Path d={arc.path} strokeWidth={0} fill={arc.color} />
<G
rotation={angleOffset}
origin={`${x}, ${y}`}
>
{_textRender(x, y, number, i)}
</G>
</G>
),
})
}
paths = paths.sort((a, b) => a.index - b.index)
return paths.map((i) => i.body)
}
...
const _renderSvgWheel = () => {
return (
<View style={styles.container}>
{_renderKnob()}
<Animated.View
style={{...}}>
{props.options.middleImgSource ? (
<Image
source={props.options.middleImgSource}
style={{
position: 'absolute',
width: props.options.innerRadius * 2,
height: props.options.innerRadius * 2,
zIndex: 1,
}}
/>
) : null}
<AnimatedSvg
width={gameScreen}
height={gameScreen}
viewBox={`0 0 ${width} ${width}`}
style={{
transform: [{rotate: `-${angleOffset}deg`}],
margin: 10,
zIndex: 0,
}}>
<G y={width / 2} x={width / 2}>
{renderBulb(_wheelPaths?.length * 2)}
{renderPaths()}
</G>
</AnimatedSvg>
</Animated.View>
</View>
)
}
...
return (
<View style={styles.container}>
<View
style={{
width: width,
justifyContent: 'center',
alignItems: 'center',
}}>
<Animated.View style={styles.content}>{_renderSvgWheel()}</Animated.View>
</View>
{props.options.playButton ? _renderTopToPlay() : null}
</View>
)
As you can see I intended to use paths.sort before return it, just to make sure the index is right, but the problem still there

chart in range of time react native svg charts

I'm working on chart with react-native-svg-charts library, and seems like it is very flexible and cool, but I faced the issue. I need to build 24 hours timeline for xAxis and can not understand how to do it.
So I have a chart it looks like this
and the code of the chart is next:
<>
<View style={{flex: 1}} {...panResponder.current.panHandlers}>
<AreaChart
animate
style={{flex: 1}}
data={glucoseValues}
yMin={0}
yMax={180}
curve={shape.curveNatural}
contentInset={{...verticalContentInset}}
svg={{fill: 'url(#gradient)'}}>
<CustomLine/>
<Tooltip/>
</AreaChart>
<SafeArea/>
</View>
<YAxis
style={{width: apx(44)}}
data={[...Array(19).fill(1).map((el, i) => el + i * 10)]}
contentInset={verticalContentInset}
svg={{fontSize: apx(20), fill: '#617485'}}
/>
<XAxis
style={{
alignSelf: 'stretch',
width: apx(INIT_WIDTH),
height: apx(40),
}}
data={[...Array(24).fill(1)]}
formatLabel={(value, i) => {
return i === 0 ? '00:00' :
i % 4 === 0 ? moment().startOf('day').add(i, 'hours').format('HH:mm') :
i === 23 ? '23:59' : '';
}}
contentInset={{
left: 20,
right: 20,
}}
svg={{
fontSize: apx(20),
fill: '#617485',
alignmentBaseline: 'center',
textAnchor: 'middle',
y: apx(10),
}}
/>
</>
So as you see XAxis is kinda shows timeline but it is not applied. If it would, before and after the chart, we should see empty space like on this picture.
My question is next. How can I set up domain for xAxis that will work as on picture above?
For chart I user array of values for Y and array of timestamps for X. Maybe I should point somehow xMax, xMin properties for AreaChart. Any ideas?

How can I automatically scale an SVG element within a React Native View?

I am trying to put a react-native-svg element inside of a View such that it's rendered with a certain, fixed aspect ratio, but then scaled to be as large as possible, within the confines of the containing view.
The Svg element (from react-native-svg) seems to only accept absolute width and height attributes (I've tried using percentages, but nothing renders, and debugging confirms that percent values are NSNull by the time they get to the native view). I'm not sure how to achieve the desired effect. Here's what I've tried so far:
// I have a component defined like this:
export default class MySvgCircle extends React.Component {
render() {
return (
<View style={[this.props.style, {alignItems: 'center', justifyContent: 'center'}]} ref="containingView">
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center', aspectRatio: 1.0}}>
<Svg.Svg width="100" height="100">
<Svg.Circle cx="50" cy="50" r="40" stroke="blue" strokeWidth="1.0" fill="transparent" />
<Svg.Circle cx="50" cy="50" r="37" stroke="red" strokeWidth="6.0" fill="transparent" />
</Svg.Svg>
</View>
</View>
);
}
}
// And then consumed like this:
<MySvgCircle style={{height: 200, width: 200, backgroundColor: "powderblue"}}/>
And this is what I see when it renders.
I want the red and blue circles to be scaled up to fill the 200x200 area (staying circular if the containing view is rectangular and not square), without having foreknowledge of the size desired by the consumer/user of the component.
As mentioned, I tried using percentages, like this (the rest is the same):
<Svg.Svg width="100%" height="100%">
But then the SVG part doesn't draw at all. Like this:
There are no error messages, or other indications of why this doesn't work, in the console logs.
The methods for measuring UI elements after layout in RN appears to be asynchronous, which seems like a poor match to what I'm trying to do. Is there some sort of scaling or transform magic that I could use?
The desired output would look like this (obtained by hardcoding values):
And when the containing view isn't a perfect square I'd like it to work like this:
Here is a component that behaves like your images:
import React from 'react';
import { View } from 'react-native';
import Svg, { Circle } from 'react-native-svg';
const WrappedSvg = () =>
(
<View style={{ aspectRatio: 1, backgroundColor: 'blue' }}>
<Svg height="100%" width="100%" viewBox="0 0 100 100">
<Circle r="50" cx="50" cy="50" fill="red" />
</Svg>
</View>
);
In context:
const WrappedSvgTest = () => (
<View>
<View style={{
width: '100%',
height: 140,
alignItems: 'center',
backgroundColor: '#eeeeee'
}}
>
<WrappedSvg />
</View>
{/* spacer */}
<View style={{ height: 100 }} />
<View style={{
width: 120,
height: 280,
justifyContent: 'space-around',
backgroundColor: '#eeeeee'
}}
>
<WrappedSvg />
</View>
</View>
);
The trick is to wrap the SVG element in a view that preserves its aspect ratio, then set the SVG sizing to 100% width and height.
I believe there is some complex interaction between the SVG element size and the viewbox size that makes the SVG render smaller than you would expect, or in some cases not render at all. You can avoid this by keeping your <View> tags at a fixed aspect ratio and setting the <Svg> tags to 100% width and height, so the viewbox aspect ratio always matches the element ratio.
Be sure to set aspectRatio to viewbox.width / viewbox.height.
the trick in
preserveAspectRatio="xMinYMin slice"
you should do that
<Svg
height="100%"
preserveAspectRatio="xMinYMin slice"
width="100%"
viewBox="0 0 100 100"
>
You have to play with the width and height together with the viewBox. Usually the viewBox you have to place the original dimensions of your desired shape. And by defining the width/height based on your needs your shape will be down/up scaled properly.
Please have a look to this tutorial where this concepts have been explained pretty clear.
https://www.sarasoueidan.com/blog/svg-coordinate-systems/
For my SVG, I was using those provided at https://material.io/resources/icons
What fixed it for me, was to make sure you don't mess with the viewBox or given values in the Paths (like I did) but only change the height and width to fill and then use the containers like the other answers:
<View style={{
height: 100, display: 'flex',
}}>
<TouchableOpacity style={{
display: 'flex', justifyContent: 'center',
alignItems: 'center', aspectRatio: 1,
}}>
<Svg fill="white" height="100%"
width="100%" viewBox="0 0 24 24">
<Path d="M0 0h24v24H0z" fill="none"/>
<Path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>
</Svg>
</TouchableOpacity>
</View>
i'm using react-native-svg-transformer without using react-native-svg which i found very heavy in term of size,
so i can resize and change the stroke color also the fill color, but just instead of passing a fill prop, just pass color as seen below, it works perfectly
import React from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
} from 'react-native';
import { StatusBar } from 'expo-status-bar';
import Logo from "../../assets/profile.svg";
function FirstScreen(props) {
return (
<View style={styles.container}>
<TouchableOpacity
onPress={() => { props.navigation.navigate('SecondScreen'); }}
>
<Text>Welcome</Text>
<View style={{ aspectRatio: 1,justifyContent:"center",alignItems:"center", backgroundColor: 'blue',width:200,height:200 }}>
<Logo color="white" stroke="black" height={50} width={50} />
</View>
</TouchableOpacity>
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
export default FirstScreen;
the svg code
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><title>profile</title><g fill="currentColor" class="nc-icon-wrapper"><path d="M38,37H26A19.021,19.021,0,0,0,7,56a1,1,0,0,0,.594.914C7.97,57.081,16.961,61,32,61s24.03-3.919,24.406-4.086A1,1,0,0,0,57,56,19.021,19.021,0,0,0,38,37Z"></path><path data-color="color-2" d="M32,32c8.013,0,14-8.412,14-15.933a14,14,0,1,0-28,0C18,23.588,23.987,32,32,32Z"></path></g></svg>
dependencies
"dependencies": {
"#expo/webpack-config": "~0.16.2",
"#react-navigation/native": "^6.0.10",
"#react-navigation/native-stack": "^6.6.2",
"expo": "~45.0.0",
"expo-font": "^10.1.0",
"expo-status-bar": "~1.3.0",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-native": "0.68.2",
"react-native-svg-transformer": "^1.0.0",
},
metro.config.js file to add in the root
const { getDefaultConfig } = require('expo/metro-config');
module.exports = (() => {
const config = getDefaultConfig(__dirname);
const { transformer, resolver } = config;
config.transformer = {
...transformer,
babelTransformerPath: require.resolve('react-native-svg-transformer'),
};
config.resolver = {
...resolver,
assetExts: resolver.assetExts.filter((ext) => ext !== 'svg'),
sourceExts: [...resolver.sourceExts, 'svg'],
};
return config;
})();
I put this whole thing into an example Snack, maybe it helps.
SNACK:
https://snack.expo.dev/#changnoi69/fbf937
When you change the marginLeft and marginRight of that view that is wrapped around the SVG-Component the SVG resizes according to it.
<View style={{marginLeft:"20%", marginRight:"20%", backgroundColor: "pink"}}>
<NoInternetConnectionSVG />
</View>
Original Stackoverflow post is here:
https://stackoverflow.com/a/73511233/12647753
You will need this variables
const originalWidth = 744;
const originalHeight = 539.286;
const aspectRatio = originalWidth / originalHeight;
Wrap your svg in a view with this properties:
<View style={{ width: '100%', aspectRatio }}></View>
or
<View style={{ width: Dimensions.get('window').width, aspectRatio }}>
</View>
Use the svg inside, with this properties:
<Svg
width='100%'
height='100%'
viewBox={`0 0 ${originalWidth} ${originalHeight}`}
>
And you should be ok!
In my case, I had to scale a SVG icon based on the device size and it was using <G> and <Path> for drawing the icon. After hours of trial and error method, I found a solution - give a dynamic scale value (based on the device size) to the inner component of Svg component. Here, the inner component is <G>
<Svg width={RfH(24)} height={RfH(24)} style={{backgroundColor: 'salmon'}}>
<G
scale={RfH(1)} // Scaling added to the inner component
fill="none"
fillRule="evenodd">
<G
stroke={props?.isFocused ? '#302F4C' : '#8B8B88'}
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={1.5}>
<Path
d="M9.393 2.792 3.63 7.022c-.9.7-1.63 2.19-1.63 3.32v7.41c0 2.32 1.89 4.22 4.21 4.22h11.58c2.32 0 4.21-1.9 4.21-4.21v-7.28c0-1.21-.81-2.76-1.8-3.45l-5.807-4.36c-1.4-.98-3.65-.93-5 .12Z"
fill={props?.isFocused ? '#7BBDFF' : 'none'}
fillRule="nonzero"
/>
<Path fill="#FFF" d="M12 17.993v-2.924" />
</G>
</G>
- iPad home icon with scaling
- iPad home icon without scaling
- iPhone home icon with scaling
- iPhone home icon without scaling
Rfh just converts an input value to the current device equivalent.
import {Dimensions} from 'react-native';
const STANDARD_SCREEN_DIMENSIONS = {height: 812, width: 375};
const RfH = (value) => {
const dim = Dimensions.get('window');
return dim.height * (value / STANDARD_SCREEN_DIMENSIONS.height);
};