Merge branch 'main' of https://git.csclub.uwaterloo.ca/www/cs-2022-class-profile into histogram-component
commit
5db7c7ce61
@ -0,0 +1,59 @@ |
||||
.aboutWrapper { |
||||
position: relative; |
||||
width: 90%; |
||||
} |
||||
|
||||
.about { |
||||
display: flex; |
||||
flex-direction: row; |
||||
padding: calc(45rem / 16); |
||||
} |
||||
|
||||
.about h1 { |
||||
margin: 0; |
||||
} |
||||
|
||||
.about h4 { |
||||
margin: 0; |
||||
} |
||||
|
||||
.about aside { |
||||
flex: 1; |
||||
margin-right: calc(40rem / 16); |
||||
} |
||||
|
||||
.about aside h1 { |
||||
color: var(--secondary-accent) |
||||
} |
||||
|
||||
.about aside p { |
||||
color: var(--primary-accent-lighter) |
||||
} |
||||
|
||||
.about article { |
||||
flex: 3; |
||||
} |
||||
|
||||
.about article p { |
||||
color: var(--primary-text); |
||||
} |
||||
|
||||
.angle { |
||||
position: absolute; |
||||
top: 0; |
||||
left: 0; |
||||
width: calc(70rem / 16); |
||||
height: calc(70rem / 16); |
||||
} |
||||
|
||||
.anglePath { |
||||
stroke: var(--primary-accent-light) |
||||
} |
||||
|
||||
.left.angle { |
||||
transform: rotate(180deg); |
||||
top: unset; |
||||
left: unset; |
||||
bottom: 0; |
||||
right: 0; |
||||
} |
@ -0,0 +1,65 @@ |
||||
import React from "react"; |
||||
|
||||
import styles from "./About.module.css"; |
||||
|
||||
export function About() { |
||||
return ( |
||||
<div className={styles.aboutWrapper}> |
||||
<AngleDecoration isBottom={false} /> |
||||
<section className={styles.about}> |
||||
<aside> |
||||
<h1>About the Programs</h1> |
||||
<p>Getting an overview of the CS programs in UW</p> |
||||
</aside> |
||||
<article> |
||||
<h4>Computer Science</h4> |
||||
<p> |
||||
Offered from the Faculty of Mathematics as most commonly a co-op |
||||
program, students usually attend 8 school and 6 co-op terms in their |
||||
degree. However, CS is very flexible, as many students historically |
||||
have dropped co-ops, taking terms off, and messing with their |
||||
schedule to fit their desires. |
||||
</p> |
||||
<h4>Computing and Financial Management</h4> |
||||
<p> |
||||
Computing and Financial Management (CFM) combines the core CS |
||||
courses with electives from areas such as accounting, economics, and |
||||
financial management. This is a joint offer from the Faculty of |
||||
Mathematics and the School of Accounting and Finance, and has the |
||||
same schedule (and flexibility) as CS. |
||||
</p> |
||||
<h4>CS/BBA</h4> |
||||
<p> |
||||
Joint with Wilfrid Laurier University, the Business Administration |
||||
and Computer Science Double Degree (CS/BBA) is an exclusive offering |
||||
that allows students to get experience in CS as well as many |
||||
subfields of business. There are 10 school terms and either 4 or 5 |
||||
co-op terms in the usual schedule, so it’s a bit more work than CS |
||||
or CFM. |
||||
</p> |
||||
</article> |
||||
</section> |
||||
<AngleDecoration isBottom={true} /> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
interface AngleDecorationProps { |
||||
isBottom: boolean; |
||||
} |
||||
|
||||
function AngleDecoration({ isBottom }: AngleDecorationProps) { |
||||
return ( |
||||
<svg |
||||
viewBox="0 0 100 100" |
||||
className={isBottom ? `${styles.left} ${styles.angle}` : styles.angle} |
||||
> |
||||
<path |
||||
d="M100,2 L2,2 L2,100" |
||||
fill="none" |
||||
strokeWidth="10" |
||||
className={styles.anglePath} |
||||
/> |
||||
</svg> |
||||
); |
||||
} |
@ -0,0 +1,7 @@ |
||||
.textbox { |
||||
width: 80%; |
||||
padding: calc(80rem / 16); |
||||
background-color: var(--secondary-background); |
||||
border-radius: calc(20rem / 16); |
||||
margin: 0 auto; |
||||
} |
@ -0,0 +1,11 @@ |
||||
import React, { ReactNode } from "react"; |
||||
|
||||
import styles from "./CenterWrapper.module.css"; |
||||
|
||||
export interface TextboxProps { |
||||
children: ReactNode; |
||||
} |
||||
|
||||
export function CenterWrapper({ children }: TextboxProps) { |
||||
return <section className={styles.textbox}>{children}</section>; |
||||
} |
@ -0,0 +1,26 @@ |
||||
.piePath { |
||||
fill: var(--tertiary-background); |
||||
} |
||||
|
||||
.labelPath { |
||||
fill-opacity: 0; |
||||
} |
||||
|
||||
.pieText, |
||||
.labelText { |
||||
fill: var(--label); |
||||
font-weight: 800; |
||||
} |
||||
|
||||
.pieText { |
||||
display: none; |
||||
} |
||||
|
||||
.group:hover > .piePath { |
||||
fill: var(--primary-accent); |
||||
filter: drop-shadow(0px 0px calc(6rem / 16) var(--primary-accent)); |
||||
} |
||||
|
||||
.group:hover .pieText { |
||||
display: inline; |
||||
} |
@ -0,0 +1,180 @@ |
||||
import { Group } from "@visx/group"; |
||||
import Pie, { ProvidedProps } from "@visx/shape/lib/shapes/Pie"; |
||||
import { Text } from "@visx/text"; |
||||
import React from "react"; |
||||
|
||||
import styles from "./PieChart.module.css"; |
||||
|
||||
interface PieChartProps { |
||||
data: PieChartData[]; |
||||
/** Width of the entire graph, including labels, in pixels. */ |
||||
width: number; |
||||
/** Width of the outer ring of labels, in pixels. Label text may be cut off if specified value is too small. */ |
||||
labelWidth: number; |
||||
/** Distance between pie slices, in pixels. */ |
||||
padRadius?: number; |
||||
/** Distance of gap in center of pie graph, in pixels. */ |
||||
innerRadius?: number; |
||||
/** Font size of text inside the pie, in pixels. */ |
||||
pieTextSize?: number; |
||||
/** X-axis offset of the pie text, in pixels. */ |
||||
pieTextXOffset?: number; |
||||
/** Y-axis offset of the pie text, in pixels. */ |
||||
pieTextYOffset?: number; |
||||
/** Accessor function to get value to display as pie text from datum. */ |
||||
getPieDisplayValueFromDatum?: (datum: PieChartData) => string; |
||||
/** Font size of labels outside the pie, in pixels. */ |
||||
labelTextSize?: number; |
||||
/** X-axis offset of the label text, in pixels. */ |
||||
labelTextXOffset?: number; |
||||
/** Y-axis offset of the label text, in pixels. */ |
||||
labelTextYOffset?: number; |
||||
/** Accessor function to get value to display as label text from datum. */ |
||||
getLabelDisplayValueFromDatum?: (datum: PieChartData) => string; |
||||
className?: string; |
||||
} |
||||
|
||||
interface PieChartData { |
||||
category: string; |
||||
value: number; |
||||
} |
||||
|
||||
export function PieChart({ |
||||
data, |
||||
width, |
||||
labelWidth, |
||||
padRadius = width * 0.35, |
||||
innerRadius = width * 0.015, |
||||
pieTextSize = 40, |
||||
pieTextXOffset = 0, |
||||
pieTextYOffset = 10, |
||||
getPieDisplayValueFromDatum = (datum: PieChartData) => `${datum.value}%`, |
||||
labelTextSize = 40, |
||||
labelTextXOffset = 0, |
||||
labelTextYOffset = 0, |
||||
getLabelDisplayValueFromDatum = (datum: PieChartData) => `${datum.category}`, |
||||
className, |
||||
}: PieChartProps) { |
||||
const pieWidth = width * 0.5 - labelWidth; |
||||
return ( |
||||
<svg className={className} width={width} height={width}> |
||||
<Group top={width * 0.5} left={width * 0.5}> |
||||
<Pie |
||||
data={data} |
||||
pieValue={(d: PieChartData) => d.value} |
||||
cornerRadius={10} |
||||
padAngle={0.075} |
||||
padRadius={padRadius} |
||||
innerRadius={innerRadius} |
||||
outerRadius={pieWidth} |
||||
> |
||||
{(pie) => ( |
||||
<PieSlice |
||||
{...pie} |
||||
pieTextSize={pieTextSize} |
||||
pieTextXOffset={pieTextXOffset} |
||||
pieTextYOffset={pieTextYOffset} |
||||
getPieDisplayValueFromDatum={getPieDisplayValueFromDatum} |
||||
/> |
||||
)} |
||||
</Pie> |
||||
<Pie |
||||
data={data} |
||||
pieValue={(d: PieChartData) => d.value} |
||||
innerRadius={pieWidth} |
||||
outerRadius={width * 0.5} |
||||
> |
||||
{(pie) => ( |
||||
<PieSliceLabel |
||||
{...pie} |
||||
labelTextSize={labelTextSize} |
||||
labelTextXOffset={labelTextXOffset} |
||||
labelTextYOffset={labelTextYOffset} |
||||
getLabelDisplayValueFromDatum={getLabelDisplayValueFromDatum} |
||||
/> |
||||
)} |
||||
</Pie> |
||||
</Group> |
||||
</svg> |
||||
); |
||||
} |
||||
|
||||
type PieSliceProps<PieChartData> = ProvidedProps<PieChartData> & { |
||||
pieTextSize: number; |
||||
pieTextXOffset: number; |
||||
pieTextYOffset: number; |
||||
getPieDisplayValueFromDatum: (datum: PieChartData) => string; |
||||
}; |
||||
|
||||
export function PieSlice({ |
||||
path, |
||||
arcs, |
||||
pieTextSize, |
||||
pieTextXOffset, |
||||
pieTextYOffset, |
||||
getPieDisplayValueFromDatum, |
||||
}: PieSliceProps<PieChartData>) { |
||||
return ( |
||||
<> |
||||
{arcs.map((arc) => { |
||||
const [centroidX, centroidY] = path.centroid(arc); |
||||
const pathArc = path(arc) as string; |
||||
|
||||
return ( |
||||
<Group className={styles.group} key={`arc-${arc.data.category}`}> |
||||
<path className={styles.piePath} d={pathArc} /> |
||||
<Text |
||||
className={styles.pieText} |
||||
x={centroidX + pieTextXOffset} |
||||
y={centroidY + pieTextYOffset} |
||||
textAnchor="middle" |
||||
fontSize={pieTextSize} |
||||
> |
||||
{`${getPieDisplayValueFromDatum(arc.data)}`} |
||||
</Text> |
||||
</Group> |
||||
); |
||||
})} |
||||
</> |
||||
); |
||||
} |
||||
|
||||
type PieSliceLabelProps<PieChartData> = ProvidedProps<PieChartData> & { |
||||
labelTextSize: number; |
||||
labelTextXOffset: number; |
||||
labelTextYOffset: number; |
||||
getLabelDisplayValueFromDatum: (datum: PieChartData) => string; |
||||
}; |
||||
|
||||
export function PieSliceLabel({ |
||||
path, |
||||
arcs, |
||||
labelTextSize, |
||||
labelTextXOffset, |
||||
labelTextYOffset, |
||||
getLabelDisplayValueFromDatum, |
||||
}: PieSliceLabelProps<PieChartData>) { |
||||
return ( |
||||
<> |
||||
{arcs.map((arc) => { |
||||
const [centroidX, centroidY] = path.centroid(arc); |
||||
const pathArc = path(arc) as string; |
||||
|
||||
return ( |
||||
<Group className={styles.group} key={`arc-${arc.data.category}`}> |
||||
<path className={styles.labelPath} d={pathArc} /> |
||||
<Text |
||||
className={styles.labelText} |
||||
x={centroidX + labelTextXOffset} |
||||
y={centroidY + labelTextYOffset} |
||||
textAnchor="middle" |
||||
fontSize={labelTextSize} |
||||
> |
||||
{`${getLabelDisplayValueFromDatum(arc.data)}`} |
||||
</Text> |
||||
</Group> |
||||
); |
||||
})} |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,60 @@ |
||||
.sections { |
||||
display: flex; |
||||
flex-direction: row; |
||||
gap: calc(15rem / 16); |
||||
} |
||||
|
||||
.sections h1 { |
||||
flex: 3; |
||||
text-align: right; |
||||
margin: 0; |
||||
} |
||||
|
||||
.separator { |
||||
flex: 1; |
||||
background-color: var(--label); |
||||
height: calc(1rem / 16); |
||||
width: 100%; |
||||
margin-top: calc(30rem / 16); |
||||
} |
||||
|
||||
.nav { |
||||
flex: 3; |
||||
display: flex; |
||||
flex-direction: column; |
||||
} |
||||
|
||||
.nav ul { |
||||
list-style-type: none; |
||||
margin: 0; |
||||
padding: 0; |
||||
} |
||||
|
||||
.nav li { |
||||
margin: calc(20rem / 16); |
||||
margin-left: 0; |
||||
} |
||||
|
||||
.nav li:first-child { |
||||
margin-top: calc(18rem / 16); |
||||
} |
||||
|
||||
.nav li .linkNumber { |
||||
color: var(--secondary-accent); |
||||
margin: 0; |
||||
display: inline; |
||||
} |
||||
|
||||
.nav li a { |
||||
font-size: calc(24rem / 16); |
||||
color: var(--primary-text); |
||||
} |
||||
|
||||
.nav li a:hover .linkName { |
||||
text-decoration: underline; |
||||
} |
||||
|
||||
.nav li .linkName { |
||||
margin: 0; |
||||
display: inline; |
||||
} |
@ -0,0 +1,45 @@ |
||||
import React from "react"; |
||||
|
||||
import styles from "./Sections.module.css"; |
||||
|
||||
interface SectionsData { |
||||
name: string; |
||||
url: string; |
||||
} |
||||
|
||||
interface SectionsProps { |
||||
/* Whether to display the "Sections" title and separator that appears on the left. */ |
||||
showHeader?: boolean; |
||||
data: SectionsData[]; |
||||
} |
||||
|
||||
export function Sections({ data, showHeader = true }: SectionsProps) { |
||||
return ( |
||||
<section className={styles.sections}> |
||||
{showHeader ? ( |
||||
<> |
||||
<h1>Sections</h1> |
||||
<div className={styles.separator} /> |
||||
</> |
||||
) : ( |
||||
"" |
||||
)} |
||||
<nav className={styles.nav}> |
||||
<ul> |
||||
{data.map((datum, index) => { |
||||
return ( |
||||
<li key={`${datum.name}-${index}`}> |
||||
<a href={datum.url}> |
||||
<span className={styles.linkNumber}> |
||||
{String(index).padStart(2, "0")}{" "} |
||||
</span> |
||||
<span className={styles.linkName}>{datum.name}</span> |
||||
</a> |
||||
</li> |
||||
); |
||||
})} |
||||
</ul> |
||||
</nav> |
||||
</section> |
||||
); |
||||
} |
@ -0,0 +1,75 @@ |
||||
.wrapper { |
||||
position: relative; |
||||
} |
||||
|
||||
.line { |
||||
position: absolute; |
||||
height: 100%; |
||||
border-radius: calc(10rem / 16); |
||||
background-color: var(--secondary-accent); |
||||
z-index: -1; |
||||
} |
||||
|
||||
.timelineSections { |
||||
width: 100%; |
||||
display: flex; |
||||
flex-direction: column; |
||||
justify-content: space-around; |
||||
gap: calc(20rem / 16); |
||||
} |
||||
|
||||
.timelineSection { |
||||
width: 100%; |
||||
display: flex; |
||||
flex-direction: row; |
||||
justify-content: center; |
||||
} |
||||
|
||||
.time { |
||||
margin: 0; |
||||
text-align: right; |
||||
font-size: calc(30rem / 16); |
||||
font-weight: 700; |
||||
color: var(--secondary-accent); |
||||
word-wrap: break-word; |
||||
} |
||||
|
||||
.circle { |
||||
background-color: var(--secondary-accent); |
||||
box-shadow: calc(0rem / 16) calc(0rem / 16) calc(30rem / 16) var(--secondary-accent); |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
} |
||||
|
||||
.innerCircle { |
||||
background-color: var(--label); |
||||
display: none; |
||||
} |
||||
|
||||
.text { |
||||
height: fit-content; |
||||
margin: 0; |
||||
padding: calc(15rem / 16); |
||||
border-radius: calc(10rem / 16); |
||||
font-size: calc(20rem / 16); |
||||
font-weight: 700; |
||||
color: var(--label); |
||||
border: calc(2rem / 16) solid var(--card-background); |
||||
background-color: var(--card-background); |
||||
word-wrap: break-word; |
||||
box-sizing: border-box; |
||||
} |
||||
|
||||
.timelineSection:hover .time { |
||||
color: var(--secondary-accent-light); |
||||
} |
||||
|
||||
.timelineSection:hover .innerCircle { |
||||
display: inline; |
||||
} |
||||
|
||||
.timelineSection:hover .text { |
||||
border: calc(2rem / 16) solid var(--secondary-accent-light); |
||||
box-shadow: calc(0rem / 16) calc(0rem / 16) calc(20rem / 16) var(--secondary-accent); |
||||
} |
@ -0,0 +1,142 @@ |
||||
import React from "react"; |
||||
|
||||
import styles from "./Timeline.module.css"; |
||||
|
||||
interface TimelineData { |
||||
time: string; |
||||
text: string; |
||||
} |
||||
|
||||
interface TimelineProps { |
||||
data: TimelineData[]; |
||||
/** Whether the time is transformed to uppercase. */ |
||||
isTimeUppercase?: boolean; |
||||
/** Width of the middle timeline line, in pixels */ |
||||
lineWidth?: number; |
||||
/** Width of the outer circles on the timeline, in pixels. */ |
||||
outerCircleWidth?: number; |
||||
/** Width of the inner circles on the timeline, in pixels. */ |
||||
innerCircleWidth?: number; |
||||
/** Width of time label, in pixels. */ |
||||
timeWidth?: number; |
||||
/** Width of text label, in pixels. */ |
||||
textWidth?: number; |
||||
/** Distance between the time label AND the text label to middle line, in pixels. */ |
||||
gap?: number; |
||||
className?: string; |
||||
} |
||||
|
||||
export function Timeline({ |
||||
data, |
||||
isTimeUppercase = true, |
||||
lineWidth = 5, |
||||
outerCircleWidth = 30, |
||||
innerCircleWidth = 15, |
||||
timeWidth = 200, |
||||
textWidth = 300, |
||||
gap = 50, |
||||
className, |
||||
}: TimelineProps) { |
||||
const largerMiddleElement = |
||||
outerCircleWidth > lineWidth ? outerCircleWidth : lineWidth; |
||||
const width = timeWidth + gap + largerMiddleElement + gap + textWidth; |
||||
if (innerCircleWidth > outerCircleWidth) { |
||||
throw new Error( |
||||
`<Timeline /> - innerCircleWidth (${innerCircleWidth}) is larger than outerCircleWidth (${outerCircleWidth})` |
||||
); |
||||
} |
||||
|
||||
return ( |
||||
<div |
||||
className={ |
||||
className ? `${className} ${styles.wrapper}` : `${styles.wrapper}` |
||||
} |
||||
style={{ width: width }} |
||||
> |
||||
<div |
||||
className={styles.line} |
||||
style={{ |
||||
width: lineWidth, |
||||
left: width / 2 - lineWidth / 2, |
||||
}} |
||||
/> |
||||
<div className={styles.timelineSections}> |
||||
{data.map((datum) => ( |
||||
<TimelineSection |
||||
key={datum.time} |
||||
datum={datum} |
||||
width={width} |
||||
isTimeUppercase={isTimeUppercase} |
||||
outerCircleWidth={outerCircleWidth} |
||||
innerCircleWidth={innerCircleWidth} |
||||
timeWidth={timeWidth} |
||||
textWidth={textWidth} |
||||
gap={gap} |
||||
/> |
||||
))} |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
interface TimelineSectionProps { |
||||
datum: TimelineData; |
||||
width: number; |
||||
isTimeUppercase: boolean; |
||||
outerCircleWidth: number; |
||||
innerCircleWidth: number; |
||||
timeWidth: number; |
||||
textWidth: number; |
||||
gap: number; |
||||
} |
||||
|
||||
function TimelineSection({ |
||||
datum, |
||||
width, |
||||
isTimeUppercase, |
||||
outerCircleWidth, |
||||
innerCircleWidth, |
||||
timeWidth, |
||||
textWidth, |
||||
gap, |
||||
}: TimelineSectionProps) { |
||||
return ( |
||||
<div className={styles.timelineSection} style={{ gap: gap }}> |
||||
<div |
||||
className={styles.time} |
||||
style={{ |
||||
width: timeWidth, |
||||
marginLeft: (width - 2 * gap - outerCircleWidth) / 2 - timeWidth, |
||||
}} |
||||
> |
||||
{isTimeUppercase ? datum.time.toUpperCase() : datum.time} |
||||
</div> |
||||
<div |
||||
className={styles.circle} |
||||
style={{ |
||||
width: outerCircleWidth, |
||||
height: outerCircleWidth, |
||||
borderRadius: outerCircleWidth, |
||||
}} |
||||
> |
||||
<div |
||||
className={styles.innerCircle} |
||||
style={{ |
||||
width: innerCircleWidth, |
||||
height: innerCircleWidth, |
||||
borderRadius: innerCircleWidth, |
||||
}} |
||||
/> |
||||
</div> |
||||
<div |
||||
className={styles.text} |
||||
style={{ |
||||
width: textWidth, |
||||
marginRight: (width - 2 * gap - outerCircleWidth) / 2 - textWidth, |
||||
}} |
||||
> |
||||
{datum.text} |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
@ -0,0 +1,46 @@ |
||||
export const sectionsData = [ |
||||
{ |
||||
name: "Demographics", |
||||
url: "/", |
||||
}, |
||||
{ |
||||
name: "Academics", |
||||
url: "/", |
||||
}, |
||||
{ |
||||
name: "Co-op", |
||||
url: "/", |
||||
}, |
||||
{ |
||||
name: "Lifestyle and Interests", |
||||
url: "/", |
||||
}, |
||||
{ |
||||
name: "Intimacy and Drugs", |
||||
url: "/", |
||||
}, |
||||
{ |
||||
name: "Post-grad", |
||||
url: "/", |
||||
}, |
||||
{ |
||||
name: "Friends", |
||||
url: "/", |
||||
}, |
||||
{ |
||||
name: "Miscellaneous", |
||||
url: "/", |
||||
}, |
||||
{ |
||||
name: "Mental Health", |
||||
url: "/", |
||||
}, |
||||
{ |
||||
name: "Personal", |
||||
url: "/", |
||||
}, |
||||
{ |
||||
name: "Contributors", |
||||
url: "/", |
||||
}, |
||||
]; |
Loading…
Reference in new issue