Skip to content

Commit

Permalink
Legend filter
Browse files Browse the repository at this point in the history
  • Loading branch information
nofurtherinformation committed May 3, 2024
1 parent cd558c4 commit 8b19209
Show file tree
Hide file tree
Showing 11 changed files with 135 additions and 35 deletions.
53 changes: 37 additions & 16 deletions components/Legend/Legend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@
import LegendBreakText from "components/LegendBreakText"
import { LegendProps } from "./types"
import { useEffect, useState } from "react"
import { useAppDispatch, useAppSelector } from "utils/state/store"
import { upcertColorFilter } from "utils/state/map"
import { deepCompare2d1d, deepCompareArray } from "utils/data/compareArrayElements"

export const Legend: React.FC<LegendProps> = ({ column, isBivariate, colors, breaks }) => {
const colorFilter = useAppSelector((state) => state.map.colorFilter)
const dispatch = useAppDispatch()

const [legendtooltip, setLegendTooltip] = useState<number[]>([])
const [legendClicked, setLegendClicked] = useState(false)
const [clearLegendTimeout, setClearLegendTimeout] = useState<null | ReturnType<typeof setTimeout>>(null)
Expand All @@ -14,7 +20,9 @@ export const Legend: React.FC<LegendProps> = ({ column, isBivariate, colors, bre
} else {
clearLegendTimeout && clearTimeout(clearLegendTimeout)
}
return () => {clearLegendTimeout && clearTimeout(clearLegendTimeout)}
return () => {
clearLegendTimeout && clearTimeout(clearLegendTimeout)
}
}, [legendtooltip])

if (isBivariate && Array.isArray(colors?.[0]?.[0])) {
Expand All @@ -32,20 +40,24 @@ export const Legend: React.FC<LegendProps> = ({ column, isBivariate, colors, bre
>
{colors.map((crow: number[][], i) => (
<div key={i} className="flex flex-col">
{crow.map((c: number[], j: number) => (
<button
onClick={() => setLegendTooltip([i, j])}
className={`h-8 w-8 ${
legendtooltip[0] === i && legendtooltip[1] === j
? "opacity-100 shadow-sm"
: legendtooltip?.length > 0
? "opacity-10"
: "opacity-100"
}`}
style={{ background: `rgb(${c.join(",")})` }}
key={`${i}-${j}`}
></button>
))}
{crow.map((c: number[], j: number) => {
return (
<button
onClick={() => dispatch(upcertColorFilter(c))}
onMouseEnter={() => setLegendTooltip([i, j])}
onMouseLeave={() => setLegendTooltip([])}
className={`h-8 w-8 ${
legendtooltip[0] === i && legendtooltip[1] === j
? "opacity-100 shadow-sm"
: legendtooltip?.length > 0 || (colorFilter?.length && !deepCompare2d1d(colorFilter, c))
? "opacity-10"
: "opacity-100"
}`}
style={{ background: `rgb(${c.join(",")})` }}
key={`${i}-${j}`}
></button>
)
})}
</div>
))}
<span className="absolute bottom-[-1rem] left-[-0.375rem] font-black">&darr;</span>
Expand Down Expand Up @@ -83,7 +95,16 @@ export const Legend: React.FC<LegendProps> = ({ column, isBivariate, colors, bre
<div className="ColorLegend">
<h3>{column.name}</h3>
{!!(colors?.length && breaks?.length) &&
colors.map((_, i) => <LegendBreakText key={i} colors={colors as any} breaks={breaks as any} index={i} />)}
colors.map((_, i) => (
<LegendBreakText
key={i}
colors={colors as any}
breaks={breaks as any}
index={i}
colorFilter={colorFilter}
onClick={(color, _i) => dispatch(upcertColorFilter(color))}
/>
))}
<p style={{ maxWidth: "35ch", fontSize: "0.75rem" }}>{/* <i>{currentDataSpec?.attribution}</i> */}</p>
</div>
)
Expand Down
13 changes: 10 additions & 3 deletions components/LegendBreakText/LegendBreakText.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { formatNumber } from "utils/display/formatNumber"
import { LegendBreakTextProps } from "./types"
import { deepCompare2d1d } from "utils/data/compareArrayElements"

export const LegendBreakText: React.FC<LegendBreakTextProps> = ({ breaks, index, colors }) => {
export const LegendBreakText: React.FC<LegendBreakTextProps> = ({ breaks, index, colors, onClick, colorFilter }) => {
let text = ""
if (index === 0) {
text = `< ${formatNumber(breaks[0] as number)}`
Expand All @@ -10,10 +11,16 @@ export const LegendBreakText: React.FC<LegendBreakTextProps> = ({ breaks, index,
} else {
text = `${formatNumber(breaks[index - 1] as number)} - ${formatNumber(breaks[index] as number)}`
}
const color = colors[index]!
const isOpaque = !colorFilter?.length || deepCompare2d1d(colorFilter, color)

return (
<div className="ColorRow">
{/* @ts-ignore */}
<span style={{ background: `rgb(${colors[index].join(",")})` }}></span>
<button
className={`inline-block w-6 h-6 mr-2 ${isOpaque ? 'opactiy-100' : 'opacity-10'}`}
onClick={() => onClick && onClick(color, index)}
style={{ background: `rgb(${color.join(",")})` }}
></button>
<p>{text}</p>
</div>
)
Expand Down
10 changes: 9 additions & 1 deletion components/LegendBreakText/types.ts
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
export type LegendBreakTextProps = { breaks: number[]; index: number; colors: number[][] }
import { MapState } from "utils/state/map"

export type LegendBreakTextProps = {
breaks: number[]
index: number
colors: number[][]
onClick?: (color: number[], index?: number) => void
colorFilter?: MapState["colorFilter"]
}
18 changes: 13 additions & 5 deletions components/Map/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { store, useAppDispatch, useAppSelector } from "utils/state/store"
import { zeroPopTracts } from "utils/zeroPopTracts"
import Legend from "components/Legend"
import MapTooltip from "components/MapTooltip"
import { deepCompare2d1d } from "utils/data/compareArrayElements"

export type MapProps = {
initialFilter?: string
Expand Down Expand Up @@ -85,7 +86,7 @@ export const Map: React.FC<MapProps> = ({ initialFilter }) => {
}
}, [])

const { isReady, colorFunc, colors, ds, breaks, currentColumnSpec, currentColumnGroup, filter, isBivariate } =
const { isReady, colorFunc, colors, ds, breaks, currentColumnSpec, colorFilter, currentColumnGroup, filter, isBivariate } =
useDataService()
const availableColumns = columnGroups[currentColumnGroup]?.columns || []
const getElementColor = (element: GeoJSON.Feature<GeoJSON.Polygon, GeoJSON.GeoJsonProperties>) => {
Expand All @@ -100,9 +101,16 @@ export const Map: React.FC<MapProps> = ({ initialFilter }) => {
return [120, 120, 120, 120]
}
// @ts-ignore
return colorFunc(id)
const color = colorFunc(id)
if (colorFilter && colorFilter.length) {
const isInFilter = deepCompare2d1d(colorFilter, color)
if (!isInFilter) {
return [color[0], color[1], color[2], 20]
}
}
return color
}
// console.log("UPDATE", isReady, currentColumnSpec.name, colorFunc)

const layers = [
new GeoJsonLayer({
// @ts-ignore
Expand Down Expand Up @@ -130,7 +138,7 @@ export const Map: React.FC<MapProps> = ({ initialFilter }) => {
getFillColor: getElementColor,
autoHighlight: true,
updateTriggers: {
getFillColor: [isReady, currentColumnSpec.name, colorFunc],
getFillColor: [isReady, currentColumnSpec.name, colorFunc, colorFilter],
},
onClick: (info, event) => {
if (event?.srcEvent?.altKey) {
Expand Down Expand Up @@ -182,7 +190,7 @@ export const Map: React.FC<MapProps> = ({ initialFilter }) => {
}
const res = await fetch(`/api/stores/${clickedGeo.geoid}`)
const data = (await res.json()) as any
console.log("data", data)

setClickedGeo((prev: any) => ({
geoid: prev.geoid,
geometry: data.geometry,
Expand Down
2 changes: 1 addition & 1 deletion components/MapTooltip/MapTooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const MapTooltip: React.FC<MapTooltipProps> = ({ dataService }) => {
if (!x || !y) {
return null
}
console.log(data)

return (
<div
className="padding-4 pointer-events-none fixed z-[1001] rounded-md border border-gray-200 bg-white/90 p-2 shadow-md"
Expand Down
14 changes: 14 additions & 0 deletions utils/data/compareArrayElements.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const deepCompareArray = (arr1: unknown[] | undefined, arr2: unknown[] | undefined) => {
if (!arr1 || !arr2 || arr1.length !== arr2.length) return false
for (let i=0;i<arr1.length;i++){
if (arr1[i] !== arr2[i]) return false
}
return true
}

export const deepCompare2d1d = (arr1: unknown[][], arr2: unknown[]) => {
for (let i=0;i<arr1.length;i++){
if (deepCompareArray(arr1[i], arr2)) return true
}
return false
}
16 changes: 13 additions & 3 deletions utils/data/service/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getDuckDb, runQuery } from "utils/duckdb"
import * as d3 from "d3"
import tinycolor from "tinycolor2"
import { BivariateColorParamteres, MonovariateColorParamteres, d3Bivariate } from "./types"
import { deepCompare2d1d } from "../compareArrayElements"

// 2000 to 2021
const fullYears = new Array(22).fill(0).map((_, i) => 2000 + i)
Expand Down Expand Up @@ -193,7 +194,7 @@ export class DataService {
return query
}

async getBivariateColorValues({ idColumn, colorScheme, column, table, filter, reversed }: BivariateColorParamteres) {
async getBivariateColorValues({ idColumn, colorScheme, column, table, filter, reversed, colorFilter }: BivariateColorParamteres) {
if (idColumn.length !== 2 || column.length !== 2 || table.length !== 2) {
console.error(
`Invalid Bivariate Color Values request: ${idColumn}, ${colorScheme}, ${column}, ${table}, ${filter}`
Expand All @@ -217,6 +218,17 @@ export class DataService {
// ]
colors = legendColors.map((row: any) => row.slice().reverse())
}
if (colorFilter) {
for (let i=0;i<colors.length; i++){
for (let j=0;j<colors[i].length; j++){
const color = colors[i][j]
const isInFilter = deepCompare2d1d(colorFilter, color)
if (!isInFilter) {
colors[i][j] = [...color, 120]
}
}
}
}
// console.log("COLORS", colors)
// console.log("LEGEND COLORS", legendColors)
// console.log("REVERSED", reversed)
Expand Down Expand Up @@ -377,8 +389,6 @@ export class DataService {
data.push(sectionData)
}
this.tooltipResults[id] = data
console.log('id', data)

}

async getTimeseries(id: string) {
Expand Down
2 changes: 2 additions & 0 deletions utils/data/service/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export type BivariateColorParamteres = {bivariate: true}&{
table: string[],
reversed?: boolean[],
filter?: string
colorFilter?: number[][]
}

export type MonovariateColorParamteres = {bivariate: false}&{
Expand All @@ -39,4 +40,5 @@ export type MonovariateColorParamteres = {bivariate: false}&{
reversed?: boolean,
filter?: string,
range?: "continuous" | "categorical"
colorFilter?: number[][]
}
2 changes: 2 additions & 0 deletions utils/hooks/useDataService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const useDataService = () => {
}
const currentColumn = useAppSelector((state) => state.map.currentColumn)
const currentColumnGroup = useAppSelector((state) => state.map.currentColumnGroup)
const colorFilter = useAppSelector((state) => state.map.colorFilter)
// @ts-ignore
const currentColumnSpec = columnsDict[currentColumn]!
if (!currentColumnSpec) {
Expand Down Expand Up @@ -70,6 +71,7 @@ export const useDataService = () => {
isReady,
currentColumn,
colorFunc,
colorFilter,
breaks,
colors,
currentColumnSpec,
Expand Down
9 changes: 5 additions & 4 deletions utils/hooks/useMapColor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type ColorHook = (props: {
nBins?: number
reversed?: boolean | boolean[]
filter?: string
colorFilter?: number[][]
breaksSchema?:
| {
type: "manual"
Expand Down Expand Up @@ -40,25 +41,25 @@ export const useMapColor: ColorHook = (args,isReady) => {
f: args.filter,
b: args.nBins,
r: args.reversed,
cf: args.colorFilter,
isReady
})
console.log("UPDATE TRIGGER", updateTrigger)

useEffect(() => {
const main = async () => {
if (!isReady) {
return
}
console.log('args', args)
const colorParams = args.bivariate
? (args as BivariateColorParamteres)
: (args as unknown as MonovariateColorParamteres)
const colorResults = await ds.getColorValues(colorParams)

const colorResults = await ds.getColorValues(colorParams)
if (!colorResults) {
return
}
const { colorMap, breaks, colors } = colorResults

const colorFunc = (_id: string | number) => {
const id = _id.toString()
if (args.filter?.length && id.startsWith(args.filter) === false) {
Expand Down
31 changes: 29 additions & 2 deletions utils/state/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface MapState {
currentColumn: string | number
currentColumnGroup: keyof typeof columnGroups
idFilter?: string
colorFilter?: number[][]
tooltip: {
x: number
y: number
Expand Down Expand Up @@ -64,24 +65,50 @@ export const mapSlice = createSlice({
return
}
state.currentColumnGroup = action.payload
state.colorFilter = undefined
if (!columnGroup?.columns.includes(`${state.currentColumn}`) && columnGroup.columns.length > 0) {
state.currentColumn = columnGroup.columns[0]!
}
},
setCurrentColumn: (state, action: PayloadAction<string | number>) => {
state.currentColumn = action.payload
state.colorFilter = undefined
},
setTooltipInfo: (state, action: PayloadAction<{ x: number; y: number; id: string } | null>) => {
state.tooltip = action.payload
},
setCurrentFilter: (state, action: PayloadAction<string>) => {
state.idFilter = action.payload
},
upcertColorFilter: (state, action: PayloadAction<number[]>) => {
if (!state.colorFilter) {
state.colorFilter = [action.payload]
return
} else {
const idx = state.colorFilter.findIndex(
(f) => f[0] === action.payload[0] && f[1] === action.payload[1] && f[2] === action.payload[2]
)
if (idx === -1) {
state.colorFilter.push(action.payload)
} else {
state.colorFilter.splice(idx, 1)
}
}
},
},
})

// Action creators are generated for each case reducer function
export const { setYear, setBreaks, setColors, setCurrentColumn, setTooltipInfo, setCurrentData, setCurrentFilter, setCurrentColumnGroup} =
mapSlice.actions
export const {
setYear,
setBreaks,
setColors,
setCurrentColumn,
setTooltipInfo,
setCurrentData,
setCurrentFilter,
setCurrentColumnGroup,
upcertColorFilter
} = mapSlice.actions

export default mapSlice.reducer

0 comments on commit 8b19209

Please sign in to comment.