@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.
Files changed (34) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/Dataframe.stories.svelte +1 -2
  3. package/Index.svelte +3 -13
  4. package/dist/Index.svelte +3 -10
  5. package/dist/Index.svelte.d.ts +1 -5
  6. package/dist/shared/CellMenu.svelte +37 -0
  7. package/dist/shared/CellMenu.svelte.d.ts +4 -1
  8. package/dist/shared/CellMenuIcons.svelte +48 -0
  9. package/dist/shared/EditableCell.svelte +0 -2
  10. package/dist/shared/EditableCell.svelte.d.ts +0 -1
  11. package/dist/shared/FilterMenu.svelte +235 -0
  12. package/dist/shared/FilterMenu.svelte.d.ts +19 -0
  13. package/dist/shared/Table.svelte +59 -5
  14. package/dist/shared/TableCell.svelte +0 -2
  15. package/dist/shared/TableCell.svelte.d.ts +0 -1
  16. package/dist/shared/TableHeader.svelte +26 -2
  17. package/dist/shared/TableHeader.svelte.d.ts +7 -1
  18. package/dist/shared/context/dataframe_context.d.ts +19 -0
  19. package/dist/shared/context/dataframe_context.js +46 -6
  20. package/dist/shared/utils/filter_utils.d.ts +28 -0
  21. package/dist/shared/utils/filter_utils.js +123 -0
  22. package/dist/shared/utils/table_utils.d.ts +7 -0
  23. package/dist/shared/utils/table_utils.js +29 -0
  24. package/package.json +9 -9
  25. package/shared/CellMenu.svelte +45 -1
  26. package/shared/CellMenuIcons.svelte +48 -0
  27. package/shared/EditableCell.svelte +0 -2
  28. package/shared/FilterMenu.svelte +248 -0
  29. package/shared/Table.svelte +78 -8
  30. package/shared/TableCell.svelte +0 -2
  31. package/shared/TableHeader.svelte +31 -2
  32. package/shared/context/dataframe_context.ts +80 -17
  33. package/shared/utils/filter_utils.ts +207 -0
  34. 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