@kanaries/graphic-walker 0.2.13 → 0.2.15
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 +6 -3
- package/dist/assets/explainer.worker-8428eb12.js.map +1 -1
- package/dist/components/button/base.d.ts +1 -0
- package/dist/components/button/defaultMini.d.ts +4 -0
- package/dist/components/button/primaryMini.d.ts +4 -0
- package/dist/components/callout.d.ts +2 -0
- package/dist/components/dropdownContext/index.d.ts +13 -0
- package/dist/components/dropdownSelect/index.d.ts +17 -0
- package/dist/components/modal.d.ts +1 -0
- package/dist/components/toolbar/components.d.ts +4 -1
- package/dist/components/toolbar/index.d.ts +2 -0
- package/dist/components/toolbar/toolbar-item.d.ts +4 -0
- package/dist/components/tooltip.d.ts +2 -0
- package/dist/dataSource/dataSelection/config.d.ts +2 -0
- package/dist/dataSource/index.d.ts +1 -1
- package/dist/fields/components.d.ts +0 -1
- package/dist/fields/datasetFields/dimFields.d.ts +2 -2
- package/dist/fields/datasetFields/meaFields.d.ts +2 -2
- package/dist/fields/encodeFields/singleEncodeDropDown.d.ts +17 -0
- package/dist/fields/encodeFields/singleEncodeEditor.d.ts +11 -0
- package/dist/fields/filterField/filterEditDialog.d.ts +1 -1
- package/dist/fields/obComponents/obPill.d.ts +3 -3
- package/dist/graphic-walker.es.js +21205 -19397
- package/dist/graphic-walker.es.js.map +1 -1
- package/dist/graphic-walker.umd.js +181 -236
- package/dist/graphic-walker.umd.js.map +1 -1
- package/dist/insightBoard/index.d.ts +1 -1
- package/dist/interfaces.d.ts +3 -0
- package/dist/renderer/index.d.ts +7 -3
- package/dist/store/visualSpecStore.d.ts +1 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/media.d.ts +3 -0
- package/dist/vis/react-vega.d.ts +5 -1
- package/dist/vis/theme.d.ts +146 -0
- package/dist/visualSettings/index.d.ts +2 -1
- package/package.json +2 -1
- package/src/App.tsx +24 -16
- package/src/components/button/base.ts +1 -0
- package/src/components/button/default.tsx +6 -2
- package/src/components/button/defaultMini.tsx +17 -0
- package/src/components/button/primary.tsx +6 -2
- package/src/components/button/primaryMini.tsx +21 -0
- package/src/components/callout.tsx +9 -4
- package/src/components/clickMenu.tsx +1 -5
- package/src/components/dataTable/index.tsx +42 -52
- package/src/components/dataTable/pagination.tsx +4 -4
- package/src/components/dataTypeIcon.tsx +1 -1
- package/src/components/dropdownContext/index.tsx +64 -0
- package/src/components/dropdownSelect/index.tsx +92 -0
- package/src/components/modal.tsx +20 -22
- package/src/components/sizeSetting.tsx +2 -2
- package/src/components/tabs/defaultTab.tsx +4 -4
- package/src/components/tabs/editableTab.tsx +5 -5
- package/src/components/toolbar/components.tsx +10 -8
- package/src/components/toolbar/index.tsx +16 -4
- package/src/components/toolbar/toolbar-button.tsx +8 -2
- package/src/components/toolbar/toolbar-item.tsx +18 -9
- package/src/components/toolbar/toolbar-select-button.tsx +21 -8
- package/src/components/toolbar/toolbar-toggle-button.tsx +8 -2
- package/src/components/tooltip.tsx +10 -3
- package/src/dataSource/dataSelection/config.ts +28 -0
- package/src/dataSource/dataSelection/csvData.tsx +77 -32
- package/src/dataSource/dataSelection/gwFile.tsx +0 -8
- package/src/dataSource/dataSelection/index.tsx +1 -2
- package/src/dataSource/dataSelection/publicData.tsx +2 -3
- package/src/dataSource/index.tsx +80 -61
- package/src/fields/aestheticFields.tsx +3 -1
- package/src/fields/components.tsx +20 -38
- package/src/fields/datasetFields/dimFields.tsx +43 -35
- package/src/fields/datasetFields/index.tsx +3 -4
- package/src/fields/datasetFields/meaFields.tsx +73 -47
- package/src/fields/encodeFields/singleEncodeDropDown.tsx +92 -0
- package/src/fields/encodeFields/singleEncodeEditor.tsx +78 -0
- package/src/fields/filterField/filterEditDialog.tsx +63 -98
- package/src/fields/filterField/filterPill.tsx +1 -1
- package/src/fields/filterField/slider.tsx +2 -2
- package/src/fields/filterField/tabs.tsx +11 -21
- package/src/fields/obComponents/obPill.tsx +65 -35
- package/src/index.css +13 -0
- package/src/insightBoard/index.tsx +24 -23
- package/src/insightBoard/mainBoard.tsx +9 -2
- package/src/insightBoard/radioGroupButtons.tsx +7 -0
- package/src/interfaces.ts +5 -1
- package/src/lib/inferMeta.ts +1 -1
- package/src/locales/en-US.json +11 -5
- package/src/locales/i18n.ts +7 -0
- package/src/locales/ja-JP.json +195 -0
- package/src/locales/zh-CN.json +9 -3
- package/src/main.tsx +1 -1
- package/src/renderer/index.tsx +96 -70
- package/src/store/visualSpecStore.ts +16 -0
- package/src/utils/index.ts +19 -0
- package/src/utils/media.ts +31 -0
- package/src/utils/normalization.ts +2 -1
- package/src/vis/react-vega.tsx +36 -5
- package/src/vis/theme.ts +124 -0
- package/src/visualSettings/index.tsx +29 -33
- package/dist/components/container.d.ts +0 -2
- package/src/components/container.tsx +0 -16
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
import { IDraggableStateKey } from "../../interfaces";
|
|
3
|
+
import { observer } from "mobx-react-lite";
|
|
4
|
+
import { useGlobalStore } from "../../store";
|
|
5
|
+
import DropdownSelect from "./singleEncodeDropDown";
|
|
6
|
+
import { DroppableProvided } from "react-beautiful-dnd";
|
|
7
|
+
import { ChevronUpDownIcon, TrashIcon } from "@heroicons/react/24/outline";
|
|
8
|
+
import { useTranslation } from "react-i18next";
|
|
9
|
+
import { COUNT_FIELD_ID } from "../../constants";
|
|
10
|
+
import DropdownContext from "../../components/dropdownContext";
|
|
11
|
+
import { AGGREGATOR_LIST } from "../fieldsContext";
|
|
12
|
+
import { Draggable, DroppableStateSnapshot } from "@kanaries/react-beautiful-dnd";
|
|
13
|
+
|
|
14
|
+
interface SingleEncodeEditorProps {
|
|
15
|
+
dkey: IDraggableStateKey;
|
|
16
|
+
provided: DroppableProvided;
|
|
17
|
+
snapshot: DroppableStateSnapshot;
|
|
18
|
+
}
|
|
19
|
+
const SingleEncodeEditor: React.FC<SingleEncodeEditorProps> = (props) => {
|
|
20
|
+
const { dkey, provided, snapshot } = props;
|
|
21
|
+
const { vizStore } = useGlobalStore();
|
|
22
|
+
const { draggableFieldState, visualConfig } = vizStore;
|
|
23
|
+
const channelItem = draggableFieldState[dkey.id][0];
|
|
24
|
+
const { t } = useTranslation();
|
|
25
|
+
|
|
26
|
+
const aggregationOptions = useMemo(() => {
|
|
27
|
+
return AGGREGATOR_LIST.map((op) => ({
|
|
28
|
+
value: op,
|
|
29
|
+
label: t(`constant.aggregator.${op}`),
|
|
30
|
+
}));
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className="p-1 select-none relative" {...provided.droppableProps} ref={provided.innerRef}>
|
|
35
|
+
<div className={`p-1.5 bg-gray-50 dark:bg-gray-900 border border-gray-200 dark:border-gray-700 flex item-center justify-center grow text-gray-500 dark:text-gray-400 ${snapshot.draggingFromThisWith || snapshot.isDraggingOver || !channelItem ? 'opacity-100' : 'opacity-0'} relative z-0`}>
|
|
36
|
+
{t('actions.drop_field')}
|
|
37
|
+
</div>
|
|
38
|
+
{channelItem && (
|
|
39
|
+
<Draggable key={channelItem.dragId} draggableId={channelItem.dragId} index={0}>
|
|
40
|
+
{(provided, snapshot) => {
|
|
41
|
+
return (
|
|
42
|
+
<div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} className="flex items-stretch absolute z-10 top-0 left-0 right-0 bottom-0 m-1">
|
|
43
|
+
<div
|
|
44
|
+
onClick={() => {
|
|
45
|
+
vizStore.removeField(dkey.id, 0);
|
|
46
|
+
}}
|
|
47
|
+
className="grow-0 shrink-0 px-1.5 flex items-center justify-center bg-red-50 dark:bg-red-900 border border-red-200 dark:border-red-700 cursor-pointer"
|
|
48
|
+
>
|
|
49
|
+
<TrashIcon className="w-4" />
|
|
50
|
+
</div>
|
|
51
|
+
<div className="flex-1 flex items-center border border-gray-200 dark:border-gray-700 border-l-0 px-2 space-x-2 truncate">
|
|
52
|
+
<span className="flex-1 truncate">
|
|
53
|
+
{channelItem.name}
|
|
54
|
+
</span>
|
|
55
|
+
{channelItem.analyticType === "measure" && channelItem.fid !== COUNT_FIELD_ID && visualConfig.defaultAggregated && (
|
|
56
|
+
<DropdownContext
|
|
57
|
+
options={aggregationOptions}
|
|
58
|
+
onSelect={(value) => {
|
|
59
|
+
vizStore.setFieldAggregator(dkey.id, 0, value);
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
<span className="bg-transparent text-gray-700 dark:text-gray-200 float-right focus:outline-none focus:border-gray-500 dark:focus:border-gray-400 flex items-center ml-2">
|
|
63
|
+
{channelItem.aggName || ""}
|
|
64
|
+
<ChevronUpDownIcon className="w-3" />
|
|
65
|
+
</span>
|
|
66
|
+
</DropdownContext>
|
|
67
|
+
)}
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
}}
|
|
72
|
+
</Draggable>
|
|
73
|
+
)}
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export default observer(SingleEncodeEditor);
|
|
@@ -1,64 +1,29 @@
|
|
|
1
|
-
import { CheckCircleIcon } from
|
|
2
|
-
import { observer } from
|
|
3
|
-
import React from
|
|
4
|
-
import { useTranslation } from
|
|
5
|
-
|
|
6
|
-
import Modal from
|
|
7
|
-
import type { IFilterField, IFilterRule } from
|
|
8
|
-
import { useGlobalStore } from
|
|
9
|
-
import Tabs, { RuleFormProps } from
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
onChange,
|
|
15
|
-
}) => {
|
|
16
|
-
return (
|
|
17
|
-
<Tabs
|
|
18
|
-
field={field}
|
|
19
|
-
onChange={onChange}
|
|
20
|
-
tabs={['range', 'one of']}
|
|
21
|
-
/>
|
|
22
|
-
);
|
|
1
|
+
import { CheckCircleIcon } from "@heroicons/react/24/outline";
|
|
2
|
+
import { observer } from "mobx-react-lite";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { useTranslation } from "react-i18next";
|
|
5
|
+
|
|
6
|
+
import Modal from "../../components/modal";
|
|
7
|
+
import type { IFilterField, IFilterRule } from "../../interfaces";
|
|
8
|
+
import { useGlobalStore } from "../../store";
|
|
9
|
+
import Tabs, { RuleFormProps } from "./tabs";
|
|
10
|
+
import DefaultButton from "../../components/button/default";
|
|
11
|
+
import PrimaryButton from "../../components/button/primary";
|
|
12
|
+
|
|
13
|
+
const QuantitativeRuleForm: React.FC<RuleFormProps> = ({ field, onChange }) => {
|
|
14
|
+
return <Tabs field={field} onChange={onChange} tabs={["range", "one of"]} />;
|
|
23
15
|
};
|
|
24
16
|
|
|
25
|
-
const NominalRuleForm: React.FC<RuleFormProps> = ({
|
|
26
|
-
field
|
|
27
|
-
onChange,
|
|
28
|
-
}) => {
|
|
29
|
-
return (
|
|
30
|
-
<Tabs
|
|
31
|
-
field={field}
|
|
32
|
-
onChange={onChange}
|
|
33
|
-
tabs={['one of']}
|
|
34
|
-
/>
|
|
35
|
-
);
|
|
17
|
+
const NominalRuleForm: React.FC<RuleFormProps> = ({ field, onChange }) => {
|
|
18
|
+
return <Tabs field={field} onChange={onChange} tabs={["one of"]} />;
|
|
36
19
|
};
|
|
37
20
|
|
|
38
|
-
const OrdinalRuleForm: React.FC<RuleFormProps> = ({
|
|
39
|
-
field,
|
|
40
|
-
onChange,
|
|
41
|
-
}) => {
|
|
42
|
-
return (
|
|
43
|
-
<Tabs
|
|
44
|
-
field={field}
|
|
45
|
-
onChange={onChange}
|
|
46
|
-
tabs={['range', 'one of']}
|
|
47
|
-
/>
|
|
48
|
-
);
|
|
21
|
+
const OrdinalRuleForm: React.FC<RuleFormProps> = ({ field, onChange }) => {
|
|
22
|
+
return <Tabs field={field} onChange={onChange} tabs={["range", "one of"]} />;
|
|
49
23
|
};
|
|
50
24
|
|
|
51
|
-
const TemporalRuleForm: React.FC<RuleFormProps> = ({
|
|
52
|
-
field,
|
|
53
|
-
onChange,
|
|
54
|
-
}) => {
|
|
55
|
-
return (
|
|
56
|
-
<Tabs
|
|
57
|
-
field={field}
|
|
58
|
-
onChange={onChange}
|
|
59
|
-
tabs={['one of', 'temporal range']}
|
|
60
|
-
/>
|
|
61
|
-
);
|
|
25
|
+
const TemporalRuleForm: React.FC<RuleFormProps> = ({ field, onChange }) => {
|
|
26
|
+
return <Tabs field={field} onChange={onChange} tabs={["one of", "temporal range"]} />;
|
|
62
27
|
};
|
|
63
28
|
|
|
64
29
|
const EmptyForm: React.FC<RuleFormProps> = () => <React.Fragment />;
|
|
@@ -67,7 +32,7 @@ const FilterEditDialog: React.FC = observer(() => {
|
|
|
67
32
|
const { vizStore } = useGlobalStore();
|
|
68
33
|
const { editingFilterIdx, draggableFieldState } = vizStore;
|
|
69
34
|
|
|
70
|
-
const { t } = useTranslation(
|
|
35
|
+
const { t } = useTranslation("translation", { keyPrefix: "filters" });
|
|
71
36
|
|
|
72
37
|
const field = React.useMemo(() => {
|
|
73
38
|
return editingFilterIdx !== null ? draggableFieldState.filters[editingFilterIdx] : null;
|
|
@@ -83,14 +48,20 @@ const FilterEditDialog: React.FC = observer(() => {
|
|
|
83
48
|
}
|
|
84
49
|
}, [field]);
|
|
85
50
|
|
|
86
|
-
const handleChange = React.useCallback(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
51
|
+
const handleChange = React.useCallback(
|
|
52
|
+
(r: IFilterRule) => {
|
|
53
|
+
if (editingFilterIdx !== null) {
|
|
54
|
+
setUncontrolledField(
|
|
55
|
+
(uf) =>
|
|
56
|
+
({
|
|
57
|
+
...uf,
|
|
58
|
+
rule: r,
|
|
59
|
+
} as IFilterField)
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
[editingFilterIdx]
|
|
64
|
+
);
|
|
94
65
|
|
|
95
66
|
const handleSubmit = React.useCallback(() => {
|
|
96
67
|
if (editingFilterIdx !== null) {
|
|
@@ -100,44 +71,38 @@ const FilterEditDialog: React.FC = observer(() => {
|
|
|
100
71
|
vizStore.closeFilterEditing();
|
|
101
72
|
}, [editingFilterIdx, uncontrolledField]);
|
|
102
73
|
|
|
103
|
-
const Form = field
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
74
|
+
const Form = field
|
|
75
|
+
? ({
|
|
76
|
+
quantitative: QuantitativeRuleForm,
|
|
77
|
+
nominal: NominalRuleForm,
|
|
78
|
+
ordinal: OrdinalRuleForm,
|
|
79
|
+
temporal: TemporalRuleForm,
|
|
80
|
+
}[field.semanticType] as React.FC<RuleFormProps>)
|
|
81
|
+
: EmptyForm;
|
|
82
|
+
|
|
110
83
|
return uncontrolledField ? (
|
|
111
|
-
<Modal
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
role="button"
|
|
131
|
-
tabIndex={0}
|
|
132
|
-
aria-label="ok"
|
|
133
|
-
className="cursor-pointer hover:bg-green-50 p-1"
|
|
134
|
-
onClick={handleSubmit}
|
|
135
|
-
strokeWidth="1.5"
|
|
136
|
-
/>
|
|
84
|
+
<Modal show={Boolean(uncontrolledField)} title={t("editing")} onClose={() => vizStore.closeFilterEditing()}>
|
|
85
|
+
<div className="p-4">
|
|
86
|
+
<h2 className="text-base font-semibold py-2 outline-none">{t("form.name")}</h2>
|
|
87
|
+
<span className="inline-flex items-center rounded-full bg-indigo-100 px-3 py-0.5 text-sm font-medium text-indigo-800">
|
|
88
|
+
{uncontrolledField.name}
|
|
89
|
+
</span>
|
|
90
|
+
<h3 className="text-base font-semibold py-2 outline-none">{t("form.rule")}</h3>
|
|
91
|
+
<Form field={uncontrolledField} onChange={handleChange} />
|
|
92
|
+
<div className="mt-4">
|
|
93
|
+
<PrimaryButton
|
|
94
|
+
onClick={handleSubmit}
|
|
95
|
+
text={t("btn.confirm")}
|
|
96
|
+
/>
|
|
97
|
+
<DefaultButton
|
|
98
|
+
className="ml-2"
|
|
99
|
+
onClick={() => vizStore.closeFilterEditing()}
|
|
100
|
+
text={t("btn.cancel")}
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
137
103
|
</div>
|
|
138
104
|
</Modal>
|
|
139
105
|
) : null;
|
|
140
106
|
});
|
|
141
107
|
|
|
142
|
-
|
|
143
108
|
export default FilterEditDialog;
|
|
@@ -73,7 +73,7 @@ const FilterPill: React.FC<FilterPillProps> = observer(props => {
|
|
|
73
73
|
{field.name}
|
|
74
74
|
</header>
|
|
75
75
|
<div
|
|
76
|
-
className="bg-white text-gray-500 hover:bg-gray-100 flex flex-row output"
|
|
76
|
+
className="bg-white dark:bg-zinc-900 text-gray-500 hover:bg-gray-100 flex flex-row output"
|
|
77
77
|
onClick={() => vizStore.setFilterEditing(fIndex)}
|
|
78
78
|
style={{ cursor: 'pointer' }}
|
|
79
79
|
title={t('to_edit')}
|
|
@@ -118,7 +118,6 @@ const Slider: React.FC<SliderProps> = React.memo(function Slider ({
|
|
|
118
118
|
};
|
|
119
119
|
|
|
120
120
|
const dragHandler = fromEvent(document.body, 'mousemove').pipe(
|
|
121
|
-
throttleTime(100),
|
|
122
121
|
map(ev => {
|
|
123
122
|
if (!trackRef.current || !dragging) {
|
|
124
123
|
return null;
|
|
@@ -142,6 +141,7 @@ const Slider: React.FC<SliderProps> = React.memo(function Slider ({
|
|
|
142
141
|
|
|
143
142
|
return pos;
|
|
144
143
|
}),
|
|
144
|
+
throttleTime(100),
|
|
145
145
|
filter(pos => {
|
|
146
146
|
return pos !== null && pos !== range[dragging === 'left' ? 0 : 1];
|
|
147
147
|
}),
|
|
@@ -197,7 +197,7 @@ const Slider: React.FC<SliderProps> = React.memo(function Slider ({
|
|
|
197
197
|
tabIndex={-1}
|
|
198
198
|
onMouseDown={ev => {
|
|
199
199
|
if (ev.buttons === 1) {
|
|
200
|
-
mouseOffsetRef.current = ev.nativeEvent.offsetX;
|
|
200
|
+
mouseOffsetRef.current = ev.nativeEvent.offsetX - (ev.target as HTMLDivElement).getBoundingClientRect().width;
|
|
201
201
|
setDragging('left');
|
|
202
202
|
}
|
|
203
203
|
}}
|
|
@@ -5,6 +5,7 @@ import styled from 'styled-components';
|
|
|
5
5
|
|
|
6
6
|
import type { IFilterField, IFilterRule } from '../../interfaces';
|
|
7
7
|
import { useGlobalStore } from '../../store';
|
|
8
|
+
import PureTabs from '../../components/tabs/defaultTab';
|
|
8
9
|
import Slider from './slider';
|
|
9
10
|
|
|
10
11
|
|
|
@@ -368,27 +369,16 @@ const Tabs: React.FC<TabsProps> = observer(({ field, onChange, tabs }) => {
|
|
|
368
369
|
|
|
369
370
|
return (
|
|
370
371
|
<TabsContainer>
|
|
371
|
-
<
|
|
372
|
-
{
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
onClick={() => {
|
|
382
|
-
if (which !== tab) {
|
|
383
|
-
setWhich(tab);
|
|
384
|
-
}
|
|
385
|
-
}}
|
|
386
|
-
>
|
|
387
|
-
{t(tab.replaceAll(/ /g, '_'))}
|
|
388
|
-
</TabHeader>
|
|
389
|
-
))
|
|
390
|
-
}
|
|
391
|
-
</TabList>
|
|
372
|
+
<PureTabs
|
|
373
|
+
selectedKey={which}
|
|
374
|
+
tabs={tabs.map(tab => ({
|
|
375
|
+
key: tab,
|
|
376
|
+
label: t(tab.replaceAll(/ /g, '_')),
|
|
377
|
+
}))}
|
|
378
|
+
onSelected={sk => {
|
|
379
|
+
setWhich(sk as typeof which);
|
|
380
|
+
}}
|
|
381
|
+
/>
|
|
392
382
|
<TabPanel>
|
|
393
383
|
{
|
|
394
384
|
tabs.map((tab, i) => {
|
|
@@ -1,48 +1,78 @@
|
|
|
1
|
-
import { BarsArrowDownIcon, BarsArrowUpIcon } from
|
|
2
|
-
import { observer } from
|
|
3
|
-
import React from
|
|
4
|
-
import { useTranslation } from
|
|
5
|
-
import { DraggableProvided } from
|
|
6
|
-
import { COUNT_FIELD_ID } from
|
|
7
|
-
import { IDraggableStateKey } from
|
|
8
|
-
import { useGlobalStore } from
|
|
9
|
-
import { Pill } from
|
|
10
|
-
import { AGGREGATOR_LIST } from
|
|
11
|
-
|
|
1
|
+
import { BarsArrowDownIcon, BarsArrowUpIcon, ChevronUpDownIcon } from "@heroicons/react/24/outline";
|
|
2
|
+
import { observer } from "mobx-react-lite";
|
|
3
|
+
import React, { useMemo } from "react";
|
|
4
|
+
import { useTranslation } from "react-i18next";
|
|
5
|
+
import { DraggableProvided } from "@kanaries/react-beautiful-dnd";
|
|
6
|
+
import { COUNT_FIELD_ID } from "../../constants";
|
|
7
|
+
import { IDraggableStateKey } from "../../interfaces";
|
|
8
|
+
import { useGlobalStore } from "../../store";
|
|
9
|
+
import { Pill } from "../components";
|
|
10
|
+
import { AGGREGATOR_LIST } from "../fieldsContext";
|
|
11
|
+
import DropdownContext from "../../components/dropdownContext";
|
|
12
12
|
|
|
13
13
|
interface PillProps {
|
|
14
14
|
provided: DraggableProvided;
|
|
15
15
|
fIndex: number;
|
|
16
16
|
dkey: IDraggableStateKey;
|
|
17
17
|
}
|
|
18
|
-
const OBPill: React.FC<PillProps> = props => {
|
|
18
|
+
const OBPill: React.FC<PillProps> = (props) => {
|
|
19
19
|
const { provided, dkey, fIndex } = props;
|
|
20
20
|
const { vizStore } = useGlobalStore();
|
|
21
21
|
const { visualConfig } = vizStore;
|
|
22
22
|
const field = vizStore.draggableFieldState[dkey.id][fIndex];
|
|
23
|
-
const { t } = useTranslation(
|
|
23
|
+
const { t } = useTranslation("translation", { keyPrefix: "constant.aggregator" });
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
25
|
+
const aggregationOptions = useMemo(() => {
|
|
26
|
+
return AGGREGATOR_LIST.map((op) => ({
|
|
27
|
+
value: op,
|
|
28
|
+
label: t(op),
|
|
29
|
+
}));
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Pill
|
|
34
|
+
ref={provided.innerRef}
|
|
35
|
+
colType={field.analyticType === "dimension" ? "discrete" : "continuous"}
|
|
36
|
+
{...provided.draggableProps}
|
|
37
|
+
{...provided.dragHandleProps}
|
|
38
|
+
>
|
|
39
|
+
<span className="flex-1 truncate">{field.name}</span>
|
|
40
|
+
{field.analyticType === "measure" && field.fid !== COUNT_FIELD_ID && visualConfig.defaultAggregated && (
|
|
41
|
+
<DropdownContext
|
|
42
|
+
options={aggregationOptions}
|
|
43
|
+
onSelect={(value) => {
|
|
44
|
+
vizStore.setFieldAggregator(dkey.id, fIndex, value);
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
<span className="bg-transparent text-gray-700 float-right focus:outline-none focus:border-gray-500 dark:focus:border-gray-400 flex items-center ml-2">
|
|
48
|
+
{field.aggName || ""}
|
|
49
|
+
<ChevronUpDownIcon className="w-3" />
|
|
50
|
+
</span>
|
|
51
|
+
</DropdownContext>
|
|
52
|
+
)}
|
|
53
|
+
{/* {field.analyticType === "measure" && field.fid !== COUNT_FIELD_ID && visualConfig.defaultAggregated && (
|
|
54
|
+
<select
|
|
55
|
+
className="bg-transparent text-gray-700 float-right focus:outline-none focus:border-gray-500"
|
|
56
|
+
value={field.aggName || ""}
|
|
57
|
+
onChange={(e) => {
|
|
58
|
+
vizStore.setFieldAggregator(dkey.id, fIndex, e.target.value);
|
|
59
|
+
}}
|
|
60
|
+
>
|
|
61
|
+
{AGGREGATOR_LIST.map((op) => (
|
|
62
|
+
<option className="inline" value={op} key={op}>
|
|
63
|
+
{t(op)}
|
|
64
|
+
</option>
|
|
65
|
+
))}
|
|
66
|
+
</select>
|
|
67
|
+
)} */}
|
|
68
|
+
{field.analyticType === "dimension" && field.sort === "ascending" && (
|
|
69
|
+
<BarsArrowUpIcon className="float-right w-3" role="status" aria-label="Sorted in ascending order" />
|
|
70
|
+
)}
|
|
71
|
+
{field.analyticType === "dimension" && field.sort === "descending" && (
|
|
72
|
+
<BarsArrowDownIcon className="float-right w-3" role="status" aria-label="Sorted in descending order" />
|
|
73
|
+
)}
|
|
74
|
+
</Pill>
|
|
75
|
+
);
|
|
76
|
+
};
|
|
47
77
|
|
|
48
78
|
export default observer(OBPill);
|
package/src/index.css
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
|
+
html{
|
|
2
|
+
margin: 0px;
|
|
3
|
+
padding: 0px;
|
|
4
|
+
background-color: #fff;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
@media (prefers-color-scheme: dark) {
|
|
8
|
+
html {
|
|
9
|
+
background-color: rgb(24 24 27);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
1
12
|
body {
|
|
2
13
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
3
14
|
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
4
15
|
sans-serif;
|
|
5
16
|
-webkit-font-smoothing: antialiased;
|
|
6
17
|
-moz-osx-font-smoothing: grayscale;
|
|
18
|
+
margin: 0px;
|
|
19
|
+
padding: 0px;
|
|
7
20
|
}
|
|
8
21
|
|
|
9
22
|
code {
|
|
@@ -1,30 +1,31 @@
|
|
|
1
|
-
import { toJS } from
|
|
2
|
-
import { observer } from
|
|
3
|
-
import React, { useCallback } from
|
|
4
|
-
import Modal from
|
|
5
|
-
import { useGlobalStore } from
|
|
6
|
-
import InsightMainBoard from
|
|
1
|
+
import { toJS } from "mobx";
|
|
2
|
+
import { observer } from "mobx-react-lite";
|
|
3
|
+
import React, { useCallback } from "react";
|
|
4
|
+
import Modal from "../components/modal";
|
|
5
|
+
import { useGlobalStore } from "../store";
|
|
6
|
+
import InsightMainBoard from "./mainBoard";
|
|
7
7
|
|
|
8
|
-
const InsightBoard: React.FC = props => {
|
|
8
|
+
const InsightBoard: React.FC = (props) => {
|
|
9
9
|
const { commonStore, vizStore } = useGlobalStore();
|
|
10
10
|
const { showInsightBoard, currentDataset, filters } = commonStore;
|
|
11
11
|
const { viewDimensions, viewMeasures, draggableFieldState } = vizStore;
|
|
12
12
|
const onCloseModal = useCallback(() => {
|
|
13
13
|
commonStore.setShowInsightBoard(false);
|
|
14
|
-
}, [])
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
14
|
+
}, []);
|
|
15
|
+
if (!showInsightBoard) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return (
|
|
19
|
+
<Modal onClose={onCloseModal} show={showInsightBoard}>
|
|
20
|
+
<InsightMainBoard
|
|
21
|
+
dataSource={currentDataset.dataSource}
|
|
22
|
+
fields={toJS(draggableFieldState.fields)}
|
|
23
|
+
viewDs={viewDimensions}
|
|
24
|
+
viewMs={viewMeasures}
|
|
25
|
+
filters={toJS(filters)}
|
|
26
|
+
/>
|
|
27
|
+
</Modal>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
29
30
|
|
|
30
|
-
export default observer(InsightBoard);
|
|
31
|
+
export default observer(InsightBoard);
|
|
@@ -9,6 +9,8 @@ import { baseVis, IReasonType } from "./std2vegaSpec";
|
|
|
9
9
|
import RadioGroupButtons from "./radioGroupButtons";
|
|
10
10
|
import { formatFieldName, mergeMeasures } from "./utils";
|
|
11
11
|
import { useTranslation } from "react-i18next";
|
|
12
|
+
import { useCurrentMediaTheme } from "../utils/media";
|
|
13
|
+
import { builtInThemes } from "../vis/theme";
|
|
12
14
|
|
|
13
15
|
const collection = Insight.IntentionWorkerCollection.init();
|
|
14
16
|
|
|
@@ -41,6 +43,8 @@ const InsightMainBoard: React.FC<InsightMainBoardProps> = (props) => {
|
|
|
41
43
|
const [valueExp, setValueExp] = useState<IMeasureWithStat[]>([]);
|
|
42
44
|
const { t } = useTranslation();
|
|
43
45
|
const container = useRef<HTMLDivElement>(null);
|
|
46
|
+
const mediaTheme = useCurrentMediaTheme();
|
|
47
|
+
const themeConfig = builtInThemes['g2'][mediaTheme];
|
|
44
48
|
|
|
45
49
|
const dimsWithTypes = useMemo(() => {
|
|
46
50
|
const dimensions = fields
|
|
@@ -111,10 +115,13 @@ const InsightMainBoard: React.FC<InsightMainBoardProps> = (props) => {
|
|
|
111
115
|
true
|
|
112
116
|
);
|
|
113
117
|
if (container.current) {
|
|
114
|
-
embed(container.current, _vegaSpec
|
|
118
|
+
embed(container.current, _vegaSpec, {
|
|
119
|
+
actions: false,
|
|
120
|
+
config: themeConfig
|
|
121
|
+
});
|
|
115
122
|
}
|
|
116
123
|
}
|
|
117
|
-
}, [visIndex, recSpaces, visSpaces, fields, dataSource]);
|
|
124
|
+
}, [visIndex, recSpaces, visSpaces, fields, dataSource, themeConfig]);
|
|
118
125
|
|
|
119
126
|
const FilterDesc = useMemo<React.ReactElement[]>(() => {
|
|
120
127
|
if (filters) {
|
|
@@ -9,9 +9,16 @@ const RGBContainer = styled.div`
|
|
|
9
9
|
border: 1px solid #f0f0f0;
|
|
10
10
|
background-color: #f0f0f0;
|
|
11
11
|
cursor: pointer;
|
|
12
|
+
@media (prefers-color-scheme: dark) {
|
|
13
|
+
background-color: #000;
|
|
14
|
+
border: 1px solid #4b5563;
|
|
15
|
+
}
|
|
12
16
|
}
|
|
13
17
|
.choosen.option {
|
|
14
18
|
background-color: #fff;
|
|
19
|
+
@media (prefers-color-scheme: dark) {
|
|
20
|
+
background-color: #000;
|
|
21
|
+
}
|
|
15
22
|
}
|
|
16
23
|
`;
|
|
17
24
|
|
package/src/interfaces.ts
CHANGED
|
@@ -119,6 +119,7 @@ export interface DraggableFieldState {
|
|
|
119
119
|
shape: IViewField[];
|
|
120
120
|
theta: IViewField[];
|
|
121
121
|
radius: IViewField[];
|
|
122
|
+
details: IViewField[];
|
|
122
123
|
filters: IFilterField[];
|
|
123
124
|
}
|
|
124
125
|
|
|
@@ -183,4 +184,7 @@ export interface IVisSpec {
|
|
|
183
184
|
export enum ISegmentKey {
|
|
184
185
|
vis = 'vis',
|
|
185
186
|
data = 'data'
|
|
186
|
-
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export type IThemeKey = 'vega' | 'g2';
|
|
190
|
+
export type IDarkMode = 'media' | 'light' | 'dark';
|
package/src/lib/inferMeta.ts
CHANGED
|
@@ -53,7 +53,7 @@ function inferAnalyticTypeFromSemanticType(semanticType: ISemanticType): IAnalyt
|
|
|
53
53
|
export function inferSemanticType(data: IRow[], fid: string): ISemanticType {
|
|
54
54
|
let st = UnivariateSummary.getFieldType(data, fid);
|
|
55
55
|
if (st === 'nominal') {
|
|
56
|
-
if (isDateTimeArray(data.map((row) => row[fid]))) st = 'temporal';
|
|
56
|
+
if (isDateTimeArray(data.map((row) => `${row[fid]}`))) st = 'temporal';
|
|
57
57
|
} else if (st === 'ordinal') {
|
|
58
58
|
const valueSet: Set<number> = new Set();
|
|
59
59
|
let _max = -Infinity;
|