@kanaries/graphic-walker 0.2.13 → 0.2.14

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 (87) hide show
  1. package/dist/App.d.ts +4 -2
  2. package/dist/assets/explainer.worker-8428eb12.js.map +1 -1
  3. package/dist/components/button/base.d.ts +1 -0
  4. package/dist/components/button/defaultMini.d.ts +4 -0
  5. package/dist/components/button/primaryMini.d.ts +4 -0
  6. package/dist/components/dropdownContext/index.d.ts +13 -0
  7. package/dist/components/dropdownSelect/index.d.ts +17 -0
  8. package/dist/components/modal.d.ts +1 -0
  9. package/dist/components/toolbar/toolbar-item.d.ts +1 -0
  10. package/dist/dataSource/dataSelection/config.d.ts +2 -0
  11. package/dist/dataSource/index.d.ts +1 -1
  12. package/dist/fields/datasetFields/dimFields.d.ts +2 -2
  13. package/dist/fields/datasetFields/meaFields.d.ts +2 -2
  14. package/dist/fields/encodeFields/singleEncodeDropDown.d.ts +17 -0
  15. package/dist/fields/encodeFields/singleEncodeEditor.d.ts +11 -0
  16. package/dist/fields/obComponents/obPill.d.ts +3 -3
  17. package/dist/graphic-walker.es.js +20874 -19172
  18. package/dist/graphic-walker.es.js.map +1 -1
  19. package/dist/graphic-walker.umd.js +245 -170
  20. package/dist/graphic-walker.umd.js.map +1 -1
  21. package/dist/insightBoard/index.d.ts +1 -1
  22. package/dist/interfaces.d.ts +1 -0
  23. package/dist/renderer/index.d.ts +3 -1
  24. package/dist/store/visualSpecStore.d.ts +1 -0
  25. package/dist/utils/index.d.ts +2 -0
  26. package/dist/utils/media.d.ts +2 -0
  27. package/dist/vis/react-vega.d.ts +2 -0
  28. package/dist/vis/theme.d.ts +130 -0
  29. package/package.json +2 -1
  30. package/src/App.tsx +8 -5
  31. package/src/components/button/base.ts +1 -0
  32. package/src/components/button/default.tsx +6 -2
  33. package/src/components/button/defaultMini.tsx +17 -0
  34. package/src/components/button/primary.tsx +6 -2
  35. package/src/components/button/primaryMini.tsx +21 -0
  36. package/src/components/callout.tsx +4 -1
  37. package/src/components/clickMenu.tsx +4 -2
  38. package/src/components/container.tsx +9 -0
  39. package/src/components/dataTable/index.tsx +42 -52
  40. package/src/components/dataTable/pagination.tsx +4 -4
  41. package/src/components/dataTypeIcon.tsx +1 -1
  42. package/src/components/dropdownContext/index.tsx +64 -0
  43. package/src/components/dropdownSelect/index.tsx +92 -0
  44. package/src/components/modal.tsx +26 -14
  45. package/src/components/tabs/defaultTab.tsx +4 -4
  46. package/src/components/tabs/editableTab.tsx +5 -5
  47. package/src/components/toolbar/components.tsx +18 -1
  48. package/src/components/toolbar/index.tsx +5 -0
  49. package/src/components/toolbar/toolbar-button.tsx +6 -1
  50. package/src/components/toolbar/toolbar-item.tsx +4 -0
  51. package/src/components/toolbar/toolbar-select-button.tsx +21 -4
  52. package/src/components/toolbar/toolbar-toggle-button.tsx +6 -1
  53. package/src/components/tooltip.tsx +4 -1
  54. package/src/dataSource/dataSelection/config.ts +28 -0
  55. package/src/dataSource/dataSelection/csvData.tsx +77 -32
  56. package/src/dataSource/dataSelection/gwFile.tsx +0 -8
  57. package/src/dataSource/dataSelection/index.tsx +1 -2
  58. package/src/dataSource/dataSelection/publicData.tsx +2 -3
  59. package/src/dataSource/index.tsx +81 -61
  60. package/src/fields/aestheticFields.tsx +3 -1
  61. package/src/fields/components.tsx +21 -2
  62. package/src/fields/datasetFields/dimFields.tsx +43 -35
  63. package/src/fields/datasetFields/index.tsx +2 -2
  64. package/src/fields/datasetFields/meaFields.tsx +73 -47
  65. package/src/fields/encodeFields/singleEncodeDropDown.tsx +92 -0
  66. package/src/fields/encodeFields/singleEncodeEditor.tsx +78 -0
  67. package/src/fields/filterField/filterEditDialog.tsx +2 -1
  68. package/src/fields/filterField/filterPill.tsx +1 -1
  69. package/src/fields/filterField/slider.tsx +1 -1
  70. package/src/fields/filterField/tabs.tsx +11 -21
  71. package/src/fields/obComponents/obPill.tsx +65 -35
  72. package/src/index.css +13 -0
  73. package/src/insightBoard/index.tsx +24 -23
  74. package/src/insightBoard/radioGroupButtons.tsx +7 -0
  75. package/src/interfaces.ts +1 -0
  76. package/src/lib/inferMeta.ts +1 -1
  77. package/src/locales/en-US.json +7 -4
  78. package/src/locales/zh-CN.json +5 -2
  79. package/src/main.tsx +1 -1
  80. package/src/renderer/index.tsx +2 -1
  81. package/src/store/visualSpecStore.ts +16 -0
  82. package/src/utils/index.ts +19 -0
  83. package/src/utils/media.ts +26 -0
  84. package/src/utils/normalization.ts +2 -1
  85. package/src/vis/react-vega.tsx +20 -4
  86. package/src/vis/theme.ts +126 -0
  87. package/src/visualSettings/index.tsx +24 -8
@@ -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">
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);
@@ -109,13 +109,14 @@ const FilterEditDialog: React.FC = observer(() => {
109
109
 
110
110
  return uncontrolledField ? (
111
111
  <Modal
112
+ show={Boolean(uncontrolledField)}
112
113
  title={t('editing')}
113
114
  onClose={() => vizStore.closeFilterEditing()}
114
115
  >
115
116
  <header className="text-lg font-semibold py-2 outline-none">
116
117
  {t('form.name')}
117
118
  </header>
118
- <input className="border py-1 px-4" readOnly value={uncontrolledField.name}/>
119
+ <input className="border py-1 px-4 bg-white text-gray-800" readOnly value={uncontrolledField.name}/>
119
120
  <header className="text-lg font-semibold py-2 outline-none">
120
121
  {t('form.rule')}
121
122
  </header>
@@ -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')}
@@ -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
- <TabList role="tablist">
372
- {
373
- tabs.map((tab, i) => (
374
- <TabHeader
375
- key={i}
376
- role="tab"
377
- aria-selected={which === tab}
378
- id={`filter-tab-${tab.replaceAll(/ /g, '_')}`}
379
- aria-controls={`filter-panel-${tab.replaceAll(/ /g, '_')}`}
380
- tabIndex={-1}
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 '@heroicons/react/24/outline';
2
- import { observer } from 'mobx-react-lite';
3
- import React 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
-
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('translation', { keyPrefix: 'constant.aggregator' });
23
+ const { t } = useTranslation("translation", { keyPrefix: "constant.aggregator" });
24
24
 
25
- return <Pill
26
- ref={provided.innerRef}
27
- colType={field.analyticType === 'dimension' ? 'discrete' : 'continuous'}
28
- {...provided.draggableProps}
29
- {...provided.dragHandleProps}
30
- >
31
- {field.name}&nbsp;
32
- {field.analyticType === 'measure' && field.fid !== COUNT_FIELD_ID && visualConfig.defaultAggregated && (
33
- <select
34
- className="bg-transparent text-gray-700 float-right focus:outline-none focus:border-gray-500"
35
- value={field.aggName || ''}
36
- onChange={(e) => { vizStore.setFieldAggregator(dkey.id, fIndex, e.target.value) }}
37
- >
38
- {
39
- AGGREGATOR_LIST.map(op => <option value={op} key={op}>{t(op)}</option>)
40
- }
41
- </select>
42
- )}
43
- {field.analyticType === 'dimension' && field.sort === 'ascending' && <BarsArrowUpIcon className='float-right w-3' role="status" aria-label="Sorted in ascending order" />}
44
- {field.analyticType === 'dimension' && field.sort === 'descending' && <BarsArrowDownIcon className='float-right w-3' role="status" aria-label="Sorted in descending order" />}
45
- </Pill>
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>&nbsp;
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 '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';
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
- return <div>
16
- {
17
- showInsightBoard && <Modal onClose={onCloseModal}>
18
- <InsightMainBoard
19
- dataSource={currentDataset.dataSource}
20
- fields={toJS(draggableFieldState.fields)}
21
- viewDs={viewDimensions}
22
- viewMs={viewMeasures}
23
- filters={toJS(filters)}
24
- />
25
- </Modal>
26
- }
27
- </div>
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,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
 
@@ -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;
@@ -50,8 +50,8 @@
50
50
  },
51
51
  "draggable_key": {
52
52
  "fields": "Fields",
53
- "columns": "Columns",
54
- "rows": "Rows",
53
+ "columns": "X-Axis",
54
+ "rows": "Y-Axis",
55
55
  "color": "Color",
56
56
  "opacity": "Opacity",
57
57
  "size": "Size",
@@ -105,7 +105,9 @@
105
105
  "file": {
106
106
  "open": "Open...",
107
107
  "submit": "Submit",
108
- "dataset_name": "Dataset Name"
108
+ "dataset_name": "Dataset Name",
109
+ "choose_file": "Choose a Data File",
110
+ "get_start_desc": "Get started by creating a dataset."
109
111
  },
110
112
  "public": {
111
113
  "submit": "Submit"
@@ -184,6 +186,7 @@
184
186
  },
185
187
  "actions": {
186
188
  "prev": "Previous",
187
- "next": "Next"
189
+ "next": "Next",
190
+ "drop_field": "Drop Field Here"
188
191
  }
189
192
  }
@@ -105,7 +105,9 @@
105
105
  "file": {
106
106
  "open": "打开文件...",
107
107
  "submit": "确认",
108
- "dataset_name": "数据集名称"
108
+ "dataset_name": "数据集名称",
109
+ "choose_file": "选择文件",
110
+ "get_start_desc": "创建一个数据集以开始分析"
109
111
  },
110
112
  "public": {
111
113
  "submit": "确认"
@@ -184,6 +186,7 @@
184
186
  },
185
187
  "actions": {
186
188
  "prev": "向前",
187
- "next": "向后"
189
+ "next": "向后",
190
+ "drop_field": "拖拽字段至此"
188
191
  }
189
192
  }
package/src/main.tsx CHANGED
@@ -9,7 +9,7 @@ inject();
9
9
 
10
10
  ReactDOM.render(
11
11
  <React.StrictMode>
12
- <GraphicWalker />
12
+ <GraphicWalker themeKey="g2" />
13
13
  </React.StrictMode>,
14
14
  document.getElementById("root")
15
15
  );
@@ -7,7 +7,7 @@ import { useGlobalStore } from '../store';
7
7
  import ReactVega, { IReactVegaHandler } from '../vis/react-vega';
8
8
 
9
9
 
10
- const ReactiveRenderer = forwardRef<IReactVegaHandler, {}>(function ReactiveRenderer (props, ref) {
10
+ const ReactiveRenderer = forwardRef<IReactVegaHandler, { themeKey?: 'vega' | 'g2' }>(function ReactiveRenderer ({ themeKey }, ref) {
11
11
  const { vizStore, commonStore } = useGlobalStore();
12
12
  const { draggableFieldState, visualConfig } = vizStore;
13
13
  const { geoms, interactiveScale, defaultAggregated, stack, showActions, size, exploration } = visualConfig;
@@ -105,6 +105,7 @@ const ReactiveRenderer = forwardRef<IReactVegaHandler, {}>(function ReactiveRend
105
105
  brushEncoding={exploration.mode === 'brush' ? exploration.brushDirection : 'none'}
106
106
  selectEncoding={exploration.mode === 'point' ? 'default' : 'none'}
107
107
  onGeomClick={handleGeomClick}
108
+ themeKey={themeKey}
108
109
  />
109
110
  </Resizable>
110
111
  });
@@ -53,6 +53,7 @@ function initEncoding(): DraggableFieldState {
53
53
  shape: [],
54
54
  radius: [],
55
55
  theta: [],
56
+ details: [],
56
57
  filters: [],
57
58
  };
58
59
  }
@@ -444,6 +445,21 @@ export class VizSpecStore {
444
445
  fields.splice(sourceIndex, 1);
445
446
  });
446
447
  }
448
+ public replaceField(sourceKey: keyof DraggableFieldState, sourceIndex: number, fid: string) {
449
+ if (MetaFieldKeys.includes(sourceKey)) return;
450
+ const enteringField = [
451
+ ...this.draggableFieldState.dimensions,
452
+ ...this.draggableFieldState.measures
453
+ ].find(which => which.fid === fid);
454
+ if (!enteringField) {
455
+ return;
456
+ }
457
+
458
+ this.useMutable(({ encodings }) => {
459
+ const fields = encodings[sourceKey];
460
+ fields.splice(sourceIndex, 1, toJS(enteringField));
461
+ });
462
+ }
447
463
  private appendFilter(index: number, data: IViewField) {
448
464
  this.useMutable(({ encodings }) => {
449
465
  encodings.filters.splice(index, 0, {
@@ -258,3 +258,22 @@ export function extendCountField(
258
258
  fields: nextFields,
259
259
  };
260
260
  }
261
+
262
+ export function getRange (nums: number[]): [number, number] {
263
+ let _min = Infinity;
264
+ let _max = -Infinity;
265
+ for (let i = 0; i < nums.length; i++) {
266
+ _min = Math.min(_min, nums[i]);
267
+ _max = Math.max(_max, nums[i]);
268
+ }
269
+ return [_min, _max];
270
+ }
271
+
272
+ export function makeNumbersBeautiful (nums: number[]): number[] {
273
+ const [min, max] = getRange(nums);
274
+ const range = max - min;
275
+ const step = Math.pow(10, Math.floor(Math.log10(range)));
276
+ return nums.map((num) => {
277
+ return Math.round(num / step) * step;
278
+ })
279
+ }
@@ -0,0 +1,26 @@
1
+ import { useEffect, useState } from "react";
2
+
3
+ export function currentMediaTheme(): "dark" | "light" {
4
+ if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
5
+ return "dark";
6
+ } else {
7
+ return "light";
8
+ }
9
+ }
10
+
11
+ export function useCurrentMediaTheme(): "dark" | "light" {
12
+ const [theme, setTheme] = useState<"dark" | "light">(currentMediaTheme());
13
+
14
+ useEffect(() => {
15
+ const mediaQuery = window.matchMedia?.("(prefers-color-scheme: dark)") as MediaQueryList | undefined;
16
+ const listener = (e: MediaQueryListEvent) => {
17
+ setTheme(e.matches ? "dark" : "light");
18
+ };
19
+ mediaQuery?.addEventListener("change", listener);
20
+ return () => {
21
+ mediaQuery?.removeEventListener("change", listener);
22
+ };
23
+ }, []);
24
+
25
+ return theme;
26
+ }
@@ -140,12 +140,13 @@ export function makeBinField(dataSource: IRow[], fid: string, binFid: string, bi
140
140
  if (val < _min) _min = val;
141
141
  }
142
142
  const step = (_max - _min) / binSize;
143
+ const beaStep = Math.max(-Math.round(Math.log10(_max - _min)) + 2, 0)
143
144
  return dataSource.map((r) => {
144
145
  let bIndex = Math.floor((r[fid] - _min) / step);
145
146
  if (bIndex === binSize) bIndex = binSize - 1;
146
147
  return {
147
148
  ...r,
148
- [binFid]: bIndex * step + _min,
149
+ [binFid]: Number(((bIndex * step + _min)).toFixed(beaStep)),
149
150
  };
150
151
  });
151
152
  }