@kanaries/graphic-walker 0.4.0 → 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 (73) 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/dataTable/index.d.ts +2 -1
  4. package/dist/components/leafletRenderer/ChoroplethRenderer.d.ts +21 -0
  5. package/dist/components/leafletRenderer/POIRenderer.d.ts +19 -0
  6. package/dist/components/leafletRenderer/encodings.d.ts +7 -0
  7. package/dist/components/leafletRenderer/geoConfigPanel.d.ts +3 -0
  8. package/dist/components/leafletRenderer/index.d.ts +14 -0
  9. package/dist/components/leafletRenderer/tooltip.d.ts +9 -0
  10. package/dist/components/leafletRenderer/utils.d.ts +2 -0
  11. package/dist/components/pivotTable/index.d.ts +2 -1
  12. package/dist/components/pivotTable/inteface.d.ts +6 -2
  13. package/dist/components/pivotTable/leftTree.d.ts +1 -0
  14. package/dist/components/pivotTable/topTree.d.ts +2 -0
  15. package/dist/components/pivotTable/utils.d.ts +1 -2
  16. package/dist/config.d.ts +3 -2
  17. package/dist/graphic-walker.es.js +37802 -30402
  18. package/dist/graphic-walker.es.js.map +1 -1
  19. package/dist/graphic-walker.umd.js +145 -137
  20. package/dist/graphic-walker.umd.js.map +1 -1
  21. package/dist/hooks/index.d.ts +1 -0
  22. package/dist/interfaces.d.ts +27 -0
  23. package/dist/renderer/specRenderer.d.ts +2 -1
  24. package/dist/services.d.ts +7 -1
  25. package/dist/store/commonStore.d.ts +6 -0
  26. package/dist/store/visualSpecStore.d.ts +180 -4
  27. package/dist/utils/save.d.ts +1 -0
  28. package/dist/workers/buildPivotTable.d.ts +7 -0
  29. package/package.json +14 -2
  30. package/src/App.tsx +18 -4
  31. package/src/components/askViz/index.tsx +2 -1
  32. package/src/components/dataTable/index.tsx +7 -5
  33. package/src/components/leafletRenderer/ChoroplethRenderer.tsx +293 -0
  34. package/src/components/leafletRenderer/POIRenderer.tsx +170 -0
  35. package/src/components/leafletRenderer/encodings.ts +194 -0
  36. package/src/components/leafletRenderer/geoConfigPanel.tsx +197 -0
  37. package/src/components/leafletRenderer/index.tsx +67 -0
  38. package/src/components/leafletRenderer/tooltip.tsx +24 -0
  39. package/src/components/leafletRenderer/utils.ts +52 -0
  40. package/src/components/limitSetting.tsx +8 -6
  41. package/src/components/pivotTable/index.tsx +171 -67
  42. package/src/components/pivotTable/inteface.ts +6 -2
  43. package/src/components/pivotTable/leftTree.tsx +24 -11
  44. package/src/components/pivotTable/metricTable.tsx +15 -10
  45. package/src/components/pivotTable/topTree.tsx +50 -17
  46. package/src/components/pivotTable/utils.ts +70 -11
  47. package/src/components/sizeSetting.tsx +9 -7
  48. package/src/components/visualConfig/index.tsx +17 -1
  49. package/src/computation/serverComputation.ts +8 -3
  50. package/src/config.ts +27 -16
  51. package/src/dataSource/table.tsx +9 -9
  52. package/src/fields/aestheticFields.tsx +4 -0
  53. package/src/fields/fieldsContext.tsx +3 -0
  54. package/src/fields/posFields/index.tsx +8 -2
  55. package/src/global.d.ts +4 -4
  56. package/src/hooks/index.ts +8 -0
  57. package/src/index.tsx +11 -9
  58. package/src/interfaces.ts +34 -0
  59. package/src/locales/en-US.json +27 -2
  60. package/src/locales/ja-JP.json +27 -2
  61. package/src/locales/zh-CN.json +27 -2
  62. package/src/renderer/hooks.ts +2 -48
  63. package/src/renderer/index.tsx +24 -1
  64. package/src/renderer/pureRenderer.tsx +26 -13
  65. package/src/renderer/specRenderer.tsx +45 -30
  66. package/src/services.ts +32 -23
  67. package/src/shadow-dom.tsx +7 -0
  68. package/src/store/commonStore.ts +29 -1
  69. package/src/store/visualSpecStore.ts +40 -24
  70. package/src/utils/save.ts +28 -1
  71. package/src/visualSettings/index.tsx +58 -7
  72. package/src/workers/buildMetricTable.worker.js +27 -0
  73. package/src/workers/buildPivotTable.ts +27 -0
@@ -3,12 +3,13 @@ import { INestNode } from "./inteface";
3
3
 
4
4
  const key_prefix = 'nk_';
5
5
 
6
- export function insertNode (tree: INestNode, layerKeys: string[], nodeData: IRow, depth: number) {
6
+ function insertNode (tree: INestNode, layerKeys: string[], nodeData: IRow, depth: number, collapsedKeyList: string[]) {
7
7
  if (depth >= layerKeys.length) {
8
8
  // tree.key = nodeData[layerKeys[depth]];
9
9
  return;
10
10
  }
11
11
  const key = nodeData[layerKeys[depth]];
12
+ const uniqueKey = `${tree.uniqueKey}__${key}`;
12
13
  // console.log({
13
14
  // key,
14
15
  // nodeData,
@@ -23,25 +24,78 @@ export function insertNode (tree: INestNode, layerKeys: string[], nodeData: IRow
23
24
  child = {
24
25
  key,
25
26
  value: key,
27
+ uniqueKey: uniqueKey,
26
28
  fieldKey: layerKeys[depth],
27
29
  children: [],
30
+ path: [...tree.path, {key: layerKeys[depth], value: key}],
31
+ height: layerKeys.length - depth - 1,
32
+ isCollapsed: false,
28
33
  }
29
- tree.children.push(child);
34
+ if (collapsedKeyList.includes(tree.uniqueKey)) {
35
+ tree.isCollapsed = true;
36
+ }
37
+ tree.children.splice(binarySearchIndex(tree.children, child.key), 0, child);
30
38
  }
31
- insertNode(child, layerKeys, nodeData, depth + 1);
39
+ insertNode(child, layerKeys, nodeData, depth + 1, collapsedKeyList);
40
+
41
+ }
42
+
43
+ // Custom binary search function to find appropriate index for insertion.
44
+ function binarySearchIndex(arr: INestNode[], keyVal: string | number): number {
45
+ let start = 0, end = arr.length - 1;
32
46
 
47
+ while (start <= end) {
48
+ let middle = Math.floor((start + end) / 2);
49
+ let middleVal = arr[middle].key;
50
+ if (typeof middleVal === 'number' && typeof keyVal === 'number') {
51
+ if (middleVal < keyVal) start = middle + 1;
52
+ else end = middle - 1;
53
+ } else {
54
+ let cmp = String(middleVal).localeCompare(String(keyVal));
55
+ if (cmp < 0) start = middle + 1;
56
+ else end = middle - 1;
57
+ }
58
+ }
59
+ return start;
33
60
  }
61
+
34
62
  const ROOT_KEY = '__root';
63
+ const TOTAL_KEY = '__total'
35
64
 
36
- export function buildNestTree (layerKeys: string[], data: IRow[]): INestNode {
65
+ function insertSummaryNode (node: INestNode): void {
66
+ if (node.children.length > 0) {
67
+ node.children.push({
68
+ key: TOTAL_KEY,
69
+ value: 'total',
70
+ fieldKey: node.children[0].fieldKey,
71
+ uniqueKey: `${node.uniqueKey}${TOTAL_KEY}`,
72
+ children: [],
73
+ path: [],
74
+ height: node.children[0].height,
75
+ isCollapsed: true,
76
+ });
77
+ for (let i = 0; i < node.children.length - 1; i ++) {
78
+ insertSummaryNode(node.children[i]);
79
+ }
80
+ }
81
+ };
82
+
83
+ export function buildNestTree (layerKeys: string[], data: IRow[], collapsedKeyList: string[], showSummary: boolean): INestNode {
37
84
  const tree: INestNode = {
38
85
  key: ROOT_KEY,
39
86
  value: 'root',
40
87
  fieldKey: 'root',
88
+ uniqueKey: ROOT_KEY,
41
89
  children: [],
90
+ path: [],
91
+ height: layerKeys.length,
92
+ isCollapsed: false,
42
93
  };
43
94
  for (let row of data) {
44
- insertNode(tree, layerKeys, row, 0);
95
+ insertNode(tree, layerKeys, row, 0, collapsedKeyList);
96
+ }
97
+ if (showSummary) {
98
+ insertSummaryNode(tree);
45
99
  }
46
100
  return tree;
47
101
  }
@@ -56,7 +110,7 @@ class NodeIterator {
56
110
  public first () {
57
111
  let node = this.tree
58
112
  this.nodeStack = [node];
59
- while (node.children.length > 0) {
113
+ while (node.children.length > 0 && !node.isCollapsed) {
60
114
  this.nodeStack.push(node.children[0])
61
115
  node = node.children[0]
62
116
  }
@@ -75,7 +129,7 @@ class NodeIterator {
75
129
  if (nodeIndex === -1) break;
76
130
  // console.log(this.nodeStack.map(n => `${n.fieldKey}-${n.value}`))
77
131
  if (cursorMoved) {
78
- if (node.children.length > 0) {
132
+ if (node.children.length > 0 && !node.isCollapsed) {
79
133
  this.nodeStack.push(node.children[0]);
80
134
  continue;
81
135
  } else {
@@ -102,7 +156,7 @@ class NodeIterator {
102
156
  // console.log(this.current)
103
157
  return this.current;
104
158
  }
105
- public predicates (): { key: string; value: any }[] {
159
+ public predicates (): { key: string; value: string | number }[] {
106
160
  return this.nodeStack.filter(node => node.key !== ROOT_KEY).map(node => ({
107
161
  key: node.fieldKey,
108
162
  value: node.value
@@ -121,9 +175,14 @@ export function buildMetricTableFromNestTree (leftTree: INestNode, topTree: INes
121
175
  const vec: any[] = [];
122
176
  iteTop.first();
123
177
  while (iteTop.current !== null) {
124
- const predicates = iteLeft.predicates().concat(iteTop.predicates());
125
- const row = data.find(r => predicates.every(pre => r[pre.key] === pre.value))
126
- vec.push(row)
178
+ const predicates = iteLeft.predicates().concat(iteTop.predicates()).filter((ele) => ele.value !== "total");
179
+ const matchedRows = data.filter(r => predicates.every(pre => r[pre.key] === pre.value));
180
+ if (matchedRows.length > 0) {
181
+ // If multiple rows are matched, then find the most matched one (the row with smallest number of keys)
182
+ vec.push(matchedRows.reduce((a, b) => Object.keys(a).length < Object.keys(b).length ? a : b));
183
+ } else {
184
+ vec.push(undefined);
185
+ }
127
186
  iteTop.next();
128
187
  }
129
188
  mat.push(vec)
@@ -1,6 +1,7 @@
1
1
  import { ArrowsPointingOutIcon, XMarkIcon } from "@heroicons/react/24/outline";
2
2
  import React, { useState, useEffect } from "react";
3
3
  import { useTranslation } from "react-i18next";
4
+ import { useDebounceValueBind } from "../hooks";
4
5
 
5
6
  interface SizeSettingProps {
6
7
  onWidthChange: (val: number) => void;
@@ -12,7 +13,8 @@ interface SizeSettingProps {
12
13
  export const ResizeDialog: React.FC<SizeSettingProps> = (props) => {
13
14
  const { onWidthChange, onHeightChange, width, height, children } = props;
14
15
  const { t } = useTranslation("translation", { keyPrefix: "main.tabpanel.settings.size_setting" });
15
-
16
+ const [innerWidth, setInnerWidth] = useDebounceValueBind(width, onWidthChange);
17
+ const [innerHeight, setInnerHeight] = useDebounceValueBind(height, onHeightChange);
16
18
  return (
17
19
  <div className="text-zinc-400">
18
20
  {children}
@@ -22,16 +24,16 @@ export const ResizeDialog: React.FC<SizeSettingProps> = (props) => {
22
24
  style={{ cursor: "ew-resize" }}
23
25
  type="range"
24
26
  name="width"
25
- value={Math.sqrt(width / 1000)}
27
+ value={Math.sqrt(innerWidth / 1000)}
26
28
  min="0"
27
29
  max="1"
28
30
  step="0.01"
29
31
  onChange={(e) => {
30
- onWidthChange(Math.round(Number(e.target.value) ** 2 * 1000));
32
+ setInnerWidth(Math.round(Number(e.target.value) ** 2 * 1000));
31
33
  }}
32
34
  />
33
35
  <output className="text-sm ml-1" htmlFor="width">
34
- {`${t("width")}: ${width}`}
36
+ {`${t("width")}: ${innerWidth}`}
35
37
  </output>
36
38
  </div>
37
39
  <div className=" mt-2">
@@ -40,16 +42,16 @@ export const ResizeDialog: React.FC<SizeSettingProps> = (props) => {
40
42
  style={{ cursor: "ew-resize" }}
41
43
  type="range"
42
44
  name="height"
43
- value={Math.sqrt(height / 1000)}
45
+ value={Math.sqrt(innerHeight / 1000)}
44
46
  min="0"
45
47
  max="1"
46
48
  step="0.01"
47
49
  onChange={(e) => {
48
- onHeightChange(Math.round(Number(e.target.value) ** 2 * 1000));
50
+ setInnerHeight(Math.round(Number(e.target.value) ** 2 * 1000));
49
51
  }}
50
52
  />
51
53
  <output className="text-sm ml-1" htmlFor="height">
52
- {`${t("height")}: ${height}`}
54
+ {`${t("height")}: ${innerHeight}`}
53
55
  </output>
54
56
  </div>
55
57
  </div>
@@ -1,5 +1,5 @@
1
1
  import { observer } from 'mobx-react-lite';
2
- import React, { useEffect, useRef, useState } from 'react';
2
+ import React, { useEffect, useState } from 'react';
3
3
  import { useGlobalStore } from '../../store';
4
4
  import { NonPositionChannelConfigList,PositionChannelConfigList } from '../../config';
5
5
 
@@ -15,6 +15,8 @@ const VisualConfigPanel: React.FC = (props) => {
15
15
  const { commonStore, vizStore } = useGlobalStore();
16
16
  const { showVisualConfigPanel } = commonStore;
17
17
  const { visualConfig } = vizStore;
18
+ const { coordSystem, geoms: [markType] } = visualConfig;
19
+ const isChoropleth = coordSystem === 'geographic' && markType === 'choropleth';
18
20
  const { t } = useTranslation();
19
21
  const formatConfigList: (keyof IVisualConfig['format'])[] = [
20
22
  'numberFormat',
@@ -35,12 +37,14 @@ const VisualConfigPanel: React.FC = (props) => {
35
37
  size: visualConfig.resolve.size,
36
38
  });
37
39
  const [zeroScale, setZeroScale] = useState<boolean>(visualConfig.zeroScale);
40
+ const [scaleIncludeUnmatchedChoropleth, setScaleIncludeUnmatchedChoropleth] = useState<boolean>(visualConfig.scaleIncludeUnmatchedChoropleth ?? false);
38
41
  const [background, setBackground] = useState<string | undefined>(visualConfig.background);
39
42
 
40
43
  useEffect(() => {
41
44
  setZeroScale(visualConfig.zeroScale);
42
45
  setBackground(visualConfig.background);
43
46
  setResolve(toJS(visualConfig.resolve));
47
+ setScaleIncludeUnmatchedChoropleth(visualConfig.scaleIncludeUnmatchedChoropleth ?? false);
44
48
  setFormat({
45
49
  numberFormat: visualConfig.format.numberFormat,
46
50
  timeFormat: visualConfig.format.timeFormat,
@@ -140,6 +144,17 @@ const VisualConfigPanel: React.FC = (props) => {
140
144
  }}
141
145
  />
142
146
  </div>
147
+ {isChoropleth && (
148
+ <div className="my-2">
149
+ <Toggle
150
+ label="include unmatched choropleth in scale"
151
+ enabled={scaleIncludeUnmatchedChoropleth}
152
+ onChange={(en) => {
153
+ setScaleIncludeUnmatchedChoropleth(en);
154
+ }}
155
+ />
156
+ </div>
157
+ )}
143
158
  <div className="mt-4">
144
159
  <PrimaryButton
145
160
  text={t('actions.confirm')}
@@ -148,6 +163,7 @@ const VisualConfigPanel: React.FC = (props) => {
148
163
  runInAction(() => {
149
164
  vizStore.setVisualConfig('format', format);
150
165
  vizStore.setVisualConfig('zeroScale', zeroScale);
166
+ vizStore.setVisualConfig('scaleIncludeUnmatchedChoropleth', scaleIncludeUnmatchedChoropleth);
151
167
  vizStore.setVisualConfig('background', background);
152
168
  vizStore.setVisualConfig('resolve', resolve);
153
169
  commonStore.setShowVisualConfigPanel(false);
@@ -29,7 +29,7 @@ export const datasetStatsServer = async (service: IComputationFunction): Promise
29
29
  ],
30
30
  })) as [{ count: number }];
31
31
  return {
32
- rowCount: res[0].count,
32
+ rowCount: res[0]?.count ?? 0,
33
33
  };
34
34
  };
35
35
 
@@ -72,7 +72,7 @@ export const dataQueryServer = async (
72
72
  }
73
73
  const res = await service({
74
74
  workflow,
75
- limit
75
+ limit,
76
76
  });
77
77
  return res;
78
78
  };
@@ -132,7 +132,12 @@ export const fieldStatServer = async (
132
132
  },
133
133
  ],
134
134
  };
135
- const [rangeRes] = range
135
+ const [
136
+ rangeRes = {
137
+ [MIN_ID]: 0,
138
+ [MAX_ID]: 0,
139
+ },
140
+ ] = range
136
141
  ? await service(rangeQueryPayload)
137
142
  : [
138
143
  {
package/src/config.ts CHANGED
@@ -1,20 +1,31 @@
1
- import { DraggableFieldState, IStackMode, IVisualConfig } from "./interfaces";
1
+ import { DraggableFieldState, ICoordMode, IStackMode, IVisualConfig } from "./interfaces";
2
2
 
3
- export const GEMO_TYPES: Readonly<string[]> = [
4
- 'auto',
5
- 'bar',
6
- 'line',
7
- 'area',
8
- 'trail',
9
- 'point',
10
- 'circle',
11
- 'tick',
12
- 'rect',
13
- 'arc',
14
- 'text',
15
- 'boxplot',
16
- 'table'
17
- ] as const;
3
+ export const GEMO_TYPES: Record<ICoordMode, Readonly<string[]>> = {
4
+ generic: [
5
+ 'auto',
6
+ 'bar',
7
+ 'line',
8
+ 'area',
9
+ 'trail',
10
+ 'point',
11
+ 'circle',
12
+ 'tick',
13
+ 'rect',
14
+ 'arc',
15
+ 'text',
16
+ 'boxplot',
17
+ 'table',
18
+ ],
19
+ geographic: [
20
+ 'poi',
21
+ 'choropleth',
22
+ ],
23
+ } as const;
24
+
25
+ export const COORD_TYPES: Readonly<ICoordMode[]> = [
26
+ 'generic',
27
+ 'geographic',
28
+ ];
18
29
 
19
30
  export const STACK_MODE: Readonly<IStackMode[]> = [
20
31
  'none',
@@ -3,6 +3,7 @@ import { observer } from "mobx-react-lite";
3
3
  import { useGlobalStore } from "../store";
4
4
  import DataTable from "../components/dataTable";
5
5
  import { toJS } from "mobx";
6
+ import { getComputation } from "../computation/clientComputation";
6
7
 
7
8
  interface TableProps {
8
9
  size?: number;
@@ -13,19 +14,18 @@ const Table: React.FC<TableProps> = (props) => {
13
14
  const { commonStore } = useGlobalStore();
14
15
  const { tmpDSRawFields, tmpDataSource } = commonStore;
15
16
 
16
- const tempDataset = React.useMemo(() => {
17
- return {
18
- id: "tmp",
19
- name: "tmp",
20
- dataSource: tmpDataSource,
21
- rawFields: toJS(tmpDSRawFields),
22
- };
23
- }, [tmpDataSource, tmpDSRawFields]);
17
+ const computation = React.useMemo(() => getComputation(tmpDataSource), [tmpDataSource])
24
18
 
25
19
  return (
26
20
  <DataTable
27
21
  size={size}
28
- dataset={tempDataset}
22
+ dataset={{
23
+ id: "tmp",
24
+ name: "tmp",
25
+ dataSource: tmpDataSource,
26
+ rawFields: toJS(tmpDSRawFields),
27
+ }}
28
+ computation={computation}
29
29
  total={tmpDataSource.length}
30
30
  onMetaChange={(fid, fIndex, diffMeta) => {
31
31
  commonStore.updateTempDatasetMetas(fid, diffMeta);
@@ -26,6 +26,10 @@ const AestheticFields: React.FC = props => {
26
26
  return aestheticFields.filter(f => f.id === 'text' || f.id === 'color' || f.id === 'size' || f.id === 'opacity');
27
27
  case 'table':
28
28
  return []
29
+ case 'poi':
30
+ return aestheticFields.filter(f => f.id === 'color' || f.id === 'opacity' || f.id === 'size' || f.id === 'details');
31
+ case 'choropleth':
32
+ return aestheticFields.filter(f => f.id === 'color' || f.id === 'opacity' || f.id === 'text' || f.id === 'details');
29
33
  default:
30
34
  return aestheticFields.filter(f => f.id !== 'text');
31
35
  }
@@ -48,6 +48,9 @@ export const DRAGGABLE_STATE_KEYS: Readonly<IDraggableStateKey[]> = [
48
48
  { id: 'shape', mode: 1},
49
49
  { id: 'theta', mode: 1 },
50
50
  { id: 'radius', mode: 1 },
51
+ { id: 'longitude', mode: 1 },
52
+ { id: 'latitude', mode: 1 },
53
+ { id: 'geoId', mode: 1 },
51
54
  { id: 'filters', mode: 1 },
52
55
  { id: 'details', mode: 1 },
53
56
  { id: 'text', mode: 1 },
@@ -9,14 +9,20 @@ import OBFieldContainer from '../obComponents/obFContainer';
9
9
  const PosFields: React.FC = props => {
10
10
  const { vizStore } = useGlobalStore();
11
11
  const { visualConfig } = vizStore;
12
- const { geoms } = visualConfig;
12
+ const { geoms, coordSystem = 'generic' } = visualConfig;
13
13
 
14
14
  const channels = useMemo(() => {
15
+ if (coordSystem === 'geographic') {
16
+ if (geoms[0] === 'choropleth') {
17
+ return DRAGGABLE_STATE_KEYS.filter(f => f.id === 'geoId');
18
+ }
19
+ return DRAGGABLE_STATE_KEYS.filter(f => f.id === 'longitude' || f.id === 'latitude');
20
+ }
15
21
  if (geoms[0] === 'arc') {
16
22
  return DRAGGABLE_STATE_KEYS.filter(f => f.id === 'radius' || f.id === 'theta');
17
23
  }
18
24
  return DRAGGABLE_STATE_KEYS.filter(f => f.id === 'columns' || f.id === 'rows');
19
- }, [geoms[0]])
25
+ }, [geoms[0], coordSystem])
20
26
  return <div>
21
27
  {
22
28
  channels.map(dkey => <FieldListContainer name={dkey.id} key={dkey.id}>
package/src/global.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  declare module '@kanaries/react-beautiful-dnd' {
2
- export const DOM: {
3
- setHead: (head: HTMLElement | ShadowRoot) => void;
4
- setBody: (body: HTMLElement | ShadowRoot) => void;
5
- };
2
+ export const DOMProvider: import('react').Provider<{
3
+ head: HTMLElement | ShadowRoot;
4
+ body: HTMLElement | ShadowRoot;
5
+ }>;
6
6
  export * from 'react-beautiful-dnd';
7
7
  }
@@ -8,3 +8,11 @@ export function useDebounceValue<T>(value: T, timeout = 200): T {
8
8
  }, [value]);
9
9
  return innerValue;
10
10
  }
11
+
12
+ export function useDebounceValueBind<T>(value: T, setter: (v: T) => void, timeout = 200): [T, (v: T) => void] {
13
+ const [innerValue, setInnerValue] = useState(value);
14
+ const valueToSet = useDebounceValue(innerValue, timeout);
15
+ useEffect(() => setInnerValue(value), [value])
16
+ useEffect(() => setter(valueToSet), [valueToSet]);
17
+ return [innerValue, setInnerValue];
18
+ }
package/src/index.tsx CHANGED
@@ -1,5 +1,5 @@
1
- import React, { type ForwardedRef, forwardRef } from "react";
2
- import { DOM } from "@kanaries/react-beautiful-dnd";
1
+ import React, { type ForwardedRef, forwardRef, useState } from "react";
2
+ import { DOMProvider } from "@kanaries/react-beautiful-dnd";
3
3
  import { observer } from "mobx-react-lite";
4
4
  import App, { IGWProps } from "./App";
5
5
  import { StoreWrapper } from "./store/index";
@@ -13,22 +13,24 @@ import "./empty_sheet.css";
13
13
  export const GraphicWalker = observer(forwardRef<IGWHandler, IGWProps>((props, ref) => {
14
14
  const { storeRef } = props;
15
15
 
16
+ const [shadowRoot, setShadowRoot] = useState<ShadowRoot | null>(null);
17
+
16
18
  const handleMount = (shadowRoot: ShadowRoot) => {
17
- DOM.setBody(shadowRoot);
18
- DOM.setHead(shadowRoot);
19
+ setShadowRoot(shadowRoot);
19
20
  };
20
21
  const handleUnmount = () => {
21
- DOM.setBody(document.body);
22
- DOM.setHead(document.head);
22
+ setShadowRoot(null);
23
23
  };
24
24
 
25
25
  return (
26
26
  <StoreWrapper keepAlive={props.keepAlive} storeRef={storeRef}>
27
27
  <AppRoot ref={ref as ForwardedRef<IGWHandlerInsider>}>
28
28
  <ShadowDom onMount={handleMount} onUnmount={handleUnmount}>
29
- <FieldsContextWrapper>
30
- <App {...props} />
31
- </FieldsContextWrapper>
29
+ <DOMProvider value={{ head: shadowRoot ?? document.head, body: shadowRoot ?? document.body }}>
30
+ <FieldsContextWrapper>
31
+ <App {...props} />
32
+ </FieldsContextWrapper>
33
+ </DOMProvider>
32
34
  </ShadowDom>
33
35
  </AppRoot>
34
36
  </StoreWrapper>
package/src/interfaces.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import {Config as VgConfig, View} from 'vega';
2
2
  import {Config as VlConfig} from 'vega-lite';
3
+ import type { FeatureCollection } from 'geojson';
4
+ import type { feature } from 'topojson-client';
3
5
  import type {IViewQuery} from "./lib/viewQuery";
4
6
 
5
7
  export type DeepReadonly<T extends Record<keyof any, any>> = {
@@ -86,6 +88,8 @@ export interface IExpression {
86
88
  as: string;
87
89
  }
88
90
 
91
+ export type IGeoRole = 'longitude' | 'latitude' | 'none';
92
+
89
93
  export interface IField {
90
94
  /**
91
95
  * fid: key in data record
@@ -101,6 +105,7 @@ export interface IField {
101
105
  aggName?: string;
102
106
  semanticType: ISemanticType;
103
107
  analyticType: IAnalyticType;
108
+ geoRole?: IGeoRole;
104
109
  cmp?: (a: any, b: any) => number;
105
110
  computed?: boolean;
106
111
  expression?: IExpression;
@@ -183,6 +188,9 @@ export interface DraggableFieldState {
183
188
  shape: IViewField[];
184
189
  theta: IViewField[];
185
190
  radius: IViewField[];
191
+ longitude: IViewField[];
192
+ latitude: IViewField[];
193
+ geoId: IViewField[];
186
194
  details: IViewField[];
187
195
  filters: IFilterField[];
188
196
  text: IViewField[];
@@ -209,14 +217,21 @@ export type IFilterRule =
209
217
 
210
218
  export type IStackMode = 'none' | 'stack' | 'normalize' | 'zero' | 'center';
211
219
 
220
+ export type ICoordMode = 'generic' | 'geographic';
221
+
212
222
  export interface IVisualConfig {
213
223
  defaultAggregated: boolean;
214
224
  geoms: string[];
225
+ showTableSummary: boolean;
226
+ /** @default "generic" */
227
+ coordSystem?: ICoordMode;
215
228
  stack: IStackMode;
216
229
  showActions: boolean;
217
230
  interactiveScale: boolean;
218
231
  sorted: ISortMode;
219
232
  zeroScale: boolean;
233
+ /** @default false */
234
+ scaleIncludeUnmatchedChoropleth?: boolean;
220
235
  background?: string;
221
236
  format: {
222
237
  numberFormat?: string;
@@ -236,6 +251,8 @@ export interface IVisualConfig {
236
251
  width: number;
237
252
  height: number;
238
253
  };
254
+ geojson?: FeatureCollection;
255
+ geoKey?: string;
239
256
  limit: number;
240
257
  }
241
258
 
@@ -448,3 +465,20 @@ export type IResponse<T> = (
448
465
  };
449
466
  }
450
467
  );
468
+
469
+ export type Topology = Parameters<typeof feature>[0];
470
+
471
+ export type IGeographicData = (
472
+ | {
473
+ type: 'GeoJSON';
474
+ data: FeatureCollection;
475
+ }
476
+ | {
477
+ type: 'TopoJSON';
478
+ data: Topology;
479
+ /**
480
+ * default to the first key of `objects` in Topology
481
+ */
482
+ objectKey?: string;
483
+ }
484
+ );
@@ -39,7 +39,14 @@
39
39
  "arc": "Arc",
40
40
  "boxplot": "Box (Box Plot)",
41
41
  "table": "Table",
42
- "text": "Text"
42
+ "text": "Text",
43
+ "poi": "POI",
44
+ "choropleth": "Choropleth"
45
+ },
46
+ "coord_system": {
47
+ "__enum__": "Coordinate System",
48
+ "generic": "Generic",
49
+ "geographic": "Geographic"
43
50
  },
44
51
  "stack_mode": {
45
52
  "__enum__": "Stack Mode",
@@ -77,7 +84,10 @@
77
84
  "radius": "Radius",
78
85
  "filters": "Filters",
79
86
  "details": "Details",
80
- "text": "Text"
87
+ "text": "Text",
88
+ "longitude": "Longitude",
89
+ "latitude": "Latitude",
90
+ "geoId": "Geometry ID"
81
91
  },
82
92
  "aggregator": {
83
93
  "sum": "Sum",
@@ -168,6 +178,21 @@
168
178
  "size_setting": {
169
179
  "width": "Width",
170
180
  "height": "Height"
181
+ },
182
+ "table": {
183
+ "summary": "Show summary"
184
+ },
185
+ "geography": "Geography Configuration",
186
+ "geography_settings": {
187
+ "geoKey": "Feature ID",
188
+ "format": "Format",
189
+ "geojson": "GeoJSON",
190
+ "topojson": "TopoJSON",
191
+ "objectKey": "Extract Feature Key",
192
+ "jsonInputPlaceholder": "Paste {{format}} here",
193
+ "href": "{{format}} URL",
194
+ "hrefPlaceholder": "Enter {{format}} URL",
195
+ "load": "Load"
171
196
  }
172
197
  },
173
198
  "DatasetFields": {
@@ -39,7 +39,14 @@
39
39
  "arc": "アーク",
40
40
  "boxplot": "ボックスプロット",
41
41
  "table": "表",
42
- "text": "本文"
42
+ "text": "テキスト",
43
+ "poi": "POI",
44
+ "choropleth": "コロプレス"
45
+ },
46
+ "coord_system": {
47
+ "__enum__": "座標系",
48
+ "generic": "ジェネリック",
49
+ "geographic": "地理"
43
50
  },
44
51
  "stack_mode": {
45
52
  "__enum__": "スタックモード",
@@ -76,7 +83,10 @@
76
83
  "theta": "角度",
77
84
  "radius": "半径",
78
85
  "filters": "フィルター",
79
- "text": "本文"
86
+ "text": "本文",
87
+ "longitude": "経度",
88
+ "latitude": "緯度",
89
+ "geoId": "地理ID"
80
90
  },
81
91
  "aggregator": {
82
92
  "sum": "合計",
@@ -167,6 +177,21 @@
167
177
  "size_setting": {
168
178
  "width": "幅",
169
179
  "height": "高さ"
180
+ },
181
+ "table": {
182
+ "summary": "サマリを表示"
183
+ },
184
+ "geography": "地理情報設定",
185
+ "geography_settings": {
186
+ "geoKey": "フィーチャーID",
187
+ "format": "データ形式",
188
+ "geojson": "GeoJSON",
189
+ "topojson": "TopoJSON",
190
+ "objectKey": "Feature のキー",
191
+ "jsonInputPlaceholder": "{{format}}をここに貼り付けてください",
192
+ "href": "{{format}} URL",
193
+ "hrefPlaceholder": "{{format}} URLを入力",
194
+ "load": "ロード"
170
195
  }
171
196
  },
172
197
  "DatasetFields": {