fix dataframe support

This commit is contained in:
Ali Abid 2021-07-15 17:48:52 -07:00
parent 06a5e20d9e
commit 59d1df64f3
6 changed files with 179 additions and 45 deletions

View File

@ -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]

View File

@ -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 };

View File

@ -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 {

View File

@ -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;
}

View File

@ -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

View File

@ -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()
}