From 9cd5c158e7a3d75222490d07728e7348f97032a4 Mon Sep 17 00:00:00 2001 From: Shahan Neda Date: Fri, 2 Sep 2022 17:39:46 -0400 Subject: [PATCH 1/9] Sample page and graph wrappers (#32) --- .vscode/settings.json | 3 +- components/ComponentWrapper.module.css | 59 ++++++++++++ components/ComponentWrapper.tsx | 42 ++++++++ components/WordCloud.tsx | 10 +- package.json | 2 +- pages/_app.css | 10 +- pages/_app.tsx | 13 ++- pages/index.tsx | 2 + pages/samplePage.module.css | 5 + pages/samplePage.tsx | 127 +++++++++++++++++++++++++ utils/getWindowDimensions.ts | 35 +++++++ utils/isMobile.ts | 3 + 12 files changed, 303 insertions(+), 8 deletions(-) create mode 100644 components/ComponentWrapper.module.css create mode 100644 components/ComponentWrapper.tsx create mode 100644 pages/samplePage.module.css create mode 100644 pages/samplePage.tsx create mode 100644 utils/getWindowDimensions.ts create mode 100644 utils/isMobile.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 99e58e0..eb98407 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,12 +2,13 @@ "typescript.tsdk": "node_modules/typescript/lib", "eslint.format.enable": true, "eslint.codeActionsOnSave.mode": "all", + "css.lint.validProperties": ["composes"], "css.format.spaceAroundSelectorSeparator": true, "[css]": { "editor.suggest.insertMode": "replace", "gitlens.codeLens.scopes": ["document"], "editor.formatOnSave": true, - "editor.defaultFormatter": "vscode.css-language-features" + "editor.defaultFormatter": "vscode.css-language-features", }, "[javascript]": { "editor.formatOnSave": false, diff --git a/components/ComponentWrapper.module.css b/components/ComponentWrapper.module.css new file mode 100644 index 0000000..4fad333 --- /dev/null +++ b/components/ComponentWrapper.module.css @@ -0,0 +1,59 @@ +.sideWrapperCommon { + background-color: var(--secondary-background); + display: flex; + padding: calc(40rem / 16) calc(50rem / 16); + margin: calc(65rem / 16) 0; + width: 90%; +} + +.wrapperRight { + composes: sideWrapperCommon; + align-self: end; + margin-right: 0; + padding-right: 0; + border-radius: calc(200rem / 16) 0 0 calc(200rem / 16); + flex-direction: row-reverse; + padding-right: calc(50rem / 16); +} + +.wrapperLeft { + composes: sideWrapperCommon; + align-self: start; + margin-left: 0; + padding-left: 0; + border-radius: 0 calc(200rem / 16) calc(200rem / 16) 0; + flex-direction: row; + padding-left: calc(50rem / 16); +} + +.noBackground { + background: none; + align-self: center; +} + +.wrapperCenter { + flex-direction: column; + text-align: center; + gap: calc(25rem / 16); + /* to match the 65px margin with the left/right variant: + add 45px bottom margin, since internal wrapper contributes 20px for the center component + 0px top margin, since h3 contributes 45px and internal wrapper contributes 20px for the center component + */ + margin: 0 0 calc(45rem / 16) 0; + padding: 0 15%; +} + +@media screen and (max-width: 768px) { + .sideWrapperCommon { + margin: auto; + flex-direction: column; + text-align: center; + padding: 0; + border-radius: 0; + width: 100%; + } +} + +.internalWrapper { + padding: calc(20rem / 16); +} \ No newline at end of file diff --git a/components/ComponentWrapper.tsx b/components/ComponentWrapper.tsx new file mode 100644 index 0000000..8893905 --- /dev/null +++ b/components/ComponentWrapper.tsx @@ -0,0 +1,42 @@ +import React from "react"; + +import styles from "./ComponentWrapper.module.css"; + +type AlignOption = "left" | "center" | "right"; + +type ComponentWrapperProps = { + children: React.ReactNode; + heading: string; + bodyText: string; + align?: AlignOption; + noBackground?: boolean; +}; + +export function ComponentWrapper({ + heading, + bodyText, + children, + align = "left", + noBackground = false, +}: ComponentWrapperProps) { + const alignClasses: { [key in AlignOption]: string } = { + left: styles.wrapperLeft, + center: styles.wrapperCenter, + right: styles.wrapperRight, + }; + + return ( +
+
+

{heading}

+

{bodyText}

+
+
{children}
+
+ ); +} diff --git a/components/WordCloud.tsx b/components/WordCloud.tsx index 9591dac..ca295e5 100644 --- a/components/WordCloud.tsx +++ b/components/WordCloud.tsx @@ -197,10 +197,12 @@ const shouldNotRerender = ( nextProps: WordCloudWordsProps ) => { if ( - prevProps.tooltipLeft !== nextProps.tooltipLeft || - prevProps.tooltipTop !== nextProps.tooltipTop || - nextProps.tooltipLeft === undefined || - nextProps.tooltipTop === undefined + // if width changes, rerender, else don't rerender for a tooltip change + prevProps.width === nextProps.width && + (prevProps.tooltipLeft !== nextProps.tooltipLeft || + prevProps.tooltipTop !== nextProps.tooltipTop || + nextProps.tooltipLeft === undefined || + nextProps.tooltipTop === undefined) ) { return true; // do not re-render } diff --git a/package.json b/package.json index 6358840..814c0d7 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,9 @@ "@visx/group": "^2.10.0", "@visx/scale": "^2.2.2", "@visx/shape": "^2.10.0", + "@visx/text": "^2.10.0", "@visx/tooltip": "^2.10.0", "@visx/wordcloud": "^2.10.0", - "@visx/text": "^2.10.0", "next": "12.1.6", "react": "18.1.0", "react-dom": "18.1.0" diff --git a/pages/_app.css b/pages/_app.css index 0c34108..30aea2c 100644 --- a/pages/_app.css +++ b/pages/_app.css @@ -73,7 +73,11 @@ h2 { color: var(--primary-heading); } -h3, +h3 { + color: var(--secondary-heading); + font-size: calc(45rem / 16); +} + h4, h5, h6 { @@ -89,6 +93,10 @@ a:hover { color: var(--link-hover); } +p { + font-size: calc(28rem / 16); +} + @media only screen and (prefers-color-scheme: dark) { body { --primary-background: var(--dark--primary-background); diff --git a/pages/_app.tsx b/pages/_app.tsx index 7803bd0..52d7142 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,9 +1,20 @@ import type { AppProps } from "next/app"; +import Head from "next/head"; import React from "react"; import "./_app.css"; import "./font.css"; export default function App({ Component, pageProps }: AppProps): JSX.Element { - return ; + return ( + <> + + + + + + ); } diff --git a/pages/index.tsx b/pages/index.tsx index 4ff1fb7..58dda94 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -5,6 +5,8 @@ export default function Home() { return (

Click here to visit the playground +
+ Click here to visit a sample page

); } diff --git a/pages/samplePage.module.css b/pages/samplePage.module.css new file mode 100644 index 0000000..b50d07b --- /dev/null +++ b/pages/samplePage.module.css @@ -0,0 +1,5 @@ +.page { + display: flex; + flex-direction: column; + justify-content: center; +} \ No newline at end of file diff --git a/pages/samplePage.tsx b/pages/samplePage.tsx new file mode 100644 index 0000000..f067888 --- /dev/null +++ b/pages/samplePage.tsx @@ -0,0 +1,127 @@ +import { BarGraphHorizontal, BarGraphVertical } from "components/BarGraph"; +import { mockCategoricalData, moreMockCategoricalData } from "data/mocks"; +import React from "react"; +import { useWindowDimensions } from "utils/getWindowDimensions"; +import { useIsMobile } from "utils/isMobile"; + +import { ComponentWrapper } from "@/components/ComponentWrapper"; + +import { WordCloud } from "../components/WordCloud"; + +import styles from "./samplePage.module.css"; + +export default function SamplePage() { + const { width } = useWindowDimensions(); + const isMobile = useIsMobile(); + + return ( +
+ + + + + + ({ + text: word.key, + value: word.value, + }))} + // For components that we don't want to match the width necessarily we can provide direct values + width={isMobile ? width / 1.5 : 800} + height={500} + /> + + + + + + + + + + + + + + + + ({ + text: word.key, + value: word.value, + }))} + width={isMobile ? width / 1.5 : width / 2} + height={500} + /> + +
+ ); +} diff --git a/utils/getWindowDimensions.ts b/utils/getWindowDimensions.ts new file mode 100644 index 0000000..427568b --- /dev/null +++ b/utils/getWindowDimensions.ts @@ -0,0 +1,35 @@ +// Attribution: https://stackoverflow.com/questions/36862334/get-viewport-window-height-in-reactjs +import { useState, useEffect } from "react"; + +type WindowDimensions = { + width: number; + height: number; +}; + +const getWindowDimensions = (): WindowDimensions => { + const { innerWidth, innerHeight } = window; + + return { + width: innerWidth, + height: innerHeight, + }; +}; + +export const useWindowDimensions = (): WindowDimensions => { + const [windowDimensions, setWindowDimensions] = useState({ + width: 0, + height: 0, + }); + + const handleResize = () => { + setWindowDimensions(getWindowDimensions()); + }; + + useEffect(() => { + handleResize(); + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + }, []); + + return windowDimensions; +}; diff --git a/utils/isMobile.ts b/utils/isMobile.ts new file mode 100644 index 0000000..4022df1 --- /dev/null +++ b/utils/isMobile.ts @@ -0,0 +1,3 @@ +import { useWindowDimensions } from "./getWindowDimensions"; + +export const useIsMobile = () => useWindowDimensions().width <= 768; From a2dbcb90c6b7400eff033cb0975e724ec6b353c6 Mon Sep 17 00:00:00 2001 From: Amy Date: Fri, 2 Sep 2022 21:53:05 -0400 Subject: [PATCH 2/9] Add Quotation Carousel (#36) Made without visx, because that was easier. Closes #10. Co-authored-by: Amy Wang Reviewed-on: https://git.csclub.uwaterloo.ca/www/cs-2022-class-profile/pulls/36 Reviewed-by: j285he --- components/QuotationCarousel.module.css | 124 ++++++++++++++++++++++ components/QuotationCarousel.tsx | 130 ++++++++++++++++++++++++ data/mocks.ts | 12 +++ pages/_app.css | 5 +- pages/playground.module.css | 8 ++ pages/playground.tsx | 22 +++- 6 files changed, 299 insertions(+), 2 deletions(-) create mode 100644 components/QuotationCarousel.module.css create mode 100644 components/QuotationCarousel.tsx diff --git a/components/QuotationCarousel.module.css b/components/QuotationCarousel.module.css new file mode 100644 index 0000000..40241da --- /dev/null +++ b/components/QuotationCarousel.module.css @@ -0,0 +1,124 @@ +.carousel { + position: relative; + display: flex; + justify-content: space-between; + align-items: center; + gap: calc(8rem / 16); +} + +.circle { + position: absolute; + top: 30%; + right: 52%; + z-index: -1; + + background-color: var(--tertiary-background); + clip-path: circle(); +} + +.right.circle { + top: unset; + right: unset; + bottom: 30%; + left: 52%; +} + +.carouselButton { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + padding: calc(16rem / 16); + height: min-content; + + background: none; + border: none; + + cursor: pointer; +} + +.arrow { + position: relative; + width: calc(20rem / 16); + height: calc(40rem / 16); + + transition: 0.2s; +} + +.previous.arrow { + transform: rotate(180deg); +} + +.carouselButton:hover > .arrow { + translate: calc(4rem / 16); +} + +.carouselButton:hover > .previous.arrow { + translate: calc(-4rem / 16); +} + +.card { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: stretch; + gap: calc(16rem / 16); + + min-height: inherit; + height: 100%; + width: 100%; + padding: calc(30rem / 16); + + background-color: var(--translucent-accent); + border: calc(2rem / 16) solid var(--primary-text); + border-radius: calc(12rem / 16); + box-shadow: 0 calc(1rem / 16) calc(10rem / 16) var(--primary-accent); +} + +.card ul { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + position: relative; + width: 100%; + margin: 0; + padding: 0; + flex-grow: 1; +} + +.card li { + position: absolute; + left: 0; + right: 0; + margin: 0; + padding: 0; + list-style: none; + + visibility: visible; + opacity: 1; + transition: 0.1s; +} + +.card li.hidden { + visibility: hidden; + opacity: 0; +} + +.card p { + margin: 0 calc(16rem / 16); + font-weight: bold; + text-align: center; +} + +.quotationMark { + width: calc(20rem / 16); + height: calc(20rem / 16); +} + +.right.quotationMark { + transform: rotate(180deg); + align-self: end; +} diff --git a/components/QuotationCarousel.tsx b/components/QuotationCarousel.tsx new file mode 100644 index 0000000..fed81a7 --- /dev/null +++ b/components/QuotationCarousel.tsx @@ -0,0 +1,130 @@ +import React, { useState } from "react"; +import { Color } from "utils/Color"; + +import styles from "./QuotationCarousel.module.css"; + +interface QuotationCarouselProps { + data: string[]; + /** Width of the entire carousel including the buttons, in px. */ + width?: number; + /** Minimum height of the carousel, in px. */ + height?: number; + /** Diameter of the background circles, in px. Set to 0 for no circles. */ + circleDiameter?: number; + className?: string; +} + +interface CarouselButtonProps { + onClick: () => void; + isPrevious?: boolean; +} + +export function QuotationCarousel(props: QuotationCarouselProps) { + const { + data, + width = 600, + height = 100, + circleDiameter = 120, + className, + } = props; + + const [activeIdx, setActiveIdx] = useState(0); + + function showNextCard() { + setActiveIdx((activeIdx + 1) % data.length); + } + + function showPreviousCard() { + setActiveIdx((activeIdx - 1 + data.length) % data.length); + } + + return ( +
+ + + +
+ +
    + {data.map((quote, idx) => ( +
  • +

    {quote}

    +
  • + ))} +
+ +
+ +
+ ); +} + +function Circle({ + className, + diameter, +}: { + className: string; + diameter: number; +}) { + return ( +
+ ); +} + +function CarouselButton({ isPrevious, onClick }: CarouselButtonProps) { + return ( + + ); +} + +function QuotationMark({ className }: { className: string }) { + return ( + + + + ); +} diff --git a/data/mocks.ts b/data/mocks.ts index 162f5aa..3631cd6 100644 --- a/data/mocks.ts +++ b/data/mocks.ts @@ -64,3 +64,15 @@ export const moreMockCategoricalData = [ { key: "Ada", value: 2.21 }, { key: "Dart", value: 2.21 }, ]; + +export const mockQuoteData = [ + "The quick brown fox jumps over the lazy dog.", + "Sphinx of black quartz, judge my vow!", + "Pack my box with five dozen liquor jugs.", +]; + +export const mockQuoteDataLong = [ + "Here, have some quotes of varying lengths, and see how they look.", + "Hello, world!", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla in enim neque. Sed sit amet convallis tellus. Integer condimentum a felis id gravida. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nullam metus libero, sagittis in consectetur in, scelerisque sed sapien. Nullam ut feugiat sapien. Praesent dictum ac ipsum ac lacinia.", +]; diff --git a/pages/_app.css b/pages/_app.css index 30aea2c..8c5d52f 100644 --- a/pages/_app.css +++ b/pages/_app.css @@ -20,6 +20,7 @@ body { --light--primary-accent-light: var(--orange); --light--primary-accent-lighter: #FFBC9F; --light--secondary-accent: #E55F98; + --light--translucent-accent: rgba(255, 231, 231, 0.75); --light--secondary-accent-light: #FEA0C8; --light--primary-heading: #D02B53; --light--primary-text: #483B35; @@ -37,6 +38,7 @@ body { --dark--primary-accent-lighter: var(--lighter-pink); --dark--secondary-accent: var(--orange); --dark--secondary-accent-light: var(--light-orange); + --dark--translucent-accent: rgba(239, 131, 157, 0.75); --dark--primary-heading: #FFC48D; --dark--secondary-heading: var(--pink); --dark--link: var(--pink); @@ -54,6 +56,7 @@ body { --primary-accent-lighter: var(--dark--primary-accent-lighter); --secondary-accent: var(--dark--secondary-accent); --secondary-accent-light: var(--dark--secondary-accent-light); + --translucent-accent: var(--dark--translucent-accent); --primary-heading: var(--dark--primary-heading); --secondary-heading: var(--dark--secondary-heading); --link: var(--dark--link); @@ -117,4 +120,4 @@ p { --card-background: var(--dark--card-background); --label: var(--dark--label); } -} \ No newline at end of file +} diff --git a/pages/playground.module.css b/pages/playground.module.css index a7aefd8..97f5eac 100644 --- a/pages/playground.module.css +++ b/pages/playground.module.css @@ -5,3 +5,11 @@ .barGraphDemo { border: calc(1rem / 16) solid black; } + +.quotationCarouselDemo { + display: flex; + flex-direction: column; + align-items: center; + gap: calc(48rem / 16); + margin: calc(32rem / 16); +} diff --git a/pages/playground.tsx b/pages/playground.tsx index 5707edb..1ff00bb 100644 --- a/pages/playground.tsx +++ b/pages/playground.tsx @@ -1,7 +1,14 @@ import { BarGraphHorizontal, BarGraphVertical } from "components/BarGraph"; -import { mockCategoricalData, moreMockCategoricalData } from "data/mocks"; +import { + mockCategoricalData, + moreMockCategoricalData, + mockQuoteData, + mockQuoteDataLong, +} from "data/mocks"; import React from "react"; +import { QuotationCarousel } from "@/components/QuotationCarousel"; + import { ColorPalette } from "../components/ColorPalette"; import { WordCloud } from "../components/WordCloud"; @@ -59,6 +66,19 @@ export default function Home() { value: word.value, }))} /> + +

+ {""} +

+
+ + +
); } From 9526f1b0f5bdf5646096de52183789a9c7ab3f7c Mon Sep 17 00:00:00 2001 From: Emily Chiu Date: Sat, 3 Sep 2022 11:13:58 -0400 Subject: [PATCH 3/9] Add BoxPlot component (Closes #6) (#34) Done: - [x] Display boxplot with left and bottom axis in `playground.tsx` - [x] Add mock data for boxplot - [x] Hovering over boxplot displays a Tool Tip box. My version: ![image](/attachments/6c8c4499-a1bd-4434-9230-7117266691ea) Note: - No percentage displayed next to value axis labels. - We can add outlier points although it could complicate the graph. - Still displays the following error: - Error: Hydration failed because the initial UI does not match what was rendered on the server. - Error: Text content does not match server-rendered HTML. - Error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering. Staging: https://boxplot-component-csc-class-profile-staging-snedadah.k8s.csclub.cloud/ Co-authored-by: Miniapple8888 Co-authored-by: Emily Chiu Co-authored-by: e26chiu Reviewed-on: https://git.csclub.uwaterloo.ca/www/cs-2022-class-profile/pulls/34 Reviewed-by: Amy --- components/Boxplot.module.css | 38 ++++ components/Boxplot.tsx | 361 ++++++++++++++++++++++++++++++++++ data/mocks.ts | 30 +++ package-lock.json | 72 +++++++ package.json | 2 + pages/playground.tsx | 17 +- 6 files changed, 519 insertions(+), 1 deletion(-) create mode 100644 components/Boxplot.module.css create mode 100644 components/Boxplot.tsx diff --git a/components/Boxplot.module.css b/components/Boxplot.module.css new file mode 100644 index 0000000..f776cf6 --- /dev/null +++ b/components/Boxplot.module.css @@ -0,0 +1,38 @@ +.boxplot { + fill: var(--primary-accent-light); +} + +.boxplot:hover { + fill: var(--primary-accent); + filter: drop-shadow(0 0 calc(4rem / 16) var(--primary-accent)); +} + +.tooltip { + font-family: "Inconsolata", monospace; + top: 0; + left: 0; + position: absolute; + background-color: var(--label); + color: var(--primary-background); + pointer-events: none; + padding: calc(10rem / 16); + border-radius: calc(10rem / 16); +} + +.tooltip .category { + margin: calc(10rem / 16) 0 0 0; + font-size: calc(16rem / 16); + font-weight: 700; +} + +.tooltip .toolTipData { + margin-top: calc(5rem / 16); + margin-bottom: calc(10rem / 16); + font-size: calc(16rem / 16); +} + +.tooltip .toolTipData p { + margin: 0; + padding: 0; + font-size: calc(16rem / 16); +} diff --git a/components/Boxplot.tsx b/components/Boxplot.tsx new file mode 100644 index 0000000..924bc65 --- /dev/null +++ b/components/Boxplot.tsx @@ -0,0 +1,361 @@ +import { AxisLeft, AxisBottom } from "@visx/axis"; +import { GridRows, GridColumns } from "@visx/grid"; +import { Group } from "@visx/group"; +import { Stats } from "@visx/mock-data/lib/generators/genStats"; +import { Point } from "@visx/point"; +import { scaleBand, scaleLinear } from "@visx/scale"; +import { Line } from "@visx/shape"; +import { BoxPlot as VisxBoxPlot } from "@visx/stats"; +import { withTooltip, Tooltip } from "@visx/tooltip"; +import { WithTooltipProvidedProps } from "@visx/tooltip/lib/enhancers/withTooltip"; +import React from "react"; +import { Color } from "utils/Color"; + +import styles from "./Boxplot.module.css"; + +const DEFAULT_LABEL_SIZE = 16; +const TICK_LABEL_FONT_WEIGHT = 800; + +interface BoxPlotData { + category: string; + min: number; + median: number; + max: number; + firstQuartile: number; + thirdQuartile: number; + outliers?: number[]; +} + +type TooltipData = Omit; + +export type StatsPlotProps = { + data: BoxPlotData[]; + /** Width of the entire graph, in pixels, greater than 10. */ + width: number; + /** Height of the entire graph, in pixels. */ + height: number; + /** Distance between the edge of the graph and the area where the bars are drawn, in pixels. */ + margin: { + top: number; + left: number; + }; + /** Width of the lines in the graph, in px. */ + strokeWidth?: number; + /** Length of the dashes and the gaps in the graph, in px. */ + strokeDashArray?: string; + /** Number of ticks for the value (y-)axis */ + numTicksLeftAxis?: number; + /** Distance between the boxplot and the top of the grid, in px. */ + plotTopOffset?: number; + /** Distance between the left axis labels and the start of the lines of the graph, in px. */ + valueAxisLeftOffset?: number; + /** Distance between the top and the first label of the y axis, in px. */ + valueAxisLabelTopOffset?: number; + /** Distance between the left and the labels of the y axis, in px. */ + valueAxisLabelLeftOffset?: number; + /** Distance between the left and the start of the first label of the x axis, in px. */ + categoryAxisLabelLeftOffset?: number; + /** Distance between the top and the column lines of the grid of the graph, in px. */ + gridColumnTopOffset?: number; + /** Distance between the top of the point in the boxplot and the start of the tooltip box, in px. */ + toolTipTopOffset?: number; + /** Distance between the left of the point in the boxplot and the start of the tooltip box, in px. */ + toolTipLeftOffset?: number; + /** Font size of the category (x-)axis labels */ + categoryAxisLabelSize?: number; + /** Font size of the value (y-)axis labels */ + valueAxisLabelSize?: number; + /** Font size of the text in the tool tip box */ + toolTipFontSize?: number; + /** Factor multiplied with the compressed width to determine the box width, in px. */ + boxPlotWidthFactor?: number; + /** Factor multiplied with the compressed width to determine the distance between boxes, in px. */ + boxPlotLeftOffset?: number; +}; + +export const BoxPlot = withTooltip( + ({ + width, + height, + data, + margin, + tooltipOpen, + tooltipLeft, + tooltipTop, + tooltipData, + showTooltip, + hideTooltip, + strokeWidth = 2.5, + strokeDashArray = "10,4", + numTicksLeftAxis = 6, + plotTopOffset = 10, + valueAxisLeftOffset = 40, + gridColumnTopOffset = -20, + valueAxisLabelTopOffset = 5, + valueAxisLabelLeftOffset = 10, + categoryAxisLabelLeftOffset = 30, + toolTipTopOffset = 20, + toolTipLeftOffset = 5, + categoryAxisLabelSize = DEFAULT_LABEL_SIZE, + valueAxisLabelSize = DEFAULT_LABEL_SIZE, + boxPlotWidthFactor = 0.4, + boxPlotLeftOffset = 0.3, + }: StatsPlotProps & WithTooltipProvidedProps) => { + // bounds + const xMax = width; + const yMax = height - 120; + // formatting data + const plotData: Stats[] = data.map((d) => { + return { + boxPlot: { + ...d, + x: d.category, + outliers: [], + }, + binData: [], + }; + }); + + // accessors + const getX = (d: Stats) => d.boxPlot.x; + const getMin = (d: Stats) => d.boxPlot.min; + const getMax = (d: Stats) => d.boxPlot.max; + const getMedian = (d: Stats) => d.boxPlot.median; + const getFirstQuartile = (d: Stats) => d.boxPlot.firstQuartile; + const getThirdQuartile = (d: Stats) => d.boxPlot.thirdQuartile; + + // scales + const xScale = scaleBand({ + range: [18, xMax - 80], // scaling is needed due to the left offset + round: true, + domain: plotData.map(getX), + padding: 0.3, + }); + + const values = plotData.reduce((allValues, { boxPlot }) => { + allValues.push(boxPlot.min, boxPlot.max); + return allValues; + }, [] as number[]); + const minYValue = Math.min(...values); + const maxYValue = Math.max(...values); + + const yScale = scaleLinear({ + range: [yMax, 0], + round: true, + domain: [minYValue, maxYValue], + }); + + const constrainedWidth = Math.min(200, xScale.bandwidth()); + + return width < 10 ? null : ( +
+ + + + + + + { + return { + fill: Color.label, + fontWeight: TICK_LABEL_FONT_WEIGHT, + }; + }} + /> + { + return { + fill: Color.label, + fontWeight: TICK_LABEL_FONT_WEIGHT, + }; + }} + /> + + {plotData.map((d: Stats, i) => ( + + { + showTooltip({ + tooltipTop: + (yScale(getMin(d)) ?? 0) + toolTipTopOffset, + tooltipLeft: + xScale(getX(d))! + + constrainedWidth + + toolTipLeftOffset, + tooltipData: { + ...d.boxPlot, + category: getX(d), + }, + }); + }, + onMouseLeave: () => { + hideTooltip(); + }, + }} + maxProps={{ + onMouseOver: () => { + showTooltip({ + tooltipTop: + (yScale(getMax(d)) ?? 0) + toolTipTopOffset, + tooltipLeft: + xScale(getX(d))! + + constrainedWidth + + toolTipLeftOffset, + tooltipData: { + ...d.boxPlot, + category: getX(d), + }, + }); + }, + onMouseLeave: () => { + hideTooltip(); + }, + }} + boxProps={{ + onMouseOver: () => { + showTooltip({ + tooltipTop: + (yScale(getMedian(d)) ?? 0) + toolTipTopOffset, + tooltipLeft: + xScale(getX(d))! + + constrainedWidth + + toolTipLeftOffset, + tooltipData: { + ...d.boxPlot, + category: getX(d), + }, + }); + }, + strokeWidth: 0, + onMouseLeave: () => { + hideTooltip(); + }, + }} + medianProps={{ + style: { + stroke: Color.label, + }, + onMouseOver: () => { + showTooltip({ + tooltipTop: + (yScale(getMedian(d)) ?? 0) + toolTipTopOffset, + tooltipLeft: + xScale(getX(d))! + + constrainedWidth + + toolTipLeftOffset, + tooltipData: { + ...d.boxPlot, + category: getX(d), + }, + }); + }, + onMouseLeave: () => { + hideTooltip(); + }, + }} + /> + + ))} + + + + + {tooltipOpen && tooltipData && ( + +

{tooltipData.category}

+
+

max: {tooltipData.max}

+

third quartile: {tooltipData.thirdQuartile}

+

median: {tooltipData.median}

+

first quartile: {tooltipData.firstQuartile}

+

min: {tooltipData.min}

+
+
+ )} +
+ ); + } +); diff --git a/data/mocks.ts b/data/mocks.ts index 3631cd6..8a35df7 100644 --- a/data/mocks.ts +++ b/data/mocks.ts @@ -65,6 +65,36 @@ export const moreMockCategoricalData = [ { key: "Dart", value: 2.21 }, ]; +export const mockBoxPlotData = [ + { + category: "1A", + min: 20, + firstQuartile: 25, + median: 30, + thirdQuartile: 80, + max: 100, + outliers: [], + }, + { + category: "1B", + min: 0, + firstQuartile: 20, + median: 30, + thirdQuartile: 50, + max: 100, + outliers: [], + }, + { + category: "2A", + min: 25, + firstQuartile: 35, + median: 50, + thirdQuartile: 90, + max: 100, + outliers: [], + }, +]; + export const mockQuoteData = [ "The quick brown fox jumps over the lazy dog.", "Sphinx of black quartz, judge my vow!", diff --git a/package-lock.json b/package-lock.json index 7f2a1f1..f5a74dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,8 +12,10 @@ "@visx/event": "^2.6.0", "@visx/grid": "^2.10.0", "@visx/group": "^2.10.0", + "@visx/mock-data": "^2.1.2", "@visx/scale": "^2.2.2", "@visx/shape": "^2.10.0", + "@visx/stats": "^2.10.0", "@visx/text": "^2.10.0", "@visx/tooltip": "^2.10.0", "@visx/wordcloud": "^2.10.0", @@ -608,6 +610,11 @@ "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-1.0.9.tgz", "integrity": "sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ==" }, + "node_modules/@types/d3-random": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-2.2.1.tgz", + "integrity": "sha512-5vvxn6//poNeOxt1ZwC7QU//dG9QqABjy1T7fP/xmFHY95GnaOw3yABf29hiu5SR1Oo34XcpyHFbzod+vemQjA==" + }, "node_modules/@types/d3-scale": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-3.3.2.tgz", @@ -967,6 +974,15 @@ "react": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0" } }, + "node_modules/@visx/mock-data": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@visx/mock-data/-/mock-data-2.1.2.tgz", + "integrity": "sha512-6xUVP56tiPwVi3BxvoXPQzDYWG6iX2nnOlsHEYsHgK8gHq1r7AhjQtdbQUX7QF0QkmkJM0cW8TBjZ2e+dItB8Q==", + "dependencies": { + "@types/d3-random": "^2.2.0", + "d3-random": "^2.2.2" + } + }, "node_modules/@visx/point": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/@visx/point/-/point-2.6.0.tgz", @@ -1007,6 +1023,23 @@ "react": "^16.3.0-0 || ^17.0.0-0 || ^18.0.0-0" } }, + "node_modules/@visx/stats": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@visx/stats/-/stats-2.10.0.tgz", + "integrity": "sha512-4p8rQamOc1IC3IkqTHgfMHbSXvRl9DMWFCglJy+DmbH6Wx1TaWt2nj/N0Ttp350UTRzBy4o5ou/D4Gts8LZHuA==", + "dependencies": { + "@types/d3-shape": "^1.3.2", + "@types/react": "*", + "@visx/group": "2.10.0", + "@visx/scale": "2.2.2", + "classnames": "^2.3.1", + "d3-shape": "^1.2.0", + "prop-types": "^15.5.10" + }, + "peerDependencies": { + "react": "^16.3.0-0 || ^17.0.0-0 || ^18.0.0-0" + } + }, "node_modules/@visx/text": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/@visx/text/-/text-2.10.0.tgz", @@ -1392,6 +1425,7 @@ "version": "3.22.7", "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.22.7.tgz", "integrity": "sha512-wTriFxiZI+C8msGeh7fJcbC/a0V8fdInN1oS2eK79DMBGs8iIJiXhtFJCiT3rBa8w6zroHWW3p8ArlujZ/Mz+w==", + "deprecated": "core-js-pure@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js-pure.", "dev": true, "hasInstallScript": true, "funding": { @@ -1535,6 +1569,11 @@ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" }, + "node_modules/d3-random": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-2.2.2.tgz", + "integrity": "sha512-0D9P8TRj6qDAtHhRQn6EfdOtHMfsUWanl3yb/84C4DqpZ+VsgfI5iTVRNRbELCfNvRfpMr8OrqqUTQ6ANGCijw==" + }, "node_modules/d3-scale": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", @@ -4790,6 +4829,11 @@ "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-1.0.9.tgz", "integrity": "sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ==" }, + "@types/d3-random": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-2.2.1.tgz", + "integrity": "sha512-5vvxn6//poNeOxt1ZwC7QU//dG9QqABjy1T7fP/xmFHY95GnaOw3yABf29hiu5SR1Oo34XcpyHFbzod+vemQjA==" + }, "@types/d3-scale": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-3.3.2.tgz", @@ -5043,6 +5087,15 @@ "prop-types": "^15.6.2" } }, + "@visx/mock-data": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@visx/mock-data/-/mock-data-2.1.2.tgz", + "integrity": "sha512-6xUVP56tiPwVi3BxvoXPQzDYWG6iX2nnOlsHEYsHgK8gHq1r7AhjQtdbQUX7QF0QkmkJM0cW8TBjZ2e+dItB8Q==", + "requires": { + "@types/d3-random": "^2.2.0", + "d3-random": "^2.2.2" + } + }, "@visx/point": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/@visx/point/-/point-2.6.0.tgz", @@ -5080,6 +5133,20 @@ "prop-types": "^15.5.10" } }, + "@visx/stats": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@visx/stats/-/stats-2.10.0.tgz", + "integrity": "sha512-4p8rQamOc1IC3IkqTHgfMHbSXvRl9DMWFCglJy+DmbH6Wx1TaWt2nj/N0Ttp350UTRzBy4o5ou/D4Gts8LZHuA==", + "requires": { + "@types/d3-shape": "^1.3.2", + "@types/react": "*", + "@visx/group": "2.10.0", + "@visx/scale": "2.2.2", + "classnames": "^2.3.1", + "d3-shape": "^1.2.0", + "prop-types": "^15.5.10" + } + }, "@visx/text": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/@visx/text/-/text-2.10.0.tgz", @@ -5443,6 +5510,11 @@ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" }, + "d3-random": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-2.2.2.tgz", + "integrity": "sha512-0D9P8TRj6qDAtHhRQn6EfdOtHMfsUWanl3yb/84C4DqpZ+VsgfI5iTVRNRbELCfNvRfpMr8OrqqUTQ6ANGCijw==" + }, "d3-scale": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", diff --git a/package.json b/package.json index 814c0d7..f59b11a 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,10 @@ "@visx/event": "^2.6.0", "@visx/grid": "^2.10.0", "@visx/group": "^2.10.0", + "@visx/mock-data": "^2.1.2", "@visx/scale": "^2.2.2", "@visx/shape": "^2.10.0", + "@visx/stats": "^2.10.0", "@visx/text": "^2.10.0", "@visx/tooltip": "^2.10.0", "@visx/wordcloud": "^2.10.0", diff --git a/pages/playground.tsx b/pages/playground.tsx index 1ff00bb..5a9fd37 100644 --- a/pages/playground.tsx +++ b/pages/playground.tsx @@ -1,7 +1,9 @@ import { BarGraphHorizontal, BarGraphVertical } from "components/BarGraph"; +import { BoxPlot } from "components/Boxplot"; import { mockCategoricalData, moreMockCategoricalData, + mockBoxPlotData, mockQuoteData, mockQuoteDataLong, } from "data/mocks"; @@ -30,7 +32,7 @@ export default function Home() { width={800} height={500} margin={{ - top: 20, + top: 25, bottom: 40, left: 150, right: 20, @@ -67,6 +69,19 @@ export default function Home() { }))} /> +

+ {""} +

+ +

{""}

From 9200e5f491da3cc6537ec09baaa8ccb83242d340 Mon Sep 17 00:00:00 2001 From: j285he Date: Wed, 7 Sep 2022 22:20:30 -0400 Subject: [PATCH 4/9] Pie chart component (#19) Closes #5. To fix: - [x] Inner pie slice text not perfectly centered - [x] Create optional props for `padRadius` and `innerRadius` - [x] Change colors to global colors and merge from main to get fonts Also, outer labels can get cut off if they are long enough, but the `labelWidth` is provided as a prop for the user to adjust. Staging url: https://j285he-pie-chart-csc-class-profile-staging-snedadah.k8s.csclub.cloud Co-authored-by: Jared He <66887902+jaredjhe@users.noreply.github.com> Reviewed-on: https://git.csclub.uwaterloo.ca/www/cs-2022-class-profile/pulls/19 Reviewed-by: Amy --- components/PieChart.module.css | 26 +++++ components/PieChart.tsx | 180 +++++++++++++++++++++++++++++++++ data/mocks.ts | 15 +++ package-lock.json | 1 - pages/playground.tsx | 11 +- 5 files changed, 229 insertions(+), 4 deletions(-) create mode 100644 components/PieChart.module.css create mode 100644 components/PieChart.tsx diff --git a/components/PieChart.module.css b/components/PieChart.module.css new file mode 100644 index 0000000..29bb6dc --- /dev/null +++ b/components/PieChart.module.css @@ -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; +} \ No newline at end of file diff --git a/components/PieChart.tsx b/components/PieChart.tsx new file mode 100644 index 0000000..536ccfc --- /dev/null +++ b/components/PieChart.tsx @@ -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 ( + + + d.value} + cornerRadius={10} + padAngle={0.075} + padRadius={padRadius} + innerRadius={innerRadius} + outerRadius={pieWidth} + > + {(pie) => ( + + )} + + d.value} + innerRadius={pieWidth} + outerRadius={width * 0.5} + > + {(pie) => ( + + )} + + + + ); +} + +type PieSliceProps = ProvidedProps & { + pieTextSize: number; + pieTextXOffset: number; + pieTextYOffset: number; + getPieDisplayValueFromDatum: (datum: PieChartData) => string; +}; + +export function PieSlice({ + path, + arcs, + pieTextSize, + pieTextXOffset, + pieTextYOffset, + getPieDisplayValueFromDatum, +}: PieSliceProps) { + return ( + <> + {arcs.map((arc) => { + const [centroidX, centroidY] = path.centroid(arc); + const pathArc = path(arc) as string; + + return ( + + + + {`${getPieDisplayValueFromDatum(arc.data)}`} + + + ); + })} + + ); +} + +type PieSliceLabelProps = ProvidedProps & { + labelTextSize: number; + labelTextXOffset: number; + labelTextYOffset: number; + getLabelDisplayValueFromDatum: (datum: PieChartData) => string; +}; + +export function PieSliceLabel({ + path, + arcs, + labelTextSize, + labelTextXOffset, + labelTextYOffset, + getLabelDisplayValueFromDatum, +}: PieSliceLabelProps) { + return ( + <> + {arcs.map((arc) => { + const [centroidX, centroidY] = path.centroid(arc); + const pathArc = path(arc) as string; + + return ( + + + + {`${getLabelDisplayValueFromDatum(arc.data)}`} + + + ); + })} + + ); +} diff --git a/data/mocks.ts b/data/mocks.ts index 8a35df7..7d0252b 100644 --- a/data/mocks.ts +++ b/data/mocks.ts @@ -36,6 +36,21 @@ export const mockCategoricalData = [ }, ]; +export const mockPieData = [ + { + category: "Nightingale", + value: 42, + }, + { + category: "Quail", + value: 48, + }, + { + category: "Cuckoo", + value: 10, + }, +]; + export const moreMockCategoricalData = [ { key: "Python", value: 29.53 }, { key: "Java", value: 17.06 }, diff --git a/package-lock.json b/package-lock.json index f5a74dc..1e58472 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,6 @@ "requires": true, "packages": { "": { - "name": "cs-2022-class-profile", "version": "0.1.0", "dependencies": { "@visx/axis": "^2.10.0", diff --git a/pages/playground.tsx b/pages/playground.tsx index 5a9fd37..426da06 100644 --- a/pages/playground.tsx +++ b/pages/playground.tsx @@ -6,9 +6,11 @@ import { mockBoxPlotData, mockQuoteData, mockQuoteDataLong, + mockPieData, } from "data/mocks"; import React from "react"; +import { PieChart } from "@/components/PieChart"; import { QuotationCarousel } from "@/components/QuotationCarousel"; import { ColorPalette } from "../components/ColorPalette"; @@ -21,8 +23,13 @@ export default function Home() {

Playground

Show off your components here!

+

+ {""} +

+
+ +
-

{""}

@@ -38,7 +45,6 @@ export default function Home() { right: 20, }} /> -

{""}

@@ -58,7 +64,6 @@ export default function Home() { right: 20, }} /> -

{""}

From e2d19a281a7649597b6940cfd1f60c3d213ba3d0 Mon Sep 17 00:00:00 2001 From: j285he Date: Fri, 9 Sep 2022 17:01:34 -0400 Subject: [PATCH 5/9] Add Textbox Component (#45) Closes #39. Padding will look correct when !46 is merged in. https://j285he-textbox-component-csc-class-profile-staging-snedadah.k8s.csclub.cloud Co-authored-by: Jared He <66887902+jaredjhe@users.noreply.github.com> Reviewed-on: https://git.csclub.uwaterloo.ca/www/cs-2022-class-profile/pulls/45 Reviewed-by: Amy Reviewed-by: Shahan Neda --- components/CenterWrapper.module.css | 7 +++++++ components/CenterWrapper.tsx | 11 ++++++++++ pages/playground.tsx | 32 +++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 components/CenterWrapper.module.css create mode 100644 components/CenterWrapper.tsx diff --git a/components/CenterWrapper.module.css b/components/CenterWrapper.module.css new file mode 100644 index 0000000..6370ab3 --- /dev/null +++ b/components/CenterWrapper.module.css @@ -0,0 +1,7 @@ +.textbox { + width: 80%; + padding: calc(80rem / 16); + background-color: var(--secondary-background); + border-radius: calc(20rem / 16); + margin: 0 auto; +} diff --git a/components/CenterWrapper.tsx b/components/CenterWrapper.tsx new file mode 100644 index 0000000..377bb14 --- /dev/null +++ b/components/CenterWrapper.tsx @@ -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
{children}
; +} diff --git a/pages/playground.tsx b/pages/playground.tsx index 426da06..d7320bb 100644 --- a/pages/playground.tsx +++ b/pages/playground.tsx @@ -13,6 +13,7 @@ import React from "react"; import { PieChart } from "@/components/PieChart"; import { QuotationCarousel } from "@/components/QuotationCarousel"; +import { CenterWrapper } from "../components/CenterWrapper"; import { ColorPalette } from "../components/ColorPalette"; import { WordCloud } from "../components/WordCloud"; @@ -73,6 +74,37 @@ export default function Home() { value: word.value, }))} /> +

+ {""} +

+ +

Preface

+

+ The CS Class Profile consists of data relevant to CS, CFM, and CS/BBA + students. These were combined with the knowledge that students in + these programs tend to have similar experiences, as many of the same + CS required courses are shared. In the standard co-op offering, CS and + CFM take 4 years and 2 semesters to complete, while CS/BBA can take up + to a full 5 years. +

+

+ Computer Science (and the others) is known to be a very prestigious + program, and is very well known in Canada as well as across the world. + For prospective students or anyone who is interested in learning more + about what the students are like, this CS Class Profile will attempt + to answer some of your questions, and you may even learn a thing or + two you didn’t expect! +

+

+ The survey questions were approved by the Institutional Analysis & + Planning, where all University of Waterloo stakeholders that are + interested in conducting a non-academic research survey involving a + large portion of the UWaterloo population are reviewed and approved. + The entirety of the survey creation and data processing was done by + the UW Computer Science Club, so please check us out if you enjoy what + you see! +

+

{""} From 3cb5780964cc0f3821847ad34c8e7330684854cd Mon Sep 17 00:00:00 2001 From: j285he Date: Mon, 12 Sep 2022 10:08:01 -0400 Subject: [PATCH 6/9] Standardize font sizes (#46) Closes #33. There were many more font sizes in the Figma, but as a best design practice, I chose the four most common. Staging: https://j285he-standardize-font-sizes-csc-class-prof-snedadah.k8s.csclub.cloud/ Co-authored-by: Jared He <66887902+jaredjhe@users.noreply.github.com> Co-authored-by: shahanneda Reviewed-on: https://git.csclub.uwaterloo.ca/www/cs-2022-class-profile/pulls/46 Reviewed-by: Amy --- pages/_app.css | 44 +++++++++++++++++++++++++++++++++++--------- pages/playground.tsx | 10 ++++++++++ 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/pages/_app.css b/pages/_app.css index 8c5d52f..70294e9 100644 --- a/pages/_app.css +++ b/pages/_app.css @@ -69,22 +69,52 @@ body { color: var(--primary-text); font-family: "Inconsolata", monospace; margin: 0; + + /* Font styling for body */ + font-size: calc(18rem / 16); + font-weight: 500; } -h1, +/* Page titles (e.g. Demographics) */ +h1 { + font-size: calc(48rem / 16); + font-weight: 700; + color: var(--primary-accent-light); + margin-top: calc(32rem / 16); + margin-bottom: calc(16rem / 16); +} + +/* Major section headings (e.g. About the Programs) */ h2 { + font-size: calc(36rem / 16); + font-weight: 700; color: var(--primary-heading); + margin-top: calc(32rem / 16); + margin-bottom: calc(16rem / 16); } +/* Minor section headings & questions (e.g. What Program Are You In?) */ h3 { + font-size: calc(32rem / 16); + font-weight: 700; color: var(--secondary-heading); - font-size: calc(45rem / 16); + margin-top: calc(24rem / 16); + margin-bottom: calc(16rem / 16); } -h4, -h5, -h6 { +/* Titles within paragraphs (e.g. About the Programs -> Computer Science)*/ +h4 { + font-size: calc(24rem / 16); + font-weight: 700; color: var(--secondary-heading); + margin-top: calc(24rem / 16); + margin-bottom: calc(8rem / 16); +} + +p { + color: var(--primary-text); + margin-top: 1rem; + margin-bottom: 1rem; } a { @@ -96,10 +126,6 @@ a:hover { color: var(--link-hover); } -p { - font-size: calc(28rem / 16); -} - @media only screen and (prefers-color-scheme: dark) { body { --primary-background: var(--dark--primary-background); diff --git a/pages/playground.tsx b/pages/playground.tsx index d7320bb..459f5f8 100644 --- a/pages/playground.tsx +++ b/pages/playground.tsx @@ -31,6 +31,16 @@ export default function Home() {

+

+ Text Styles +

+

h1 h1 h1 h1 h1 h1 h1 h1 h1 h1 h1 h1 h1 h1 h1 h1 h1 h1

+

h2 h2 h2 h2 h2 h2 h2 h2 h2 h2 h2 h2 h2 h2 h2 h2 h2 h2

+

h3 h3 h3 h3 h3 h3 h3 h3 h3 h3 h3 h3 h3 h3 h3 h3 h3 h3

+

h4 h4 h4 h4 h4 h4 h4 h4 h4 h4 h4 h4 h4 h4 h4 h4 h4 h4

+

p p p p p p p p p p p p p p p p p p p p p p p p p p p p

+ a a a a a a a a a a a a a a a a a a a a a a a a a a a a +

{""}

From fc5600cb20a79ee076a96a4b392aa49e1aa3e72b Mon Sep 17 00:00:00 2001 From: j285he Date: Mon, 12 Sep 2022 10:20:10 -0400 Subject: [PATCH 7/9] Add About Component (#48) Closes #40. Will look correct when !46 is merged in. https://j285he-about-csc-class-profile-staging-snedadah.k8s.csclub.cloud/ Co-authored-by: Jared He <66887902+jaredjhe@users.noreply.github.com> Reviewed-on: https://git.csclub.uwaterloo.ca/www/cs-2022-class-profile/pulls/48 Reviewed-by: Amy --- components/About.module.css | 59 +++++++++++++++++++++++++++++++++ components/About.tsx | 65 +++++++++++++++++++++++++++++++++++++ pages/_app.css | 1 - pages/playground.tsx | 6 ++++ 4 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 components/About.module.css create mode 100644 components/About.tsx diff --git a/components/About.module.css b/components/About.module.css new file mode 100644 index 0000000..3a82b92 --- /dev/null +++ b/components/About.module.css @@ -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; +} diff --git a/components/About.tsx b/components/About.tsx new file mode 100644 index 0000000..d176012 --- /dev/null +++ b/components/About.tsx @@ -0,0 +1,65 @@ +import React from "react"; + +import styles from "./About.module.css"; + +export default function About() { + return ( +
+ +
+ +
+

Computer Science

+

+ 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. +

+

Computing and Financial Management

+

+ 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. +

+

CS/BBA

+

+ 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. +

+
+
+ +
+ ); +} + +interface AngleDecorationProps { + isBottom: boolean; +} + +function AngleDecoration({ isBottom }: AngleDecorationProps) { + return ( + + + + ); +} diff --git a/pages/_app.css b/pages/_app.css index 70294e9..af6ace7 100644 --- a/pages/_app.css +++ b/pages/_app.css @@ -24,7 +24,6 @@ body { --light--secondary-accent-light: #FEA0C8; --light--primary-heading: #D02B53; --light--primary-text: #483B35; - --light--secondary-text: var(--pink); --light--link: var(--orange); --light--link-hover: #FFBC9F; --light--card-background: #FFFFFF; diff --git a/pages/playground.tsx b/pages/playground.tsx index 459f5f8..ae354f4 100644 --- a/pages/playground.tsx +++ b/pages/playground.tsx @@ -10,6 +10,7 @@ import { } from "data/mocks"; import React from "react"; +import About from "@/components/About"; import { PieChart } from "@/components/PieChart"; import { QuotationCarousel } from "@/components/QuotationCarousel"; @@ -116,6 +117,11 @@ export default function Home() {

+

+ {""} + +

+

{""}

From e3948c057753de03a7eadfa6929b89de80ee92b8 Mon Sep 17 00:00:00 2001 From: j285he Date: Mon, 12 Sep 2022 20:00:01 -0400 Subject: [PATCH 8/9] Add Timeline Component (#35) Closes #7 Staging: https://j285he-timeline-csc-class-profile-staging-snedadah.k8s.csclub.cloud/playground/ Co-authored-by: Jared He <66887902+jaredjhe@users.noreply.github.com> Reviewed-on: https://git.csclub.uwaterloo.ca/www/cs-2022-class-profile/pulls/35 Reviewed-by: Amy --- components/Timeline.module.css | 75 +++++++++++++++++ components/Timeline.tsx | 142 +++++++++++++++++++++++++++++++++ data/mocks.ts | 27 +++++++ pages/playground.tsx | 14 +++- 4 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 components/Timeline.module.css create mode 100644 components/Timeline.tsx diff --git a/components/Timeline.module.css b/components/Timeline.module.css new file mode 100644 index 0000000..6e46450 --- /dev/null +++ b/components/Timeline.module.css @@ -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); +} \ No newline at end of file diff --git a/components/Timeline.tsx b/components/Timeline.tsx new file mode 100644 index 0000000..643e835 --- /dev/null +++ b/components/Timeline.tsx @@ -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( + ` - innerCircleWidth (${innerCircleWidth}) is larger than outerCircleWidth (${outerCircleWidth})` + ); + } + + return ( +
+
+
+ {data.map((datum) => ( + + ))} +
+
+ ); +} + +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 ( +
+
+ {isTimeUppercase ? datum.time.toUpperCase() : datum.time} +
+
+
+
+
+ {datum.text} +
+
+ ); +} diff --git a/data/mocks.ts b/data/mocks.ts index 7d0252b..95c9d40 100644 --- a/data/mocks.ts +++ b/data/mocks.ts @@ -80,6 +80,33 @@ export const moreMockCategoricalData = [ { key: "Dart", value: 2.21 }, ]; +export const mockTimelineData = [ + { + time: "Fall 2020", + text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", + }, + { + time: "Winter 2021", + text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad", + }, + { + time: "Spring 2021", + text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor i", + }, + { + time: "Fall 2021", + text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proid", + }, + { + time: "Winter 2022", + text: "Lorem ipsum dolor sit amet, consectetur adipi", + }, + { + time: "Spring 2022", + text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut en", + }, +]; + export const mockBoxPlotData = [ { category: "1A", diff --git a/pages/playground.tsx b/pages/playground.tsx index ae354f4..3ddf484 100644 --- a/pages/playground.tsx +++ b/pages/playground.tsx @@ -7,12 +7,14 @@ import { mockQuoteData, mockQuoteDataLong, mockPieData, + mockTimelineData, } from "data/mocks"; import React from "react"; import About from "@/components/About"; import { PieChart } from "@/components/PieChart"; import { QuotationCarousel } from "@/components/QuotationCarousel"; +import { Timeline } from "@/components/Timeline"; import { CenterWrapper } from "../components/CenterWrapper"; import { ColorPalette } from "../components/ColorPalette"; @@ -25,13 +27,15 @@ export default function Home() {

Playground

Show off your components here!

+ +

{""}

- +

Text Styles

@@ -57,6 +61,7 @@ export default function Home() { right: 20, }} /> +

{""}

@@ -76,6 +81,7 @@ export default function Home() { right: 20, }} /> +

{""}

@@ -85,6 +91,12 @@ export default function Home() { value: word.value, }))} /> + +

+ {""} +

+ +

{""}

From 8253f6cbab930c9005bff5f499c0e14fa8713eaa Mon Sep 17 00:00:00 2001 From: j285he Date: Mon, 12 Sep 2022 20:07:03 -0400 Subject: [PATCH 9/9] Add Sections Component (#49) Closes #43. Will look better when !46 is merged in. https://j285he-sections-csc-class-profile-staging-snedadah.k8s.csclub.cloud/playground/ Co-authored-by: Jared He <66887902+jaredjhe@users.noreply.github.com> Reviewed-on: https://git.csclub.uwaterloo.ca/www/cs-2022-class-profile/pulls/49 Reviewed-by: Amy --- components/About.tsx | 2 +- components/Sections.module.css | 60 ++++++++++++++++++++++++++++++++++ components/Sections.tsx | 45 +++++++++++++++++++++++++ data/routes.ts | 46 ++++++++++++++++++++++++++ pages/playground.tsx | 28 ++++++++++------ 5 files changed, 170 insertions(+), 11 deletions(-) create mode 100644 components/Sections.module.css create mode 100644 components/Sections.tsx create mode 100644 data/routes.ts diff --git a/components/About.tsx b/components/About.tsx index d176012..f409610 100644 --- a/components/About.tsx +++ b/components/About.tsx @@ -2,7 +2,7 @@ import React from "react"; import styles from "./About.module.css"; -export default function About() { +export function About() { return (
diff --git a/components/Sections.module.css b/components/Sections.module.css new file mode 100644 index 0000000..575dc97 --- /dev/null +++ b/components/Sections.module.css @@ -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; +} \ No newline at end of file diff --git a/components/Sections.tsx b/components/Sections.tsx new file mode 100644 index 0000000..226d383 --- /dev/null +++ b/components/Sections.tsx @@ -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 ( +
+ {showHeader ? ( + <> +

Sections

+
+ + ) : ( + "" + )} + +
+ ); +} diff --git a/data/routes.ts b/data/routes.ts new file mode 100644 index 0000000..ddf9393 --- /dev/null +++ b/data/routes.ts @@ -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: "/", + }, +]; diff --git a/pages/playground.tsx b/pages/playground.tsx index 3ddf484..18e20b2 100644 --- a/pages/playground.tsx +++ b/pages/playground.tsx @@ -9,11 +9,13 @@ import { mockPieData, mockTimelineData, } from "data/mocks"; +import { sectionsData } from "data/routes"; import React from "react"; -import About from "@/components/About"; +import { About } from "@/components/About"; import { PieChart } from "@/components/PieChart"; import { QuotationCarousel } from "@/components/QuotationCarousel"; +import { Sections } from "@/components/Sections"; import { Timeline } from "@/components/Timeline"; import { CenterWrapper } from "../components/CenterWrapper"; @@ -27,14 +29,8 @@ export default function Home() {

Playground

Show off your components here!

- -

- {""} -

-
- -
+

Text Styles @@ -46,6 +42,13 @@ export default function Home() {

p p p p p p p p p p p p p p p p p p p p p p p p p p p p

a a a a a a a a a a a a a a a a a a a a a a a a a a a a +

+ {""} +

+
+ +
+

{""}

@@ -130,9 +133,14 @@ export default function Home() {

- {""} - + {""}

+ + +

+ {""} +

+

{""}