mirror of
https://github.com/gradio-app/gradio.git
synced 2025-01-30 11:00:11 +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}")
|
raise ValueError(f"Cannot process value as a Dataframe: {y}")
|
||||||
return asdict(value)
|
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
|
@staticmethod
|
||||||
def __extract_metadata(df: Styler) -> dict[str, list[list]]:
|
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
|
style_data = df._compute()._translate(None, None) # type: ignore
|
||||||
|
cell_styles = style_data.get("cellstyle", [])
|
||||||
for i in range(len(style_data["body"])):
|
for i in range(len(style_data["body"])):
|
||||||
metadata["display_value"].append([])
|
metadata["display_value"].append([])
|
||||||
|
metadata["styling"].append([])
|
||||||
for j in range(len(style_data["body"][i])):
|
for j in range(len(style_data["body"][i])):
|
||||||
cell_type = style_data["body"][i][j]["type"]
|
cell_type = style_data["body"][i][j]["type"]
|
||||||
if cell_type != "td":
|
if cell_type != "td":
|
||||||
continue
|
continue
|
||||||
display_value = style_data["body"][i][j]["display_value"]
|
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["display_value"][i].append(display_value)
|
||||||
|
metadata["styling"][i].append(styles_str)
|
||||||
return metadata
|
return metadata
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -23,12 +23,15 @@
|
|||||||
<Story
|
<Story
|
||||||
name="Interactive dataframe"
|
name="Interactive dataframe"
|
||||||
args={{
|
args={{
|
||||||
values: [
|
value: {
|
||||||
|
data: [
|
||||||
["Cat", 5],
|
["Cat", 5],
|
||||||
["Horse", 3],
|
["Horse", 3],
|
||||||
["Snake", 1]
|
["Snake", 1]
|
||||||
],
|
],
|
||||||
headers: ["Animal", "Votes"],
|
headers: ["Animal", "Votes"],
|
||||||
|
metadata: null
|
||||||
|
},
|
||||||
label: "Animals",
|
label: "Animals",
|
||||||
col_count: [2, "dynamic"],
|
col_count: [2, "dynamic"],
|
||||||
row_count: [3, "dynamic"]
|
row_count: [3, "dynamic"]
|
||||||
@ -38,12 +41,15 @@
|
|||||||
<Story
|
<Story
|
||||||
name="Static dataframe"
|
name="Static dataframe"
|
||||||
args={{
|
args={{
|
||||||
values: [
|
value: {
|
||||||
|
data: [
|
||||||
["Cat", 5],
|
["Cat", 5],
|
||||||
["Horse", 3],
|
["Horse", 3],
|
||||||
["Snake", 1]
|
["Snake", 1]
|
||||||
],
|
],
|
||||||
headers: ["Animal", "Votes"],
|
headers: ["Animal", "Votes"],
|
||||||
|
metadata: null
|
||||||
|
},
|
||||||
label: "Animals",
|
label: "Animals",
|
||||||
col_count: [2, "dynamic"],
|
col_count: [2, "dynamic"],
|
||||||
row_count: [3, "dynamic"],
|
row_count: [3, "dynamic"],
|
||||||
@ -54,11 +60,12 @@
|
|||||||
<Story
|
<Story
|
||||||
name="Dataframe with different precisions"
|
name="Dataframe with different precisions"
|
||||||
args={{
|
args={{
|
||||||
values: {
|
value: {
|
||||||
data: [
|
data: [
|
||||||
[1.24, 1.24, 1.24],
|
[1.24, 1.24, 1.24],
|
||||||
[1.21, 1.21, 1.21]
|
[1.21, 1.21, 1.21]
|
||||||
],
|
],
|
||||||
|
headers: ["Precision=0", "Precision=1", "Precision=2"],
|
||||||
metadata: {
|
metadata: {
|
||||||
display_value: [
|
display_value: [
|
||||||
["1", "1.2", "1.24"],
|
["1", "1.2", "1.24"],
|
||||||
@ -66,8 +73,7 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
headers: ["Precision=0", "Precision=1", "Precision=2"],
|
label: "Numbers",
|
||||||
label: "Animals",
|
|
||||||
col_count: [3, "dynamic"],
|
col_count: [3, "dynamic"],
|
||||||
row_count: [2, "dynamic"],
|
row_count: [2, "dynamic"],
|
||||||
editable: false
|
editable: false
|
||||||
@ -77,12 +83,15 @@
|
|||||||
<Story
|
<Story
|
||||||
name="Dataframe with markdown and math"
|
name="Dataframe with markdown and math"
|
||||||
args={{
|
args={{
|
||||||
values: [
|
value: {
|
||||||
|
data: [
|
||||||
["Linear", "$y=x$", "Has a *maximum* of 1 root"],
|
["Linear", "$y=x$", "Has a *maximum* of 1 root"],
|
||||||
["Quadratic", "$y=x^2$", "Has a *maximum* of 2 roots"],
|
["Quadratic", "$y=x^2$", "Has a *maximum* of 2 roots"],
|
||||||
["Cubic", "$y=x^3$", "Has a *maximum* of 3 roots"]
|
["Cubic", "$y=x^3$", "Has a *maximum* of 3 roots"]
|
||||||
],
|
],
|
||||||
headers: ["Type", "Example", "Roots"],
|
headers: ["Type", "Example", "Roots"],
|
||||||
|
metadata: null
|
||||||
|
},
|
||||||
datatype: ["str", "markdown", "markdown"],
|
datatype: ["str", "markdown", "markdown"],
|
||||||
latex_delimiters: [{ left: "$", right: "$", display: false }],
|
latex_delimiters: [{ left: "$", right: "$", display: false }],
|
||||||
label: "Math",
|
label: "Math",
|
||||||
@ -93,12 +102,28 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Story
|
<Story
|
||||||
name="Empty dataframe"
|
name="Dataframe with different colors"
|
||||||
args={{
|
args={{
|
||||||
values: [[]],
|
value: {
|
||||||
headers: ["Animal", "Votes"],
|
data: [
|
||||||
label: "Animals",
|
[800, 100, 800],
|
||||||
col_count: [2, "dynamic"],
|
[200, 800, 700]
|
||||||
row_count: [0, "dynamic"]
|
],
|
||||||
|
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 edit: boolean;
|
||||||
export let value: string | number = "";
|
export let value: string | number = "";
|
||||||
export let display_value: string | null = null;
|
export let display_value: string | null = null;
|
||||||
|
export let styling = "";
|
||||||
export let header = false;
|
export let header = false;
|
||||||
export let datatype:
|
export let datatype:
|
||||||
| "str"
|
| "str"
|
||||||
@ -64,6 +65,7 @@
|
|||||||
role="button"
|
role="button"
|
||||||
class:edit
|
class:edit
|
||||||
on:focus|preventDefault
|
on:focus|preventDefault
|
||||||
|
style={styling}
|
||||||
>
|
>
|
||||||
{#if datatype === "html"}
|
{#if datatype === "html"}
|
||||||
{@html value}
|
{@html value}
|
||||||
|
@ -36,12 +36,14 @@
|
|||||||
export let line_breaks = true;
|
export let line_breaks = true;
|
||||||
let selected: false | [number, number] = false;
|
let selected: false | [number, number] = false;
|
||||||
let display_value: string[][] | null = value?.metadata?.display_value ?? null;
|
let display_value: string[][] | null = value?.metadata?.display_value ?? null;
|
||||||
|
let styling: string[][] | null = value?.metadata?.styling ?? null;
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (value) {
|
if (value) {
|
||||||
headers = value.headers;
|
headers = value.headers;
|
||||||
values = value.data;
|
values = value.data;
|
||||||
display_value = value?.metadata?.display_value ?? null;
|
display_value = value?.metadata?.display_value ?? null;
|
||||||
|
styling = value?.metadata?.styling ?? null;
|
||||||
} else if (values === null) {
|
} else if (values === null) {
|
||||||
values = [];
|
values = [];
|
||||||
}
|
}
|
||||||
@ -162,7 +164,9 @@
|
|||||||
dispatch("change", {
|
dispatch("change", {
|
||||||
data: data.map((r) => r.map(({ value }) => value)),
|
data: data.map((r) => r.map(({ value }) => value)),
|
||||||
headers: _headers.map((h) => h.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(
|
function get_sort_status(
|
||||||
@ -556,13 +560,14 @@
|
|||||||
function sort_data(
|
function sort_data(
|
||||||
_data: typeof data,
|
_data: typeof data,
|
||||||
_display_value: string[][] | null,
|
_display_value: string[][] | null,
|
||||||
|
_styling: string[][] | null,
|
||||||
col?: number,
|
col?: number,
|
||||||
dir?: SortDirection
|
dir?: SortDirection
|
||||||
): void {
|
): void {
|
||||||
let id = null
|
let id = null;
|
||||||
//Checks if the selected cell is still in the data
|
//Checks if the selected cell is still in the data
|
||||||
if (selected && selected[0] in data && selected[1] in data[selected[0]]) {
|
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) {
|
if (typeof col !== "number" || !dir) {
|
||||||
return;
|
return;
|
||||||
@ -581,13 +586,16 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort both data and display_value in place based on the values in data
|
// sort all the data and metadata based on the values in the data
|
||||||
const tempData = [..._data];
|
const temp_data = [..._data];
|
||||||
const tempData2 = _display_value ? [..._display_value] : null;
|
const temp_display_value = _display_value ? [..._display_value] : null;
|
||||||
|
const temp_styling = _styling ? [..._styling] : null;
|
||||||
indices.forEach((originalIndex, sortedIndex) => {
|
indices.forEach((originalIndex, sortedIndex) => {
|
||||||
_data[sortedIndex] = tempData[originalIndex];
|
_data[sortedIndex] = temp_data[originalIndex];
|
||||||
if (_display_value && tempData2)
|
if (_display_value && temp_display_value)
|
||||||
_display_value[sortedIndex] = tempData2[originalIndex];
|
_display_value[sortedIndex] = temp_display_value[originalIndex];
|
||||||
|
if (_styling && temp_styling)
|
||||||
|
_styling[sortedIndex] = temp_styling[originalIndex];
|
||||||
});
|
});
|
||||||
|
|
||||||
data = data;
|
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];
|
$: selected_index = !!selected && selected[0];
|
||||||
|
|
||||||
@ -773,7 +781,8 @@
|
|||||||
on:touchstart={() => start_edit(index, j)}
|
on:touchstart={() => start_edit(index, j)}
|
||||||
on:click={() => handle_cell_click(index, j)}
|
on:click={() => handle_cell_click(index, j)}
|
||||||
on:dblclick={() => start_edit(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])}
|
class:focus={dequal(selected, [index, j])}
|
||||||
>
|
>
|
||||||
<div class="cell-wrap">
|
<div class="cell-wrap">
|
||||||
|
@ -1417,7 +1417,68 @@ class TestDataframe:
|
|||||||
["Mike", "1,1", "800"],
|
["Mike", "1,1", "800"],
|
||||||
["Adam", "1,1", "800"],
|
["Adam", "1,1", "800"],
|
||||||
["Mike", "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