Skip to content

Commit

Permalink
tooltip / map cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
nofurtherinformation committed Mar 29, 2024
1 parent 67cb682 commit 61c500a
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 91 deletions.
84 changes: 40 additions & 44 deletions components/Map/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Provider, useSelector } from "react-redux"
import { MapboxOverlay, MapboxOverlayProps } from "@deck.gl/mapbox/typed"
import GlMap, { NavigationControl, useControl } from "react-map-gl"
import "mapbox-gl/dist/mapbox-gl.css"
import React, { useMemo, useRef } from "react"
import React, { useEffect, useMemo, useRef, useState } from "react"
import { MVTLayer } from "@deck.gl/geo-layers/typed"
import DropdownMenuDemo from "components/Dropdown/Dropdown"
import * as DropdownMenu from "@radix-ui/react-dropdown-menu"
Expand Down Expand Up @@ -40,27 +40,22 @@ const BreakText: React.FC<{ breaks: number[]; index: number; colors: number[][]
const Tooltip: React.FC<{ dataService: DataService }> = ({ dataService }) => {
const tooltip = useAppSelector((state) => state.map.tooltip)
const { x, y, id } = tooltip || {}
// @ts-ignore
const data = dataService.tooltipResults[id]
const [_updateTrigger, setUpdateTrigger] = useState<boolean>(true)

const data = useMemo(() => {
if (!id) {
return []
}
const output = config.map((d) => {
const dataOutput = {
header: d.name,
} as any
const data = dataService.data[d.filename]?.[id]
if (data) {
d.columns.forEach((c) => {
dataOutput[c.name] = data[c.column]
})
useEffect(() => {
const main = async () => {
if (!id) {
return
}
return dataOutput
})
return output
const tooltipData = await dataService.getTooltipValues(id)
setUpdateTrigger((v) => !v)
}
main()
}, [id])

if (!x || !y || !id) {
if (!x || !y) {
return null
}

Expand All @@ -72,22 +67,26 @@ const Tooltip: React.FC<{ dataService: DataService }> = ({ dataService }) => {
top: y + 10,
}}
>
{data.map((d, i) => {
const keys = Object.keys(d).filter((k) => k !== "header")
// nice skeumorphic shadow
return (
<p className="pb-2" key={i}>
<b>{d.header}</b>
<ul>
{keys.map((k,i) => (
<li key={i}>
{k}: {d[k]}
</li>
))}
</ul>
</p>
)
})}
{/* @ts-ignore */}
{data ? (
data.map((d: any, i: number) => {
const keys = Object.keys(d).filter((k) => k !== "header")
return (
<p className="pb-2" key={i}>
<b>{d.header}</b>
<ul>
{keys.map((k, i) => (
<li key={i}>
{k}: {d[k]}
</li>
))}
</ul>
</p>
)
})
) : (
<p>Loading...</p>
)}
</div>
)
}
Expand Down Expand Up @@ -134,8 +133,12 @@ export const Map = () => {
updateTriggers: {
getFillColor: [isReady, currentColumnSpec?.column, currentDataSpec?.filename, colorFunc],
},
onClick: (info: any) => {
console.log(info)
},
onHover: (info: any) => {
if (info?.x && info?.y && info?.object) {
const isFiltered = currentFilter && info.object?.properties?.GEOID?.startsWith(currentFilter) === false
if (info?.x && info?.y && info?.object && !isFiltered) {
dispatch(setTooltipInfo({ x: info.x, y: info.y, id: info.object?.properties?.GEOID }))
} else {
dispatch(setTooltipInfo(null))
Expand All @@ -157,7 +160,7 @@ export const Map = () => {
const handleSetColumn = (col: string | number) => dispatch(setCurrentColumn(col))
const handleChangeData = (data: string) => dispatch(setCurrentData(data))
const handleSetFilter = (filter: string) => dispatch(setCurrentFilter(filter))

return (
<div style={{ width: "100vw", height: "100vh", position: "relative", top: 0, left: 0 }}>
<div style={{ position: "absolute", bottom: "2rem", right: "1rem", zIndex: 1000 }}>
Expand All @@ -170,16 +173,9 @@ export const Map = () => {
</p>
</div>
</div>
{/* <button style={{
position:'fixed',
top: 0,
left: 0,
zIndex: 500,
background:'red'
}} onClick={testfn}>TEST FN</button> */}
<div className="absolute left-4 top-4 z-50">
<DropdownMenuDemo>
<div className="p-4 max-w-[100vw]">
<div className="max-w-[100vw] p-4">
<p>Choose Data</p>
<hr />
{config.map((c, i) => (
Expand Down
101 changes: 59 additions & 42 deletions utils/data/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export class DataService {
db?: AsyncDuckDB
baseURL: string = window.location.origin
conn?: AsyncDuckDBConnection
tooltipResults: any = {}

constructor(completeCallback?: (s: string) => void, config: DataConfig[] = defaultConfig) {
this.config = config
Expand Down Expand Up @@ -79,27 +80,37 @@ export class DataService {
}
}

async runQuery(query: string) {
async runQuery(query: string, freshConn?: boolean) {
await this.initDb()
try {
return await runQuery({
conn: this.conn!,
const conn = freshConn ? await this.db!.connect() : this.conn!
const r = await runQuery({
conn,
query,
})
if (freshConn) {
await conn.close()
}
return r
} catch (e) {
console.error(e)
return []
}
}
async getQuantiles(column: string | number, table: string, n: number): Promise<Array<number>> {
async getQuantiles(column: string | number, table: string, n: number, idCol: string, filter?: string): Promise<Array<number>> {
// breakpoints to use for quantile breaks
// eg. n=5 - 0.2, 0.4, 0.6, 0.8 - 4 breaks
// eg. n=4 - 0.25, 0.5, 0.75 - 3 breaks
const quantileFractions = Array.from({ length: n - 1 }, (_, i) => (i + 1) / n)
const query = `SELECT
let query = `SELECT
${quantileFractions.map((f, i) => `approx_quantile("${column}", ${f}) as break${i}`)}
FROM ${this.getFromQueryString(table)};
FROM ${this.getFromQueryString(table)}
`
if (filter) {
query += ` WHERE "${idCol}" LIKE '${filter}%';`
} else {
query += ";"
}
const result = await this.runQuery(query)
if (!result || result.length === 0) {
console.error(`No results for quantile query: ${query}`)
Expand All @@ -114,7 +125,8 @@ export class DataService {
reversed: boolean,
column: string | number,
table: string,
n: number
n: number,
filter?: string
) {
// @ts-ignore
const d3Colors = d3[colorScheme]?.[n]
Expand All @@ -133,15 +145,21 @@ export class DataService {
if (reversed) {
rgbColors.reverse()
}
const quantiles = await this.getQuantiles(column, table, n)
const query = `
const quantiles = await this.getQuantiles(column, table, n, idColumn, filter)
let query = `
SELECT "${column}", "${idColumn}",
CASE
${quantiles.map((q, i) => `WHEN "${column}" < ${q} THEN [${rgbColors[i]}]`).join("\n")}
WHEN "${column}" IS NULL THEN [120,120,120,0]
ELSE [${rgbColors[rgbColors.length - 1]}]
END as color
FROM ${this.getFromQueryString(table)};
FROM ${this.getFromQueryString(table)}
`
if (filter) {
query += ` WHERE "${idColumn}" LIKE '${filter}%';`
} else {
query += ";"
}
// @ts-ignore
const colorValues = await this.runQuery(query)
const colorMap = {}
Expand All @@ -156,41 +174,40 @@ export class DataService {
}
}

ingestData(data: Array<any>, config: DataConfig, dataStore: any) {
console.log(config, data[0])
for (let i = 0; i < data.length; i++) {
const row = data[i]
if (!row?.[config.id]) {
console.error(`Row ${i} in ${config.filename} is missing a valid id`)
async getTooltipValues(
id: string
) {
if (this.tooltipResults[id]) {
return this.tooltipResults[id]
}
let data: any[] = []
for (let i = 0; i < this.config.length; i++) {
const c = this.config[i]
if (!c) {
continue
}
let id = `${row[config.id]}`
// if (id.length === 10) {
// id = `0${id}`
// }
dataStore[id] = {
...row,
id,
}
// @ts-ignore
}
console.log("All done!")
if (this.completeCallback) {
this.completeCallback(config.filename)
}
this.complete.push(config.filename)
}
async fetchData(config: DataConfig) {
if (this.complete.includes(config.filename)) {
return
}
await this.initDb()
const dataStore = this.data[config.filename]
if (this.data[config.filename]) {
// console.error(`Data store already exists for ${config.filename}`);
return

const query = `SELECT "${c.columns.map(spec => spec.column).join('","')}" FROM ${this.getFromQueryString(c.filename)} WHERE "${c.id}" = '${id}'`
const result = await this.runQuery(query, true)
data.push(result[0])
}
this.data[config.filename] = {}
const mappedTooltipContent = this.config.map((c,i) => {
const dataOutput = {
header: c.name,
}
if (!data[i]) {
return dataOutput
}
const columns = JSON.parse(JSON.stringify(data![i]))
if (columns) {
c.columns.forEach((col) => {
// @ts-ignore
dataOutput[col.name] = columns[col.column]
})
}
return dataOutput
})
this.tooltipResults[id] = mappedTooltipContent
}

setCompleteCallback(cb: (s: string) => void) {
Expand Down
11 changes: 6 additions & 5 deletions utils/hooks/useD3Color.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { current } from "@reduxjs/toolkit"
import * as d3 from "d3"
import { useEffect, useMemo, useState } from "react"
import tinycolor from "tinycolor2"
Expand Down Expand Up @@ -36,7 +37,7 @@ export const useMapColor: ColorHook = ({
currentFilter,
}) => {
const [out, setOut] = useState<any>({
colorFunc: () => [120, 120, 120, 0],
colorFunc: () => [0, 0, 0, 0],
breaks: [],
colors: [],
})
Expand All @@ -50,16 +51,16 @@ export const useMapColor: ColorHook = ({
column,
table,
// @ts-ignore
breaksSchema.nBins || 5
breaksSchema.nBins || 5,
currentFilter
)

const colorFunc = (_id: string | number) => {
const id = _id.toString()
if (currentFilter?.length && id.startsWith(currentFilter) === false) {
return [120, 120, 120, 0]
}
// @ts-ignore
return colorMap?.[+id] || [120, 120, 120, 0]
return colorMap?.[id] || [120, 120, 120, 0]
}
setOut({
colorFunc,
Expand All @@ -68,6 +69,6 @@ export const useMapColor: ColorHook = ({
})
}
main()
}, [table, column, colorScheme, JSON.stringify(breaksSchema.type)])
}, [table, column, colorScheme, JSON.stringify(breaksSchema.type), currentFilter])
return out
}

0 comments on commit 61c500a

Please sign in to comment.