From 67369592159295530035a97f6fc14b2b3013a9d3 Mon Sep 17 00:00:00 2001 From: Joshua Wuebbolt <72772245+JoshuaWuebbolt@users.noreply.github.com> Date: Tue, 4 Feb 2025 11:24:17 -0500 Subject: [PATCH] SC-4 Account page. (#12) * SC-4 - Created the account page. --- webapp/src/components/pie-chart.tsx | 131 ++++++++++++++++++++++++++++ webapp/src/pages/Account.tsx | 81 +++++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 webapp/src/components/pie-chart.tsx create mode 100644 webapp/src/pages/Account.tsx diff --git a/webapp/src/components/pie-chart.tsx b/webapp/src/components/pie-chart.tsx new file mode 100644 index 0000000..9736b8e --- /dev/null +++ b/webapp/src/components/pie-chart.tsx @@ -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 = ({ data, colors = defaultColors, labels = [] }) => { + const [hoveredIndex, setHoveredIndex] = useState(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 ( + + + + {combinedData.labels[0]} + + + 100% + + + ); + } + + return ( + + {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 ( + 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 ( + + {combinedData.labels[index] && ( + + {combinedData.labels[index]} + + )} + + {percentage.toFixed(1)}% + + + ); + })} + + ); +}; + +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; diff --git a/webapp/src/pages/Account.tsx b/webapp/src/pages/Account.tsx new file mode 100644 index 0000000..a5ee715 --- /dev/null +++ b/webapp/src/pages/Account.tsx @@ -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 ( + <> +
+
+ + {/* Welcome message */} +
+
+

+ Hello {user_name}, +

+

+ Thanks for supporting those in need through Stock Charity! +

+
+
+
+ {/* Stocks information */} +
+ + {/* Stocks owned */} +
+

Stocks owned:

+

Total stock value: ${stock_total_value.toFixed(2)}

+

+ + + {stocks_owned.map((stock, index) => ( +
    {number_of_stocks_owned_per_stock[index]} stocks of {stock} worth ${stocks_owned_values[index].toFixed(2)} each
+ ))} + +
+ + {/* Dividends earned */} +
+

Dividends earned:

+

Total dividends earned: $27.62

+

+

$5.43 on Jan. 16th 2025 from {stocks_owned[2]}

+

$6.76 on Oct. 23th 2024 from {stocks_owned[1]}

+

$15.43 on Aug. 6th 2024 from {stocks_owned[0]}

+ + +
+ +
+
+
+ {/* Pie chart of stocks owned */} +
+

Your impact:

+ +
+
+ + ); +} + +export default Account; \ No newline at end of file