From cf9e36cc9998c7ec66f218d7a853b84636e675e4 Mon Sep 17 00:00:00 2001 From: lublagg Date: Wed, 13 Sep 2023 17:59:03 -0400 Subject: [PATCH 1/8] User can select a year from dropdown menu. --- package-lock.json | 11 ++ package.json | 1 + src/components/app.tsx | 38 +++--- src/components/attribute-options.tsx | 7 +- src/components/constants.ts | 36 +++++- src/components/dropdown.tsx | 36 ++---- src/components/options.tsx | 4 +- src/components/place-options.tsx | 40 +++--- src/components/summary.tsx | 53 ++++++++ src/components/types.ts | 2 +- src/components/years-options.tsx | 26 ++++ src/scripts/api.ts | 23 ++++ src/scripts/query-headers.ts | 182 +++++++++++++++++++++++++++ 13 files changed, 379 insertions(+), 80 deletions(-) create mode 100644 src/components/summary.tsx create mode 100644 src/components/years-options.tsx create mode 100644 src/scripts/api.ts create mode 100644 src/scripts/query-headers.ts diff --git a/package-lock.json b/package-lock.json index 4eda71c..801b9e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@testing-library/user-event": "^13.5.0", "axios": "^1.5.0", "classnames": "^2.3.2", + "fetch-jsonp": "^1.3.0", "iframe-phone": "^1.3.1", "react": "^17.0.2", "react-dom": "^17.0.2", @@ -9247,6 +9248,11 @@ "pend": "~1.2.0" } }, + "node_modules/fetch-jsonp": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fetch-jsonp/-/fetch-jsonp-1.3.0.tgz", + "integrity": "sha512-hxCYGvmANEmpkHpeWY8Kawfa5Z1t2csTpIClIDG/0S92eALWHRU1RnGaj86Tf5Cc0QF+afSa4SQ4pFB2rFM5QA==" + }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -26279,6 +26285,11 @@ "pend": "~1.2.0" } }, + "fetch-jsonp": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fetch-jsonp/-/fetch-jsonp-1.3.0.tgz", + "integrity": "sha512-hxCYGvmANEmpkHpeWY8Kawfa5Z1t2csTpIClIDG/0S92eALWHRU1RnGaj86Tf5Cc0QF+afSa4SQ4pFB2rFM5QA==" + }, "figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", diff --git a/package.json b/package.json index 6b3d26e..b801767 100644 --- a/package.json +++ b/package.json @@ -137,6 +137,7 @@ "@testing-library/user-event": "^13.5.0", "axios": "^1.5.0", "classnames": "^2.3.2", + "fetch-jsonp": "^1.3.0", "iframe-phone": "^1.3.1", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/src/components/app.tsx b/src/components/app.tsx index 6ad8457..51613dc 100644 --- a/src/components/app.tsx +++ b/src/components/app.tsx @@ -1,16 +1,21 @@ -import React, {useState} from "react"; +import React, {useEffect, useState} from "react"; import { Dropdown } from "./dropdown"; import classnames from "classnames"; import { Information } from "./information"; -import { defaultSelectedOptions } from "./constants"; +import { categories, defaultSelectedOptions } from "./constants"; import { IStateOptions } from "./types"; import css from "./app.scss"; +import { runTestQuery } from "../scripts/api"; function App() { const [showInfo, setShowInfo] = useState(false); const [selectedOptions, setSelectedOptions] = useState(defaultSelectedOptions); + useEffect(() => { + runTestQuery(); + }, []) + const handleSetSelectedOptions = (option: string, value: string | string[]) => { const newSelectedOptions = {...selectedOptions, [option]: value}; setSelectedOptions(newSelectedOptions); @@ -38,24 +43,17 @@ function App() {
- - - + {categories.map((cat) => { + return ( + + ) + })}
diff --git a/src/components/attribute-options.tsx b/src/components/attribute-options.tsx index 4dbdc65..6e373a5 100644 --- a/src/components/attribute-options.tsx +++ b/src/components/attribute-options.tsx @@ -34,10 +34,11 @@ export const AttributeOptions: React.FC = (props) => { attributeOptions.map((attr) => { return ( <> - {attr.label &&
{attr.label}
} - {attr.instructions &&
{attr.instructions}
} -
+ {attr.label &&
{attr.label}
} + {attr.instructions &&
{attr.instructions}
} +
= 1910; year--) { + yearsArray.push(`${year}`); +} + +export const yearsOptions: IAttrOptions = { + key: "years", + label: "Years", + options: yearsArray, + instructions: null +} + +export const categories = [ + {header: "Place", options: placeOptions, altText: ""}, + {header: "Attributes", options: attributeOptions, altText: ""}, + {header: "Years", options: yearsOptions, altText: ""} +] + export const defaultSelectedOptions: IStateOptions = { "geographicLevel": "", "states": [], @@ -103,4 +127,4 @@ export const defaultSelectedOptions: IStateOptions = { "cropUnits": "", "crops": [], "years": [] -}; +}; \ No newline at end of file diff --git a/src/components/dropdown.tsx b/src/components/dropdown.tsx index 29a237a..2ab0357 100644 --- a/src/components/dropdown.tsx +++ b/src/components/dropdown.tsx @@ -5,55 +5,37 @@ import { defaultSelectedOptions } from "./constants"; import { AttributeOptions } from "./attribute-options"; import css from "./dropdown.scss"; +import { YearsOptions } from "./years-options"; +import { Summary } from "./summary"; interface IProps { - sectionName: string + category: string sectionAltText: string handleSetSelectedOptions: (option: string, value: string|string[]) => void selectedOptions: typeof defaultSelectedOptions; } export const Dropdown: React.FC = (props) => { - const {sectionName, sectionAltText, handleSetSelectedOptions, selectedOptions} = props; + const {category, sectionAltText, handleSetSelectedOptions, selectedOptions} = props; const [showItems, setShowItems] = useState(false); const handleClick = () => { setShowItems(!showItems); }; - const renderSummary = () => { - if (sectionName === "Place") { - const place = selectedOptions.geographicLevel || ""; - const states = selectedOptions.states.join(`, `); - return ( - `${place}: ${states}` - ); - } else if (sectionName === "Attributes") { - return ( - "" - ); - } - }; - const commonProps = {handleSetSelectedOptions, selectedOptions}; const renderOptions = () => { - if (sectionName === "Place") { - return ( - - ); - } else if (sectionName === "Attributes") { - return ( - - ); - } + return category === "Place" ? : + category === "Attributes" ? : + }; return (
- {sectionName} -
{renderSummary()}
+ {category} +
diff --git a/src/components/options.tsx b/src/components/options.tsx index 85dc513..023c494 100644 --- a/src/components/options.tsx +++ b/src/components/options.tsx @@ -29,7 +29,9 @@ export const Options: React.FC = (props) => { if (e.currentTarget.checked) { newArray.push(e.target.value); newArray.sort(); - + if (optionKey === "years") { + newArray.reverse(); + } } else { if (isOptionSelected(e.target.value)) { newArray = newArray.filter((o) => o !== e.target.value); diff --git a/src/components/place-options.tsx b/src/components/place-options.tsx index 58a0ad4..f165fbd 100644 --- a/src/components/place-options.tsx +++ b/src/components/place-options.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { placeOptions, stateOptions } from "./constants"; +import { placeOptions } from "./constants"; import { IStateOptions } from "./types"; import { Options } from "./options"; @@ -12,29 +12,25 @@ interface IProps { export const PlaceOptions: React.FC = (props) => { const {handleSetSelectedOptions, selectedOptions} = props; - - const commonProps = {selectedOptions, handleSetSelectedOptions}; - return ( <> -
{placeOptions.label}:
-
- -
-
{stateOptions.label}:
-
- -
+ {placeOptions.map((placeOpt) => { + return ( + <> +
{placeOpt.instructions}:
+
+ +
+ + ); + })} ); }; diff --git a/src/components/summary.tsx b/src/components/summary.tsx new file mode 100644 index 0000000..f02a931 --- /dev/null +++ b/src/components/summary.tsx @@ -0,0 +1,53 @@ +import React from "react"; +import { IStateOptions } from "./types"; +import { attributeOptions } from "./constants"; + +interface IProps { + category: string; + selectedOptions: IStateOptions; +} + +export const Summary: React.FC = ({category, selectedOptions}) => { + + const getSummaryText = () => { + if (category === "Place") { + const place = selectedOptions.geographicLevel || ""; + const states = selectedOptions.states.join(`, `); + const colon = place && states ? ": " : ""; + return ( + `${place}${colon}${states}` + ); + } else if (category === "Attributes") { + const resultString = attributeOptions.filter((attr) => { + const value = selectedOptions[attr.key]; + const valueIsArrayWithLength = Array.isArray(value) && value.length > 0; + const valueIsDefined = typeof value === "string" && value; + return valueIsArrayWithLength || valueIsDefined; + }).map((attr) => { + const value = selectedOptions[attr.key]; + const valueIsArrayWithLength = Array.isArray(value) && value.length > 0; + const valueIsDefined = typeof value === "string" && value; + const label = attr.label && (valueIsArrayWithLength || valueIsDefined) ? `${attr.label}: ` : ""; + + if (Array.isArray(value) && value.length > 0) { + return `${label}${value.join(", ")}`; + } else if (value) { + return `${attr.label}: ${value}`; + } else { + return null; + } + }) + .filter((item) => item !== null) + const finalString = resultString.join(", "); + return finalString; + } else if (category === "Years") { + return selectedOptions.years.join(", ") + } + } + + return ( +
+ {getSummaryText()} +
+ ) +} \ No newline at end of file diff --git a/src/components/types.ts b/src/components/types.ts index d9f23a9..6eaa4a9 100644 --- a/src/components/types.ts +++ b/src/components/types.ts @@ -14,6 +14,6 @@ export type OptionKey = keyof IStateOptions; export interface IAttrOptions { key: keyof IStateOptions, label: string|null, - options: string[] + options: string[], instructions: string|null } diff --git a/src/components/years-options.tsx b/src/components/years-options.tsx new file mode 100644 index 0000000..d9b605a --- /dev/null +++ b/src/components/years-options.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import { yearsOptions } from "./constants"; +import { IStateOptions } from "./types"; +import { Options } from "./options"; + +import css from "./options.scss"; + +interface IProps { + handleSetSelectedOptions: (option: string, value: string|string[]) => void; + selectedOptions: IStateOptions; +} + +export const YearsOptions: React.FC = (props) => { + const {handleSetSelectedOptions, selectedOptions} = props; + return ( +
+ +
+ ); +}; diff --git a/src/scripts/api.ts b/src/scripts/api.ts new file mode 100644 index 0000000..5cfd9ef --- /dev/null +++ b/src/scripts/api.ts @@ -0,0 +1,23 @@ +import fetchJsonp from "fetch-jsonp"; +import { queryData } from "./query-headers"; + +const baseURL = `https://quickstats.nass.usda.gov/api/api_GET/?key=9ED0BFB8-8DDD-3609-9940-A2341ED6A9E3`; + +export const createRequest = (attribute: string, geoLevel: string, location: string, year: string) => { + const queryParams = queryData.find((d) => d.plugInAttribute === attribute); + const {sector, group, commodity, category, domains, dataItem} = queryParams!; + const req = `${baseURL}§_desc=${sector}&group_desc=${group}&commodity_desc=${commodity}&statisticcat_desc=${category}&domain_desc=${domains}&short_desc=${dataItem}&agg_level_desc=${geoLevel}&state_name=${location}&year=${year}`; + return req; +}; + +export const runTestQuery = () => { + const request = createRequest("Total Farmers", "STATE", "CALIFORNIA", "2017"); + fetchJsonp(request) + .then(function(response) { + return response.json(); + }).then(function(json) { + console.log("parsed json", json); + }).catch(function(ex) { + console.log("parsing failed", ex); + }) +}; diff --git a/src/scripts/query-headers.ts b/src/scripts/query-headers.ts new file mode 100644 index 0000000..e487f4a --- /dev/null +++ b/src/scripts/query-headers.ts @@ -0,0 +1,182 @@ + +interface IQueryHeaders { + plugInAttribute: string, + numberOfAttributeColumnsInCodap: number|string, + sector: string, + group: string, + commodity: string, + category: string + dataItem: string|string[], + domains: string, + geographicLevels?: string, + years?: string +} + +const sharedDemographicHeaders = { +sector: "Demographics", +group: "Producers", +commodity: "Producers", +category: "Producers", +domains: "Total", +}; + +const sharedEconomicHeaders = { +sector: "Economics", +group: "Farms & Land & Assets", +commodity: "Farm Operations", +category: "Operations" +}; + +const sharedLaborHeaders = { +sector: "Economics", +group: "Expenses", +commodity: "Labor", +}; + +export const queryData: Array = [ +{ + plugInAttribute: "Total Farmers", + numberOfAttributeColumnsInCodap: 1, + ...sharedDemographicHeaders, + dataItem: "PRODUCERS, (ALL) - NUMBER OF PRODUCERS", +}, +{ + plugInAttribute: "Age", + numberOfAttributeColumnsInCodap: 7, + ...sharedDemographicHeaders, + dataItem: [ + "PRODUCERS, AGE LT 25 - NUMBER OF PRODUCERS", + "PRODUCERS, AGE 25 TO 34 - NUMBER OF PRODUCERS", + "PRODUCERS, AGE 35 TO 44 - NUMBER OF PRODUCERS", + "PRODUCERS, AGE 45 TO 54 - NUMBER OF PRODUCERS", + "PRODUCERS, AGE 55 TO 64 - NUMBER OF PRODUCERS", + "PRODUCERS, AGE 65 TO 74 - NUMBER OF PRODUCERS", + "PRODUCERS, AGE GE 75 - NUMBER OF PRODUCERS" + ], + +}, +{ + plugInAttribute: "Gender", + numberOfAttributeColumnsInCodap: 2, + ...sharedDemographicHeaders, + dataItem: [ + "PRODUCERS, (ALL), FEMALE - NUMBER OF PRODUCERS", + "PRODUCERS, (ALL), MALE - NUMBER OF PRODUCERS" + ], +}, +{ + plugInAttribute: "Race", + numberOfAttributeColumnsInCodap: 7, + ...sharedDemographicHeaders, + dataItem: [ + "PRODUCERS, AMERICAN INDIAN OR ALASKAN NATIVE - NUMBER OF PRODUCERS", + "PRODUCERS, ASIAN - NUMBER OF PRODUCERS", + "PRODUCERS, BLACK OR AFRICAN AMERICAN - NUMBER OF PRODUCERS", + "PRODUCERS, HISPANIC - NUMBER OF PRODUCERS", + "PRODUCERS, MULTI-RACE - NUMBER OF PRODUCERS", + "PRODUCERS, NATIVE HAWAIIAN OR OTHER PACIFIC ISLANDERS - NUMBER OF PRODUCERS", + "PRODUCERS, WHITE - NUMBER OF PRODUCERS" + ] +}, +{ + plugInAttribute: "Total Farms", + numberOfAttributeColumnsInCodap: 1, + ...sharedEconomicHeaders, + dataItem: "Farm Operations - Number of Operations", + domains: "Total", + geographicLevels: "State, County", + years: "1910 - 2022" +}, +{ + plugInAttribute: "Organization Type", + numberOfAttributeColumnsInCodap: 5, + sector: "Demographics", + group: "Farms & Land & Assets", + commodity: "Farm Operations", + category: "Operations", + dataItem: [ + "FARM OPERATIONS, ORGANIZATION, TAX PURPOSES, CORPORATION (EXCL FAMILY HELD) - NUMBER OF OPERATIONS", + "FARM OPERATIONS, ORGANIZATION, TAX PURPOSES, CORPORATION, FAMILY HELD - NUMBER OF OPERATIONS", + "FARM OPERATIONS, ORGANIZATION, TAX PURPOSES, FAMILY & INDIVIDUAL - NUMBER OF OPERATIONS", + "FARM OPERATIONS, ORGANIZATION, TAX PURPOSES, INSTITUTIONAL & RESEARCH & RESERVATION & OTHER - NUMBER OF OPERATIONS", + "FARM OPERATIONS, ORGANIZATION, TAX PURPOSES, PARTNERSHIP - NUMBER OF OPERATIONS" + ], + domains: "Total", + geographicLevels: "State, County", + years: "County: 1997, 2002, 2007, 2012, 2017\nState: 1910 - 2022" +}, +{ + plugInAttribute: "Economic Class", + numberOfAttributeColumnsInCodap: "3 - 6", + ...sharedEconomicHeaders, + dataItem: "Farm Operations - Number of Operations", + domains: "Economic Class", + geographicLevels: "State ", + years: "1987 - 2022" +}, +{ + plugInAttribute: "Acres Operated", + numberOfAttributeColumnsInCodap: 14, + sector: "Economics", + group: "Farms & Land & Assets", + commodity: "Farm Operations", + category: "Area Operated", + dataItem: "Farm Operations - Acres Operated", + domains: "Area Operated", + geographicLevels: "State, County", + years: "1997, 2002, 2007, 2012, 2017" +}, +{ + plugInAttribute: "Organic", + numberOfAttributeColumnsInCodap: 1, + ...sharedEconomicHeaders, + dataItem: "Farm Operations, Organic - Number of Operations", + domains: "Organic Status" +}, +{ + plugInAttribute: "Labor Status", + numberOfAttributeColumnsInCodap: 3, + ...sharedLaborHeaders, + category: "Workers", + dataItem: [ + "LABOR, MIGRANT - NUMBER OF WORKERS", + "LABOR, UNPAID - NUMBER OF WORKERS", + "LABOR, HIRED - NUMBER OF WORKERS" + ], + domains: "Total", + geographicLevels: "State, County", + years: "2012, 2017" +}, +{ + plugInAttribute: "Wages", + numberOfAttributeColumnsInCodap: 1, + ...sharedLaborHeaders, + category: "Wage Rate", + dataItem: "LABOR, HIRED - WAGE RATE, MEASURED IN $/HOUR", + domains: "Total", + geographicLevels: "Region: Multi-state", + years: "1989 - 2022", +}, +{ + plugInAttribute: "Time Worked", + numberOfAttributeColumnsInCodap: 1, + ...sharedLaborHeaders, + category: "Wage Rate", + dataItem: "Labor, Hired - Time Worked, Measured in Hours/Week", + domains: "Total", + geographicLevels: "Region: Multi-state", + years: "1989 - 2022", +}, +{ + plugInAttribute: "Corn", + numberOfAttributeColumnsInCodap: 1, + sector: "Crops", + group: "Field Crops", + commodity: "Corn", + category: "Yield", + dataItem: "Corn, Grain - Yield, measured in BU / acre", + domains: "Total", + geographicLevels: "State, County", + years: "County: 1910 - 2022\nState:1866 - 2022 (only add 1910 - 2022)", +} +]; From 4f69f8e803c880fde6e75f7a1ce0a4e17c3d33bb Mon Sep 17 00:00:00 2001 From: lublagg Date: Thu, 14 Sep 2023 15:17:16 -0400 Subject: [PATCH 2/8] Check if years are valid. --- src/components/years-options.tsx | 37 +++++- src/scripts/query-headers.ts | 220 ++++++++++++++++++++++++++++--- 2 files changed, 240 insertions(+), 17 deletions(-) diff --git a/src/components/years-options.tsx b/src/components/years-options.tsx index d9b605a..524b1f7 100644 --- a/src/components/years-options.tsx +++ b/src/components/years-options.tsx @@ -1,9 +1,10 @@ import React from "react"; -import { yearsOptions } from "./constants"; +import { attributeOptions, yearsOptions } from "./constants"; import { IStateOptions } from "./types"; import { Options } from "./options"; import css from "./options.scss"; +import { queryData } from "../scripts/query-headers"; interface IProps { handleSetSelectedOptions: (option: string, value: string|string[]) => void; @@ -12,6 +13,38 @@ interface IProps { export const YearsOptions: React.FC = (props) => { const {handleSetSelectedOptions, selectedOptions} = props; + + const handleSelectYear = (yearKey: string, years: string|string[]) => { + handleSetSelectedOptions(yearKey, years); + + // check if any attributeOption keys have values in selectedOptions + const attrKeys = attributeOptions.map((attr) => attr.key); + const selectedAttrKeys = attrKeys.filter((key) => selectedOptions[key].length > 0); + const areAnyAttrsSelected = selectedAttrKeys.length > 0; + // if any attributes are selected, check that selected year data is available for that selected attribute + if (areAnyAttrsSelected) { + const selectedYears = Array.isArray(years) ? years : [years]; + const selectedAttrs = selectedAttrKeys.map((key) => selectedOptions[key]); + selectedAttrs.forEach((attr) => { + if (Array.isArray(attr)) { + attr.forEach((subAttr) => { + const subAttrData = queryData.find((d) => d.plugInAttribute === subAttr); + const yearKeyToUse = selectedOptions.geographicLevel === "county" ? "county" : "state"; + const availableYears = subAttrData?.years[yearKeyToUse]; + if (availableYears) { + // check -- are selectedYears included in queryParams?.years[yearKeyTouse] ? + const areYearsValid = selectedYears.map((y) => availableYears.indexOf(y) > -1).indexOf(false) < 0; + console.log({areYearsValid}); + console.log({availableYears}); + console.log({selectedYears}); + // do something if years are not valid + } + }) + } + }) + } + } + return (
= (props) => { optionKey={yearsOptions.key} inputType={"checkbox"} selectedOptions={selectedOptions} - handleSetSelectedOptions={handleSetSelectedOptions} + handleSetSelectedOptions={handleSelectYear} />
); diff --git a/src/scripts/query-headers.ts b/src/scripts/query-headers.ts index e487f4a..311211f 100644 --- a/src/scripts/query-headers.ts +++ b/src/scripts/query-headers.ts @@ -1,3 +1,9 @@ +const areaHarvested = "Area Harvested"; +const yieldInBU = "Yield"; +interface ICropDataItem { + [areaHarvested]: string, + [yieldInBU]: string +} interface IQueryHeaders { plugInAttribute: string, @@ -5,11 +11,14 @@ interface IQueryHeaders { sector: string, group: string, commodity: string, - category: string - dataItem: string|string[], + category: string|ICropDataItem, + dataItem: string|string[]|ICropDataItem, domains: string, geographicLevels?: string, - years?: string + years: { + county: string[] + state: string[] + } } const sharedDemographicHeaders = { @@ -33,12 +42,21 @@ group: "Expenses", commodity: "Labor", }; +const allYears = []; +for (let year = 2022; year >= 1910; year--) { + allYears.push(`${year}`); +} + export const queryData: Array = [ { plugInAttribute: "Total Farmers", numberOfAttributeColumnsInCodap: 1, ...sharedDemographicHeaders, dataItem: "PRODUCERS, (ALL) - NUMBER OF PRODUCERS", + years: { + county: ["2017"], + state: ["2017"] + } }, { plugInAttribute: "Age", @@ -53,6 +71,10 @@ export const queryData: Array = [ "PRODUCERS, AGE 65 TO 74 - NUMBER OF PRODUCERS", "PRODUCERS, AGE GE 75 - NUMBER OF PRODUCERS" ], + years: { + county: ["2017"], + state: ["2017"] + } }, { @@ -63,6 +85,10 @@ export const queryData: Array = [ "PRODUCERS, (ALL), FEMALE - NUMBER OF PRODUCERS", "PRODUCERS, (ALL), MALE - NUMBER OF PRODUCERS" ], + years: { + county: ["2017"], + state: ["2017"] + } }, { plugInAttribute: "Race", @@ -76,7 +102,11 @@ export const queryData: Array = [ "PRODUCERS, MULTI-RACE - NUMBER OF PRODUCERS", "PRODUCERS, NATIVE HAWAIIAN OR OTHER PACIFIC ISLANDERS - NUMBER OF PRODUCERS", "PRODUCERS, WHITE - NUMBER OF PRODUCERS" - ] + ], + years: { + county: ["2017"], + state: ["2017"] + } }, { plugInAttribute: "Total Farms", @@ -85,7 +115,10 @@ export const queryData: Array = [ dataItem: "Farm Operations - Number of Operations", domains: "Total", geographicLevels: "State, County", - years: "1910 - 2022" + years: { + county: allYears, + state: allYears + } }, { plugInAttribute: "Organization Type", @@ -103,7 +136,10 @@ export const queryData: Array = [ ], domains: "Total", geographicLevels: "State, County", - years: "County: 1997, 2002, 2007, 2012, 2017\nState: 1910 - 2022" + years: { + county: ["1997", "2002", "2007", "2012", "2017"], + state: allYears + } }, { plugInAttribute: "Economic Class", @@ -112,7 +148,10 @@ export const queryData: Array = [ dataItem: "Farm Operations - Number of Operations", domains: "Economic Class", geographicLevels: "State ", - years: "1987 - 2022" + years: { + county: allYears.filter(y => Number(y) >= 1987), + state: allYears.filter(y => Number(y) >= 1987) + } }, { plugInAttribute: "Acres Operated", @@ -124,14 +163,21 @@ export const queryData: Array = [ dataItem: "Farm Operations - Acres Operated", domains: "Area Operated", geographicLevels: "State, County", - years: "1997, 2002, 2007, 2012, 2017" + years: { + county: ["1997", "2002", "2007", "2012", "2017"], + state: ["1997", "2002", "2007", "2012", "2017"] + } }, { plugInAttribute: "Organic", numberOfAttributeColumnsInCodap: 1, ...sharedEconomicHeaders, dataItem: "Farm Operations, Organic - Number of Operations", - domains: "Organic Status" + domains: "Organic Status", + years: { + county: ["2008", "2011", "2012", "2014", "2015", "2016", "2017", "2019", "2021"], + state: ["2008", "2011", "2012", "2014", "2015", "2016", "2017", "2019", "2021"] + } }, { plugInAttribute: "Labor Status", @@ -145,7 +191,10 @@ export const queryData: Array = [ ], domains: "Total", geographicLevels: "State, County", - years: "2012, 2017" + years: { + county: ["2012", "2017"], + state: ["2012", "2017"] + } }, { plugInAttribute: "Wages", @@ -155,7 +204,10 @@ export const queryData: Array = [ dataItem: "LABOR, HIRED - WAGE RATE, MEASURED IN $/HOUR", domains: "Total", geographicLevels: "Region: Multi-state", - years: "1989 - 2022", + years: { + county: allYears.filter(y => Number(y) >= 1989), + state: allYears.filter(y => Number(y) >= 1989) + } }, { plugInAttribute: "Time Worked", @@ -165,7 +217,10 @@ export const queryData: Array = [ dataItem: "Labor, Hired - Time Worked, Measured in Hours/Week", domains: "Total", geographicLevels: "Region: Multi-state", - years: "1989 - 2022", + years: { + county: allYears.filter(y => Number(y) >= 1989), + state: allYears.filter(y => Number(y) >= 1989) + } }, { plugInAttribute: "Corn", @@ -173,10 +228,145 @@ export const queryData: Array = [ sector: "Crops", group: "Field Crops", commodity: "Corn", - category: "Yield", - dataItem: "Corn, Grain - Yield, measured in BU / acre", + category: { + [areaHarvested]: "Area Harvested", + [yieldInBU]: "Yield" + }, + dataItem: { + [areaHarvested]: "Corn, Grain - Acres Harvested", + [yieldInBU]: "Corn, Grain - Yield, measured in BU / acre" + }, domains: "Total", geographicLevels: "State, County", - years: "County: 1910 - 2022\nState:1866 - 2022 (only add 1910 - 2022)", + years: { + county: allYears, + state: allYears + } +}, +{ + plugInAttribute: "Cotton", + numberOfAttributeColumnsInCodap: 1, + sector: "Crops", + group: "Field Crops", + commodity: "Cotton", + category: { + [areaHarvested]: "Area Harvested", + [yieldInBU]: "Yield" + }, + dataItem: { + [areaHarvested]: "Cotton - Acres Harvested", + [yieldInBU]: "Cotton - Yield, measured in LB / acre" + }, + domains: "Total", + geographicLevels: "State, County", + years: { + county: allYears, + state: allYears + } +}, +{ + plugInAttribute: "Grapes", + numberOfAttributeColumnsInCodap: 1, + sector: "Crops", + group: "Fruit & Tree Nuts", + commodity: "Grapes", + category: { + [areaHarvested]: "Area Harvested", + [yieldInBU]: "Yield" + }, + dataItem: { + [areaHarvested]: "Grapes, Organic - Acres Harvested", + [yieldInBU]: "Grapes - Yield, measured in tons / acre" + }, + domains: "Total", + geographicLevels: "State, County", + years: { + county: allYears, + state: allYears + } +}, +{ + plugInAttribute: "Grasses", + numberOfAttributeColumnsInCodap: 1, + sector: "Crops", + group: "Field Crops", + commodity: "Grasses", + category: { + [areaHarvested]: "Area Harvested", + [yieldInBU]: "Yield" + }, + dataItem: { + [areaHarvested]: "", + [yieldInBU]: "" + }, + domains: "Total", + geographicLevels: "State, County", + years: { + county: allYears, + state: allYears + } +}, +{ + plugInAttribute: "Oats", + numberOfAttributeColumnsInCodap: 1, + sector: "Crops", + group: "Field Crops", + commodity: "Oats", + category: { + [areaHarvested]: "Area Harvested", + [yieldInBU]: "Yield" + }, + dataItem: { + [areaHarvested]: "Oats - Acres Harvested", + [yieldInBU]: "Oats - Yield, measured in BU / acre" + }, + domains: "Total", + geographicLevels: "State, County", + years: { + county: allYears, + state: allYears + } +}, +{ + plugInAttribute: "Soybeans", + numberOfAttributeColumnsInCodap: 1, + sector: "Crops", + group: "Field Crops", + commodity: "Soybeans", + category: { + [areaHarvested]: "Area Harvested", + [yieldInBU]: "Yield" + }, + dataItem: { + [areaHarvested]: "Soybeans - Acres Harvested", + [yieldInBU]: "Soybeans - Yield, measured in BU / acre" + }, + domains: "Total", + geographicLevels: "State, County", + years: { + county: allYears, + state: allYears + } +}, +{ + plugInAttribute: "Wheat", + numberOfAttributeColumnsInCodap: 1, + sector: "Crops", + group: "Field Crops", + commodity: "Wheat", + category: { + [areaHarvested]: "Area Harvested", + [yieldInBU]: "Yield" + }, + dataItem: { + [areaHarvested]: "Wheat - Acres Harvested", + [yieldInBU]: "Wheat - Yield, measured in BU / acre" + }, + domains: "Total", + geographicLevels: "State, County", + years: { + county: allYears, + state: allYears + } } ]; From a24f45aa2b21cf59e54d7a46ce42381f344ffdcf Mon Sep 17 00:00:00 2001 From: lublagg Date: Thu, 14 Sep 2023 21:49:46 -0400 Subject: [PATCH 3/8] Checkpoint --- src/components/app.tsx | 27 +++++++++-- src/components/constants.ts | 11 +++-- src/components/dropdown.tsx | 2 +- src/components/summary.tsx | 10 ++-- src/components/years-options.tsx | 6 +-- src/scripts/api.ts | 83 +++++++++++++++++++++++++++++--- src/scripts/connect.js | 82 ++++++++++++++++++++++++++++++- 7 files changed, 195 insertions(+), 26 deletions(-) diff --git a/src/components/app.tsx b/src/components/app.tsx index 51613dc..0cce018 100644 --- a/src/components/app.tsx +++ b/src/components/app.tsx @@ -4,17 +4,22 @@ import classnames from "classnames"; import { Information } from "./information"; import { categories, defaultSelectedOptions } from "./constants"; import { IStateOptions } from "./types"; +import { createQueryFromSelections } from "../scripts/api"; +import { connect } from "../scripts/connect"; + import css from "./app.scss"; -import { runTestQuery } from "../scripts/api"; function App() { const [showInfo, setShowInfo] = useState(false); const [selectedOptions, setSelectedOptions] = useState(defaultSelectedOptions); useEffect(() => { - runTestQuery(); - }, []) + const init = async () => { + await connect.initialize(); + }; + init(); + }, []); const handleSetSelectedOptions = (option: string, value: string | string[]) => { const newSelectedOptions = {...selectedOptions, [option]: value}; @@ -25,6 +30,18 @@ function App() { setShowInfo(true); }; + const handleGetData = () => { + createQueryFromSelections(selectedOptions); + + const makeDataSetAndTable = async () => { + const dS = await connect.guaranteeDataset(selectedOptions.geographicLevel); + if (dS.success) { + await connect.makeCaseTableAppear(); + } + }; + makeDataSetAndTable(); + }; + return (
{ showInfo && @@ -52,12 +69,12 @@ function App() { handleSetSelectedOptions={handleSetSelectedOptions} selectedOptions={selectedOptions} /> - ) + ); })}
- +
diff --git a/src/components/constants.ts b/src/components/constants.ts index 6cf2da9..ef141c0 100644 --- a/src/components/constants.ts +++ b/src/components/constants.ts @@ -12,6 +12,7 @@ export const stateOptions: IAttrOptions = { key: "states", instructions: "Choose states to include in your dataset from the list below", options: [ + "All States", "Alabama", "Alaska", "Arizona", @@ -110,21 +111,21 @@ export const yearsOptions: IAttrOptions = { label: "Years", options: yearsArray, instructions: null -} +}; export const categories = [ {header: "Place", options: placeOptions, altText: ""}, {header: "Attributes", options: attributeOptions, altText: ""}, {header: "Years", options: yearsOptions, altText: ""} -] +]; export const defaultSelectedOptions: IStateOptions = { - "geographicLevel": "", - "states": [], + "geographicLevel": "State", + "states": ["All States"], "farmerDemographics": [], "farmDemographics": [], "economicsAndWages": [], "cropUnits": "", "crops": [], "years": [] -}; \ No newline at end of file +}; diff --git a/src/components/dropdown.tsx b/src/components/dropdown.tsx index 2ab0357..27e56a3 100644 --- a/src/components/dropdown.tsx +++ b/src/components/dropdown.tsx @@ -28,7 +28,7 @@ export const Dropdown: React.FC = (props) => { const renderOptions = () => { return category === "Place" ? : category === "Attributes" ? : - + ; }; return ( diff --git a/src/components/summary.tsx b/src/components/summary.tsx index f02a931..43a32a7 100644 --- a/src/components/summary.tsx +++ b/src/components/summary.tsx @@ -37,17 +37,17 @@ export const Summary: React.FC = ({category, selectedOptions}) => { return null; } }) - .filter((item) => item !== null) + .filter((item) => item !== null); const finalString = resultString.join(", "); return finalString; } else if (category === "Years") { - return selectedOptions.years.join(", ") + return selectedOptions.years.join(", "); } - } + }; return (
{getSummaryText()}
- ) -} \ No newline at end of file + ); +}; diff --git a/src/components/years-options.tsx b/src/components/years-options.tsx index 524b1f7..924bd47 100644 --- a/src/components/years-options.tsx +++ b/src/components/years-options.tsx @@ -39,11 +39,11 @@ export const YearsOptions: React.FC = (props) => { console.log({selectedYears}); // do something if years are not valid } - }) + }); } - }) + }); } - } + }; return (
diff --git a/src/scripts/api.ts b/src/scripts/api.ts index 5cfd9ef..e773ac6 100644 --- a/src/scripts/api.ts +++ b/src/scripts/api.ts @@ -1,23 +1,94 @@ import fetchJsonp from "fetch-jsonp"; import { queryData } from "./query-headers"; +import { IStateOptions } from "../components/types"; +import { connect } from "./connect"; const baseURL = `https://quickstats.nass.usda.gov/api/api_GET/?key=9ED0BFB8-8DDD-3609-9940-A2341ED6A9E3`; export const createRequest = (attribute: string, geoLevel: string, location: string, year: string) => { const queryParams = queryData.find((d) => d.plugInAttribute === attribute); const {sector, group, commodity, category, domains, dataItem} = queryParams!; - const req = `${baseURL}§_desc=${sector}&group_desc=${group}&commodity_desc=${commodity}&statisticcat_desc=${category}&domain_desc=${domains}&short_desc=${dataItem}&agg_level_desc=${geoLevel}&state_name=${location}&year=${year}`; - return req; + + const baseReq = `${baseURL}§_desc=${sector}&group_desc=${group}&commodity_desc=${commodity}&statisticcat_desc=${category}&domain_desc=${domains}&agg_level_desc=${geoLevel}&state_name=${location}&year=${year}`; + + const reqs = []; + + if (Array.isArray(dataItem)) { + dataItem.forEach(item => { + reqs.push(`${baseReq}&short_desc=${item}`); + }); + } else { + reqs.push(`${baseReq}&short_desc=${dataItem}`); + } + + return reqs; +}; + +export const createQueryFromSelections = (selectedOptions: IStateOptions) => { + const {geographicLevel, states, years, ...subOptions} = selectedOptions; + const multipleStatesSelected = states.length > 1 || states[0] === "All States"; + const multipleYearsSelected = years.length > 1; + + if (multipleStatesSelected) { + states.forEach((state) => { + if (multipleYearsSelected) { + years.forEach(year => { + // do something + console.log("multiple years and mulitple states selected"); + }); + } else { + console.log("multiple states selected with one year"); + } + }); + } else { + for (const key in subOptions) { + const value = subOptions[key as keyof typeof subOptions]; + console.log("current value", value); + if (value && Array.isArray(value) && value.length > 1) { + console.log("you selected more than one value from a sub-category"); + } else if (value && Array.isArray(value) && value.length === 1) { + console.log("you selected only one value from a sub-category and it is this value", value); + const reqArray = createRequest(value[0], geographicLevel, states[0], years[0]); + console.log("REQUEST", reqArray[0]); + getDataAndCreateCodapTable(reqArray); + } + } + } }; -export const runTestQuery = () => { - const request = createRequest("Total Farmers", "STATE", "CALIFORNIA", "2017"); - fetchJsonp(request) +export const getDataAndCreateCodapTable = (reqs: string[]) => { + reqs.forEach((req) => { + fetchJsonp(req) .then(function(response) { return response.json(); }).then(function(json) { console.log("parsed json", json); + const formattedData = formatDataForCODAP(json); }).catch(function(ex) { console.log("parsing failed", ex); - }) + }); + }); }; + +const formatDataForCODAP = (res: any) => { + console.log({res}); + return res; +}; + +// export const runTestQuery = () => { +// const request1 = createRequest("Total Farmers", "STATE", "CALIFORNIA", "2017")[0]; +// const request2 = createRequest("Total Farmers", "STATE", "ARKANSAS", "2017")[0]; +// const request3 = createRequest("Total Farmers", "STATE", "ALABAMA", "2017")[0]; +// const request4 = createRequest("Total Farmers", "STATE", "MONTANA", "2017")[0]; +// const requests = [request1, request2, request3, request4]; +// requests.forEach((req) => { +// fetchJsonp(req) +// .then(function(response) { +// return response.json(); +// }).then(function(json) { +// console.log("parsed json", json); +// }).catch(function(ex) { +// console.log("parsing failed", ex); +// }) +// }) +// }; diff --git a/src/scripts/connect.js b/src/scripts/connect.js index 0ac3759..22a45a1 100644 --- a/src/scripts/connect.js +++ b/src/scripts/connect.js @@ -1,10 +1,90 @@ import { codapInterface } from "./codapInterface"; +const dataSetName = "NASS Quickstats Data"; +const dataSetTitle = "NASS Quickstats Data"; + export const connect = { initialize: async function () { return await codapInterface.init(this.iFrameDescriptor, null); }, + makeCODAPAttributeDef: function (attr) { + return { + name: attr.title, + title: attr.title, + description: attr.description, + type: attr.format, + formula: attr.formula + } + }, + + createNewDataset: async function (geoLevel) { + const geoLabel = geoLevel === "State" ? "States" : "Counties"; + return codapInterface.sendRequest({ + action: 'create', + resource: 'dataContext', + values: { + name: dataSetName, + title: dataSetTitle, + collections: [{ + name: geoLabel, + attrs: [ + { + name: geoLevel, + title: geoLevel, + description: `Selected ${geoLabel}` + }, + { + name: "Boundaries", + title: "Boundaries", + formula: 'lookupBoundary(US_state_boundaries, State)', + formulaDependents: 'State' + } + ] + }, { + name: "Data", + parent: geoLabel, + attrs: [ // note how this is an array of objects. + {name: "Year", title: "Year"} + ] + }] + } + }); + }, + + + guaranteeDataset: async function (geoLevel) { + let datasetResource = 'dataContext[' + dataSetName + + ']'; + await this.createNewDataset(geoLevel); + const response = await codapInterface.sendRequest({ + action: 'get', + resource: datasetResource}); + return response; + }, + + makeCaseTableAppear : async function() { + const theMessage = { + action : "create", + resource : "component", + values : { + type : 'caseTable', + dataContext : dataSetName, + name : dataSetName, + title: dataSetName, + cannotClose : true + } + }; + + const makeCaseTableResult = await codapInterface.sendRequest(theMessage); + if (makeCaseTableResult.success) { + console.log("Success creating case table: " + theMessage.title); + } else { + console.log("FAILED to create case table: " + theMessage.title); + } + return makeCaseTableResult.success && makeCaseTableResult.values.id; + }, + createNewCollection: async function(dSName, collName) { const message = { "action": "create", @@ -33,6 +113,6 @@ export const connect = { iFrameDescriptor: { version: '0.0.1', name: 'nass-plugin', - title: 'MultiData' + title: 'NASS Quickstats Data' }, } From b5d6406ffd6323adf181ab1177583b2b98f5d39e Mon Sep 17 00:00:00 2001 From: lublagg Date: Fri, 15 Sep 2023 15:31:04 -0400 Subject: [PATCH 4/8] Limit years options and "get data" button availability. --- src/components/app.scss | 2 +- src/components/app.tsx | 17 ++++++++- src/components/constants.ts | 16 ++++---- src/components/types.ts | 16 ++++---- src/components/utils.ts | 4 ++ src/components/years-options.tsx | 65 +++++++++++++++++--------------- src/scripts/connect.js | 2 +- 7 files changed, 71 insertions(+), 51 deletions(-) create mode 100644 src/components/utils.ts diff --git a/src/components/app.scss b/src/components/app.scss index 2edc7c5..b9c2a4e 100644 --- a/src/components/app.scss +++ b/src/components/app.scss @@ -86,7 +86,7 @@ button { font-family: 'Montserrat', sans-serif; font-size: 12px; font-weight: 500; - &:hover { + &:hover:not(:disabled) { cursor: pointer; background: $teal-light-12; } diff --git a/src/components/app.tsx b/src/components/app.tsx index 0cce018..d8228a7 100644 --- a/src/components/app.tsx +++ b/src/components/app.tsx @@ -2,7 +2,7 @@ import React, {useEffect, useState} from "react"; import { Dropdown } from "./dropdown"; import classnames from "classnames"; import { Information } from "./information"; -import { categories, defaultSelectedOptions } from "./constants"; +import { attributeOptions, categories, defaultSelectedOptions, yearsOptions } from "./constants"; import { IStateOptions } from "./types"; import { createQueryFromSelections } from "../scripts/api"; import { connect } from "../scripts/connect"; @@ -13,6 +13,8 @@ import css from "./app.scss"; function App() { const [showInfo, setShowInfo] = useState(false); const [selectedOptions, setSelectedOptions] = useState(defaultSelectedOptions); + const [getDataDisabled, setGetDataDisabled] = useState(true); + const {farmerDemographics, farmDemographics, crops, economicsAndWages} = selectedOptions; useEffect(() => { const init = async () => { @@ -21,6 +23,17 @@ function App() { init(); }, []); + useEffect(() => { + const {geographicLevel, states, years} = selectedOptions; + const attrKeys = attributeOptions.filter((attr) => attr.key !== "cropUnits").map((attr) => attr.key); + const selectedAttrKeys = attrKeys.filter((key) => selectedOptions[key].length > 0); + if (selectedAttrKeys.length && geographicLevel && states.length && years.length) { + setGetDataDisabled(false); + } else { + setGetDataDisabled(true); + } + }, [selectedOptions]); + const handleSetSelectedOptions = (option: string, value: string | string[]) => { const newSelectedOptions = {...selectedOptions, [option]: value}; setSelectedOptions(newSelectedOptions); @@ -74,7 +87,7 @@ function App() {
- +
diff --git a/src/components/constants.ts b/src/components/constants.ts index ef141c0..b1061cb 100644 --- a/src/components/constants.ts +++ b/src/components/constants.ts @@ -120,12 +120,12 @@ export const categories = [ ]; export const defaultSelectedOptions: IStateOptions = { - "geographicLevel": "State", - "states": ["All States"], - "farmerDemographics": [], - "farmDemographics": [], - "economicsAndWages": [], - "cropUnits": "", - "crops": [], - "years": [] + geographicLevel: "State", + states: ["All States"], + farmerDemographics: [], + farmDemographics: [], + economicsAndWages: [], + cropUnits: "", + crops: [], + years: [] }; diff --git a/src/components/types.ts b/src/components/types.ts index 6eaa4a9..5e78030 100644 --- a/src/components/types.ts +++ b/src/components/types.ts @@ -1,12 +1,12 @@ export interface IStateOptions { - "geographicLevel": string, - "states": string[] - "farmerDemographics": string[], - "farmDemographics": string[], - "economicsAndWages": string[], - "cropUnits": string, - "crops": string[] - "years": string[] + geographicLevel: string, + states: string[] + farmerDemographics: string[], + farmDemographics: string[], + economicsAndWages: string[], + cropUnits: string, + crops: string[] + years: string[] } export type OptionKey = keyof IStateOptions; diff --git a/src/components/utils.ts b/src/components/utils.ts new file mode 100644 index 0000000..5d9f3b0 --- /dev/null +++ b/src/components/utils.ts @@ -0,0 +1,4 @@ +export const flatten = (arr: any[]): any[] => { + return arr.reduce((acc: any[], val: any) => + Array.isArray(val) ? acc.concat(flatten(val)) : acc.concat(val), []); +} diff --git a/src/components/years-options.tsx b/src/components/years-options.tsx index 924bd47..8bfd780 100644 --- a/src/components/years-options.tsx +++ b/src/components/years-options.tsx @@ -1,10 +1,11 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import { attributeOptions, yearsOptions } from "./constants"; import { IStateOptions } from "./types"; import { Options } from "./options"; +import { queryData } from "../scripts/query-headers"; +import { flatten } from "./utils"; import css from "./options.scss"; -import { queryData } from "../scripts/query-headers"; interface IProps { handleSetSelectedOptions: (option: string, value: string|string[]) => void; @@ -13,47 +14,49 @@ interface IProps { export const YearsOptions: React.FC = (props) => { const {handleSetSelectedOptions, selectedOptions} = props; + const [availableYearOptions, setAvailableYearOptions] = useState([]); + const {farmerDemographics, farmDemographics, crops, economicsAndWages} = selectedOptions; - const handleSelectYear = (yearKey: string, years: string|string[]) => { - handleSetSelectedOptions(yearKey, years); - - // check if any attributeOption keys have values in selectedOptions - const attrKeys = attributeOptions.map((attr) => attr.key); + useEffect(() => { + const attrKeys = attributeOptions.filter((attr) => attr.key !== "cropUnits").map((attr) => attr.key); const selectedAttrKeys = attrKeys.filter((key) => selectedOptions[key].length > 0); - const areAnyAttrsSelected = selectedAttrKeys.length > 0; - // if any attributes are selected, check that selected year data is available for that selected attribute - if (areAnyAttrsSelected) { - const selectedYears = Array.isArray(years) ? years : [years]; - const selectedAttrs = selectedAttrKeys.map((key) => selectedOptions[key]); - selectedAttrs.forEach((attr) => { - if (Array.isArray(attr)) { - attr.forEach((subAttr) => { - const subAttrData = queryData.find((d) => d.plugInAttribute === subAttr); - const yearKeyToUse = selectedOptions.geographicLevel === "county" ? "county" : "state"; - const availableYears = subAttrData?.years[yearKeyToUse]; - if (availableYears) { - // check -- are selectedYears included in queryParams?.years[yearKeyTouse] ? - const areYearsValid = selectedYears.map((y) => availableYears.indexOf(y) > -1).indexOf(false) < 0; - console.log({areYearsValid}); - console.log({availableYears}); - console.log({selectedYears}); - // do something if years are not valid - } - }); - } - }); + + if (!selectedAttrKeys.length) { + return; } + + const yearKeyToUse = selectedOptions.geographicLevel === "County" ? "county" : "state"; + const allSelectedAttrs = flatten(selectedAttrKeys.map((key) => selectedOptions[key])); + const newAvailableYears = allSelectedAttrs.reduce((years, attr) => { + const subAttrData = queryData.find((d) => d.plugInAttribute === attr); + const availableYears = subAttrData?.years[yearKeyToUse]; + if (availableYears) { + availableYears.forEach((y) => { + years.add(y); + }); + } + return years; + }, new Set()); + + setAvailableYearOptions(Array.from(newAvailableYears)); + }, [farmerDemographics, farmDemographics, crops, economicsAndWages]) + + const handleSelectYear = (yearKey: string, years: string|string[]) => { + handleSetSelectedOptions(yearKey, years); }; return (
+ {availableYearOptions.length === 0 ? +
Please select attributes to see available years.
+ : + />}
); }; diff --git a/src/scripts/connect.js b/src/scripts/connect.js index 22a45a1..6a648b7 100644 --- a/src/scripts/connect.js +++ b/src/scripts/connect.js @@ -19,7 +19,7 @@ export const connect = { }, createNewDataset: async function (geoLevel) { - const geoLabel = geoLevel === "State" ? "States" : "Counties"; + const geoLabel = geoLevel === "State" ? states : "Counties"; return codapInterface.sendRequest({ action: 'create', resource: 'dataContext', From 1b402243ef527ba4fd7953858057af00ec8d2a75 Mon Sep 17 00:00:00 2001 From: lublagg Date: Sat, 16 Sep 2023 18:38:18 -0400 Subject: [PATCH 5/8] User can select attributes and create CODAP table. --- src/components/app.tsx | 14 +-- src/components/constants.ts | 2 +- src/components/types.ts | 42 ++++++++ src/scripts/api.ts | 185 +++++++++++++++++++++++------------ src/scripts/connect.js | 126 ++++++++++++------------ src/scripts/query-headers.ts | 2 +- 6 files changed, 235 insertions(+), 136 deletions(-) diff --git a/src/components/app.tsx b/src/components/app.tsx index d8228a7..7564101 100644 --- a/src/components/app.tsx +++ b/src/components/app.tsx @@ -4,7 +4,7 @@ import classnames from "classnames"; import { Information } from "./information"; import { attributeOptions, categories, defaultSelectedOptions, yearsOptions } from "./constants"; import { IStateOptions } from "./types"; -import { createQueryFromSelections } from "../scripts/api"; +import { createTableFromSelections } from "../scripts/api"; import { connect } from "../scripts/connect"; @@ -43,16 +43,8 @@ function App() { setShowInfo(true); }; - const handleGetData = () => { - createQueryFromSelections(selectedOptions); - - const makeDataSetAndTable = async () => { - const dS = await connect.guaranteeDataset(selectedOptions.geographicLevel); - if (dS.success) { - await connect.makeCaseTableAppear(); - } - }; - makeDataSetAndTable(); + const handleGetData = async () => { + await createTableFromSelections(selectedOptions); }; return ( diff --git a/src/components/constants.ts b/src/components/constants.ts index b1061cb..23aaecf 100644 --- a/src/components/constants.ts +++ b/src/components/constants.ts @@ -92,7 +92,7 @@ const cropUnitOptions: IAttrOptions = { options: ["Area Harvested", "Yield"], instructions: "(Choose units)" }; -const cropOptions: IAttrOptions = { +export const cropOptions: IAttrOptions = { key: "crops", label: null, options: ["Corn", "Cotton", "Grapes", "Grasses", "Oats", "Soybeans", "Wheat"], diff --git a/src/components/types.ts b/src/components/types.ts index 5e78030..d19581c 100644 --- a/src/components/types.ts +++ b/src/components/types.ts @@ -17,3 +17,45 @@ export interface IAttrOptions { options: string[], instructions: string|null } + +export interface IResData { + "CV (%)": string; + Value: string; + agg_level_desc: string; + asd_code: string; + asd_desc: string; + begin_code: string; + class_desc: string; + commodity_desc: string; + congr_district_code: string; + country_code: string; + country_name: string; + county_ansi: string; + county_code: string; + county_name: string; + domain_desc: string; + domaincat_desc: string; + end_code: string; + freq_desc: string; + group_desc: string; + load_time: string; + location_desc: string; + prodn_practice_desc: string; + reference_period_desc: string; + region_desc: string; + sector_desc: string; + short_desc: string; + source_desc: string; + state_alpha: string; + state_ansi: string; + state_fips_code: string; + state_name: string; + statisticcat_desc: string; + unit_desc: string; + util_practice_desc: string; + watershed_code: string; + watershed_desc: string; + week_ending: string; + year: number; + zip_5: string; +}; \ No newline at end of file diff --git a/src/scripts/api.ts b/src/scripts/api.ts index e773ac6..ba10ec7 100644 --- a/src/scripts/api.ts +++ b/src/scripts/api.ts @@ -1,94 +1,155 @@ import fetchJsonp from "fetch-jsonp"; -import { queryData } from "./query-headers"; +import { ICropDataItem, queryData } from "./query-headers"; import { IStateOptions } from "../components/types"; import { connect } from "./connect"; +import { cropOptions } from "../components/constants"; const baseURL = `https://quickstats.nass.usda.gov/api/api_GET/?key=9ED0BFB8-8DDD-3609-9940-A2341ED6A9E3`; -export const createRequest = (attribute: string, geoLevel: string, location: string, year: string) => { +interface IRequestParams { + attribute: string, + geographicLevel: string, + location: string, + year: string, + cropCategory?: keyof ICropDataItem +} + +interface IGetAttrDataParams { + attribute: string, + geographicLevel: string, + cropUnits: string, + state: string, + year: string +} + +export const createRequest = ({attribute, geographicLevel, location, year, cropCategory}: IRequestParams) => { const queryParams = queryData.find((d) => d.plugInAttribute === attribute); const {sector, group, commodity, category, domains, dataItem} = queryParams!; - const baseReq = `${baseURL}§_desc=${sector}&group_desc=${group}&commodity_desc=${commodity}&statisticcat_desc=${category}&domain_desc=${domains}&agg_level_desc=${geoLevel}&state_name=${location}&year=${year}`; - - const reqs = []; + let item; + let cat; + if (cropCategory) { + const cropDataItem = queryParams?.dataItem as ICropDataItem; + const cropCat = queryParams?.category as ICropDataItem; + item = cropDataItem[cropCategory]; + cat = cropCat[cropCategory]; + } else { + item = dataItem; + cat = category; + } + const baseReq = `${baseURL}§_desc=${sector}&group_desc=${group}&commodity_desc=${commodity}&statisticcat_desc=${cat}&domain_desc=${domains}&agg_level_desc=${geographicLevel}&state_name=${location}&year=${year}`; + let req = baseReq; if (Array.isArray(dataItem)) { - dataItem.forEach(item => { - reqs.push(`${baseReq}&short_desc=${item}`); + dataItem.forEach(dItem => { + req = req + `&short_desc=${dItem}`; }); } else { - reqs.push(`${baseReq}&short_desc=${dataItem}`); + req = req + `&short_desc=${item}`; } - return reqs; + return req; }; -export const createQueryFromSelections = (selectedOptions: IStateOptions) => { - const {geographicLevel, states, years, ...subOptions} = selectedOptions; +export const createTableFromSelections = async (selectedOptions: IStateOptions) => { + const {geographicLevel, states, cropUnits, years, ...subOptions} = selectedOptions; + await connect.getNewDataContext(); + await connect.createTopCollection(); + + // need to change this - instead of creating based on UI names, create based on dataItems in queryParams + const allAttrs: Array = ["Year"]; + for (const key in subOptions) { + const selections = subOptions[key as keyof typeof subOptions]; + for (const attribute of selections) { + const queryParams = queryData.find((d) => d.plugInAttribute === attribute); + const {dataItem} = queryParams!; + if (Array.isArray(dataItem)) { + allAttrs.push(...dataItem); + } else { + allAttrs.push(dataItem); + } + } + } + await connect.createSubCollection(allAttrs); + const items = await getItems(selectedOptions); + await connect.createItems(items); + await connect.makeCaseTableAppear(); +} + +const getItems = async (selectedOptions: IStateOptions) => { + const {states, years} = selectedOptions; const multipleStatesSelected = states.length > 1 || states[0] === "All States"; const multipleYearsSelected = years.length > 1; + const items = []; if (multipleStatesSelected) { - states.forEach((state) => { + for (const state of states) { if (multipleYearsSelected) { - years.forEach(year => { - // do something - console.log("multiple years and mulitple states selected"); - }); + for (const year of years) { + const item = await getDataForSingleYearAndState(selectedOptions, state, year); + items.push(item); + } } else { - console.log("multiple states selected with one year"); + const item = await getDataForSingleYearAndState(selectedOptions, state, years[0]); + items.push(item); } - }); + } } else { - for (const key in subOptions) { - const value = subOptions[key as keyof typeof subOptions]; - console.log("current value", value); - if (value && Array.isArray(value) && value.length > 1) { - console.log("you selected more than one value from a sub-category"); - } else if (value && Array.isArray(value) && value.length === 1) { - console.log("you selected only one value from a sub-category and it is this value", value); - const reqArray = createRequest(value[0], geographicLevel, states[0], years[0]); - console.log("REQUEST", reqArray[0]); - getDataAndCreateCodapTable(reqArray); + const item = await getDataForSingleYearAndState(selectedOptions, states[0], years[0]); + items.push(item); + } + + return items; +}; + +const getDataForSingleYearAndState = async (selectedOptions: IStateOptions, state: string, year: string) => { + const {geographicLevel, states, years, cropUnits, ...subOptions} = selectedOptions; + + let item: any = { + "State": state, + "Year": year, + } + + for (const key in subOptions) { + const value = subOptions[key as keyof typeof subOptions]; + if (value && Array.isArray(value)) { + for (const attribute of value) { + const attrData = await getAttrData({attribute, geographicLevel, state, year, cropUnits}); + item = {...item, ...attrData}; } } } -}; -export const getDataAndCreateCodapTable = (reqs: string[]) => { - reqs.forEach((req) => { - fetchJsonp(req) - .then(function(response) { - return response.json(); - }).then(function(json) { - console.log("parsed json", json); - const formattedData = formatDataForCODAP(json); - }).catch(function(ex) { - console.log("parsing failed", ex); - }); - }); + return item; }; -const formatDataForCODAP = (res: any) => { - console.log({res}); - return res; -}; +const getAttrData = async (params: IGetAttrDataParams) => { + const {attribute, geographicLevel, state, year, cropUnits} = params; + const reqParams: IRequestParams = {attribute, geographicLevel, location: state, year}; + if (cropOptions.options.includes(attribute) && cropUnits) { + reqParams.cropCategory = cropUnits as keyof ICropDataItem; + } + const req = createRequest(reqParams); + const res = await fetchData(req); + const values: any = {}; + if (res) { + const {data} = res; + data.forEach((dataItem: any) => { + values[dataItem.short_desc] = dataItem.Value; + }) + } else { + console.log("error"); + } + return values; +} -// export const runTestQuery = () => { -// const request1 = createRequest("Total Farmers", "STATE", "CALIFORNIA", "2017")[0]; -// const request2 = createRequest("Total Farmers", "STATE", "ARKANSAS", "2017")[0]; -// const request3 = createRequest("Total Farmers", "STATE", "ALABAMA", "2017")[0]; -// const request4 = createRequest("Total Farmers", "STATE", "MONTANA", "2017")[0]; -// const requests = [request1, request2, request3, request4]; -// requests.forEach((req) => { -// fetchJsonp(req) -// .then(function(response) { -// return response.json(); -// }).then(function(json) { -// console.log("parsed json", json); -// }).catch(function(ex) { -// console.log("parsing failed", ex); -// }) -// }) -// }; +export const fetchData = async (req: string) => { + try { + const response = await fetchJsonp(req); + const json = await response.json(); + return json; + } catch (error) { + console.log("parsing failed", error); + throw error; + } +}; \ No newline at end of file diff --git a/src/scripts/connect.js b/src/scripts/connect.js index 6a648b7..0b24088 100644 --- a/src/scripts/connect.js +++ b/src/scripts/connect.js @@ -10,106 +10,110 @@ export const connect = { makeCODAPAttributeDef: function (attr) { return { - name: attr.title, - title: attr.title, - description: attr.description, - type: attr.format, - formula: attr.formula + name: attr } }, - createNewDataset: async function (geoLevel) { - const geoLabel = geoLevel === "State" ? states : "Counties"; + createNewDataContext: async function () { return codapInterface.sendRequest({ action: 'create', resource: 'dataContext', values: { name: dataSetName, - title: dataSetTitle, - collections: [{ - name: geoLabel, - attrs: [ - { - name: geoLevel, - title: geoLevel, - description: `Selected ${geoLabel}` - }, - { - name: "Boundaries", - title: "Boundaries", - formula: 'lookupBoundary(US_state_boundaries, State)', - formulaDependents: 'State' - } - ] - }, { - name: "Data", - parent: geoLabel, - attrs: [ // note how this is an array of objects. - {name: "Year", title: "Year"} - ] - }] + title: dataSetTitle } }); }, + deleteOldDataContext: async function () { + return codapInterface.sendRequest({ + action: 'delete', + resource: `dataContext[${dataSetName}]` + }); + }, - guaranteeDataset: async function (geoLevel) { - let datasetResource = 'dataContext[' + dataSetName + - ']'; - await this.createNewDataset(geoLevel); + checkIfDataContextExists: async function () { const response = await codapInterface.sendRequest({ action: 'get', - resource: datasetResource}); + resource: `dataContext[${dataSetName}]`}); return response; }, - makeCaseTableAppear : async function() { - const theMessage = { - action : "create", - resource : "component", - values : { - type : 'caseTable', - dataContext : dataSetName, - name : dataSetName, - title: dataSetName, - cannotClose : true - } - }; - - const makeCaseTableResult = await codapInterface.sendRequest(theMessage); - if (makeCaseTableResult.success) { - console.log("Success creating case table: " + theMessage.title); - } else { - console.log("FAILED to create case table: " + theMessage.title); + getNewDataContext: async function () { + const doesDataContextExist = await this.checkIfDataContextExists(); + if (doesDataContextExist.success) { + await connect.deleteOldDataContext(); } - return makeCaseTableResult.success && makeCaseTableResult.values.id; + const res = await connect.createNewDataContext(); + return res; }, - createNewCollection: async function(dSName, collName) { + createTopCollection: async function() { const message = { "action": "create", - "resource": `dataContext[${dSName}].collection`, + "resource": `dataContext[${dataSetName}].collection`, "values": { - "name": collName, + "name": "States", + "parent": "_root_", "attributes": [{ - "name": "newAttr", + "name": "State", + }, + { + "name": "Boundary", + "formula": "lookupBoundary(US_state_boundaries, State)", + "formulaDependents": "State" }] } }; await codapInterface.sendRequest(message); }, - createNewAttribute: async function(dSName, collName, attrName) { + createSubCollection: async function(attrs) { const message = { "action": "create", - "resource": `dataContext[${dSName}].collection[${collName}].attribute`, + "resource": `dataContext[${dataSetName}].collection`, "values": { - "name": attrName, + "name": "Data", + "parent": "States", + "attributes": attrs.map((attr) => this.makeCODAPAttributeDef(attr)) } }; await codapInterface.sendRequest(message); }, + createItems: async function(items) { + for (const item of items) { + const message = { + "action": "create", + "resource": `dataContext[${dataSetName}].item`, + "values": item + }; + await codapInterface.sendRequest(message); + } + }, + + makeCaseTableAppear : async function() { + const theMessage = { + action : "create", + resource : "component", + values : { + type : 'caseTable', + dataContext : dataSetName, + name : dataSetName, + title: dataSetName, + cannotClose : false + } + }; + + const makeCaseTableResult = await codapInterface.sendRequest(theMessage); + if (makeCaseTableResult.success) { + console.log("Success creating case table: " + theMessage.title); + } else { + console.log("FAILED to create case table: " + theMessage.title); + } + return makeCaseTableResult.success && makeCaseTableResult.values.id; + }, + iFrameDescriptor: { version: '0.0.1', name: 'nass-plugin', diff --git a/src/scripts/query-headers.ts b/src/scripts/query-headers.ts index 311211f..2151acf 100644 --- a/src/scripts/query-headers.ts +++ b/src/scripts/query-headers.ts @@ -1,6 +1,6 @@ const areaHarvested = "Area Harvested"; const yieldInBU = "Yield"; -interface ICropDataItem { +export interface ICropDataItem { [areaHarvested]: string, [yieldInBU]: string } From 1c0c59c58ebe391d5b40778442dc228abb47bcc3 Mon Sep 17 00:00:00 2001 From: lublagg Date: Sun, 17 Sep 2023 17:59:45 -0400 Subject: [PATCH 6/8] Encode parameters and unselect unavailable years. --- src/components/app.tsx | 3 +- src/components/attribute-options.tsx | 8 +- src/components/information.tsx | 2 +- src/components/options.scss | 2 +- src/components/place-options.tsx | 5 +- src/components/types.ts | 2 +- src/components/utils.ts | 2 +- src/components/years-options.tsx | 16 +- src/scripts/api.ts | 76 +++++---- src/scripts/query-headers.ts | 228 +++++++++++++-------------- 10 files changed, 179 insertions(+), 165 deletions(-) diff --git a/src/components/app.tsx b/src/components/app.tsx index 7564101..542371c 100644 --- a/src/components/app.tsx +++ b/src/components/app.tsx @@ -2,7 +2,7 @@ import React, {useEffect, useState} from "react"; import { Dropdown } from "./dropdown"; import classnames from "classnames"; import { Information } from "./information"; -import { attributeOptions, categories, defaultSelectedOptions, yearsOptions } from "./constants"; +import { attributeOptions, categories, defaultSelectedOptions } from "./constants"; import { IStateOptions } from "./types"; import { createTableFromSelections } from "../scripts/api"; import { connect } from "../scripts/connect"; @@ -14,7 +14,6 @@ function App() { const [showInfo, setShowInfo] = useState(false); const [selectedOptions, setSelectedOptions] = useState(defaultSelectedOptions); const [getDataDisabled, setGetDataDisabled] = useState(true); - const {farmerDemographics, farmDemographics, crops, economicsAndWages} = selectedOptions; useEffect(() => { const init = async () => { diff --git a/src/components/attribute-options.tsx b/src/components/attribute-options.tsx index 6e373a5..939aeee 100644 --- a/src/components/attribute-options.tsx +++ b/src/components/attribute-options.tsx @@ -34,8 +34,12 @@ export const AttributeOptions: React.FC = (props) => { attributeOptions.map((attr) => { return ( <> - {attr.label &&
{attr.label}
} - {attr.instructions &&
{attr.instructions}
} + {attr.label &&
{attr.label}
} + {attr.instructions && +
+ {attr.instructions} +
+ }
= (props) => {
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ipsum velit, pellentesque eget turpis non, vestibulum egestas tellus. Fusce sed dolor hendrerit, rutrum ligula et, imperdiet est. Nam tincidunt leo a ultricies elementum. Quisque ornare eget massa ac congue. Curabitur et nisi orci. Nulla mollis lacus eu velit mollis, in vehicula lectus ultricies. Nulla facilisi. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ipsum velit, pellentesque eget turpis.
diff --git a/src/components/options.scss b/src/components/options.scss index 70b558b..bb1821e 100644 --- a/src/components/options.scss +++ b/src/components/options.scss @@ -68,7 +68,7 @@ } } -.category { +.statisticcat_desc { font-weight: 600; } diff --git a/src/components/place-options.tsx b/src/components/place-options.tsx index f165fbd..7e5bb04 100644 --- a/src/components/place-options.tsx +++ b/src/components/place-options.tsx @@ -18,7 +18,10 @@ export const PlaceOptions: React.FC = (props) => { return ( <>
{placeOpt.instructions}:
-
+
{ return arr.reduce((acc: any[], val: any) => Array.isArray(val) ? acc.concat(flatten(val)) : acc.concat(val), []); -} +}; diff --git a/src/components/years-options.tsx b/src/components/years-options.tsx index 8bfd780..bd0ab11 100644 --- a/src/components/years-options.tsx +++ b/src/components/years-options.tsx @@ -15,7 +15,6 @@ interface IProps { export const YearsOptions: React.FC = (props) => { const {handleSetSelectedOptions, selectedOptions} = props; const [availableYearOptions, setAvailableYearOptions] = useState([]); - const {farmerDemographics, farmDemographics, crops, economicsAndWages} = selectedOptions; useEffect(() => { const attrKeys = attributeOptions.filter((attr) => attr.key !== "cropUnits").map((attr) => attr.key); @@ -37,9 +36,20 @@ export const YearsOptions: React.FC = (props) => { } return years; }, new Set()); + const newSet: string[] = Array.from(newAvailableYears); + setAvailableYearOptions(newSet); - setAvailableYearOptions(Array.from(newAvailableYears)); - }, [farmerDemographics, farmDemographics, crops, economicsAndWages]) + // if selected years includes years not in available options, remove them from selection + const selectionsNotAvailable = selectedOptions.years.filter(year => !newSet.includes(year)); + if (selectionsNotAvailable.length) { + const newSelectedYears = [...selectedOptions.years]; + selectionsNotAvailable.forEach((year) => { + newSelectedYears.splice(newSelectedYears.indexOf(year), 1); + }); + handleSetSelectedOptions("years", newSelectedYears); + } + + }, [selectedOptions, handleSetSelectedOptions]); const handleSelectYear = (yearKey: string, years: string|string[]) => { handleSetSelectedOptions(yearKey, years); diff --git a/src/scripts/api.ts b/src/scripts/api.ts index ba10ec7..b44fbfa 100644 --- a/src/scripts/api.ts +++ b/src/scripts/api.ts @@ -1,5 +1,5 @@ import fetchJsonp from "fetch-jsonp"; -import { ICropDataItem, queryData } from "./query-headers"; +import { ICropCategory, ICropDataItem, queryData } from "./query-headers"; import { IStateOptions } from "../components/types"; import { connect } from "./connect"; import { cropOptions } from "../components/constants"; @@ -24,30 +24,41 @@ interface IGetAttrDataParams { export const createRequest = ({attribute, geographicLevel, location, year, cropCategory}: IRequestParams) => { const queryParams = queryData.find((d) => d.plugInAttribute === attribute); - const {sector, group, commodity, category, domains, dataItem} = queryParams!; - - let item; - let cat; - if (cropCategory) { - const cropDataItem = queryParams?.dataItem as ICropDataItem; - const cropCat = queryParams?.category as ICropDataItem; - item = cropDataItem[cropCategory]; - cat = cropCat[cropCategory]; - } else { - item = dataItem; - cat = category; - } - const baseReq = `${baseURL}§_desc=${sector}&group_desc=${group}&commodity_desc=${commodity}&statisticcat_desc=${cat}&domain_desc=${domains}&agg_level_desc=${geographicLevel}&state_name=${location}&year=${year}`; - let req = baseReq; - if (Array.isArray(dataItem)) { - dataItem.forEach(dItem => { - req = req + `&short_desc=${dItem}`; - }); - } else { - req = req + `&short_desc=${item}`; + if (!queryParams) { + throw new Error("Invalid attribute"); } + const { + sect_desc, + group_desc, + commodity_desc, + statisticcat_desc, + domain_desc, + short_desc, + } = queryParams; + + const item = cropCategory ? + (queryParams?.short_desc as ICropDataItem)[cropCategory] : + short_desc as string[]; + const cat = cropCategory ? + (queryParams?.statisticcat_desc as ICropCategory)[cropCategory] : + statisticcat_desc; + + const baseReq = `${baseURL}` + + `§_desc=${encodeURIComponent(sect_desc)}` + + `&group_desc=${encodeURIComponent(group_desc)}` + + `&commodity_desc=${encodeURIComponent(commodity_desc)}` + + `&statisticcat_desc=${encodeURIComponent(cat as string)}` + + `&domain_desc=${encodeURIComponent(domain_desc)}` + + `&agg_level_desc=${geographicLevel}` + + `&state_name=${location}` + + `&year=${year}`; + + let req = baseReq; + item.forEach(subItem => { + req = req + `&short_desc=${encodeURIComponent(subItem)}`; + }); return req; }; @@ -62,11 +73,14 @@ export const createTableFromSelections = async (selectedOptions: IStateOptions) const selections = subOptions[key as keyof typeof subOptions]; for (const attribute of selections) { const queryParams = queryData.find((d) => d.plugInAttribute === attribute); - const {dataItem} = queryParams!; - if (Array.isArray(dataItem)) { - allAttrs.push(...dataItem); + if (!queryParams) { + throw new Error("Invalid attribute"); + } + const {short_desc} = queryParams; + if (Array.isArray(short_desc)) { + allAttrs.push(...short_desc); } else { - allAttrs.push(dataItem); + allAttrs.push(short_desc); } } } @@ -74,7 +88,7 @@ export const createTableFromSelections = async (selectedOptions: IStateOptions) const items = await getItems(selectedOptions); await connect.createItems(items); await connect.makeCaseTableAppear(); -} +}; const getItems = async (selectedOptions: IStateOptions) => { const {states, years} = selectedOptions; @@ -108,7 +122,7 @@ const getDataForSingleYearAndState = async (selectedOptions: IStateOptions, stat let item: any = { "State": state, "Year": year, - } + }; for (const key in subOptions) { const value = subOptions[key as keyof typeof subOptions]; @@ -136,12 +150,12 @@ const getAttrData = async (params: IGetAttrDataParams) => { const {data} = res; data.forEach((dataItem: any) => { values[dataItem.short_desc] = dataItem.Value; - }) + }); } else { console.log("error"); } return values; -} +}; export const fetchData = async (req: string) => { try { @@ -152,4 +166,4 @@ export const fetchData = async (req: string) => { console.log("parsing failed", error); throw error; } -}; \ No newline at end of file +}; diff --git a/src/scripts/query-headers.ts b/src/scripts/query-headers.ts index 2151acf..32110af 100644 --- a/src/scripts/query-headers.ts +++ b/src/scripts/query-headers.ts @@ -1,19 +1,24 @@ const areaHarvested = "Area Harvested"; const yieldInBU = "Yield"; -export interface ICropDataItem { + +export interface ICropCategory { [areaHarvested]: string, [yieldInBU]: string } +export interface ICropDataItem { + [areaHarvested]: string[], + [yieldInBU]: string[] +} -interface IQueryHeaders { +export interface IQueryHeaders { plugInAttribute: string, numberOfAttributeColumnsInCodap: number|string, - sector: string, - group: string, - commodity: string, - category: string|ICropDataItem, - dataItem: string|string[]|ICropDataItem, - domains: string, + sect_desc: string, + group_desc: string, + commodity_desc: string, + statisticcat_desc: string|ICropCategory, + short_desc: string[]|ICropDataItem, + domain_desc: string, geographicLevels?: string, years: { county: string[] @@ -22,24 +27,24 @@ interface IQueryHeaders { } const sharedDemographicHeaders = { -sector: "Demographics", -group: "Producers", -commodity: "Producers", -category: "Producers", -domains: "Total", +sect_desc: "Demographics", +group_desc: "Producers", +commodity_desc: "Producers", +statisticcat_desc: "Producers", +domain_desc: "Total", }; const sharedEconomicHeaders = { -sector: "Economics", -group: "Farms & Land & Assets", -commodity: "Farm Operations", -category: "Operations" +sect_desc: "Economics", +group_desc: "Farms & Land & Assets", +commodity_desc: "Farm Operations", +statisticcat_desc: "Operations" }; const sharedLaborHeaders = { -sector: "Economics", -group: "Expenses", -commodity: "Labor", +sect_desc: "Economics", +group_desc: "Expenses", +commodity_desc: "Labor", }; const allYears = []; @@ -52,7 +57,7 @@ export const queryData: Array = [ plugInAttribute: "Total Farmers", numberOfAttributeColumnsInCodap: 1, ...sharedDemographicHeaders, - dataItem: "PRODUCERS, (ALL) - NUMBER OF PRODUCERS", + short_desc: ["PRODUCERS, (ALL) - NUMBER OF PRODUCERS"], years: { county: ["2017"], state: ["2017"] @@ -62,7 +67,7 @@ export const queryData: Array = [ plugInAttribute: "Age", numberOfAttributeColumnsInCodap: 7, ...sharedDemographicHeaders, - dataItem: [ + short_desc: [ "PRODUCERS, AGE LT 25 - NUMBER OF PRODUCERS", "PRODUCERS, AGE 25 TO 34 - NUMBER OF PRODUCERS", "PRODUCERS, AGE 35 TO 44 - NUMBER OF PRODUCERS", @@ -81,7 +86,7 @@ export const queryData: Array = [ plugInAttribute: "Gender", numberOfAttributeColumnsInCodap: 2, ...sharedDemographicHeaders, - dataItem: [ + short_desc: [ "PRODUCERS, (ALL), FEMALE - NUMBER OF PRODUCERS", "PRODUCERS, (ALL), MALE - NUMBER OF PRODUCERS" ], @@ -94,7 +99,7 @@ export const queryData: Array = [ plugInAttribute: "Race", numberOfAttributeColumnsInCodap: 7, ...sharedDemographicHeaders, - dataItem: [ + short_desc: [ "PRODUCERS, AMERICAN INDIAN OR ALASKAN NATIVE - NUMBER OF PRODUCERS", "PRODUCERS, ASIAN - NUMBER OF PRODUCERS", "PRODUCERS, BLACK OR AFRICAN AMERICAN - NUMBER OF PRODUCERS", @@ -112,8 +117,8 @@ export const queryData: Array = [ plugInAttribute: "Total Farms", numberOfAttributeColumnsInCodap: 1, ...sharedEconomicHeaders, - dataItem: "Farm Operations - Number of Operations", - domains: "Total", + short_desc: ["FARM OPERATIONS - NUMBER OF OPERATIONS"], + domain_desc: "Total", geographicLevels: "State, County", years: { county: allYears, @@ -123,30 +128,30 @@ export const queryData: Array = [ { plugInAttribute: "Organization Type", numberOfAttributeColumnsInCodap: 5, - sector: "Demographics", - group: "Farms & Land & Assets", - commodity: "Farm Operations", - category: "Operations", - dataItem: [ + sect_desc: "Demographics", + group_desc: "Farms & Land & Assets", + commodity_desc: "Farm Operations", + statisticcat_desc: "Operations", + short_desc: [ "FARM OPERATIONS, ORGANIZATION, TAX PURPOSES, CORPORATION (EXCL FAMILY HELD) - NUMBER OF OPERATIONS", "FARM OPERATIONS, ORGANIZATION, TAX PURPOSES, CORPORATION, FAMILY HELD - NUMBER OF OPERATIONS", "FARM OPERATIONS, ORGANIZATION, TAX PURPOSES, FAMILY & INDIVIDUAL - NUMBER OF OPERATIONS", "FARM OPERATIONS, ORGANIZATION, TAX PURPOSES, INSTITUTIONAL & RESEARCH & RESERVATION & OTHER - NUMBER OF OPERATIONS", "FARM OPERATIONS, ORGANIZATION, TAX PURPOSES, PARTNERSHIP - NUMBER OF OPERATIONS" ], - domains: "Total", - geographicLevels: "State, County", + domain_desc: "Total", + geographicLevels: "County", years: { county: ["1997", "2002", "2007", "2012", "2017"], - state: allYears + state: [] } }, { plugInAttribute: "Economic Class", numberOfAttributeColumnsInCodap: "3 - 6", ...sharedEconomicHeaders, - dataItem: "Farm Operations - Number of Operations", - domains: "Economic Class", + short_desc: ["FARM OPERATIONS - NUMBER OF OPERATIONS"], + domain_desc: "Economic Class", geographicLevels: "State ", years: { county: allYears.filter(y => Number(y) >= 1987), @@ -156,12 +161,12 @@ export const queryData: Array = [ { plugInAttribute: "Acres Operated", numberOfAttributeColumnsInCodap: 14, - sector: "Economics", - group: "Farms & Land & Assets", - commodity: "Farm Operations", - category: "Area Operated", - dataItem: "Farm Operations - Acres Operated", - domains: "Area Operated", + sect_desc: "Economics", + group_desc: "Farms & Land & Assets", + commodity_desc: "Farm Operations", + statisticcat_desc: "Area Operated", + short_desc: ["FARM OPERATIONS - ACRES OPERATED"], + domain_desc: "Area Operated", geographicLevels: "State, County", years: { county: ["1997", "2002", "2007", "2012", "2017"], @@ -172,8 +177,8 @@ export const queryData: Array = [ plugInAttribute: "Organic", numberOfAttributeColumnsInCodap: 1, ...sharedEconomicHeaders, - dataItem: "Farm Operations, Organic - Number of Operations", - domains: "Organic Status", + short_desc: ["FARM OPERATIONS, ORGANIC - NUMBER OF OPERATIONS"], + domain_desc: "Organic Status", years: { county: ["2008", "2011", "2012", "2014", "2015", "2016", "2017", "2019", "2021"], state: ["2008", "2011", "2012", "2014", "2015", "2016", "2017", "2019", "2021"] @@ -183,13 +188,13 @@ export const queryData: Array = [ plugInAttribute: "Labor Status", numberOfAttributeColumnsInCodap: 3, ...sharedLaborHeaders, - category: "Workers", - dataItem: [ + statisticcat_desc: "Workers", + short_desc: [ "LABOR, MIGRANT - NUMBER OF WORKERS", "LABOR, UNPAID - NUMBER OF WORKERS", "LABOR, HIRED - NUMBER OF WORKERS" ], - domains: "Total", + domain_desc: "Total", geographicLevels: "State, County", years: { county: ["2012", "2017"], @@ -200,9 +205,9 @@ export const queryData: Array = [ plugInAttribute: "Wages", numberOfAttributeColumnsInCodap: 1, ...sharedLaborHeaders, - category: "Wage Rate", - dataItem: "LABOR, HIRED - WAGE RATE, MEASURED IN $/HOUR", - domains: "Total", + statisticcat_desc: "Wage Rate", + short_desc: ["LABOR, HIRED - WAGE RATE, MEASURED IN $/HOUR"], + domain_desc: "Total", geographicLevels: "Region: Multi-state", years: { county: allYears.filter(y => Number(y) >= 1989), @@ -213,9 +218,9 @@ export const queryData: Array = [ plugInAttribute: "Time Worked", numberOfAttributeColumnsInCodap: 1, ...sharedLaborHeaders, - category: "Wage Rate", - dataItem: "Labor, Hired - Time Worked, Measured in Hours/Week", - domains: "Total", + statisticcat_desc: "Wage Rate", + short_desc: ["LABOR, HIRED - TIME WORKED, MEASURED IN HOURS/WEEK"], + domain_desc: "Total", geographicLevels: "Region: Multi-state", years: { county: allYears.filter(y => Number(y) >= 1989), @@ -225,18 +230,18 @@ export const queryData: Array = [ { plugInAttribute: "Corn", numberOfAttributeColumnsInCodap: 1, - sector: "Crops", - group: "Field Crops", - commodity: "Corn", - category: { + sect_desc: "Crops", + group_desc: "Field Crops", + commodity_desc: "Corn", + statisticcat_desc: { [areaHarvested]: "Area Harvested", [yieldInBU]: "Yield" }, - dataItem: { - [areaHarvested]: "Corn, Grain - Acres Harvested", - [yieldInBU]: "Corn, Grain - Yield, measured in BU / acre" + short_desc: { + [areaHarvested]: ["CORN, GRAIN - ACRES HARVESTED"], + [yieldInBU]: ["CORN, GRAIN - YIELD, MEASURED IN BU / ACRE"] }, - domains: "Total", + domain_desc: "Total", geographicLevels: "State, County", years: { county: allYears, @@ -246,18 +251,18 @@ export const queryData: Array = [ { plugInAttribute: "Cotton", numberOfAttributeColumnsInCodap: 1, - sector: "Crops", - group: "Field Crops", - commodity: "Cotton", - category: { + sect_desc: "Crops", + group_desc: "Field Crops", + commodity_desc: "Cotton", + statisticcat_desc: { [areaHarvested]: "Area Harvested", [yieldInBU]: "Yield" }, - dataItem: { - [areaHarvested]: "Cotton - Acres Harvested", - [yieldInBU]: "Cotton - Yield, measured in LB / acre" + short_desc: { + [areaHarvested]: ["COTTON - ACRES HARVESTED"], + [yieldInBU]: ["COTTON - YIELD, MEASURED IN LB / ACRE"] }, - domains: "Total", + domain_desc: "Total", geographicLevels: "State, County", years: { county: allYears, @@ -267,39 +272,18 @@ export const queryData: Array = [ { plugInAttribute: "Grapes", numberOfAttributeColumnsInCodap: 1, - sector: "Crops", - group: "Fruit & Tree Nuts", - commodity: "Grapes", - category: { - [areaHarvested]: "Area Harvested", - [yieldInBU]: "Yield" - }, - dataItem: { - [areaHarvested]: "Grapes, Organic - Acres Harvested", - [yieldInBU]: "Grapes - Yield, measured in tons / acre" - }, - domains: "Total", - geographicLevels: "State, County", - years: { - county: allYears, - state: allYears - } -}, -{ - plugInAttribute: "Grasses", - numberOfAttributeColumnsInCodap: 1, - sector: "Crops", - group: "Field Crops", - commodity: "Grasses", - category: { + sect_desc: "Crops", + group_desc: "Fruit & Tree Nuts", + commodity_desc: "Grapes", + statisticcat_desc: { [areaHarvested]: "Area Harvested", [yieldInBU]: "Yield" }, - dataItem: { - [areaHarvested]: "", - [yieldInBU]: "" + short_desc: { + [areaHarvested]: ["GRAPES, ORGANIC - ACRES HARVESTED"], + [yieldInBU]: ["GRAPES - YIELD, MEASURED IN TONS / ACRE"] }, - domains: "Total", + domain_desc: "Total", geographicLevels: "State, County", years: { county: allYears, @@ -309,18 +293,18 @@ export const queryData: Array = [ { plugInAttribute: "Oats", numberOfAttributeColumnsInCodap: 1, - sector: "Crops", - group: "Field Crops", - commodity: "Oats", - category: { + sect_desc: "Crops", + group_desc: "Field Crops", + commodity_desc: "Oats", + statisticcat_desc: { [areaHarvested]: "Area Harvested", [yieldInBU]: "Yield" }, - dataItem: { - [areaHarvested]: "Oats - Acres Harvested", - [yieldInBU]: "Oats - Yield, measured in BU / acre" + short_desc: { + [areaHarvested]: ["Oats - Acres Harvested"], + [yieldInBU]: ["Oats - Yield, measured in BU / acre"] }, - domains: "Total", + domain_desc: "Total", geographicLevels: "State, County", years: { county: allYears, @@ -330,18 +314,18 @@ export const queryData: Array = [ { plugInAttribute: "Soybeans", numberOfAttributeColumnsInCodap: 1, - sector: "Crops", - group: "Field Crops", - commodity: "Soybeans", - category: { + sect_desc: "Crops", + group_desc: "Field Crops", + commodity_desc: "Soybeans", + statisticcat_desc: { [areaHarvested]: "Area Harvested", [yieldInBU]: "Yield" }, - dataItem: { - [areaHarvested]: "Soybeans - Acres Harvested", - [yieldInBU]: "Soybeans - Yield, measured in BU / acre" + short_desc: { + [areaHarvested]: ["Soybeans - Acres Harvested"], + [yieldInBU]: ["Soybeans - Yield, measured in BU / acre"] }, - domains: "Total", + domain_desc: "Total", geographicLevels: "State, County", years: { county: allYears, @@ -351,18 +335,18 @@ export const queryData: Array = [ { plugInAttribute: "Wheat", numberOfAttributeColumnsInCodap: 1, - sector: "Crops", - group: "Field Crops", - commodity: "Wheat", - category: { + sect_desc: "Crops", + group_desc: "Field Crops", + commodity_desc: "Wheat", + statisticcat_desc: { [areaHarvested]: "Area Harvested", [yieldInBU]: "Yield" }, - dataItem: { - [areaHarvested]: "Wheat - Acres Harvested", - [yieldInBU]: "Wheat - Yield, measured in BU / acre" + short_desc: { + [areaHarvested]: ["Wheat - Acres Harvested"], + [yieldInBU]: ["Wheat - Yield, measured in BU / acre"] }, - domains: "Total", + domain_desc: "Total", geographicLevels: "State, County", years: { county: allYears, From c879b5e68e2cd9046d27606bed1f4acdfd3f4a55 Mon Sep 17 00:00:00 2001 From: lublagg Date: Mon, 18 Sep 2023 21:22:32 -0400 Subject: [PATCH 7/8] Add support for counties. --- src/components/app.tsx | 4 +- src/components/attribute-options.tsx | 4 +- src/components/dropdown.tsx | 2 +- src/components/options.tsx | 2 +- src/components/place-options.tsx | 4 +- src/components/summary.tsx | 4 +- src/components/utils.ts | 4 - src/components/years-options.tsx | 11 +- src/{components => constants}/constants.ts | 106 +- src/constants/counties.ts | 3285 ++++++++++++++++++++ src/constants/query-headers.ts | 352 +++ src/{components => constants}/types.ts | 33 +- src/scripts/api.ts | 118 +- src/scripts/connect.js | 14 +- src/scripts/query-headers.ts | 356 --- src/scripts/utils.ts | 10 + 16 files changed, 3837 insertions(+), 472 deletions(-) delete mode 100644 src/components/utils.ts rename src/{components => constants}/constants.ts (72%) create mode 100644 src/constants/counties.ts create mode 100644 src/constants/query-headers.ts rename src/{components => constants}/types.ts (68%) delete mode 100644 src/scripts/query-headers.ts create mode 100644 src/scripts/utils.ts diff --git a/src/components/app.tsx b/src/components/app.tsx index 542371c..57539de 100644 --- a/src/components/app.tsx +++ b/src/components/app.tsx @@ -2,8 +2,8 @@ import React, {useEffect, useState} from "react"; import { Dropdown } from "./dropdown"; import classnames from "classnames"; import { Information } from "./information"; -import { attributeOptions, categories, defaultSelectedOptions } from "./constants"; -import { IStateOptions } from "./types"; +import { attributeOptions, categories, defaultSelectedOptions } from "../constants/constants"; +import { IStateOptions } from "../constants/types"; import { createTableFromSelections } from "../scripts/api"; import { connect } from "../scripts/connect"; diff --git a/src/components/attribute-options.tsx b/src/components/attribute-options.tsx index 939aeee..3a47779 100644 --- a/src/components/attribute-options.tsx +++ b/src/components/attribute-options.tsx @@ -1,8 +1,8 @@ import React from "react"; import { Options } from "./options"; -import { attributeOptions } from "./constants"; +import { attributeOptions } from "../constants/constants"; import classnames from "classnames"; -import { IStateOptions } from "./types"; +import { IStateOptions } from "../constants/types"; import css from "./options.scss"; diff --git a/src/components/dropdown.tsx b/src/components/dropdown.tsx index 27e56a3..974314e 100644 --- a/src/components/dropdown.tsx +++ b/src/components/dropdown.tsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; import classnames from "classnames"; import { PlaceOptions } from "./place-options"; -import { defaultSelectedOptions } from "./constants"; +import { defaultSelectedOptions } from "../constants/constants"; import { AttributeOptions } from "./attribute-options"; import css from "./dropdown.scss"; diff --git a/src/components/options.tsx b/src/components/options.tsx index 023c494..008dd5b 100644 --- a/src/components/options.tsx +++ b/src/components/options.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { IStateOptions, OptionKey } from "./types"; +import { IStateOptions, OptionKey } from "../constants/types"; import css from "./options.scss"; diff --git a/src/components/place-options.tsx b/src/components/place-options.tsx index 7e5bb04..6294bea 100644 --- a/src/components/place-options.tsx +++ b/src/components/place-options.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { placeOptions } from "./constants"; -import { IStateOptions } from "./types"; +import { placeOptions } from "../constants/constants"; +import { IStateOptions } from "../constants/types"; import { Options } from "./options"; import css from "./options.scss"; diff --git a/src/components/summary.tsx b/src/components/summary.tsx index 43a32a7..71d60ea 100644 --- a/src/components/summary.tsx +++ b/src/components/summary.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { IStateOptions } from "./types"; -import { attributeOptions } from "./constants"; +import { IStateOptions } from "../constants/types"; +import { attributeOptions } from "../constants/constants"; interface IProps { category: string; diff --git a/src/components/utils.ts b/src/components/utils.ts deleted file mode 100644 index 3f05872..0000000 --- a/src/components/utils.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const flatten = (arr: any[]): any[] => { - return arr.reduce((acc: any[], val: any) => - Array.isArray(val) ? acc.concat(flatten(val)) : acc.concat(val), []); -}; diff --git a/src/components/years-options.tsx b/src/components/years-options.tsx index bd0ab11..8413fbc 100644 --- a/src/components/years-options.tsx +++ b/src/components/years-options.tsx @@ -1,9 +1,9 @@ import React, { useEffect, useState } from "react"; -import { attributeOptions, yearsOptions } from "./constants"; -import { IStateOptions } from "./types"; +import { attributeOptions, yearsOptions } from "../constants/constants"; +import { IStateOptions } from "../constants/types"; import { Options } from "./options"; -import { queryData } from "../scripts/query-headers"; -import { flatten } from "./utils"; +import { queryData } from "../constants/query-headers"; +import { flatten } from "../scripts/utils"; import css from "./options.scss"; @@ -24,11 +24,10 @@ export const YearsOptions: React.FC = (props) => { return; } - const yearKeyToUse = selectedOptions.geographicLevel === "County" ? "county" : "state"; const allSelectedAttrs = flatten(selectedAttrKeys.map((key) => selectedOptions[key])); const newAvailableYears = allSelectedAttrs.reduce((years, attr) => { const subAttrData = queryData.find((d) => d.plugInAttribute === attr); - const availableYears = subAttrData?.years[yearKeyToUse]; + const availableYears = subAttrData?.years[selectedOptions.geographicLevel]; if (availableYears) { availableYears.forEach((y) => { years.add(y); diff --git a/src/components/constants.ts b/src/constants/constants.ts similarity index 72% rename from src/components/constants.ts rename to src/constants/constants.ts index 23aaecf..234e08c 100644 --- a/src/components/constants.ts +++ b/src/constants/constants.ts @@ -7,62 +7,66 @@ export const geographicLevelOptions: IAttrOptions = { options : ["State", "County"] }; +export const fiftyStates = [ + "Alabama", + "Alaska", + "Arizona", + "Arkansas", + "California", + "Colorado", + "Connecticut", + "Delaware", + "Florida", + "Georgia", + "Hawaii", + "Idaho", + "Illinois", + "Indiana", + "Iowa", + "Kansas", + "Kentucky", + "Louisiana", + "Maine", + "Maryland", + "Massachusetts", + "Michigan", + "Minnesota", + "Mississippi", + "Missouri", + "Montana", + "Nebraska", + "Nevada", + "New Hampshire", + "New Jersey", + "New Mexico", + "New York", + "North Carolina", + "North Dakota", + "Ohio", + "Oklahoma", + "Oregon", + "Pennsylvania", + "Rhode Island", + "South Carolina", + "South Dakota", + "Tennessee", + "Texas", + "Utah", + "Vermont", + "Virginia", + "Washington", + "West Virginia", + "Wisconsin", + "Wyoming" +] + export const stateOptions: IAttrOptions = { label: null, key: "states", instructions: "Choose states to include in your dataset from the list below", options: [ "All States", - "Alabama", - "Alaska", - "Arizona", - "Arkansas", - "California", - "Colorado", - "Connecticut", - "Delaware", - "Florida", - "Georgia", - "Hawaii", - "Idaho", - "Illinois", - "Indiana", - "Iowa", - "Kansas", - "Kentucky", - "Louisiana", - "Maine", - "Maryland", - "Massachusetts", - "Michigan", - "Minnesota", - "Mississippi", - "Missouri", - "Montana", - "Nebraska", - "Nevada", - "New Hampshire", - "New Jersey", - "New Mexico", - "New York", - "North Carolina", - "North Dakota", - "Ohio", - "Oklahoma", - "Oregon", - "Pennsylvania", - "Rhode Island", - "South Carolina", - "South Dakota", - "Tennessee", - "Texas", - "Utah", - "Vermont", - "Virginia", - "Washington", - "West Virginia", - "Wisconsin", - "Wyoming" + ...fiftyStates ] }; @@ -128,4 +132,4 @@ export const defaultSelectedOptions: IStateOptions = { cropUnits: "", crops: [], years: [] -}; +}; \ No newline at end of file diff --git a/src/constants/counties.ts b/src/constants/counties.ts new file mode 100644 index 0000000..f97563e --- /dev/null +++ b/src/constants/counties.ts @@ -0,0 +1,3285 @@ +export const countyData: {[state: string]: string[]} = { + "Alabama": [ + "AUTAUGA", + "BALDWIN", + "BARBOUR", + "BIBB", + "BLOUNT", + "BULLOCK", + "BUTLER", + "CALHOUN", + "CHAMBERS", + "CHEROKEE", + "CHILTON", + "CHOCTAW", + "CLARKE", + "CLAY", + "CLEBURNE", + "COFFEE", + "COLBERT", + "CONECUH", + "COOSA", + "COVINGTON", + "CRENSHAW", + "CULLMAN", + "DALE", + "DALLAS", + "DE KALB", + "ELMORE", + "ESCAMBIA", + "ETOWAH", + "FAYETTE", + "FRANKLIN", + "GENEVA", + "GREENE", + "HALE", + "HENRY", + "HOUSTON", + "JACKSON", + "JEFFERSON", + "LAMAR", + "LAUDERDALE", + "LAWRENCE", + "LEE", + "LIMESTONE", + "LOWNDES", + "MACON", + "MADISON", + "MARENGO", + "MARION", + "MARSHALL", + "MOBILE", + "MONROE", + "MONTGOMERY", + "MORGAN", + + "OTHER COUNTIES", + "PERRY", + "PICKENS", + "PIKE", + "RANDOLPH", + "RUSSELL", + "SAINT CLAIR", + "SHELBY", + "SUMTER", + "TALLADEGA", + "TALLAPOOSA", + "TUSCALOOSA", + "WALKER", + "WASHINGTON", + "WILCOX", + "WINSTON" + ], + "Alaska": [ + "ALEUTIAN ISLANDS", + "ANCHORAGE", + "FAIRBANKS NORTH STAR", + "JUNEAU", + "KENAI PENINSULA", + + "OTHER COUNTIES" + ], + "Arizona": [ + "APACHE", + "COCHISE", + "COCONINO", + "GILA", + "GRAHAM", + "GREENLEE", + "LAPAZ", + "MARICOPA", + "MOHAVE", + "NAVAJO", + + "OTHER COUNTIES", + "PIMA", + "PINAL", + "SANTA CRUZ", + "YAVAPAI", + "YUMA" + ], + "Arkansas": [ + "ARKANSAS", + "ASHLEY", + "BAXTER", + "BENTON", + "BOONE", + "BRADLEY", + "CALHOUN", + "CARROLL", + "CHICOT", + "CLARK", + "CLAY", + "CLEBURNE", + "CLEVELAND", + "COLUMBIA", + "CONWAY", + "CRAIGHEAD", + "CRAWFORD", + "CRITTENDEN", + "CROSS", + "DALLAS", + "DESHA", + "DREW", + "FAULKNER", + "FRANKLIN", + "FULTON", + "GARLAND", + "GRANT", + "GREENE", + "HEMPSTEAD", + "HOT SPRING", + "HOWARD", + "INDEPENDENCE", + "IZARD", + "JACKSON", + "JEFFERSON", + "JOHNSON", + "LAFAYETTE", + "LAWRENCE", + "LEE", + "LINCOLN", + "LITTLE RIVER", + "LOGAN", + "LONOKE", + "MADISON", + "MARION", + "MILLER", + "MISSISSIPPI", + "MONROE", + "MONTGOMERY", + "NEVADA", + "NEWTON", + + "OTHER COUNTIES", + "OUACHITA", + "PERRY", + "PHILLIPS", + "PIKE", + "POINSETT", + "POLK", + "POPE", + "PRAIRIE", + "PULASKI", + "RANDOLPH", + "SAINT FRANCIS", + "SALINE", + "SCOTT", + "SEARCY", + "SEBASTIAN", + "SEVIER", + "SHARP", + "STONE", + "UNION", + "VAN BUREN", + "WASHINGTON", + "WHITE", + "WOODRUFF", + "YELL" +], + "California": [ + "ALAMEDA", + "ALPINE", + "AMADOR", + "BUTTE", + "CALAVERAS", + "COLUSA", + "CONTRA COSTA", + "DEL NORTE", + "EL DORADO", + "FRESNO", + "GLENN", + "HUMBOLDT", + "IMPERIAL", + "INYO", + "KERN", + "KINGS", + "LAKE", + "LASSEN", + "LOS ANGELES", + "MADERA", + "MARIN", + "MARIPOSA", + "MENDOCINO", + "MERCED", + "MODOC", + "MONO", + "MONTEREY", + "NAPA", + "NEVADA", + "ORANGE", + + "OTHER COUNTIES", + "PLACER", + "PLUMAS", + "RIVERSIDE", + "SACRAMENTO", + "SAN BENITO", + "SAN BERNARDINO", + "SAN DIEGO", + "SAN FRANCISCO", + "SAN JOAQUIN", + "SAN LUIS OBISPO", + "SAN MATEO", + "SANTA BARBARA", + "SANTA CLARA", + "SANTA CRUZ", + "SHASTA", + "SIERRA", + "SISKIYOU", + "SOLANO", + "SONOMA", + "STANISLAUS", + "SUTTER", + "TEHAMA", + "TRINITY", + "TULARE", + "TUOLUMNE", + "VENTURA", + "YOLO", + "YUBA" + ], + "Colorado": [ + "ADAMS", + "ALAMOSA", + "ARAPAHOE", + "ARCHULETA", + "BACA", + "BENT", + "BOULDER", + "BROOMFIELD", + "CHAFFEE", + "CHEYENNE", + "CLEAR CREEK", + "CONEJOS", + "COSTILLA", + "CROWLEY", + "CUSTER", + "DELTA", + "DENVER", + "DOLORES", + "DOUGLAS", + "EAGLE", + "EL PASO", + "ELBERT", + "FREMONT", + "GARFIELD", + "GILPIN", + "GRAND", + "GUNNISON", + "HINSDALE", + "HUERFANO", + "JACKSON", + "JEFFERSON", + "KIOWA", + "KIT CARSON", + "LA PLATA", + "LAKE", + "LARIMER", + "LAS ANIMAS", + "LINCOLN", + "LOGAN", + "MESA", + "MINERAL", + "MOFFAT", + "MONTEZUMA", + "MONTROSE", + "MORGAN", + "OTERO", + + "OTHER COUNTIES", + "OURAY", + "PARK", + "PHILLIPS", + "PITKIN", + "PROWERS", + "PUEBLO", + "RIO BLANCO", + "RIO GRANDE", + "ROUTT", + "SAGUACHE", + "SAN JUAN", + "SAN MIGUEL", + "SEDGWICK", + "SUMMIT", + "TELLER", + "WASHINGTON", + "WELD", + "YUMA" +], + "Connecticut": [ + "FAIRFIELD", + "HARTFORD", + "LITCHFIELD", + "MIDDLESEX", + "NEW HAVEN", + "NEW LONDON", + + "OTHER COUNTIES", + "TOLLAND", + "WINDHAM" +], + "Delaware": [ + "KENT", + "NEW CASTLE", + "OTHER COUNTIES", + "SUSSEX" +], + "Florida": [ + "ALACHUA", + "BAKER", + "BAY", + "BRADFORD", + "BREVARD", + "BROWARD", + "CALHOUN", + "CHARLOTTE", + "CITRUS", + "CLAY", + "COLLIER", + "COLUMBIA", + "DE SOTO", + "DIXIE", + "DUVAL", + "ESCAMBIA", + "FLAGLER", + "FRANKLIN", + "GADSDEN", + "GILCHRIST", + "GLADES", + "GULF", + "HAMILTON", + "HARDEE", + "HENDRY", + "HERNANDO", + "HIGHLANDS", + "HILLSBOROUGH", + "HOLMES", + "INDIAN RIVER", + "JACKSON", + "JEFFERSON", + "LAFAYETTE", + "LAKE", + "LEE", + "LEON", + "LEVY", + "LIBERTY", + "MADISON", + "MANATEE", + "MARION", + "MARTIN", + "MIAMI-DADE", + "MONROE", + "NASSAU", + "OKALOOSA", + "OKEECHOBEE", + "ORANGE", + "OSCEOLA", + + "OTHER COUNTIES", + "PALM BEACH", + "PASCO", + "PINELLAS", + "POLK", + "PUTNAM", + "SANTA ROSA", + "SARASOTA", + "SEMINOLE", + "ST. JOHNS", + "ST. LUCIE", + "SUMTER", + "SUWANNEE", + "TAYLOR", + "UNION", + "VOLUSIA", + "WAKULLA", + "WALTON", + "WASHINGTON" +], + "Georgia": [ + "APPLING", + "ATKINSON", + "BACON", + "BAKER", + "BALDWIN", + "BANKS", + "BARROW", + "BARTOW", + "BEN HILL", + "BERRIEN", + "BIBB", + "BLECKLEY", + "BRANTLEY", + "BROOKS", + "BRYAN", + "BULLOCH", + "BURKE", + "BUTTS", + "CALHOUN", + "CAMDEN", + "CANDLER", + "CARROLL", + "CATOOSA", + "CHARLTON", + "CHATHAM", + "CHATTAHOOCHEE", + "CHATTOOGA", + "CHEROKEE", + "CLARKE", + "CLAY", + "CLAYTON", + "CLINCH", + "COBB", + "COFFEE", + "COLQUITT", + "COLUMBIA", + "COOK", + "COWETA", + "CRAWFORD", + "CRISP", + "DADE", + "DAWSON", + "DE KALB", + "DECATUR", + "DODGE", + "DOOLY", + "DOUGHERTY", + "DOUGLAS", + "EARLY", + "ECHOLS", + "EFFINGHAM", + "ELBERT", + "EMANUEL", + "EVANS", + "FANNIN", + "FAYETTE", + "FLOYD", + "FORSYTH", + "FRANKLIN", + "FULTON", + "GILMER", + "GLASCOCK", + "GLYNN", + "GORDON", + "GRADY", + "GREENE", + "GWINNETT", + "HABERSHAM", + "HALL", + "HANCOCK", + "HARALSON", + "HARRIS", + "HART", + "HEARD", + "HENRY", + "HOUSTON", + "IRWIN", + "JACKSON", + "JASPER", + "JEFF DAVIS", + "JEFFERSON", + "JENKINS", + "JOHNSON", + "JONES", + "LAMAR", + "LANIER", + "LAURENS", + "LEE", + "LIBERTY", + "LINCOLN", + "LONG", + "LOWNDES", + "LUMPKIN", + "MACON", + "MADISON", + "MARION", + "MCDUFFIE", + "MCINTOSH", + "MERIWETHER", + "MILLER", + "MITCHELL", + "MONROE", + "MONTGOMERY", + "MORGAN", + "MURRAY", + "MUSCOGEE", + "NEWTON", + "OCONEE", + "OGLETHORPE", + + "OTHER COUNTIES", + "PAULDING", + "PEACH", + "PICKENS", + "PIERCE", + "PIKE", + "POLK", + "PULASKI", + "PUTNAM", + "QUITMAN", + "RABUN", + "RANDOLPH", + "RICHMOND", + "ROCKDALE", + "SCHLEY", + "SCREVEN", + "SEMINOLE", + "SPALDING", + "STEPHENS", + "STEWART", + "SUMTER", + "TALBOT", + "TALIAFERRO", + "TATTNALL", + "TAYLOR", + "TELFAIR", + "TERRELL", + "THOMAS", + "TIFT", + "TOOMBS", + "TOWNS", + "TREUTLEN", + "TROUP", + "TURNER", + "TWIGGS", + "UNION", + "UPSON", + "WALKER", + "WALTON", + "WARE", + "WARREN", + "WASHINGTON", + "WAYNE", + "WEBSTER", + "WHEELER", + "WHITE", + "WHITFIELD", + "WILCOX", + "WILKES", + "WILKINSON", + "WORTH" +], + "Hawaii": [ + "HAWAII", + "HONOLULU", + "KAUAI", + "MAUI & KALWAO", + + "OTHER COUNTIES" +], + "Idaho": [ + "ADA", + "ADAMS", + "BANNOCK", + "BEAR LAKE", + "BENEWAH", + "BINGHAM", + "BLAINE", + "BOISE", + "BONNER", + "BONNEVILLE", + "BOUNDARY", + "BUTTE", + "CAMAS", + "CANYON", + "CARIBOU", + "CASSIA", + "CLARK", + "CLEARWATER", + "CUSTER", + "ELMORE", + "FRANKLIN", + "FREMONT", + "GEM", + "GOODING", + "IDAHO", + "JEFFERSON", + "JEROME", + "KOOTENAI", + "LATAH", + "LEMHI", + "LEWIS", + "LINCOLN", + "MADISON", + "MINIDOKA", + "NEZ PERCE", + "ONEIDA", + + "OTHER COUNTIES", + "OWYHEE", + "PAYETTE", + "POWER", + "SHOSHONE", + "TETON", + "TWIN FALLS", + "VALLEY", + "WASHINGTON" +], + "Illinois": [ + "ADAMS", + "ALEXANDER", + "BOND", + "BOONE", + "BROWN", + "BUREAU", + "CALHOUN", + "CARROLL", + "CASS", + "CHAMPAIGN", + "CHRISTIAN", + "CLARK", + "CLAY", + "CLINTON", + "COLES", + "COOK", + "CRAWFORD", + "CUMBERLAND", + "DE KALB", + "DE WITT", + "DOUGLAS", + "DU PAGE", + "EDGAR", + "EDWARDS", + "EFFINGHAM", + "FAYETTE", + "FORD", + "FRANKLIN", + "FULTON", + "GALLATIN", + "GREENE", + "GRUNDY", + "HAMILTON", + "HANCOCK", + "HARDIN", + "HENDERSON", + "HENRY", + "IROQUOIS", + "JACKSON", + "JASPER", + "JEFFERSON", + "JERSEY", + "JO DAVIESS", + "JOHNSON", + "KANE", + "KANKAKEE", + "KENDALL", + "KNOX", + "LA SALLE", + "LAKE", + "LAWRENCE", + "LEE", + "LIVINGSTON", + "LOGAN", + "MACON", + "MACOUPIN", + "MADISON", + "MARION", + "MARSHALL", + "MASON", + "MASSAC", + "MCDONOUGH", + "MCHENRY", + "MCLEAN", + "MENARD", + "MERCER", + "MONROE", + "MONTGOMERY", + "MORGAN", + "MOULTRIE", + "OGLE", + + "OTHER COUNTIES", + "PEORIA", + "PERRY", + "PIATT", + "PIKE", + "POPE", + "PULASKI", + "PUTNAM", + "RANDOLPH", + "RICHLAND", + "ROCK ISLAND", + "SALINE", + "SANGAMON", + "SCHUYLER", + "SCOTT", + "SHELBY", + "ST CLAIR", + "STARK", + "STEPHENSON", + "TAZEWELL", + "UNION", + "VERMILION", + "WABASH", + "WARREN", + "WASHINGTON", + "WAYNE", + "WHITE", + "WHITESIDE", + "WILL", + "WILLIAMSON", + "WINNEBAGO", + "WOODFORD" +], + "Indiana": [ + "ADAMS", + "ALLEN", + "BARTHOLOMEW", + "BENTON", + "BLACKFORD", + "BOONE", + "BROWN", + "CARROLL", + "CASS", + "CLARK", + "CLAY", + "CLINTON", + "CRAWFORD", + "DAVIESS", + "DE KALB", + "DEARBORN", + "DECATUR", + "DELAWARE", + "DUBOIS", + "ELKHART", + "FAYETTE", + "FLOYD", + "FOUNTAIN", + "FRANKLIN", + "FULTON", + "GIBSON", + "GRANT", + "GREENE", + "HAMILTON", + "HANCOCK", + "HARRISON", + "HENDRICKS", + "HENRY", + "HOWARD", + "HUNTINGTON", + "JACKSON", + "JASPER", + "JAY", + "JEFFERSON", + "JENNINGS", + "JOHNSON", + "KNOX", + "KOSCIUSKO", + "LA PORTE", + "LAGRANGE", + "LAKE", + "LAWRENCE", + "MADISON", + "MARION", + "MARSHALL", + "MARTIN", + "MIAMI", + "MONROE", + "MONTGOMERY", + "MORGAN", + "NEWTON", + "NOBLE", + "OHIO", + "ORANGE", + + "OTHER COUNTIES", + "OWEN", + "PARKE", + "PERRY", + "PIKE", + "PORTER", + "POSEY", + "PULASKI", + "PUTNAM", + "RANDOLPH", + "RIPLEY", + "RUSH", + "SCOTT", + "SHELBY", + "SPENCER", + "ST. JOSEPH", + "STARKE", + "STEUBEN", + "SULLIVAN", + "SWITZERLAND", + "TIPPECANOE", + "TIPTON", + "UNION", + "VANDERBURGH", + "VERMILLION", + "VIGO", + "WABASH", + "WARREN", + "WARRICK", + "WASHINGTON", + "WAYNE", + "WELLS", + "WHITE", + "WHITLEY" +], + "Iowa": [ + "ADAIR", + "ADAMS", + "ALLAMAKEE", + "APPANOOSE", + "AUDUBON", + "BENTON", + "BLACK HAWK", + "BOONE", + "BREMER", + "BUCHANAN", + "BUENA VISTA", + "BUTLER", + "CALHOUN", + "CARROLL", + "CASS", + "CEDAR", + "CERRO GORDO", + "CHEROKEE", + "CHICKASAW", + "CLARKE", + "CLAY", + "CLAYTON", + "CLINTON", + "CRAWFORD", + "DALLAS", + "DAVIS", + "DECATUR", + "DELAWARE", + "DES MOINES", + "DICKINSON", + "DUBUQUE", + "EMMET", + "FAYETTE", + "FLOYD", + "FRANKLIN", + "FREMONT", + "GREENE", + "GRUNDY", + "GUTHRIE", + "HAMILTON", + "HANCOCK", + "HARDIN", + "HARRISON", + "HENRY", + "HOWARD", + "HUMBOLDT", + "IDA", + "IOWA", + "JACKSON", + "JASPER", + "JEFFERSON", + "JOHNSON", + "JONES", + "KEOKUK", + "KOSSUTH", + "LEE", + "LINN", + "LOUISA", + "LUCAS", + "LYON", + "MADISON", + "MAHASKA", + "MARION", + "MARSHALL", + "MILLS", + "MITCHELL", + "MONONA", + "MONROE", + "MONTGOMERY", + "MUSCATINE", + "O BRIEN", + "OSCEOLA", + + "OTHER COUNTIES", + "PAGE", + "PALO ALTO", + "PLYMOUTH", + "POCAHONTAS", + "POLK", + "POTTAWATTAMIE", + "POWESHIEK", + "RINGGOLD", + "SAC", + "SCOTT", + "SHELBY", + "SIOUX", + "STORY", + "TAMA", + "TAYLOR", + "UNION", + "VAN BUREN", + "WAPELLO", + "WARREN", + "WASHINGTON", + "WAYNE", + "WEBSTER", + "WINNEBAGO", + "WINNESHIEK", + "WOODBURY", + "WORTH", + "WRIGHT" +], + "Kansas": [ + "ALLEN", + "ANDERSON", + "ATCHISON", + "BARBER", + "BARTON", + "BOURBON", + "BROWN", + "BUTLER", + "CHASE", + "CHAUTAUQUA", + "CHEROKEE", + "CHEYENNE", + "CLARK", + "CLAY", + "CLOUD", + "COFFEY", + "COMANCHE", + "COWLEY", + "CRAWFORD", + "DECATUR", + "DICKINSON", + "DONIPHAN", + "DOUGLAS", + "EDWARDS", + "ELK", + "ELLIS", + "ELLSWORTH", + "FINNEY", + "FORD", + "FRANKLIN", + "GEARY", + "GOVE", + "GRAHAM", + "GRANT", + "GRAY", + "GREELEY", + "GREENWOOD", + "HAMILTON", + "HARPER", + "HARVEY", + "HASKELL", + "HODGEMAN", + "JACKSON", + "JEFFERSON", + "JEWELL", + "JOHNSON", + "KEARNY", + "KINGMAN", + "KIOWA", + "LABETTE", + "LANE", + "LEAVENWORTH", + "LINCOLN", + "LINN", + "LOGAN", + "LYON", + "MARION", + "MARSHALL", + "MCPHERSON", + "MEADE", + "MIAMI", + "MITCHELL", + "MONTGOMERY", + "MORRIS", + "MORTON", + "NEMAHA", + "NEOSHO", + "NESS", + "NORTON", + "OSAGE", + "OSBORNE", + + "OTHER COUNTIES", + "OTTAWA", + "PAWNEE", + "PHILLIPS", + "POTTAWATOMIE", + "PRATT", + "RAWLINS", + "RENO", + "REPUBLIC", + "RICE", + "RILEY", + "ROOKS", + "RUSH", + "RUSSELL", + "SALINE", + "SCOTT", + "SEDGWICK", + "SEWARD", + "SHAWNEE", + "SHERIDAN", + "SHERMAN", + "SMITH", + "STAFFORD", + "STANTON", + "STEVENS", + "SUMNER", + "THOMAS", + "TREGO", + "WABAUNSEE", + "WALLACE", + "WASHINGTON", + "WICHITA", + "WILSON", + "WOODSON", + "WYANDOTTE" +], + "Kentucky": [ + "ADAIR", + "ALLEN", + "ANDERSON", + "BALLARD", + "BARREN", + "BATH", + "BELL", + "BOONE", + "BOURBON", + "BOYD", + "BOYLE", + "BRACKEN", + "BREATHITT", + "BRECKINRIDGE", + "BULLITT", + "BUTLER", + "CALDWELL", + "CALLOWAY", + "CAMPBELL", + "CARLISLE", + "CARROLL", + "CARTER", + "CASEY", + "CHRISTIAN", + "CLARK", + "CLAY", + "CLINTON", + "CRITTENDEN", + "CUMBERLAND", + "DAVIESS", + "EDMONSON", + "ELLIOTT", + "ESTILL", + "FAYETTE", + "FLEMING", + "FLOYD", + "FRANKLIN", + "FULTON", + "GALLATIN", + "GARRARD", + "GRANT", + "GRAVES", + "GRAYSON", + "GREEN", + "GREENUP", + "HANCOCK", + "HARDIN", + "HARLAN", + "HARRISON", + "HART", + "HENDERSON", + "HENRY", + "HICKMAN", + "HOPKINS", + "JACKSON", + "JEFFERSON", + "JESSAMINE", + "JOHNSON", + "KENTON", + "KNOTT", + "KNOX", + "LARUE", + "LAUREL", + "LAWRENCE", + "LEE", + "LESLIE", + "LETCHER", + "LEWIS", + "LINCOLN", + "LIVINGSTON", + "LOGAN", + "LYON", + "MADISON", + "MAGOFFIN", + "MARION", + "MARSHALL", + "MARTIN", + "MASON", + "MCCRACKEN", + "MCCREARY", + "MCLEAN", + "MEADE", + "MENIFEE", + "MERCER", + "METCALFE", + "MONROE", + "MONTGOMERY", + "MORGAN", + "MUHLENBERG", + "NELSON", + "NICHOLAS", + "OHIO", + "OLDHAM", + + "OTHER COUNTIES", + "OWEN", + "OWSLEY", + "PENDLETON", + "PERRY", + "PIKE", + "POWELL", + "PULASKI", + "ROBERTSON", + "ROCKCASTLE", + "ROWAN", + "RUSSELL", + "SCOTT", + "SHELBY", + "SIMPSON", + "SPENCER", + "TAYLOR", + "TODD", + "TRIGG", + "TRIMBLE", + "UNION", + "WARREN", + "WASHINGTON", + "WAYNE", + "WEBSTER", + "WHITLEY", + "WOLFE", + "WOODFORD" +], + "Louisiana": [ + "ACADIA", + "ALLEN", + "ASCENSION", + "ASSUMPTION", + "AVOYELLES", + "BEAUREGARD", + "BIENVILLE", + "BOSSIER", + "CADDO", + "CALCASIEU", + "CALDWELL", + "CAMERON", + "CATAHOULA", + "CLAIBORNE", + "CONCORDIA", + "DE SOTO", + "EAST BATON ROUGE", + "EAST CARROLL", + "EAST FELICIANA", + "EVANGELINE", + "FRANKLIN", + "GRANT", + "IBERIA", + "IBERVILLE", + "JACKSON", + "JEFFERSON", + "JEFFERSON DAVIS", + "LA SALLE", + "LAFAYETTE", + "LAFOURCHE", + "LINCOLN", + "LIVINGSTON", + "MADISON", + "MOREHOUSE", + "NATCHITOCHES", + "ORLEANS", + + "OTHER COUNTIES", + "OUACHITA", + "PLAQUEMINES", + "POINTE COUPEE", + "RAPIDES", + "RED RIVER", + "RICHLAND", + "SABINE", + "SAINT BERNARD", + "SAINT CHARLES", + "SAINT HELENA", + "SAINT JAMES", + "SAINT LANDRY", + "SAINT MARTIN", + "SAINT MARY", + "SAINT TAMMANY", + "ST. JOHN THE BAPTIST", + "TANGIPAHOA", + "TENSAS", + "TERREBONNE", + "UNION", + "VERMILION", + "VERNON", + "WASHINGTON", + "WEBSTER", + "WEST BATON ROUGE", + "WEST CARROLL", + "WEST FELICIANA", + "WINN" +], + "Maine": [ + "ANDROSCOGGIN", + "AROOSTOOK", + "CUMBERLAND", + "FRANKLIN", + "HANCOCK", + "KENNEBEC", + "KNOX", + "LINCOLN", + + "OTHER COUNTIES", + "OXFORD", + "PENOBSCOT", + "PISCATAQUIS", + "SAGADAHOC", + "SOMERSET", + "WALDO", + "WASHINGTON", + "YORK" +], + "Maryland": [ + "ALLEGANY", + "ANNE ARUNDEL", + "BALTIMORE", + "CALVERT", + "CAROLINE", + "CARROLL", + "CECIL", + "CHARLES", + "DORCHESTER", + "FREDERICK", + "GARRETT", + "HARFORD", + "HOWARD", + "KENT", + "MONTGOMERY", + + "OTHER COUNTIES", + "PRINCE GEORGES", + "QUEEN ANNES", + "SOMERSET", + "ST MARYS", + "TALBOT", + "WASHINGTON", + "WICOMICO", + "WORCESTER" +], + "Massachusetts": [ + "BARNSTABLE", + "BERKSHIRE", + "BRISTOL", + "DUKES", + "ESSEX", + "FRANKLIN", + "HAMPDEN", + "HAMPSHIRE", + "MIDDLESEX", + "NANTUCKET", + "NORFOLK", + + "OTHER COUNTIES", + "PLYMOUTH", + "SUFFOLK", + "WORCESTER" +], + "Michigan": [ + "ALCONA", + "ALGER", + "ALLEGAN", + "ALPENA", + "ANTRIM", + "ARENAC", + "BARAGA", + "BARRY", + "BAY", + "BENZIE", + "BERRIEN", + "BRANCH", + "CALHOUN", + "CASS", + "CHARLEVOIX", + "CHEBOYGAN", + "CHIPPEWA", + "CLARE", + "CLINTON", + "CRAWFORD", + "DELTA", + "DICKINSON", + "EATON", + "EMMET", + "GENESEE", + "GLADWIN", + "GOGEBIC", + "GRAND TRAVERSE", + "GRATIOT", + "HILLSDALE", + "HOUGHTON", + "HURON", + "INGHAM", + "IONIA", + "IOSCO", + "IRON", + "ISABELLA", + "JACKSON", + "KALAMAZOO", + "KALKASKA", + "KENT", + "KEWEENAW", + "LAKE", + "LAPEER", + "LEELANAU", + "LENAWEE", + "LIVINGSTON", + "LUCE", + "MACKINAC", + "MACOMB", + "MANISTEE", + "MARQUETTE", + "MASON", + "MECOSTA", + "MENOMINEE", + "MIDLAND", + "MISSAUKEE", + "MONROE", + "MONTCALM", + "MONTMORENCY", + "MUSKEGON", + "NEWAYGO", + "OAKLAND", + "OCEANA", + "OGEMAW", + "ONTONAGON", + "OSCEOLA", + "OSCODA", + + "OTHER COUNTIES", + "OTSEGO", + "OTTAWA", + "PRESQUE ISLE", + "ROSCOMMON", + "SAGINAW", + "SANILAC", + "SCHOOLCRAFT", + "SHIAWASSEE", + "ST CLAIR", + "ST JOSEPH", + "TUSCOLA", + "VAN BUREN", + "WASHTENAW", + "WAYNE", + "WEXFORD" +], + "Minnesota": [ + "AITKIN", + "ANOKA", + "BECKER", + "BELTRAMI", + "BENTON", + "BIG STONE", + "BLUE EARTH", + "BROWN", + "CARLTON", + "CARVER", + "CASS", + "CHIPPEWA", + "CHISAGO", + "CLAY", + "CLEARWATER", + "COOK", + "COTTONWOOD", + "CROW WING", + "DAKOTA", + "DODGE", + "DOUGLAS", + "FARIBAULT", + "FILLMORE", + "FREEBORN", + "GOODHUE", + "GRANT", + "HENNEPIN", + "HOUSTON", + "HUBBARD", + "ISANTI", + "ITASCA", + "JACKSON", + "KANABEC", + "KANDIYOHI", + "KITTSON", + "KOOCHICHING", + "LAC QUI PARLE", + "LAKE", + "LAKE OF THE WOODS", + "LE SUEUR", + "LINCOLN", + "LYON", + "MAHNOMEN", + "MARSHALL", + "MARTIN", + "MCLEOD", + "MEEKER", + "MILLE LACS", + "MORRISON", + "MOWER", + "MURRAY", + "NICOLLET", + "NOBLES", + "NORMAN", + "OLMSTED", + + "OTHER COUNTIES", + "OTTER TAIL", + "PENNINGTON", + "PINE", + "PIPESTONE", + "POLK", + "POPE", + "RAMSEY", + "RED LAKE", + "REDWOOD", + "RENVILLE", + "RICE", + "ROCK", + "ROSEAU", + "SCOTT", + "SHERBURNE", + "SIBLEY", + "ST. LOUIS", + "STEARNS", + "STEELE", + "STEVENS", + "SWIFT", + "TODD", + "TRAVERSE", + "WABASHA", + "WADENA", + "WASECA", + "WASHINGTON", + "WATONWAN", + "WILKIN", + "WINONA", + "WRIGHT", + "YELLOW MEDICINE" +], + "Mississippi": [ + "ADAMS", + "ALCORN", + "AMITE", + "ATTALA", + "BENTON", + "BOLIVAR", + "CALHOUN", + "CARROLL", + "CHICKASAW", + "CHOCTAW", + "CLAIBORNE", + "CLARKE", + "CLAY", + "COAHOMA", + "COPIAH", + "COVINGTON", + "DE SOTO", + "FORREST", + "FRANKLIN", + "GEORGE", + "GREENE", + "GRENADA", + "HANCOCK", + "HARRISON", + "HINDS", + "HOLMES", + "HUMPHREYS", + "ISSAQUENA", + "ITAWAMBA", + "JACKSON", + "JASPER", + "JEFFERSON", + "JEFFERSON DAVIS", + "JONES", + "KEMPER", + "LAFAYETTE", + "LAMAR", + "LAUDERDALE", + "LAWRENCE", + "LEAKE", + "LEE", + "LEFLORE", + "LINCOLN", + "LOWNDES", + "MADISON", + "MARION", + "MARSHALL", + "MONROE", + "MONTGOMERY", + "NESHOBA", + "NEWTON", + "NOXUBEE", + "OKTIBBEHA", + + "OTHER COUNTIES", + "PANOLA", + "PEARL RIVER", + "PERRY", + "PIKE", + "PONTOTOC", + "PRENTISS", + "QUITMAN", + "RANKIN", + "SCOTT", + "SHARKEY", + "SIMPSON", + "SMITH", + "STONE", + "SUNFLOWER", + "TALLAHATCHIE", + "TATE", + "TIPPAH", + "TISHOMINGO", + "TUNICA", + "UNION", + "WALTHALL", + "WARREN", + "WASHINGTON", + "WAYNE", + "WEBSTER", + "WILKINSON", + "WINSTON", + "YALOBUSHA", + "YAZOO" +], + "Missouri": [ + "ADAIR", + "ANDREW", + "ATCHISON", + "AUDRAIN", + "BARRY", + "BARTON", + "BATES", + "BENTON", + "BOLLINGER", + "BOONE", + "BUCHANAN", + "BUTLER", + "CALDWELL", + "CALLAWAY", + "CAMDEN", + "CAPE GIRARDEAU", + "CARROLL", + "CARTER", + "CASS", + "CEDAR", + "CHARITON", + "CHRISTIAN", + "CLARK", + "CLAY", + "CLINTON", + "COLE", + "COOPER", + "CRAWFORD", + "DADE", + "DALLAS", + "DAVIESS", + "DE KALB", + "DENT", + "DOUGLAS", + "DUNKLIN", + "FRANKLIN", + "GASCONADE", + "GENTRY", + "GREENE", + "GRUNDY", + "HARRISON", + "HENRY", + "HICKORY", + "HOLT", + "HOWARD", + "HOWELL", + "IRON", + "JACKSON", + "JASPER", + "JEFFERSON", + "JOHNSON", + "KNOX", + "LACLEDE", + "LAFAYETTE", + "LAWRENCE", + "LEWIS", + "LINCOLN", + "LINN", + "LIVINGSTON", + "MACON", + "MADISON", + "MARIES", + "MARION", + "MCDONALD", + "MERCER", + "MILLER", + "MISSISSIPPI", + "MONITEAU", + "MONROE", + "MONTGOMERY", + "MORGAN", + "NEW MADRID", + "NEWTON", + "NODAWAY", + "OREGON", + "OSAGE", + + "OTHER COUNTIES", + "OZARK", + "PEMISCOT", + "PERRY", + "PETTIS", + "PHELPS", + "PIKE", + "PLATTE", + "POLK", + "PULASKI", + "PUTNAM", + "RALLS", + "RANDOLPH", + "RAY", + "REYNOLDS", + "RIPLEY", + "SALINE", + "SCHUYLER", + "SCOTLAND", + "SCOTT", + "SHANNON", + "SHELBY", + "ST CHARLES", + "ST CLAIR", + "ST FRANCOIS", + "ST LOUIS", + "STE GENEVIEVE", + "STE. GENEVIEVE", + "STODDARD", + "STONE", + "SULLIVAN", + "TANEY", + "TEXAS", + "VERNON", + "WARREN", + "WASHINGTON", + "WAYNE", + "WEBSTER", + "WORTH", + "WRIGHT" +], + "Montana": [ + "BEAVERHEAD", + "BIG HORN", + "BLAINE", + "BROADWATER", + "CARBON", + "CARTER", + "CASCADE", + "CHOUTEAU", + "CUSTER", + "DANIELS", + "DAWSON", + "DEER LODGE", + "FALLON", + "FERGUS", + "FLATHEAD", + "GALLATIN", + "GARFIELD", + "GLACIER", + "GOLDEN VALLEY", + "GRANITE", + "HILL", + "JEFFERSON", + "JUDITH BASIN", + "LAKE", + "LEWIS AND CLARK", + "LIBERTY", + "LINCOLN", + "MADISON", + "MCCONE", + "MEAGHER", + "MINERAL", + "MISSOULA", + "MUSSELSHELL", + + "OTHER COUNTIES", + "PARK", + "PETROLEUM", + "PHILLIPS", + "PONDERA", + "POWDER RIVER", + "POWELL", + "PRAIRIE", + "RAVALLI", + "RICHLAND", + "ROOSEVELT", + "ROSEBUD", + "SANDERS", + "SHERIDAN", + "SILVER BOW", + "STILLWATER", + "SWEET GRASS", + "TETON", + "TOOLE", + "TREASURE", + "VALLEY", + "WHEATLAND", + "WIBAUX", + "YELLOWSTONE" +], + "Nebraska": [ + "ADAMS", + "ANTELOPE", + "ARTHUR", + "BANNER", + "BLAINE", + "BOONE", + "BOX BUTTE", + "BOYD", + "BROWN", + "BUFFALO", + "BURT", + "BUTLER", + "CASS", + "CEDAR", + "CHASE", + "CHERRY", + "CHEYENNE", + "CLAY", + "COLFAX", + "CUMING", + "CUSTER", + "DAKOTA", + "DAWES", + "DAWSON", + "DEUEL", + "DIXON", + "DODGE", + "DOUGLAS", + "DUNDY", + "FILLMORE", + "FRANKLIN", + "FRONTIER", + "FURNAS", + "GAGE", + "GARDEN", + "GARFIELD", + "GOSPER", + "GRANT", + "GREELEY", + "HALL", + "HAMILTON", + "HARLAN", + "HAYES", + "HITCHCOCK", + "HOLT", + "HOOKER", + "HOWARD", + "JEFFERSON", + "JOHNSON", + "KEARNEY", + "KEITH", + "KEYA PAHA", + "KIMBALL", + "KNOX", + "LANCASTER", + "LINCOLN", + "LOGAN", + "LOUP", + "MADISON", + "MCPHERSON", + "MERRICK", + "MORRILL", + "NANCE", + "NEMAHA", + "NUCKOLLS", + + "OTHER COUNTIES", + "OTOE", + "PAWNEE", + "PERKINS", + "PHELPS", + "PIERCE", + "PLATTE", + "POLK", + "RED WILLOW", + "RICHARDSON", + "ROCK", + "SALINE", + "SARPY", + "SAUNDERS", + "SCOTTS BLUFF", + "SEWARD", + "SHERIDAN", + "SHERMAN", + "SIOUX", + "STANTON", + "THAYER", + "THOMAS", + "THURSTON", + "VALLEY", + "WASHINGTON", + "WAYNE", + "WEBSTER", + "WHEELER", + "YORK" +], + "Nevada": [ + "CARSON CITY", + "CHURCHILL", + "CLARK", + "DOUGLAS", + "ELKO", + "ESMERALDA", + "EUREKA", + "HUMBOLDT", + "LANDER", + "LINCOLN", + "LYON", + "MINERAL", + "NYE", + "ORMSBY", + + "OTHER COUNTIES", + "PERSHING", + "STOREY", + "WASHOE", + "WHITE PINE" +], + "New Hampshire": [ + "BELKNAP", + "CARROLL", + "CHESHIRE", + "COOS", + "GRAFTON", + "HILLSBOROUGH", + "MERRIMACK", + + "OTHER COUNTIES", + "ROCKINGHAM", + "STRAFFORD", + "SULLIVAN" +], + "New Jersey": [ + "ATLANTIC", + "BERGEN", + "BURLINGTON", + "CAMDEN", + "CAPE MAY", + "CUMBERLAND", + "ESSEX", + "GLOUCESTER", + "HUDSON", + "HUNTERDON", + "MERCER", + "MIDDLESEX", + "MONMOUTH", + "MORRIS", + "OCEAN", + + "OTHER COUNTIES", + "PASSAIC", + "SALEM", + "SOMERSET", + "SUSSEX", + "UNION", + "WARREN" +], + "New Mexico": [ + "BERNALILLO", + "CATRON", + "CHAVES", + "CIBOLA", + "COLFAX", + "CURRY", + "DE BACA", + "DONA ANA", + "EDDY", + "GRANT", + "GUADALUPE", + "HARDING", + "HIDALGO", + "LEA", + "LINCOLN", + "LOS ALAMOS", + "LUNA", + "MCKINLEY", + "MORA", + "OTERO", + + "OTHER COUNTIES", + "QUAY", + "RIO ARRIBA", + "ROOSEVELT", + "SAN JUAN", + "SAN MIGUEL", + "SANDOVAL", + "SANTA FE", + "SIERRA", + "SOCORRO", + "TAOS", + "TORRANCE", + "UNION", + "VALENCIA" +], + "New York": [ + "ALBANY", + "ALLEGANY", + "BRONX", + "BROOME", + "CATTARAUGUS", + "CAYUGA", + "CHAUTAUQUA", + "CHEMUNG", + "CHENANGO", + "CLINTON", + "COLUMBIA", + "CORTLAND", + "DELAWARE", + "DUTCHESS", + "ERIE", + "ESSEX", + "FRANKLIN", + "FULTON", + "GENESEE", + "GREENE", + "HAMILTON", + "HERKIMER", + "JEFFERSON", + "KINGS", + "LEWIS", + "LIVINGSTON", + "MADISON", + "MONROE", + "MONTGOMERY", + "NASSAU", + "NEW YORK", + "NIAGARA", + "ONEIDA", + "ONONDAGA", + "ONTARIO", + "ORANGE", + "ORLEANS", + "OSWEGO", + + "OTHER COUNTIES", + "OTSEGO", + "PUTNAM", + "QUEENS", + "RENSSELAER", + "RICHMOND", + "ROCKLAND", + "SARATOGA", + "SCHENECTADY", + "SCHOHARIE", + "SCHUYLER", + "SENECA", + "ST LAWRENCE", + "STEUBEN", + "SUFFOLK", + "SULLIVAN", + "TIOGA", + "TOMPKINS", + "ULSTER", + "WARREN", + "WASHINGTON", + "WAYNE", + "WESTCHESTER", + "WYOMING", + "YATES" +], + "North Carolina": [ + "ALAMANCE", + "ALEXANDER", + "ALLEGHANY", + "ANSON", + "ASHE", + "AVERY", + "BEAUFORT", + "BERTIE", + "BLADEN", + "BRUNSWICK", + "BUNCOMBE", + "BURKE", + "CABARRUS", + "CALDWELL", + "CAMDEN", + "CARTERET", + "CASWELL", + "CATAWBA", + "CHATHAM", + "CHEROKEE", + "CHOWAN", + "CLAY", + "CLEVELAND", + "COLUMBUS", + "CRAVEN", + "CUMBERLAND", + "CURRITUCK", + "DARE", + "DAVIDSON", + "DAVIE", + "DUPLIN", + "DURHAM", + "EDGECOMBE", + "FORSYTH", + "FRANKLIN", + "GASTON", + "GATES", + "GRAHAM", + "GRANVILLE", + "GREENE", + "GUILFORD", + "HALIFAX", + "HARNETT", + "HAYWOOD", + "HENDERSON", + "HERTFORD", + "HOKE", + "HYDE", + "IREDELL", + "JACKSON", + "JOHNSTON", + "JONES", + "LEE", + "LENOIR", + "LINCOLN", + "MACON", + "MADISON", + "MARTIN", + "MCDOWELL", + "MECKLENBURG", + "MITCHELL", + "MONTGOMERY", + "MOORE", + "NASH", + "NEW HANOVER", + "NORTHAMPTON", + "ONSLOW", + "ORANGE", + + "OTHER COUNTIES", + "PAMLICO", + "PASQUOTANK", + "PENDER", + "PERQUIMANS", + "PERSON", + "PITT", + "POLK", + "RANDOLPH", + "RICHMOND", + "ROBESON", + "ROCKINGHAM", + "ROWAN", + "RUTHERFORD", + "SAMPSON", + "SCOTLAND", + "STANLY", + "STOKES", + "SURRY", + "SWAIN", + "TRANSYLVANIA", + "TYRRELL", + "UNION", + "VANCE", + "WAKE", + "WARREN", + "WASHINGTON", + "WATAUGA", + "WAYNE", + "WILKES", + "WILSON", + "YADKIN", + "YANCEY" +], + "North Dakota": [ + "ADAMS", + "BARNES", + "BENSON", + "BILLINGS", + "BOTTINEAU", + "BOWMAN", + "BURKE", + "BURLEIGH", + "CASS", + "CAVALIER", + "DICKEY", + "DIVIDE", + "DUNN", + "EDDY", + "EMMONS", + "FOSTER", + "GOLDEN VALLEY", + "GRAND FORKS", + "GRANT", + "GRIGGS", + "HETTINGER", + "KIDDER", + "LA MOURE", + "LOGAN", + "MCHENRY", + "MCINTOSH", + "MCKENZIE", + "MCLEAN", + "MERCER", + "MORTON", + "MOUNTRAIL", + "NELSON", + "OLIVER", + + "OTHER COUNTIES", + "PEMBINA", + "PIERCE", + "RAMSEY", + "RANSOM", + "RENVILLE", + "RICHLAND", + "ROLETTE", + "SARGENT", + "SHERIDAN", + "SIOUX", + "SLOPE", + "STARK", + "STEELE", + "STUTSMAN", + "TOWNER", + "TRAILL", + "WALSH", + "WARD", + "WELLS", + "WILLIAMS" +], + "Ohio": [ + "ADAMS", + "ALLEN", + "ASHLAND", + "ASHTABULA", + "ATHENS", + "AUGLAIZE", + "BELMONT", + "BROWN", + "BUTLER", + "CARROLL", + "CHAMPAIGN", + "CLARK", + "CLERMONT", + "CLINTON", + "COLUMBIANA", + "COSHOCTON", + "CRAWFORD", + "CUYAHOGA", + "DARKE", + "DEFIANCE", + "DELAWARE", + "ERIE", + "FAIRFIELD", + "FAYETTE", + "FRANKLIN", + "FULTON", + "GALLIA", + "GEAUGA", + "GREENE", + "GUERNSEY", + "HAMILTON", + "HANCOCK", + "HARDIN", + "HARRISON", + "HENRY", + "HIGHLAND", + "HOCKING", + "HOLMES", + "HURON", + "JACKSON", + "JEFFERSON", + "KNOX", + "LAKE", + "LAWRENCE", + "LICKING", + "LOGAN", + "LORAIN", + "LUCAS", + "MADISON", + "MAHONING", + "MARION", + "MEDINA", + "MEIGS", + "MERCER", + "MIAMI", + "MONROE", + "MONTGOMERY", + "MORGAN", + "MORROW", + "MUSKINGUM", + "NOBLE", + + "OTHER COUNTIES", + "OTTAWA", + "PAULDING", + "PERRY", + "PICKAWAY", + "PIKE", + "PORTAGE", + "PREBLE", + "PUTNAM", + "RICHLAND", + "ROSS", + "SANDUSKY", + "SCIOTO", + "SENECA", + "SHELBY", + "STARK", + "SUMMIT", + "TRUMBULL", + "TUSCARAWAS", + "UNION", + "VAN WERT", + "VINTON", + "WARREN", + "WASHINGTON", + "WAYNE", + "WILLIAMS", + "WOOD", + "WYANDOT" +], + "Oklahoma": [ + "ADAIR", + "ALFALFA", + "ATOKA", + "BEAVER", + "BECKHAM", + "BLAINE", + "BRYAN", + "CADDO", + "CANADIAN", + "CARTER", + "CHEROKEE", + "CHOCTAW", + "CIMARRON", + "CLEVELAND", + "COAL", + "COMANCHE", + "COTTON", + "CRAIG", + "CREEK", + "CUSTER", + "DELAWARE", + "DEWEY", + "ELLIS", + "GARFIELD", + "GARVIN", + "GRADY", + "GRANT", + "GREER", + "HARMON", + "HARPER", + "HASKELL", + "HUGHES", + "JACKSON", + "JEFFERSON", + "JOHNSTON", + "KAY", + "KINGFISHER", + "KIOWA", + "LATIMER", + "LEFLORE", + "LINCOLN", + "LOGAN", + "LOVE", + "MAJOR", + "MARSHALL", + "MAYES", + "MCCLAIN", + "MCCURTAIN", + "MCINTOSH", + "MURRAY", + "MUSKOGEE", + "NOBLE", + "NOWATA", + "OKFUSKEE", + "OKLAHOMA", + "OKMULGEE", + "OSAGE", + + "OTHER COUNTIES", + "OTTAWA", + "PAWNEE", + "PAYNE", + "PITTSBURG", + "PONTOTOC", + "POTTAWATOMIE", + "PUSHMATAHA", + "ROGER MILLS", + "ROGERS", + "SEMINOLE", + "SEQUOYAH", + "STEPHENS", + "TEXAS", + "TILLMAN", + "TULSA", + "WAGONER", + "WASHINGTON", + "WASHITA", + "WOODS", + "WOODWARD" +], + "Oregon": [ + "BAKER", + "BENTON", + "CLACKAMAS", + "CLATSOP", + "COLUMBIA", + "COOS", + "CROOK", + "CURRY", + "DESCHUTES", + "DOUGLAS", + "GILLIAM", + "GRANT", + "HARNEY", + "HOOD RIVER", + "JACKSON", + "JEFFERSON", + "JOSEPHINE", + "KLAMATH", + "LAKE", + "LANE", + "LINCOLN", + "LINN", + "MALHEUR", + "MARION", + "MORROW", + "MULTNOMAH", + + "OTHER COUNTIES", + "POLK", + "SHERMAN", + "TILLAMOOK", + "UMATILLA", + "UNION", + "WALLOWA", + "WASCO", + "WASHINGTON", + "WHEELER", + "YAMHILL" +], + "Pennsylvania": [ + "ADAMS", + "ALLEGHENY", + "ARMSTRONG", + "BEAVER", + "BEDFORD", + "BERKS", + "BLAIR", + "BRADFORD", + "BUCKS", + "BUTLER", + "CAMBRIA", + "CAMERON", + "CARBON", + "CENTRE", + "CHESTER", + "CLARION", + "CLEARFIELD", + "CLINTON", + "COLUMBIA", + "CRAWFORD", + "CUMBERLAND", + "DAUPHIN", + "DELAWARE", + "ELK", + "ERIE", + "FAYETTE", + "FOREST", + "FRANKLIN", + "FULTON", + "GREENE", + "HUNTINGDON", + "INDIANA", + "JEFFERSON", + "JUNIATA", + "LACKAWANNA", + "LANCASTER", + "LAWRENCE", + "LEBANON", + "LEHIGH", + "LUZERNE", + "LYCOMING", + "MCKEAN", + "MERCER", + "MIFFLIN", + "MONROE", + "MONTGOMERY", + "MONTOUR", + "NORTHAMPTON", + "NORTHUMBERLAND", + + "OTHER COUNTIES", + "PERRY", + "PHILADELPHIA", + "PIKE", + "POTTER", + "SCHUYLKILL", + "SNYDER", + "SOMERSET", + "SULLIVAN", + "SUSQUEHANNA", + "TIOGA", + "UNION", + "VENANGO", + "WARREN", + "WASHINGTON", + "WAYNE", + "WESTMORELAND", + "WYOMING", + "YORK" +], + "Rhode Island": [ + "BRISTOL", + "KENT", + "NEWPORT", + "PROVIDENCE", + "WASHINGTON" +], + "South Carolina": [ + "ABBEVILLE", + "AIKEN", + "ALLENDALE", + "ANDERSON", + "BAMBERG", + "BARNWELL", + "BEAUFORT", + "BERKELEY", + "CALHOUN", + "CHARLESTON", + "CHEROKEE", + "CHESTER", + "CHESTERFIELD", + "CLARENDON", + "COLLETON", + "DARLINGTON", + "DILLON", + "DORCHESTER", + "EDGEFIELD", + "FAIRFIELD", + "FLORENCE", + "GEORGETOWN", + "GREENVILLE", + "GREENWOOD", + "HAMPTON", + "HORRY", + "JASPER", + "KERSHAW", + "LANCASTER", + "LAURENS", + "LEE", + "LEXINGTON", + "MARION", + "MARLBORO", + "MCCORMICK", + "NEWBERRY", + "OCONEE", + "ORANGEBURG", + + "OTHER COUNTIES", + "PICKENS", + "RICHLAND", + "SALUDA", + "SPARTANBURG", + "SUMTER", + "UNION", + "WILLIAMSBURG", + "YORK" +], + "South Dakota": [ + "AURORA", + "BEADLE", + "BENNETT", + "BON HOMME", + "BROOKINGS", + "BROWN", + "BRULE", + "BUFFALO", + "BUTTE", + "CAMPBELL", + "CHARLES MIX", + "CLARK", + "CLAY", + "CODINGTON", + "CORSON", + "CUSTER", + "DAVISON", + "DAY", + "DEUEL", + "DEWEY", + "DOUGLAS", + "EDMUNDS", + "FALL RIVER", + "FAULK", + "GRANT", + "GREGORY", + "HAAKON", + "HAMLIN", + "HAND", + "HANSON", + "HARDING", + "HUGHES", + "HUTCHINSON", + "HYDE", + "JACKSON", + "JERAULD", + "JONES", + "KINGSBURY", + "LAKE", + "LAWRENCE", + "LINCOLN", + "LYMAN", + "MARSHALL", + "MCCOOK", + "MCPHERSON", + "MEADE", + "MELLETTE", + "MINER", + "MINNEHAHA", + "MOODY", + "OGLALA LAKOTA", + + "OTHER COUNTIES", + "PENNINGTON", + "PERKINS", + "POTTER", + "ROBERTS", + "SANBORN", + "SPINK", + "STANLEY", + "SULLY", + "TODD", + "TRIPP", + "TURNER", + "UNION", + "WALWORTH", + "WASHABAUGH", + "WASHINGTON", + "YANKTON", + "ZIEBACH" +], + "Tennessee": [ + "ANDERSON", + "BEDFORD", + "BENTON", + "BLEDSOE", + "BLOUNT", + "BRADLEY", + "CAMPBELL", + "CANNON", + "CARROLL", + "CARTER", + "CHEATHAM", + "CHESTER", + "CLAIBORNE", + "CLAY", + "COCKE", + "COFFEE", + "CROCKETT", + "CUMBERLAND", + "DAVIDSON", + "DE KALB", + "DECATUR", + "DICKSON", + "DYER", + "FAYETTE", + "FENTRESS", + "FRANKLIN", + "GIBSON", + "GILES", + "GRAINGER", + "GREENE", + "GRUNDY", + "HAMBLEN", + "HAMILTON", + "HANCOCK", + "HARDEMAN", + "HARDIN", + "HAWKINS", + "HAYWOOD", + "HENDERSON", + "HENRY", + "HICKMAN", + "HOUSTON", + "HUMPHREYS", + "JACKSON", + "JEFFERSON", + "JOHNSON", + "KNOX", + "LAKE", + "LAUDERDALE", + "LAWRENCE", + "LEWIS", + "LINCOLN", + "LOUDON", + "MACON", + "MADISON", + "MARION", + "MARSHALL", + "MAURY", + "MCMINN", + "MCNAIRY", + "MEIGS", + "MONROE", + "MONTGOMERY", + "MOORE", + "MORGAN", + "OBION", + + "OTHER COUNTIES", + "OVERTON", + "PERRY", + "PICKETT", + "POLK", + "PUTNAM", + "RHEA", + "ROANE", + "ROBERTSON", + "RUTHERFORD", + "SCOTT", + "SEQUATCHIE", + "SEVIER", + "SHELBY", + "SMITH", + "STEWART", + "SULLIVAN", + "SUMNER", + "TIPTON", + "TROUSDALE", + "UNICOI", + "UNION", + "VAN BUREN", + "WARREN", + "WASHINGTON", + "WAYNE", + "WEAKLEY", + "WHITE", + "WILLIAMSON", + "WILSON" +], + "Texas": [ + "ANDERSON", + "ANDREWS", + "ANGELINA", + "ARANSAS", + "ARCHER", + "ARMSTRONG", + "ATASCOSA", + "AUSTIN", + "BAILEY", + "BANDERA", + "BASTROP", + "BAYLOR", + "BEE", + "BELL", + "BEXAR", + "BLANCO", + "BORDEN", + "BOSQUE", + "BOWIE", + "BRAZORIA", + "BRAZOS", + "BREWSTER", + "BRISCOE", + "BROOKS", + "BROWN", + "BURLESON", + "BURNET", + "CALDWELL", + "CALHOUN", + "CALLAHAN", + "CAMERON", + "CAMP", + "CARSON", + "CASS", + "CASTRO", + "CHAMBERS", + "CHEROKEE", + "CHILDRESS", + "CLAY", + "COCHRAN", + "COKE", + "COLEMAN", + "COLLIN", + "COLLINGSWORTH", + "COLORADO", + "COMAL", + "COMANCHE", + "CONCHO", + "COOKE", + "CORYELL", + "COTTLE", + "CRANE", + "CROCKETT", + "CROSBY", + "CULBERSON", + "DALLAM", + "DALLAS", + "DAWSON", + "DE WITT", + "DEAF SMITH", + "DELTA", + "DENTON", + "DICKENS", + "DIMMIT", + "DONLEY", + "DUVAL", + "EASTLAND", + "ECTOR", + "EDWARDS", + "EL PASO", + "ELLIS", + "ERATH", + "FALLS", + "FANNIN", + "FAYETTE", + "FISHER", + "FLOYD", + "FOARD", + "FORT BEND", + "FRANKLIN", + "FREESTONE", + "FRIO", + "GAINES", + "GALVESTON", + "GARZA", + "GILLESPIE", + "GLASSCOCK", + "GOLIAD", + "GONZALES", + "GRAY", + "GRAYSON", + "GREGG", + "GRIMES", + "GUADALUPE", + "HALE", + "HALL", + "HAMILTON", + "HANSFORD", + "HARDEMAN", + "HARDIN", + "HARRIS", + "HARRISON", + "HARTLEY", + "HASKELL", + "HAYS", + "HEMPHILL", + "HENDERSON", + "HIDALGO", + "HILL", + "HOCKLEY", + "HOOD", + "HOPKINS", + "HOUSTON", + "HOWARD", + "HUDSPETH", + "HUNT", + "HUTCHINSON", + "IRION", + "JACK", + "JACKSON", + "JASPER", + "JEFF DAVIS", + "JEFFERSON", + "JIM HOGG", + "JIM WELLS", + "JOHNSON", + "JONES", + "KARNES", + "KAUFMAN", + "KENDALL", + "KENEDY", + "KENT", + "KERR", + "KIMBLE", + "KING", + "KINNEY", + "KLEBERG", + "KNOX", + "LA SALLE", + "LAMAR", + "LAMB", + "LAMPASAS", + "LAVACA", + "LEE", + "LEON", + "LIBERTY", + "LIMESTONE", + "LIPSCOMB", + "LIVE OAK", + "LLANO", + "LOVING", + "LUBBOCK", + "LYNN", + "MADISON", + "MARION", + "MARTIN", + "MASON", + "MATAGORDA", + "MAVERICK", + "MCCULLOCH", + "MCLENNAN", + "MCMULLEN", + "MEDINA", + "MENARD", + "MIDLAND", + "MILAM", + "MILLS", + "MITCHELL", + "MONTAGUE", + "MONTGOMERY", + "MOORE", + "MORRIS", + "MOTLEY", + "NACOGDOCHES", + "NAVARRO", + "NEWTON", + "NOLAN", + "NUECES", + "OCHILTREE", + "OLDHAM", + "ORANGE", + + "OTHER COUNTIES", + "PALO PINTO", + "PANOLA", + "PARKER", + "PARMER", + "PECOS", + "POLK", + "POTTER", + "PRESIDIO", + "RAINS", + "RANDALL", + "REAGAN", + "REAL", + "RED RIVER", + "REEVES", + "REFUGIO", + "ROBERTS", + "ROBERTSON", + "ROCKWALL", + "RUNNELS", + "RUSK", + "SABINE", + "SAN AUGUSTINE", + "SAN JACINTO", + "SAN PATRICIO", + "SAN SABA", + "SCHLEICHER", + "SCURRY", + "SHACKELFORD", + "SHELBY", + "SHERMAN", + "SMITH", + "SOMERVELL", + "STARR", + "STEPHENS", + "STERLING", + "STONEWALL", + "SUTTON", + "SWISHER", + "TARRANT", + "TAYLOR", + "TERRELL", + "TERRY", + "THROCKMORTON", + "TITUS", + "TOM GREEN", + "TRAVIS", + "TRINITY", + "TYLER", + "UPSHUR", + "UPTON", + "UVALDE", + "VAL VERDE", + "VAN ZANDT", + "VICTORIA", + "WALKER", + "WALLER", + "WARD", + "WASHINGTON", + "WEBB", + "WHARTON", + "WHEELER", + "WICHITA", + "WILBARGER", + "WILLACY", + "WILLIAMSON", + "WILSON", + "WINKLER", + "WISE", + "WOOD", + "YOAKUM", + "YOUNG", + "ZAPATA", + "ZAVALA" +], + "Utah": [ + "BEAVER", + "BOX ELDER", + "CACHE", + "CARBON", + "DAGGETT", + "DAVIS", + "DUCHESNE", + "EMERY", + "GARFIELD", + "GRAND", + "IRON", + "JUAB", + "KANE", + "MILLARD", + "MORGAN", + + "OTHER COUNTIES", + "PIUTE", + "RICH", + "SALT LAKE", + "SAN JUAN", + "SANPETE", + "SEVIER", + "SUMMIT", + "TOOELE", + "UINTAH", + "UTAH", + "WASATCH", + "WASHINGTON", + "WAYNE", + "WEBER" +], + "Vermont": [ + "ADDISON", + "BENNINGTON", + "CALEDONIA", + "CHITTENDEN", + "ESSEX", + "FRANKLIN", + "GRAND ISLE", + "LAMOILLE", + "ORANGE", + "ORLEANS", + + "OTHER COUNTIES", + "RUTLAND", + "WASHINGTON", + "WINDHAM", + "WINDSOR" +], + "Virginia": [ + "ACCOMACK", + "ALBEMARLE", + "ALLEGHANY", + "AMELIA", + "AMHERST", + "APPOMATTOX", + "ARLINGTON", + "AUGUSTA", + "BATH", + "BEDFORD", + "BLAND", + "BOTETOURT", + "BRUNSWICK", + "BUCHANAN", + "BUCKINGHAM", + "CAMPBELL", + "CAROLINE", + "CARROLL", + "CHARLES CITY", + "CHARLOTTE", + "CHESAPEAKE CITY", + "CHESTERFIELD", + "CLARKE", + "CRAIG", + "CULPEPER", + "CUMBERLAND", + "DICKENSON", + "DINWIDDIE", + "ESSEX", + "FAIRFAX", + "FAUQUIER", + "FLOYD", + "FLUVANNA", + "FRANKLIN", + "FREDERICK", + "GILES", + "GLOUCESTER", + "GOOCHLAND", + "GRAYSON", + "GREENE", + "GREENSVILLE", + "HALIFAX", + "HAMPTON CITY", + "HANOVER", + "HENRICO", + "HENRY", + "HIGHLAND", + "ISLE OF WIGHT", + "JAMES CITY", + "KING AND QUEEN", + "KING GEORGE", + "KING WILLIAM", + "LANCASTER", + "LEE", + "LOUDOUN", + "LOUISA", + "LUNENBURG", + "MADISON", + "MATHEWS", + "MECKLENBURG", + "MIDDLESEX", + "MONTGOMERY", + "NANSEMOND", + "NELSON", + "NEW KENT", + "NEWPORT NEWS CITY", + "NORTHAMPTON", + "NORTHUMBERLAND", + "NOTTOWAY", + "ORANGE", + + "OTHER COUNTIES", + "PAGE", + "PATRICK", + "PITTSYLVANIA", + "POWHATAN", + "PRINCE EDWARD", + "PRINCE GEORGE", + "PRINCE WILLIAM", + "PULASKI", + "RAPPAHANNOCK", + "RICHMOND", + "ROANOKE", + "ROCKBRIDGE", + "ROCKINGHAM", + "RUSSELL", + "SCOTT", + "SHENANDOAH", + "SMYTH", + "SOUTHAMPTON", + "SPOTSYLVANIA", + "STAFFORD", + "SUFFOLK CITY", + "SURRY", + "SUSSEX", + "TAZEWELL", + "VIRGINIA BEACH CITY", + "WARREN", + "WASHINGTON", + "WESTMORELAND", + "WISE", + "WYTHE", + "YORK" +], + "Washington": [ + "ADAMS", + "ASOTIN", + "BENTON", + "CHELAN", + "CLALLAM", + "CLARK", + "COLUMBIA", + "COWLITZ", + "DOUGLAS", + "FERRY", + "FRANKLIN", + "GARFIELD", + "GRANT", + "GRAYS HARBOR", + "ISLAND", + "JEFFERSON", + "KING", + "KITSAP", + "KITTITAS", + "KLICKITAT", + "LEWIS", + "LINCOLN", + "MASON", + "OKANOGAN", + + "OTHER COUNTIES", + "PACIFIC", + "PEND OREILLE", + "PIERCE", + "SAN JUAN", + "SKAGIT", + "SKAMANIA", + "SNOHOMISH", + "SPOKANE", + "STEVENS", + "THURSTON", + "WAHKIAKUM", + "WALLA WALLA", + "WHATCOM", + "WHITMAN", + "YAKIMA" +], + "West Virginia": [ + "BARBOUR", + "BERKELEY", + "BOONE", + "BRAXTON", + "BROOKE", + "CABELL", + "CALHOUN", + "CLAY", + "DODDRIDGE", + "FAYETTE", + "GILMER", + "GRANT", + "GREENBRIER", + "HAMPSHIRE", + "HANCOCK", + "HARDY", + "HARRISON", + "JACKSON", + "JEFFERSON", + "KANAWHA", + "LEWIS", + "LINCOLN", + "LOGAN", + "MARION", + "MARSHALL", + "MASON", + "MCDOWELL", + "MERCER", + "MINERAL", + "MINGO", + "MONONGALIA", + "MONROE", + "MORGAN", + "NICHOLAS", + "OHIO", + + "OTHER COUNTIES", + "PENDLETON", + "PLEASANTS", + "POCAHONTAS", + "PRESTON", + "PUTNAM", + "RALEIGH", + "RANDOLPH", + "RITCHIE", + "ROANE", + "SUMMERS", + "TAYLOR", + "TUCKER", + "TYLER", + "UPSHUR", + "WAYNE", + "WEBSTER", + "WETZEL", + "WIRT", + "WOOD", + "WYOMING" +], + "Wisconsin": [ + "ADAMS", + "ASHLAND", + "BARRON", + "BAYFIELD", + "BROWN", + "BUFFALO", + "BURNETT", + "CALUMET", + "CHIPPEWA", + "CLARK", + "COLUMBIA", + "CRAWFORD", + "DANE", + "DODGE", + "DOOR", + "DOUGLAS", + "DUNN", + "EAU CLAIRE", + "FLORENCE", + "FOND DU LAC", + "FOREST", + "GRANT", + "GREEN", + "GREEN LAKE", + "IOWA", + "IRON", + "JACKSON", + "JEFFERSON", + "JUNEAU", + "KENOSHA", + "KEWAUNEE", + "LA CROSSE", + "LAFAYETTE", + "LANGLADE", + "LINCOLN", + "MANITOWOC", + "MARATHON", + "MARINETTE", + "MARQUETTE", + "MENOMINEE", + "MILWAUKEE", + "MONROE", + "OCONTO", + "ONEIDA", + + "OTHER COUNTIES", + "OUTAGAMIE", + "OZAUKEE", + "PEPIN", + "PIERCE", + "POLK", + "PORTAGE", + "PRICE", + "RACINE", + "RICHLAND", + "ROCK", + "RUSK", + "SAUK", + "SAWYER", + "SHAWANO", + "SHEBOYGAN", + "ST CROIX", + "TAYLOR", + "TREMPEALEAU", + "VERNON", + "VILAS", + "WALWORTH", + "WASHBURN", + "WASHINGTON", + "WAUKESHA", + "WAUPACA", + "WAUSHARA", + "WINNEBAGO", + "WOOD" +], + "Wyoming": [ + "ALBANY", + "BIG HORN", + "CAMPBELL", + "CARBON", + "CONVERSE", + "CROOK", + "FREMONT", + "GOSHEN", + "HOT SPRINGS", + "JOHNSON", + "LARAMIE", + "LINCOLN", + "NATRONA", + "NIOBRARA", + + "OTHER COUNTIES", + "PARK", + "PLATTE", + "SHERIDAN", + "SUBLETTE", + "SWEETWATER", + "TETON", + "UINTA", + "WASHAKIE", + "WESTON" +] +} \ No newline at end of file diff --git a/src/constants/query-headers.ts b/src/constants/query-headers.ts new file mode 100644 index 0000000..dcac5f7 --- /dev/null +++ b/src/constants/query-headers.ts @@ -0,0 +1,352 @@ +import { fiftyStates } from "./constants"; +import { IQueryHeaders } from "./types"; + +const allYears = []; +for (let year = 2022; year >= 1910; year--) { + allYears.push(`${year}`); +} + + +const sharedDemographicHeaders = { + sect_desc: "Demographics", + group_desc: "Producers", + commodity_desc: "Producers", + statisticcat_desc: "Producers", + domain_desc: "Total", + geographicAreas: ["State", "County"] +}; + +const sharedEconomicHeaders = { + sect_desc: "Economics", + group_desc: "Farms & Land & Assets", + commodity_desc: "Farm Operations", + statisticcat_desc: "Operations" +}; + +const sharedLaborHeaders = { + sect_desc: "Economics", + group_desc: "Expenses", + commodity_desc: "Labor", +}; + +const sharedCropHeaders = { + sect_desc: "Crops", + statisticcat_desc: { + ["Area Harvested"]: "Area Harvested", + ["Yield"]: "Yield" + }, + domain_desc: "Total", + geographicAreas: ["State", "County"], + years: { + "County": allYears, + "State": allYears + } +} + +export const queryData: Array = [ + { + plugInAttribute: "Total Farmers", + ...sharedDemographicHeaders, + short_desc: ["PRODUCERS, (ALL) - NUMBER OF PRODUCERS"], + years: { + "County": ["2017"], + "State": ["2017"] + } + }, + { + plugInAttribute: "Age", + ...sharedDemographicHeaders, + short_desc: [ + "PRODUCERS, AGE LT 25 - NUMBER OF PRODUCERS", + "PRODUCERS, AGE 25 TO 34 - NUMBER OF PRODUCERS", + "PRODUCERS, AGE 35 TO 44 - NUMBER OF PRODUCERS", + "PRODUCERS, AGE 45 TO 54 - NUMBER OF PRODUCERS", + "PRODUCERS, AGE 55 TO 64 - NUMBER OF PRODUCERS", + "PRODUCERS, AGE 65 TO 74 - NUMBER OF PRODUCERS", + "PRODUCERS, AGE GE 75 - NUMBER OF PRODUCERS" + ], + years: { + "County": ["2017"], + "State": ["2017"] + } + + }, + { + plugInAttribute: "Gender", + ...sharedDemographicHeaders, + short_desc: [ + "PRODUCERS, (ALL), FEMALE - NUMBER OF PRODUCERS", + "PRODUCERS, (ALL), MALE - NUMBER OF PRODUCERS" + ], + years: { + "County": ["2017"], + "State": ["2017"] + } + }, + { + plugInAttribute: "Race", + ...sharedDemographicHeaders, + short_desc: [ + "PRODUCERS, AMERICAN INDIAN OR ALASKAN NATIVE - NUMBER OF PRODUCERS", + "PRODUCERS, ASIAN - NUMBER OF PRODUCERS", + "PRODUCERS, BLACK OR AFRICAN AMERICAN - NUMBER OF PRODUCERS", + "PRODUCERS, HISPANIC - NUMBER OF PRODUCERS", + "PRODUCERS, MULTI-RACE - NUMBER OF PRODUCERS", + "PRODUCERS, NATIVE HAWAIIAN OR OTHER PACIFIC ISLANDERS - NUMBER OF PRODUCERS", + "PRODUCERS, WHITE - NUMBER OF PRODUCERS" + ], + years: { + "County": ["2017"], + "State": ["2017"] + } + }, + { + plugInAttribute: "Total Farms", + ...sharedEconomicHeaders, + short_desc: ["FARM OPERATIONS - NUMBER OF OPERATIONS"], + domain_desc: "Total", + geographicAreas: ["State", "County"], + years: { + "County": allYears, + "State": allYears + } + }, + { + plugInAttribute: "Organization Type", + sect_desc: "Demographics", + group_desc: "Farms & Land & Assets", + commodity_desc: "Farm Operations", + statisticcat_desc: "Operations", + short_desc: [ + "FARM OPERATIONS, ORGANIZATION, TAX PURPOSES, CORPORATION (EXCL FAMILY HELD) - NUMBER OF OPERATIONS", + "FARM OPERATIONS, ORGANIZATION, TAX PURPOSES, CORPORATION, FAMILY HELD - NUMBER OF OPERATIONS", + "FARM OPERATIONS, ORGANIZATION, TAX PURPOSES, FAMILY & INDIVIDUAL - NUMBER OF OPERATIONS", + "FARM OPERATIONS, ORGANIZATION, TAX PURPOSES, INSTITUTIONAL & RESEARCH & RESERVATION & OTHER - NUMBER OF OPERATIONS", + "FARM OPERATIONS, ORGANIZATION, TAX PURPOSES, PARTNERSHIP - NUMBER OF OPERATIONS" + ], + domain_desc: "Total", + geographicAreas: ["County"], + years: { + "County": ["1997", "2002", "2007", "2012", "2017"], + "State": [] + } + }, + { + plugInAttribute: "Economic Class", + ...sharedEconomicHeaders, + short_desc: ["FARM OPERATIONS - NUMBER OF OPERATIONS"], + domain_desc: "Economic Class", + geographicAreas: ["State"], + years: { + "County": allYears.filter(y => Number(y) >= 1987), + "State": allYears.filter(y => Number(y) >= 1987) + } + }, + { + plugInAttribute: "Acres Operated", + sect_desc: "Economics", + group_desc: "Farms & Land & Assets", + commodity_desc: "Farm Operations", + statisticcat_desc: "Area Operated", + short_desc: ["FARM OPERATIONS - ACRES OPERATED"], + domain_desc: "Area Operated", + geographicAreas: ["State", "County"], + years: { + "County": ["1997", "2002", "2007", "2012", "2017"], + "State": ["1997", "2002", "2007", "2012", "2017"] + } + }, + { + plugInAttribute: "Organic", + ...sharedEconomicHeaders, + short_desc: ["FARM OPERATIONS, ORGANIC - NUMBER OF OPERATIONS"], + domain_desc: "Organic Status", + geographicAreas: ["State", "County"], + years: { + "County": ["2008", "2011", "2012", "2014", "2015", "2016", "2017", "2019", "2021"], + "State": ["2008", "2011", "2012", "2014", "2015", "2016", "2017", "2019", "2021"] + } + }, + { + plugInAttribute: "Labor Status", + ...sharedLaborHeaders, + statisticcat_desc: "Workers", + short_desc: [ + "LABOR, MIGRANT - NUMBER OF WORKERS", + "LABOR, UNPAID - NUMBER OF WORKERS", + "LABOR, HIRED - NUMBER OF WORKERS" + ], + domain_desc: "Total", + geographicAreas: ["State", "County"], + years: { + "County": ["2012", "2017"], + "State": ["2012", "2017"] + } + }, + { + plugInAttribute: "Wages", + ...sharedLaborHeaders, + statisticcat_desc: "Wage Rate", + short_desc: ["LABOR, HIRED - WAGE RATE, MEASURED IN $ / HOUR"], + domain_desc: "Total", + geographicAreas: ["REGION : MULTI-STATE"], + years: { + "County": allYears.filter(y => Number(y) >= 1989), + "State": allYears.filter(y => Number(y) >= 1989) + } + }, + { + plugInAttribute: "Time Worked", + ...sharedLaborHeaders, + statisticcat_desc: "Wage Rate", + short_desc: ["LABOR, HIRED - TIME WORKED, MEASURED IN HOURS/WEEK"], + domain_desc: "Total", + geographicAreas: ["REGION : MULTI-STATE"], + years: { + "County": allYears.filter(y => Number(y) >= 1989), + "State": allYears.filter(y => Number(y) >= 1989) + } + }, + { + plugInAttribute: "Corn", + group_desc: "Field Crops", + commodity_desc: "Corn", + short_desc: { + ["Area Harvested"]: ["CORN, GRAIN - ACRES HARVESTED"], + ["Yield"]: ["CORN, GRAIN - YIELD, MEASURED IN BU / ACRE"] + }, + ...sharedCropHeaders + }, + { + plugInAttribute: "Cotton", + group_desc: "Field Crops", + commodity_desc: "Cotton", + short_desc: { + ["Area Harvested"]: ["COTTON - ACRES HARVESTED"], + ["Yield"]: ["COTTON - YIELD, MEASURED IN LB / ACRE"] + }, + ...sharedCropHeaders + }, + { + plugInAttribute: "Grapes", + group_desc: "Fruit & Tree Nuts", + commodity_desc: "Grapes", + short_desc: { + ["Area Harvested"]: ["GRAPES, ORGANIC - ACRES HARVESTED"], + ["Yield"]: ["GRAPES - YIELD, MEASURED IN TONS / ACRE"] + }, + ...sharedCropHeaders + }, + { + plugInAttribute: "Oats", + group_desc: "Field Crops", + commodity_desc: "Oats", + short_desc: { + ["Area Harvested"]: ["Oats - Acres Harvested"], + ["Yield"]: ["Oats - Yield, measured in BU / acre"] + }, + ...sharedCropHeaders + }, + { + plugInAttribute: "Soybeans", + group_desc: "Field Crops", + commodity_desc: "Soybeans", + short_desc: { + ["Area Harvested"]: ["Soybeans - Acres Harvested"], + ["Yield"]: ["Soybeans - Yield, measured in BU / acre"] + }, + ...sharedCropHeaders + }, + { + plugInAttribute: "Wheat", + group_desc: "Field Crops", + commodity_desc: "Wheat", + + short_desc: { + ["Area Harvested"]: ["Wheat - Acres Harvested"], + ["Yield"]: ["Wheat - Yield, measured in BU / acre"] + }, + ...sharedCropHeaders + } +]; + +interface IRegion { + "Region": string + "States": string[] +} + + +export const multiRegions: IRegion[] = [ + { + "Region": "Pacific", + "States": ["Washington", "Oregon"] + }, + { + "Region": "Mountain I", + "States": ["Montana", "Idaho", "Wyoming"] + }, + { + "Region": "Mountain II", + "States": ["Nevada", "Utah", "Colorado"] + }, + { + "Region": "Mountain III", + "States": ["Arizona", "New Mexico"] + }, + { + "Region": "Northern Plains", + "States": ["North Dakota", "South Dakota", "Kansas", "Nebraska"] + }, + { + "Region": "Southern Plains", + "States": ["Oklahoma", "Texas"] + }, + { + "Region": "Lake", + "States": ["Minnesota", "Wisconsin", "Michigan"] + }, + { + "Region": "Cornbelt I", + "States": ["Illinois", "Indiana", "Ohio"] + }, + { + "Region": "Cornbelt II", + "States": ["Iowa", "Missouri"] + }, + { + "Region": "Delta", + "States": ["Mississippi", "Louisiana", "Arkansas"] + }, + { + "Region": "Appalachian I", + "States": ["Virginia", "North Carolina"] + }, + { + "Region": "Appalachian II", + "States": ["West Virginia", "Kentucky", "Tennessee"] + }, + { + "Region": "Southeast", + "States": ["South Carolina", "Alabama", "Georgia"] + }, + { + "Region": "Northeast I", + "States": ["Maine", "New Hampshire", "Vermont", "Massachusetts", "Connecticut", "Rhode Island", "New York"] + }, + { + "Region": "Northeast II", + "States": ["Pennsylvania", "New Jersey", "Delaware", "Maryland"] + }, + { + "Region": "California", + "States": ["California"] + }, + { + "Region": "Florida", + "States": ["Florida"] + }, + { + "Region": "Hawaii", + "States": ["Hawaii"] + } +]; diff --git a/src/components/types.ts b/src/constants/types.ts similarity index 68% rename from src/components/types.ts rename to src/constants/types.ts index fc058bd..1c3b2f5 100644 --- a/src/components/types.ts +++ b/src/constants/types.ts @@ -1,6 +1,10 @@ +import { fiftyStates } from "./constants"; + + + export interface IStateOptions { - geographicLevel: string, - states: string[] + geographicLevel: "County"|"State", + states: string[], farmerDemographics: string[], farmDemographics: string[], economicsAndWages: string[], @@ -59,3 +63,28 @@ export interface IResData { year: number; zip_5: string; }; + +export interface ICropCategory { + ["Area Harvested"]: string, + ["Yield"]: string +}; + +export interface ICropDataItem { + ["Area Harvested"]: string[], + ["Yield"]: string[] +}; + +export interface IQueryHeaders { + plugInAttribute: string, + sect_desc: string, + group_desc: string, + commodity_desc: string, + statisticcat_desc: string|ICropCategory, + short_desc: string[]|ICropDataItem, + domain_desc: string, + geographicAreas: string[], + years: { + "County": string[] + "State": string[] + } +}; diff --git a/src/scripts/api.ts b/src/scripts/api.ts index b44fbfa..5ec688a 100644 --- a/src/scripts/api.ts +++ b/src/scripts/api.ts @@ -1,8 +1,10 @@ import fetchJsonp from "fetch-jsonp"; -import { ICropCategory, ICropDataItem, queryData } from "./query-headers"; -import { IStateOptions } from "../components/types"; +import { multiRegions, queryData } from "../constants/query-headers"; +import { ICropCategory, ICropDataItem, IStateOptions } from "../constants/types"; import { connect } from "./connect"; -import { cropOptions } from "../components/constants"; +import { cropOptions } from "../constants/constants"; +import { countyData } from "../constants/counties"; +import { getQueryParams } from "./utils"; const baseURL = `https://quickstats.nass.usda.gov/api/api_GET/?key=9ED0BFB8-8DDD-3609-9940-A2341ED6A9E3`; @@ -11,19 +13,32 @@ interface IRequestParams { geographicLevel: string, location: string, year: string, - cropCategory?: keyof ICropDataItem + cropCategory?: keyof ICropDataItem, + state?: string } interface IGetAttrDataParams { attribute: string, geographicLevel: string, cropUnits: string, - state: string, + location: string, year: string + state?: string } -export const createRequest = ({attribute, geographicLevel, location, year, cropCategory}: IRequestParams) => { - const queryParams = queryData.find((d) => d.plugInAttribute === attribute); +export const fetchData = async (req: string) => { + try { + const response = await fetchJsonp(req, {timeout: 10000}); + const json = await response.json(); + return json; + } catch (error) { + console.log("parsing failed", error); + throw error; + } +}; + +export const createRequest = ({attribute, geographicLevel, location, year, cropCategory, state}: IRequestParams) => { + const queryParams = getQueryParams(attribute); if (!queryParams) { throw new Error("Invalid attribute"); @@ -45,17 +60,25 @@ export const createRequest = ({attribute, geographicLevel, location, year, cropC (queryParams?.statisticcat_desc as ICropCategory)[cropCategory] : statisticcat_desc; + const locationHeader = geographicLevel === "REGION : MULTI-STATE" ? "region_desc" : geographicLevel === "County" ? "county_name" : "state_name"; + const baseReq = `${baseURL}` + `§_desc=${encodeURIComponent(sect_desc)}` + `&group_desc=${encodeURIComponent(group_desc)}` + `&commodity_desc=${encodeURIComponent(commodity_desc)}` + `&statisticcat_desc=${encodeURIComponent(cat as string)}` + `&domain_desc=${encodeURIComponent(domain_desc)}` + - `&agg_level_desc=${geographicLevel}` + - `&state_name=${location}` + + `&agg_level_desc=${encodeURIComponent(geographicLevel)}` + + `&${locationHeader}=${encodeURIComponent(location)}` + `&year=${year}`; let req = baseReq; + + // if we are creating a request at the county level, we need to also pass in a state + if (state) { + req += `&state_name=${state}` + } + item.forEach(subItem => { req = req + `&short_desc=${encodeURIComponent(subItem)}`; }); @@ -65,14 +88,13 @@ export const createRequest = ({attribute, geographicLevel, location, year, cropC export const createTableFromSelections = async (selectedOptions: IStateOptions) => { const {geographicLevel, states, cropUnits, years, ...subOptions} = selectedOptions; await connect.getNewDataContext(); - await connect.createTopCollection(); + await connect.createTopCollection(geographicLevel); - // need to change this - instead of creating based on UI names, create based on dataItems in queryParams const allAttrs: Array = ["Year"]; for (const key in subOptions) { const selections = subOptions[key as keyof typeof subOptions]; for (const attribute of selections) { - const queryParams = queryData.find((d) => d.plugInAttribute === attribute); + const queryParams = getQueryParams(attribute); if (!queryParams) { throw new Error("Invalid attribute"); } @@ -84,24 +106,35 @@ export const createTableFromSelections = async (selectedOptions: IStateOptions) } } } - await connect.createSubCollection(allAttrs); + await connect.createSubCollection(geographicLevel, allAttrs); const items = await getItems(selectedOptions); await connect.createItems(items); await connect.makeCaseTableAppear(); + // await connect.createItems(items); }; const getItems = async (selectedOptions: IStateOptions) => { const {states, years} = selectedOptions; const multipleStatesSelected = states.length > 1 || states[0] === "All States"; const multipleYearsSelected = years.length > 1; + const countySelected = selectedOptions.geographicLevel === "County"; const items = []; + if (multipleStatesSelected) { for (const state of states) { if (multipleYearsSelected) { for (const year of years) { - const item = await getDataForSingleYearAndState(selectedOptions, state, year); - items.push(item); + if (countySelected) { + const allCounties = countyData[state]; + for (const county of allCounties) { + const item = await getDataForSingleYearAndState(selectedOptions, county, year, state); + items.push(item); + } + } else { + const item = await getDataForSingleYearAndState(selectedOptions, state, year); + items.push(item); + } } } else { const item = await getDataForSingleYearAndState(selectedOptions, state, years[0]); @@ -109,18 +142,25 @@ const getItems = async (selectedOptions: IStateOptions) => { } } } else { - const item = await getDataForSingleYearAndState(selectedOptions, states[0], years[0]); - items.push(item); + if (countySelected) { + const allCounties = countyData[states[0]]; + for (const county of allCounties) { + const item = await getDataForSingleYearAndState(selectedOptions, county, years[0], states[0]); + items.push(item); + } + } else { + const item = await getDataForSingleYearAndState(selectedOptions, states[0], years[0]); + items.push(item); + } } - return items; }; -const getDataForSingleYearAndState = async (selectedOptions: IStateOptions, state: string, year: string) => { +const getDataForSingleYearAndState = async (selectedOptions: IStateOptions, countyOrState: string, year: string, state?: string) => { const {geographicLevel, states, years, cropUnits, ...subOptions} = selectedOptions; let item: any = { - "State": state, + [geographicLevel]: countyOrState, "Year": year, }; @@ -128,8 +168,23 @@ const getDataForSingleYearAndState = async (selectedOptions: IStateOptions, stat const value = subOptions[key as keyof typeof subOptions]; if (value && Array.isArray(value)) { for (const attribute of value) { - const attrData = await getAttrData({attribute, geographicLevel, state, year, cropUnits}); - item = {...item, ...attrData}; + const queryParams = getQueryParams(attribute); + const yearAvailable = queryParams?.years[geographicLevel].includes(year); + const isMultiStateRegion = queryParams?.geographicAreas[0] === "REGION : MULTI-STATE"; + const geoLevel = isMultiStateRegion ? "REGION : MULTI-STATE" : geographicLevel; + if (yearAvailable) { + let location = countyOrState; + if (isMultiStateRegion) { + const itemToCheck = state ? state : countyOrState; + location = multiRegions.find((region) => region.States.includes(itemToCheck))!.Region; + } + const params: IGetAttrDataParams = {attribute, geographicLevel: geoLevel, location, year, cropUnits}; + if (geoLevel === "County") { + params.state = state; + } + const attrData = await getAttrData(params); + item = {...item, ...attrData}; + } } } } @@ -138,8 +193,8 @@ const getDataForSingleYearAndState = async (selectedOptions: IStateOptions, stat }; const getAttrData = async (params: IGetAttrDataParams) => { - const {attribute, geographicLevel, state, year, cropUnits} = params; - const reqParams: IRequestParams = {attribute, geographicLevel, location: state, year}; + const {attribute, geographicLevel, location, year, cropUnits, state} = params; + const reqParams: IRequestParams = {attribute, geographicLevel, location, year, state}; if (cropOptions.options.includes(attribute) && cropUnits) { reqParams.cropCategory = cropUnits as keyof ICropDataItem; } @@ -148,22 +203,11 @@ const getAttrData = async (params: IGetAttrDataParams) => { const values: any = {}; if (res) { const {data} = res; - data.forEach((dataItem: any) => { - values[dataItem.short_desc] = dataItem.Value; + data.map((dataItem: any) => { + return values[dataItem.short_desc] = dataItem.Value; }); } else { console.log("error"); } return values; }; - -export const fetchData = async (req: string) => { - try { - const response = await fetchJsonp(req); - const json = await response.json(); - return json; - } catch (error) { - console.log("parsing failed", error); - throw error; - } -}; diff --git a/src/scripts/connect.js b/src/scripts/connect.js index 0b24088..8676f21 100644 --- a/src/scripts/connect.js +++ b/src/scripts/connect.js @@ -48,19 +48,20 @@ export const connect = { return res; }, - createTopCollection: async function() { + createTopCollection: async function(geoLevel) { + const plural = geoLevel === "State" ? "States" : "Counties"; const message = { "action": "create", "resource": `dataContext[${dataSetName}].collection`, "values": { - "name": "States", + "name": plural, "parent": "_root_", "attributes": [{ - "name": "State", + "name": geoLevel, }, { "name": "Boundary", - "formula": "lookupBoundary(US_state_boundaries, State)", + "formula": `lookupBoundary(US_${geoLevel.toLowerCase()}_boundaries, ${geoLevel})`, "formulaDependents": "State" }] } @@ -68,13 +69,14 @@ export const connect = { await codapInterface.sendRequest(message); }, - createSubCollection: async function(attrs) { + createSubCollection: async function(geoLevel, attrs) { + const plural = geoLevel === "State" ? "States" : "Counties"; const message = { "action": "create", "resource": `dataContext[${dataSetName}].collection`, "values": { "name": "Data", - "parent": "States", + "parent": plural, "attributes": attrs.map((attr) => this.makeCODAPAttributeDef(attr)) } }; diff --git a/src/scripts/query-headers.ts b/src/scripts/query-headers.ts deleted file mode 100644 index 32110af..0000000 --- a/src/scripts/query-headers.ts +++ /dev/null @@ -1,356 +0,0 @@ -const areaHarvested = "Area Harvested"; -const yieldInBU = "Yield"; - -export interface ICropCategory { - [areaHarvested]: string, - [yieldInBU]: string -} -export interface ICropDataItem { - [areaHarvested]: string[], - [yieldInBU]: string[] -} - -export interface IQueryHeaders { - plugInAttribute: string, - numberOfAttributeColumnsInCodap: number|string, - sect_desc: string, - group_desc: string, - commodity_desc: string, - statisticcat_desc: string|ICropCategory, - short_desc: string[]|ICropDataItem, - domain_desc: string, - geographicLevels?: string, - years: { - county: string[] - state: string[] - } -} - -const sharedDemographicHeaders = { -sect_desc: "Demographics", -group_desc: "Producers", -commodity_desc: "Producers", -statisticcat_desc: "Producers", -domain_desc: "Total", -}; - -const sharedEconomicHeaders = { -sect_desc: "Economics", -group_desc: "Farms & Land & Assets", -commodity_desc: "Farm Operations", -statisticcat_desc: "Operations" -}; - -const sharedLaborHeaders = { -sect_desc: "Economics", -group_desc: "Expenses", -commodity_desc: "Labor", -}; - -const allYears = []; -for (let year = 2022; year >= 1910; year--) { - allYears.push(`${year}`); -} - -export const queryData: Array = [ -{ - plugInAttribute: "Total Farmers", - numberOfAttributeColumnsInCodap: 1, - ...sharedDemographicHeaders, - short_desc: ["PRODUCERS, (ALL) - NUMBER OF PRODUCERS"], - years: { - county: ["2017"], - state: ["2017"] - } -}, -{ - plugInAttribute: "Age", - numberOfAttributeColumnsInCodap: 7, - ...sharedDemographicHeaders, - short_desc: [ - "PRODUCERS, AGE LT 25 - NUMBER OF PRODUCERS", - "PRODUCERS, AGE 25 TO 34 - NUMBER OF PRODUCERS", - "PRODUCERS, AGE 35 TO 44 - NUMBER OF PRODUCERS", - "PRODUCERS, AGE 45 TO 54 - NUMBER OF PRODUCERS", - "PRODUCERS, AGE 55 TO 64 - NUMBER OF PRODUCERS", - "PRODUCERS, AGE 65 TO 74 - NUMBER OF PRODUCERS", - "PRODUCERS, AGE GE 75 - NUMBER OF PRODUCERS" - ], - years: { - county: ["2017"], - state: ["2017"] - } - -}, -{ - plugInAttribute: "Gender", - numberOfAttributeColumnsInCodap: 2, - ...sharedDemographicHeaders, - short_desc: [ - "PRODUCERS, (ALL), FEMALE - NUMBER OF PRODUCERS", - "PRODUCERS, (ALL), MALE - NUMBER OF PRODUCERS" - ], - years: { - county: ["2017"], - state: ["2017"] - } -}, -{ - plugInAttribute: "Race", - numberOfAttributeColumnsInCodap: 7, - ...sharedDemographicHeaders, - short_desc: [ - "PRODUCERS, AMERICAN INDIAN OR ALASKAN NATIVE - NUMBER OF PRODUCERS", - "PRODUCERS, ASIAN - NUMBER OF PRODUCERS", - "PRODUCERS, BLACK OR AFRICAN AMERICAN - NUMBER OF PRODUCERS", - "PRODUCERS, HISPANIC - NUMBER OF PRODUCERS", - "PRODUCERS, MULTI-RACE - NUMBER OF PRODUCERS", - "PRODUCERS, NATIVE HAWAIIAN OR OTHER PACIFIC ISLANDERS - NUMBER OF PRODUCERS", - "PRODUCERS, WHITE - NUMBER OF PRODUCERS" - ], - years: { - county: ["2017"], - state: ["2017"] - } -}, -{ - plugInAttribute: "Total Farms", - numberOfAttributeColumnsInCodap: 1, - ...sharedEconomicHeaders, - short_desc: ["FARM OPERATIONS - NUMBER OF OPERATIONS"], - domain_desc: "Total", - geographicLevels: "State, County", - years: { - county: allYears, - state: allYears - } -}, -{ - plugInAttribute: "Organization Type", - numberOfAttributeColumnsInCodap: 5, - sect_desc: "Demographics", - group_desc: "Farms & Land & Assets", - commodity_desc: "Farm Operations", - statisticcat_desc: "Operations", - short_desc: [ - "FARM OPERATIONS, ORGANIZATION, TAX PURPOSES, CORPORATION (EXCL FAMILY HELD) - NUMBER OF OPERATIONS", - "FARM OPERATIONS, ORGANIZATION, TAX PURPOSES, CORPORATION, FAMILY HELD - NUMBER OF OPERATIONS", - "FARM OPERATIONS, ORGANIZATION, TAX PURPOSES, FAMILY & INDIVIDUAL - NUMBER OF OPERATIONS", - "FARM OPERATIONS, ORGANIZATION, TAX PURPOSES, INSTITUTIONAL & RESEARCH & RESERVATION & OTHER - NUMBER OF OPERATIONS", - "FARM OPERATIONS, ORGANIZATION, TAX PURPOSES, PARTNERSHIP - NUMBER OF OPERATIONS" - ], - domain_desc: "Total", - geographicLevels: "County", - years: { - county: ["1997", "2002", "2007", "2012", "2017"], - state: [] - } -}, -{ - plugInAttribute: "Economic Class", - numberOfAttributeColumnsInCodap: "3 - 6", - ...sharedEconomicHeaders, - short_desc: ["FARM OPERATIONS - NUMBER OF OPERATIONS"], - domain_desc: "Economic Class", - geographicLevels: "State ", - years: { - county: allYears.filter(y => Number(y) >= 1987), - state: allYears.filter(y => Number(y) >= 1987) - } -}, -{ - plugInAttribute: "Acres Operated", - numberOfAttributeColumnsInCodap: 14, - sect_desc: "Economics", - group_desc: "Farms & Land & Assets", - commodity_desc: "Farm Operations", - statisticcat_desc: "Area Operated", - short_desc: ["FARM OPERATIONS - ACRES OPERATED"], - domain_desc: "Area Operated", - geographicLevels: "State, County", - years: { - county: ["1997", "2002", "2007", "2012", "2017"], - state: ["1997", "2002", "2007", "2012", "2017"] - } -}, -{ - plugInAttribute: "Organic", - numberOfAttributeColumnsInCodap: 1, - ...sharedEconomicHeaders, - short_desc: ["FARM OPERATIONS, ORGANIC - NUMBER OF OPERATIONS"], - domain_desc: "Organic Status", - years: { - county: ["2008", "2011", "2012", "2014", "2015", "2016", "2017", "2019", "2021"], - state: ["2008", "2011", "2012", "2014", "2015", "2016", "2017", "2019", "2021"] - } -}, -{ - plugInAttribute: "Labor Status", - numberOfAttributeColumnsInCodap: 3, - ...sharedLaborHeaders, - statisticcat_desc: "Workers", - short_desc: [ - "LABOR, MIGRANT - NUMBER OF WORKERS", - "LABOR, UNPAID - NUMBER OF WORKERS", - "LABOR, HIRED - NUMBER OF WORKERS" - ], - domain_desc: "Total", - geographicLevels: "State, County", - years: { - county: ["2012", "2017"], - state: ["2012", "2017"] - } -}, -{ - plugInAttribute: "Wages", - numberOfAttributeColumnsInCodap: 1, - ...sharedLaborHeaders, - statisticcat_desc: "Wage Rate", - short_desc: ["LABOR, HIRED - WAGE RATE, MEASURED IN $/HOUR"], - domain_desc: "Total", - geographicLevels: "Region: Multi-state", - years: { - county: allYears.filter(y => Number(y) >= 1989), - state: allYears.filter(y => Number(y) >= 1989) - } -}, -{ - plugInAttribute: "Time Worked", - numberOfAttributeColumnsInCodap: 1, - ...sharedLaborHeaders, - statisticcat_desc: "Wage Rate", - short_desc: ["LABOR, HIRED - TIME WORKED, MEASURED IN HOURS/WEEK"], - domain_desc: "Total", - geographicLevels: "Region: Multi-state", - years: { - county: allYears.filter(y => Number(y) >= 1989), - state: allYears.filter(y => Number(y) >= 1989) - } -}, -{ - plugInAttribute: "Corn", - numberOfAttributeColumnsInCodap: 1, - sect_desc: "Crops", - group_desc: "Field Crops", - commodity_desc: "Corn", - statisticcat_desc: { - [areaHarvested]: "Area Harvested", - [yieldInBU]: "Yield" - }, - short_desc: { - [areaHarvested]: ["CORN, GRAIN - ACRES HARVESTED"], - [yieldInBU]: ["CORN, GRAIN - YIELD, MEASURED IN BU / ACRE"] - }, - domain_desc: "Total", - geographicLevels: "State, County", - years: { - county: allYears, - state: allYears - } -}, -{ - plugInAttribute: "Cotton", - numberOfAttributeColumnsInCodap: 1, - sect_desc: "Crops", - group_desc: "Field Crops", - commodity_desc: "Cotton", - statisticcat_desc: { - [areaHarvested]: "Area Harvested", - [yieldInBU]: "Yield" - }, - short_desc: { - [areaHarvested]: ["COTTON - ACRES HARVESTED"], - [yieldInBU]: ["COTTON - YIELD, MEASURED IN LB / ACRE"] - }, - domain_desc: "Total", - geographicLevels: "State, County", - years: { - county: allYears, - state: allYears - } -}, -{ - plugInAttribute: "Grapes", - numberOfAttributeColumnsInCodap: 1, - sect_desc: "Crops", - group_desc: "Fruit & Tree Nuts", - commodity_desc: "Grapes", - statisticcat_desc: { - [areaHarvested]: "Area Harvested", - [yieldInBU]: "Yield" - }, - short_desc: { - [areaHarvested]: ["GRAPES, ORGANIC - ACRES HARVESTED"], - [yieldInBU]: ["GRAPES - YIELD, MEASURED IN TONS / ACRE"] - }, - domain_desc: "Total", - geographicLevels: "State, County", - years: { - county: allYears, - state: allYears - } -}, -{ - plugInAttribute: "Oats", - numberOfAttributeColumnsInCodap: 1, - sect_desc: "Crops", - group_desc: "Field Crops", - commodity_desc: "Oats", - statisticcat_desc: { - [areaHarvested]: "Area Harvested", - [yieldInBU]: "Yield" - }, - short_desc: { - [areaHarvested]: ["Oats - Acres Harvested"], - [yieldInBU]: ["Oats - Yield, measured in BU / acre"] - }, - domain_desc: "Total", - geographicLevels: "State, County", - years: { - county: allYears, - state: allYears - } -}, -{ - plugInAttribute: "Soybeans", - numberOfAttributeColumnsInCodap: 1, - sect_desc: "Crops", - group_desc: "Field Crops", - commodity_desc: "Soybeans", - statisticcat_desc: { - [areaHarvested]: "Area Harvested", - [yieldInBU]: "Yield" - }, - short_desc: { - [areaHarvested]: ["Soybeans - Acres Harvested"], - [yieldInBU]: ["Soybeans - Yield, measured in BU / acre"] - }, - domain_desc: "Total", - geographicLevels: "State, County", - years: { - county: allYears, - state: allYears - } -}, -{ - plugInAttribute: "Wheat", - numberOfAttributeColumnsInCodap: 1, - sect_desc: "Crops", - group_desc: "Field Crops", - commodity_desc: "Wheat", - statisticcat_desc: { - [areaHarvested]: "Area Harvested", - [yieldInBU]: "Yield" - }, - short_desc: { - [areaHarvested]: ["Wheat - Acres Harvested"], - [yieldInBU]: ["Wheat - Yield, measured in BU / acre"] - }, - domain_desc: "Total", - geographicLevels: "State, County", - years: { - county: allYears, - state: allYears - } -} -]; diff --git a/src/scripts/utils.ts b/src/scripts/utils.ts new file mode 100644 index 0000000..d8296e6 --- /dev/null +++ b/src/scripts/utils.ts @@ -0,0 +1,10 @@ +import { queryData } from "../constants/query-headers"; + +export const flatten = (arr: any[]): any[] => { + return arr.reduce((acc: any[], val: any) => + Array.isArray(val) ? acc.concat(flatten(val)) : acc.concat(val), []); +}; + +export const getQueryParams = (attribute: string) => { + return queryData.find((d) => d.plugInAttribute === attribute); +} \ No newline at end of file From 7ac5795336d09753ee6cad096fed69700034a25c Mon Sep 17 00:00:00 2001 From: lublagg Date: Tue, 19 Sep 2023 17:11:03 -0400 Subject: [PATCH 8/8] Various UI improvements. --- src/components/attribute-options.tsx | 2 +- src/components/options.tsx | 19 +- src/components/place-options.tsx | 25 +++ src/components/years-options.tsx | 2 +- src/constants/codapMetadata.ts | 183 ++++++++++++++++++ src/constants/constants.ts | 7 +- src/constants/counties.ts | 2 +- .../{query-headers.ts => queryHeaders.ts} | 101 +--------- src/constants/regionData.ts | 79 ++++++++ src/constants/types.ts | 12 +- src/scripts/api.ts | 90 ++++++--- src/scripts/pluginHelper.js | 162 ---------------- src/scripts/utils.ts | 4 +- 13 files changed, 385 insertions(+), 303 deletions(-) create mode 100644 src/constants/codapMetadata.ts rename src/constants/{query-headers.ts => queryHeaders.ts} (78%) create mode 100644 src/constants/regionData.ts delete mode 100644 src/scripts/pluginHelper.js diff --git a/src/components/attribute-options.tsx b/src/components/attribute-options.tsx index 3a47779..a60328e 100644 --- a/src/components/attribute-options.tsx +++ b/src/components/attribute-options.tsx @@ -15,7 +15,6 @@ export const AttributeOptions: React.FC = (props) => { const {handleSetSelectedOptions, selectedOptions} = props; const commonProps = { - inputType: "checkbox" as "checkbox"|"radio", handleSetSelectedOptions, selectedOptions }; @@ -43,6 +42,7 @@ export const AttributeOptions: React.FC = (props) => {
= (props) => { let newArray = [...selectedOptions[optionKey]]; if (e.currentTarget.checked) { newArray.push(e.target.value); + // If user selects "Age", "Gender", or "Race", auto-select "Total Farmers" as well + if (optionKey === "farmerDemographics" && !newArray.includes("Total Farmers")) { + newArray.push("Total Farmers"); + } + // If user selects a state, de-select "All States" + if (optionKey === "states" && newArray.includes("All States")) { + newArray = newArray.filter((state) => state !== "All States"); + } newArray.sort(); if (optionKey === "years") { newArray.reverse(); } } else { if (isOptionSelected(e.target.value)) { - newArray = newArray.filter((o) => o !== e.target.value); + const includes = (opt: string) => selectedOptions.farmerDemographics.includes(opt); + // "Total Farmers" can only be unselected if race, gender, and age are unselected + if (optionKey === "farmerDemographics" && e.target.value === "Total Farmers") { + const shouldFilter = !includes("Race") && !includes("Gender") && !includes("Age"); + if (shouldFilter) { + newArray = newArray.filter((o) => o !== e.target.value); + } + } else { + newArray = newArray.filter((o) => o !== e.target.value); + } } } handleSetSelectedOptions(optionKey, newArray); diff --git a/src/components/place-options.tsx b/src/components/place-options.tsx index 6294bea..25d9012 100644 --- a/src/components/place-options.tsx +++ b/src/components/place-options.tsx @@ -12,6 +12,15 @@ interface IProps { export const PlaceOptions: React.FC = (props) => { const {handleSetSelectedOptions, selectedOptions} = props; + + const isAllStatesSelected = () => { + return selectedOptions.states[0] === "All States"; + }; + + const handleSelectAllStates = () => { + handleSetSelectedOptions("states", ["All States"]); + }; + return ( <> {placeOptions.map((placeOpt) => { @@ -22,6 +31,22 @@ export const PlaceOptions: React.FC = (props) => { key={`options-container-${placeOpt.key}`} className={placeOpt.key === "geographicLevel" ? css.radioOptions : css.checkOptions} > + {placeOpt.key === "states" && +
+ + +
+ } 74", + "unitInCodapTable": "# of Farmers" + }, + "PRODUCERS, (ALL), FEMALE - NUMBER OF PRODUCERS": { + "attributeNameInCodapTable": "Female", + "unitInCodapTable": "# of Farmers" + }, + "PRODUCERS, (ALL), MALE - NUMBER OF PRODUCERS": { + "attributeNameInCodapTable": "Male", + "unitInCodapTable": "# of Farmers" + }, + "PRODUCERS, AMERICAN INDIAN OR ALASKAN NATIVE - NUMBER OF PRODUCERS": { + "attributeNameInCodapTable": "American Indian or Alaskan Native", + "unitInCodapTable": "# of Farmers" + }, + "PRODUCERS, ASIAN - NUMBER OF PRODUCERS": { + "attributeNameInCodapTable": "Asian", + "unitInCodapTable": "# of Farmers" + }, + "PRODUCERS, BLACK OR AFRICAN AMERICAN - NUMBER OF PRODUCERS": { + "attributeNameInCodapTable": "Black or African American", + "unitInCodapTable": "# of Farmers" + }, + "PRODUCERS, HISPANIC - NUMBER OF PRODUCERS": { + "attributeNameInCodapTable": "Hispanic", + "unitInCodapTable": "# of Farmers" + }, + "PRODUCERS, MULTI-RACE - NUMBER OF PRODUCERS": { + "attributeNameInCodapTable": "Multi-race", + "unitInCodapTable": "# of Farmers" + }, + "PRODUCERS, NATIVE HAWAIIAN OR OTHER PACIFIC ISLANDERS - NUMBER OF PRODUCERS": { + "attributeNameInCodapTable": "Native Hawaiian or Other Pacific Islanders", + "unitInCodapTable": "# of Farmers" + }, + "PRODUCERS, WHITE - NUMBER OF PRODUCERS": { + "attributeNameInCodapTable": "White", + "unitInCodapTable": "# of Farmers" + }, + "FARM OPERATIONS - NUMBER OF OPERATIONS": { + "attributeNameInCodapTable": "Total Number of Farms", + "unitInCodapTable": "# of Farms" + }, + "FARM OPERATIONS, ORGANIZATION, TAX PURPOSES, CORPORATION (EXCL FAMILY HELD) - NUMBER OF OPERATIONS": { + "attributeNameInCodapTable": "Corporate", + "unitInCodapTable": "# of Farms" + }, + "FARM OPERATIONS, ORGANIZATION, TAX PURPOSES, CORPORATION, FAMILY HELD - NUMBER OF OPERATIONS": { + "attributeNameInCodapTable": "Corporate, Family Held", + "unitInCodapTable": "# of Farms" + }, + "FARM OPERATIONS, ORGANIZATION, TAX PURPOSES, FAMILY & INDIVIDUAL - NUMBER OF OPERATIONS": { + "attributeNameInCodapTable": "Family & Individual", + "unitInCodapTable": "# of Farms" + }, + "FARM OPERATIONS, ORGANIZATION, TAX PURPOSES, INSTITUTIONAL & RESEARCH & RESERVATION & OTHER - NUMBER OF OPERATIONS": { + "attributeNameInCodapTable": "Institutional, Research, Reservation, & Other", + "unitInCodapTable": "# of Farms" + }, + "FARM OPERATIONS, ORGANIZATION, TAX PURPOSES, PARTNERSHIP - NUMBER OF OPERATIONS": { + "attributeNameInCodapTable": "Partnership", + "unitInCodapTable": "# of Farms" + }, + "FARM OPERATIONS - ACRES OPERATED": { + "attributeNameInCodapTable": "", + "unitInCodapTable": "# of Farms" + }, + "FARM OPERATIONS, ORGANIC - NUMBER OF OPERATIONS": { + "attributeNameInCodapTable": "Organic", + "unitInCodapTable": "# of Farms" + }, + "LABOR, MIGRANT - NUMBER OF WORKERS": { + "attributeNameInCodapTable": "Migrant", + "unitInCodapTable": "# of Farm Laborers" + }, + "LABOR, UNPAID - NUMBER OF WORKERS": { + "attributeNameInCodapTable": "Unpaid", + "unitInCodapTable": "# of Farm Laborers" + }, + "LABOR, HIRED - NUMBER OF WORKERS": { + "attributeNameInCodapTable": "Hired", + "unitInCodapTable": "# of Farm Laborers" + }, + "LABOR, HIRED - WAGE RATE, MEASURED IN $ / HOUR": { + "attributeNameInCodapTable": "Wage Rate of Laborers", + "unitInCodapTable": "$/Hour" + }, + "LABOR, HIRED - TIME WORKED, MEASURED IN HOURS / WEEK": { + "attributeNameInCodapTable": "Time Worked by Laborers", + "unitInCodapTable": "Hours/Week" + }, + "CORN, GRAIN - YIELD, MEASURED IN BU / ACRE": { + "attributeNameInCodapTable": "Corn Yield", + "unitInCodapTable": "BU/Acre" + }, + "CORN, GRAIN - ACRES HARVESTED": { + "attributeNameInCodapTable": "Corn Area Harvested", + "unitInCodapTable": "Acres Harvested" + }, + "COTTON, - YIELD, MEASURED IN LB / ACRE": { + "attributeNameInCodapTable": "Cotton Yield", + "unitInCodapTable": "LB/Acre" + }, + "COTTON - ACRES HARVESTED": { + "attributeNameInCodapTable": "Cotton Area Harvested", + "unitInCodapTable": "Acres Harvested" + }, + "GRAPES - YIELD, MEASURED IN TONS / ACRE": { + "attributeNameInCodapTable": "Grapes Yield", + "unitInCodapTable": "Tons/Acre" + }, + "GRAPES, ORGANIC - ACRES HARVESTED": { + "attributeNameInCodapTable": "Grapes Area Harvested", + "unitInCodapTable": "Acres Harvested" + }, + "OATS - YIELD, MEASURED IN BU / ACRE": { + "attributeNameInCodapTable": "Oats Yield", + "unitInCodapTable": "BU/Acre" + }, + "OATS - ACRES HARVESTED": { + "attributeNameInCodapTable": "Oats Area Harvested", + "unitInCodapTable": "Acres Harvested" + }, + "SOYBEANS - YIELD MEASURED IN BU / ACRE": { + "attributeNameInCodapTable": "Soybeans Yield", + "unitInCodapTable": "BU/Acre" + }, + "SOYBEANS - ACRES HARVESTED": { + "attributeNameInCodapTable": "Soybeans Area Harvested", + "unitInCodapTable": "Acres Harvested" + }, + "WHEAT - YIELD MEASURED IN BU / ACRE": { + "attributeNameInCodapTable": "Wheat Yield", + "unitInCodapTable": "BU/Acre" + }, + "WHEAT - ACRES HARVESTED": { + "attributeNameInCodapTable": "Wheat Area Harvested", + "unitInCodapTable": "Acres Harvested" + } +}; diff --git a/src/constants/constants.ts b/src/constants/constants.ts index 234e08c..f23e8c9 100644 --- a/src/constants/constants.ts +++ b/src/constants/constants.ts @@ -58,14 +58,13 @@ export const fiftyStates = [ "West Virginia", "Wisconsin", "Wyoming" -] +]; export const stateOptions: IAttrOptions = { label: null, key: "states", instructions: "Choose states to include in your dataset from the list below", options: [ - "All States", ...fiftyStates ] }; @@ -99,7 +98,7 @@ const cropUnitOptions: IAttrOptions = { export const cropOptions: IAttrOptions = { key: "crops", label: null, - options: ["Corn", "Cotton", "Grapes", "Grasses", "Oats", "Soybeans", "Wheat"], + options: ["Corn", "Cotton", "Grapes", "Oats", "Soybeans", "Wheat"], instructions: "(Choose crops)" }; @@ -132,4 +131,4 @@ export const defaultSelectedOptions: IStateOptions = { cropUnits: "", crops: [], years: [] -}; \ No newline at end of file +}; diff --git a/src/constants/counties.ts b/src/constants/counties.ts index f97563e..abd1d50 100644 --- a/src/constants/counties.ts +++ b/src/constants/counties.ts @@ -3282,4 +3282,4 @@ export const countyData: {[state: string]: string[]} = { "WASHAKIE", "WESTON" ] -} \ No newline at end of file +}; diff --git a/src/constants/query-headers.ts b/src/constants/queryHeaders.ts similarity index 78% rename from src/constants/query-headers.ts rename to src/constants/queryHeaders.ts index dcac5f7..b2a50ab 100644 --- a/src/constants/query-headers.ts +++ b/src/constants/queryHeaders.ts @@ -1,4 +1,3 @@ -import { fiftyStates } from "./constants"; import { IQueryHeaders } from "./types"; const allYears = []; @@ -6,7 +5,6 @@ for (let year = 2022; year >= 1910; year--) { allYears.push(`${year}`); } - const sharedDemographicHeaders = { sect_desc: "Demographics", group_desc: "Producers", @@ -41,7 +39,7 @@ const sharedCropHeaders = { "County": allYears, "State": allYears } -} +}; export const queryData: Array = [ { @@ -198,8 +196,8 @@ export const queryData: Array = [ { plugInAttribute: "Time Worked", ...sharedLaborHeaders, - statisticcat_desc: "Wage Rate", - short_desc: ["LABOR, HIRED - TIME WORKED, MEASURED IN HOURS/WEEK"], + statisticcat_desc: "Time Worked", + short_desc: ["LABOR, HIRED - TIME WORKED, MEASURED IN HOURS / WEEK"], domain_desc: "Total", geographicAreas: ["REGION : MULTI-STATE"], years: { @@ -242,8 +240,8 @@ export const queryData: Array = [ group_desc: "Field Crops", commodity_desc: "Oats", short_desc: { - ["Area Harvested"]: ["Oats - Acres Harvested"], - ["Yield"]: ["Oats - Yield, measured in BU / acre"] + ["Area Harvested"]: ["OATS - ACRES HARVESTED"], + ["Yield"]: ["OATS - YIELD, MEASURED IN BU / ACRE"] }, ...sharedCropHeaders }, @@ -252,8 +250,8 @@ export const queryData: Array = [ group_desc: "Field Crops", commodity_desc: "Soybeans", short_desc: { - ["Area Harvested"]: ["Soybeans - Acres Harvested"], - ["Yield"]: ["Soybeans - Yield, measured in BU / acre"] + ["Area Harvested"]: ["SOYBEANS - ACRES HARVESTED"], + ["Yield"]: ["SOYBEANS - YIELD MEASURED IN BU / ACRE"] }, ...sharedCropHeaders }, @@ -263,90 +261,9 @@ export const queryData: Array = [ commodity_desc: "Wheat", short_desc: { - ["Area Harvested"]: ["Wheat - Acres Harvested"], - ["Yield"]: ["Wheat - Yield, measured in BU / acre"] + ["Area Harvested"]: ["WHEAT - ACRES HARVESTED"], + ["Yield"]: ["WHEAT - YIELD MEASURED IN BU / ACRE"] }, ...sharedCropHeaders } ]; - -interface IRegion { - "Region": string - "States": string[] -} - - -export const multiRegions: IRegion[] = [ - { - "Region": "Pacific", - "States": ["Washington", "Oregon"] - }, - { - "Region": "Mountain I", - "States": ["Montana", "Idaho", "Wyoming"] - }, - { - "Region": "Mountain II", - "States": ["Nevada", "Utah", "Colorado"] - }, - { - "Region": "Mountain III", - "States": ["Arizona", "New Mexico"] - }, - { - "Region": "Northern Plains", - "States": ["North Dakota", "South Dakota", "Kansas", "Nebraska"] - }, - { - "Region": "Southern Plains", - "States": ["Oklahoma", "Texas"] - }, - { - "Region": "Lake", - "States": ["Minnesota", "Wisconsin", "Michigan"] - }, - { - "Region": "Cornbelt I", - "States": ["Illinois", "Indiana", "Ohio"] - }, - { - "Region": "Cornbelt II", - "States": ["Iowa", "Missouri"] - }, - { - "Region": "Delta", - "States": ["Mississippi", "Louisiana", "Arkansas"] - }, - { - "Region": "Appalachian I", - "States": ["Virginia", "North Carolina"] - }, - { - "Region": "Appalachian II", - "States": ["West Virginia", "Kentucky", "Tennessee"] - }, - { - "Region": "Southeast", - "States": ["South Carolina", "Alabama", "Georgia"] - }, - { - "Region": "Northeast I", - "States": ["Maine", "New Hampshire", "Vermont", "Massachusetts", "Connecticut", "Rhode Island", "New York"] - }, - { - "Region": "Northeast II", - "States": ["Pennsylvania", "New Jersey", "Delaware", "Maryland"] - }, - { - "Region": "California", - "States": ["California"] - }, - { - "Region": "Florida", - "States": ["Florida"] - }, - { - "Region": "Hawaii", - "States": ["Hawaii"] - } -]; diff --git a/src/constants/regionData.ts b/src/constants/regionData.ts new file mode 100644 index 0000000..4a5317c --- /dev/null +++ b/src/constants/regionData.ts @@ -0,0 +1,79 @@ +interface IRegion { + "Region": string + "States": string[] +} + +export const multiRegions: IRegion[] = [ + { + "Region": "Pacific", + "States": ["Washington", "Oregon"] + }, + { + "Region": "Mountain I", + "States": ["Montana", "Idaho", "Wyoming"] + }, + { + "Region": "Mountain II", + "States": ["Nevada", "Utah", "Colorado"] + }, + { + "Region": "Mountain III", + "States": ["Arizona", "New Mexico"] + }, + { + "Region": "Northern Plains", + "States": ["North Dakota", "South Dakota", "Kansas", "Nebraska"] + }, + { + "Region": "Southern Plains", + "States": ["Oklahoma", "Texas"] + }, + { + "Region": "Lake", + "States": ["Minnesota", "Wisconsin", "Michigan"] + }, + { + "Region": "Cornbelt I", + "States": ["Illinois", "Indiana", "Ohio"] + }, + { + "Region": "Cornbelt II", + "States": ["Iowa", "Missouri"] + }, + { + "Region": "Delta", + "States": ["Mississippi", "Louisiana", "Arkansas"] + }, + { + "Region": "Appalachian I", + "States": ["Virginia", "North Carolina"] + }, + { + "Region": "Appalachian II", + "States": ["West Virginia", "Kentucky", "Tennessee"] + }, + { + "Region": "Southeast", + "States": ["South Carolina", "Alabama", "Georgia"] + }, + { + "Region": "Northeast I", + "States": ["Maine", "New Hampshire", "Vermont", "Massachusetts", "Connecticut", "Rhode Island", "New York"] + }, + { + "Region": "Northeast II", + "States": ["Pennsylvania", "New Jersey", "Delaware", "Maryland"] + }, + { + "Region": "California", + "States": ["California"] + }, + { + "Region": "Florida", + "States": ["Florida"] + }, + { + "Region": "Hawaii", + "States": ["Hawaii"] + } +]; diff --git a/src/constants/types.ts b/src/constants/types.ts index 1c3b2f5..d210207 100644 --- a/src/constants/types.ts +++ b/src/constants/types.ts @@ -1,7 +1,3 @@ -import { fiftyStates } from "./constants"; - - - export interface IStateOptions { geographicLevel: "County"|"State", states: string[], @@ -62,17 +58,17 @@ export interface IResData { week_ending: string; year: number; zip_5: string; -}; +} export interface ICropCategory { ["Area Harvested"]: string, ["Yield"]: string -}; +} export interface ICropDataItem { ["Area Harvested"]: string[], ["Yield"]: string[] -}; +} export interface IQueryHeaders { plugInAttribute: string, @@ -87,4 +83,4 @@ export interface IQueryHeaders { "County": string[] "State": string[] } -}; +} diff --git a/src/scripts/api.ts b/src/scripts/api.ts index 5ec688a..8dcd166 100644 --- a/src/scripts/api.ts +++ b/src/scripts/api.ts @@ -1,10 +1,11 @@ import fetchJsonp from "fetch-jsonp"; -import { multiRegions, queryData } from "../constants/query-headers"; import { ICropCategory, ICropDataItem, IStateOptions } from "../constants/types"; +import { multiRegions } from "../constants/regionData"; import { connect } from "./connect"; -import { cropOptions } from "../constants/constants"; +import { cropOptions, fiftyStates } from "../constants/constants"; import { countyData } from "../constants/counties"; import { getQueryParams } from "./utils"; +import { attrToCODAPColumnName } from "../constants/codapMetadata"; const baseURL = `https://quickstats.nass.usda.gov/api/api_GET/?key=9ED0BFB8-8DDD-3609-9940-A2341ED6A9E3`; @@ -26,15 +27,30 @@ interface IGetAttrDataParams { state?: string } -export const fetchData = async (req: string) => { - try { - const response = await fetchJsonp(req, {timeout: 10000}); - const json = await response.json(); - return json; - } catch (error) { - console.log("parsing failed", error); - throw error; +// export const fetchData = async (req: string) => { +// try { +// const response = await fetchJsonp(req, {timeout: 10000}); +// const json = await response.json(); +// return json; +// } catch (error) { +// console.log("parsing failed", error); +// throw error; +// } +// }; + +export const fetchDataWithRetry = async (req: string, maxRetries = 3) => { + let retries = 0; + while (retries < maxRetries) { + try { + const response = await fetchJsonp(req, { timeout: 30000 }); // Increase the timeout + const json = await response.json(); + return json; + } catch (error) { + console.log(`Request attempt ${retries + 1} failed:`, error); + retries++; + } } + throw new Error(`Request failed after ${maxRetries} attempts`); }; export const createRequest = ({attribute, geographicLevel, location, year, cropCategory, state}: IRequestParams) => { @@ -76,7 +92,7 @@ export const createRequest = ({attribute, geographicLevel, location, year, cropC // if we are creating a request at the county level, we need to also pass in a state if (state) { - req += `&state_name=${state}` + req += `&state_name=${state}`; } item.forEach(subItem => { @@ -91,6 +107,7 @@ export const createTableFromSelections = async (selectedOptions: IStateOptions) await connect.createTopCollection(geographicLevel); const allAttrs: Array = ["Year"]; + for (const key in subOptions) { const selections = subOptions[key as keyof typeof subOptions]; for (const attribute of selections) { @@ -100,26 +117,35 @@ export const createTableFromSelections = async (selectedOptions: IStateOptions) } const {short_desc} = queryParams; if (Array.isArray(short_desc)) { - allAttrs.push(...short_desc); - } else { - allAttrs.push(short_desc); + for (const desc of short_desc) { + console.log({desc}); + const codapColumnName = attrToCODAPColumnName[desc].attributeNameInCodapTable; + allAttrs.push(codapColumnName); + } + } else if (typeof short_desc === "object" && cropUnits) { + const attr = short_desc[cropUnits as keyof ICropDataItem][0]; + allAttrs.push(attrToCODAPColumnName[attr].attributeNameInCodapTable); } } } + await connect.createSubCollection(geographicLevel, allAttrs); const items = await getItems(selectedOptions); await connect.createItems(items); await connect.makeCaseTableAppear(); - // await connect.createItems(items); }; const getItems = async (selectedOptions: IStateOptions) => { - const {states, years} = selectedOptions; + let {states, years} = selectedOptions; const multipleStatesSelected = states.length > 1 || states[0] === "All States"; const multipleYearsSelected = years.length > 1; const countySelected = selectedOptions.geographicLevel === "County"; - const items = []; + if (states[0] === "All States") { + states = fiftyStates; + } + + const promises = []; if (multipleStatesSelected) { for (const state of states) { @@ -128,32 +154,32 @@ const getItems = async (selectedOptions: IStateOptions) => { if (countySelected) { const allCounties = countyData[state]; for (const county of allCounties) { - const item = await getDataForSingleYearAndState(selectedOptions, county, year, state); - items.push(item); + const item = getDataForSingleYearAndState(selectedOptions, county, year, state); + promises.push(item); } } else { - const item = await getDataForSingleYearAndState(selectedOptions, state, year); - items.push(item); + const item = getDataForSingleYearAndState(selectedOptions, state, year); + promises.push(item); } } } else { - const item = await getDataForSingleYearAndState(selectedOptions, state, years[0]); - items.push(item); + const item = getDataForSingleYearAndState(selectedOptions, state, years[0]); + promises.push(item); } } } else { if (countySelected) { const allCounties = countyData[states[0]]; for (const county of allCounties) { - const item = await getDataForSingleYearAndState(selectedOptions, county, years[0], states[0]); - items.push(item); + const item = getDataForSingleYearAndState(selectedOptions, county, years[0], states[0]); + promises.push(item); } } else { - const item = await getDataForSingleYearAndState(selectedOptions, states[0], years[0]); - items.push(item); + const item = getDataForSingleYearAndState(selectedOptions, states[0], years[0]); + promises.push(item); } } - return items; + return await Promise.all(promises); }; const getDataForSingleYearAndState = async (selectedOptions: IStateOptions, countyOrState: string, year: string, state?: string) => { @@ -176,7 +202,8 @@ const getDataForSingleYearAndState = async (selectedOptions: IStateOptions, coun let location = countyOrState; if (isMultiStateRegion) { const itemToCheck = state ? state : countyOrState; - location = multiRegions.find((region) => region.States.includes(itemToCheck))!.Region; + const regData = multiRegions.find((region) => region.States.includes(itemToCheck)); + location = regData?.Region ? regData.Region : countyOrState; } const params: IGetAttrDataParams = {attribute, geographicLevel: geoLevel, location, year, cropUnits}; if (geoLevel === "County") { @@ -199,12 +226,13 @@ const getAttrData = async (params: IGetAttrDataParams) => { reqParams.cropCategory = cropUnits as keyof ICropDataItem; } const req = createRequest(reqParams); - const res = await fetchData(req); + const res = await fetchDataWithRetry(req); const values: any = {}; if (res) { const {data} = res; data.map((dataItem: any) => { - return values[dataItem.short_desc] = dataItem.Value; + const codapColumnName = attrToCODAPColumnName[dataItem.short_desc].attributeNameInCodapTable; + return values[codapColumnName] = dataItem.Value; }); } else { console.log("error"); diff --git a/src/scripts/pluginHelper.js b/src/scripts/pluginHelper.js deleted file mode 100644 index fab10ec..0000000 --- a/src/scripts/pluginHelper.js +++ /dev/null @@ -1,162 +0,0 @@ -/** - * Created by tim on 1/19/17. - - - ========================================================================== - pluginHelper.js in gamePrototypes. - - Author: Tim Erickson - - Copyright (c) 2016 by The Concord Consortium, Inc. All rights reserved. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - ========================================================================== - - - */ - -var pluginHelper = { - - - /** - * Create a new data set (data context) using the input object - * @param iDataSetDescription the object that describes the data set. See the API documentation. - * @returns {Promise} which, when resolved, means that the data set exists - */ - initDataSet: function (iDataSetDescription) { - return new Promise( function( resolve, reject ) { - var tDataContextResourceString = 'dataContext[' + iDataSetDescription.name + ']'; - var tMessage = { action: 'get', resource: tDataContextResourceString }; - - // if the data set already exists, we will not ask CODAP to create one. So we check... - var tAlreadyExistsPromise = codapInterface.sendRequest(tMessage); - - tAlreadyExistsPromise.then( - // iValue is the result of the resolved "get dataContext" call - function( iValue ) { - if (iValue.success) { - console.log("dataContext[" + iDataSetDescription.name + "] already exists"); - resolve( iValue ); - } else { - // the data set did not exist. (Since get dataContext returned success = false) - console.log("Creating dataContext[" + iDataSetDescription.name + "]" ); - tMessage = { - action: 'create', - resource: 'dataContext', - values: iDataSetDescription - }; - codapInterface.sendRequest(tMessage).then( - // iValue is the result of the resolved "create dataContext" call. - function( iValue ) { - resolve( iValue ); - } - ); - } - } - ).catch (function (msg) { - console.log('warning in pluginHelper.initDataSet: ' + msg); - reject( msg ); - }); - }); - }, - - /** - * Create new data items (broader than cases; see the documentation for the API) - * Notes: (1) this refers only to the data context, not to any collections. Right? Has to. - * (2) notice how the values array does not have a "values" key inside it as with createCases. - * - * @param iValuesArray the array (or not) of objects, each of which will be an item. The keys are attribute names. - * @param iDataContextName the name of the data set (or "data context"). - */ - createItems : function(iValuesArray, iDataContextName, iCallback) { - return new Promise( function(resolve, reject) { - iValuesArray = pluginHelper.arrayify( iValuesArray ); - - var tResourceString = iDataContextName ? "dataContext[" + iDataContextName + "].item" : "item"; - - var tMessage = { - action : 'create', - resource : tResourceString, - values : iValuesArray - }; - - var tCreateItemsPromise = codapInterface.sendRequest(tMessage); - resolve( tCreateItemsPromise ); - }) - }, - - createCases : function(iValues, iCollection, iDataContext, iCallback) { - iValues = pluginHelper.arrayify( iValues ); - console.log("DO NOT CALL pluginHelper.createCases YET!!"); - }, - - /** - * - * @param IDs array of case IDs to be selected - * @param iDataContextName name of the data context in which these things live. OK if absent. - * @returns {Promise} - */ - selectCasesByIDs: function (IDs, iDataContextName) { - return new Promise( function( resolve, reject ) { - IDs = pluginHelper.arrayify( IDs ); - - var tResourceString = "selectionList"; - - if (typeof iDataContextName !== 'undefined') { - tResourceString = 'dataContext[' + iDataContextName + '].' + tResourceString; - } - - var tMessage = { - action: 'create', - resource: tResourceString, - values: IDs - }; - - var tSelectCasesPromise = codapInterface.sendRequest(tMessage); - resolve( tSelectCasesPromise ); - }) - }, - - getCaseValuesByCaseID: function (iCaseID, iDataContext) { - return new Promise(function (resolve, reject) { - var tMessage = { - action: 'get', - resource: "dataContext[" - + iDataContext + "].caseByID[" - + iCaseID + "]" - }; - - codapInterface.sendRequest(tMessage).then( - function (iResult) { - if (iResult.success) { - var tCaseValues = iResult.values.case.values; - resolve(tCaseValues); - } - } - ) - }) - }, - - /** - * Change the input to an array if it is not one! - * @param iValuesArray the thing which might be an array - * @returns {*} if it was not an array, a single-item array with the thing. Otherwise, the array. - */ - arrayify : function( iValuesArray ) { - if (iValuesArray && !Array.isArray(iValuesArray)) { - iValuesArray = [iValuesArray]; - } - return iValuesArray; - } - -} \ No newline at end of file diff --git a/src/scripts/utils.ts b/src/scripts/utils.ts index d8296e6..9450ea4 100644 --- a/src/scripts/utils.ts +++ b/src/scripts/utils.ts @@ -1,4 +1,4 @@ -import { queryData } from "../constants/query-headers"; +import { queryData } from "../constants/queryHeaders"; export const flatten = (arr: any[]): any[] => { return arr.reduce((acc: any[], val: any) => @@ -7,4 +7,4 @@ export const flatten = (arr: any[]): any[] => { export const getQueryParams = (attribute: string) => { return queryData.find((d) => d.plugInAttribute === attribute); -} \ No newline at end of file +};