@kanaries/graphic-walker 0.4.1 → 0.4.2

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 (65) hide show
  1. package/dist/App.d.ts +5 -1
  2. package/dist/assets/buildMetricTable.worker-5555966a.js.map +1 -0
  3. package/dist/components/leafletRenderer/ChoroplethRenderer.d.ts +21 -0
  4. package/dist/components/leafletRenderer/POIRenderer.d.ts +19 -0
  5. package/dist/components/leafletRenderer/encodings.d.ts +7 -0
  6. package/dist/components/leafletRenderer/geoConfigPanel.d.ts +3 -0
  7. package/dist/components/leafletRenderer/index.d.ts +14 -0
  8. package/dist/components/leafletRenderer/tooltip.d.ts +9 -0
  9. package/dist/components/leafletRenderer/utils.d.ts +2 -0
  10. package/dist/components/pivotTable/index.d.ts +2 -1
  11. package/dist/components/pivotTable/inteface.d.ts +6 -2
  12. package/dist/components/pivotTable/leftTree.d.ts +1 -0
  13. package/dist/components/pivotTable/topTree.d.ts +2 -0
  14. package/dist/components/pivotTable/utils.d.ts +1 -2
  15. package/dist/config.d.ts +3 -2
  16. package/dist/graphic-walker.es.js +37775 -30385
  17. package/dist/graphic-walker.es.js.map +1 -1
  18. package/dist/graphic-walker.umd.js +145 -137
  19. package/dist/graphic-walker.umd.js.map +1 -1
  20. package/dist/interfaces.d.ts +27 -0
  21. package/dist/renderer/specRenderer.d.ts +2 -1
  22. package/dist/services.d.ts +7 -1
  23. package/dist/store/commonStore.d.ts +6 -0
  24. package/dist/store/visualSpecStore.d.ts +180 -4
  25. package/dist/utils/save.d.ts +1 -0
  26. package/dist/workers/buildPivotTable.d.ts +7 -0
  27. package/package.json +14 -2
  28. package/src/App.tsx +18 -4
  29. package/src/components/leafletRenderer/ChoroplethRenderer.tsx +293 -0
  30. package/src/components/leafletRenderer/POIRenderer.tsx +170 -0
  31. package/src/components/leafletRenderer/encodings.ts +194 -0
  32. package/src/components/leafletRenderer/geoConfigPanel.tsx +197 -0
  33. package/src/components/leafletRenderer/index.tsx +67 -0
  34. package/src/components/leafletRenderer/tooltip.tsx +24 -0
  35. package/src/components/leafletRenderer/utils.ts +52 -0
  36. package/src/components/pivotTable/index.tsx +171 -67
  37. package/src/components/pivotTable/inteface.ts +6 -2
  38. package/src/components/pivotTable/leftTree.tsx +24 -11
  39. package/src/components/pivotTable/metricTable.tsx +15 -10
  40. package/src/components/pivotTable/topTree.tsx +50 -17
  41. package/src/components/pivotTable/utils.ts +70 -11
  42. package/src/components/visualConfig/index.tsx +17 -1
  43. package/src/config.ts +27 -16
  44. package/src/dataSource/table.tsx +7 -11
  45. package/src/fields/aestheticFields.tsx +4 -0
  46. package/src/fields/fieldsContext.tsx +3 -0
  47. package/src/fields/posFields/index.tsx +8 -2
  48. package/src/global.d.ts +4 -4
  49. package/src/index.tsx +11 -9
  50. package/src/interfaces.ts +34 -0
  51. package/src/locales/en-US.json +27 -2
  52. package/src/locales/ja-JP.json +27 -2
  53. package/src/locales/zh-CN.json +27 -2
  54. package/src/renderer/hooks.ts +1 -1
  55. package/src/renderer/index.tsx +24 -1
  56. package/src/renderer/pureRenderer.tsx +26 -13
  57. package/src/renderer/specRenderer.tsx +45 -30
  58. package/src/services.ts +32 -23
  59. package/src/shadow-dom.tsx +7 -0
  60. package/src/store/commonStore.ts +29 -1
  61. package/src/store/visualSpecStore.ts +38 -23
  62. package/src/utils/save.ts +28 -1
  63. package/src/visualSettings/index.tsx +58 -6
  64. package/src/workers/buildMetricTable.worker.js +27 -0
  65. package/src/workers/buildPivotTable.ts +27 -0
@@ -1,6 +1,7 @@
1
1
  import { DataSet, Filters, IDataSet, IDataSetInfo, IDataSource, IMutField, IRow, ISegmentKey } from '../interfaces';
2
2
  import { makeAutoObservable, observable, toJS } from 'mobx';
3
3
  import { transData } from '../dataSource/utils';
4
+ import { INestNode } from '../components/pivotTable/inteface';
4
5
 
5
6
  export class CommonStore {
6
7
  public datasets: IDataSet[] = [];
@@ -15,15 +16,18 @@ export class CommonStore {
15
16
  public showDataConfig: boolean = false;
16
17
  public showCodeExportPanel: boolean = false;
17
18
  public showVisualConfigPanel: boolean = false;
19
+ public showGeoJSONConfigPanel: boolean = false;
18
20
  public filters: Filters = {};
19
21
  public segmentKey: ISegmentKey = ISegmentKey.vis;
22
+ public tableCollapsedHeaderMap: Map<string, INestNode["path"]> = new Map();
20
23
  constructor () {
21
24
  this.datasets = [];
22
25
  this.dataSources = [];
23
26
  makeAutoObservable(this, {
24
27
  dataSources: observable.ref,
25
28
  tmpDataSource: observable.ref,
26
- filters: observable.ref
29
+ filters: observable.ref,
30
+ tableCollapsedHeaderMap: observable.ref,
27
31
  });
28
32
  }
29
33
  public get currentDataset (): DataSet {
@@ -68,6 +72,30 @@ export class CommonStore {
68
72
  public setShowVisualConfigPanel (show: boolean) {
69
73
  this.showVisualConfigPanel = show;
70
74
  }
75
+ public updateTableCollapsedHeader (node: INestNode) {
76
+ const {uniqueKey, height} = node;
77
+ if (height < 1) return;
78
+ const updatedMap = new Map(this.tableCollapsedHeaderMap)
79
+ // if some child nodes of the incoming node are collapsed, remove them first
80
+ updatedMap.forEach((existingPath, existingKey) => {
81
+ if (existingKey.startsWith(uniqueKey) && existingKey.length > uniqueKey.length) {
82
+ updatedMap.delete(existingKey)
83
+ }
84
+ })
85
+ if (!updatedMap.has(uniqueKey)) {
86
+ updatedMap.set(uniqueKey, node.path)
87
+ } else {
88
+ updatedMap.delete(uniqueKey)
89
+ }
90
+ this.tableCollapsedHeaderMap = updatedMap
91
+ }
92
+ public resetTableCollapsedHeader () {
93
+ const updatedMap: Map<string, INestNode["path"]> = new Map();
94
+ this.tableCollapsedHeaderMap = updatedMap;
95
+ }
96
+ public setShowGeoJSONConfigPanel (show: boolean) {
97
+ this.showGeoJSONConfigPanel = show;
98
+ }
71
99
  public closeEmbededMenu () {
72
100
  this.vizEmbededMenu.show = false;
73
101
  }
@@ -1,9 +1,12 @@
1
1
  import { IReactionDisposer, makeAutoObservable, observable, computed, reaction, toJS } from 'mobx';
2
2
  import produce from 'immer';
3
+ import { feature } from 'topojson-client';
4
+ import type { FeatureCollection } from "geojson";
3
5
  import {
4
6
  DataSet,
5
7
  DraggableFieldState,
6
8
  IFilterRule,
9
+ IGeographicData,
7
10
  ISortMode,
8
11
  IStackMode,
9
12
  IViewField,
@@ -25,6 +28,7 @@ import {
25
28
  initVisualConfig,
26
29
  forwardVisualConfigs,
27
30
  visSpecDecoder,
31
+ initEncoding,
28
32
  } from '../utils/save';
29
33
  import { CommonStore } from './commonStore';
30
34
  import { createCountField } from '../utils';
@@ -68,24 +72,6 @@ function geomAdapter(geom: string) {
68
72
  }
69
73
  }
70
74
 
71
- export function initEncoding(): DraggableFieldState {
72
- return {
73
- dimensions: [],
74
- measures: [],
75
- rows: [],
76
- columns: [],
77
- color: [],
78
- opacity: [],
79
- size: [],
80
- shape: [],
81
- radius: [],
82
- theta: [],
83
- details: [],
84
- filters: [],
85
- text: [],
86
- };
87
- }
88
-
89
75
  function stackValueTransform(vlValue: string | undefined | null): IStackMode {
90
76
  if (vlValue === 'center') return 'center';
91
77
  if (vlValue === 'normalize') return 'normalize';
@@ -117,6 +103,12 @@ function isDraggableStateEmpty(state: DeepReadonly<DraggableFieldState>): boolea
117
103
  return Object.values(state).every((value) => value.length === 0);
118
104
  }
119
105
 
106
+ function withTimeout<T extends any[], U>(f: (...args: T) => Promise<U>, timeout: number){
107
+ return (...args: T) => Promise.race([f(...args), new Promise<never>((_, reject) => {
108
+ setTimeout(() => reject(new Error('timeout')), timeout)
109
+ })])
110
+ }
111
+
120
112
  export class VizSpecStore {
121
113
  // public fields: IViewField[] = [];
122
114
  private commonStore: CommonStore;
@@ -159,7 +151,7 @@ export class VizSpecStore {
159
151
  public canRedo = false;
160
152
  public editingFilterIdx: number | null = null;
161
153
  // TODO
162
- public computationFuction: IComputationFunction = async () => [];
154
+ public computationFunction: IComputationFunction = async () => [];
163
155
  constructor(commonStore: CommonStore) {
164
156
  this.commonStore = commonStore;
165
157
  this.draggableFieldState = initEncoding();
@@ -174,7 +166,7 @@ export class VizSpecStore {
174
166
  );
175
167
  makeAutoObservable(this, {
176
168
  visList: observable.shallow,
177
- computationFuction: observable.ref,
169
+ computationFunction: observable.ref,
178
170
  // @ts-expect-error private fields are not supported
179
171
  reactions: false,
180
172
  });
@@ -387,10 +379,12 @@ export class VizSpecStore {
387
379
  public setVisualConfig<K extends keyof IVisualConfig>(configKey: K, value: IVisualConfig[K]) {
388
380
  this.useMutable(({ config }) => {
389
381
  switch (true) {
390
- case ['defaultAggregated', 'defaultStack', 'showActions', 'interactiveScale'].includes(configKey): {
382
+ case ['defaultAggregated', 'defaultStack', 'showActions', 'interactiveScale', 'scaleIncludeUnmatchedChoropleth'].includes(configKey): {
391
383
  return ((config as unknown as { [k: string]: boolean })[configKey] = Boolean(value));
392
384
  }
393
385
  case configKey === 'geoms' && Array.isArray(value):
386
+ case configKey === "showTableSummary":
387
+ case configKey === "coordSystem":
394
388
  case configKey === 'size' && typeof value === 'object':
395
389
  case configKey === 'sorted':
396
390
  case configKey === 'zeroScale':
@@ -511,6 +505,10 @@ export class VizSpecStore {
511
505
 
512
506
  encodings.columns = encodings.rows;
513
507
  encodings.rows = fieldsInCup as typeof encodings.rows; // assume this as writable
508
+
509
+ const fieldsInCup2 = encodings.longitude;
510
+ encodings.longitude = encodings.latitude;
511
+ encodings.latitude = fieldsInCup2 as typeof encodings.latitude; // assume this as writable
514
512
  });
515
513
  }
516
514
  public createBinField(stateKey: keyof DraggableFieldState, index: number, binType: 'bin' | 'binCount'): string {
@@ -819,6 +817,23 @@ export class VizSpecStore {
819
817
  const content = parseGWContent(raw);
820
818
  this.importStoInfo(content);
821
819
  }
820
+
821
+ public setGeographicData(data: IGeographicData, geoKey: string) {
822
+ const geoJSON = data.type === 'GeoJSON' ? data.data : feature(data.data, data.objectKey || Object.keys(data.data.objects)[0]) as unknown as FeatureCollection;
823
+ if (!('features' in geoJSON)) {
824
+ console.error('Invalid GeoJSON: GeoJSON must be a FeatureCollection, but got', geoJSON);
825
+ return;
826
+ }
827
+ this.useMutable(({ config }) => {
828
+ config.geojson = geoJSON;
829
+ config.geoKey = geoKey;
830
+ });
831
+ }
832
+ public updateGeoKey(key: string) {
833
+ this.useMutable(({ config }) => {
834
+ config.geoKey = key;
835
+ });
836
+ }
822
837
 
823
838
  private visSpecEncoder(visList: IVisSpec[]): IVisSpecForExport[] {
824
839
  const updatedVisList = visList.map((visSpec) => {
@@ -876,7 +891,7 @@ export class VizSpecStore {
876
891
  );
877
892
  }
878
893
 
879
- public setComputationFunction(f: IComputationFunction) {
880
- this.computationFuction = f;
894
+ public setComputationFunction(f: IComputationFunction, timeout = 60000) {
895
+ this.computationFunction = withTimeout(f, timeout);
881
896
  }
882
897
  }
package/src/utils/save.ts CHANGED
@@ -11,15 +11,40 @@ export function parseGWPureSpec(list: IVisSpec[]): VisSpecWithHistory[] {
11
11
  return list.map((l) => new VisSpecWithHistory(l));
12
12
  }
13
13
 
14
+ export function initEncoding(): DraggableFieldState {
15
+ return {
16
+ dimensions: [],
17
+ measures: [],
18
+ rows: [],
19
+ columns: [],
20
+ color: [],
21
+ opacity: [],
22
+ size: [],
23
+ shape: [],
24
+ radius: [],
25
+ theta: [],
26
+ longitude: [],
27
+ latitude: [],
28
+ geoId: [],
29
+ details: [],
30
+ filters: [],
31
+ text: [],
32
+ };
33
+ }
34
+
14
35
  export function initVisualConfig(): IVisualConfig {
36
+ const [ geom ] = GEMO_TYPES.generic;
15
37
  return {
16
38
  defaultAggregated: true,
17
- geoms: [GEMO_TYPES[0]!],
39
+ geoms: [geom],
40
+ showTableSummary: false,
41
+ coordSystem: 'generic',
18
42
  stack: 'stack',
19
43
  showActions: false,
20
44
  interactiveScale: false,
21
45
  sorted: 'none',
22
46
  zeroScale: true,
47
+ scaleIncludeUnmatchedChoropleth: false,
23
48
  background: undefined,
24
49
  size: {
25
50
  mode: "auto",
@@ -31,6 +56,7 @@ export function initVisualConfig(): IVisualConfig {
31
56
  timeFormat: undefined,
32
57
  normalizedNumberFormat: undefined,
33
58
  },
59
+ geoKey: 'name',
34
60
  resolve: {
35
61
  x: false,
36
62
  y: false,
@@ -60,6 +86,7 @@ export function visSpecDecoder(visList: IVisSpecForExport[]): IVisSpec[] {
60
86
  return {
61
87
  ...visSpec,
62
88
  encodings: {
89
+ ...initEncoding(),
63
90
  ...visSpec.encodings,
64
91
  filters: updatedFilters,
65
92
  },
@@ -19,6 +19,11 @@ import {
19
19
  LightBulbIcon,
20
20
  CodeBracketSquareIcon,
21
21
  Cog6ToothIcon,
22
+ TableCellsIcon,
23
+ MapPinIcon,
24
+ GlobeAltIcon,
25
+ RectangleGroupIcon,
26
+ GlobeAmericasIcon,
22
27
  HashtagIcon,
23
28
  } from '@heroicons/react/24/outline';
24
29
  import { observer } from 'mobx-react-lite';
@@ -26,7 +31,7 @@ import React, { SVGProps, useCallback, useMemo } from 'react';
26
31
  import styled from 'styled-components';
27
32
  import { useTranslation } from 'react-i18next';
28
33
  import { ResizeDialog } from '../components/sizeSetting';
29
- import { GEMO_TYPES, STACK_MODE, CHART_LAYOUT_TYPE } from '../config';
34
+ import { GEMO_TYPES, STACK_MODE, CHART_LAYOUT_TYPE, COORD_TYPES } from '../config';
30
35
  import { useGlobalStore } from '../store';
31
36
  import { IStackMode, IDarkMode } from '../interfaces';
32
37
  import { IReactVegaHandler } from '../vis/react-vega';
@@ -82,6 +87,8 @@ const VisualSettings: React.FC<IVisualSettings> = ({
82
87
  const {
83
88
  defaultAggregated,
84
89
  geoms: [markType],
90
+ showTableSummary,
91
+ coordSystem = 'generic',
85
92
  stack,
86
93
  interactiveScale,
87
94
  size: { mode: sizeMode, width, height },
@@ -163,7 +170,7 @@ const VisualSettings: React.FC<IVisualSettings> = ({
163
170
  color: 'rgb(294,115,22)',
164
171
  },
165
172
  },
166
- options: GEMO_TYPES.map((g) => ({
173
+ options: GEMO_TYPES[coordSystem].map((g) => ({
167
174
  key: g,
168
175
  label: tGlobal(`constant.mark_type.${g}`),
169
176
  icon: {
@@ -348,6 +355,8 @@ const VisualSettings: React.FC<IVisualSettings> = ({
348
355
  />
349
356
  </svg>
350
357
  ),
358
+ poi: MapPinIcon,
359
+ choropleth: RectangleGroupIcon,
351
360
  }[g],
352
361
  })),
353
362
  value: markType,
@@ -393,6 +402,15 @@ const VisualSettings: React.FC<IVisualSettings> = ({
393
402
  icon: BarsArrowDownIcon,
394
403
  onClick: () => vizStore.applyDefaultSort('descending'),
395
404
  },
405
+ {
406
+ key: 'table:summary',
407
+ label: t('table.summary'),
408
+ icon: TableCellsIcon,
409
+ checked: showTableSummary,
410
+ onChange: checked => {
411
+ vizStore.setVisualConfig('showTableSummary', checked);
412
+ },
413
+ },
396
414
  '-',
397
415
  {
398
416
  key: 'axes_resize',
@@ -438,6 +456,34 @@ const VisualSettings: React.FC<IVisualSettings> = ({
438
456
  ),
439
457
  },
440
458
  '-',
459
+ {
460
+ key: 'coord_system',
461
+ label: tGlobal('constant.coord_system.__enum__'),
462
+ icon: StopIcon,
463
+ options: COORD_TYPES.map(c => ({
464
+ key: c,
465
+ label: tGlobal(`constant.coord_system.${c}`),
466
+ icon: {
467
+ generic: (props: SVGProps<SVGSVGElement>) => <svg stroke="currentColor" fill="none" strokeWidth="1.5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden {...props}><path strokeLinecap="round" strokeLinejoin="round" d="M2 12h20M12 2v20" /><path strokeLinecap="round" strokeLinejoin="round" d="M12 7h2M12 16h2M7 12v-2M16 12v-2"/></svg>,
468
+ geographic: GlobeAltIcon,
469
+ }[c],
470
+ })),
471
+ value: coordSystem,
472
+ onSelect: value => {
473
+ const coord = value as typeof COORD_TYPES[number];
474
+ vizStore.setVisualConfig('coordSystem', coord);
475
+ vizStore.setVisualConfig('geoms', [GEMO_TYPES[coord][0]]);
476
+ },
477
+ },
478
+ coordSystem === 'geographic' && markType === 'choropleth' && {
479
+ key: 'geojson',
480
+ label: t('button.geojson'),
481
+ icon: GlobeAmericasIcon,
482
+ onClick: () => {
483
+ commonStore.setShowGeoJSONConfigPanel(true);
484
+ },
485
+ },
486
+ '-',
441
487
  {
442
488
  key: 'debug',
443
489
  label: t('toggle.debug'),
@@ -447,7 +493,7 @@ const VisualSettings: React.FC<IVisualSettings> = ({
447
493
  vizStore.setVisualConfig('showActions', checked);
448
494
  },
449
495
  },
450
- {
496
+ ...coordSystem === 'generic' ?[{
451
497
  key: 'export_chart',
452
498
  label: t('button.export_chart'),
453
499
  icon: PhotoIcon,
@@ -477,7 +523,7 @@ const VisualSettings: React.FC<IVisualSettings> = ({
477
523
  </button>
478
524
  </FormContainer>
479
525
  ),
480
- },
526
+ }]:[],
481
527
  {
482
528
  key: 'config',
483
529
  label: 'config',
@@ -529,17 +575,23 @@ const VisualSettings: React.FC<IVisualSettings> = ({
529
575
  </a>
530
576
  ),
531
577
  },
532
- ] as ToolbarItemProps[];
578
+ ].filter(Boolean) as ToolbarItemProps[];
533
579
 
534
580
  const items = builtInItems.filter((item) => typeof item === 'string' || !exclude.includes(item.key));
535
581
 
536
- return items;
582
+ switch (vizStore.visualConfig.geoms[0]) {
583
+ case 'table':
584
+ return items;
585
+ default:
586
+ return items.filter(item => typeof item === 'string' || item.key !== 'table:summary');
587
+ }
537
588
  }, [
538
589
  vizStore,
539
590
  canUndo,
540
591
  canRedo,
541
592
  defaultAggregated,
542
593
  markType,
594
+ coordSystem,
543
595
  stack,
544
596
  interactiveScale,
545
597
  sizeMode,
@@ -0,0 +1,27 @@
1
+ /* eslint no-restricted-globals: 0 */
2
+ /* eslint-disable */
3
+ import { buildPivotTable } from "./buildPivotTable"
4
+ /**
5
+ * @param {import('../interfaces').IViewField[]} dimsInRow
6
+ * @param {import('../interfaces').IViewField[]} dimsInColumn
7
+ * @param {import('../interfaces').IRow[]} allData
8
+ * @param {import('../interfaces').IRow} aggData
9
+ * @param {string[]} collapsedKeyList
10
+ * @param {boolean} showTableSummary
11
+ * @return {{lt: import('../components/pivotTable/inteface').INestNode, tt: import('../components/pivotTable/inteface').INestNode, metric: import('../interfaces').(IRow | null)[][]}}
12
+ */
13
+
14
+ /**
15
+ * @param {MessageEvent<{ dimsInRow: import('../interfaces').IViewField[]; dimsInColumn: import('../interfaces').IViewField[]; allData: import('../interfaces').IRow[]; aggData: import('../interfaces').IRow[]; collapsedKeyList: string[]; showTableSummary: boolean }>} e
16
+ */
17
+ const main = e => {
18
+ const { dimsInRow, dimsInColumn, allData, aggData, collapsedKeyList, showTableSummary } = e.data;
19
+ try {
20
+ const ans = buildPivotTable(dimsInRow, dimsInColumn, allData, aggData, collapsedKeyList, showTableSummary);
21
+ self.postMessage(ans);
22
+ } catch (error) {
23
+ self.postMessage({ error: error.message });
24
+ }
25
+ };
26
+
27
+ self.addEventListener('message', main, false);
@@ -0,0 +1,27 @@
1
+ import { INestNode } from "../components/pivotTable/inteface";
2
+ import { buildMetricTableFromNestTree, buildNestTree } from "../components/pivotTable/utils"
3
+ import { IViewField, IRow } from "../interfaces"
4
+
5
+ export function buildPivotTable (
6
+ dimsInRow: IViewField[],
7
+ dimsInColumn: IViewField[],
8
+ allData: IRow[],
9
+ aggData: IRow[],
10
+ collapsedKeyList: string[],
11
+ showTableSummary: boolean
12
+ ): {lt: INestNode, tt: INestNode, metric: (IRow | null)[][]} {
13
+ const lt = buildNestTree(
14
+ dimsInRow.map((d) => d.fid),
15
+ allData,
16
+ collapsedKeyList,
17
+ showTableSummary
18
+ );
19
+ const tt = buildNestTree(
20
+ dimsInColumn.map((d) => d.fid),
21
+ allData,
22
+ collapsedKeyList,
23
+ showTableSummary
24
+ );
25
+ const metric = buildMetricTableFromNestTree(lt, tt, [...allData, ...aggData])
26
+ return {lt, tt, metric}
27
+ }