@kanaries/graphic-walker 0.3.9 → 0.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/transform.worker-90e4f506.js.map +1 -1
- package/dist/assets/{viewQuery.worker-95bc9cf6.js.map → viewQuery.worker-03404216.js.map} +1 -1
- package/dist/components/appRoot.d.ts +7 -0
- package/dist/graphic-walker.es.js +21167 -23955
- package/dist/graphic-walker.es.js.map +1 -1
- package/dist/graphic-walker.umd.js +123 -147
- package/dist/graphic-walker.umd.js.map +1 -1
- package/dist/index.d.ts +4 -6
- package/dist/interfaces.d.ts +21 -0
- package/dist/renderer/pureRenderer.d.ts +1 -0
- package/dist/renderer/specRenderer.d.ts +1 -0
- package/dist/shadow-dom.d.ts +10 -0
- package/dist/utils/vegaApiExport.d.ts +10 -0
- package/dist/vis/react-vega.d.ts +1 -0
- package/package.json +2 -2
- package/src/components/appRoot.tsx +32 -0
- package/src/components/callout.tsx +1 -1
- package/src/components/tooltip.tsx +1 -1
- package/src/index.tsx +23 -38
- package/src/interfaces.ts +24 -1
- package/src/renderer/hooks.ts +9 -1
- package/src/renderer/index.tsx +3 -1
- package/src/renderer/pureRenderer.tsx +20 -9
- package/src/renderer/specRenderer.tsx +3 -1
- package/src/shadow-dom.tsx +48 -0
- package/src/utils/vegaApiExport.ts +102 -0
- package/src/vis/react-vega.tsx +22 -43
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { IGWProps } from "./App";
|
|
3
|
+
import type { IGWHandler } from "./interfaces";
|
|
3
4
|
import "./empty_sheet.css";
|
|
4
|
-
export declare const
|
|
5
|
-
root: ShadowRoot | null;
|
|
6
|
-
}>;
|
|
7
|
-
export type { IGWProps };
|
|
8
|
-
export { embedGraphicWalker } from './vanilla';
|
|
9
|
-
export declare const GraphicWalker: React.FC<IGWProps>;
|
|
5
|
+
export declare const GraphicWalker: React.MemoExoticComponent<React.ForwardRefExoticComponent<Pick<IGWProps & React.RefAttributes<IGWHandler>, "key" | keyof IGWProps> & React.RefAttributes<IGWHandler>>>;
|
|
10
6
|
export { default as PureRenderer } from './renderer/pureRenderer';
|
|
7
|
+
export { embedGraphicWalker } from './vanilla';
|
|
8
|
+
export type { IGWProps };
|
package/dist/interfaces.d.ts
CHANGED
|
@@ -191,3 +191,24 @@ export declare enum ISegmentKey {
|
|
|
191
191
|
export type IThemeKey = 'vega' | 'g2';
|
|
192
192
|
export type IDarkMode = 'media' | 'light' | 'dark';
|
|
193
193
|
export type VegaGlobalConfig = VgConfig | VlConfig;
|
|
194
|
+
export interface IChartExportResult<T extends 'svg' | 'data-url' = 'svg' | 'data-url'> {
|
|
195
|
+
mode: T;
|
|
196
|
+
title: string;
|
|
197
|
+
nCols: number;
|
|
198
|
+
nRows: number;
|
|
199
|
+
charts: {
|
|
200
|
+
colIndex: number;
|
|
201
|
+
rowIndex: number;
|
|
202
|
+
width: number;
|
|
203
|
+
height: number;
|
|
204
|
+
data: string;
|
|
205
|
+
}[];
|
|
206
|
+
}
|
|
207
|
+
interface IExportChart {
|
|
208
|
+
<T extends Extract<IChartExportResult['mode'], 'svg'>>(mode?: T): Promise<IChartExportResult<T>>;
|
|
209
|
+
<T extends IChartExportResult['mode']>(mode: T): Promise<IChartExportResult<T>>;
|
|
210
|
+
}
|
|
211
|
+
export interface IGWHandler {
|
|
212
|
+
exportChart: IExportChart;
|
|
213
|
+
}
|
|
214
|
+
export {};
|
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import type { IDarkMode, IRow, IThemeKey, DraggableFieldState, IVisualConfig } from '../interfaces';
|
|
3
3
|
import type { IReactVegaHandler } from '../vis/react-vega';
|
|
4
4
|
interface IPureRendererProps {
|
|
5
|
+
name?: string;
|
|
5
6
|
themeKey?: IThemeKey;
|
|
6
7
|
dark?: IDarkMode;
|
|
7
8
|
rawData?: IRow[];
|
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { IReactVegaHandler } from '../vis/react-vega';
|
|
3
3
|
import { DeepReadonly, DraggableFieldState, IDarkMode, IRow, IThemeKey, IVisualConfig } from '../interfaces';
|
|
4
4
|
interface SpecRendererProps {
|
|
5
|
+
name?: string;
|
|
5
6
|
themeKey?: IThemeKey;
|
|
6
7
|
dark?: IDarkMode;
|
|
7
8
|
data: IRow[];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React, { HTMLAttributes } from "react";
|
|
2
|
+
export declare const ShadowDomContext: React.Context<{
|
|
3
|
+
root: ShadowRoot | null;
|
|
4
|
+
}>;
|
|
5
|
+
interface IShadowDomProps extends HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
onMount?: (shadowRoot: ShadowRoot) => void;
|
|
7
|
+
onUnmount?: () => void;
|
|
8
|
+
}
|
|
9
|
+
export declare const ShadowDom: React.FC<IShadowDomProps>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ForwardedRef, type MutableRefObject } from "react";
|
|
2
|
+
import type { View } from "vega";
|
|
3
|
+
import type { IReactVegaHandler } from "../vis/react-vega";
|
|
4
|
+
export declare const useVegaExportApi: (name: string | undefined, viewsRef: MutableRefObject<{
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
w: number;
|
|
8
|
+
h: number;
|
|
9
|
+
view: View;
|
|
10
|
+
}[]>, ref: ForwardedRef<IReactVegaHandler>) => void;
|
package/dist/vis/react-vega.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kanaries/graphic-walker",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.10",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev:front_end": "vite --host",
|
|
6
6
|
"dev": "npm run dev:front_end",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"@headlessui/react": "^1.7.12",
|
|
38
38
|
"@heroicons/react": "^2.0.8",
|
|
39
39
|
"@kanaries/graphic-walker": "0.3.9",
|
|
40
|
-
"@kanaries/react-beautiful-dnd": "0.0.
|
|
40
|
+
"@kanaries/react-beautiful-dnd": "0.0.2",
|
|
41
41
|
"@kanaries/web-data-loader": "^0.1.7",
|
|
42
42
|
"autoprefixer": "^10.3.5",
|
|
43
43
|
"i18next": "^21.9.1",
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React, { createContext, forwardRef, useImperativeHandle, type ForwardedRef, useContext } from "react";
|
|
2
|
+
import type { IGWHandler } from "../interfaces";
|
|
3
|
+
|
|
4
|
+
const AppRootContext = createContext<ForwardedRef<IGWHandler>>(null!);
|
|
5
|
+
|
|
6
|
+
export const useAppRootContext = () => {
|
|
7
|
+
return useContext(AppRootContext);
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const AppRoot = forwardRef<IGWHandler, { children: any }>(({ children }, ref) => {
|
|
11
|
+
useImperativeHandle(ref, () => {
|
|
12
|
+
return {
|
|
13
|
+
exportChart: (async mode => {
|
|
14
|
+
return {
|
|
15
|
+
mode,
|
|
16
|
+
title: '',
|
|
17
|
+
nCols: 0,
|
|
18
|
+
nRows: 0,
|
|
19
|
+
charts: [],
|
|
20
|
+
};
|
|
21
|
+
}) as IGWHandler['exportChart'],
|
|
22
|
+
};
|
|
23
|
+
}, []);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<AppRootContext.Provider value={ref}>
|
|
27
|
+
{children}
|
|
28
|
+
</AppRootContext.Provider>
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export default AppRoot;
|
|
@@ -2,7 +2,7 @@ import React, { memo, ReactNode, useContext, useEffect, useState } from "react";
|
|
|
2
2
|
import { createPortal } from "react-dom";
|
|
3
3
|
import styled from "styled-components";
|
|
4
4
|
import type { IDarkMode } from "../interfaces";
|
|
5
|
-
import { ShadowDomContext } from "
|
|
5
|
+
import { ShadowDomContext } from "../shadow-dom";
|
|
6
6
|
import { useCurrentMediaTheme } from "../utils/media";
|
|
7
7
|
|
|
8
8
|
export interface CalloutProps {
|
|
@@ -2,7 +2,7 @@ import React, { memo, useContext, useEffect, useMemo, useRef, useState } from "r
|
|
|
2
2
|
import { createPortal } from "react-dom";
|
|
3
3
|
import styled from "styled-components";
|
|
4
4
|
import type { IDarkMode } from "../interfaces";
|
|
5
|
-
import { ShadowDomContext } from "
|
|
5
|
+
import { ShadowDomContext } from "../shadow-dom";
|
|
6
6
|
import { useCurrentMediaTheme } from "../utils/media";
|
|
7
7
|
|
|
8
8
|
export interface TooltipProps {
|
package/src/index.tsx
CHANGED
|
@@ -1,55 +1,40 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
import { StyleSheetManager } from "styled-components";
|
|
3
|
-
import root from "react-shadow";
|
|
1
|
+
import React, { forwardRef } from "react";
|
|
4
2
|
import { DOM } from "@kanaries/react-beautiful-dnd";
|
|
5
3
|
import { observer } from "mobx-react-lite";
|
|
6
4
|
import App, { IGWProps } from "./App";
|
|
7
5
|
import { StoreWrapper } from "./store/index";
|
|
8
6
|
import { FieldsContextWrapper } from "./fields/fieldsContext";
|
|
7
|
+
import { ShadowDom } from "./shadow-dom";
|
|
8
|
+
import AppRoot from "./components/appRoot";
|
|
9
|
+
import type { IGWHandler } from "./interfaces";
|
|
9
10
|
|
|
10
11
|
import "./empty_sheet.css";
|
|
11
|
-
import tailwindStyle from "tailwindcss/tailwind.css?inline";
|
|
12
|
-
import style from "./index.css?inline";
|
|
13
12
|
|
|
14
|
-
export const
|
|
15
|
-
export type { IGWProps }
|
|
16
|
-
export { embedGraphicWalker } from './vanilla';
|
|
17
|
-
|
|
18
|
-
export const GraphicWalker: React.FC<IGWProps> = observer((props) => {
|
|
19
|
-
const [shadowRoot, setShadowRoot] = useState<ShadowRoot | null>(null);
|
|
20
|
-
const rootRef = useRef<HTMLDivElement>(null);
|
|
13
|
+
export const GraphicWalker = observer(forwardRef<IGWHandler, IGWProps>((props, ref) => {
|
|
21
14
|
const { storeRef } = props;
|
|
22
15
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
DOM.setHead(document.head);
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
}, []);
|
|
16
|
+
const handleMount = (shadowRoot: ShadowRoot) => {
|
|
17
|
+
DOM.setBody(shadowRoot);
|
|
18
|
+
DOM.setHead(shadowRoot);
|
|
19
|
+
};
|
|
20
|
+
const handleUnmount = () => {
|
|
21
|
+
DOM.setBody(document.body);
|
|
22
|
+
DOM.setHead(document.head);
|
|
23
|
+
};
|
|
35
24
|
|
|
36
25
|
return (
|
|
37
26
|
<StoreWrapper keepAlive={props.keepAlive} storeRef={storeRef}>
|
|
38
|
-
<
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
<App {...props} />
|
|
46
|
-
</ShadowDomContext.Provider>
|
|
47
|
-
</FieldsContextWrapper>
|
|
48
|
-
</StyleSheetManager>
|
|
49
|
-
)}
|
|
50
|
-
</root.div>
|
|
27
|
+
<AppRoot ref={ref}>
|
|
28
|
+
<ShadowDom onMount={handleMount} onUnmount={handleUnmount}>
|
|
29
|
+
<FieldsContextWrapper>
|
|
30
|
+
<App {...props} />
|
|
31
|
+
</FieldsContextWrapper>
|
|
32
|
+
</ShadowDom>
|
|
33
|
+
</AppRoot>
|
|
51
34
|
</StoreWrapper>
|
|
52
35
|
);
|
|
53
|
-
});
|
|
36
|
+
}));
|
|
54
37
|
|
|
55
38
|
export { default as PureRenderer } from './renderer/pureRenderer';
|
|
39
|
+
export { embedGraphicWalker } from './vanilla';
|
|
40
|
+
export type { IGWProps };
|
package/src/interfaces.ts
CHANGED
|
@@ -223,4 +223,27 @@ export enum ISegmentKey {
|
|
|
223
223
|
export type IThemeKey = 'vega' | 'g2';
|
|
224
224
|
export type IDarkMode = 'media' | 'light' | 'dark';
|
|
225
225
|
|
|
226
|
-
export type VegaGlobalConfig = VgConfig | VlConfig;
|
|
226
|
+
export type VegaGlobalConfig = VgConfig | VlConfig;
|
|
227
|
+
|
|
228
|
+
export interface IChartExportResult<T extends 'svg' | 'data-url' = 'svg' | 'data-url'> {
|
|
229
|
+
mode: T;
|
|
230
|
+
title: string;
|
|
231
|
+
nCols: number;
|
|
232
|
+
nRows: number;
|
|
233
|
+
charts: {
|
|
234
|
+
colIndex: number;
|
|
235
|
+
rowIndex: number;
|
|
236
|
+
width: number;
|
|
237
|
+
height: number;
|
|
238
|
+
data: string;
|
|
239
|
+
}[];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
interface IExportChart {
|
|
243
|
+
<T extends Extract<IChartExportResult['mode'], 'svg'>>(mode?: T): Promise<IChartExportResult<T>>;
|
|
244
|
+
<T extends IChartExportResult['mode']>(mode: T): Promise<IChartExportResult<T>>;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export interface IGWHandler {
|
|
248
|
+
exportChart: IExportChart;
|
|
249
|
+
}
|
package/src/renderer/hooks.ts
CHANGED
|
@@ -30,8 +30,16 @@ export const useRenderer = (props: UseRendererProps): UseRendererResult => {
|
|
|
30
30
|
const taskId = ++taskIdRef.current;
|
|
31
31
|
setComputing(true);
|
|
32
32
|
applyFilter(data, filters)
|
|
33
|
-
.then((data) =>
|
|
33
|
+
.then((data) => {
|
|
34
|
+
if (viewDimensions.length === 0 && viewMeasures.length === 0) {
|
|
35
|
+
return data;
|
|
36
|
+
}
|
|
37
|
+
return transformDataService(data, allFields);
|
|
38
|
+
})
|
|
34
39
|
.then((d) => {
|
|
40
|
+
if (viewDimensions.length === 0 && viewMeasures.length === 0) {
|
|
41
|
+
return data;
|
|
42
|
+
}
|
|
35
43
|
// setViewData(d);
|
|
36
44
|
const dims = viewDimensions;
|
|
37
45
|
const meas = viewMeasures;
|
package/src/renderer/index.tsx
CHANGED
|
@@ -20,7 +20,8 @@ interface RendererProps {
|
|
|
20
20
|
const Renderer = forwardRef<IReactVegaHandler, RendererProps>(function (props, ref) {
|
|
21
21
|
const { themeKey, dark } = props;
|
|
22
22
|
const { vizStore, commonStore } = useGlobalStore();
|
|
23
|
-
const { allFields, viewFilters, viewDimensions, viewMeasures, visualConfig, draggableFieldState } = vizStore;
|
|
23
|
+
const { allFields, viewFilters, viewDimensions, viewMeasures, visualConfig, draggableFieldState, visList, visIndex } = vizStore;
|
|
24
|
+
const chart = visList[visIndex];
|
|
24
25
|
const { currentDataset } = commonStore;
|
|
25
26
|
const { dataSource } = currentDataset;
|
|
26
27
|
|
|
@@ -83,6 +84,7 @@ const Renderer = forwardRef<IReactVegaHandler, RendererProps>(function (props, r
|
|
|
83
84
|
|
|
84
85
|
return (
|
|
85
86
|
<SpecRenderer
|
|
87
|
+
name={chart?.name}
|
|
86
88
|
loading={waiting}
|
|
87
89
|
data={viewData}
|
|
88
90
|
ref={ref}
|
|
@@ -2,6 +2,8 @@ import React, { useState, useEffect, forwardRef, useMemo, useRef } from 'react';
|
|
|
2
2
|
import { unstable_batchedUpdates } from 'react-dom';
|
|
3
3
|
import { toJS } from 'mobx';
|
|
4
4
|
import { observer } from 'mobx-react-lite';
|
|
5
|
+
import { ShadowDom } from '../shadow-dom';
|
|
6
|
+
import AppRoot from '../components/appRoot';
|
|
5
7
|
import type { IDarkMode, IViewField, IRow, IThemeKey, DraggableFieldState, IVisualConfig } from '../interfaces';
|
|
6
8
|
import type { IReactVegaHandler } from '../vis/react-vega';
|
|
7
9
|
import SpecRenderer from './specRenderer';
|
|
@@ -9,6 +11,7 @@ import { useRenderer } from './hooks';
|
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
interface IPureRendererProps {
|
|
14
|
+
name?: string;
|
|
12
15
|
themeKey?: IThemeKey;
|
|
13
16
|
dark?: IDarkMode;
|
|
14
17
|
rawData?: IRow[];
|
|
@@ -22,6 +25,7 @@ interface IPureRendererProps {
|
|
|
22
25
|
*/
|
|
23
26
|
const PureRenderer = forwardRef<IReactVegaHandler, IPureRendererProps>(function PureRenderer (props, ref) {
|
|
24
27
|
const {
|
|
28
|
+
name,
|
|
25
29
|
themeKey,
|
|
26
30
|
dark,
|
|
27
31
|
rawData,
|
|
@@ -75,15 +79,22 @@ const PureRenderer = forwardRef<IReactVegaHandler, IPureRendererProps>(function
|
|
|
75
79
|
}, [waiting]);
|
|
76
80
|
|
|
77
81
|
return (
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
<AppRoot>
|
|
83
|
+
<ShadowDom>
|
|
84
|
+
<div className="relative">
|
|
85
|
+
<SpecRenderer
|
|
86
|
+
name={name}
|
|
87
|
+
loading={waiting}
|
|
88
|
+
data={viewData}
|
|
89
|
+
ref={ref}
|
|
90
|
+
themeKey={themeKey}
|
|
91
|
+
dark={dark}
|
|
92
|
+
draggableFieldState={visualState}
|
|
93
|
+
visualConfig={visualConfig}
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
</ShadowDom>
|
|
97
|
+
</AppRoot>
|
|
87
98
|
);
|
|
88
99
|
});
|
|
89
100
|
|
|
@@ -10,6 +10,7 @@ import { useCurrentMediaTheme } from '../utils/media';
|
|
|
10
10
|
import { builtInThemes } from '../vis/theme';
|
|
11
11
|
|
|
12
12
|
interface SpecRendererProps {
|
|
13
|
+
name?: string;
|
|
13
14
|
themeKey?: IThemeKey;
|
|
14
15
|
dark?: IDarkMode;
|
|
15
16
|
data: IRow[];
|
|
@@ -24,7 +25,7 @@ interface SpecRendererProps {
|
|
|
24
25
|
* This is a pure component, which means it will not depend on any global state.
|
|
25
26
|
*/
|
|
26
27
|
const SpecRenderer = forwardRef<IReactVegaHandler, SpecRendererProps>(function (
|
|
27
|
-
{ themeKey, dark, data, loading, draggableFieldState, visualConfig, onGeomClick, onChartResize },
|
|
28
|
+
{ name, themeKey, dark, data, loading, draggableFieldState, visualConfig, onGeomClick, onChartResize },
|
|
28
29
|
ref
|
|
29
30
|
) {
|
|
30
31
|
// const { draggableFieldState, visualConfig } = vizStore;
|
|
@@ -123,6 +124,7 @@ const SpecRenderer = forwardRef<IReactVegaHandler, SpecRendererProps>(function (
|
|
|
123
124
|
>
|
|
124
125
|
{loading && <LoadingLayer />}
|
|
125
126
|
<ReactVega
|
|
127
|
+
name={name}
|
|
126
128
|
vegaConfig={vegaConfig}
|
|
127
129
|
// format={format}
|
|
128
130
|
layoutMode={size.mode}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React, { HTMLAttributes, createContext, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { StyleSheetManager } from "styled-components";
|
|
3
|
+
import root from "react-shadow";
|
|
4
|
+
|
|
5
|
+
import tailwindStyle from "tailwindcss/tailwind.css?inline";
|
|
6
|
+
import style from "./index.css?inline";
|
|
7
|
+
|
|
8
|
+
export const ShadowDomContext = createContext<{ root: ShadowRoot | null }>({ root: null });
|
|
9
|
+
|
|
10
|
+
interface IShadowDomProps extends HTMLAttributes<HTMLDivElement> {
|
|
11
|
+
onMount?: (shadowRoot: ShadowRoot) => void;
|
|
12
|
+
onUnmount?: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const ShadowDom: React.FC<IShadowDomProps> = function ShadowDom ({ onMount, onUnmount, children, ...attrs }) {
|
|
16
|
+
const [shadowRoot, setShadowRoot] = useState<ShadowRoot | null>(null);
|
|
17
|
+
const rootRef = useRef<HTMLDivElement>(null);
|
|
18
|
+
|
|
19
|
+
const onMountRef = useRef(onMount);
|
|
20
|
+
onMountRef.current = onMount;
|
|
21
|
+
const onUnmountRef = useRef(onUnmount);
|
|
22
|
+
onUnmountRef.current = onUnmount;
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (rootRef.current) {
|
|
26
|
+
const shadowRoot = rootRef.current.shadowRoot!;
|
|
27
|
+
setShadowRoot(shadowRoot);
|
|
28
|
+
onMountRef.current?.(shadowRoot);
|
|
29
|
+
return () => {
|
|
30
|
+
onUnmountRef.current?.();
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<root.div {...attrs} mode="open" ref={rootRef}>
|
|
37
|
+
<style>{tailwindStyle}</style>
|
|
38
|
+
<style>{style}</style>
|
|
39
|
+
{shadowRoot && (
|
|
40
|
+
<StyleSheetManager target={shadowRoot}>
|
|
41
|
+
<ShadowDomContext.Provider value={{ root: shadowRoot }}>
|
|
42
|
+
{children}
|
|
43
|
+
</ShadowDomContext.Provider>
|
|
44
|
+
</StyleSheetManager>
|
|
45
|
+
)}
|
|
46
|
+
</root.div>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { useImperativeHandle, type ForwardedRef, type MutableRefObject, useEffect } from "react";
|
|
2
|
+
import type { View } from "vega";
|
|
3
|
+
import { useAppRootContext } from "../components/appRoot";
|
|
4
|
+
import type { IReactVegaHandler } from "../vis/react-vega";
|
|
5
|
+
import type { IChartExportResult } from "../interfaces";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export const useVegaExportApi = (name: string | undefined, viewsRef: MutableRefObject<{ x: number; y: number; w: number; h: number; view: View }[]>, ref: ForwardedRef<IReactVegaHandler>) => {
|
|
9
|
+
const renderHandle = {
|
|
10
|
+
getSVGData() {
|
|
11
|
+
return Promise.all(viewsRef.current.map(item => item.view.toSVG()));
|
|
12
|
+
},
|
|
13
|
+
async getCanvasData() {
|
|
14
|
+
const canvases = await Promise.all(viewsRef.current.map(item => item.view.toCanvas(2)));
|
|
15
|
+
return canvases.map(canvas => canvas.toDataURL('image/png', 1));
|
|
16
|
+
},
|
|
17
|
+
async downloadSVG(filename = `gw chart ${Date.now() % 1_000_000}`.padStart(6, '0')) {
|
|
18
|
+
const data = await Promise.all(viewsRef.current.map(item => item.view.toSVG()));
|
|
19
|
+
const files: string[] = [];
|
|
20
|
+
for (let i = 0; i < data.length; i += 1) {
|
|
21
|
+
const d = data[i];
|
|
22
|
+
const file = new File([d], `${filename}${data.length > 1 ? `_${i + 1}` : ''}.svg`);
|
|
23
|
+
const url = URL.createObjectURL(file);
|
|
24
|
+
const a = document.createElement('a');
|
|
25
|
+
a.download = file.name;
|
|
26
|
+
a.href = url;
|
|
27
|
+
a.click();
|
|
28
|
+
requestAnimationFrame(() => {
|
|
29
|
+
URL.revokeObjectURL(url);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return files;
|
|
33
|
+
},
|
|
34
|
+
async downloadPNG(filename = `gw chart ${Date.now() % 1_000_000}`.padStart(6, '0')) {
|
|
35
|
+
const canvases = await Promise.all(viewsRef.current.map(item => item.view.toCanvas(2)));
|
|
36
|
+
const data = canvases.map(canvas => canvas.toDataURL('image/png', 1));
|
|
37
|
+
const files: string[] = [];
|
|
38
|
+
for (let i = 0; i < data.length; i += 1) {
|
|
39
|
+
const d = data[i];
|
|
40
|
+
const a = document.createElement('a');
|
|
41
|
+
a.download = `${filename}${data.length > 1 ? `_${i + 1}` : ''}.png`;
|
|
42
|
+
a.href = d.replace(/^data:image\/[^;]/, 'data:application/octet-stream');
|
|
43
|
+
a.click();
|
|
44
|
+
}
|
|
45
|
+
return files;
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
useImperativeHandle(ref, () => renderHandle);
|
|
50
|
+
|
|
51
|
+
const appRef = useAppRootContext();
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (appRef && 'current' in appRef && appRef.current) {
|
|
55
|
+
appRef.current.exportChart = (async (mode: IChartExportResult['mode'] = 'svg') => {
|
|
56
|
+
const res: IChartExportResult = {
|
|
57
|
+
mode,
|
|
58
|
+
title: name || 'untitled',
|
|
59
|
+
nCols: viewsRef.current.map(item => item.x).reduce((a, b) => Math.max(a, b), 0) + 1,
|
|
60
|
+
nRows: viewsRef.current.map(item => item.y).reduce((a, b) => Math.max(a, b), 0) + 1,
|
|
61
|
+
charts: viewsRef.current.map(item => ({
|
|
62
|
+
rowIndex: item.y,
|
|
63
|
+
colIndex: item.x,
|
|
64
|
+
width: item.w,
|
|
65
|
+
height: item.h,
|
|
66
|
+
data: '',
|
|
67
|
+
})),
|
|
68
|
+
};
|
|
69
|
+
if (mode === 'data-url') {
|
|
70
|
+
const imgData = await renderHandle.getCanvasData();
|
|
71
|
+
if (imgData) {
|
|
72
|
+
for (let i = 0; i < imgData.length; i += 1) {
|
|
73
|
+
res.charts[i].data = imgData[i];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} else if (mode === 'svg') {
|
|
77
|
+
const svgData = await renderHandle.getSVGData();
|
|
78
|
+
if (svgData) {
|
|
79
|
+
for (let i = 0; i < svgData.length; i += 1) {
|
|
80
|
+
res.charts[i].data = svgData[i];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return res;
|
|
85
|
+
}) as typeof appRef.current.exportChart;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
return () => {
|
|
91
|
+
if (appRef && 'current' in appRef && appRef.current) {
|
|
92
|
+
appRef.current.exportChart = async mode => ({
|
|
93
|
+
mode,
|
|
94
|
+
title: '',
|
|
95
|
+
nCols: 0,
|
|
96
|
+
nRows: 0,
|
|
97
|
+
charts: [],
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}, []);
|
|
102
|
+
};
|
package/src/vis/react-vega.tsx
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import React, { useEffect, useState, useMemo, forwardRef,
|
|
1
|
+
import React, { useEffect, useState, useMemo, forwardRef, useRef } from 'react';
|
|
2
2
|
import embed from 'vega-embed';
|
|
3
3
|
import { Subject, Subscription } from 'rxjs'
|
|
4
4
|
import * as op from 'rxjs/operators';
|
|
5
5
|
import type { ScenegraphEvent, View } from 'vega';
|
|
6
6
|
import styled from 'styled-components';
|
|
7
7
|
|
|
8
|
+
import { useVegaExportApi } from '../utils/vegaApiExport';
|
|
8
9
|
import { IViewField, IRow, IStackMode, VegaGlobalConfig } from '../interfaces';
|
|
9
10
|
import { useTranslation } from 'react-i18next';
|
|
10
11
|
import { getVegaTimeFormatRules } from './temporalFormat';
|
|
@@ -25,6 +26,7 @@ export interface IReactVegaHandler {
|
|
|
25
26
|
downloadPNG: (filename?: string) => Promise<string[]>;
|
|
26
27
|
}
|
|
27
28
|
interface ReactVegaProps {
|
|
29
|
+
name?: string;
|
|
28
30
|
rows: Readonly<IViewField[]>;
|
|
29
31
|
columns: Readonly<IViewField[]>;
|
|
30
32
|
dataSource: IRow[];
|
|
@@ -73,6 +75,7 @@ interface ParamStoreEntry {
|
|
|
73
75
|
|
|
74
76
|
const ReactVega = forwardRef<IReactVegaHandler, ReactVegaProps>(function ReactVega (props, ref) {
|
|
75
77
|
const {
|
|
78
|
+
name,
|
|
76
79
|
dataSource = [],
|
|
77
80
|
rows = [],
|
|
78
81
|
columns = [],
|
|
@@ -150,7 +153,7 @@ const ReactVega = forwardRef<IReactVegaHandler, ReactVegaProps>(function ReactVe
|
|
|
150
153
|
})
|
|
151
154
|
}, [rowRepeatFields, colRepeatFields])
|
|
152
155
|
|
|
153
|
-
const vegaRefs = useRef<View[]>([]);
|
|
156
|
+
const vegaRefs = useRef<{ x: number; y: number; w: number; h: number; view: View }[]>([]);
|
|
154
157
|
|
|
155
158
|
useEffect(() => {
|
|
156
159
|
vegaRefs.current = [];
|
|
@@ -218,7 +221,13 @@ const ReactVega = forwardRef<IReactVegaHandler, ReactVegaProps>(function ReactVe
|
|
|
218
221
|
|
|
219
222
|
if (viewPlaceholders.length > 0 && viewPlaceholders[0].current) {
|
|
220
223
|
embed(viewPlaceholders[0].current, spec, { mode: 'vega-lite', actions: showActions, timeFormatLocale: getVegaTimeFormatRules(i18n.language), config: vegaConfig }).then(res => {
|
|
221
|
-
vegaRefs.current = [
|
|
224
|
+
vegaRefs.current = [{
|
|
225
|
+
w: res.view.container()?.clientWidth ?? res.view.width(),
|
|
226
|
+
h: res.view.container()?.clientHeight ?? res.view.height(),
|
|
227
|
+
x: 0,
|
|
228
|
+
y: 0,
|
|
229
|
+
view: res.view,
|
|
230
|
+
}];
|
|
222
231
|
try {
|
|
223
232
|
res.view.addEventListener('click', (e) => {
|
|
224
233
|
click$.next(e);
|
|
@@ -250,6 +259,7 @@ const ReactVega = forwardRef<IReactVegaHandler, ReactVegaProps>(function ReactVe
|
|
|
250
259
|
subscriptions.push(throttledParamStore$.subscribe(cb));
|
|
251
260
|
};
|
|
252
261
|
let index = 0;
|
|
262
|
+
vegaRefs.current = new Array(rowRepeatFields.length * colRepeatFields.length);
|
|
253
263
|
for (let i = 0; i < rowRepeatFields.length; i++) {
|
|
254
264
|
for (let j = 0; j < colRepeatFields.length; j++, index++) {
|
|
255
265
|
const sourceId = index;
|
|
@@ -282,8 +292,15 @@ const ReactVega = forwardRef<IReactVegaHandler, ReactVegaProps>(function ReactVe
|
|
|
282
292
|
ans.params = commonSpec.params;
|
|
283
293
|
}
|
|
284
294
|
if (node) {
|
|
295
|
+
const id = index;
|
|
285
296
|
embed(node, ans, { mode: 'vega-lite', actions: showActions, timeFormatLocale: getVegaTimeFormatRules(i18n.language), config: vegaConfig }).then(res => {
|
|
286
|
-
vegaRefs.current
|
|
297
|
+
vegaRefs.current[id] = {
|
|
298
|
+
w: res.view.container()?.clientWidth ?? res.view.width(),
|
|
299
|
+
h: res.view.container()?.clientHeight ?? res.view.height(),
|
|
300
|
+
x: j,
|
|
301
|
+
y: i,
|
|
302
|
+
view: res.view,
|
|
303
|
+
};
|
|
287
304
|
const paramStores = (res.vgSpec.data?.map(d => d.name) ?? []).filter(
|
|
288
305
|
name => [BRUSH_SIGNAL_NAME, POINT_SIGNAL_NAME].map(p => `${p}_store`).includes(name)
|
|
289
306
|
).map(name => name.replace(/_store$/, ''));
|
|
@@ -374,45 +391,7 @@ const ReactVega = forwardRef<IReactVegaHandler, ReactVegaProps>(function ReactVe
|
|
|
374
391
|
text
|
|
375
392
|
]);
|
|
376
393
|
|
|
377
|
-
|
|
378
|
-
getSVGData() {
|
|
379
|
-
return Promise.all(vegaRefs.current.map(view => view.toSVG()));
|
|
380
|
-
},
|
|
381
|
-
async getCanvasData() {
|
|
382
|
-
const canvases = await Promise.all(vegaRefs.current.map(view => view.toCanvas()));
|
|
383
|
-
return canvases.map(canvas => canvas.toDataURL('image/png'));
|
|
384
|
-
},
|
|
385
|
-
async downloadSVG(filename = `gw chart ${Date.now() % 1_000_000}`.padStart(6, '0')) {
|
|
386
|
-
const data = await Promise.all(vegaRefs.current.map(view => view.toSVG()));
|
|
387
|
-
const files: string[] = [];
|
|
388
|
-
for (let i = 0; i < data.length; i += 1) {
|
|
389
|
-
const d = data[i];
|
|
390
|
-
const file = new File([d], `${filename}${data.length > 1 ? `_${i + 1}` : ''}.svg`);
|
|
391
|
-
const url = URL.createObjectURL(file);
|
|
392
|
-
const a = document.createElement('a');
|
|
393
|
-
a.download = file.name;
|
|
394
|
-
a.href = url;
|
|
395
|
-
a.click();
|
|
396
|
-
requestAnimationFrame(() => {
|
|
397
|
-
URL.revokeObjectURL(url);
|
|
398
|
-
});
|
|
399
|
-
}
|
|
400
|
-
return files;
|
|
401
|
-
},
|
|
402
|
-
async downloadPNG(filename = `gw chart ${Date.now() % 1_000_000}`.padStart(6, '0')) {
|
|
403
|
-
const canvases = await Promise.all(vegaRefs.current.map(view => view.toCanvas(2)));
|
|
404
|
-
const data = canvases.map(canvas => canvas.toDataURL('image/png', 1));
|
|
405
|
-
const files: string[] = [];
|
|
406
|
-
for (let i = 0; i < data.length; i += 1) {
|
|
407
|
-
const d = data[i];
|
|
408
|
-
const a = document.createElement('a');
|
|
409
|
-
a.download = `${filename}${data.length > 1 ? `_${i + 1}` : ''}.png`;
|
|
410
|
-
a.href = d.replace(/^data:image\/[^;]/, 'data:application/octet-stream');
|
|
411
|
-
a.click();
|
|
412
|
-
}
|
|
413
|
-
return files;
|
|
414
|
-
},
|
|
415
|
-
}));
|
|
394
|
+
useVegaExportApi(name, vegaRefs, ref);
|
|
416
395
|
|
|
417
396
|
return <CanvaContainer rowSize={Math.max(rowRepeatFields.length, 1)} colSize={Math.max(colRepeatFields.length, 1)}>
|
|
418
397
|
{/* <div ref={container}></div> */}
|