im trying to create a personality picker as seen in the image below. The intended use of it is that depending on the score of each personality from 0 to 5 a black line will be drawn and expand to each of the five circles to indicate the score.
Here is my code so far to create this, I have gotten the circles in line and I am currently creating the black lines however I am unable to do them dynamically and I am having to plot each point specifically which may be a problem for different screensizes. I am hoping there is a better way than what I am doing right now as it is a tonne of nested Views forming shapes. Any help would be great on how to make this simpiler / more dynamic!
Thanks :)
return (
<View style={{margin: 32}}>
<Text style={{fontFamily: 'Roboto_400Regular', fontSize: 36}}>Plot your{"\n"}personality</Text>
<View style={{flexDirection: 'row', justifyContent: 'space-between'}}>
<Text style={{
fontFamily: 'Roboto_400Regular',
fontSize: 14,
color: '#878787',
paddingTop: 4,
alignSelf: 'flex-end'
}}>Use this to match with similar people?</Text>
<Switch
trackColor={{false: "#878787", true: "#5BF675"}}
thumbColor="#f4f3f4"
ios_backgroundColor="#878787"
onValueChange={toggleSwitch}
value={isEnabled}
/>
</View>
<View style={{alignItems: 'center', justifyContent: 'center', marginTop: 32}}>
<View style={{
borderColor: '#DBDBDB',
borderWidth: 1,
height: 275,
width: 275,
borderRadius: 275 / 2,
alignItems: 'center',
justifyContent: 'center'
}}>
<View style={{
borderColor: '#DBDBDB',
borderWidth: 1,
height: 225,
width: 225,
borderRadius: 225 / 2,
alignItems: 'center',
justifyContent: 'center'
}}>
<View style={{
borderColor: '#DBDBDB',
borderWidth: 1,
height: 175,
width: 175,
borderRadius: 175 / 2,
alignItems: 'center',
justifyContent: 'center'
}}>
<View style={{
borderColor: '#DBDBDB',
borderWidth: 1,
height: 125,
width: 125,
borderRadius: 125 / 2,
alignItems: 'center',
justifyContent: 'center'
}}>
<View style={{
borderColor: '#DBDBDB',
borderWidth: 1,
height: 75,
width: 75,
borderRadius: 75 / 2,
alignItems: 'center',
justifyContent: 'center'
}}>
<View style={{position: 'absolute', justifyContent: 'center', alignItems: 'center'}}>
<View
style={{
borderColor: '#DBDBDB',
borderWidth: 1,
width: 275,
alignItems: 'center',
justifyContent: 'center',
transform: [{ rotate: '45deg'}],
position: 'absolute',
}}
/>
<View
style={{
borderColor: '#DBDBDB',
borderWidth: 1,
width: 275,
alignItems: 'center',
justifyContent: 'center',
transform: [{ rotate: '90deg'}],
position: 'absolute',
}}
/>
<View
style={{
borderColor: '#DBDBDB',
borderWidth: 1,
width: 275,
alignItems: 'center',
justifyContent: 'center',
transform: [{ rotate: '135deg'}],
position: 'absolute',
}}
/>
<View
style={{
borderColor: '#DBDBDB',
borderWidth: 1,
width: 275,
alignItems: 'center',
justifyContent: 'center',
transform: [{ rotate: '180deg'}],
position: 'absolute',
}}
/>
<View style={{position: 'absolute', justifyContent: 'center', alignItems: 'center'}}>
<View
style={{
backgroundColor: '#000',
borderWidth: 3,
width: 90.5,
bottom: -3,
left: 0,
alignItems: 'center',
justifyContent: 'center',
transform: [{ rotate: '0deg'}],
position: 'absolute',
}}
/>
<View
style={{
backgroundColor: '#000',
borderWidth: 3,
width: 137.5,
bottom: -52,
right: -116,
alignItems: 'center',
justifyContent: 'center',
transform: [{ rotate: '45deg'}],
position: 'absolute',
}}
/>
<View
style={{
backgroundColor: '#000',
borderWidth: 3,
width: 137.5,
alignItems: 'center',
justifyContent: 'center',
bottom: -72,
left: -70,
transform: [{ rotate: '90deg'}],
position: 'absolute',
}}
/>
<View
style={{
backgroundColor: '#000',
borderWidth: 3,
width: 137.5,
alignItems: 'center',
justifyContent: 'center',
transform: [{ rotate: '135deg'}, {translateX: 68}],
position: 'absolute',
}}
/>
<View
style={{
backgroundColor: '#000',
borderWidth: 3,
width: 137.5,
alignItems: 'center',
justifyContent: 'center',
transform: [{ rotate: '180deg'}, {scaleX: 0}],
position: 'absolute',
}}
/>
<View
style={{
backgroundColor: '#000',
borderWidth: 3,
width: 137.5,
alignItems: 'center',
justifyContent: 'center',
transform: [{ rotate: '225deg'}, {translateX: 68}],
position: 'absolute',
}}
/>
<View
style={{
backgroundColor: '#000',
borderWidth: 3,
width: 137.5,
alignItems: 'center',
justifyContent: 'center',
transform: [{ rotate: '270deg'}, {translateX: 68}],
position: 'absolute',
}}
/>
<View
style={{
backgroundColor: '#000',
borderWidth: 3,
width: (137.5),
alignItems: 'center',
justifyContent: 'center',
transform: [{ rotate: '315deg'}, {translateX: 68}],
position: 'absolute',
}}
/>
</View>
</View>
</View>
</View>
</View>
</View>
</View>
</View>
</View>
);
I have found an answer, although I am not 100% satisfied and believe it may fall over when screens resize.
import React from 'react';
import {View} from "react-native";
interface PersonalityPlotterDialProps {
value: number
}
function PersonalityPlotterDial(props: PersonalityPlotterDialProps) {
const {value} = props;
const widthCalculator = (value: number) => {
switch (value) {
case 0:
return 0;
case 1:
return 37.5;
case 2:
return 62.5;
case 3:
return 87.5;
case 4:
return 112.5;
case 5:
return 137.5;
default:
return 0;
}
};
const translateCalculator = (value: number) => {
switch (value) {
case 0:
return 0;
case 1:
return 1.05;
case 2:
return 1.75;
case 3:
return 2.5;
case 4:
return 3.2;
case 5:
return 3.95;
default:
return 0;
}
}
const displayCalculator = () => {
if (widthCalculator(value) === 0) {
return 'none'
}
}
return (
<View style={{alignItems: 'center', justifyContent: 'center', marginTop: 32, marginBottom: 64}}>
<View style={{
borderColor: '#DBDBDB',
borderWidth: 1,
height: 275,
width: 275,
borderRadius: 275 / 2,
alignItems: 'center',
justifyContent: 'center'
}}>
<View style={{
borderColor: '#DBDBDB',
borderWidth: 1,
height: 225,
width: 225,
borderRadius: 225 / 2,
alignItems: 'center',
justifyContent: 'center'
}}>
<View style={{
borderColor: '#DBDBDB',
borderWidth: 1,
height: 175,
width: 175,
borderRadius: 175 / 2,
alignItems: 'center',
justifyContent: 'center'
}}>
<View style={{
borderColor: '#DBDBDB',
borderWidth: 1,
height: 125,
width: 125,
borderRadius: 125 / 2,
alignItems: 'center',
justifyContent: 'center'
}}>
<View style={{
borderColor: '#DBDBDB',
borderWidth: 1,
height: 75,
width: 75,
borderRadius: 75 / 2,
alignItems: 'center',
justifyContent: 'center',
position: 'relative'
}}>
<View style={{position: 'absolute', justifyContent: 'center', alignItems: 'center'}}>
<View
style={{
borderColor: '#DBDBDB',
borderWidth: 1,
width: 275,
alignItems: 'center',
justifyContent: 'center',
transform: [{ rotate: '45deg'}],
position: 'absolute',
}}
/>
<View
style={{
borderColor: '#DBDBDB',
borderWidth: 1,
width: 275,
alignItems: 'center',
justifyContent: 'center',
transform: [{ rotate: '90deg'}],
position: 'absolute',
}}
/>
<View
style={{
borderColor: '#DBDBDB',
borderWidth: 1,
width: 275,
alignItems: 'center',
justifyContent: 'center',
transform: [{ rotate: '135deg'}],
position: 'absolute',
}}
/>
<View
style={{
borderColor: '#DBDBDB',
borderWidth: 1,
width: 275,
alignItems: 'center',
justifyContent: 'center',
transform: [{ rotate: '180deg'}],
position: 'absolute',
}}
/>
<View style={{position: 'absolute', justifyContent: 'center', alignItems: 'center'}}>
<View
style={{
backgroundColor: '#000',
borderWidth: 3,
width: widthCalculator(value),
bottom: -3,
left: 0,
alignItems: 'center',
justifyContent: 'center',
transform: [{ rotate: '0deg'}],
position: 'absolute',
display: displayCalculator(),
}}
/>
<View
style={{
backgroundColor: '#000',
borderWidth: 3,
width: widthCalculator(value),
alignItems: 'center',
justifyContent: 'center',
transform: [{ rotate: '45deg'}, {translateX: (translateCalculator(value)) * 17.5}],
position: 'absolute',
display: displayCalculator(),
}}
/>
<View
style={{
backgroundColor: '#000',
borderWidth: 3,
width: widthCalculator(value),
alignItems: 'center',
justifyContent: 'center',
transform: [{ rotate: '90deg'}, {translateX: (translateCalculator(value)) * 17.5}],
position: 'absolute',
display: displayCalculator(),
}}
/>
<View
style={{
backgroundColor: '#000',
borderWidth: 3,
width: widthCalculator(value),
alignItems: 'center',
justifyContent: 'center',
transform: [{ rotate: '135deg'}, {translateX: (translateCalculator(value)) * 17.5}],
position: 'absolute',
display: displayCalculator(),
}}
/>
<View
style={{
backgroundColor: '#000',
borderWidth: 3,
width: widthCalculator(value),
alignItems: 'center',
justifyContent: 'center',
bottom: -3,
right: 0,
transform: [{ rotate: '180deg'}],
position: 'absolute',
display: displayCalculator(),
}}
/>
<View
style={{
backgroundColor: '#000',
borderWidth: 3,
width: widthCalculator(value),
alignItems: 'center',
justifyContent: 'center',
transform: [{ rotate: '225deg'}, {translateX: (translateCalculator(value)) * 17.5}],
position: 'absolute',
display: displayCalculator(),
}}
/>
<View
style={{
backgroundColor: '#000',
borderWidth: 3,
width: widthCalculator(value),
alignItems: 'center',
justifyContent: 'center',
transform: [{ rotate: '270deg'}, {translateX: (translateCalculator(value)) * 17.5}],
position: 'absolute',
display: displayCalculator(),
}}
/>
<View
style={{
backgroundColor: '#000',
borderWidth: 3,
width: widthCalculator(value),
alignItems: 'center',
justifyContent: 'center',
transform: [{ rotate: '315deg'}, {translateX: (translateCalculator(value)) * 17.5}],
position: 'absolute',
display: displayCalculator(),
}}
/>
</View>
</View>
</View>
</View>
</View>
</View>
</View>
</View>
);
}
export default PersonalityPlotterDial;
In general, there is a cleaner solution for this (maybe I will add one later), but for now, lets try and make your code work.
You can have dynamic line size by passing the width to each View component with a math formula. meaning, think of a way to represent the size of each line. for example:
0: 0px,
1: 50px,
2: 100px
and so on... (the 50px should be the radius divided by 5)
Now, for each View tag, you can just set the width by multiplying 50px with the value of the size of it(0-5: 0-250px).
This should do the trick.
(I assume you have a state or a way to keep the values of each label, and by that you multiply this value with 50px for each line)
Hey, sorry for taking so long I wasn't available so much. anyway, I wrote a small piece of code that does the behavior you want. the design isn't amazing but it does the trick. I used styled-components just for fun, you can run it easily by installing styled-component or just converting it to regular form:
App.js
import { useState } from 'react';
import styled from 'styled-components';
import Line from './components/line';
function App() {
const [values, setValues] = useState(config);
return (
<Container>
<Circle>
<Center/>
<Lines>{values.map((value) => <Line {...value} />)}</Lines></Circle>
<Menu>
{values.map((line, index) =>
<SizeButtons>
<Button onClick={() => {
const copyValues = [...values];
copyValues[index].value = line.value - 1 > 0 ? line.value - 1 : 0;
setValues(copyValues);
}}>-</Button>
<Button onClick={() => {
const copyValues = [...values];
copyValues[index].value = line.value + 1 < 5 ? line.value + 1 : 5;
setValues(copyValues);
}}>+</Button>
</SizeButtons>
)}
</Menu>
</Container>
);
}
export default App;
const Container = styled.div`
position: relative;
margin-top: 200px;
margin-left: 200px;
`;
const Circle = styled.div`
position: relative;
width: 250px;
height: 250px;
border: 1px solid black;
border-radius: 50%;
position: absolute;
top: 0;
left: 0;
`;
const Center = styled.div`
position: absolute;
top: 116px;
left: 116px;
width: 20px;
height: 20px;
border-radius: 50%;
background-color: black;
`;
const Lines = styled.div`
position: absolute;
top: 125px;
left: 125px;
`;
const SizeButtons = styled.div``;
const Menu = styled.div`
position: absolute;
top: 0;
right: 0;
`;
const Button = styled.button``;
const config = [
{ value: 1, degree: 0 },
{ value: 1, degree: 45, },
{ value: 1, degree: 90, left: '2px' },
{ value: 1, degree: 135, top: '2px', left: '1px' },
{ value: 1, degree: 181, top: '2px' },
{ value: 1, degree: 225, top: '0px' },
{ value: 1, degree: 270, top: '0px' },
{ value: 1, degree: 315, top: '0px' },
]
line.js:
import React from 'react';
import styled from 'styled-components'
const Line = (props) => {
return (
<Container {...props}/>
);
};
export default Line;
const Container = styled.div`
width: ${props => props.value * 25 }px;
background: black;
height: 2px;
transform: rotate(${props => props.degree}deg);
transform-origin: top left;
position: absolute;
top: ${props => props.top};
left: ${props => props.left}
`;
let me know if something is unclear.
I am trying to make an element with style={styles.settingsButtonContainer} not to depend on position of other elements on the view, but to be always in the top right corner of the screen. I am trying in this way:
<View style={styles.container}>
<RCActivityIndicator animating={showActivityIndicator} />
<RCModal title={alertTitle} visible={alertVisible} onButtonPress={alertOnPress} />
<ScrollView contentContainerStyle={{ flexGrow: 1, justifyContent: 'center' }}>
<Text style={styles.title}>Noughts and Crosses</Text>
<Text style={styles.tip}>Get {winCondition()} in row, column or diagonal</Text>
<TouchableHighlight underlayColor="#000000" style={[styles.button, styles.newGameButton]} onPress={setInitialFieldState}>
<Text style={styles.buttonText}>New game</Text>
</TouchableHighlight>
<Text style={styles.turnContainer}>Turn: <Text style={[styles.turn, { color: turn === 'X' ? '#2E86C1' : '#E74C3C'}]}>{turn}</Text></Text>
<Field state={fieldState} size={fieldSize} winner={winCombination} onCellPress={onCellPress} />
</ScrollView>
<View style={styles.settingsButtonContainer}>
<TouchableHighlight underlayColor={theme.colors.secondary} style={styles.settingsButton} onPress={onSettingsPress}>
<Image source={require('../img/settings.png')} style={styles.settingsIcon} />
</TouchableHighlight>
</View>
</View>
const styles = StyleSheet.create({
container: {
flex: 1
},
title: {
textAlign: 'center',
fontSize: 33,
fontFamily: theme.fonts.primary,
color: theme.colors.text
},
button: {
alignItems: 'center',
padding: 5,
margin: 5,
marginLeft: 10,
marginRight: 10,
borderRadius: 20,
height: 35,
justifyContent: 'center'
},
newGameButton: {
backgroundColor: theme.colors.primary,
paddingLeft: 15,
paddingRight: 15,
},
buttonText: {
fontSize: 25,
fontFamily: theme.fonts.primary,
color: theme.colors.text
},
tip: {
textAlign: 'center',
fontSize: 25,
marginLeft: 10,
marginRight: 10,
fontFamily: theme.fonts.primary,
color: theme.colors.text
},
turnContainer: {
textAlign: 'center',
fontSize: 33,
fontFamily: theme.fonts.primary,
color: theme.colors.text,
marginLeft: 10,
},
turn: {
fontSize: 30,
fontFamily: theme.fonts.noughtsAndCrosses,
},
privacy: {
textAlign: 'center',
color: theme.colors.secondaryText,
fontSize: 15,
textDecorationLine: "underline",
marginTop: 20
},
settingsButtonContainer: {
position: 'absolute',
top: 5,
right: 5,
height: 50,
width: 50,
alignSelf: 'flex-end',
},
settingsButton: {
height: 50,
width: 50,
borderRadius: 25,
},
settingsIcon: {
height: 50,
width: 50
},
settings: {
marginTop: 30
},
buttonsContainer: {
flexDirection: 'row',
alignItems: 'center'
}
});
But it just takes a full row for itself, so the ScrollView doesn't take 100% height. How to make it work?
try to wrap the settingsButtonContainer
and use
settingButtonContainerWrapped: {
alignItem: 'flex-end',
flexDirection : 'row',
},
Actually the issue was in another element, that was on top of the ScrollView - it was RCActivityIndicator who took this place. I styled it, and it worked.
Use css:
[eleSelector] {
position: absolute;
right: 0;
top: 0;
}
Check HTML element Selector in CSS