mirror of
https://github.com/gradio-app/gradio.git
synced 2025-03-07 11:46:51 +08:00
fix dataframe support
This commit is contained in:
parent
06a5e20d9e
commit
59d1df64f3
@ -1,8 +1,6 @@
|
|||||||
# Demo: (Dataframe, Dropdown) -> (Dataframe)
|
# Demo: (Dataframe, Dropdown) -> (Dataframe)
|
||||||
|
|
||||||
import gradio as gr
|
import gradio as gr
|
||||||
import numpy as np
|
|
||||||
import random
|
|
||||||
|
|
||||||
def filter_records(records, gender):
|
def filter_records(records, gender):
|
||||||
return records[records['gender'] == gender]
|
return records[records['gender'] == gender]
|
||||||
|
@ -1,45 +1,129 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ComponentExample from '../component_example';
|
import ComponentExample from '../component_example';
|
||||||
import jspreadsheet from "jspreadsheet-ce";
|
|
||||||
import "../../../node_modules/jspreadsheet-ce/dist/jspreadsheet.css"
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { array_compare } from "../../utils";
|
||||||
|
|
||||||
class DataframeOutput extends React.Component {
|
class DataframeOutput extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.wrapper = React.createRef();
|
this.state = this.constructor.get_default_state()
|
||||||
}
|
}
|
||||||
componentDidMount = function() {
|
static get_default_state() {
|
||||||
this.el = jspreadsheet(this.wrapper.current, {minDimensions: [1, 1]});
|
return {
|
||||||
|
"page": 0,
|
||||||
|
"sort_by": null,
|
||||||
|
"sort_descending": false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
resetData(new_data) {
|
static getDerivedStateFromProps(nextProps, prevState) {
|
||||||
let [new_rows, new_cols] = [new_data.length, new_data[0].length];
|
if (prevState.data === undefined ||
|
||||||
let current_data = this.el.getData();
|
!array_compare(nextProps.value.data, prevState.data)) {
|
||||||
let [cur_rows, cur_cols] = [current_data.length, current_data[0].length];
|
let new_state = DataframeOutput.get_default_state();
|
||||||
if (cur_rows < new_rows) {
|
new_state["data"] = nextProps.value.data;
|
||||||
this.el.insertRow(new_rows - cur_rows);
|
return new_state;
|
||||||
} else if (cur_rows > new_rows) {
|
|
||||||
this.el.deleteRow(0, cur_rows - new_rows);
|
|
||||||
}
|
}
|
||||||
if (cur_cols < new_cols) {
|
return null;
|
||||||
this.el.insertColumn(new_cols - cur_cols);
|
}
|
||||||
} else if (cur_cols > new_cols) {
|
set_page(page) {
|
||||||
this.el.deleteColumn(0, cur_cols - new_cols);
|
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() {
|
render() {
|
||||||
if (this.props.value && this.props.value.headers && this.el) {
|
if (this.props.value.data.length === 0) {
|
||||||
for (let [i, header] of this.props.value.headers.entries())
|
return null;
|
||||||
this.el.setHeader(i, header);
|
|
||||||
}
|
}
|
||||||
if (this.props.value && this.el) {
|
let headers = this.props.headers || this.props.value.headers;
|
||||||
this.resetData(this.props.value.data)
|
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 (
|
let visible_pages = null;
|
||||||
<div className={classNames("output_dataframe", {"hidden": this.props.value === null})}>
|
if (this.props.max_rows !== null && row_count > this.props.max_rows) {
|
||||||
<div ref={this.wrapper}/>
|
if (this.props.overflow_row_behaviour === "paginate") {
|
||||||
</div>)
|
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;
|
@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 {
|
.output_carousel {
|
||||||
@apply flex flex-col gap-2;
|
@apply flex flex-col gap-2;
|
||||||
.carousel_control {
|
.carousel_control {
|
||||||
|
@ -88,4 +88,22 @@ export function saveAs(uri, filename) {
|
|||||||
} else {
|
} else {
|
||||||
window.open(uri);
|
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
|
Flask-Cors>=3.0.8
|
||||||
scipy
|
Flask-Login
|
||||||
matplotlib
|
Flask>=1.1.1
|
||||||
pandas
|
analytics-python
|
||||||
pillow
|
|
||||||
ffmpy
|
ffmpy
|
||||||
|
flask-cachebuster
|
||||||
markdown2
|
markdown2
|
||||||
|
matplotlib
|
||||||
|
numpy
|
||||||
|
pandas
|
||||||
|
paramiko
|
||||||
|
pillow
|
||||||
pycryptodome
|
pycryptodome
|
||||||
requests
|
requests
|
||||||
paramiko
|
scipy
|
||||||
analytics-python
|
|
||||||
Flask>=1.1.1
|
|
||||||
Flask-Cors>=3.0.8
|
|
||||||
flask-cachebuster
|
|
||||||
Flask-Login
|
|
||||||
|
@ -441,21 +441,30 @@ class Dataframe(OutputComponent):
|
|||||||
Output type: Union[pandas.DataFrame, numpy.array, List[Union[str, float]], List[List[Union[str, float]]]]
|
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:
|
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.
|
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.
|
label (str): component name in interface.
|
||||||
'''
|
'''
|
||||||
self.type = type
|
|
||||||
self.headers = headers
|
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)
|
super().__init__(label)
|
||||||
|
|
||||||
|
|
||||||
def get_template_context(self):
|
def get_template_context(self):
|
||||||
return {
|
return {
|
||||||
"headers": self.headers,
|
"headers": self.headers,
|
||||||
|
"max_rows": self.max_rows,
|
||||||
|
"max_cols": self.max_cols,
|
||||||
|
"overflow_row_behaviour": self.overflow_row_behaviour,
|
||||||
**super().get_template_context()
|
**super().get_template_context()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user