@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.
- package/dist/App.d.ts +2 -0
- package/dist/assets/explainer.worker-8428eb12.js.map +1 -1
- package/dist/assets/transform.worker-5d54ff09.js.map +1 -0
- package/dist/assets/viewQuery.worker-ffefc111.js.map +1 -0
- package/dist/components/codeExport/index.d.ts +3 -0
- package/dist/components/loadingLayer.d.ts +2 -0
- package/dist/components/tabs/defaultTab.d.ts +1 -0
- package/dist/components/tabs/editableTab.d.ts +1 -2
- package/dist/dataSource/dataSelection/config.d.ts +1 -0
- package/dist/dataSource/dataSelection/utils.d.ts +2 -0
- package/dist/datasets/tmp/test.json +1 -0
- package/dist/graphic-walker.es.js +23081 -22577
- package/dist/graphic-walker.es.js.map +1 -1
- package/dist/graphic-walker.umd.js +130 -130
- package/dist/graphic-walker.umd.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/interfaces.d.ts +21 -1
- package/dist/lib/execExp.d.ts +8 -0
- package/dist/lib/interfaces.d.ts +22 -0
- package/dist/lib/op/aggregate.d.ts +3 -0
- package/dist/lib/op/bin.d.ts +3 -0
- package/dist/lib/op/fold.d.ts +3 -0
- package/dist/lib/op/stat.d.ts +8 -0
- package/dist/lib/viewQuery.d.ts +5 -0
- package/dist/models/visSpecHistory.d.ts +2 -0
- package/dist/renderer/index.d.ts +8 -7
- package/dist/renderer/specRenderer.d.ts +13 -0
- package/dist/services.d.ts +4 -1
- package/dist/store/commonStore.d.ts +6 -0
- package/dist/store/index.d.ts +3 -2
- package/dist/store/visualSpecStore.d.ts +11 -4
- package/dist/utils/dataPrep.d.ts +2 -0
- package/dist/utils/index.d.ts +3 -5
- package/dist/utils/save.d.ts +1 -2
- package/dist/vis/react-vega.d.ts +1 -22
- package/dist/vis/spec/aggregate.d.ts +4 -0
- package/dist/vis/spec/encode.d.ts +19 -0
- package/dist/vis/spec/field.d.ts +2 -0
- package/dist/vis/spec/mark.d.ts +7 -0
- package/dist/vis/spec/stack.d.ts +4 -0
- package/dist/vis/spec/view.d.ts +67 -0
- package/dist/workers/transform.d.ts +2 -0
- package/package.json +4 -3
- package/src/App.tsx +5 -2
- package/src/components/codeExport/index.tsx +114 -0
- package/src/components/dataTable/index.tsx +10 -10
- package/src/components/loadingLayer.tsx +7 -0
- package/src/components/tabs/defaultTab.tsx +4 -2
- package/src/components/tabs/editableTab.tsx +74 -39
- package/src/dataSource/dataSelection/config.ts +11 -0
- package/src/dataSource/dataSelection/csvData.tsx +71 -39
- package/src/dataSource/dataSelection/gwFile.tsx +2 -2
- package/src/dataSource/dataSelection/utils.ts +28 -0
- package/src/dataSource/utils.ts +8 -3
- package/src/fields/datasetFields/meaFields.tsx +12 -4
- package/src/index.css +4 -4
- package/src/index.tsx +22 -22
- package/src/interfaces.ts +26 -2
- package/src/lib/execExp.ts +147 -0
- package/src/lib/interfaces.ts +39 -0
- package/src/lib/op/aggregate.ts +49 -0
- package/src/lib/op/bin.ts +25 -0
- package/src/lib/op/fold.ts +17 -0
- package/src/lib/op/stat.ts +46 -0
- package/src/lib/viewQuery.ts +23 -0
- package/src/locales/en-US.json +4 -2
- package/src/locales/i18n.ts +0 -1
- package/src/locales/ja-JP.json +4 -2
- package/src/locales/zh-CN.json +4 -2
- package/src/main.tsx +1 -1
- package/src/models/visSpecHistory.ts +14 -0
- package/src/renderer/index.tsx +58 -126
- package/src/renderer/specRenderer.tsx +119 -0
- package/src/segments/segmentNav.tsx +3 -16
- package/src/segments/visNav.tsx +17 -6
- package/src/services.ts +37 -1
- package/src/store/commonStore.ts +14 -9
- package/src/store/index.tsx +11 -4
- package/src/store/visualSpecStore.ts +89 -50
- package/src/utils/dataPrep.ts +24 -0
- package/src/utils/index.ts +16 -17
- package/src/utils/normalization.ts +3 -1
- package/src/utils/save.ts +1 -2
- package/src/vis/react-vega.tsx +4 -340
- package/src/vis/spec/aggregate.ts +13 -0
- package/src/vis/spec/encode.ts +69 -0
- package/src/vis/spec/field.ts +10 -0
- package/src/vis/spec/mark.ts +30 -0
- package/src/vis/spec/stack.ts +11 -0
- package/src/vis/spec/view.ts +138 -0
- package/src/vis/theme.ts +12 -0
- package/src/visualSettings/index.tsx +10 -1
- package/src/workers/transform.ts +12 -0
- package/src/workers/transform.worker.js +13 -0
- package/src/workers/viewQuery.worker.js +16 -0
- package/dist/dataSource/pannel.d.ts +0 -5
- 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-
|
|
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-
|
|
156
|
-
{data.slice(from, to).map((row, index) => (
|
|
157
|
-
<tr className={"divide-x divide-gray-200 dark:divide-gray-
|
|
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>
|
|
@@ -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, {
|
|
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
|
-
|
|
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,
|
|
23
|
-
const [
|
|
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"
|
|
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
|
-
|
|
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-
|
|
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(
|
|
53
|
-
<p className="mt-1 text-sm text-gray-500">{t(
|
|
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
|
|
77
|
-
<
|
|
78
|
-
className="
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
|
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 {
|
|
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
|
+
// }
|
package/src/dataSource/utils.ts
CHANGED
|
@@ -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
|
|
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
|
|
2
|
-
import { StyleSheetManager } from
|
|
3
|
-
import root from
|
|
4
|
-
import { DOM } from
|
|
5
|
-
import { observer } from
|
|
6
|
-
import App, { IGWProps } from
|
|
7
|
-
import { StoreWrapper } from
|
|
8
|
-
import { FieldsContextWrapper } from
|
|
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
|
|
10
|
+
import "./empty_sheet.css";
|
|
11
11
|
import tailwindStyle from "tailwindcss/tailwind.css?inline";
|
|
12
|
-
import style from
|
|
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
|
-
<
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
<
|
|
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
|
-
</
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
</
|
|
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,
|
|
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?:
|
|
203
|
+
readonly name?: string;
|
|
180
204
|
readonly encodings: DeepReadonly<DraggableFieldState>;
|
|
181
205
|
readonly config: DeepReadonly<IVisualConfig>;
|
|
182
206
|
}
|