WIP: Line Graph Component #21

Closed
b72zhou wants to merge 14 commits from b72zhou-line-graph into main
5 changed files with 342 additions and 8 deletions
Showing only changes of commit 6c59b76560 - Show all commits

144
components/LineGraph.tsx Normal file
View File

@ -0,0 +1,144 @@
import { AxisLeft, AxisBottom } from "@visx/axis";
import { bottomTickLabelProps } from "@visx/axis/lib/axis/AxisBottom";
import { curveLinear } from "@visx/curve";
import { GridRows, GridColumns } from "@visx/grid";
import { Group } from "@visx/group";
import { scaleBand, scaleLinear } from "@visx/scale";
import { LinePath } from "@visx/shape";
import React from "react";
export const background = "#252D41";
interface LineGraphData {
x: string;
y: number;
}
Review

Two nits here:

  1. How can we ensure that all lines use the same x-axis labels?
  2. Can we somehow encode the lineKey as part of the data prop passed into the component?

My idea would be to make the LineGraphData interface an object that holds more information, and then adjust the xAccessor and yAccessor functions to accomodate:

interface LineGraphData {
  xValues: string[];
  // could make this a separate type/interface, eg. LineData
  lines: {
    label: string;
    // yValues should be the same length as xValues
    yValues: number[];
  }[];
}

interface LineGraphProps {
  data: LineGraphData;
  // ...
}
Two nits here: 1. How can we ensure that all lines use the same x-axis labels? 2. Can we somehow encode the `lineKey` as part of the `data` prop passed into the component? My idea would be to make the `LineGraphData` interface an object that holds more information, and then adjust the `xAccessor` and `yAccessor` functions to accomodate: ```typescript interface LineGraphData { xValues: string[]; // could make this a separate type/interface, eg. LineData lines: { label: string; // yValues should be the same length as xValues yValues: number[]; }[]; } interface LineGraphProps { data: LineGraphData; // ... } ```
interface BarGraphProps {
data: LineGraphData[];
width: number;
height: number;
margin: {
top: number;
bottom: number;
left: number;
right: number;
};
}
const COLOURS = {
salmon: "#ffcad0",
navy: "#2c3651",
white: "#ffffff",
pink: "#ef83b1",
darkpink: "#cc5773",
};
Review

This probably shouldn't be hard coded in the final component 😛

This probably shouldn't be hard coded in the final component 😛
const defaultMargin = { top: 40, right: 30, bottom: 50, left: 40 };
export default function LineGraph({
data,
width,
height,
margin = defaultMargin,
}: BarGraphProps) {
if (width < 10) return null;
Review

Is it possible to cast this to a type (maybe visx has a type we can import?) to get rid of the unsafe assignment complaints?

Is it possible to cast this to a type (maybe visx has a type we can import?) to get rid of the unsafe assignment complaints?
// bounds
const xMax = width - margin.left - margin.right - 15;
const yMax = height - margin.top - margin.bottom - 15;
// accessors
const getX = (d: LineGraphData) => d.x;
const getY = (d: LineGraphData) => d.y;
// scales
const xScale = scaleBand({
range: [0, xMax],
domain: data.map(getX),
});
const yScale = scaleLinear({
range: [yMax, 0],
nice: true,
domain: [0, 100],
// domain: [0, Math.max(...data.map(getY))],
});
const xPoint = (d: LineGraphData) => xScale(getX(d));
const yPoint = (d: LineGraphData) => yScale(getY(d));
console.log(xPoint);
return (
<div>
<svg width={width} height={height}>
<rect
x={0}
y={0}
width={width}
height={height}
fill={background}
rx={14}
/>
<Group left={margin.left} top={margin.top}>
<GridRows
left={30}
scale={yScale}
width={xMax - 30}
height={yMax}
stroke="#354265"
/>
<GridColumns
scale={xScale}
width={xMax}
height={yMax}
stroke="#354265"
/>
<AxisBottom
top={yMax + 20}
scale={xScale}
hideAxisLine
hideTicks
tickLabelProps={() => {
return {
...bottomTickLabelProps(),
fill: COLOURS.white,
fontSize: "1rem",
fontFamily: "Inconsolata",
fontWeight: 800,
};
}}
/>
<AxisLeft
scale={yScale}
hideAxisLine
hideTicks
tickLabelProps={() => {
return {
...bottomTickLabelProps(),
fill: COLOURS.white,
fontSize: "1rem",
fontFamily: "Inconsolata",
fontWeight: 800,
};
}}
/>
<text x="15" y="5" fontSize={10} color={COLOURS.white}>
(%)
</text>
{/* BUG: alignment with x-axis */}
<LinePath
data={data}
curve={curveLinear}
x={xPoint}
y={yPoint}
translate={margin.left}
stroke="#EF839D"
strokeWidth={1.5}
/>
</Group>
</svg>
</div>
);
}

View File

@ -35,3 +35,30 @@ export const mockCategoricalData = [
value: 29,
},
];
export const mockLineGraphlData = [
{
x: "1A",
y: 80,
},
{
x: "1B",
y: 84,
},
{
x: "2A",
y: 90,
},
{
x: "2B",
y: 70,
},
{
x: "3A",
y: 60,
},
{
x: "3B",
y: 80,
},
];

157
package-lock.json generated
View File

@ -10,12 +10,16 @@
"dependencies": {
"@visx/axis": "^2.10.0",
"@visx/grid": "^2.10.0",
"@visx/mock-data": "^2.1.2",
"@visx/shape": "^2.10.0",
"@visx/threshold": "^2.10.0",
"d3-array": "^3.1.6",
"next": "12.1.6",
"react": "18.1.0",
"react-dom": "18.1.0"
},
"devDependencies": {
"@types/d3-array": "^3.0.3",
"@types/node": "17.0.38",
"@types/react": "18.0.10",
"@types/react-dom": "18.0.5",
@ -571,6 +575,12 @@
"integrity": "sha512-WiBSI6JBIhC6LRIsB2Kwh8DsGTlbBU+mLRxJmAe3LjHTdkDpwIbEOZgoXBbZilk/vlfjK8i6nKRAvIRn1XaIMw==",
"dev": true
},
"node_modules/@types/d3-array": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.3.tgz",
"integrity": "sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==",
"dev": true
},
"node_modules/@types/d3-color": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-1.4.2.tgz",
@ -589,6 +599,11 @@
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-1.0.9.tgz",
"integrity": "sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ=="
},
"node_modules/@types/d3-random": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-2.2.1.tgz",
"integrity": "sha512-5vvxn6//poNeOxt1ZwC7QU//dG9QqABjy1T7fP/xmFHY95GnaOw3yABf29hiu5SR1Oo34XcpyHFbzod+vemQjA=="
},
"node_modules/@types/d3-scale": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-3.3.2.tgz",
@ -886,6 +901,18 @@
"react": "^16.3.0-0 || ^17.0.0-0 || ^18.0.0-0"
}
},
"node_modules/@visx/clip-path": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/@visx/clip-path/-/clip-path-2.10.0.tgz",
"integrity": "sha512-YIUQstsHKGysyDISOV5p+fMymkUdHCs89nSY/w6TG9EIl8x2jRhrQdojwtCYvjLkPZiZiKa8MBT6fFzsSfGImg==",
"dependencies": {
"@types/react": "*",
"prop-types": "^15.5.10"
},
"peerDependencies": {
"react": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0"
}
},
"node_modules/@visx/curve": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@visx/curve/-/curve-2.1.0.tgz",
@ -926,6 +953,15 @@
"react": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0"
}
},
"node_modules/@visx/mock-data": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@visx/mock-data/-/mock-data-2.1.2.tgz",
"integrity": "sha512-6xUVP56tiPwVi3BxvoXPQzDYWG6iX2nnOlsHEYsHgK8gHq1r7AhjQtdbQUX7QF0QkmkJM0cW8TBjZ2e+dItB8Q==",
"dependencies": {
"@types/d3-random": "^2.2.0",
"d3-random": "^2.2.2"
}
},
"node_modules/@visx/point": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/@visx/point/-/point-2.6.0.tgz",
@ -982,6 +1018,21 @@
"react": "^16.3.0-0 || ^17.0.0-0 || ^18.0.0-0"
}
},
"node_modules/@visx/threshold": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/@visx/threshold/-/threshold-2.10.0.tgz",
"integrity": "sha512-wFYB0Q7la1CT5qpUjyFQ+aJALWIRPOO+BNCBhYDfL8Lqk7IpXOaHUiaCG05DM/212NHaxNtHbRPp00qLoUFLyg==",
"dependencies": {
"@types/react": "*",
"@visx/clip-path": "2.10.0",
"@visx/shape": "2.10.0",
"classnames": "^2.3.1",
"prop-types": "^15.5.10"
},
"peerDependencies": {
"react": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0"
}
},
"node_modules/acorn": {
"version": "8.7.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz",
@ -1422,11 +1473,14 @@
"integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA=="
},
"node_modules/d3-array": {
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
"integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
"version": "3.1.6",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.1.6.tgz",
"integrity": "sha512-DCbBBNuKOeiR9h04ySRBMW52TFVc91O9wJziuyXw6Ztmy8D3oZbmCkOO3UHKC7ceNJsN2Mavo9+vwV8EAEUXzA==",
"dependencies": {
"internmap": "^1.0.0"
"internmap": "1 - 2"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-color": {
@ -1452,6 +1506,11 @@
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
"integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="
},
"node_modules/d3-random": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/d3-random/-/d3-random-2.2.2.tgz",
"integrity": "sha512-0D9P8TRj6qDAtHhRQn6EfdOtHMfsUWanl3yb/84C4DqpZ+VsgfI5iTVRNRbELCfNvRfpMr8OrqqUTQ6ANGCijw=="
},
"node_modules/d3-scale": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz",
@ -1464,6 +1523,14 @@
"d3-time-format": "2 - 3"
}
},
"node_modules/d3-scale/node_modules/d3-array": {
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
"integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
"dependencies": {
"internmap": "^1.0.0"
}
},
"node_modules/d3-shape": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
@ -1488,6 +1555,14 @@
"d3-time": "1 - 2"
}
},
"node_modules/d3-time/node_modules/d3-array": {
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
"integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
"dependencies": {
"internmap": "^1.0.0"
}
},
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
@ -4659,6 +4734,12 @@
"integrity": "sha512-WiBSI6JBIhC6LRIsB2Kwh8DsGTlbBU+mLRxJmAe3LjHTdkDpwIbEOZgoXBbZilk/vlfjK8i6nKRAvIRn1XaIMw==",
"dev": true
},
"@types/d3-array": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.3.tgz",
"integrity": "sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==",
"dev": true
},
"@types/d3-color": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-1.4.2.tgz",
@ -4677,6 +4758,11 @@
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-1.0.9.tgz",
"integrity": "sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ=="
},
"@types/d3-random": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-2.2.1.tgz",
"integrity": "sha512-5vvxn6//poNeOxt1ZwC7QU//dG9QqABjy1T7fP/xmFHY95GnaOw3yABf29hiu5SR1Oo34XcpyHFbzod+vemQjA=="
},
"@types/d3-scale": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-3.3.2.tgz",
@ -4878,6 +4964,15 @@
"prop-types": "^15.6.0"
}
},
"@visx/clip-path": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/@visx/clip-path/-/clip-path-2.10.0.tgz",
"integrity": "sha512-YIUQstsHKGysyDISOV5p+fMymkUdHCs89nSY/w6TG9EIl8x2jRhrQdojwtCYvjLkPZiZiKa8MBT6fFzsSfGImg==",
"requires": {
"@types/react": "*",
"prop-types": "^15.5.10"
}
},
"@visx/curve": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@visx/curve/-/curve-2.1.0.tgz",
@ -4912,6 +5007,15 @@
"prop-types": "^15.6.2"
}
},
"@visx/mock-data": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@visx/mock-data/-/mock-data-2.1.2.tgz",
"integrity": "sha512-6xUVP56tiPwVi3BxvoXPQzDYWG6iX2nnOlsHEYsHgK8gHq1r7AhjQtdbQUX7QF0QkmkJM0cW8TBjZ2e+dItB8Q==",
"requires": {
"@types/d3-random": "^2.2.0",
"d3-random": "^2.2.2"
}
},
"@visx/point": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/@visx/point/-/point-2.6.0.tgz",
@ -4962,6 +5066,18 @@
"reduce-css-calc": "^1.3.0"
}
},
"@visx/threshold": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/@visx/threshold/-/threshold-2.10.0.tgz",
"integrity": "sha512-wFYB0Q7la1CT5qpUjyFQ+aJALWIRPOO+BNCBhYDfL8Lqk7IpXOaHUiaCG05DM/212NHaxNtHbRPp00qLoUFLyg==",
"requires": {
"@types/react": "*",
"@visx/clip-path": "2.10.0",
"@visx/shape": "2.10.0",
"classnames": "^2.3.1",
"prop-types": "^15.5.10"
}
},
"acorn": {
"version": "8.7.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz",
@ -5247,11 +5363,11 @@
"integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA=="
},
"d3-array": {
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
"integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
"version": "3.1.6",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.1.6.tgz",
"integrity": "sha512-DCbBBNuKOeiR9h04ySRBMW52TFVc91O9wJziuyXw6Ztmy8D3oZbmCkOO3UHKC7ceNJsN2Mavo9+vwV8EAEUXzA==",
"requires": {
"internmap": "^1.0.0"
"internmap": "1 - 2"
}
},
"d3-color": {
@ -5277,6 +5393,11 @@
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
"integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="
},
"d3-random": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/d3-random/-/d3-random-2.2.2.tgz",
"integrity": "sha512-0D9P8TRj6qDAtHhRQn6EfdOtHMfsUWanl3yb/84C4DqpZ+VsgfI5iTVRNRbELCfNvRfpMr8OrqqUTQ6ANGCijw=="
},
"d3-scale": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz",
@ -5287,6 +5408,16 @@
"d3-interpolate": "1.2.0 - 2",
"d3-time": "^2.1.1",
"d3-time-format": "2 - 3"
},
"dependencies": {
"d3-array": {
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
"integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
"requires": {
"internmap": "^1.0.0"
}
}
}
},
"d3-shape": {
@ -5303,6 +5434,16 @@
"integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==",
"requires": {
"d3-array": "2"
},
"dependencies": {
"d3-array": {
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
"integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
"requires": {
"internmap": "^1.0.0"
}
}
}
},
"d3-time-format": {

View File

@ -17,12 +17,16 @@
"dependencies": {
"@visx/axis": "^2.10.0",
"@visx/grid": "^2.10.0",
"@visx/mock-data": "^2.1.2",
"@visx/shape": "^2.10.0",
"@visx/threshold": "^2.10.0",
"d3-array": "^3.1.6",
"next": "12.1.6",
"react": "18.1.0",
"react-dom": "18.1.0"
},
"devDependencies": {
"@types/d3-array": "^3.0.3",
"@types/node": "17.0.38",
"@types/react": "18.0.10",
"@types/react-dom": "18.0.5",

View File

@ -1,10 +1,28 @@
import { mockLineGraphlData } from "data/mocks";
import React from "react";
import LineGraph from "@/components/LineGraph";
export default function Home() {
return (
<>
<h1>Playground</h1>
<p>Show off your components here!</p>
<h2>
<code>{"<LineGraph />"}</code>
</h2>
<LineGraph
// className={styles.barGraphDemo}
data={mockLineGraphlData}
width={700}
height={400}
margin={{
top: 20,
bottom: 40,
left: 50,
right: 20,
}}
/>
</>
);
}