react-native-svg: LinearGradient doesn't change between components when given different colors - react-native

I have a component that generates a square linear gradient using react-native-svg. When I pass in different hue colors, it stays the same color.
Things I tried:
Passing in hex instead of hsl colors and the issue still arises
What am I missing?
Expo Snack: https://snack.expo.dev/#paulwongx/react-native-svg_color_stop_issue
Gradient.js
import * as React from 'react';
import Svg, { Defs, LinearGradient, Rect, Stop } from "react-native-svg";
export default function Gradient({hue}) {
return (
<Svg width={128} height={128} viewBox="0 0 24 24">
<Defs>
<LinearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
<Stop offset="0" stopColor={`hsl(${hue}, 100%, 74%)`} />
<Stop offset="1" stopColor={`hsl(${hue}, 100%, 55%)`} />
</LinearGradient>
</Defs>
<Rect width="100%" height="100%" fill="url(#gradient)" />
</Svg>
);
}
App.js
import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import Gradient from './Gradient';
export default function App() {
return (
<View style={{flexDirection: "row", flexWrap: "wrap"}}>
<Gradient hue={20} />
<Gradient hue={50} />
<Gradient hue={100} />
<Gradient hue={150} />
<Gradient hue={200} />
<Gradient hue={250} />
<Gradient hue={300} />
<Gradient hue={350} />
</View>
);
}
Output:
Looking at the rendered code within Inspect Element, the components show different hues but they all render the same colors.
// first component
<stop offset="0" stop-color="hsl(20, 100%, 74%)"></stop>
<stop offset="1" stop-color="hsl(20, 100%, 55%)"></stop>
// last component
<stop offset="0" stop-color="hsl(350, 100%, 74%)"></stop>
<stop offset="1" stop-color="hsl(350, 100%, 55%)"></stop>

I think you have to give a unique id to the linear gradient. The problem is that the id is always the same.
import * as React from 'react';
import Svg, { Defs, LinearGradient, Rect, Stop } from "react-native-svg";
export default function Gradient({hue, id}) {
return (
<Svg width={128} height={128} viewBox="0 0 24 24">
<Defs>
<LinearGradient id={`gradient_${id}`} x1="0%" y1="0%" x2="100%" y2="100%">
<Stop offset="0" stopColor={`hsl(${hue}, 100%, 74%)`} />
<Stop offset="1" stopColor={`hsl(${hue}, 100%, 55%)`} />
</LinearGradient>
</Defs>
<Rect width="100%" height="100%" fill={`url(#gradient_${id})`} />
</Svg>
);
}
And then
import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import Gradient from './Gradient';
export default function App() {
return (
<View style={{flexDirection: "row", flexWrap: "wrap"}}>
<Gradient hue={20} id="first" />
<Gradient hue={50} id="second" />
</View>
);
}

Related

make svg responsive in react native

ive got an svg im using as a header in my app, i want it to be responsive so it could fit any screen.
the thing is , there is a weird white space on the left and it still not responsive.
thanks!
svg code:
import Svg, { Path, Defs, LinearGradient, Stop } from "react-native-svg"
import { Dimensions } from 'react-native';
const { height, width } = Dimensions.get('window');
function SvgComponent(props) {
const viewBox = '0 0 '+width+' '+height*0.117;
return (
<Svg width={width} height={height*0.117} viewBox={viewBox} fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<Path
d="M0 99V0h395v99c-19.439-4.282-85.706-7.897-195.26-7.897C85.186 91.103 19.266 94.718 0 99z"
fill="url(#paint0_linear_1134_4959)"
/>
<Defs>
<LinearGradient
id="paint0_linear_1134_4959"
x1={1.8637e-7}
y1={49.5}
x2={505.032}
y2={49.5}
gradientUnits="userSpaceOnUse"
>
<Stop stopColor="#7D55AE" />
<Stop offset={1} stopColor="#5E00D0" />
</LinearGradient>
</Defs>
</Svg>
)
}
export default SvgComponent ```

How to make victorychart look the same across platforms(using simulator)

Trying out VictoryChart for react native and it looks good in webview preview
But If you see Android for example it looks very different. I'm assuming it's an issue with my code, not the simulator.
On iOS it looks like this
Code
export function VictoryAreaChart({ chartData, yName }) {
return (
<View>
<Svg style={{ height: 0 }}>
<Defs>
<LinearGradient id="gradient" x1="0%" y1="0%" x2="0%" y2="100%">
<Stop offset="0%" stopColor="#4E54C8" />
<Stop offset="100%" stopColor="#a8abe4" />
</LinearGradient>
</Defs>
</Svg>
{chartData.length > 0 && (
<VictoryArea
padding
height={172}
interpolation="natural"
x={d => new Date(d.date_time)}
y={yName}
style={{
data: {
stroke: '#4E54C8',
fill: 'url(#gradient)',
strokeWidth: 2,
},
}}
data={chartData}
interpolation="natural"
/>
)}
</View>
);

Can I display tiny dots on polygon in react-native-maps

I have a requirement to display the polygon on the Map as follows
Can anyone suggest me if there is a way to do that ?
I managed to display using react-native-svg elements inside Marker
import * as React from "react";
import { Marker} from "react-native-maps";
import Svg, { Circle, Rect } from "react-native-svg";
export default function SvgComponent(props) {
const {
coordinate,
} = props;
return (
<Marker coordinate={coordinate} pinColor={pinColor}>
<Svg height="10" width="20" viewBox="0 0 100 100">
<Circle cx="50" cy="50" r="45" stroke="blue" fill="green" />
</Svg>
</Marker>
);
}

How to create a gradient text in react-native

I want to create text with linear gradient color in react-native, but cannot find a suitable way or package to do this. I tried to use this package : https://github.com/iyegoroff/react-native-text-gradient. But, while trying to run an example with expo, it is giving me the following error :
TypeError: Cannot read property 'x' of undefined
This error is located at:
in RNLinearTextGradient (at App.js:26)
in RCTView (at View.js:60)
in View (at App.js:17)
in App (at registerRootComponent.js:35)
in RootErrorBoundary (at registerRootComponent.js:34)
in ExpoRootComponent (at renderApplication.js:33)
in RCTView (at View.js:60)
in View (at AppContainer.js:102)
in RCTView (at View.js:60)
in View (at AppContainer.js:122)
in AppContainer (at renderApplication.js:32)
at linear-text-gradient.js:16
at Object.render (create-text-gradient-class.js:219)
at finishClassComponent (ReactNativeRenderer-dev.js:8811)
at updateClassComponent (ReactNativeRenderer-dev.js:8761)
at beginWork (ReactNativeRenderer-dev.js:9580)
at performUnitOfWork (ReactNativeRenderer-dev.js:12924)
at workLoop (ReactNativeRenderer-dev.js:12953)
at renderRoot (ReactNativeRenderer-dev.js:12996)
at performWorkOnRoot (ReactNativeRenderer-dev.js:13632)
at performWork (ReactNativeRenderer-dev.js:13545)
Would you please help me to resolve this issue or find another way to create gradient text in react-native ?
Implemented Gradient on Text using #react-native-community/masked-view and react-native-linear-gradient
Step 1: Creating our custom GradientText component
import React from "react";
import { Text } from "react-native";
import MaskedView from "#react-native-community/masked-view";
import LinearGradient from "react-native-linear-gradient";
const GradientText = (props) => {
return (
<MaskedView maskElement={<Text {...props} />}>
<LinearGradient
colors={["red", "green"]}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
>
<Text {...props} style={[props.style, { opacity: 0 }]} />
</LinearGradient>
</MaskedView>
);
};
export default GradientText;
Step 2: Using our GradientText component
<GradientText style={styles.textStyle}>Hello world</GradientText>
use [react native svg][1]
[1]: https://github.com/react-native-community/react-native-svg for example:
import Svg, {
LinearGradient,
Text,
Defs,
Stop,
TSpan
} from 'react-native-svg';
<Svg viewBox="0 0 300 300" height="300"
width="300">
<Defs>
<LinearGradient id="rainbow" x1="0" x2="0" y1="0" y2="100%" gradientUnits="userSpaceOnUse" >
<Stop stopColor="#FF5B99" offset="0%" />
<Stop stopColor="#FF5447" offset="20%" />
<Stop stopColor="#FF7B21" offset="40%" />
<Stop stopColor="#EAFC37" offset="60%" />
<Stop stopColor="#4FCB6B" offset="80%" />
<Stop stopColor="#51F7FE" offset="100%" />
</LinearGradient>
</Defs>
<Text fill="url(#rainbow)">
<TSpan
fonSize="72"
x="0"
y="72"
>
gradient
</TSpan>
<TSpan fonSize="72" x="0" dy="72">text</TSpan>
<TSpan fonSize="72" x="0" dy="72">all up in here</TSpan>
</Text>
</Svg>
There are 2 packages you could use for gradient text:
1) react-native-text-gradient
<LinearTextGradient
style={{ fontWeight: 'bold', fontSize: 72 }}
locations={[0, 1]}
colors={['red', 'blue']}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
>
THIS IS TEXT GRADIENT
</LinearTextGradient>
Result would be like this:
Sources: https://github.com/iyegoroff/react-native-text-gradient
2) react-native-linear-gradient:
import LinearGradient from 'react-native-linear-gradient';
const styles = StyleSheet.create({
text: {
color: 'black',
fontSize: 14,
},
gradient: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
},
});
<View>
<View>
<Text style={styles.text}>
This is a gradiented text
</Text>
<Text style={styles.text}>
This is a gradiented text
</Text><Text style={styles.text}>
This is a gradiented text
</Text><Text style={styles.text}>
This is a gradiented text
</Text><Text style={styles.text}>
This is a gradiented text
</Text>
</View>
<LinearGradient
start={{ x: 0.0, y: 0.0 }}
end={{ x: 0.0, y: 1.0 }}
locations={[0.0, 1.0]}
colors={['#ffffff40', '#fffffff5']} //<-- last 2 chars from color control the opacity
useViewFrame={false}
style={styles.gradient}
/>
<View>
The result will look like this:
More info of react-native-linear-gradient here:
https://github.com/react-native-community/react-native-linear-gradient

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);
};