From 8bd4ef8fcb1331737af74dc3a520b1f3ecaf6790 Mon Sep 17 00:00:00 2001 From: YONG WOOK KIM Date: Fri, 1 Sep 2023 11:06:12 -0500 Subject: [PATCH 01/16] working process for category map and donut chart --- src/components/ProgramDrawer.tsx | 171 ++++++++++- src/components/crp/CRPTotalMap.tsx | 266 +++++++++++++++++ src/components/crp/CRPTotalTable.tsx | 191 +++++++++++++ src/components/crp/CategoryMap.tsx | 409 +++++++++++++++++++++++++++ src/components/crp/CategoryTable.tsx | 299 ++++++++++++++++++++ src/main.tsx | 2 + src/pages/CRPPage.tsx | 284 +++++++++++++++++++ 7 files changed, 1619 insertions(+), 3 deletions(-) create mode 100644 src/components/crp/CRPTotalMap.tsx create mode 100644 src/components/crp/CRPTotalTable.tsx create mode 100644 src/components/crp/CategoryMap.tsx create mode 100644 src/components/crp/CategoryTable.tsx create mode 100644 src/pages/CRPPage.tsx diff --git a/src/components/ProgramDrawer.tsx b/src/components/ProgramDrawer.tsx index aa3fcc23..6e12f36a 100644 --- a/src/components/ProgramDrawer.tsx +++ b/src/components/ProgramDrawer.tsx @@ -336,15 +336,113 @@ function CSPCheckboxList({ setCSPChecked, setShowPopUp, zeroCategory }) { ); } +function CRPCheckboxList({ setCRPChecked, setShowPopUp, zeroCategory }) { + const [checked, setChecked] = React.useState(currentChecked); + + const handleToggle = (value: number) => () => { + setChecked(value); + setCRPChecked(value); + currentChecked = value; + setShowPopUp(false); + }; + + const CRPList = [ + "Total CRP", + "Total General Sign-up", + "Total Continuous Sign-Up", + "CREP Only", + "Continuous Non-CREP", + "Farmable Wetland", + "Grassland" + ]; + + return ( + + {CRPList.map((category, value) => { + const labelId = `checkbox-list-label-${value}`; + if (zeroCategory && zeroCategory.includes(category)) { + return ( + + + + + + + ); + } + if (category !== "CREP Only" && category !== "Continuous Non-CREP" && category !== "Farmable Wetland") { + return ( + + + + + + + ); + } + return ( + + + + + + + + + ); + })} + + ); +} + interface ProgramDrawerProps { setEQIPChecked?: (value: number) => void; setCSPChecked?: (value: number) => void; + setCRPChecked?: (value: number) => void; zeroCategories?: string[]; } export default function ProgramDrawer({ setEQIPChecked, setCSPChecked, + setCRPChecked, zeroCategories }: ProgramDrawerProps): JSX.Element { const location = useLocation(); @@ -387,6 +485,24 @@ export default function ProgramDrawer({ prevCspOpen.current = cspOpen; }, [cspOpen]); + const [crpOpen, setCrpOpen] = React.useState(false); + const crpRef = React.useRef(null); + const handleCrpClick = () => { + if (location.pathname !== "/crp") { + navigate("/crp"); + window.location.reload(false); + } else { + setCrpOpen((prevCrpOpen) => !prevCrpOpen); + } + }; + const prevCrpOpen = React.useRef(crpOpen); + React.useEffect(() => { + if (prevCrpOpen.current && !crpOpen) { + crpRef.current.focus(); + } + + prevCrpOpen.current = crpOpen; + }, [crpOpen]); return ( - - CRP: Conservation Reserve Program - + + + + {location.pathname === "/crp" ? ( + + CRP: Conservation Reserve Program + + ) : ( + CRP: Conservation Reserve Program + )} + + + + STATUTE + + + + + + + + + + + + ACEP: Agriculture Conservation Easement Program diff --git a/src/components/crp/CRPTotalMap.tsx b/src/components/crp/CRPTotalMap.tsx new file mode 100644 index 00000000..b54ed65c --- /dev/null +++ b/src/components/crp/CRPTotalMap.tsx @@ -0,0 +1,266 @@ +import React, { useState } from "react"; +import { geoCentroid } from "d3-geo"; +import { ComposableMap, Geographies, Geography, Marker, Annotation } from "react-simple-maps"; +import ReactTooltip from "react-tooltip"; +import { scaleQuantize } from "d3-scale"; +import Divider from "@mui/material/Divider"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; + +import PropTypes from "prop-types"; +import "../../styles/map.css"; +import HorizontalStackedBar from "../HorizontalStackedBar"; + +const geoUrl = "https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json"; + +const offsets = { + VT: [50, -8], + NH: [34, 2], + MA: [30, -1], + RI: [28, 2], + CT: [35, 10], + NJ: [34, 1], + DE: [33, 0], + MD: [47, 10], + DC: [49, 21] +}; + +const MapChart = (props) => { + const { setTooltipContent, maxValue, allStates, statePerformance, year, stateCodes } = props; + const colorScale = scaleQuantize() + .domain([0, maxValue]) + .range(["#F0F9E8", "#BAE4BC", "#7BCCC4", "#43A2CA", "#0868AC"]); + return ( +
+ {allStates.length > 0 && statePerformance[year] !== undefined ? ( + + + {({ geographies }) => ( + <> + {geographies.map((geo) => { + const record = statePerformance[year].filter( + (v) => stateCodes[v.state] === geo.properties.name + )[0]; + if (record === undefined || record.length === 0) { + return null; + } + const totalPaymentInDollars = record.programs[0].paymentInDollars; + const totalPaymentInPercentageNationwide = + record.programs[0].paymentInPercentageNationwide; + const hoverContent = ( + + + {geo.properties.name} + + + {Number(totalPaymentInDollars) < 1000000 + ? `$${Number( + Number(totalPaymentInDollars) / 1000.0 + ).toLocaleString(undefined, { + maximumFractionDigits: 2 + })}K` + : `$${Number( + Number(totalPaymentInDollars) / 1000000.0 + ).toLocaleString(undefined, { + maximumFractionDigits: 2 + })}M`} + + + + {totalPaymentInPercentageNationwide + ? `${totalPaymentInPercentageNationwide} %` + : "0%"} + + + + + ); + const fillColour = () => { + if (totalPaymentInDollars) { + if (totalPaymentInDollars !== 0) return colorScale(totalPaymentInDollars); + return "#D2D2D2"; + } + return "#D2D2D2"; + }; + return ( + { + setTooltipContent(hoverContent); + }} + onMouseLeave={() => { + setTooltipContent(""); + }} + fill={fillColour()} + stroke="#FFF" + style={{ + default: { stroke: "#FFFFFF", strokeWidth: 0.75, outline: "none" }, + hover: { + stroke: "#232323", + strokeWidth: 2, + outline: "none" + }, + pressed: { + fill: "#345feb", + outline: "none" + } + }} + /> + ); + })} + {geographies.map((geo) => { + const centroid = geoCentroid(geo); + const cur = allStates.find((s) => s.val === geo.id); + return ( + + {cur && + centroid[0] > -160 && + centroid[0] < -67 && + (Object.keys(offsets).indexOf(cur.id) === -1 ? ( + + + {cur.id} + + + ) : ( + + + {cur.id} + + + ))} + + ); + })} + + )} + + + ) : ( + +

Loading Map Data...

+
+ )} +
+ ); +}; + +MapChart.propTypes = { + setTooltipContent: PropTypes.func, + maxValue: PropTypes.number +}; + +const CRPTotalMap = ({ + program, + attribute, + year, + statePerformance, + stateCodes, + allStates +}: { + program: string; + attribute: any; + year: string; + statePerformance: any; + stateCodes: any; + allStates: any; +}): JSX.Element => { + const [content, setContent] = useState(""); + const quantizeArray: number[] = []; + const zeroPoints = []; + statePerformance[year].forEach((value) => { + const programRecord = value.programs; + const ACur = programRecord.find((s) => s.programName === program); + let key = getValueFromAttr(ACur, attribute); + key = key !== "" ? key : attribute; + quantizeArray.push(ACur[key]); + ACur[key] === 0 && zeroPoints.push(value.state); + return null; + }); + const maxValue = Math.max(...quantizeArray); + const label1 = (maxValue / 5) * 0; + const label2 = (maxValue / 5) * 1; + const label3 = (maxValue / 5) * 2; + const label4 = (maxValue / 5) * 3; + const label5 = (maxValue / 5) * 4; + return ( +
+
+ + + + + + +
+ + {content} + +
+
+
+ ); +}; + +const getValueFromAttr = (stateRecord, attribute): string => { + let ans = ""; + Object.keys(stateRecord).forEach((key) => { + const match = key.match(/^(.*?)(?=\s*InDollars)/); + const extractedKey = match ? match[1] : key; + if (extractedKey === attribute) { + ans = key; + } + }); + return ans; +}; + +export default CRPTotalMap; diff --git a/src/components/crp/CRPTotalTable.tsx b/src/components/crp/CRPTotalTable.tsx new file mode 100644 index 00000000..4f9256b1 --- /dev/null +++ b/src/components/crp/CRPTotalTable.tsx @@ -0,0 +1,191 @@ +import React from "react"; +import styled from "styled-components"; +import { useTable, useSortBy } from "react-table"; +import Box from "@mui/material/Box"; +import "../../styles/table.css"; + +const Styles = styled.div` + padding: 1rem; + + table { + border-spacing: 0; + border: 1px solid #e4ebe7; + border-left: none; + border-right: none; + + tr { + :last-child { + td { + border-bottom: 0; + } + } + } + + th { + background-color: #f1f1f1; + padding: 1rem; + } + + td { + margin: 0; + padding: 1rem; + padding-left: 5rem; + padding-right: 5rem; + border-bottom: 1px solid #e4ebe7; + border-right: none; + + :last-child { + border-right: 0; + } + } + } +`; + +// eslint-disable-next-line +function Table({ columns, data }: { columns: any; data: any }) { + const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } = useTable( + { + columns, + data + }, + useSortBy + ); + + const firstPageRows = rows.slice(0, 50); + + return ( + <> + + + {headerGroups.map((headerGroup) => ( + + {headerGroup.headers.map((column) => ( + // Add the sorting props to control sorting. + + ))} + + ))} + + + { + // eslint-disable-next-line + firstPageRows.map((row, i) => { + prepareRow(row); + return ( + + {row.cells.map((cell) => { + return ( + + ); + })} + + ); + }) + } + +
+ + {column.render("Header")} +
+ {(() => { + if (!column.isSorted) return {"\u{2B83}"}; + if (column.isSortedDesc) return {"\u{25BC}"}; + return {"\u{25B2}"}; + })()} +
+
+
+ {cell.render("Cell")} +
+
+
+ Showing the first {rows.length} results of {rows.length} rows +
+ + ); +} + +function App({ statePerformance }: { statePerformance: any }): JSX.Element { + function compareWithDollarSign(rowA, rowB, id, desc) { + const a = Number.parseFloat(rowA.values[id].substring(1).replaceAll(",", "")); + const b = Number.parseFloat(rowB.values[id].substring(1).replaceAll(",", "")); + if (a > b) return 1; + if (a < b) return -1; + return 0; + } + + function compareWithPercentSign(rowA, rowB, id, desc) { + const a = Number.parseFloat(rowA.values[id].replaceAll("%", "")); + const b = Number.parseFloat(rowB.values[id].replaceAll("%", "")); + if (a > b) return 1; + if (a < b) return -1; + return 0; + } + + const crpTableData: any[] = []; + + // eslint-disable-next-line no-restricted-syntax + for (const [key, value] of Object.entries(statePerformance)) { + const newRecord = () => { + return { + state: key, + crpBenefit: `$${value[0].totalPaymentInDollars + .toLocaleString(undefined, { minimumFractionDigits: 2 }) + .toString()}`, + percentage: `${value[0].totalPaymentInPercentageNationwide.toString()}%` + }; + }; + crpTableData.push(newRecord()); + } + + const columns = React.useMemo( + () => [ + { + Header: STATES, + accessor: "state", + paddingLeft: "5rem", + paddingRight: "32rem" + }, + { + Header: ( + + CRP BENEFITS + + ), + accessor: "crpBenefit", + sortType: compareWithDollarSign, + Cell: function styleCells(row) { + return
{row.value}
; + } + }, + { + Header: PCT. NATIONWIDE, + accessor: "percentage", + sortType: compareWithPercentSign, + Cell: function styleCells(row) { + return
{row.value}
; + } + } + ], + [] + ); + + return ( + + + + + + ); +} + +export default App; diff --git a/src/components/crp/CategoryMap.tsx b/src/components/crp/CategoryMap.tsx new file mode 100644 index 00000000..0dc19fa3 --- /dev/null +++ b/src/components/crp/CategoryMap.tsx @@ -0,0 +1,409 @@ +import React, { useState } from "react"; +import { geoCentroid } from "d3-geo"; +import { ComposableMap, Geographies, Geography, Marker, Annotation } from "react-simple-maps"; +import ReactTooltip from "react-tooltip"; +import { scaleQuantize } from "d3-scale"; +import Divider from "@mui/material/Divider"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import PropTypes from "prop-types"; +import "../../styles/map.css"; +import HorizontalStackedBar from "../HorizontalStackedBar"; + +const geoUrl = "https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json"; + +const offsets = { + VT: [50, -8], + NH: [34, 2], + MA: [30, -1], + RI: [28, 2], + CT: [35, 10], + NJ: [34, 1], + DE: [33, 0], + MD: [47, 10], + DC: [49, 21] +}; + +const MapChart = (props) => { + const { year, setTooltipContent, category, maxValue, allStates, stateCodes, statePerformance } = props; + const colorScale = scaleQuantize() + .domain([0, maxValue]) + .range(["#F0F9E8", "#BAE4BC", "#7BCCC4", "#43A2CA", "#0868AC"]); + let categoryRecord; + return ( +
+ + + {({ geographies }) => ( + <> + {geographies.map((geo) => { + const record = statePerformance[year].filter( + (v) => stateCodes[v.state] === geo.properties.name + )[0]; + if (record === undefined || record.length === 0) { + return null; + } + let ACur = {}; + let BCur = {}; + let CCur = {}; + let DCur = {}; + let ECur = {}; + let FCur = {}; + record.programs.forEach((value) => { + if (value.programName === "Total General Sign-Up") { + ACur = value; + } else if (value.programName === "Total Continuous Sign-Up") { + BCur = value; + value.subPrograms.forEach((subValue) => { + if (subValue.programName === "CREP Only") { + CCur = subValue; + } else if (subValue.programName === "Continuous Non-CREP") { + DCur = subValue; + } else if (subValue.programName === "Farmable Wetland") { + ECur = subValue; + } + }); + } else if (value.programName === "Grassland") { + FCur = value; + } + }); + if (category === "Total General Sign-Up") { + categoryRecord = ACur; + } else if (category === "Total Continuous Sign-Up") { + categoryRecord = BCur; + } else if (category === "CREP Only") { + categoryRecord = CCur; + } else if (category === "Continuous Non-CREP") { + categoryRecord = DCur; + } else if (category === "Farmable Wetland") { + categoryRecord = ECur; + } else { + categoryRecord = FCur; + } + const categoryPayment = categoryRecord.paymentInDollars; + const nationwidePercentage = categoryRecord.paymentInPercentageNationwide; + const hoverContent = ( + + + {geo.properties.name} + + + {Number(categoryPayment) < 1000000 + ? `$${Number(Number(categoryPayment) / 1000.0).toLocaleString( + undefined, + { + maximumFractionDigits: 2 + } + )}K` + : `$${Number( + Number(categoryPayment) / 1000000.0 + ).toLocaleString(undefined, { + maximumFractionDigits: 2 + })}M`} + + + + {nationwidePercentage ? `${nationwidePercentage} %` : "0%"} + + + + + ); + const fillColour = () => { + if (categoryPayment) { + if (categoryPayment !== 0) return colorScale(categoryPayment); + return "#D2D2D2"; + } + return "#D2D2D2"; + }; + return ( + { + setTooltipContent(hoverContent); + }} + onMouseLeave={() => { + setTooltipContent(""); + }} + fill={fillColour()} + stroke="#FFFFFF" + style={{ + default: { stroke: "#FFFFFF", strokeWidth: 0.75, outline: "none" }, + hover: { + stroke: "#232323", + strokeWidth: 2, + outline: "none" + }, + pressed: { + fill: "#345feb", + outline: "none" + } + }} + /> + ); + })} + {geographies.map((geo) => { + const centroid = geoCentroid(geo); + const cur = allStates.find((s) => s.val === geo.id); + return ( + + {cur && + centroid[0] > -160 && + centroid[0] < -67 && + (Object.keys(offsets).indexOf(cur.id) === -1 ? ( + + + {cur.id} + + + ) : ( + + + {cur.id} + + + ))} + + ); + })} + + )} + + +
+ ); +}; + +MapChart.propTypes = { + year: PropTypes.string, + setTooltipContent: PropTypes.func, + category: PropTypes.string, + maxValue: PropTypes.number +}; + +const CategoryMap = ({ + year, + category, + attribute, + statePerformance, + allStates, + stateCodes +}: { + year: string; + category: string; + attribute: string; + statePerformance: any; + allStates: any; + stateCodes: any; +}): JSX.Element => { + const [content, setContent] = useState(""); + const title = `${category} Benefits from ${year}`; + const quantizeArray: number[] = []; + // let categoryRecord = {}; + const zeroPoints = []; + // statePerformance[year].forEach((value) => { + // const programRecord = value.programs; + // const ACur = programRecord.find((s) => s.programName === category); + // let key = getValueFromAttr(ACur, attribute); + // key = key !== "" ? key : attribute; + // quantizeArray.push(ACur[key]); + // ACur[key] === 0 && zeroPoints.push(value.state); + // return null; + // }); + + statePerformance[year].forEach((value) => { + const programRecord = value.programs; + let ACur = {}; + if ( + category === "Total General Sign-Up" || + category === "Total Continuous Sign-Up" || + category === "Grassland" + ) { + ACur = programRecord.find((s) => s.programName === category); + } else if (category === "CREP Only" || category === "Continuous Non-CREP" || category === "Farmable Wetland") { + const contSingUp = programRecord.find((s) => s.programName === "Total Continuous Sign-Up"); + const subPrograms = contSingUp.subPrograms; + subPrograms.forEach((subValue) => { + if (subValue.programName === category) { + ACur = subValue; + } + }); + } + let key = getValueFromAttr(ACur, attribute); + key = key !== "" ? key : attribute; + quantizeArray.push(ACur[key]); + ACur[key] === 0 && zeroPoints.push(value.state); + return null; + }); + // if (category === "Total General Sign-Up") { + // categoryRecord = ACur; + // } else if (category === "Total Continuous Sign-Up") { + // categoryRecord = BCur; + // } else if (category === "CREP Only") { + // categoryRecord = CCur; + // } else if (category === "Continuous Non-CREP") { + // categoryRecord = DCur; + // } else if (category === "Farmable Wetland") { + // categoryRecord = ECur; + // } else { + // categoryRecord = FCur; + // } + // Object.values(statePerformance).map((value) => { + // if (Array.isArray(value)) { + // const statuteRecord = value[0].statutes; + // const ACur = statuteRecord.find((s) => s.statuteName === "2018 Practices"); + // const AArray = ACur.practiceCategories; + // const BCur = statuteRecord.find((s) => s.statuteName === "2014 Eligible Land"); + // const BArray = BCur.practiceCategories; + // const TotalArray = AArray.concat(BArray); + // if (category === "2018 Practices") { + // categoryRecord = statuteRecord[0]; + // } else if (category === "2014 Eligible Land") { + // categoryRecord = statuteRecord[1]; + // } else { + // categoryRecord = TotalArray.find((s) => s.practiceCategoryName === category); + // } + // if (categoryRecord !== undefined) { + // if (category === "2018 Practices" || category === "2014 Eligible Land") + // quantizeArray.push(categoryRecord.statutePaymentInDollars); + // else quantizeArray.push(categoryRecord.paymentInDollars); + // } + // } + // return null; + // }); + const maxValue = Math.max(...quantizeArray); + const label1 = (maxValue / 5) * 0; + const label2 = (maxValue / 5) * 1; + const label3 = (maxValue / 5) * 2; + const label4 = (maxValue / 5) * 3; + const label5 = (maxValue / 5) * 4; + return ( +
+ {maxValue !== 0 ? ( + + = 1000000 + ? `$${Number(label2 / 1000000).toLocaleString(undefined, { + maximumFractionDigits: 0 + })}M` + : `$${Number(label2 / 1000.0).toLocaleString(undefined, { + maximumFractionDigits: 1 + })}K` + } + label3={ + label3 >= 1000000 + ? `$${Number(label3 / 1000000).toLocaleString(undefined, { + maximumFractionDigits: 0 + })}M` + : `$${Number(label3 / 1000.0).toLocaleString(undefined, { + maximumFractionDigits: 1 + })}K` + } + label4={ + label4 >= 1000000 + ? `$${Number(label4 / 1000000).toLocaleString(undefined, { + maximumFractionDigits: 0 + })}M` + : `$${Number(label4 / 1000.0).toLocaleString(undefined, { + maximumFractionDigits: 1 + })}K` + } + label5={ + label5 >= 1000000 + ? `$${Number(label5 / 1000000).toLocaleString(undefined, { + maximumFractionDigits: 0 + })}M` + : `$${Number(label5 / 1000.0).toLocaleString(undefined, { + maximumFractionDigits: 1 + })}K` + } + label6={ + maxValue >= 1000000 + ? `$${Number(maxValue / 1000000).toLocaleString(undefined, { + maximumFractionDigits: 0 + })}M` + : `$${Number(maxValue / 1000.0).toLocaleString(undefined, { + maximumFractionDigits: 1 + })}K` + } + /> + + ) : ( + + + + {title} + + + + + {title} data is unavailable for all states. + + + + )} + +
+ + {content} + +
+
+ ); +}; + +const getValueFromAttr = (stateRecord, attribute): string => { + let ans = ""; + Object.keys(stateRecord).forEach((key) => { + const match = key.match(/^(.*?)(?=\s*InDollars)/); + const extractedKey = match ? match[1] : key; + if (extractedKey === attribute) { + ans = key; + } + }); + return ans; +}; + +export default CategoryMap; diff --git a/src/components/crp/CategoryTable.tsx b/src/components/crp/CategoryTable.tsx new file mode 100644 index 00000000..6fed9c81 --- /dev/null +++ b/src/components/crp/CategoryTable.tsx @@ -0,0 +1,299 @@ +import React from "react"; +import styled from "styled-components"; +import { useTable, useSortBy } from "react-table"; +import Box from "@mui/material/Box"; +import "../../styles/table.css"; + +const Styles = styled.div` + padding: 1rem; + + table { + border-spacing: 0; + border: 1px solid #e4ebe7; + border-left: none; + border-right: none; + + tr { + :last-child { + td { + border-bottom: 0; + } + } + } + + th { + background-color: #f1f1f1; + padding-top: 1rem; + padding-bottom: 1rem; + } + + td { + margin: 0; + padding: 0rem; + padding-left: 3rem; + padding-right: 3rem; + border-bottom: 1px solid #e4ebe7; + border-right: none; + + :last-child { + border-right: 2rem; + } + } + } +`; + +// eslint-disable-next-line +function Table({ columns, data }: { columns: any; data: any; statePerformance: any }) { + const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } = useTable( + { + columns, + data + }, + useSortBy + ); + + const firstPageRows = rows.slice(0, 50); + + return ( + <> +
+ + {headerGroups.map((headerGroup) => ( + + {headerGroup.headers.map((column) => ( + // Add the sorting props to control sorting. + + ))} + + ))} + + + { + // eslint-disable-next-line + firstPageRows.map((row, i) => { + prepareRow(row); + return ( + + {row.cells.map((cell) => { + return ( + + ); + })} + + ); + }) + } + +
+ + {column.render("Header")} +
+ {(() => { + if (!column.isSorted) return {"\u{2B83}"}; + if (column.isSortedDesc) return {"\u{25BC}"}; + return {"\u{25B2}"}; + })()} +
+
+
+ {cell.render("Cell")} +
+
+
+ Showing the first {rows.length} results of {rows.length} rows +
+ + ); +} + +function App({ category, statePerformance }: { category: string; statePerformance: any }): JSX.Element { + const crpTableData: any[] = []; + let categoryRecord = []; + // eslint-disable-next-line no-restricted-syntax + for (const [key, value] of Object.entries(statePerformance)) { + if (Array.isArray(value)) { + const statuteRecord = value[0].statutes; + const ACur = statuteRecord.find((s) => s.statuteName === "2018 Practices"); + const AArray = ACur.practiceCategories; + const BCur = statuteRecord.find((s) => s.statuteName === "2014 Eligible Land"); + const BArray = BCur.practiceCategories; + const TotalArray = AArray.concat(BArray); + if (category === "2018 Practices") { + categoryRecord = statuteRecord[0]; + } else if (category === "2014 Eligible Land") { + categoryRecord = statuteRecord[1]; + } else { + categoryRecord = TotalArray.find((s) => s.practiceCategoryName === category); + } + if (categoryRecord !== undefined) { + const paymentInDollars = + category === "2018 Practices" || category === "2014 Eligible Land" + ? categoryRecord.statutePaymentInDollars + : categoryRecord.paymentInDollars; + const paymentInPercentageWithinState = + category === "2018 Practices" || category === "2014 Eligible Land" + ? categoryRecord.statutePaymentInPercentageWithinState + : categoryRecord.paymentInPercentageWithinState; + const newRecord = () => { + return { + state: key, + categoryBenefit: `$${Number(paymentInDollars).toLocaleString(undefined, { + minimumFractionDigits: 2 + })}`, + categoryPercentage: `${paymentInPercentageWithinState.toString()}%`, + crpBenefit: `$${value[0].totalPaymentInDollars.toLocaleString(undefined, { + minimumFractionDigits: 2 + })}`, + percentage: `${value[0].totalPaymentInPercentageNationwide.toString()}%` + }; + }; + crpTableData.push(newRecord()); + } + } + } + + function compareWithDollarSign(rowA, rowB, id, desc) { + const a = Number.parseFloat(rowA.values[id].substring(1).replaceAll(",", "")); + const b = Number.parseFloat(rowB.values[id].substring(1).replaceAll(",", "")); + if (a > b) return 1; + if (a < b) return -1; + return 0; + } + + function compareWithPercentSign(rowA, rowB, id, desc) { + const a = Number.parseFloat(rowA.values[id].replaceAll("%", "")); + const b = Number.parseFloat(rowB.values[id].replaceAll("%", "")); + if (a > b) return 1; + if (a < b) return -1; + return 0; + } + + const columns = React.useMemo( + () => [ + { + Header: ( + + STATES + + ), + accessor: "state", + Cell: function styleCells(props: { + value: boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | null | undefined; + }) { + return ( +
+ + {props.value} + +
+ ); + } + }, + { + Header: ( + + {`${category} Benefit`.toUpperCase()} + + ), + accessor: "categoryBenefit", + sortType: compareWithDollarSign, + Cell: function styleCells(props: { + value: boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | null | undefined; + }) { + return ( +
+ + {props.value} + +
+ ); + } + }, + { + Header: ( + + {`${category} Percentage Within State`.toUpperCase()} + + ), + accessor: "categoryPercentage", + sortType: compareWithPercentSign, + Cell: function styleCells(props: { + value: boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | null | undefined; + }) { + return ( +
+ + {props.value} + +
+ ); + } + }, + { + Header: ( + + CRP BENEFITS + + ), + accessor: "crpBenefit", + sortType: compareWithDollarSign, + Cell: function styleCells(row) { + return
{row.value}
; + } + }, + { + Header: PCT. NATIONWIDE, + accessor: "percentage", + sortType: compareWithPercentSign, + Cell: function styleCells(row) { + return
{row.value}
; + } + } + ], + [] + ); + + return ( + + + + + + ); +} + +export default App; diff --git a/src/main.tsx b/src/main.tsx index be345804..ef383461 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -3,6 +3,7 @@ import { Routes, Route, useLocation } from "react-router-dom"; import LandingPage from "./pages/LandingPage"; import EQIPPage from "./pages/EQIPPage"; import CSPPage from "./pages/CSPPage"; +import CRPPage from "./pages/CRPPage"; import SNAPPage from "./pages/SNAPPage"; import TitleIPage from "./pages/TitleIPage"; @@ -21,6 +22,7 @@ export default function Main(): JSX.Element { } /> } /> } /> + } /> } /> } /> diff --git a/src/pages/CRPPage.tsx b/src/pages/CRPPage.tsx new file mode 100644 index 00000000..ec063a32 --- /dev/null +++ b/src/pages/CRPPage.tsx @@ -0,0 +1,284 @@ +import Box from "@mui/material/Box"; +import * as React from "react"; +import { createTheme, ThemeProvider, Typography } from "@mui/material"; +import NavBar from "../components/NavBar"; +import Drawer from "../components/ProgramDrawer"; +import SemiDonutChart from "../components/SemiDonutChart"; +// import DataTable from "../components/crp/CRPTotalTable"; +import CRPTotalMap from "../components/crp/CRPTotalMap"; +// import CategoryTable from "../components/crp/CategoryTable"; +import CategoryMap from "../components/crp/CategoryMap"; +import { config } from "../app.config"; +import { convertAllState, getJsonDataFromUrl } from "../utils/apiutil"; +import NavSearchBar from "../components/shared/NavSearchBar"; + +export default function CRPPage(): JSX.Element { + const year = "2018-2022"; + const attribute = "paymentInDollars"; + const [checked, setChecked] = React.useState(0); + + const [stateDistributionData, setStateDistributionData] = React.useState({}); + const [stateCodesData, setStateCodesData] = React.useState({}); + const [allStatesData, setAllStatesData] = React.useState([]); + const [totalChartData, setTotalChartData] = React.useState([{}]); + const [subChartData, setSubChartData] = React.useState([{}]); + const [zeroCategories, setZeroCategories] = React.useState([]); + const [totalCrp, setTotalCrp] = React.useState(0); + const [totalSub, setTotalSub] = React.useState(0); + + const defaultTheme = createTheme(); + const zeroCategory = []; + let totalCRPPaymentInDollars = 0; + let generalSignUpPaymentInDollars = 0; + let continuousSingUpPaymentInDollars = 0; + let crepPaymentInDollars = 0; + let nocCrepPaymentInDollars = 0; + let wetlandPaymentInDollars = 0; + let grasslandPyamentInDollars = 0; + + React.useEffect(() => { + const allstates_url = `${config.apiUrl}/states`; + getJsonDataFromUrl(allstates_url).then((response) => { + setAllStatesData(response); + }); + + const statecode_url = `${config.apiUrl}/statecodes`; + getJsonDataFromUrl(statecode_url).then((response) => { + const converted_json = convertAllState(response); + setStateCodesData(converted_json); + }); + + const statedistribution_url = `${config.apiUrl}/programs/conservation/crp/state-distribution`; + getJsonDataFromUrl(statedistribution_url).then((response) => { + setStateDistributionData(response); + }); + + const chartData_url = `${config.apiUrl}/programs/conservation/crp/subprograms`; + getJsonDataFromUrl(chartData_url).then((response) => { + processData(response); + }); + }, []); + + const processData = (chartData) => { + if (chartData.programs === undefined) return; + + const cur1 = chartData.programs.find((s) => s.programName === "Total CRP"); + const cur2 = chartData.programs.find((s) => s.programName === "Total General Sign-Up"); + const cur3 = chartData.programs.find((s) => s.programName === "Total Continuous"); + let cur4; + let cur5; + let cur6; + const cur7 = chartData.programs.find((s) => s.programName === "Grassland"); + const subCurs = cur3.subPrograms; + + if (subCurs !== undefined) { + subCurs.forEach((value) => { + if (value.programName === "CREP Only") { + cur4 = value; + } else if (value.programName === "Continuous Non-CREP") { + cur5 = value; + } else if (value.programName === "Farmable Wetland") { + cur6 = value; + } + }); + + totalCRPPaymentInDollars = cur1.paymentInDollars; + setTotalCrp(totalCRPPaymentInDollars); + if (totalCRPPaymentInDollars === 0) zeroCategory.push("Total CRP"); + generalSignUpPaymentInDollars = cur2.paymentInDollars; + if (generalSignUpPaymentInDollars === 0) zeroCategory.push("Total General Sign-Up"); + continuousSingUpPaymentInDollars = cur3.paymentInDollars; + if (continuousSingUpPaymentInDollars === 0) zeroCategory.push("Total Continuous"); + setTotalSub(continuousSingUpPaymentInDollars); + crepPaymentInDollars = cur4.paymentInDollars; + if (crepPaymentInDollars === 0) zeroCategory.push("CREP Only"); + nocCrepPaymentInDollars = cur5.paymentInDollars; + if (nocCrepPaymentInDollars === 0) zeroCategory.push("Continuous Non-CREP"); + wetlandPaymentInDollars = cur6.paymentInDollars; + if (wetlandPaymentInDollars === 0) zeroCategory.push("Farmable Wetland"); + grasslandPyamentInDollars = cur7.paymentInDollars; + if (grasslandPyamentInDollars === 0) zeroCategory.push("Grassland"); + console.log(totalCRPPaymentInDollars); + console.log(generalSignUpPaymentInDollars); + console.log(continuousSingUpPaymentInDollars); + + setZeroCategories(zeroCategory); + + setTotalChartData([ + { name: "Total General Sign-Up", value: generalSignUpPaymentInDollars, color: "#2F7164" }, + { name: "Total Continuous", value: continuousSingUpPaymentInDollars, color: "#869397" }, + { name: "Grassland", value: grasslandPyamentInDollars, color: "#9CBAB4" } + ]); + + setSubChartData([ + { name: "CREP Only", value: crepPaymentInDollars, color: "#2F7164" }, + { name: "Continuous Non-CREP", value: nocCrepPaymentInDollars, color: "#869397" }, + { name: "Farmable Wetland", value: wetlandPaymentInDollars, color: "#9CBAB4" } + ]); + } + }; + + return ( + + {Object.keys(stateCodesData).length > 0 && + Object.keys(allStatesData).length > 0 && + Object.keys(stateDistributionData).length > 0 ? ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CRP: State Performance by Category + + + + CRP provides five-year annual contract payments to farmers in return for increasing, + improving or advancing conservation across the entire farm operation. + + + +
+ + + + + + + 2 && checked < 6 ? "block" : "none" }}> + + +
+ + + + Performance by State + + +
+
+ ) : ( +

Loading data...

+ )} +
+ ); +} From 82218ea19f191f212b3d45afeccf0032588a15eb Mon Sep 17 00:00:00 2001 From: YONG WOOK KIM Date: Fri, 1 Sep 2023 11:11:14 -0500 Subject: [PATCH 02/16] removed console command --- src/pages/CRPPage.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/pages/CRPPage.tsx b/src/pages/CRPPage.tsx index ec063a32..314db6ba 100644 --- a/src/pages/CRPPage.tsx +++ b/src/pages/CRPPage.tsx @@ -98,9 +98,6 @@ export default function CRPPage(): JSX.Element { if (wetlandPaymentInDollars === 0) zeroCategory.push("Farmable Wetland"); grasslandPyamentInDollars = cur7.paymentInDollars; if (grasslandPyamentInDollars === 0) zeroCategory.push("Grassland"); - console.log(totalCRPPaymentInDollars); - console.log(generalSignUpPaymentInDollars); - console.log(continuousSingUpPaymentInDollars); setZeroCategories(zeroCategory); From 2ee79bde6956331d9fcadd9972cb22b4e6b978e7 Mon Sep 17 00:00:00 2001 From: YONG WOOK KIM Date: Fri, 1 Sep 2023 15:38:25 -0500 Subject: [PATCH 03/16] category table works --- src/components/crp/CRPTotalMap.tsx | 13 +--- src/components/crp/CRPTotalTable.tsx | 87 +++++++++++++++++++-- src/components/crp/CategoryMap.tsx | 60 +-------------- src/components/crp/CategoryTable.tsx | 110 ++++++++++++++++----------- src/pages/CRPPage.tsx | 106 ++++++++++++++++++++++++-- src/utils/apiutil.tsx | 12 +++ 6 files changed, 263 insertions(+), 125 deletions(-) diff --git a/src/components/crp/CRPTotalMap.tsx b/src/components/crp/CRPTotalMap.tsx index b54ed65c..b9cc47a4 100644 --- a/src/components/crp/CRPTotalMap.tsx +++ b/src/components/crp/CRPTotalMap.tsx @@ -10,6 +10,7 @@ import Typography from "@mui/material/Typography"; import PropTypes from "prop-types"; import "../../styles/map.css"; import HorizontalStackedBar from "../HorizontalStackedBar"; +import { getValueFromAttr } from "../../utils/apiutil"; const geoUrl = "https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json"; @@ -251,16 +252,4 @@ const CRPTotalMap = ({ ); }; -const getValueFromAttr = (stateRecord, attribute): string => { - let ans = ""; - Object.keys(stateRecord).forEach((key) => { - const match = key.match(/^(.*?)(?=\s*InDollars)/); - const extractedKey = match ? match[1] : key; - if (extractedKey === attribute) { - ans = key; - } - }); - return ans; -}; - export default CRPTotalMap; diff --git a/src/components/crp/CRPTotalTable.tsx b/src/components/crp/CRPTotalTable.tsx index 4f9256b1..31535892 100644 --- a/src/components/crp/CRPTotalTable.tsx +++ b/src/components/crp/CRPTotalTable.tsx @@ -111,7 +111,15 @@ function Table({ columns, data }: { columns: any; data: any }) { ); } -function App({ statePerformance }: { statePerformance: any }): JSX.Element { +function App({ + statePerformance, + year, + stateCodes +}: { + statePerformance: any; + year: any; + stateCodes: any; +}): JSX.Element { function compareWithDollarSign(rowA, rowB, id, desc) { const a = Number.parseFloat(rowA.values[id].substring(1).replaceAll(",", "")); const b = Number.parseFloat(rowB.values[id].substring(1).replaceAll(",", "")); @@ -128,21 +136,41 @@ function App({ statePerformance }: { statePerformance: any }): JSX.Element { return 0; } + function compareNumber(rowA, rowB, id, desc) { + const a = Number.parseInt(rowA.values[id].replaceAll(",", ""), 10); + const b = Number.parseInt(rowB.values[id].replaceAll(",", ""), 10); + if (a > b) return 1; + if (a < b) return -1; + return 0; + } + const crpTableData: any[] = []; // eslint-disable-next-line no-restricted-syntax - for (const [key, value] of Object.entries(statePerformance)) { + statePerformance[year].forEach((value) => { + const totalCrp = value.programs.find((s) => s.programName === "Total CRP"); + let stateName; + stateCodes.forEach((sValue) => { + if (sValue.code.toUpperCase() === value.state.toUpperCase()) { + stateName = sValue.name; + } + }); const newRecord = () => { return { - state: key, - crpBenefit: `$${value[0].totalPaymentInDollars + state: stateName, + crpBenefit: `$${totalCrp.paymentInDollars .toLocaleString(undefined, { minimumFractionDigits: 2 }) .toString()}`, - percentage: `${value[0].totalPaymentInPercentageNationwide.toString()}%` + percentage: `${totalCrp.paymentInPercentageNationwide.toString()}%`, + noContract: `${totalCrp.totalContracts + .toLocaleString(undefined, { minimumFractionDigits: 0 }) + .toString()}`, + noFarm: `${totalCrp.totalFarms.toLocaleString(undefined, { minimumFractionDigits: 0 }).toString()}`, + totAcre: `${totalCrp.totalAcre.toLocaleString(undefined, { minimumFractionDigits: 0 }).toString()}` }; }; crpTableData.push(newRecord()); - } + }); const columns = React.useMemo( () => [ @@ -150,7 +178,7 @@ function App({ statePerformance }: { statePerformance: any }): JSX.Element { Header: STATES, accessor: "state", paddingLeft: "5rem", - paddingRight: "32rem" + paddingRight: "5rem" }, { Header: ( @@ -174,6 +202,51 @@ function App({ statePerformance }: { statePerformance: any }): JSX.Element { Cell: function styleCells(row) { return
{row.value}
; } + }, + { + Header: ( + + NO. OF CONTRACTS + + ), + accessor: "noContract", + sortType: compareNumber, + Cell: function styleCells(row) { + return
{row.value}
; + } + }, + { + Header: ( + + NO. OF FARMS + + ), + accessor: "noFarm", + sortType: compareNumber, + Cell: function styleCells(row) { + return
{row.value}
; + } + }, + { + Header: ( + + ACRES + + ), + accessor: "totAcre", + sortType: compareNumber, + Cell: function styleCells(row) { + return
{row.value}
; + } } ], [] diff --git a/src/components/crp/CategoryMap.tsx b/src/components/crp/CategoryMap.tsx index 0dc19fa3..61be3fdd 100644 --- a/src/components/crp/CategoryMap.tsx +++ b/src/components/crp/CategoryMap.tsx @@ -9,6 +9,7 @@ import Typography from "@mui/material/Typography"; import PropTypes from "prop-types"; import "../../styles/map.css"; import HorizontalStackedBar from "../HorizontalStackedBar"; +import { getValueFromAttr } from "../../utils/apiutil"; const geoUrl = "https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json"; @@ -216,17 +217,7 @@ const CategoryMap = ({ const [content, setContent] = useState(""); const title = `${category} Benefits from ${year}`; const quantizeArray: number[] = []; - // let categoryRecord = {}; const zeroPoints = []; - // statePerformance[year].forEach((value) => { - // const programRecord = value.programs; - // const ACur = programRecord.find((s) => s.programName === category); - // let key = getValueFromAttr(ACur, attribute); - // key = key !== "" ? key : attribute; - // quantizeArray.push(ACur[key]); - // ACur[key] === 0 && zeroPoints.push(value.state); - // return null; - // }); statePerformance[year].forEach((value) => { const programRecord = value.programs; @@ -252,42 +243,7 @@ const CategoryMap = ({ ACur[key] === 0 && zeroPoints.push(value.state); return null; }); - // if (category === "Total General Sign-Up") { - // categoryRecord = ACur; - // } else if (category === "Total Continuous Sign-Up") { - // categoryRecord = BCur; - // } else if (category === "CREP Only") { - // categoryRecord = CCur; - // } else if (category === "Continuous Non-CREP") { - // categoryRecord = DCur; - // } else if (category === "Farmable Wetland") { - // categoryRecord = ECur; - // } else { - // categoryRecord = FCur; - // } - // Object.values(statePerformance).map((value) => { - // if (Array.isArray(value)) { - // const statuteRecord = value[0].statutes; - // const ACur = statuteRecord.find((s) => s.statuteName === "2018 Practices"); - // const AArray = ACur.practiceCategories; - // const BCur = statuteRecord.find((s) => s.statuteName === "2014 Eligible Land"); - // const BArray = BCur.practiceCategories; - // const TotalArray = AArray.concat(BArray); - // if (category === "2018 Practices") { - // categoryRecord = statuteRecord[0]; - // } else if (category === "2014 Eligible Land") { - // categoryRecord = statuteRecord[1]; - // } else { - // categoryRecord = TotalArray.find((s) => s.practiceCategoryName === category); - // } - // if (categoryRecord !== undefined) { - // if (category === "2018 Practices" || category === "2014 Eligible Land") - // quantizeArray.push(categoryRecord.statutePaymentInDollars); - // else quantizeArray.push(categoryRecord.paymentInDollars); - // } - // } - // return null; - // }); + const maxValue = Math.max(...quantizeArray); const label1 = (maxValue / 5) * 0; const label2 = (maxValue / 5) * 1; @@ -394,16 +350,4 @@ const CategoryMap = ({ ); }; -const getValueFromAttr = (stateRecord, attribute): string => { - let ans = ""; - Object.keys(stateRecord).forEach((key) => { - const match = key.match(/^(.*?)(?=\s*InDollars)/); - const extractedKey = match ? match[1] : key; - if (extractedKey === attribute) { - ans = key; - } - }); - return ans; -}; - export default CategoryMap; diff --git a/src/components/crp/CategoryTable.tsx b/src/components/crp/CategoryTable.tsx index 6fed9c81..a7362e1e 100644 --- a/src/components/crp/CategoryTable.tsx +++ b/src/components/crp/CategoryTable.tsx @@ -112,51 +112,67 @@ function Table({ columns, data }: { columns: any; data: any; statePerformance: a ); } -function App({ category, statePerformance }: { category: string; statePerformance: any }): JSX.Element { +function App({ + category, + statePerformance, + year, + stateCodes +}: { + category: string; + statePerformance: any; + year: any; + stateCodes: any; +}): JSX.Element { const crpTableData: any[] = []; - let categoryRecord = []; - // eslint-disable-next-line no-restricted-syntax - for (const [key, value] of Object.entries(statePerformance)) { - if (Array.isArray(value)) { - const statuteRecord = value[0].statutes; - const ACur = statuteRecord.find((s) => s.statuteName === "2018 Practices"); - const AArray = ACur.practiceCategories; - const BCur = statuteRecord.find((s) => s.statuteName === "2014 Eligible Land"); - const BArray = BCur.practiceCategories; - const TotalArray = AArray.concat(BArray); - if (category === "2018 Practices") { - categoryRecord = statuteRecord[0]; - } else if (category === "2014 Eligible Land") { - categoryRecord = statuteRecord[1]; - } else { - categoryRecord = TotalArray.find((s) => s.practiceCategoryName === category); - } - if (categoryRecord !== undefined) { - const paymentInDollars = - category === "2018 Practices" || category === "2014 Eligible Land" - ? categoryRecord.statutePaymentInDollars - : categoryRecord.paymentInDollars; - const paymentInPercentageWithinState = - category === "2018 Practices" || category === "2014 Eligible Land" - ? categoryRecord.statutePaymentInPercentageWithinState - : categoryRecord.paymentInPercentageWithinState; - const newRecord = () => { - return { - state: key, - categoryBenefit: `$${Number(paymentInDollars).toLocaleString(undefined, { - minimumFractionDigits: 2 - })}`, - categoryPercentage: `${paymentInPercentageWithinState.toString()}%`, - crpBenefit: `$${value[0].totalPaymentInDollars.toLocaleString(undefined, { - minimumFractionDigits: 2 - })}`, - percentage: `${value[0].totalPaymentInPercentageNationwide.toString()}%` - }; - }; - crpTableData.push(newRecord()); - } + statePerformance[year].forEach((value) => { + const totalCrp = value.programs.find((s) => s.programName === "Total CRP"); + let categoryCrp; + if ( + category === "Total General Sign-Up" || + category === "Total Continuous Sign-Up" || + category === "Grassland" + ) { + categoryCrp = value.programs.find((s) => s.programName === category); + } else if (category === "CREP Only" || category === "Continuous Non-CREP" || category === "Farmable Wetland") { + const contSingUp = value.programs.find((s) => s.programName === "Total Continuous Sign-Up"); + const subPrograms = contSingUp.subPrograms; + subPrograms.forEach((subValue) => { + if (subValue.programName === category) { + categoryCrp = subValue; + } + }); } - } + + let stateName; + const percentageValue = + (Number.parseInt(categoryCrp.paymentInDollars, 10) / Number.parseInt(totalCrp.paymentInDollars, 10)) * 100; + stateCodes.forEach((sValue) => { + if (sValue.code.toUpperCase() === value.state.toUpperCase()) { + stateName = sValue.name; + } + }); + const newRecord = () => { + return { + state: stateName, + categoryBenefit: `$${categoryCrp.paymentInDollars + .toLocaleString(undefined, { minimumFractionDigits: 2 }) + .toString()}`, + categoryPercentage: `${percentageValue + .toLocaleString(undefined, { minimumFractionDigits: 2 }) + .toString()}%`, + crpBenefit: `$${totalCrp.paymentInDollars + .toLocaleString(undefined, { minimumFractionDigits: 2 }) + .toString()}`, + percentage: `${categoryCrp.paymentInPercentageNationwide.toString()}%` + // noContract: `${totalCrp.totalContracts + // .toLocaleString(undefined, { minimumFractionDigits: 0 }) + // .toString()}`, + // noFarm: `${totalCrp.totalFarms.toLocaleString(undefined, { minimumFractionDigits: 0 }).toString()}`, + // totAcre: `${totalCrp.totalAcre.toLocaleString(undefined, { minimumFractionDigits: 0 }).toString()}` + }; + }; + crpTableData.push(newRecord()); + }); function compareWithDollarSign(rowA, rowB, id, desc) { const a = Number.parseFloat(rowA.values[id].substring(1).replaceAll(",", "")); @@ -174,6 +190,14 @@ function App({ category, statePerformance }: { category: string; statePerformanc return 0; } + function compareNumber(rowA, rowB, id, desc) { + const a = Number.parseInt(rowA.values[id].replaceAll(",", ""), 10); + const b = Number.parseInt(rowB.values[id].replaceAll(",", ""), 10); + if (a > b) return 1; + if (a < b) return -1; + return 0; + } + const columns = React.useMemo( () => [ { diff --git a/src/pages/CRPPage.tsx b/src/pages/CRPPage.tsx index 314db6ba..3fb4f6a0 100644 --- a/src/pages/CRPPage.tsx +++ b/src/pages/CRPPage.tsx @@ -4,9 +4,9 @@ import { createTheme, ThemeProvider, Typography } from "@mui/material"; import NavBar from "../components/NavBar"; import Drawer from "../components/ProgramDrawer"; import SemiDonutChart from "../components/SemiDonutChart"; -// import DataTable from "../components/crp/CRPTotalTable"; +import DataTable from "../components/crp/CRPTotalTable"; import CRPTotalMap from "../components/crp/CRPTotalMap"; -// import CategoryTable from "../components/crp/CategoryTable"; +import CategoryTable from "../components/crp/CategoryTable"; import CategoryMap from "../components/crp/CategoryMap"; import { config } from "../app.config"; import { convertAllState, getJsonDataFromUrl } from "../utils/apiutil"; @@ -19,6 +19,7 @@ export default function CRPPage(): JSX.Element { const [stateDistributionData, setStateDistributionData] = React.useState({}); const [stateCodesData, setStateCodesData] = React.useState({}); + const [stateCodesArray, setStateCodesArray] = React.useState({}); const [allStatesData, setAllStatesData] = React.useState([]); const [totalChartData, setTotalChartData] = React.useState([{}]); const [subChartData, setSubChartData] = React.useState([{}]); @@ -44,6 +45,7 @@ export default function CRPPage(): JSX.Element { const statecode_url = `${config.apiUrl}/statecodes`; getJsonDataFromUrl(statecode_url).then((response) => { + setStateCodesArray(response); const converted_json = convertAllState(response); setStateCodesData(converted_json); }); @@ -227,12 +229,51 @@ export default function CRPPage(): JSX.Element { /> - + + + + CRP: Category of Practice Performance + + + + + + + CRP: Category of Practice Performance + + + + 2 && checked < 6 ? "block" : "none" + }} + > - CRP: State Performance by Category + CRP Total Continuous: Sub-Category of Practice Performance + + CRP provides five-year annual contract payments to farmers in return for increasing, improving or advancing conservation across the entire farm operation. @@ -268,9 +309,64 @@ export default function CRPPage(): JSX.Element { - Performance by State + Overall Performance of States + + + + + + + + + + + + + + + + + + + + + ) : ( diff --git a/src/utils/apiutil.tsx b/src/utils/apiutil.tsx index 99e9be50..8d417e1d 100644 --- a/src/utils/apiutil.tsx +++ b/src/utils/apiutil.tsx @@ -17,3 +17,15 @@ export function convertAllState(inlist) { return JSON.parse(conv_str); } + +export const getValueFromAttr = (stateRecord, attribute): string => { + let ans = ""; + Object.keys(stateRecord).forEach((key) => { + const match = key.match(/^(.*?)(?=\s*InDollars)/); + const extractedKey = match ? match[1] : key; + if (extractedKey === attribute) { + ans = key; + } + }); + return ans; +}; From c157e48f3d8eb6a0c85ca8871f913d8a7844ed77 Mon Sep 17 00:00:00 2001 From: YONG WOOK KIM Date: Fri, 1 Sep 2023 15:43:51 -0500 Subject: [PATCH 04/16] fixed NaN value problem --- src/components/crp/CategoryTable.tsx | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/components/crp/CategoryTable.tsx b/src/components/crp/CategoryTable.tsx index a7362e1e..f0a03c41 100644 --- a/src/components/crp/CategoryTable.tsx +++ b/src/components/crp/CategoryTable.tsx @@ -144,8 +144,13 @@ function App({ } let stateName; - const percentageValue = - (Number.parseInt(categoryCrp.paymentInDollars, 10) / Number.parseInt(totalCrp.paymentInDollars, 10)) * 100; + let percentageValue = 0; + if (Number.parseInt(totalCrp.paymentInDollars, 10) > 0) { + percentageValue = + (Number.parseInt(categoryCrp.paymentInDollars, 10) / Number.parseInt(totalCrp.paymentInDollars, 10)) * + 100; + } + stateCodes.forEach((sValue) => { if (sValue.code.toUpperCase() === value.state.toUpperCase()) { stateName = sValue.name; @@ -190,14 +195,6 @@ function App({ return 0; } - function compareNumber(rowA, rowB, id, desc) { - const a = Number.parseInt(rowA.values[id].replaceAll(",", ""), 10); - const b = Number.parseInt(rowB.values[id].replaceAll(",", ""), 10); - if (a > b) return 1; - if (a < b) return -1; - return 0; - } - const columns = React.useMemo( () => [ { From 72f8488a7a60192c020c70093e12597d6622a4c1 Mon Sep 17 00:00:00 2001 From: YONG WOOK KIM Date: Thu, 7 Sep 2023 12:23:03 -0500 Subject: [PATCH 05/16] added percentage within state values --- src/components/crp/CRPTotalMap.tsx | 2 +- src/components/crp/CategoryMap.tsx | 3 ++- src/components/crp/CategoryTable.tsx | 19 +++++++------------ 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/components/crp/CRPTotalMap.tsx b/src/components/crp/CRPTotalMap.tsx index b9cc47a4..e1fa84b8 100644 --- a/src/components/crp/CRPTotalMap.tsx +++ b/src/components/crp/CRPTotalMap.tsx @@ -206,7 +206,7 @@ const CRPTotalMap = ({
{ const [content, setContent] = useState(""); - const title = `${category} Benefits from ${year}`; + let title = `CRP ${category} from ${year}`; const quantizeArray: number[] = []; const zeroPoints = []; @@ -231,6 +231,7 @@ const CategoryMap = ({ } else if (category === "CREP Only" || category === "Continuous Non-CREP" || category === "Farmable Wetland") { const contSingUp = programRecord.find((s) => s.programName === "Total Continuous Sign-Up"); const subPrograms = contSingUp.subPrograms; + title = `CRP Total Continuous, ${category} from ${year}`; subPrograms.forEach((subValue) => { if (subValue.programName === category) { ACur = subValue; diff --git a/src/components/crp/CategoryTable.tsx b/src/components/crp/CategoryTable.tsx index f0a03c41..a5d573dd 100644 --- a/src/components/crp/CategoryTable.tsx +++ b/src/components/crp/CategoryTable.tsx @@ -144,12 +144,12 @@ function App({ } let stateName; - let percentageValue = 0; - if (Number.parseInt(totalCrp.paymentInDollars, 10) > 0) { - percentageValue = - (Number.parseInt(categoryCrp.paymentInDollars, 10) / Number.parseInt(totalCrp.paymentInDollars, 10)) * - 100; - } + // let percentageValue = 0; + // if (Number.parseInt(totalCrp.paymentInDollars, 10) > 0) { + // percentageValue = + // (Number.parseInt(categoryCrp.paymentInDollars, 10) / Number.parseInt(totalCrp.paymentInDollars, 10)) * + // 100; + // } stateCodes.forEach((sValue) => { if (sValue.code.toUpperCase() === value.state.toUpperCase()) { @@ -162,18 +162,13 @@ function App({ categoryBenefit: `$${categoryCrp.paymentInDollars .toLocaleString(undefined, { minimumFractionDigits: 2 }) .toString()}`, - categoryPercentage: `${percentageValue + categoryPercentage: `${categoryCrp.paymentInPercentageWithinState .toLocaleString(undefined, { minimumFractionDigits: 2 }) .toString()}%`, crpBenefit: `$${totalCrp.paymentInDollars .toLocaleString(undefined, { minimumFractionDigits: 2 }) .toString()}`, percentage: `${categoryCrp.paymentInPercentageNationwide.toString()}%` - // noContract: `${totalCrp.totalContracts - // .toLocaleString(undefined, { minimumFractionDigits: 0 }) - // .toString()}`, - // noFarm: `${totalCrp.totalFarms.toLocaleString(undefined, { minimumFractionDigits: 0 }).toString()}`, - // totAcre: `${totalCrp.totalAcre.toLocaleString(undefined, { minimumFractionDigits: 0 }).toString()}` }; }; crpTableData.push(newRecord()); From 625e9b44db69a81a9863a50f704451a77b155d92 Mon Sep 17 00:00:00 2001 From: YONG WOOK KIM Date: Thu, 7 Sep 2023 12:35:23 -0500 Subject: [PATCH 06/16] modified changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 161adb82..7cf64607 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Add Crop Insurance page and corresponding components for its attributes [#125](https://github.com/policy-design-lab/pdl-frontend/issues/125) - Added Average Insured Area In Acres sub-page to Crop Insurance pages [#183](https://github.com/policy-design-lab/pdl-frontend/issues/183) +- CRP page and corresponding components for its attributes [#170](https://github.com/policy-design-lab/pdl-frontend/issues/170) ### Changed From f461102f5348432cefd47cdce5ed4a321e0ea36a Mon Sep 17 00:00:00 2001 From: YONG WOOK KIM Date: Thu, 7 Sep 2023 14:20:01 -0500 Subject: [PATCH 07/16] modified changelog --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ade6bf34..957c0e92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,13 +4,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added +- CRP page and corresponding components for its attributes [#170](https://github.com/policy-design-lab/pdl-frontend/issues/170) + ## [0.8.0] - 2023-09-06 ### Added - Add Crop Insurance page and corresponding components for its attributes [#125](https://github.com/policy-design-lab/pdl-frontend/issues/125) - Added Average Insured Area In Acres sub-page to Crop Insurance pages [#183](https://github.com/policy-design-lab/pdl-frontend/issues/183) -- CRP page and corresponding components for its attributes [#170](https://github.com/policy-design-lab/pdl-frontend/issues/170) ### Changed From 8ce4890584852c92078e4f949c23ae76daec07cb Mon Sep 17 00:00:00 2001 From: YONG WOOK KIM Date: Mon, 11 Sep 2023 10:15:41 -0500 Subject: [PATCH 08/16] updated CRP blob in CRP page --- src/pages/CRPPage.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pages/CRPPage.tsx b/src/pages/CRPPage.tsx index 3fb4f6a0..0f367372 100644 --- a/src/pages/CRPPage.tsx +++ b/src/pages/CRPPage.tsx @@ -275,8 +275,11 @@ export default function CRPPage(): JSX.Element { - CRP provides five-year annual contract payments to farmers in return for increasing, - improving or advancing conservation across the entire farm operation. + CRP provides annual rental payments to landowners in return for conservation practices + that remove the acres from production and implements conservation cover (e.g., grasses + and/or trees). CRP contracts are for 10 or 15 years and enrolled land can be an entire + field or portions of a field (e.g., grass waterways or buffers). CRP spending will be + visualized using the following categories. From 772931774d0a8b017f62c3db1206c22c5eb79ac6 Mon Sep 17 00:00:00 2001 From: YONG WOOK KIM Date: Mon, 11 Sep 2023 11:56:50 -0500 Subject: [PATCH 09/16] increased padding in category table --- src/components/crp/CategoryTable.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/crp/CategoryTable.tsx b/src/components/crp/CategoryTable.tsx index a5d573dd..6055e1cf 100644 --- a/src/components/crp/CategoryTable.tsx +++ b/src/components/crp/CategoryTable.tsx @@ -30,8 +30,8 @@ const Styles = styled.div` td { margin: 0; padding: 0rem; - padding-left: 3rem; - padding-right: 3rem; + padding-left: 5rem; + padding-right: 5rem; border-bottom: 1px solid #e4ebe7; border-right: none; From a683980de279d52c94f0ecd0b673cdf4e5448b1e Mon Sep 17 00:00:00 2001 From: YONG WOOK KIM Date: Mon, 11 Sep 2023 12:25:03 -0500 Subject: [PATCH 10/16] added as of 2022 caption in main table --- src/components/crp/CRPTotalTable.tsx | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/components/crp/CRPTotalTable.tsx b/src/components/crp/CRPTotalTable.tsx index 31535892..7ae991b3 100644 --- a/src/components/crp/CRPTotalTable.tsx +++ b/src/components/crp/CRPTotalTable.tsx @@ -184,9 +184,9 @@ function App({ Header: ( - CRP BENEFITS + TOTAL CRP BENEFITS ), accessor: "crpBenefit", @@ -196,7 +196,14 @@ function App({ } }, { - Header: PCT. NATIONWIDE, + Header: ( + + PCT. NATIONWIDE
(as of Sep. 2022) +
+ ), accessor: "percentage", sortType: compareWithPercentSign, Cell: function styleCells(row) { @@ -207,9 +214,9 @@ function App({ Header: ( - NO. OF CONTRACTS + NO. OF CONTRACTS
(as of Sep. 2022)
), accessor: "noContract", @@ -222,9 +229,9 @@ function App({ Header: ( - NO. OF FARMS + NO. OF FARMS
(as of Sep. 2022)
), accessor: "noFarm", @@ -237,9 +244,9 @@ function App({ Header: ( - ACRES + ACRES
(as of Sep. 2022)
), accessor: "totAcre", From f12010b0552edfcfccb5cb12b91d24e88bc25c16 Mon Sep 17 00:00:00 2001 From: YONG WOOK KIM Date: Mon, 11 Sep 2023 13:35:44 -0500 Subject: [PATCH 11/16] changed the word from stewardship to reserve for CRP title --- src/pages/CRPPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/CRPPage.tsx b/src/pages/CRPPage.tsx index 0f367372..57d1085c 100644 --- a/src/pages/CRPPage.tsx +++ b/src/pages/CRPPage.tsx @@ -127,7 +127,7 @@ export default function CRPPage(): JSX.Element { Date: Wed, 13 Sep 2023 13:29:29 -0500 Subject: [PATCH 12/16] fixed drawer sizing problem --- src/components/ProgramDrawer.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/ProgramDrawer.tsx b/src/components/ProgramDrawer.tsx index 6e12f36a..e89c24f6 100644 --- a/src/components/ProgramDrawer.tsx +++ b/src/components/ProgramDrawer.tsx @@ -504,6 +504,8 @@ export default function ProgramDrawer({ prevCrpOpen.current = crpOpen; }, [crpOpen]); + const crpMenuHeight = window.innerHeight < 900 ? "38%" : "40%"; + return ( Date: Thu, 14 Sep 2023 10:30:38 -0500 Subject: [PATCH 13/16] #189-Update EQIP, CSP and CRP color legend --- CHANGELOG.md | 5 + src/components/HorizontalStackedBar.tsx | 71 --------- src/components/ProgramDrawer.tsx | 6 +- src/components/crp/CRPTotalMap.tsx | 87 +++++------ src/components/crp/CategoryMap.tsx | 115 ++++++--------- src/components/csp/CSPTotalMap.tsx | 85 +++++------ src/components/csp/CategoryMap.tsx | 120 +++++++-------- src/components/eqip/CategoryMap.tsx | 115 ++++++--------- src/components/eqip/EQIPTotalMap.tsx | 82 ++++++----- src/components/shared/DrawLegend.tsx | 185 ++++++++++++++---------- src/components/shared/NavSearchBar.tsx | 2 +- src/pages/EQIPPage.tsx | 18 ++- src/utils/legendConfig.json | 43 +++++- 13 files changed, 446 insertions(+), 488 deletions(-) delete mode 100644 src/components/HorizontalStackedBar.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 957c0e92..b56db423 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - CRP page and corresponding components for its attributes [#170](https://github.com/policy-design-lab/pdl-frontend/issues/170) +### Changed + +- Replace the color legends on the EQIP, CSP, and CRP pages with the customized scheme [189](https://github.com/policy-design-lab/pdl-frontend/issues/189) +- Adjusted the menu height of CRP page on small screen [#190](https://github.com/policy-design-lab/pdl-frontend/issues/190) + ## [0.8.0] - 2023-09-06 ### Added diff --git a/src/components/HorizontalStackedBar.tsx b/src/components/HorizontalStackedBar.tsx deleted file mode 100644 index c7a393b1..00000000 --- a/src/components/HorizontalStackedBar.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React from "react"; -import HSBar from "react-horizontal-stacked-bar-chart"; -import { Box, Typography } from "@mui/material"; - -export default function HorizontalStackedBar({ - title, - color1, - color2, - color3, - color4, - color5, - label1, - label2, - label3, - label4, - label5, - label6 -}: { - title: string; - color1: string; - color2: string; - color3: string; - color4: string; - color5: string; - label1: string; - label2: string; - label3: string; - label4: string; - label5: string; - label6: string; -}): JSX.Element { - return ( - - - - {title} - - - - - - - - ); -} diff --git a/src/components/ProgramDrawer.tsx b/src/components/ProgramDrawer.tsx index 6e12f36a..098db124 100644 --- a/src/components/ProgramDrawer.tsx +++ b/src/components/ProgramDrawer.tsx @@ -69,7 +69,11 @@ function EQIPCheckboxList({ setEQIPChecked, setShowPopUp, zeroCategory }) { } }} /> - + ); diff --git a/src/components/crp/CRPTotalMap.tsx b/src/components/crp/CRPTotalMap.tsx index e1fa84b8..0eb0a188 100644 --- a/src/components/crp/CRPTotalMap.tsx +++ b/src/components/crp/CRPTotalMap.tsx @@ -2,14 +2,14 @@ import React, { useState } from "react"; import { geoCentroid } from "d3-geo"; import { ComposableMap, Geographies, Geography, Marker, Annotation } from "react-simple-maps"; import ReactTooltip from "react-tooltip"; -import { scaleQuantize } from "d3-scale"; import Divider from "@mui/material/Divider"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; - +import * as d3 from "d3"; import PropTypes from "prop-types"; import "../../styles/map.css"; -import HorizontalStackedBar from "../HorizontalStackedBar"; +import legendConfig from "../../utils/legendConfig.json"; +import DrawLegend from "../shared/DrawLegend"; import { getValueFromAttr } from "../../utils/apiutil"; const geoUrl = "https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json"; @@ -27,10 +27,8 @@ const offsets = { }; const MapChart = (props) => { - const { setTooltipContent, maxValue, allStates, statePerformance, year, stateCodes } = props; - const colorScale = scaleQuantize() - .domain([0, maxValue]) - .range(["#F0F9E8", "#BAE4BC", "#7BCCC4", "#43A2CA", "#0868AC"]); + const { setTooltipContent, allStates, statePerformance, year, stateCodes, colorScale } = props; + return (
{allStates.length > 0 && statePerformance[year] !== undefined ? ( @@ -164,8 +162,7 @@ const MapChart = (props) => { }; MapChart.propTypes = { - setTooltipContent: PropTypes.func, - maxValue: PropTypes.number + setTooltipContent: PropTypes.func }; const CRPTotalMap = ({ @@ -195,42 +192,38 @@ const CRPTotalMap = ({ ACur[key] === 0 && zeroPoints.push(value.state); return null; }); + const category = "Total CRP"; + const years = "2018-2022"; const maxValue = Math.max(...quantizeArray); - const label1 = (maxValue / 5) * 0; - const label2 = (maxValue / 5) * 1; - const label3 = (maxValue / 5) * 2; - const label4 = (maxValue / 5) * 3; - const label5 = (maxValue / 5) * 4; + const mapColor = ["#F0F9E8", "#BAE4BC", "#7BCCC4", "#43A2CA", "#0868AC"]; + const customScale = legendConfig[category]; + const colorScale = d3.scaleThreshold(customScale, mapColor); return (
- + {maxValue !== 0 ? ( + + ) : ( +
+ {titleElement(category, years)} + + + {category} data in {years} is unavailable for all states. + + +
+ )}
@@ -251,5 +245,16 @@ const CRPTotalMap = ({
); }; - +const titleElement = (attribute, year): JSX.Element => { + return ( + + + {attribute} Benefits from {year} + {" "} + + In any state that appears in grey, there is no available data + + + ); +}; export default CRPTotalMap; diff --git a/src/components/crp/CategoryMap.tsx b/src/components/crp/CategoryMap.tsx index 091bb53d..07e8a20f 100644 --- a/src/components/crp/CategoryMap.tsx +++ b/src/components/crp/CategoryMap.tsx @@ -7,8 +7,10 @@ import Divider from "@mui/material/Divider"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import PropTypes from "prop-types"; +import * as d3 from "d3"; import "../../styles/map.css"; -import HorizontalStackedBar from "../HorizontalStackedBar"; +import legendConfig from "../../utils/legendConfig.json"; +import DrawLegend from "../shared/DrawLegend"; import { getValueFromAttr } from "../../utils/apiutil"; const geoUrl = "https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json"; @@ -26,10 +28,7 @@ const offsets = { }; const MapChart = (props) => { - const { year, setTooltipContent, category, maxValue, allStates, stateCodes, statePerformance } = props; - const colorScale = scaleQuantize() - .domain([0, maxValue]) - .range(["#F0F9E8", "#BAE4BC", "#7BCCC4", "#43A2CA", "#0868AC"]); + const { year, setTooltipContent, category, allStates, stateCodes, statePerformance, colorScale } = props; let categoryRecord; return (
@@ -195,8 +194,7 @@ const MapChart = (props) => { MapChart.propTypes = { year: PropTypes.string, setTooltipContent: PropTypes.func, - category: PropTypes.string, - maxValue: PropTypes.number + category: PropTypes.string }; const CategoryMap = ({ @@ -244,73 +242,37 @@ const CategoryMap = ({ ACur[key] === 0 && zeroPoints.push(value.state); return null; }); - + const years = "2018-2022"; const maxValue = Math.max(...quantizeArray); - const label1 = (maxValue / 5) * 0; - const label2 = (maxValue / 5) * 1; - const label3 = (maxValue / 5) * 2; - const label4 = (maxValue / 5) * 3; - const label5 = (maxValue / 5) * 4; + const mapColor = ["#F0F9E8", "#BAE4BC", "#7BCCC4", "#43A2CA", "#0868AC"]; + const customScale = legendConfig[category === "Grassland" ? "Grassland-CRP" : category]; + const colorScale = d3.scaleThreshold(customScale, mapColor); return (
{maxValue !== 0 ? ( - = 1000000 - ? `$${Number(label2 / 1000000).toLocaleString(undefined, { - maximumFractionDigits: 0 - })}M` - : `$${Number(label2 / 1000.0).toLocaleString(undefined, { - maximumFractionDigits: 1 - })}K` - } - label3={ - label3 >= 1000000 - ? `$${Number(label3 / 1000000).toLocaleString(undefined, { - maximumFractionDigits: 0 - })}M` - : `$${Number(label3 / 1000.0).toLocaleString(undefined, { - maximumFractionDigits: 1 - })}K` - } - label4={ - label4 >= 1000000 - ? `$${Number(label4 / 1000000).toLocaleString(undefined, { - maximumFractionDigits: 0 - })}M` - : `$${Number(label4 / 1000.0).toLocaleString(undefined, { - maximumFractionDigits: 1 - })}K` - } - label5={ - label5 >= 1000000 - ? `$${Number(label5 / 1000000).toLocaleString(undefined, { - maximumFractionDigits: 0 - })}M` - : `$${Number(label5 / 1000.0).toLocaleString(undefined, { - maximumFractionDigits: 1 - })}K` - } - label6={ - maxValue >= 1000000 - ? `$${Number(maxValue / 1000000).toLocaleString(undefined, { - maximumFractionDigits: 0 - })}M` - : `$${Number(maxValue / 1000.0).toLocaleString(undefined, { - maximumFractionDigits: 1 - })}K` - } - /> + {maxValue !== 0 ? ( + + ) : ( +
+ {titleElement(category, years)} + + + {category} data in {years} is unavailable for all states. + + +
+ )}
) : ( ); }; - +const titleElement = (attribute, year): JSX.Element => { + return ( + + + {attribute} Benefits from {year} + {" "} + + In any state that appears in grey, there is no available data + + + ); +}; export default CategoryMap; diff --git a/src/components/csp/CSPTotalMap.tsx b/src/components/csp/CSPTotalMap.tsx index b2520fa4..13ab076e 100644 --- a/src/components/csp/CSPTotalMap.tsx +++ b/src/components/csp/CSPTotalMap.tsx @@ -2,14 +2,14 @@ import React, { useState } from "react"; import { geoCentroid } from "d3-geo"; import { ComposableMap, Geographies, Geography, Marker, Annotation } from "react-simple-maps"; import ReactTooltip from "react-tooltip"; -import { scaleQuantize } from "d3-scale"; import Divider from "@mui/material/Divider"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; - +import * as d3 from "d3"; import PropTypes from "prop-types"; import "../../styles/map.css"; -import HorizontalStackedBar from "../HorizontalStackedBar"; +import legendConfig from "../../utils/legendConfig.json"; +import DrawLegend from "../shared/DrawLegend"; const geoUrl = "https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json"; @@ -26,10 +26,7 @@ const offsets = { }; const MapChart = (props) => { - const { setTooltipContent, maxValue, allStates, statePerformance } = props; - const colorScale = scaleQuantize() - .domain([0, maxValue]) - .range(["#F0F9E8", "#BAE4BC", "#7BCCC4", "#43A2CA", "#0868AC"]); + const { setTooltipContent, allStates, statePerformance, colorScale } = props; return (
@@ -162,12 +159,12 @@ const MapChart = (props) => { }; MapChart.propTypes = { - setTooltipContent: PropTypes.func, - maxValue: PropTypes.number + setTooltipContent: PropTypes.func }; const CSPTotalMap = ({ statePerformance, allStates }: { statePerformance: any; allStates: any }): JSX.Element => { const quantizeArray: number[] = []; + const category = "Total CSP"; Object.values(statePerformance).map((value) => { if (Array.isArray(value)) { quantizeArray.push(value[0].totalPaymentInDollars); @@ -175,11 +172,9 @@ const CSPTotalMap = ({ statePerformance, allStates }: { statePerformance: any; a return null; }); const maxValue = Math.max(...quantizeArray); - const label1 = (maxValue / 5) * 0; - const label2 = (maxValue / 5) * 1; - const label3 = (maxValue / 5) * 2; - const label4 = (maxValue / 5) * 3; - const label5 = (maxValue / 5) * 4; + const mapColor = ["#F0F9E8", "#BAE4BC", "#7BCCC4", "#43A2CA", "#0868AC"]; + const customScale = legendConfig[category]; + const colorScale = d3.scaleThreshold(customScale, mapColor); const [content, setContent] = useState(""); // issue158: since eqip and csp are using old data structure (i.e. year is not the first level of data structure), going into array to find the year let years = "2018-2022"; @@ -193,32 +188,28 @@ const CSPTotalMap = ({ statePerformance, allStates }: { statePerformance: any; a
- + {maxValue !== 0 ? ( + + ) : ( +
+ {titleElement(category, years)} + + + {category} data in {years} is unavailable for all states. + + +
+ )}
@@ -237,5 +229,16 @@ const CSPTotalMap = ({ statePerformance, allStates }: { statePerformance: any; a
); }; - +const titleElement = (attribute, year): JSX.Element => { + return ( + + + {attribute} Benefits from {year} + {" "} + + In any state that appears in grey, there is no available data + + + ); +}; export default CSPTotalMap; diff --git a/src/components/csp/CategoryMap.tsx b/src/components/csp/CategoryMap.tsx index f6675d8b..d25df0d2 100644 --- a/src/components/csp/CategoryMap.tsx +++ b/src/components/csp/CategoryMap.tsx @@ -7,8 +7,10 @@ import Divider from "@mui/material/Divider"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import PropTypes from "prop-types"; +import * as d3 from "d3"; import "../../styles/map.css"; -import HorizontalStackedBar from "../HorizontalStackedBar"; +import legendConfig from "../../utils/legendConfig.json"; +import DrawLegend from "../shared/DrawLegend"; const geoUrl = "https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json"; @@ -25,10 +27,7 @@ const offsets = { }; const MapChart = (props) => { - const { setTooltipContent, category, maxValue, allStates, statePerformance } = props; - const colorScale = scaleQuantize() - .domain([0, maxValue]) - .range(["#F0F9E8", "#BAE4BC", "#7BCCC4", "#43A2CA", "#0868AC"]); + const { setTooltipContent, category, allStates, statePerformance, colorScale } = props; let categoryRecord; return (
@@ -176,8 +175,7 @@ const MapChart = (props) => { MapChart.propTypes = { setTooltipContent: PropTypes.func, - category: PropTypes.string, - maxValue: PropTypes.number + category: PropTypes.string }; const CategoryMap = ({ @@ -225,71 +223,42 @@ const CategoryMap = ({ return null; }); const maxValue = Math.max(...quantizeArray); - const label1 = (maxValue / 5) * 0; - const label2 = (maxValue / 5) * 1; - const label3 = (maxValue / 5) * 2; - const label4 = (maxValue / 5) * 3; - const label5 = (maxValue / 5) * 4; + const mapColor = ["#F0F9E8", "#BAE4BC", "#7BCCC4", "#43A2CA", "#0868AC"]; + let legendCategory = category; + if (category === "Structural") legendCategory = "Structural-CSP"; + if (category === "Vegetative") legendCategory = "Vegetative-CSP"; + if (category === "Land management") legendCategory = "Land management-CSP"; + if (category === "Forest management") legendCategory = "Forest management-CSP"; + if (category === "Soil testing") legendCategory = "Soil testing-CSP"; + if (category === "Other improvement") legendCategory = "Other improvement-CSP"; + const customScale = legendConfig[legendCategory]; + const colorScale = d3.scaleThreshold(customScale, mapColor); return (
{maxValue !== 0 ? ( - = 1000000 - ? `$${Number(label2 / 1000000).toLocaleString(undefined, { - maximumFractionDigits: 0 - })}M` - : `$${Number(label2 / 1000.0).toLocaleString(undefined, { - maximumFractionDigits: 1 - })}K` - } - label3={ - label3 >= 1000000 - ? `$${Number(label3 / 1000000).toLocaleString(undefined, { - maximumFractionDigits: 0 - })}M` - : `$${Number(label3 / 1000.0).toLocaleString(undefined, { - maximumFractionDigits: 1 - })}K` - } - label4={ - label4 >= 1000000 - ? `$${Number(label4 / 1000000).toLocaleString(undefined, { - maximumFractionDigits: 0 - })}M` - : `$${Number(label4 / 1000.0).toLocaleString(undefined, { - maximumFractionDigits: 1 - })}K` - } - label5={ - label5 >= 1000000 - ? `$${Number(label5 / 1000000).toLocaleString(undefined, { - maximumFractionDigits: 0 - })}M` - : `$${Number(label5 / 1000.0).toLocaleString(undefined, { - maximumFractionDigits: 1 - })}K` - } - label6={ - maxValue >= 1000000 - ? `$${Number(maxValue / 1000000).toLocaleString(undefined, { - maximumFractionDigits: 0 - })}M` - : `$${Number(maxValue / 1000.0).toLocaleString(undefined, { - maximumFractionDigits: 1 - })}K` - } - /> + {maxValue !== 0 ? ( + + ) : ( +
+ {titleElement(category, years)} + + + {category} data in {years} is unavailable for all states. + + +
+ )}
) : (
@@ -327,5 +296,16 @@ const CategoryMap = ({
); }; - +const titleElement = (attribute, year): JSX.Element => { + return ( + + + {attribute} Benefits from {year} + {" "} + + In any state that appears in grey, there is no available data + + + ); +}; export default CategoryMap; diff --git a/src/components/eqip/CategoryMap.tsx b/src/components/eqip/CategoryMap.tsx index 389074b9..93c6a976 100644 --- a/src/components/eqip/CategoryMap.tsx +++ b/src/components/eqip/CategoryMap.tsx @@ -2,13 +2,14 @@ import React, { useState } from "react"; import { geoCentroid } from "d3-geo"; import { ComposableMap, Geographies, Geography, Marker, Annotation } from "react-simple-maps"; import ReactTooltip from "react-tooltip"; -import { scaleQuantize } from "d3-scale"; import Divider from "@mui/material/Divider"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import PropTypes from "prop-types"; +import * as d3 from "d3"; import "../../styles/map.css"; -import HorizontalStackedBar from "../HorizontalStackedBar"; +import legendConfig from "../../utils/legendConfig.json"; +import DrawLegend from "../shared/DrawLegend"; const geoUrl = "https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json"; @@ -25,10 +26,7 @@ const offsets = { }; const MapChart = (props) => { - const { setTooltipContent, category, maxValue, statePerformance, allStates } = props; - const colorScale = scaleQuantize() - .domain([0, maxValue]) - .range(["#F0F9E8", "#BAE4BC", "#7BCCC4", "#43A2CA", "#0868AC"]); + const { setTooltipContent, category, statePerformance, allStates, colorScale } = props; return (
@@ -163,8 +161,7 @@ const MapChart = (props) => { MapChart.propTypes = { setTooltipContent: PropTypes.func, - category: PropTypes.string, - maxValue: PropTypes.number + category: PropTypes.string }; const CategoryMap = ({ @@ -185,7 +182,6 @@ const CategoryMap = ({ ) { years = Array(Array(Array(Object.values(statePerformance)[0])[0])[0])[0][0].years; } - const title = `${category} Benefits from ${years}`; const quantizeArray: number[] = []; Object.values(statePerformance).map((value) => { const statuteRecord = value[0].statutes; @@ -199,77 +195,41 @@ const CategoryMap = ({ return null; }); const maxValue = Math.max(...quantizeArray); - const label1 = (maxValue / 5) * 0; - const label2 = (maxValue / 5) * 1; - const label3 = (maxValue / 5) * 2; - const label4 = (maxValue / 5) * 3; - const label5 = (maxValue / 5) * 4; + const mapColor = ["#F0F9E8", "#BAE4BC", "#7BCCC4", "#43A2CA", "#0868AC"]; + const customScale = legendConfig[category]; + const colorScale = d3.scaleThreshold(customScale, mapColor); return (
- = 1000000 - ? `$${Number(label2 / 1000000).toLocaleString(undefined, { - maximumFractionDigits: 0 - })}M` - : `$${Number(label2 / 1000.0).toLocaleString(undefined, { - maximumFractionDigits: 1 - })}K` - } - label3={ - label3 >= 1000000 - ? `$${Number(label3 / 1000000).toLocaleString(undefined, { - maximumFractionDigits: 0 - })}M` - : `$${Number(label3 / 1000.0).toLocaleString(undefined, { - maximumFractionDigits: 1 - })}K` - } - label4={ - label4 >= 1000000 - ? `$${Number(label4 / 1000000).toLocaleString(undefined, { - maximumFractionDigits: 0 - })}M` - : `$${Number(label4 / 1000.0).toLocaleString(undefined, { - maximumFractionDigits: 1 - })}K` - } - label5={ - label5 >= 1000000 - ? `$${Number(label5 / 1000000).toLocaleString(undefined, { - maximumFractionDigits: 0 - })}M` - : `$${Number(label5 / 1000.0).toLocaleString(undefined, { - maximumFractionDigits: 1 - })}K` - } - label6={ - maxValue >= 1000000 - ? `$${Number(maxValue / 1000000).toLocaleString(undefined, { - maximumFractionDigits: 0 - })}M` - : `$${Number(maxValue / 1000.0).toLocaleString(undefined, { - maximumFractionDigits: 1 - })}K` - } - /> + {maxValue !== 0 ? ( + + ) : ( +
+ {titleElement(category, years)} + + + {category} data in {years} is unavailable for all states. + + +
+ )}
@@ -279,5 +239,16 @@ const CategoryMap = ({
); }; - +const titleElement = (attribute, year): JSX.Element => { + return ( + + + {attribute} Benefits from {year} + {" "} + + In any state that appears in grey, there is no available data + + + ); +}; export default CategoryMap; diff --git a/src/components/eqip/EQIPTotalMap.tsx b/src/components/eqip/EQIPTotalMap.tsx index 71a9ed49..f766998b 100644 --- a/src/components/eqip/EQIPTotalMap.tsx +++ b/src/components/eqip/EQIPTotalMap.tsx @@ -6,10 +6,11 @@ import { scaleQuantize } from "d3-scale"; import Divider from "@mui/material/Divider"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; - +import * as d3 from "d3"; import PropTypes from "prop-types"; import "../../styles/map.css"; -import HorizontalStackedBar from "../HorizontalStackedBar"; +import legendConfig from "../../utils/legendConfig.json"; +import DrawLegend from "../shared/DrawLegend"; const geoUrl = "https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json"; @@ -25,11 +26,7 @@ const offsets = { DC: [49, 21] }; -const MapChart = ({ setTooltipContent, maxValue, allStates, statePerformance }) => { - const colorScale = scaleQuantize() - .domain([0, maxValue]) - .range(["#F0F9E8", "#BAE4BC", "#7BCCC4", "#43A2CA", "#0868AC"]); - +const MapChart = ({ setTooltipContent, maxValue, allStates, statePerformance, colorScale }) => { return (
@@ -153,13 +150,12 @@ MapChart.propTypes = { const EQIPTotalMap = ({ statePerformance, allStates }: { statePerformance: any; allStates: any }): JSX.Element => { const quantizeArray: number[] = []; + const category = "Total EQIP"; Object.values(statePerformance).map((value) => quantizeArray.push(value[0].totalPaymentInDollars)); const maxValue = Math.max(...quantizeArray); - const label1 = (maxValue / 5) * 0; - const label2 = (maxValue / 5) * 1; - const label3 = (maxValue / 5) * 2; - const label4 = (maxValue / 5) * 3; - const label5 = (maxValue / 5) * 4; + const mapColor = ["#F0F9E8", "#BAE4BC", "#7BCCC4", "#43A2CA", "#0868AC"]; + const customScale = legendConfig[category]; + const colorScale = d3.scaleThreshold(customScale, mapColor); const [content, setContent] = useState(""); // issue158: since eqip and csp are using old data structure (i.e. year is not the first level of data structure), going into array to find the year let years = "2018-2022"; @@ -172,38 +168,35 @@ const EQIPTotalMap = ({ statePerformance, allStates }: { statePerformance: any; return (
- + {maxValue !== 0 ? ( + + ) : ( +
+ {titleElement(category, years)} + + + {category} data in {years} is unavailable for all states. + + +
+ )}
@@ -213,5 +206,16 @@ const EQIPTotalMap = ({ statePerformance, allStates }: { statePerformance: any;
); }; - +const titleElement = (attribute, year): JSX.Element => { + return ( + + + {attribute} Benefits from {year} + {" "} + + In any state that appears in grey, there is no available data + + + ); +}; export default EQIPTotalMap; diff --git a/src/components/shared/DrawLegend.tsx b/src/components/shared/DrawLegend.tsx index 742e0713..ebc913f0 100644 --- a/src/components/shared/DrawLegend.tsx +++ b/src/components/shared/DrawLegend.tsx @@ -3,6 +3,7 @@ import * as d3 from "d3"; import { Box, Typography } from "@mui/material"; import { ShortFormat } from "./ConvertionFormats"; import "../../styles/drawLegend.css"; + /** * Keys in legendConfig.json must match the 'searchKey' variable in DrawLegend.tsx file. * If there's any changes in legendConfig.json, please re-check and update the 'searchKey' variable here. @@ -47,15 +48,15 @@ export default function DrawLegend({ const customScale = colorScale.domain(); cut_points.push(Math.min(...programData)); cut_points = cut_points.concat(customScale); - const legendRectX: number[] = []; if (Math.min(...programData) !== Infinity && Math.max(...programData) !== Infinity) { baseSVG.selectAll("text").remove(); baseSVG.selectAll("rect").remove(); - const data_distribution: number[] = []; + const d_distribution: number[] = []; const cutCount = Array.from({ length: cut_points.length - 1 }, (v, i) => 1 + i); + cutCount.forEach((i) => { - data_distribution.push( + d_distribution.push( programData.filter((d) => d >= cut_points[i - 1] && d < cut_points[i]).length / programData.length ); @@ -65,7 +66,7 @@ export default function DrawLegend({ // .reduce((accumulator, currentValue) => accumulator + currentValue, 0) / // programData.reduce((accumulator, currentValue) => accumulator + currentValue, 0) }); - data_distribution.push( + d_distribution.push( programData.filter((d) => d >= cut_points[cut_points.length - 1]).length / programData.length ); // Leave following part as the backup of solution 2. @@ -75,101 +76,127 @@ export default function DrawLegend({ // .reduce((accumulator, currentValue) => accumulator + currentValue, 0) / // programData.reduce((accumulator, currentValue) => accumulator + currentValue, 0) // ); + const data_distribution = d_distribution.reduce((acc, num) => { + if (!acc.includes(num)) { + acc.push(num); + } + return acc; + }, []); const svgWidth = baseSVG.attr("width") - margin * 2; - baseSVG - .selectAll(null) - .data(data_distribution) - .enter() - .append("rect") - .attr("class", "legendRect") - .attr("id", (d) => `legendRect${d}`) - .attr("x", (d, i) => { - if (i === 0) { - return margin; - } - const sum = data_distribution.slice(0, i).reduce((acc, curr) => acc + curr, 0); - return margin + svgWidth * sum; - }) - .attr("y", () => { - return 20; - }) - .attr("width", (d) => { - return d * svgWidth; - }) - .attr("height", 10) - .style("fill", (d, i) => prepColor[i]); - cut_points = cut_points.concat(Math.max(...programData)); - baseSVG - .selectAll(".legendRect") - .nodes() - .forEach((d) => { - legendRectX.push(Number(d3.select(d).attr("x"))); - }); - const last = - legendRectX[legendRectX.length - 1] + data_distribution[data_distribution.length - 1] * svgWidth; - legendRectX.push(last); - if (window.innerWidth > 1679) { + // No need to show color length if there are less than five colors (i.e. not enough data points or label is not correctly identified) + if (data_distribution.length === 5) { baseSVG .selectAll(null) - .data(legendRectX) + .data(data_distribution) .enter() - .append("text") - .attr("class", "legendText") - .attr("id", (d) => `legendText${d}`) - .attr("y", 50) + .append("rect") + .attr("class", "legendRect") + .attr("id", (d) => `legendRect${d}`) .attr("x", (d, i) => { - return i === 0 ? d : d - margin / 4; - }) - .text((d, i) => { - if (isRatio) { - return `${Math.round(cut_points[i] * 100)}%`; + if (i === 0) { + return margin; } - if (i === 0 && !notDollar) { - const res = ShortFormat(Math.round(cut_points[i]), i); - return res.indexOf("-") < 0 ? `$${res}` : `-$${res.substring(1)}`; - } - return ShortFormat(Math.round(cut_points[i]), i); - }); - } else { - baseSVG - .selectAll(null) - .data(legendRectX) - .enter() - .append("text") - .attr("class", "legendText") - .attr("id", (d) => `legendText${d}`) - .attr("y", (d, i) => { - return i % 2 === 0 ? 50 : 10; + const sum = data_distribution.slice(0, i).reduce((acc, curr) => acc + curr, 0); + return margin + svgWidth * sum; }) - .attr("x", (d, i) => { - return i === 0 ? d : d - margin / 4; + .attr("y", () => { + return 20; }) - .text((d, i) => { - if (isRatio) { - return `${Math.round(cut_points[i] * 100)}%`; - } - if (i === 0 && !notDollar) { - const res = ShortFormat(Math.round(cut_points[i]), i); - return res.indexOf("-") < 0 ? `$${res}` : `-$${res.substring(1)}`; - } - return ShortFormat(Math.round(cut_points[i]), i); + .attr("width", (d) => { + return d * svgWidth; + }) + .attr("height", 10) + .style("fill", (d, i) => prepColor[i]); + cut_points = cut_points.concat(Math.max(...programData)); + baseSVG + .selectAll(".legendRect") + .nodes() + .forEach((d) => { + legendRectX.push(Number(d3.select(d).attr("x"))); }); - } - if (emptyState.length !== 0) { + const last = + legendRectX[legendRectX.length - 1] + + data_distribution[data_distribution.length - 1] * svgWidth; + legendRectX.push(last); + if (window.innerWidth > 1679) { + baseSVG + .selectAll(null) + .data(legendRectX) + .enter() + .append("text") + .attr("class", "legendText") + .attr("id", (d) => `legendText${d}`) + .attr("y", 50) + .attr("x", (d, i) => { + return i === 0 ? d : d - margin / 4; + }) + .text((d, i) => { + if (isRatio) { + return `${Math.round(cut_points[i] * 100)}%`; + } + if (i === 0 && !notDollar) { + const res = ShortFormat(Math.round(cut_points[i]), i); + return res.indexOf("-") < 0 ? `$${res}` : `-$${res.substring(1)}`; + } + return ShortFormat(Math.round(cut_points[i]), i); + }); + } else { + baseSVG + .selectAll(null) + .data(legendRectX) + .enter() + .append("text") + .attr("class", "legendText") + .attr("id", (d) => `legendText${d}`) + .attr("y", (d, i) => { + return i % 2 === 0 ? 50 : 10; + }) + .attr("x", (d, i) => { + return i === 0 ? d : d - margin / 4; + }) + .text((d, i) => { + if (isRatio) { + return `${Math.round(cut_points[i] * 100)}%`; + } + if (i === 0 && !notDollar) { + const res = ShortFormat(Math.round(cut_points[i]), i); + return res.indexOf("-") < 0 ? `$${res}` : `-$${res.substring(1)}`; + } + return ShortFormat(Math.round(cut_points[i]), i); + }); + } + if (emptyState.length !== 0) { + const middleText = baseSVG + .append("text") + .attr("class", "legendTextSide") + .attr("x", -1000) + .attr("y", -1000) + .text(`${emptyState.join(", ")}'s data is not available`); + const middleBox = middleText.node().getBBox(); + middleText.remove(); + baseSVG + .append("text") + .attr("class", "legendTextSide") + .attr("x", (svgWidth + margin * 2) / 2 - middleBox.width / 2) + .attr("y", 80) + .text(`${emptyState.join(", ")}'s data is not available`); + } + } else { + baseSVG.attr("height", 40); const middleText = baseSVG .append("text") .attr("class", "legendTextSide") .attr("x", -1000) .attr("y", -1000) - .text(`${emptyState.join(", ")}'s data is not available`); + .text("There isn't sufficient data to display the legend"); const middleBox = middleText.node().getBBox(); middleText.remove(); baseSVG .append("text") .attr("class", "legendTextSide") .attr("x", (svgWidth + margin * 2) / 2 - middleBox.width / 2) - .attr("y", 80) - .text(`${emptyState.join(", ")}'s data is not available`); + .attr("y", 16) + .text("There isn't sufficient data to display the legend"); } } } diff --git a/src/components/shared/NavSearchBar.tsx b/src/components/shared/NavSearchBar.tsx index a9b768ab..6a27915c 100644 --- a/src/components/shared/NavSearchBar.tsx +++ b/src/components/shared/NavSearchBar.tsx @@ -17,7 +17,7 @@ export default function NavSearchBar({ borderLeft: 0, borderRight: 0, borderColor: brColor, - padding: 1, + margin: 1, backgroundColor: bkColor }} > diff --git a/src/pages/EQIPPage.tsx b/src/pages/EQIPPage.tsx index 1c67bc3f..dc35e581 100644 --- a/src/pages/EQIPPage.tsx +++ b/src/pages/EQIPPage.tsx @@ -28,6 +28,7 @@ export default function EQIPPage(): JSX.Element { let comprehensiveNutrientMgtTotal = 0; let resourceConservingCropRotationTotal = 0; let soilHealthTotal = 0; + const zeroCategory = []; // connect to api endpoint const [statePerformance, setStatePerformance] = React.useState({}); @@ -37,6 +38,7 @@ export default function EQIPPage(): JSX.Element { const [sixBChartData, setSixBChartData] = React.useState([{}]); const [aTotal, setATotal] = React.useState(0); const [bTotal, setBTotal] = React.useState(0); + const [zeroCategories, setZeroCategories] = React.useState([]); React.useEffect(() => { const state_perf_url = `${config.apiUrl}/programs/conservation/eqip/state-distribution`; @@ -90,18 +92,30 @@ export default function EQIPPage(): JSX.Element { const soilHealthCur = BCur.find((s) => s.practiceCategoryName === "Soil health"); structuralTotal += Number(structuralCur.totalPaymentInDollars); + if (structuralTotal === 0) zeroCategory.push("Structural"); landManagementTotal += Number(landManagementCur.totalPaymentInDollars); + if (landManagementTotal === 0) zeroCategory.push("Land management"); vegetativeTotal += Number(vegetativeCur.totalPaymentInDollars); + if (vegetativeTotal === 0) zeroCategory.push("Vegetative"); forestManagementTotal += Number(forestManagementCur.totalPaymentInDollars); + if (forestManagementTotal === 0) zeroCategory.push("Forest management"); soilRemediationTotal += Number(soilRemediationCur.totalPaymentInDollars); + if (soilRemediationTotal === 0) zeroCategory.push("Soil remediation"); other6ATotal += Number(other6ACur.totalPaymentInDollars); + if (other6ATotal === 0) zeroCategory.push("Other improvement"); soilTestingTotal += Number(soilTestingCur.totalPaymentInDollars); + if (soilTestingTotal === 0) zeroCategory.push("Soil testing"); otherPlanningTotal += Number(otherPlanningCur.totalPaymentInDollars); + if (otherPlanningTotal === 0) zeroCategory.push("Other planning"); conservationPlanningAssessmentTotal += Number(conservationPlanningAssessmentCur.totalPaymentInDollars); + if (conservationPlanningAssessmentTotal === 0) zeroCategory.push("Conservation planning assessment"); comprehensiveNutrientMgtTotal += Number(comprehensiveNutrientMgtCur.totalPaymentInDollars); + if (comprehensiveNutrientMgtTotal === 0) zeroCategory.push("Comprehensive Nutrient Mgt."); resourceConservingCropRotationTotal += Number(resourceConservingCropRotationCur.totalPaymentInDollars); + if (resourceConservingCropRotationTotal === 0) zeroCategory.push("Resource-conserving crop rotation"); soilHealthTotal += Number(soilHealthCur.totalPaymentInDollars); + if (soilHealthTotal === 0) zeroCategory.push("Soil health"); setSixAChartData([ { name: "Structural", value: structuralTotal, color: "#2F7164" }, @@ -127,6 +141,8 @@ export default function EQIPPage(): JSX.Element { { name: "6 (A)", value: sixATotal, color: "#2F7164" }, { name: "6 (B)", value: sixBTotal, color: "#9CBAB4" } ]); + + setZeroCategories(zeroCategory); }; return ( @@ -140,7 +156,7 @@ export default function EQIPPage(): JSX.Element { subtext="Environmental Quality Incentives Program (EQIP)" /> - + Date: Thu, 14 Sep 2023 17:27:24 -0500 Subject: [PATCH 14/16] adjust color design method so that less than five colors will show error message --- src/components/ProgramDrawer.tsx | 2 +- src/components/shared/DrawLegend.tsx | 22 ++++------------------ src/utils/legendConfig.json | 4 +++- 3 files changed, 8 insertions(+), 20 deletions(-) diff --git a/src/components/ProgramDrawer.tsx b/src/components/ProgramDrawer.tsx index 23698f33..6afcd731 100644 --- a/src/components/ProgramDrawer.tsx +++ b/src/components/ProgramDrawer.tsx @@ -56,7 +56,7 @@ function EQIPCheckboxList({ setEQIPChecked, setShowPopUp, zeroCategory }) { if (zeroCategory && zeroCategory.includes(category)) { return ( - + 1 + i); - cutCount.forEach((i) => { - d_distribution.push( + data_distribution.push( programData.filter((d) => d >= cut_points[i - 1] && d < cut_points[i]).length / programData.length ); @@ -66,25 +65,12 @@ export default function DrawLegend({ // .reduce((accumulator, currentValue) => accumulator + currentValue, 0) / // programData.reduce((accumulator, currentValue) => accumulator + currentValue, 0) }); - d_distribution.push( + data_distribution.push( programData.filter((d) => d >= cut_points[cut_points.length - 1]).length / programData.length ); - // Leave following part as the backup of solution 2. - // data_distribution.push( - // programData - // .filter((d) => d >= cut_points[cut_points.length - 1]) - // .reduce((accumulator, currentValue) => accumulator + currentValue, 0) / - // programData.reduce((accumulator, currentValue) => accumulator + currentValue, 0) - // ); - const data_distribution = d_distribution.reduce((acc, num) => { - if (!acc.includes(num)) { - acc.push(num); - } - return acc; - }, []); const svgWidth = baseSVG.attr("width") - margin * 2; // No need to show color length if there are less than five colors (i.e. not enough data points or label is not correctly identified) - if (data_distribution.length === 5) { + if (!data_distribution.includes(0)) { baseSVG .selectAll(null) .data(data_distribution) diff --git a/src/utils/legendConfig.json b/src/utils/legendConfig.json index 460aea71..edd6c2f5 100644 --- a/src/utils/legendConfig.json +++ b/src/utils/legendConfig.json @@ -2,14 +2,16 @@ "18-22 All Programs Total": [5000000000, 10000000000, 30000000000, 40000000000], "Crop Insurance Total":[0, 500000000, 1000000000, 5000000000], "totalNetFarmerBenefit": [0, 500000000, 1000000000, 5000000000], - "Title I Total":[100000000, 500000000, 1000000000, 2000000000], + "Title I Total":[50000000, 100000000, 500000000, 1000000000], "Total Commodities Programs": [100000000, 500000000, 1000000000, 2000000000], "Title II Total":[100000000, 500000000, 1000000000, 2000000000], "SNAP Total": [1000000000, 5000000000, 10000000000, 20000000000], + "Agriculture Risk Coverage (ARC)": [10000000, 50000000, 100000000, 500000000], "Agriculture Risk Coverage County Option (ARC-CO)": [50000000, 100000000, 250000000, 500000000], "Agriculture Risk Coverage Individual Coverage (ARC-IC)": [5000000, 10000000, 25000000, 50000000], "Price Loss Coverage (PLC)":[50000000, 100000000, 500000000, 1000000000], + "totalIndemnities":[10000000, 100000000, 1000000000, 5000000000], "totalPremium":[10000000, 100000000, 1000000000, 5000000000], "totalPremiumSubsidy": [5000000, 100000000, 500000000, 1000000000], From 18cd3daca3ea62d10fccc8fb96822f8c5a675b84 Mon Sep 17 00:00:00 2001 From: "Wendy(Pengyin) Shan" Date: Mon, 18 Sep 2023 14:18:08 -0500 Subject: [PATCH 15/16] release-0.9.0 --- CHANGELOG.md | 3 ++- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b56db423..f04eed9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [0.9.0] - 2023-09-18 ### Added - CRP page and corresponding components for its attributes [#170](https://github.com/policy-design-lab/pdl-frontend/issues/170) @@ -149,6 +149,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Map data json [#12](https://github.com/policy-design-lab/pdl-frontend/issues/12) - Final landing page changes for initial milestone [#15](https://github.com/policy-design-lab/pdl-frontend/issues/15) +[0.9.0]: https://github.com/policy-design-lab/pdl-frontend/compare/0.8.0...0.9.0 [0.8.0]: https://github.com/policy-design-lab/pdl-frontend/compare/0.7.0...0.8.0 [0.7.0]: https://github.com/policy-design-lab/pdl-frontend/compare/0.6.0...0.7.0 [0.6.0]: https://github.com/policy-design-lab/pdl-frontend/compare/0.5.1...0.6.0 diff --git a/package-lock.json b/package-lock.json index 3172fa74..37a3fa67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "policy-design-lab", - "version": "0.8.0", + "version": "0.9.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 0be04d68..86774664 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "policy-design-lab", - "version": "0.8.0", + "version": "0.9.0", "description": "the front end of policy design lab", "repository": "https://github.com/policy-design-lab/pdl-frontend", "main": "src/app.tsx", From 8ad0178906a3de70186b693ae95517beafabfdc7 Mon Sep 17 00:00:00 2001 From: YONG WOOK KIM Date: Mon, 18 Sep 2023 16:54:18 -0500 Subject: [PATCH 16/16] fixed the table's right most column not showing in smaller screen (#193) --- CHANGELOG.md | 3 +++ src/components/crp/CRPTotalTable.tsx | 1 + src/components/crp/CategoryTable.tsx | 1 + src/components/csp/CSPTotalTable.tsx | 1 + src/components/csp/CategoryTable.tsx | 1 + src/components/eqip/CategoryTable.tsx | 1 + src/components/eqip/EQIPTotalTable.tsx | 1 + 7 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b56db423..77fb3170 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Replace the color legends on the EQIP, CSP, and CRP pages with the customized scheme [189](https://github.com/policy-design-lab/pdl-frontend/issues/189) - Adjusted the menu height of CRP page on small screen [#190](https://github.com/policy-design-lab/pdl-frontend/issues/190) +### Fixed +- The tables for Title II shows right most column in any screen size [#192](https://github.com/policy-design-lab/pdl-frontend/issues/192) + ## [0.8.0] - 2023-09-06 ### Added diff --git a/src/components/crp/CRPTotalTable.tsx b/src/components/crp/CRPTotalTable.tsx index 7ae991b3..0853e7ef 100644 --- a/src/components/crp/CRPTotalTable.tsx +++ b/src/components/crp/CRPTotalTable.tsx @@ -6,6 +6,7 @@ import "../../styles/table.css"; const Styles = styled.div` padding: 1rem; + margin-left: ${window.innerWidth <= 1440 ? "480px" : "auto"}; table { border-spacing: 0; diff --git a/src/components/crp/CategoryTable.tsx b/src/components/crp/CategoryTable.tsx index 6055e1cf..0b61037b 100644 --- a/src/components/crp/CategoryTable.tsx +++ b/src/components/crp/CategoryTable.tsx @@ -6,6 +6,7 @@ import "../../styles/table.css"; const Styles = styled.div` padding: 1rem; + margin-left: ${window.innerWidth <= 1440 ? "480px" : "auto"}; table { border-spacing: 0; diff --git a/src/components/csp/CSPTotalTable.tsx b/src/components/csp/CSPTotalTable.tsx index 067a0fb8..2f97ca6a 100644 --- a/src/components/csp/CSPTotalTable.tsx +++ b/src/components/csp/CSPTotalTable.tsx @@ -6,6 +6,7 @@ import "../../styles/table.css"; const Styles = styled.div` padding: 1rem; + margin-left: ${window.innerWidth <= 1440 ? "480px" : "auto"}; table { border-spacing: 0; diff --git a/src/components/csp/CategoryTable.tsx b/src/components/csp/CategoryTable.tsx index be2b7e3e..6c60acdb 100644 --- a/src/components/csp/CategoryTable.tsx +++ b/src/components/csp/CategoryTable.tsx @@ -6,6 +6,7 @@ import "../../styles/table.css"; const Styles = styled.div` padding: 1rem; + margin-left: ${window.innerWidth <= 1440 ? "480px" : "auto"}; table { border-spacing: 0; diff --git a/src/components/eqip/CategoryTable.tsx b/src/components/eqip/CategoryTable.tsx index 4e6ad158..8874ec4a 100644 --- a/src/components/eqip/CategoryTable.tsx +++ b/src/components/eqip/CategoryTable.tsx @@ -6,6 +6,7 @@ import "../../styles/table.css"; const Styles = styled.div` padding: 1rem; + margin-left: ${window.innerWidth <= 1440 ? "480px" : "auto"}; table { border-spacing: 0; diff --git a/src/components/eqip/EQIPTotalTable.tsx b/src/components/eqip/EQIPTotalTable.tsx index b58a2ceb..e00c7cc8 100644 --- a/src/components/eqip/EQIPTotalTable.tsx +++ b/src/components/eqip/EQIPTotalTable.tsx @@ -6,6 +6,7 @@ import "../../styles/table.css"; const Styles = styled.div` padding: 1rem; + margin-left: ${window.innerWidth <= 1440 ? "480px" : "auto"}; table { border-spacing: 0;