diff --git a/packages/graphic-walker/package.json b/packages/graphic-walker/package.json index fae34f19..f560b4e5 100644 --- a/packages/graphic-walker/package.json +++ b/packages/graphic-walker/package.json @@ -1,6 +1,6 @@ { "name": "@kanaries/graphic-walker", - "version": "0.4.17", + "version": "0.4.19", "scripts": { "dev:front_end": "vite --host", "dev": "npm run dev:front_end", diff --git a/packages/graphic-walker/src/App.tsx b/packages/graphic-walker/src/App.tsx index 25f944f0..56758af3 100644 --- a/packages/graphic-walker/src/App.tsx +++ b/packages/graphic-walker/src/App.tsx @@ -31,6 +31,7 @@ import BinPanel from './fields/datasetFields/binPanel'; import { ErrorContext } from './utils/reportError'; import { ErrorBoundary } from "react-error-boundary"; import Errorpanel from './components/errorpanel'; +import { GWGlobalConfig } from './vis/theme'; export interface IGWProps { dataSource?: IRow[]; @@ -46,7 +47,7 @@ export interface IGWProps { fieldKeyGuard?: boolean; /** @default "vega" */ themeKey?: IThemeKey; - themeConfig?: VegaGlobalConfig; + themeConfig?: GWGlobalConfig; dark?: IDarkMode; storeRef?: React.MutableRefObject; computation?: IComputationFunction; diff --git a/packages/graphic-walker/src/components/visualConfig/config-item.tsx b/packages/graphic-walker/src/components/visualConfig/config-item.tsx new file mode 100644 index 00000000..41d3b4e6 --- /dev/null +++ b/packages/graphic-walker/src/components/visualConfig/config-item.tsx @@ -0,0 +1,25 @@ +import React from 'react'; + +export function ConfigItemContainer (props: { children?: React.ReactNode; className?: string }) { + return
+ {props.children} +
+} + +export function ConfigItemContent (props: { children?: React.ReactNode }) { + return
+ {props.children} +
+} + +export function ConfigItemHeader (props: { children?: React.ReactNode }) { + return
+ {props.children} +
+} + +export function ConfigItemTitle (props: { children?: React.ReactNode }) { + return

+ {props.children} +

+} \ No newline at end of file diff --git a/packages/graphic-walker/src/components/visualConfig/index.tsx b/packages/graphic-walker/src/components/visualConfig/index.tsx index 633090af..4c9e4d46 100644 --- a/packages/graphic-walker/src/components/visualConfig/index.tsx +++ b/packages/graphic-walker/src/components/visualConfig/index.tsx @@ -15,6 +15,7 @@ import Toggle from '../toggle'; import DropdownSelect from '../dropdownSelect'; import { ColorSchemes, extractRGBA } from './colorScheme'; import { RangeScale } from './range-scale'; +import { ConfigItemContainer, ConfigItemContent, ConfigItemHeader, ConfigItemTitle } from './config-item'; const DEFAULT_COLOR_SCHEME = ['#5B8FF9', '#FF6900', '#FCB900', '#7BDCB5', '#00D084', '#8ED1FC', '#0693E3', '#ABB8C3', '#EB144C', '#F78DA7', '#9900EF']; @@ -129,172 +130,195 @@ const VisualConfigPanel: React.FC = (props) => { setDisplayColorPicker(false); }} > - {coordSystem === 'generic' && ( - <> -
-

{t('config.scheme')}

-
-
- + + + Colors + + +
+
+ +
{ + e.stopPropagation(); + e.preventDefault(); + }} + >
{ e.stopPropagation(); e.preventDefault(); + setDisplayColorPicker(true); }} - > -
{ - e.stopPropagation(); - e.preventDefault(); - setDisplayColorPicker(true); - }} - >
-
- {displayColorPicker && ( - { - setColorEdited(true); - setDefaultColor({ - ...color.rgb, - a: color.rgb.a ?? 1, - }); - }} - /> - )} -
+ >
+
+ {displayColorPicker && ( + { + setColorEdited(true); + setDefaultColor({ + ...color.rgb, + a: color.rgb.a ?? 1, + }); + }} + /> + )}
-
- - ({ - value: scheme.name, - label: ( - <> -
-
{scheme.name}
-
- {scheme.value.map((c, index) => { - return ( -
- ); - })} -
+
+
+ + ({ + value: scheme.name, + label: ( + <> +
+
{scheme.name}
+
+ {scheme.value.map((c, index) => { + return
; + })}
- - ), - })).concat({ - value: '', - label: <>{t('config.default_color_palette')}, - })} - /> -
+
+ + ), + })).concat({ + value: '', + label: <>{t('config.default_color_palette')}, + })} + />
+
+ + { + setBackground(e.target.value); + }} + /> +
+
+ + + + + Scale + + +
+
+
-
- - )} -

{t('config.format')}

-

- {t(`config.formatGuidesDocs`)}:{' '} - - {t(`config.readHere`)} - -

- {formatConfigList.map((fc) => ( -
- -
- { - setFormat((f) => ({ - ...f, - [fc]: e.target.value, - })); - }} - /> + + + + + {t('config.format')} +

+ {t(`config.formatGuidesDocs`)}:{' '} + + {t(`config.readHere`)} + +

+
+ +
+ {formatConfigList.map((fc) => ( +
+ +
+ { + setFormat((f) => ({ + ...f, + [fc]: e.target.value, + })); + }} + /> +
+
+ ))}
-
- ))} -
-
- -
- { - setBackground(e.target.value); - }} - /> -
-
- -
-
- {GLOBAL_CONFIG.POSITION_CHANNEL_CONFIG_LIST.map((pc) => ( + + + + + {t('config.independence')} +

+ {t(`config.formatGuidesDocs`)}:{' '} + + {t(`config.readHere`)} + +

+
+ +
+ {GLOBAL_CONFIG.POSITION_CHANNEL_CONFIG_LIST.map((pc) => ( + { + setResolve((r) => ({ + ...r, + [pc]: e, + })); + }} + /> + ))} + {GLOBAL_CONFIG.NON_POSITION_CHANNEL_CONFIG_LIST.map((npc) => ( + { + setResolve((r) => ({ + ...r, + [npc]: e, + })); + }} + /> + ))} +
+
+
+ + + {t('config.misc')} + + +
{ - setResolve((r) => ({ - ...r, - [pc]: e, - })); + label={t(`config.zeroScale`)} + enabled={zeroScale} + onChange={(en) => { + setZeroScale(en); }} /> - ))} - {GLOBAL_CONFIG.NON_POSITION_CHANNEL_CONFIG_LIST.map((npc) => ( { - setResolve((r) => ({ - ...r, - [npc]: e, - })); + label={t(`config.svg`)} + enabled={svg} + onChange={(en) => { + setSvg(en); }} /> - ))} -
-
- -
-
- { - setZeroScale(en); - }} - /> - { - setSvg(en); - }} - /> - {isChoropleth && ( { setScaleIncludeUnmatchedChoropleth(en); }} /> - )} -
-
-
+
+ + + +
@@ -198,7 +198,7 @@ export function RangeScale(props: { }} type="number" disabled={!props.enableMaxDomain} - className="block w-full rounded-md border-0 py-1 px-2 text-gray-900 dark:text-white shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 dark:bg-zinc-900 dark:border-gray-700 focus:ring-1 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" + className="block w-full rounded-md border-0 py-1 px-2 text-gray-900 dark:text-white shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-600 placeholder:text-gray-400 dark:bg-zinc-900 dark:border-gray-700 focus:ring-1 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6 disabled:opacity-50" />
diff --git a/packages/graphic-walker/src/utils/useTheme.ts b/packages/graphic-walker/src/utils/useTheme.ts index a371f7d8..3bb49b62 100644 --- a/packages/graphic-walker/src/utils/useTheme.ts +++ b/packages/graphic-walker/src/utils/useTheme.ts @@ -1,35 +1,34 @@ import { IThemeKey } from '../interfaces'; import { builtInThemes, getColorPalette, getPrimaryColor } from '../vis/theme'; -const isPlainObject = (a): a is object => typeof a === 'object' && !(a instanceof Array); +type PlainObject = { [key: string]: any }; -const clone = (a: T) => { - if (isPlainObject(a)) { - if (a === null) return a; - return Object.fromEntries(Object.keys(a).map((k) => [k, clone(a[k])])); +const isPlainObject = (obj: any): obj is PlainObject => { + return obj && typeof obj === 'object' && obj.constructor === Object; +}; + +const clone = (a: T): T => { + if (Array.isArray(a)) { + return a.map(clone) as any; + } else if (isPlainObject(a)) { + return { ...a }; } return a; }; -const merge = (a: any, b: any) => { +const deepMerge = (a: PlainObject, b: PlainObject): PlainObject => { if (isPlainObject(a) && isPlainObject(b)) { - const result = clone(a); - Object.keys(b).forEach((k) => { - result[k] = merge(result[k], b[k]); + const result = { ...a }; + Object.keys(b).forEach((key) => { + result[key] = isPlainObject(result[key]) ? deepMerge(result[key], b[key]) : b[key]; }); return result; } return b; }; -const merge2 = (...a: any[]) => { - if (a.length === 0) return undefined; - if (a.length === 1) return a[0]; - let result = merge(a[0], a[1]); - for (let i = 2; i < a.length; i++) { - result = merge(result, a[i]); - } - return result; +const deepMergeAll = (...objects: PlainObject[]): PlainObject => { + return objects.reduce((acc, obj) => deepMerge(acc, obj), {}); }; export function getTheme(props: { themeKey?: IThemeKey; themeConfig?: any; primaryColor?: string; colorPalette?: string; mediaTheme: 'dark' | 'light' }) { @@ -37,6 +36,6 @@ export function getTheme(props: { themeKey?: IThemeKey; themeConfig?: any; prima const presetConfig = themeConfig ?? builtInThemes[themeKey ?? 'vega']; const colorConfig = primaryColor ? getPrimaryColor(primaryColor) : {}; const paletteConfig = colorPalette ? getColorPalette(colorPalette) : {}; - const config = merge2(presetConfig, colorConfig, paletteConfig)?.[mediaTheme]; + const config = deepMergeAll(presetConfig, colorConfig, paletteConfig)?.[mediaTheme]; return config; } diff --git a/packages/graphic-walker/src/vis/theme.ts b/packages/graphic-walker/src/vis/theme.ts index 371e99de..4add30f3 100644 --- a/packages/graphic-walker/src/vis/theme.ts +++ b/packages/graphic-walker/src/vis/theme.ts @@ -1,3 +1,18 @@ +import { VegaGlobalConfig } from '../interfaces'; + +export type GWGlobalConfig< + T extends VegaGlobalConfig = VegaGlobalConfig & { + scale: { + continuous: { + range: string[]; + }; + }; + } +> = { + light: T; + dark: T; +}; + const DEFAULT_COLOR = '#5B8FF9'; const DARK_COMMON_DESIGN = { background: 'transparent', @@ -28,7 +43,7 @@ export const VegaTheme = { dark: DARK_COMMON_DESIGN, } as const; -export const AntVTheme = { +export const AntVTheme: GWGlobalConfig = { light: { area: { fill: DEFAULT_COLOR }, bar: { fill: DEFAULT_COLOR }, @@ -37,42 +52,17 @@ export const AntVTheme = { point: { stroke: DEFAULT_COLOR }, rect: { fill: DEFAULT_COLOR }, tick: { stroke: DEFAULT_COLOR }, - boxplot: { fill: DEFAULT_COLOR }, - errorbar: { stroke: DEFAULT_COLOR }, - errorband: { fill: DEFAULT_COLOR }, arc: { fill: DEFAULT_COLOR }, background: 'transparent', range: { - category: [ - "#5B8FF9", - "#61DDAA", - "#65789B", - "#F6BD16", - "#7262FD", - "#78D3F8", - "#9661BC", - "#F6903D", - "#008685", - "#F08BB4", - ], - diverging: ["#7b3294", "#c2a5cf", "#f7f7f7", "#a6dba0", "#008837"], - heatmap: ["#0d0887", "#46039f", "#7201a8", "#9c179e", "#bd3786", "#d8576b", "#ed7953", "#fb9f3a", "#fdca26", "#f0f921"], - // ordinal: [ - // '#B8E1FF', - // // '#9AC5FF', - // // '#7DAAFF', - // // '#5B8FF9', - // // '#3D76DD', - // // '#085EC0', - // // '#0047A5', - // // '#00318A', - // '#001D70' - // ], - // ordinal: 'blues', + category: ['#5B8FF9', '#61DDAA', '#65789B', '#F6BD16', '#7262FD', '#78D3F8', '#9661BC', '#F6903D', '#008685', '#F08BB4'], + diverging: ['#7b3294', '#c2a5cf', '#f7f7f7', '#a6dba0', '#008837'], + heatmap: ['#0d0887', '#46039f', '#7201a8', '#9c179e', '#bd3786', '#d8576b', '#ed7953', '#fb9f3a', '#fdca26', '#f0f921'], ramp: ['#EBCCFF', '#CCB0FF', '#AE95FF', '#907BFF', '#7262FD', '#5349E0', '#2F32C3', '#001BA7', '#00068C'], }, + // scale for geo only scale: { - continuous: { range: ["#d4d4e8", "#3b196f"] }, + continuous: { range: ['#d4d4e8', '#3b196f'] }, }, }, dark: { @@ -84,44 +74,21 @@ export const AntVTheme = { point: { stroke: DEFAULT_COLOR }, rect: { fill: DEFAULT_COLOR }, tick: { stroke: DEFAULT_COLOR }, - boxplot: { fill: DEFAULT_COLOR }, - errorbar: { stroke: DEFAULT_COLOR }, - errorband: { fill: DEFAULT_COLOR }, arc: { fill: DEFAULT_COLOR }, range: { - category: [ - "#5B8FF9", - "#61DDAA", - "#65789B", - "#F6BD16", - "#7262FD", - "#78D3F8", - "#9661BC", - "#F6903D", - "#008685", - "#F08BB4", - ], - diverging: ["#7b3294", "#c2a5cf", "#f7f7f7", "#a6dba0", "#008837"], - heatmap: ["#0d0887", "#46039f", "#7201a8", "#9c179e", "#bd3786", "#d8576b", "#ed7953", "#fb9f3a", "#fdca26", "#f0f921"], - ramp: [ - "#EBCCFF", - "#CCB0FF", - "#AE95FF", - "#907BFF", - "#7262FD", - "#5349E0", - "#2F32C3", - "#001BA7", - "#00068C" - ], + category: ['#5B8FF9', '#61DDAA', '#65789B', '#F6BD16', '#7262FD', '#78D3F8', '#9661BC', '#F6903D', '#008685', '#F08BB4'], + diverging: ['#7b3294', '#c2a5cf', '#f7f7f7', '#a6dba0', '#008837'], + heatmap: ['#0d0887', '#46039f', '#7201a8', '#9c179e', '#bd3786', '#d8576b', '#ed7953', '#fb9f3a', '#fdca26', '#f0f921'], + ramp: ['#EBCCFF', '#CCB0FF', '#AE95FF', '#907BFF', '#7262FD', '#5349E0', '#2F32C3', '#001BA7', '#00068C'], }, + // scale for geo only scale: { - continuous: { range: ["#d4d4e8", "#3b196f"] }, + continuous: { range: ['#d4d4e8', '#3b196f'] }, }, }, -} as const; +}; -export const builtInThemes: { [themeKey: string]: { light: any; dark: any } } = { +export const builtInThemes: { [themeKey: string]: { light: VegaGlobalConfig; dark: VegaGlobalConfig } } = { vega: VegaTheme, g2: AntVTheme, };