mirror of
https://github.com/gradio-app/gradio.git
synced 2024-12-15 02:11:15 +08:00
fix dataframe support
This commit is contained in:
parent
06a5e20d9e
commit
59d1df64f3
@ -1,8 +1,6 @@
|
||||
# Demo: (Dataframe, Dropdown) -> (Dataframe)
|
||||
|
||||
import gradio as gr
|
||||
import numpy as np
|
||||
import random
|
||||
|
||||
def filter_records(records, gender):
|
||||
return records[records['gender'] == gender]
|
||||
|
@ -1,45 +1,129 @@
|
||||
import React from 'react';
|
||||
import ComponentExample from '../component_example';
|
||||
import jspreadsheet from "jspreadsheet-ce";
|
||||
import "../../../node_modules/jspreadsheet-ce/dist/jspreadsheet.css"
|
||||
import classNames from 'classnames';
|
||||
import { array_compare } from "../../utils";
|
||||
|
||||
class DataframeOutput extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.wrapper = React.createRef();
|
||||
this.state = this.constructor.get_default_state()
|
||||
}
|
||||
componentDidMount = function() {
|
||||
this.el = jspreadsheet(this.wrapper.current, {minDimensions: [1, 1]});
|
||||
static get_default_state() {
|
||||
return {
|
||||
"page": 0,
|
||||
"sort_by": null,
|
||||
"sort_descending": false
|
||||
}
|
||||
}
|
||||
resetData(new_data) {
|
||||
let [new_rows, new_cols] = [new_data.length, new_data[0].length];
|
||||
let current_data = this.el.getData();
|
||||
let [cur_rows, cur_cols] = [current_data.length, current_data[0].length];
|
||||
if (cur_rows < new_rows) {
|
||||
this.el.insertRow(new_rows - cur_rows);
|
||||
} else if (cur_rows > new_rows) {
|
||||
this.el.deleteRow(0, cur_rows - new_rows);
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
if (prevState.data === undefined ||
|
||||
!array_compare(nextProps.value.data, prevState.data)) {
|
||||
let new_state = DataframeOutput.get_default_state();
|
||||
new_state["data"] = nextProps.value.data;
|
||||
return new_state;
|
||||
}
|
||||
if (cur_cols < new_cols) {
|
||||
this.el.insertColumn(new_cols - cur_cols);
|
||||
} else if (cur_cols > new_cols) {
|
||||
this.el.deleteColumn(0, cur_cols - new_cols);
|
||||
return null;
|
||||
}
|
||||
set_page(page) {
|
||||
this.setState({ "page": page });
|
||||
}
|
||||
sort_table(col_index) {
|
||||
if (this.state.sort_by === col_index) {
|
||||
this.setState({ "sort_descending": !this.state.sort_descending, "page": 0 });
|
||||
} else {
|
||||
this.setState({ "sort_by": col_index, "sort_descending": false, "page": 0 });
|
||||
}
|
||||
this.el.setData(new_data);
|
||||
}
|
||||
render() {
|
||||
if (this.props.value && this.props.value.headers && this.el) {
|
||||
for (let [i, header] of this.props.value.headers.entries())
|
||||
this.el.setHeader(i, header);
|
||||
if (this.props.value.data.length === 0) {
|
||||
return null;
|
||||
}
|
||||
if (this.props.value && this.el) {
|
||||
this.resetData(this.props.value.data)
|
||||
let headers = this.props.headers || this.props.value.headers;
|
||||
let row_count = this.props.value.data.length;
|
||||
let col_count = this.props.value.data[0].length;
|
||||
let selected_data = this.props.value.data.slice();
|
||||
if (this.state.sort_by !== null) {
|
||||
selected_data.sort((function (index) {
|
||||
return function (a, b) {
|
||||
return (a[index] === b[index] ? 0 : (a[index] < b[index] ? -1 : 1));
|
||||
};
|
||||
})(this.state.sort_by));
|
||||
if (this.state.sort_descending) {
|
||||
selected_data.reverse();
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className={classNames("output_dataframe", {"hidden": this.props.value === null})}>
|
||||
<div ref={this.wrapper}/>
|
||||
</div>)
|
||||
let visible_pages = null;
|
||||
if (this.props.max_rows !== null && row_count > this.props.max_rows) {
|
||||
if (this.props.overflow_row_behaviour === "paginate") {
|
||||
selected_data = selected_data.slice(
|
||||
this.state.page * this.props.max_rows,
|
||||
(this.state.page + 1) * this.props.max_rows
|
||||
);
|
||||
let page_count = Math.ceil(row_count / this.props.max_rows);
|
||||
visible_pages = [];
|
||||
[0, this.state.page, page_count - 1].forEach(anchor => {
|
||||
for (let i = anchor - 2; i <= anchor + 2; i++) {
|
||||
if (i >= 0 && i < page_count && !visible_pages.includes(i)) {
|
||||
if (visible_pages.length > 0 && i - visible_pages[visible_pages.length - 1] > 1) {
|
||||
visible_pages.push(null);
|
||||
}
|
||||
visible_pages.push(i);
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
selected_data = selected_data.slice(
|
||||
0,
|
||||
Math.ceil(this.props.max_rows / 2)
|
||||
).concat(
|
||||
[Array(col_count).fill("...")],
|
||||
selected_data.slice(
|
||||
row_count - Math.floor(this.props.max_rows / 2)
|
||||
));
|
||||
}
|
||||
}
|
||||
if (this.props.max_cols !== null && col_count > this.props.max_cols) {
|
||||
let [hidden_col_start, hidden_col_end] = [
|
||||
Math.ceil(this.props.max_cols / 2),
|
||||
col_count - Math.floor(this.props.max_cols / 2) - 1
|
||||
];
|
||||
headers = headers.slice(0, hidden_col_start) + ["..."] + headers.slice(hidden_col_end);
|
||||
selected_data = selected_data.map(row =>
|
||||
row.slice(0, hidden_col_start) + ["..."] + row.slice(hidden_col_end)
|
||||
);
|
||||
}
|
||||
return <div className="output_dataframe">
|
||||
<table>
|
||||
{headers ?
|
||||
<thead>
|
||||
{headers.map((header, i) =>
|
||||
<th key={i} onClick={this.sort_table.bind(this, i)}>
|
||||
{header}
|
||||
{this.state.sort_by === i ? (
|
||||
this.state.sort_descending ? "⇧" : "⇩")
|
||||
: false}
|
||||
</th>)}
|
||||
</thead>
|
||||
: false}
|
||||
<tbody>
|
||||
{selected_data.map(row => {
|
||||
return <tr>{row.map(cell => <td>{cell}</td>)}</tr>
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
{visible_pages !== null ?
|
||||
<div className="pages">
|
||||
Pages: {visible_pages.map(page => page === null ?
|
||||
<div>...</div> :
|
||||
<button
|
||||
className={classNames("page", { "selected": page === this.state.page })}
|
||||
key={page} onClick={this.set_page.bind(this, page)}>
|
||||
{page + 1}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
: false}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,4 +155,4 @@ class DataframeOutputExample extends ComponentExample {
|
||||
}
|
||||
}
|
||||
|
||||
export {DataframeOutput, DataframeOutputExample};
|
||||
export { DataframeOutput, DataframeOutputExample };
|
||||
|
@ -472,6 +472,31 @@
|
||||
@apply text-2xl p-2;
|
||||
}
|
||||
}
|
||||
.output_dataframe {
|
||||
table {
|
||||
thead {
|
||||
@apply font-bold border-gray-200 border-b-2;
|
||||
}
|
||||
th {
|
||||
@apply transition cursor-pointer;
|
||||
}
|
||||
th:hover {
|
||||
@apply bg-gray-200;
|
||||
}
|
||||
td, th {
|
||||
@apply px-4;
|
||||
}
|
||||
}
|
||||
.pages {
|
||||
@apply flex gap-1 items-center;
|
||||
}
|
||||
.page {
|
||||
@apply px-2 py-1 bg-gray-200;
|
||||
}
|
||||
.page.selected {
|
||||
@apply bg-gray-300;
|
||||
}
|
||||
}
|
||||
.output_carousel {
|
||||
@apply flex flex-col gap-2;
|
||||
.carousel_control {
|
||||
|
@ -88,4 +88,22 @@ export function saveAs(uri, filename) {
|
||||
} else {
|
||||
window.open(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function array_compare(a1, a2) {
|
||||
if (a1.length != a2.length) {
|
||||
return false;
|
||||
}
|
||||
for (var i in a1) {
|
||||
// Don't forget to check for arrays in our arrays.
|
||||
if (a1[i] instanceof Array && a2[i] instanceof Array) {
|
||||
if (!array_compare(a1[i], a2[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (a1[i] != a2[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
numpy
|
||||
scipy
|
||||
matplotlib
|
||||
pandas
|
||||
pillow
|
||||
Flask-Cors>=3.0.8
|
||||
Flask-Login
|
||||
Flask>=1.1.1
|
||||
analytics-python
|
||||
ffmpy
|
||||
flask-cachebuster
|
||||
markdown2
|
||||
matplotlib
|
||||
numpy
|
||||
pandas
|
||||
paramiko
|
||||
pillow
|
||||
pycryptodome
|
||||
requests
|
||||
paramiko
|
||||
analytics-python
|
||||
Flask>=1.1.1
|
||||
Flask-Cors>=3.0.8
|
||||
flask-cachebuster
|
||||
Flask-Login
|
||||
scipy
|
||||
|
@ -441,21 +441,30 @@ class Dataframe(OutputComponent):
|
||||
Output type: Union[pandas.DataFrame, numpy.array, List[Union[str, float]], List[List[Union[str, float]]]]
|
||||
"""
|
||||
|
||||
def __init__(self, headers=None, type="auto", label=None):
|
||||
def __init__(self, headers=None, max_rows=20, max_cols=None, overflow_row_behaviour="paginate", type="auto", label=None):
|
||||
'''
|
||||
Parameters:
|
||||
headers (List[str]): Header names to dataframe.
|
||||
headers (List[str]): Header names to dataframe. Only applicable if type is "numpy" or "array".
|
||||
max_rows (int): Maximum number of rows to display at once. Set to None for infinite.
|
||||
max_cols (int): Maximum number of columns to display at once. Set to None for infinite.
|
||||
overflow_row_behaviour (str): If set to "paginate", will create pages for overflow rows. If set to "show_ends", will show initial and final rows and truncate middle rows.
|
||||
type (str): Type of value to be passed to component. "pandas" for pandas dataframe, "numpy" for numpy array, or "array" for Python array, "auto" detects return type.
|
||||
label (str): component name in interface.
|
||||
'''
|
||||
self.type = type
|
||||
self.headers = headers
|
||||
self.max_rows = max_rows
|
||||
self.max_cols = max_cols
|
||||
self.overflow_row_behaviour = overflow_row_behaviour
|
||||
self.type = type
|
||||
super().__init__(label)
|
||||
|
||||
|
||||
def get_template_context(self):
|
||||
return {
|
||||
"headers": self.headers,
|
||||
"max_rows": self.max_rows,
|
||||
"max_cols": self.max_cols,
|
||||
"overflow_row_behaviour": self.overflow_row_behaviour,
|
||||
**super().get_template_context()
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user