-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
2 changed files
with
212 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
import React, { useState } from "react"; | ||
|
||
interface PieChartProps { | ||
data: number[]; | ||
colors?: string[]; | ||
labels?: string[]; | ||
} | ||
|
||
const defaultColors = [ | ||
"#FF6384", "#36A2EB", "#FFCE56", "#4BC0C0", "#9966FF", "#FF9F40", | ||
"#B4FF9F", "#FF9FB4", "#9F40FF", "#40FF9F", "#9FB4FF", "#FFB440", | ||
"#FF4040", "#40FF40", "#4040FF", "#FFFF40", "#40FFFF", "#FF40FF", | ||
"#B440FF", "#FFB440" | ||
]; | ||
|
||
const PieChart: React.FC<PieChartProps> = ({ data, colors = defaultColors, labels = [] }) => { | ||
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null); | ||
|
||
const total = data.reduce((acc, value) => acc + value, 0); | ||
|
||
// Combine sections with values less than 5% into an "Other" section | ||
const combinedData = data.reduce<{ data: number[], labels: string[] }>((acc, value, index) => { | ||
if ((value / total) * 100 < 5) { | ||
if (acc.labels.includes("Other")) { | ||
acc.data[acc.labels.indexOf("Other")] += value; | ||
} else { | ||
acc.data.push(value); | ||
acc.labels.push("Other"); | ||
} | ||
} else { | ||
acc.data.push(value); | ||
acc.labels.push(labels[index] || ""); | ||
} | ||
return acc; | ||
}, { data: [], labels: [] }); | ||
|
||
let cumulativeValue = 0; | ||
|
||
// If there is only one section, render a full circle | ||
if (combinedData.data.length === 1) { | ||
return ( | ||
<svg width="400" height="400" viewBox="0 0 32 32"> | ||
<circle cx="16" cy="16" r="12" fill={colors[0]} /> | ||
<text x="16" y="15" fill="#000" fontSize="4" textAnchor="middle" dominantBaseline="middle"> | ||
{combinedData.labels[0]} | ||
</text> | ||
<text x="16" y="19" fill="#000" fontSize="3" textAnchor="middle" dominantBaseline="middle"> | ||
100% | ||
</text> | ||
</svg> | ||
); | ||
} | ||
|
||
return ( | ||
<svg width="400" height="400" viewBox="0 0 32 32"> | ||
{combinedData.data.map((value, index) => { | ||
const [startX, startY] = getCoordinatesForPercent(cumulativeValue / total); | ||
cumulativeValue += value; | ||
const [endX, endY] = getCoordinatesForPercent(cumulativeValue / total); | ||
const largeArcFlag = value / total > 0.5 ? 1 : 0; | ||
const [labelX, labelY] = getCoordinatesForPercent((cumulativeValue - value / 2) / total); | ||
|
||
const isHovered = index === hoveredIndex; | ||
const translateFactor = isHovered ? 0.25 : 0; // Increased translation factor | ||
const translateX = isHovered ? (labelX - 16) * translateFactor : 0; | ||
const translateY = isHovered ? (labelY - 16) * translateFactor : 0; | ||
const transform = `translate(${translateX}, ${translateY})`; | ||
|
||
return ( | ||
<path | ||
key={index} | ||
d={`M16 16 L ${startX} ${startY} A 12 12 0 ${largeArcFlag} 1 ${endX} ${endY} Z`} // Reduced radius from 16 to 12 | ||
fill={colors[index % colors.length]} | ||
transform={transform} | ||
style={{ transition: 'transform 0.2s' }} | ||
onMouseEnter={() => setHoveredIndex(index)} | ||
onMouseLeave={() => setHoveredIndex(null)} | ||
/> | ||
); | ||
})} | ||
{combinedData.data.map((value, index) => { | ||
cumulativeValue += value; | ||
const [labelX, labelY] = getCoordinatesForPercent((cumulativeValue - value / 2) / total); | ||
|
||
// Adjust label position to avoid clipping | ||
let adjustedLabelX = labelX; | ||
let adjustedLabelY = labelY; | ||
|
||
// Ensure labels are within the bounds of the circle | ||
const distanceFromCenter = Math.sqrt(Math.pow(labelX - 16, 2) + Math.pow(labelY - 16, 2)); | ||
if (distanceFromCenter > 10) { | ||
const angle = Math.atan2(labelY - 16, labelX - 16); | ||
const labelDistance = 9 - (4 * (value / total)); | ||
adjustedLabelX = 16 + Math.cos(angle) * labelDistance; | ||
adjustedLabelY = 16 + Math.sin(angle) * labelDistance; | ||
} | ||
|
||
const isHovered = index === hoveredIndex; | ||
const translateFactor = isHovered ? 0.25 : 0; // Increased translation factor | ||
const translateX = isHovered ? (labelX - 16) * translateFactor : 0; | ||
const translateY = isHovered ? (labelY - 16) * translateFactor : 0; | ||
const transform = `translate(${translateX}, ${translateY})`; | ||
|
||
const percentage = (value / total) * 100; | ||
const fontSize = 0.75 + (percentage / 100) * 3; // Adjust font size based on percentage | ||
|
||
return ( | ||
<g key={index}> | ||
{combinedData.labels[index] && ( | ||
<text x={adjustedLabelX} y={adjustedLabelY} fill="#000" fontSize={fontSize} textAnchor="middle" dominantBaseline="middle" transform={transform} style={{ transition: 'transform 0.2s' }}> | ||
{combinedData.labels[index]} | ||
</text> | ||
)} | ||
<text x={adjustedLabelX} y={adjustedLabelY + fontSize} fill="#000" fontSize={fontSize * 0.75} textAnchor="middle" dominantBaseline="middle" transform={transform} style={{ transition: 'transform 0.2s' }}> | ||
{percentage.toFixed(1)}% | ||
</text> | ||
</g> | ||
); | ||
})} | ||
</svg> | ||
); | ||
}; | ||
|
||
const getCoordinatesForPercent = (percent: number) => { | ||
const radius = 12; // Reduced radius from 16 to 12 | ||
const x = Math.cos(2 * Math.PI * percent) * radius + 16; | ||
const y = Math.sin(2 * Math.PI * percent) * radius + 16; | ||
return [x, y]; | ||
}; | ||
|
||
export default PieChart; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import { JSX } from "react"; | ||
import PieChart from "@/components/pie-chart"; | ||
|
||
|
||
const Account = (): JSX.Element => { | ||
|
||
// Variables are hard coded for now to demo until backend is implemented. | ||
const user_name = "USER_NAME"; | ||
const stocks_owned = ["Stock 1", "Stock 2", "Stock 3", "Stock 4", "Stock 5", "Stock 6", "Stock 7"]; | ||
const stocks_owned_values = [300.50, 500.00, 199.50, 100.009, 50.119, 50.00, 150.34]; | ||
const number_of_stocks_owned_per_stock = [3, 1, 1, 4, 1, 1, 3]; | ||
let stock_total_value = 0 | ||
for (let i = 0; i < stocks_owned.length; i++){ | ||
stock_total_value += stocks_owned_values[i] * number_of_stocks_owned_per_stock[i]; | ||
} | ||
const charities_donated_to = [ | ||
"Charity 1", "Charity 2", "Charity 3", "Charity 4", "Charity 5", | ||
"Charity 6", "Charity 7", "Charity 8", "Charity 9", | ||
]; | ||
|
||
const donations_made = [1, 5, 5, 10, 15, 15, 18, 29, 2]; | ||
|
||
return ( | ||
<> | ||
<div className="flex flex-row justify-around items-start p-4 text-black"> | ||
<div className="flex flex-col justify-around items-start p-4 text-black"> | ||
|
||
{/* Welcome message */} | ||
<div className="py-10 px-20 w-full max-w-lg grid grid-cols-1 gap-6"> | ||
<div className=""> | ||
<h2 className="text-4xl font-semibold tracking-tight text-gray-900 sm:text-5xl"> | ||
Hello {user_name}, | ||
</h2> | ||
<p className="mt-4 text-lg text-gray-600"> | ||
Thanks for supporting those in need through Stock Charity! | ||
</p> | ||
</div> | ||
</div> | ||
<div className="flex flex-col justify-around items-start p-4 text-black"> | ||
{/* Stocks information */} | ||
<div className="flex flex-row gap-10 justify-around items-start"> | ||
|
||
{/* Stocks owned */} | ||
<div className="bg-gray-300 p-2 rounded-md"> | ||
<h3 className="text-lg font-bold">Stocks owned:</h3> | ||
<p>Total stock value: ${stock_total_value.toFixed(2)}</p> | ||
<br></br> | ||
|
||
|
||
{stocks_owned.map((stock, index) => ( | ||
<ul key={index}>{number_of_stocks_owned_per_stock[index]} stocks of {stock} worth ${stocks_owned_values[index].toFixed(2)} each</ul> | ||
))} | ||
|
||
</div> | ||
|
||
{/* Dividends earned */} | ||
<div className="bg-gray-300 p-2 rounded-md"> | ||
<h3 className="text-lg font-bold">Dividends earned:</h3> | ||
<p>Total dividends earned: $27.62</p> | ||
<br></br> | ||
<p>$5.43 on Jan. 16th 2025 from {stocks_owned[2]}</p> | ||
<p>$6.76 on Oct. 23th 2024 from {stocks_owned[1]}</p> | ||
<p>$15.43 on Aug. 6th 2024 from {stocks_owned[0]}</p> | ||
|
||
|
||
</div> | ||
|
||
</div> | ||
</div> | ||
</div> | ||
{/* Pie chart of stocks owned */} | ||
<div className="flex flex-col items-center justify-center py-9 text-black"> | ||
<p className="text-lg font-bold">Your impact:</p> | ||
<PieChart data={donations_made} labels={charities_donated_to} /> | ||
</div> | ||
</div> | ||
</> | ||
); | ||
} | ||
|
||
export default Account; |