@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,398 @@
1
+ import React, { useEffect, useState, useMemo } from 'react';
2
+ import embed from 'vega-embed';
3
+ import { Subject } from 'rxjs'
4
+ import * as op from 'rxjs/operators';
5
+ import { ScenegraphEvent } from 'vega';
6
+ import { ISemanticType } from 'visual-insights';
7
+ import styled from 'styled-components';
8
+ import { autoMark } from '../utils/autoMark';
9
+ import { COUNT_FIELD_ID } from '../constants';
10
+
11
+ import { IViewField, IRow, IStackMode } from '../interfaces';
12
+
13
+ const CanvaContainer = styled.div<{rowSize: number; colSize: number;}>`
14
+ display: grid;
15
+ grid-template-columns: repeat(${props => props.colSize}, 1fr);
16
+ grid-template-rows: repeat(${props => props.rowSize}, 1fr);
17
+ `
18
+
19
+ const SELECTION_NAME = 'geom';
20
+ interface ReactVegaProps {
21
+ rows: Readonly<IViewField[]>;
22
+ columns: Readonly<IViewField[]>;
23
+ dataSource: IRow[];
24
+ defaultAggregate?: boolean;
25
+ stack: IStackMode;
26
+ interactiveScale: boolean;
27
+ geomType: string;
28
+ color?: IViewField;
29
+ opacity?: IViewField;
30
+ size?: IViewField;
31
+ shape?: IViewField;
32
+ theta?: IViewField;
33
+ radius?: IViewField;
34
+ showActions: boolean;
35
+ layoutMode: string;
36
+ width: number;
37
+ height: number;
38
+ onGeomClick?: (values: any, e: any) => void
39
+ }
40
+ const NULL_FIELD: IViewField = {
41
+ dragId: '',
42
+ fid: '',
43
+ name: '',
44
+ semanticType: 'quantitative',
45
+ analyticType: 'measure',
46
+ aggName: 'sum'
47
+ }
48
+ const click$ = new Subject<ScenegraphEvent>();
49
+ const selection$ = new Subject<any>();
50
+ const geomClick$ = selection$.pipe(
51
+ op.withLatestFrom(click$),
52
+ op.filter(([values, _]) => {
53
+ if (Object.keys(values).length > 0) {
54
+ return true
55
+ }
56
+ return false
57
+ })
58
+ );
59
+ function getFieldType(field: IViewField): 'quantitative' | 'nominal' | 'ordinal' | 'temporal' {
60
+ return field.semanticType
61
+ }
62
+ interface SingleViewProps {
63
+ x: IViewField;
64
+ y: IViewField;
65
+ color: IViewField;
66
+ opacity: IViewField;
67
+ size: IViewField;
68
+ shape: IViewField,
69
+ xOffset: IViewField;
70
+ yOffset: IViewField;
71
+ row: IViewField;
72
+ column: IViewField;
73
+ theta: IViewField;
74
+ radius: IViewField;
75
+ defaultAggregated: boolean;
76
+ stack: IStackMode;
77
+ geomType: string;
78
+ }
79
+
80
+ function availableChannels (geomType: string): Set<string> {
81
+ if (geomType === 'arc') {
82
+ return new Set(['opacity', 'color', 'size', 'theta', 'radius'])
83
+ }
84
+ return new Set(['column', 'opacity', 'color', 'row', 'size', 'x', 'y', 'xOffset', 'yOffset', 'shape'])
85
+ }
86
+ interface EncodeProps extends Pick<SingleViewProps, 'column' | 'opacity' | 'color' | 'row' | 'size' | 'x' | 'y' | 'xOffset' | 'yOffset' | 'shape' | 'theta' | 'radius'> {
87
+ geomType: string;
88
+ }
89
+ function channelEncode(props: EncodeProps) {
90
+ const avcs = availableChannels(props.geomType)
91
+ const encoding: {[key: string]: any} = {}
92
+ Object.keys(props).filter(c => avcs.has(c)).forEach(c => {
93
+ if (props[c] !== NULL_FIELD) {
94
+ encoding[c] = {
95
+ field: props[c].fid,
96
+ title: props[c].name,
97
+ type: props[c].semanticType
98
+ }
99
+ if (props[c].semanticType === 'temporal') {
100
+ encoding[c].axis = { format: '%Y-%m' }
101
+ }
102
+ }
103
+ })
104
+ // FIXME: 临时处理逻辑,只处理xy排序
105
+ if (encoding.x && encoding.y) {
106
+ if ((props.x.sort && props.x.sort) || (props.y && props.y.sort)) {
107
+ if (props.x.sort !== 'none' && (props.y.sort === 'none' || !Boolean(props.y.sort))) {
108
+ encoding.x.sort = {
109
+ encoding: 'y',
110
+ order: props.x.sort
111
+ }
112
+ } else if (props.y.sort && props.y.sort !== 'none' && (props.x.sort === 'none' || !Boolean(props.x.sort))) {
113
+ encoding.y.sort = {
114
+ encoding: 'x',
115
+ order: props.y.sort
116
+ }
117
+ }
118
+ }
119
+ }
120
+ return encoding
121
+ }
122
+ function channelAggregate(encoding: {[key: string]: any}, fields: IViewField[]) {
123
+ Object.values(encoding).forEach(c => {
124
+ const targetField = fields.find(f => f.fid === c.field);
125
+ if (targetField && targetField.fid === COUNT_FIELD_ID) {
126
+ c.field = undefined;
127
+ c.aggregate = 'count';
128
+ c.title = 'Count'
129
+ } else if (targetField && targetField.analyticType === 'measure') {
130
+ c.title = `${targetField.aggName}(${targetField.name})`;
131
+ c.aggregate = targetField.aggName;
132
+ }
133
+ })
134
+ }
135
+ function channelStack(encoding: {[key: string]: any}, stackMode: IStackMode) {
136
+ if (stackMode === 'stack') return;
137
+ if (encoding.x && encoding.x.type === 'quantitative') {
138
+ encoding.x.stack = stackMode === 'none' ? null : 'normalize'
139
+ }
140
+ if (encoding.y && encoding.y.type === 'quantitative') {
141
+ encoding.y.stack = stackMode === 'none' ? null : 'normalize'
142
+ }
143
+ }
144
+ // TODO: xOffset等通道的特性不太稳定,建议后续vega相关特性稳定后,再使用。
145
+ // 1. 场景太细,仅仅对对应的坐标轴是nominal(可能由ordinal)时,才可用
146
+ // 2. 部分geom type会出现bug,如line,会出现组间的错误连接
147
+ // "vega": "^5.22.0",
148
+ // "vega-embed": "^6.20.8",
149
+ // "vega-lite": "^5.2.0",
150
+ function getSingleView(props: SingleViewProps) {
151
+ const {
152
+ x,
153
+ y,
154
+ color,
155
+ opacity,
156
+ size,
157
+ shape,
158
+ theta,
159
+ radius,
160
+ row,
161
+ column,
162
+ xOffset,
163
+ yOffset,
164
+ defaultAggregated,
165
+ stack,
166
+ geomType
167
+ } = props
168
+ const fields: IViewField[] = [x, y, color, opacity, size, shape, row, column, xOffset, yOffset, theta, radius]
169
+ let markType = geomType;
170
+ if (geomType === 'auto') {
171
+ const types: ISemanticType[] = [];
172
+ if (x !== NULL_FIELD) types.push(x.semanticType)//types.push(getFieldType(x));
173
+ if (y !== NULL_FIELD) types.push(y.semanticType)//types.push(getFieldType(yField));
174
+ markType = autoMark(types);
175
+ }
176
+
177
+ let encoding = channelEncode({ geomType: markType, x, y, color, opacity, size, shape, row, column, xOffset, yOffset, theta, radius })
178
+ if (defaultAggregated) {
179
+ channelAggregate(encoding, fields);
180
+ }
181
+ channelStack(encoding, stack);
182
+ const spec = {
183
+ mark: {
184
+ type: markType,
185
+ opacity: 0.96,
186
+ tooltip: true
187
+ },
188
+ encoding
189
+ };
190
+ return spec;
191
+ }
192
+ const ReactVega: React.FC<ReactVegaProps> = props => {
193
+ const {
194
+ dataSource = [],
195
+ rows = [],
196
+ columns = [],
197
+ defaultAggregate = true,
198
+ stack = 'stack',
199
+ geomType,
200
+ color,
201
+ opacity,
202
+ size,
203
+ theta,
204
+ radius,
205
+ shape,
206
+ onGeomClick,
207
+ showActions,
208
+ interactiveScale,
209
+ layoutMode,
210
+ width,
211
+ height
212
+ } = props;
213
+ // const container = useRef<HTMLDivElement>(null);
214
+ // const containers = useRef<(HTMLDivElement | null)[]>([]);
215
+ const [viewPlaceholders, setViewPlaceholders] = useState<React.MutableRefObject<HTMLDivElement>[]>([]);
216
+ useEffect(() => {
217
+ const clickSub = geomClick$.subscribe(([values, e]) => {
218
+ if (onGeomClick) {
219
+ onGeomClick(values, e);
220
+ }
221
+ })
222
+ return () => {
223
+ clickSub.unsubscribe();
224
+ }
225
+ }, []);
226
+ const rowDims = useMemo(() => rows.filter(f => f.analyticType === 'dimension'), [rows]);
227
+ const colDims = useMemo(() => columns.filter(f => f.analyticType === 'dimension'), [columns]);
228
+ const rowMeas = useMemo(() => rows.filter(f => f.analyticType === 'measure'), [rows]);
229
+ const colMeas = useMemo(() => columns.filter(f => f.analyticType === 'measure'), [columns]);
230
+ const rowFacetFields = useMemo(() => rowDims.slice(0, -1), [rowDims]);
231
+ const colFacetFields = useMemo(() => colDims.slice(0, -1), [colDims]);
232
+ const rowRepeatFields = useMemo(() => rowMeas.length === 0 ? rowDims.slice(-1) : rowMeas, [rowDims, rowMeas]);//rowMeas.slice(0, -1);
233
+ const colRepeatFields = useMemo(() => colMeas.length === 0 ? colDims.slice(-1) : colMeas, [rowDims, rowMeas]);//colMeas.slice(0, -1);
234
+ const allFieldIds = useMemo(() => [...rows, ...columns, color, opacity, size].filter(f => Boolean(f)).map(f => (f as IViewField).fid), [rows, columns, color, opacity, size]);
235
+
236
+
237
+ useEffect(() => {
238
+ setViewPlaceholders(views => {
239
+ const viewNum = Math.max(1, rowRepeatFields.length * colRepeatFields.length)
240
+ const nextViews = new Array(viewNum).fill(null).map((v, i) => views[i] || React.createRef())
241
+ return nextViews;
242
+ })
243
+ }, [rowRepeatFields, colRepeatFields])
244
+
245
+ useEffect(() => {
246
+
247
+ const yField = rows.length > 0 ? rows[rows.length - 1] : NULL_FIELD;
248
+ const xField = columns.length > 0 ? columns[columns.length - 1] : NULL_FIELD;
249
+
250
+ const rowLeftFacetFields = rows.slice(0, -1).filter(f => f.analyticType === 'dimension');
251
+ const colLeftFacetFields = columns.slice(0, -1).filter(f => f.analyticType === 'dimension');
252
+
253
+ const rowFacetField = rowLeftFacetFields.length > 0 ? rowLeftFacetFields[rowLeftFacetFields.length - 1] : NULL_FIELD;
254
+ const colFacetField = colLeftFacetFields.length > 0 ? colLeftFacetFields[colLeftFacetFields.length - 1] : NULL_FIELD;
255
+
256
+ const spec: any = {
257
+ data: {
258
+ values: dataSource,
259
+ },
260
+ params: [{
261
+ name: SELECTION_NAME,
262
+ select: {
263
+ type: 'point',
264
+ fields: allFieldIds
265
+ }
266
+ }]
267
+ };
268
+ if (interactiveScale) {
269
+ spec.params.push({
270
+ name: "grid",
271
+ select: "interval",
272
+ bind: "scales"
273
+ })
274
+ }
275
+ if (rowRepeatFields.length <= 1 && colRepeatFields.length <= 1) {
276
+ if (layoutMode === 'fixed') {
277
+ if (rowFacetField === NULL_FIELD && colFacetField === NULL_FIELD) {
278
+ spec.autosize = 'fit'
279
+ }
280
+ spec.width = width;
281
+ spec.height = height;
282
+ }
283
+ const singleView = getSingleView({
284
+ x: xField,
285
+ y: yField,
286
+ color: color ? color : NULL_FIELD,
287
+ opacity: opacity ? opacity : NULL_FIELD,
288
+ size: size ? size : NULL_FIELD,
289
+ shape: shape ? shape : NULL_FIELD,
290
+ theta: theta ? theta : NULL_FIELD,
291
+ radius: radius ? radius : NULL_FIELD,
292
+ row: rowFacetField,
293
+ column: colFacetField,
294
+ xOffset: NULL_FIELD,
295
+ yOffset: NULL_FIELD,
296
+ defaultAggregated: defaultAggregate,
297
+ stack,
298
+ geomType
299
+ });
300
+ // if (layoutMode === 'fixed') {
301
+ // spec.width = 800;
302
+ // spec.height = 600;
303
+ // }
304
+ spec.mark = singleView.mark;
305
+ spec.encoding = singleView.encoding;
306
+ if (viewPlaceholders.length > 0 && viewPlaceholders[0].current) {
307
+ embed(viewPlaceholders[0].current, spec, { mode: 'vega-lite', actions: showActions }).then(res => {
308
+ try {
309
+ res.view.addEventListener('click', (e) => {
310
+ click$.next(e);
311
+ })
312
+ res.view.addSignalListener(SELECTION_NAME, (name: any, values: any) => {
313
+ selection$.next(values);
314
+ });
315
+ } catch (error) {
316
+ console.warn(error)
317
+ }
318
+ });
319
+ }
320
+ } else {
321
+ if (layoutMode === 'fixed') {
322
+ spec.width = Math.floor(width / colRepeatFields.length) - 5;
323
+ spec.height = Math.floor(height / rowRepeatFields.length) - 5;
324
+ spec.autosize = 'fit'
325
+ }
326
+ for (let i = 0; i < rowRepeatFields.length; i++) {
327
+ for (let j = 0; j < colRepeatFields.length; j++) {
328
+ const singleView = getSingleView({
329
+ x: colRepeatFields[j] || NULL_FIELD,
330
+ y: rowRepeatFields[i] || NULL_FIELD,
331
+ color: color ? color : NULL_FIELD,
332
+ opacity: opacity ? opacity : NULL_FIELD,
333
+ size: size ? size : NULL_FIELD,
334
+ shape: shape ? shape : NULL_FIELD,
335
+ theta: theta ? theta : NULL_FIELD,
336
+ radius: radius ? radius : NULL_FIELD,
337
+ row: rowFacetField,
338
+ column: colFacetField,
339
+ xOffset: NULL_FIELD,
340
+ yOffset: NULL_FIELD,
341
+ defaultAggregated: defaultAggregate,
342
+ stack,
343
+ geomType
344
+ });
345
+ const node = i * colRepeatFields.length + j < viewPlaceholders.length ? viewPlaceholders[i * colRepeatFields.length + j].current : null
346
+ const ans = { ...spec, ...singleView }
347
+ if (node) {
348
+ embed(node, ans, { mode: 'vega-lite', actions: showActions }).then(res => {
349
+ try {
350
+ res.view.addEventListener('click', (e) => {
351
+ click$.next(e);
352
+ })
353
+ res.view.addSignalListener(SELECTION_NAME, (name: any, values: any) => {
354
+ selection$.next(values);
355
+ });
356
+ } catch (error) {
357
+ console.warn(error);
358
+ }
359
+ })
360
+ }
361
+ }
362
+ }
363
+ }
364
+
365
+ }, [
366
+ dataSource,
367
+ allFieldIds,
368
+ rows,
369
+ columns,
370
+ defaultAggregate,
371
+ geomType,
372
+ color,
373
+ opacity,
374
+ size,
375
+ shape,
376
+ theta, radius,
377
+ viewPlaceholders,
378
+ rowFacetFields,
379
+ colFacetFields,
380
+ rowRepeatFields,
381
+ colRepeatFields,
382
+ stack,
383
+ showActions,
384
+ interactiveScale,
385
+ layoutMode,
386
+ width,
387
+ height
388
+ ]);
389
+
390
+ return <CanvaContainer rowSize={Math.max(rowRepeatFields.length, 1)} colSize={Math.max(colRepeatFields.length, 1)}>
391
+ {/* <div ref={container}></div> */}
392
+ {
393
+ viewPlaceholders.map((view, i) => <div key={i} ref={view}></div>)
394
+ }
395
+ </CanvaContainer>
396
+ }
397
+
398
+ export default ReactVega;
@@ -0,0 +1,252 @@
1
+ import { BarsArrowDownIcon, BarsArrowUpIcon } from '@heroicons/react/24/outline';
2
+ import { observer } from 'mobx-react-lite';
3
+ import React from 'react';
4
+ import styled from 'styled-components'
5
+ import { ArrowPathIcon } from '@heroicons/react/24/solid';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { LiteForm } from '../components/liteForm';
8
+ import SizeSetting from '../components/sizeSetting';
9
+ import { CHART_LAYOUT_TYPE, GEMO_TYPES, STACK_MODE } from '../config';
10
+ import { useGlobalStore } from '../store';
11
+ import { IStackMode } from '../interfaces';
12
+
13
+
14
+ export const LiteContainer = styled.div`
15
+ margin: 0.2em;
16
+ border: 1px solid #d9d9d9;
17
+ padding: 1em;
18
+ background-color: #fff;
19
+ `;
20
+
21
+ const VisualSettings: React.FC = () => {
22
+ const { vizStore } = useGlobalStore();
23
+ const { visualConfig, sortCondition } = vizStore;
24
+ const { t: tGlobal } = useTranslation();
25
+ const { t } = useTranslation('translation', { keyPrefix: 'main.tabpanel.settings' });
26
+
27
+ return <LiteContainer>
28
+ <LiteForm style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center' }}>
29
+ <div className="item">
30
+ <input
31
+ className="cursor-pointer"
32
+ type="checkbox"
33
+ id="toggle:aggregation"
34
+ aria-describedby="toggle:aggregation:label"
35
+ checked={visualConfig.defaultAggregated}
36
+ onChange={(e) => {
37
+ vizStore.setVisualConfig('defaultAggregated', e.target.checked);
38
+ }}
39
+ />
40
+ <label
41
+ className="text-xs text-color-gray-700 ml-2 cursor-pointer"
42
+ id="toggle:aggregation:label"
43
+ htmlFor="toggle:aggregation"
44
+ >
45
+ {t('toggle.aggregation')}
46
+ </label>
47
+ </div>
48
+ <div className="item">
49
+ <label
50
+ className="px-2"
51
+ id="dropdown:mark_type:label"
52
+ htmlFor="dropdown:mark_type"
53
+ >
54
+ {tGlobal('constant.mark_type.__enum__')}
55
+ </label>
56
+ <select
57
+ className="border border-gray-500 rounded-sm text-xs pt-0.5 pb-0.5 pl-2 pr-2 cursor-pointer"
58
+ id="dropdown:mark_type"
59
+ aria-describedby="dropdown:mark_type:label"
60
+ value={visualConfig.geoms[0]}
61
+ onChange={(e) => {
62
+ vizStore.setVisualConfig('geoms', [e.target.value]);
63
+ }}
64
+ >
65
+ {GEMO_TYPES.map(g => (
66
+ <option
67
+ key={g}
68
+ value={g}
69
+ aria-selected={visualConfig.geoms[0] === g}
70
+ className="cursor-pointer"
71
+ >
72
+ {tGlobal(`constant.mark_type.${g}`)}
73
+ </option>
74
+ ))}
75
+ </select>
76
+ </div>
77
+ <div className="item">
78
+ <label
79
+ className="px-2"
80
+ id="dropdown:stack:label"
81
+ htmlFor="dropdown:stack"
82
+ >
83
+ {tGlobal('constant.stack_mode.__enum__')}
84
+ </label>
85
+ <select
86
+ className="border border-gray-500 rounded-sm text-xs pt-0.5 pb-0.5 pl-2 pr-2 cursor-pointer"
87
+ id="dropdown:stack"
88
+ aria-describedby="dropdown:stack:label"
89
+ value={visualConfig.stack}
90
+ onChange={(e) => {
91
+ vizStore.setVisualConfig('stack', e.target.value as IStackMode);
92
+ }}
93
+ >
94
+ {STACK_MODE.map(g => (
95
+ <option
96
+ key={g}
97
+ value={g}
98
+ aria-selected={visualConfig.stack === g}
99
+ className="cursor-pointer"
100
+ >
101
+ {tGlobal(`constant.stack_mode.${g}`)}
102
+ </option>
103
+ ))}
104
+ </select>
105
+ </div>
106
+ <div className="item">
107
+ <input
108
+ type="checkbox"
109
+ className="cursor-pointer"
110
+ id="toggle:axes_resize"
111
+ aria-describedby="toggle:axes_resize:label"
112
+ checked={visualConfig.interactiveScale}
113
+ onChange={(e) => {
114
+ vizStore.setVisualConfig('interactiveScale', e.target.checked);
115
+ }}
116
+ />
117
+ <label
118
+ className="text-xs text-color-gray-700 ml-2 cursor-pointer"
119
+ id="toggle:axes_resize:label"
120
+ htmlFor="toggle:axes_resize"
121
+ >
122
+ {t('toggle.axes_resize')}
123
+ </label>
124
+ </div>
125
+ <div className="item">
126
+ <label className="text-xs text-color-gray-700 mx-2">
127
+ {t('sort')}
128
+ </label>
129
+ <BarsArrowUpIcon
130
+ className={`w-4 inline-block mr-1 ${!sortCondition ? 'text-gray-300 cursor-not-allowed' : 'cursor-pointer'}`}
131
+ onClick={() => {
132
+ vizStore.applyDefaultSort('ascending')
133
+ }}
134
+ role="button"
135
+ tabIndex={!sortCondition ? undefined : 0}
136
+ aria-disabled={!sortCondition}
137
+ xlinkTitle={t('button.ascending')}
138
+ aria-label={t('button.ascending')}
139
+ />
140
+ <BarsArrowDownIcon
141
+ className={`w-4 inline-block mr-1 ${!sortCondition ? 'text-gray-300 cursor-not-allowed' : 'cursor-pointer'}`}
142
+ onClick={() => {
143
+ vizStore.applyDefaultSort('descending');
144
+ }}
145
+ role="button"
146
+ tabIndex={!sortCondition ? undefined : 0}
147
+ aria-disabled={!sortCondition}
148
+ xlinkTitle={t('button.descending')}
149
+ aria-label={t('button.descending')}
150
+ />
151
+ </div>
152
+ <div className='item'>
153
+ <label
154
+ className="text-xs text-color-gray-700 mr-2"
155
+ htmlFor="button:transpose"
156
+ id="button:transpose:label"
157
+ >
158
+ {t('button.transpose')}
159
+ </label>
160
+ <ArrowPathIcon
161
+ className="w-4 inline-block mr-1 cursor-pointer"
162
+ role="button"
163
+ tabIndex={0}
164
+ id="button:transpose"
165
+ aria-describedby="button:transpose:label"
166
+ xlinkTitle={t('button.transpose')}
167
+ aria-label={t('button.transpose')}
168
+ onClick={() => {
169
+ vizStore.transpose();
170
+ }}
171
+ />
172
+ </div>
173
+ <div className="item">
174
+ <label
175
+ id="dropdown:layout_type:label"
176
+ htmlFor="dropdown:layout_type"
177
+ >
178
+ {tGlobal(`constant.layout_type.__enum__`)}
179
+ </label>
180
+ <select
181
+ className="border border-gray-500 rounded-sm text-xs pt-0.5 pb-0.5 pl-2 pr-2 cursor-pointer"
182
+ id="dropdown:layout_type"
183
+ aria-describedby="dropdown:layout_type:label"
184
+ value={visualConfig.size.mode}
185
+ onChange={(e) => {
186
+ // vizStore.setVisualConfig('geoms', [e.target.value]);
187
+ vizStore.setChartLayout({
188
+ mode: e.target.value as any
189
+ })
190
+ }}
191
+ >
192
+ {CHART_LAYOUT_TYPE.map(g => (
193
+ <option
194
+ key={g}
195
+ value={g}
196
+ className="cursor-pointer"
197
+ aria-selected={visualConfig.size.mode === g}
198
+ >
199
+ {tGlobal(`constant.layout_type.${g}`)}
200
+ </option>
201
+ ))}
202
+ </select>
203
+ </div>
204
+ <div className="item hover:bg-yellow-100">
205
+ <SizeSetting
206
+ width={visualConfig.size.width}
207
+ height={visualConfig.size.height}
208
+ onHeightChange={(v) => {
209
+ vizStore.setChartLayout({
210
+ mode: "fixed",
211
+ height: v
212
+ })
213
+ }}
214
+ onWidthChange={(v) => {
215
+ vizStore.setChartLayout({
216
+ mode: "fixed",
217
+ width: v
218
+ })
219
+ }}
220
+ />
221
+ <label
222
+ className="text-xs text-color-gray-700 ml-2"
223
+ htmlFor="button:size_setting"
224
+ id="button:size_setting:label"
225
+ >
226
+ {t('size')}
227
+ </label>
228
+ </div>
229
+ <div className="item">
230
+ <input
231
+ type="checkbox"
232
+ checked={visualConfig.showActions}
233
+ id="toggle:debug"
234
+ aria-describedby="toggle:debug:label"
235
+ className="cursor-pointer"
236
+ onChange={(e) => {
237
+ vizStore.setVisualConfig('showActions', e.target.checked);
238
+ }}
239
+ />
240
+ <label
241
+ className="text-xs text-color-gray-700 ml-2 cursor-pointer"
242
+ id="toggle:debug:label"
243
+ htmlFor="toggle:debug"
244
+ >
245
+ {t('toggle.debug')}
246
+ </label>
247
+ </div>
248
+ </LiteForm>
249
+ </LiteContainer>
250
+ }
251
+
252
+ export default observer(VisualSettings);