mirror of
https://github.com/gradio-app/gradio.git
synced 2025-01-18 10:44:33 +08:00
Add styling (e.g. font colors and background colors) support to gr.DataFrame
through the pd.Styler
object (#5877)
* colors * add changeset * revert * revert * colors * move box shadow * done * lint * lint * fixes * lint * added focused false * revert focus * revert focus * revert focus * revert focus * revert focus * apply style to td instead * dataframe * lint * test * Update js/dataframe/shared/Table.svelte Co-authored-by: aliabid94 <aabid94@gmail.com> * notebooks --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: aliabid94 <aabid94@gmail.com>
This commit is contained in:
parent
e70805d54c
commit
a55b809423
6
.changeset/fluffy-goats-dig.md
Normal file
6
.changeset/fluffy-goats-dig.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
"@gradio/dataframe": minor
|
||||
"gradio": minor
|
||||
---
|
||||
|
||||
feat:Add styling (e.g. font colors and background colors) support to `gr.DataFrame` through the `pd.Styler` object
|
File diff suppressed because one or more lines are too long
1
demo/dataframe_colorful/run.ipynb
Normal file
1
demo/dataframe_colorful/run.ipynb
Normal file
@ -0,0 +1 @@
|
||||
{"cells": [{"cell_type": "markdown", "id": 302934307671667531413257853548643485645, "metadata": {}, "source": ["# Gradio Demo: dataframe_colorful"]}, {"cell_type": "code", "execution_count": null, "id": 272996653310673477252411125948039410165, "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": 288918539441861185822528903084949547379, "metadata": {}, "outputs": [], "source": ["import pandas as pd \n", "import gradio as gr\n", "\n", "df = pd.DataFrame({\"A\" : [14, 4, 5, 4, 1], \n", "\t\t\t\t\"B\" : [5, 2, 54, 3, 2], \n", "\t\t\t\t\"C\" : [20, 20, 7, 3, 8], \n", "\t\t\t\t\"D\" : [14, 3, 6, 2, 6], \n", "\t\t\t\t\"E\" : [23, 45, 64, 32, 23]}) \n", "\n", "t = df.style.highlight_max(color = 'lightgreen', axis = 0)\n", "\n", "with gr.Blocks() as demo:\n", " gr.Dataframe(t)\n", " \n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
|
16
demo/dataframe_colorful/run.py
Normal file
16
demo/dataframe_colorful/run.py
Normal file
@ -0,0 +1,16 @@
|
||||
import pandas as pd
|
||||
import gradio as gr
|
||||
|
||||
df = pd.DataFrame({"A" : [14, 4, 5, 4, 1],
|
||||
"B" : [5, 2, 54, 3, 2],
|
||||
"C" : [20, 20, 7, 3, 8],
|
||||
"D" : [14, 3, 6, 2, 6],
|
||||
"E" : [23, 45, 64, 32, 23]})
|
||||
|
||||
t = df.style.highlight_max(color = 'lightgreen', axis = 0)
|
||||
|
||||
with gr.Blocks() as demo:
|
||||
gr.Dataframe(t)
|
||||
|
||||
if __name__ == "__main__":
|
||||
demo.launch()
|
@ -300,18 +300,32 @@ class Dataframe(Changeable, Inputable, Selectable, IOComponent, JSONSerializable
|
||||
raise ValueError(f"Cannot process value as a Dataframe: {y}")
|
||||
return asdict(value)
|
||||
|
||||
@staticmethod
|
||||
def __get_cell_style(cell_id: str, cell_styles: list[dict]) -> str:
|
||||
styles_for_cell = []
|
||||
for style in cell_styles:
|
||||
if cell_id in style.get("selectors", []):
|
||||
styles_for_cell.extend(style.get("props", []))
|
||||
styles_str = "; ".join([f"{prop}: {value}" for prop, value in styles_for_cell])
|
||||
return styles_str
|
||||
|
||||
@staticmethod
|
||||
def __extract_metadata(df: Styler) -> dict[str, list[list]]:
|
||||
metadata = {"display_value": []}
|
||||
metadata = {"display_value": [], "styling": []}
|
||||
style_data = df._compute()._translate(None, None) # type: ignore
|
||||
cell_styles = style_data.get("cellstyle", [])
|
||||
for i in range(len(style_data["body"])):
|
||||
metadata["display_value"].append([])
|
||||
metadata["styling"].append([])
|
||||
for j in range(len(style_data["body"][i])):
|
||||
cell_type = style_data["body"][i][j]["type"]
|
||||
if cell_type != "td":
|
||||
continue
|
||||
display_value = style_data["body"][i][j]["display_value"]
|
||||
cell_id = style_data["body"][i][j]["id"]
|
||||
styles_str = Dataframe.__get_cell_style(cell_id, cell_styles)
|
||||
metadata["display_value"][i].append(display_value)
|
||||
metadata["styling"][i].append(styles_str)
|
||||
return metadata
|
||||
|
||||
@staticmethod
|
||||
|
@ -23,12 +23,15 @@
|
||||
<Story
|
||||
name="Interactive dataframe"
|
||||
args={{
|
||||
values: [
|
||||
value: {
|
||||
data: [
|
||||
["Cat", 5],
|
||||
["Horse", 3],
|
||||
["Snake", 1]
|
||||
],
|
||||
headers: ["Animal", "Votes"],
|
||||
metadata: null
|
||||
},
|
||||
label: "Animals",
|
||||
col_count: [2, "dynamic"],
|
||||
row_count: [3, "dynamic"]
|
||||
@ -38,12 +41,15 @@
|
||||
<Story
|
||||
name="Static dataframe"
|
||||
args={{
|
||||
values: [
|
||||
value: {
|
||||
data: [
|
||||
["Cat", 5],
|
||||
["Horse", 3],
|
||||
["Snake", 1]
|
||||
],
|
||||
headers: ["Animal", "Votes"],
|
||||
metadata: null
|
||||
},
|
||||
label: "Animals",
|
||||
col_count: [2, "dynamic"],
|
||||
row_count: [3, "dynamic"],
|
||||
@ -54,11 +60,12 @@
|
||||
<Story
|
||||
name="Dataframe with different precisions"
|
||||
args={{
|
||||
values: {
|
||||
value: {
|
||||
data: [
|
||||
[1.24, 1.24, 1.24],
|
||||
[1.21, 1.21, 1.21]
|
||||
],
|
||||
headers: ["Precision=0", "Precision=1", "Precision=2"],
|
||||
metadata: {
|
||||
display_value: [
|
||||
["1", "1.2", "1.24"],
|
||||
@ -66,8 +73,7 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
headers: ["Precision=0", "Precision=1", "Precision=2"],
|
||||
label: "Animals",
|
||||
label: "Numbers",
|
||||
col_count: [3, "dynamic"],
|
||||
row_count: [2, "dynamic"],
|
||||
editable: false
|
||||
@ -77,12 +83,15 @@
|
||||
<Story
|
||||
name="Dataframe with markdown and math"
|
||||
args={{
|
||||
values: [
|
||||
value: {
|
||||
data: [
|
||||
["Linear", "$y=x$", "Has a *maximum* of 1 root"],
|
||||
["Quadratic", "$y=x^2$", "Has a *maximum* of 2 roots"],
|
||||
["Cubic", "$y=x^3$", "Has a *maximum* of 3 roots"]
|
||||
],
|
||||
headers: ["Type", "Example", "Roots"],
|
||||
metadata: null
|
||||
},
|
||||
datatype: ["str", "markdown", "markdown"],
|
||||
latex_delimiters: [{ left: "$", right: "$", display: false }],
|
||||
label: "Math",
|
||||
@ -93,12 +102,28 @@
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Empty dataframe"
|
||||
name="Dataframe with different colors"
|
||||
args={{
|
||||
values: [[]],
|
||||
headers: ["Animal", "Votes"],
|
||||
label: "Animals",
|
||||
col_count: [2, "dynamic"],
|
||||
row_count: [0, "dynamic"]
|
||||
value: {
|
||||
data: [
|
||||
[800, 100, 800],
|
||||
[200, 800, 700]
|
||||
],
|
||||
headers: ["Math", "Reading", "Writing"],
|
||||
metadata: {
|
||||
styling: [
|
||||
[
|
||||
"background-color:teal; color: white",
|
||||
"1.2",
|
||||
"background-color:teal; color: white"
|
||||
],
|
||||
["1", "background-color:teal; color: white", "1.21"]
|
||||
]
|
||||
}
|
||||
},
|
||||
label: "Test scores",
|
||||
col_count: [3, "dynamic"],
|
||||
row_count: [2, "dynamic"],
|
||||
editable: false
|
||||
}}
|
||||
/>
|
||||
|
@ -6,6 +6,7 @@
|
||||
export let edit: boolean;
|
||||
export let value: string | number = "";
|
||||
export let display_value: string | null = null;
|
||||
export let styling = "";
|
||||
export let header = false;
|
||||
export let datatype:
|
||||
| "str"
|
||||
@ -64,6 +65,7 @@
|
||||
role="button"
|
||||
class:edit
|
||||
on:focus|preventDefault
|
||||
style={styling}
|
||||
>
|
||||
{#if datatype === "html"}
|
||||
{@html value}
|
||||
|
@ -36,12 +36,14 @@
|
||||
export let line_breaks = true;
|
||||
let selected: false | [number, number] = false;
|
||||
let display_value: string[][] | null = value?.metadata?.display_value ?? null;
|
||||
let styling: string[][] | null = value?.metadata?.styling ?? null;
|
||||
|
||||
$: {
|
||||
if (value) {
|
||||
headers = value.headers;
|
||||
values = value.data;
|
||||
display_value = value?.metadata?.display_value ?? null;
|
||||
styling = value?.metadata?.styling ?? null;
|
||||
} else if (values === null) {
|
||||
values = [];
|
||||
}
|
||||
@ -162,7 +164,9 @@
|
||||
dispatch("change", {
|
||||
data: data.map((r) => r.map(({ value }) => value)),
|
||||
headers: _headers.map((h) => h.value),
|
||||
metadata: editable ? null : { display_value: display_value }
|
||||
metadata: editable
|
||||
? null
|
||||
: { display_value: display_value, styling: styling }
|
||||
});
|
||||
|
||||
function get_sort_status(
|
||||
@ -556,13 +560,14 @@
|
||||
function sort_data(
|
||||
_data: typeof data,
|
||||
_display_value: string[][] | null,
|
||||
_styling: string[][] | null,
|
||||
col?: number,
|
||||
dir?: SortDirection
|
||||
): void {
|
||||
let id = null
|
||||
let id = null;
|
||||
//Checks if the selected cell is still in the data
|
||||
if (selected && selected[0] in data && selected[1] in data[selected[0]]) {
|
||||
id = data[selected[0]][selected[1]].id
|
||||
id = data[selected[0]][selected[1]].id;
|
||||
}
|
||||
if (typeof col !== "number" || !dir) {
|
||||
return;
|
||||
@ -581,13 +586,16 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// sort both data and display_value in place based on the values in data
|
||||
const tempData = [..._data];
|
||||
const tempData2 = _display_value ? [..._display_value] : null;
|
||||
// sort all the data and metadata based on the values in the data
|
||||
const temp_data = [..._data];
|
||||
const temp_display_value = _display_value ? [..._display_value] : null;
|
||||
const temp_styling = _styling ? [..._styling] : null;
|
||||
indices.forEach((originalIndex, sortedIndex) => {
|
||||
_data[sortedIndex] = tempData[originalIndex];
|
||||
if (_display_value && tempData2)
|
||||
_display_value[sortedIndex] = tempData2[originalIndex];
|
||||
_data[sortedIndex] = temp_data[originalIndex];
|
||||
if (_display_value && temp_display_value)
|
||||
_display_value[sortedIndex] = temp_display_value[originalIndex];
|
||||
if (_styling && temp_styling)
|
||||
_styling[sortedIndex] = temp_styling[originalIndex];
|
||||
});
|
||||
|
||||
data = data;
|
||||
@ -598,7 +606,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: sort_data(data, display_value, sort_by, sort_direction);
|
||||
$: sort_data(data, display_value, styling, sort_by, sort_direction);
|
||||
|
||||
$: selected_index = !!selected && selected[0];
|
||||
|
||||
@ -773,7 +781,8 @@
|
||||
on:touchstart={() => start_edit(index, j)}
|
||||
on:click={() => handle_cell_click(index, j)}
|
||||
on:dblclick={() => start_edit(index, j)}
|
||||
style="width: var(--cell-width-{j});"
|
||||
style:width="var(--cell-width-{j})"
|
||||
style={styling?.[index]?.[j] || ""}
|
||||
class:focus={dequal(selected, [index, j])}
|
||||
>
|
||||
<div class="cell-wrap">
|
||||
|
@ -1417,7 +1417,68 @@ class TestDataframe:
|
||||
["Mike", "1,1", "800"],
|
||||
["Adam", "1,1", "800"],
|
||||
["Mike", "1,1", "800"],
|
||||
]
|
||||
],
|
||||
"styling": [
|
||||
["", "", ""],
|
||||
["", "", ""],
|
||||
["", "", ""],
|
||||
["", "", ""],
|
||||
["", "", ""],
|
||||
["", "", ""],
|
||||
["", "", ""],
|
||||
["", "", ""],
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
df = pd.DataFrame(
|
||||
{
|
||||
"A": [14, 4, 5, 4, 1],
|
||||
"B": [5, 2, 54, 3, 2],
|
||||
"C": [20, 20, 7, 3, 8],
|
||||
"D": [14, 3, 6, 2, 6],
|
||||
"E": [23, 45, 64, 32, 23],
|
||||
}
|
||||
)
|
||||
|
||||
t = df.style.highlight_max(color="lightgreen", axis=0)
|
||||
output = component.postprocess(t)
|
||||
assert output == {
|
||||
"data": [
|
||||
[14, 5, 20, 14, 23],
|
||||
[4, 2, 20, 3, 45],
|
||||
[5, 54, 7, 6, 64],
|
||||
[4, 3, 3, 2, 32],
|
||||
[1, 2, 8, 6, 23],
|
||||
],
|
||||
"headers": ["A", "B", "C", "D", "E"],
|
||||
"metadata": {
|
||||
"display_value": [
|
||||
["14", "5", "20", "14", "23"],
|
||||
["4", "2", "20", "3", "45"],
|
||||
["5", "54", "7", "6", "64"],
|
||||
["4", "3", "3", "2", "32"],
|
||||
["1", "2", "8", "6", "23"],
|
||||
],
|
||||
"styling": [
|
||||
[
|
||||
"background-color: lightgreen",
|
||||
"",
|
||||
"background-color: lightgreen",
|
||||
"background-color: lightgreen",
|
||||
"",
|
||||
],
|
||||
["", "", "background-color: lightgreen", "", ""],
|
||||
[
|
||||
"",
|
||||
"background-color: lightgreen",
|
||||
"",
|
||||
"",
|
||||
"background-color: lightgreen",
|
||||
],
|
||||
["", "", "", "", ""],
|
||||
["", "", "", "", ""],
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user