@gradio/dataframe 0.17.16 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/Dataframe.stories.svelte +1 -2
- package/Index.svelte +3 -13
- package/dist/Index.svelte +3 -10
- package/dist/Index.svelte.d.ts +1 -5
- package/dist/shared/CellMenu.svelte +37 -0
- package/dist/shared/CellMenu.svelte.d.ts +4 -1
- package/dist/shared/CellMenuIcons.svelte +48 -0
- package/dist/shared/EditableCell.svelte +0 -2
- package/dist/shared/EditableCell.svelte.d.ts +0 -1
- package/dist/shared/FilterMenu.svelte +235 -0
- package/dist/shared/FilterMenu.svelte.d.ts +19 -0
- package/dist/shared/Table.svelte +59 -5
- package/dist/shared/TableCell.svelte +0 -2
- package/dist/shared/TableCell.svelte.d.ts +0 -1
- package/dist/shared/TableHeader.svelte +26 -2
- package/dist/shared/TableHeader.svelte.d.ts +7 -1
- package/dist/shared/context/dataframe_context.d.ts +19 -0
- package/dist/shared/context/dataframe_context.js +46 -6
- package/dist/shared/utils/filter_utils.d.ts +28 -0
- package/dist/shared/utils/filter_utils.js +123 -0
- package/dist/shared/utils/table_utils.d.ts +7 -0
- package/dist/shared/utils/table_utils.js +29 -0
- package/package.json +9 -9
- package/shared/CellMenu.svelte +45 -1
- package/shared/CellMenuIcons.svelte +48 -0
- package/shared/EditableCell.svelte +0 -2
- package/shared/FilterMenu.svelte +248 -0
- package/shared/Table.svelte +78 -8
- package/shared/TableCell.svelte +0 -2
- package/shared/TableHeader.svelte +31 -2
- package/shared/context/dataframe_context.ts +80 -17
- package/shared/utils/filter_utils.ts +207 -0
- package/shared/utils/table_utils.ts +52 -0
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
export const DATAFRAME_KEY = Symbol("dataframe");
|
|
14
14
|
|
|
15
15
|
export type SortDirection = "asc" | "desc";
|
|
16
|
+
export type FilterDatatype = "string" | "number";
|
|
16
17
|
export type CellCoordinate = [number, number];
|
|
17
18
|
|
|
18
19
|
interface DataFrameState {
|
|
@@ -40,6 +41,19 @@ interface DataFrameState {
|
|
|
40
41
|
styling: string[][] | null;
|
|
41
42
|
} | null;
|
|
42
43
|
};
|
|
44
|
+
filter_state: {
|
|
45
|
+
filter_columns: {
|
|
46
|
+
col: number;
|
|
47
|
+
datatype: FilterDatatype;
|
|
48
|
+
filter: string;
|
|
49
|
+
value: string;
|
|
50
|
+
}[];
|
|
51
|
+
initial_data: {
|
|
52
|
+
data: { id: string; value: string | number }[][];
|
|
53
|
+
display_value: string[][] | null;
|
|
54
|
+
styling: string[][] | null;
|
|
55
|
+
} | null;
|
|
56
|
+
};
|
|
43
57
|
ui_state: {
|
|
44
58
|
active_cell_menu: { row: number; col: number; x: number; y: number } | null;
|
|
45
59
|
active_header_menu: { col: number; x: number; y: number } | null;
|
|
@@ -60,6 +74,12 @@ interface DataFrameState {
|
|
|
60
74
|
interface DataFrameActions {
|
|
61
75
|
handle_search: (query: string | null) => void;
|
|
62
76
|
handle_sort: (col: number, direction: SortDirection) => void;
|
|
77
|
+
handle_filter: (
|
|
78
|
+
col: number,
|
|
79
|
+
datatype: FilterDatatype,
|
|
80
|
+
filter: string,
|
|
81
|
+
value: string
|
|
82
|
+
) => void;
|
|
63
83
|
get_sort_status: (name: string, headers: string[]) => "none" | "asc" | "desc";
|
|
64
84
|
sort_data: (
|
|
65
85
|
data: any[][],
|
|
@@ -109,6 +129,7 @@ interface DataFrameActions {
|
|
|
109
129
|
dispatch: (e: "change" | "input", detail?: any) => void
|
|
110
130
|
) => Promise<void>;
|
|
111
131
|
reset_sort_state: () => void;
|
|
132
|
+
reset_filter_state: () => void;
|
|
112
133
|
set_active_cell_menu: (
|
|
113
134
|
menu: { row: number; col: number; x: number; y: number } | null
|
|
114
135
|
) => void;
|
|
@@ -209,6 +230,15 @@ function create_actions(
|
|
|
209
230
|
return { data: new_data, headers: new_headers };
|
|
210
231
|
};
|
|
211
232
|
|
|
233
|
+
const update_array = (
|
|
234
|
+
source: { id: string; value: string | number }[][] | string[][] | null,
|
|
235
|
+
target: any[] | null | undefined
|
|
236
|
+
): void => {
|
|
237
|
+
if (source && target) {
|
|
238
|
+
target.splice(0, target.length, ...JSON.parse(JSON.stringify(source)));
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
|
|
212
242
|
return {
|
|
213
243
|
handle_search: (query) =>
|
|
214
244
|
update_state((s) => ({ current_search_query: query })),
|
|
@@ -247,6 +277,39 @@ function create_actions(
|
|
|
247
277
|
}
|
|
248
278
|
};
|
|
249
279
|
}),
|
|
280
|
+
handle_filter: (col, datatype, filter, value) =>
|
|
281
|
+
update_state((s) => {
|
|
282
|
+
const filter_cols = s.filter_state.filter_columns.some(
|
|
283
|
+
(c) => c.col === col
|
|
284
|
+
)
|
|
285
|
+
? s.filter_state.filter_columns.filter((c) => c.col !== col)
|
|
286
|
+
: [
|
|
287
|
+
...s.filter_state.filter_columns,
|
|
288
|
+
{ col, datatype, filter, value }
|
|
289
|
+
];
|
|
290
|
+
|
|
291
|
+
const initial_data =
|
|
292
|
+
s.filter_state.initial_data ||
|
|
293
|
+
(context.data && filter_cols.length > 0
|
|
294
|
+
? {
|
|
295
|
+
data: JSON.parse(JSON.stringify(context.data)),
|
|
296
|
+
display_value: context.display_value
|
|
297
|
+
? JSON.parse(JSON.stringify(context.display_value))
|
|
298
|
+
: null,
|
|
299
|
+
styling: context.styling
|
|
300
|
+
? JSON.parse(JSON.stringify(context.styling))
|
|
301
|
+
: null
|
|
302
|
+
}
|
|
303
|
+
: null);
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
filter_state: {
|
|
307
|
+
...s.filter_state,
|
|
308
|
+
filter_columns: filter_cols,
|
|
309
|
+
initial_data: initial_data
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
}),
|
|
250
313
|
get_sort_status: (name, headers) => {
|
|
251
314
|
const s = get(state);
|
|
252
315
|
const sort_item = s.sort_state.sort_columns.find(
|
|
@@ -345,7 +408,8 @@ function create_actions(
|
|
|
345
408
|
) {
|
|
346
409
|
if (!dequal(current_headers, previous_headers)) {
|
|
347
410
|
update_state((s) => ({
|
|
348
|
-
sort_state: { sort_columns: [], row_order: [], initial_data: null }
|
|
411
|
+
sort_state: { sort_columns: [], row_order: [], initial_data: null },
|
|
412
|
+
filter_state: { filter_columns: [], initial_data: null }
|
|
349
413
|
}));
|
|
350
414
|
}
|
|
351
415
|
dispatch("change", {
|
|
@@ -361,22 +425,6 @@ function create_actions(
|
|
|
361
425
|
if (s.sort_state.initial_data && context.data) {
|
|
362
426
|
const original = s.sort_state.initial_data;
|
|
363
427
|
|
|
364
|
-
const update_array = (
|
|
365
|
-
source:
|
|
366
|
-
| { id: string; value: string | number }[][]
|
|
367
|
-
| string[][]
|
|
368
|
-
| null,
|
|
369
|
-
target: any[] | null | undefined
|
|
370
|
-
): void => {
|
|
371
|
-
if (source && target) {
|
|
372
|
-
target.splice(
|
|
373
|
-
0,
|
|
374
|
-
target.length,
|
|
375
|
-
...JSON.parse(JSON.stringify(source))
|
|
376
|
-
);
|
|
377
|
-
}
|
|
378
|
-
};
|
|
379
|
-
|
|
380
428
|
update_array(original.data, context.data);
|
|
381
429
|
update_array(original.display_value, context.display_value);
|
|
382
430
|
update_array(original.styling, context.styling);
|
|
@@ -386,6 +434,20 @@ function create_actions(
|
|
|
386
434
|
sort_state: { sort_columns: [], row_order: [], initial_data: null }
|
|
387
435
|
};
|
|
388
436
|
}),
|
|
437
|
+
reset_filter_state: () =>
|
|
438
|
+
update_state((s) => {
|
|
439
|
+
if (s.filter_state.initial_data && context.data) {
|
|
440
|
+
const original = s.filter_state.initial_data;
|
|
441
|
+
|
|
442
|
+
update_array(original.data, context.data);
|
|
443
|
+
update_array(original.display_value, context.display_value);
|
|
444
|
+
update_array(original.styling, context.styling);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return {
|
|
448
|
+
filter_state: { filter_columns: [], initial_data: null }
|
|
449
|
+
};
|
|
450
|
+
}),
|
|
389
451
|
set_active_cell_menu: (menu) =>
|
|
390
452
|
update_state((s) => ({
|
|
391
453
|
ui_state: { ...s.ui_state, active_cell_menu: menu }
|
|
@@ -599,6 +661,7 @@ export function create_dataframe_context(
|
|
|
599
661
|
config,
|
|
600
662
|
current_search_query: null,
|
|
601
663
|
sort_state: { sort_columns: [], row_order: [], initial_data: null },
|
|
664
|
+
filter_state: { filter_columns: [], initial_data: null },
|
|
602
665
|
ui_state: {
|
|
603
666
|
active_cell_menu: null,
|
|
604
667
|
active_header_menu: null,
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { filter_table_data } from "./table_utils";
|
|
2
|
+
|
|
3
|
+
export type FilterDatatype = "string" | "number";
|
|
4
|
+
|
|
5
|
+
export function filter_data(
|
|
6
|
+
data: { id: string; value: string | number }[][],
|
|
7
|
+
filter_columns: {
|
|
8
|
+
col: number;
|
|
9
|
+
datatype: FilterDatatype;
|
|
10
|
+
filter: string;
|
|
11
|
+
value: string;
|
|
12
|
+
}[]
|
|
13
|
+
): number[] {
|
|
14
|
+
if (!data || !data.length || !data[0]) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
let row_indices = [...Array(data.length)].map((_, i) => i);
|
|
18
|
+
|
|
19
|
+
if (filter_columns.length > 0) {
|
|
20
|
+
filter_columns.forEach((column) => {
|
|
21
|
+
if (column.datatype === "string") {
|
|
22
|
+
switch (column.filter) {
|
|
23
|
+
case "Contains":
|
|
24
|
+
row_indices = row_indices.filter((i) =>
|
|
25
|
+
data[i][column.col]?.value.toString().includes(column.value)
|
|
26
|
+
);
|
|
27
|
+
break;
|
|
28
|
+
case "Does not contain":
|
|
29
|
+
row_indices = row_indices.filter(
|
|
30
|
+
(i) =>
|
|
31
|
+
!data[i][column.col]?.value.toString().includes(column.value)
|
|
32
|
+
);
|
|
33
|
+
break;
|
|
34
|
+
case "Starts with":
|
|
35
|
+
row_indices = row_indices.filter((i) =>
|
|
36
|
+
data[i][column.col]?.value.toString().startsWith(column.value)
|
|
37
|
+
);
|
|
38
|
+
break;
|
|
39
|
+
case "Ends with":
|
|
40
|
+
row_indices = row_indices.filter((i) =>
|
|
41
|
+
data[i][column.col]?.value.toString().endsWith(column.value)
|
|
42
|
+
);
|
|
43
|
+
break;
|
|
44
|
+
case "Is":
|
|
45
|
+
row_indices = row_indices.filter(
|
|
46
|
+
(i) => data[i][column.col]?.value.toString() === column.value
|
|
47
|
+
);
|
|
48
|
+
break;
|
|
49
|
+
case "Is not":
|
|
50
|
+
row_indices = row_indices.filter(
|
|
51
|
+
(i) => !(data[i][column.col]?.value.toString() === column.value)
|
|
52
|
+
);
|
|
53
|
+
break;
|
|
54
|
+
case "Is empty":
|
|
55
|
+
row_indices = row_indices.filter(
|
|
56
|
+
(i) => data[i][column.col]?.value.toString() === ""
|
|
57
|
+
);
|
|
58
|
+
break;
|
|
59
|
+
case "Is not empty":
|
|
60
|
+
row_indices = row_indices.filter(
|
|
61
|
+
(i) => !(data[i][column.col]?.value.toString() === "")
|
|
62
|
+
);
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
} else if (column.datatype === "number") {
|
|
66
|
+
switch (column.filter) {
|
|
67
|
+
case "=":
|
|
68
|
+
row_indices = row_indices.filter((i) => {
|
|
69
|
+
if (
|
|
70
|
+
!isNaN(Number(data[i][column.col]?.value)) &&
|
|
71
|
+
!isNaN(Number(column.value))
|
|
72
|
+
) {
|
|
73
|
+
return (
|
|
74
|
+
Number(data[i][column.col]?.value) === Number(column.value)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
});
|
|
79
|
+
break;
|
|
80
|
+
case "≠":
|
|
81
|
+
row_indices = row_indices.filter((i) => {
|
|
82
|
+
if (
|
|
83
|
+
!isNaN(Number(data[i][column.col]?.value)) &&
|
|
84
|
+
!isNaN(Number(column.value))
|
|
85
|
+
) {
|
|
86
|
+
return !(
|
|
87
|
+
Number(data[i][column.col]?.value) === Number(column.value)
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return false;
|
|
91
|
+
});
|
|
92
|
+
break;
|
|
93
|
+
case ">":
|
|
94
|
+
row_indices = row_indices.filter((i) => {
|
|
95
|
+
if (
|
|
96
|
+
!isNaN(Number(data[i][column.col]?.value)) &&
|
|
97
|
+
!isNaN(Number(column.value))
|
|
98
|
+
) {
|
|
99
|
+
return (
|
|
100
|
+
Number(data[i][column.col]?.value) > Number(column.value)
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
});
|
|
105
|
+
break;
|
|
106
|
+
case "<":
|
|
107
|
+
row_indices = row_indices.filter((i) => {
|
|
108
|
+
if (
|
|
109
|
+
!isNaN(Number(data[i][column.col]?.value)) &&
|
|
110
|
+
!isNaN(Number(column.value))
|
|
111
|
+
) {
|
|
112
|
+
return (
|
|
113
|
+
Number(data[i][column.col]?.value) < Number(column.value)
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
});
|
|
118
|
+
break;
|
|
119
|
+
case "≥":
|
|
120
|
+
row_indices = row_indices.filter((i) => {
|
|
121
|
+
if (
|
|
122
|
+
!isNaN(Number(data[i][column.col]?.value)) &&
|
|
123
|
+
!isNaN(Number(column.value))
|
|
124
|
+
) {
|
|
125
|
+
return (
|
|
126
|
+
Number(data[i][column.col]?.value) >= Number(column.value)
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
130
|
+
});
|
|
131
|
+
break;
|
|
132
|
+
case "≤":
|
|
133
|
+
row_indices = row_indices.filter((i) => {
|
|
134
|
+
if (
|
|
135
|
+
!isNaN(Number(data[i][column.col]?.value)) &&
|
|
136
|
+
!isNaN(Number(column.value))
|
|
137
|
+
) {
|
|
138
|
+
return (
|
|
139
|
+
Number(data[i][column.col]?.value) <= Number(column.value)
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
return false;
|
|
143
|
+
});
|
|
144
|
+
break;
|
|
145
|
+
case "Is empty":
|
|
146
|
+
row_indices = row_indices.filter(
|
|
147
|
+
(i) => data[i][column.col]?.value.toString() === ""
|
|
148
|
+
);
|
|
149
|
+
break;
|
|
150
|
+
case "Is not empty":
|
|
151
|
+
row_indices = row_indices.filter((i) => {
|
|
152
|
+
if (!isNaN(Number(data[i][column.col]?.value))) {
|
|
153
|
+
return !(data[i][column.col]?.value.toString() === "");
|
|
154
|
+
}
|
|
155
|
+
return false;
|
|
156
|
+
});
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
return row_indices;
|
|
162
|
+
}
|
|
163
|
+
return [...Array(data.length)].map((_, i) => i);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function filter_data_and_preserve_selection(
|
|
167
|
+
data: { id: string; value: string | number }[][],
|
|
168
|
+
display_value: string[][] | null,
|
|
169
|
+
styling: string[][] | null,
|
|
170
|
+
filter_columns: {
|
|
171
|
+
col: number;
|
|
172
|
+
datatype: FilterDatatype;
|
|
173
|
+
filter: string;
|
|
174
|
+
value: string;
|
|
175
|
+
}[],
|
|
176
|
+
selected: [number, number] | false,
|
|
177
|
+
get_current_indices: (
|
|
178
|
+
id: string,
|
|
179
|
+
data: { id: string; value: string | number }[][]
|
|
180
|
+
) => [number, number],
|
|
181
|
+
original_data?: { id: string; value: string | number }[][],
|
|
182
|
+
original_display_value?: string[][] | null,
|
|
183
|
+
original_styling?: string[][] | null
|
|
184
|
+
): { data: typeof data; selected: [number, number] | false } {
|
|
185
|
+
let id = null;
|
|
186
|
+
if (selected && selected[0] in data && selected[1] in data[selected[0]]) {
|
|
187
|
+
id = data[selected[0]][selected[1]].id;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
filter_table_data(
|
|
191
|
+
data,
|
|
192
|
+
display_value,
|
|
193
|
+
styling,
|
|
194
|
+
filter_columns,
|
|
195
|
+
original_data,
|
|
196
|
+
original_display_value,
|
|
197
|
+
original_styling
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
let new_selected = selected;
|
|
201
|
+
if (id) {
|
|
202
|
+
const [i, j] = get_current_indices(id, data);
|
|
203
|
+
new_selected = [i, j];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return { data, selected: new_selected };
|
|
207
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Headers, HeadersWithIDs, TableCell, TableData } from "../types";
|
|
2
2
|
import { sort_data } from "./sort_utils";
|
|
3
|
+
import { filter_data } from "./filter_utils";
|
|
3
4
|
import type { SortDirection } from "./sort_utils";
|
|
5
|
+
import type { FilterDatatype } from "./filter_utils";
|
|
4
6
|
import { dsvFormat } from "d3-dsv";
|
|
5
7
|
|
|
6
8
|
export function make_cell_id(row: number, col: number): string {
|
|
@@ -49,6 +51,56 @@ export function sort_table_data(
|
|
|
49
51
|
}
|
|
50
52
|
}
|
|
51
53
|
|
|
54
|
+
export function filter_table_data(
|
|
55
|
+
data: TableData,
|
|
56
|
+
display_value: string[][] | null,
|
|
57
|
+
styling: string[][] | null,
|
|
58
|
+
filter_columns: {
|
|
59
|
+
col: number;
|
|
60
|
+
datatype: FilterDatatype;
|
|
61
|
+
filter: string;
|
|
62
|
+
value: string;
|
|
63
|
+
}[],
|
|
64
|
+
original_data?: TableData,
|
|
65
|
+
original_display_value?: string[][] | null,
|
|
66
|
+
original_styling?: string[][] | null
|
|
67
|
+
): void {
|
|
68
|
+
const base_data = original_data ?? data;
|
|
69
|
+
const base_display_value = original_display_value ?? display_value;
|
|
70
|
+
const base_styling = original_styling ?? styling;
|
|
71
|
+
|
|
72
|
+
if (!filter_columns.length) {
|
|
73
|
+
data.splice(0, data.length, ...base_data.map((row) => [...row]));
|
|
74
|
+
if (display_value && base_display_value) {
|
|
75
|
+
display_value.splice(
|
|
76
|
+
0,
|
|
77
|
+
display_value.length,
|
|
78
|
+
...base_display_value.map((row) => [...row])
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
if (styling && base_styling) {
|
|
82
|
+
styling.splice(0, styling.length, ...base_styling.map((row) => [...row]));
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (!data || !data.length) return;
|
|
87
|
+
|
|
88
|
+
const indices = filter_data(base_data, filter_columns);
|
|
89
|
+
|
|
90
|
+
const new_data = indices.map((i: number) => base_data[i]);
|
|
91
|
+
data.splice(0, data.length, ...new_data);
|
|
92
|
+
|
|
93
|
+
if (display_value && base_display_value) {
|
|
94
|
+
const new_display = indices.map((i: number) => base_display_value[i]);
|
|
95
|
+
display_value.splice(0, display_value.length, ...new_display);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (styling && base_styling) {
|
|
99
|
+
const new_styling = indices.map((i: number) => base_styling[i]);
|
|
100
|
+
styling.splice(0, styling.length, ...new_styling);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
52
104
|
export async function copy_table_data(
|
|
53
105
|
data: TableData,
|
|
54
106
|
selected_cells: [number, number][] | null
|