Skip to content

Commit

Permalink
SC-4 Account page. (#12)
Browse files Browse the repository at this point in the history
* SC-4 - Created the account page.
  • Loading branch information
JoshuaWuebbolt authored Feb 4, 2025
1 parent 18f5873 commit 6736959
Show file tree
Hide file tree
Showing 2 changed files with 212 additions and 0 deletions.
131 changes: 131 additions & 0 deletions webapp/src/components/pie-chart.tsx
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;
81 changes: 81 additions & 0 deletions webapp/src/pages/Account.tsx
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;

0 comments on commit 6736959

Please sign in to comment.