@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.
Files changed (76) hide show
  1. package/dist/App.d.ts +1 -0
  2. package/dist/graphic-walker.es.js +1 -1
  3. package/dist/graphic-walker.es.js.map +1 -1
  4. package/dist/graphic-walker.umd.js +1 -1
  5. package/dist/graphic-walker.umd.js.map +1 -1
  6. package/package.json +3 -2
  7. package/src/App.tsx +141 -0
  8. package/src/assets/kanaries.ico +0 -0
  9. package/src/components/clickMenu.tsx +29 -0
  10. package/src/components/container.tsx +16 -0
  11. package/src/components/dataTypeIcon.tsx +20 -0
  12. package/src/components/liteForm.tsx +16 -0
  13. package/src/components/modal.tsx +85 -0
  14. package/src/components/sizeSetting.tsx +95 -0
  15. package/src/components/tabs/pureTab.tsx +70 -0
  16. package/src/config.ts +57 -0
  17. package/src/constants.ts +1 -0
  18. package/src/dataSource/config.ts +62 -0
  19. package/src/dataSource/dataSelection/csvData.tsx +77 -0
  20. package/src/dataSource/dataSelection/gwFile.tsx +38 -0
  21. package/src/dataSource/dataSelection/index.tsx +57 -0
  22. package/src/dataSource/dataSelection/publicData.tsx +57 -0
  23. package/src/dataSource/index.tsx +78 -0
  24. package/src/dataSource/pannel.tsx +71 -0
  25. package/src/dataSource/table.tsx +125 -0
  26. package/src/dataSource/utils.ts +47 -0
  27. package/src/fields/aestheticFields.tsx +23 -0
  28. package/src/fields/components.tsx +159 -0
  29. package/src/fields/datasetFields/dimFields.tsx +45 -0
  30. package/src/fields/datasetFields/fieldPill.tsx +10 -0
  31. package/src/fields/datasetFields/index.tsx +28 -0
  32. package/src/fields/datasetFields/meaFields.tsx +58 -0
  33. package/src/fields/fieldsContext.tsx +59 -0
  34. package/src/fields/filterField/filterEditDialog.tsx +143 -0
  35. package/src/fields/filterField/filterPill.tsx +113 -0
  36. package/src/fields/filterField/index.tsx +61 -0
  37. package/src/fields/filterField/slider.tsx +236 -0
  38. package/src/fields/filterField/tabs.tsx +421 -0
  39. package/src/fields/obComponents/obFContainer.tsx +40 -0
  40. package/src/fields/obComponents/obPill.tsx +48 -0
  41. package/src/fields/posFields/index.tsx +33 -0
  42. package/src/fields/select.tsx +31 -0
  43. package/src/fields/utils.ts +31 -0
  44. package/src/index.css +13 -0
  45. package/src/index.tsx +12 -0
  46. package/src/insightBoard/index.tsx +30 -0
  47. package/src/insightBoard/mainBoard.tsx +203 -0
  48. package/src/insightBoard/radioGroupButtons.tsx +50 -0
  49. package/src/insightBoard/selectionSpec.ts +113 -0
  50. package/src/insightBoard/std2vegaSpec.ts +184 -0
  51. package/src/insightBoard/utils.ts +32 -0
  52. package/src/insights.ts +408 -0
  53. package/src/interfaces.ts +154 -0
  54. package/src/locales/en-US.json +140 -0
  55. package/src/locales/i18n.ts +50 -0
  56. package/src/locales/zh-CN.json +140 -0
  57. package/src/main.tsx +10 -0
  58. package/src/models/visSpecHistory.ts +129 -0
  59. package/src/renderer/index.tsx +104 -0
  60. package/src/segments/visNav.tsx +48 -0
  61. package/src/services.ts +139 -0
  62. package/src/store/commonStore.ts +158 -0
  63. package/src/store/index.tsx +53 -0
  64. package/src/store/visualSpecStore.ts +586 -0
  65. package/src/utils/autoMark.ts +34 -0
  66. package/src/utils/index.ts +251 -0
  67. package/src/utils/normalization.ts +158 -0
  68. package/src/utils/save.ts +46 -0
  69. package/src/vis/future-react-vega.tsx +193 -0
  70. package/src/vis/gen-vega.tsx +52 -0
  71. package/src/vis/react-vega.tsx +398 -0
  72. package/src/visualSettings/index.tsx +252 -0
  73. package/src/visualSettings/menubar.tsx +109 -0
  74. package/src/vite-env.d.ts +1 -0
  75. package/src/workers/explainer.worker.js +78 -0
  76. package/src/workers/filter.worker.js +70 -0
@@ -0,0 +1,77 @@
1
+ import React, { useRef, useCallback } from 'react';
2
+ import { FileReader } from '@kanaries/web-data-loader';
3
+ import { IRow } from '../../interfaces';
4
+ import Table from '../table';
5
+ import styled from 'styled-components';
6
+ import { useGlobalStore } from '../../store';
7
+ import { observer } from 'mobx-react-lite';
8
+ import { useTranslation } from 'react-i18next';
9
+
10
+ const Container = styled.div`
11
+ overflow-x: auto;
12
+ `;
13
+
14
+ interface ICSVData {
15
+ }
16
+ const CSVData: React.FC<ICSVData> = props => {
17
+ const fileRef = useRef<HTMLInputElement>(null);
18
+ const { commonStore } = useGlobalStore();
19
+ const { tmpDSName, tmpDataSource } = commonStore;
20
+
21
+ const onSubmitData = useCallback(() => {
22
+ commonStore.commitTempDS();
23
+ }, []);
24
+
25
+ const { t } = useTranslation('translation', { keyPrefix: 'DataSource.dialog.file' });
26
+
27
+ return (
28
+ <Container>
29
+ <input
30
+ style={{ display: 'none' }}
31
+ type="file"
32
+ ref={fileRef}
33
+ onChange={(e) => {
34
+ const files = e.target.files;
35
+ if (files !== null) {
36
+ const file = files[0];
37
+ FileReader.csvReader({
38
+ file,
39
+ config: { type: 'reservoirSampling', size: Infinity },
40
+ onLoading: () => {}
41
+ }).then((data) => {
42
+ commonStore.updateTempDS(data as IRow[]);
43
+ });
44
+ }
45
+ }}
46
+ />
47
+ <div className="mt-1 mb-1">
48
+ <button className="inline-block min-w-96 text-xs mr-2 pt-1 pb-1 pl-6 pr-6 border border-gray-500 rounded-sm cursor-pointer hover:bg-gray-200"
49
+ onClick={() => { if (fileRef.current) { fileRef.current.click(); }}}
50
+ >
51
+ {t('open')}
52
+ </button>
53
+ <button className="inline-block min-w-96 text-xs mr-2 pt-1 pb-1 pl-6 pr-6 bg-yellow-600 rounded-sm hover:bg-yellow-500 text-white font-bold disabled:bg-gray-300"
54
+ disabled={tmpDataSource.length === 0}
55
+ onClick={() => { onSubmitData(); }}
56
+ >
57
+ {t('submit')}
58
+ </button>
59
+ </div>
60
+ <div className="mt-1 mb-1">
61
+ <label className="block text-xs text-gray-800">
62
+ {t('dataset_name')}
63
+ </label>
64
+ <input type="text" placeholder={t('dataset_name')}
65
+ value={tmpDSName}
66
+ onChange={e => {
67
+ commonStore.updateTempName(e.target.value)
68
+ }}
69
+ className="text-xs p-1 border border-gray-300 outline-none focus:outline-none focus:border-blue-500"
70
+ />
71
+ </div>
72
+ <Table />
73
+ </Container>
74
+ );
75
+ }
76
+
77
+ export default observer(CSVData);
@@ -0,0 +1,38 @@
1
+ import React, { useRef, useCallback } from "react";
2
+ import { FileReader } from "@kanaries/web-data-loader";
3
+ import { IRow } from "../../interfaces";
4
+ import Table from "../table";
5
+ import styled from "styled-components";
6
+ import { useGlobalStore } from "../../store";
7
+ import { observer } from "mobx-react-lite";
8
+ import { useTranslation } from "react-i18next";
9
+
10
+ const Container = styled.div`
11
+ overflow-x: auto;
12
+ `;
13
+
14
+ interface GWFileProps {
15
+ fileRef: React.RefObject<HTMLInputElement>;
16
+ }
17
+ const GWFile: React.FC<GWFileProps> = (props) => {
18
+ const { commonStore, vizStore } = useGlobalStore();
19
+
20
+ return (
21
+ <input
22
+ style={{ display: "none" }}
23
+ type="file"
24
+ ref={props.fileRef}
25
+ onChange={(e) => {
26
+ const files = e.target.files;
27
+ if (files !== null) {
28
+ const file = files[0];
29
+ file.text().then(res => {
30
+ vizStore.importRaw(res)
31
+ })
32
+ }
33
+ }}
34
+ />
35
+ );
36
+ };
37
+
38
+ export default observer(GWFile);
@@ -0,0 +1,57 @@
1
+ import React from 'react';
2
+ import { useState } from 'react';
3
+ import CSVData from './csvData';
4
+ import PublicData from './publicData';
5
+ import { useTranslation } from 'react-i18next';
6
+
7
+ interface IDataSelectionProps {
8
+
9
+ }
10
+
11
+ const DataSelection: React.FC = props =>{
12
+ const [sourceType, setSourceType] = useState<'file' | 'public'>('file');
13
+ const { t } = useTranslation('translation', { keyPrefix: 'DataSource' });
14
+
15
+ return <div className="grid grid-cols-6 text-sm">
16
+ <div className="col-span-1 bg-gray-100 pl-2 pr-2 pb-2 pt-1 text-xs">
17
+ <h1 className="cursor-default">
18
+ {t('dialog.data_types')}
19
+ </h1>
20
+ <hr className="mt-1 mb-1" />
21
+ <div
22
+ className={
23
+ `pb-1 cursor-pointer ${
24
+ sourceType === 'file' ? 'underline' : ''
25
+ } hover:bg-gray-200 hover:text-purple-600`
26
+ }
27
+ onClick={() => { setSourceType('file'); }}
28
+ >
29
+ {t('dialog.text_file_data')}
30
+ </div>
31
+ <div
32
+ className={
33
+ `pb-1 cursor-pointer ${
34
+ sourceType === 'public' ? 'underline' : ''
35
+ } hover:bg-gray-200 hover:text-purple-600`
36
+ }
37
+ onClick={() => { setSourceType('public'); }}
38
+ >
39
+ {t('dialog.public_data')}
40
+ </div>
41
+ </div>
42
+ <div className="col-span-5 pl-2 pr-2">
43
+ <h1 className="text-base font-semibold">
44
+ {t('dialog.data_source_type', { sourceType })}
45
+ </h1>
46
+ <hr className="mt-1 mb-1" />
47
+ {
48
+ sourceType === 'file' && <CSVData />
49
+ }
50
+ {
51
+ sourceType === 'public' && <PublicData />
52
+ }
53
+ </div>
54
+ </div>
55
+ }
56
+
57
+ export default DataSelection;
@@ -0,0 +1,57 @@
1
+ import React from 'react';
2
+ import Table from '../table';
3
+ import { DemoDataAssets, PUBLIC_DATA_LIST } from '../config'
4
+ import { useGlobalStore } from '../../store';
5
+ import { observer } from 'mobx-react-lite';
6
+ import { useTranslation } from 'react-i18next';
7
+
8
+
9
+ interface IPublicDataProps {
10
+
11
+ }
12
+
13
+ const PublicData: React.FC<IPublicDataProps> = props => {
14
+ const { commonStore } = useGlobalStore();
15
+ const { tmpDataSource } = commonStore;
16
+ const { t } = useTranslation('translation', { keyPrefix: 'DataSource.dialog.public' });
17
+
18
+ return <div>
19
+ <div className="h-48 overflow-auto mb-1">
20
+ {
21
+ PUBLIC_DATA_LIST.map(data => <div key={data.key}
22
+ onClick={() => {
23
+ fetch(DemoDataAssets[data.key]).then(res => res.json())
24
+ .then(res => {
25
+ commonStore.updateTempSTDDS({
26
+ dataSource: res.dataSource,
27
+ rawFields: res.fields.map(f => ({
28
+ fid: f.fid,
29
+ name: f.name,
30
+ analyticType: f.analyticType,
31
+ semanticType: f.semanticType,
32
+ dataType: f.dataType || '?'
33
+ })),
34
+ name: data.title
35
+ })
36
+ })
37
+ }}
38
+ className="border rounded border-gray-400 p-2 m-2 cursor-pointer hover:bg-gray-50"
39
+ >
40
+ <div>{data.title}</div>
41
+ {/* <p>{data.title}</p> */}
42
+ </div>)
43
+ }
44
+ </div>
45
+ <hr className="m-1" />
46
+ <button className="inline-block min-w-96 text-xs mr-2 pt-1 pb-1 pl-6 pr-6 bg-yellow-600 rounded-sm hover:bg-yellow-500 text-white font-bold disabled:bg-gray-300"
47
+ disabled={tmpDataSource.length === 0}
48
+ onClick={() => { commonStore.commitTempDS() }}
49
+ >
50
+ {t('submit')}
51
+ </button>
52
+ <hr className="m-1" />
53
+ <Table />
54
+ </div>
55
+ }
56
+
57
+ export default observer(PublicData);
@@ -0,0 +1,78 @@
1
+ import React, { useRef } from 'react';
2
+ import { observer } from 'mobx-react-lite';
3
+ import { CheckCircleIcon, ArrowPathIcon } from '@heroicons/react/24/outline';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { Container } from '../components/container';
6
+ import Modal from '../components/modal';
7
+ import { useGlobalStore } from '../store';
8
+ import { download } from '../utils/save';
9
+ import GwFile from './dataSelection/gwFile';
10
+ import DataSelection from './dataSelection';
11
+
12
+ interface DSSegmentProps {
13
+ preWorkDone: boolean;
14
+ }
15
+
16
+ const DataSourceSegment: React.FC<DSSegmentProps> = props => {
17
+ const { preWorkDone } = props;
18
+ const { commonStore, vizStore } = useGlobalStore();
19
+ const gwFileRef = useRef<HTMLInputElement>(null);
20
+ const { t } = useTranslation();
21
+
22
+ const { currentDataset, datasets, showDSPanel } = commonStore;
23
+
24
+ return <Container className="flex flex-row items-stretch">
25
+ <GwFile fileRef={gwFileRef} />
26
+ {!preWorkDone && <div className="animate-spin inline-block mr-2 ml-2 w-4 h-4 rounded-full border-t-2 border-l-2 border-blue-500"></div>}
27
+ <label className="text-xs mr-1 whitespace-nowrap self-center h-4">
28
+ {t('DataSource.labels.cur_dataset')}
29
+ </label>
30
+ <select
31
+ className="border border-gray-500 rounded-sm text-xs pt-0.5 pb-0.5 pl-2 pr-2"
32
+ value={currentDataset.id}
33
+ onChange={(e) => { commonStore.useDS(e.target.value); }}
34
+ >
35
+ {datasets.map((ds) => (
36
+ <option value={ds.id} key={ds.id}>
37
+ {ds.name}
38
+ </option>
39
+ ))}
40
+ </select>
41
+
42
+ <button className="inline-block min-w-96 text-xs ml-2 pt-1 pb-1 pl-6 pr-6 border border-gray-500 rounded-sm hover:bg-gray-200"
43
+ onClick={() => { commonStore.startDSBuildingTask() }}
44
+ >
45
+ {t('DataSource.buttons.create_dataset')}
46
+ </button>
47
+ <button className="inline-block min-w-96 text-xs ml-2 pt-1 pb-1 pl-6 pr-6 border border-gray-500 rounded-sm hover:bg-gray-200"
48
+ onClick={() => {
49
+ const res = vizStore.exportAsRaw();
50
+ download(res, 'graphic-walker-notebook.json', 'text/plain')
51
+ }}
52
+ >
53
+ {t('DataSource.buttons.export_as_file')}
54
+ </button>
55
+ <button className="inline-block min-w-96 text-xs ml-2 pt-1 pb-1 pl-6 pr-6 border border-gray-500 rounded-sm hover:bg-gray-200"
56
+ onClick={() => {
57
+ if (gwFileRef.current) {
58
+ gwFileRef.current.click();
59
+ }
60
+ }}
61
+ >
62
+ {t('DataSource.buttons.import_file')}
63
+ </button>
64
+ {showDSPanel && (
65
+ <Modal
66
+ title={t('DataSource.dialog.create_data_source')}
67
+ onClose={() => { commonStore.setShowDSPanel(false) }}
68
+ >
69
+ <DataSelection />
70
+ {/* <DataSourcePanel /> */}
71
+ </Modal>
72
+ )}
73
+ { preWorkDone && <CheckCircleIcon className="text-green-500 w-5 inline-block ml-2" /> }
74
+ { !preWorkDone && <ArrowPathIcon className="text-yellow-500 w-5 inline-block ml-2" />}
75
+ </Container>
76
+ }
77
+
78
+ export default observer(DataSourceSegment);
@@ -0,0 +1,71 @@
1
+ import React, { useRef, useCallback } from 'react';
2
+ import { FileReader } from '@kanaries/web-data-loader';
3
+ import { IRow } from '../interfaces';
4
+ import Table from './table';
5
+ import styled from 'styled-components';
6
+ import { useGlobalStore } from '../store';
7
+ import { observer } from 'mobx-react-lite';
8
+
9
+ const Container = styled.div`
10
+ overflow-x: auto;
11
+ `;
12
+
13
+ interface DSPanelProps {
14
+ }
15
+ const DataSourcePanel: React.FC<DSPanelProps> = props => {
16
+ const fileRef = useRef<HTMLInputElement>(null);
17
+ const { commonStore } = useGlobalStore();
18
+ const { tmpDSName, tmpDataSource } = commonStore;
19
+
20
+ const onSubmitData = useCallback(() => {
21
+ commonStore.commitTempDS();
22
+ }, [])
23
+ return (
24
+ <Container>
25
+ <input
26
+ style={{ display: 'none' }}
27
+ type="file"
28
+ ref={fileRef}
29
+ onChange={(e) => {
30
+ const files = e.target.files;
31
+ if (files !== null) {
32
+ const file = files[0];
33
+ FileReader.csvReader({
34
+ file,
35
+ config: { type: 'reservoirSampling', size: Infinity },
36
+ onLoading: () => {}
37
+ }).then((data) => {
38
+ commonStore.updateTempDS(data as IRow[]);
39
+ });
40
+ }
41
+ }}
42
+ />
43
+ <div className="mt-1 mb-1">
44
+ <button className="inline-block min-w-96 text-xs mr-2 pt-1 pb-1 pl-6 pr-6 border border-gray-500 rounded-sm cursor-pointer hover:bg-gray-200"
45
+ onClick={() => { if (fileRef.current) { fileRef.current.click(); }}}
46
+ >
47
+ 上传数据
48
+ </button>
49
+ <button className="inline-block min-w-96 text-xs mr-2 pt-1 pb-1 pl-6 pr-6 bg-yellow-600 rounded-sm hover:bg-yellow-500 text-white font-bold disabled:bg-gray-300"
50
+ disabled={tmpDataSource.length === 0}
51
+ onClick={() => { onSubmitData(); }}
52
+ >
53
+ 确认
54
+ </button>
55
+ </div>
56
+ <div className="mt-1 mb-1">
57
+ <label className="block text-xs text-gray-800">数据集名称</label>
58
+ <input type="text" placeholder="数据集名称"
59
+ value={tmpDSName}
60
+ onChange={e => {
61
+ commonStore.updateTempName(e.target.value)
62
+ }}
63
+ className="text-xs p-1 border border-gray-300 outline-none focus:outline-none focus:border-blue-500"
64
+ />
65
+ </div>
66
+ <Table />
67
+ </Container>
68
+ );
69
+ }
70
+
71
+ export default observer(DataSourcePanel);
@@ -0,0 +1,125 @@
1
+ import React, { useMemo } from 'react';
2
+ import styled from 'styled-components';
3
+ import { observer } from 'mobx-react-lite';
4
+ import { IMutField } from '../interfaces';
5
+ import { useGlobalStore } from '../store';
6
+ import { useTranslation } from 'react-i18next';
7
+
8
+ interface TableProps {
9
+ size?: number;
10
+ }
11
+ const Container = styled.div`
12
+ overflow-x: auto;
13
+ table {
14
+ box-sizing: content-box;
15
+ font-family: Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif;
16
+ border-collapse: collapse;
17
+ font-size: 12px;
18
+ thead {
19
+ th {
20
+ text-align: left;
21
+ border: 1px solid #f5f5f5;
22
+ padding: 12px;
23
+ background-color: #f7f7f7;
24
+ margin: 2px;
25
+ white-space: nowrap;
26
+ }
27
+ th.number {
28
+ border-top: 3px solid #5cdbd3;
29
+ }
30
+ th.text {
31
+ border-top: 3px solid #69c0ff;
32
+ }
33
+ }
34
+ tbody {
35
+ td {
36
+ border: 1px solid #f5f5f5;
37
+ padding: 4px 12px;
38
+ color: #434343;
39
+ overflow: hidden;
40
+ text-overflow: ellipsis;
41
+ white-space: nowrap;
42
+ }
43
+ td.number {
44
+ text-align: right;
45
+ }
46
+ td.text {
47
+ text-align: left;
48
+ }
49
+ }
50
+ }
51
+ `;
52
+ const TYPE_LIST = [
53
+ {
54
+ value: 'dimension',
55
+ label: '维度'
56
+ },
57
+ {
58
+ value: 'measure',
59
+ label: '度量'
60
+ }
61
+ ];
62
+ // function getCellType(field: IMutField): 'number' | 'text' {
63
+ // return field.dataType === 'number' || field.dataType === 'integer' ? 'number' : 'text';
64
+ // }
65
+ function getHeaderType(field: IMutField): 'number' | 'text' {
66
+ return field.analyticType === 'dimension'? 'text' : 'number';
67
+ }
68
+
69
+ const Table: React.FC<TableProps> = props => {
70
+ const { size = 10 } = props;
71
+ const { commonStore } = useGlobalStore();
72
+ const { tmpDSRawFields, tmpDataSource } = commonStore;
73
+ const { t } = useTranslation();
74
+
75
+ const analyticTypeList = useMemo<{value: string; label: string}[]>(() => {
76
+ return TYPE_LIST.map(at => ({
77
+ value: at.value,
78
+ label: t(`constant.analytic_type.${at.value}`)
79
+ }))
80
+ }, [])
81
+
82
+ return (
83
+ <Container>
84
+ <table>
85
+ <thead>
86
+ <tr>
87
+ {tmpDSRawFields.map((field, fIndex) => (
88
+ <th key={field.fid} className={getHeaderType(field)}>
89
+ <b>{field.name || field.fid}</b>
90
+ <div>
91
+ <select
92
+ className="border-b border-gray-300 hover:bg-gray-100 hover:border-gray-600"
93
+ value={field.analyticType} onChange={(e) => {
94
+ commonStore.updateTempFieldAnalyticType(field.fid, e.target.value as IMutField['analyticType'])
95
+ }}>
96
+ {
97
+ analyticTypeList.map(type => <option key={type.value} value={type.value}>{type.label}</option>)
98
+ }
99
+ </select>
100
+ </div>
101
+
102
+ </th>
103
+ ))}
104
+ </tr>
105
+ </thead>
106
+ <tbody>
107
+ {tmpDataSource.slice(0, size).map((record, index) => (
108
+ <tr key={index}>
109
+ {tmpDSRawFields.map((field) => (
110
+ <td
111
+ key={field.fid + index}
112
+ // className={getCellType(field)}
113
+ >
114
+ {record[field.fid]}
115
+ </td>
116
+ ))}
117
+ </tr>
118
+ ))}
119
+ </tbody>
120
+ </table>
121
+ </Container>
122
+ );
123
+ }
124
+
125
+ export default observer(Table);
@@ -0,0 +1,47 @@
1
+ import { IRow, IMutField } from '../interfaces';
2
+ import { Insight } from 'visual-insights';
3
+
4
+ export function transData(dataSource: IRow[]): {
5
+ dataSource: IRow[];
6
+ fields: IMutField[]
7
+ } {
8
+ if (dataSource.length === 0) return {
9
+ dataSource: [],
10
+ fields: []
11
+ };
12
+ let ans: IRow[] = [];
13
+ const keys = Object.keys(dataSource[0]);
14
+ // TODO: 冗余设计,单变量统计被进行了多次重复计算。另外对于这种不完整的分析任务,不建议使用VIEngine。
15
+ const vie = new Insight.VIEngine();
16
+ vie.setData(dataSource)
17
+ .setFields(keys.map(k => ({
18
+ key: k,
19
+ analyticType: '?',
20
+ dataType: '?',
21
+ semanticType: '?'
22
+ })))
23
+ // TODO: 结合上面的TODO,讨论,VIEngine是否要提供不需要进行univarSelection就提供summary的接口。
24
+ // 这里我们使用了一种非原API设计时期待的用法,即强制指定单变量选择时要全选字段。但我们无法阻止对变量的转换。
25
+ vie.univarSelection('percent', 1);
26
+ const fields = vie.fields;
27
+ for (let record of dataSource) {
28
+ const newRecord: IRow = {};
29
+ for (let field of fields) {
30
+ if (field.dataType === 'number' || field.dataType === 'integer') {
31
+ newRecord[field.key] = Number(record[field.key])
32
+ } else {
33
+ newRecord[field.key] = record[field.key]
34
+ }
35
+ }
36
+ ans.push(newRecord);
37
+ }
38
+ return {
39
+ dataSource: ans,
40
+ fields: fields.map(f => ({
41
+ fid: f.key,
42
+ analyticType: f.analyticType,
43
+ dataType: f.dataType,
44
+ semanticType: f.semanticType
45
+ }))
46
+ }
47
+ }
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ import { Droppable } from "react-beautiful-dnd";
3
+ import { DRAGGABLE_STATE_KEYS } from './fieldsContext';
4
+ import { AestheticFieldContainer } from './components'
5
+ import OBFieldContainer from './obComponents/obFContainer';
6
+
7
+ const aestheticFields = DRAGGABLE_STATE_KEYS.filter(f => ['color', 'opacity', 'size', 'shape'].includes(f.id));
8
+
9
+ const AestheticFields: React.FC = props => {
10
+ return <div>
11
+ {
12
+ aestheticFields.map(dkey => <AestheticFieldContainer name={dkey.id} key={dkey.id}>
13
+ <Droppable droppableId={dkey.id} direction="horizontal">
14
+ {(provided, snapshot) => (
15
+ <OBFieldContainer dkey={dkey} provided={provided} />
16
+ )}
17
+ </Droppable>
18
+ </AestheticFieldContainer>)
19
+ }
20
+ </div>
21
+ }
22
+
23
+ export default AestheticFields;