diff --git a/components/BarGraph.tsx b/components/BarGraph.tsx index b4b0a03..6884ab3 100644 --- a/components/BarGraph.tsx +++ b/components/BarGraph.tsx @@ -43,6 +43,16 @@ interface BarGraphProps { valueAxisLabelSize?: number; /** Controls the distance between the value axis label and the value axis. */ valueAxisLabelOffset?: number; + /** Minimum width of the graph. */ + minWidth?: number; + /** Breakpoint width of graph where alernating labels are displayed. Only for Vertical graphs */ + widthAlternatingLabel?: number; + /** Space added to the bottom of the graph to show overflowing labels. Only for Vertical graphs */ + alternatingLabelSpace?: number; + /** Default position of labels in x-axis, in px. */ + defaultLabelDy?: string; + /** Position of lower labels in x-axis, in px. Only for Vertical graphs */ + lowerLabelDy?: string; } interface BarGraphData { @@ -54,11 +64,11 @@ const DEFAULT_LABEL_SIZE = 16; export function BarGraphHorizontal(props: BarGraphProps) { const { - width, height, margin, data, className, + minWidth = 500, categoryTickLabelSize = DEFAULT_LABEL_SIZE, valueTickLabelSize = DEFAULT_LABEL_SIZE, hoverLabelSize, @@ -68,8 +78,9 @@ export function BarGraphHorizontal(props: BarGraphProps) { valueAxisLabel, valueAxisLabelSize = DEFAULT_LABEL_SIZE, valueAxisLabelOffset = 0, + defaultLabelDy = "0", } = props; - + const width = props.width < minWidth ? minWidth : props.width; // Ensuring graph's width >= minWidth const barPadding = 0.4; const categoryMax = height - margin.top - margin.bottom; @@ -163,8 +174,6 @@ export function BarGraphHorizontal(props: BarGraphProps) { return { ...leftTickLabelProps(), className: styles.tickLabel, - dx: "-0.5rem", - dy: "0.25rem", fontSize: `${categoryTickLabelSize / 16}rem`, }; }} @@ -186,7 +195,7 @@ export function BarGraphHorizontal(props: BarGraphProps) { return { ...bottomTickLabelProps(), className: styles.tickLabel, - dy: "0.25rem", + dy: defaultLabelDy, fontSize: `${valueTickLabelSize / 16}rem`, }; }} @@ -203,11 +212,11 @@ export function BarGraphHorizontal(props: BarGraphProps) { export function BarGraphVertical(props: BarGraphProps) { const { - width, height, margin, data, className, + minWidth = 500, categoryTickLabelSize = DEFAULT_LABEL_SIZE, valueTickLabelSize = DEFAULT_LABEL_SIZE, hoverLabelSize, @@ -217,12 +226,20 @@ export function BarGraphVertical(props: BarGraphProps) { valueAxisLabel, valueAxisLabelSize = DEFAULT_LABEL_SIZE, valueAxisLabelOffset = 0, + widthAlternatingLabel = 600, + alternatingLabelSpace = 80, + defaultLabelDy = `0px`, + lowerLabelDy = `30px`, } = props; - + const width = props.width < minWidth ? minWidth : props.width; // Ensuring graph's width >= minWidth const barPadding = 0.4; + const alternatingLabel = width <= widthAlternatingLabel; + const final_margin_bottom = alternatingLabel + ? margin.bottom + alternatingLabelSpace + : margin.bottom; const categoryMax = width - margin.left - margin.right; - const valueMax = height - margin.top - margin.bottom; + const valueMax = height - margin.top - final_margin_bottom; const getCategory = (d: BarGraphData) => d.category; const getValue = (d: BarGraphData) => d.value; @@ -308,11 +325,12 @@ export function BarGraphVertical(props: BarGraphProps) { left={margin.left} hideAxisLine hideTicks - tickLabelProps={() => { + tickLabelProps={(value, index) => { + const alternatingDy = index % 2 == 0 ? defaultLabelDy : lowerLabelDy; return { ...bottomTickLabelProps(), className: styles.tickLabel, - dy: "-0.25rem", + dy: alternatingLabel ? alternatingDy : defaultLabelDy, fontSize: `${categoryTickLabelSize / 16}rem`, width: categoryScale.bandwidth(), verticalAnchor: "start", diff --git a/components/Boxplot.module.css b/components/Boxplot.module.css index f776cf6..66dda09 100644 --- a/components/Boxplot.module.css +++ b/components/Boxplot.module.css @@ -5,34 +5,4 @@ .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); -} +} \ No newline at end of file diff --git a/components/Boxplot.tsx b/components/Boxplot.tsx index 924bc65..f413b2b 100644 --- a/components/Boxplot.tsx +++ b/components/Boxplot.tsx @@ -6,11 +6,13 @@ 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 { withTooltip } from "@visx/tooltip"; import { WithTooltipProvidedProps } from "@visx/tooltip/lib/enhancers/withTooltip"; import React from "react"; import { Color } from "utils/Color"; +import { TooltipWrapper } from "./TooltipWrapper"; + import styles from "./Boxplot.module.css"; const DEFAULT_LABEL_SIZE = 16; @@ -339,21 +341,17 @@ export const BoxPlot = withTooltip( {tooltipOpen && tooltipData && ( - -

{tooltipData.category}

-
-

max: {tooltipData.max}

-

third quartile: {tooltipData.thirdQuartile}

-

median: {tooltipData.median}

-

first quartile: {tooltipData.firstQuartile}

-

min: {tooltipData.min}

-
-
+

max: {tooltipData.max}

+

third quartile: {tooltipData.thirdQuartile}

+

median: {tooltipData.median}

+

first quartile: {tooltipData.firstQuartile}

+

min: {tooltipData.min}

+ )} ); diff --git a/components/ComponentWrapper.module.css b/components/ComponentWrapper.module.css index 8cbdc3b..25f8ae3 100644 --- a/components/ComponentWrapper.module.css +++ b/components/ComponentWrapper.module.css @@ -45,7 +45,10 @@ .wrapperNoBodyText { flex-direction: column; - align-items: center; +} + +.wrapperNoBodyText .internalWrapper { + text-align: center; } @media screen and (max-width: 900px) { @@ -61,8 +64,18 @@ .wrapperCenter { padding: 0; } + + .horizontalScrollOnMobile { + overflow: scroll; + } } .internalWrapper { padding: calc(20rem / 16); +} + +.internalWrapper p { + font-size: calc(24rem / 16); + opacity: .85; + line-height: 1.25; } \ No newline at end of file diff --git a/components/ComponentWrapper.tsx b/components/ComponentWrapper.tsx index 400b0c5..1d98bee 100644 --- a/components/ComponentWrapper.tsx +++ b/components/ComponentWrapper.tsx @@ -37,7 +37,11 @@ export function ComponentWrapper({

{heading}

{bodyText ?

{bodyText}

: null} -
{children}
+
+ {children} +
); } diff --git a/components/LineGraph.module.css b/components/LineGraph.module.css index bb8cdfe..8fedff0 100644 --- a/components/LineGraph.module.css +++ b/components/LineGraph.module.css @@ -8,20 +8,6 @@ filter: drop-shadow(0 0 calc(4rem / 16) var(--primary-accent)); } -.tooltip { - font-family: "Inconsolata", monospace; - font-weight: bold; - top: 0; - left: 0; - position: absolute; - background-color: var(--label); - color: var(--primary-background); - box-shadow: 0px calc(1rem / 16) calc(2rem / 16) var(--card-background); - pointer-events: none; - padding: calc(10rem / 16); - font-size: calc(18rem / 16); - border-radius: calc(10rem / 16); -} .wrapper { display: flex; diff --git a/components/LineGraph.tsx b/components/LineGraph.tsx index 7e98241..878a948 100644 --- a/components/LineGraph.tsx +++ b/components/LineGraph.tsx @@ -8,10 +8,12 @@ import { LegendOrdinal } from "@visx/legend"; import { Point } from "@visx/point"; import { scaleBand, scaleLinear, scaleOrdinal } from "@visx/scale"; import { LinePath } from "@visx/shape"; -import { useTooltip, useTooltipInPortal } from "@visx/tooltip"; +import { withTooltip } from "@visx/tooltip"; import React from "react"; import { Color } from "utils/Color"; +import { TooltipWrapper } from "./TooltipWrapper"; + import styles from "./LineGraph.module.css"; interface LineData { @@ -85,8 +87,11 @@ const DEFAULT_LEGEND_GAP = 16; // TODO: Address unused props in this file /* eslint-disable unused-imports/no-unused-vars*/ -export function LineGraph(props: LineGraphProps) { - const { + +type TooltipData = string; + +export const LineGraph = withTooltip( + ({ width, height, margin, @@ -101,211 +106,199 @@ export function LineGraph(props: LineGraphProps) { yAxisLabel, yAxisLabelSize = DEFAULT_LABEL_SIZE, yAxisLabelOffset = 0, - legendProps, - } = props; - - const { - position: legendPosition = "right", - itemLabelSize: legendLabelSize = DEFAULT_LABEL_SIZE, - itemGap: legendItemGap = DEFAULT_LEGEND_GAP, - margin: legendMargin = {}, - } = legendProps ?? {}; - - const xLength = data.xValues.length; - - data.lines.forEach((line) => { - if (line.yValues.length != xLength) { - throw new Error("Invalid data with wrong length."); - } - }); - - const { - tooltipData, + tooltipOpen, tooltipLeft, tooltipTop, - tooltipOpen, - showTooltip, + tooltipData, hideTooltip, - } = useTooltip(); + showTooltip, + legendProps, + }) => { + const { + position: legendPosition = "right", + itemLabelSize: legendLabelSize = DEFAULT_LABEL_SIZE, + itemGap: legendItemGap = DEFAULT_LEGEND_GAP, + margin: legendMargin = {}, + } = legendProps ?? {}; - const { containerRef, TooltipInPortal } = useTooltipInPortal({ - // use TooltipWithBounds - detectBounds: true, - // when tooltip containers are scrolled, this will correctly update the Tooltip position - scroll: true, - }); + const xLength = data.xValues.length; - const yMax = height - margin.top - margin.bottom; - const xMax = width - margin.left - margin.right; - - const actualData = data.lines.map((line) => { - return line.yValues.map((val, idx) => { - return { x: data.xValues[idx], y: val }; + data.lines.forEach((line) => { + if (line.yValues.length != xLength) { + throw new Error("Invalid data with wrong length."); + } }); - }); - const yMaxValue = Math.max( - ...data.lines.map((line) => { - return Math.max(...line.yValues); - }) - ); + const yMax = height - margin.top - margin.bottom; + const xMax = width - margin.left - margin.right; - // data accessors - const getX = (d: PointData) => d.x; - const getY = (d: PointData) => d.y; + const actualData = data.lines.map((line) => { + return line.yValues.map((val, idx) => { + return { x: data.xValues[idx], y: val }; + }); + }); - // scales - const xScale = scaleBand({ - range: [0, xMax], - domain: data.xValues, - }); + const yMaxValue = Math.max( + ...data.lines.map((line) => { + return Math.max(...line.yValues); + }) + ); - const yScale = scaleLinear({ - range: [0, yMax], - nice: true, - domain: [yMaxValue, 0], - }); + // data accessors + const getX = (d: PointData) => d.x; + const getY = (d: PointData) => d.y; - const keys = data.lines.map((line) => line.label); + // scales + const xScale = scaleBand({ + range: [0, xMax], + domain: data.xValues, + }); - const legendScale = scaleOrdinal({ - domain: keys, - range: [Color.primaryAccent, Color.secondaryAccent], - }); + const yScale = scaleLinear({ + range: [0, yMax], + nice: true, + domain: [yMaxValue, 0], + }); - return ( -
- - - - - { - return { - ...bottomTickLabelProps(), - className: styles.tickLabel, - dy: "-0.25rem", - fontSize: `${xTickLabelSize / 16}rem`, - width: xScale.bandwidth(), - }; - }} - /> - { - return { - ...leftTickLabelProps(), - className: styles.tickLabel, - dx: "1.25rem", - dy: "0.25rem", - fontSize: `${yTickLabelSize / 16}rem`, - }; - }} - /> - - {actualData.map((lineData, i) => { - const isEven = i % 2 === 0; - return ( - - { - const eventSvgCoords = localPoint( - // ownerSVGElement is given by visx docs but not recognized by typescript - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - e.target.ownerSVGElement as Element, - e - ) as Point; - showTooltip({ - tooltipData: data.lines[i].label, - tooltipTop: eventSvgCoords.y, - tooltipLeft: eventSvgCoords.x, - }); - }} - onMouseOut={hideTooltip} - data={lineData} - className={styles.line} - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - x={(d) => xScale(getX(d))!} - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - y={(d) => yScale(getY(d))!} - stroke={ - isEven ? Color.primaryAccent : Color.secondaryAccent - } - strokeWidth={4} - strokeOpacity={2} - /> - - ); - })} - - - - line.label); + + const legendScale = scaleOrdinal({ + domain: keys, + range: [Color.primaryAccent, Color.secondaryAccent], + }); + + return ( +
+ style={{ + flexDirection: legendPosition === "right" ? "row" : "column-reverse", + }} + > + + + + + { + return { + ...bottomTickLabelProps(), + className: styles.tickLabel, + dy: "-0.25rem", + fontSize: `${xTickLabelSize / 16}rem`, + width: xScale.bandwidth(), + }; + }} + /> + { + return { + ...leftTickLabelProps(), + className: styles.tickLabel, + dx: "1.25rem", + dy: "0.25rem", + fontSize: `${yTickLabelSize / 16}rem`, + }; + }} + /> + + {actualData.map((lineData, i) => { + const isEven = i % 2 === 0; + return ( + + { + const eventSvgCoords = localPoint( + // ownerSVGElement is given by visx docs but not recognized by typescript + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + e.target.ownerSVGElement as Element, + e + ) as Point; + showTooltip({ + tooltipData: data.lines[i].label, + tooltipTop: eventSvgCoords.y, + tooltipLeft: eventSvgCoords.x, + }); + }} + onMouseOut={hideTooltip} + data={lineData} + className={styles.line} + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + x={(d) => xScale(getX(d))!} + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + y={(d) => yScale(getY(d))!} + stroke={ + isEven ? Color.primaryAccent : Color.secondaryAccent + } + strokeWidth={4} + strokeOpacity={2} + /> + + ); + })} + + + + - {tooltipOpen && ( - - <>{tooltipData} - - )} -
- ); -} + {tooltipOpen && ( + + )} +
+ ); + } +); diff --git a/components/SectionHeader.module.css b/components/SectionHeader.module.css index a9faa52..6da437d 100644 --- a/components/SectionHeader.module.css +++ b/components/SectionHeader.module.css @@ -10,10 +10,23 @@ color: var(--primary-accent-light); font-size: calc(70rem / 16); margin: calc(40rem / 16) auto; + word-break: break-all; } .subTitle { color: var(--primary-accent-lighter); font-size: calc(26rem / 16); margin: auto; +} + +@media screen and (max-width: 900px) { + .title { + font-size: calc(50rem / 16); + margin: calc(20rem / 16) auto; + } + + .subTitle { + font-size: calc(30rem / 16); + margin: auto calc(15rem / 16); + } } \ No newline at end of file diff --git a/components/StackedBarGraph.module.css b/components/StackedBarGraph.module.css index ffe660f..4c25e0e 100644 --- a/components/StackedBarGraph.module.css +++ b/components/StackedBarGraph.module.css @@ -13,23 +13,6 @@ top: 0; } -.toolTip { - font-family: "Inconsolata", monospace; - top: 0; - left: 0; - position: absolute; - background-color: var(--label); - color: var(--primary-background); - pointer-events: none; - border-radius: calc(10rem / 16); - padding: calc(10rem / 16); -} - -.toolTip p { - margin: 0 calc(5rem / 16); - font-size: calc(16rem / 16); -} - .key { font-weight: bold; } \ No newline at end of file diff --git a/components/StackedBarGraph.tsx b/components/StackedBarGraph.tsx index 7c195e0..9b0bd44 100644 --- a/components/StackedBarGraph.tsx +++ b/components/StackedBarGraph.tsx @@ -7,11 +7,13 @@ import { Point } from "@visx/point"; import { scaleBand, scaleLinear, scaleOrdinal } from "@visx/scale"; import { BarStack, BarStackHorizontal, Line } from "@visx/shape"; import { SeriesPoint } from "@visx/shape/lib/types"; -import { withTooltip, Tooltip } from "@visx/tooltip"; +import { withTooltip } from "@visx/tooltip"; import { WithTooltipProvidedProps } from "@visx/tooltip/lib/enhancers/withTooltip"; import React from "react"; import { Color } from "utils/Color"; +import { TooltipWrapper } from "./TooltipWrapper"; + import styles from "./StackedBarGraph.module.css"; interface StackedBarData { @@ -248,16 +250,14 @@ export const StackedBarGraphVertical = withTooltip< {tooltipOpen && tooltipData ? ( - -

{tooltipData.key}

{tooltipData.bar.data[tooltipData.key]}

{getCategory(tooltipData.bar.data)}

-
+ ) : null} ); @@ -438,16 +438,14 @@ export const StackedBarGraphHorizontal = withTooltip< {tooltipOpen && tooltipData ? ( - -

{tooltipData.key}

{tooltipData.bar.data[tooltipData.key]}

{getCategory(tooltipData.bar.data)}

-
+ ) : null} ); diff --git a/components/TooltipWrapper.module.css b/components/TooltipWrapper.module.css new file mode 100644 index 0000000..c6c0ed5 --- /dev/null +++ b/components/TooltipWrapper.module.css @@ -0,0 +1,31 @@ +.tooltip { + font-family: "Inconsolata", monospace; + top: 0; + left: 0; + position: absolute; + background-color: var(--label); + pointer-events: none; + padding: calc(10rem / 16); + border-radius: calc(10rem / 16); + font-size: calc(18rem / 16); +} + +.header { + color: var(--primary-background); + margin: 0; + font-size: calc(16rem / 16); + font-weight: 700; +} + +.body { + color: var(--primary-background); + margin-top: calc(5rem / 16); + font-size: calc(16rem / 16); +} + +.body p { + color: var(--primary-background); + margin: 0; + padding: 0; + font-size: calc(16rem / 16) !important; +} \ No newline at end of file diff --git a/components/TooltipWrapper.tsx b/components/TooltipWrapper.tsx new file mode 100644 index 0000000..d260852 --- /dev/null +++ b/components/TooltipWrapper.tsx @@ -0,0 +1,35 @@ +import { Tooltip } from "@visx/tooltip"; +import React from "react"; + +import styles from "./TooltipWrapper.module.css"; + +type TooltipWrapperProps = { + top?: number; + left?: number; + className?: string; + header?: string; + children?: React.ReactNode; +}; + +const TooltipWrapper = ({ + top, + left, + className, + header, + children, +}: TooltipWrapperProps) => { + return ( + + {header ? {header} : null} + {children ?
{children}
: null} +
+ ); +}; + +export { TooltipWrapper }; diff --git a/components/WordCloud.module.css b/components/WordCloud.module.css index 728094a..a6059ae 100644 --- a/components/WordCloud.module.css +++ b/components/WordCloud.module.css @@ -2,19 +2,4 @@ text-shadow: var(--primary-accent) 0 0 calc(20rem / 16); text-anchor: "middle"; cursor: default; -} - -.tooltip { - font-family: "Inconsolata", monospace; - font-weight: bold; - top: 0; - left: 0; - position: absolute; - background-color: var(--label); - color: var(--primary-background); - box-shadow: 0px calc(1rem / 16) calc(2rem / 16) var(--card-background); - pointer-events: none; - padding: calc(10rem / 16); - font-size: calc(18rem / 16); - border-radius: calc(10rem / 16); } \ No newline at end of file diff --git a/components/WordCloud.tsx b/components/WordCloud.tsx index ca295e5..fb09684 100644 --- a/components/WordCloud.tsx +++ b/components/WordCloud.tsx @@ -2,10 +2,14 @@ import { localPoint } from "@visx/event"; import { Point } from "@visx/point"; import { scaleLog } from "@visx/scale"; import { Text } from "@visx/text"; -import { TooltipWithBounds, useTooltip, withTooltip } from "@visx/tooltip"; +import { useTooltip, withTooltip } from "@visx/tooltip"; import { Wordcloud as VisxWordcloud } from "@visx/wordcloud"; import React from "react"; import { Color } from "utils/Color"; +import { inDevEnvironment } from "utils/inDevEnviroment"; +import { useIsMobile } from "utils/isMobile"; + +import { TooltipWrapper } from "./TooltipWrapper"; import styles from "./WordCloud.module.css"; @@ -13,16 +17,22 @@ interface WordCloudProps { data: Array; /** Width of the graph, in px */ width?: number; + /** The minimum width of the graph */ + minWidth?: number; /** Height of the graph, in px */ height?: number; /** Minimum padding between words, in px */ wordPadding?: number; /** Weight of the font of the words */ fontWeight?: number; - /** The desired font size of the smallest word, in px.*/ - minFontSize?: number; - /** The desired font size of the largest word, in px. */ - maxFontSize?: number; + /** The desired font size of the smallest word on desktop, in px.*/ + desktopMinFontSize?: number; + /** The desired font size of the smallest word on mobile, in px.*/ + mobileMinFontSize?: number; + /** The desired font size of the largest word on desktop, in px. */ + desktopMaxFontSize?: number; + /** The desired font size of the largest word on mobile, in px. */ + mobileMaxFontSize?: number; /** A random seed in the range [0, 1) used for placing the words, change this value to get an alternate placement of words */ randomSeed?: number; /** Type of spiral used for rendering the words, either rectangular or archimedean */ @@ -46,11 +56,14 @@ export const WordCloud = withTooltip( height, wordPadding, fontWeight, - minFontSize, - maxFontSize, + desktopMinFontSize, + mobileMinFontSize, + desktopMaxFontSize, + mobileMaxFontSize, randomSeed, spiral, className, + minWidth, }: WordCloudProps) => { const { tooltipData, @@ -69,8 +82,10 @@ export const WordCloud = withTooltip( data={data} wordPadding={wordPadding} fontWeight={fontWeight} - minFontSize={minFontSize} - maxFontSize={maxFontSize} + desktopMinFontSize={desktopMinFontSize} + mobileMinFontSize={mobileMinFontSize} + desktopMaxFontSize={desktopMaxFontSize} + mobileMaxFontSize={mobileMaxFontSize} showTooltip={(data, left, top) => { showTooltip({ tooltipData: data, @@ -83,20 +98,18 @@ export const WordCloud = withTooltip( tooltipTop={tooltipTop} randomSeed={randomSeed} spiral={spiral} + isMobile={useIsMobile()} + minWidth={minWidth} /> {tooltipOpen && tooltipData ? ( - - {tooltipData.text} ({tooltipData.value}) - + header={`${tooltipData.text} (${tooltipData.value})`} + > ) : null} ); @@ -114,27 +127,38 @@ type WordCloudWordsProps = Omit & { // tooltipLeft and tooltipTop are used for preventing unnessary renders tooltipLeft?: number; tooltipTop?: number; + isMobile: boolean; // passing in isMobile as a prop so we can rerender if this changes }; const WordCloudWords: React.FC = ({ data, width = 1000, + minWidth = 500, height = 500, - wordPadding = 30, - fontWeight = 500, - minFontSize = 20, - maxFontSize = 150, + wordPadding = 20, + fontWeight = 400, + desktopMinFontSize = 15, + desktopMaxFontSize = 100, + mobileMinFontSize = 15, + mobileMaxFontSize = 60, randomSeed = 0.5, spiral = "rectangular", showTooltip, hideTooltip, + isMobile, }) => { + width = width < minWidth ? minWidth : width; + + const minFontSize = isMobile ? mobileMinFontSize : desktopMinFontSize; + const maxFontSize = isMobile ? mobileMaxFontSize : desktopMaxFontSize; + + const maxVal = Math.max(...data.map((w) => w.value)); + const minVal = Math.min(...data.map((w) => w.value)); + const fontScale = scaleLog({ - domain: [ - Math.min(...data.map((w) => w.value)), - Math.max(...data.map((w) => w.value)), - ], + domain: [minVal, maxVal], range: [minFontSize, maxFontSize], }); + const fontSizeSetter = (datum: WordData) => fontScale(datum.value); const fixedValueGenerator = () => randomSeed; return ( @@ -149,8 +173,19 @@ const WordCloudWords: React.FC = ({ rotate={0} random={fixedValueGenerator} > - {(cloudWords) => - cloudWords.map((word, index) => { + {(cloudWords) => { + if ( + inDevEnvironment && + cloudWords.length != 0 && // since on initial load the length is 0, but thats not an error + cloudWords.length != data.length + ) { + console.error( + `Not all words rendered for wordcloud! (${ + data.length - cloudWords.length + } words missing) Please try adjusting the min/max font size, the random seed, and the wordPadding` + ); + } + return cloudWords.map((word, index) => { return ( = ({ textAnchor="middle" onMouseMove={ ((e: React.MouseEvent) => { - const eventSvgCoords = localPoint( - // ownerSVGElement is given by visx docs but not recognized by typescript - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - e.target.ownerSVGElement as Element, - e - ) as Point; + // ownerSVGElement is given by visx docs but not recognized by typescript + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const eventElement = e.target.ownerSVGElement as Element; + const eventSvgCoords = localPoint(eventElement, e) as Point; + const rootSVGLeft = + eventElement.parentElement?.parentElement?.getBoundingClientRect() + .left ?? 0; + const parentDivLeft = + eventElement.parentElement?.parentElement?.parentElement?.getBoundingClientRect() + .left ?? 0; + + // visx localPoint does not account for the horizontal shift due to centering of the parent element, + // so manually add any shift from that + const alignmentOffset = rootSVGLeft - parentDivLeft; if (word.text) { showTooltip( { text: word.text, value: data[index].value }, eventSvgCoords.x - - word.text.length * TOOLTIP_HORIZONTAL_SHIFT_SCALER, + word.text.length * TOOLTIP_HORIZONTAL_SHIFT_SCALER + + alignmentOffset, eventSvgCoords.y ); } @@ -186,8 +230,8 @@ const WordCloudWords: React.FC = ({ {word.text} ); - }) - } + }); + }} ); }; @@ -199,6 +243,7 @@ const shouldNotRerender = ( if ( // if width changes, rerender, else don't rerender for a tooltip change prevProps.width === nextProps.width && + prevProps.isMobile === nextProps.isMobile && (prevProps.tooltipLeft !== nextProps.tooltipLeft || prevProps.tooltipTop !== nextProps.tooltipTop || nextProps.tooltipLeft === undefined || diff --git a/data/demographics.ts b/data/demographics.ts new file mode 100644 index 0000000..2c35a22 --- /dev/null +++ b/data/demographics.ts @@ -0,0 +1,348 @@ +export const D1 = [ + { + category: "CS", + value: 88, + }, + { + category: "CS/BBA", + value: 12, + }, + { + category: "CFM", + value: 5, + }, +]; + +export const D2 = [ + { + category: "Man", + value: 72, + }, + { + category: "Woman", + value: 29, + }, + { + category: "Gender non-conforming", + value: 4, + }, +]; + +export const D3 = [ + { + category: "She/Her/Her", + value: 31, + }, + { + category: "He/Him/His", + value: 73, + }, + { + category: "They/Them/Their", + value: 3, + }, +]; + +export const D4 = [ + { + category: "Black", + value: 2, + }, + { + category: "Latin", + value: 1, + }, + { + category: "East Asian", + value: 68, + }, + { + category: "Middle Eastern", + value: 2, + }, + { + category: "South Asian", + value: 13, + }, + { + category: "Southeast Asian", + value: 2, + }, + { + category: "White", + value: 18, + }, + { + category: "Prefer not to say", + value: 1, + }, +]; + +export const D5 = [ + { + category: "89", + value: 1, + }, + { + category: "90", + value: 6, + }, + { + category: "91", + value: 1, + }, + { + category: "92", + value: 5, + }, + { + category: "93", + value: 4, + }, + { + category: "94", + value: 10, + }, + { + category: "95", + value: 15, + }, + { + category: "96", + value: 22, + }, + { + category: "97", + value: 21, + }, + { + category: "98", + value: 10, + }, + { + category: "99", + value: 8, + }, +]; + +export const D6 = [ + { + category: "Asexual", + value: 6, + }, + { + category: "Bisexual", + value: 12, + }, + { + category: "Gay", + value: 3, + }, + { + category: "Heterosexual", + value: 81, + }, + { + category: "Queer", + value: 3, + }, + { + category: "Pansexual", + value: 3, + }, + { + category: "Questioning", + value: 2, + }, + { + category: "Prefer not to say", + value: 2, + }, +]; + +export const D7 = [ + { + text: "Ontario (Other)", + value: 15, + }, + { + text: "British Columbia", + value: 11, + }, + { + text: "Kitchener / Waterloo", + value: 5, + }, + { + text: "Alberta", + value: 3, + }, + { + text: "Quebec", + value: 3, + }, + { + text: "USA", + value: 2, + }, + { + text: "Hong Kong", + value: 2, + }, + { + text: "Manitoba", + value: 2, + }, + { + text: "India", + value: 1, + }, + { + text: "GTA / Toronto", + value: 1, + }, + { + text: "United Arab Emirates", + value: 1, + }, + { + text: "Indonesia", + value: 1, + }, + { + text: "Saskatchewan", + value: 1, + }, +]; + +export const D8 = [ + { + category: "High School Diploma", + value: 2, + }, + { + category: "College Diploma", + value: 6, + }, + { + category: "Bachelor's Degree", + value: 43, + }, + { + category: "Master's Degree", + value: 33, + }, + { + category: "Doctoral Degree", + value: 17, + }, + { + category: "Not Applicable", + value: 1, + }, + { + category: "Prefer not to say", + value: 2, + }, +]; + +export const D9 = [ + { + category: "0-50", + value: 9, + }, + { + category: "51-100", + value: 28, + }, + { + category: "101-150", + value: 22, + }, + { + category: "151-200", + value: 23, + }, + { + category: "201-250", + value: 7, + }, + { + category: "251-300", + value: 4, + }, + { + category: "301+", + value: 5, + }, + { + category: "Prefer not to say", + value: 6, + }, +]; + +export const D10 = [ + { + category: "0", + value: 74, + }, + { + category: "1", + value: 17, + }, + { + category: "2", + value: 5, + }, + { + category: "3", + value: 2, + }, + { + category: "4", + value: 4, + }, + { + category: "5", + value: 2, + }, +]; + +export const D11 = [ + { + category: "No religious affiliation", + value: 79, + }, + { + category: "Christianity", + value: 16, + }, + { + category: "Atheist", + value: 1, + }, + { + category: "Judaism", + value: 3, + }, + { + category: "Buddhism", + value: 2, + }, + { + category: "Islam", + value: 1, + }, + { + category: "Hinduism", + value: 5, + }, + { + category: "Jainism", + value: 1, + }, + { + category: "Prefer not to say", + value: 2, + }, +]; diff --git a/pages/_app.css b/pages/_app.css index 94a87f7..d1bf82b 100644 --- a/pages/_app.css +++ b/pages/_app.css @@ -2,6 +2,10 @@ html { scroll-behavior: smooth; } +html, body { + overflow-x: hidden; +} + body { /* Theme colours */ --pink: #EF839D; @@ -68,6 +72,7 @@ body { color: var(--primary-text); font-family: "Inconsolata", monospace; margin: 0; + position: relative; /* Font styling for body */ font-size: calc(18rem / 16); diff --git a/pages/demographics.tsx b/pages/demographics.tsx new file mode 100644 index 0000000..b365e08 --- /dev/null +++ b/pages/demographics.tsx @@ -0,0 +1,163 @@ +import { + D1, + D2, + D3, + D4, + D5, + D6, + D7, + D8, + D9, + D10, + D11, +} from "data/demographics"; +import { pageRoutes } from "data/routes"; +import React from "react"; +import { + barGraphProps, + DefaultProp, + pieChartProps, + barGraphMargin, + barGraphWidth, +} from "utils/defaultProps"; +import { useWindowDimensions } from "utils/getWindowDimensions"; +import { useIsMobile } from "utils/isMobile"; + +import { BarGraphVertical, BarGraphHorizontal } from "@/components/BarGraph"; +import { BottomNav } from "@/components/BottomNav"; +import { ComponentWrapper } from "@/components/ComponentWrapper"; +import { Header } from "@/components/Header"; +import { PieChart } from "@/components/PieChart"; +import { SectionHeader } from "@/components/SectionHeader"; +import { WordCloud } from "@/components/WordCloud"; + +import styles from "./samplePage.module.css"; + +export default function Demographics() { + const pageWidth = useWindowDimensions().width; + const isMobile = useIsMobile(); + + return ( +
+
+ + + +
+ +
+
+ + +
+ +
+
+ + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ); +} diff --git a/pages/samplePage.module.css b/pages/samplePage.module.css index b50d07b..f3761c2 100644 --- a/pages/samplePage.module.css +++ b/pages/samplePage.module.css @@ -2,4 +2,26 @@ display: flex; flex-direction: column; justify-content: center; +} + +.graphContainer { + padding: 0 calc(70rem / 16); +} + +@media screen and (max-width: 1200px) { + .graphContainer { + padding: 0 calc(40rem / 16); + } +} + +@media screen and (max-width: 1100px) { + .graphContainer { + padding: 0 calc(20rem / 16); + } +} + +@media screen and (max-width: 900px) { + .graphContainer { + padding: 0; + } } \ No newline at end of file diff --git a/pages/samplePage.tsx b/pages/samplePage.tsx index a1be48d..715e1fa 100644 --- a/pages/samplePage.tsx +++ b/pages/samplePage.tsx @@ -1,4 +1,9 @@ -import { mockCategoricalData, moreMockCategoricalData } from "data/mocks"; +import { + mockBoxPlotData, + mockCategoricalData, + mockLineData, + moreMockCategoricalData, +} from "data/mocks"; import { pageRoutes } from "data/routes"; import React from "react"; import { useWindowDimensions } from "utils/getWindowDimensions"; @@ -6,8 +11,10 @@ import { useIsMobile } from "utils/isMobile"; import { BarGraphVertical, BarGraphHorizontal } from "@/components/BarGraph"; import { BottomNav } from "@/components/BottomNav"; +import { BoxPlot } from "@/components/Boxplot"; import { ComponentWrapper } from "@/components/ComponentWrapper"; import { Header } from "@/components/Header"; +import { LineGraph } from "@/components/LineGraph"; import { SectionHeader } from "@/components/SectionHeader"; import { WordCloud } from "@/components/WordCloud"; @@ -22,7 +29,18 @@ export default function SamplePage() { const defaultGraphHeight = 500; // Make vars for common configs such as common margins - const defaultBarGraphMargin = { top: 20, bottom: 40, left: 150, right: 20 }; + const defaultVerticalBarGraphMargin = { + top: 20, + bottom: 80, + left: 60, + right: 20, + }; + const defaultHorizontalBarGraphMargin = { + top: 20, + bottom: 80, + left: 120, + right: 20, + }; return (
@@ -39,10 +57,9 @@ export default function SamplePage() { data={mockCategoricalData} width={defaultGraphWidth} height={defaultGraphHeight} - margin={defaultBarGraphMargin} + margin={defaultVerticalBarGraphMargin} /> - - - + - - - - ({ @@ -134,7 +143,6 @@ export default function SamplePage() { height={defaultGraphHeight} /> - ({ @@ -145,7 +153,6 @@ export default function SamplePage() { height={defaultGraphHeight} /> - - - - ({ - text: word.key, - value: word.value, - }))} - width={defaultGraphWidth} - height={defaultGraphHeight} + + + + - { + const mobileFactor = isPieChart ? mobilePieChartFactor : mobileBarGraphFactor; + const desktopFactor = isPieChart + ? desktopPieChartFactor + : desktopBarGraphFactor; + return isMobile ? pageWidth / mobileFactor : pageWidth / desktopFactor; +}; + +export const barGraphWidth = (isMobile: boolean, width: number) => + graphWidth(isMobile, width, false); + +export const pieChartWidth = (isMobile: boolean, width: number) => + graphWidth(isMobile, width, true); + +export const barGraphMargin = { + top: 20, + bottom: 80, + left: 60, + right: 20, +}; + +export const DefaultProp: { [key in PropName]: number } = { + graphHeight: 500, + labelSize: 24, + labelWidth: 100, +}; + +export const barGraphProps = (isMobile: boolean, pageWidth: number) => { + return { + width: barGraphWidth(isMobile, pageWidth), + height: DefaultProp.graphHeight, + margin: barGraphMargin, + }; +}; + +export const pieChartProps = (isMobile: boolean, pageWidth: number) => { + return { + width: pieChartWidth(isMobile, pageWidth), + labelWidth: DefaultProp.labelWidth, + labelTextSize: DefaultProp.labelSize, + }; +}; diff --git a/utils/inDevEnviroment.ts b/utils/inDevEnviroment.ts new file mode 100644 index 0000000..872ea50 --- /dev/null +++ b/utils/inDevEnviroment.ts @@ -0,0 +1,3 @@ +const inDevEnvironment = process && process.env.NODE_ENV === "development"; + +export { inDevEnvironment }; diff --git a/utils/isMobile.ts b/utils/isMobile.ts index 4022df1..a8fbc43 100644 --- a/utils/isMobile.ts +++ b/utils/isMobile.ts @@ -1,3 +1,3 @@ import { useWindowDimensions } from "./getWindowDimensions"; -export const useIsMobile = () => useWindowDimensions().width <= 768; +export const useIsMobile = () => useWindowDimensions().width <= 900;