import React, { useState } from 'react'; import { initializeBlock, useBase, useRecords, useGlobalConfig, useSettingsButton, useViewport, TablePickerSynced, FieldPickerSynced, FormField, Box, Switch, colorUtils, } from '@airtable/blocks/ui'; import {FieldType} from '@airtable/blocks/models'; import USAMap from "react-usa-map"; import colormap from "colormap"; import SelectedState from "./selectedState"; import codeStateMap from "./stateCodes" const stateCodeMap = Object.fromEntries(Object.entries(codeStateMap).map(a => a.reverse())); let colors = colormap({ colormap: 'hot', nshades: 100, format: 'hex', alpha: 1 }) function USAMapBlock() { const [isShowingSettings, setIsShowingSettings] = useState(false); const [selectedState, setSelectedState] = useState(null); useSettingsButton(function() { setIsShowingSettings(!isShowingSettings); }); const viewport = useViewport(); const base = useBase(); const globalConfig = useGlobalConfig(); const tableId = globalConfig.get('selectedTableId'); const stateFieldId = globalConfig.get('selectedStateFieldId'); const colorFieldId = globalConfig.get('selectedColorFieldId'); const table = base.getTableByIdIfExists(tableId); const stateField = table ? table.getFieldByIdIfExists(stateFieldId) : null; const colorField = table ? table.getFieldByIdIfExists(colorFieldId) : null; // if (table == null || stateField == null || colorField == null) { // setIsShowingSettings(true); // } const records = useRecords(stateField ? table : null); let mapData = null; if (stateField !== null && colorField !== null) { mapData = getMapData(records, stateField, colorField); } const mapHandler = (event) => { setSelectedState(event.target.dataset.name); }; // If settings is showing, draw settings only if (isShowingSettings) { return ( <Box padding={3} display="flex"> <FormField label="Table" description="Choose the table you want to your State data to come from." padding={1} marginBottom={0} > <TablePickerSynced globalConfigKey="selectedTableId" /> </FormField> <FormField label="State Field" description='The State field will select a state by either abbreviation ("NJ") or name ("New Jersey")' marginBottom={0} padding={1} > <FieldPickerSynced table={table} globalConfigKey="selectedStateFieldId" placeholder="Pick a 'state' field..." allowedTypes={[FieldType.SINGLE_LINE_TEXT, FieldType.SINGLE_SELECT, FieldType.MULTIPLE_RECORD_LINKS, FieldType.MULTIPLE_LOOKUP_VALUES]} /> </FormField> <FormField label="Color Field" marginBottom={0} description="Choose the state color using either a text field which describes the color name, or a single select." padding={1} > <FieldPickerSynced table={table} globalConfigKey="selectedColorFieldId" placeholder="Pick a 'color' field..." allowedTypes={[FieldType.SINGLE_LINE_TEXT, FieldType.SINGLE_SELECT, FieldType.MULTIPLE_LOOKUP_VALUES, FieldType.NUMBER]} /> {colorField != null && colorField.type === FieldType.NUMBER && <text>You have selected a numeric type; state colors will be normalized to the maximum value in your field.</text> } </FormField> </Box> ) } // otherwise draw the map. return ( <div> <Box border="default" backgroundColor="lightGray1" // padding={} > {/* TODO Allow selected state to show a column of data. */} {/* {selectedState ? <SelectedState selected={selectedState}/> : <div>Click to select a state</div> } */} <USAMap title="USA USA USA" width={viewport.size.width} height={viewport.size.height - 5} customize={mapData} onClick={mapHandler} /> </Box> </div> ) } initializeBlock(() => <USAMapBlock />); function getMapData(records, stateField, colorField) { // If the color is a numeric field, get the maximum value to normalize by. let maxColorValue = null; if (colorField.type === FieldType.NUMBER) { for (const record of records) { let v = record.getCellValue(colorField.name); if (maxColorValue == null) { maxColorValue = v; } else { maxColorValue = Math.max(maxColorValue, v); } } } const stateColorMap = new Map(); for (const record of records) { // First handle the state let stateText; const stateCell = record.getCellValue(stateField.name); if (stateField.type == FieldType.SINGLE_LINE_TEXT) { stateText = stateCell; } else if (stateField.type == FieldType.SINGLE_SELECT) { stateText = stateCell.name; } else if (stateField.type == FieldType.MULTIPLE_RECORD_LINKS && stateCell.length > 0 && stateCell[0].name) { stateText = stateCell[0].name; } else if (stateField.type == FieldType.MULTIPLE_LOOKUP_VALUES && stateCell.length > 0) { stateText = stateCell[0]; } else { stateText = null; } // Validate the state field // This uses a map of stateCode:stateName, and an inverse map of stateName:stateCode to allow the user to // enter either the code or the name. let stateCode; // If it was in a state abbreviation format if (stateText in codeStateMap) { stateCode = stateText; } // state name format else if (stateText in stateCodeMap) { stateCode = stateCodeMap[stateText]; } // anything else else { stateCode = null; } const colorValue = record.getCellValue(colorField.name); // console.log(`State: ${JSON.stringify(stateCell)}, color: ${colorValue}`); if (stateCode !== null && colorValue !== null) { let color = null; if (colorField.type == FieldType.SINGLE_SELECT) { color = colorUtils.getHexForColor(colorValue.color); } else if (colorField.type == FieldType.NUMBER && maxColorValue) { // Normalized from 0-1, and then scaled from 0-100 (99 really) let normalizedColorValueIndex = Math.floor((colorValue / maxColorValue) * 100); color = colors[normalizedColorValueIndex]; console.log(`color: ${colorValue} | max: ${maxColorValue} | color: ${color} | indez: ${normalizedColorValueIndex}`); } else { color = colorValue; } if (stateCode !== null && color !== null) { stateColorMap[stateCode] = {fill: color}; } } } return stateColorMap; }