diff --git a/data/fruit.json b/data/fruit.json index 384c20d..4cb2678 100644 --- a/data/fruit.json +++ b/data/fruit.json @@ -1,23 +1,22 @@ [ { - "label" : "apples", - "value" : 20 - }, + "label": "apples", + "value": 20 + }, { - "label" : "bananas", - "value" : 40 + "label": "bananas", + "value": 40 }, { - "label" : "pears", - "value" : 30 + "label": "pears", + "value": 30 }, { - "label" : "papaya", - "value" : 50 + "label": "papaya", + "value": 50 }, { - "label" : "oranges", - "value" : 70 + "label": "oranges", + "value": 70 } - -] \ No newline at end of file +] diff --git a/data/penguins.json b/data/penguins.json index c993f69..096b310 100644 --- a/data/penguins.json +++ b/data/penguins.json @@ -5,7 +5,7 @@ "culmen_length_mm": 39.1, "culmen_depth_mm": 18.7, "flipper_length_mm": 181, - "body_mass_g": 3750, + "body_mass_g": null, "sex": "MALE" }, { @@ -3059,4 +3059,4 @@ "body_mass_g": 5400, "sex": "MALE" } -] \ No newline at end of file +] diff --git a/data/portfolio.json b/data/portfolio.json index c6c252a..28fb09d 100644 --- a/data/portfolio.json +++ b/data/portfolio.json @@ -895,4 +895,4 @@ "marketvalue": 93951.489511, "value": -2.6029436385849567 } -] \ No newline at end of file +] diff --git a/data/skinny_fruit.json b/data/skinny_fruit.json index 0b77b9f..f9071dc 100644 --- a/data/skinny_fruit.json +++ b/data/skinny_fruit.json @@ -47,7 +47,7 @@ { "date": "Thu Mar 01 2018 00:00:00 GMT-0500 (Eastern Standard Time)", "fruit": "Bananas", - "value":15 + "value": 15 }, { "date": "Thu Mar 08 2018 00:00:00 GMT-0500 (Eastern Standard Time)", @@ -67,7 +67,7 @@ { "date": "Thu Mar 01 2018 00:00:00 GMT-0500 (Eastern Standard Time)", "fruit": "Apricots", - "value":3 + "value": 3 }, { "date": "Thu Mar 08 2018 00:00:00 GMT-0500 (Eastern Standard Time)", @@ -79,4 +79,4 @@ "fruit": "Apricots", "value": 40 } -] \ No newline at end of file +] diff --git a/data/unemployment.json b/data/unemployment.json index ded3d54..f6442ba 100644 --- a/data/unemployment.json +++ b/data/unemployment.json @@ -2,7 +2,7 @@ { "division": "Bethesda-Rockville-Frederick, MD Met Div", "date": "2000-01-01T00:00:00.000Z", - "unemployment": 2.6 + "unemployment": null }, { "division": "Bethesda-Rockville-Frederick, MD Met Div", diff --git a/src/App.tsx b/src/App.tsx index 166ebcc..122353c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import React, {useState, useEffect } from 'react'; +import React, { useState, useEffect } from 'react'; import BarChart from './charts/BarChart/BarChart'; import AreaChart from './charts/AreaChart/AreaChart'; @@ -10,24 +10,35 @@ import { Container } from './styles/componentStyles'; import portfolio from '../data/portfolio.json'; import penguins from '../data/penguins.json'; import fruit from '../data/fruit.json'; -import unemployment from '../data/unemployment.json' +import unemployment from '../data/unemployment.json'; import skinny_fruit from '../data/skinny_fruit.json'; - function App() { - const [pie, setPie] = useState(fruit.sort((a, b) => a.value - b.value).slice(2)) - const [bar, setBar] = useState(skinny_fruit.reverse().slice(2)) - const [area, setArea] = useState(portfolio.slice(30, 60)) - const [line, setLine] = useState(unemployment.slice(0, 60)) - const [scatter, setScatter] = useState(penguins.slice(30, 60)) + const [pie, setPie] = useState( + fruit.sort((a, b) => a.value - b.value).slice(2) + ); + const [bar, setBar] = useState(skinny_fruit.reverse().slice(2)); + const [area, setArea] = useState(portfolio.slice(30, 60)); + const [line, setLine] = useState(unemployment.slice(0, 60)); + const [scatter, setScatter] = useState(penguins.slice(30, 60)); useEffect(() => { - setTimeout(() => {setPie(fruit.sort((a, b) => a.value - b.value))}, 1000); - setTimeout(() => {setBar(skinny_fruit.reverse())}, 2000); - setTimeout(() => {setArea(portfolio.slice(0, 60))}, 4000); - setTimeout(() => {setLine(unemployment)}, 6000); - setTimeout(() => {setScatter(penguins)}, 8000); - }, []) + setTimeout(() => { + setPie(fruit.sort((a, b) => a.value - b.value)); + }, 1000); + setTimeout(() => { + setBar(skinny_fruit.reverse()); + }, 2000); + setTimeout(() => { + setArea(portfolio.slice(0, 60)); + }, 4000); + setTimeout(() => { + setLine(unemployment); + }, 6000); + setTimeout(() => { + setScatter(penguins); + }, 8000); + }, []); return ( ); diff --git a/src/charts/AreaChart/AreaChart.tsx b/src/charts/AreaChart/AreaChart.tsx index 26064b7..85a246b 100644 --- a/src/charts/AreaChart/AreaChart.tsx +++ b/src/charts/AreaChart/AreaChart.tsx @@ -76,23 +76,28 @@ export default function AreaChart({ return (d) => d[yKey]; }, [yKey]); + const cleanData = useMemo( + () => data.filter((el) => el[xKey] !== null && el[yKey] !== null), + [data] + ); + // if no xKey datatype is passed in, determine if it's Date if (!xDataType) { - xDataType = inferXDataType(data[0], xKey); + xDataType = inferXDataType(cleanData[0], xKey); } // generate arr of keys. these are used to render discrete areas to be displayed const keys = useMemo(() => { const groupAccessor = (d: Data) => d[groupBy ?? '']; - const groups = d3.group(data, groupAccessor); + const groups = d3.group(cleanData, groupAccessor); return groupBy ? Array.from(groups).map((group) => group[0]) : [yKey]; - }, [groupBy, yKey, data]); + }, [groupBy, yKey, cleanData]); const transData = useMemo(() => { return groupBy - ? transformSkinnyToWide(data, keys, groupBy, xKey, yKey) - : data; - }, [data, keys, groupBy, xKey, yKey]); + ? transformSkinnyToWide(cleanData, keys, groupBy, xKey, yKey) + : cleanData; + }, [cleanData, keys, groupBy, xKey, yKey]); // generate stack: an array of Series representing the x and associated y0 & y1 values for each area const stack = d3.stack().keys(keys); diff --git a/src/charts/BarChart/BarChart.tsx b/src/charts/BarChart/BarChart.tsx index 13539f2..32e1f5c 100644 --- a/src/charts/BarChart/BarChart.tsx +++ b/src/charts/BarChart/BarChart.tsx @@ -24,8 +24,7 @@ import { } from '../../utils'; import { yScaleDef } from '../../functionality/yScale'; import { Label } from '../../components/Label'; -import{ ThemeProvider } from 'styled-components'; - +import { ThemeProvider } from 'styled-components'; export default function BarChart({ theme = 'light', @@ -61,6 +60,18 @@ export default function BarChart({ // Look at the data structure and declare how to access the values we'll need. // ******************** + const cleanData = useMemo(() => { + return data + .filter((d) => d[xKey] !== null) + .map((d) => { + if (d[yKey] === null) { + d[yKey] = 0; + } + + return d; + }); + }, [data]); + const xAccessor: (d: Data) => string = useMemo(() => { return (d) => d[xKey]; }, []); @@ -72,15 +83,15 @@ export default function BarChart({ // When the yKey key has been assigned to the groupBy variable we know the user didn't specify grouping const keys: string[] = useMemo(() => { const groupAccessor = (d: Data) => d[groupBy ?? '']; - const groups = d3.group(data, groupAccessor); + const groups = d3.group(cleanData, groupAccessor); return groupBy ? Array.from(groups).map((group) => group[0]) : [yKey]; - }, [groupBy, yKey, data]); + }, [groupBy, yKey, cleanData]); const transData = useMemo(() => { return groupBy - ? transformSkinnyToWide(data, keys, groupBy, xKey, yKey) - : data; - }, [data, keys, groupBy, xKey, yKey]); + ? transformSkinnyToWide(cleanData, keys, groupBy, xKey, yKey) + : cleanData; + }, [cleanData, keys, groupBy, xKey, yKey]); const stack = d3.stack().keys(keys).order(d3.stackOrderAscending); @@ -156,7 +167,7 @@ export default function BarChart({ .scaleBand() .paddingInner(0.1) .paddingOuter(0.1) - .domain(data.map(xAccessor)) + .domain(transData.map(xAccessor)) .range([0, rangeMax > 40 ? rangeMax : 40]); }, [transData, xAccessor, cWidth, margin]); @@ -311,7 +322,7 @@ export default function BarChart({ ) ) - : data.map((d: Data, i: number) => { + : cleanData.map((d: Data, i: number) => { return ( // SINGLE CHART data.filter((el) => el[xKey] !== null && el[yKey] !== null), + [data] + ); + // if no xKey datatype is passed in, determine if it's Date - let xType: 'number' | 'date' = inferXDataType(data[0], xKey); + let xType: 'number' | 'date' = inferXDataType(cleanData[0], xKey); if (xDataType !== undefined) xType = xDataType; const xAccessor: xAccessorFunc = useMemo(() => { @@ -74,13 +80,7 @@ export default function LineChart({ return (d) => d[yKey]; }, []); - // Null values must be removed from the dataset so as to not break our the - // Line generator function. - const cleanData = useMemo(() => { - return data.filter((el) => el[yKey] !== null); - }, [data]); - - const lineGroups: any = d3.group(data, (d) => d[groupBy ?? '']); + const lineGroups: any = d3.group(cleanData, (d) => d[groupBy ?? '']); let keys: string[] = []; if (groupBy !== undefined) { @@ -137,12 +137,12 @@ export default function LineChart({ // ******************** const yScale = useMemo(() => { - return yScaleDef(data, yAccessor, margin, cHeight, 'line-chart'); - }, [data, yAccessor, margin, cHeight]); + return yScaleDef(cleanData, yAccessor, margin, cHeight, 'line-chart'); + }, [cleanData, yAccessor, margin, cHeight]); const { xScale, xMin, xMax } = useMemo(() => { - return xScaleDef(data, xType, xAccessor, margin, cWidth, chartType); - }, [data, cWidth, margin]); + return xScaleDef(cleanData, xType, xAccessor, margin, cWidth, chartType); + }, [cleanData, cWidth, margin]); const line: any = d3 .line() @@ -192,7 +192,7 @@ export default function LineChart({ const voronoi = useMemo(() => { return d3Voronoi( - data, + cleanData, xScale, yScale, xAccessor, @@ -201,7 +201,16 @@ export default function LineChart({ cWidth, margin ); - }, [data, xScale, yScale, xAccessor, yAccessor, cHeight, cWidth, margin]); + }, [ + cleanData, + xScale, + yScale, + xAccessor, + yAccessor, + cHeight, + cWidth, + margin, + ]); return (
diff --git a/src/charts/PieChart/PieChart.tsx b/src/charts/PieChart/PieChart.tsx index c84dba8..db978c8 100644 --- a/src/charts/PieChart/PieChart.tsx +++ b/src/charts/PieChart/PieChart.tsx @@ -53,6 +53,10 @@ export default function PieChart({ // STEP 1. Process data // Look at the data structure and declare how to access the values we'll need. // ******************** + const cleanData = useMemo( + () => data.filter((el: any) => el.value !== null), + [data] + ); const keys = useMemo(() => { const groupAccessor = (d: Data) => d[label ?? '']; diff --git a/src/charts/ScatterPlot/ScatterPlot.tsx b/src/charts/ScatterPlot/ScatterPlot.tsx index 0774336..e09a20d 100644 --- a/src/charts/ScatterPlot/ScatterPlot.tsx +++ b/src/charts/ScatterPlot/ScatterPlot.tsx @@ -65,14 +65,21 @@ export default function ScatterPlot({ // STEP 1. Process data // Look at the data structure and declare how to access the values we'll need. // ******************** - let xType: 'number' | 'date' = inferXDataType(data[0], xKey); + + // Null values must be removed from the dataset + const cleanData = useMemo( + () => data.filter((el) => el[xKey] !== null && el[yKey] !== null), + [data] + ); + + let xType: 'number' | 'date' = inferXDataType(cleanData[0], xKey); if (xDataType !== undefined) xType = xDataType; const keys = useMemo(() => { const groupAccessor = (d: Data) => d[groupBy ?? '']; - const groups = d3.group(data, groupAccessor); + const groups = d3.group(cleanData, groupAccessor); return groupBy ? Array.from(groups).map((group) => group[0]) : [yKey]; - }, [groupBy, yKey, data]); + }, [groupBy, yKey, cleanData]); const xAccessor: xAccessorFunc = useMemo(() => { return xType === 'number' ? (d) => d[xKey] : (d) => new Date(d[xKey]); @@ -126,14 +133,14 @@ export default function ScatterPlot({ // ******************** const { xScale } = useMemo(() => { - return xScaleDef(data, xType, xAccessor, margin, cWidth, chartType); - }, [data, cWidth, margin]); + return xScaleDef(cleanData, xType, xAccessor, margin, cWidth, chartType); + }, [cleanData, cWidth, margin]); const xAccessorScaled = (d: any) => xScale(xAccessor(d)); const yScale = useMemo(() => { - return yScaleDef(data, yAccessor, margin, cHeight, 'scatter-plot'); - }, [data, yAccessor, margin, cHeight]); + return yScaleDef(cleanData, yAccessor, margin, cHeight, 'scatter-plot'); + }, [cleanData, yAccessor, margin, cHeight]); const yAccessorScaled = (d: any) => yScale(yAccessor(d)); // ******************** @@ -175,7 +182,7 @@ export default function ScatterPlot({ const voronoi = useMemo(() => { return d3Voronoi( - data, + cleanData, xScale, yScale, xAccessor, @@ -184,7 +191,16 @@ export default function ScatterPlot({ cWidth, margin ); - }, [data, xScale, yScale, xAccessor, yAccessor, cHeight, cWidth, margin]); + }, [ + cleanData, + xScale, + yScale, + xAccessor, + yAccessor, + cHeight, + cWidth, + margin, + ]); return ( @@ -258,7 +274,7 @@ export default function ScatterPlot({ label={xAxisLabel} /> )} - {data.map((element: any, i: number) => + {cleanData.map((element: any, i: number) => !groupBy ? (