@kanaries/graphic-walker 0.3.6 → 0.3.8

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.
@@ -1,11 +1,13 @@
1
- import { runInAction, toJS } from 'mobx';
1
+ import { toJS } from 'mobx';
2
2
  import { Resizable } from 're-resizable';
3
- import React, { useCallback, forwardRef, useMemo } from 'react';
3
+ import React, { forwardRef, useMemo } from 'react';
4
4
 
5
- import { useGlobalStore } from '../store';
5
+ import PivotTable from '../components/pivotTable';
6
6
  import ReactVega, { IReactVegaHandler } from '../vis/react-vega';
7
- import { DeepReadonly, DraggableFieldState, IDarkMode, IRow, IThemeKey, IVisualConfig } from '../interfaces';
7
+ import { DeepReadonly, DraggableFieldState, IDarkMode, IRow, IThemeKey, IVisualConfig, VegaGlobalConfig } from '../interfaces';
8
8
  import LoadingLayer from '../components/loadingLayer';
9
+ import { useCurrentMediaTheme } from '../utils/media';
10
+ import { builtInThemes } from '../vis/theme';
9
11
 
10
12
  interface SpecRendererProps {
11
13
  themeKey?: IThemeKey;
@@ -13,15 +15,20 @@ interface SpecRendererProps {
13
15
  data: IRow[];
14
16
  loading: boolean;
15
17
  draggableFieldState: DeepReadonly<DraggableFieldState>;
16
- visualConfig: IVisualConfig;
18
+ visualConfig: DeepReadonly<IVisualConfig>;
19
+ onGeomClick?: ((values: any, e: any) => void) | undefined;
20
+ onChartResize?: ((width: number, height: number) => void) | undefined;
17
21
  }
22
+ /**
23
+ * Sans-store renderer of GraphicWalker.
24
+ * This is a pure component, which means it will not depend on any global state.
25
+ */
18
26
  const SpecRenderer = forwardRef<IReactVegaHandler, SpecRendererProps>(function (
19
- { themeKey, dark, data, loading, draggableFieldState, visualConfig },
27
+ { themeKey, dark, data, loading, draggableFieldState, visualConfig, onGeomClick, onChartResize },
20
28
  ref
21
29
  ) {
22
- const { vizStore, commonStore } = useGlobalStore();
23
30
  // const { draggableFieldState, visualConfig } = vizStore;
24
- const { geoms, interactiveScale, defaultAggregated, stack, showActions, size, format: _format } = visualConfig;
31
+ const { geoms, interactiveScale, defaultAggregated, stack, showActions, size, format: _format, zeroScale } = visualConfig;
25
32
 
26
33
  const rows = draggableFieldState.rows;
27
34
  const columns = draggableFieldState.columns;
@@ -41,30 +48,59 @@ const SpecRenderer = forwardRef<IReactVegaHandler, SpecRendererProps>(function (
41
48
  [columns]
42
49
  );
43
50
 
51
+ const isPivotTable = geoms[0] === 'table';
52
+
44
53
  const hasFacet = rowLeftFacetFields.length > 0 || colLeftFacetFields.length > 0;
45
54
 
46
- const handleGeomClick = useCallback(
47
- (values: any, e: any) => {
48
- e.stopPropagation();
49
- runInAction(() => {
50
- commonStore.showEmbededMenu([e.pageX, e.pageY]);
51
- commonStore.setFilters(values);
52
- });
53
- },
54
- []
55
- );
56
- const enableResize = size.mode === 'fixed' && !hasFacet;
55
+ const enableResize = size.mode === 'fixed' && !hasFacet && Boolean(onChartResize);
56
+ const mediaTheme = useCurrentMediaTheme(dark);
57
+ const themeConfig = builtInThemes[themeKey ?? 'vega']?.[mediaTheme];
58
+
59
+ const vegaConfig = useMemo<VegaGlobalConfig>(() => {
60
+ const config: VegaGlobalConfig = {
61
+ ...themeConfig,
62
+ }
63
+ if (format.normalizedNumberFormat && format.normalizedNumberFormat.length > 0) {
64
+ // @ts-ignore
65
+ config.normalizedNumberFormat = format.normalizedNumberFormat;
66
+ }
67
+ if (format.numberFormat && format.numberFormat.length > 0) {
68
+ // @ts-ignore
69
+ config.numberFormat = format.numberFormat;
70
+ }
71
+ if (format.timeFormat && format.timeFormat.length > 0) {
72
+ // @ts-ignore
73
+ config.timeFormat = format.timeFormat;
74
+ }
75
+ // @ts-ignore
76
+ if (!config.scale) {
77
+ // @ts-ignore
78
+ config.scale = {};
79
+ }
80
+ // @ts-ignore
81
+ config.scale.zero = Boolean(zeroScale)
82
+ return config;
83
+ }, [themeConfig, zeroScale, format.normalizedNumberFormat, format.numberFormat, format.timeFormat])
84
+
85
+ if (isPivotTable) {
86
+ return (
87
+ <PivotTable
88
+ data={data}
89
+ draggableFieldState={draggableFieldState}
90
+ visualConfig={visualConfig}
91
+ loading={loading}
92
+ themeKey={themeKey}
93
+ dark={dark}
94
+ />
95
+ );
96
+ }
57
97
 
58
98
  return (
59
99
  <Resizable
60
100
  className={enableResize ? 'border-blue-400 border-2 overflow-hidden' : ''}
61
101
  style={{ padding: '12px' }}
62
102
  onResizeStop={(e, direction, ref, d) => {
63
- vizStore.setChartLayout({
64
- mode: 'fixed',
65
- width: size.width + d.width,
66
- height: size.height + d.height,
67
- });
103
+ onChartResize?.(size.width + d.width, size.height + d.height);
68
104
  }}
69
105
  enable={
70
106
  enableResize
@@ -87,7 +123,8 @@ const SpecRenderer = forwardRef<IReactVegaHandler, SpecRendererProps>(function (
87
123
  >
88
124
  {loading && <LoadingLayer />}
89
125
  <ReactVega
90
- format={format}
126
+ vegaConfig={vegaConfig}
127
+ // format={format}
91
128
  layoutMode={size.mode}
92
129
  interactiveScale={interactiveScale}
93
130
  geomType={geoms[0]}
@@ -108,9 +145,7 @@ const SpecRenderer = forwardRef<IReactVegaHandler, SpecRendererProps>(function (
108
145
  width={size.width - 12 * 4}
109
146
  height={size.height - 12 * 4}
110
147
  ref={ref}
111
- onGeomClick={handleGeomClick}
112
- themeKey={themeKey}
113
- dark={dark}
148
+ onGeomClick={onGeomClick}
114
149
  />
115
150
  </Resizable>
116
151
  );
@@ -70,6 +70,7 @@ export function initVisualConfig(): IVisualConfig {
70
70
  showActions: false,
71
71
  interactiveScale: false,
72
72
  sorted: "none",
73
+ zeroScale: true,
73
74
  size: {
74
75
  mode: "auto",
75
76
  width: 320,
@@ -371,14 +372,16 @@ export class VizSpecStore {
371
372
  case configKey === "geoms" && Array.isArray(value):
372
373
  case configKey === "size" && typeof value === "object":
373
374
  case configKey === "sorted":
375
+ case configKey === "zeroScale":
374
376
  case configKey === "stack": {
375
377
  return (config[configKey] = value);
376
378
  }
377
379
  case configKey === 'format' && typeof value === "object": {
378
380
  return config[configKey] = value
379
381
  }
382
+
380
383
  default: {
381
- console.error("unknown key" + configKey);
384
+ console.error("[unknown key] " + configKey + " You should registered visualConfig at setVisualConfig");
382
385
  }
383
386
  }
384
387
  });
@@ -1,16 +1,14 @@
1
1
  import React, { useEffect, useState, useMemo, forwardRef, useImperativeHandle, useRef } from 'react';
2
- import embed, { vega } from 'vega-embed';
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 { IViewField, IRow, IStackMode, IDarkMode, IThemeKey, IVisualConfig } from '../interfaces';
8
+ import { IViewField, IRow, IStackMode, VegaGlobalConfig } from '../interfaces';
9
9
  import { useTranslation } from 'react-i18next';
10
10
  import { getVegaTimeFormatRules } from './temporalFormat';
11
- import { builtInThemes } from './theme';
12
- import { useCurrentMediaTheme } from '../utils/media';
13
- import { SingleViewProps, getSingleView } from './spec/view';
11
+ import { getSingleView } from './spec/view';
14
12
  import { NULL_FIELD } from './spec/field';
15
13
 
16
14
  const CanvaContainer = styled.div<{rowSize: number; colSize: number;}>`
@@ -27,7 +25,6 @@ export interface IReactVegaHandler {
27
25
  downloadPNG: (filename?: string) => Promise<string[]>;
28
26
  }
29
27
  interface ReactVegaProps {
30
- format: IVisualConfig['format'];
31
28
  rows: Readonly<IViewField[]>;
32
29
  columns: Readonly<IViewField[]>;
33
30
  dataSource: IRow[];
@@ -48,9 +45,7 @@ interface ReactVegaProps {
48
45
  width: number;
49
46
  height: number;
50
47
  onGeomClick?: (values: any, e: any) => void
51
- /** @default "vega" */
52
- themeKey?: IThemeKey;
53
- dark?: IDarkMode;
48
+ vegaConfig: VegaGlobalConfig;
54
49
  }
55
50
 
56
51
  const click$ = new Subject<ScenegraphEvent>();
@@ -98,30 +93,31 @@ const ReactVega = forwardRef<IReactVegaHandler, ReactVegaProps>(function ReactVe
98
93
  width,
99
94
  height,
100
95
  details = [],
101
- themeKey = 'vega',
102
- dark = 'media',
103
- format
96
+ // themeKey = 'vega',
97
+ // dark = 'media',
98
+ vegaConfig,
99
+ // format
104
100
  } = props;
105
101
  const [viewPlaceholders, setViewPlaceholders] = useState<React.MutableRefObject<HTMLDivElement>[]>([]);
106
102
  const { i18n } = useTranslation();
107
- const mediaTheme = useCurrentMediaTheme(dark);
108
- const themeConfig = builtInThemes[themeKey]?.[mediaTheme];
103
+ // const mediaTheme = useCurrentMediaTheme(dark);
104
+ // const themeConfig = builtInThemes[themeKey]?.[mediaTheme];
109
105
 
110
- const vegaConfig = useMemo(() => {
111
- const config: any = {
112
- ...themeConfig,
113
- }
114
- if (format.normalizedNumberFormat && format.normalizedNumberFormat.length > 0) {
115
- config.normalizedNumberFormat = format.normalizedNumberFormat;
116
- }
117
- if (format.numberFormat && format.numberFormat.length > 0) {
118
- config.numberFormat = format.numberFormat;
119
- }
120
- if (format.timeFormat && format.timeFormat.length > 0) {
121
- config.timeFormat = format.timeFormat;
122
- }
123
- return config;
124
- }, [themeConfig, format.normalizedNumberFormat, format.numberFormat, format.timeFormat])
106
+ // const vegaConfig = useMemo(() => {
107
+ // const config: any = {
108
+ // ...themeConfig,
109
+ // }
110
+ // if (format.normalizedNumberFormat && format.normalizedNumberFormat.length > 0) {
111
+ // config.normalizedNumberFormat = format.normalizedNumberFormat;
112
+ // }
113
+ // if (format.numberFormat && format.numberFormat.length > 0) {
114
+ // config.numberFormat = format.numberFormat;
115
+ // }
116
+ // if (format.timeFormat && format.timeFormat.length > 0) {
117
+ // config.timeFormat = format.timeFormat;
118
+ // }
119
+ // return config;
120
+ // }, [themeConfig, format.normalizedNumberFormat, format.numberFormat, format.timeFormat])
125
121
 
126
122
  useEffect(() => {
127
123
  const clickSub = geomClick$.subscribe(([values, e]) => {
@@ -7,7 +7,7 @@ import { ISemanticType } from "../../interfaces";
7
7
  */
8
8
  export function autoMark(semanticTypeList: ISemanticType[]): string {
9
9
  if (semanticTypeList.length < 2) {
10
- if (semanticTypeList[0] === "temporal") return "tick";
10
+ if (semanticTypeList[0] === "temporal" || semanticTypeList[0] === 'quantitative') return "tick";
11
11
  return "bar";
12
12
  }
13
13
  const couter: Map<ISemanticType, number> = new Map();
@@ -1,6 +1,7 @@
1
1
  import { IViewField } from "../../interfaces";
2
+ import { getMeaAggKey } from "../../utils";
2
3
 
3
- export function addTooltipEncode (encoding: {[key: string]: any}, details: Readonly<IViewField[]> = []) {
4
+ export function addTooltipEncode (encoding: {[key: string]: any}, details: Readonly<IViewField[]> = [], defaultAggregated = false) {
4
5
  const encs = Object.keys(encoding).filter(ck => ck !== 'tooltip').map(ck => {
5
6
  return {
6
7
  field: encoding[ck].field,
@@ -8,8 +9,8 @@ export function addTooltipEncode (encoding: {[key: string]: any}, details: Reado
8
9
  title: encoding[ck].title
9
10
  }
10
11
  }).concat(details.map(f => ({
11
- field: f.fid,
12
- title: f.name,
12
+ field: defaultAggregated ? getMeaAggKey(f.fid, f.aggName) : f.fid,
13
+ title: defaultAggregated && f.aggName ? `${f.aggName}(${f.name})` : f.name,
13
14
  type: f.semanticType
14
15
  })))
15
16
  encoding.tooltip = encs
@@ -64,10 +64,10 @@ export function getSingleView(props: SingleViewProps) {
64
64
  details,
65
65
  text
66
66
  });
67
- addTooltipEncode(encoding, details)
68
67
  if (defaultAggregated) {
69
68
  channelAggregate(encoding, fields);
70
69
  }
70
+ addTooltipEncode(encoding, details, defaultAggregated);
71
71
  channelStack(encoding, stack);
72
72
  const mark = {
73
73
  type: markType,
@@ -1,7 +0,0 @@
1
- import { ISemanticType } from "../interfaces";
2
- /**
3
- *
4
- * @param semanticTypeList semanticTypeList.length <= 2,调用时,手动将columns 和 rows的最后一个元素组合传进来
5
- * @returns geom(mark) type
6
- */
7
- export declare function autoMark(semanticTypeList: ISemanticType[]): string;
@@ -1,30 +0,0 @@
1
- import { ISemanticType } from "../interfaces";
2
-
3
- /**
4
- *
5
- * @param semanticTypeList semanticTypeList.length <= 2,调用时,手动将columns 和 rows的最后一个元素组合传进来
6
- * @returns geom(mark) type
7
- */
8
- export function autoMark(semanticTypeList: ISemanticType[]): string {
9
- if (semanticTypeList.length < 2) {
10
- if (semanticTypeList[0] === "temporal") return "tick";
11
- return "bar";
12
- }
13
- const couter: Map<ISemanticType, number> = new Map();
14
- (["nominal", "ordinal", "quantitative", "temporal"] as ISemanticType[]).forEach((s) => {
15
- couter.set(s, 0);
16
- });
17
- for (let st of semanticTypeList) {
18
- couter.set(st, couter.get(st)! + 1);
19
- }
20
- if (couter.get("nominal") === 1 || couter.get("ordinal") === 1) {
21
- return "bar";
22
- }
23
- if (couter.get("temporal") === 1 && couter.get("quantitative") === 1) {
24
- return "line";
25
- }
26
- if (couter.get("quantitative") === 2) {
27
- return "point";
28
- }
29
- return "point";
30
- }