From 61419055598f5b8a9e7d64590c6370aa12e3b5ff Mon Sep 17 00:00:00 2001 From: "Wendy(Pengyin) Shan" Date: Fri, 26 May 2023 19:13:35 -0500 Subject: [PATCH 1/3] feature/0.5.1 --- .github/ISSUE_TEMPLATE.md | 32 -- .github/ISSUE_TEMPLATE/bug_report.md | 31 ++ .github/ISSUE_TEMPLATE/epic.md | 18 + .github/ISSUE_TEMPLATE/feature_request.md | 20 + .github/ISSUE_TEMPLATE/task.md | 16 + CHANGELOG.md | 29 +- package-lock.json | 2 +- package.json | 2 +- src/components/AllProgramMap.tsx | 273 ------------ src/components/LandingPageMap.tsx | 439 ++++++++++++++------ src/components/LandingPageMapTab.tsx | 63 +-- src/components/shared/ConvertionFormats.tsx | 32 +- src/components/shared/DrawLegend.tsx | 168 ++++++++ src/styles/drawLegend.css | 19 + src/utils/legendConfig.json | 7 + 15 files changed, 686 insertions(+), 465 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/epic.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/task.md delete mode 100644 src/components/AllProgramMap.tsx create mode 100644 src/components/shared/DrawLegend.tsx create mode 100644 src/styles/drawLegend.css create mode 100644 src/utils/legendConfig.json diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index be4f8168..00000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,32 +0,0 @@ - - -## Expected Behavior - - - -## Current Behavior - - - -## Possible Solution - - - -## Steps to Reproduce (for bugs) - - -1. -2. -3. -4. - -## Context - - - -## Your Environment - -* Version used: -* Browser Name and version: -* Operating System and version (desktop or mobile): -* Link to your project: diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..8b4f5067 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,31 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG] " +labels: bug, epic +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Version: [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/epic.md b/.github/ISSUE_TEMPLATE/epic.md new file mode 100644 index 00000000..3be00e80 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/epic.md @@ -0,0 +1,18 @@ +--- +name: Epic +about: Create an Epic to capture a large volume of work. +title: "[EPIC] " +labels: epic +assignees: '' + +--- + +## Description +_Please provide a detailed description of the Epic._ +_Example: As a (user or developer), I want to be able to (feature or function) so that (reason to implement)._ + +## Linked Issues +- [ ] + +## Acceptance Criteria +_Please describe what it means for this Epic to be considered complete._ diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..8403b86c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "[FEATURE] " +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/task.md b/.github/ISSUE_TEMPLATE/task.md new file mode 100644 index 00000000..5221dca4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/task.md @@ -0,0 +1,16 @@ +--- +name: Task +about: Capture a general work task (something that's not a feature, bug report, or an epic. E.g., perform security updates on a server). +title: "[TASK] " +labels: task +assignees: '' + +--- + +## Description +_Please provide a detailed description of the task._ +_Example: As a (user or developer), I want to be able to (task) so that (reason to perform the task)._ + + +## Acceptance Criteria +_Please describe what it means for this task to be considered complete._ diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d64829a..fb2a8719 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,28 @@ 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.5.1] - 2023-05-30 + +### Changed + +- Change the format of negative values in landing page maps' tips and legend bar to fit client's request +- Modify the title of each map's tip to match each program + +## [0.5.0] - 2023-05-26 + +### Added + +- Add dynamic color bar for landing page and SNAP page [#121](https://github.com/policy-design-lab/pdl-frontend/issues/121) +- New GitHub issue templates. [#127](https://github.com/policy-design-lab/pdl-frontend/issues/127) + ### Changed -- Landing page maps and tabs uses api enpoint instead of local json file [#110](https://github.com/policy-design-lab/pdl-frontend/issues/110) + +- Landing page maps & tabs use api endpoints instead of local json files [#110](https://github.com/policy-design-lab/pdl-frontend/issues/110) +- Add crop insurance tab back to landing page [#130](https://github.com/policy-design-lab/pdl-frontend/issues/130) +- Removed the duplicate 'AllProgramMap' component on the landing page. Instead, modified the 'LandingPageMap' component to include the all-program map + +### Removed +- Previously used default GitHub issue template. [#127](https://github.com/policy-design-lab/pdl-frontend/issues/127) ## [0.4.0] - 2023-05-10 @@ -76,6 +95,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Map data json [#12](https://github.com/policy-design-lab/pdl-frontend/issues/12) - Final landing page changes for initial milestone [#15](https://github.com/policy-design-lab/pdl-frontend/issues/15) -[unreleased]: https://github.com/policy-design-lab/pdl-frontend/compare/0.4.0...HEAD -[0.4.0]: https://github.com/policy-design-lab/pdl-frontend/compare/0.3.0...0.4.0 -[0.3.0]: https://github.com/policy-design-lab/pdl-frontend/tag/0.3.0 +[unreleased]: https://github.com/policy-design-lab/pdl-frontend/compare/0.5.1...HEAD +[0.5.1]: https://github.com/policy-design-lab/pdl-frontend/compare/0.5.0...0.5.1 +[0.5.0]: https://github.com/policy-design-lab/pdl-frontend/tag/0.5.0 diff --git a/package-lock.json b/package-lock.json index a6c87b2a..99ebd067 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "policy-design-lab", - "version": "0.4.0", + "version": "0.5.1", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index c0e73fc9..3cd05038 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "policy-design-lab", - "version": "0.4.0", + "version": "0.5.1", "description": "the front end of policy design lab", "repository": "https://github.com/policy-design-lab/pdl-frontend", "main": "src/app.tsx", diff --git a/src/components/AllProgramMap.tsx b/src/components/AllProgramMap.tsx deleted file mode 100644 index 5ec06315..00000000 --- a/src/components/AllProgramMap.tsx +++ /dev/null @@ -1,273 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { geoCentroid } from "d3-geo"; -import { ComposableMap, Geographies, Geography, Marker, Annotation } from "react-simple-maps"; -import ReactTooltip from "react-tooltip"; -import { scaleQuantile, scaleQuantize } from "d3-scale"; -import Divider from "@mui/material/Divider"; -import Box from "@mui/material/Box"; -import Typography from "@mui/material/Typography"; -import PropTypes from "prop-types"; -import { config } from "../app.config"; - -import "../styles/map.css"; -import { getJsonDataFromUrl, convertAllState } from "../utils/apiutil"; - -import HorizontalStackedBar from "./HorizontalStackedBar"; - -const geoUrl = "https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json"; - -const offsets = { - VT: [50, -8], - NH: [34, 2], - MA: [30, -1], - RI: [28, 2], - CT: [35, 10], - NJ: [34, 1], - DE: [33, 0], - MD: [47, 10], - DC: [49, 21] -}; - -const MapChart = (props) => { - const { setTooltipContent, stateCodes, allPrograms, allStates, summary } = props; - - const minValue = 0; - let maxValue = 0; - - const hashmap = new Map([]); - if (summary !== undefined) { - summary.forEach((item) => { - const state = item.State; - if (!hashmap.has(state)) { - hashmap.set(state, 0); - } - hashmap.set(state, hashmap.get(state) + item.Amount); - }); - maxValue = Math.max(...hashmap.values()); - } - - const colorScale = scaleQuantile() - .domain(allPrograms.map((d) => d["18-22 All Programs Total"])) - .range(["#FFF9D8", "#E1F2C4", "#9FD9BA", "#1B9577", "#005A45"]); - - const label1 = ((maxValue - minValue) / 5) * 0 + minValue; - const label2 = ((maxValue - minValue) / 5) * 1 + minValue; - const label3 = ((maxValue - minValue) / 5) * 2 + minValue; - const label4 = ((maxValue - minValue) / 5) * 3 + minValue; - const label5 = ((maxValue - minValue) / 5) * 4 + minValue; - - return ( -
- - - Total Farm Bill Benefits from 2018 - 2022 - - } - color1="#FFF9D8" - color2="#E1F2C4" - color3="#9FD9BA" - color4="#1B9577" - color5="#005A45" - label1="0" - label2="20%" - label3="40%" - label4="60%" - label5="80%" - label6="100%" - /> - - {allPrograms.length === 0 ? null : ( - - - {({ geographies }) => ( - <> - {geographies.map((geo) => { - let records = []; - let total = 0; - const cur = allStates.find((s) => s.val === geo.id); - if (cur !== undefined) { - records = allPrograms.filter((s) => s.State === cur.id); - records.forEach((record) => { - total += record["18-22 All Programs Total"]; - }); - } - const hoverContent = ( - - - - {cur ? stateCodes[cur.id] : ""} - - Total Benefit - - $ - {Number(total / 1000000.0).toLocaleString(undefined, { - maximumFractionDigits: 2 - })} - M - - - - - - Payments: -
- {records.map((record) => ( -
- 2018: $ - {Number( - record["2018 All Programs Total"] / 1000000.0 - ).toLocaleString(undefined, { maximumFractionDigits: 2 })} - M -
- 2019: $ - {Number( - record["2019 All Programs Total"] / 1000000.0 - ).toLocaleString(undefined, { maximumFractionDigits: 2 })} - M -
- 2020: $ - {Number( - record["2020 All Programs Total"] / 1000000.0 - ).toLocaleString(undefined, { maximumFractionDigits: 2 })} - M -
- 2021: $ - {Number( - record["2021 All Programs Total"] / 1000000.0 - ).toLocaleString(undefined, { maximumFractionDigits: 2 })} - M -
- 2022: $ - {Number( - record["2022 All Programs Total"] / 1000000.0 - ).toLocaleString(undefined, { maximumFractionDigits: 2 })} - M -
-
- ))} -
-
-
- ); - return ( - { - setTooltipContent(hoverContent); - }} - onMouseLeave={() => { - setTooltipContent(""); - }} - fill={colorScale(total)} - 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} - - - ))} - - ); - })} - - )} -
-
- )} -
- ); -}; - -MapChart.propTypes = { - setTooltipContent: PropTypes.func -}; - -const AllProgramMap = (): JSX.Element => { - const [content, setContent] = useState(""); - const [stateCodesData, setStateCodesData] = useState([]); - const [allProgramsData, setAllProgramsData] = useState([]); - const [allStatesData, setAllStatesData] = useState([]); - const [summaryData, setSummaryData] = useState([]); - useEffect(() => { - const statecode_url = `${config.apiUrl}/statecodes`; - getJsonDataFromUrl(statecode_url).then((response) => { - const converted_json = convertAllState(response); - setStateCodesData(converted_json); - }); - const allprograms_url = `${config.apiUrl}/allprograms`; - getJsonDataFromUrl(allprograms_url).then((response) => { - setAllProgramsData(response); - }); - const allstates_url = `${config.apiUrl}/states`; - getJsonDataFromUrl(allstates_url).then((response) => { - setAllStatesData(response); - }); - const summary_url = `${config.apiUrl}/summary`; - getJsonDataFromUrl(summary_url).then((response) => { - setSummaryData(response); - }); - }, []); - - return ( -
- -
- - {content} - -
-
- ); -}; - -export default AllProgramMap; diff --git a/src/components/LandingPageMap.tsx b/src/components/LandingPageMap.tsx index f61b6c9a..64e74db0 100644 --- a/src/components/LandingPageMap.tsx +++ b/src/components/LandingPageMap.tsx @@ -1,16 +1,17 @@ -import React, { useEffect, useState } from "react"; +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 { scaleQuantile, scaleQuantize, scaleThreshold } from "d3-scale"; +import * as d3 from "d3"; import Divider from "@mui/material/Divider"; import PropTypes from "prop-types"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import "../styles/map.css"; -import HorizontalStackedBar from "./HorizontalStackedBar"; import { config } from "../app.config"; import { getJsonDataFromUrl, convertAllState } from "../utils/apiutil"; +import DrawLegend from "./shared/DrawLegend"; +import legendConfig from "../utils/legendConfig.json"; const geoUrl = "https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json"; @@ -27,7 +28,7 @@ const offsets = { }; const MapChart = (props) => { - const { setTooltipContent, title, stateCodes, allPrograms, allStates, summary } = props; + const { setTooltipContent, title, stateCodes, allPrograms, allStates, summary, screenWidth } = props; let searchKey = ""; let color1 = ""; let color2 = ""; @@ -37,22 +38,33 @@ const MapChart = (props) => { let minValue = 0; let maxValue = 0; let legendTitle =
; - + let customScale: number[] = []; const hashmap = new Map([]); - if (summary !== undefined) { - summary.forEach((item) => { - if (item.Title === title) { - const state = item.State; - if (!hashmap.has(state)) { - hashmap.set(state, 0); - } - hashmap.set(state, hashmap.get(state) + item.Amount); + summary.forEach((item) => { + if (item.Title === title) { + const state = item.State; + if (!hashmap.has(state)) { + hashmap.set(state, 0); } - }); - maxValue = Math.max(...hashmap.values()); - } - + hashmap.set(state, hashmap.get(state) + item.Amount); + } + }); + maxValue = Math.max(...hashmap.values()); switch (title) { + case "All Programs": + searchKey = "18-22 All Programs Total"; + color1 = "#FFF9D8"; + color2 = "#E1F2C4"; + color3 = "#9FD9BA"; + color4 = "#1B9577"; + color5 = "#005A45"; + legendTitle = ( + + Total Farm Bill Benefits from 2018 - 2022 + + ); + minValue = Math.min(...hashmap.values()); + break; case "Title I: Commodities": searchKey = "Title I Total"; color1 = "#F9F9D3"; @@ -65,6 +77,7 @@ const MapChart = (props) => { Total Commodities Programs (Title I) Benefits from 2018 - 2022 ); + minValue = Math.min(...hashmap.values()); // Self defined by observation break; case "Title II: Conservation": searchKey = "Title II Total"; @@ -86,7 +99,6 @@ const MapChart = (props) => { color3 = "#E3E3E3"; color4 = "#89CBC1"; color5 = "#2C8472"; - minValue = -1000000000; legendTitle = (
@@ -110,25 +122,23 @@ const MapChart = (props) => { color4 = "#2B8CBE"; color5 = "#045A8D"; legendTitle = ( - + Total Supplemental Nutrition Assistance Program (SNAP) Costs from 2018 - 2022 ); break; } + customScale = legendConfig[searchKey]; + const colorScale = d3.scaleThreshold(customScale, [color1, color2, color3, color4, color5]); + const yearList = summary + .map((item) => item["Fiscal Year"]) + .filter((value, index, self) => self.indexOf(value) === index); - const colorScale = scaleQuantile() - .domain(allPrograms.map((d) => d[searchKey])) - .range([color1, color2, color3, color4, color5]); - - // Get list of unique years - let yearList = []; - if (summary !== undefined) { - yearList = summary - .map((item) => item["Fiscal Year"]) - .filter((value, index, self) => self.indexOf(value) === index); - } + const zeroPoints = []; + allPrograms.forEach((d) => { + if (d[searchKey] === 0) zeroPoints.push(d.State); + }); const label1 = ((maxValue - minValue) / 5) * 0 + minValue; const label2 = ((maxValue - minValue) / 5) * 1 + minValue; const label3 = ((maxValue - minValue) / 5) * 2 + minValue; @@ -137,20 +147,14 @@ const MapChart = (props) => { return (
- - + d.State !== "Total").map((d) => d[searchKey])} + prepColor={[color1, color2, color3, color4, color5]} + emptyState={zeroPoints} + initWidth={window.innerWidth > 1679 ? window.innerWidth * 0.75 : window.innerWidth * 0.8} /> {summary.length === 0 ? null : ( @@ -164,11 +168,17 @@ const MapChart = (props) => { let totalAverageMonthlyParticipation = 0; const cur = allStates.find((s) => s.val === geo.id); if (cur !== undefined) { - records = summary.filter((s) => s.State === cur.id && s.Title === title); - records.forEach((record) => { - total += record.Amount; - }); - + if (title === "All Programs") { + records = allPrograms.filter((s) => s.State === cur.id); + records.forEach((record) => { + total += record["18-22 All Programs Total"]; + }); + } else { + records = summary.filter((s) => s.State === cur.id && s.Title === title); + records.forEach((record) => { + total += record.Amount; + }); + } if (title === "Supplemental Nutrition Assistance Program (SNAP)") { records.forEach((record) => { totalAverageMonthlyParticipation += @@ -176,72 +186,249 @@ const MapChart = (props) => { }); } - const hoverContent = ( - - - - {stateCodes[cur.id]} - - {title === "Supplemental Nutrition Assistance Program (SNAP)" ? ( - Total Cost - ) : ( - Total Benefit - )} - - $ - {Number(total / 1000000.0).toLocaleString(undefined, { - maximumFractionDigits: 2 - })} - M - -
- {/* Show additional data on hover for SNAP */} - {title === "Supplemental Nutrition Assistance Program (SNAP)" && ( + const hoverContent = + title !== "All Programs" ? ( + + - Avg. Monthly Participation + {stateCodes[cur.id]} - )} - {/* Average SNAP monthly participation for the current years */} - {title === "Supplemental Nutrition Assistance Program (SNAP)" && ( + {title === + "Supplemental Nutrition Assistance Program (SNAP)" ? ( + + Total Cost + + ) : ( + + Total Benefit + + )} - {Number( - totalAverageMonthlyParticipation / yearList.length - ).toLocaleString(undefined, { - maximumFractionDigits: 0 - })} + {Math.round(Number(total / 1000000.0)) >= 0 + ? `$${Number( + Math.abs(total) / 1000000.0 + ).toLocaleString(undefined, { + maximumFractionDigits: 2 + })}M` + : `-$${Number( + Math.abs(total) / 1000000.0 + ).toLocaleString(undefined, { + maximumFractionDigits: 2 + })}M`} - )} - - - - - Payments:
- {records.map((record) => ( -
- {record["Fiscal Year"]}: $ - {Number(record.Amount / 1000000.0).toLocaleString( - undefined, - { maximumFractionDigits: 2 } - )} - M -
- ))} -
+ {/* Show additional data on hover for SNAP */} + {title === + "Supplemental Nutrition Assistance Program (SNAP)" && ( + + Avg. Monthly Participation + + )} + {/* Average SNAP monthly participation for the current years */} + {title === + "Supplemental Nutrition Assistance Program (SNAP)" && ( + + {Number( + totalAverageMonthlyParticipation / yearList.length + ).toLocaleString(undefined, { + maximumFractionDigits: 0 + })} + + )} +
+ + + {title === + "Supplemental Nutrition Assistance Program (SNAP)" ? ( + + Costs: +
+ {records.map((record) => ( +
+ {record["Fiscal Year"]}:{" "} + {Number(record.Amount / 1000000.0) >= 0 + ? `$${Number( + Math.abs(record.Amount) / 1000000.0 + ).toLocaleString(undefined, { + maximumFractionDigits: 2 + })}M` + : `-$${Number( + Math.abs(record.Amount) / 1000000.0 + ).toLocaleString(undefined, { + maximumFractionDigits: 2 + })}M`} +
+ ))} +
+ ) : ( + + {title === "Crop Insurance" ? "Benefits:" : "Payments:"} +
+ {records.map((record) => ( +
+ {record["Fiscal Year"]}:{" "} + {Number(record.Amount / 1000000.0) >= 0 + ? `$${Number( + Math.abs(record.Amount) / 1000000.0 + ).toLocaleString(undefined, { + maximumFractionDigits: 2 + })}M` + : `-$${Number( + Math.abs(record.Amount) / 1000000.0 + ).toLocaleString(undefined, { + maximumFractionDigits: 2 + })}M`} +
+ ))} +
+ )} +
-
- ); + ) : ( + + + + {cur ? stateCodes[cur.id] : ""} + + Total Benefit + + {Math.round(Number(total / 1000000.0)) >= 0 + ? `$${Number( + Math.abs(total) / 1000000.0 + ).toLocaleString(undefined, { + maximumFractionDigits: 2 + })}M` + : `-$${Number( + Math.abs(total) / 1000000.0 + ).toLocaleString(undefined, { + maximumFractionDigits: 2 + })}M`} + + + + + + Payments: +
+ {records.map((record) => ( +
+ 2018:{" "} + {record["2018 All Programs Total"] / 1000000.0 >= 0 + ? `$${Number( + Math.abs( + record["2018 All Programs Total"] + ) / 1000000.0 + ).toLocaleString(undefined, { + maximumFractionDigits: 2 + })}M` + : `-$${Number( + Math.abs( + record["2018 All Programs Total"] + ) / 1000000.0 + ).toLocaleString(undefined, { + maximumFractionDigits: 2 + })}M`} +
+ 2019:{" "} + {record["2019 All Programs Total"] / 1000000.0 >= 0 + ? `$${Number( + Math.abs( + record["2019 All Programs Total"] + ) / 1000000.0 + ).toLocaleString(undefined, { + maximumFractionDigits: 2 + })}M` + : `-$${Number( + Math.abs( + record["2019 All Programs Total"] + ) / 1000000.0 + ).toLocaleString(undefined, { + maximumFractionDigits: 2 + })}M`} +
+ 2020:{" "} + {record["2020 All Programs Total"] / 1000000.0 >= 0 + ? `$${Number( + Math.abs( + record["2020 All Programs Total"] + ) / 1000000.0 + ).toLocaleString(undefined, { + maximumFractionDigits: 2 + })}M` + : `-$${Number( + Math.abs( + record["2020 All Programs Total"] + ) / 1000000.0 + ).toLocaleString(undefined, { + maximumFractionDigits: 2 + })}M`} +
+ 2021:{" "} + {record["2021 All Programs Total"] / 1000000.0 >= 0 + ? `$${Number( + Math.abs( + record["2021 All Programs Total"] + ) / 1000000.0 + ).toLocaleString(undefined, { + maximumFractionDigits: 2 + })}M` + : `-$${Number( + Math.abs( + record["2021 All Programs Total"] + ) / 1000000.0 + ).toLocaleString(undefined, { + maximumFractionDigits: 2 + })}M`} +
+ 2022:{" "} + {record["2022 All Programs Total"] / 1000000.0 >= 0 + ? `$${Number( + Math.abs( + record["2022 All Programs Total"] + ) / 1000000.0 + ).toLocaleString(undefined, { + maximumFractionDigits: 2 + })}M` + : `-$${Number( + Math.abs( + record["2022 All Programs Total"] + ) / 1000000.0 + ).toLocaleString(undefined, { + maximumFractionDigits: 2 + })}M`} +
+
+ ))} +
+
+
+ ); const fillColour = () => { if (total) { if (total !== 0) return colorScale(total); @@ -326,7 +513,8 @@ const LandingPageMap = ({ programTitle }: { programTitle: string }): JSX.Element const [allProgramsData, setAllProgramsData] = useState([]); const [allStatesData, setAllStatesData] = useState([]); const [summaryData, setSummaryData] = useState([]); - useEffect(() => { + const [screenWidth, setScreenWidth] = useState(window.innerWidth); + React.useEffect(() => { const statecode_url = `${config.apiUrl}/statecodes`; getJsonDataFromUrl(statecode_url).then((response) => { const converted_json = convertAllState(response); @@ -347,19 +535,26 @@ const LandingPageMap = ({ programTitle }: { programTitle: string }): JSX.Element }, []); return (
- -
- - {content} - -
+ {allProgramsData.length > 0 ? ( +
+ +
+ + {content} + +
+
+ ) : ( +
Loading data...
+ )}
); }; diff --git a/src/components/LandingPageMapTab.tsx b/src/components/LandingPageMapTab.tsx index c22788d5..2cbec526 100644 --- a/src/components/LandingPageMapTab.tsx +++ b/src/components/LandingPageMapTab.tsx @@ -5,7 +5,6 @@ import Box from "@mui/material/Box"; import { CardMedia, createTheme, styled, Typography, ThemeProvider } from "@mui/material"; import Divider from "@mui/material/Divider"; import LandingPageMap from "./LandingPageMap"; -import AllProgramMap from "./AllProgramMap"; import LandingDisplay from "./LandingDisplay"; import { config } from "../app.config"; import { getJsonDataFromUrl } from "../utils/apiutil"; @@ -26,17 +25,33 @@ interface TabPanelProps { function TabPanel(props: TabPanelProps) { const { value, index, title, ...other } = props; - return ( - - } - /> */} + + + Crop Insurance +
+ ${Number(cropTotal / 1000000000.0).toFixed(2)}B +
+ } + /> - {/* */} - + + ); } diff --git a/src/components/shared/ConvertionFormats.tsx b/src/components/shared/ConvertionFormats.tsx index 395a3948..9471449d 100644 --- a/src/components/shared/ConvertionFormats.tsx +++ b/src/components/shared/ConvertionFormats.tsx @@ -1,14 +1,32 @@ -export function ShortFormat(labelValue) { - const absoluteValue = Math.abs(Number(labelValue)); - let result; +export function ShortFormat(labelValue, position?: number) { + const absoluteValue = Math.abs(Number.parseFloat(labelValue)); + let decimalPart = ""; + let result = ""; if (absoluteValue >= 1.0e9) { - result = `${absoluteValue / 1.0e9}B`; + result = + (absoluteValue / 1.0e9) % 1 !== 0 ? `${(absoluteValue / 1.0e9).toFixed(1)}B` : `${absoluteValue / 1.0e9}B`; + decimalPart = (absoluteValue / 1.0e9) % 1 !== 0 ? result.match(/\.(.*)B$/)[1] : ""; } else if (absoluteValue >= 1.0e6) { - result = `${absoluteValue / 1.0e6}M`; + result = + (absoluteValue / 1.0e6) % 1 !== 0 ? `${(absoluteValue / 1.0e6).toFixed(1)}M` : `${absoluteValue / 1.0e6}M`; + decimalPart = (absoluteValue / 1.0e6) % 1 !== 0 ? result.match(/\.(.*)M$/)[1] : ""; } else if (absoluteValue >= 1.0e3) { - result = `${absoluteValue / 1.0e3}K`; + result = + (absoluteValue / 1.0e3) % 1 !== 0 ? `${(absoluteValue / 1.0e3).toFixed(1)}M` : `${absoluteValue / 1.0e3}M`; + decimalPart = (absoluteValue / 1.0e3) % 1 !== 0 ? result.match(/\.(.*)M$/)[1] : ""; } else { - result = absoluteValue; + result = absoluteValue % 1 !== 0 ? `${absoluteValue.toFixed(1)}` : `${absoluteValue}`; + } + if (labelValue.toString().includes("-")) { + result = `-${result}`; + } + if (decimalPart.length > 0) { + const numberPart = result.match(/^(.*).$/)?.[1]; + if (position === 0) { + result = result.replace(/^(.*)(.)$/, `${Math.floor(Number(numberPart))}$2`); + } else if (position !== undefined) { + result = result.replace(/^(.*)(.)$/, `${Math.ceil(Number(numberPart))}$2`); + } } return result; } diff --git a/src/components/shared/DrawLegend.tsx b/src/components/shared/DrawLegend.tsx new file mode 100644 index 00000000..9df76734 --- /dev/null +++ b/src/components/shared/DrawLegend.tsx @@ -0,0 +1,168 @@ +import * as React from "react"; +import * as d3 from "d3"; +import { Box, Typography } from "@mui/material"; +import { ShortFormat } from "./ConvertionFormats"; +import "../../styles/drawLegend.css"; +/** + * Keys in legendConfig.json must match the 'searchKey' variable in DrawLegend.tsx file. + * If there's any changes in legendConfig.json, please re-check and update the 'searchKey' variable here. + */ +export default function DrawLegend({ + colorScale, + title, + programData, + prepColor, + emptyState, + initWidth +}: { + colorScale: d3.ScaleThreshold; + title: React.ReactElement; + programData: number[]; + prepColor: string[]; + emptyState: string[]; + initWidth: number; +}): JSX.Element { + const legendRn = React.useRef(null); + const margin = 40; + let cut_points: number[] = []; + const [width, setWidth] = React.useState(initWidth); + React.useEffect(() => { + drawLegend(); + }, []); + const drawLegend = () => { + if (legendRn.current) { + d3.select(legendRn.current).selectAll("svg").remove(); + const baseSVG = d3.select(legendRn.current).append("svg").attr("width", width).attr("height", 90); + const customScale = colorScale.domain(); + + cut_points.push(Math.min(...programData)); + cut_points = cut_points.concat(customScale); + const legendRectX: number[] = []; + if (Math.min(...programData) !== Infinity && Math.max(...programData) !== Infinity) { + baseSVG.selectAll("text").remove(); + baseSVG.selectAll("rect").remove(); + const data_distribution: number[] = []; + const cutCount = Array.from({ length: cut_points.length - 1 }, (v, i) => 1 + i); + cutCount.forEach((i) => { + data_distribution.push( + programData.filter((d) => d >= cut_points[i - 1] && d < cut_points[i]).length / + programData.length + ); + // Leave following part as the backup of solution 2. + // programData + // .filter((d) => d >= cut_points[i - 1] && d < cut_points[i]) + // .reduce((accumulator, currentValue) => accumulator + currentValue, 0) / + // programData.reduce((accumulator, currentValue) => accumulator + currentValue, 0) + }); + data_distribution.push( + programData.filter((d) => d >= cut_points[cut_points.length - 1]).length / programData.length + ); + // Leave following part as the backup of solution 2. + // data_distribution.push( + // programData + // .filter((d) => d >= cut_points[cut_points.length - 1]) + // .reduce((accumulator, currentValue) => accumulator + currentValue, 0) / + // programData.reduce((accumulator, currentValue) => accumulator + currentValue, 0) + // ); + const svgWidth = baseSVG.attr("width") - margin * 2; + baseSVG + .selectAll(null) + .data(data_distribution) + .enter() + .append("rect") + .attr("class", "legendRect") + .attr("id", (d) => `legendRect${d}`) + .attr("x", (d, i) => { + if (i === 0) { + return margin; + } + const sum = data_distribution.slice(0, i).reduce((acc, curr) => acc + curr, 0); + return margin + svgWidth * sum; + }) + .attr("y", (d, index) => { + return 20; + }) + .attr("width", (d, index) => { + return d * svgWidth; + }) + .attr("height", 10) + .style("fill", (d, i) => prepColor[i]); + cut_points = cut_points.concat(Math.max(...programData)); + baseSVG + .selectAll(".legendRect") + .nodes() + .forEach((d) => { + legendRectX.push(Number(d3.select(d).attr("x"))); + }); + const last = + legendRectX[legendRectX.length - 1] + data_distribution[data_distribution.length - 1] * svgWidth; + legendRectX.push(last); + if (window.innerWidth > 1679) { + baseSVG + .selectAll(null) + .data(legendRectX) + .enter() + .append("text") + .attr("class", "legendText") + .attr("id", (d) => `legendText${d}`) + .attr("y", 50) + .attr("x", (d, i) => { + return i === 0 ? d : d - margin / 4; + }) + .text((d, i) => { + if (i === 0) { + const res = ShortFormat(Math.round(cut_points[i]), i); + return res.indexOf("-") < 0 ? `${res}` : `-$${res.substring(1)}`; + } + return ShortFormat(Math.round(cut_points[i]), i); + }); + } else { + baseSVG + .selectAll(null) + .data(legendRectX) + .enter() + .append("text") + .attr("class", "legendText") + .attr("id", (d) => `legendText${d}`) + .attr("y", (d, i) => { + return i % 2 === 0 ? 50 : 10; + }) + .attr("x", (d, i) => { + return i === 0 ? d : d - margin / 4; + }) + .text((d, i) => { + if (i === 0) { + const res = ShortFormat(Math.round(cut_points[i]), i); + return res.indexOf("-") < 0 ? `${res}` : `-$${res.substring(1)}`; + } + return ShortFormat(Math.round(cut_points[i]), i); + }); + } + if (emptyState.length !== 0) { + baseSVG + .append("text") + .attr("class", "legendTextSide") + .attr("x", (svgWidth + margin * 2) / 2 - margin * 2) + .attr("y", 80) + .text(`${emptyState.join(", ")}'s data is not available`); + } + } + } + }; + return ( + + + + {title} + + +
+ + ); +} diff --git a/src/styles/drawLegend.css b/src/styles/drawLegend.css new file mode 100644 index 00000000..d2cb7eb6 --- /dev/null +++ b/src/styles/drawLegend.css @@ -0,0 +1,19 @@ +.MapLegendSVG { + font-family: "Roboto", sans-serif; +} +.legendText{ + font-size: 0.75em; +} +.legendTextSide{ + font: italic 0.75em "Roboto", sans-serif ; + color: #DDD; +} +@media (max-width: 1679px) { + .legendText{ + font-size: 0.65em; + } + .legendTextSide{ + font: italic 0.65em "Roboto", sans-serif ; + color: #DDD; + } +} diff --git a/src/utils/legendConfig.json b/src/utils/legendConfig.json new file mode 100644 index 00000000..ac8984e8 --- /dev/null +++ b/src/utils/legendConfig.json @@ -0,0 +1,7 @@ +{ + "18-22 All Programs Total": [5000000000, 10000000000, 30000000000, 40000000000], + "Crop Insurance Total":[0, 500000000, 1000000000, 2000000000], + "Title I Total":[100000000, 500000000, 1000000000, 2000000000], + "Title II Total":[100000000, 500000000, 1000000000, 2000000000], + "SNAP Total": [1000000000, 5000000000, 10000000000, 20000000000] +} From 4feb8fc7b71c4c3b978914d134c298d708814147 Mon Sep 17 00:00:00 2001 From: "Wendy(Pengyin) Shan" Date: Fri, 26 May 2023 19:27:53 -0500 Subject: [PATCH 2/3] update changelog --- CHANGELOG.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index abd66567..fb2a8719 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,12 +95,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Map data json [#12](https://github.com/policy-design-lab/pdl-frontend/issues/12) - Final landing page changes for initial milestone [#15](https://github.com/policy-design-lab/pdl-frontend/issues/15) -<<<<<<< HEAD [unreleased]: https://github.com/policy-design-lab/pdl-frontend/compare/0.5.1...HEAD [0.5.1]: https://github.com/policy-design-lab/pdl-frontend/compare/0.5.0...0.5.1 [0.5.0]: https://github.com/policy-design-lab/pdl-frontend/tag/0.5.0 -======= -[unreleased]: https://github.com/policy-design-lab/pdl-frontend/compare/0.5.0...HEAD -[0.5.0]: https://github.com/policy-design-lab/pdl-frontend/compare/0.4.0...0.5.0 -[0.4.0]: https://github.com/policy-design-lab/pdl-frontend/tag/0.4.0 ->>>>>>> develop From 9eadf8a9605626d048bc5ea61707abb2084c88c4 Mon Sep 17 00:00:00 2001 From: "Wendy(Pengyin) Shan" Date: Tue, 30 May 2023 08:52:16 -0500 Subject: [PATCH 3/3] remove unreleased section in changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb2a8719..24596847 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,6 +95,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Map data json [#12](https://github.com/policy-design-lab/pdl-frontend/issues/12) - Final landing page changes for initial milestone [#15](https://github.com/policy-design-lab/pdl-frontend/issues/15) -[unreleased]: https://github.com/policy-design-lab/pdl-frontend/compare/0.5.1...HEAD [0.5.1]: https://github.com/policy-design-lab/pdl-frontend/compare/0.5.0...0.5.1 -[0.5.0]: https://github.com/policy-design-lab/pdl-frontend/tag/0.5.0 +[0.5.0]: https://github.com/policy-design-lab/pdl-frontend/compare/0.4.0...0.5.0 +[0.4.0]: https://github.com/policy-design-lab/pdl-frontend/compare/0.3.0...0.4.0 +[0.3.0]: https://github.com/policy-design-lab/pdl-api/releases/tag/0.3.0