@kanaries/graphic-walker 0.2.15 → 0.2.16

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 (97) hide show
  1. package/dist/App.d.ts +2 -0
  2. package/dist/assets/explainer.worker-8428eb12.js.map +1 -1
  3. package/dist/assets/transform.worker-5d54ff09.js.map +1 -0
  4. package/dist/assets/viewQuery.worker-ffefc111.js.map +1 -0
  5. package/dist/components/codeExport/index.d.ts +3 -0
  6. package/dist/components/loadingLayer.d.ts +2 -0
  7. package/dist/components/tabs/defaultTab.d.ts +1 -0
  8. package/dist/components/tabs/editableTab.d.ts +1 -2
  9. package/dist/dataSource/dataSelection/config.d.ts +1 -0
  10. package/dist/dataSource/dataSelection/utils.d.ts +2 -0
  11. package/dist/datasets/tmp/test.json +1 -0
  12. package/dist/graphic-walker.es.js +23081 -22577
  13. package/dist/graphic-walker.es.js.map +1 -1
  14. package/dist/graphic-walker.umd.js +130 -130
  15. package/dist/graphic-walker.umd.js.map +1 -1
  16. package/dist/index.d.ts +3 -3
  17. package/dist/interfaces.d.ts +21 -1
  18. package/dist/lib/execExp.d.ts +8 -0
  19. package/dist/lib/interfaces.d.ts +22 -0
  20. package/dist/lib/op/aggregate.d.ts +3 -0
  21. package/dist/lib/op/bin.d.ts +3 -0
  22. package/dist/lib/op/fold.d.ts +3 -0
  23. package/dist/lib/op/stat.d.ts +8 -0
  24. package/dist/lib/viewQuery.d.ts +5 -0
  25. package/dist/models/visSpecHistory.d.ts +2 -0
  26. package/dist/renderer/index.d.ts +8 -7
  27. package/dist/renderer/specRenderer.d.ts +13 -0
  28. package/dist/services.d.ts +4 -1
  29. package/dist/store/commonStore.d.ts +6 -0
  30. package/dist/store/index.d.ts +3 -2
  31. package/dist/store/visualSpecStore.d.ts +11 -4
  32. package/dist/utils/dataPrep.d.ts +2 -0
  33. package/dist/utils/index.d.ts +3 -5
  34. package/dist/utils/save.d.ts +1 -2
  35. package/dist/vis/react-vega.d.ts +1 -22
  36. package/dist/vis/spec/aggregate.d.ts +4 -0
  37. package/dist/vis/spec/encode.d.ts +19 -0
  38. package/dist/vis/spec/field.d.ts +2 -0
  39. package/dist/vis/spec/mark.d.ts +7 -0
  40. package/dist/vis/spec/stack.d.ts +4 -0
  41. package/dist/vis/spec/view.d.ts +67 -0
  42. package/dist/workers/transform.d.ts +2 -0
  43. package/package.json +4 -3
  44. package/src/App.tsx +5 -2
  45. package/src/components/codeExport/index.tsx +114 -0
  46. package/src/components/dataTable/index.tsx +10 -10
  47. package/src/components/loadingLayer.tsx +7 -0
  48. package/src/components/tabs/defaultTab.tsx +4 -2
  49. package/src/components/tabs/editableTab.tsx +74 -39
  50. package/src/dataSource/dataSelection/config.ts +11 -0
  51. package/src/dataSource/dataSelection/csvData.tsx +71 -39
  52. package/src/dataSource/dataSelection/gwFile.tsx +2 -2
  53. package/src/dataSource/dataSelection/utils.ts +28 -0
  54. package/src/dataSource/utils.ts +8 -3
  55. package/src/fields/datasetFields/meaFields.tsx +12 -4
  56. package/src/index.css +4 -4
  57. package/src/index.tsx +22 -22
  58. package/src/interfaces.ts +26 -2
  59. package/src/lib/execExp.ts +147 -0
  60. package/src/lib/interfaces.ts +39 -0
  61. package/src/lib/op/aggregate.ts +49 -0
  62. package/src/lib/op/bin.ts +25 -0
  63. package/src/lib/op/fold.ts +17 -0
  64. package/src/lib/op/stat.ts +46 -0
  65. package/src/lib/viewQuery.ts +23 -0
  66. package/src/locales/en-US.json +4 -2
  67. package/src/locales/i18n.ts +0 -1
  68. package/src/locales/ja-JP.json +4 -2
  69. package/src/locales/zh-CN.json +4 -2
  70. package/src/main.tsx +1 -1
  71. package/src/models/visSpecHistory.ts +14 -0
  72. package/src/renderer/index.tsx +58 -126
  73. package/src/renderer/specRenderer.tsx +119 -0
  74. package/src/segments/segmentNav.tsx +3 -16
  75. package/src/segments/visNav.tsx +17 -6
  76. package/src/services.ts +37 -1
  77. package/src/store/commonStore.ts +14 -9
  78. package/src/store/index.tsx +11 -4
  79. package/src/store/visualSpecStore.ts +89 -50
  80. package/src/utils/dataPrep.ts +24 -0
  81. package/src/utils/index.ts +16 -17
  82. package/src/utils/normalization.ts +3 -1
  83. package/src/utils/save.ts +1 -2
  84. package/src/vis/react-vega.tsx +4 -340
  85. package/src/vis/spec/aggregate.ts +13 -0
  86. package/src/vis/spec/encode.ts +69 -0
  87. package/src/vis/spec/field.ts +10 -0
  88. package/src/vis/spec/mark.ts +30 -0
  89. package/src/vis/spec/stack.ts +11 -0
  90. package/src/vis/spec/view.ts +138 -0
  91. package/src/vis/theme.ts +12 -0
  92. package/src/visualSettings/index.tsx +10 -1
  93. package/src/workers/transform.ts +12 -0
  94. package/src/workers/transform.worker.js +13 -0
  95. package/src/workers/viewQuery.worker.js +16 -0
  96. package/dist/dataSource/pannel.d.ts +0 -5
  97. package/src/dataSource/pannel.tsx +0 -71
@@ -48,15 +48,15 @@ function getHeaderClassNames(field: IMutField) {
48
48
  function getSemanticColors(field: IMutField): string {
49
49
  switch (field.semanticType) {
50
50
  case "nominal":
51
- return "bg-sky-100 text-sky-800";
51
+ return "border border-transparent bg-sky-100 text-sky-800 dark:bg-sky-900 dark:text-sky-100 dark:border-sky-600";
52
52
  case "ordinal":
53
- return "bg-indigo-100 text-indigo-800";
53
+ return "border border-transparent bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-100 dark:border-indigo-600";
54
54
  case "quantitative":
55
- return "bg-purple-100 text-purple-800";
55
+ return "border border-transparent bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-100 dark:border-purple-600";
56
56
  case "temporal":
57
- return "bg-yellow-100 text-yellow-800";
57
+ return "border border-transparent bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-100 dark:border-yellow-600";
58
58
  default:
59
- return "bg-gray-400";
59
+ return "border border-transparent bg-gray-400";
60
60
  }
61
61
  }
62
62
 
@@ -97,7 +97,7 @@ const DataTable: React.FC<DataTableProps> = (props) => {
97
97
  />
98
98
  <table className="min-w-full divide-y">
99
99
  <thead className="bg-gray-50 dark:bg-gray-900">
100
- <tr className="divide-x divide-gray-200 dark:divide-gray-600">
100
+ <tr className="divide-x divide-gray-200 dark:divide-gray-700">
101
101
  {metas.map((field, fIndex) => (
102
102
  <th key={field.fid} className={""}>
103
103
  <div
@@ -152,9 +152,9 @@ const DataTable: React.FC<DataTableProps> = (props) => {
152
152
  ))}
153
153
  </tr>
154
154
  </thead>
155
- <tbody className="divide-y divide-gray-100 dark:divide-gray-600 bg-white dark:bg-zinc-900">
156
- {data.slice(from, to).map((row, index) => (
157
- <tr className={"divide-x divide-gray-200 dark:divide-gray-600 " + (index % 2 ? "bg-gray-50 dark:bg-gray-800" : "")} key={index}>
155
+ <tbody className="divide-y divide-gray-100 dark:divide-gray-700 bg-white dark:bg-zinc-900">
156
+ {data.slice(from, to + 1).map((row, index) => (
157
+ <tr className={"divide-x divide-gray-200 dark:divide-gray-700 " + (index % 2 ? "bg-gray-50 dark:bg-gray-900" : "")} key={index}>
158
158
  {metas.map((field) => (
159
159
  <td
160
160
  key={field.fid + index}
@@ -163,7 +163,7 @@ const DataTable: React.FC<DataTableProps> = (props) => {
163
163
  " whitespace-nowrap py-2 pl-4 pr-3 text-xs text-gray-500 dark:text-gray-300 sm:pl-6"
164
164
  }
165
165
  >
166
- {row[field.fid]}
166
+ {`${row[field.fid]}`}
167
167
  </td>
168
168
  ))}
169
169
  </tr>
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+
3
+ export default function LoadingLayer () {
4
+ return <div className="bg-gray-100/50 dark:bg-gray-700/50 absolute top-0 left-0 right-0 bottom-0 z-50 flex items-center justify-center">
5
+ Loading...
6
+ </div>
7
+ }
@@ -7,6 +7,7 @@ function classNames(...classes: string[]) {
7
7
  export interface ITabOption {
8
8
  label: string | ReactElement;
9
9
  key: string;
10
+ disabled?: boolean;
10
11
  }
11
12
  interface DefaultProps {
12
13
  tabs: ITabOption[];
@@ -26,14 +27,15 @@ export default function Default(props: DefaultProps) {
26
27
  role="tab"
27
28
  tabIndex={0}
28
29
  onClick={() => {
29
- onSelected(tab.key, tabIndex)
30
+ !tab.disabled && onSelected(tab.key, tabIndex)
30
31
  }}
31
32
  key={tab.key}
32
33
  className={classNames(
33
34
  tab.key === selectedKey
34
35
  ? 'border-indigo-500 text-indigo-600 dark:border-indigo-400 dark:text-indigo-300'
35
36
  : 'border-transparent text-gray-500 hover:text-gray-700 dark:hover:text-gray-200 hover:border-gray-300 dark:text-gray-400',
36
- 'whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm cursor-pointer'
37
+ 'whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm cursor-pointer',
38
+ tab.disabled ? 'opacity-50 cursor-not-allowed' : ''
37
39
  )}
38
40
  >{tab.label}</span>
39
41
  ))}
@@ -1,74 +1,109 @@
1
- import React, { useCallback, useEffect, useState } from "react";
1
+ import React, { useState } from "react";
2
2
  import { useTranslation } from "react-i18next";
3
-
3
+ import { PencilSquareIcon } from "@heroicons/react/24/outline";
4
+ import Modal from "../modal";
5
+ import { unstable_batchedUpdates } from "react-dom";
6
+ import DefaultButton from "../button/default";
7
+ import PrimaryButton from "../button/primary";
4
8
 
5
9
  function classNames(...classes: string[]) {
6
- return classes.filter(Boolean).join(' ')
10
+ return classes.filter(Boolean).join(" ");
7
11
  }
8
12
 
9
13
  export interface ITabOption {
10
14
  label: string;
11
15
  key: string;
12
- options?: Record<string, any>;
16
+ editable?: boolean;
13
17
  }
14
18
  interface EditableTabsProps {
15
19
  tabs: ITabOption[];
16
20
  selectedKey: string;
17
21
  onSelected: (selectedKey: string, index: number) => void;
18
- allowEdit?: boolean;
19
22
  onEditLabel?: (label: string, index: number) => void;
20
23
  }
21
24
  export default function EditableTabs(props: EditableTabsProps) {
22
- const { tabs, selectedKey, onSelected, allowEdit, onEditLabel } = props;
23
- const [editList, setEditList] = useState<boolean[]>([]);
25
+ const { tabs, selectedKey, onSelected, onEditLabel } = props;
26
+ const [editingIndex, setEditingIndex] = useState<number>(-1);
27
+ const [name, setName] = useState<string>("");
24
28
  const { t } = useTranslation();
25
29
 
26
- const clearEditStatus = useCallback(() => {
27
- setEditList(new Array(tabs.length).fill(false))
28
- }, [tabs.length]);
29
-
30
- useEffect(() => {
31
- clearEditStatus();
32
- }, [clearEditStatus]);
33
-
34
30
  return (
35
- <div className="border-b border-gray-200 dark:border-gray-700 overflow-x-auto overflow-y-hidden" onMouseLeave={clearEditStatus}>
31
+ <div className="border-b border-gray-200 dark:border-gray-700 overflow-x-auto overflow-y-hidden">
32
+ <Modal
33
+ show={editingIndex > -1}
34
+ onClose={() => {
35
+ setEditingIndex(-1);
36
+ }}
37
+ >
38
+ <div>
39
+ <span className="block text-sm font-medium leading-6">{t('main.tablist.chart_name')}</span>
40
+ <div className="mt-2">
41
+ <input
42
+ value={name}
43
+ onChange={(e) => {
44
+ setName(e.target.value);
45
+ }}
46
+ type="text"
47
+ name="text"
48
+ className="block w-full rounded-md border-0 px-2 py-1.5 bg-transparent shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
49
+ />
50
+ </div>
51
+ <div className="mt-4 flex justify-end">
52
+ <DefaultButton
53
+ className="mr-2"
54
+ text={t("actions.cancel")}
55
+ onClick={() => {
56
+ unstable_batchedUpdates(() => {
57
+ setEditingIndex(-1);
58
+ setName("");
59
+ });
60
+ }}
61
+ />
62
+ <PrimaryButton
63
+ text={t("actions.confirm")}
64
+ onClick={() => {
65
+ unstable_batchedUpdates(() => {
66
+ onEditLabel && onEditLabel(name, editingIndex);
67
+ setEditingIndex(-1);
68
+ setName("");
69
+ });
70
+ }}
71
+ />
72
+ </div>
73
+ </div>
74
+ </Modal>
36
75
  <nav className="-mb-px flex h-8 border-gray-200 dark:border-gray-700" role="tablist" aria-label="Tabs">
37
76
  {tabs.map((tab, tabIndex) => (
38
77
  <span
39
78
  role="tab"
40
79
  tabIndex={0}
41
- dangerouslySetInnerHTML={{
42
- __html: t(tab.label, tab.options)
43
- }}
80
+ // dangerouslySetInnerHTML={{
81
+ // __html: tab.label
82
+ // }}
44
83
  onClick={() => {
45
- onSelected(tab.key, tabIndex)
46
- }}
47
- onDoubleClick={() => {
48
- setEditList(v => {
49
- const nv = [...v];
50
- nv[tabIndex] = true;
51
- return nv
52
- })
53
- }}
54
- contentEditable={editList[tabIndex]}
55
- onInput={(e) => {
56
- onEditLabel && onEditLabel(`${e.currentTarget.textContent}`, tabIndex)
57
- }}
58
- onKeyDown={(e) => {
59
- if (e.key === 'Enter') {
60
- clearEditStatus();
61
- e.preventDefault();
62
- }
84
+ onSelected(tab.key, tabIndex);
63
85
  }}
64
86
  key={tab.key}
65
87
  className={classNames(
66
88
  tab.key === selectedKey
67
89
  ? "border rounded-t"
68
90
  : "text-gray-500 dark:text-gray-400 hover:text-gray-700 hover:bg-gray-50 dark:hover:text-gray-200 dark:hover:bg-gray-800",
69
- "whitespace-nowrap border-gray-200 dark:border-gray-700 py-1 px-2 pr-6 text-sm cursor-pointer dark:text-white"
91
+ "whitespace-nowrap border-gray-200 dark:border-gray-700 py-1 px-2 pr-6 text-sm cursor-default dark:text-white"
92
+ )}
93
+ >
94
+ {tab.label}{" "}
95
+ {tab.key === selectedKey && tab.editable && (
96
+ <PencilSquareIcon
97
+ className="w-3 inline cursor-pointer"
98
+ onClick={() => {
99
+ unstable_batchedUpdates(() => {
100
+ setEditingIndex(tabIndex);
101
+ setName(tab.label);
102
+ });
103
+ }}
104
+ />
70
105
  )}
71
- />
106
+ </span>
72
107
  ))}
73
108
  </nav>
74
109
  </div>
@@ -25,4 +25,15 @@ export const charsetOptions: IDropdownSelectOption[] = [
25
25
  label: 'GB18030',
26
26
  value: 'GB18030',
27
27
  },
28
+ ]
29
+
30
+ export const SUPPORTED_FILE_TYPES: IDropdownSelectOption[] = [
31
+ {
32
+ label: 'CSV',
33
+ value: 'csv',
34
+ },
35
+ {
36
+ label: 'JSON',
37
+ value: 'json',
38
+ }
28
39
  ]
@@ -9,7 +9,10 @@ import { useTranslation } from "react-i18next";
9
9
  import DefaultButton from "../../components/button/default";
10
10
  import PrimaryButton from "../../components/button/primary";
11
11
  import DropdownSelect from "../../components/dropdownSelect";
12
- import { charsetOptions } from "./config";
12
+ import { SUPPORTED_FILE_TYPES, charsetOptions } from "./config";
13
+ import { classNames } from "../../utils";
14
+ import { RadioGroup } from "@headlessui/react";
15
+ import { jsonReader } from "./utils";
13
16
 
14
17
  const Container = styled.div`
15
18
  overflow-x: auto;
@@ -22,6 +25,7 @@ const CSVData: React.FC<ICSVData> = (props) => {
22
25
  const { commonStore } = useGlobalStore();
23
26
  const { tmpDSName, tmpDataSource, tmpDSRawFields } = commonStore;
24
27
  const [encoding, setEncoding] = useState<string>("utf-8");
28
+ const [fileType, setFileType] = useState<string>("csv");
25
29
 
26
30
  const onSubmitData = useCallback(() => {
27
31
  commonStore.commitTempDS();
@@ -30,6 +34,27 @@ const CSVData: React.FC<ICSVData> = (props) => {
30
34
  const { t } = useTranslation("translation", { keyPrefix: "DataSource.dialog.file" });
31
35
  const fileLoaded = tmpDataSource.length > 0 && tmpDSRawFields.length > 0;
32
36
 
37
+ const fileUpload = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
38
+ const files = e.target.files;
39
+ if (files !== null) {
40
+ const file = files[0];
41
+ if (fileType === 'csv') {
42
+ FileReader.csvReader({
43
+ file,
44
+ config: { type: "reservoirSampling", size: Infinity },
45
+ onLoading: () => {},
46
+ encoding,
47
+ }).then((data) => {
48
+ commonStore.updateTempDS(data as IRow[]);
49
+ });
50
+ } else {
51
+ jsonReader(file).then((data) => {
52
+ commonStore.updateTempDS(data as IRow[]);
53
+ });
54
+ }
55
+ }
56
+ }, [fileType, encoding]);
57
+
33
58
  return (
34
59
  <Container>
35
60
  {!fileLoaded && (
@@ -49,49 +74,56 @@ const CSVData: React.FC<ICSVData> = (props) => {
49
74
  d="M9 13h6m-3-3v6m-9 1V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z"
50
75
  />
51
76
  </svg>
52
- <h3 className="mt-2 text-sm font-semibold text-gray-900">{t('choose_file')}</h3>
53
- <p className="mt-1 text-sm text-gray-500">{t('get_start_desc')}</p>
77
+ <h3 className="mt-2 text-sm font-semibold text-gray-900 dark:text-gray-50">{t("choose_file")}</h3>
78
+ <p className="mt-1 text-sm text-gray-500">{t("get_start_desc")}</p>
54
79
  </div>
55
80
  )}
56
- <input
57
- style={{ display: "none" }}
58
- type="file"
59
- ref={fileRef}
60
- onChange={(e) => {
61
- const files = e.target.files;
62
- if (files !== null) {
63
- const file = files[0];
64
- FileReader.csvReader({
65
- file,
66
- config: { type: "reservoirSampling", size: Infinity },
67
- onLoading: () => {},
68
- encoding,
69
- }).then((data) => {
70
- commonStore.updateTempDS(data as IRow[]);
71
- });
72
- }
73
- }}
74
- />
81
+ <input style={{ display: "none" }} type="file" ref={fileRef} onChange={fileUpload} />
75
82
  {!fileLoaded && (
76
- <div className="my-1 flex justify-center">
77
- <DefaultButton
78
- className="mr-2"
79
- onClick={() => {
80
- if (fileRef.current) {
81
- fileRef.current.click();
82
- }
83
- }}
84
- text={t("open")}
85
- />
86
- <div className="inline-block relative">
87
- <DropdownSelect
88
- buttonClassName="w-36"
89
- options={charsetOptions}
90
- selectedKey={encoding}
91
- onSelect={(k) => {
92
- setEncoding(k);
83
+ <div className="my-1">
84
+ <div className="flex justify-center">
85
+ <RadioGroup value={fileType} onChange={setFileType} className="mt-2">
86
+ <RadioGroup.Label className="sr-only"> Choose a memory option </RadioGroup.Label>
87
+ <div className="grid grid-cols-2 gap-3">
88
+ {SUPPORTED_FILE_TYPES.map((option) => (
89
+ <RadioGroup.Option
90
+ key={option.value}
91
+ value={option.value}
92
+ className={({ active, checked }) =>
93
+ classNames(
94
+ checked
95
+ ? "bg-indigo-600 text-white hover:bg-indigo-500"
96
+ : "ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800",
97
+ "flex cursor-pointer items-center justify-center rounded py-1 px-8 text-sm font-semibold uppercase sm:flex-1"
98
+ )
99
+ }
100
+ >
101
+ <RadioGroup.Label as="span">{option.label}</RadioGroup.Label>
102
+ </RadioGroup.Option>
103
+ ))}
104
+ </div>
105
+ </RadioGroup>
106
+ </div>
107
+ <div className="my-1 flex justify-center">
108
+ <DefaultButton
109
+ className="mr-2"
110
+ onClick={() => {
111
+ if (fileRef.current) {
112
+ fileRef.current.click();
113
+ }
93
114
  }}
115
+ text={t("open")}
94
116
  />
117
+ <div className="inline-block relative">
118
+ <DropdownSelect
119
+ buttonClassName="w-36"
120
+ options={charsetOptions}
121
+ selectedKey={encoding}
122
+ onSelect={(k) => {
123
+ setEncoding(k);
124
+ }}
125
+ />
126
+ </div>
95
127
  </div>
96
128
  </div>
97
129
  )}
@@ -1,4 +1,4 @@
1
- import React, { useRef, useCallback } from "react";
1
+ import React from "react";
2
2
  import { useGlobalStore } from "../../store";
3
3
  import { observer } from "mobx-react-lite";
4
4
 
@@ -7,7 +7,7 @@ interface GWFileProps {
7
7
  fileRef: React.RefObject<HTMLInputElement>;
8
8
  }
9
9
  const GWFile: React.FC<GWFileProps> = (props) => {
10
- const { commonStore, vizStore } = useGlobalStore();
10
+ const { vizStore } = useGlobalStore();
11
11
 
12
12
  return (
13
13
  <input
@@ -0,0 +1,28 @@
1
+ import { IDataSetInfo, IRow } from "../../interfaces";
2
+
3
+ export function jsonReader (file: File): Promise<IRow[]> {
4
+ return new Promise((resolve, reject) => {
5
+ const reader = new FileReader();
6
+ reader.onload = () => {
7
+ try {
8
+ const data = JSON.parse(reader.result as string);
9
+ if (!Array.isArray(data)) {
10
+ throw new Error('Invalid JSON file');
11
+ }
12
+ resolve(data);
13
+ } catch (e) {
14
+ reject(e);
15
+ }
16
+ };
17
+ reader.readAsText(file);
18
+ });
19
+ }
20
+
21
+ // export function jsonArray2DatasetInfo (data: IRow[]): IDataSetInfo {
22
+ // const fields = Object.keys(data[0]);
23
+ // return {
24
+ // name: 'New Dataset',
25
+ // rawFields: fields.map(f => ({ name: f, type: 'number' })),
26
+ // dataSource: data
27
+ // }
28
+ // }
@@ -2,6 +2,7 @@ import { IRow, IMutField } from "../interfaces";
2
2
  import { inferMeta } from "../lib/inferMeta";
3
3
  import { guardDataKeys } from "../utils/dataPrep";
4
4
 
5
+
5
6
  export function transData(dataSource: IRow[]): {
6
7
  dataSource: IRow[];
7
8
  fields: IMutField[];
@@ -12,7 +13,11 @@ export function transData(dataSource: IRow[]): {
12
13
  fields: [],
13
14
  };
14
15
  }
15
- const keys = Object.keys(dataSource[0]);
16
+ const sampleRecord = dataSource[0];
17
+ // const rawKeys = Object.keys(sampleRecord);
18
+ // let flatColKeys: string[] = flatNestKeys(sampleRecord);
19
+
20
+ const keys = Object.keys(sampleRecord);
16
21
  const metas = inferMeta({
17
22
  dataSource,
18
23
  fields: keys.map((k) => ({
@@ -21,7 +26,7 @@ export function transData(dataSource: IRow[]): {
21
26
  analyticType: "?",
22
27
  semanticType: "?",
23
28
  })),
24
- });
29
+ })
25
30
  const { safeData, safeMetas } = guardDataKeys(dataSource, metas);
26
31
  const finalData: IRow[] = [];
27
32
  for (let record of safeData) {
@@ -30,7 +35,7 @@ export function transData(dataSource: IRow[]): {
30
35
  if (field.semanticType === "quantitative") {
31
36
  newRecord[field.fid] = Number(record[field.fid]);
32
37
  } else {
33
- newRecord[field.fid] = record[field.fid];
38
+ newRecord[field.fid] = record[field.fid];//getValueByKeyPath(record, field.fid);// record[field.fid];
34
39
  }
35
40
  }
36
41
  finalData.push(newRecord);
@@ -20,18 +20,26 @@ const MeaFields: React.FC<Props> = (props) => {
20
20
  value: "bin",
21
21
  label: "Bin",
22
22
  },
23
+ {
24
+ value: 'binCount',
25
+ label: 'Bin Count'
26
+ },
23
27
  {
24
28
  value: "log10",
25
29
  label: "Log10",
26
30
  },
31
+ {
32
+ value: "log2",
33
+ label: "Log2",
34
+ },
27
35
  ];
28
36
  }, []);
29
37
 
30
38
  const fieldActionHandler = useCallback((selectedValue: any, opIndex: number, meaIndex: number) => {
31
- if (selectedValue === "bin") {
32
- vizStore.createBinField("measures", meaIndex);
33
- } else if (selectedValue === "log10") {
34
- vizStore.createLogField("measures", meaIndex);
39
+ if (selectedValue === "bin" || selectedValue === 'binCount') {
40
+ vizStore.createBinField("measures", meaIndex, selectedValue);
41
+ } else if (selectedValue === "log10" || selectedValue === "log2") {
42
+ vizStore.createLogField("measures", meaIndex, selectedValue);
35
43
  }
36
44
  }, []);
37
45
  return (
package/src/index.css CHANGED
@@ -10,11 +10,11 @@ html{
10
10
  }
11
11
  }
12
12
  body {
13
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
13
+ /* font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
14
14
  'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
15
- sans-serif;
16
- -webkit-font-smoothing: antialiased;
17
- -moz-osx-font-smoothing: grayscale;
15
+ sans-serif; */
16
+ /* -webkit-font-smoothing: antialiased;
17
+ -moz-osx-font-smoothing: grayscale; */
18
18
  margin: 0px;
19
19
  padding: 0px;
20
20
  }
package/src/index.tsx CHANGED
@@ -1,22 +1,22 @@
1
- import React, { createContext, useEffect, useRef, useState } from 'react';
2
- import { StyleSheetManager } from 'styled-components';
3
- import root from 'react-shadow';
4
- import { DOM } from '@kanaries/react-beautiful-dnd';
5
- import { observer } from 'mobx-react-lite';
6
- import App, { IGWProps } from './App';
7
- import { StoreWrapper } from './store/index';
8
- import { FieldsContextWrapper } from './fields/fieldsContext';
1
+ import React, { createContext, useEffect, useRef, useState } from "react";
2
+ import { StyleSheetManager } from "styled-components";
3
+ import root from "react-shadow";
4
+ import { DOM } from "@kanaries/react-beautiful-dnd";
5
+ import { observer } from "mobx-react-lite";
6
+ import App, { IGWProps } from "./App";
7
+ import { StoreWrapper } from "./store/index";
8
+ import { FieldsContextWrapper } from "./fields/fieldsContext";
9
9
 
10
- import './empty_sheet.css';
10
+ import "./empty_sheet.css";
11
11
  import tailwindStyle from "tailwindcss/tailwind.css?inline";
12
- import style from './index.css?inline';
13
-
12
+ import style from "./index.css?inline";
14
13
 
15
14
  export const ShadowDomContext = createContext<{ root: ShadowRoot | null }>({ root: null });
16
15
 
17
- export const GraphicWalker: React.FC<IGWProps> = observer(props => {
16
+ export const GraphicWalker: React.FC<IGWProps> = observer((props) => {
18
17
  const [shadowRoot, setShadowRoot] = useState<ShadowRoot | null>(null);
19
18
  const rootRef = useRef<HTMLDivElement>(null);
19
+ const { storeRef } = props;
20
20
 
21
21
  useEffect(() => {
22
22
  if (rootRef.current) {
@@ -32,20 +32,20 @@ export const GraphicWalker: React.FC<IGWProps> = observer(props => {
32
32
  }, []);
33
33
 
34
34
  return (
35
- <root.div mode="open" ref={rootRef}>
36
- <style>{tailwindStyle}</style>
37
- <style>{style}</style>
38
- {shadowRoot && (
39
- <StyleSheetManager target={shadowRoot}>
40
- <StoreWrapper keepAlive={props.keepAlive}>
35
+ <StoreWrapper keepAlive={props.keepAlive} storeRef={storeRef}>
36
+ <root.div mode="open" ref={rootRef}>
37
+ <style>{tailwindStyle}</style>
38
+ <style>{style}</style>
39
+ {shadowRoot && (
40
+ <StyleSheetManager target={shadowRoot}>
41
41
  <FieldsContextWrapper>
42
42
  <ShadowDomContext.Provider value={{ root: shadowRoot }}>
43
43
  <App {...props} />
44
44
  </ShadowDomContext.Provider>
45
45
  </FieldsContextWrapper>
46
- </StoreWrapper>
47
- </StyleSheetManager>
48
- )}
49
- </root.div>
46
+ </StyleSheetManager>
47
+ )}
48
+ </root.div>
49
+ </StoreWrapper>
50
50
  );
51
51
  });
package/src/interfaces.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { StatFuncName } from "visual-insights/build/esm/statistics";
2
2
  import { AggFC } from 'cube-core/built/types';
3
- import { IAnalyticType, IMutField as VIMutField, ISemanticType } from 'visual-insights';
3
+ import { IAnalyticType, ISemanticType } from 'visual-insights';
4
4
 
5
5
  export type DeepReadonly<T extends Record<keyof any, any>> = {
6
6
  readonly [K in keyof T]: T[K] extends Record<keyof any, any> ? DeepReadonly<T[K]> : T[K];
@@ -36,6 +36,28 @@ export interface IUncertainMutField {
36
36
  analyticType: IAnalyticType | '?';
37
37
  }
38
38
 
39
+
40
+ export type IExpParamter = {
41
+ type: 'field';
42
+ value: string;
43
+ } | {
44
+ type: 'value';
45
+ value: any;
46
+ } | {
47
+ type: 'expression';
48
+ value: IExpression;
49
+ } | {
50
+ type: 'constant';
51
+ value: any;
52
+ }
53
+
54
+
55
+ export interface IExpression {
56
+ op: 'bin' | 'log2' | 'log10' | 'one' | 'binCount';
57
+ params: IExpParamter[];
58
+ as: string;
59
+ }
60
+
39
61
  export interface IField {
40
62
  /**
41
63
  * fid: key in data record
@@ -52,6 +74,8 @@ export interface IField {
52
74
  semanticType: ISemanticType;
53
75
  analyticType: IAnalyticType;
54
76
  cmp?: (a: any, b: any) => number;
77
+ computed?: boolean;
78
+ expressoion?: IExpression;
55
79
  }
56
80
 
57
81
  export interface IViewField extends IField {
@@ -176,7 +200,7 @@ export interface IVisualConfig {
176
200
 
177
201
  export interface IVisSpec {
178
202
  readonly visId: string;
179
- readonly name?: [string, Record<string, any>?];
203
+ readonly name?: string;
180
204
  readonly encodings: DeepReadonly<DraggableFieldState>;
181
205
  readonly config: DeepReadonly<IVisualConfig>;
182
206
  }