diff --git a/ts/index.ts b/ts/index.ts index b790fdf367..6a764f9f5c 100644 --- a/ts/index.ts +++ b/ts/index.ts @@ -182,6 +182,7 @@ export type Packed = T | S export interface Data { list(): (Rec | null)[] dict(): Dict + getTupByIdx(i: U): Rec | null } interface OpsD { @@ -399,6 +400,14 @@ export function unpack(data: any): T { : data } +export function unpackByIdx(data: any, idx: U): T { + return (typeof data === 'string') + ? decodeStringByIdx(data, idx) + : (isData(data)) + ? data.getTupByIdx(idx) + : data?.[idx] || getObjectValueByIdx(data, idx) +} + const errorCodes: Dict = { not_found: WaveErrorCode.PageNotFound, @@ -407,6 +416,22 @@ const const i = d.indexOf(':') return (i > 0) ? [d.substring(0, i), d.substring(i + 1)] : ['', d] }, + rowToRowObj = (item: any, fields: any) => { + const rec: Rec = {} + for (let j = 0; j < fields.length; j++) { + const f = fields[j], v = item[j] + rec[f] = v + } + return rec + }, + colToRowObj = (columns: any, fields: any, idx: U) => { + const rec: Rec = {} + for (let j = 0; j < fields.length; j++) { + const f = fields[j], v = columns[j][idx] + rec[f] = v + } + return rec + }, decodeString = (data: S): any => { if (data === '') return data const [t, d] = decodeType(data) @@ -423,17 +448,11 @@ const const [fields, rows] = JSON.parse(d) if (!Array.isArray(fields)) return data if (!Array.isArray(rows)) return data - const w = fields.length // width const recs: Rec[] = [] for (const r of rows) { if (!Array.isArray(r)) continue - if (r.length !== w) continue - const rec: Rec = {} - for (let j = 0; j < w; j++) { - const f = fields[j], v = r[j] - rec[f] = v - } - recs.push(rec) + if (r.length !== fields.length) continue + recs.push(rowToRowObj(r, fields)) } return recs } catch (e) { @@ -445,18 +464,12 @@ const const [fields, columns] = JSON.parse(d) if (!Array.isArray(fields)) return data if (!Array.isArray(columns)) return data - const w = fields.length // width - if (columns.length !== w) return data + if (columns.length !== fields.length) return data if (columns.length === 0) return data const n = columns[0].length - const recs = new Array(n) + const recs: Rec[] = [] for (let i = 0; i < n; i++) { - const rec: Rec = {} - for (let j = 0; j < w; j++) { - const f = fields[j], v = columns[j][i] - rec[f] = v - } - recs[i] = rec + recs.push(colToRowObj(columns, fields, i)) } return recs } catch (e) { @@ -466,6 +479,31 @@ const } return data }, + decodeStringByIdx = (data: S, idx: U): any => { + if (data === '') return + const [t, d] = decodeType(data) + try { + const parsedData = JSON.parse(d) + const [fields, items] = parsedData + if (Array.isArray(fields) && Array.isArray(items)) { + if (t === 'rows') return rowToRowObj(items[idx], fields) + if (t === 'cols' && items.length && items.length === fields.length) return colToRowObj(items, fields, idx) + } + return parsedData?.[idx] + } catch (e) { + console.error(e) + } + }, + getObjectValueByIdx = (data: any, idx: U): T | undefined => { + // const sortedKeys = Object.keys(data).sort() + // return data?.[sortedKeys[idx]] + let i = 0 + for (const k in data) { + if (i === idx) return data?.[k] + i++ + } + return + }, keysOf = (d: Dict): S[] => { const a: S[] = [] for (const k in d) a.push(k) @@ -475,7 +513,6 @@ const const a: T[] = [] for (const k in d) a.push(d[k]) return a - }, isMap = (x: any): B => { // for JSON data only: anything not null, string, number, bool, array @@ -566,13 +603,20 @@ const } return null }, + getTupByIdx = (i: U): Rec | null => { + if (i >= 0 && i < n) { + const tup = tups[i] + if (tup) return t.make(tup) + } + return null + }, list = (): (Rec | null)[] => { const xs: (Rec | null)[] = [] for (const tup of tups) xs.push(tup ? t.make(tup) : null) return xs }, dict = (): Dict => ({}) - return { __buf__: true, n, put, set, seti, get, geti, list, dict } + return { __buf__: true, n, put, set, seti, get, geti, getTupByIdx, list, dict } }, newCycBuf = (t: Typ, tups: (Tup | null)[], i: U): CycBuf => { const @@ -590,6 +634,13 @@ const get = (_k: S): Cur | null => { return b.geti(i) }, + getTupByIdx = (i: U): Rec | null => { + if (i >= 0 && i < n) { + const tup = tups[i] + if (tup) return t.make(tup) + } + return null + }, list = (): Rec[] => { const xs: Rec[] = [] for (let j = i, k = 0; k < n; j++, k++) { @@ -600,7 +651,7 @@ const return xs }, dict = (): Dict => ({}) - return { __buf__: true, put, set, get, list, dict } + return { __buf__: true, put, set, get, getTupByIdx, list, dict } }, newMapBuf = (t: Typ, tups: Dict): MapBuf => { const @@ -624,6 +675,10 @@ const const tup = tups[k] return tup ? newCur(t, tup) : null }, + getTupByIdx = (i: U): Rec | null => { + const tup = getObjectValueByIdx(tups, i) + return tup ? t.make(tup) : null + }, list = (): Rec[] => { const keys = keysOf(tups) keys.sort() @@ -636,7 +691,7 @@ const for (const k in tups) d[k] = t.make(tups[k]) return d } - return { __buf__: true, put, set, get, list, dict } + return { __buf__: true, put, set, get, getTupByIdx, list, dict } }, newTups = (n: U) => { const xs = new Array(n) diff --git a/ts/package.json b/ts/package.json index 0a3c073d8b..be82f669c9 100644 --- a/ts/package.json +++ b/ts/package.json @@ -7,6 +7,7 @@ "scripts": { "build": "tsc", "build-dev": "tsc --project dev.tsconfig.json", + "watch": "tsc --project dev.tsconfig.json --watch", "minify": "terser dist/index.js -c -m -o dist/index.min.js", "prepublishOnly": "npm run build && npm run minify", "test": "echo \"Error: no test specified\" && exit 1" diff --git a/ui/src/plot.tsx b/ui/src/plot.tsx index cce444f866..9507d6b085 100644 --- a/ui/src/plot.tsx +++ b/ui/src/plot.tsx @@ -14,7 +14,7 @@ import { Chart } from '@antv/g2' import { AdjustOption, AnnotationPosition, ArcOption, AxisOption, ChartCfg, CoordinateActions, CoordinateOption, DataMarkerOption, DataRegionOption, GeometryOption, LineOption, RegionOption, ScaleOption, TextOption, TooltipItem } from '@antv/g2/lib/interface' -import { B, Dict, Disposable, F, Model, on, parseI, parseU, Rec, S, unpack, V } from 'h2o-wave' +import { B, Dict, Disposable, F, Model, on, parseI, parseU, Rec, S, unpack, unpackByIdx, V } from 'h2o-wave' import React from 'react' import ReactDOM from 'react-dom' import { stylesheet } from 'typestyle' @@ -1040,19 +1040,18 @@ export interface Visualization { const tooltipContainer = document.createElement('div') tooltipContainer.className = 'g2-tooltip' -const PlotTooltip = ({ items, originalItems }: { items: TooltipItem[], originalItems: any[] }) => +const PlotTooltip = ({ items, originalData }: { items: TooltipItem[], originalData: Rec }) => <> {items.map(({ data, mappingData, color }: TooltipItem) => - Object.keys(originalItems[data.idx]).map((itemKey, idx) => { - const item = originalItems[data.idx][itemKey] - return
  • + Object.entries(unpackByIdx(originalData, data.idx)).map(([key, item], idx) => +
  • - {itemKey}: + {key}: {(item instanceof Date ? item.toISOString().split('T')[0] : item)}
  • - } + ) )} @@ -1066,7 +1065,6 @@ export const currentChart = React.useRef(null), currentPlot = React.useRef(null), themeWatchRef = React.useRef(null), - originalDataRef = React.useRef([]), checkDimensionsPostInit = (w: F, h: F) => { // Safari fix const el = container.current if (!el) return @@ -1091,7 +1089,6 @@ export const data = refactorData(raw_data, plot.marks), { Chart } = await import('@antv/g2'), chart = plot.marks ? new Chart(makeChart(el, space, plot.marks, model.interactions || [])) : null - originalDataRef.current = unpack(model.data) currentPlot.current = plot if (chart) { chart.tooltip({ @@ -1104,10 +1101,10 @@ export const color: cssVar('$text') }, }, - customContent: (_title, items) => { - ReactDOM.render(, tooltipContainer) - return tooltipContainer - } + customContent: () => tooltipContainer + }) + chart.on('tooltip:change', ({ data }: any) => { + ReactDOM.render(, tooltipContainer) }) currentChart.current = chart chart.data(data) @@ -1155,7 +1152,6 @@ export const const raw_data = unpack(model.data), data = refactorData(raw_data, currentPlot.current.marks) - originalDataRef.current = unpack(model.data) currentChart.current.changeData(data) }, [currentChart, currentPlot, model]) diff --git a/ui/vite.config.js b/ui/vite.config.js index 7882c4605c..53f1e265b3 100644 --- a/ui/vite.config.js +++ b/ui/vite.config.js @@ -31,6 +31,9 @@ export default defineConfig({ assetsDir: 'wave-static', chunkSizeWarningLimit: 900 }, + optimizeDeps: { + link: ['h2o-wave'] + }, server: { port: 3000, proxy: {