From 7a93e814fc24f6033cb72afc48cba4c7a0311a33 Mon Sep 17 00:00:00 2001 From: "Wendy(Pengyin) Shan" Date: Fri, 6 Oct 2023 09:32:16 -0500 Subject: [PATCH 01/18] draft initial version of acep for user to test --- src/components/ProgramDrawer.tsx | 159 ++++++- src/components/SemiDonutChart.tsx | 2 +- src/components/acep/ACEPCategoryMap.tsx | 289 ++++++++++++ src/components/acep/ACEPTable.tsx | 343 ++++++++++++++ src/components/acep/ACEPTotalMap.tsx | 258 +++++++++++ src/components/acep/AcepTreeMap.tsx | 387 ++++++++++++++++ src/components/acep/TreeMapSquares.tsx | 261 +++++++++++ .../cropinsurance/CropInsuranceMap.tsx | 8 +- src/components/crp/CRPTotalMap.tsx | 4 +- src/components/crp/CRPTotalTable.tsx | 2 +- src/components/crp/CategoryMap.tsx | 4 +- src/components/crp/CategoryTable.tsx | 2 +- src/components/csp/CSPTotalTable.tsx | 1 - src/components/csp/CategoryTable.tsx | 1 - src/components/eqip/CategoryTable.tsx | 1 - src/components/eqip/EQIPTotalTable.tsx | 2 +- src/components/shared/NavSearchBar.tsx | 5 +- src/main.tsx | 2 + src/pages/ACEPPage.tsx | 435 ++++++++++++++++++ src/styles/subpage.css | 12 +- src/utils/apiutil.tsx | 22 +- src/utils/legendConfig.json | 9 +- 22 files changed, 2174 insertions(+), 35 deletions(-) create mode 100644 src/components/acep/ACEPCategoryMap.tsx create mode 100644 src/components/acep/ACEPTable.tsx create mode 100644 src/components/acep/ACEPTotalMap.tsx create mode 100644 src/components/acep/AcepTreeMap.tsx create mode 100644 src/components/acep/TreeMapSquares.tsx create mode 100644 src/pages/ACEPPage.tsx diff --git a/src/components/ProgramDrawer.tsx b/src/components/ProgramDrawer.tsx index 6afcd731..fd8fb048 100644 --- a/src/components/ProgramDrawer.tsx +++ b/src/components/ProgramDrawer.tsx @@ -23,6 +23,8 @@ ProgramDrawer.propTypes = { }; let currentChecked = 0; +const menuHeight = window.innerHeight < 900 ? "38%" : "40%"; + function EQIPCheckboxList({ setEQIPChecked, setShowPopUp, zeroCategory }) { const [checked, setChecked] = React.useState(currentChecked); @@ -436,10 +438,83 @@ function CRPCheckboxList({ setCRPChecked, setShowPopUp, zeroCategory }) { ); } +function ACEPCheckboxList({ setACEPChecked, setShowPopUp, zeroCategory }) { + const [checked, setChecked] = React.useState(currentChecked); + + const handleToggle = (value: number) => () => { + setChecked(value); + setACEPChecked(value); + currentChecked = value; + setShowPopUp(false); + }; + + const ACEPList = [ + "Total ACEP", + "Total Contracts", + "Total Acres", + "Assistance Payment", + "Reimburse Payment", + "Tech Payment" + ]; + + return ( + + {ACEPList.map((category, value) => { + const labelId = `checkbox-list-label-${value}`; + if (zeroCategory && zeroCategory.includes(category)) { + return ( + + + + + + + ); + } + return ( + + + + + + + ); + })} + + ); +} + interface ProgramDrawerProps { setEQIPChecked?: (value: number) => void; setCSPChecked?: (value: number) => void; setCRPChecked?: (value: number) => void; + setACEPChecked?: (value: number) => void; zeroCategories?: string[]; } @@ -447,6 +522,7 @@ export default function ProgramDrawer({ setEQIPChecked, setCSPChecked, setCRPChecked, + setACEPChecked, zeroCategories }: ProgramDrawerProps): JSX.Element { const location = useLocation(); @@ -508,7 +584,25 @@ export default function ProgramDrawer({ prevCrpOpen.current = crpOpen; }, [crpOpen]); - const crpMenuHeight = window.innerHeight < 900 ? "38%" : "40%"; + // ACEP Menu + const [acepOpen, setAcepOpen] = React.useState(false); + const acepRef = React.useRef(null); + const handleAcepClick = () => { + if (location.pathname !== "/acep") { + navigate("/acep"); + window.location.reload(false); + } else { + setAcepOpen((prevAcepOpen) => !prevAcepOpen); + } + }; + const prevAcepOpen = React.useRef(acepOpen); + React.useEffect(() => { + if (prevAcepOpen.current && !acepOpen) { + acepRef.current.focus(); + } + + prevAcepOpen.current = acepOpen; + }, [acepOpen]); return ( - + Total Conservation Programs Benefits @@ -570,7 +664,7 @@ export default function ProgramDrawer({ anchorEl={eqipRef.current} role={undefined} placement="right-start" - sx={{ height: "50%", overflowY: "scroll", maxWidth: "20%" }} + sx={{ height: "50%", overflowY: "auto", maxWidth: "20%" }} > - - ACEP: Agriculture Conservation Easement Program - + + + + {location.pathname === "/acep" ? ( + + ACEP: Agriculture Conservation Easement Program + + ) : ( + ACEP: Agriculture Conservation Easement Program + )} + + + + STATUE + + + + + + + + + + + + RCPP: Regional Conservation Partnership Program diff --git a/src/components/SemiDonutChart.tsx b/src/components/SemiDonutChart.tsx index d41e4d3c..dcc94073 100644 --- a/src/components/SemiDonutChart.tsx +++ b/src/components/SemiDonutChart.tsx @@ -14,7 +14,7 @@ export default function SemiDonutChart({ data, label1, label2 }: any): JSX.Eleme return ( cx ? "start" : "end"} dominantBaseline="central"> - {`${(percent * 100).toFixed(1)}%`} + {`${(percent * 100).toFixed(2)}%`} ); }; diff --git a/src/components/acep/ACEPCategoryMap.tsx b/src/components/acep/ACEPCategoryMap.tsx new file mode 100644 index 00000000..0a1e5f62 --- /dev/null +++ b/src/components/acep/ACEPCategoryMap.tsx @@ -0,0 +1,289 @@ +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 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 legendConfig from "../../utils/legendConfig.json"; +import DrawLegend from "../shared/DrawLegend"; +import { getValueFromAttrDollar, getValueFromAttrPercentage } from "../../utils/apiutil"; + +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, allStates, stateCodes, statePerformance, colorScale, attribute } = props; + + return ( +
+ + + {({ geographies }) => ( + <> + {geographies.map((geo) => { + let keyDollar; + let keyPercentage = ""; + const record = statePerformance[year].filter((v) => v.state === geo.properties.name)[0]; + if (record === undefined || record.length === 0) { + return null; + } + if (attribute === "contracts") { + keyDollar = "totalContracts"; + keyPercentage = "contractsInPercentageNationwide"; + } else if (attribute === "acres") { + keyDollar = "totalAcres"; + keyPercentage = "acresInPercentageNationwide"; + } else { + keyDollar = getValueFromAttrDollar(record.programs[0], attribute); + keyPercentage = getValueFromAttrPercentage(record.programs[0], attribute); + } + const categoryPayment = record.programs[0][keyDollar]; + const nationwidePercentage = record.programs[0][keyPercentage]; + 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 +}; + +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 = `ACEP ${category} from ${year}`; + const quantizeArray: number[] = []; + const zeroPoints = []; + + statePerformance[year].forEach((value) => { + const programRecord = value.programs; + const ACur = programRecord[0]; + let key = ""; + if (attribute === "contracts") key = "totalContracts"; + else if (attribute === "acres") key = "totalAcres"; + else { + key = getValueFromAttrDollar(ACur, attribute); + key = key !== "" ? key : attribute; + } + quantizeArray.push(ACur[key]); + ACur[key] === 0 && zeroPoints.push(value.state); + return null; + }); + const years = "2018-2022"; + const maxValue = Math.max(...quantizeArray); + const mapColor = ["#F0F9E8", "#BAE4BC", "#7BCCC4", "#43A2CA", "#0868AC"]; + const customScale = legendConfig[category]; + const colorScale = d3.scaleThreshold(customScale, mapColor); + return ( +
+ {maxValue !== 0 ? ( + + {maxValue !== 0 ? ( + + ) : ( +
+ {titleElement(category, years)} + + + {category} data in {years} is unavailable for all states. + + +
+ )} +
+ ) : ( + + + + {title} + + + + + {title} data is unavailable for all states. + + + + )} + +
+ + {content} + +
+
+ ); +}; +const titleElement = (attribute, year): JSX.Element => { + return ( + + + {attribute} from {year} + {" "} + + In any state that appears in grey, there is no available data + + + ); +}; +export default CategoryMap; diff --git a/src/components/acep/ACEPTable.tsx b/src/components/acep/ACEPTable.tsx new file mode 100644 index 00000000..6669f668 --- /dev/null +++ b/src/components/acep/ACEPTable.tsx @@ -0,0 +1,343 @@ +import React from "react"; +import styled from "styled-components"; +import { useTable, useSortBy, usePagination } from "react-table"; +import { Grid, TableContainer, Typography, Box } from "@mui/material"; +import { + compareWithNumber, + compareWithAlphabetic, + compareWithDollarSign, + compareWithPercentSign, + sortByDollars +} from "../shared/TableCompareFunctions"; +import "../../styles/table.css"; + +function AcepProgramTable({ + tableTitle, + program, + attributes, + stateCodes, + AcepData, + year, + colors, + skipColumns +}): JSX.Element { + const resultData = []; + const hashmap = {}; + // eslint-disable-next-line no-restricted-syntax + AcepData[year].forEach((stateData) => { + const state = stateData.state; + let programData = null; + programData = stateData.programs.filter((p) => { + return p.programName.toString() === program; + }); + hashmap[state] = {}; + attributes.forEach((attribute) => { + const attributeData = programData[0][attribute]; + hashmap[state][attribute] = attributeData; + }); + }); + Object.keys(hashmap).forEach((s) => { + const newRecord = { + state: Object.values(stateCodes).filter((stateCode) => { + return stateCode === s; + })[0] + }; + Object.entries(hashmap[s]).forEach(([attr, value]) => { + if (attr.includes("Percentage")) { + newRecord[attr] = `${value.toString()}%`; + } else { + newRecord[attr] = `$${ + value.toLocaleString(undefined, { minimumFractionDigits: 2 }).toString().split(".")[0] + }`; + } + }); + resultData.push(newRecord); + }); + const columnPrep = []; + columnPrep.push({ Header: "STATE", accessor: "state", sortType: compareWithAlphabetic }); + attributes.forEach((attribute) => { + let sortMethod = compareWithDollarSign; + if (attribute.includes("Percentage")) sortMethod = compareWithPercentSign; + if (attribute.includes("totalAcres") || attribute.includes("totalAcres")) sortMethod = compareWithNumber; + const json = { + Header: attribute + .replace(/([A-Z])/g, " $1") + .trim() + .split(" ") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" ") + .toUpperCase(), + accessor: attribute, + sortType: sortMethod + }; + columnPrep.push(json); + }); + const columns = React.useMemo(() => columnPrep, []); + const Styles = styled.div` + padding: 0; + margin: 0; + + table { + border-spacing: 0; + border: 1px solid #e4ebe7; + border-left: none; + border-right: none; + width: 100%; + + tr { + :last-child { + td { + border-bottom: 0; + } + } + } + + th { + background-color: rgba(241, 241, 241, 1); + padding: 1em 3em; + cursor: pointer; + text-align: left; + } + + th:not(:first-of-type) { + text-align: right; + } + + td[class$="cell0"] { + padding-right: 10em; + } + + td[class$="cell1"], + td[class$="cell2"], + td[class$="cell3"], + td[class$="cell4"], + td[class$="cell5"], + td[class$="cell6"] { + text-align: right; + } + + td { + padding: 1em 3em; + border-bottom: 1px solid #e4ebe7; + border-right: none; + + :last-child { + border-right: 0; + } + } + } + .pagination { + margin-top: 1.5em; + } + + @media screen and (max-width: 1024px) { + th, + td { + padding: 8px; + } + td[class$="cell0"] { + padding-right: 1em; + } + .pagination { + margin-top: 8px; + } + } + + .acepBox > .stateTitle { + font-weight: 700; + margin-top: 0.5em; + font-size: 1.2em; + text-align: left; + margin-bottom: 1em; + padding-top: 0.5em; + } + + . + `; + return ( + + + + + + + Comparing {tableTitle} ({year}) + + + + + + !skipColumns.includes(column.accessor))} + data={resultData} + initialState={{ + pageSize: 5, + pageIndex: 0 + }} + /> + + + + ); +} + +// eslint-disable-next-line +function Table({ columns, data, initialState }: { columns: any; data: any; initialState: any }) { + const state = React.useMemo(() => initialState, []); + const { + getTableProps, + getTableBodyProps, + headerGroups, + rows, + prepareRow, + page, + canPreviousPage, + canNextPage, + pageOptions, + pageCount, + gotoPage, + nextPage, + previousPage, + setPageSize, + state: { pageIndex, pageSize } + } = useTable( + { + columns, + data, + state + }, + useSortBy, + usePagination + ); + return ( +
+
+ + {headerGroups.map((headerGroup) => ( + + {headerGroup.headers.map((column) => ( + // Add the sorting props to control sorting. + + ))} + + ))} + + + { + // eslint-disable-next-line + page.map((row, i) => { + prepareRow(row); + return ( + + {row.cells.map((cell, j) => { + return ( + + ); + })} + + ); + }) + } + +
+ {column.render("Header")} + + {(() => { + if (!column.isSorted) + return {"\u{2B83}"}; + if (column.isSortedDesc) + return {"\u{25BC}"}; + return {"\u{25B2}"}; + })()} + +
+ {cell.render("Cell")} +
+ + + {" "} + {" "} + {" "} + {" "} + + Page{" "} + + {pageIndex + 1} of {pageOptions.length} + {" "} + + + | Go to page:{" "} + { + let p = e.target.value ? Number(e.target.value) - 1 : 0; + if (p > pageOptions.length) p = pageOptions.length - 1; + if (p < 0) p = 0; + gotoPage(p); + }} + style={{ width: "3em" }} + />{" "} + + + + + {" "} + {pageSize * (pageIndex + 1) <= rows.length ? ( + + Showing the first {parseInt(pageSize, 10) * (pageIndex + 1)} results of {rows.length} rows + + ) : ( + + Showing the first {rows.length} results of {rows.length}rows + + )} + + + + ); +} + +export default AcepProgramTable; diff --git a/src/components/acep/ACEPTotalMap.tsx b/src/components/acep/ACEPTotalMap.tsx new file mode 100644 index 00000000..3a3a56b0 --- /dev/null +++ b/src/components/acep/ACEPTotalMap.tsx @@ -0,0 +1,258 @@ +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 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 legendConfig from "../../utils/legendConfig.json"; +import DrawLegend from "../shared/DrawLegend"; + +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, allStates, statePerformance, year, stateCodes, colorScale } = props; + + return ( +
+ {allStates.length > 0 && statePerformance[year] !== undefined ? ( + + + {({ geographies }) => ( + <> + {geographies.map((geo) => { + const record = statePerformance[year].filter( + (v) => 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].totalPaymentInPercentageNationwide; + 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 +}; + +const ACEPTotalMap = ({ + 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); + const key = "paymentInDollars"; + quantizeArray.push(ACur[key]); + ACur[key] === 0 && zeroPoints.push(value.state); + return null; + }); + const category = "Total ACEP"; + const years = "2018-2022"; + const maxValue = Math.max(...quantizeArray); + 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. + + +
+ )} +
+ + + +
+ + {content} + +
+
+
+ ); +}; +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 ACEPTotalMap; diff --git a/src/components/acep/AcepTreeMap.tsx b/src/components/acep/AcepTreeMap.tsx new file mode 100644 index 00000000..628cf56b --- /dev/null +++ b/src/components/acep/AcepTreeMap.tsx @@ -0,0 +1,387 @@ +import * as React from "react"; +import * as d3 from "d3"; +import styled from "styled-components"; +import { Checkbox, FormControlLabel, FormGroup, Grid, IconButton, SvgIcon, Typography } from "@mui/material"; +import SortIcon from "@mui/icons-material/Sort"; +import TreeMapSquares from "./TreeMapSquares"; +import { DownloadIcon } from "../shared/DownloadIcon"; + +const Styles = styled.div` + ".muibuttonbase-root, muicheckbox-root:hover": { + background: "none"; + } +`; +const sortDataByAttribute = (data, attr) => { + data.sort((a, b) => { + if (a[attr] < b[attr]) { + return 1; + } + if (a[attr] > b[attr]) { + return -1; + } + return 0; + }); + return data; +}; +const scaling = (value, scaling_factor, base, max, min, maxCut, minCut) => { + let temp = Math.min(Math.max(value, minCut), maxCut); + temp = Math.log(temp * (base ** scaling_factor - 1) + 1) / (Math.log(base) * scaling_factor); + return value === 0 ? 0 : temp * (max - min) + min; +}; +const transform = (data) => { + let minacres = Infinity; + let maxacres = -Infinity; + let minpayments = Infinity; + let maxpayments = -Infinity; + let mincontracts = Infinity; + let maxcontracts = -Infinity; + data.forEach((stateData) => { + minacres = Math.min(minacres, stateData.acres); + maxacres = Math.max(maxacres, stateData.acres); + minpayments = Math.min(minpayments, stateData.payments); + maxpayments = Math.max(maxpayments, stateData.payments); + mincontracts = Math.min(mincontracts, stateData.contracts); + maxcontracts = Math.max(maxcontracts, stateData.contracts); + }); + const res = data.map((stateData) => { + const transformedJson = { acres: 0, payments: 0, contracts: 0, state: "" }; + transformedJson.acres = stateData.acres === 0 ? 0 : (stateData.acres - minacres) / (maxacres - minacres); + transformedJson.payments = + stateData.payments === 0 ? 0 : (stateData.payments - minpayments) / (maxpayments - minpayments); + transformedJson.contracts = + stateData.contracts === 0 ? 0 : (stateData.contracts - mincontracts) / (maxcontracts - mincontracts); + transformedJson.state = stateData.state; + return transformedJson; + }); + const res2 = res.map((stateData) => { + const transformedJson = { acres: 0, payments: 0, contracts: 0, state: "" }; + transformedJson.acres = scaling(stateData.acres, 1, 10, 0.9, 0.2, 1, 0); + transformedJson.payments = scaling(stateData.payments, 1, 10, 1, 0.1, 1, 0); + transformedJson.contracts = scaling(stateData.contracts, 1, 10, 0.7, 0.05, 1, 0); + transformedJson.state = stateData.state; + return transformedJson; + }); + return res2; +}; +export default function AcepTreeMap({ program, TreeMapData, year, stateCodes, svgW, svgH }): JSX.Element { + const paymentsColor = "#1F78B4"; + const acresColor = "#66BB6A"; + const contractsColor = "#C81194"; + const stCodes = stateCodes; + const rn = React.useRef(null); + const acepDiv = React.useRef(null); + const [sortPaymentButtonColor, setPaymentSortButtonColor] = React.useState(paymentsColor); + const [sortBaseAcresButtonColor, setSortBaseAcresButtonColor] = React.useState("#CCC"); + const [sortRecipientsButtonColor, setSortRecipientsButtonColor] = React.useState("#CCC"); + const [AcepTreeMapIllustration, setAcepTreeMapIllustration] = React.useState(window.innerWidth * 0.06); + const [chartData, setChartData] = React.useState(sortDataByAttribute(transform(TreeMapData[1]), "payments")); + const [availableAttributes, setAvailableAttributes] = React.useState(["payments", "acres", "contracts"]); + const [svgWidth, setSvgWidth] = React.useState(svgW); + const [svgHeight, setSvgHeight] = React.useState(svgH); + const [checkedState, setCheckedState] = React.useState({ + paymentsChecked: true, + acresChecked: true, + contractsChecked: true + }); + const { paymentsChecked, acresChecked, contractsChecked } = checkedState; + let widthPercentage = 0.5; + const heightPercentage = 0.8; + if (window.innerWidth >= 1920) { + widthPercentage = 0.6; + } + const handleResize: () => void = () => { + setSvgWidth(window.innerWidth * widthPercentage); + setSvgHeight(window.innerHeight * heightPercentage); + setAcepTreeMapIllustration(window.innerWidth * 0.05); + }; + React.useEffect(() => { + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + }); + const handleSortClick = (e, attr) => { + setPaymentSortButtonColor("#CCC"); + setSortBaseAcresButtonColor("#CCC"); + setSortRecipientsButtonColor("#CCC"); + Array.from(document.querySelectorAll(".sortIcon")).forEach((el) => { + if (el.parentElement === e.currentTarget) { + if (el.classList.contains("sortPayments")) { + setPaymentSortButtonColor(paymentsColor); + setChartData(sortDataByAttribute(transform(TreeMapData[1]), "payments")); + } + if (el.classList.contains("sortBaseAcres")) { + setSortBaseAcresButtonColor(acresColor); + setChartData(sortDataByAttribute(transform(TreeMapData[1]), "acres")); + } + if (el.classList.contains("sortRecipients")) { + setSortRecipientsButtonColor(contractsColor); + setChartData(sortDataByAttribute(transform(TreeMapData[1]), "contracts")); + } + } + }); + }; + const handleSquareChange = (event: React.ChangeEvent) => { + const checkedList: string[] = []; + const temp = { + ...checkedState, + [event.target.name]: event.target.checked + }; + if (temp.acresChecked === true) checkedList.push("acres"); + if (temp.paymentsChecked === true) checkedList.push("payments"); + if (temp.contractsChecked === true) checkedList.push("contracts"); + setCheckedState(temp); + setAvailableAttributes(checkedList); + }; + const downloadSVG = (status) => { + if (acepDiv.current !== undefined && status) { + const svgElement = acepDiv.current.querySelector("#AcepTreeMap"); + const svgData = new XMLSerializer().serializeToString(svgElement); + const blob = new Blob([svgData], { type: "image/svg+xml" }); + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = "acep-treemap.svg"; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + } + }; + if (chartData[0].payments !== 0) { + d3.select(rn.current) + .append("rect") + .attr("x", 0) + .attr("y", 0) + .attr("width", AcepTreeMapIllustration) + .attr("height", AcepTreeMapIllustration) + .attr("fill", paymentsColor); + } + if (chartData[0].acres !== 0) { + d3.select(rn.current) + .append("rect") + .attr("x", 0) + .attr("y", AcepTreeMapIllustration * 0.3) + .attr("width", AcepTreeMapIllustration * 0.7) + .attr("height", AcepTreeMapIllustration * 0.7) + .attr("fill", acresColor); + } + if (chartData[0].contracts !== 0) { + d3.select(rn.current) + .append("rect") + .attr("x", 0) + .attr("y", AcepTreeMapIllustration * 0.6) + .attr("width", AcepTreeMapIllustration * 0.4) + .attr("height", AcepTreeMapIllustration * 0.4) + .attr("fill", contractsColor); + } + /* eslint-disable */ + return ( + + + + + + {program.includes("(") + ? `Comparing ${program + .match(/\((.*?)\)/g) + .map((match) => + match.slice(1, -1) + )} Payments, No. of Contracts and Farms/acreas(ac)` + : `Comparing ${program} Payments, No. of Contracts and Farms/acreas(ac)`} + { + event.stopPropagation(); + downloadSVG(true); + }} + /> + + + + {" "} + + Hover over the squares to view detailed data. +
+ The size differences of the squares represent the differences in relative amount{" "} + within the same category. For example, a larger purple square indicate a higher + number of avg. contracts compared to another smaller purple square, but it does not necessarily + indicate a greater number of avg. contracts compared to a smaller yellow square representing + payments. +
+
+
+ + + + + + {chartData[0].payments !== 0 ? ( + handleSortClick(event, "payments")} + sx={{ + borderRadius: "2px" + }} + > + + + ) : null} + {chartData[0].acres !== 0 ? ( + handleSortClick(event, "acres")} + sx={{ + borderRadius: "2px" + }} + > + + + ) : null} + {chartData[0].contracts !== 0 ? ( + handleSortClick(event, "contracts")} + sx={{ + borderRadius: "2px" + }} + > + + + ) : null} + + + + + {chartData[0].payments !== 0 ? ( + + } + label="Total Benefits ($)" + sx={{ color: paymentsColor }} + /> + ) : null} + {chartData[0].acres !== 0 ? ( + + } + label="Farms/acreas(ac)" + sx={{ color: acresColor }} + /> + ) : null} + {chartData[0].contracts !== 0 ? ( + + } + label="Contracts" + sx={{ color: contractsColor }} + /> + ) : null} + + + + + + + + + Click the buttons + above to sort squares by payments, avg. base acres or avg. contracts. + + + +
+ + + + + + +
+ ); + /* eslint-enable */ +} diff --git a/src/components/acep/TreeMapSquares.tsx b/src/components/acep/TreeMapSquares.tsx new file mode 100644 index 00000000..c4ba76f1 --- /dev/null +++ b/src/components/acep/TreeMapSquares.tsx @@ -0,0 +1,261 @@ +import React, { useState } from "react"; +import styled from "@emotion/styled"; +import * as d3 from "d3"; +import { ShortFormat } from "../shared/ConvertionFormats"; + +export default function TreeMapSquares({ + svgWidth, + svgHeight, + stateCodes, + originalData, + chartData, + color, + availableAttributes, + program +}): JSX.Element { + const Styles = styled.div` + #AcepTreeMap { + font-family: "Roboto", sans-serif; + } + `; + + const rn = React.useRef(null); + React.useEffect(() => { + if (chartData) { + d3.select(rn.current).selectAll(".base").remove(); + const base = d3.select(rn.current).append("g").attr("class", "base"); + const margin = 30; + const lineMargin = 80; + const baseSize = 10; + const stepSize = 20; + let rowTrack = 0; + let largestSquare = 200; + if (window.innerWidth >= 1920) { + largestSquare = 250; + } + let yTrack = largestSquare + lineMargin; + const d = chartData.filter( + (stateData) => stateData.acres !== 0 || stateData.payments !== 0 || stateData.contracts !== 0 + ); + let i = 0; + let lineNumber = 0; + let maxInLine = 0; + while (i < d.length) { + const stateData = d[i]; + if ( + rowTrack + margin <= + svgWidth - largestSquare * Math.max(stateData.acres, stateData.payments, stateData.contracts) + ) { + const squareGroup = base.append("g").attr("class", "squareGroup"); + const re_sorted = [stateData.acres, stateData.payments, stateData.contracts].sort((a, b) => b - a); + maxInLine = re_sorted[0]; + const acresOriginalData = ShortFormat(originalData.find((s) => s.state === stateData.state).acres); + const paymentsOriginalData = ShortFormat( + originalData.find((s) => s.state === stateData.state).payments + ); + const contractsOriginalData = ShortFormat( + originalData.find((s) => s.state === stateData.state).contracts + ); + const collectedOriginalData = { + acres: acresOriginalData, + payments: paymentsOriginalData, + contracts: contractsOriginalData + }; + for (let index = 0; index < re_sorted.length; index += 1) { + const value = re_sorted[index]; + for (let j = 0; j < Object.values(stateData).length; j += 1) { + const v = Object.values(stateData)[j]; + if (Number(v) === value) { + const [key] = Object.entries(stateData).find(([_, val]) => val === value) || []; + if (key && availableAttributes.includes(key)) { + squareGroup + .append("rect") + .attr("class", "TreeMapSquare") + .attr("width", value * largestSquare) + .attr("height", value * largestSquare) + .attr("x", rowTrack) + .attr("y", yTrack - value * largestSquare) + .attr("fill", color[key]); + const firstFewLines = window.innerWidth >= 1920 ? 3 : 2; + if (yTrack <= (largestSquare + lineMargin) * firstFewLines) { + const inSquareText = squareGroup + .append("text") + .attr("id", `inSquareText${value}`) + .text( + key === "payments" + ? `$${collectedOriginalData[key]}` + : collectedOriginalData[key] + ) + .style("font-size", "0.7em") + .style("fill", "white"); + const textLength = inSquareText.node().getComputedTextLength(); + if (rowTrack + value * largestSquare - textLength - 10 > 0) { + inSquareText + .attr("x", rowTrack + value * largestSquare - textLength - 10) + .attr("y", yTrack - value * largestSquare + 18); + } else { + inSquareText.attr("x", 0).attr("y", yTrack - value * largestSquare + 18); + } + } + } + } + } + } + squareGroup + .on("mouseover", function (e) { + base.selectAll(".TreeMapSquareTip").remove(); + // eslint-disable-next-line no-restricted-globals + const mousePos = d3.pointer(event, squareGroup.node()); + const tipGroup = base.append("g").attr("class", "TreeMapSquareTip"); + const xPosition = mousePos[0] + 160 > svgWidth ? mousePos[0] - 160 : mousePos[0]; + const tipHeight = + program === "Agriculture Risk Coverage Individual Coverage (ARC-IC)" ? 80 : 100; + tipGroup + .append("rect") + .attr("x", xPosition) + .attr("y", mousePos[1]) + .attr("width", 155) + .attr("height", tipHeight) + .attr("rx", 5) + .attr("ry", 5) + .attr("fill", "#2F7164") + .style("opacity", 0.8) + .style("z-index", 100000); + tipGroup + .append("text") + .text( + `${ + Object.values(stateCodes).filter( + (stateCode) => stateCode === stateData.state + )[0] + }` + ) + .attr("x", xPosition + baseSize) + .attr("y", mousePos[1] + stepSize * 1) + .style("font-size", "0.9em") + .style("font-weight", "700") + .style("fill", "white"); + tipGroup + .append("text") + .text(`Total Benefits: $${paymentsOriginalData}`) + .attr("x", xPosition + baseSize) + .attr("y", mousePos[1] + stepSize * 2) + .style("font-size", "0.8em") + .style("fill", "white"); + if (program === "Agriculture Risk Coverage Individual Coverage (ARC-IC)") { + tipGroup + .append("text") + .text(`Contracts: ${contractsOriginalData}`) + .attr("x", xPosition + baseSize) + .attr("y", mousePos[1] + stepSize * 3) + .style("font-size", "0.8em") + .style("fill", "white"); + } else { + tipGroup + .append("text") + .text(`Farms/acreas(ac): ${acresOriginalData}`) + .attr("x", xPosition + baseSize) + .attr("y", mousePos[1] + stepSize * 3) + .style("font-size", "0.8em") + .style("fill", "white"); + tipGroup + .append("text") + .text(`Contracts: ${contractsOriginalData}`) + .attr("x", xPosition + 10) + .attr("y", mousePos[1] + stepSize * 4) + .style("font-size", "0.8em") + .style("fill", "white"); + } + }) + .on("mouseleave", function (e) { + base.selectAll(".TreeMapSquareTip").remove(); + }); + const stateName = squareGroup + .append("text") + .text(Object.values(stateCodes).filter((stateCode) => stateCode === stateData.state)[0]) + .style("font-size", "0.75em"); + const textLength = stateName.node().getComputedTextLength(); + if (rowTrack + (maxInLine * largestSquare) / 2 - textLength / 2 < 0) { + stateName.attr("x", 0).attr("y", yTrack + 16); + } else { + stateName + .attr("x", rowTrack + (maxInLine * largestSquare) / 2 - textLength / 2) + .attr("y", yTrack + 16); + } + rowTrack = rowTrack + maxInLine * largestSquare + margin * 2; + i += 1; + } else { + let max = 0; + for (let temp = 1; temp <= 5; temp += 1) { + if (chartData[temp + 1]) { + const nextOne = chartData[temp + 1]; + max = + max > [nextOne.acres, nextOne.payments, nextOne.contracts].sort((a, b) => b - a)[0] + ? max + : [nextOne.acres, nextOne.payments, nextOne.contracts].sort((a, b) => b - a)[0]; + } + } + if (max <= 0.8) { + yTrack = yTrack + largestSquare * max + lineMargin * 0.5; + } else { + yTrack = yTrack + largestSquare + lineMargin; + } + lineNumber += 1; + rowTrack = 0; + } + } + // check if see if any of state has all zeros + const zeroData = chartData.filter( + (stateData) => stateData.acres === 0 && stateData.payments === 0 && stateData.contracts === 0 + ); + let j = 0; + lineNumber = 0; + yTrack = rowTrack !== margin * 0.8 ? yTrack : yTrack - 20; + while (j < zeroData.length) { + const stateData = zeroData[j]; + if (stateData) { + if ( + rowTrack + margin <= + svgWidth - largestSquare * Math.max(stateData.acres, stateData.payments, stateData.contracts) + ) { + const zeros = base.append("g").attr("class", "ZeroGroup"); + zeros + .append("rect") + .attr("width", 20) + .attr("height", 20) + .attr("x", rowTrack) + .attr("y", yTrack - 20) + .attr("fill", "#DDD"); + zeros + .append("text") + .text("0") + .attr("x", rowTrack + 6.5) + .attr("y", yTrack - 5) + .style("font-size", "0.8em") + .style("fill", "white"); + const underSquare = zeros.append("text").text(stateData.state).style("font-size", "0.8em"); + const textLength = underSquare.node().getComputedTextLength(); + if (rowTrack + (20 - textLength) / 2 < 0) { + underSquare.attr("x", 0).attr("y", yTrack + 16); + } else { + underSquare.attr("x", rowTrack + (20 - textLength) / 2).attr("y", yTrack + 16); + } + rowTrack = rowTrack + 20 + margin * 2; + j += 1; + } else { + yTrack = yTrack + largestSquare * 0.8 - lineNumber * 20; + lineNumber += 5; + rowTrack = 0; + } + } + } + d3.select(rn.current).attr("height", yTrack + largestSquare); + } + }); + + return ( + + + + ); +} diff --git a/src/components/cropinsurance/CropInsuranceMap.tsx b/src/components/cropinsurance/CropInsuranceMap.tsx index 636ba943..e418f666 100644 --- a/src/components/cropinsurance/CropInsuranceMap.tsx +++ b/src/components/cropinsurance/CropInsuranceMap.tsx @@ -55,8 +55,8 @@ const MapChart = ({ return null; } const key = - getValueFromAttr(state.programs[0], attribute) !== "" - ? getValueFromAttr(state.programs[0], attribute) + getValueFromAttrDollar(state.programs[0], attribute) !== "" + ? getValueFromAttrDollar(state.programs[0], attribute) : "totalNetFarmerBenefit"; programPayment = state.programs[0][key]; const hoverContent = ( @@ -196,7 +196,7 @@ const CropInsuranceMap = ({ statePerformance[year].forEach((value) => { const programRecord = value.programs; const ACur = programRecord.find((s) => s.programName === program); - let key = getValueFromAttr(ACur, attribute); + let key = getValueFromAttrDollar(ACur, attribute); key = key !== "" ? key : "totalNetFarmerBenefit"; quantizeArray.push(ACur[key]); ACur[key] === 0 && zeroPoints.push(value.state); @@ -309,7 +309,7 @@ const titleElement = ({ attribute, year }): JSX.Element => { ); }; -const getValueFromAttr = (stateRecord, attribute): string => { +const getValueFromAttrDollar = (stateRecord, attribute): string => { let ans = ""; Object.keys(stateRecord).forEach((key) => { const match = key.match(/^(.*?)(?=\s*InDollars)/); diff --git a/src/components/crp/CRPTotalMap.tsx b/src/components/crp/CRPTotalMap.tsx index 0eb0a188..7fe1cf24 100644 --- a/src/components/crp/CRPTotalMap.tsx +++ b/src/components/crp/CRPTotalMap.tsx @@ -10,7 +10,7 @@ import PropTypes from "prop-types"; import "../../styles/map.css"; import legendConfig from "../../utils/legendConfig.json"; import DrawLegend from "../shared/DrawLegend"; -import { getValueFromAttr } from "../../utils/apiutil"; +import { getValueFromAttrDollar } from "../../utils/apiutil"; const geoUrl = "https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json"; @@ -186,7 +186,7 @@ const CRPTotalMap = ({ statePerformance[year].forEach((value) => { const programRecord = value.programs; const ACur = programRecord.find((s) => s.programName === program); - let key = getValueFromAttr(ACur, attribute); + let key = getValueFromAttrDollar(ACur, attribute); key = key !== "" ? key : attribute; quantizeArray.push(ACur[key]); ACur[key] === 0 && zeroPoints.push(value.state); diff --git a/src/components/crp/CRPTotalTable.tsx b/src/components/crp/CRPTotalTable.tsx index 0853e7ef..cc4089e6 100644 --- a/src/components/crp/CRPTotalTable.tsx +++ b/src/components/crp/CRPTotalTable.tsx @@ -6,7 +6,7 @@ import "../../styles/table.css"; const Styles = styled.div` padding: 1rem; - margin-left: ${window.innerWidth <= 1440 ? "480px" : "auto"}; + ${window.innerWidth <= 1440 ? "margin-left: 480px" : ""}; table { border-spacing: 0; diff --git a/src/components/crp/CategoryMap.tsx b/src/components/crp/CategoryMap.tsx index 07e8a20f..e3ab9cba 100644 --- a/src/components/crp/CategoryMap.tsx +++ b/src/components/crp/CategoryMap.tsx @@ -11,7 +11,7 @@ import * as d3 from "d3"; import "../../styles/map.css"; import legendConfig from "../../utils/legendConfig.json"; import DrawLegend from "../shared/DrawLegend"; -import { getValueFromAttr } from "../../utils/apiutil"; +import { getValueFromAttrDollar } from "../../utils/apiutil"; const geoUrl = "https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json"; @@ -236,7 +236,7 @@ const CategoryMap = ({ } }); } - let key = getValueFromAttr(ACur, attribute); + let key = getValueFromAttrDollar(ACur, attribute); key = key !== "" ? key : attribute; quantizeArray.push(ACur[key]); ACur[key] === 0 && zeroPoints.push(value.state); diff --git a/src/components/crp/CategoryTable.tsx b/src/components/crp/CategoryTable.tsx index 0b61037b..2f15c7c4 100644 --- a/src/components/crp/CategoryTable.tsx +++ b/src/components/crp/CategoryTable.tsx @@ -6,7 +6,7 @@ import "../../styles/table.css"; const Styles = styled.div` padding: 1rem; - margin-left: ${window.innerWidth <= 1440 ? "480px" : "auto"}; + ${window.innerWidth <= 1440 ? "margin-left: 480px" : ""}; table { border-spacing: 0; diff --git a/src/components/csp/CSPTotalTable.tsx b/src/components/csp/CSPTotalTable.tsx index 2f97ca6a..067a0fb8 100644 --- a/src/components/csp/CSPTotalTable.tsx +++ b/src/components/csp/CSPTotalTable.tsx @@ -6,7 +6,6 @@ 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 6c60acdb..be2b7e3e 100644 --- a/src/components/csp/CategoryTable.tsx +++ b/src/components/csp/CategoryTable.tsx @@ -6,7 +6,6 @@ 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 8874ec4a..4e6ad158 100644 --- a/src/components/eqip/CategoryTable.tsx +++ b/src/components/eqip/CategoryTable.tsx @@ -6,7 +6,6 @@ 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 e00c7cc8..425850d4 100644 --- a/src/components/eqip/EQIPTotalTable.tsx +++ b/src/components/eqip/EQIPTotalTable.tsx @@ -6,7 +6,7 @@ import "../../styles/table.css"; const Styles = styled.div` padding: 1rem; - margin-left: ${window.innerWidth <= 1440 ? "480px" : "auto"}; + ${window.innerWidth <= 1440 ? "margin-left: 480px;" : ""} table { border-spacing: 0; diff --git a/src/components/shared/NavSearchBar.tsx b/src/components/shared/NavSearchBar.tsx index 6a27915c..3d98ea78 100644 --- a/src/components/shared/NavSearchBar.tsx +++ b/src/components/shared/NavSearchBar.tsx @@ -1,7 +1,6 @@ import * as React from "react"; -import { Grid, IconButton, Typography, Box } from "@mui/material"; +import { Grid, Typography, Box } from "@mui/material"; import SearchIcon from "@mui/icons-material/Search"; -import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown"; export default function NavSearchBar({ bkColor = "rgba(255, 255, 255, 1)", @@ -17,7 +16,7 @@ export default function NavSearchBar({ borderLeft: 0, borderRight: 0, borderColor: brColor, - margin: 1, + mb: 1, backgroundColor: bkColor }} > diff --git a/src/main.tsx b/src/main.tsx index 4c198db2..c9bb5567 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -7,6 +7,7 @@ import CRPPage from "./pages/CRPPage"; import SNAPPage from "./pages/SNAPPage"; import TitleIPage from "./pages/TitleIPage"; import CropInsurancePage from "./pages/CropInsurancePage"; +import ACEPPage from "./pages/ACEPPage"; const ScrollToTop = (props: any) => { const location = useLocation(); @@ -25,6 +26,7 @@ export default function Main(): JSX.Element { } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/pages/ACEPPage.tsx b/src/pages/ACEPPage.tsx new file mode 100644 index 00000000..0f448b78 --- /dev/null +++ b/src/pages/ACEPPage.tsx @@ -0,0 +1,435 @@ +import Box from "@mui/material/Box"; +import * as React from "react"; +import { createTheme, Grid, ThemeProvider, ToggleButton, ToggleButtonGroup, Typography } from "@mui/material"; +import TableChartIcon from "@mui/icons-material/TableChart"; +import InsertChartIcon from "@mui/icons-material/InsertChart"; +import NavBar from "../components/NavBar"; +import Drawer from "../components/ProgramDrawer"; +import SemiDonutChart from "../components/SemiDonutChart"; +import ACEPTotalMap from "../components/acep/ACEPTotalMap"; +import CategoryMap from "../components/acep/ACEPCategoryMap"; +import { config } from "../app.config"; +import { convertAllState, getJsonDataFromUrl } from "../utils/apiutil"; +import NavSearchBar from "../components/shared/NavSearchBar"; +import ACEPTable from "../components/acep/ACEPTable"; +import AcepTreeMap from "../components/acep/AcepTreeMap"; +import "../styles/subpage.css"; + +export default function ACEPPage(): JSX.Element { + const year = "2018-2022"; + const [checked, setChecked] = React.useState(0); + + 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([{}]); + const [zeroCategories, setZeroCategories] = React.useState([]); + const [totalAcep, setTotalAcep] = React.useState(0); + const [tab, setTab] = React.useState(0); + const initTreeMapWidthRatio = 0.6; + + const defaultTheme = createTheme(); + const zeroCategory = []; + let totalACEPPaymentInDollars = 0; + let totalContracts = 0; + let totalAcres = 0; + let assistancePaymentInDollars = 0; + let reimbursePaymentInDollars = 0; + let techPaymentInDollars = 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) => { + setStateCodesArray(response); + const converted_json = convertAllState(response); + setStateCodesData(converted_json); + }); + + const statedistribution_url = `${config.apiUrl}/programs/conservation/acep/state-distribution`; + getJsonDataFromUrl(statedistribution_url).then((response) => { + setStateDistributionData(response); + }); + + const chartData_url = `${config.apiUrl}/programs/conservation/acep/subprograms`; + getJsonDataFromUrl(chartData_url).then((response) => { + processData(response); + }); + }, []); + + const switchChartTable = (event, newTab) => { + if (newTab !== null) { + setTab(newTab); + } + }; + const processData = (chartData) => { + if (chartData.programs === undefined) return; + const cur1 = chartData.programs.find((s) => s.programName === "ACEP"); + + totalACEPPaymentInDollars = cur1.paymentInDollars; + setTotalAcep(totalACEPPaymentInDollars); + if (totalACEPPaymentInDollars === 0) zeroCategory.push("ACEP"); + totalContracts = cur1.totalContracts; + if (totalContracts === 0) zeroCategory.push("Total Contracts"); + totalAcres = cur1.totalAcre; + if (totalAcres === 0) zeroCategory.push("Total Acres"); + assistancePaymentInDollars = cur1.assistancePaymentInDollars; + if (assistancePaymentInDollars === 0) zeroCategory.push("Assistance Payment"); + reimbursePaymentInDollars = cur1.reimbursePaymentInDollars; + if (reimbursePaymentInDollars === 0) zeroCategory.push("Reimburse Payment"); + techPaymentInDollars = cur1.techPaymentInDollars; + if (techPaymentInDollars === 0) zeroCategory.push("Tech Payment"); + setZeroCategories(zeroCategory); + + setTotalChartData([ + { name: "ACEP", value: totalACEPPaymentInDollars, color: "#2F7164" }, + { name: "Assistance Payment", value: assistancePaymentInDollars, color: "#869397" }, + { name: "Reimburse Payment", value: reimbursePaymentInDollars, color: "#9CBAB4" }, + { name: "Tech Payment", value: techPaymentInDollars, color: "#C9D6D2" } + ]); + }; + function prepData(program, subprogram, data, dataYear) { + const organizedData: Record[] = []; + const originalData: Record[] = []; + data[dataYear].forEach((stateData) => { + const state = stateData.state; + const programData = stateData.programs.filter((p) => { + return p.programName.toString() === program; + }); + organizedData.push({ + state, + acres: programData[0].totalAcres, + payments: programData[0].paymentInDollars, + contracts: programData[0].totalContracts + }); + originalData.push({ + state, + acres: programData[0].totalAcres, + payments: programData[0].paymentInDollars, + contracts: programData[0].totalContracts + }); + }); + return [organizedData, originalData]; + } + return ( + + {Object.keys(stateCodesData).length > 0 && + Object.keys(allStatesData).length > 0 && + Object.keys(stateDistributionData).length > 0 ? ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ACEP: Agriculture Conservation Easement Program + + + + + + [TO BE UPDATED]The ACEP was first introduced in the 2014 Farm Bill as a consolidation of + three previously separate easement programs – the Wetlands Reserve Program, Grassland + Reserve Program, and Farm and Ranch Land Protection Program. The program is divided into + two basic tracks, the Wetland Reserve Easement (“WRE”) and the Agricultural Land + Easement (“ALE”). The wetland land easement track is largely similar to the old Wetlands + Reserve Program, while the agricultural land easement track incorporates the other two + former easement programs. ACEP participants receive financial and technical assistance + in exchange for enrolling their lands in one of the two tracks. + + + +
+ + + +
+ + + + + + + + Performance by States + + + + + + + + + + + + + + + + + + + + + + + + Performance by States + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ) : ( +

Loading data...

+ )} +
+ ); +} diff --git a/src/styles/subpage.css b/src/styles/subpage.css index e05bbcf0..00cc6f4c 100644 --- a/src/styles/subpage.css +++ b/src/styles/subpage.css @@ -31,8 +31,10 @@ display: flex; } - .stateTitleContainer .stateTitle { - margin-top: 0.5em; + .stateTitleContainer > .stateTitle { + font-weight: 700; + margin-top: 1em; + font-size: 1.2em; } @media only screen and (max-width: 1440px) { @@ -65,10 +67,12 @@ display: flex; } - .stateTitleContainer .stateTitle { + + .stateTitleContainer > .stateTitle { margin-top: 0.5em; } - + + .sideBar-short .MuiPaper-root { max-height: 99vh; } diff --git a/src/utils/apiutil.tsx b/src/utils/apiutil.tsx index 8d417e1d..10b31f23 100644 --- a/src/utils/apiutil.tsx +++ b/src/utils/apiutil.tsx @@ -18,12 +18,26 @@ export function convertAllState(inlist) { return JSON.parse(conv_str); } -export const getValueFromAttr = (stateRecord, attribute): string => { +export const getValueFromAttrDollar = (stateRecord, attribute): string => { + let ans = ""; + if (attribute) { + Object.keys(stateRecord).forEach((key) => { + const match = key.toLowerCase().match(/(.*?)(?=\s*indollars)/); + const extractedKey = match ? match[1] : ""; + if (extractedKey.includes(attribute)) { + ans = key; + } + }); + } + return ans; +}; + +export const getValueFromAttrPercentage = (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) { + const match = key.toLowerCase().match(/(.*?)(?=\s*inpercentagenationwide)/); + const extractedKey = match ? match[1] : ""; + if (extractedKey.includes(attribute)) { ans = key; } }); diff --git a/src/utils/legendConfig.json b/src/utils/legendConfig.json index edd6c2f5..d4807f02 100644 --- a/src/utils/legendConfig.json +++ b/src/utils/legendConfig.json @@ -60,6 +60,13 @@ "CREP Only":[100000, 10000000, 50000000, 80000000], "Continuous Non-CREP":[10000, 100000, 100000000, 200000000], "Farmable Wetland":[10000, 100000, 500000, 10000000], - "Grassland-CRP":[10000, 100000, 500000, 10000000] + "Grassland-CRP":[10000, 100000, 500000, 10000000], + + "Total ACEP": [5000000, 10000000, 50000000, 100000000], + "Total Contracts": [10, 40, 70, 100], + "Total Acres": [50,100, 1000, 10000], + "Assistance Payment": [2000000, 10000000, 50000000, 100000000], + "Reimburse Payment": [10000, 50000 ,100000, 150000], + "Tech Payment": [2000000, 6000000, 8000000, 10000000] } From df4cd28d98e70cf6081ae0527d8bc38dd6efc8a8 Mon Sep 17 00:00:00 2001 From: YONG WOOK KIM Date: Fri, 6 Oct 2023 15:37:19 -0500 Subject: [PATCH 02/18] RCPP frontend page --- src/components/ProgramDrawer.tsx | 164 +++++++++++++- src/components/rcpp/RCPPTotalMap.tsx | 260 ++++++++++++++++++++++ src/components/rcpp/RCPPTotalTable.tsx | 290 +++++++++++++++++++++++++ src/main.tsx | 2 + src/pages/RCPPPage.tsx | 202 +++++++++++++++++ src/utils/legendConfig.json | 5 +- 6 files changed, 918 insertions(+), 5 deletions(-) create mode 100644 src/components/rcpp/RCPPTotalMap.tsx create mode 100644 src/components/rcpp/RCPPTotalTable.tsx create mode 100644 src/pages/RCPPPage.tsx diff --git a/src/components/ProgramDrawer.tsx b/src/components/ProgramDrawer.tsx index 6afcd731..226c3a47 100644 --- a/src/components/ProgramDrawer.tsx +++ b/src/components/ProgramDrawer.tsx @@ -436,10 +436,99 @@ function CRPCheckboxList({ setCRPChecked, setShowPopUp, zeroCategory }) { ); } +function RCPPCheckboxList({ setRCPPChecked, setShowPopUp, zeroCategory }) { + const [checked, setChecked] = React.useState(currentChecked); + + const handleToggle = (value: number) => () => { + setChecked(value); + setRCPPChecked(value); + currentChecked = value; + setShowPopUp(false); + }; + + const RCPPList = ["Total RCPP"]; + + return ( + + {RCPPList.map((category, value) => { + const labelId = `checkbox-list-label-${value}`; + if (zeroCategory && zeroCategory.includes(category)) { + return ( + + + + + + + ); + } + // if (category !== "Total RCPP") { + // return ( + // + // + // + // + // + // + // ); + // } + return ( + + + + + + + + + ); + })} + + ); +} + interface ProgramDrawerProps { setEQIPChecked?: (value: number) => void; setCSPChecked?: (value: number) => void; setCRPChecked?: (value: number) => void; + setRCPPChecked?: (value: number) => void; zeroCategories?: string[]; } @@ -447,6 +536,7 @@ export default function ProgramDrawer({ setEQIPChecked, setCSPChecked, setCRPChecked, + setRCPPChecked, zeroCategories }: ProgramDrawerProps): JSX.Element { const location = useLocation(); @@ -507,6 +597,25 @@ export default function ProgramDrawer({ prevCrpOpen.current = crpOpen; }, [crpOpen]); + const [rcppOpen, setRcppOpen] = React.useState(false); + const rcppRef = React.useRef(null); + const handleRcppClick = () => { + if (location.pathname !== "/rcpp") { + navigate("/rcpp"); + window.location.reload(false); + } + // else { + // setRcppOpen((prevRcppOpen) => !prevRcppOpen); + // } + }; + const prevRcppOpen = React.useRef(rcppOpen); + React.useEffect(() => { + if (prevRcppOpen.current && !rcppOpen) { + rcppRef.current.focus(); + } + + prevRcppOpen.current = rcppOpen; + }, [rcppOpen]); const crpMenuHeight = window.innerHeight < 900 ? "38%" : "40%"; @@ -688,9 +797,58 @@ export default function ProgramDrawer({ ACEP: Agriculture Conservation Easement Program - - RCPP: Regional Conservation Partnership Program - + + + + {location.pathname === "/rcpp" ? ( + + RCPP: Regional Conservation Partnership Program + + ) : ( + RCPP: Regional Conservation Partnership Program + )} + + {/* + + STATUTE + + + */} + + + + + + + + + Other Conservation diff --git a/src/components/rcpp/RCPPTotalMap.tsx b/src/components/rcpp/RCPPTotalMap.tsx new file mode 100644 index 00000000..57ebf41a --- /dev/null +++ b/src/components/rcpp/RCPPTotalMap.tsx @@ -0,0 +1,260 @@ +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 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 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"; + +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, allStates, statePerformance, year, stateCodes, colorScale } = props; + return ( +
+ {allStates.length > 0 && statePerformance[year] !== undefined ? ( + + + {({ geographies }) => ( + <> + {geographies.map((geo) => { + const record = statePerformance[year].filter( + (v) => 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 +}; + +const RCPPTotalMap = ({ + 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 category = "Total RCPP"; + const years = "2018-2022"; + const maxValue = Math.max(...quantizeArray); + 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. + + +
+ )} +
+ + + +
+ + {content} + +
+
+
+ ); +}; + +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 RCPPTotalMap; diff --git a/src/components/rcpp/RCPPTotalTable.tsx b/src/components/rcpp/RCPPTotalTable.tsx new file mode 100644 index 00000000..98e32ede --- /dev/null +++ b/src/components/rcpp/RCPPTotalTable.tsx @@ -0,0 +1,290 @@ +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; + margin-left: ${window.innerWidth <= 1440 ? "480px" : "auto"}; + + 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, + 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(",", "")); + 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; + } + + 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 rcppTableData: any[] = []; + + // eslint-disable-next-line no-restricted-syntax + statePerformance[year].forEach((value) => { + const totalRcpp = value.programs.find((s) => s.programName === "RCPP"); + const stateName = value.state; + const newRecord = () => { + return { + state: stateName, + rcppBenefit: `$${totalRcpp.paymentInDollars + .toLocaleString(undefined, { minimumFractionDigits: 2 }) + .toString()}`, + percentage: `${totalRcpp.totalPaymentInPercentageNationwide.toString()}%`, + noContract: `${totalRcpp.totalContracts + .toLocaleString(undefined, { minimumFractionDigits: 0 }) + .toString()}`, + totAcre: `${totalRcpp.totalAcres.toLocaleString(undefined, { minimumFractionDigits: 0 }).toString()}`, + finPayment: `${totalRcpp.assistancePaymentInDollars + .toLocaleString(undefined, { minimumFractionDigits: 2 }) + .toString()}`, + reimbursePayment: `${totalRcpp.reimbursePaymentInDollars + .toLocaleString(undefined, { minimumFractionDigits: 2 }) + .toString()}`, + techPayment: `${totalRcpp.techPaymentInDollars + .toLocaleString(undefined, { minimumFractionDigits: 2 }) + .toString()}` + }; + }; + rcppTableData.push(newRecord()); + }); + + const columns = React.useMemo( + () => [ + { + Header: STATES, + accessor: "state", + paddingLeft: "5rem", + paddingRight: "5rem" + }, + { + Header: ( + + TOTAL RCPP BENEFITS + + ), + accessor: "rcppBenefit", + sortType: compareWithDollarSign, + Cell: function styleCells(row) { + return
{row.value}
; + } + }, + { + Header: ( + + NO. OF CONTRACTS
+
+ ), + accessor: "noContract", + sortType: compareNumber, + Cell: function styleCells(row) { + return
{row.value}
; + } + }, + { + Header: ( + + ACRES
+
+ ), + accessor: "totAcre", + sortType: compareNumber, + Cell: function styleCells(row) { + return
{row.value}
; + } + }, + { + Header: ( + + FINANCIAL ASSISTANT PAYMENTS
+
+ ), + accessor: "finPayment", + sortType: compareWithPercentSign, + Cell: function styleCells(row) { + return
{row.value}
; + } + }, + { + Header: ( + + REIMBURSABLE ASSISTANT PAYMENTS
+
+ ), + accessor: "reimbursePayment", + sortType: compareWithPercentSign, + Cell: function styleCells(row) { + return
{row.value}
; + } + }, + { + Header: ( + + TECHNICAL ASSISTANT PAYMENTS
+
+ ), + accessor: "techPayment", + sortType: compareWithPercentSign, + Cell: function styleCells(row) { + return
{row.value}
; + } + } + ], + [] + ); + + return ( + + + + + + ); +} + +export default App; diff --git a/src/main.tsx b/src/main.tsx index 4c198db2..fb09bb7d 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -4,6 +4,7 @@ import LandingPage from "./pages/LandingPage"; import EQIPPage from "./pages/EQIPPage"; import CSPPage from "./pages/CSPPage"; import CRPPage from "./pages/CRPPage"; +import RCPPPage from "./pages/RCPPPage"; import SNAPPage from "./pages/SNAPPage"; import TitleIPage from "./pages/TitleIPage"; import CropInsurancePage from "./pages/CropInsurancePage"; @@ -25,6 +26,7 @@ export default function Main(): JSX.Element { } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/pages/RCPPPage.tsx b/src/pages/RCPPPage.tsx new file mode 100644 index 00000000..9ae4f89c --- /dev/null +++ b/src/pages/RCPPPage.tsx @@ -0,0 +1,202 @@ +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/rcpp/RCPPTotalTable"; +import RCPPTotalMap from "../components/rcpp/RCPPTotalMap"; +// import CategoryTable from "../components/rcpp/CategoryTable"; +// import CategoryMap from "../components/rcpp/CategoryMap"; +import { config } from "../app.config"; +import { convertAllState, getJsonDataFromUrl } from "../utils/apiutil"; +import NavSearchBar from "../components/shared/NavSearchBar"; + +export default function RCPPPage(): 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 [stateCodesArray, setStateCodesArray] = React.useState({}); + const [allStatesData, setAllStatesData] = React.useState([]); + const [totalChartData, setTotalChartData] = React.useState([{}]); + const [zeroCategories, setZeroCategories] = React.useState([]); + const [totalRcpp, setTotalRcpp] = React.useState(0); + + const defaultTheme = createTheme(); + const zeroCategory = []; + let totalRCPPPaymentInDollars = 0; + let totalContracts = 0; + let totalAcres = 0; + let assistancePayments = 0; + let reimbursePayments = 0; + let techPayments = 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) => { + setStateCodesArray(response); + const converted_json = convertAllState(response); + setStateCodesData(converted_json); + }); + + const statedistribution_url = `${config.apiUrl}/programs/conservation/rcpp/state-distribution`; + getJsonDataFromUrl(statedistribution_url).then((response) => { + setStateDistributionData(response); + }); + + const chartData_url = `${config.apiUrl}/programs/conservation/rcpp/subprograms`; + getJsonDataFromUrl(chartData_url).then((response) => { + processData(response); + }); + }, []); + + const processData = (chartData) => { + if (chartData.programs === undefined) return; + + const cur1 = chartData.programs.find((s) => s.programName === "RCPP"); + + totalRCPPPaymentInDollars = cur1.paymentInDollars; + setTotalRcpp(totalRCPPPaymentInDollars); + if (totalRCPPPaymentInDollars === 0) zeroCategory.push("RCPP"); + totalContracts = cur1.totalContracts; + totalAcres = cur1.totalAcres; + assistancePayments = cur1.assistancePaymentInDollars; + reimbursePayments = cur1.reimbursePaymentInDollars; + techPayments = cur1.techPaymentInDollars; + + setZeroCategories(zeroCategory); + + setTotalChartData([ + { name: "Total Financial Assistance Payments", value: assistancePayments, color: "#2F7164" }, + { name: "Total Reimbursable Payments", value: reimbursePayments, color: "#869397" }, + { name: "Total Techinical Assistance Payments", value: techPayments, color: "#9CBAB4" } + ]); + }; + + return ( + + {Object.keys(stateCodesData).length > 0 && + Object.keys(allStatesData).length > 0 && + Object.keys(stateDistributionData).length > 0 ? ( + + + + + + + + + + + + + + + RCPP: Regional Conservation Partnership Program + + + + + + + RCPP: Regional Conservation Partnership Program + + + + 2 && checked < 6 ? "block" : "none" + }} + > + + + CRP Total Continuous: Sub-Category of Practice Performance + + + + + + 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. + + + +
+ + + +
+ + + + Overall Performance of States + + + + + +
+
+ ) : ( +

Loading data...

+ )} +
+ ); +} diff --git a/src/utils/legendConfig.json b/src/utils/legendConfig.json index edd6c2f5..ae0f25bc 100644 --- a/src/utils/legendConfig.json +++ b/src/utils/legendConfig.json @@ -60,6 +60,7 @@ "CREP Only":[100000, 10000000, 50000000, 80000000], "Continuous Non-CREP":[10000, 100000, 100000000, 200000000], "Farmable Wetland":[10000, 100000, 500000, 10000000], - "Grassland-CRP":[10000, 100000, 500000, 10000000] -} + "Grassland-CRP":[10000, 100000, 500000, 10000000], + "Total RCPP": [1000000, 5000000, 10000000, 20000000] +} From 36492209a63d58679c4150b9b59edb586e03f235 Mon Sep 17 00:00:00 2001 From: YONG WOOK KIM Date: Fri, 6 Oct 2023 15:47:32 -0500 Subject: [PATCH 03/18] removed unnecessary comments --- src/components/ProgramDrawer.tsx | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/components/ProgramDrawer.tsx b/src/components/ProgramDrawer.tsx index 226c3a47..8ff09162 100644 --- a/src/components/ProgramDrawer.tsx +++ b/src/components/ProgramDrawer.tsx @@ -477,27 +477,6 @@ function RCPPCheckboxList({ setRCPPChecked, setShowPopUp, zeroCategory }) { ); } - // if (category !== "Total RCPP") { - // return ( - // - // - // - // - // - // - // ); - // } return ( @@ -604,9 +583,6 @@ export default function ProgramDrawer({ navigate("/rcpp"); window.location.reload(false); } - // else { - // setRcppOpen((prevRcppOpen) => !prevRcppOpen); - // } }; const prevRcppOpen = React.useRef(rcppOpen); React.useEffect(() => { From 1159a3f61cc79448e07351b1b1ad3cd355918a23 Mon Sep 17 00:00:00 2001 From: "Wendy(Pengyin) Shan" Date: Mon, 9 Oct 2023 12:45:54 -0500 Subject: [PATCH 04/18] remove total acep from semi-donut chart --- src/pages/ACEPPage.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/ACEPPage.tsx b/src/pages/ACEPPage.tsx index 0f448b78..ae61b159 100644 --- a/src/pages/ACEPPage.tsx +++ b/src/pages/ACEPPage.tsx @@ -88,8 +88,7 @@ export default function ACEPPage(): JSX.Element { setZeroCategories(zeroCategory); setTotalChartData([ - { name: "ACEP", value: totalACEPPaymentInDollars, color: "#2F7164" }, - { name: "Assistance Payment", value: assistancePaymentInDollars, color: "#869397" }, + { name: "Assistance Payment", value: assistancePaymentInDollars, color: "#2F7164" }, { name: "Reimburse Payment", value: reimbursePaymentInDollars, color: "#9CBAB4" }, { name: "Tech Payment", value: techPaymentInDollars, color: "#C9D6D2" } ]); From 38d2b36b97a0c0d69489a82436319ac88ec7cccd Mon Sep 17 00:00:00 2001 From: YONG WOOK KIM Date: Mon, 9 Oct 2023 14:21:54 -0500 Subject: [PATCH 05/18] fixed based on Jonathan's suggestion --- src/components/rcpp/RCPPTotalMap.tsx | 2 +- src/components/rcpp/RCPPTotalTable.tsx | 40 ++++---------------------- src/pages/RCPPPage.tsx | 40 +++++++++++++++++++------- 3 files changed, 36 insertions(+), 46 deletions(-) diff --git a/src/components/rcpp/RCPPTotalMap.tsx b/src/components/rcpp/RCPPTotalMap.tsx index 57ebf41a..6ee8ed0c 100644 --- a/src/components/rcpp/RCPPTotalMap.tsx +++ b/src/components/rcpp/RCPPTotalMap.tsx @@ -44,7 +44,7 @@ const MapChart = (props) => { } const totalPaymentInDollars = record.programs[0].paymentInDollars; const totalPaymentInPercentageNationwide = - record.programs[0].paymentInPercentageNationwide; + record.programs[0].totalPaymentInPercentageNationwide; const hoverContent = ( ACRES
@@ -239,37 +239,7 @@ function App({
), accessor: "finPayment", - sortType: compareWithPercentSign, - Cell: function styleCells(row) { - return
{row.value}
; - } - }, - { - Header: ( - - REIMBURSABLE ASSISTANT PAYMENTS
-
- ), - accessor: "reimbursePayment", - sortType: compareWithPercentSign, - Cell: function styleCells(row) { - return
{row.value}
; - } - }, - { - Header: ( - - TECHNICAL ASSISTANT PAYMENTS
-
- ), - accessor: "techPayment", - sortType: compareWithPercentSign, + sortType: compareWithDollarSign, Cell: function styleCells(row) { return
{row.value}
; } diff --git a/src/pages/RCPPPage.tsx b/src/pages/RCPPPage.tsx index 9ae4f89c..37cf2a8a 100644 --- a/src/pages/RCPPPage.tsx +++ b/src/pages/RCPPPage.tsx @@ -1,6 +1,7 @@ import Box from "@mui/material/Box"; import * as React from "react"; import { createTheme, ThemeProvider, Typography } from "@mui/material"; +import { Label } from "recharts"; import NavBar from "../components/NavBar"; import Drawer from "../components/ProgramDrawer"; import SemiDonutChart from "../components/SemiDonutChart"; @@ -24,6 +25,7 @@ export default function RCPPPage(): JSX.Element { const [totalChartData, setTotalChartData] = React.useState([{}]); const [zeroCategories, setZeroCategories] = React.useState([]); const [totalRcpp, setTotalRcpp] = React.useState(0); + const [totalBenefit, setTotalBenefit] = React.useState(""); const defaultTheme = createTheme(); const zeroCategory = []; @@ -79,6 +81,27 @@ export default function RCPPPage(): JSX.Element { { name: "Total Reimbursable Payments", value: reimbursePayments, color: "#869397" }, { name: "Total Techinical Assistance Payments", value: techPayments, color: "#9CBAB4" } ]); + + if (Number(totalRCPPPaymentInDollars.toString()) >= 1000000000) { + const totalBenefitTmp = `$${Number( + Number(totalRCPPPaymentInDollars.toString()) / 1000000000.0 + ).toLocaleString(undefined, { + maximumFractionDigits: 2 + })}B`; + setTotalBenefit(totalBenefitTmp); + } else { + const totalBenefitTmp = `$${Number(Number(totalRCPPPaymentInDollars.toString()) / 1000000.0).toLocaleString( + undefined, + { + maximumFractionDigits: 2 + } + )}M`; + setTotalBenefit(totalBenefitTmp); + } + + // position="center" + // dy={-75} + // style={{ textAnchor: "middle", fontSize: "200%", fill: "rgba(0, 0, 0, 0.87)" }} }; return ( @@ -170,21 +193,18 @@ export default function RCPPPage(): JSX.Element { -
- - - -
- Overall Performance of States + + + + {totalBenefit} + + + Date: Mon, 9 Oct 2023 16:36:10 -0500 Subject: [PATCH 06/18] update acep based on client's request and adjust Treemaps on both acep and Title1 page --- CHANGELOG.md | 14 ++ src/components/ProgramDrawer.tsx | 38 +--- src/components/acep/ACEPTable.tsx | 78 ++++++-- src/components/acep/AcepTreeMap.tsx | 79 ++++---- src/components/acep/TreeMapSquares.tsx | 42 ++--- src/components/eqip/EQIPTotalTable.tsx | 1 - src/components/title1/Title1TreeMap.tsx | 68 +++---- src/pages/ACEPPage.tsx | 229 ++++-------------------- src/pages/TitleIPage.tsx | 11 +- src/utils/legendConfig.json | 7 +- 10 files changed, 210 insertions(+), 357 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd6a0444..745e608a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,20 @@ 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 + +- ACEP page and corresponding components for its attributes [#200](https://github.com/policy-design-lab/pdl-frontend/issues/200) + +### Changed +- Reverse some unnecessary changes in EQIP, CSP and CRP table [#199](https://github.com/policy-design-lab/pdl-frontend/issues/199) + + +## Fixed +- Adjust program drawer to remove over-length scroll bar in Chrome/Firefox [#198](https://github.com/policy-design-lab/pdl-frontend/issues/198) + + ## [0.9.0] - 2023-09-18 ### Added diff --git a/src/components/ProgramDrawer.tsx b/src/components/ProgramDrawer.tsx index fd8fb048..26019146 100644 --- a/src/components/ProgramDrawer.tsx +++ b/src/components/ProgramDrawer.tsx @@ -786,50 +786,16 @@ export default function ProgramDrawer({ sx={{ my: 1, pl: 3, pr: 0, py: 0, backgroundColor: acepOpen ? "#ecf0ee" : "grey" }} onClick={handleAcepClick} > - + {location.pathname === "/acep" ? ( - + ACEP: Agriculture Conservation Easement Program ) : ( ACEP: Agriculture Conservation Easement Program )} - - - - STATUE - - - - - - - - - RCPP: Regional Conservation Partnership Program diff --git a/src/components/acep/ACEPTable.tsx b/src/components/acep/ACEPTable.tsx index 6669f668..2136ba21 100644 --- a/src/components/acep/ACEPTable.tsx +++ b/src/components/acep/ACEPTable.tsx @@ -45,6 +45,10 @@ function AcepProgramTable({ Object.entries(hashmap[s]).forEach(([attr, value]) => { if (attr.includes("Percentage")) { newRecord[attr] = `${value.toString()}%`; + } else if (attr === "totalAcres" || attr === "totalContracts") { + newRecord[attr] = `${ + value.toLocaleString(undefined, { minimumFractionDigits: 2 }).toString().split(".")[0] + }`; } else { newRecord[attr] = `$${ value.toLocaleString(undefined, { minimumFractionDigits: 2 }).toString().split(".")[0] @@ -58,7 +62,7 @@ function AcepProgramTable({ attributes.forEach((attribute) => { let sortMethod = compareWithDollarSign; if (attribute.includes("Percentage")) sortMethod = compareWithPercentSign; - if (attribute.includes("totalAcres") || attribute.includes("totalAcres")) sortMethod = compareWithNumber; + if (attribute.includes("totalContracts") || attribute.includes("totalAcres")) sortMethod = compareWithNumber; const json = { Header: attribute .replace(/([A-Z])/g, " $1") @@ -73,6 +77,12 @@ function AcepProgramTable({ columnPrep.push(json); }); const columns = React.useMemo(() => columnPrep, []); + const paymentsIndex = columns.findIndex((c) => c.accessor === "paymentInDollars"); + const acresIndex = columns.findIndex((c) => c.accessor === "totalAcres"); + const contractsIndex = columns.findIndex((c) => c.accessor === "totalContracts"); + const paymentsPercentageIndex = columns.findIndex((c) => c.accessor === "totalPaymentInPercentageNationwide"); + const contractsPercentageIndex = columns.findIndex((c) => c.accessor === "contractsInPercentageNationwide"); + const acresPercentageIndex = columns.findIndex((c) => c.accessor === "acresInPercentageNationwide"); const Styles = styled.div` padding: 0; margin: 0; @@ -107,6 +117,26 @@ function AcepProgramTable({ padding-right: 10em; } + td[class$="cell${paymentsIndex}"] { + background-color: ${colors[0]}; + } + + td[class$="cell${paymentsPercentageIndex}"] { + background-color: ${colors[0]}; + } + td[class$="cell${contractsIndex}"] { + background-color: ${colors[1]}; + } + td[class$="cell${contractsPercentageIndex}"] { + background-color: ${colors[1]}; + } + td[class$="cell${acresIndex}"] { + background-color: ${colors[2]}; + } + td[class$="cell${acresPercentageIndex}"] { + background-color: ${colors[2]}; + } + td[class$="cell1"], td[class$="cell2"], td[class$="cell3"], @@ -125,26 +155,34 @@ function AcepProgramTable({ border-right: 0; } } + + table .tableArrow{ + margin-left: 8px; + } } .pagination { margin-top: 1.5em; } - @media screen and (max-width: 1024px) { - th, - td { - padding: 8px; - } - td[class$="cell0"] { - padding-right: 1em; + @media screen and (max-width: 1440px) { + table { + font-size: 0.9em; + + th:not(:first-of-type) { + text-align: left; } - .pagination { + } + table th, + table td { + padding: 1em; + text-align: left; + } + .pagination { margin-top: 8px; - } + } } .acepBox > .stateTitle { - font-weight: 700; margin-top: 0.5em; font-size: 1.2em; text-align: left; @@ -240,10 +278,22 @@ function Table({ columns, data, initialState }: { columns: any; data: any; initi {(() => { if (!column.isSorted) - return {"\u{2B83}"}; + return ( + + {"\u{2B83}"} + + ); if (column.isSortedDesc) - return {"\u{25BC}"}; - return {"\u{25B2}"}; + return ( + + {"\u{25BC}"} + + ); + return ( + + {"\u{25B2}"} + + ); })()} diff --git a/src/components/acep/AcepTreeMap.tsx b/src/components/acep/AcepTreeMap.tsx index 628cf56b..03c4497c 100644 --- a/src/components/acep/AcepTreeMap.tsx +++ b/src/components/acep/AcepTreeMap.tsx @@ -84,9 +84,9 @@ export default function AcepTreeMap({ program, TreeMapData, year, stateCodes, sv contractsChecked: true }); const { paymentsChecked, acresChecked, contractsChecked } = checkedState; - let widthPercentage = 0.5; + let widthPercentage = 0.7; const heightPercentage = 0.8; - if (window.innerWidth >= 1920) { + if (window.innerWidth <= 1440) { widthPercentage = 0.6; } const handleResize: () => void = () => { @@ -96,6 +96,7 @@ export default function AcepTreeMap({ program, TreeMapData, year, stateCodes, sv }; React.useEffect(() => { window.addEventListener("resize", handleResize); + drawIllustration(); return () => window.removeEventListener("resize", handleResize); }); const handleSortClick = (e, attr) => { @@ -146,33 +147,38 @@ export default function AcepTreeMap({ program, TreeMapData, year, stateCodes, sv URL.revokeObjectURL(url); } }; - if (chartData[0].payments !== 0) { - d3.select(rn.current) - .append("rect") - .attr("x", 0) - .attr("y", 0) - .attr("width", AcepTreeMapIllustration) - .attr("height", AcepTreeMapIllustration) - .attr("fill", paymentsColor); - } - if (chartData[0].acres !== 0) { - d3.select(rn.current) - .append("rect") - .attr("x", 0) - .attr("y", AcepTreeMapIllustration * 0.3) - .attr("width", AcepTreeMapIllustration * 0.7) - .attr("height", AcepTreeMapIllustration * 0.7) - .attr("fill", acresColor); - } - if (chartData[0].contracts !== 0) { - d3.select(rn.current) - .append("rect") - .attr("x", 0) - .attr("y", AcepTreeMapIllustration * 0.6) - .attr("width", AcepTreeMapIllustration * 0.4) - .attr("height", AcepTreeMapIllustration * 0.4) - .attr("fill", contractsColor); + function drawIllustration() { + d3.select(rn.current).selectAll("*").remove(); + if (chartData[0].payments !== 0) { + const test = d3 + .select(rn.current) + .append("rect") + .attr("x", 0) + .attr("y", 0) + .attr("width", AcepTreeMapIllustration) + .attr("height", AcepTreeMapIllustration) + .attr("fill", paymentsColor); + } + if (chartData[0].acres !== 0) { + d3.select(rn.current) + .append("rect") + .attr("x", 0) + .attr("y", AcepTreeMapIllustration * 0.3) + .attr("width", AcepTreeMapIllustration * 0.7) + .attr("height", AcepTreeMapIllustration * 0.7) + .attr("fill", acresColor); + } + if (chartData[0].contracts !== 0) { + d3.select(rn.current) + .append("rect") + .attr("x", 0) + .attr("y", AcepTreeMapIllustration * 0.6) + .attr("width", AcepTreeMapIllustration * 0.4) + .attr("height", AcepTreeMapIllustration * 0.4) + .attr("fill", contractsColor); + } } + /* eslint-disable */ return ( @@ -200,10 +206,8 @@ export default function AcepTreeMap({ program, TreeMapData, year, stateCodes, sv {program.includes("(") ? `Comparing ${program .match(/\((.*?)\)/g) - .map((match) => - match.slice(1, -1) - )} Payments, No. of Contracts and Farms/acreas(ac)` - : `Comparing ${program} Payments, No. of Contracts and Farms/acreas(ac)`} + .map((match) => match.slice(1, -1))} Payments, Acres and No. of Contracts` + : `Comparing ${program} Payments, Acres and No. of Contracts`} The size differences of the squares represent the differences in relative amount{" "} within the same category. For example, a larger purple square indicate a higher - number of avg. contracts compared to another smaller purple square, but it does not necessarily - indicate a greater number of avg. contracts compared to a smaller yellow square representing - payments. + number of avg. contracts compared to another smaller purple square, but it does not + necessarily indicate a greater number of avg. contracts compared to a smaller yellow square + representing payments. @@ -320,7 +324,7 @@ export default function AcepTreeMap({ program, TreeMapData, year, stateCodes, sv style={{ color: acresColor }} /> } - label="Farms/acreas(ac)" + label="Acres" sx={{ color: acresColor }} /> ) : null} @@ -335,7 +339,7 @@ export default function AcepTreeMap({ program, TreeMapData, year, stateCodes, sv style={{ color: contractsColor }} /> } - label="Contracts" + label="No. of Contracts" sx={{ color: contractsColor }} /> ) : null} @@ -378,7 +382,6 @@ export default function AcepTreeMap({ program, TreeMapData, year, stateCodes, sv availableAttributes={availableAttributes} program={program} /> - diff --git a/src/components/acep/TreeMapSquares.tsx b/src/components/acep/TreeMapSquares.tsx index c4ba76f1..a775c77f 100644 --- a/src/components/acep/TreeMapSquares.tsx +++ b/src/components/acep/TreeMapSquares.tsx @@ -108,8 +108,7 @@ export default function TreeMapSquares({ const mousePos = d3.pointer(event, squareGroup.node()); const tipGroup = base.append("g").attr("class", "TreeMapSquareTip"); const xPosition = mousePos[0] + 160 > svgWidth ? mousePos[0] - 160 : mousePos[0]; - const tipHeight = - program === "Agriculture Risk Coverage Individual Coverage (ARC-IC)" ? 80 : 100; + const tipHeight = 100; tipGroup .append("rect") .attr("x", xPosition) @@ -142,30 +141,21 @@ export default function TreeMapSquares({ .attr("y", mousePos[1] + stepSize * 2) .style("font-size", "0.8em") .style("fill", "white"); - if (program === "Agriculture Risk Coverage Individual Coverage (ARC-IC)") { - tipGroup - .append("text") - .text(`Contracts: ${contractsOriginalData}`) - .attr("x", xPosition + baseSize) - .attr("y", mousePos[1] + stepSize * 3) - .style("font-size", "0.8em") - .style("fill", "white"); - } else { - tipGroup - .append("text") - .text(`Farms/acreas(ac): ${acresOriginalData}`) - .attr("x", xPosition + baseSize) - .attr("y", mousePos[1] + stepSize * 3) - .style("font-size", "0.8em") - .style("fill", "white"); - tipGroup - .append("text") - .text(`Contracts: ${contractsOriginalData}`) - .attr("x", xPosition + 10) - .attr("y", mousePos[1] + stepSize * 4) - .style("font-size", "0.8em") - .style("fill", "white"); - } + + tipGroup + .append("text") + .text(`Acres: ${acresOriginalData}`) + .attr("x", xPosition + baseSize) + .attr("y", mousePos[1] + stepSize * 3) + .style("font-size", "0.8em") + .style("fill", "white"); + tipGroup + .append("text") + .text(`No. of Contracts: ${contractsOriginalData}`) + .attr("x", xPosition + 10) + .attr("y", mousePos[1] + stepSize * 4) + .style("font-size", "0.8em") + .style("fill", "white"); }) .on("mouseleave", function (e) { base.selectAll(".TreeMapSquareTip").remove(); diff --git a/src/components/eqip/EQIPTotalTable.tsx b/src/components/eqip/EQIPTotalTable.tsx index 425850d4..b58a2ceb 100644 --- a/src/components/eqip/EQIPTotalTable.tsx +++ b/src/components/eqip/EQIPTotalTable.tsx @@ -6,7 +6,6 @@ import "../../styles/table.css"; const Styles = styled.div` padding: 1rem; - ${window.innerWidth <= 1440 ? "margin-left: 480px;" : ""} table { border-spacing: 0; diff --git a/src/components/title1/Title1TreeMap.tsx b/src/components/title1/Title1TreeMap.tsx index 1eb4a903..2d8a3cdb 100644 --- a/src/components/title1/Title1TreeMap.tsx +++ b/src/components/title1/Title1TreeMap.tsx @@ -85,9 +85,9 @@ export default function Title1TreeMap({ program, TreeMapData, year, stateCodes, recipientsChecked: true }); const { paymentsChecked, baseAcresChecked, recipientsChecked } = checkedState; - let widthPercentage = 0.5; + let widthPercentage = 0.7; const heightPercentage = 0.8; - if (window.innerWidth >= 1920) { + if (window.innerWidth <= 1440) { widthPercentage = 0.6; } const handleResize: () => void = () => { @@ -97,6 +97,7 @@ export default function Title1TreeMap({ program, TreeMapData, year, stateCodes, }; React.useEffect(() => { window.addEventListener("resize", handleResize); + drawIllustration(); return () => window.removeEventListener("resize", handleResize); }); const handleSortClick = (e, attr) => { @@ -147,32 +148,34 @@ export default function Title1TreeMap({ program, TreeMapData, year, stateCodes, URL.revokeObjectURL(url); } }; - if (chartData[0].payments !== 0) { - d3.select(rn.current) - .append("rect") - .attr("x", 0) - .attr("y", 0) - .attr("width", Title1TreeMapIllustration) - .attr("height", Title1TreeMapIllustration) - .attr("fill", paymentsColor); - } - if (chartData[0].baseAcres !== 0) { - d3.select(rn.current) - .append("rect") - .attr("x", 0) - .attr("y", Title1TreeMapIllustration * 0.3) - .attr("width", Title1TreeMapIllustration * 0.7) - .attr("height", Title1TreeMapIllustration * 0.7) - .attr("fill", baseAcresColor); - } - if (chartData[0].recipients !== 0) { - d3.select(rn.current) - .append("rect") - .attr("x", 0) - .attr("y", Title1TreeMapIllustration * 0.6) - .attr("width", Title1TreeMapIllustration * 0.4) - .attr("height", Title1TreeMapIllustration * 0.4) - .attr("fill", recipientsColor); + function drawIllustration() { + if (chartData[0].payments !== 0) { + d3.select(rn.current) + .append("rect") + .attr("x", 0) + .attr("y", 0) + .attr("width", Title1TreeMapIllustration) + .attr("height", Title1TreeMapIllustration) + .attr("fill", paymentsColor); + } + if (chartData[0].baseAcres !== 0) { + d3.select(rn.current) + .append("rect") + .attr("x", 0) + .attr("y", Title1TreeMapIllustration * 0.3) + .attr("width", Title1TreeMapIllustration * 0.7) + .attr("height", Title1TreeMapIllustration * 0.7) + .attr("fill", baseAcresColor); + } + if (chartData[0].recipients !== 0) { + d3.select(rn.current) + .append("rect") + .attr("x", 0) + .attr("y", Title1TreeMapIllustration * 0.6) + .attr("width", Title1TreeMapIllustration * 0.4) + .attr("height", Title1TreeMapIllustration * 0.4) + .attr("fill", recipientsColor); + } } /* eslint-disable */ return ( @@ -197,7 +200,8 @@ export default function Title1TreeMap({ program, TreeMapData, year, stateCodes, }} > - The payments,base acres and payment recipients are calculated as the total of the data from 2014-2021. 2022 payments for Title I have not yet been paid. + The payments,base acres and payment recipients are calculated as the total of the data + from 2014-2021. 2022 payments for Title I have not yet been paid. The size differences of the squares represent the differences in relative amount{" "} within the same category. For example, a larger purple square indicate a higher - number of avg. recipients compared to another smaller purple square, but it does not necessarily - indicate a greater number of avg. recipients compared to a smaller yellow square representing - payments. + number of avg. recipients compared to another smaller purple square, but it does not + necessarily indicate a greater number of avg. recipients compared to a smaller yellow square + representing payments. diff --git a/src/pages/ACEPPage.tsx b/src/pages/ACEPPage.tsx index ae61b159..2fa00a8e 100644 --- a/src/pages/ACEPPage.tsx +++ b/src/pages/ACEPPage.tsx @@ -5,9 +5,7 @@ import TableChartIcon from "@mui/icons-material/TableChart"; import InsertChartIcon from "@mui/icons-material/InsertChart"; import NavBar from "../components/NavBar"; import Drawer from "../components/ProgramDrawer"; -import SemiDonutChart from "../components/SemiDonutChart"; import ACEPTotalMap from "../components/acep/ACEPTotalMap"; -import CategoryMap from "../components/acep/ACEPCategoryMap"; import { config } from "../app.config"; import { convertAllState, getJsonDataFromUrl } from "../utils/apiutil"; import NavSearchBar from "../components/shared/NavSearchBar"; @@ -23,12 +21,9 @@ export default function ACEPPage(): JSX.Element { 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([{}]); const [zeroCategories, setZeroCategories] = React.useState([]); const [totalAcep, setTotalAcep] = React.useState(0); const [tab, setTab] = React.useState(0); - const initTreeMapWidthRatio = 0.6; const defaultTheme = createTheme(); const zeroCategory = []; @@ -86,12 +81,6 @@ export default function ACEPPage(): JSX.Element { techPaymentInDollars = cur1.techPaymentInDollars; if (techPaymentInDollars === 0) zeroCategory.push("Tech Payment"); setZeroCategories(zeroCategory); - - setTotalChartData([ - { name: "Assistance Payment", value: assistancePaymentInDollars, color: "#2F7164" }, - { name: "Reimburse Payment", value: reimbursePaymentInDollars, color: "#9CBAB4" }, - { name: "Tech Payment", value: techPaymentInDollars, color: "#C9D6D2" } - ]); }; function prepData(program, subprogram, data, dataYear) { const organizedData: Record[] = []; @@ -149,71 +138,6 @@ export default function ACEPPage(): JSX.Element { allStates={allStatesData} /> - - - - - - - - - - - - - - - ACEP: Agriculture Conservation Easement Program + + + Total Benefits ({year}): + + $ + { + totalAcep + .toLocaleString(undefined, { minimumFractionDigits: 2 }) + .toString() + .split(".")[0] + } + + + @@ -239,17 +177,7 @@ export default function ACEPPage(): JSX.Element { -
- - - -
- - + {/* - - - - - Performance by States - - - - - - - - - - - - - - - - - - - - - + */} + 1440 ? window.innerWidth * 0.7 : window.innerWidth * 0.6 + } svgH={3000} /> - - - - - - - - - ) : ( diff --git a/src/pages/TitleIPage.tsx b/src/pages/TitleIPage.tsx index 70578c20..c6c0e800 100644 --- a/src/pages/TitleIPage.tsx +++ b/src/pages/TitleIPage.tsx @@ -33,7 +33,7 @@ export default function TitleIPage(): JSX.Element { const title1Div = React.useRef(null); const [checked, setChecked] = React.useState("0"); const mapColor = ["#F9F9D3", "#F9D48B", "#F59020", "#D95F0E", "#993404"]; - let initTreeMapWidthRatio = 0.6; + const initTreeMapWidthRatio = 0.6; React.useEffect(() => { const allstates_url = `${config.apiUrl}/states`; @@ -49,9 +49,6 @@ export default function TitleIPage(): JSX.Element { getJsonDataFromUrl(statedistribution_url).then((response) => { setStateDistributionData(response); }); - if (window.innerWidth >= 1920) { - initTreeMapWidthRatio = 0.7; - } }, []); const switchChartTable = (event, newTab) => { @@ -256,7 +253,11 @@ export default function TitleIPage(): JSX.Element { )} stateCodes={stateCodesData} year="2014-2021" - svgW={window.innerWidth * initTreeMapWidthRatio} + svgW={ + window.innerWidth > 1440 + ? window.innerWidth * 0.75 + : window.innerWidth * 0.65 + } svgH={3000} /> diff --git a/src/utils/legendConfig.json b/src/utils/legendConfig.json index d4807f02..377b8507 100644 --- a/src/utils/legendConfig.json +++ b/src/utils/legendConfig.json @@ -62,11 +62,6 @@ "Farmable Wetland":[10000, 100000, 500000, 10000000], "Grassland-CRP":[10000, 100000, 500000, 10000000], - "Total ACEP": [5000000, 10000000, 50000000, 100000000], - "Total Contracts": [10, 40, 70, 100], - "Total Acres": [50,100, 1000, 10000], - "Assistance Payment": [2000000, 10000000, 50000000, 100000000], - "Reimburse Payment": [10000, 50000 ,100000, 150000], - "Tech Payment": [2000000, 6000000, 8000000, 10000000] + "Total ACEP": [5000000, 10000000, 50000000, 100000000] } From c2f5afe45e2dd8bf417fe93ccf4e419bec62b629 Mon Sep 17 00:00:00 2001 From: "Wendy(Pengyin) Shan" Date: Tue, 10 Oct 2023 09:25:16 -0500 Subject: [PATCH 07/18] adjust text of acep treemap --- src/components/acep/AcepTreeMap.tsx | 18 +++++++++--------- src/pages/ACEPPage.tsx | 15 +-------------- 2 files changed, 10 insertions(+), 23 deletions(-) diff --git a/src/components/acep/AcepTreeMap.tsx b/src/components/acep/AcepTreeMap.tsx index 03c4497c..709480f6 100644 --- a/src/components/acep/AcepTreeMap.tsx +++ b/src/components/acep/AcepTreeMap.tsx @@ -191,7 +191,7 @@ export default function AcepTreeMap({ program, TreeMapData, year, stateCodes, sv // marginLeft: 3 }} > - + {program.includes("(") - ? `Comparing ${program + ? `Comparing Total ${program .match(/\((.*?)\)/g) - .map((match) => match.slice(1, -1))} Payments, Acres and No. of Contracts` - : `Comparing ${program} Payments, Acres and No. of Contracts`} + .map((match) => match.slice(1, -1))} Benefits, Acres and No. of Contracts (${year})` + : `Comparing Total ${program} Benefits, Acres and No. of Contracts (${year})`} The size differences of the squares represent the differences in relative amount{" "} within the same category. For example, a larger purple square indicate a higher - number of avg. contracts compared to another smaller purple square, but it does not - necessarily indicate a greater number of avg. contracts compared to a smaller yellow square - representing payments. + number of no. of contracts compared to another smaller purple square, but it does not + necessarily indicate a greater number of no. of contracts compared to a smaller green square + representing acres. - + @@ -365,7 +365,7 @@ export default function AcepTreeMap({ program, TreeMapData, year, stateCodes, sv }} > Click the buttons - above to sort squares by payments, avg. base acres or avg. contracts. + above to sort squares by total benefits, acres or no. of contracts. diff --git a/src/pages/ACEPPage.tsx b/src/pages/ACEPPage.tsx index 2fa00a8e..2983a973 100644 --- a/src/pages/ACEPPage.tsx +++ b/src/pages/ACEPPage.tsx @@ -176,19 +176,6 @@ export default function ACEPPage(): JSX.Element { in exchange for enrolling their lands in one of the two tracks. - - {/* - - */} Date: Tue, 10 Oct 2023 09:47:01 -0500 Subject: [PATCH 08/18] adjust acep treemap layout at the smaller screen --- src/components/acep/AcepTreeMap.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/acep/AcepTreeMap.tsx b/src/components/acep/AcepTreeMap.tsx index 709480f6..bb47c264 100644 --- a/src/components/acep/AcepTreeMap.tsx +++ b/src/components/acep/AcepTreeMap.tsx @@ -247,8 +247,8 @@ export default function AcepTreeMap({ program, TreeMapData, year, stateCodes, sv - - + + From 841fe01f09bcf5e1cc0f369b9ab87931e85c86f9 Mon Sep 17 00:00:00 2001 From: YONG WOOK KIM Date: Tue, 10 Oct 2023 14:42:21 -0500 Subject: [PATCH 09/18] added paragraph for RCPP --- src/pages/RCPPPage.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/pages/RCPPPage.tsx b/src/pages/RCPPPage.tsx index 37cf2a8a..535aec97 100644 --- a/src/pages/RCPPPage.tsx +++ b/src/pages/RCPPPage.tsx @@ -185,11 +185,14 @@ export default function RCPPPage(): JSX.Element { - 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. + RCPP assists with the conservation, protection, restoration, and sustainable uses of + soil, water, wildlife, and related natural resources on a regional or watershed scale + and combines federal funding with private partner funding and other contributions. RCPP + works across multiple farms (or nonindustrial private forest operations) through + multiple conservation practices to achieve greater conservation outcomes, encourage + flexibility in program operation, and leverage private resources. Congress created the + RCPP in the Agricultural Act of 2014 and revised the program in the Agricultural + Improvement Act of 2018 From e064aa9f3a024ff4da01c780508cd7b0d40dae16 Mon Sep 17 00:00:00 2001 From: "Wendy(Pengyin) Shan" Date: Tue, 10 Oct 2023 16:25:03 -0500 Subject: [PATCH 10/18] update acep description paragraph --- src/pages/ACEPPage.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/pages/ACEPPage.tsx b/src/pages/ACEPPage.tsx index 2983a973..f60df32c 100644 --- a/src/pages/ACEPPage.tsx +++ b/src/pages/ACEPPage.tsx @@ -166,14 +166,14 @@ export default function ACEPPage(): JSX.Element { - [TO BE UPDATED]The ACEP was first introduced in the 2014 Farm Bill as a consolidation of - three previously separate easement programs – the Wetlands Reserve Program, Grassland - Reserve Program, and Farm and Ranch Land Protection Program. The program is divided into - two basic tracks, the Wetland Reserve Easement (“WRE”) and the Agricultural Land - Easement (“ALE”). The wetland land easement track is largely similar to the old Wetlands - Reserve Program, while the agricultural land easement track incorporates the other two - former easement programs. ACEP participants receive financial and technical assistance - in exchange for enrolling their lands in one of the two tracks. + A conservation easement is a permanent (or long term) property right in agricultural + land for conservation of natural resources. Agricultural Land Easements (ALE) limit + non-agricultural uses of eligible land, including grazing land, to protect farmland from + development or other pressures and works through land trusts or other entities such as + state and local governments. Wetland Reserve Easements (WRE) protect, restore, and + enhance wetlands that have been previously degraded due to agricultural uses. In the + Agricultural Act of 2014, Congress combined existing conservation easement programs into + a single program. From 06c36fda7cc49c5f30950a16b07a63a79754cbe7 Mon Sep 17 00:00:00 2001 From: "Wendy(Pengyin) Shan" Date: Thu, 12 Oct 2023 11:36:03 -0500 Subject: [PATCH 11/18] add acep missing changes and modify some code of rcpp --- CHANGELOG.md | 2 + src/components/ProgramDrawer.tsx | 48 +++++++--------- src/components/rcpp/RCPPTotalMap.tsx | 4 +- src/components/rcpp/RCPPTotalTable.tsx | 75 +++++++++++------------- src/main.tsx | 1 + src/pages/RCPPPage.tsx | 80 ++++++++------------------ src/utils/legendConfig.json | 1 + 7 files changed, 84 insertions(+), 127 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 745e608a..1ebb56da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - ACEP page and corresponding components for its attributes [#200](https://github.com/policy-design-lab/pdl-frontend/issues/200) +- RCPP page and corresponding components for its attributes [#196](https://github.com/policy-design-lab/pdl-frontend/issues/196) + ### Changed - Reverse some unnecessary changes in EQIP, CSP and CRP table [#199](https://github.com/policy-design-lab/pdl-frontend/issues/199) diff --git a/src/components/ProgramDrawer.tsx b/src/components/ProgramDrawer.tsx index bf573d6b..a30e1e82 100644 --- a/src/components/ProgramDrawer.tsx +++ b/src/components/ProgramDrawer.tsx @@ -790,9 +790,24 @@ export default function ProgramDrawer({ - - ACEP: Agriculture Conservation Easement Program - + + + + {location.pathname === "/acep" ? ( + + ACEP: Agriculture Conservation Easement Program + + ) : ( + ACEP: Agriculture Conservation Easement Program + )} + + + {location.pathname === "/rcpp" ? ( - + RCPP: Regional Conservation Partnership Program ) : ( RCPP: Regional Conservation Partnership Program )} - - {/* - - STATUTE - - - */} - + */} - - - - - Other Conservation diff --git a/src/components/rcpp/RCPPTotalMap.tsx b/src/components/rcpp/RCPPTotalMap.tsx index 6ee8ed0c..fdfc37e2 100644 --- a/src/components/rcpp/RCPPTotalMap.tsx +++ b/src/components/rcpp/RCPPTotalMap.tsx @@ -10,7 +10,7 @@ import PropTypes from "prop-types"; import "../../styles/map.css"; import legendConfig from "../../utils/legendConfig.json"; import DrawLegend from "../shared/DrawLegend"; -import { getValueFromAttr } from "../../utils/apiutil"; +import { getValueFromAttrDollar } from "../../utils/apiutil"; const geoUrl = "https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json"; @@ -185,7 +185,7 @@ const RCPPTotalMap = ({ statePerformance[year].forEach((value) => { const programRecord = value.programs; const ACur = programRecord.find((s) => s.programName === program); - let key = getValueFromAttr(ACur, attribute); + let key = getValueFromAttrDollar(ACur, attribute); key = key !== "" ? key : attribute; quantizeArray.push(ACur[key]); ACur[key] === 0 && zeroPoints.push(value.state); diff --git a/src/components/rcpp/RCPPTotalTable.tsx b/src/components/rcpp/RCPPTotalTable.tsx index cd7c8ca9..0fe5138b 100644 --- a/src/components/rcpp/RCPPTotalTable.tsx +++ b/src/components/rcpp/RCPPTotalTable.tsx @@ -3,10 +3,10 @@ import styled from "styled-components"; import { useTable, useSortBy } from "react-table"; import Box from "@mui/material/Box"; import "../../styles/table.css"; +import { compareWithDollarSign, compareWithNumber } from "../shared/TableCompareFunctions"; const Styles = styled.div` padding: 1rem; - margin-left: ${window.innerWidth <= 1440 ? "480px" : "auto"}; table { border-spacing: 0; @@ -121,30 +121,6 @@ function App({ 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(",", "")); - 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; - } - - 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 rcppTableData: any[] = []; // eslint-disable-next-line no-restricted-syntax @@ -154,28 +130,41 @@ function App({ const newRecord = () => { return { state: stateName, - rcppBenefit: `$${totalRcpp.paymentInDollars - .toLocaleString(undefined, { minimumFractionDigits: 2 }) - .toString()}`, + rcppBenefit: `$${ + totalRcpp.paymentInDollars + .toLocaleString(undefined, { minimumFractionDigits: 2 }) + .toString() + .split(".")[0] + }`, percentage: `${totalRcpp.totalPaymentInPercentageNationwide.toString()}%`, noContract: `${totalRcpp.totalContracts .toLocaleString(undefined, { minimumFractionDigits: 0 }) .toString()}`, totAcre: `${totalRcpp.totalAcres.toLocaleString(undefined, { minimumFractionDigits: 0 }).toString()}`, - finPayment: `$${totalRcpp.assistancePaymentInDollars - .toLocaleString(undefined, { minimumFractionDigits: 2 }) - .toString()}`, - reimbursePayment: `$${totalRcpp.reimbursePaymentInDollars - .toLocaleString(undefined, { minimumFractionDigits: 2 }) - .toString()}`, - techPayment: `$${totalRcpp.techPaymentInDollars - .toLocaleString(undefined, { minimumFractionDigits: 2 }) - .toString()}` + finPayment: `$${ + totalRcpp.assistancePaymentInDollars + .toLocaleString(undefined, { minimumFractionDigits: 2 }) + .toString() + .split(".")[0] + }`, + reimbursePayment: `$${ + totalRcpp.reimbursePaymentInDollars + .toLocaleString(undefined, { minimumFractionDigits: 2 }) + .toString() + .split(".")[0] + }`, + techPayment: `$${ + totalRcpp.techPaymentInDollars + .toLocaleString(undefined, { minimumFractionDigits: 2 }) + .toString() + .split(".")[0] + }` }; }; rcppTableData.push(newRecord()); }); + // PENDING: The 'pl' below are hard coded values that are inherited from old code design. Need to update this in the future const columns = React.useMemo( () => [ { @@ -205,11 +194,11 @@ function App({ className="tableHeader" sx={{ maxWidth: 240, pl: 2, display: "flex", justifyContent: "center" }} > - NO. OF CONTRACTS
+ NO. OF CONTRACTS ), accessor: "noContract", - sortType: compareNumber, + sortType: compareWithNumber, Cell: function styleCells(row) { return
{row.value}
; } @@ -218,13 +207,13 @@ function App({ Header: ( - ACRES
+
ACRES
), accessor: "totAcre", - sortType: compareNumber, + sortType: compareWithNumber, Cell: function styleCells(row) { return
{row.value}
; } @@ -235,7 +224,7 @@ function App({ className="tableHeader" sx={{ maxWidth: 240, pl: 2, display: "flex", justifyContent: "center" }} > - FINANCIAL ASSISTANT PAYMENTS
+ FINANCIAL ASSISTANT PAYMENTS ), accessor: "finPayment", diff --git a/src/main.tsx b/src/main.tsx index ac6525ec..6625aeb0 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -28,6 +28,7 @@ export default function Main(): JSX.Element { } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/pages/RCPPPage.tsx b/src/pages/RCPPPage.tsx index 535aec97..fa395771 100644 --- a/src/pages/RCPPPage.tsx +++ b/src/pages/RCPPPage.tsx @@ -82,22 +82,25 @@ export default function RCPPPage(): JSX.Element { { name: "Total Techinical Assistance Payments", value: techPayments, color: "#9CBAB4" } ]); - if (Number(totalRCPPPaymentInDollars.toString()) >= 1000000000) { - const totalBenefitTmp = `$${Number( - Number(totalRCPPPaymentInDollars.toString()) / 1000000000.0 - ).toLocaleString(undefined, { - maximumFractionDigits: 2 - })}B`; - setTotalBenefit(totalBenefitTmp); - } else { - const totalBenefitTmp = `$${Number(Number(totalRCPPPaymentInDollars.toString()) / 1000000.0).toLocaleString( - undefined, - { - maximumFractionDigits: 2 - } - )}M`; - setTotalBenefit(totalBenefitTmp); - } + setTotalBenefit( + totalRCPPPaymentInDollars.toLocaleString(undefined, { minimumFractionDigits: 2 }).toString().split(".")[0] + ); + // if (Number(totalRCPPPaymentInDollars.toString()) >= 1000000000) { + // const totalBenefitTmp = `$${Number( + // Number(totalRCPPPaymentInDollars.toString()) / 1000000000.0 + // ).toLocaleString(undefined, { + // maximumFractionDigits: 2 + // })}B`; + // setTotalBenefit(totalBenefitTmp); + // } else { + // const totalBenefitTmp = `$${Number(Number(totalRCPPPaymentInDollars.toString()) / 1000000.0).toLocaleString( + // undefined, + // { + // maximumFractionDigits: 2 + // } + // )}M`; + // setTotalBenefit(totalBenefitTmp); + // } // position="center" // dy={-75} @@ -125,10 +128,7 @@ export default function RCPPPage(): JSX.Element { zeroCategories={zeroCategories} /> - + - - - - - RCPP: Regional Conservation Partnership Program - - - @@ -166,20 +153,10 @@ export default function RCPPPage(): JSX.Element { RCPP: Regional Conservation Partnership Program - - 2 && checked < 6 ? "block" : "none" - }} - > - - CRP Total Continuous: Sub-Category of Practice Performance + + Total Benefits ({year}): + ${totalBenefit} @@ -192,7 +169,7 @@ export default function RCPPPage(): JSX.Element { multiple conservation practices to achieve greater conservation outcomes, encourage flexibility in program operation, and leverage private resources. Congress created the RCPP in the Agricultural Act of 2014 and revised the program in the Agricultural - Improvement Act of 2018 + Improvement Act of 2018. @@ -201,13 +178,6 @@ export default function RCPPPage(): JSX.Element { Overall Performance of States - - - - {totalBenefit} - - - Date: Mon, 16 Oct 2023 14:14:42 -0500 Subject: [PATCH 12/18] switched total benefit to use financial assistance --- src/components/rcpp/RCPPTotalMap.tsx | 6 ++++-- src/components/rcpp/RCPPTotalTable.tsx | 17 +---------------- src/pages/RCPPPage.tsx | 23 ++--------------------- 3 files changed, 7 insertions(+), 39 deletions(-) diff --git a/src/components/rcpp/RCPPTotalMap.tsx b/src/components/rcpp/RCPPTotalMap.tsx index fdfc37e2..f264d47e 100644 --- a/src/components/rcpp/RCPPTotalMap.tsx +++ b/src/components/rcpp/RCPPTotalMap.tsx @@ -42,9 +42,11 @@ const MapChart = (props) => { if (record === undefined || record.length === 0) { return null; } - const totalPaymentInDollars = record.programs[0].paymentInDollars; + // the totalPaymentInDolloars in RCPP is financial assistancePaymentInDollars + const totalPaymentInDollars = record.programs[0].assistancePaymentInDollars; + // since the total is financial assistance, the percentage should also be financial one const totalPaymentInPercentageNationwide = - record.programs[0].totalPaymentInPercentageNationwide; + record.programs[0].assistancePaymentInPercentageNationwide; const hoverContent = ( ), - accessor: "rcppBenefit", + accessor: "finPayment", sortType: compareWithDollarSign, Cell: function styleCells(row) { return
{row.value}
; @@ -217,21 +217,6 @@ function App({ Cell: function styleCells(row) { return
{row.value}
; } - }, - { - Header: ( - - FINANCIAL ASSISTANT PAYMENTS - - ), - accessor: "finPayment", - sortType: compareWithDollarSign, - Cell: function styleCells(row) { - return
{row.value}
; - } } ], [] diff --git a/src/pages/RCPPPage.tsx b/src/pages/RCPPPage.tsx index fa395771..0c1f4448 100644 --- a/src/pages/RCPPPage.tsx +++ b/src/pages/RCPPPage.tsx @@ -83,28 +83,9 @@ export default function RCPPPage(): JSX.Element { ]); setTotalBenefit( - totalRCPPPaymentInDollars.toLocaleString(undefined, { minimumFractionDigits: 2 }).toString().split(".")[0] + // total benefit should be financial assistance payment + assistancePayments.toLocaleString(undefined, { minimumFractionDigits: 2 }).toString().split(".")[0] ); - // if (Number(totalRCPPPaymentInDollars.toString()) >= 1000000000) { - // const totalBenefitTmp = `$${Number( - // Number(totalRCPPPaymentInDollars.toString()) / 1000000000.0 - // ).toLocaleString(undefined, { - // maximumFractionDigits: 2 - // })}B`; - // setTotalBenefit(totalBenefitTmp); - // } else { - // const totalBenefitTmp = `$${Number(Number(totalRCPPPaymentInDollars.toString()) / 1000000.0).toLocaleString( - // undefined, - // { - // maximumFractionDigits: 2 - // } - // )}M`; - // setTotalBenefit(totalBenefitTmp); - // } - - // position="center" - // dy={-75} - // style={{ textAnchor: "middle", fontSize: "200%", fill: "rgba(0, 0, 0, 0.87)" }} }; return ( From 2307d366c4a8ccfc0c4c330b19a1478eaaa7b322 Mon Sep 17 00:00:00 2001 From: YONG WOOK KIM Date: Mon, 16 Oct 2023 16:32:19 -0500 Subject: [PATCH 13/18] updated legend bar --- src/components/rcpp/RCPPTotalMap.tsx | 1 + src/pages/RCPPPage.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/rcpp/RCPPTotalMap.tsx b/src/components/rcpp/RCPPTotalMap.tsx index f264d47e..89164ce5 100644 --- a/src/components/rcpp/RCPPTotalMap.tsx +++ b/src/components/rcpp/RCPPTotalMap.tsx @@ -189,6 +189,7 @@ const RCPPTotalMap = ({ const ACur = programRecord.find((s) => s.programName === program); let key = getValueFromAttrDollar(ACur, attribute); key = key !== "" ? key : attribute; + console.log(key); quantizeArray.push(ACur[key]); ACur[key] === 0 && zeroPoints.push(value.state); return null; diff --git a/src/pages/RCPPPage.tsx b/src/pages/RCPPPage.tsx index 0c1f4448..4c694352 100644 --- a/src/pages/RCPPPage.tsx +++ b/src/pages/RCPPPage.tsx @@ -15,7 +15,7 @@ import NavSearchBar from "../components/shared/NavSearchBar"; export default function RCPPPage(): JSX.Element { const year = "2018-2022"; - const attribute = "paymentInDollars"; + const attribute = "assistancePaymentInDollars"; const [checked, setChecked] = React.useState(0); const [stateDistributionData, setStateDistributionData] = React.useState({}); From 08549a4ed27d5af2240d9542613e285715852635 Mon Sep 17 00:00:00 2001 From: YONG WOOK KIM Date: Mon, 16 Oct 2023 16:52:24 -0500 Subject: [PATCH 14/18] removed console command --- src/components/rcpp/RCPPTotalMap.tsx | 1 - src/pages/RCPPPage.tsx | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/components/rcpp/RCPPTotalMap.tsx b/src/components/rcpp/RCPPTotalMap.tsx index 89164ce5..f264d47e 100644 --- a/src/components/rcpp/RCPPTotalMap.tsx +++ b/src/components/rcpp/RCPPTotalMap.tsx @@ -189,7 +189,6 @@ const RCPPTotalMap = ({ const ACur = programRecord.find((s) => s.programName === program); let key = getValueFromAttrDollar(ACur, attribute); key = key !== "" ? key : attribute; - console.log(key); quantizeArray.push(ACur[key]); ACur[key] === 0 && zeroPoints.push(value.state); return null; diff --git a/src/pages/RCPPPage.tsx b/src/pages/RCPPPage.tsx index 4c694352..1180536a 100644 --- a/src/pages/RCPPPage.tsx +++ b/src/pages/RCPPPage.tsx @@ -1,10 +1,8 @@ import Box from "@mui/material/Box"; import * as React from "react"; import { createTheme, ThemeProvider, Typography } from "@mui/material"; -import { Label } from "recharts"; import NavBar from "../components/NavBar"; import Drawer from "../components/ProgramDrawer"; -import SemiDonutChart from "../components/SemiDonutChart"; import DataTable from "../components/rcpp/RCPPTotalTable"; import RCPPTotalMap from "../components/rcpp/RCPPTotalMap"; // import CategoryTable from "../components/rcpp/CategoryTable"; From 587dca46d0ec3ded1b91857b0f3ec1762bb9e388 Mon Sep 17 00:00:00 2001 From: YONG WOOK KIM Date: Tue, 17 Oct 2023 08:31:49 -0500 Subject: [PATCH 15/18] added percentage nationwide in rcpp table --- src/components/rcpp/RCPPTotalTable.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/components/rcpp/RCPPTotalTable.tsx b/src/components/rcpp/RCPPTotalTable.tsx index e411809d..845589d0 100644 --- a/src/components/rcpp/RCPPTotalTable.tsx +++ b/src/components/rcpp/RCPPTotalTable.tsx @@ -136,7 +136,7 @@ function App({ .toString() .split(".")[0] }`, - percentage: `${totalRcpp.totalPaymentInPercentageNationwide.toString()}%`, + percentage: `${totalRcpp.assistancePaymentInPercentageNationwide.toString()}%`, noContract: `${totalRcpp.totalContracts .toLocaleString(undefined, { minimumFractionDigits: 0 }) .toString()}`, @@ -217,6 +217,21 @@ function App({ Cell: function styleCells(row) { return
{row.value}
; } + }, + { + Header: ( + +
PCT. NATIONWIDE
+
+ ), + accessor: "percentage", + sortType: compareWithNumber, + Cell: function styleCells(row) { + return
{row.value}
; + } } ], [] From f18b0247b1fc1c8a1b4c4e0bb07cb798144f0080 Mon Sep 17 00:00:00 2001 From: "Wendy(Pengyin) Shan" Date: Tue, 17 Oct 2023 09:51:53 -0500 Subject: [PATCH 16/18] update acep based on client"s comments --- src/components/acep/ACEPTable.tsx | 24 +++++++++++++----------- src/components/acep/ACEPTotalMap.tsx | 12 ++++++------ src/pages/ACEPPage.tsx | 28 ++++++++++++++-------------- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/src/components/acep/ACEPTable.tsx b/src/components/acep/ACEPTable.tsx index 2136ba21..45d87769 100644 --- a/src/components/acep/ACEPTable.tsx +++ b/src/components/acep/ACEPTable.tsx @@ -6,8 +6,7 @@ import { compareWithNumber, compareWithAlphabetic, compareWithDollarSign, - compareWithPercentSign, - sortByDollars + compareWithPercentSign } from "../shared/TableCompareFunctions"; import "../../styles/table.css"; @@ -64,23 +63,26 @@ function AcepProgramTable({ if (attribute.includes("Percentage")) sortMethod = compareWithPercentSign; if (attribute.includes("totalContracts") || attribute.includes("totalAcres")) sortMethod = compareWithNumber; const json = { - Header: attribute - .replace(/([A-Z])/g, " $1") - .trim() - .split(" ") - .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) - .join(" ") - .toUpperCase(), + Header: + attribute === "assistancePaymentInDollars" + ? "Total Payment in Dollars".toUpperCase() + : attribute + .replace(/([A-Z])/g, " $1") + .trim() + .split(" ") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" ") + .toUpperCase(), accessor: attribute, sortType: sortMethod }; columnPrep.push(json); }); const columns = React.useMemo(() => columnPrep, []); - const paymentsIndex = columns.findIndex((c) => c.accessor === "paymentInDollars"); + const paymentsIndex = columns.findIndex((c) => c.accessor === "assistancePaymentInDollars"); const acresIndex = columns.findIndex((c) => c.accessor === "totalAcres"); const contractsIndex = columns.findIndex((c) => c.accessor === "totalContracts"); - const paymentsPercentageIndex = columns.findIndex((c) => c.accessor === "totalPaymentInPercentageNationwide"); + const paymentsPercentageIndex = columns.findIndex((c) => c.accessor === "assistancePaymentInPercentageNationwide"); const contractsPercentageIndex = columns.findIndex((c) => c.accessor === "contractsInPercentageNationwide"); const acresPercentageIndex = columns.findIndex((c) => c.accessor === "acresInPercentageNationwide"); const Styles = styled.div` diff --git a/src/components/acep/ACEPTotalMap.tsx b/src/components/acep/ACEPTotalMap.tsx index 3a3a56b0..1cc1c1ae 100644 --- a/src/components/acep/ACEPTotalMap.tsx +++ b/src/components/acep/ACEPTotalMap.tsx @@ -42,9 +42,9 @@ const MapChart = (props) => { if (record === undefined || record.length === 0) { return null; } - const totalPaymentInDollars = record.programs[0].paymentInDollars; - const totalPaymentInPercentageNationwide = - record.programs[0].totalPaymentInPercentageNationwide; + const totalPaymentInDollars = record.programs[0].assistancePaymentInDollars; + const assistancePaymentInPercentageNationwide = + record.programs[0].assistancePaymentInPercentageNationwide; const hoverContent = ( { - {totalPaymentInPercentageNationwide - ? `${totalPaymentInPercentageNationwide} %` + {assistancePaymentInPercentageNationwide + ? `${assistancePaymentInPercentageNationwide} %` : "0%"} @@ -185,7 +185,7 @@ const ACEPTotalMap = ({ statePerformance[year].forEach((value) => { const programRecord = value.programs; const ACur = programRecord.find((s) => s.programName === program); - const key = "paymentInDollars"; + const key = "assistancePaymentInDollars"; quantizeArray.push(ACur[key]); ACur[key] === 0 && zeroPoints.push(value.state); return null; diff --git a/src/pages/ACEPPage.tsx b/src/pages/ACEPPage.tsx index f60df32c..739b0a25 100644 --- a/src/pages/ACEPPage.tsx +++ b/src/pages/ACEPPage.tsx @@ -30,9 +30,9 @@ export default function ACEPPage(): JSX.Element { let totalACEPPaymentInDollars = 0; let totalContracts = 0; let totalAcres = 0; - let assistancePaymentInDollars = 0; - let reimbursePaymentInDollars = 0; - let techPaymentInDollars = 0; + const assistancePaymentInDollars = 0; + const reimbursePaymentInDollars = 0; + const techPaymentInDollars = 0; React.useEffect(() => { const allstates_url = `${config.apiUrl}/states`; @@ -67,19 +67,19 @@ export default function ACEPPage(): JSX.Element { if (chartData.programs === undefined) return; const cur1 = chartData.programs.find((s) => s.programName === "ACEP"); - totalACEPPaymentInDollars = cur1.paymentInDollars; + totalACEPPaymentInDollars = cur1.assistancePaymentInDollars; setTotalAcep(totalACEPPaymentInDollars); if (totalACEPPaymentInDollars === 0) zeroCategory.push("ACEP"); totalContracts = cur1.totalContracts; if (totalContracts === 0) zeroCategory.push("Total Contracts"); totalAcres = cur1.totalAcre; if (totalAcres === 0) zeroCategory.push("Total Acres"); - assistancePaymentInDollars = cur1.assistancePaymentInDollars; - if (assistancePaymentInDollars === 0) zeroCategory.push("Assistance Payment"); - reimbursePaymentInDollars = cur1.reimbursePaymentInDollars; - if (reimbursePaymentInDollars === 0) zeroCategory.push("Reimburse Payment"); - techPaymentInDollars = cur1.techPaymentInDollars; - if (techPaymentInDollars === 0) zeroCategory.push("Tech Payment"); + // assistancePaymentInDollars = cur1.assistancePaymentInDollars; + // if (assistancePaymentInDollars === 0) zeroCategory.push("Assistance Payment"); + // reimbursePaymentInDollars = cur1.reimbursePaymentInDollars; + // if (reimbursePaymentInDollars === 0) zeroCategory.push("Reimburse Payment"); + // techPaymentInDollars = cur1.techPaymentInDollars; + // if (techPaymentInDollars === 0) zeroCategory.push("Tech Payment"); setZeroCategories(zeroCategory); }; function prepData(program, subprogram, data, dataYear) { @@ -93,13 +93,13 @@ export default function ACEPPage(): JSX.Element { organizedData.push({ state, acres: programData[0].totalAcres, - payments: programData[0].paymentInDollars, + payments: programData[0].assistancePaymentInDollars, contracts: programData[0].totalContracts }); originalData.push({ state, acres: programData[0].totalAcres, - payments: programData[0].paymentInDollars, + payments: programData[0].assistancePaymentInDollars, contracts: programData[0].totalContracts }); }); @@ -226,8 +226,8 @@ export default function ACEPPage(): JSX.Element { tableTitle="Total ACEP Benefits, Acres and No. of Contracts" program="ACEP" attributes={[ - "paymentInDollars", - "totalPaymentInPercentageNationwide", + "assistancePaymentInDollars", + "assistancePaymentInPercentageNationwide", "totalAcres", "acresInPercentageNationwide", "totalContracts", From 9a1dd867c49a165e23a11c776acdc670cba06997 Mon Sep 17 00:00:00 2001 From: "Wendy(Pengyin) Shan" Date: Tue, 17 Oct 2023 10:18:41 -0500 Subject: [PATCH 17/18] change column header --- src/components/acep/ACEPTable.tsx | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/components/acep/ACEPTable.tsx b/src/components/acep/ACEPTable.tsx index 45d87769..d54b53c8 100644 --- a/src/components/acep/ACEPTable.tsx +++ b/src/components/acep/ACEPTable.tsx @@ -62,17 +62,18 @@ function AcepProgramTable({ let sortMethod = compareWithDollarSign; if (attribute.includes("Percentage")) sortMethod = compareWithPercentSign; if (attribute.includes("totalContracts") || attribute.includes("totalAcres")) sortMethod = compareWithNumber; + let attrName = attribute + .replace(/([A-Z])/g, " $1") + .trim() + .split(" ") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" ") + .toUpperCase(); + if (attribute === "assistancePaymentInDollars") attrName = "Total Payment in Dollars".toUpperCase(); + if (attribute === "assistancePaymentInPercentageNationwide") + attrName = "Total Payment In Percentage Nationwide".toUpperCase(); const json = { - Header: - attribute === "assistancePaymentInDollars" - ? "Total Payment in Dollars".toUpperCase() - : attribute - .replace(/([A-Z])/g, " $1") - .trim() - .split(" ") - .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) - .join(" ") - .toUpperCase(), + Header: attrName, accessor: attribute, sortType: sortMethod }; From 8973b430156e444bf7b632d28842036fe09a502b Mon Sep 17 00:00:00 2001 From: "Wendy(Pengyin) Shan" Date: Tue, 17 Oct 2023 12:13:09 -0500 Subject: [PATCH 18/18] prepare for 0.10.0 release --- CHANGELOG.md | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ebb56da..ea2e914a 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.10.0] - 2023-10-17 ### Added diff --git a/package-lock.json b/package-lock.json index 37a3fa67..0477b923 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "policy-design-lab", - "version": "0.9.0", + "version": "0.10.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 86774664..44c9dc71 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "policy-design-lab", - "version": "0.9.0", + "version": "0.10.0", "description": "the front end of policy design lab", "repository": "https://github.com/policy-design-lab/pdl-frontend", "main": "src/app.tsx",