diff --git a/CHANGELOG.md b/CHANGELOG.md index 29a990b9..66e61631 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ 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). +## [0.8.0] - 2023-09-06 + +### Added + +- Add Crop Insurance page and corresponding components for its attributes [#125](https://github.com/policy-design-lab/pdl-frontend/issues/125) +- Added Average Insured Area In Acres sub-page to Crop Insurance pages [#183](https://github.com/policy-design-lab/pdl-frontend/issues/183) + +### Changed + +- Use different title equations for the bar charts in pages under the Net Farmer Benefit section [#179](https://github.com/policy-design-lab/pdl-frontend/issues/179) +- Adjusted font size of chart headers on Crop Insurance page [#182](https://github.com/policy-design-lab/pdl-frontend/issues/182) +- Adjusted '$' sign for some sub-pages of Crop Insurance page [#184](https://github.com/policy-design-lab/pdl-frontend/issues/184) +- Adjusted menu height of Crop Insurance page [#185](https://github.com/policy-design-lab/pdl-frontend/issues/185) +- Added explanation to the Insured Acres subpage of Crop Insurance page [#186](https://github.com/policy-design-lab/pdl-frontend/issues/186) + + ## [0.7.0] - 2023-08-22 ### Changed @@ -14,6 +30,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Updated the year related labels on landing page and title 1 page to reflect changes of new title 1 API [#168](https://github.com/policy-design-lab/pdl-frontend/issues/168) - Updated the landing page map tab to include a label to explain that the top-line numbers are not finalized [#171](https://github.com/policy-design-lab/pdl-frontend/issues/171) - Added '(Numbers have not yet been finalized)' label on landing page top line tab and updated several details on Title 1 page [#172](https://github.com/policy-design-lab/pdl-frontend/issues/172) +- Changed the "Total Commodities Programs" on the Title I page menu to "Total Commodities Programs, Subtitle A" [#176](https://github.com/policy-design-lab/pdl-frontend/issues/176) ## [0.6.0] - 2023-07-18 @@ -122,7 +139,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Map data json [#12](https://github.com/policy-design-lab/pdl-frontend/issues/12) - Final landing page changes for initial milestone [#15](https://github.com/policy-design-lab/pdl-frontend/issues/15) -[0.7.0]: https://github.com/policy-design-lab/pdl-frontend/compare/0.5.1...0.7.0 +[0.8.0]: https://github.com/policy-design-lab/pdl-frontend/compare/0.7.0...0.8.0 +[0.7.0]: https://github.com/policy-design-lab/pdl-frontend/compare/0.6.0...0.7.0 [0.6.0]: https://github.com/policy-design-lab/pdl-frontend/compare/0.5.1...0.6.0 [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/compare/0.4.0...0.5.0 diff --git a/package-lock.json b/package-lock.json index 73189c08..3172fa74 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "policy-design-lab", - "version": "0.7.0", + "version": "0.8.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 6c1df5eb..0be04d68 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "policy-design-lab", - "version": "0.7.0", + "version": "0.8.0", "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/LandingDisplay.tsx b/src/components/LandingDisplay.tsx index 8ee0b433..0fd76b43 100644 --- a/src/components/LandingDisplay.tsx +++ b/src/components/LandingDisplay.tsx @@ -86,7 +86,7 @@ export default function LandingDisplay({ programTitle }: { programTitle: string bodyText = "Crop Insurance provides farmers with the option to purchase insurance policies on the acres of crops they plant to help manage the risks of farming, including to indemnify against losses in yields, crop or whole farm revenue, crop margins and other risks. The program also offsets the cost of the insurance policies through premium subsidies. In addition, the program provides Administrative and Operating (A&O) subsidies to the private crop insurance companies who provide federal crop insurance to farmers. The map shows the total farmer net benefitbenefit of the crop insurance program by state from 2018-2022."; - route = "/"; + route = "/cropinsurance"; buttonText = "Explore Maps of Crop Insurance"; button = ( {" "} + {" "} + {" "} + {" "} + + 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 CropInsuranceProgramTable; diff --git a/src/components/cropinsurance/chart/CropInsuranceBar.tsx b/src/components/cropinsurance/chart/CropInsuranceBar.tsx new file mode 100644 index 00000000..5250830e --- /dev/null +++ b/src/components/cropinsurance/chart/CropInsuranceBar.tsx @@ -0,0 +1,957 @@ +import * as React from "react"; +import * as d3 from "d3"; +import styled from "styled-components"; +import { randomBytes } from "crypto"; +import { Box, Typography } from "@mui/material"; +import { ShortFormat, ToPercentageString, ToDollarString } from "../../shared/ConvertionFormats"; + +export default function CropInsuranceBar({ + CIData, + status, + yearKey, + barKeyAttr, + margin, + topSpace, + w, + h, + color1, + color2, + widthPercentage, + heightPercentage +}: { + CIData: any; + status: number; + yearKey: string; + barKeyAttr: string; + margin: { top: number; right: number; bottom: number; left: number }; + topSpace: number; + w: number; + h: number; + color1: string; + color2: string; + widthPercentage: number; + heightPercentage: number; +}): JSX.Element { + const rnBar = React.useRef(null); + const Styles = styled.div` + svg { + font-family: "Roboto", sans-serif; + } + + .y0.axis path, + .y0.axis line, + .y1.axis path, + .y1.axis line, + .x.axis path, + .x.axis line { + stroke: #000; + opacity: 10%; + } + + .y0.axis text { + color: ${color1}; + } + + .y1.axis text { + color: ${color2}; + } + .y0.axis .tick line, + .x.axis .tick line, + .y1.axis .tick line { + visibility: hidden; + } + + .x.axis text { + fill: #00000099; + } + + .x.axis .tick, + .barChart rect { + cursor: pointer; + } + `; + const textPadding = 8; + const InfoGap = 8; + const lineGenerator = d3 + .line() + .x((d) => d.x) + .y((d) => d.y); + + const [width, setWidth] = React.useState(w); + const [height, setHeight] = React.useState(h); + const handleResize: () => void = () => { + setWidth(window.innerWidth * widthPercentage); + setHeight(window.innerWidth * heightPercentage); + }; + const [whichBar, setWhichBar] = React.useState(barKeyAttr); + const [barStatus, setBarStatus] = React.useState(status); + React.useEffect(() => { + d3.select(rnBar.current).html(""); + setWhichBar(barKeyAttr); + setBarStatus(status); + if (["0", "00", "01", "02", "03"].includes(whichBar)) { + if (barStatus === 0) { + renderTotalBar(); + } else if (barStatus === 1) { + renderSingleBar( + prepStackedBarData(CIData[yearKey]), + "totalFarmerPaidPremiumInDollars", + "Farmer Paid Premium ($)", + "Farmer Paid Premium", + color1 + ); + } else if (barStatus === 2) + renderSingleBar( + prepStackedBarData(CIData[yearKey]), + "totalPremiumSubsidyInDollars", + "Premium Subsidy ($)", + "Premium Subsidy", + color2 + ); + } else if (barStatus === 3) { + renderDuoBar(prepDueBarData(CIData[yearKey], "totalPoliciesEarningPremium")); + } else if (barStatus === 4) { + renderSingleBar( + prepDueBarData(CIData[yearKey], "totalPoliciesEarningPremium"), + "totalPoliciesEarningPremium", + "Total Policies Earning Premium", + "Total Policies Earning Premium", + color1 + ); + } else if (barStatus === 5) + renderSingleBar( + prepDueBarData(CIData[yearKey], "totalIndemnitiesInDollars"), + "totalIndemnitiesInDollars", + "Total Indemnities ($)", + "Total Indemnities", + color2 + ); + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + }); + + const prepStackedBarData = (data) => { + data.sort(function (a, b) { + if (a.programs[0].totalPremiumInDollars === b.programs[0].totalPremiumInDollars) return 0; + if (a.programs[0].totalPremiumInDollars > b.programs[0].totalPremiumInDollars) return -1; + return 1; + }); + const states = data.map((d) => d.state); + const res = states.map((state, i) => ({ + state, + totalFarmerPaidPremiumInDollars: data[i].programs[0].totalFarmerPaidPremiumInDollars, + totalPremiumSubsidyInDollars: data[i].programs[0].totalPremiumSubsidyInDollars + })); + return res; + }; + const prepDueBarData = (data, sortedAttr) => { + data.sort(function (a, b) { + if (a.programs[0][sortedAttr] === b.programs[0][sortedAttr]) return 0; + if (a.programs[0][sortedAttr] > b.programs[0][sortedAttr]) return -1; + return 1; + }); + const states = data.map((d) => d.state); + const res = states.map((state, i) => ({ + state, + totalPoliciesEarningPremium: data[i].programs[0].totalPoliciesEarningPremium, + totalIndemnitiesInDollars: data[i].programs[0].totalIndemnitiesInDollars + })); + return res; + }; + const barColorRecover = () => { + d3.select(rnBar.current).selectAll(".leftLine").remove(); + d3.select(rnBar.current).selectAll(".leftText").remove(); + d3.select(rnBar.current).selectAll(".leftInfo").remove(); + d3.select(rnBar.current).selectAll(".rightLine").remove(); + d3.select(rnBar.current).selectAll(".rightText").remove(); + d3.select(rnBar.current).selectAll(".rightInfo").remove(); + d3.select(rnBar.current).select(".x.axis").selectAll("text").style("opacity", 1); + d3.select(rnBar.current).select(".x.axis").selectAll(".tick").select("text").style("font-weight", 400); + d3.select(rnBar.current) + .selectAll(".barChart") + .selectAll("rect") + .nodes() + .forEach((d) => { + d3.select(d).style("opacity", 1); + }); + }; + const renderTotalBar = () => { + d3.select(rnBar.current).html(""); + const data = prepStackedBarData(CIData[yearKey]); + const stack = d3.stack().keys(["totalFarmerPaidPremiumInDollars", "totalPremiumSubsidyInDollars"])(data); + + const keys = Object.keys(data[0]).slice(1); + + stack.map((d, i) => { + d.map((item: { key: string }) => { + item.key = keys[i]; + return item; + }); + return d; + }); + const graphWidth = width - margin.left - margin.right; + const graphHeight = height - margin.top - margin.bottom - topSpace; + const TotalLabel = (TotalBars, state) => { + d3.select(rnBar.current).select(".x.axis").selectAll("text").style("opacity", 0.1); + d3.select(rnBar.current).select(`.x-${state}`).select("text").style("font-weight", 600).style("opacity", 1); + + d3.select(rnBar.current).selectAll(".barChart").selectAll("rect").style("opacity", 0.1); + TotalBars.style("opacity", 1); + const topBar = d3.select(TotalBars.nodes()[1]); + const leftLineData = [ + { + x: Number(topBar.attr("x")) + Number(topBar.attr("width")) + margin.left, + y: topBar.attr("y") + }, + { x: margin.left, y: topBar.attr("y") } + ]; + d3.select(rnBar.current) + .append("path") + .attr("class", "leftLine") + .attr("d", lineGenerator(leftLineData)) + .attr("stroke", "#672500") + .attr("stroke-width", parseFloat(topBar.attr("width")) / 4) + .attr("fill", "none"); + }; + const TotalText = (TotalBars, state) => { + const bottomBar = d3.select(TotalBars.nodes()[0]); + const topBar = d3.select(TotalBars.nodes()[1]); + const leftTextContent1 = `Total Premium: $${ToDollarString( + bottomBar.data()[0].data.totalFarmerPaidPremiumInDollars + + topBar.data()[0].data.totalPremiumSubsidyInDollars, + 6 + )}`; + const leftText1 = d3 + .select(rnBar.current) + .append("text") + .style("font-size", "0.81rem") + .attr("x", -1000) + .attr("y", -1000) + .text(leftTextContent1); + const leftBox1 = leftText1.node().getBBox(); + leftText1.remove(); + const leftTextBK1 = d3 + .select(rnBar.current) + .append("rect") + .attr("class", "leftInfo") + .attr("id", `${state}LeftInfo1`) + .attr("x", margin.left) + .attr("y", topBar.attr("y") - InfoGap * 2 - leftBox1.height) + .attr("width", leftBox1.width + textPadding * 2) + .attr("height", leftBox1.height + textPadding) + .attr("fill", "#672500") + .attr("rx", 3) + .attr("ry", 3); + d3.select(rnBar.current) + .append("text") + .attr("x", margin.left + textPadding) + .attr("y", topBar.attr("y") - InfoGap * 2) + .attr("class", "leftText") + .attr("id", `${state}LeftText2`) + .text(leftTextContent1) + .style("font-size", "0.81rem") + .style("fill", "white"); + d3.select(rnBar.current) + .append("text") + .attr("x", margin.left + leftBox1.width + textPadding * 2 + textPadding / 2) + .attr("y", topBar.attr("y") - InfoGap * 2) + .attr("class", "leftText") + .attr("id", `${state}LeftText2`) + .text("=") + .style("font-size", "0.81rem") + .style("fill", "black"); + const leftTextContent2 = `Total Farmer Paid Premium: $${ToDollarString( + bottomBar.data()[0].data.totalFarmerPaidPremiumInDollars, + 6 + )}`; + const leftText2 = d3 + .select(rnBar.current) + .append("text") + .style("font-size", "0.81rem") + .attr("x", -1000) + .attr("y", -1000) + .text(leftTextContent2); + const leftBox2 = leftText2.node().getBBox(); + leftText2.remove(); + const leftTextBK2 = d3 + .select(rnBar.current) + .append("rect") + .attr("class", "leftInfo") + .attr("id", `${state}LeftInfo2`) + .attr("x", margin.left + leftBox1.width + textPadding * 4) + .attr("y", topBar.attr("y") - InfoGap * 2 - leftBox2.height) + .attr("width", leftBox2.width + textPadding * 2) + .attr("height", leftBox2.height + textPadding) + .attr("fill", color1) + .attr("opacity", 1) + .attr("rx", 3) + .attr("ry", 3); + d3.select(rnBar.current) + .append("text") + .attr("x", margin.left + leftBox1.width + textPadding * 4 + textPadding) + .attr("y", topBar.attr("y") - InfoGap * 2) + .attr("class", "leftText") + .attr("id", `${state}LeftText2`) + .text(leftTextContent2) + .style("font-size", "0.81rem") + .style("fill", "white"); + d3.select(rnBar.current) + .append("text") + .attr( + "x", + margin.left + + leftBox1.width + + textPadding * 4 + + textPadding + + leftBox2.width + + textPadding + + textPadding / 2 + ) + .attr("y", topBar.attr("y") - InfoGap * 2) + .attr("class", "leftText") + .attr("id", `${state}LeftText2`) + .text("+") + .style("font-size", "0.81rem") + .style("fill", "black"); + const leftTextContent3 = `Total Premium Subsidy: $${ToDollarString( + topBar.data()[0].data.totalPremiumSubsidyInDollars, + 6 + )}`; + + const leftText3 = d3 + .select(rnBar.current) + .append("text") + .style("font-size", "0.81rem") + .attr("x", -1000) + .attr("y", -1000) + .text(leftTextContent3); + const leftBox3 = leftText3.node().getBBox(); + leftText3.remove(); + const leftTextBK3 = d3 + .select(rnBar.current) + .append("rect") + .attr("class", "leftInfo") + .attr("id", `${state}LeftInfo2`) + .attr( + "x", + margin.left + leftBox1.width + textPadding * 4 + textPadding + leftBox2.width + textPadding * 3 + ) + .attr("y", topBar.attr("y") - InfoGap * 2 - leftBox3.height) + .attr("width", leftBox3.width + textPadding * 2) + .attr("height", leftBox3.height + textPadding) + .attr("fill", color2) + .attr("opacity", 1) + .attr("rx", 3) + .attr("ry", 3); + d3.select(rnBar.current) + .append("text") + .attr( + "x", + margin.left + + leftBox1.width + + textPadding * 4 + + textPadding + + leftBox2.width + + textPadding + + textPadding * 3 + ) + .attr("y", topBar.attr("y") - InfoGap * 2) + .attr("class", "leftText") + .attr("id", `${state}LeftText3`) + .text(leftTextContent3) + .style("font-size", "0.81rem") + .style("fill", "white"); + }; + + const x0 = d3.scaleBand().range([0, graphWidth]).paddingInner(0.4).paddingOuter(0.4); + const y0 = d3.scaleLinear().range([graphHeight, 0]); + const xAxis = d3.axisBottom(x0).ticks(5).tickSizeOuter(0); + const yAxisLeft = d3 + .axisLeft(y0) + .ticks(10) + .tickFormat(function (d) { + if (barStatus === 4) return `${ShortFormat(parseInt(d, 10))}`; + return `$${ShortFormat(parseInt(d, 10))}`; + }) + .tickSizeOuter(0); + x0.domain( + data.map(function (d) { + return d.state; + }) + ); + const yMax = d3.max(data, (d) => keys.reduce((acc, k) => acc + d[k], 0)); + y0.domain([0, yMax * 1.1]); + d3.select(rnBar.current).attr("width", width).attr("height", height).append("g"); + const x_axis = d3 + .select(rnBar.current) + .append("g") + .attr("class", "x axis") + .attr("transform", `translate(${margin.left},${topSpace + graphHeight + margin.top})`) + .call(xAxis); + x_axis.selectAll(".tick").attr("class", (d) => `tick x-${d}`); + d3.select(rnBar.current) + .append("g") + .attr("class", "y0 axis") + .call(yAxisLeft) + .attr("transform", `translate(${margin.left}, ${topSpace + margin.top})`) + .append("text") + .attr("x", 0 - margin.left) + .attr("y", -margin.top) + .attr("dy", "1em") + .style("text-anchor", "start") + .style("fill", color1) + .text("Total Premium ($)") + .style("font-weight", "400") + .style("font-size", "0.785rem"); + d3.select(rnBar.current) + .select(".y0.axis") + .selectAll(".tick") + .style("fill", color1) + .style("font-size", "0.7rem"); + const base = d3 + .select(rnBar.current) + .append("g") + .attr("class", "barChart") + .attr("transform", `translate(${margin.left},0)`); + const temp = base + .selectAll("g") + .data(stack) + .enter() + .append("g") + .selectAll("rect") + .data((d) => d) + .enter(); + + temp.append("rect") + .attr("x", (d, i) => { + return x0(data[i].state); + }) + .attr("width", x0.bandwidth()) + .attr("height", (d) => { + return y0(d[0]) - y0(d[1]); + }) + .attr("y", (d) => y0(d[1]) + topSpace + margin.top) + .attr("fill", (d) => (d.key === "totalFarmerPaidPremiumInDollars" ? color1 : color2)) + .attr("class", (d) => d.key) + .attr("id", (d) => `${d.data.state}${d.key}`); + const bar1 = base.selectAll("rect").filter((d) => d.key === "totalFarmerPaidPremiumInDollars"); + const bar2 = base.selectAll("rect").filter((d) => d.key === "totalPremiumSubsidyInDollars"); + bar1.on("mouseover", function (e) { + const allRect = base.selectAll("rect").filter((d) => d.data.state === d3.select(this).data()[0].data.state); + TotalLabel(allRect, d3.select(this).data()[0].data.state); + TotalText(allRect, d3.select(this).data()[0].data.state); + }).on("mouseleave", function (e) { + barColorRecover(); + }); + bar2.on("mouseover", function (e) { + const allRect = base.selectAll("rect").filter((d) => d.data.state === d3.select(this).data()[0].data.state); + TotalLabel(allRect, d3.select(this).data()[0].data.state); + TotalText(allRect, d3.select(this).data()[0].data.state); + }).on("mouseleave", function (e) { + barColorRecover(); + }); + d3.select(rnBar.current) + .select(".x.axis") + .selectAll(".tick") + .on("mouseover", function (e) { + const state = d3.select(this).data()[0]; + if (status === 0) { + const allRect = base.selectAll("rect").filter((d) => d.data.state === state); + TotalLabel(allRect, state); + TotalText(allRect, state); + } + }) + .on("mouseout", function () { + barColorRecover(); + }); + }; + const renderSingleBar = (data, attr, yLabel, tipText, color) => { + d3.select(rnBar.current).html(""); + data.sort(function (a, b) { + if (a[attr] === b[attr]) return 0; + if (a[attr] > b[attr]) return -1; + return 1; + }); + const graphWidth = width - margin.left - margin.right; + const graphHeight = height - margin.top - margin.bottom - topSpace; + const x0 = d3.scaleBand().range([0, graphWidth]).paddingInner(0.4).paddingOuter(0.4); + const y0 = d3.scaleLinear().range([graphHeight, 0]); + const xAxis = d3.axisBottom(x0).ticks(5).tickSizeOuter(0); + const yAxisLeft = d3 + .axisLeft(y0) + .ticks(10) + .tickFormat(function (d) { + if (barStatus === 4) return `${ShortFormat(parseInt(d, 10))}`; + return `$${ShortFormat(parseInt(d, 10))}`; + }) + .tickSizeOuter(0); + x0.domain( + data.map(function (d) { + return d.state; + }) + ); + y0.domain([0, d3.max(data, (d) => d[attr] * 1.01)]); + d3.select(rnBar.current).attr("width", width).attr("height", height).append("g"); + const x_axis = d3 + .select(rnBar.current) + .append("g") + .attr("class", "x axis") + .attr("transform", `translate(${margin.left},${topSpace + graphHeight + margin.top})`) + .call(xAxis); + x_axis.selectAll(".tick").attr("class", (d) => `tick x-${d}`); + d3.select(rnBar.current) + .append("g") + .attr("class", "y0 axis") + .call(yAxisLeft) + .attr("transform", `translate(${margin.left}, ${topSpace + margin.top})`) + .append("text") + .attr("x", 0 - margin.left) + .attr("y", -margin.top) + .attr("dy", "1em") + .style("text-anchor", "start") + .style("fill", color) + .text(yLabel) + .style("font-weight", "400") + .style("font-size", "0.785rem"); + d3.select(rnBar.current) + .select(".y0.axis") + .selectAll(".tick") + .style("fill", color) + .style("font-size", "0.7rem"); + const base = d3 + .select(rnBar.current) + .selectAll(null) + .data(data) + .enter() + .append("g") + .attr("class", "barChart") + .attr("transform", function (d) { + return `translate(${x0(d.state) + margin.left},0)`; + }); + + const bars = base + .append("rect") + .attr("class", (d) => d.state) + .attr("id", (d) => `${d.state + attr}`) + .attr("width", x0.bandwidth()) + .attr("x", function (d) { + return x0(attr); + }) + .attr("y", function (d) { + return y0(d[attr]) + topSpace + margin.top; + }) + .attr("height", function (d) { + return graphHeight - y0(d[attr]); + }) + .style("fill", color); + const createLabel = (Bar, state) => { + d3.select(rnBar.current).select(".x.axis").selectAll("text").style("opacity", 0.1); + d3.select(rnBar.current).select(`.x-${state}`).select("text").style("font-weight", 600).style("opacity", 1); + + d3.select(rnBar.current).selectAll(".barChart").selectAll("rect").style("opacity", 0.1); + Bar.style("opacity", 1); + const topBar = Bar; + // eslint-disable-next-line no-restricted-globals + const mousePos = d3.pointer(event, d3.select(rnBar.current).select(".y0.axis").node()); + // eslint-disable-next-line no-restricted-globals + const minusMouse = d3.pointer(event, topBar.node()); + const leftLineData = [ + { + x: mousePos[0] + margin.left + parseFloat(topBar.attr("width")) - minusMouse[0], + y: topBar.attr("y") + }, + { x: margin.left, y: topBar.attr("y") } + ]; + d3.select(rnBar.current) + .append("path") + .attr("class", "leftLine") + .attr("d", lineGenerator(leftLineData)) + .attr("stroke", color) + .attr("stroke-width", parseFloat(topBar.attr("width")) / 4) + .attr("fill", "none"); + }; + const createText = (Bar, state) => { + const topBar = Bar; + + const leftTextContent3 = `${tipText}: $${ToDollarString(topBar.data()[0][attr], 6)}`; + const leftText3 = d3 + .select(rnBar.current) + .append("text") + .style("font-size", "0.81rem") + .attr("x", -1000) + .attr("y", -1000) + .text(leftTextContent3); + const leftBox3 = leftText3.node().getBBox(); + leftText3.remove(); + const leftTextBK3 = d3 + .select(rnBar.current) + .append("rect") + .attr("class", "leftInfo") + .attr("id", `${state}LeftInfo2`) + .attr("x", margin.left) + .attr("y", topBar.attr("y") - InfoGap * 2 - leftBox3.height) + .attr("width", leftBox3.width + textPadding * 2) + .attr("height", leftBox3.height + textPadding) + .attr("fill", color) + .attr("opacity", 1) + .attr("rx", 3) + .attr("ry", 3); + d3.select(rnBar.current) + .append("text") + .attr("x", margin.left + textPadding) + .attr("y", topBar.attr("y") - InfoGap * 2) + .attr("class", "leftText") + .attr("id", `${state}LeftText3`) + .text(leftTextContent3) + .style("font-size", "0.81rem") + .style("fill", "white"); + }; + bars.on("mouseover", function (e) { + createLabel(d3.select(this), d3.select(this).data()[0].state); + createText(d3.select(this), d3.select(this).data()[0].state); + }).on("mouseleave", function (e) { + barColorRecover(); + }); + barColorRecover(); + }; + const renderDuoBar = (data) => { + d3.select(rnBar.current).html(""); + const graphWidth = width - margin.left - margin.right; + const graphHeight = height - margin.top - margin.bottom - topSpace; + const color = d3 + .scaleOrdinal() + .domain(["totalPoliciesEarningPremium", "totalIndemnitiesInDollars"]) + .range([color1, color2]); + const totalIndemnitiesInDollarsLabel = (totalIndemnitiesInDollarsBar, state) => { + if (status === 3 || status === 4) { + d3.select(rnBar.current).select(".x.axis").selectAll("text").style("opacity", 0.1); + d3.select(rnBar.current) + .select(`.x-${state}`) + .select("text") + .style("font-weight", 600) + .style("opacity", 1); + d3.select(rnBar.current).selectAll(".barChart").selectAll("rect").style("opacity", 0.1); + totalIndemnitiesInDollarsBar.style("opacity", 1); + // eslint-disable-next-line no-restricted-globals + const mousePos = d3.pointer(event, d3.select(rnBar.current).select(".y1.axis").node()); + // eslint-disable-next-line no-restricted-globals + const minusMouse = d3.pointer(event, totalIndemnitiesInDollarsBar.node()); + const rightLineData = [ + { + x: + margin.left + + graphWidth + + mousePos[0] - + minusMouse[0] + + parseFloat(totalIndemnitiesInDollarsBar.attr("width")), + y: totalIndemnitiesInDollarsBar.attr("y") + }, + { x: margin.left + graphWidth, y: totalIndemnitiesInDollarsBar.attr("y") } + ]; + d3.select(rnBar.current) + .append("path") + .attr("class", "rightLine") + .attr("d", lineGenerator(rightLineData)) + .attr("stroke", color2) + .attr("stroke-width", parseFloat(totalIndemnitiesInDollarsBar.attr("width")) / 2) + .attr("fill", "none"); + } + }; + const totalIndemnitiesInDollarsText = (totalIndemnitiesInDollarsBar, state) => { + const rightTextContent = `Total Indemnities: ${ToDollarString( + totalIndemnitiesInDollarsBar.data()[0].totalIndemnitiesInDollars, + 0 + )} `; + const rightText = d3 + .select(rnBar.current) + .append("text") + .style("font-size", "0.81rem") + .attr("x", -1000) + .attr("y", -1000) + .text(rightTextContent); + const rightBox = rightText.node().getBBox(); + rightText.remove(); + d3.select(rnBar.current) + .append("rect") + .attr("class", "rightInfo") + .attr("id", `${state}RightInfo`) + .attr("x", graphWidth + margin.left - InfoGap * 2 - rightBox.width) + .attr("y", totalIndemnitiesInDollarsBar.attr("y") - InfoGap * 2 - rightBox.height) + .attr("width", rightBox.width + textPadding * 2) + .attr("height", rightBox.height + textPadding) + .attr("fill", color2) + .attr("rx", 3) + .attr("ry", 3); + d3.select(rnBar.current) + .append("text") + .attr("x", graphWidth + margin.left - InfoGap - rightBox.width) + .attr("y", totalIndemnitiesInDollarsBar.attr("y") - InfoGap * 2) + .attr("class", "rightText") + .attr("id", `${state}RightText`) + .text(rightTextContent) + .style("font-size", "0.81rem") + .style("fill", "white"); + }; + const totalPoliciesEarningPremiumLabel = (totalPoliciesEarningPremiumBar, state) => { + d3.select(rnBar.current).select(".x.axis").selectAll("text").style("opacity", 0.1); + d3.select(rnBar.current).select(`.x-${state}`).select("text").style("font-weight", 600).style("opacity", 1); + if (status === 3 || status === 4) { + d3.select(rnBar.current).selectAll(".barChart").selectAll("rect").style("opacity", 0.1); + totalPoliciesEarningPremiumBar.style("opacity", 1); + // eslint-disable-next-line no-restricted-globals + const mousePos = d3.pointer(event, d3.select(rnBar.current).select(".y0.axis").node()); + // eslint-disable-next-line no-restricted-globals + const minusMouse = d3.pointer(event, totalPoliciesEarningPremiumBar.node()); + const leftLineData = [ + { + x: + mousePos[0] + + margin.left + + parseFloat(totalPoliciesEarningPremiumBar.attr("width")) - + minusMouse[0], + y: totalPoliciesEarningPremiumBar.attr("y") + }, + { x: margin.left, y: totalPoliciesEarningPremiumBar.attr("y") } + ]; + d3.select(rnBar.current) + .append("path") + .attr("class", "leftLine") + .attr("d", lineGenerator(leftLineData)) + .attr("stroke", color1) + .attr("stroke-width", parseFloat(totalPoliciesEarningPremiumBar.attr("width")) / 2) + .attr("fill", "none"); + } + }; + const totalPoliciesEarningPremiumText = (totalPoliciesEarningPremiumBar, state) => { + const leftTextContent = `Total Policies Earning Premium: ${ToDollarString( + totalPoliciesEarningPremiumBar.data()[0].totalPoliciesEarningPremium, + 0 + )}`; + const leftText = d3 + .select(rnBar.current) + .append("text") + .style("font-size", "0.81rem") + .attr("x", -1000) + .attr("y", -1000) + .text(leftTextContent); + const leftBox = leftText.node().getBBox(); + leftText.remove(); + d3.select(rnBar.current) + .append("rect") + .attr("class", "leftInfo") + .attr("id", `${state}LeftInfo`) + .attr("x", margin.left) + .attr("y", totalPoliciesEarningPremiumBar.attr("y") - InfoGap * 2 - leftBox.height) + .attr("width", leftBox.width + textPadding * 2) + .attr("height", leftBox.height + textPadding) + .attr("fill", color1) + .attr("rx", 3) + .attr("ry", 3); + d3.select(rnBar.current) + .append("text") + .attr("x", margin.left + textPadding) + .attr("y", totalPoliciesEarningPremiumBar.attr("y") - InfoGap * 2) + .attr("class", "leftText") + .attr("id", `${state}LeftText`) + .text(leftTextContent) + .style("font-size", "0.81rem") + .style("fill", "white"); + }; + const x0 = d3.scaleBand().range([0, graphWidth]).paddingInner(0.4).paddingOuter(0.4); + const x1 = d3.scaleBand(); + const y0 = d3.scaleLinear().range([graphHeight, 0]); + const y1 = d3.scaleLinear().range([graphHeight, 0]); + const xAxis = d3.axisBottom(x0).ticks(5).tickSizeOuter(0); + const yAxisLeft = d3 + .axisLeft(y0) + .ticks(10) + .tickFormat(function (d) { + if (barStatus === 4 || barStatus === 3) return `${ShortFormat(parseInt(d, 10))}`; + return `$${ShortFormat(parseInt(d, 10))}`; + }) + .tickSizeOuter(0); + const yAxisRight = d3 + .axisRight(y1) + .tickFormat(function (d) { + return ShortFormat(parseInt(d, 10)); + }) + .tickSizeOuter(0); + x0.domain( + data.map(function (d) { + return d.state; + }) + ); + x1.domain(["totalPoliciesEarningPremium", "totalIndemnitiesInDollars"]).range([0, x0.bandwidth()]); + y0.domain([ + 0, + d3.max(data, function (d) { + return d.totalPoliciesEarningPremium * 1.11; + }) + ]); + y1.domain([ + 0, + d3.max(data, function (d) { + return d.totalIndemnitiesInDollars * 1.11; + }) + ]); + d3.select(rnBar.current).attr("width", width).attr("height", height).append("g"); + const x_axis = d3 + .select(rnBar.current) + .append("g") + .attr("class", "x axis") + .attr("transform", `translate(${margin.left},${topSpace + graphHeight + margin.top})`) + .call(xAxis); + x_axis.selectAll(".tick").attr("class", (d) => `tick x-${d}`); + d3.select(rnBar.current) + .append("g") + .attr("class", "y0 axis") + .call(yAxisLeft) + .attr("transform", `translate(${margin.left}, ${topSpace + margin.top})`) + .append("text") + .attr("x", 0 - margin.left) + .attr("y", -margin.top) + .attr("dy", "1em") + .style("text-anchor", "start") + .style("fill", color1) + .text("Total Policies Earning Premium") + .style("font-weight", "400") + .style("font-size", "0.785rem"); + d3.select(rnBar.current) + .select(".y0.axis") + .selectAll(".tick") + .style("fill", color1) + .style("font-size", "0.7rem"); + d3.select(rnBar.current) + .append("g") + .attr("class", "y1 axis") + .attr("transform", `translate(${graphWidth + margin.left}, ${topSpace + margin.top})`) + .call(yAxisRight) + .append("text") + .attr("y", -margin.top) + .attr("x", margin.right) + .attr("dy", "1em") + .style("text-anchor", "end") + .style("fill", color2) + .style("font-weight", "400") + .text("Total Indemnities ($)") + .style("font-weight", "400") + .style("font-size", "0.785rem"); + d3.select(rnBar.current) + .select(".y1.axis") + .selectAll(".tick") + .style("fill", color2) + .style("font-size", "0.7rem"); + const base = d3 + .select(rnBar.current) + .selectAll(null) + .data(data) + .enter() + .append("g") + .attr("class", "barChart") + .attr("transform", function (d) { + return `translate(${x0(d.state) + margin.left},0)`; + }); + const totalPoliciesEarningPremiums = base + .append("rect") + .attr("class", (d) => d.state) + .attr("id", (d) => `${d.state}Blue`) + .attr("width", x1.bandwidth()) + .attr("x", function (d) { + return x1("totalPoliciesEarningPremium"); + }) + .attr("y", function (d) { + return y0(d.totalPoliciesEarningPremium) + topSpace + margin.top; + }) + .attr("height", function (d) { + return graphHeight - y0(d.totalPoliciesEarningPremium); + }) + .style("fill", function (d) { + return color("totalPoliciesEarningPremium"); + }); + if (status === 3 || status === 4) { + totalPoliciesEarningPremiums + .on("mouseover", function (e) { + totalPoliciesEarningPremiumLabel(d3.select(this), d3.select(this).data()[0].state); + totalPoliciesEarningPremiumText(d3.select(this), d3.select(this).data()[0].state); + }) + .on("mouseleave", function (e) { + barColorRecover(); + }); + } + const totalIndemnitiesInDollarss = base + .append("rect") + .attr("class", (d) => d.state) + .attr("id", (d) => `${d.state}Purple`) + .attr("width", x1.bandwidth()) + .attr("x", function (d) { + return x1("totalIndemnitiesInDollars"); + }) + .attr("y", function (d) { + return y1(d.totalIndemnitiesInDollars) + topSpace + margin.top; + }) + .attr("height", function (d) { + return graphHeight - y1(d.totalIndemnitiesInDollars); + }) + .style("fill", function (d) { + return color("totalIndemnitiesInDollars"); + }); + if (status === 3 || status === 5) { + totalIndemnitiesInDollarss + .on("mouseover", function (e) { + totalIndemnitiesInDollarsLabel(d3.select(this), d3.select(this).data()[0].state); + totalIndemnitiesInDollarsText(d3.select(this), d3.select(this).data()[0].state); + }) + .on("mouseleave", function (e) { + barColorRecover(); + }); + } + d3.select(rnBar.current) + .select(".x.axis") + .selectAll(".tick") + .on("mouseover", function (e) { + d3.select(rnBar.current).selectAll(".barChart").selectAll("rect").style("opacity", 0.1); + const state = d3.select(this).data(); + if (status === 3 || status === 4) { + totalPoliciesEarningPremiumLabel(d3.select(rnBar.current).selectAll(`#${state}Blue`), state); + } + if (status === 3 || status === 5) { + totalIndemnitiesInDollarsLabel(d3.select(rnBar.current).selectAll(`#${state}Purple`), state); + } + if (status === 3 || status === 4) { + d3.select(rnBar.current).selectAll(`#${state}Blue`).style("opacity", 1); + totalPoliciesEarningPremiumText(d3.select(rnBar.current).selectAll(`#${state}Blue`), state); + } + if (status === 3 || status === 5) { + totalIndemnitiesInDollarsText(d3.select(rnBar.current).selectAll(`#${state}Purple`), state); + } + }) + .on("mouseleave", function () { + barColorRecover(); + }); + barColorRecover(); + }; + return ( +
+ + {barStatus === 0 ? ( + + Total Premium = Farmer Paid Premium + Premium Subsidy + + ) : null} + {barStatus === 1 ? ( + + Farmer Paid Premium = Total Premium - Premium Subsidy + + ) : null} + {barStatus === 2 ? ( + + Premium Subsidy = Total Premium - Farmer Paid Premium + + ) : null} + + + + +
+ ); +} diff --git a/src/components/cropinsurance/chart/CropInsuranceBars.tsx b/src/components/cropinsurance/chart/CropInsuranceBars.tsx new file mode 100644 index 00000000..19aaa0d1 --- /dev/null +++ b/src/components/cropinsurance/chart/CropInsuranceBars.tsx @@ -0,0 +1,297 @@ +import * as React from "react"; +import * as d3 from "d3"; +import styled from "styled-components"; +import { + ToggleButton, + Box, + ToggleButtonGroup, + createTheme, + ThemeProvider, + Typography, + CardMedia, + Grid, + RadioGroup, + FormControlLabel, + Radio, + SvgIcon +} from "@mui/material"; +import CropInsuranceBar from "./CropInsuranceBar"; +import { DownloadIcon } from "../../shared/DownloadIcon"; + +export default function CropInsuranceBars({ + stateDistributionData, + checkedMenu, + initChartWidthRatio +}: { + stateDistributionData: any; + checkedMenu: string; + initChartWidthRatio: number; +}): JSX.Element { + const cropinsuranceDiv = React.useRef(null); + const [barStatus, setBarStatus] = React.useState(0); + const [secondarybarStatus, setSecondaryBarStatus] = React.useState(3); + const yearKey = "2018-2022"; + const widthPercentage = initChartWidthRatio; + const heightPercentage = 0.4; + const paddingLR = 60; + const paddingTB = 20; + const color1 = "#B65700"; + const color2 = "#D3AA3D"; + const [whichBar, setWhichBar] = React.useState(checkedMenu); + const defaultTheme = createTheme({ + spacing: 8 + }); + const downloadSVG = (status) => { + if (cropinsuranceDiv.current !== undefined && status) { + const svgElement = cropinsuranceDiv.current.querySelector("#CropInsuranceBarChart"); + 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 = "cropinsurance-bar.svg"; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + } + }; + const switchBarStatus = (event) => { + if (event.target.value !== null && ["0", "00", "01", "02", "03"].includes(whichBar)) { + setBarStatus(event.target.value); + } else if (event.target.value !== null) { + setSecondaryBarStatus(event.target.value); + } + }; + React.useEffect(() => { + setWhichBar(checkedMenu); + }); + return ( + + {["0", "00", "01", "02", "03"].includes(whichBar) ? ( +
+ {" "} + + + + How is the Farmer Paid Premium Calculated? + + { + event.stopPropagation(); + downloadSVG(true); + }} + /> + + + + } + label="Total Premium" + sx={{ color: "#672500" }} + /> + } + label="Farmer Paid Premium" + sx={{ color: color1 }} + /> + } + label="Premium Subsidy" + sx={{ marginRight: 0, color: color2 }} + /> + + + + + + {stateDistributionData ? ( + + ) : ( +

Loading Bar Chart...

+ )} +
+
+
+ ) : ( +
+ + + + Total Policies Earning Premium and Total Indemnities ({yearKey}) + + { + event.stopPropagation(); + downloadSVG(true); + }} + /> + + + + } + label="Both" + sx={{ color: "#672500" }} + /> + } + label="Total Policies Earning Premium" + sx={{ color: color1 }} + /> + } + label="Total Indemnities" + sx={{ marginRight: 0, color: color2 }} + /> + + + + + + {stateDistributionData ? ( + + ) : ( +

Loading Bar Chart...

+ )} +
+
+
+ )} +
+ ); +} diff --git a/src/components/cropinsurance/chart/CropInsuranceBubble.tsx b/src/components/cropinsurance/chart/CropInsuranceBubble.tsx new file mode 100644 index 00000000..21a504bd --- /dev/null +++ b/src/components/cropinsurance/chart/CropInsuranceBubble.tsx @@ -0,0 +1,579 @@ +import * as React from "react"; +import * as d3 from "d3"; +import styled from "styled-components"; +import { Box, FormControlLabel, Grid, Radio, RadioGroup, Typography } from "@mui/material"; +import ReactDOMServer from "react-dom/server"; +import { ShortFormat, ToDollarString } from "../../shared/ConvertionFormats"; +import { DownloadIcon } from "../../shared/DownloadIcon"; + +export default function CropInsuranceBubble({ originalData, initChartWidthRatio, stateCodesData }): JSX.Element { + const rn = React.useRef(null); + const baseColor = "#00000099"; + const lightGreen = "#66BB6A33"; + const darkGreen = "#205026"; + const lightBrown = "#DCC9B9"; + const darkBrown = "#412813"; + const Styles = styled.div` + svg { + font-family: "Roboto", sans-serif; + } + .y.axis path, + .y.axis line, + .x.axis path, + .x.axis line { + stroke: #000; + opacity: 10%; + } + + .x.axis text, + .y.axis text { + color: ${baseColor}; + } + + .x.axis .tick line, + .y.axis .tick line { + visibility: hidden; + } + `; + const margin = 30; + const tipWidth = 300; + const tipHeight = 200; + const data = originalData.map((obj) => ({ ...obj })); + const [width, setWidth] = React.useState(window.innerWidth * initChartWidthRatio); + const [height, setHeight] = React.useState((window.innerWidth * initChartWidthRatio) / 2); + const graphWidth = width - margin * 4; + const graphHeight = height - margin * 4; + const [bubbleStatus, setBubbleStatus] = React.useState(0); + const [stateCodes, setStateCodes] = React.useState(stateCodesData); + const handleResize: () => void = () => { + setWidth(window.innerWidth * initChartWidthRatio); + setHeight((window.innerWidth * initChartWidthRatio) / 2); + }; + const switchBubbleStatus = (event, selectItem) => { + if (selectItem !== null) { + setBubbleStatus(selectItem); + } + }; + React.useEffect(() => { + renderBubble(bubbleStatus); + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + }); + const scaling = (value, maxCut, minCut, max, min) => { + const temp = (value - min) / (max - min); + return value === 0 ? 0 : temp * (maxCut - minCut) + minCut; + }; + const renderBubble = (zoneSelection) => { + d3.select(rn.current).selectAll("*").remove(); + data.forEach((d) => { + d.SqrtResult = Math.sqrt(Math.abs(d.OriginalNetFarmerBenefit)); + d.OriginalTotalFarmerPaidPremium = d.TotalFarmerPaidPremium; + d.OriginalTotalIndemnities = d.TotalIndemnities; + d.TotalFarmerPaidPremium = + Math.sqrt(Math.abs(d.TotalFarmerPaidPremium)) * Math.sign(d.TotalFarmerPaidPremium); + d.TotalIndemnities = Math.sqrt(Math.abs(d.TotalIndemnities)) * Math.sign(d.TotalIndemnities); + }); + let maxNetFarmerBenefit = d3.max(data, function (d) { + return d.SqrtResult; + }); + const minNetFarmerBenefit = d3.min(data, function (d) { + return d.SqrtResult; + }); + data.forEach((d) => { + d.NetFarmerBenefit = scaling(d.SqrtResult, 10000, 100, maxNetFarmerBenefit, minNetFarmerBenefit); + }); + const z = d3 + .scaleLinear() + .domain([ + d3.min(data, function (d) { + return d.NetFarmerBenefit; + }), + d3.max(data, function (d) { + return d.NetFarmerBenefit; + }) + ]) + .range([5, 60]); + + maxNetFarmerBenefit = d3.max(data, function (d) { + return d.NetFarmerBenefit; + }); + const x = d3 + .scaleLinear() + .range([0, graphWidth]) + .domain([ + 0, + d3.max(data, function (d) { + return d.TotalIndemnities; + }) * 1.1 + ]); + const y = d3 + .scaleLinear() + .range([graphHeight, 0]) + .domain([ + 0, + d3.max(data, function (d) { + return d.TotalFarmerPaidPremium; + }) * 1.1 + ]); + const xAxis = d3 + .axisBottom(x) + .ticks(15) + .tickSizeOuter(0) + .tickValues(x.ticks().filter((value) => value !== 0)) + .tickFormat((d) => { + return ToDollarString(String(d ** 2 * Math.sign(d)), 6); + }); + const yAxis = d3 + .axisLeft(y) + .ticks(10) + .tickSizeOuter(0) + .tickFormat((d) => { + return ToDollarString(String(d ** 2 * Math.sign(d)), 6); + }); + d3.select(rn.current).attr("width", width).attr("height", height).append("g"); + + const x_axis = d3 + .select(rn.current) + .append("g") + .attr("class", "x axis") + .attr("transform", `translate(${margin * 2},${margin * 2 + graphHeight})`) + .call(xAxis) + .append("text") + .attr("x", graphWidth / 2) + .attr("y", 16 + margin) + .style("text-anchor", "start") + .style("fill", baseColor) + .text("Total Indemnities ($)") + .style("font-weight", "400") + .style("font-size", "1.2em"); + x_axis.selectAll(".tick").attr("class", (d) => `tick x-${d}`); + const y_axis = d3 + .select(rn.current) + .append("g") + .attr("class", "y axis") + .call(yAxis) + .attr("transform", `translate(${margin * 2}, ${margin * 2})`) + .append("text") + .attr("x", -1 * margin) + .attr("y", -16 - margin / 2) + .style("text-anchor", "start") + .style("fill", baseColor) + .text("Farmer Paid Premium ($)") + .style("font-weight", "400") + .style("font-size", "1.2em"); + const base = d3.select(rn.current).selectAll("dot").data(data).enter(); + + const maxY = d3.max(data, function (d) { + return d.TotalFarmerPaidPremium; + }); + const lineData = [ + { x: 0, y: 0 }, + { x: maxY * 1.1, y: maxY * 1.1 } + ]; + const lineG = d3 + .line() + .x(function (d) { + return x(d.x); + }) + .y(function (d) { + return y(d.y); + }); + if (Number(zoneSelection) === 1) { + const point1 = `${(margin * 2).toString()},${(margin * 2 + graphHeight).toString()}`; + const point2 = `${(margin * 2 + x(maxY * 1.1)).toString()},${(y(maxY * 1.1) + margin * 2).toString()}`; + const point3 = `${(margin + graphWidth + margin).toString()},${(margin * 2).toString()}`; + const point4 = `${(margin + graphWidth + margin).toString()},${(margin * 2 + graphHeight).toString()}`; + base.append("polygon") + .attr("id", "greenBackground") + .attr("points", `${point1} ${point2} ${point3} ${point4}`) + .attr("fill", "#66BB6A") + .attr("opacity", 0.01); + } + + if (Number(zoneSelection) === 2) { + const point1 = `${(margin * 2).toString()},${(margin * 2 + graphHeight).toString()}`; + const point2 = `${(margin * 2).toString()},${(y(maxY * 1.1) + margin * 2).toString()}`; + const point3 = `${(margin * 2 + x(maxY * 1.1)).toString()},${(y(maxY * 1.1) + margin * 2).toString()}`; + base.append("polygon") + .attr("id", "brownBackground") + .attr("points", `${point1} ${point2} ${point3}`) + .attr("fill", "#A2632F") + .attr("opacity", 0.01); + } + const cutLine = d3 + .select(rn.current) + .append("path") + .attr("class", "cutLine") + .attr("d", lineG(lineData)) + .attr("stroke", baseColor) + .attr("stroke-width", 1.5) + .attr("fill", "none") + .attr("stroke-dasharray", "4,4") + .attr("transform", `translate(${margin * 2}, ${margin * 2})`); + const cutLineLabel = d3 + .select(rn.current) + .append("text") + .attr("class", "cutLineLabel") + .attr("x", x(maxY) + margin * 2 - 16) + .attr("y", y(maxY) + margin * 2 - 16) + .text("$0") + .style("fill", baseColor) + .style("font-size", "0.9em") + .style("font-weight", "400") + .attr("transform", `translate(${margin * 2}, ${margin * -2})`); + const bubbles = base + .append("circle") + .attr("class", (d) => { + return d.TotalIndemnities > d.TotalFarmerPaidPremium ? "bubbles greenBubble" : "bubbles brownBubble"; + }) + .attr("id", (d) => { + return `bubble-${d.State}`; + }) + .attr("cx", function (d) { + return x(d.TotalIndemnities) + margin * 2; + }) + .attr("cy", function (d) { + return y(d.TotalFarmerPaidPremium) + margin * 2; + }) + .attr("r", (d) => { + return z(d.NetFarmerBenefit); + }) + .style("fill", (d) => { + return d.TotalIndemnities > d.TotalFarmerPaidPremium ? lightGreen : lightBrown; + }) + .style("fill-opacity", 0.5) + .style("stroke", (d) => { + return d.TotalIndemnities > d.TotalFarmerPaidPremium ? darkGreen : darkBrown; + }) + .style("stroke-opacity", 1); + + const states = base + .append("text") + .attr("class", "states") + .attr("id", (d) => { + return `state-${d.State}`; + }) + .attr("x", function (d) { + return x(d.TotalIndemnities) - 5 + margin * 2; + }) + .attr("y", function (d) { + return y(d.TotalFarmerPaidPremium) + 5 + margin * 2; + }) + .text(function (d) { + return d.State; + }) + .style("fill", "#000") + .style("font-size", "0.5em"); + bubbles + .on("mouseover", function (d) { + removeHoverEffect(); + const state = d3.select(this).data()[0].State; + const stateElement = d3.select(rn.current).select(`#state-${state}`); + drawLines(stateElement); + }) + .on("mouseout", function (d) { + removeHoverEffect(); + }); + states + .on("mouseover", function (d) { + removeHoverEffect(); + drawLines(d3.select(this)); + }) + .on("mouseout", function (d) { + removeHoverEffect(); + }); + + const drawLines = (theState) => { + const theBubble = d3.select(rn.current).select(`#bubble-${theState.data()[0].State}`); + d3.select(rn.current).selectAll(".bubbles").style("fill-opacity", 0.1).style("stroke-opacity", 0.1); + d3.select(rn.current).selectAll(".states").style("fill-opacity", 0.5).style("stroke-opacity", 0.5); + if (theBubble.data()[0].TotalIndemnities > theBubble.data()[0].TotalFarmerPaidPremium) { + theBubble + .style("fill-opacity", 1) + .style("stroke-opacity", 1) + .style("fill", darkGreen) + .style("stroke", darkGreen); + } else { + theBubble + .style("fill-opacity", 1) + .style("stroke-opacity", 1) + .style("fill", darkBrown) + .style("stroke", darkBrown); + } + theState.style("fill-opacity", 1).style("stroke-opacity", 1).style("fill", "white"); + const lineDataVertical = [ + { x: x(theState.data()[0].TotalIndemnities), y: y(0) }, + { + x: x(theState.data()[0].TotalIndemnities), + y: y(theState.data()[0].TotalFarmerPaidPremium) + Number(theBubble.attr("r")) + } + ]; + const pureLineG = d3 + .line() + .x(function (d) { + return d.x; + }) + .y(function (d) { + return d.y; + }); + const verticalLine = d3 + .select(rn.current) + .append("path") + .attr("class", "verticalLine") + .attr("d", pureLineG(lineDataVertical)) + .attr( + "stroke", + theBubble.data()[0].TotalIndemnities > theBubble.data()[0].TotalFarmerPaidPremium + ? darkGreen + : darkBrown + ) + .attr("stroke-width", 1) + .attr("fill", "none") + .attr("transform", `translate(${margin * 2}, ${margin * 2})`); + const lineDataHorizontal = [ + { x: x(0), y: y(theState.data()[0].TotalFarmerPaidPremium) }, + { + x: x(theState.data()[0].TotalIndemnities) - Number(theBubble.attr("r")), + y: y(theState.data()[0].TotalFarmerPaidPremium) + } + ]; + const horizontalLine = d3 + .select(rn.current) + .append("path") + .attr("class", "horizontalLine") + .attr("d", pureLineG(lineDataHorizontal)) + .attr( + "stroke", + theBubble.data()[0].TotalIndemnities > theBubble.data()[0].TotalFarmerPaidPremium + ? darkGreen + : darkBrown + ) + .attr("stroke-width", 1) + .attr("fill", "none") + .attr("transform", `translate(${margin * 2}, ${margin * 2})`); + + drawTips(theState); + }; + const drawTips = (theState) => { + const theBubble = d3.select(rn.current).select(`#bubble-${theState.data()[0].State}`); + let xTips = Number(theBubble.attr("cx")) + Number(theBubble.attr("r")) + 16; + let yTips = Number(theBubble.attr("cy")); + if (xTips + tipWidth > width) { + xTips = xTips - Number(theBubble.attr("r")) - tipWidth - Number(theBubble.attr("r")) / 2; + yTips = Number(theBubble.attr("cy")) + Number(theBubble.attr("r")) + 16; + } + if (yTips + tipHeight > height) { + yTips -= tipHeight; + } + const fullName = stateCodes[theState.data()[0].State.toString()]; + const CustomLabel = () => ( +
+
+ {fullName} +
+ + + + + + + + + + + + + + + +
Total Indemnities: {`$${ToDollarString(theState.data()[0].OriginalTotalIndemnities, 6)}`}
Farmer Paid Premium:{`-$${ToDollarString(theState.data()[0].OriginalTotalFarmerPaidPremium, 6)}`}
+ Net Farmer Benefit:{" "} + {`$${ToDollarString(theState.data()[0].OriginalNetFarmerBenefit, 6)}`}
+
+ ); + const html = ReactDOMServer.renderToString(); + const label = d3 + .select(rn.current) + .append("foreignObject") + .attr("class", "hoverLabel") + .attr("width", tipWidth) + .attr("height", tipHeight) + .attr("x", xTips) + .attr("y", yTips) + .html(html); + }; + const removeHoverEffect = () => { + d3.select(rn.current) + .selectAll(".greenBubble") + .style("fill-opacity", 1) + .style("stroke-opacity", 1) + .style("opacity", 1) + .style("fill", lightGreen) + .style("stroke", darkGreen); + d3.select(rn.current) + .selectAll(".brownBubble") + .style("fill-opacity", 1) + .style("stroke-opacity", 1) + .style("opacity", 1) + .style("fill", lightBrown) + .style("stroke", darkBrown); + d3.select(rn.current) + .selectAll(".states") + .style("fill-opacity", 1) + .style("stroke-opacity", 1) + .style("fill", "black"); + d3.select(rn.current).selectAll(".hoverBackground").remove(); + d3.select(rn.current).selectAll(".hoverLabel").remove(); + d3.select(rn.current).selectAll(".horizontalLine").remove(); + d3.select(rn.current).selectAll(".verticalLine").remove(); + }; + }; + return ( +
+ + + + How is the Net Farmer Benefits calculated (2018 - 2022)? + + { + event.stopPropagation(); + downloadSVG(true); + }} + /> + + + + + } label="Both" sx={{ color: "#000" }} /> + } + label="Positive Net Farmer Benefit" + sx={{ color: darkGreen }} + /> + } + label="Negative Net Farmer Benefit" + sx={{ marginRight: 0, color: darkBrown }} + /> + + + + + + Hover on the state names to see detailed data + + + + + Net farmer benefit = Total Indemnities - Farmer Paid Premium + + + + + (If Total Indemnities = Farmer Paid Premium, Net Farmer Benefits = $0) + + + + + +
+ ); +} diff --git a/src/components/cropinsurance/sideBar/ShortSideBar.tsx b/src/components/cropinsurance/sideBar/ShortSideBar.tsx new file mode 100644 index 00000000..30f4659e --- /dev/null +++ b/src/components/cropinsurance/sideBar/ShortSideBar.tsx @@ -0,0 +1,182 @@ +import React, { useState } from "react"; +import { + Box, + Collapse, + Drawer, + List, + ListItem, + ListItemButton, + ListItemIcon, + ListItemText, + MenuItem, + Typography +} from "@mui/material"; +import styled from "styled-components"; +import { menu } from "./SideBarMenuItem"; +import { hasChildren } from "./Utils"; + +const Styles = styled.div` + .Mui-disabled { + pointerevents: auto !important; + opacity: 1 !important; + } +`; +let currentChecked = "0"; + +export default function SideBar({ setCropInsuranceChecked }): JSX.Element { + const [checked, setChecked] = React.useState(currentChecked); + const [disabled, setDisabled] = React.useState(false); + const [selectedItem, setSelectedItem] = useState("0"); // State to track selected item + const handleToggle = (value: string) => () => { + setChecked(value); + setCropInsuranceChecked(value); + currentChecked = value; + setSelectedItem(value === selectedItem ? "" : value); + return null; + }; + const sameCategory = (item, value) => { + if (value[0] === selectedItem[0]) { + return true; + } + return false; + }; + const SideBarItem = ({ item, value, childStyle }) => { + const Component = hasChildren(item) ? MultiLevel : SingleLevel; + return ; + }; + + const SingleLevel = ({ value, item, childStyle }) => { + const highlight = sameCategory(item, value); + return ( + + {selectedItem === value ? ( + + {item.title} + + } + /> + ) : ( + + {item.title} + + } + /> + )} + + ); + }; + + const MultiLevel = ({ value, item }) => { + const { items: children } = item; + const [open, setOpen] = useState(false); + const handleClick = (event) => { + event.stopPropagation(); + event.preventDefault(); + setOpen((prev) => !prev); + }; + const highlight = sameCategory(item, value); + return ( + + + + {item.title} + + } + /> + + + + {children.map((child, k) => ( + + ))} + + + + ); + }; + + return ( + + + + {menu.map((item, key) => ( + + ))} + + + ); +} diff --git a/src/components/cropinsurance/sideBar/SideBarMenuItem.tsx b/src/components/cropinsurance/sideBar/SideBarMenuItem.tsx new file mode 100644 index 00000000..afc0021f --- /dev/null +++ b/src/components/cropinsurance/sideBar/SideBarMenuItem.tsx @@ -0,0 +1,42 @@ +export const menu = [ + { + icon: "", + title: "Total Net Farmer Benefits", + items: [ + { + icon: "", + title: "Total Indemnities" + }, + { + icon: "", + title: "Total Farmer Paid Premium" + }, + { + icon: "", + title: "Total Premium" + }, + { + icon: "", + title: "Total Premium Subsidy" + } + ] + }, + { + icon: "", + title: "Loss Ratio" + }, + { + icon: "", + title: "Average Liabilities" + }, + + { + icon: "", + title: "Total Policies Earning Premium" + }, + + { + icon: "", + title: "Average Insured Area in Acres" + } +]; diff --git a/src/components/cropinsurance/sideBar/Utils.tsx b/src/components/cropinsurance/sideBar/Utils.tsx new file mode 100644 index 00000000..379bcf90 --- /dev/null +++ b/src/components/cropinsurance/sideBar/Utils.tsx @@ -0,0 +1,17 @@ +export function hasChildren(item) { + const { items: children } = item; + + if (children === undefined) { + return false; + } + + if (children.constructor !== Array) { + return false; + } + + if (children.length === 0) { + return false; + } + + return true; +} diff --git a/src/components/shared/ConvertionFormats.tsx b/src/components/shared/ConvertionFormats.tsx index 478becdc..b1cb81e6 100644 --- a/src/components/shared/ConvertionFormats.tsx +++ b/src/components/shared/ConvertionFormats.tsx @@ -33,7 +33,22 @@ export function ShortFormat(labelValue, position?: number) { export function ToPercentageString(value: string): string { return `${parseFloat(value).toFixed(2)}%`; } -export function ToDollarString(value: string, decimals = 6): string { +export function ToDollarString(value: string, decimals: number): string { + if (decimals === 3) { + return `${Number(parseFloat(value) / 10 ** decimals).toLocaleString(undefined, { + maximumFractionDigits: 2 + })}K`; + } + if (decimals === 6) { + return `${Number(parseFloat(value) / 10 ** decimals).toLocaleString(undefined, { + maximumFractionDigits: 2 + })}M`; + } + if (decimals === 9) { + return `${Number(parseFloat(value) / 10 ** decimals).toLocaleString(undefined, { + maximumFractionDigits: 2 + })}B`; + } return Number(parseFloat(value) / 10 ** decimals).toLocaleString(undefined, { maximumFractionDigits: 2 }); diff --git a/src/components/shared/DownloadIcon.tsx b/src/components/shared/DownloadIcon.tsx new file mode 100644 index 00000000..8e9241fa --- /dev/null +++ b/src/components/shared/DownloadIcon.tsx @@ -0,0 +1,20 @@ +import { SvgIcon } from "@mui/material"; +import React from "react"; + +export const DownloadIcon = (props) => { + return ( + + + + + + + ); +}; diff --git a/src/components/shared/DrawLegend.tsx b/src/components/shared/DrawLegend.tsx index 8f008c16..742e0713 100644 --- a/src/components/shared/DrawLegend.tsx +++ b/src/components/shared/DrawLegend.tsx @@ -9,6 +9,8 @@ import "../../styles/drawLegend.css"; * The programData parameter is the array of all data points that will be used to draw the legend. */ export default function DrawLegend({ + isRatio = false, + notDollar = false, colorScale, title, programData, @@ -17,6 +19,8 @@ export default function DrawLegend({ initRatioLarge, initRatioSmall }: { + isRatio: boolean; + notDollar: boolean; colorScale: d3.ScaleThreshold; title: React.ReactElement; programData: number[]; @@ -43,6 +47,7 @@ export default function DrawLegend({ const customScale = colorScale.domain(); cut_points.push(Math.min(...programData)); cut_points = cut_points.concat(customScale); + const legendRectX: number[] = []; if (Math.min(...programData) !== Infinity && Math.max(...programData) !== Infinity) { baseSVG.selectAll("text").remove(); @@ -85,10 +90,10 @@ export default function DrawLegend({ const sum = data_distribution.slice(0, i).reduce((acc, curr) => acc + curr, 0); return margin + svgWidth * sum; }) - .attr("y", (d, index) => { + .attr("y", () => { return 20; }) - .attr("width", (d, index) => { + .attr("width", (d) => { return d * svgWidth; }) .attr("height", 10) @@ -116,7 +121,10 @@ export default function DrawLegend({ return i === 0 ? d : d - margin / 4; }) .text((d, i) => { - if (i === 0) { + if (isRatio) { + return `${Math.round(cut_points[i] * 100)}%`; + } + if (i === 0 && !notDollar) { const res = ShortFormat(Math.round(cut_points[i]), i); return res.indexOf("-") < 0 ? `$${res}` : `-$${res.substring(1)}`; } @@ -137,7 +145,10 @@ export default function DrawLegend({ return i === 0 ? d : d - margin / 4; }) .text((d, i) => { - if (i === 0) { + if (isRatio) { + return `${Math.round(cut_points[i] * 100)}%`; + } + if (i === 0 && !notDollar) { const res = ShortFormat(Math.round(cut_points[i]), i); return res.indexOf("-") < 0 ? `$${res}` : `-$${res.substring(1)}`; } diff --git a/src/components/snap/SNAPTable.tsx b/src/components/snap/SNAPTable.tsx index 2eb2ee94..f4f686c5 100644 --- a/src/components/snap/SNAPTable.tsx +++ b/src/components/snap/SNAPTable.tsx @@ -5,15 +5,16 @@ import { useTable, useSortBy, usePagination } from "react-table"; import Box from "@mui/material/Box"; import { compareWithAlphabetic, compareWithDollarSign, compareWithPercentSign } from "../shared/TableCompareFunctions"; import "../../styles/table.css"; -import stateCodes from "../../data/stateCodes.json"; function SnapTable({ SnapData, + stateCodes, yearKey, color1, color2 }: { SnapData: any; + stateCodes: any; yearKey: string; color1: string; color2: string; diff --git a/src/components/title1/Title1TreeMap.tsx b/src/components/title1/Title1TreeMap.tsx index e71c4220..1eb4a903 100644 --- a/src/components/title1/Title1TreeMap.tsx +++ b/src/components/title1/Title1TreeMap.tsx @@ -4,6 +4,7 @@ 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": { @@ -27,23 +28,6 @@ const scaling = (value, scaling_factor, base, max, min, maxCut, minCut) => { temp = Math.log(temp * (base ** scaling_factor - 1) + 1) / (Math.log(base) * scaling_factor); return value === 0 ? 0 : temp * (max - min) + min; }; -const DownloadIcon = (props) => { - return ( - - - - - - - ); -}; const transform = (data) => { let minbaseAcres = Infinity; let maxbaseAcres = -Infinity; diff --git a/src/components/title1/sideBar/SideBar.tsx b/src/components/title1/sideBar/SideBar.tsx index c2d7942b..a66b0b7d 100644 --- a/src/components/title1/sideBar/SideBar.tsx +++ b/src/components/title1/sideBar/SideBar.tsx @@ -1,16 +1,5 @@ import React, { useState } from "react"; -import { - Box, - Collapse, - Drawer, - List, - ListItem, - ListItemButton, - ListItemIcon, - ListItemText, - MenuItem, - Typography -} from "@mui/material"; +import { Box, Collapse, Drawer, List, ListItemButton, ListItemText } from "@mui/material"; import styled from "styled-components"; import { menu } from "./SideBarMenuItem"; import { hasChildren } from "./Utils"; @@ -25,8 +14,7 @@ let currentChecked = "0"; export default function SideBar({ setTitle1Checked }): JSX.Element { const [checked, setChecked] = React.useState(currentChecked); - const [disabled, setDisabled] = React.useState(false); - const [selectedItem, setSelectedItem] = useState("0"); // State to track selected item + const [selectedItem, setSelectedItem] = useState("0"); const handleToggle = (value: string) => () => { setChecked(value); setTitle1Checked(value); diff --git a/src/components/title1/sideBar/SideBarMenuItem.tsx b/src/components/title1/sideBar/SideBarMenuItem.tsx index fae4d21d..b54c49fa 100644 --- a/src/components/title1/sideBar/SideBarMenuItem.tsx +++ b/src/components/title1/sideBar/SideBarMenuItem.tsx @@ -20,37 +20,3 @@ export const menu = [ title: "Price Loss Coverage (PLC)" } ]; - -// { -// icon: "", -// title: "Dairy", -// items: [ -// { -// title: "Dairy Margin Coverage Program (DMC)" -// }, -// { -// title: "Dairy Indemnity Payment Program (DIPP)" -// } -// ] -// }, -// { -// icon: "", -// title: "Disaster Assistance", -// items: [ -// { -// title: "Livestock Forage Disaster Program (LFP)" -// }, -// { -// title: "Noninsured Crop Disaster Assistance Program (NAP)" -// }, -// { -// title: "Emergency Assistance for Livestock, Honeybees, and Farm-Raised Fish (ELAP)" -// }, -// { -// title: "Livestock Indemnity Program (LIP)" -// }, -// { -// title: "Tree Assistance Program (TAP)" -// } -// ] -// } diff --git a/src/data/allstates.json b/src/data/allstates.json deleted file mode 100644 index 1a7880d7..00000000 --- a/src/data/allstates.json +++ /dev/null @@ -1,62 +0,0 @@ -[ - { "id": "AL", "val": "01" }, - { "id": "AK", "val": "02" }, - { "id": "AS", "val": "60" }, - { "id": "AZ", "val": "04" }, - { "id": "AR", "val": "05" }, - { "id": "CA", "val": "06" }, - { "id": "CO", "val": "08" }, - { "id": "CT", "val": "09" }, - { "id": "DE", "val": "10" }, - { "id": "DC", "val": "11" }, - { "id": "FL", "val": "12" }, - { "id": "FM", "val": "64" }, - { "id": "GA", "val": "13" }, - { "id": "GU", "val": "66" }, - { "id": "HI", "val": "15" }, - { "id": "ID", "val": "16" }, - { "id": "IL", "val": "17" }, - { "id": "IN", "val": "18" }, - { "id": "IA", "val": "19" }, - { "id": "KS", "val": "20" }, - { "id": "KY", "val": "21" }, - { "id": "LA", "val": "22" }, - { "id": "ME", "val": "23" }, - { "id": "MH", "val": "68" }, - { "id": "MD", "val": "24" }, - { "id": "MA", "val": "25" }, - { "id": "MI", "val": "26" }, - { "id": "MN", "val": "27" }, - { "id": "MS", "val": "28" }, - { "id": "MO", "val": "29" }, - { "id": "MT", "val": "30" }, - { "id": "NE", "val": "31" }, - { "id": "NV", "val": "32" }, - { "id": "NH", "val": "33" }, - { "id": "NJ", "val": "34" }, - { "id": "NM", "val": "35" }, - { "id": "NY", "val": "36" }, - { "id": "NC", "val": "37" }, - { "id": "ND", "val": "38" }, - { "id": "MP", "val": "69" }, - { "id": "OH", "val": "39" }, - { "id": "OK", "val": "40" }, - { "id": "OR", "val": "41" }, - { "id": "PW", "val": "70" }, - { "id": "PA", "val": "42" }, - { "id": "PR", "val": "72" }, - { "id": "RI", "val": "44" }, - { "id": "SC", "val": "45" }, - { "id": "SD", "val": "46" }, - { "id": "TN", "val": "47" }, - { "id": "TX", "val": "48" }, - { "id": "UM", "val": "74" }, - { "id": "UT", "val": "49" }, - { "id": "VT", "val": "50" }, - { "id": "VA", "val": "51" }, - { "id": "VI", "val": "78" }, - { "id": "WA", "val": "53" }, - { "id": "WV", "val": "54" }, - { "id": "WI", "val": "55" }, - { "id": "WY", "val": "56" } -] diff --git a/src/data/stateCodes.json b/src/data/stateCodes.json deleted file mode 100644 index 7a4db7fd..00000000 --- a/src/data/stateCodes.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "AL": "Alabama", - "AK": "Alaska", - "AS": "American Samoa", - "AZ": "Arizona", - "AR": "Arkansas", - "CA": "California", - "CO": "Colorado", - "CT": "Connecticut", - "DE": "Delaware", - "DC": "District Of Columbia", - "FM": "Federated States Of Micronesia", - "FL": "Florida", - "GA": "Georgia", - "GU": "Guam", - "HI": "Hawaii", - "ID": "Idaho", - "IL": "Illinois", - "IN": "Indiana", - "IA": "Iowa", - "KS": "Kansas", - "KY": "Kentucky", - "LA": "Louisiana", - "ME": "Maine", - "MH": "Marshall Islands", - "MD": "Maryland", - "MA": "Massachusetts", - "MI": "Michigan", - "MN": "Minnesota", - "MS": "Mississippi", - "MO": "Missouri", - "MT": "Montana", - "NE": "Nebraska", - "NV": "Nevada", - "NH": "New Hampshire", - "NJ": "New Jersey", - "NM": "New Mexico", - "NY": "New York", - "NC": "North Carolina", - "ND": "North Dakota", - "MP": "Northern Mariana Islands", - "OH": "Ohio", - "OK": "Oklahoma", - "OR": "Oregon", - "PW": "Palau", - "PA": "Pennsylvania", - "PR": "Puerto Rico", - "RI": "Rhode Island", - "SC": "South Carolina", - "SD": "South Dakota", - "TN": "Tennessee", - "TX": "Texas", - "UT": "Utah", - "VT": "Vermont", - "VI": "Virgin Islands", - "VA": "Virginia", - "WA": "Washington", - "WV": "West Virginia", - "WI": "Wisconsin", - "WY": "Wyoming" -} diff --git a/src/main.tsx b/src/main.tsx index be345804..cf749636 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -5,6 +5,7 @@ import EQIPPage from "./pages/EQIPPage"; import CSPPage from "./pages/CSPPage"; import SNAPPage from "./pages/SNAPPage"; import TitleIPage from "./pages/TitleIPage"; +import CropInsurancePage from "./pages/CropInsurancePage"; const ScrollToTop = (props: any) => { const location = useLocation(); @@ -14,6 +15,7 @@ const ScrollToTop = (props: any) => { return <>{props.children}; }; + export default function Main(): JSX.Element { return ( @@ -22,6 +24,7 @@ export default function Main(): JSX.Element { } /> } /> } /> + } /> } /> diff --git a/src/pages/CropInsurancePage.tsx b/src/pages/CropInsurancePage.tsx new file mode 100644 index 00000000..e31df3b9 --- /dev/null +++ b/src/pages/CropInsurancePage.tsx @@ -0,0 +1,886 @@ +import Box from "@mui/material/Box"; +import * as React from "react"; +import TableChartIcon from "@mui/icons-material/TableChart"; +import InsertChartIcon from "@mui/icons-material/InsertChart"; +import { + CircularProgress, + createTheme, + Grid, + ThemeProvider, + ToggleButton, + ToggleButtonGroup, + Typography +} from "@mui/material"; +import NavBar from "../components/NavBar"; +import CropInsuranceMap from "../components/cropinsurance/CropInsuranceMap"; +import NavSearchBar from "../components/shared/NavSearchBar"; +import CropInsuranceProgramTable from "../components/cropinsurance/CropInsuranceTable"; +import SideBar from "../components/cropinsurance/sideBar/ShortSideBar"; +import { config } from "../app.config"; +import { convertAllState, getJsonDataFromUrl } from "../utils/apiutil"; +import "../styles/subpage.css"; +import "../styles/cropinsurance.css"; +import CropInsuranceBubble from "../components/cropinsurance/chart/CropInsuranceBubble"; +import CropInsuranceBars from "../components/cropinsurance/chart/CropInsuranceBars"; + +export default function CropInsurancePage(): JSX.Element { + const [tab, setTab] = React.useState(0); + const [stateDistributionData, setStateDistributionData] = React.useState({}); + const [stateCodesData, setStateCodesData] = React.useState({}); + const [allStatesData, setAllStatesData] = React.useState([]); + const [initChartWidthRatio, setChartMapWidthRatio] = React.useState(0.9); + const cropInsuranceDiv = React.useRef(null); + const [checked, setChecked] = React.useState("0"); + const mapColor = ["#C26C06", "#CCECE6", "#66C2A4", "#238B45", "#005C24"]; + + React.useEffect(() => { + const allstates_url = `${config.apiUrl}/states`; + getJsonDataFromUrl(allstates_url).then((response) => { + setAllStatesData(response); + }); + const statecode_url = `${config.apiUrl}/statecodes`; + getJsonDataFromUrl(statecode_url).then((response) => { + const converted_json = convertAllState(response); + setStateCodesData(converted_json); + }); + const statedistribution_url = `${config.apiUrl}/programs/crop-insurance/state-distribution`; + getJsonDataFromUrl(statedistribution_url).then((response) => { + setStateDistributionData(response); + }); + }, []); + + const switchChartTable = (event, newTab) => { + if (newTab !== null) { + setTab(newTab); + } + }; + const defaultTheme = createTheme(); + /** + * Derive TotalIndemnities, TotalFarmerPaidPremium, OriginalNetFarmerBenefit for the bubble chart + Set NetFarmerBenefit as 0. The post processing will be done in CropInsuranceBubble.tsx + */ + const setBubbleData = (year) => { + const res: any[] = []; + stateDistributionData[year].forEach((stateData) => { + const initObject = { + State: stateData.state, + TotalIndemnities: stateData.programs[0].totalIndemnitiesInDollars, + TotalFarmerPaidPremium: stateData.programs[0].totalFarmerPaidPremiumInDollars, + OriginalNetFarmerBenefit: stateData.programs[0].totalNetFarmerBenefitInDollars, + NetFarmerBenefit: 0 + }; + res.push(initObject); + }); + return res; + }; + return ( + + {Object.keys(stateCodesData).length > 0 && + Object.keys(allStatesData).length > 0 && + Object.keys(stateDistributionData).length > 0 ? ( + + + + + + + {/* Net Farmer Premium Section */} + + + + + + + + + + Comparison by States + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comparison by States + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comparison by States + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comparison by States + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Comparison by States + + + + + + + + + + + + + + + + + + + + + + {/* Loss Ratio Section */} + + + + + + + + + + + + + + {/* Liabilities Section */} + + + + + + + + + + + + + + {/* Policy Earning Premium Section */} + + + + + + + + + Comparison by States + + + + + + + + + + + + + + + + + + + + + {/* Average Acres Insured Section */} + + + + + + + + + + + + + + + ) : ( +
+ + Loading data... +
+ )} +
+ ); +} diff --git a/src/pages/SNAPPage.tsx b/src/pages/SNAPPage.tsx index 43945a45..85f0c668 100644 --- a/src/pages/SNAPPage.tsx +++ b/src/pages/SNAPPage.tsx @@ -367,6 +367,7 @@ export default function SNAPPage(): JSX.Element { {data ? ( { - setChartData(response); - }); const statedistribution_url = `${config.apiUrl}/programs/commodities/state-distribution`; getJsonDataFromUrl(statedistribution_url).then((response) => { setStateDistributionData(response); @@ -66,7 +60,6 @@ export default function TitleIPage(): JSX.Element { } }; const defaultTheme = createTheme(); - // } function prepData(program, subprogram, data, year) { const organizedData: Record[] = []; const originalData: Record[] = []; @@ -112,7 +105,6 @@ export default function TitleIPage(): JSX.Element { {Object.keys(stateCodesData).length > 0 && Object.keys(allStatesData).length > 0 && - Object.keys(chartData).length > 0 && Object.keys(stateDistributionData).length > 0 ? ( diff --git a/src/styles/cropinsurance.css b/src/styles/cropinsurance.css new file mode 100644 index 00000000..c052a842 --- /dev/null +++ b/src/styles/cropinsurance.css @@ -0,0 +1,19 @@ +#cropInsuranceTableHeader{ + padding-bottom: 1.5em; +} +#totalPremium .MuiSvgIcon-root, +#bothBar .MuiSvgIcon-root { + fill: #662500 +} +#totalPremium .Mui-checked, +#bothBar .Mui-checked { + color: #662500 +} +#farmerPaidPremium .Mui-checked, +#totalPoliciesEarningPremium .Mui-checked { + color: #B65700; +} +#premiumSubsidy .Mui-checked, +#totalIndemnities .Mui-checked { + color: #E2B12C; +} diff --git a/src/styles/subpage.css b/src/styles/subpage.css index c80d4fe1..e05bbcf0 100644 --- a/src/styles/subpage.css +++ b/src/styles/subpage.css @@ -1,7 +1,21 @@ -@media only screen and (min-width: 1680px) { - .sideBar #filler { - /* height: 11em; */ +.sideBar-short .MuiPaper-root { + box-sizing: border-box; + width: 300px; + height: auto; + overflow-x: hidden; + margin-bottom: 1em; + padding-bottom: 1em; + border-bottom: 0.1em solid #DDD; +} + + .fullWidthMainContent { + padding: 6em; } + .fullWidthMainContent .mapArea, .fullWidthMainContent .narrowChartArea{ + padding: 3em 1em 0 20em; + max-width: 65%; + } + .halfWidthMainContent { padding: 10em 10em 6em 25em; } @@ -18,15 +32,24 @@ } .stateTitleContainer .stateTitle { - font-weight: 700; margin-top: 0.5em; } -} -@media only screen and (max-width: 1679px) { - .sideBar #filler { - /* height: 11em; */ +@media only screen and (max-width: 1440px) { + + .sideBar-short .MuiPaper-root { + max-height: 99vh; + } + + .fullWidthMainContent { + padding: 5em; } + + .fullWidthMainContent .mapArea, .fullWidthMainContent .narrowChartArea{ + padding: 3em 1em 0 15em; + max-width: 65%; + } + .halfWidthMainContent { padding: 10em 5em 6em 25em; } @@ -43,7 +66,13 @@ } .stateTitleContainer .stateTitle { - font-weight: 700; margin-top: 0.5em; } + + .sideBar-short .MuiPaper-root { + max-height: 99vh; + } } + + + diff --git a/src/utils/legendConfig.json b/src/utils/legendConfig.json index c34db362..72937b00 100644 --- a/src/utils/legendConfig.json +++ b/src/utils/legendConfig.json @@ -1,6 +1,7 @@ { "18-22 All Programs Total": [5000000000, 10000000000, 30000000000, 40000000000], - "Crop Insurance Total":[0, 500000000, 1000000000, 2000000000], + "Crop Insurance Total":[0, 500000000, 1000000000, 5000000000], + "totalNetFarmerBenefit": [0, 500000000, 1000000000, 5000000000], "Title I Total":[100000000, 500000000, 1000000000, 2000000000], "Total Commodities Programs": [100000000, 500000000, 1000000000, 2000000000], "Title II Total":[100000000, 500000000, 1000000000, 2000000000], @@ -8,5 +9,14 @@ "Agriculture Risk Coverage (ARC)": [10000000, 50000000, 100000000, 500000000], "Agriculture Risk Coverage County Option (ARC-CO)": [50000000, 100000000, 250000000, 500000000], "Agriculture Risk Coverage Individual Coverage (ARC-IC)": [5000000, 10000000, 25000000, 50000000], - "Price Loss Coverage (PLC)":[50000000, 100000000, 500000000, 1000000000] + "Price Loss Coverage (PLC)":[50000000, 100000000, 500000000, 1000000000], + "totalIndemnities":[10000000, 100000000, 1000000000, 5000000000], + "totalPremium":[10000000, 100000000, 1000000000, 5000000000], + "totalPremiumSubsidy": [5000000, 100000000, 500000000, 1000000000], + "totalFarmerPaidPremium": [10000000,100000000, 500000000,1000000000], + "totalPoliciesEarningPremium": [1000, 10000, 100000, 500000], + "averageLiabilities":[50000000, 1000000000, 5000000000, 10000000000], + "lossRatio":[0.5, 0.75, 1, 1.5], + "averageInsuredAreaInAcres":[10000, 5000000, 10000000, 20000000] } +