@kanaries/graphic-walker 0.2.4 → 0.2.6
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 +1 -0
- package/dist/graphic-walker.es.js +1 -1
- package/dist/graphic-walker.es.js.map +1 -1
- package/dist/graphic-walker.umd.js +1 -1
- package/dist/graphic-walker.umd.js.map +1 -1
- package/package.json +3 -2
- package/src/App.tsx +141 -0
- package/src/assets/kanaries.ico +0 -0
- package/src/components/clickMenu.tsx +29 -0
- package/src/components/container.tsx +16 -0
- package/src/components/dataTypeIcon.tsx +20 -0
- package/src/components/liteForm.tsx +16 -0
- package/src/components/modal.tsx +85 -0
- package/src/components/sizeSetting.tsx +95 -0
- package/src/components/tabs/pureTab.tsx +70 -0
- package/src/config.ts +57 -0
- package/src/constants.ts +1 -0
- package/src/dataSource/config.ts +62 -0
- package/src/dataSource/dataSelection/csvData.tsx +77 -0
- package/src/dataSource/dataSelection/gwFile.tsx +38 -0
- package/src/dataSource/dataSelection/index.tsx +57 -0
- package/src/dataSource/dataSelection/publicData.tsx +57 -0
- package/src/dataSource/index.tsx +78 -0
- package/src/dataSource/pannel.tsx +71 -0
- package/src/dataSource/table.tsx +125 -0
- package/src/dataSource/utils.ts +47 -0
- package/src/fields/aestheticFields.tsx +23 -0
- package/src/fields/components.tsx +159 -0
- package/src/fields/datasetFields/dimFields.tsx +45 -0
- package/src/fields/datasetFields/fieldPill.tsx +10 -0
- package/src/fields/datasetFields/index.tsx +28 -0
- package/src/fields/datasetFields/meaFields.tsx +58 -0
- package/src/fields/fieldsContext.tsx +59 -0
- package/src/fields/filterField/filterEditDialog.tsx +143 -0
- package/src/fields/filterField/filterPill.tsx +113 -0
- package/src/fields/filterField/index.tsx +61 -0
- package/src/fields/filterField/slider.tsx +236 -0
- package/src/fields/filterField/tabs.tsx +421 -0
- package/src/fields/obComponents/obFContainer.tsx +40 -0
- package/src/fields/obComponents/obPill.tsx +48 -0
- package/src/fields/posFields/index.tsx +33 -0
- package/src/fields/select.tsx +31 -0
- package/src/fields/utils.ts +31 -0
- package/src/index.css +13 -0
- package/src/index.tsx +12 -0
- package/src/insightBoard/index.tsx +30 -0
- package/src/insightBoard/mainBoard.tsx +203 -0
- package/src/insightBoard/radioGroupButtons.tsx +50 -0
- package/src/insightBoard/selectionSpec.ts +113 -0
- package/src/insightBoard/std2vegaSpec.ts +184 -0
- package/src/insightBoard/utils.ts +32 -0
- package/src/insights.ts +408 -0
- package/src/interfaces.ts +154 -0
- package/src/locales/en-US.json +140 -0
- package/src/locales/i18n.ts +50 -0
- package/src/locales/zh-CN.json +140 -0
- package/src/main.tsx +10 -0
- package/src/models/visSpecHistory.ts +129 -0
- package/src/renderer/index.tsx +104 -0
- package/src/segments/visNav.tsx +48 -0
- package/src/services.ts +139 -0
- package/src/store/commonStore.ts +158 -0
- package/src/store/index.tsx +53 -0
- package/src/store/visualSpecStore.ts +586 -0
- package/src/utils/autoMark.ts +34 -0
- package/src/utils/index.ts +251 -0
- package/src/utils/normalization.ts +158 -0
- package/src/utils/save.ts +46 -0
- package/src/vis/future-react-vega.tsx +193 -0
- package/src/vis/gen-vega.tsx +52 -0
- package/src/vis/react-vega.tsx +398 -0
- package/src/visualSettings/index.tsx +252 -0
- package/src/visualSettings/menubar.tsx +109 -0
- package/src/vite-env.d.ts +1 -0
- package/src/workers/explainer.worker.js +78 -0
- package/src/workers/filter.worker.js +70 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { observer } from 'mobx-react-lite';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import styled from 'styled-components'
|
|
4
|
+
import { ArrowUturnLeftIcon, ArrowUturnRightIcon } from '@heroicons/react/24/solid';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { useGlobalStore } from '../store';
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export const MenubarContainer = styled.div({
|
|
10
|
+
marginBlock: '0 0.6em',
|
|
11
|
+
marginInline: '0.2em',
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const Button = styled.button(({ disabled = false }) => ({
|
|
15
|
+
'&:hover': disabled ? {} : {
|
|
16
|
+
backgroundColor: 'rgba(243, 244, 246, 0.7)',
|
|
17
|
+
},
|
|
18
|
+
color: disabled ? 'rgba(156, 163, 175, 0.5)' : 'rgb(55, 65, 81)',
|
|
19
|
+
'& > pre': {
|
|
20
|
+
display: 'inline-block',
|
|
21
|
+
marginInlineStart: '0.2em',
|
|
22
|
+
},
|
|
23
|
+
marginInlineStart: '0.6em',
|
|
24
|
+
'&:first-child': {
|
|
25
|
+
marginInlineStart: '0',
|
|
26
|
+
},
|
|
27
|
+
cursor: disabled ? 'default' : 'pointer',
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
interface ButtonWithShortcutProps {
|
|
31
|
+
label: string;
|
|
32
|
+
shortcut: string;
|
|
33
|
+
disabled: boolean;
|
|
34
|
+
handler: () => void;
|
|
35
|
+
icon?: JSX.Element;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const ButtonWithShortcut: React.FC<ButtonWithShortcutProps> = ({ label, shortcut, disabled, handler, icon }) => {
|
|
39
|
+
const { t } = useTranslation('translation', { keyPrefix: 'main.tabpanel.menubar' });
|
|
40
|
+
|
|
41
|
+
const rule = React.useMemo(() => {
|
|
42
|
+
const keys = shortcut.split('+').map(d => d.trim());
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
key: keys.filter(
|
|
46
|
+
d => /^[a-z]$/i.test(d)
|
|
47
|
+
)[0],
|
|
48
|
+
ctrlKey: keys.includes('Ctrl'),
|
|
49
|
+
shiftKey: keys.includes('Shift'),
|
|
50
|
+
altKey: keys.includes('Alt'),
|
|
51
|
+
};
|
|
52
|
+
}, [shortcut]);
|
|
53
|
+
|
|
54
|
+
React.useEffect(() => {
|
|
55
|
+
const cb = (ev: KeyboardEvent) => {
|
|
56
|
+
if (
|
|
57
|
+
ev.ctrlKey === rule.ctrlKey
|
|
58
|
+
&& ev.shiftKey === rule.shiftKey
|
|
59
|
+
&& ev.altKey === rule.altKey
|
|
60
|
+
&& ev.key.toLowerCase() === rule.key.toLowerCase()
|
|
61
|
+
) {
|
|
62
|
+
handler();
|
|
63
|
+
ev.stopPropagation();
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
document.body.addEventListener('keydown', cb);
|
|
68
|
+
|
|
69
|
+
return () => document.body.removeEventListener('keydown', cb);
|
|
70
|
+
}, [rule, handler]);
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<Button
|
|
74
|
+
className="text-sm px-3 py-1 border text-gray-400 select-none"
|
|
75
|
+
disabled={disabled}
|
|
76
|
+
onClick={handler}
|
|
77
|
+
aria-label={t(label)}
|
|
78
|
+
title={`${t(label)} (${shortcut})`}
|
|
79
|
+
>
|
|
80
|
+
{icon || t(label)}
|
|
81
|
+
</Button>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const Menubar: React.FC = () => {
|
|
86
|
+
const { vizStore } = useGlobalStore();
|
|
87
|
+
const { canUndo, canRedo } = vizStore;
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<MenubarContainer>
|
|
91
|
+
<ButtonWithShortcut
|
|
92
|
+
label="undo"
|
|
93
|
+
disabled={!canUndo}
|
|
94
|
+
handler={vizStore.undo.bind(vizStore)}
|
|
95
|
+
shortcut="Ctrl+Z"
|
|
96
|
+
icon={<ArrowUturnLeftIcon width="1.4em" height="1.4em" />}
|
|
97
|
+
/>
|
|
98
|
+
<ButtonWithShortcut
|
|
99
|
+
label="redo"
|
|
100
|
+
disabled={!canRedo}
|
|
101
|
+
handler={vizStore.redo.bind(vizStore)}
|
|
102
|
+
shortcut="Ctrl+Shift+Z"
|
|
103
|
+
icon={<ArrowUturnRightIcon width="1.4em" height="1.4em" />}
|
|
104
|
+
/>
|
|
105
|
+
</MenubarContainer>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export default observer(Menubar);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/* eslint no-restricted-globals: 0 */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
// import { Record, Filters } from '../interfaces';
|
|
4
|
+
import { getPredicatesFromVegaSignals } from '../utils';
|
|
5
|
+
import { DataExplainer } from '../insights';
|
|
6
|
+
const state = {
|
|
7
|
+
de: null
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
function preAnalysis (props) {
|
|
11
|
+
const { fields, dataSource } = props; // as ReqData;
|
|
12
|
+
const de = new DataExplainer(dataSource);
|
|
13
|
+
de.setFields(fields);
|
|
14
|
+
de.preAnalysis();
|
|
15
|
+
state.de = de;
|
|
16
|
+
return true
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getFieldsSummary (props) {
|
|
20
|
+
const de = state.de;
|
|
21
|
+
if (de !== null) {
|
|
22
|
+
return de.engine.fields;
|
|
23
|
+
}
|
|
24
|
+
throw new Error('data explainer is not init.')
|
|
25
|
+
}
|
|
26
|
+
function getExplaination(props) {
|
|
27
|
+
|
|
28
|
+
const { filters = {}, currentSpace } = props; // as ReqData;
|
|
29
|
+
const predicates = getPredicatesFromVegaSignals(filters, currentSpace.dimensions, []);
|
|
30
|
+
const de = state.de;
|
|
31
|
+
const ansSpaces = de.explain(predicates, currentSpace.dimensions, currentSpace.measures);
|
|
32
|
+
const visSpaces = de.getVisSpec(ansSpaces);
|
|
33
|
+
const valueExp = de.explainValue(predicates, currentSpace.dimensions, currentSpace.measures);
|
|
34
|
+
const measureStats = [];
|
|
35
|
+
for (let i = 0; i < valueExp.length; i++) {
|
|
36
|
+
if (valueExp[i] !== 0) {
|
|
37
|
+
measureStats.push({
|
|
38
|
+
...currentSpace.measures[i],
|
|
39
|
+
score: valueExp[i]
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const fields = de.engine.fields;
|
|
44
|
+
return {
|
|
45
|
+
explainations: ansSpaces,
|
|
46
|
+
visSpaces,
|
|
47
|
+
valueExp: measureStats,
|
|
48
|
+
fieldsWithSemanticType: fields.map(f => ({
|
|
49
|
+
key: f.key,
|
|
50
|
+
type: f.semanticType
|
|
51
|
+
}))
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function main (e) {
|
|
56
|
+
const { type, data } = e.data;
|
|
57
|
+
console.log(type, data)
|
|
58
|
+
let res = false;
|
|
59
|
+
try {
|
|
60
|
+
switch (type) {
|
|
61
|
+
case 'getExplaination':
|
|
62
|
+
res = getExplaination(data);
|
|
63
|
+
break;
|
|
64
|
+
case 'preAnalysis':
|
|
65
|
+
res = preAnalysis(data);
|
|
66
|
+
break;
|
|
67
|
+
default:
|
|
68
|
+
throw new Error(`type ${type} is not supported.`);
|
|
69
|
+
}
|
|
70
|
+
self.postMessage(res);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
// discuss
|
|
73
|
+
console.log(error)
|
|
74
|
+
self.postMessage(false)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
self.addEventListener('message', main, false);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/* eslint no-restricted-globals: 0 */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {import('../interfaces').IRow[]} dataSource
|
|
6
|
+
* @param {import('../interfaces').IFilterField[]} filters
|
|
7
|
+
* @return {import('../interfaces').IRow[]}
|
|
8
|
+
*/
|
|
9
|
+
const filter = (dataSource, filters) => {
|
|
10
|
+
return dataSource.filter(which => {
|
|
11
|
+
for (const { rule, fid } of filters) {
|
|
12
|
+
if (!rule) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
switch (rule.type) {
|
|
17
|
+
case 'one of': {
|
|
18
|
+
if (rule.value.has(which[fid])) {
|
|
19
|
+
break;
|
|
20
|
+
} else {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
case 'range': {
|
|
25
|
+
if (rule.value[0] <= which[fid] && which[fid] <= rule.value[1]) {
|
|
26
|
+
break;
|
|
27
|
+
} else {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
case 'temporal range': {
|
|
32
|
+
try {
|
|
33
|
+
const time = new Date(which[fid]).getTime();
|
|
34
|
+
|
|
35
|
+
if (
|
|
36
|
+
rule.value[0] <= time && time <= rule.value[1]
|
|
37
|
+
) {
|
|
38
|
+
break;
|
|
39
|
+
} else {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error(error);
|
|
44
|
+
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
default: {
|
|
49
|
+
console.warn('Unresolvable filter rule', rule);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return true;
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @param {MessageEvent<{ dataSource: import('../interfaces').IRow[]; filters: import('../interfaces').IFilterField[] }>} e
|
|
61
|
+
*/
|
|
62
|
+
const main = e => {
|
|
63
|
+
const { dataSource, filters } = e.data;
|
|
64
|
+
|
|
65
|
+
const filtered = filter(dataSource, filters);
|
|
66
|
+
|
|
67
|
+
self.postMessage(filtered);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
self.addEventListener('message', main, false);
|