@kanaries/graphic-walker 0.3.14 → 0.3.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.
@@ -1,10 +1,10 @@
1
- import React, { useMemo, useState } from "react";
2
- import styled from "styled-components";
3
- import { IMutField, IRow } from "../../interfaces";
4
- import { useTranslation } from "react-i18next";
5
- import Pagination from "./pagination";
6
- import { ChevronUpDownIcon } from "@heroicons/react/24/outline";
7
- import DropdownContext from "../dropdownContext";
1
+ import React, { useMemo, useState } from 'react';
2
+ import styled from 'styled-components';
3
+ import { IMutField, IRow } from '../../interfaces';
4
+ import { useTranslation } from 'react-i18next';
5
+ import Pagination from './pagination';
6
+ import { ChevronUpDownIcon } from '@heroicons/react/24/outline';
7
+ import DropdownContext from '../dropdownContext';
8
8
 
9
9
  interface DataTableProps {
10
10
  size?: number;
@@ -32,34 +32,83 @@ const Container = styled.div`
32
32
  }
33
33
  }
34
34
  `;
35
- const ANALYTIC_TYPE_LIST = ["dimension", "measure"];
36
- const SEMANTIC_TYPE_LIST = ["nominal", "ordinal", "quantitative", "temporal"];
35
+ const ANALYTIC_TYPE_LIST = ['dimension', 'measure'];
36
+ const SEMANTIC_TYPE_LIST = ['nominal', 'ordinal', 'quantitative', 'temporal'];
37
37
  // function getCellType(field: IMutField): 'number' | 'text' {
38
38
  // return field.dataType === 'number' || field.dataType === 'integer' ? 'number' : 'text';
39
39
  // }
40
- function getHeaderType(field: IMutField): "number" | "text" {
41
- return field.analyticType === "dimension" ? "text" : "number";
40
+ function getHeaderType(field: IMutField): 'number' | 'text' {
41
+ return field.analyticType === 'dimension' ? 'text' : 'number';
42
42
  }
43
43
 
44
44
  function getHeaderClassNames(field: IMutField) {
45
- return field.analyticType === "dimension" ? "border-t-4 border-blue-400" : "border-t-4 border-purple-400";
45
+ return field.analyticType === 'dimension' ? 'border-t-4 border-blue-400' : 'border-t-4 border-purple-400';
46
46
  }
47
47
 
48
48
  function getSemanticColors(field: IMutField): string {
49
49
  switch (field.semanticType) {
50
- case "nominal":
51
- return "border border-transparent bg-sky-100 text-sky-800 dark:bg-sky-900 dark:text-sky-100 dark:border-sky-600";
52
- case "ordinal":
53
- return "border border-transparent bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-100 dark:border-indigo-600";
54
- case "quantitative":
55
- return "border border-transparent bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-100 dark:border-purple-600";
56
- case "temporal":
57
- return "border border-transparent bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-100 dark:border-yellow-600";
50
+ case 'nominal':
51
+ return 'border border-transparent bg-sky-100 text-sky-800 dark:bg-sky-900 dark:text-sky-100 dark:border-sky-600';
52
+ case 'ordinal':
53
+ return 'border border-transparent bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-100 dark:border-indigo-600';
54
+ case 'quantitative':
55
+ return 'border border-transparent bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-100 dark:border-purple-600';
56
+ case 'temporal':
57
+ return 'border border-transparent bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-100 dark:border-yellow-600';
58
58
  default:
59
- return "border border-transparent bg-gray-400";
59
+ return 'border border-transparent bg-gray-400';
60
60
  }
61
61
  }
62
62
 
63
+ type wrapMutField = {
64
+ colSpan: number;
65
+ rowSpan: number;
66
+ } & (
67
+ | { type: 'field'; value: IMutField; fIndex: number }
68
+ | {
69
+ type: 'name';
70
+ value: string;
71
+ }
72
+ );
73
+
74
+ const getHeaders = (metas: IMutField[]): wrapMutField[][] => {
75
+ const height = metas.map((x) => x.path?.length ?? 1).reduce((a, b) => Math.max(a, b), 0);
76
+ const result: wrapMutField[][] = [...Array(height)].map(() => []);
77
+ let now = 1;
78
+ metas.forEach((x, fIndex) => {
79
+ const path = x.path ?? [x.name ?? x.fid];
80
+ if (path.length > now) {
81
+ for (let i = now - 1; i < path.length - 1; i++) {
82
+ result[i].push({
83
+ colSpan: 0,
84
+ rowSpan: 1,
85
+ type: 'name',
86
+ value: path[i],
87
+ });
88
+ }
89
+ }
90
+ now = path.length;
91
+ for (let i = 0; i < path.length - 1; i++) {
92
+ result[i][result[i].length - 1].colSpan++;
93
+ }
94
+ result[path.length - 1].push({
95
+ type: 'field',
96
+ value: x,
97
+ colSpan: 1,
98
+ rowSpan: height - path.length + 1,
99
+ fIndex,
100
+ });
101
+ });
102
+ return result;
103
+ };
104
+
105
+ const getHeaderKey = (f: wrapMutField) => {
106
+ if (f.type === 'name') {
107
+ return f.value;
108
+ }
109
+ return f.value.name ?? f.value.fid;
110
+ };
111
+
63
112
  const DataTable: React.FC<DataTableProps> = (props) => {
64
113
  const { size = 10, data, metas, onMetaChange } = props;
65
114
  const [pageIndex, setPageIndex] = useState(0);
@@ -82,6 +131,8 @@ const DataTable: React.FC<DataTableProps> = (props) => {
82
131
  const from = pageIndex * size;
83
132
  const to = Math.min((pageIndex + 1) * size, data.length - 1);
84
133
 
134
+ const headers = useMemo(() => getHeaders(metas), [metas]);
135
+
85
136
  return (
86
137
  <Container className="rounded border-gray-200 dark:border-gray-700 border">
87
138
  <Pagination
@@ -97,70 +148,99 @@ const DataTable: React.FC<DataTableProps> = (props) => {
97
148
  />
98
149
  <table className="min-w-full divide-y">
99
150
  <thead className="bg-gray-50 dark:bg-gray-900">
100
- <tr className="divide-x divide-gray-200 dark:divide-gray-700">
101
- {metas.map((field, fIndex) => (
102
- <th key={field.fid} className={""}>
103
- <div
104
- className={
105
- getHeaderClassNames(field) +
106
- " whitespace-nowrap py-3.5 px-6 text-left text-xs font-semibold text-gray-900 dark:text-gray-50 sm:pl-6"
107
- }
151
+ {headers.map((row) => (
152
+ <tr
153
+ className="divide-x divide-gray-200 dark:divide-gray-700"
154
+ key={`row_${getHeaderKey(row[0])}`}
155
+ >
156
+ {row.map((f) => (
157
+ <th
158
+ colSpan={f.colSpan}
159
+ rowSpan={f.rowSpan}
160
+ key={getHeaderKey(f)}
161
+ className={'align-top'}
108
162
  >
109
- <b>{field.name || field.fid}</b>
110
- <div>
111
- <DropdownContext
112
- options={analyticTypeList}
113
- onSelect={(value) => {
114
- onMetaChange(field.fid, fIndex, {
115
- analyticType: value as IMutField["analyticType"],
116
- });
117
- }}
163
+ {f.type === 'name' && (
164
+ <div
165
+ className={
166
+ 'border-t-4 border-yellow-400 whitespace-nowrap py-3.5 text-left text-xs font-semibold text-gray-900 dark:text-gray-50'
167
+ }
118
168
  >
119
- <span
120
- className={
121
- "cursor-pointer inline-flex px-2.5 py-0.5 text-xs font-medium mt-1 rounded-full text-xs text-white " +
122
- (field.analyticType === "dimension" ? "bg-blue-500" : "bg-purple-500")
123
- }
124
- >
125
- {field.analyticType}
126
- <ChevronUpDownIcon className="ml-2 w-3" />
127
- </span>
128
- </DropdownContext>
129
- </div>
130
- <div>
131
- <DropdownContext
132
- options={semanticTypeList}
133
- onSelect={(value) => {
134
- onMetaChange(field.fid, fIndex, {
135
- semanticType: value as IMutField["semanticType"],
136
- });
137
- }}
169
+ <b className="sticky inset-x-0 w-fit px-6 sm:pl-6">{f.value}</b>
170
+ </div>
171
+ )}
172
+ {f.type === 'field' && (
173
+ <div
174
+ className={
175
+ getHeaderClassNames(f.value) +
176
+ ' whitespace-nowrap py-3.5 px-6 text-left text-xs font-semibold text-gray-900 dark:text-gray-50 sm:pl-6'
177
+ }
138
178
  >
139
- <span
140
- className={
141
- "cursor-pointer inline-flex px-2.5 py-0.5 text-xs font-medium mt-1 rounded-full text-xs " +
142
- getSemanticColors(field)
143
- }
144
- >
145
- {field.semanticType}
146
- <ChevronUpDownIcon className="ml-2 w-3" />
147
- </span>
148
- </DropdownContext>
149
- </div>
150
- </div>
151
- </th>
152
- ))}
153
- </tr>
179
+ <b>{f.value.basename || f.value.name || f.value.fid}</b>
180
+ <div>
181
+ <DropdownContext
182
+ options={analyticTypeList}
183
+ onSelect={(value) => {
184
+ onMetaChange(f.value.fid, f.fIndex, {
185
+ analyticType: value as IMutField['analyticType'],
186
+ });
187
+ }}
188
+ >
189
+ <span
190
+ className={
191
+ 'cursor-pointer inline-flex px-2.5 py-0.5 text-xs font-medium mt-1 rounded-full text-xs text-white ' +
192
+ (f.value.analyticType === 'dimension'
193
+ ? 'bg-blue-500'
194
+ : 'bg-purple-500')
195
+ }
196
+ >
197
+ {f.value.analyticType}
198
+ <ChevronUpDownIcon className="ml-2 w-3" />
199
+ </span>
200
+ </DropdownContext>
201
+ </div>
202
+ <div>
203
+ <DropdownContext
204
+ options={semanticTypeList}
205
+ onSelect={(value) => {
206
+ onMetaChange(f.value.fid, f.fIndex, {
207
+ semanticType: value as IMutField['semanticType'],
208
+ });
209
+ }}
210
+ >
211
+ <span
212
+ className={
213
+ 'cursor-pointer inline-flex px-2.5 py-0.5 text-xs font-medium mt-1 rounded-full text-xs ' +
214
+ getSemanticColors(f.value)
215
+ }
216
+ >
217
+ {f.value.semanticType}
218
+ <ChevronUpDownIcon className="ml-2 w-3" />
219
+ </span>
220
+ </DropdownContext>
221
+ </div>
222
+ </div>
223
+ )}
224
+ </th>
225
+ ))}
226
+ </tr>
227
+ ))}
154
228
  </thead>
155
229
  <tbody className="divide-y divide-gray-100 dark:divide-gray-700 bg-white dark:bg-zinc-900">
156
230
  {data.slice(from, to + 1).map((row, index) => (
157
- <tr className={"divide-x divide-gray-200 dark:divide-gray-700 " + (index % 2 ? "bg-gray-50 dark:bg-gray-900" : "")} key={index}>
231
+ <tr
232
+ className={
233
+ 'divide-x divide-gray-200 dark:divide-gray-700 ' +
234
+ (index % 2 ? 'bg-gray-50 dark:bg-gray-900' : '')
235
+ }
236
+ key={index}
237
+ >
158
238
  {metas.map((field) => (
159
239
  <td
160
240
  key={field.fid + index}
161
241
  className={
162
242
  getHeaderType(field) +
163
- " whitespace-nowrap py-2 pl-4 pr-3 text-xs text-gray-500 dark:text-gray-300 sm:pl-6"
243
+ ' whitespace-nowrap py-2 pl-4 pr-3 text-xs text-gray-500 dark:text-gray-300 sm:pl-6'
164
244
  }
165
245
  >
166
246
  {`${row[field.fid]}`}
@@ -12,7 +12,7 @@ export default function Pagination(props: PaginationProps) {
12
12
  const { t } = useTranslation();
13
13
  return (
14
14
  <nav
15
- className="flex items-center justify-between border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-zinc-900 px-4 py-3 sm:px-6"
15
+ className="sticky inset-x-0 flex items-center justify-between border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-zinc-900 px-4 py-3 sm:px-6"
16
16
  aria-label="Pagination"
17
17
  >
18
18
  <div className="hidden sm:block">
@@ -1,7 +1,6 @@
1
- import { IRow, IMutField } from "../interfaces";
2
- import { inferMeta } from "../lib/inferMeta";
3
- import { guardDataKeys } from "../utils/dataPrep";
4
-
1
+ import { IRow, IMutField } from '../interfaces';
2
+ import { inferMeta } from '../lib/inferMeta';
3
+ import { flatKeys, guardDataKeys } from '../utils/dataPrep';
5
4
 
6
5
  export function transData(dataSource: IRow[]): {
7
6
  dataSource: IRow[];
@@ -17,31 +16,34 @@ export function transData(dataSource: IRow[]): {
17
16
  // const rawKeys = Object.keys(sampleRecord);
18
17
  // let flatColKeys: string[] = flatNestKeys(sampleRecord);
19
18
 
20
- const keys = Object.keys(sampleRecord);
19
+ const keys = flatKeys(sampleRecord);
21
20
  const metas = inferMeta({
22
21
  dataSource,
23
22
  fields: keys.map((k) => ({
24
- fid: k,
25
- key: k,
26
- analyticType: "?",
27
- semanticType: "?",
23
+ fid: k[k.length - 1],
24
+ key: k[k.length - 1],
25
+ analyticType: '?',
26
+ semanticType: '?',
27
+ path: k,
28
+ basename: k[k.length - 1],
29
+ name: k.join('.'),
28
30
  })),
29
- })
31
+ });
30
32
  const { safeData, safeMetas } = guardDataKeys(dataSource, metas);
31
33
  const finalData: IRow[] = [];
32
34
  for (let record of safeData) {
33
35
  const newRecord: IRow = {};
34
36
  for (let field of safeMetas) {
35
- if (field.semanticType === "quantitative") {
37
+ if (field.semanticType === 'quantitative') {
36
38
  newRecord[field.fid] = Number(record[field.fid]);
37
39
  } else {
38
- newRecord[field.fid] = record[field.fid];//getValueByKeyPath(record, field.fid);// record[field.fid];
40
+ newRecord[field.fid] = record[field.fid]; //getValueByKeyPath(record, field.fid);// record[field.fid];
39
41
  }
40
42
  }
41
43
  finalData.push(newRecord);
42
44
  }
43
45
  return {
44
46
  dataSource: finalData,
45
- fields: safeMetas
47
+ fields: safeMetas,
46
48
  };
47
49
  }
package/src/interfaces.ts CHANGED
@@ -34,18 +34,22 @@ export interface IMutField {
34
34
  fid: string;
35
35
  key?: string;
36
36
  name?: string;
37
+ basename?: string;
37
38
  disable?: boolean;
38
39
  semanticType: ISemanticType;
39
40
  analyticType: IAnalyticType;
41
+ path?: string[];
40
42
  }
41
43
 
42
44
  export interface IUncertainMutField {
43
45
  fid: string;
44
46
  key?: string;
45
47
  name?: string;
48
+ basename?: string;
46
49
  disable?: boolean;
47
50
  semanticType: ISemanticType | '?';
48
51
  analyticType: IAnalyticType | '?';
52
+ path: string[];
49
53
  }
50
54
 
51
55
  export type IExpParamter =
@@ -90,6 +94,8 @@ export interface IField {
90
94
  cmp?: (a: any, b: any) => number;
91
95
  computed?: boolean;
92
96
  expression?: IExpression;
97
+ basename?: string;
98
+ path?: [],
93
99
  }
94
100
 
95
101
  export interface IViewField extends IField {
@@ -215,6 +221,16 @@ export interface IVisSpec {
215
221
  readonly config: DeepReadonly<IVisualConfig>;
216
222
  }
217
223
 
224
+ export type SetToArray<T> = (
225
+ T extends object ? (
226
+ T extends Set<infer U> ? Array<U> : { [K in keyof T]: SetToArray<T[K]> }
227
+ ) : T
228
+ );
229
+
230
+ export type IVisSpecForExport = SetToArray<IVisSpec>;
231
+
232
+ export type IFilterFieldForExport = SetToArray<IFilterField>;
233
+
218
234
  export enum ISegmentKey {
219
235
  vis = 'vis',
220
236
  data = 'data',
@@ -1,4 +1,5 @@
1
1
  import { IAnalyticType, IMutField, IRow, ISemanticType, IUncertainMutField } from '../interfaces';
2
+ import { getValueByKeyPath } from '../utils/dataPrep';
2
3
 
3
4
  const COMMON_TIME_FORMAT: RegExp[] = [
4
5
  /^\d{4}-\d{2}-\d{2}$/, // YYYY-MM-DD
@@ -55,12 +56,12 @@ function inferAnalyticTypeFromSemanticType(semanticType: ISemanticType): IAnalyt
55
56
  }
56
57
  }
57
58
 
58
- export function inferSemanticType(data: IRow[], fid: string): ISemanticType {
59
- const values = data.map((row) => row[fid]);
59
+ export function inferSemanticType(data: IRow[], path: string[]): ISemanticType {
60
+ const values = data.map((row) => getValueByKeyPath(row, path));
60
61
 
61
62
  let st: ISemanticType = isNumericArray(values) ? 'quantitative' : 'nominal';
62
63
  if (st === 'nominal') {
63
- if (isDateTimeArray(data.map((row) => `${row[fid]}`))) st = 'temporal';
64
+ if (isDateTimeArray(data.map((row) => `${getValueByKeyPath(row, path)}`))) st = 'temporal';
64
65
  }
65
66
  return st;
66
67
  }
@@ -70,7 +71,7 @@ export function inferMeta(props: { dataSource: IRow[]; fields: IUncertainMutFiel
70
71
  const finalFieldMetas: IMutField[] = [];
71
72
  for (let field of fields) {
72
73
  let semanticType: ISemanticType =
73
- field.semanticType === '?' ? inferSemanticType(dataSource, field.fid) : field.semanticType;
74
+ field.semanticType === '?' ? inferSemanticType(dataSource, field.path) : field.semanticType;
74
75
  let analyticType: IAnalyticType = inferAnalyticTypeFromSemanticType(semanticType);
75
76
 
76
77
  finalFieldMetas.push({
@@ -79,6 +80,8 @@ export function inferMeta(props: { dataSource: IRow[]; fields: IUncertainMutFiel
79
80
  name: field.name ? field.name : field.fid,
80
81
  analyticType,
81
82
  semanticType,
83
+ basename: field.basename || field.name || field.fid,
84
+ path: field.path,
82
85
  });
83
86
  }
84
87
  return finalFieldMetas;
@@ -1,4 +1,4 @@
1
- import { IMutField, IRow } from "../interfaces";
1
+ import { IField, IRow } from "../interfaces";
2
2
  import { aggregate } from "./op/aggregate";
3
3
  import { fold } from "./op/fold";
4
4
  import { IAggQuery, IBinQuery, IFoldQuery, IRawQuery } from "./interfaces";
@@ -6,7 +6,7 @@ import { bin } from "./op/bin";
6
6
 
7
7
  export type IViewQuery = IAggQuery | IFoldQuery | IBinQuery | IRawQuery;
8
8
 
9
- export function queryView (rawData: IRow[], metas: IMutField[], query: IViewQuery) {
9
+ export function queryView (rawData: IRow[], metas: IField[], query: IViewQuery) {
10
10
  switch (query.op) {
11
11
  case 'aggregate':
12
12
  return aggregate(rawData, query);
package/src/services.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { toJS } from 'mobx';
2
- import { IRow, IMutField, IFilterField, Specification } from './interfaces';
2
+ import { IRow, IMutField, IField, IFilterField, Specification } from './interfaces';
3
3
  /* eslint import/no-webpack-loader-syntax:0 */
4
4
  // @ts-ignore
5
5
  // eslint-disable-next-line
@@ -140,7 +140,7 @@ export const applyFilter = async (data: IRow[], filters: readonly IFilterField[]
140
140
  }
141
141
  };
142
142
 
143
- export const transformDataService = async (data: IRow[], columns: IMutField[]): Promise<IRow[]> => {
143
+ export const transformDataService = async (data: IRow[], columns: IField[]): Promise<IRow[]> => {
144
144
  if (columns.length === 0 || data.length === 0) return data;
145
145
  const worker = new TransformDataWorker();
146
146
  try {
@@ -156,7 +156,7 @@ export const transformDataService = async (data: IRow[], columns: IMutField[]):
156
156
  }
157
157
  }
158
158
 
159
- export const applyViewQuery = async (data: IRow[], metas: IMutField[], query: IViewQuery): Promise<IRow[]> => {
159
+ export const applyViewQuery = async (data: IRow[], metas: IField[], query: IViewQuery): Promise<IRow[]> => {
160
160
  const worker = new ViewQueryWorker();
161
161
  try {
162
162
  const res: IRow[] = await workerService(worker, {
@@ -1,6 +1,6 @@
1
1
  import { IReactionDisposer, makeAutoObservable, observable, reaction, toJS } from "mobx";
2
2
  import produce from "immer";
3
- import { DataSet, DraggableFieldState, IFilterRule, IViewField, IVisSpec, IVisualConfig, Specification } from "../interfaces";
3
+ import { DataSet, DraggableFieldState, IFilterRule, IViewField, IVisSpec, IVisSpecForExport, IFilterFieldForExport, IVisualConfig, Specification } from "../interfaces";
4
4
  import { CHANNEL_LIMIT, GEMO_TYPES, MetaFieldKeys } from "../config";
5
5
  import { VisSpecWithHistory } from "../models/visSpecHistory";
6
6
  import { IStoInfo, dumpsGWPureSpec, parseGWContent, parseGWPureSpec, stringifyGWContent } from "../utils/save";
@@ -88,7 +88,7 @@ type DeepReadonly<T extends Record<keyof any, any>> = {
88
88
  readonly [K in keyof T]: T[K] extends Record<keyof any, any> ? DeepReadonly<T[K]> : T[K];
89
89
  };
90
90
 
91
- const forwardVisualConfigs = (backwards: ReturnType<typeof parseGWContent>["specList"]): IVisSpec[] => {
91
+ const forwardVisualConfigs = (backwards: ReturnType<typeof parseGWContent>["specList"]): IVisSpecForExport[] => {
92
92
  return backwards.map((content) => ({
93
93
  ...content,
94
94
  config: {
@@ -336,6 +336,7 @@ export class VizSpecStore {
336
336
  dragId: uniqueId(),
337
337
  fid: f.fid,
338
338
  name: f.name || f.fid,
339
+ basename: f.basename || f.name || f.fid,
339
340
  semanticType: f.semanticType,
340
341
  analyticType: f.analyticType,
341
342
  }));
@@ -345,6 +346,7 @@ export class VizSpecStore {
345
346
  dragId: uniqueId(),
346
347
  fid: f.fid,
347
348
  name: f.name || f.fid,
349
+ basename: f.basename || f.name || f.fid,
348
350
  analyticType: f.analyticType,
349
351
  semanticType: f.semanticType,
350
352
  aggName: "sum",
@@ -701,15 +703,15 @@ export class VizSpecStore {
701
703
  return stringifyGWContent({
702
704
  datasets: toJS(this.commonStore.datasets),
703
705
  dataSources: this.commonStore.dataSources,
704
- specList: pureVisList,
706
+ specList: this.visSpecEncoder(pureVisList),
705
707
  });
706
708
  }
707
709
  public exportViewSpec() {
708
710
  const pureVisList = dumpsGWPureSpec(this.visList);
709
- return pureVisList
711
+ return this.visSpecEncoder(pureVisList);
710
712
  }
711
713
  public importStoInfo (stoInfo: IStoInfo) {
712
- this.visList = parseGWPureSpec(forwardVisualConfigs(stoInfo.specList));
714
+ this.visList = parseGWPureSpec(this.visSpecDecoder(forwardVisualConfigs(stoInfo.specList)));
713
715
  this.visIndex = 0;
714
716
  this.commonStore.datasets = stoInfo.datasets;
715
717
  this.commonStore.dataSources = stoInfo.dataSources;
@@ -719,4 +721,54 @@ export class VizSpecStore {
719
721
  const content = parseGWContent(raw);
720
722
  this.importStoInfo(content);
721
723
  }
724
+
725
+ private visSpecEncoder(visList: IVisSpec[]): IVisSpecForExport[] {
726
+ const updatedVisList = visList.map((visSpec) => {
727
+ const updatedFilters = visSpec.encodings.filters.map((filter) => {
728
+ if (filter.rule?.type === "one of") {
729
+ const rule = {
730
+ ...filter.rule,
731
+ value: Array.from(filter.rule.value)
732
+ }
733
+ return {
734
+ ...filter,
735
+ rule
736
+ }
737
+ }
738
+ return filter as IFilterFieldForExport;
739
+ });
740
+ return {
741
+ ...visSpec,
742
+ encodings: {
743
+ ...visSpec.encodings,
744
+ filters: updatedFilters
745
+ }
746
+ }
747
+ });
748
+ return updatedVisList;
749
+ }
750
+ private visSpecDecoder(visList: IVisSpecForExport[]): IVisSpec[] {
751
+ const updatedVisList = visList.map((visSpec) => {
752
+ const updatedFilters = visSpec.encodings.filters.map((filter) => {
753
+ if (filter.rule?.type === "one of" && Array.isArray(filter.rule.value)) {
754
+ return {
755
+ ...filter,
756
+ rule: {
757
+ ...filter.rule,
758
+ value: new Set(filter.rule.value)
759
+ }
760
+ }
761
+ }
762
+ return filter;
763
+ })
764
+ return {
765
+ ...visSpec,
766
+ encodings: {
767
+ ...visSpec.encodings,
768
+ filters: updatedFilters
769
+ }
770
+ } as IVisSpec;
771
+ });
772
+ return updatedVisList;
773
+ }
722
774
  }