Skip to content

Commit 61c500a

Browse files
tooltip / map cleanup
1 parent 67cb682 commit 61c500a

File tree

3 files changed

+105
-91
lines changed

3 files changed

+105
-91
lines changed

components/Map/Map.tsx

+40-44
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Provider, useSelector } from "react-redux"
55
import { MapboxOverlay, MapboxOverlayProps } from "@deck.gl/mapbox/typed"
66
import GlMap, { NavigationControl, useControl } from "react-map-gl"
77
import "mapbox-gl/dist/mapbox-gl.css"
8-
import React, { useMemo, useRef } from "react"
8+
import React, { useEffect, useMemo, useRef, useState } from "react"
99
import { MVTLayer } from "@deck.gl/geo-layers/typed"
1010
import DropdownMenuDemo from "components/Dropdown/Dropdown"
1111
import * as DropdownMenu from "@radix-ui/react-dropdown-menu"
@@ -40,27 +40,22 @@ const BreakText: React.FC<{ breaks: number[]; index: number; colors: number[][]
4040
const Tooltip: React.FC<{ dataService: DataService }> = ({ dataService }) => {
4141
const tooltip = useAppSelector((state) => state.map.tooltip)
4242
const { x, y, id } = tooltip || {}
43+
// @ts-ignore
44+
const data = dataService.tooltipResults[id]
45+
const [_updateTrigger, setUpdateTrigger] = useState<boolean>(true)
4346

44-
const data = useMemo(() => {
45-
if (!id) {
46-
return []
47-
}
48-
const output = config.map((d) => {
49-
const dataOutput = {
50-
header: d.name,
51-
} as any
52-
const data = dataService.data[d.filename]?.[id]
53-
if (data) {
54-
d.columns.forEach((c) => {
55-
dataOutput[c.name] = data[c.column]
56-
})
47+
useEffect(() => {
48+
const main = async () => {
49+
if (!id) {
50+
return
5751
}
58-
return dataOutput
59-
})
60-
return output
52+
const tooltipData = await dataService.getTooltipValues(id)
53+
setUpdateTrigger((v) => !v)
54+
}
55+
main()
6156
}, [id])
6257

63-
if (!x || !y || !id) {
58+
if (!x || !y) {
6459
return null
6560
}
6661

@@ -72,22 +67,26 @@ const Tooltip: React.FC<{ dataService: DataService }> = ({ dataService }) => {
7267
top: y + 10,
7368
}}
7469
>
75-
{data.map((d, i) => {
76-
const keys = Object.keys(d).filter((k) => k !== "header")
77-
// nice skeumorphic shadow
78-
return (
79-
<p className="pb-2" key={i}>
80-
<b>{d.header}</b>
81-
<ul>
82-
{keys.map((k,i) => (
83-
<li key={i}>
84-
{k}: {d[k]}
85-
</li>
86-
))}
87-
</ul>
88-
</p>
89-
)
90-
})}
70+
{/* @ts-ignore */}
71+
{data ? (
72+
data.map((d: any, i: number) => {
73+
const keys = Object.keys(d).filter((k) => k !== "header")
74+
return (
75+
<p className="pb-2" key={i}>
76+
<b>{d.header}</b>
77+
<ul>
78+
{keys.map((k, i) => (
79+
<li key={i}>
80+
{k}: {d[k]}
81+
</li>
82+
))}
83+
</ul>
84+
</p>
85+
)
86+
})
87+
) : (
88+
<p>Loading...</p>
89+
)}
9190
</div>
9291
)
9392
}
@@ -134,8 +133,12 @@ export const Map = () => {
134133
updateTriggers: {
135134
getFillColor: [isReady, currentColumnSpec?.column, currentDataSpec?.filename, colorFunc],
136135
},
136+
onClick: (info: any) => {
137+
console.log(info)
138+
},
137139
onHover: (info: any) => {
138-
if (info?.x && info?.y && info?.object) {
140+
const isFiltered = currentFilter && info.object?.properties?.GEOID?.startsWith(currentFilter) === false
141+
if (info?.x && info?.y && info?.object && !isFiltered) {
139142
dispatch(setTooltipInfo({ x: info.x, y: info.y, id: info.object?.properties?.GEOID }))
140143
} else {
141144
dispatch(setTooltipInfo(null))
@@ -157,7 +160,7 @@ export const Map = () => {
157160
const handleSetColumn = (col: string | number) => dispatch(setCurrentColumn(col))
158161
const handleChangeData = (data: string) => dispatch(setCurrentData(data))
159162
const handleSetFilter = (filter: string) => dispatch(setCurrentFilter(filter))
160-
163+
161164
return (
162165
<div style={{ width: "100vw", height: "100vh", position: "relative", top: 0, left: 0 }}>
163166
<div style={{ position: "absolute", bottom: "2rem", right: "1rem", zIndex: 1000 }}>
@@ -170,16 +173,9 @@ export const Map = () => {
170173
</p>
171174
</div>
172175
</div>
173-
{/* <button style={{
174-
position:'fixed',
175-
top: 0,
176-
left: 0,
177-
zIndex: 500,
178-
background:'red'
179-
}} onClick={testfn}>TEST FN</button> */}
180176
<div className="absolute left-4 top-4 z-50">
181177
<DropdownMenuDemo>
182-
<div className="p-4 max-w-[100vw]">
178+
<div className="max-w-[100vw] p-4">
183179
<p>Choose Data</p>
184180
<hr />
185181
{config.map((c, i) => (

utils/data/service.ts

+59-42
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export class DataService {
1616
db?: AsyncDuckDB
1717
baseURL: string = window.location.origin
1818
conn?: AsyncDuckDBConnection
19+
tooltipResults: any = {}
1920

2021
constructor(completeCallback?: (s: string) => void, config: DataConfig[] = defaultConfig) {
2122
this.config = config
@@ -79,27 +80,37 @@ export class DataService {
7980
}
8081
}
8182

82-
async runQuery(query: string) {
83+
async runQuery(query: string, freshConn?: boolean) {
8384
await this.initDb()
8485
try {
85-
return await runQuery({
86-
conn: this.conn!,
86+
const conn = freshConn ? await this.db!.connect() : this.conn!
87+
const r = await runQuery({
88+
conn,
8789
query,
8890
})
91+
if (freshConn) {
92+
await conn.close()
93+
}
94+
return r
8995
} catch (e) {
9096
console.error(e)
9197
return []
9298
}
9399
}
94-
async getQuantiles(column: string | number, table: string, n: number): Promise<Array<number>> {
100+
async getQuantiles(column: string | number, table: string, n: number, idCol: string, filter?: string): Promise<Array<number>> {
95101
// breakpoints to use for quantile breaks
96102
// eg. n=5 - 0.2, 0.4, 0.6, 0.8 - 4 breaks
97103
// eg. n=4 - 0.25, 0.5, 0.75 - 3 breaks
98104
const quantileFractions = Array.from({ length: n - 1 }, (_, i) => (i + 1) / n)
99-
const query = `SELECT
105+
let query = `SELECT
100106
${quantileFractions.map((f, i) => `approx_quantile("${column}", ${f}) as break${i}`)}
101-
FROM ${this.getFromQueryString(table)};
107+
FROM ${this.getFromQueryString(table)}
102108
`
109+
if (filter) {
110+
query += ` WHERE "${idCol}" LIKE '${filter}%';`
111+
} else {
112+
query += ";"
113+
}
103114
const result = await this.runQuery(query)
104115
if (!result || result.length === 0) {
105116
console.error(`No results for quantile query: ${query}`)
@@ -114,7 +125,8 @@ export class DataService {
114125
reversed: boolean,
115126
column: string | number,
116127
table: string,
117-
n: number
128+
n: number,
129+
filter?: string
118130
) {
119131
// @ts-ignore
120132
const d3Colors = d3[colorScheme]?.[n]
@@ -133,15 +145,21 @@ export class DataService {
133145
if (reversed) {
134146
rgbColors.reverse()
135147
}
136-
const quantiles = await this.getQuantiles(column, table, n)
137-
const query = `
148+
const quantiles = await this.getQuantiles(column, table, n, idColumn, filter)
149+
let query = `
138150
SELECT "${column}", "${idColumn}",
139151
CASE
140152
${quantiles.map((q, i) => `WHEN "${column}" < ${q} THEN [${rgbColors[i]}]`).join("\n")}
153+
WHEN "${column}" IS NULL THEN [120,120,120,0]
141154
ELSE [${rgbColors[rgbColors.length - 1]}]
142155
END as color
143-
FROM ${this.getFromQueryString(table)};
156+
FROM ${this.getFromQueryString(table)}
144157
`
158+
if (filter) {
159+
query += ` WHERE "${idColumn}" LIKE '${filter}%';`
160+
} else {
161+
query += ";"
162+
}
145163
// @ts-ignore
146164
const colorValues = await this.runQuery(query)
147165
const colorMap = {}
@@ -156,41 +174,40 @@ export class DataService {
156174
}
157175
}
158176

159-
ingestData(data: Array<any>, config: DataConfig, dataStore: any) {
160-
console.log(config, data[0])
161-
for (let i = 0; i < data.length; i++) {
162-
const row = data[i]
163-
if (!row?.[config.id]) {
164-
console.error(`Row ${i} in ${config.filename} is missing a valid id`)
177+
async getTooltipValues(
178+
id: string
179+
) {
180+
if (this.tooltipResults[id]) {
181+
return this.tooltipResults[id]
182+
}
183+
let data: any[] = []
184+
for (let i = 0; i < this.config.length; i++) {
185+
const c = this.config[i]
186+
if (!c) {
165187
continue
166188
}
167-
let id = `${row[config.id]}`
168-
// if (id.length === 10) {
169-
// id = `0${id}`
170-
// }
171-
dataStore[id] = {
172-
...row,
173-
id,
174-
}
175-
// @ts-ignore
176-
}
177-
console.log("All done!")
178-
if (this.completeCallback) {
179-
this.completeCallback(config.filename)
180-
}
181-
this.complete.push(config.filename)
182-
}
183-
async fetchData(config: DataConfig) {
184-
if (this.complete.includes(config.filename)) {
185-
return
186-
}
187-
await this.initDb()
188-
const dataStore = this.data[config.filename]
189-
if (this.data[config.filename]) {
190-
// console.error(`Data store already exists for ${config.filename}`);
191-
return
189+
190+
const query = `SELECT "${c.columns.map(spec => spec.column).join('","')}" FROM ${this.getFromQueryString(c.filename)} WHERE "${c.id}" = '${id}'`
191+
const result = await this.runQuery(query, true)
192+
data.push(result[0])
192193
}
193-
this.data[config.filename] = {}
194+
const mappedTooltipContent = this.config.map((c,i) => {
195+
const dataOutput = {
196+
header: c.name,
197+
}
198+
if (!data[i]) {
199+
return dataOutput
200+
}
201+
const columns = JSON.parse(JSON.stringify(data![i]))
202+
if (columns) {
203+
c.columns.forEach((col) => {
204+
// @ts-ignore
205+
dataOutput[col.name] = columns[col.column]
206+
})
207+
}
208+
return dataOutput
209+
})
210+
this.tooltipResults[id] = mappedTooltipContent
194211
}
195212

196213
setCompleteCallback(cb: (s: string) => void) {

utils/hooks/useD3Color.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { current } from "@reduxjs/toolkit"
12
import * as d3 from "d3"
23
import { useEffect, useMemo, useState } from "react"
34
import tinycolor from "tinycolor2"
@@ -36,7 +37,7 @@ export const useMapColor: ColorHook = ({
3637
currentFilter,
3738
}) => {
3839
const [out, setOut] = useState<any>({
39-
colorFunc: () => [120, 120, 120, 0],
40+
colorFunc: () => [0, 0, 0, 0],
4041
breaks: [],
4142
colors: [],
4243
})
@@ -50,16 +51,16 @@ export const useMapColor: ColorHook = ({
5051
column,
5152
table,
5253
// @ts-ignore
53-
breaksSchema.nBins || 5
54+
breaksSchema.nBins || 5,
55+
currentFilter
5456
)
55-
5657
const colorFunc = (_id: string | number) => {
5758
const id = _id.toString()
5859
if (currentFilter?.length && id.startsWith(currentFilter) === false) {
5960
return [120, 120, 120, 0]
6061
}
6162
// @ts-ignore
62-
return colorMap?.[+id] || [120, 120, 120, 0]
63+
return colorMap?.[id] || [120, 120, 120, 0]
6364
}
6465
setOut({
6566
colorFunc,
@@ -68,6 +69,6 @@ export const useMapColor: ColorHook = ({
6869
})
6970
}
7071
main()
71-
}, [table, column, colorScheme, JSON.stringify(breaksSchema.type)])
72+
}, [table, column, colorScheme, JSON.stringify(breaksSchema.type), currentFilter])
7273
return out
7374
}

0 commit comments

Comments
 (0)