@kanaries/graphic-walker 0.3.13 → 0.3.15

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.
Files changed (37) hide show
  1. package/dist/assets/viewQuery.worker-03404216.js.map +1 -1
  2. package/dist/components/dataTable/index.d.ts +2 -2
  3. package/dist/components/timeoutImg.d.ts +5 -0
  4. package/dist/dataSource/utils.d.ts +1 -1
  5. package/dist/graphic-walker.es.js +24450 -24270
  6. package/dist/graphic-walker.es.js.map +1 -1
  7. package/dist/graphic-walker.umd.js +142 -136
  8. package/dist/graphic-walker.umd.js.map +1 -1
  9. package/dist/interfaces.d.ts +11 -0
  10. package/dist/lib/inferMeta.d.ts +1 -1
  11. package/dist/lib/viewQuery.d.ts +2 -2
  12. package/dist/services.d.ts +3 -3
  13. package/dist/store/index.d.ts +3 -8
  14. package/dist/store/visualSpecStore.d.ts +428 -2
  15. package/dist/utils/dataPrep.d.ts +2 -2
  16. package/dist/utils/is-plain-object.d.ts +2 -0
  17. package/dist/utils/save.d.ts +2 -2
  18. package/package.json +1 -1
  19. package/src/assets/kanaries.png +0 -0
  20. package/src/components/dataTable/index.tsx +154 -74
  21. package/src/components/dataTable/pagination.tsx +1 -1
  22. package/src/components/timeoutImg.tsx +29 -0
  23. package/src/components/toolbar/components.tsx +1 -0
  24. package/src/dataSource/utils.ts +15 -13
  25. package/src/fields/filterField/slider.tsx +1 -1
  26. package/src/fields/filterField/tabs.tsx +67 -18
  27. package/src/interfaces.ts +16 -0
  28. package/src/lib/inferMeta.ts +7 -4
  29. package/src/lib/viewQuery.ts +2 -2
  30. package/src/services.ts +3 -3
  31. package/src/store/index.tsx +46 -45
  32. package/src/store/visualSpecStore.ts +57 -5
  33. package/src/utils/dataPrep.ts +21 -28
  34. package/src/utils/is-plain-object.ts +33 -0
  35. package/src/utils/save.ts +2 -2
  36. package/src/visualSettings/index.tsx +21 -27
  37. package/src/assets/kanaries-logo.svg +0 -1
@@ -1,4 +1,5 @@
1
1
  import { IAnalyticType, IMutField, IRow, ISemanticType, IUncertainMutField } from '../interfaces';
2
+ import { getValueByKeyPath } from '../utils/dataPrep';
2
3
 
3
4
  const COMMON_TIME_FORMAT: RegExp[] = [
4
5
  /^\d{4}-\d{2}-\d{2}$/, // YYYY-MM-DD
@@ -55,12 +56,12 @@ function inferAnalyticTypeFromSemanticType(semanticType: ISemanticType): IAnalyt
55
56
  }
56
57
  }
57
58
 
58
- export function inferSemanticType(data: IRow[], fid: string): ISemanticType {
59
- const values = data.map((row) => row[fid]);
59
+ export function inferSemanticType(data: IRow[], path: string[]): ISemanticType {
60
+ const values = data.map((row) => getValueByKeyPath(row, path));
60
61
 
61
62
  let st: ISemanticType = isNumericArray(values) ? 'quantitative' : 'nominal';
62
63
  if (st === 'nominal') {
63
- if (isDateTimeArray(data.map((row) => `${row[fid]}`))) st = 'temporal';
64
+ if (isDateTimeArray(data.map((row) => `${getValueByKeyPath(row, path)}`))) st = 'temporal';
64
65
  }
65
66
  return st;
66
67
  }
@@ -70,7 +71,7 @@ export function inferMeta(props: { dataSource: IRow[]; fields: IUncertainMutFiel
70
71
  const finalFieldMetas: IMutField[] = [];
71
72
  for (let field of fields) {
72
73
  let semanticType: ISemanticType =
73
- field.semanticType === '?' ? inferSemanticType(dataSource, field.fid) : field.semanticType;
74
+ field.semanticType === '?' ? inferSemanticType(dataSource, field.path) : field.semanticType;
74
75
  let analyticType: IAnalyticType = inferAnalyticTypeFromSemanticType(semanticType);
75
76
 
76
77
  finalFieldMetas.push({
@@ -79,6 +80,8 @@ export function inferMeta(props: { dataSource: IRow[]; fields: IUncertainMutFiel
79
80
  name: field.name ? field.name : field.fid,
80
81
  analyticType,
81
82
  semanticType,
83
+ basename: field.basename || field.name || field.fid,
84
+ path: field.path,
82
85
  });
83
86
  }
84
87
  return finalFieldMetas;
@@ -1,4 +1,4 @@
1
- import { IMutField, IRow } from "../interfaces";
1
+ import { IField, IRow } from "../interfaces";
2
2
  import { aggregate } from "./op/aggregate";
3
3
  import { fold } from "./op/fold";
4
4
  import { IAggQuery, IBinQuery, IFoldQuery, IRawQuery } from "./interfaces";
@@ -6,7 +6,7 @@ import { bin } from "./op/bin";
6
6
 
7
7
  export type IViewQuery = IAggQuery | IFoldQuery | IBinQuery | IRawQuery;
8
8
 
9
- export function queryView (rawData: IRow[], metas: IMutField[], query: IViewQuery) {
9
+ export function queryView (rawData: IRow[], metas: IField[], query: IViewQuery) {
10
10
  switch (query.op) {
11
11
  case 'aggregate':
12
12
  return aggregate(rawData, query);
package/src/services.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { toJS } from 'mobx';
2
- import { IRow, IMutField, IFilterField, Specification } from './interfaces';
2
+ import { IRow, IMutField, IField, IFilterField, Specification } from './interfaces';
3
3
  /* eslint import/no-webpack-loader-syntax:0 */
4
4
  // @ts-ignore
5
5
  // eslint-disable-next-line
@@ -140,7 +140,7 @@ export const applyFilter = async (data: IRow[], filters: readonly IFilterField[]
140
140
  }
141
141
  };
142
142
 
143
- export const transformDataService = async (data: IRow[], columns: IMutField[]): Promise<IRow[]> => {
143
+ export const transformDataService = async (data: IRow[], columns: IField[]): Promise<IRow[]> => {
144
144
  if (columns.length === 0 || data.length === 0) return data;
145
145
  const worker = new TransformDataWorker();
146
146
  try {
@@ -156,7 +156,7 @@ export const transformDataService = async (data: IRow[], columns: IMutField[]):
156
156
  }
157
157
  }
158
158
 
159
- export const applyViewQuery = async (data: IRow[], metas: IMutField[], query: IViewQuery): Promise<IRow[]> => {
159
+ export const applyViewQuery = async (data: IRow[], metas: IField[], query: IViewQuery): Promise<IRow[]> => {
160
160
  const worker = new ViewQueryWorker();
161
161
  try {
162
162
  const res: IRow[] = await workerService(worker, {
@@ -1,62 +1,63 @@
1
- import React, { useContext } from 'react';
2
- import { CommonStore } from './commonStore'
3
- import { VizSpecStore } from './visualSpecStore'
1
+ import React, { useContext, useMemo, useEffect } from 'react';
2
+ import { CommonStore } from './commonStore';
3
+ import { VizSpecStore } from './visualSpecStore';
4
4
 
5
5
  export interface IGlobalStore {
6
6
  commonStore: CommonStore;
7
7
  vizStore: VizSpecStore;
8
8
  }
9
9
 
10
- const commonStore = new CommonStore();
11
- const vizStore = new VizSpecStore(commonStore);
12
-
13
- const initStore: IGlobalStore = {
14
- commonStore,
15
- vizStore
16
- }
10
+ const StoreDict: Record<string, IGlobalStore> = {};
11
+ const createStore = () => {
12
+ const commonStore = new CommonStore();
13
+ const vizStore = new VizSpecStore(commonStore);
14
+ return {
15
+ commonStore,
16
+ vizStore,
17
+ };
18
+ };
19
+ const getStore = (key?: string): IGlobalStore => {
20
+ if (key) {
21
+ if (!StoreDict[key]) StoreDict[key] = createStore();
22
+ return StoreDict[key];
23
+ } else {
24
+ return createStore();
25
+ }
26
+ };
17
27
 
18
28
  const StoreContext = React.createContext<IGlobalStore>(null!);
19
-
20
- export function destroyGWStore() {
21
- initStore.commonStore.destroy();
22
- initStore.vizStore.destroy();
23
- }
24
-
25
- export function rebootGWStore() {
26
- const cs = new CommonStore();
27
- const vs = new VizSpecStore(cs);
28
- initStore.commonStore = cs;
29
- initStore.vizStore = vs;
30
- }
31
-
32
29
  interface StoreWrapperProps {
33
- keepAlive?: boolean;
30
+ keepAlive?: boolean | string;
34
31
  storeRef?: React.MutableRefObject<IGlobalStore | null>;
32
+ children?: React.ReactNode;
35
33
  }
36
- export class StoreWrapper extends React.Component<StoreWrapperProps> {
37
- constructor(props: StoreWrapperProps) {
38
- super(props)
34
+
35
+ const noop = () => {};
36
+
37
+ export const StoreWrapper = (props: StoreWrapperProps) => {
38
+ const storeKey = props.keepAlive ? `${props.keepAlive}` : '';
39
+ const store = useMemo(() => getStore(storeKey), [storeKey]);
40
+ useEffect(() => {
39
41
  if (props.storeRef) {
40
- props.storeRef.current = initStore;
42
+ const ref = props.storeRef;
43
+ ref.current = store;
44
+ return () => {
45
+ ref.current = null;
46
+ };
41
47
  }
42
- if (props.keepAlive) {
43
- rebootGWStore();
44
- }
45
- }
46
- componentWillUnmount() {
47
- if (!this.props.keepAlive) {
48
- if (this.props.storeRef) {
49
- this.props.storeRef.current = null;
50
- }
51
- destroyGWStore();
48
+ return noop;
49
+ }, [props.storeRef]);
50
+ useEffect(() => {
51
+ if (!storeKey) {
52
+ return () => {
53
+ store.commonStore.destroy();
54
+ store.vizStore.destroy();
55
+ };
52
56
  }
53
- }
54
- render() {
55
- return <StoreContext.Provider value={initStore}>
56
- { this.props.children }
57
- </StoreContext.Provider>
58
- }
59
- }
57
+ return noop;
58
+ }, [storeKey]);
59
+ return <StoreContext.Provider value={store}>{props.children}</StoreContext.Provider>;
60
+ };
60
61
 
61
62
  export function useGlobalStore() {
62
63
  return useContext(StoreContext);
@@ -1,6 +1,6 @@
1
1
  import { IReactionDisposer, makeAutoObservable, observable, reaction, toJS } from "mobx";
2
2
  import produce from "immer";
3
- import { DataSet, DraggableFieldState, IFilterRule, IViewField, IVisSpec, IVisualConfig, Specification } from "../interfaces";
3
+ import { DataSet, DraggableFieldState, IFilterRule, IViewField, IVisSpec, IVisSpecForExport, IFilterFieldForExport, IVisualConfig, Specification } from "../interfaces";
4
4
  import { CHANNEL_LIMIT, GEMO_TYPES, MetaFieldKeys } from "../config";
5
5
  import { VisSpecWithHistory } from "../models/visSpecHistory";
6
6
  import { IStoInfo, dumpsGWPureSpec, parseGWContent, parseGWPureSpec, stringifyGWContent } from "../utils/save";
@@ -88,7 +88,7 @@ type DeepReadonly<T extends Record<keyof any, any>> = {
88
88
  readonly [K in keyof T]: T[K] extends Record<keyof any, any> ? DeepReadonly<T[K]> : T[K];
89
89
  };
90
90
 
91
- const forwardVisualConfigs = (backwards: ReturnType<typeof parseGWContent>["specList"]): IVisSpec[] => {
91
+ const forwardVisualConfigs = (backwards: ReturnType<typeof parseGWContent>["specList"]): IVisSpecForExport[] => {
92
92
  return backwards.map((content) => ({
93
93
  ...content,
94
94
  config: {
@@ -336,6 +336,7 @@ export class VizSpecStore {
336
336
  dragId: uniqueId(),
337
337
  fid: f.fid,
338
338
  name: f.name || f.fid,
339
+ basename: f.basename || f.name || f.fid,
339
340
  semanticType: f.semanticType,
340
341
  analyticType: f.analyticType,
341
342
  }));
@@ -345,6 +346,7 @@ export class VizSpecStore {
345
346
  dragId: uniqueId(),
346
347
  fid: f.fid,
347
348
  name: f.name || f.fid,
349
+ basename: f.basename || f.name || f.fid,
348
350
  analyticType: f.analyticType,
349
351
  semanticType: f.semanticType,
350
352
  aggName: "sum",
@@ -701,15 +703,15 @@ export class VizSpecStore {
701
703
  return stringifyGWContent({
702
704
  datasets: toJS(this.commonStore.datasets),
703
705
  dataSources: this.commonStore.dataSources,
704
- specList: pureVisList,
706
+ specList: this.visSpecEncoder(pureVisList),
705
707
  });
706
708
  }
707
709
  public exportViewSpec() {
708
710
  const pureVisList = dumpsGWPureSpec(this.visList);
709
- return pureVisList
711
+ return this.visSpecEncoder(pureVisList);
710
712
  }
711
713
  public importStoInfo (stoInfo: IStoInfo) {
712
- this.visList = parseGWPureSpec(forwardVisualConfigs(stoInfo.specList));
714
+ this.visList = parseGWPureSpec(this.visSpecDecoder(forwardVisualConfigs(stoInfo.specList)));
713
715
  this.visIndex = 0;
714
716
  this.commonStore.datasets = stoInfo.datasets;
715
717
  this.commonStore.dataSources = stoInfo.dataSources;
@@ -719,4 +721,54 @@ export class VizSpecStore {
719
721
  const content = parseGWContent(raw);
720
722
  this.importStoInfo(content);
721
723
  }
724
+
725
+ private visSpecEncoder(visList: IVisSpec[]): IVisSpecForExport[] {
726
+ const updatedVisList = visList.map((visSpec) => {
727
+ const updatedFilters = visSpec.encodings.filters.map((filter) => {
728
+ if (filter.rule?.type === "one of") {
729
+ const rule = {
730
+ ...filter.rule,
731
+ value: Array.from(filter.rule.value)
732
+ }
733
+ return {
734
+ ...filter,
735
+ rule
736
+ }
737
+ }
738
+ return filter as IFilterFieldForExport;
739
+ });
740
+ return {
741
+ ...visSpec,
742
+ encodings: {
743
+ ...visSpec.encodings,
744
+ filters: updatedFilters
745
+ }
746
+ }
747
+ });
748
+ return updatedVisList;
749
+ }
750
+ private visSpecDecoder(visList: IVisSpecForExport[]): IVisSpec[] {
751
+ const updatedVisList = visList.map((visSpec) => {
752
+ const updatedFilters = visSpec.encodings.filters.map((filter) => {
753
+ if (filter.rule?.type === "one of" && Array.isArray(filter.rule.value)) {
754
+ return {
755
+ ...filter,
756
+ rule: {
757
+ ...filter.rule,
758
+ value: new Set(filter.rule.value)
759
+ }
760
+ }
761
+ }
762
+ return filter;
763
+ })
764
+ return {
765
+ ...visSpec,
766
+ encodings: {
767
+ ...visSpec.encodings,
768
+ filters: updatedFilters
769
+ }
770
+ } as IVisSpec;
771
+ });
772
+ return updatedVisList;
773
+ }
722
774
  }
@@ -1,11 +1,12 @@
1
1
  import { IMutField, IRow } from "../interfaces";
2
+ import { isPlainObject } from "./is-plain-object";
2
3
 
3
- function updateRowKeys(data: IRow[], keyEncodeList: {from: string; to: string}[]): IRow[] {
4
+ function updateRowKeys(data: IRow[], keyEncodeList: { from: string[]; to: string }[]): IRow[] {
4
5
  return data.map((row) => {
5
6
  const newRow: IRow = {};
6
7
  for (let k in keyEncodeList) {
7
8
  const { from, to } = keyEncodeList[k];
8
- newRow[to] = row[from];
9
+ newRow[to] = getValueByKeyPath(row, from);
9
10
  }
10
11
  return newRow;
11
12
  });
@@ -13,9 +14,9 @@ function updateRowKeys(data: IRow[], keyEncodeList: {from: string; to: string}[]
13
14
 
14
15
  /**
15
16
  * parse column id(key) to a safe string
16
- * @param metas
17
+ * @param metas
17
18
  */
18
- function parseColumnMetas (metas: IMutField[]) {
19
+ function parseColumnMetas(metas: IMutField[]) {
19
20
  return metas.map((meta, i) => {
20
21
  const safeKey = `gwc_${i}`;
21
22
  return {
@@ -26,42 +27,34 @@ function parseColumnMetas (metas: IMutField[]) {
26
27
  });
27
28
  }
28
29
 
29
- export function guardDataKeys (data: IRow[], metas: IMutField[]): {
30
+ export function guardDataKeys(
31
+ data: IRow[],
32
+ metas: IMutField[]
33
+ ): {
30
34
  safeData: IRow[];
31
35
  safeMetas: IMutField[];
32
36
  } {
33
- const safeMetas = parseColumnMetas(metas)
37
+ const safeMetas = parseColumnMetas(metas);
34
38
  const keyEncodeList = safeMetas.map((f, i) => ({
35
- from: metas[i].fid,
36
- to: f.fid
39
+ from: metas[i].path ?? [metas[i].fid],
40
+ to: f.fid,
37
41
  }));
38
42
  const safeData = updateRowKeys(data, keyEncodeList);
39
43
  return {
40
44
  safeData,
41
- safeMetas
42
- }
45
+ safeMetas,
46
+ };
43
47
  }
44
-
45
- const SPLITOR = '__'
46
- export function flatNestKeys (object: any): string[] {
47
- const keys = Object.keys(object);
48
- let flatColKeys: string[] = [];
49
- for (let key of keys) {
50
- if (typeof object[key] === 'object') {
51
- const subKeys = flatNestKeys(object[key]);
52
- flatColKeys = flatColKeys.concat(subKeys.map(k => `${key}${SPLITOR}${k}`));
53
- } else {
54
- flatColKeys.push(key)
55
- }
56
- }
57
- return flatColKeys;
48
+ export function flatKeys(obj: Object, prefixKeys: string[] = []): string[][] {
49
+ return Object.keys(obj).flatMap((k) =>
50
+ isPlainObject(obj[k]) ? flatKeys(obj[k], prefixKeys.concat(k)) : [prefixKeys.concat(k)]
51
+ );
58
52
  }
59
53
 
60
- export function getValueByKeyPath (object: any, keyPath: string): any {
61
- const keys = keyPath.split(SPLITOR);
54
+ export function getValueByKeyPath(object: any, keyPath: string[]): any {
62
55
  let value = object;
63
- for (let key of keys) {
56
+ for (let key of keyPath) {
64
57
  value = value[key];
65
58
  }
66
59
  return value;
67
- }
60
+ }
@@ -0,0 +1,33 @@
1
+ /*!
2
+ * is-plain-object <https://github.com/jonschlinkert/is-plain-object>
3
+ *
4
+ * Copyright (c) 2014-2017, Jon Schlinkert.
5
+ * Released under the MIT License.
6
+ */
7
+ function isObject(o: any) {
8
+ return Object.prototype.toString.call(o) === '[object Object]';
9
+ }
10
+
11
+ function isPlainObject(o: any) {
12
+ var ctor, prot;
13
+
14
+ if (isObject(o) === false) return false;
15
+
16
+ // If has modified constructor
17
+ ctor = o.constructor;
18
+ if (ctor === undefined) return true;
19
+
20
+ // If has modified prototype
21
+ prot = ctor.prototype;
22
+ if (isObject(prot) === false) return false;
23
+
24
+ // If constructor does not have an Object-specific method
25
+ if (prot.hasOwnProperty('isPrototypeOf') === false) {
26
+ return false;
27
+ }
28
+
29
+ // Most likely a plain Object
30
+ return true;
31
+ }
32
+
33
+ export { isPlainObject };
package/src/utils/save.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { IDataSet, IDataSource, IVisSpec } from "../interfaces";
1
+ import { IDataSet, IDataSource, IVisSpec, IVisSpecForExport } from "../interfaces";
2
2
  import { VisSpecWithHistory } from "../models/visSpecHistory";
3
3
 
4
4
  export function dumpsGWPureSpec(list: VisSpecWithHistory[]): IVisSpec[] {
@@ -12,7 +12,7 @@ export function parseGWPureSpec(list: IVisSpec[]): VisSpecWithHistory[] {
12
12
  export interface IStoInfo {
13
13
  datasets: IDataSet[];
14
14
  specList: {
15
- [K in keyof IVisSpec]: K extends "config" ? Partial<IVisSpec[K]> : IVisSpec[K];
15
+ [K in keyof IVisSpecForExport]: K extends "config" ? Partial<IVisSpecForExport[K]> : IVisSpecForExport[K];
16
16
  }[];
17
17
  dataSources: IDataSource[];
18
18
  }
@@ -33,7 +33,8 @@ import Toolbar, { ToolbarItemProps } from '../components/toolbar';
33
33
  import { ButtonWithShortcut } from './menubar';
34
34
  import { useCurrentMediaTheme } from '../utils/media';
35
35
  import throttle from '../utils/throttle';
36
- import KanariesLogo from '../assets/kanaries-logo.svg';
36
+ import KanariesLogo from '../assets/kanaries.png';
37
+ import { ImageWithFallback } from '../components/timeoutImg';
37
38
 
38
39
  const Invisible = styled.div`
39
40
  clip: rect(1px, 1px, 1px, 1px);
@@ -101,30 +102,8 @@ const VisualSettings: React.FC<IVisualSettings> = ({
101
102
 
102
103
  const dark = useCurrentMediaTheme(darkModePreference) === 'dark';
103
104
 
104
- console.log('kanaries logo', KanariesLogo);
105
-
106
105
  const items = useMemo<ToolbarItemProps[]>(() => {
107
106
  const builtInItems = [
108
- {
109
- key: 'kanaries',
110
- label: 'kanaries',
111
- icon: () => (
112
- // Kanaries brand info is not allowed to be removed or changed unless you are granted with special permission.
113
- <a href="https://kanaries.net" target="_blank">
114
- <img
115
- id="kanaries-logo"
116
- className="m-1"
117
- src="https://imagedelivery.net/tSvh1MGEu9IgUanmf58srQ/b6bc899f-a129-4c3a-d08f-d406166d0c00/public"
118
- alt="kanaries"
119
- onError={(e) => {
120
- // @ts-ignore
121
- e.target.src = KanariesLogo;
122
- }}
123
- />
124
- </a>
125
- ),
126
- },
127
- '-',
128
107
  {
129
108
  key: 'undo',
130
109
  label: 'undo (Ctrl + Z)',
@@ -512,14 +491,29 @@ const VisualSettings: React.FC<IVisualSettings> = ({
512
491
  commonStore.setShowCodeExportPanel(true);
513
492
  },
514
493
  },
494
+ ...(extra.length === 0 ? [] : ['-', ...extra]),
495
+ '-',
496
+ {
497
+ key: 'kanaries',
498
+ label: 'kanaries',
499
+ icon: () => (
500
+ // Kanaries brand info is not allowed to be removed or changed unless you are granted with special permission.
501
+ <a href="https://docs.kanaries.net" target="_blank">
502
+ <ImageWithFallback
503
+ id="kanaries-logo"
504
+ className="p-1.5 opacity-70 hover:opacity-100"
505
+ src="https://imagedelivery.net/tSvh1MGEu9IgUanmf58srQ/b6bc899f-a129-4c3a-d08f-d406166d0c00/public"
506
+ fallbackSrc={KanariesLogo}
507
+ timeout={1000}
508
+ alt="kanaries"
509
+ />
510
+ </a>
511
+ ),
512
+ },
515
513
  ] as ToolbarItemProps[];
516
514
 
517
515
  const items = builtInItems.filter((item) => typeof item === 'string' || !exclude.includes(item.key));
518
516
 
519
- if (extra.length > 0) {
520
- items.push('-', ...extra);
521
- }
522
-
523
517
  return items;
524
518
  }, [
525
519
  vizStore,