Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: supporting multi dataset #351

Closed
wants to merge 33 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
04855b3
feat: add iso datetime function
islxyqwe May 20, 2024
336ceb2
fix: dataTable
islxyqwe May 21, 2024
4c94117
fix: color combo box
islxyqwe May 21, 2024
d8b0096
feat: add max width for table cells
islxyqwe May 21, 2024
5f2e2d7
chore: imporove dataTable when embed
islxyqwe May 22, 2024
8650a4d
Merge branch 'feat-table-max-width'
islxyqwe May 22, 2024
57c9d16
chore: remove read * from workflow
islxyqwe Mar 13, 2024
0a63f3d
feat: multi dataset join
islxyqwe Mar 25, 2024
f3132a2
chore: add computed field dataset
islxyqwe Mar 25, 2024
1e4d300
fix: filter tab
islxyqwe Mar 27, 2024
6cfa2a7
fix: fold bugs
islxyqwe Mar 27, 2024
35e4f6a
fix: fold types
islxyqwe Mar 27, 2024
dbcd1ca
fix: merge errors
islxyqwe Apr 1, 2024
ae353ca
fix: datasource segment
islxyqwe Apr 1, 2024
c1a4404
chore: prefix to suffix in query
islxyqwe Apr 1, 2024
5a5a171
save of datasource segment
islxyqwe Apr 12, 2024
8dc872b
fix: preview datasets
islxyqwe Apr 12, 2024
b4ff8ad
fix: foreign dialog
islxyqwe Apr 12, 2024
eb76435
fix: update dsl parser
islxyqwe Apr 18, 2024
aa4672c
fix: show dataset name only at multi dataset mode
islxyqwe Apr 18, 2024
a459054
fix: chat & pure renderer with multi info
islxyqwe Apr 19, 2024
a89f59a
fix: table walker
islxyqwe Apr 19, 2024
99c05e0
fix: merge errors
islxyqwe May 22, 2024
8be9565
Merge branch 'feat-iso-datetime' into multi-dataset
islxyqwe May 22, 2024
c76152c
chore: change multi dataset fields base select
islxyqwe May 22, 2024
06e899e
fix: time unit
islxyqwe May 22, 2024
e496e02
chore: change field list
islxyqwe May 22, 2024
1f6b09b
fix: data board
islxyqwe May 22, 2024
d5493d8
chore: change week def to duckdb
islxyqwe May 22, 2024
607c673
fix: pivot table pureRenderer
islxyqwe May 31, 2024
47abbf6
fix: computation field
islxyqwe May 31, 2024
4f141f6
fix: show table summary is not reactive in pivot table (#388)
islxyqwe May 26, 2024
689b28a
chore: disable collapse for pure renderer
islxyqwe May 31, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,6 @@ export interface IGWProps {
i18nLang?: string;
i18nResources?: { [lang: string]: Record<string, string | any> };
keepAlive?: boolean | string;
fieldKeyGuard?: boolean;
vizThemeConfig?: IThemeKey;
apperence?: IDarkMode;
storeRef?: React.MutableRefObject<IGlobalStore | null>;
Expand Down
54 changes: 50 additions & 4 deletions packages/duckdb-wasm-computation/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import duckdb_wasm from '@duckdb/duckdb-wasm/dist/duckdb-mvp.wasm?url';
import mvp_worker from '@duckdb/duckdb-wasm/dist/duckdb-browser-mvp.worker.js?url';
import duckdb_wasm_eh from '@duckdb/duckdb-wasm/dist/duckdb-eh.wasm?url';
import eh_worker from '@duckdb/duckdb-wasm/dist/duckdb-browser-eh.worker.js?url';
import initWasm, { parser_dsl_with_table } from '@kanaries/gw-dsl-parser';
import initWasm, { parser_dsl_with_meta, parser_dsl_with_table } from '@kanaries/gw-dsl-parser';
import dslWasm from '@kanaries/gw-dsl-parser/gw_dsl_parser_bg.wasm?url';
import { nanoid } from 'nanoid';
import type { IDataSourceProvider, IMutField, IDataSourceListener } from '@kanaries/graphic-walker';
Expand Down Expand Up @@ -51,7 +51,7 @@ const ArrowToJSON = (v: any): any => {
if (typeof v === 'object') {
if (v instanceof Vector) {
return Array.from(v).map(ArrowToJSON);
} else {
} else if (v !== null) {
return parseInt(bigNumToString(v as any));
}
}
Expand All @@ -68,6 +68,7 @@ const transformData = (table: Table) => {
export async function getMemoryProvider(): Promise<IDataSourceProvider> {
await init();
const conn = await db.connect();
const files: { id: string; content: any }[] = [];
const datasets: { name: string; id: string }[] = [];
const metaDict = new Map<string, IMutField[]>();
const specDict = new Map<string, string>();
Expand All @@ -82,6 +83,7 @@ export async function getMemoryProvider(): Promise<IDataSourceProvider> {
const filename = `${id}.json`;
await db.registerFileText(filename, JSON.stringify(data));
await conn.insertJSONFromPath(filename, { name: id });
files.push({ id, content: data });
datasets.push({ id, name });
metaDict.set(id, meta);
specDict.set(id, JSON.stringify([exportFullRaw(fromFields(meta, 'Chart 1'))]));
Expand All @@ -101,7 +103,11 @@ export async function getMemoryProvider(): Promise<IDataSourceProvider> {
async getSpecs(datasetId) {
const specs = specDict.get(datasetId);
if (!specs) {
throw new Error('cannot find specs');
const selectedDatasets: string[] = JSON.parse(datasetId);
const fields = selectedDatasets.flatMap((dataset) => metaDict.get(dataset)?.map((x) => ({ ...x, dataset })) ?? []);
const specs = JSON.stringify([exportFullRaw(fromFields(fields, 'Chart 1'))]);
specDict.set(datasetId, specs);
return specs;
}
return specs;
},
Expand All @@ -110,7 +116,13 @@ export async function getMemoryProvider(): Promise<IDataSourceProvider> {
listeners.forEach((cb) => cb(4, datasetId));
},
async queryData(query, datasetIds) {
const sql = parser_dsl_with_table(datasetIds[0], JSON.stringify(query));
let sql: string;
if (datasetIds.length === 1) {
sql = parser_dsl_with_table(datasetIds[0], JSON.stringify(query));
} else {
const metas = Object.fromEntries(datasetIds.map((id) => [id, metaDict.get(id)!.map((x) => ({ key: x.fid, type: 'string' }))]));
sql = parser_dsl_with_meta(query.datasets[0], JSON.stringify(query), JSON.stringify(metas));
}
if (process.env.NODE_ENV !== 'production') {
console.log(query, sql);
}
Expand All @@ -123,6 +135,40 @@ export async function getMemoryProvider(): Promise<IDataSourceProvider> {
listeners.filter((x) => x !== cb);
};
},
async onExportFile() {
const data = {
files,
datasets,
metaDict: Array.from(metaDict.entries()),
specDict: Array.from(specDict.entries()),
};
const result = new Blob([JSON.stringify(data)], { type: 'text/plain' });
return result;
},
async onImportFile(file) {
const data = JSON.parse(await file.text()) as {
files: {
id: string;
content: any;
}[];
datasets: {
name: string;
id: string;
}[];
metaDict: [string, IMutField[]][];
specDict: [string, string][];
};
files.push(...data.files);
for (const { id, content } of data.files) {
const filename = `${id}.json`;
await db.registerFileText(filename, JSON.stringify(content));
await conn.insertJSONFromPath(filename, { name: id });
}
data.datasets.forEach((x) => datasets.push(x));
data.metaDict.forEach(([k, v]) => metaDict.set(k, v));
data.specDict.forEach(([k, v]) => specDict.set(k, v));
listeners.forEach((cb) => cb(1, ''));
},
};
}

Expand Down
3 changes: 2 additions & 1 deletion packages/graphic-walker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
},
"types": "./dist/index.d.ts",
"dependencies": {
"@headlessui-float/react": "^0.11.4",
"@headlessui-float/react": "^0.13.2",
"@headlessui/react": "1.7.12",
"@heroicons/react": "^2.0.8",
"@kanaries/react-beautiful-dnd": "^0.1.1",
Expand All @@ -58,6 +58,7 @@
"@radix-ui/react-context-menu": "^2.1.5",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-hover-card": "^1.0.7",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.0.7",
Expand Down
28 changes: 10 additions & 18 deletions packages/graphic-walker/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import GeoConfigPanel from './components/leafletRenderer/geoConfigPanel';
import AskViz from './components/askViz';
import { renderSpec } from './store/visualSpecStore';
import FieldsContextWrapper from './fields/fieldsContext';
import { guardDataKeys } from './utils/dataPrep';
import { getComputation } from './computation/clientComputation';
import LogPanel from './fields/datasetFields/logPanel';
import BinPanel from './fields/datasetFields/binPanel';
Expand All @@ -41,7 +40,9 @@ import { VizAppContext } from './store/context';
import { Tabs, TabsList, TabsTrigger } from './components/ui/tabs';
import { ChartPieIcon, CircleStackIcon, ChatBubbleLeftRightIcon } from '@heroicons/react/24/outline';
import { TabsContent } from '@radix-ui/react-tabs';
import MultiDatasetFields from './fields/datasetFields/multi';
import { VegaliteChat } from './components/chat';
import { LinkDataset } from './components/linkDataset';

export type BaseVizProps = IAppI18nProps &
IVizProps &
Expand All @@ -68,6 +69,7 @@ export const VizApp = observer(function VizApp(props: BaseVizProps) {
chart,
vlSpec,
onError,
datasetNames,
} = props;

const { t, i18n } = useTranslation();
Expand Down Expand Up @@ -145,12 +147,13 @@ export const VizApp = observer(function VizApp(props: BaseVizProps) {

return (
<ErrorContext value={{ reportError }}>
<ErrorBoundary fallback={<div>Something went wrong</div>} onError={props.onError}>
<ErrorBoundary fallback={<div>Something went wrong</div>} onError={console.log}>
<VizAppContext
ComputationContext={wrappedComputation}
themeContext={darkMode}
vegaThemeContext={{ vizThemeConfig: props.vizThemeConfig ?? props.themeConfig ?? props.themeKey }}
portalContainerContext={portal}
DatasetNamesContext={props.datasetNames}
>
<div className={classNames(`App font-sans bg-background text-foreground m-0 p-0`, darkMode === 'dark' ? 'dark' : '')}>
<FieldsContextWrapper>
Expand Down Expand Up @@ -214,6 +217,7 @@ export const VizApp = observer(function VizApp(props: BaseVizProps) {
<RenamePanel />
<ComputedFieldDialog />
<Painter themeConfig={themeConfig} themeKey={themeKey} />
<LinkDataset />
{vizStore.showGeoJSONConfigPanel && <GeoConfigPanel geoList={props.geoList} />}
<div className="sm:flex">
<SideResize
Expand All @@ -222,7 +226,8 @@ export const VizApp = observer(function VizApp(props: BaseVizProps) {
className="min-w-[100%] max-w-full sm:min-w-[96px] sm:max-w-[35%] flex-shrink-0"
handlerClassName="hidden sm:block"
>
<DatasetFields />
{!vizStore.isMultiDataset && <DatasetFields />}
{vizStore.isMultiDataset && <MultiDatasetFields />}
</SideResize>
<SideResize
defaultWidth={180}
Expand Down Expand Up @@ -284,7 +289,7 @@ export const VizApp = observer(function VizApp(props: BaseVizProps) {
});

export function VizAppWithContext(props: IVizAppProps & IComputationProps) {
const { computation, onMetaChange, fieldKeyGuard, keepAlive, storeRef, defaultConfig, ...rest } = props;
const { computation, onMetaChange, keepAlive, storeRef, defaultConfig, ...rest } = props;
// @TODO remove deprecated props
const appearance = props.appearance ?? props.dark;
const data = props.data ?? props.dataSource;
Expand All @@ -295,19 +300,6 @@ export function VizAppWithContext(props: IVizAppProps & IComputationProps) {
onMetaChange: safeOnMetaChange,
} = useMemo(() => {
if (data) {
if (props.fieldKeyGuard) {
const { safeData, safeMetas } = guardDataKeys(data, fields);
return {
safeMetas,
computation: getComputation(safeData),
onMetaChange: (safeFID, meta) => {
const index = safeMetas.findIndex((x) => x.fid === safeFID);
if (index >= 0) {
props.onMetaChange?.(fields[index].fid, meta);
}
},
};
}
return {
safeMetas: fields,
computation: getComputation(data),
Expand All @@ -319,7 +311,7 @@ export function VizAppWithContext(props: IVizAppProps & IComputationProps) {
computation: props.computation,
onMetaChange: props.onMetaChange,
};
}, [fields, data ? data : props.computation, props.fieldKeyGuard, props.onMetaChange]);
}, [fields, data ? data : props.computation, props.onMetaChange]);

const darkMode = useCurrentMediaTheme(appearance);

Expand Down
59 changes: 24 additions & 35 deletions packages/graphic-walker/src/Renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,12 @@ import ReactiveRenderer from './renderer/index';
import { ComputationContext, VizStoreWrapper, useCompututaion, useVizStore, withErrorReport, withTimeout } from './store';
import { mergeLocaleRes, setLocaleLanguage } from './locales/i18n';
import { renderSpec } from './store/visualSpecStore';
import { guardDataKeys } from './utils/dataPrep';
import { getComputation } from './computation/clientComputation';
import { ErrorContext } from './utils/reportError';
import { ErrorBoundary } from 'react-error-boundary';
import Errorpanel from './components/errorpanel';
import { useCurrentMediaTheme } from './utils/media';
import { classNames, getFilterMeaAggKey, parseErrorMessage } from './utils';
import { classNames, getFieldIdentifier, getFilterMeaAggKey, isSameField, parseErrorMessage } from './utils';
import { VegaliteMapper } from './lib/vl2gw';
import { newChart } from './models/visSpecHistory';
import { SimpleOneOfSelector, SimpleRange, SimpleSearcher, SimpleTemporalRange } from './fields/filterField/simple';
Expand Down Expand Up @@ -138,6 +137,7 @@ export const RendererApp = observer(function VizApp(props: BaseVizProps) {
themeContext={darkMode}
vegaThemeContext={{ vizThemeConfig: vizThemeConfig ?? themeConfig ?? themeKey }}
portalContainerContext={portal}
DatasetNamesContext={props.datasetNames}
>
<div className={`${darkMode === 'dark' ? 'dark' : ''} App font-sans bg-background text-foreground m-0 p-0`}>
<div className="flex flex-col space-y-2 bg-background text-foreground">
Expand Down Expand Up @@ -170,12 +170,12 @@ const FilterItem = observer(function FilterItem({ filter, onChange }: { filter:

const computation = useCompututaion();

const originalField = filter.enableAgg ? allFields.find((x) => x.fid === filter.fid) : undefined;
const originalField = filter.enableAgg ? allFields.find(isSameField(filter)) : undefined;
const filterAggName = filter?.enableAgg ? filter.aggName : undefined;

const transformedComputation = useMemo((): IComputationFunction => {
if (originalField && viewDimensions.length > 0) {
const preWorkflow = toWorkflow(
const { workflow, datasets } = toWorkflow(
[],
allFields,
viewDimensions,
Expand All @@ -185,24 +185,26 @@ const FilterItem = observer(function FilterItem({ filter, onChange }: { filter:
[],
undefined,
timezoneDisplayOffset
).map((x) => {
if (x.type === 'view') {
return {
...x,
query: x.query.map((q) => {
if (q.op === 'aggregate') {
return { ...q, measures: q.measures.map((m) => ({ ...m, asFieldKey: m.field })) };
}
return q;
}),
};
}
return x;
});
);
return (query) =>
computation({
...query,
workflow: preWorkflow.concat(query.workflow.filter((x) => x.type !== 'transform')),
workflow: workflow
.map((x) => {
if (x.type === 'view') {
return {
...x,
query: x.query.map((q) => {
if (q.op === 'aggregate') {
return { ...q, measures: q.measures.map((m) => ({ ...m, asFieldKey: m.field })) };
}
return q;
}),
};
}
return x;
})
.concat(query.workflow.filter((x) => x.type !== 'transform')),
});
} else {
return computation;
Expand Down Expand Up @@ -264,7 +266,7 @@ const FilterSection = observer(function FilterSection() {
return (
<div className={classNames('grid gap-2 px-2', cols)} ref={ref}>
{vizStore.viewFilters.map((filter, idx) => (
<FilterItem key={filter.fid} filter={filter} onChange={(rule) => handleWriteFilter(idx, rule)} />
<FilterItem key={getFieldIdentifier(filter)} filter={filter} onChange={(rule) => handleWriteFilter(idx, rule)} />
))}
</div>
);
Expand All @@ -273,7 +275,7 @@ const FilterSection = observer(function FilterSection() {
export function RendererAppWithContext(
props: IVizAppProps & IComputationProps & { overrideSize?: IVisualLayout['size']; containerClassName?: string; containerStyle?: React.CSSProperties }
) {
const { dark, dataSource, computation, onMetaChange, fieldKeyGuard, keepAlive, storeRef, defaultConfig, ...rest } = props;
const { dark, dataSource, computation, onMetaChange, keepAlive, storeRef, defaultConfig, ...rest } = props;
// @TODO remove deprecated props
const appearance = props.appearance ?? props.dark;
const data = props.data ?? props.dataSource;
Expand All @@ -285,19 +287,6 @@ export function RendererAppWithContext(
onMetaChange: safeOnMetaChange,
} = useMemo(() => {
if (data) {
if (props.fieldKeyGuard) {
const { safeData, safeMetas } = guardDataKeys(data, fields);
return {
safeMetas,
computation: getComputation(safeData),
onMetaChange: (safeFID, meta) => {
const index = safeMetas.findIndex((x) => x.fid === safeFID);
if (index >= 0) {
props.onMetaChange?.(fields[index].fid, meta);
}
},
};
}
return {
safeMetas: fields,
computation: getComputation(data),
Expand All @@ -309,7 +298,7 @@ export function RendererAppWithContext(
computation: props.computation,
onMetaChange: props.onMetaChange,
};
}, [fields, data ? data : props.computation, props.fieldKeyGuard, props.onMetaChange]);
}, [fields, data ? data : props.computation, props.onMetaChange]);

const darkMode = useCurrentMediaTheme(appearance);

Expand Down
Loading