From 02e5e361d0ef396d8df0374d01862690fb9a4df5 Mon Sep 17 00:00:00 2001 From: Robert Crocker Date: Sat, 12 Mar 2022 14:55:18 -0600 Subject: [PATCH 1/8] Staging App.tsx. --- src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App.tsx b/src/App.tsx index 65b58ab..160e9c3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -40,7 +40,7 @@ function App() { tooltipVisible={true} /> Date: Mon, 25 Apr 2022 12:29:12 -0400 Subject: [PATCH 2/8] Updated the Line Chart to set the domain from the cleaned data, rather than the original data. --- data/penguins.json | 2 +- data/portfolio.json | 2 +- src/App.tsx | 46 ++++++++++++++++++------------ src/charts/LineChart/LineChart.tsx | 3 +- 4 files changed, 31 insertions(+), 22 deletions(-) diff --git a/data/penguins.json b/data/penguins.json index c993f69..2e17014 100644 --- a/data/penguins.json +++ b/data/penguins.json @@ -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/src/App.tsx b/src/App.tsx index 137e85f..3fd18ec 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,27 +10,38 @@ 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/LineChart/LineChart.tsx b/src/charts/LineChart/LineChart.tsx index 7927ec9..60556cd 100644 --- a/src/charts/LineChart/LineChart.tsx +++ b/src/charts/LineChart/LineChart.tsx @@ -25,7 +25,6 @@ import Tooltip from '../../components/Tooltip'; import { ThemeProvider } from 'styled-components'; - export default function LineChart({ theme = 'light', data, @@ -80,7 +79,7 @@ export default function LineChart({ 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) { From eaeb3db4bb581cabc1c3811e11f09df614735a83 Mon Sep 17 00:00:00 2001 From: Robert Crocker Date: Mon, 25 Apr 2022 13:55:36 -0400 Subject: [PATCH 3/8] Handle null values for the Scatter Plot and the Line chart so that if either the x or y keys' values are null we'll remove those objects from the dataset. --- data/penguins.json | 2 +- data/unemployment.json | 2 +- src/App.tsx | 4 +-- src/charts/LineChart/LineChart.tsx | 36 +++++++++++++++--------- src/charts/ScatterPlot/ScatterPlot.tsx | 38 ++++++++++++++++++-------- src/utils.ts | 2 +- 6 files changed, 55 insertions(+), 29 deletions(-) diff --git a/data/penguins.json b/data/penguins.json index 2e17014..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" }, { 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 3fd18ec..f5f8f11 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -41,7 +41,7 @@ function App() { }, []); return ( - {/* */} + /> 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(() => { @@ -73,12 +80,6 @@ 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(cleanData, (d) => d[groupBy ?? '']); let keys: string[] = []; @@ -136,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() @@ -191,7 +192,7 @@ export default function LineChart({ const voronoi = useMemo(() => { return d3Voronoi( - data, + cleanData, xScale, yScale, xAccessor, @@ -200,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/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 ? ( Date: Mon, 25 Apr 2022 16:40:05 -0400 Subject: [PATCH 4/8] The Pie Chart can handle null values now. --- data/fruit.json | 25 ++++++++++++------------- src/App.tsx | 10 ++++------ src/charts/PieChart/PieChart.tsx | 4 ++++ 3 files changed, 20 insertions(+), 19 deletions(-) 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/src/App.tsx b/src/App.tsx index f5f8f11..d28334e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -14,9 +14,7 @@ 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 [pie, setPie] = useState(fruit.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)); @@ -24,7 +22,7 @@ function App() { useEffect(() => { setTimeout(() => { - setPie(fruit.sort((a, b) => a.value - b.value)); + setPie(fruit); }, 1000); setTimeout(() => { setBar(skinny_fruit.reverse()); @@ -49,7 +47,7 @@ function App() { outerRadius={400} pieLabel={true} /> - + /> */} ); } 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 ?? '']; From 75c13b27d32994aa88326d1e98335aadba20396f Mon Sep 17 00:00:00 2001 From: Robert Crocker Date: Mon, 25 Apr 2022 18:56:57 -0400 Subject: [PATCH 5/8] Found a new bug, but also improved some of the null handling of our bar chart. The problem has to do with us using the scaleBand for the bars even when they're dates. We need to figure out how to use scaleTime. --- data/skinny_fruit.json | 6 +++--- src/App.tsx | 12 ++++++------ src/charts/BarChart/BarChart.tsx | 29 ++++++++++++++++++++--------- src/utils.ts | 1 + 4 files changed, 30 insertions(+), 18 deletions(-) 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/src/App.tsx b/src/App.tsx index d28334e..72f3456 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -15,7 +15,7 @@ import skinny_fruit from '../data/skinny_fruit.json'; function App() { const [pie, setPie] = useState(fruit.slice(2)); - const [bar, setBar] = useState(skinny_fruit.reverse().slice(2)); + const [bar, setBar] = useState(skinny_fruit); const [area, setArea] = useState(portfolio.slice(30, 60)); const [line, setLine] = useState(unemployment.slice(0, 60)); const [scatter, setScatter] = useState(penguins.slice(30, 60)); @@ -25,7 +25,7 @@ function App() { setPie(fruit); }, 1000); setTimeout(() => { - setBar(skinny_fruit.reverse()); + // setBar(skinny_fruit.reverse()); }, 2000); setTimeout(() => { setArea(portfolio.slice(0, 60)); @@ -39,15 +39,15 @@ function App() { }, []); return ( - - {/* */} + - { + 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 Date: Tue, 26 Apr 2022 11:00:40 -0400 Subject: [PATCH 6/8] The area chart is now more easily able to handle null values. --- src/App.tsx | 12 ++++++------ src/charts/AreaChart/AreaChart.tsx | 18 ++++++++++++------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 72f3456..c497e61 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -16,7 +16,7 @@ import skinny_fruit from '../data/skinny_fruit.json'; function App() { const [pie, setPie] = useState(fruit.slice(2)); const [bar, setBar] = useState(skinny_fruit); - const [area, setArea] = useState(portfolio.slice(30, 60)); + const [area, setArea] = useState(portfolio); const [line, setLine] = useState(unemployment.slice(0, 60)); const [scatter, setScatter] = useState(penguins.slice(30, 60)); @@ -28,7 +28,7 @@ function App() { // setBar(skinny_fruit.reverse()); }, 2000); setTimeout(() => { - setArea(portfolio.slice(0, 60)); + setArea(portfolio); }, 4000); setTimeout(() => { setLine(unemployment); @@ -46,7 +46,7 @@ function App() { value="value" outerRadius={400} pieLabel={true} - /> */} + /> - {/* */} + - 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); From 13e906437cfdc0977840efa1440aa82cfdfa00b0 Mon Sep 17 00:00:00 2001 From: Robert Crocker Date: Tue, 26 Apr 2022 11:06:52 -0400 Subject: [PATCH 7/8] Reset the App.tsx file to match what it was before I began this work. --- src/App.tsx | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index c497e61..122353c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -14,21 +14,23 @@ import unemployment from '../data/unemployment.json'; import skinny_fruit from '../data/skinny_fruit.json'; function App() { - const [pie, setPie] = useState(fruit.slice(2)); - const [bar, setBar] = useState(skinny_fruit); - const [area, setArea] = useState(portfolio); + 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); + setPie(fruit.sort((a, b) => a.value - b.value)); }, 1000); setTimeout(() => { - // setBar(skinny_fruit.reverse()); + setBar(skinny_fruit.reverse()); }, 2000); setTimeout(() => { - setArea(portfolio); + setArea(portfolio.slice(0, 60)); }, 4000); setTimeout(() => { setLine(unemployment); @@ -39,14 +41,14 @@ function App() { }, []); return ( - {/* + /> */} + /> - {/* */} + /> ); } From 829bd76270e66f0dc4ce19dafcb9ca3a44a5e060 Mon Sep 17 00:00:00 2001 From: eiozalp Date: Tue, 26 Apr 2022 15:54:00 -0400 Subject: [PATCH 8/8] Update AreaChart.tsx removed import ChunkGraph --- src/charts/AreaChart/AreaChart.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/charts/AreaChart/AreaChart.tsx b/src/charts/AreaChart/AreaChart.tsx index 0ec0939..85a246b 100644 --- a/src/charts/AreaChart/AreaChart.tsx +++ b/src/charts/AreaChart/AreaChart.tsx @@ -27,7 +27,6 @@ import { themes, } from '../../utils'; import styled, { ThemeProvider } from 'styled-components'; -import { ChunkGraph } from 'webpack'; const Area = styled.path` fill-opacity: 0.7;