Created TooltipWrapper (#88)

Co-authored-by: shahanneda <shahan.neda@gmail.com>
Reviewed-on: #88
Reviewed-by: Mark Chiu <e26chiu@csclub.uwaterloo.ca>
new-branch-test
Shahan Nedadahandeh 2 months ago
parent e061850575
commit 97aa2261f2
  1. 32
      components/Boxplot.module.css
  2. 26
      components/Boxplot.tsx
  3. 14
      components/LineGraph.module.css
  4. 381
      components/LineGraph.tsx
  5. 17
      components/StackedBarGraph.module.css
  6. 20
      components/StackedBarGraph.tsx
  7. 31
      components/TooltipWrapper.module.css
  8. 35
      components/TooltipWrapper.tsx
  9. 15
      components/WordCloud.module.css
  10. 14
      components/WordCloud.tsx
  11. 52
      pages/samplePage.tsx

@ -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);
}
}

@ -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<StatsPlotProps, TooltipData>(
</svg>
{tooltipOpen && tooltipData && (
<Tooltip
top={tooltipTop}
<TooltipWrapper
left={tooltipLeft}
className={styles.tooltip}
unstyled
top={tooltipTop}
header={tooltipData.category}
>
<p className={styles.category}>{tooltipData.category}</p>
<div className={styles.toolTipData}>
<p>max: {tooltipData.max}</p>
<p>third quartile: {tooltipData.thirdQuartile}</p>
<p>median: {tooltipData.median}</p>
<p>first quartile: {tooltipData.firstQuartile}</p>
<p>min: {tooltipData.min}</p>
</div>
</Tooltip>
<p>max: {tooltipData.max}</p>
<p>third quartile: {tooltipData.thirdQuartile}</p>
<p>median: {tooltipData.median}</p>
<p>first quartile: {tooltipData.firstQuartile}</p>
<p>min: {tooltipData.min}</p>
</TooltipWrapper>
)}
</div>
);

@ -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;

@ -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<LineGraphProps, TooltipData>(
({
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 xLength = data.xValues.length;
const { containerRef, TooltipInPortal } = useTooltipInPortal({
// use TooltipWithBounds
detectBounds: true,
// when tooltip containers are scrolled, this will correctly update the Tooltip position
scroll: true,
});
data.lines.forEach((line) => {
if (line.yValues.length != xLength) {
throw new Error("Invalid data with wrong length.");
}
});
const yMax = height - margin.top - margin.bottom;
const xMax = width - margin.left - margin.right;
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 };
const actualData = data.lines.map((line) => {
return line.yValues.map((val, idx) => {
return { x: data.xValues[idx], y: val };
});
});
});
const yMaxValue = Math.max(
...data.lines.map((line) => {
return Math.max(...line.yValues);
})
);
const yMaxValue = Math.max(
...data.lines.map((line) => {
return Math.max(...line.yValues);
})
);
// data accessors
const getX = (d: PointData) => d.x;
const getY = (d: PointData) => d.y;
// data accessors
const getX = (d: PointData) => d.x;
const getY = (d: PointData) => d.y;
// scales
const xScale = scaleBand({
range: [0, xMax],
domain: data.xValues,
});
// scales
const xScale = scaleBand({
range: [0, xMax],
domain: data.xValues,
});
const yScale = scaleLinear<number>({
range: [0, yMax],
nice: true,
domain: [yMaxValue, 0],
});
const yScale = scaleLinear<number>({
range: [0, yMax],
nice: true,
domain: [yMaxValue, 0],
});
const keys = data.lines.map((line) => line.label);
const keys = data.lines.map((line) => line.label);
const legendScale = scaleOrdinal<string, string>({
domain: keys,
range: [Color.primaryAccent, Color.secondaryAccent],
});
const legendScale = scaleOrdinal<string, string>({
domain: keys,
range: [Color.primaryAccent, Color.secondaryAccent],
});
return (
<div
className={className ? `${className} ${styles.wrapper}` : styles.wrapper}
style={{
flexDirection: legendPosition === "right" ? "row" : "column-reverse",
}}
>
<svg ref={containerRef} width={width} height={height}>
<Group top={margin.top} left={margin.left}>
<GridColumns
scale={xScale}
height={yMax}
left={margin.left}
numTicks={5}
stroke={Color.tertiaryBackground}
strokeWidth={4}
strokeDasharray="10"
strokeLinecap="round"
/>
<GridRows
scale={yScale}
width={xMax}
left={margin.left * 2.3}
numTicks={data.xValues.length}
stroke={Color.tertiaryBackground}
strokeWidth={4}
strokeDasharray="10"
strokeLinecap="round"
/>
<AxisBottom
scale={xScale}
top={margin.top + yMax}
left={margin.left}
hideAxisLine
hideTicks
tickLabelProps={() => {
return {
...bottomTickLabelProps(),
className: styles.tickLabel,
dy: "-0.25rem",
fontSize: `${xTickLabelSize / 16}rem`,
width: xScale.bandwidth(),
};
}}
/>
<AxisLeft
scale={yScale}
left={margin.left}
hideAxisLine
hideTicks
numTicks={5}
tickLabelProps={() => {
return {
...leftTickLabelProps(),
className: styles.tickLabel,
dx: "1.25rem",
dy: "0.25rem",
fontSize: `${yTickLabelSize / 16}rem`,
};
}}
/>
<Group left={margin.left + xMax / (data.xValues.length * 2)}>
{actualData.map((lineData, i) => {
const isEven = i % 2 === 0;
return (
<Group key={`line-${i}`}>
<LinePath
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: 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}
/>
</Group>
);
})}
</Group>
</Group>
</svg>
<LegendOrdinal
className={styles.legend}
return (
<div
className={
className ? `${className} ${styles.wrapper}` : styles.wrapper
}
style={{
marginTop: legendMargin.top,
marginRight: legendMargin.right,
marginBottom: legendMargin.bottom,
marginLeft: legendMargin.left,
fontSize: legendLabelSize,
flexDirection: legendPosition === "right" ? "row" : "column-reverse",
}}
scale={legendScale}
direction={legendPosition === "right" ? "column" : "row"}
itemMargin={
legendPosition === "right"
? `calc(${legendItemGap / 2}rem / 16) 0 calc(${
legendItemGap / 2
}rem / 16) 0`
: `0 calc(${legendItemGap / 2}rem / 16) 0 calc(${
legendItemGap / 2
}rem / 16)`
}
/>
>
<svg width={width} height={height}>
<Group top={margin.top} left={margin.left}>
<GridColumns
scale={xScale}
height={yMax}
left={margin.left}
numTicks={5}
stroke={Color.tertiaryBackground}
strokeWidth={4}
strokeDasharray="10"
strokeLinecap="round"
/>
<GridRows
scale={yScale}
width={xMax}
left={margin.left * 2.3}
numTicks={data.xValues.length}
stroke={Color.tertiaryBackground}
strokeWidth={4}
strokeDasharray="10"
strokeLinecap="round"
/>
<AxisBottom
scale={xScale}
top={margin.top + yMax}
left={margin.left}
hideAxisLine
hideTicks
tickLabelProps={() => {
return {
...bottomTickLabelProps(),
className: styles.tickLabel,
dy: "-0.25rem",
fontSize: `${xTickLabelSize / 16}rem`,
width: xScale.bandwidth(),
};
}}
/>
<AxisLeft
scale={yScale}
left={margin.left}
hideAxisLine
hideTicks
numTicks={5}
tickLabelProps={() => {
return {
...leftTickLabelProps(),
className: styles.tickLabel,
dx: "1.25rem",
dy: "0.25rem",
fontSize: `${yTickLabelSize / 16}rem`,
};
}}
/>
<Group left={margin.left + xMax / (data.xValues.length * 2)}>
{actualData.map((lineData, i) => {
const isEven = i % 2 === 0;
return (
<Group key={`line-${i}`}>
<LinePath
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: 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}
/>
</Group>
);
})}
</Group>
</Group>
</svg>
<LegendOrdinal
className={styles.legend}
style={{
marginTop: legendMargin.top,
marginRight: legendMargin.right,
marginBottom: legendMargin.bottom,
marginLeft: legendMargin.left,
fontSize: legendLabelSize,
}}
scale={legendScale}
direction={legendPosition === "right" ? "column" : "row"}
itemMargin={
legendPosition === "right"
? `calc(${legendItemGap / 2}rem / 16) 0 calc(${
legendItemGap / 2
}rem / 16) 0`
: `0 calc(${legendItemGap / 2}rem / 16) 0 calc(${
legendItemGap / 2
}rem / 16)`
}
/>
{tooltipOpen && (
<TooltipInPortal
top={tooltipTop}
left={tooltipLeft}
className={styles.tooltip}
unstyled
applyPositionStyle
>
<>{tooltipData}</>
</TooltipInPortal>
)}
</div>
);
}
{tooltipOpen && (
<TooltipWrapper
top={tooltipTop}
left={tooltipLeft}
header={tooltipData as string}
></TooltipWrapper>
)}
</div>
);
}
);

@ -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;
}

@ -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<
</div>
{tooltipOpen && tooltipData ? (
<Tooltip
className={styles.toolTip}
<TooltipWrapper
top={tooltipTop}
left={tooltipLeft}
unstyled
header={tooltipData.key}
>
<p className={styles.key}>{tooltipData.key}</p>
<p>{tooltipData.bar.data[tooltipData.key]}</p>
<p>{getCategory(tooltipData.bar.data)}</p>
</Tooltip>
</TooltipWrapper>
) : null}
</div>
);
@ -438,16 +438,14 @@ export const StackedBarGraphHorizontal = withTooltip<
</div>
{tooltipOpen && tooltipData ? (
<Tooltip
className={styles.toolTip}
<TooltipWrapper
top={tooltipTop}
left={tooltipLeft}
unstyled
header={tooltipData.key}
>
<p className={styles.key}>{tooltipData.key}</p>
<p>{tooltipData.bar.data[tooltipData.key]}</p>
<p>{getCategory(tooltipData.bar.data)}</p>
</Tooltip>
</TooltipWrapper>
) : null}
</div>
);

@ -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;
}

@ -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 (
<Tooltip
top={top}
left={left}
className={`${styles.tooltip} ${className ?? ""}`}
unstyled
applyPositionStyle
>
{header ? <span className={styles.header}>{header}</span> : null}
{children ? <div className={styles.body}>{children}</div> : null}
</Tooltip>
);
};
export { TooltipWrapper };

@ -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);
}

@ -2,13 +2,15 @@ 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";
interface WordCloudProps {
@ -101,17 +103,13 @@ export const WordCloud = withTooltip(
/>
{tooltipOpen && tooltipData ? (
<TooltipWithBounds
<TooltipWrapper
// set this to random so it correctly updates with parent bounds
key={Math.random()}
top={tooltipTop}
left={tooltipLeft}
className={styles.tooltip}
unstyled
applyPositionStyle
>
{tooltipData.text} ({tooltipData.value})
</TooltipWithBounds>
header={`${tooltipData.text} (${tooltipData.value})`}
></TooltipWrapper>
) : null}
</div>
);

@ -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";
@ -53,7 +60,6 @@ export default function SamplePage() {
margin={defaultVerticalBarGraphMargin}
/>
</ComponentWrapper>
<ComponentWrapper
heading="What program are you in?"
bodyText="There are a total of 106 respondents of the CS Class Profile. Interestingly, there are a huge number of students that are just in CS, partially due to the overwhelming number of people in CS as seen in the total demographics."
@ -69,7 +75,6 @@ export default function SamplePage() {
height={defaultGraphHeight}
/>
</ComponentWrapper>
<ComponentWrapper heading="What program are you in?" align="right">
<BarGraphHorizontal
className={styles.barGraphDemo}
@ -80,7 +85,6 @@ export default function SamplePage() {
margin={defaultHorizontalBarGraphMargin}
/>
</ComponentWrapper>
<ComponentWrapper
heading="What program are you in?"
bodyText="There are a total of 106 respondents of the CS Class Profile. Interestingly, there are a huge number of students that are just in CS, partially due to the overwhelming number of people in CS as seen in the total demographics."
@ -95,7 +99,6 @@ export default function SamplePage() {
margin={defaultHorizontalBarGraphMargin}
/>
</ComponentWrapper>
<ComponentWrapper heading="What program are you in?" align="left">
<BarGraphHorizontal
className={styles.barGraphDemo}
@ -105,7 +108,6 @@ export default function SamplePage() {
margin={defaultHorizontalBarGraphMargin}
/>
</ComponentWrapper>
<ComponentWrapper
heading="What program are you in?"
bodyText="There are a total of 106 respondents of the CS Class Profile. Interestingly, there are a huge number of students that are just in CS, partially due to the overwhelming number of people in CS as seen in the total demographics."
@ -121,7 +123,6 @@ export default function SamplePage() {
height={defaultGraphHeight}
/>
</ComponentWrapper>
<ComponentWrapper heading="What program are you in? " align="right">
<WordCloud
data={moreMockCategoricalData.map((word) => ({
@ -142,7 +143,6 @@ export default function SamplePage() {
height={defaultGraphHeight}
/>
</ComponentWrapper>
<ComponentWrapper heading="What program are you in?" align="left">
<WordCloud
data={moreMockCategoricalData.map((word) => ({
@ -153,7 +153,6 @@ export default function SamplePage() {
height={defaultGraphHeight}
/>
</ComponentWrapper>
<ComponentWrapper
heading="What program are you in?"
bodyText="There are a total of 106 respondents of the CS Class Profile. Interestingly, there are a huge number of students that are just in CS, partially due to the overwhelming number of people in CS as seen in the total demographics."
@ -168,7 +167,6 @@ export default function SamplePage() {
margin={defaultHorizontalBarGraphMargin}
/>
</ComponentWrapper>
<ComponentWrapper
heading="What program are you in?"
bodyText="There are a total of 106 respondents of the CS Class Profile. Interestingly, there are a huge number of students that are just in CS, partially due to the overwhelming number of people in CS as seen in the total demographics."
@ -182,18 +180,34 @@ export default function SamplePage() {
margin={defaultHorizontalBarGraphMargin}
/>
</ComponentWrapper>
<ComponentWrapper heading="What program are you in?" align="center">
<WordCloud
data={moreMockCategoricalData.map((word) => ({
text: word.key,
value: word.value,
}))}
width={defaultGraphWidth}
height={defaultGraphHeight}
<BoxPlot
width={600}
height={400}
data={mockBoxPlotData}
margin={{
top: 20,
left: 20,
}}
/>
</ComponentWrapper>
<ComponentWrapper
heading="What program are you in?"
align="right"
bodyText="Pariatur deserunt aute laborum ea adipisicing. Labore labore ipsum duis nisi ea incididunt ipsum occaecat. Ut occaecat et exercitation incididunt sit sit duis deserunt velit culpa nisi et dolore."
>
<LineGraph
data={mockLineData}
width={600}
height={400}
margin={{
top: 20,
bottom: 80,
left: 30,
right: 20,
}}
/>
</ComponentWrapper>
<BottomNav
leftPage={pageRoutes.demographics}
rightPage={pageRoutes.contributors}

Loading…
Cancel
Save