Issue
I'm creating a dynamic array display using a circle as a layout. Each element of the array will take the same portion of space inside the circle. If there are 3 elements in the array, 100% of the circle needs to be filled up in 3 equal spaces. I have made it work up to the point of displaying 4 elements of equal size. I need help figuring out a formula to resize the squares based on the size of the array.
My approach is to create each element individually. I need to control each child inside the circle while also centering the text inside the child. I'm also open to new ideas, I feel the code is getting a little messy. If you have a cleaner approach please share!
Below is my code:
import React, { useState, forwardRef } from "react";
import {dataFlow} from './database'
const PieDisplay = forwardRef(({ showWindow }, ref) => {
const [display, setDisplay] = useState(dataFlow.map((item) => item));
const getPieStyle = (index) => {
let pieButtonStyle = {
transform: "",
};
const rotX = (360 / display.length) * index; //set the angle for each element of the pie
pieButtonStyle.transform = `rotate(${rotX}deg) skewY(0deg)`;
return pieButtonStyle;
};
const getPieTextStyle = (index) => {
let pieButtonStyle = {
transform: "",
};
const rotX = 360 / display.length / 2; //set the angle for each element's text of the pie
pieButtonStyle.transform = `rotate(${rotX}deg) skewY(0deg)`;
return pieButtonStyle;
};
let i = 0;
let j = 0;
return (
<div className="background">
<div className="pie-parent">
{display.map((elem) => (
<div
className="pie-button"
style={getPieStyle(i += 1)}
key={elem.name}
onClick={
elem.items ? () => updateDisplay(elem.name) : () => showWindow()
}
ref={ref}
>
<pre
className="pie-button--text"
style={getPieTextStyle(j += 1)}
>
{elem.name}
</pre>
</div>
))}
</div>
</div>
);
});
export default PieDisplay;
CSS:
.background {
position: relative;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
color: var(--white);
font-size: 48px;
width: 320px;
height: 320px;
padding: 20px;
border-radius: 50rem;
box-shadow: 0px 0px 30px 10px rgba(255, 255, 255, 0.2) inset, 0px 0px 30px 10px rgba(255, 255, 255, 0.2);
}
.pie-parent {
position: absolute;
box-shadow: 0px 0px 30px 10px rgba(255, 255, 255, 0.2) inset, 0px 0px 30px 10px rgba(255, 255, 255, 0.2);
padding: 0;
margin: 1em auto;
width: 225%;
height: 225%;
border-radius: 50%;
overflow: hidden;
}
.pie-button {
overflow: hidden;
position: absolute;
top: 0;
right: 0;
width: 50%;
height: 50%;
transform-origin: 0% 100%;
z-index: 2;
}
.pie-button--text {
position: absolute;
left: -100%;
width: 200%;
height: 200%;
text-align: center;
padding-top: 50px;
}
.pie-button:hover {
background-color: grey;
cursor: pointer;
}
.pie-button:nth-child(1) {
background-color: red;
}
.pie-button:nth-child(2) {
background-color: blue;
}
.pie-button:nth-child(3) {
background-color: yellow;
}
.pie-button:nth-child(4) {
background-color: green;
}
Test Data:
export const dataFlow = [
{
name: 'Contact',
},
{
name: 'About',
},
{
name: 'Projects',
items: [
{
name: "Test1",
},
{
name: 'Test2',
},
{
name: 'Test3',
},
{
name: 'Test4',
},
{
name: 'Test5'
},
]
},
{
name: "Professional\nExperience",
items: [
{
name: 'Test1',
},
{
name: 'Test2',
},
{
name: 'Test3',
}
]
},
];
Solution
I was able to solve my issue by applying the right combination of skewY and rotation values based on the length of the array. Basically, skewY values drastically change depending on the number of elements to be displayed. Below is the table I came up with:
N Elements | SkewY |
---|---|
12 | -60 |
11 | -55 |
10 | -50 |
9 | -45 |
8 | -40 |
7 | -35 |
6 | -30 |
5 | -15 |
4 | -0 |
3 | 30 |
3 = 30, scale 1.2 ~ Text -30, scale 0.8
For 3 elements I need to apply the scale property to fill out the missing space. The parent element increases the size and the text needs to be reduced by the same quantity so the text remains centered.
Something to keep in mind is, that the skewY set for the element needs to be the opposite of the text. If the element was given skewY 30deg, the text needs to have skewY -30deg. To center the text in the middle of the element, I used the rotation property. Also, the text needs to have the skewY first, followed by the rotation property. The parent element has the rotation first, followed by the skewY property.
I was not able to display 2 elements because that requires having skewY on 90deg, at that point the elements disappear. I guess there should be a way to display them, but I haven't been able to figure that out yet.
Many thanks to this author: https://microeducate.tech/how-to-divide-a-circle-into-12-equal-parts-with-color-using-css3-javascript/
Below is the final code:
import React, { useState, forwardRef } from "react";
import {dataFlow} from './database'
const PieDisplay = forwardRef(({ showWindow }, ref) => {
const [display, setDisplay] = useState(dataFlow.map((item) => item));
const getPieStyle = (index) => {
let pieButtonStyle = {
transform: "",
};
const rotX = (360 / display.length) * index; //get the rotation for each element
if (display.length === 3) pieButtonStyle.transform = `rotate(${rotX}deg) skewY(30deg) scale(1.2)`;
else if (display.length === 4) pieButtonStyle.transform = `rotate(${rotX}deg) skewY(0deg)`;
else if (display.length === 5) pieButtonStyle.transform = `rotate(${rotX}deg) skewY(-15deg)`;
else if (display.length > 5) {
const skewY = (5 * display.length) * -1 //set the skewY in intervals of 5 for 6+ elements
pieButtonStyle.transform = `rotate(${rotX}deg) skewY(${skewY}deg)`;}
return pieButtonStyle;
};
const getPieTextStyle = () => {
let pieButtonStyle = {
transform: "",
};
if (display.length === 3) pieButtonStyle.transform = `skewY(-30deg) rotate(55deg) scale(0.8)`;
else if (display.length === 4) pieButtonStyle.transform = `skewY(0deg) rotate(45deg)`;
else if (display.length === 5) pieButtonStyle.transform = `skewY(15deg) rotate(35deg)`;
else if (display.length > 5) {
const skewY = (5 * display.length)
const rotX = 30 / (skewY / 30); //get the proper rotation based on skewY
pieButtonStyle.transform = `skewY(${skewY}deg) rotate(${rotX}deg)`;
}
return pieButtonStyle;
};
let i = 0;
return (
<ul className="pie-parent">
{display.map((elem) => (
<li
className="pie-button"
style={getPieStyle(i += 1)}
key={elem.name}
onClick={
elem.items ? () => updateDisplay(elem.name) : () => showWindow()
}
ref={ref}
>
<pre className="pie-button--text" style={getPieTextStyle()}>
{elem.name}
</pre>
</li>
))}
</ul>
);
});
export default PieDisplay;
Answered By - Jonathan Sandler
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.