Skip to content

Commit

Permalink
sidebar enhancements
Browse files Browse the repository at this point in the history
  • Loading branch information
nofurtherinformation committed Jul 2, 2024
1 parent d1376a2 commit 79e5b9c
Show file tree
Hide file tree
Showing 10 changed files with 266 additions and 159 deletions.
144 changes: 99 additions & 45 deletions components/MapInfoSection/MapInfoSection.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,64 @@
import { LeadSectionsRenderer } from "components/MapTooltip/MapTooltipSections"
import PieChart from "components/PieChart/PieChart"
import TimeseriesChart from "components/TimeseriesChart"
import React, { useEffect, useState } from "react"
import { idColumn } from "utils/data/config"
import { cleanRaceData } from "utils/data/cleaning/cleanRaceData"
import { idColumn, parentCompanyHighlightConfig } from "utils/data/config"
import { dataTableName } from "utils/data/service/service"
import { percentFormatter } from "utils/display/formatValue"
import { globals } from "utils/state/globals"
import { setClickInfo } from "utils/state/map"
import { useAppDispatch, useAppSelector } from "utils/state/store"
const Table: React.FC<{data: Record<string, any>}> = ({ data }) => {

const Table: React.FC<{ data: Record<string, any>; headers: string[] }> = ({ data, headers }) => {
if (!data || Object.keys(data).length === 0) {
return <p>No data available</p>;
return <p>No data available</p>
}

const entries = Object.entries(data);
const entries = Object.entries(data)

return (
<div className="flex justify-center px-0 py-4">
<div className="flex justify-center px-0 py-1 text-xs">
<table className="min-w-full max-w-4xl divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th
scope="col"
className="px-2 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
Property
</th>
<th
scope="col"
className="px-2 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
Value
</th>
{headers.map((header, i) => (
<th key={i} scope="col" className="px-2 py-1 text-left text-xs font-medium text-gray-500">
{header}{" "}
</th>
))}
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
<tbody className="divide-y divide-gray-200 bg-white">
{entries.map(([key, value], index) => (
<tr key={key} className={index % 2 === 0 ? 'bg-gray-50' : 'bg-white'}>
<td className="px-2 max-w-32 overflow-x-auto py-4 whitespace-nowrap text-sm font-medium text-gray-900 border-r-2 border-black/50">{key}</td>
<td className="px-2 max-w-32 overflow-x-auto py-4 whitespace-nowrap text-sm text-gray-500">{value}</td>
<tr key={key} className={index % 2 === 0 ? "bg-gray-50" : "bg-white"}>
<td className="overflow-x-auto whitespace-nowrap px-2 py-1 font-medium text-gray-900">{key}</td>
<td className="overflow-x-auto whitespace-nowrap px-2 py-1 text-gray-500">{value}</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
)
}

const formatParentCompanyData = (data: any) => {
const temp: any[] = []
Object.entries(parentCompanyHighlightConfig).forEach(([title, config]) => {
const value = data[config.column.slice(1, -1)]
if (value) {
temp.push({
label: title,
value: value,
})
}
})
const outputDict: Record<string, string> = {}
temp
.sort((a, b) => b.value - a.value)
.forEach((item) => (outputDict[item.label] = percentFormatter.format(item.value)))
return outputDict
}

export const MapInfoSection: React.FC = () => {
const dispatch = useAppDispatch()
Expand All @@ -54,38 +70,76 @@ export const MapInfoSection: React.FC = () => {
const getData = async () => {
if (clicked?.id && data?.id !== clicked.id) {
const res = await globals.ds.runQuery(`SELECT * FROM ${dataTableName} WHERE ${idColumn} = ${clicked.id}`)
const tooltipData = globals.ds.formatTooltipData(res[0])
const parentCompanyData = formatParentCompanyData(res[0])
const race = cleanRaceData(res[0])
console.log("parentCompanyData", parentCompanyData)
setData({
id: clicked.id,
data: JSON.parse(globals.ds.stringifyJsonWithBigInts(res[0])),
tooltipData,
parentCompanyData,
race,
})
}
}
getData()
},[clicked?.id])
}, [clicked?.id])

if (!clicked?.id) {
return null
}

return <div className="relative w-96 border-b-2 border-r-2 border-neutral-500 p-4 py-8">
<button onClick={close} className="absolute top-0 right-0 p-2">
&times;
</button>
<h3 className="text-2xl pb-4">{data?.data?.NAME || clicked.id}</h3>
<div className="flex flex-row w-full justify-between items-center">
<p>Open Report:</p>
<a href={`/tract/${clicked.id}`} className="border-b-2 border-black hover:text-primary-500 hover:border-primary-500 transition-colors">
Neighborhood
</a>
<a href={`/county/${clicked.id.slice(0,5)}`} className="border-b-2 border-black hover:text-primary-500 hover:border-primary-500 transition-colors">
County
</a>
<a href={`/state/${clicked.id.slice(0,2)}`} className="border-b-2 border-black hover:text-primary-500 hover:border-primary-500 transition-colors">
State
</a>
</div>
<Table data={data?.data} />
const formatted = data?.tooltipData || []
const leadSections = formatted?.filter((section: any) => section.category === "lead")

{/* <TimeseriesChart id={clicked.id} placeName={clicked.id} /> */}
</div>
return (
<div className="relative w-96 border-b-2 border-r-2 border-neutral-500 p-4 py-8">
<button onClick={close} className="absolute right-0 top-0 p-2">
&times;
</button>
<h3 className="text-2xl">{data?.data?.NAME || clicked.id}</h3>
<div className="m-x-auto my-2 flex w-full flex-row items-center justify-between gap-4 rounded-xl border-2 border-neutral-200 p-2 text-xs">
<p>Open Report:</p>
<a
href={`/tract/${clicked.id}`}
className="border-b-2 border-black transition-colors hover:border-primary-500 hover:text-primary-500"
>
Neighborhood
</a>
<a
href={`/county/${clicked.id.slice(0, 5)}`}
className="border-b-2 border-black transition-colors hover:border-primary-500 hover:text-primary-500"
>
County
</a>
<a
href={`/state/${clicked.id.slice(0, 2)}`}
className="border-b-2 border-black transition-colors hover:border-primary-500 hover:text-primary-500"
>
State
</a>
</div>
<LeadSectionsRenderer sections={leadSections} />
{!!(data?.parentCompanyData && Object.keys(data?.parentCompanyData).length > 0) && (
<>
<div className="w-full my-4 border-b-2 border-neutral-500"/>
<p className="text-bold pt-4 text-xs font-bold">Major company market dominance</p>
<Table data={data?.parentCompanyData || []} headers={["Parent Company", "Market Share"]} />
</>
)}
{!!data?.race && (
<>
<div className="w-full my-4 border-b-2 border-neutral-500"/>
<p className="text-bold pt-4 text-xs font-bold">Demographic Profile</p>
<div className="relative h-128 w-full">
<PieChart layout="vertical" data={data.race} dataKey="value" labelKey="raceEthnicity" />
</div>
</>
)}
{/* <Table data={data?.data} /> */}

{/* <TimeseriesChart id={clicked.id} placeName={clicked.id} /> */}
</div>
)
}
99 changes: 31 additions & 68 deletions components/MapTooltip/MapTooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,55 +1,30 @@
import { globals } from "utils/state/globals"
import { useAppSelector } from "utils/state/store"
import { MapState, TooltipData } from "utils/state/types"
import { MapState } from "utils/state/types"
import { MapTooltipProps } from "./types"
import './Spinner.css';
import React from "react";
import { getColorScale } from "components/PercentileLineChart";
import "./Spinner.css"
import React from "react"
import { adjustTooltipToMousePosition } from "./utils"
import { TooltipSectionsRenderer } from "./MapTooltipSections"
import { columnsDict } from "utils/data/config"

const TooltipDot: React.FC<{value:number, inverted?:boolean}> = ({value, inverted}) => {
const colorScale = getColorScale(inverted)
const clampedValue = Math.min(100, Math.max(0, value))
const color = colorScale(clampedValue)
return (
<div className="h-4 w-4 rounded-full" style={{backgroundColor: color}}></div>
)
}

export const TooltipSectionsRenderer: React.FC<{ sections: any[] }> = ({ sections }) => {
const leadSections = sections.filter((section) => section.lead)
const nonLeadSections = sections.filter((section) => !section.lead)
return (
<>
{/* flex lead sections in each row label as small text */}
<div className="flex flex-row justify-between align-middle gap-2">
{leadSections.map((section, i) => (
<div key={i} className="text-sm flex flex-col w-24">
{/* vert middle */}
<div className="flex flex-row items-center gap-2">
<p className="text-3xl">{(section.formatter ?
section.formatter(section.value) : section.value) || '--'}</p>
{section.value !== undefined && <TooltipDot value={section.value} inverted={section.inverted} />}
</div>
<b className="text-xs">{section.label}</b>
</div>
))}
</div>
{/* flex non lead sections in each row */}
<p className="text-xs pt-4">
<i>Click for more info</i>
</p>
</>
)
}
export const MapTooltipInner: React.FC<
MapTooltipProps & { tooltipStatus: MapState["tooltipStatus"]; tooltip: MapState["tooltip"] }
> = ({ simpleMap, tooltipStatus, tooltip }) => {
const { id, data: tooltipData } = tooltip || { id: "", data: [] }
const data = globals?.ds?.tooltipResults?.[id]

const _rawData = globals?.ds?._rawData?.[id]
const currentColumn = useAppSelector((state) => {
const colName = state.map.currentColumn
const colConfig = columnsDict[colName]
return {
colName,
colConfig,
}
})
if (!data) {
return (
<svg className="spinner" width="4rem" height="4rem" viewBox="0 0 50 50">
<svg className="spinner" width="2rem" height="2rem" viewBox="0 0 50 50">
<circle className="path" cx="25" cy="25" r="20" fill="none" strokeWidth="4" />
</svg>
)
Expand All @@ -65,7 +40,19 @@ export const MapTooltipInner: React.FC<
return (
<p className="pb-2">
<b>{data.name ? data.name : `Tract# ${id}`}</b>
<TooltipSectionsRenderer sections={data.sections} />
<TooltipSectionsRenderer sections={data.sections}>
{!currentColumn.colConfig.bivariate && (
<>
<p className="pt-2 mt-2 text-xs border-t-2 border-neutral-500">
<span className="font-bold pr-2">
{currentColumn.colName}:
</span>
{/* @ts-ignore */}
{_rawData[currentColumn.colConfig.column]}
</p>
</>
)}
</TooltipSectionsRenderer>
</p>
)
}
Expand All @@ -87,37 +74,13 @@ export const MapTooltipInner: React.FC<
)
}

export const adjustTooltipToMousePosition = (x:number, y:number, tooltipWidth: number): {left?:number, top?:number, right?:number, bottom?:number} => {
const screenWidth = window.innerWidth
const screenHeight = window.innerHeight
// if in bottom right quadrant of screen
// move tooltip to the left
const quadrantX = x > screenWidth / 2 ? "right" : "left"
const quadrantY = y > screenHeight / 2 ? "bottom" : "top"
let cssProps: {left?:number, top?:number, right?:number, bottom?:number} = {

}
if (quadrantX === "right") {
cssProps['right'] = screenWidth - x + 10
} else {
cssProps = {left: x + 10}
}
if (quadrantY === "bottom") {
cssProps['bottom'] = screenHeight - y + 10
} else {
cssProps['top'] = y + 10
}

return cssProps
}

export const MapTooltip: React.FC<MapTooltipProps> = ({ simpleMap }) => {
const tooltipRef = React.useRef<HTMLDivElement>(null)
const tooltip = useAppSelector((state) => state.map.tooltip)
const tooltipStatus = useAppSelector((state) => state.map.tooltipStatus)
const tooltipWidth = tooltipRef.current?.clientWidth || 0
const { x, y } = tooltip || {}
const cssProps = adjustTooltipToMousePosition(x, y, tooltipWidth)
const cssProps = adjustTooltipToMousePosition(x || 0, y || 0, tooltipWidth)
if (!x || !y) {
return null
}
Expand All @@ -127,7 +90,7 @@ export const MapTooltip: React.FC<MapTooltipProps> = ({ simpleMap }) => {
ref={tooltipRef}
className="padding-4 pointer-events-none fixed z-[1001] rounded-md border border-gray-200 bg-white/90 p-2 shadow-md"
style={{
...cssProps
...cssProps,
}}
>
<MapTooltipInner simpleMap={simpleMap} tooltipStatus={tooltipStatus} tooltip={tooltip} />
Expand Down
44 changes: 44 additions & 0 deletions components/MapTooltip/MapTooltipSections.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@

import { getColorScale } from "components/PercentileLineChart";
const TooltipDot: React.FC<{value:number, inverted?:boolean}> = ({value, inverted}) => {
const colorScale = getColorScale(inverted)
const clampedValue = Math.min(100, Math.max(0, value))
const color = colorScale(clampedValue)
return (
<div className="h-4 w-4 rounded-full" style={{backgroundColor: color}}></div>
)
}

export const LeadSectionsRenderer: React.FC<{ sections: any[] }> = ({ sections }) => {
return (
<div className="flex flex-row justify-between align-middle gap-2">
{sections.map((section, i) => (
<div key={i} className="text-sm flex flex-col w-24">
<div className="flex flex-row items-center gap-2">
<p className="text-3xl">{(section.formatter ?
section.formatter(section.value) : section.value) || '--'}</p>
{section.value !== undefined && <TooltipDot value={section.value} inverted={section.inverted} />}
</div>
<b className="text-xs">{section.label}</b>
</div>
))}
</div>
)
}


export const TooltipSectionsRenderer: React.FC<{ sections: any[], children?:React.ReactNode }> = ({ sections, children }) => {
const leadSections = sections.filter((section) => section.category === 'lead')
const nonLeadSections = sections.filter((section) => section.category !== 'lead')
return (
<>
{/* flex lead sections in each row label as small text */}
<LeadSectionsRenderer sections={leadSections} />
{/* flex non lead sections in each row */}
{children}
<p className="text-xs pt-4">
<i>Click for more info</i>
</p>
</>
)
}
23 changes: 23 additions & 0 deletions components/MapTooltip/utils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export const adjustTooltipToMousePosition = (x:number, y:number, tooltipWidth: number): {left?:number, top?:number, right?:number, bottom?:number} => {
const screenWidth = window.innerWidth
const screenHeight = window.innerHeight
// if in bottom right quadrant of screen
// move tooltip to the left
const quadrantX = x > screenWidth / 2 ? "right" : "left"
const quadrantY = y > screenHeight / 2 ? "bottom" : "top"
let cssProps: {left?:number, top?:number, right?:number, bottom?:number} = {

}
if (quadrantX === "right") {
cssProps['right'] = screenWidth - x + 10
} else {
cssProps = {left: x + 10}
}
if (quadrantY === "bottom") {
cssProps['bottom'] = screenHeight - y + 10
} else {
cssProps['top'] = y + 10
}

return cssProps
}
Loading

0 comments on commit 79e5b9c

Please sign in to comment.