Best way to add conic gradient to the circle in SVG - reactjs

I have a donut-like circle (progress circle) in React component and I want to add conic gradient to it. How to do that?
I know that in SVG we can not use conic gradients. I thought it can be done by using mask and usual block with added css with gradient but not sure how to do it correctly.
Now it looks like this:
React component:
import React from 'react';
import { Box, Text } from '#chakra-ui/react';
const GradientProgress = ({ modifier, score, size, strokeWidth }) => {
const DIAMETER = 51;
const WIDTH = DIAMETER + strokeWidth;
const RADIUS = DIAMETER / 2;
const CIRC = 2 * Math.PI * RADIUS;
const foregroundCirc = (CIRC * score) / 100;
const frontCirc = (CIRC * modifier) / 100;
return (
<Box
position='relative'
style={{ width: `${size}px`, height: `${size}px` }}
sx={{
circle: {
background:
'conic-gradient(from 270deg, #ff4800 10%, #dfd902 35%, #20dc68, #0092f4, #da54d8 72% 75%, #ff4800 95%)',
},
}}
>
<svg
className='donut'
transform='rotate(-90)'
viewBox={`0 0 ${WIDTH} ${WIDTH}`}
>
<circle
className='donut-ring'
cx={RADIUS + strokeWidth / 2}
cy={RADIUS + strokeWidth / 2}
fill='transparent'
pathLength={CIRC}
r={RADIUS}
stroke='#d2d3d4'
strokeWidth={strokeWidth}
/>
<circle
className='donut-segment'
cx={RADIUS + strokeWidth / 2}
cy={RADIUS + strokeWidth / 2}
fill='transparent'
opacity={0.5}
pathLength={CIRC}
r={RADIUS}
stroke='green'
strokeDasharray={`${frontCirc} ${CIRC - frontCirc}`}
strokeDashoffset={0}
strokeLinecap='round'
strokeWidth={strokeWidth}
/>
<circle
className='donut-segment'
cx={RADIUS + strokeWidth / 2}
cy={RADIUS + strokeWidth / 2}
fill='transparent'
pathLength={CIRC}
r={RADIUS}
stroke='red'
strokeDasharray={`${foregroundCirc} ${CIRC - foregroundCirc}`}
strokeDashoffset={0}
strokeLinecap='round'
strokeWidth={strokeWidth}
/>
</svg>
<Text>{modifier || score}</Text>
</Box>
);
};
export default GradientProgress;

You can achieve this by converting your circle to a mask and assign it to a foreignObject, this object contains a div with the conic-gradient style.
Here is an example how it works:
const control = document.getElementById('control');
const circle = document.getElementsByClassName('circle')[0];
const bg = document.getElementsByClassName('bg')[0];
control.addEventListener('input', function(event) {
circle.style.setProperty('--progress', event.target.valueAsNumber);
const deg = (event.target.valueAsNumber/100) * 360;
bg.style.setProperty('background', `conic-gradient(#00bcd4, #ffeb3b ${deg}deg)`);
});
.root {
width: 400px;
height: 400px;
text-align: center;
}
svg {
}
.circle {
stroke: white;
stroke-width: 3;
stroke-linecap: round;
stroke-dasharray: calc((2 * 3.14) * 45);
stroke-dashoffset: calc((2 * 3.14 * 45) * (1 - calc(var(--progress, 50) / 100)));
transform-origin: 50% 50%;
transform: rotate(-87deg);
}
.bg {
background: conic-gradient(#00bcd4, #ffeb3b 180deg);
width: 100%;
height: 100%;
}
<div class='wrap'>
<div class='root'>
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<defs>
<mask id="mask">
<circle class='circle' cx="50" cy="50" r="45" stroke='white' stroke-width='3' fill='none' />
</mask>
</defs>
<foreignObject x="0" y="0" width="100" height="100" mask="url(#mask)">
<div class='bg'></div>
</foreignObject>
</svg>
<input id="control" type="range" value="60" />
</div>
</div>

Related

Animated rotate circle SVG in reactjs

https://i.stack.imgur.com/kG5Sg.png
How can i dot blue running around circle. I have tried using transform:rotate in css but no success. Here is my code.
import './loadingicon.css'
const LoadingIcon = () => {
return (
< >
<div className='circle'>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
<circle
class='track'
cx="80"
cy="80"
r="70"
fill="none"
stroke="#374a67"
stroke-dasharray="26.416, 5"
stroke-width="4"
/>
<circle class="filled" cx="80" cy="80" r="70" />
</svg>
</div>
</>
)
}
export default LoadingIcon;
loadingicon.css
.track,
.filled {
stroke-width: 10;
fill: none;
}
.track {
stroke: #eee;
}
.filled {
stroke: blue;
stroke-dashoffset: 420;
stroke-dasharray: 440;
}

React crop image to specific cropped dimensions

I am trying to crop an image in react by using the react-image-crop library, and I got the cropping functionality working.
import React, { useCallback, useRef, useState } from "react";
import ReactCrop, { Crop } from "react-image-crop";
import "react-image-crop/dist/ReactCrop.css";
export const ImageCropper = () => {
const [upImg, setUpImg] = useState<string>(
"https://www.vintagemovieposters.co.uk/wp-content/uploads/2020/04/IMG_5274-scaled.jpeg"
);
const imgRef = useRef<HTMLImageElement | null>(null);
const [crop, setCrop] = useState<Partial<Crop>>({
unit: "%",
aspect: 0.68,
height: 100
});
const onLoad: (image: HTMLImageElement) => boolean | void = useCallback(
(img) => {
imgRef.current = img;
const aspect = 0.68;
const width =
img.width / aspect < img.height * aspect
? 100
: ((img.height * aspect) / img.width) * 100;
const height =
img.width / aspect > img.height * aspect
? 100
: (img.width / aspect / img.height) * 100;
const y = (100 - height) / 2;
const x = (100 - width) / 2;
setCrop({
unit: "%",
width,
height,
x,
y,
aspect
});
},
[]
);
return (
<div style={{ display: "flex", flexDirection: "column" }}>
<ReactCrop
src={upImg}
onImageLoaded={onLoad}
crop={crop}
onChange={(crop, percentageCrop) => {
setCrop(percentageCrop);
}}
keepSelection={true}
/>
<div
style={{
width: imgRef.current?.width! * (crop.width! / 100),
height: imgRef.current?.height! * (crop.height! / 100),
overflow: "hidden"
}}
>
<img
alt="cropped_image"
src={upImg}
style={{
width: imgRef.current?.width!,
height: imgRef.current?.height!,
transform: `translate(-${
(crop.x! / 100) * imgRef.current?.width!
}px, -${(crop.y! / 100) * imgRef.current?.height!}px )`
}}
/>
</div>
</div>
);
};
However, what I am trying to achieve is:
keep the original image after cropping
put the image in a preview div with specific dimensions (235px x 346px)
transform the image to fit within that preview div with the same defined crop
make sure the preview div matches with the highlighted crop
what I tried is the code above, but the issue with it is that the width + height change dynamically.
I tried to use fixed width and height values, but the cropping is off.
I also tried using the scale property in transform, but it was off too:
return (
<div style={{ display: "flex", flexDirection: "column" }}>
<ReactCrop
src={upImg}
onImageLoaded={onLoad}
crop={crop}
onChange={(crop, percentageCrop) => {
console.log("percent", percentageCrop);
setCrop(percentageCrop);
}}
keepSelection={true}
/>
<div
style={{
width: 235,
height: 346,
overflow: "hidden"
}}
>
<img
alt="cropped_image"
src={upImg}
style={{
width: imgRef.current?.width!,
height: imgRef.current?.height!,
transform: `translate(-${
(crop.x! / 100) * imgRef.current?.width!
}px, -${(crop.y! / 100) * imgRef.current?.height!}px) scale(${
crop.width/100
}, ${crop.height/100})`
}}
/>
</div>
</div>
);
I need to figure out how to constrain them to (235px x 346px), and "zoom" the image to match the crop from react-image-crop.
How can I do that?
Example in code sandbox

Reusable Component in React

I wanted to create a reusable component in my React app using Styled-Components. My problem is that i got the error
Error: Element type is invalid: expected a string (for built-in
components) or a class/function (for composite components) but got:
undefined. You likely forgot to export your component from the file
it's defined in, or you might have mixed up default and named imports.
pls check my code below:
import styled from 'styled-components'
import React from 'react'
const CustomSpinner = styled.svg`
animation: rotate 1s linear infinite;
width: 50px;
height: 30px;
& .path {
stroke: ${(props) => props.theme.colors.black};
stroke-linecap: round;
animation: dash 1.5s ease-in-out infinite;
}
#keyframes rotate {
100% {
transform: rotate(360deg);
}
}
#keyframes dash {
0% {
stroke-dasharray: 1, 150;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -35;
}
100% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -124;
}
}
`
export const Spinner = ({ className}) => {
return (
<CustomSpinner viewBox="0 0 50 50" className={className}>
<circle className="path" cx="25" cy="25" r="20" fill="none" strokeWidth="4" />
</CustomSpinner>
)
}
Just need to change the export const Spinner = (...) => to export default (...) =>, like so:
export default ({ className}) => {
return (
<CustomSpinner viewBox="0 0 50 50" className={className}>
<circle className="path" cx="25" cy="25" r="20" fill="none" strokeWidth="4" />
</CustomSpinner>
)
}
But I would suggest exporting like this instead so you can get full autocomplete from your code editor when importing components:
const Spinner = ({ className}) => {
return (
<CustomSpinner viewBox="0 0 50 50" className={className}>
<circle className="path" cx="25" cy="25" r="20" fill="none" strokeWidth="4" />
</CustomSpinner>
)
}
export default Spinner;
It has to do with how you are importing this component.
When you do
export const Spinner = ({ className}) => {
return (
<CustomSpinner viewBox="0 0 50 50" className={className}>
<circle className="path" cx="25" cy="25" r="20" fill="none" strokeWidth="4" />
</CustomSpinner>
)
}
Then you need to import it like
import {Spinner} from 'path/to/Spinner
and you are likely importing it like
import Spinner from 'path/to/Spinner'
The above line imports the default export from spinner, but you don't have a default export, so you need to change your import, or change your export to
export default ({ className}) => {
return (
<CustomSpinner viewBox="0 0 50 50" className={className}>
<circle className="path" cx="25" cy="25" r="20" fill="none" strokeWidth="4" />
</CustomSpinner>
)
}
instead

Change <svg:path/> Opacity on MouseOver

I'm trying to create an effect where if a user mouses over a line chart the parts of the svg:path elements that are to the right of the mouse are faded out while the parts of the svg:path element to the left remain at full opacity.
I've tried a few options to no avail - see below.
My first try was to use a path with mask which does change the opacity, but the rest of the lines are hidden because they are not under the mask.
<defs>
<mask
id='mask-for-line'
maskUnits="userSpaceOnUse"
maskContentUnits="userSpaceOnUse"
>
<rect style={{opacity: .5, stroke: 'none', fill: 'white'}}
x={x}
y={y}
width={width}
height={height}
/>
</mask>
</defs>
<path mask='url(#mask-for-line)' ... />
My second try was to put an svg:rect over the faded-out section, but that doesn't work either.
<rect x={x} y={0} width={width} height={height}
style={{opacity: .1, stroke: 'none', fill: 'lightgray'}}/>
Thanks to the inspiration from michael-rovinsky I was able to solve the problem. Within the mask, I have one <rect/> at full opacity covering the left-side of the chart and a second <rect/> at 25% opacity covering the right-side of the chart.
<defs>
<mask
id='mask-for-line'
maskUnits="userSpaceOnUse"
maskContentUnits="userSpaceOnUse"
>
<rect style={{fillOpacity: .25, fill: 'white'}}
x={x}
y={y}
width={width - x}
height={height}
/>
<rect style={{fillOpacity: 1, fill: 'white'}}
width={x}
height={height}
/>
</mask>
</defs>
You can try linear gradient with variable stop offsets:
const svg = d3.select('svg');
const width = parseInt(svg.attr('width'));
const height = parseInt(svg.attr('height'));
console.log(width, height);
const colors = ['red', 'green', 'blue', 'orange', 'purple', 'brown'];
const defs = svg.append('defs');
colors.forEach(color => {
const grad = defs.append('linearGradient').attr('id', `${color}-opacity-mask`);
grad.append('stop').attr('offset', '0%').attr('stop-color', color).attr('stop-opacity', 1);
grad.append('stop').attr('stop-color', color).attr('stop-opacity', 1).classed('mid-stop', true);
grad.append('stop').attr('stop-color', color).attr('stop-opacity', 0.25).classed('mid-stop', true);
grad.append('stop').attr('offset', '100%').attr('stop-color', color).attr('stop-opacity', 0.25);
})
const step = 100;
const paths = colors.map(color => {
let path = '';
for (let index = 0; index <= width / step; index++)
if (!index)
path = `M 0,${Math.random() * height}`;
else
path += `L ${index * step},${Math.random() * height}`;
return {color, path};
});
paths.forEach(({path, color}) => svg.append('path').attr('d', path).style('stroke', `url(#${color}-opacity-mask)`).style('fill', 'none'));
const line = svg.append('line')
.attr('y1', 0)
.attr('y2', height)
.style('stroke', 'black')
.style('stroke-dasharray', '3 3')
.style('visibility', 'hidden');
svg.on('mousemove', e => {
const pct = Math.round(100 * e.layerX / width);
svg.selectAll('.mid-stop').attr('offset', `${pct}%`);
line.attr('x1', e.layerX).attr('x2', e.layerX).style('visibility', 'visible');
});
svg.on('mouseleave', e => line.style('visibility', 'hidden'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>
<svg width="500" height="200">
</svg>

how to Place Items on a circle in React Native

I am developing something similar to this
I have many ways to acheive this using css and Scss but i couldn't find anything for react native ,if anyone has any idea how to do this.Their help will be much appreciated.Thank you.
Use cos and sin function to place the images
Working example: https://snack.expo.io/#msbot01/trusting-bagel
import React, { Component } from 'react';
import { Text, View, StyleSheet, ScrollView, FlatList, Image } from 'react-native';
import Constants from 'expo-constants';
import Highlighter from 'react-native-highlight-words';
const size = 200 ;
const symbolSize = 16;
const radius = size / 2;
const center = radius;
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
higightedTexts:''
}
}
componentDidMount(){
this.setupA();
this.setupB();
this.setupC();
this.setupD();
}
degToRad(deg) {
return deg * Math.PI / 180;
}
setupA(){
const angleRad = this.degToRad(0);
const x = radius * Math.cos(angleRad) + center - symbolSize / 2;
const y = radius * Math.sin(angleRad) + center - symbolSize / 2;
this.setState({
x: x,
y: y
})
}
setupB(){
const angleRad = this.degToRad(90);
const x = radius * Math.cos(angleRad) + center - symbolSize / 2;
const y = radius * Math.sin(angleRad) + center - symbolSize / 2;
this.setState({
x2: x,
y2: y
})
}
setupC(){
const angleRad = this.degToRad(180);
const x = radius * Math.cos(angleRad) + center - symbolSize / 2;
const y = radius * Math.sin(angleRad) + center - symbolSize / 2;
this.setState({
x3: x,
y3: y
})
}
setupD(){
const angleRad = this.degToRad(270);
const x = radius * Math.cos(angleRad) + center - symbolSize / 2;
const y = radius * Math.sin(angleRad) + center - symbolSize / 2;
this.setState({
x4: x,
y4: y
})
}
render() {
return (
<View style={{ flex: 1, justifyContent:'center', alignItems:'center' }}>
<View
style={[{backgroundColor:'red',
width: size,
height: size,
borderRadius: size / 2,
}]}
>
<Image
style={{width: 40,
height: 40,
borderRadius:20,
left: this.state.x-20,
top: this.state.y ,
position:'absolute'}}
source={{
uri:
'https://icons.iconarchive.com/icons/graphicloads/100-flat/256/home-icon.png',
}}
/>
<Image
style={{width: 40,
height: 40,
borderRadius: 20,
left: this.state.x2,
top: this.state.y2-20 ,
position:'absolute'}}
source={{
uri:
'https://icons.iconarchive.com/icons/graphicloads/100-flat/256/home-icon.png',
}}
/>
<Image
style={{width: 40,
height: 40,
borderRadius: 20,
left: this.state.x3,
top: this.state.y3 ,
position:'absolute'}}
source={{
uri:
'https://icons.iconarchive.com/icons/graphicloads/100-flat/256/home-icon.png',
}}
/>
<Image
style={{width: 40,
height: 40,
borderRadius: 20,
left: this.state.x4-10,
top: this.state.y4,
position:'absolute'}}
source={{
uri:
'https://icons.iconarchive.com/icons/graphicloads/100-flat/256/home-icon.png',
}}
/>
</View>
</View>
);
}
}
const styles = StyleSheet.create({});
React native doesn't have any special library do this you can do this by using CSS:
You can do something like this:
import { Dimensions, View, Image } from 'react-native';
import React, { Component } from 'react';
class App extends Component{
render(){
return(
<View style={{flex:1,justifyContent:"center",alignItems:"center"}}>
<View
style = {{
borderRadius: Math.round(Dimensions.get('window').width + Dimensions.get('window').height) / 2,
width: Dimensions.get('window').width * 0.8,
height: Dimensions.get('window').width * 0.8,
borderWidth:5,
borderColor:"red",
justifyContent: 'center',
alignItems: 'center'
}}
underlayColor = '#ccc'
>
<Image source={require('./assets/main.jpg')} style={{height:50,width:50,position:"absolute",bottom:Dimensions.get('window').width * 0.7}} />
<Image source={require('./assets/main.jpg')} style={{height:50,width:50,position:"absolute",bottom:Dimensions.get('window').width * 0.6,right: 20}} />
<Image source={require('./assets/main.jpg')} style={{height:50,width:50,position:"absolute",bottom:Dimensions.get('window').width * 0.6,left: 20}} />
<Image source={require('./assets/main.jpg')} style={{height:50,width:50,position:"absolute",top:Dimensions.get('window').width * 0.7}} />
<Image source={require('./assets/main.jpg')} style={{height:50,width:50,position:"absolute",top:Dimensions.get('window').width * 0.6,right: 20}} />
<Image source={require('./assets/main.jpg')} style={{height:50,width:50,position:"absolute",top:Dimensions.get('window').width * 0.6,left: 20}} />
<Image source={require('./assets/main.jpg')} style={{height:50,width:50,position:"absolute",bottom:Dimensions.get('window').width * 0.3,left:-20}} />
<Image source={require('./assets/main.jpg')} style={{height:50,width:50,position:"absolute",bottom:Dimensions.get('window').width * 0.3,right:-20}} />
</View>
</View>
)
}
}
export default App;
Hope this helps!

Resources