Merge branch 'main' into mental-health-page
continuous-integration/drone/push Build is passing Details

This commit is contained in:
Shahan Nedadahandeh 2022-12-23 20:11:05 -05:00
commit a70e7aab2b
8 changed files with 1297 additions and 803 deletions

View File

@ -6,23 +6,11 @@
fill: var(--primary-accent-light);
}
.barText {
visibility: hidden;
font-family: "Inconsolata", monospace;
font-weight: 800;
fill: var(--label);
}
.barGroup:hover .bar {
fill: var(--primary-accent);
filter: drop-shadow(0 0 calc(4rem / 16) var(--primary-accent));
}
.barGroup:hover .barText {
visibility: visible;
}
.tickLabel {
font-family: "Inconsolata", monospace;
font-weight: 800;

View File

@ -1,14 +1,18 @@
import { AxisBottom, AxisLeft } from "@visx/axis";
import { bottomTickLabelProps } from "@visx/axis/lib/axis/AxisBottom";
import { leftTickLabelProps } from "@visx/axis/lib/axis/AxisLeft";
import { localPoint } from "@visx/event";
import { GridColumns, GridRows } from "@visx/grid";
import { Group } from "@visx/group";
import { Point } from "@visx/point";
import { scaleBand, scaleLinear } from "@visx/scale";
import { Bar } from "@visx/shape";
import { Text } from "@visx/text";
import { withTooltip } from "@visx/tooltip";
import React from "react";
import { Color } from "utils/Color";
import { TooltipWrapper } from "./TooltipWrapper";
import styles from "./BarGraph.module.css";
interface BarGraphProps {
@ -29,8 +33,6 @@ interface BarGraphProps {
categoryTickLabelSize?: number;
/** Font size of the value axis tick labels, in pixels. Default is 16px. */
valueTickLabelSize?: number;
/** Font size of the value that appears when hovering over a bar, in pixels. */
hoverLabelSize?: number;
/** Label text for the category axis. */
categoryAxisLabel?: string;
/** Font size of the label for the cateogry axis, in pixels. */
@ -62,8 +64,11 @@ interface BarGraphData {
const DEFAULT_LABEL_SIZE = 16;
export function BarGraphHorizontal(props: BarGraphProps) {
const {
type TooltipData = string;
export const BarGraphHorizontal = withTooltip<BarGraphProps, TooltipData>(
({
width,
height,
margin,
data,
@ -71,7 +76,6 @@ export function BarGraphHorizontal(props: BarGraphProps) {
minWidth = 500,
categoryTickLabelSize = DEFAULT_LABEL_SIZE,
valueTickLabelSize = DEFAULT_LABEL_SIZE,
hoverLabelSize,
categoryAxisLabel,
categoryAxisLabelSize = DEFAULT_LABEL_SIZE,
categoryAxisLabelOffset = 0,
@ -79,139 +83,158 @@ export function BarGraphHorizontal(props: BarGraphProps) {
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;
tooltipOpen,
tooltipLeft,
tooltipTop,
tooltipData,
hideTooltip,
showTooltip,
}) => {
width = width < minWidth ? minWidth : width; // Ensuring graph's width >= minWidth
const barPadding = 0.4;
const categoryMax = height - margin.top - margin.bottom;
const valueMax = width - margin.left - margin.right;
const categoryMax = height - margin.top - margin.bottom;
const valueMax = width - margin.left - margin.right;
const getCategory = (d: BarGraphData) => d.category;
const getValue = (d: BarGraphData) => d.value;
const getCategory = (d: BarGraphData) => d.category;
const getValue = (d: BarGraphData) => d.value;
const categoryScale = scaleBand({
range: [0, categoryMax],
domain: data.map(getCategory),
padding: barPadding,
});
const categoryScale = scaleBand({
range: [0, categoryMax],
domain: data.map(getCategory),
padding: barPadding,
});
const valueScale = scaleLinear({
range: [0, valueMax],
nice: true,
domain: [0, Math.max(...data.map(getValue))],
});
const valueScale = scaleLinear({
range: [0, valueMax],
nice: true,
domain: [0, Math.max(...data.map(getValue))],
});
const categoryPoint = (d: BarGraphData) => categoryScale(getCategory(d));
const valuePoint = (d: BarGraphData) => valueScale(getValue(d));
const categoryPoint = (d: BarGraphData) => categoryScale(getCategory(d));
const valuePoint = (d: BarGraphData) => valueScale(getValue(d));
return (
<svg className={className} width={width} height={height}>
<Group top={margin.top} left={margin.left}>
<Group>
{data.map((d, idx) => {
const barName = `${getCategory(d)}-${idx}`;
const barWidth = categoryScale.bandwidth();
const backgroundBarWidth = barWidth / (1 - barPadding);
return idx % 2 === 0 ? (
<Bar
className={styles.barBackground}
key={`bar-${barName}-background`}
x={0}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
y={categoryPoint(d)! - (backgroundBarWidth - barWidth) / 2}
width={valueMax}
height={backgroundBarWidth}
/>
) : null;
})}
</Group>
<GridColumns
scale={valueScale}
height={categoryMax}
numTicks={5}
stroke={Color.label}
strokeWidth={4}
strokeDasharray="10"
strokeLinecap="round"
/>
<Group>
{data.map((d, idx) => {
const barName = `${getCategory(d)}-${idx}`;
const barLength = valuePoint(d);
const barWidth = categoryScale.bandwidth();
return (
<Group className={styles.barGroup} key={`bar-${barName}`}>
<Bar
className={styles.bar}
x={0}
y={categoryPoint(d)}
width={barLength}
height={barWidth}
/>
<Text
className={styles.barText}
x={valuePoint(d) - 12}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
y={categoryPoint(d)! + barWidth / 2}
fontSize={hoverLabelSize ?? barWidth * 0.75}
textAnchor="end"
verticalAnchor="middle"
>
{getValue(d)}
</Text>
</Group>
);
})}
</Group>
</Group>
<AxisLeft
scale={categoryScale}
top={margin.top}
left={margin.left}
hideAxisLine
hideTicks
tickLabelProps={() => {
return {
...leftTickLabelProps(),
className: styles.tickLabel,
fontSize: `${categoryTickLabelSize / 16}rem`,
};
}}
label={categoryAxisLabel}
labelClassName={styles.axisLabel}
labelOffset={categoryAxisLabelOffset}
labelProps={{
fontSize: `${categoryAxisLabelSize / 16}rem`,
}}
/>
<AxisBottom
scale={valueScale}
top={margin.top + categoryMax}
left={margin.left}
hideAxisLine
hideTicks
numTicks={5}
tickLabelProps={() => {
return {
...bottomTickLabelProps(),
className: styles.tickLabel,
dy: defaultLabelDy,
fontSize: `${valueTickLabelSize / 16}rem`,
};
}}
label={valueAxisLabel}
labelClassName={styles.axisLabel}
labelOffset={valueAxisLabelOffset}
labelProps={{
fontSize: `${valueAxisLabelSize / 16}rem`,
}}
/>
</svg>
);
}
return (
<div>
<svg className={className} width={width} height={height}>
<Group top={margin.top} left={margin.left}>
<Group>
{data.map((d, idx) => {
const barName = `${getCategory(d)}-${idx}`;
const barWidth = categoryScale.bandwidth();
const backgroundBarWidth = barWidth / (1 - barPadding);
return idx % 2 === 0 ? (
<Bar
className={styles.barBackground}
key={`bar-${barName}-background`}
x={0}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
y={categoryPoint(d)! - (backgroundBarWidth - barWidth) / 2}
width={valueMax}
height={backgroundBarWidth}
/>
) : null;
})}
</Group>
<GridColumns
scale={valueScale}
height={categoryMax}
numTicks={5}
stroke={Color.label}
strokeWidth={4}
strokeDasharray="10"
strokeLinecap="round"
/>
<Group>
{data.map((d, idx) => {
const barName = `${getCategory(d)}-${idx}`;
const barLength = valuePoint(d);
const barWidth = categoryScale.bandwidth();
return (
<Group className={styles.barGroup} key={`bar-${barName}`}>
<Bar
onMouseMove={(e) => {
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: getValue(d).toString(),
tooltipTop: eventSvgCoords.y,
tooltipLeft: eventSvgCoords.x,
});
}}
onMouseOut={hideTooltip}
className={styles.bar}
x={0}
y={categoryPoint(d)}
width={barLength}
height={barWidth}
/>
</Group>
);
})}
</Group>
<AxisLeft
scale={categoryScale}
hideAxisLine
hideTicks
tickLabelProps={() => {
return {
...leftTickLabelProps(),
className: styles.tickLabel,
fontSize: `${categoryTickLabelSize / 16}rem`,
};
}}
label={categoryAxisLabel}
labelClassName={styles.axisLabel}
labelOffset={categoryAxisLabelOffset}
labelProps={{
fontSize: `${categoryAxisLabelSize / 16}rem`,
}}
/>
<AxisBottom
scale={valueScale}
top={categoryMax}
hideAxisLine
hideTicks
numTicks={5}
tickLabelProps={() => {
return {
...bottomTickLabelProps(),
className: styles.tickLabel,
dy: defaultLabelDy,
fontSize: `${valueTickLabelSize / 16}rem`,
};
}}
label={valueAxisLabel}
labelClassName={styles.axisLabel}
labelOffset={valueAxisLabelOffset}
labelProps={{
fontSize: `${valueAxisLabelSize / 16}rem`,
}}
/>
</Group>
</svg>
export function BarGraphVertical(props: BarGraphProps) {
const {
{tooltipOpen && (
<TooltipWrapper
top={tooltipTop}
left={tooltipLeft}
header={tooltipData as string}
></TooltipWrapper>
)}
</div>
);
}
);
export const BarGraphVertical = withTooltip<BarGraphProps, TooltipData>(
({
width,
height,
margin,
data,
@ -219,7 +242,6 @@ export function BarGraphVertical(props: BarGraphProps) {
minWidth = 500,
categoryTickLabelSize = DEFAULT_LABEL_SIZE,
valueTickLabelSize = DEFAULT_LABEL_SIZE,
hoverLabelSize,
categoryAxisLabel,
categoryAxisLabelSize = DEFAULT_LABEL_SIZE,
categoryAxisLabelOffset = 0,
@ -230,142 +252,161 @@ export function BarGraphVertical(props: BarGraphProps) {
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;
tooltipOpen,
tooltipLeft,
tooltipTop,
tooltipData,
hideTooltip,
showTooltip,
}) => {
width = width < minWidth ? minWidth : 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 - final_margin_bottom;
const categoryMax = width - margin.left - margin.right;
const valueMax = height - margin.top - final_margin_bottom;
const getCategory = (d: BarGraphData) => d.category;
const getValue = (d: BarGraphData) => d.value;
const getCategory = (d: BarGraphData) => d.category;
const getValue = (d: BarGraphData) => d.value;
const categoryScale = scaleBand({
range: [0, categoryMax],
domain: data.map(getCategory),
padding: barPadding,
});
const categoryScale = scaleBand({
range: [0, categoryMax],
domain: data.map(getCategory),
padding: barPadding,
});
const valueScale = scaleLinear({
range: [valueMax, 0],
nice: true,
domain: [0, Math.max(...data.map(getValue))],
});
const valueScale = scaleLinear({
range: [valueMax, 0],
nice: true,
domain: [0, Math.max(...data.map(getValue))],
});
const categoryPoint = (d: BarGraphData) => categoryScale(getCategory(d));
const valuePoint = (d: BarGraphData) => valueScale(getValue(d));
const categoryPoint = (d: BarGraphData) => categoryScale(getCategory(d));
const valuePoint = (d: BarGraphData) => valueScale(getValue(d));
return (
<svg className={className} width={width} height={height}>
<Group top={margin.top} left={margin.left}>
<Group>
{data.map((d, idx) => {
const barName = `${getCategory(d)}-${idx}`;
const barWidth = categoryScale.bandwidth();
const backgroundBarWidth = barWidth / (1 - barPadding);
return idx % 2 === 0 ? (
<Bar
className={styles.barBackground}
key={`bar-${barName}-background`}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
x={categoryPoint(d)! - (backgroundBarWidth - barWidth) / 2}
y={0}
width={backgroundBarWidth}
height={valueMax}
/>
) : null;
})}
</Group>
<GridRows
scale={valueScale}
width={categoryMax}
numTicks={5}
stroke={Color.label}
strokeWidth={4}
strokeDasharray="10"
strokeLinecap="round"
/>
<Group>
{data.map((d, idx) => {
const barName = `${getCategory(d)}-${idx}`;
const barHeight = valueMax - valuePoint(d);
const barWidth = categoryScale.bandwidth();
return (
<Group className={styles.barGroup} key={`bar-${barName}`}>
<Bar
className={styles.bar}
x={categoryPoint(d)}
y={valueMax - barHeight}
width={barWidth}
height={barHeight}
/>
<Text
className={styles.barText}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
x={categoryPoint(d)! + barWidth / 2}
y={valueMax - barHeight + 12}
fontSize={hoverLabelSize ?? barWidth * 0.5}
textAnchor="middle"
verticalAnchor="start"
>
{getValue(d)}
</Text>
</Group>
);
})}
</Group>
</Group>
<AxisBottom
scale={categoryScale}
top={valueMax + margin.top}
left={margin.left}
hideAxisLine
hideTicks
tickLabelProps={(value, index) => {
const alternatingDy = index % 2 == 0 ? defaultLabelDy : lowerLabelDy;
return {
...bottomTickLabelProps(),
className: styles.tickLabel,
dy: alternatingLabel ? alternatingDy : defaultLabelDy,
fontSize: `${categoryTickLabelSize / 16}rem`,
width: categoryScale.bandwidth(),
verticalAnchor: "start",
};
}}
label={categoryAxisLabel}
labelClassName={styles.axisLabel}
labelOffset={categoryAxisLabelOffset}
labelProps={{
fontSize: `${categoryAxisLabelSize / 16}rem`,
}}
/>
<AxisLeft
scale={valueScale}
top={margin.top}
left={margin.left}
hideAxisLine
hideTicks
numTicks={5}
tickLabelProps={() => {
return {
...leftTickLabelProps(),
className: styles.tickLabel,
dx: "-0.5rem",
dy: "0.25rem",
fontSize: `${valueTickLabelSize / 16}rem`,
};
}}
label={valueAxisLabel}
labelClassName={styles.axisLabel}
labelOffset={valueAxisLabelOffset}
labelProps={{
fontSize: `${valueAxisLabelSize / 16}rem`,
}}
/>
</svg>
);
}
return (
<div>
<svg className={className} width={width} height={height}>
<Group top={margin.top} left={margin.left}>
<Group>
{data.map((d, idx) => {
const barName = `${getCategory(d)}-${idx}`;
const barWidth = categoryScale.bandwidth();
const backgroundBarWidth = barWidth / (1 - barPadding);
return idx % 2 === 0 ? (
<Bar
className={styles.barBackground}
key={`bar-${barName}-background`}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
x={categoryPoint(d)! - (backgroundBarWidth - barWidth) / 2}
y={0}
width={backgroundBarWidth}
height={valueMax}
/>
) : null;
})}
</Group>
<GridRows
scale={valueScale}
width={categoryMax}
numTicks={5}
stroke={Color.label}
strokeWidth={4}
strokeDasharray="10"
strokeLinecap="round"
/>
<Group>
{data.map((d, idx) => {
const barName = `${getCategory(d)}-${idx}`;
const barHeight = valueMax - valuePoint(d);
const barWidth = categoryScale.bandwidth();
return (
<Group className={styles.barGroup} key={`bar-${barName}`}>
<Bar
onMouseMove={(e) => {
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: getValue(d).toString(),
tooltipTop: eventSvgCoords.y,
tooltipLeft: eventSvgCoords.x,
});
}}
onMouseOut={hideTooltip}
className={styles.bar}
x={categoryPoint(d)}
y={valueMax - barHeight}
width={barWidth}
height={barHeight}
/>
</Group>
);
})}
</Group>
<AxisBottom
scale={categoryScale}
top={valueMax}
hideAxisLine
hideTicks
tickLabelProps={(value, index) => {
const alternatingDy =
index % 2 == 0 ? defaultLabelDy : lowerLabelDy;
return {
...bottomTickLabelProps(),
className: styles.tickLabel,
dy: alternatingLabel ? alternatingDy : defaultLabelDy,
fontSize: `${categoryTickLabelSize / 16}rem`,
width: categoryScale.bandwidth(),
verticalAnchor: "start",
};
}}
label={categoryAxisLabel}
labelClassName={styles.axisLabel}
labelOffset={categoryAxisLabelOffset}
labelProps={{
fontSize: `${categoryAxisLabelSize / 16}rem`,
}}
/>
<AxisLeft
scale={valueScale}
hideAxisLine
hideTicks
numTicks={5}
tickLabelProps={() => {
return {
...leftTickLabelProps(),
className: styles.tickLabel,
dx: "-0.5rem",
dy: "0.25rem",
fontSize: `${valueTickLabelSize / 16}rem`,
};
}}
label={valueAxisLabel}
labelClassName={styles.axisLabel}
labelOffset={valueAxisLabelOffset}
labelProps={{
fontSize: `${valueAxisLabelSize / 16}rem`,
}}
/>
</Group>
</svg>
{tooltipOpen && (
<TooltipWrapper
top={tooltipTop}
left={tooltipLeft}
header={tooltipData as string}
></TooltipWrapper>
)}
</div>
);
}
);

View File

@ -9,18 +9,6 @@
fill: var(--card-background);
}
.barText {
visibility: hidden;
font-family: "Inconsolata", monospace;
font-weight: 800;
fill: var(--label);
}
.singleBar:hover .barText {
visibility: visible;
}
.tickLabel {
font-family: "Inconsolata", monospace;
font-weight: 800;

View File

@ -1,16 +1,20 @@
import { AxisBottom, AxisLeft } from "@visx/axis";
import { bottomTickLabelProps } from "@visx/axis/lib/axis/AxisBottom";
import { leftTickLabelProps } from "@visx/axis/lib/axis/AxisLeft";
import { localPoint } from "@visx/event";
import { GridColumns, GridRows } from "@visx/grid";
import { Group } from "@visx/group";
import { LegendOrdinal } from "@visx/legend";
import { Point } from "@visx/point";
import { scaleBand, scaleLinear, scaleOrdinal } from "@visx/scale";
import { Bar, BarGroup, BarGroupHorizontal } from "@visx/shape";
import { BarGroupBar as BarGroupBarType } from "@visx/shape/lib/types";
import { Text } from "@visx/text";
import { withTooltip } from "@visx/tooltip";
import React, { useState } from "react";
import { Color } from "utils/Color";
import { TooltipWrapper } from "./TooltipWrapper";
import styles from "./GroupedBarGraph.module.css";
interface GroupedBarGraphProps {
@ -79,15 +83,20 @@ interface BarGroupData {
// BAR_PADDING must be in the range [0, 1)
const BAR_PADDING = 0.2;
const BAR_TEXT_PADDING = 12;
const DEFAULT_LABEL_SIZE = 16;
export function GroupedBarGraphVertical(props: GroupedBarGraphProps) {
const {
type TooltipData = string;
export const GroupedBarGraphVertical = withTooltip<
GroupedBarGraphProps,
TooltipData
>(
({
data: propsData,
barColors,
barHoverColorsMap,
width,
height,
margin,
className,
@ -106,209 +115,242 @@ export function GroupedBarGraphVertical(props: GroupedBarGraphProps) {
alternatingLabelSpace = 80,
defaultLabelDy = `0px`,
lowerLabelDy = `30px`,
} = props;
const width = props.width < minWidth ? minWidth : props.width; // Ensuring graph's width >= minWidth
const alternatingLabel = width <= widthAlternatingLabel;
const final_margin_bottom = alternatingLabel
? margin.bottom + alternatingLabelSpace
: margin.bottom;
tooltipOpen,
tooltipLeft,
tooltipTop,
tooltipData,
hideTooltip,
showTooltip,
}) => {
width = width < minWidth ? minWidth : width; // Ensuring graph's width >= minWidth
const alternatingLabel = width <= widthAlternatingLabel;
const final_margin_bottom = alternatingLabel
? margin.bottom + alternatingLabelSpace
: margin.bottom;
const data: BarGroupData[] = propsData.map((datum: GroupedBarGraphData) => {
return { category: datum.category, ...datum.values };
});
const data: BarGroupData[] = propsData.map((datum: GroupedBarGraphData) => {
return { category: datum.category, ...datum.values };
});
const keys = Object.keys(propsData[0].values);
propsData.forEach((d: GroupedBarGraphData) => {
const currentKeys = Object.keys(d.values);
if (
keys.length != currentKeys.length ||
!keys.every((key: string) => currentKeys.includes(key))
) {
throw new Error(
"Every category in a GroupedBarGraph must have the same keys. Check the data prop"
);
}
});
const keys = Object.keys(propsData[0].values);
propsData.forEach((d: GroupedBarGraphData) => {
const currentKeys = Object.keys(d.values);
if (
keys.length != currentKeys.length ||
!keys.every((key: string) => currentKeys.includes(key))
) {
throw new Error(
"Every category in a GroupedBarGraph must have the same keys. Check the data prop"
);
}
});
const allValues = propsData
.map((d: GroupedBarGraphData) => Object.values(d.values))
.flat();
const allValues = propsData
.map((d: GroupedBarGraphData) => Object.values(d.values))
.flat();
const categoryMax = width - margin.left - margin.right;
const valueMax = height - margin.top - final_margin_bottom;
const categoryMax = width - margin.left - margin.right;
const valueMax = height - margin.top - final_margin_bottom;
const getCategory = (d: BarGroupData) => d.category;
const getCategory = (d: BarGroupData) => d.category;
const categoryScale = scaleBand({
domain: data.map(getCategory),
padding: BAR_PADDING,
});
const categoryScale = scaleBand({
domain: data.map(getCategory),
padding: BAR_PADDING,
});
const keyScale = scaleBand({
domain: keys,
});
const keyScale = scaleBand({
domain: keys,
});
const valueScale = scaleLinear<number>({
domain: [0, Math.max(...allValues)],
});
const valueScale = scaleLinear<number>({
domain: [0, Math.max(...allValues)],
});
const colorScale = scaleOrdinal<string, string>({
domain: keys,
range: barColors,
});
const colorScale = scaleOrdinal<string, string>({
domain: keys,
range: barColors,
});
categoryScale.rangeRound([0, categoryMax]);
keyScale.rangeRound([0, categoryScale.bandwidth()]);
valueScale.rangeRound([valueMax, 0]);
categoryScale.rangeRound([0, categoryMax]);
keyScale.rangeRound([0, categoryScale.bandwidth()]);
valueScale.rangeRound([valueMax, 0]);
return (
<div
className={className ? `${className} ${styles.wrapper}` : styles.wrapper}
>
<div className={styles.legend}>
<LegendOrdinal
scale={colorScale}
direction="row"
itemMargin={itemMargin}
labelAlign="center"
/>
</div>
<svg width={width} height={height}>
<defs>
{Object.keys(barHoverColorsMap).map((color: string) => {
// remove brackets from colour name to make ids work
const colorId = removeBrackets(color);
return (
<filter key={`glow-${color}`} id={`glow-${colorId}`}>
<feDropShadow
dx="0"
dy="0"
stdDeviation="4"
floodColor={barHoverColorsMap[color]}
/>
</filter>
);
})}
</defs>
<Group top={margin.top} left={margin.left}>
<Group>
{data.map((d, idx) => {
const barName = `${getCategory(d)}-${idx}`;
const barWidth = categoryScale.bandwidth();
const backgroundBarWidth = barWidth / (1 - BAR_PADDING);
return idx % 2 === 0 ? (
<Bar
className={styles.barBackground}
key={`bar-${barName}-background`}
x={
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
categoryScale(getCategory(d))! -
(backgroundBarWidth - barWidth) / 2
}
y={0}
width={backgroundBarWidth}
height={valueMax}
/>
) : null;
})}
</Group>
<GridRows
scale={valueScale}
width={categoryMax}
numTicks={5}
stroke={Color.label}
strokeWidth={4}
strokeDasharray="10"
strokeLinecap="round"
return (
<div
className={
className ? `${className} ${styles.wrapper}` : styles.wrapper
}
>
<div className={styles.legend}>
<LegendOrdinal
scale={colorScale}
direction="row"
itemMargin={itemMargin}
labelAlign="center"
/>
<BarGroup
data={data}
keys={keys}
height={valueMax}
x0={getCategory}
x0Scale={categoryScale}
x1Scale={keyScale}
yScale={valueScale}
color={colorScale}
>
{(barGroups) =>
barGroups.map((barGroup) => (
<Group
key={`bar-group-${barGroup.x0}-${barGroup.index}`}
left={barGroup.x0}
>
{barGroup.bars.map((bar) => (
<HoverableBar
key={`bar-group-bar-${barGroup.x0}-${barGroup.index}-${bar.key}-${bar.index}`}
bar={bar}
valueMax={valueMax}
hoverFillColor={barHoverColorsMap[bar.color]}
hoverLabelSize={hoverLabelSize}
/>
))}
</Group>
))
}
</BarGroup>
</Group>
<AxisBottom
scale={categoryScale}
top={valueMax + margin.top}
left={margin.left}
hideAxisLine
hideTicks
tickLabelProps={(value, index) => {
const alternatingDy =
index % 2 == 0 ? defaultLabelDy : lowerLabelDy;
return {
...bottomTickLabelProps(),
className: styles.tickLabel,
dy: alternatingLabel ? alternatingDy : defaultLabelDy,
fontSize: `${categoryTickLabelSize / 16}rem`,
width: categoryScale.bandwidth(),
verticalAnchor: "start",
};
}}
label={categoryAxisLabel}
labelClassName={styles.axisLabel}
labelOffset={categoryAxisLabelOffset}
labelProps={{
fontSize: `${categoryAxisLabelSize / 16}rem`,
}}
/>
<AxisLeft
scale={valueScale}
top={margin.top}
left={margin.left}
hideAxisLine
hideTicks
numTicks={5}
tickLabelProps={() => {
return {
...leftTickLabelProps(),
className: styles.tickLabel,
dx: "-0.5rem",
dy: "0.25rem",
fontSize: `${valueTickLabelSize / 16}rem`,
};
}}
label={valueAxisLabel}
labelClassName={styles.axisLabel}
labelOffset={valueAxisLabelOffset}
labelProps={{
fontSize: `${valueAxisLabelSize / 16}rem`,
}}
/>
</svg>
</div>
);
}
</div>
<svg width={width} height={height}>
<defs>
{Object.keys(barHoverColorsMap).map((color: string) => {
// remove brackets from colour name to make ids work
const colorId = removeBrackets(color);
return (
<filter key={`glow-${color}`} id={`glow-${colorId}`}>
<feDropShadow
dx="0"
dy="0"
stdDeviation="4"
floodColor={barHoverColorsMap[color]}
/>
</filter>
);
})}
</defs>
<Group top={margin.top} left={margin.left}>
<Group>
{data.map((d, idx) => {
const barName = `${getCategory(d)}-${idx}`;
const barWidth = categoryScale.bandwidth();
const backgroundBarWidth = barWidth / (1 - BAR_PADDING);
return idx % 2 === 0 ? (
<Bar
className={styles.barBackground}
key={`bar-${barName}-background`}
x={
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
categoryScale(getCategory(d))! -
(backgroundBarWidth - barWidth) / 2
}
y={0}
width={backgroundBarWidth}
height={valueMax}
/>
) : null;
})}
</Group>
<GridRows
scale={valueScale}
width={categoryMax}
numTicks={5}
stroke={Color.label}
strokeWidth={4}
strokeDasharray="10"
strokeLinecap="round"
/>
<BarGroup
data={data}
keys={keys}
height={valueMax}
x0={getCategory}
x0Scale={categoryScale}
x1Scale={keyScale}
yScale={valueScale}
color={colorScale}
>
{(barGroups) =>
barGroups.map((barGroup) => (
<Group
key={`bar-group-${barGroup.x0}-${barGroup.index}`}
left={barGroup.x0}
>
{barGroup.bars.map((bar) => (
<HoverableBar
onMouseMove={(e) => {
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: bar.value.toString(),
tooltipTop: eventSvgCoords.y,
tooltipLeft: eventSvgCoords.x,
});
}}
onMouseOut={hideTooltip}
key={`bar-group-bar-${barGroup.x0}-${barGroup.index}-${bar.key}-${bar.index}`}
bar={bar}
valueMax={valueMax}
hoverFillColor={barHoverColorsMap[bar.color]}
hoverLabelSize={hoverLabelSize}
/>
))}
</Group>
))
}
</BarGroup>
<AxisBottom
scale={categoryScale}
top={valueMax}
hideAxisLine
hideTicks
tickLabelProps={(value, index) => {
const alternatingDy =
index % 2 == 0 ? defaultLabelDy : lowerLabelDy;
return {
...bottomTickLabelProps(),
className: styles.tickLabel,
dy: alternatingLabel ? alternatingDy : defaultLabelDy,
fontSize: `${categoryTickLabelSize / 16}rem`,
width: categoryScale.bandwidth(),
verticalAnchor: "start",
};
}}
label={categoryAxisLabel}
labelClassName={styles.axisLabel}
labelOffset={categoryAxisLabelOffset}
labelProps={{
fontSize: `${categoryAxisLabelSize / 16}rem`,
}}
/>
<AxisLeft
scale={valueScale}
hideAxisLine
hideTicks
numTicks={5}
tickLabelProps={() => {
return {
...leftTickLabelProps(),
className: styles.tickLabel,
dx: "-0.5rem",
dy: "0.25rem",
fontSize: `${valueTickLabelSize / 16}rem`,
};
}}
label={valueAxisLabel}
labelClassName={styles.axisLabel}
labelOffset={valueAxisLabelOffset}
labelProps={{
fontSize: `${valueAxisLabelSize / 16}rem`,
}}
/>
</Group>
</svg>
export function GroupedBarGraphHorizontal(props: GroupedBarGraphProps) {
const {
{tooltipOpen && (
<TooltipWrapper
top={tooltipTop}
left={tooltipLeft}
header={tooltipData as string}
></TooltipWrapper>
)}
</div>
);
}
);
export const GroupedBarGraphHorizontal = withTooltip<
GroupedBarGraphProps,
TooltipData
>(
({
data: propsData,
barColors,
barHoverColorsMap,
width,
height,
margin,
className,
@ -324,198 +366,227 @@ export function GroupedBarGraphHorizontal(props: GroupedBarGraphProps) {
valueAxisLabelOffset = 0,
itemMargin = "0 0 0 15px",
defaultLabelDy = "0",
} = props;
const width = props.width < minWidth ? minWidth : props.width; // Ensuring graph's width >= minWidth
tooltipOpen,
tooltipLeft,
tooltipTop,
tooltipData,
hideTooltip,
showTooltip,
}) => {
width = width < minWidth ? minWidth : width; // Ensuring graph's width >= minWidth
const data: BarGroupData[] = propsData.map((datum: GroupedBarGraphData) => {
return { category: datum.category, ...datum.values };
});
const data: BarGroupData[] = propsData.map((datum: GroupedBarGraphData) => {
return { category: datum.category, ...datum.values };
});
const keys = Object.keys(propsData[0].values);
propsData.forEach((d: GroupedBarGraphData) => {
const currentKeys = Object.keys(d.values);
if (
keys.length != currentKeys.length ||
!keys.every((key: string) => currentKeys.includes(key))
) {
throw new Error(
"Every category in a GroupedBarGraph must have the same keys. Check the data prop"
);
}
});
const keys = Object.keys(propsData[0].values);
propsData.forEach((d: GroupedBarGraphData) => {
const currentKeys = Object.keys(d.values);
if (
keys.length != currentKeys.length ||
!keys.every((key: string) => currentKeys.includes(key))
) {
throw new Error(
"Every category in a GroupedBarGraph must have the same keys. Check the data prop"
);
}
});
const allValues = propsData
.map((d: GroupedBarGraphData) => Object.values(d.values))
.flat();
const allValues = propsData
.map((d: GroupedBarGraphData) => Object.values(d.values))
.flat();
const categoryMax = height - margin.top - margin.bottom;
const valueMax = width - margin.left - margin.right;
const categoryMax = height - margin.top - margin.bottom;
const valueMax = width - margin.left - margin.right;
const getCategory = (d: BarGroupData) => d.category;
const getCategory = (d: BarGroupData) => d.category;
const categoryScale = scaleBand({
domain: data.map(getCategory),
padding: BAR_PADDING,
});
const categoryScale = scaleBand({
domain: data.map(getCategory),
padding: BAR_PADDING,
});
const keyScale = scaleBand({
domain: keys,
});
const keyScale = scaleBand({
domain: keys,
});
const valueScale = scaleLinear<number>({
domain: [Math.max(...allValues), 0],
});
const valueScale = scaleLinear<number>({
domain: [Math.max(...allValues), 0],
});
const colorScale = scaleOrdinal<string, string>({
domain: keys,
range: barColors,
});
const colorScale = scaleOrdinal<string, string>({
domain: keys,
range: barColors,
});
categoryScale.rangeRound([0, categoryMax]);
keyScale.rangeRound([0, categoryScale.bandwidth()]);
valueScale.rangeRound([valueMax, 0]);
categoryScale.rangeRound([0, categoryMax]);
keyScale.rangeRound([0, categoryScale.bandwidth()]);
valueScale.rangeRound([valueMax, 0]);
return (
<div
className={className ? `${className} ${styles.wrapper}` : styles.wrapper}
>
<div className={styles.legend}>
<LegendOrdinal
scale={colorScale}
direction="row"
itemMargin={itemMargin}
labelAlign="center"
/>
</div>
<svg width={width} height={height}>
<defs>
{Object.keys(barHoverColorsMap).map((color: string) => {
// remove brackets from colour name to make ids work
const colorId = removeBrackets(color);
return (
<filter key={`glow-${color}`} id={`glow-${colorId}`}>
<feDropShadow
dx="0"
dy="0"
stdDeviation="4"
floodColor={barHoverColorsMap[color]}
/>
</filter>
);
})}
</defs>
<Group top={margin.top} left={margin.left}>
<Group>
{data.map((d, idx) => {
const barName = `${getCategory(d)}-${idx}`;
const barWidth = categoryScale.bandwidth();
const backgroundBarWidth = barWidth / (1 - BAR_PADDING);
return idx % 2 === 0 ? (
<Bar
className={styles.barBackground}
key={`bar-${barName}-background`}
x={0}
y={
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
categoryScale(getCategory(d))! -
(backgroundBarWidth - barWidth) / 2
}
width={valueMax}
height={backgroundBarWidth}
/>
) : null;
})}
</Group>
<GridColumns
scale={valueScale}
height={categoryMax}
numTicks={5}
stroke={Color.label}
strokeWidth={4}
strokeDasharray="10"
strokeLinecap="round"
return (
<div
className={
className ? `${className} ${styles.wrapper}` : styles.wrapper
}
>
<div className={styles.legend}>
<LegendOrdinal
scale={colorScale}
direction="row"
itemMargin={itemMargin}
labelAlign="center"
/>
<BarGroupHorizontal
data={data}
keys={keys}
width={valueMax}
y0={getCategory}
y0Scale={categoryScale}
y1Scale={keyScale}
xScale={valueScale}
color={colorScale}
>
{(barGroups) =>
barGroups.map((barGroup) => (
<Group
key={`bar-group-${barGroup.y0}-${barGroup.index}`}
top={barGroup.y0}
>
{barGroup.bars.map((bar) => (
<HoverableBar
key={`bar-group-bar-${barGroup.y0}-${barGroup.index}-${bar.key}-${bar.index}`}
bar={bar}
valueMax={valueMax}
hoverFillColor={barHoverColorsMap[bar.color]}
hoverLabelSize={hoverLabelSize}
isHorizontal
/>
))}
</Group>
))
}
</BarGroupHorizontal>
</Group>
<AxisLeft
scale={categoryScale}
top={margin.top}
left={margin.left}
hideAxisLine
hideTicks
tickLabelProps={() => {
return {
...leftTickLabelProps(),
className: styles.tickLabel,
dx: "-0.5rem",
dy: defaultLabelDy,
fontSize: `${valueTickLabelSize / 16}rem`,
height: categoryScale.bandwidth(),
};
}}
label={categoryAxisLabel}
labelClassName={styles.axisLabel}
labelOffset={categoryAxisLabelOffset}
labelProps={{
fontSize: `${categoryAxisLabelSize / 16}rem`,
}}
/>
<AxisBottom
scale={valueScale}
top={categoryMax + margin.top}
left={margin.left}
hideAxisLine
hideTicks
numTicks={5}
tickLabelProps={() => {
return {
...bottomTickLabelProps(),
className: styles.tickLabel,
dy: "-0.25rem",
fontSize: `${categoryTickLabelSize / 16}rem`,
verticalAnchor: "start",
};
}}
label={valueAxisLabel}
labelClassName={styles.axisLabel}
labelOffset={valueAxisLabelOffset}
labelProps={{
fontSize: `${valueAxisLabelSize / 16}rem`,
}}
/>
</svg>
</div>
);
}
</div>
<svg width={width} height={height}>
<defs>
{Object.keys(barHoverColorsMap).map((color: string) => {
// remove brackets from colour name to make ids work
const colorId = removeBrackets(color);
return (
<filter key={`glow-${color}`} id={`glow-${colorId}`}>
<feDropShadow
dx="0"
dy="0"
stdDeviation="4"
floodColor={barHoverColorsMap[color]}
/>
</filter>
);
})}
</defs>
<Group top={margin.top} left={margin.left}>
<Group>
{data.map((d, idx) => {
const barName = `${getCategory(d)}-${idx}`;
const barWidth = categoryScale.bandwidth();
const backgroundBarWidth = barWidth / (1 - BAR_PADDING);
return idx % 2 === 0 ? (
<Bar
className={styles.barBackground}
key={`bar-${barName}-background`}
x={0}
y={
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
categoryScale(getCategory(d))! -
(backgroundBarWidth - barWidth) / 2
}
width={valueMax}
height={backgroundBarWidth}
/>
) : null;
})}
</Group>
<GridColumns
scale={valueScale}
height={categoryMax}
numTicks={5}
stroke={Color.label}
strokeWidth={4}
strokeDasharray="10"
strokeLinecap="round"
/>
<BarGroupHorizontal
data={data}
keys={keys}
width={valueMax}
y0={getCategory}
y0Scale={categoryScale}
y1Scale={keyScale}
xScale={valueScale}
color={colorScale}
>
{(barGroups) =>
barGroups.map((barGroup) => (
<Group
key={`bar-group-${barGroup.y0}-${barGroup.index}`}
top={barGroup.y0}
>
{barGroup.bars.map((bar) => (
<HoverableBar
onMouseMove={(e) => {
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: bar.value.toString(),
tooltipTop: eventSvgCoords.y,
tooltipLeft: eventSvgCoords.x,
});
}}
onMouseOut={hideTooltip}
key={`bar-group-bar-${barGroup.y0}-${barGroup.index}-${bar.key}-${bar.index}`}
bar={bar}
valueMax={valueMax}
hoverFillColor={barHoverColorsMap[bar.color]}
hoverLabelSize={hoverLabelSize}
isHorizontal
/>
))}
</Group>
))
}
</BarGroupHorizontal>
<AxisLeft
scale={categoryScale}
hideAxisLine
hideTicks
tickLabelProps={() => {
return {
...leftTickLabelProps(),
className: styles.tickLabel,
dx: "-0.5rem",
dy: defaultLabelDy,
fontSize: `${valueTickLabelSize / 16}rem`,
height: categoryScale.bandwidth(),
};
}}
label={categoryAxisLabel}
labelClassName={styles.axisLabel}
labelOffset={categoryAxisLabelOffset}
labelProps={{
fontSize: `${categoryAxisLabelSize / 16}rem`,
}}
/>
<AxisBottom
scale={valueScale}
top={categoryMax}
hideAxisLine
hideTicks
numTicks={5}
tickLabelProps={() => {
return {
...bottomTickLabelProps(),
className: styles.tickLabel,
dy: "-0.25rem",
fontSize: `${categoryTickLabelSize / 16}rem`,
verticalAnchor: "start",
};
}}
label={valueAxisLabel}
labelClassName={styles.axisLabel}
labelOffset={valueAxisLabelOffset}
labelProps={{
fontSize: `${valueAxisLabelSize / 16}rem`,
}}
/>
</Group>
</svg>
{tooltipOpen && (
<TooltipWrapper
top={tooltipTop}
left={tooltipLeft}
header={tooltipData as string}
></TooltipWrapper>
)}
</div>
);
}
);
interface HoverableBarProps {
bar: BarGroupBarType<string>;
@ -523,16 +594,12 @@ interface HoverableBarProps {
hoverFillColor?: string;
hoverLabelSize?: number;
isHorizontal?: boolean;
onMouseMove?: (e: React.MouseEvent<SVGPathElement, MouseEvent>) => void;
onMouseOut?: () => void;
}
function HoverableBar(props: HoverableBarProps) {
const {
bar,
valueMax,
hoverLabelSize,
hoverFillColor,
isHorizontal = false,
} = props;
const { bar, hoverFillColor, onMouseMove, onMouseOut } = props;
const [isHovered, setIsHovered] = useState(false);
@ -549,6 +616,8 @@ function HoverableBar(props: HoverableBarProps) {
}}
>
<Bar
onMouseMove={onMouseMove}
onMouseOut={onMouseOut}
x={bar.x}
y={bar.y}
width={bar.width}
@ -557,22 +626,6 @@ function HoverableBar(props: HoverableBarProps) {
// apply the glow effect when the bar is hovered
filter={isHovered ? `url(#glow-${colorId})` : undefined}
/>
<Text
className={styles.barText}
x={isHorizontal ? bar.width - BAR_TEXT_PADDING : bar.x + bar.width / 2}
y={
isHorizontal
? bar.y + bar.height / 2
: valueMax - bar.height + BAR_TEXT_PADDING
}
fontSize={
hoverLabelSize ?? (isHorizontal ? bar.height : bar.width) * 0.5
}
textAnchor={isHorizontal ? "end" : "middle"}
verticalAnchor={isHorizontal ? "middle" : "start"}
>
{bar.value}
</Text>
</Group>
);
}

View File

@ -6,21 +6,7 @@
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;
}

View File

@ -1,8 +1,13 @@
import { localPoint } from "@visx/event";
import { Group } from "@visx/group";
import { Point } from "@visx/point";
import Pie, { ProvidedProps } from "@visx/shape/lib/shapes/Pie";
import { Text } from "@visx/text";
import { withTooltip } from "@visx/tooltip";
import React from "react";
import { TooltipWrapper } from "./TooltipWrapper";
import styles from "./PieChart.module.css";
interface PieChartProps {
@ -15,14 +20,6 @@ interface PieChartProps {
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. */
@ -39,105 +36,102 @@ interface PieChartData {
value: number;
}
export function PieChart({
data,
width,
labelWidth,
padRadius = width * 0.35,
innerRadius = width * 0.015,
pieTextSize = 40,
pieTextXOffset = 0,
pieTextYOffset = 10,
getPieDisplayValueFromDatum = (datum: PieChartData) => `${datum.value}%`,
labelTextSize = 40,
labelTextXOffset = 0,
labelTextYOffset = 0,
getLabelDisplayValueFromDatum = (datum: PieChartData) => `${datum.category}`,
className,
}: PieChartProps) {
const pieWidth = width * 0.5 - labelWidth;
return (
<svg className={className} width={width} height={width}>
<Group top={width * 0.5} left={width * 0.5}>
<Pie
data={data}
pieValue={(d: PieChartData) => d.value}
cornerRadius={10}
padAngle={0.075}
padRadius={padRadius}
innerRadius={innerRadius}
outerRadius={pieWidth}
>
{(pie) => (
<PieSlice
{...pie}
pieTextSize={pieTextSize}
pieTextXOffset={pieTextXOffset}
pieTextYOffset={pieTextYOffset}
getPieDisplayValueFromDatum={getPieDisplayValueFromDatum}
/>
)}
</Pie>
<Pie
data={data}
pieValue={(d: PieChartData) => d.value}
innerRadius={pieWidth}
outerRadius={width * 0.5}
>
{(pie) => (
<PieSliceLabel
{...pie}
labelTextSize={labelTextSize}
labelTextXOffset={labelTextXOffset}
labelTextYOffset={labelTextYOffset}
getLabelDisplayValueFromDatum={getLabelDisplayValueFromDatum}
/>
)}
</Pie>
</Group>
</svg>
);
}
type PieSliceProps<PieChartData> = ProvidedProps<PieChartData> & {
pieTextSize: number;
pieTextXOffset: number;
pieTextYOffset: number;
getPieDisplayValueFromDatum: (datum: PieChartData) => string;
};
export function PieSlice({
path,
arcs,
pieTextSize,
pieTextXOffset,
pieTextYOffset,
getPieDisplayValueFromDatum,
}: PieSliceProps<PieChartData>) {
return (
<>
{arcs.map((arc) => {
const [centroidX, centroidY] = path.centroid(arc);
const pathArc = path(arc) as string;
return (
<Group className={styles.group} key={`arc-${arc.data.category}`}>
<path className={styles.piePath} d={pathArc} />
<Text
className={styles.pieText}
x={centroidX + pieTextXOffset}
y={centroidY + pieTextYOffset}
textAnchor="middle"
fontSize={pieTextSize}
export const PieChart = withTooltip<PieChartProps>(
({
data,
width,
labelWidth,
padRadius = width * 0.35,
innerRadius = width * 0.015,
labelTextSize = 40,
labelTextXOffset = 0,
labelTextYOffset = 0,
getLabelDisplayValueFromDatum = (datum: PieChartData) =>
`${datum.category}`,
className,
tooltipOpen,
tooltipLeft,
tooltipTop,
tooltipData,
hideTooltip,
showTooltip,
}) => {
const pieWidth = width * 0.5 - labelWidth;
return (
<div>
<svg className={className} width={width} height={width}>
<Group top={width * 0.5} left={width * 0.5}>
<Pie
data={data}
pieValue={(d: PieChartData) => d.value}
cornerRadius={10}
padAngle={0.075}
padRadius={padRadius}
innerRadius={innerRadius}
outerRadius={pieWidth}
>
{`${getPieDisplayValueFromDatum(arc.data)}`}
</Text>
{({ arcs, path }) => {
return arcs.map((arc) => {
const pathArc = path(arc) as string;
return (
<Group
className={styles.group}
key={`arc-${arc.data.category}`}
>
<path
onMouseMove={(e) => {
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: `${arc.data.category}: ${arc.data.value}%`,
tooltipTop: eventSvgCoords.y,
tooltipLeft: eventSvgCoords.x,
});
}}
onMouseOut={hideTooltip}
className={styles.piePath}
d={pathArc}
/>
</Group>
);
});
}}
</Pie>
<Pie
data={data}
pieValue={(d: PieChartData) => d.value}
innerRadius={pieWidth}
outerRadius={width * 0.5}
>
{(pie) => (
<PieSliceLabel
{...pie}
labelTextSize={labelTextSize}
labelTextXOffset={labelTextXOffset}
labelTextYOffset={labelTextYOffset}
getLabelDisplayValueFromDatum={getLabelDisplayValueFromDatum}
/>
)}
</Pie>
</Group>
);
})}
</>
);
}
</svg>
{tooltipOpen && (
<TooltipWrapper
top={tooltipTop}
left={tooltipLeft}
header={tooltipData as string}
></TooltipWrapper>
)}
</div>
);
}
);
type PieSliceLabelProps<PieChartData> = ProvidedProps<PieChartData> & {
labelTextSize: number;

334
data/personal.ts Normal file
View File

@ -0,0 +1,334 @@
export const P2 = [
"I have a bunch, but it might be singing in A Cappella. I've always loved to sing, and being surrounded by so many talented singers was an absolutely amazing experience.",
"Staying up late night and talking to friends about life",
"Travelling to Alaska during online school",
"Getting my fulltime offer after grinding for over 2 months",
"Thanks Mr. Goose",
"Just spending time with friends ",
"HvZ weeklong, Winter 2018",
"Going on dates with my boyfriend",
"Spending time with my significant other",
"Took someone else's backpack by accident",
"hiking in the snow on the grand river trail",
"Chanting 'Truth Tables, Part Marks!' over and over again with my classmates into our Math 145 Midterm",
"Running around outside with my roommates at night or coming back from a late night session.",
"probably running around SLC in my underwear",
"Spending every Friday with a couple friends, barhopping and getting drunk. Telling stories about your life and having a good time after a long week.",
"Watching White Vhicks at V1 study hall with friends instead of studying",
"nothing comes to mind =(",
"Just sitting in random classrooms with friends, hijacking the projector to play music while “studying” but really just enjoying each others company and procrastinating on work together :)",
"Jamming together with friends on campus",
"Going to class with all the other DDs",
"Watching snow",
"Getting a photo taken with friends at Busan and having it blow up on the subreddit O.o",
"Watching Netflix, playing Catan and drinking bubble tea with the boys. Simple but everything. That or anytime in SERVE in 2018/2019.",
"Taiwan trip with UW friends",
"Go shopping hand in hand with the opposite sex for the first time.",
"Meeting my girlfriend in Math 135",
"watching movies and drinking with friends",
"Looking forward to eat from some restaurant",
"meeting friends",
"the 'good ol days'. the first 2 years here were so nice, so much exploration and novelty",
"Nothing specific, but the moments I spent at V1 Center during my first year were very nice.",
"Waking up 6:50 in the morning for class and climbing a mountain of snow because my google map headed me to the wrong direction.",
"Time spent in clubs",
"celebrating with classmates/roommates after exams",
"Probably the first time I got a job I was really interested in.",
"Singing with my friends while having some beers",
"My buddy's bday party in 2B was definitely one of the best, we turned way tf upppp",
];
export const P3 = [
"Have fun! Don't overthink everything and just go for it.",
"Join intramural basketball regardless of skill level",
"you fail 100% of the exams you don't show up to",
"Actually doing hobbies outside of school is so valuable",
"Chill",
"Going into continuous can sometimes be a better decision than accepting a job you don't want in main round",
"Take your time. Everything is going to be okay, there's no rush to do anything.",
"Spend more time on the things you enjoy doing and don't worry about marks too much.",
"Don't be so stressed out about co-op, and don't stay home so much D",
"Life will be ok",
"Stop worrying about grades, make more time to meet new people and pursue your hobbies.",
"Take it easier, buy GME stock",
"Keep your toughness",
"Do not consume alcohol or other substances that inhibit your ability to make good decisions",
"Listen to others talk more; be less cringe",
"diversify your interests",
"talk to more people",
"There actually is light at the end of the tunnel... no seriously",
"Chill the f out and be yourself",
"you're gonna be a legend some day",
"Try and figure out who you are and what you stand for. Be someone who is irreplaceable and surround yourself with people that you want to be.",
"Get your resume roasted more.",
"Get out of your comfort zone by meeting more people and trying new things!",
"drink and eat edibles !",
"stop being religious, go to parties, be open minded",
"Its okay to take breaks from work!! Go get food with a friend, say yes to the spontaneous outing! Go to that weekend hackathon! youll be okay and you wont fall behind!",
"Stick with theatre! Its a lot of work but its fun after all",
"Put yourself and everything you want first!",
"Drink less",
"Pick a different program",
"Grades don't matter",
"Try more new things, uni goes by quicker than you think",
"Don't stay at home and study all night. Enjoy university life before it's over. With that said first year low-key brutal so study hard",
"Try to experience life beyond school and coop",
"u r the best people to make money !",
"Chill out my guy.",
"Join a company you love.",
"Dont copy assignments, getting a 60 and actually understanding the material is better than getting a 90 and not, you will pay for it come finals. ",
"Stop worrying about grades so much and go meet new people, nobody cares after graduation",
"ur not special, and also you should draw more",
"Try to make more friends and be more social",
"staying on track",
"Put yourself out there more while school is still in person. ",
"Your hard work paid off! good job.",
"The geese won't actually attack you. ",
"Get organized with your calendar, make a grade calculator, and find friends in every class",
"You guys did really well.",
"Take it easy on yourself. It's ok to not be perfect.",
"Just pass the classes. Don't even consider caring about learning or doing well in them. ",
"It gets better, but you've also gotta get better. It's not high school anymore, you've gotta change how you study!",
"STOP PLAYING LEAGUE and go get involved in school activities!",
"Dont get in a relationship ",
"You're not unloveable. Don't take this time for granted. Make lots of friends, work on yourself, have lots of sex. It's a process!",
];
export const P4 = [
"If you don't believe in yourself, nobody else will.",
"I see says the blind man",
"lift to gain not to lift the weights",
"It smells like updog in here",
"The most important step someone can take isn't the first one. It's the next one.",
"Don't choke on your aspirations Darth Vader",
"The imagination is precious. Don't lose it. Don't lose the child in you.",
"“He who has a why to live for can bear almost any how.”",
"Due tomorrow do tomorrow",
"Always double and triple check, just to be safe.",
"All the lovely math theorems",
"Listen to others talk more; be less cringe",
"Not all those who wander are lost",
"Learning is like rowing upstream, not to advance is to fall behind. (More precisely, 學如逆水行舟,不進則退)",
"We do this, not because it is easy, but because it is hard",
"Your strength will come from self acceptance. ",
"Run it",
"Live each week as you would want to live every week",
"Do it, otherwise you'll regret not doing it.",
"Comparison is the thief of joy",
"I am the master of my fate, I am the captain of my soul. Invictus poem.",
"Just because you may have gotten a bit lucky, doesnt mean you dont deserve it",
"It matters not how strait the gate, How charged with punishments the scroll, I am the master of my fate, I am the captain of my soul.",
"Carpe diem",
"Believe in yourself",
"Yesterday is history, tomorrow is a mystery, but today is a gift. Thats why it is called the present Master Oogway, Kung Fu Panda",
"C'est la vie",
"TIME IS MONEY",
"Selfdistrust is the cause of most of our failures.",
"Treat others how you would like to be treated",
"Nothing in life is more important than happiness.",
"If you're not enough without it, you'll never be enough with it. ",
"Due tomorrow, do tomorrow",
"The best is yet to come",
"Expect disappointment and you will never be disappointed",
"F*** it",
];
export const P5 = [
"Took notes for me while I fell asleep.",
"They brought me McDonald's when I had a late exam",
"ask me out",
"Lent me a pencil",
"Gave me all their notes when I lost my notebook",
"Did my assignment",
"Be in a committed relationship with me",
"Compiled my code every time I wanted to test it, for a whole semester, because I was too lazy to set up the student environment",
"Teaching me even though they are not currently taking the course",
"i had a classmate who got a 17% on an assignment, and that gave me a great chuckle",
"I think sometimes the most touching thing someone can do for me is just ask if I'm doing okay and genuinely care about the answer.",
"Gave me class notes",
"Held my hand and guided me through my hell of a first year.",
"Let me stay/live at their place for an extended period of time.",
"saved a seat for me every class in Daves cs135 packed section strangers at the time but still friends to this day <3",
"One time after having a heavy conversation after a club meeting, I was sitting alone on campus looking kind of down. A stranger came up to me and asked me if I was okay and chatted with me for a bit.",
"My clicker died in class and my classmate swapped their clickers batteries with me for very question!",
"Help me out with an assignment, by helping me come up with multiple solutions to each question to ensure I understood it properly",
"My roommate brought my clicker to school while I was in the middle of class",
"Offered to help me not fail japanese ",
"Be concerned about your life.",
"Sent me assignment answers ",
"Answer my question when I was confused in class? idk",
"give iclicker answers",
"Walked me through assignments so I could learn to get the right answers",
"Helped me out for an assignment question that i struggled for days",
"Brought me food after a family member died right before finals which i was forced to take. ",
"IDK, strike up a conversation randomly while waiting.",
"Bake me a cake",
];
export const P6 = [
"Have a routine. Have breakfast, workout, and make time for hobbies. Also reframing perspective; helps you to see the positives in situations.",
"Movie tickets are cheaper when you buy them at slc turnkey",
"always carry a condom on campus",
"Install autojump and fig for your terminal, use Atom as your text editor.",
"Compile your code with -fsanitize=address",
"Set the bar so low that you can't trip over it",
"Cook 4 portions of food each time. Now you won't have to cook for the next day or two.",
"Keep things in perspective. Read about the struggles of those less fortunate.",
"Go to the MathSoc office (MC 3038) for free snacks if you're hungry while on campus, and too lazy to go buy actual food.",
"When it comes to school/career, build your meta-skills first. Before studying, always think about how to study.",
"Figuring out what you want, finding out the steps to get there, and executing the steps",
"Go to Needles Hall for your class overrides... they don't care if you have an advisor signature, as long as you have a prof signature.... aka, step aside advisors lol",
"Always ask for forgiveness, never permission. ",
"optimize your bird courses and your undergrad becomes a breeze",
"Travel when you're on co-op. Know that if you have extra funds, it's always nice to do some exploring on weekends. You'll regret not doing so if you get the opportunity. Also, I try to sit next to someone new every term. Say hi to people on the first class, and if you don't vibe, try someone new next class. I've met friends in every term by doing this.",
"Sleep enough and exercise. Also drink enough water.",
"download linux and do all your work on it. THen after the conditioning, when you boot into linux, you are 110% focused.",
"submitting half an assignment is better than submitting no assignment at all",
"Let the rice cool down so it doesn't stick to the rice cooker",
"Sleep is important",
"You get more motivation when the assignment is due in a few hours",
"Document your life. Vlog or photos. You'll want to look back on them ",
"Live a self-disciplined life.",
"Air fryers are the best",
"be critical of people's opinions, lots of people bulls*** frequently & have bad sources for their opinions",
"Get organized with your calendar, make a grade calculator, and find friends in every class",
"Onky do things that make u happy",
"Get an oversized bowl and spoon for meals. Cook food, dump into bowl, mix, scoop into mouth. ",
"you can avoid bad weather by using the bridges/tunnels that connect most buildings",
"Get a rice cooker. You can make so much s*** in it, and you'll stop messing up rice.",
"Crowdmark PWA bookmark on iPhone with fingerprint-controlled password manager. Makes handing in assignments at the last minute much easier just take pic with CamScanner, export to photos, tap Crowdmark 'app', fingerprint to log in, and upload pic from photos library directly to Crowdmark without stumbling over typing your password at 11:58.",
"Eat healthy and move ",
"F*** PD",
];
export const P7 = [
"Time is fleeting! It's insane that I've spent almost 5 years as a UW student. As I move into the next stage of my life and complete that, I'm sure I'll feel the same. So, lesson is live in the moment and do what you want to do.",
"Instead of cali or bust, we should have dream or bust. Don't chase after the hype! Figure out what you are passionate about and run with it.",
"people r real idiots and they'll only get dumber",
"Taking care of my mental health is the most important thing. Coffee works.",
"Grind never stops",
"Just saying 'yes' can lead you to a lot of new experiences.",
"Capitalism is a meat grinder that grants us with lots of nice amenities.",
"Patience",
"People aren't judging you as much as you think they are.",
"No pain no gain",
"Past a certain point, the efforts you invest towards a goal are more important than your innate talent",
"I'm not the smartest guy I know",
"Even if you don't think you can do it, you can probably push through and make it.",
"you have to let go if you want to be free",
"Jump at every opportunity. Say yes to more things. You'll find yourself meeting more people, potentially meeting your next best friend or next SO. You might also find yourself advancing faster career-wise/co-op-wise simply because you're taking on new challenges.",
"To enjoy life in moments.",
"people who work full time can be equally as clueless, if not more clueless, as you are",
"Everyone is just as scared and clueless as you, so crack a joke, compliment someone, start a conversation!",
"Friendships are important",
"Grades don't matter",
"Learn how to do multiple choice questions. Look for contradictory answers. Look for long answers. Look for too specific answers. ",
"One day at a time.",
"Mass media and politics.",
"At the end of the day its your life, do what makes you happy and what you will remember 20 years from now. ",
"I took living with my parents for granted",
"going to office hours",
"How to move on from rejection/failure. ",
"perceptions are as important than work output in the real world",
"Nobody cares about your grades. ",
"Your friends will get you through",
"I am now on my own, and i need to deal with whatever comes by myself",
"Improve myself. ",
"It's fine to take L's from time to time. You can't always win.",
"Doing something is better than nothing ",
"that noone cares about you. That is both a good thing (noone remembers your f****ups) and also a bad thing (casual indifference of society is hard). ",
];
export const P8 = [
"Spending time with friends and constantly pursuing what I love. Right now that means playing as many sports as possible and making time to see my friends.",
"Being Christian",
"morning coffee",
"Playing music and teaching others",
"Spending time with close friends",
"Seeing beauty when looking at ordinary things from a different angle, I try to do that as much as possible when shooting photos as a hobby.",
"My relationships with others",
"Kyu Min",
"Being able to pursue my hobbies.",
"Being able to put efforts into work and seeing the results.",
"Sharing my achievements with my significant other",
"Friends",
"being alive",
"I'm 22, and I have a good amount of stories to tell... and I'm just getting started ;)",
"Eating and spending time with my friends",
"my ability to break the rules",
"My relationship ",
"Being able to spend time with those that I love.",
"My ability to be independent",
"rock climbing",
"sports lol",
"my friends!! i love them so much",
"Having friends and a girlfriend who support me and I can be myself with and have fun with",
"Friends and Boyfriend!",
"Spending time with my friends",
"Family/Friends",
"I finally go to the gym not out of hatred",
"Friends, games, shows",
"MY HEALTH",
"Personal growth.",
"In order to study music。",
"my friends and girl friend ",
"i like drawing",
"Feeling like I secured my future after university",
"My relationship with my parents improved a lot over university.",
"my personality and strengths",
"To be honest, feeling more mature mentally and mathematically.",
"My boyfriend",
"Music and anime and BL novel",
"my besties",
"My high school friends",
"Making stuff that makes others happier or people can find useful.",
"My friends",
"Tacos",
];
export const P9 = [
"Take a genuine interest in class material",
"have friends not in CS, the perspective of how life is enjoyable without the competition of coop is nice",
"Don't give up. It gets better",
"Prioritize your mental health, because if you're under the weather, it affects almost everything else in your life. Also, coffee works, kids",
"The hardest problems are solved with some food and a good night's sleep.",
"Treat co-op as a 6th course",
"Your time here is so precious and it goes by so quick, make sure to make lots of memories and try new things as often as you can. You likely won't have another period in your life when you're as free and as able-bodied as now.",
"Cs get degrees. You experience life as longer when undergoing new experiences, so try lots of new things!",
"Call || bust actually works! ",
"Learn. To. Cook. It's healthier and cheaper.",
"Keep your GPA high if you want to do grad school",
"Try to figure out what you want to do after graduation as soon as possible. Invest your time through classes and co-ops towards this goal. Focused effort in one direction is almost always better than scattered efforts everywhere.",
"Live your life, storytellably",
"Don't stress too much about jobs and grades, try to enjoy your time with friends and make some good memories. Things always work out.",
"legends live forever. rebels never die.",
"Go work out more, it's important and it'll help your mental health. Sleeping is actually more important than looking 'cool' and not sleeping. Getting 7-8 hours feels great and is very much worth it. Choose your friends wisely. Not everyone needs to be your friend and you choose who you allot that time to. Like people say, you are the average of the 5 people that surround you. Give yourself a break. It's okay to fail and it's okay to stop. Learn when to do so. Take some fun electives or electives that make you think differently (than just programming all the time). It's nice to be more well rounded despite all the hate on breadth and depth courses. I recommend PHIL121, PHIL101, RS121.",
"Don't compare yourself to others",
"co-op doesn't matter. In fact I would recommend not doing co-op; do something your passionate about during summer.",
"try and make at least one friend here. there are great people at this school, they help make your life much easier by being there with you when you struggle through tough courses and unsuccessful coop recruitment cycles. it really does make a world of a difference.",
"Don't stress too much about job search",
"Still care about your grades but dont let it consume your life :)",
"Make sure to spend time doing social/fun clubs. If you don't feel you have the time for this, you are too busy and need to make changes to be less busy.",
"Make sure to have time for yourself and your friends",
"Grades don't matter",
"Try to experience life beyond school and coop",
"JUST ENJOY UR SCHOOLS LIFE",
"Youll get through it :)",
"Students are more willing to learn and do better when their intrinsic motivation is stronger than their extrinsic motivation.",
"School teaches you how to be diligent and hardworking at a job. Grades arent the most important thing, just pass and do your best",
"CS is hard and you just gotta focus and grind out the work, but dont stress too much on the actual grade cause within the next year you (and everyone else) wont care what grade you got.",
"make time for your hobbies it's good for the brains or whatever",
"Meet people, make friends and have fun, you don't need to study that hard and grades aren't everything to be concerned about",
"Its a marathon not a sprint. Go to class. ",
"don't be worried, youll do fine & enjoy your time",
"Manage your time and don't feel too much pressure to get high grades. It does not matter in the long run. ",
"Get organized with your calendar, make a grade calculator, and find friends in every class",
"If you didn't like CS in high school dont think that you will like it in university, even if you enjoyed Racket.",
"Classes are easy if you want them to be. Do the minimum but not less",
"start assignments early",
"Try to not compare yourself with others too much. It's still helpful to do so at times, but sometimes, you've gotta accept that some people may end up doing better than you but that's fine, focus on yourself!",
"Make friends",
"Have fun, enjoy your time, thing will happen soon enough ",
"Grind leetcode.",
"Enjoy your life. F*** SCHOOL. have fun",
];

110
pages/personal.tsx Normal file
View File

@ -0,0 +1,110 @@
import { P2, P3, P4, P5, P6, P7, P8, P9 } from "data/personal";
import { pageRoutes } from "data/routes";
import React from "react";
import { BottomNav } from "@/components/BottomNav";
import { ComponentWrapper } from "@/components/ComponentWrapper";
import { Header } from "@/components/Header";
import { QuotationCarousel } from "@/components/QuotationCarousel";
import { SectionHeader } from "@/components/SectionHeader";
import styles from "./samplePage.module.css";
export default function Personal() {
return (
<div className={styles.page}>
<Header />
<SectionHeader title="Personal" subtitle="Life lessons from students" />
<ComponentWrapper
heading="What is your favourite memory from your time at UW?"
bodyText="Most students' favourite memory relates to their time spent with friends and significant ones! This goes to show how friends are an important factor to our happiness! :D"
align="right"
>
<div className={styles.quotationCarouselContainer}>
<QuotationCarousel data={P2} circleDiameter={0} height={300} />
</div>
</ComponentWrapper>
<ComponentWrapper
heading="What is one thing that you wish you could tell your first year self?"
bodyText="In summary, don't spend too much time worrying about your grades or coop during university. Develop new hobbies, be more open-minded, and talk to more people! There's a lot to experience in university that you don't want to miss or regret! Drink responsibly!"
noBackground
>
<div className={styles.quotationCarouselContainer}>
<QuotationCarousel data={P3} circleDiameter={0} height={300} />
</div>
</ComponentWrapper>
<ComponentWrapper
heading="What is your favourite inspirational quote/words to live by?"
align="right"
>
<div className={styles.quotationCarouselContainer}>
<QuotationCarousel data={P4} circleDiameter={0} height={300} />
</div>
</ComponentWrapper>
<ComponentWrapper
heading="What is the nicest thing a classmate did for you?"
bodyText="Any small act of kindness can make someone else's day and remain etched in their memory for a lifetime! We see that a lot of the respondants' classmates helped them with their assignments and exams. Some participants remember fondly when their classmate asked them if they were ok and struck a conversation with them!"
noBackground
>
<div className={styles.quotationCarouselContainer}>
<QuotationCarousel data={P5} circleDiameter={0} height={300} />
</div>
</ComponentWrapper>
<ComponentWrapper
heading="What is the best life hack/tip?"
bodyText="We have a range of life hacks around submitting assignments, coding shortcuts, living a healthy life (sleeping and eating well), etc. Take notes! ;)"
align="right"
>
<div className={styles.quotationCarouselContainer}>
<QuotationCarousel data={P6} circleDiameter={0} height={300} />
</div>
</ComponentWrapper>
<ComponentWrapper
heading="What is the best lesson you learned from your time at UW?"
bodyText="In short, always say 'yes' to opportunities as it can lead you to new experiences in life that you may enjoy! A lot of the small insignificant things you worry about won't matter at the end. Take care of yourself. Most importantly, enjoy your life is a message that most students agree with."
noBackground
>
<div className={styles.quotationCarouselContainer}>
<QuotationCarousel data={P7} circleDiameter={0} height={300} />
</div>
</ComponentWrapper>
<ComponentWrapper
heading="What is one aspect of your life that makes you the happiest?"
bodyText="Relationships whether friendships or romantic ones and hobbies are the aspects of CS 2022 students' life that make them the happiest!"
align="right"
>
<div className={styles.quotationCarouselContainer}>
<QuotationCarousel data={P8} circleDiameter={0} height={300} />
</div>
</ComponentWrapper>
<ComponentWrapper
heading="What advice would you give current/future CS students at UW?"
bodyText="Big recap: Socialize. Take care of yourself and your mental health. Do things beyond school and coop."
noBackground
align="center"
>
<div className={styles.quotationCarouselContainer}>
<QuotationCarousel
data={P9}
circleDiameter={0}
height={500}
width={800}
/>
</div>
</ComponentWrapper>
<BottomNav
leftPage={pageRoutes.mentalHealth}
rightPage={pageRoutes.contributors}
></BottomNav>
</div>
);
}