Add BoxPlot component (Closes #6) (#34)
continuous-integration/drone/push Build is passing Details

Done:

- [x] Display boxplot with left and bottom axis in `playground.tsx`
- [x] Add mock data for boxplot
- [x] Hovering over boxplot displays a Tool Tip box.

My version:
![image](/attachments/6c8c4499-a1bd-4434-9230-7117266691ea)

Note:

 - No percentage displayed next to value axis labels.
 - We can add outlier points although it could complicate the graph.
 - Still displays the following error:
 	- Error: Hydration failed because the initial UI does not match what was rendered on the server.
    - Error: Text content does not match server-rendered HTML.
    - Error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.

    Staging: https://boxplot-component-csc-class-profile-staging-snedadah.k8s.csclub.cloud/
Co-authored-by: Miniapple8888 <miniapple8888@gmail.com>
Co-authored-by: Emily Chiu <e26chiu@corn-syrup.csclub.uwaterloo.ca>
Co-authored-by: e26chiu <e26chiu@csc.uwaterloo.ca>
Reviewed-on: #34
Reviewed-by: Amy <a258wang@csclub.uwaterloo.ca>
This commit is contained in:
Mark Chiu 2022-09-03 11:13:58 -04:00
parent a2dbcb90c6
commit 9526f1b0f5
6 changed files with 519 additions and 1 deletions

View File

@ -0,0 +1,38 @@
.boxplot {
fill: var(--primary-accent-light);
}
.boxplot:hover {
fill: var(--primary-accent);
filter: drop-shadow(0 0 calc(4rem / 16) var(--primary-accent));
}
.tooltip {
font-family: "Inconsolata", monospace;
top: 0;
left: 0;
position: absolute;
background-color: var(--label);
color: var(--primary-background);
pointer-events: none;
padding: calc(10rem / 16);
border-radius: calc(10rem / 16);
}
.tooltip .category {
margin: calc(10rem / 16) 0 0 0;
font-size: calc(16rem / 16);
font-weight: 700;
}
.tooltip .toolTipData {
margin-top: calc(5rem / 16);
margin-bottom: calc(10rem / 16);
font-size: calc(16rem / 16);
}
.tooltip .toolTipData p {
margin: 0;
padding: 0;
font-size: calc(16rem / 16);
}

361
components/Boxplot.tsx Normal file
View File

@ -0,0 +1,361 @@
import { AxisLeft, AxisBottom } from "@visx/axis";
import { GridRows, GridColumns } from "@visx/grid";
import { Group } from "@visx/group";
import { Stats } from "@visx/mock-data/lib/generators/genStats";
import { Point } from "@visx/point";
import { scaleBand, scaleLinear } from "@visx/scale";
import { Line } from "@visx/shape";
import { BoxPlot as VisxBoxPlot } from "@visx/stats";
import { withTooltip, Tooltip } from "@visx/tooltip";
import { WithTooltipProvidedProps } from "@visx/tooltip/lib/enhancers/withTooltip";
import React from "react";
import { Color } from "utils/Color";
import styles from "./Boxplot.module.css";
const DEFAULT_LABEL_SIZE = 16;
const TICK_LABEL_FONT_WEIGHT = 800;
interface BoxPlotData {
category: string;
min: number;
median: number;
max: number;
firstQuartile: number;
thirdQuartile: number;
outliers?: number[];
}
type TooltipData = Omit<BoxPlotData, "outliers">;
export type StatsPlotProps = {
data: BoxPlotData[];
/** Width of the entire graph, in pixels, greater than 10. */
width: number;
/** Height of the entire graph, in pixels. */
height: number;
/** Distance between the edge of the graph and the area where the bars are drawn, in pixels. */
margin: {
top: number;
left: number;
};
/** Width of the lines in the graph, in px. */
strokeWidth?: number;
/** Length of the dashes and the gaps in the graph, in px. */
strokeDashArray?: string;
/** Number of ticks for the value (y-)axis */
numTicksLeftAxis?: number;
/** Distance between the boxplot and the top of the grid, in px. */
plotTopOffset?: number;
/** Distance between the left axis labels and the start of the lines of the graph, in px. */
valueAxisLeftOffset?: number;
/** Distance between the top and the first label of the y axis, in px. */
valueAxisLabelTopOffset?: number;
/** Distance between the left and the labels of the y axis, in px. */
valueAxisLabelLeftOffset?: number;
/** Distance between the left and the start of the first label of the x axis, in px. */
categoryAxisLabelLeftOffset?: number;
/** Distance between the top and the column lines of the grid of the graph, in px. */
gridColumnTopOffset?: number;
/** Distance between the top of the point in the boxplot and the start of the tooltip box, in px. */
toolTipTopOffset?: number;
/** Distance between the left of the point in the boxplot and the start of the tooltip box, in px. */
toolTipLeftOffset?: number;
/** Font size of the category (x-)axis labels */
categoryAxisLabelSize?: number;
/** Font size of the value (y-)axis labels */
valueAxisLabelSize?: number;
/** Font size of the text in the tool tip box */
toolTipFontSize?: number;
/** Factor multiplied with the compressed width to determine the box width, in px. */
boxPlotWidthFactor?: number;
/** Factor multiplied with the compressed width to determine the distance between boxes, in px. */
boxPlotLeftOffset?: number;
};
export const BoxPlot = withTooltip<StatsPlotProps, TooltipData>(
({
width,
height,
data,
margin,
tooltipOpen,
tooltipLeft,
tooltipTop,
tooltipData,
showTooltip,
hideTooltip,
strokeWidth = 2.5,
strokeDashArray = "10,4",
numTicksLeftAxis = 6,
plotTopOffset = 10,
valueAxisLeftOffset = 40,
gridColumnTopOffset = -20,
valueAxisLabelTopOffset = 5,
valueAxisLabelLeftOffset = 10,
categoryAxisLabelLeftOffset = 30,
toolTipTopOffset = 20,
toolTipLeftOffset = 5,
categoryAxisLabelSize = DEFAULT_LABEL_SIZE,
valueAxisLabelSize = DEFAULT_LABEL_SIZE,
boxPlotWidthFactor = 0.4,
boxPlotLeftOffset = 0.3,
}: StatsPlotProps & WithTooltipProvidedProps<TooltipData>) => {
// bounds
const xMax = width;
const yMax = height - 120;
// formatting data
const plotData: Stats[] = data.map((d) => {
return {
boxPlot: {
...d,
x: d.category,
outliers: [],
},
binData: [],
};
});
// accessors
const getX = (d: Stats) => d.boxPlot.x;
const getMin = (d: Stats) => d.boxPlot.min;
const getMax = (d: Stats) => d.boxPlot.max;
const getMedian = (d: Stats) => d.boxPlot.median;
const getFirstQuartile = (d: Stats) => d.boxPlot.firstQuartile;
const getThirdQuartile = (d: Stats) => d.boxPlot.thirdQuartile;
// scales
const xScale = scaleBand<string>({
range: [18, xMax - 80], // scaling is needed due to the left offset
round: true,
domain: plotData.map(getX),
padding: 0.3,
});
const values = plotData.reduce((allValues, { boxPlot }) => {
allValues.push(boxPlot.min, boxPlot.max);
return allValues;
}, [] as number[]);
const minYValue = Math.min(...values);
const maxYValue = Math.max(...values);
const yScale = scaleLinear<number>({
range: [yMax, 0],
round: true,
domain: [minYValue, maxYValue],
});
const constrainedWidth = Math.min(200, xScale.bandwidth());
return width < 10 ? null : (
<div>
<svg width={width} height={height}>
<Group top={margin.top} left={margin.left}>
<GridRows
top={plotTopOffset}
left={valueAxisLeftOffset}
scale={yScale}
width={xMax}
numTicks={numTicksLeftAxis}
stroke={Color.tertiaryBackground}
strokeWidth={strokeWidth}
strokeDasharray={strokeDashArray}
/>
<GridColumns
scale={xScale}
height={yMax + plotTopOffset - gridColumnTopOffset}
top={gridColumnTopOffset}
left={valueAxisLeftOffset}
stroke={Color.tertiaryBackground}
strokeWidth={strokeWidth}
strokeDasharray={strokeDashArray}
/>
<Line
fill={Color.tertiaryBackground}
to={new Point({ x: valueAxisLeftOffset, y: gridColumnTopOffset })}
from={
new Point({ x: valueAxisLeftOffset, y: yMax + plotTopOffset })
}
stroke={Color.tertiaryBackground}
strokeWidth={strokeWidth}
strokeDasharray={strokeDashArray}
/>
<Line
fill={Color.tertiaryBackground}
to={
new Point({
x: xMax - margin.left - strokeWidth,
y: gridColumnTopOffset,
})
}
from={
new Point({
x: xMax - margin.left - strokeWidth,
y: yMax + plotTopOffset,
})
}
stroke={Color.tertiaryBackground}
strokeWidth={strokeWidth}
strokeDasharray={strokeDashArray}
/>
<AxisBottom
top={yMax + plotTopOffset - gridColumnTopOffset}
left={categoryAxisLabelLeftOffset}
scale={xScale}
hideAxisLine
hideTicks
labelProps={{
fontSize: `${categoryAxisLabelSize / 16}rem`,
}}
tickLabelProps={() => {
return {
fill: Color.label,
fontWeight: TICK_LABEL_FONT_WEIGHT,
};
}}
/>
<AxisLeft
scale={yScale}
top={plotTopOffset + valueAxisLabelTopOffset}
left={valueAxisLabelLeftOffset}
numTicks={numTicksLeftAxis}
hideAxisLine
labelProps={{
fontSize: `${valueAxisLabelSize / 16}rem`,
}}
tickLabelProps={() => {
return {
fill: Color.label,
fontWeight: TICK_LABEL_FONT_WEIGHT,
};
}}
/>
<Group top={plotTopOffset}>
{plotData.map((d: Stats, i) => (
<Group key={i}>
<VisxBoxPlot
className={styles.boxplot}
min={getMin(d)}
max={getMax(d)}
left={
xScale(getX(d))! +
boxPlotLeftOffset * constrainedWidth +
valueAxisLeftOffset
}
firstQuartile={getFirstQuartile(d)}
thirdQuartile={getThirdQuartile(d)}
median={getMedian(d)}
boxWidth={constrainedWidth * boxPlotWidthFactor}
rx={0}
ry={0}
stroke={Color.label}
strokeWidth={strokeWidth}
valueScale={yScale}
minProps={{
onMouseOver: () => {
showTooltip({
tooltipTop:
(yScale(getMin(d)) ?? 0) + toolTipTopOffset,
tooltipLeft:
xScale(getX(d))! +
constrainedWidth +
toolTipLeftOffset,
tooltipData: {
...d.boxPlot,
category: getX(d),
},
});
},
onMouseLeave: () => {
hideTooltip();
},
}}
maxProps={{
onMouseOver: () => {
showTooltip({
tooltipTop:
(yScale(getMax(d)) ?? 0) + toolTipTopOffset,
tooltipLeft:
xScale(getX(d))! +
constrainedWidth +
toolTipLeftOffset,
tooltipData: {
...d.boxPlot,
category: getX(d),
},
});
},
onMouseLeave: () => {
hideTooltip();
},
}}
boxProps={{
onMouseOver: () => {
showTooltip({
tooltipTop:
(yScale(getMedian(d)) ?? 0) + toolTipTopOffset,
tooltipLeft:
xScale(getX(d))! +
constrainedWidth +
toolTipLeftOffset,
tooltipData: {
...d.boxPlot,
category: getX(d),
},
});
},
strokeWidth: 0,
onMouseLeave: () => {
hideTooltip();
},
}}
medianProps={{
style: {
stroke: Color.label,
},
onMouseOver: () => {
showTooltip({
tooltipTop:
(yScale(getMedian(d)) ?? 0) + toolTipTopOffset,
tooltipLeft:
xScale(getX(d))! +
constrainedWidth +
toolTipLeftOffset,
tooltipData: {
...d.boxPlot,
category: getX(d),
},
});
},
onMouseLeave: () => {
hideTooltip();
},
}}
/>
</Group>
))}
</Group>
</Group>
</svg>
{tooltipOpen && tooltipData && (
<Tooltip
top={tooltipTop}
left={tooltipLeft}
className={styles.tooltip}
unstyled
>
<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>
)}
</div>
);
}
);

View File

@ -65,6 +65,36 @@ export const moreMockCategoricalData = [
{ key: "Dart", value: 2.21 }, { key: "Dart", value: 2.21 },
]; ];
export const mockBoxPlotData = [
{
category: "1A",
min: 20,
firstQuartile: 25,
median: 30,
thirdQuartile: 80,
max: 100,
outliers: [],
},
{
category: "1B",
min: 0,
firstQuartile: 20,
median: 30,
thirdQuartile: 50,
max: 100,
outliers: [],
},
{
category: "2A",
min: 25,
firstQuartile: 35,
median: 50,
thirdQuartile: 90,
max: 100,
outliers: [],
},
];
export const mockQuoteData = [ export const mockQuoteData = [
"The quick brown fox jumps over the lazy dog.", "The quick brown fox jumps over the lazy dog.",
"Sphinx of black quartz, judge my vow!", "Sphinx of black quartz, judge my vow!",

72
package-lock.json generated
View File

@ -12,8 +12,10 @@
"@visx/event": "^2.6.0", "@visx/event": "^2.6.0",
"@visx/grid": "^2.10.0", "@visx/grid": "^2.10.0",
"@visx/group": "^2.10.0", "@visx/group": "^2.10.0",
"@visx/mock-data": "^2.1.2",
"@visx/scale": "^2.2.2", "@visx/scale": "^2.2.2",
"@visx/shape": "^2.10.0", "@visx/shape": "^2.10.0",
"@visx/stats": "^2.10.0",
"@visx/text": "^2.10.0", "@visx/text": "^2.10.0",
"@visx/tooltip": "^2.10.0", "@visx/tooltip": "^2.10.0",
"@visx/wordcloud": "^2.10.0", "@visx/wordcloud": "^2.10.0",
@ -608,6 +610,11 @@
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-1.0.9.tgz", "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-1.0.9.tgz",
"integrity": "sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ==" "integrity": "sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ=="
}, },
"node_modules/@types/d3-random": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-2.2.1.tgz",
"integrity": "sha512-5vvxn6//poNeOxt1ZwC7QU//dG9QqABjy1T7fP/xmFHY95GnaOw3yABf29hiu5SR1Oo34XcpyHFbzod+vemQjA=="
},
"node_modules/@types/d3-scale": { "node_modules/@types/d3-scale": {
"version": "3.3.2", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-3.3.2.tgz", "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-3.3.2.tgz",
@ -967,6 +974,15 @@
"react": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0" "react": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0"
} }
}, },
"node_modules/@visx/mock-data": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@visx/mock-data/-/mock-data-2.1.2.tgz",
"integrity": "sha512-6xUVP56tiPwVi3BxvoXPQzDYWG6iX2nnOlsHEYsHgK8gHq1r7AhjQtdbQUX7QF0QkmkJM0cW8TBjZ2e+dItB8Q==",
"dependencies": {
"@types/d3-random": "^2.2.0",
"d3-random": "^2.2.2"
}
},
"node_modules/@visx/point": { "node_modules/@visx/point": {
"version": "2.6.0", "version": "2.6.0",
"resolved": "https://registry.npmjs.org/@visx/point/-/point-2.6.0.tgz", "resolved": "https://registry.npmjs.org/@visx/point/-/point-2.6.0.tgz",
@ -1007,6 +1023,23 @@
"react": "^16.3.0-0 || ^17.0.0-0 || ^18.0.0-0" "react": "^16.3.0-0 || ^17.0.0-0 || ^18.0.0-0"
} }
}, },
"node_modules/@visx/stats": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/@visx/stats/-/stats-2.10.0.tgz",
"integrity": "sha512-4p8rQamOc1IC3IkqTHgfMHbSXvRl9DMWFCglJy+DmbH6Wx1TaWt2nj/N0Ttp350UTRzBy4o5ou/D4Gts8LZHuA==",
"dependencies": {
"@types/d3-shape": "^1.3.2",
"@types/react": "*",
"@visx/group": "2.10.0",
"@visx/scale": "2.2.2",
"classnames": "^2.3.1",
"d3-shape": "^1.2.0",
"prop-types": "^15.5.10"
},
"peerDependencies": {
"react": "^16.3.0-0 || ^17.0.0-0 || ^18.0.0-0"
}
},
"node_modules/@visx/text": { "node_modules/@visx/text": {
"version": "2.10.0", "version": "2.10.0",
"resolved": "https://registry.npmjs.org/@visx/text/-/text-2.10.0.tgz", "resolved": "https://registry.npmjs.org/@visx/text/-/text-2.10.0.tgz",
@ -1392,6 +1425,7 @@
"version": "3.22.7", "version": "3.22.7",
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.22.7.tgz", "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.22.7.tgz",
"integrity": "sha512-wTriFxiZI+C8msGeh7fJcbC/a0V8fdInN1oS2eK79DMBGs8iIJiXhtFJCiT3rBa8w6zroHWW3p8ArlujZ/Mz+w==", "integrity": "sha512-wTriFxiZI+C8msGeh7fJcbC/a0V8fdInN1oS2eK79DMBGs8iIJiXhtFJCiT3rBa8w6zroHWW3p8ArlujZ/Mz+w==",
"deprecated": "core-js-pure@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js-pure.",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"funding": { "funding": {
@ -1535,6 +1569,11 @@
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
"integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="
}, },
"node_modules/d3-random": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/d3-random/-/d3-random-2.2.2.tgz",
"integrity": "sha512-0D9P8TRj6qDAtHhRQn6EfdOtHMfsUWanl3yb/84C4DqpZ+VsgfI5iTVRNRbELCfNvRfpMr8OrqqUTQ6ANGCijw=="
},
"node_modules/d3-scale": { "node_modules/d3-scale": {
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz",
@ -4790,6 +4829,11 @@
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-1.0.9.tgz", "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-1.0.9.tgz",
"integrity": "sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ==" "integrity": "sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ=="
}, },
"@types/d3-random": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-2.2.1.tgz",
"integrity": "sha512-5vvxn6//poNeOxt1ZwC7QU//dG9QqABjy1T7fP/xmFHY95GnaOw3yABf29hiu5SR1Oo34XcpyHFbzod+vemQjA=="
},
"@types/d3-scale": { "@types/d3-scale": {
"version": "3.3.2", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-3.3.2.tgz", "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-3.3.2.tgz",
@ -5043,6 +5087,15 @@
"prop-types": "^15.6.2" "prop-types": "^15.6.2"
} }
}, },
"@visx/mock-data": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@visx/mock-data/-/mock-data-2.1.2.tgz",
"integrity": "sha512-6xUVP56tiPwVi3BxvoXPQzDYWG6iX2nnOlsHEYsHgK8gHq1r7AhjQtdbQUX7QF0QkmkJM0cW8TBjZ2e+dItB8Q==",
"requires": {
"@types/d3-random": "^2.2.0",
"d3-random": "^2.2.2"
}
},
"@visx/point": { "@visx/point": {
"version": "2.6.0", "version": "2.6.0",
"resolved": "https://registry.npmjs.org/@visx/point/-/point-2.6.0.tgz", "resolved": "https://registry.npmjs.org/@visx/point/-/point-2.6.0.tgz",
@ -5080,6 +5133,20 @@
"prop-types": "^15.5.10" "prop-types": "^15.5.10"
} }
}, },
"@visx/stats": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/@visx/stats/-/stats-2.10.0.tgz",
"integrity": "sha512-4p8rQamOc1IC3IkqTHgfMHbSXvRl9DMWFCglJy+DmbH6Wx1TaWt2nj/N0Ttp350UTRzBy4o5ou/D4Gts8LZHuA==",
"requires": {
"@types/d3-shape": "^1.3.2",
"@types/react": "*",
"@visx/group": "2.10.0",
"@visx/scale": "2.2.2",
"classnames": "^2.3.1",
"d3-shape": "^1.2.0",
"prop-types": "^15.5.10"
}
},
"@visx/text": { "@visx/text": {
"version": "2.10.0", "version": "2.10.0",
"resolved": "https://registry.npmjs.org/@visx/text/-/text-2.10.0.tgz", "resolved": "https://registry.npmjs.org/@visx/text/-/text-2.10.0.tgz",
@ -5443,6 +5510,11 @@
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
"integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="
}, },
"d3-random": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/d3-random/-/d3-random-2.2.2.tgz",
"integrity": "sha512-0D9P8TRj6qDAtHhRQn6EfdOtHMfsUWanl3yb/84C4DqpZ+VsgfI5iTVRNRbELCfNvRfpMr8OrqqUTQ6ANGCijw=="
},
"d3-scale": { "d3-scale": {
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz",

View File

@ -19,8 +19,10 @@
"@visx/event": "^2.6.0", "@visx/event": "^2.6.0",
"@visx/grid": "^2.10.0", "@visx/grid": "^2.10.0",
"@visx/group": "^2.10.0", "@visx/group": "^2.10.0",
"@visx/mock-data": "^2.1.2",
"@visx/scale": "^2.2.2", "@visx/scale": "^2.2.2",
"@visx/shape": "^2.10.0", "@visx/shape": "^2.10.0",
"@visx/stats": "^2.10.0",
"@visx/text": "^2.10.0", "@visx/text": "^2.10.0",
"@visx/tooltip": "^2.10.0", "@visx/tooltip": "^2.10.0",
"@visx/wordcloud": "^2.10.0", "@visx/wordcloud": "^2.10.0",

View File

@ -1,7 +1,9 @@
import { BarGraphHorizontal, BarGraphVertical } from "components/BarGraph"; import { BarGraphHorizontal, BarGraphVertical } from "components/BarGraph";
import { BoxPlot } from "components/Boxplot";
import { import {
mockCategoricalData, mockCategoricalData,
moreMockCategoricalData, moreMockCategoricalData,
mockBoxPlotData,
mockQuoteData, mockQuoteData,
mockQuoteDataLong, mockQuoteDataLong,
} from "data/mocks"; } from "data/mocks";
@ -30,7 +32,7 @@ export default function Home() {
width={800} width={800}
height={500} height={500}
margin={{ margin={{
top: 20, top: 25,
bottom: 40, bottom: 40,
left: 150, left: 150,
right: 20, right: 20,
@ -67,6 +69,19 @@ export default function Home() {
}))} }))}
/> />
<h2>
<code>{"<BoxPlot />"}</code>
</h2>
<BoxPlot
width={600}
height={400}
data={mockBoxPlotData}
margin={{
top: 20,
left: 20,
}}
/>
<h2> <h2>
<code>{"<QuotationCarousel />"}</code> <code>{"<QuotationCarousel />"}</code>
</h2> </h2>