add markdown and html support to dataframe (#1684)

* add markdown and html support to dataframe

* fix bug, address review comments

* fix test

* fix test

* rename parser instance variable
This commit is contained in:
pngwn 2022-07-04 10:27:48 +01:00 committed by GitHub
parent 00a1894bf5
commit 609de11ce8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 88 additions and 17 deletions

View File

@ -1,5 +1,26 @@
import gradio as gr
def make_markdown():
return [
[
"# hello again",
"Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.",
'<img src="https://images.unsplash.com/photo-1574613362884-f79513a5128c?fit=crop&w=500&q=80"/>',
],
[
"## hello again again",
"Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.",
'<img src="https://images.unsplash.com/photo-1574613362884-f79513a5128c?fit=crop&w=500&q=80"/>',
],
[
"### hello thrice",
"Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.",
'<img src="https://images.unsplash.com/photo-1574613362884-f79513a5128c?fit=crop&w=500&q=80"/>',
],
]
with gr.Blocks() as demo:
with gr.Column():
txt = gr.Textbox(label="Small Textbox", lines=1, show_label=False)
@ -43,27 +64,31 @@ with gr.Blocks() as demo:
gr.Dataframe(
interactive=True, headers=["One", "Two", "Three", "Four"], col_count=4
)
gr.DataFrame(
df = gr.DataFrame(
[
[
"# hello",
"Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.",
"Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.",
"Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.",
'<img src="https://images.unsplash.com/photo-1574613362884-f79513a5128c?fit=crop&w=500&q=80"/>',
],
[
"## hello",
"Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.",
"Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.",
"Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.",
'<img src="https://images.unsplash.com/photo-1574613362884-f79513a5128c?fit=crop&w=500&q=80"/>',
],
[
"### hello",
"Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.",
"Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.",
"Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.",
'<img src="https://images.unsplash.com/photo-1574613362884-f79513a5128c?fit=crop&w=500&q=80"/>',
],
],
headers=["One", "Two", "Three"],
wrap=True,
datatype=["markdown", "markdown", "html"],
interactive=True,
)
btn = gr.Button("Run")
btn.click(fn=make_markdown, inputs=None, outputs=df)
if __name__ == "__main__":

View File

@ -2354,6 +2354,8 @@ class Dataframe(Changeable, IOComponent):
Demos: filter_records, matrix_transpose, tax_calculator
"""
markdown_parser = None
def __init__(
self,
value: Optional[List[List[Any]]] = None,
@ -2403,13 +2405,17 @@ class Dataframe(Changeable, IOComponent):
self.__validate_headers(headers, self.col_count[0])
self.headers = headers
self.datatype = datatype
self.datatype = (
datatype if isinstance(datatype, list) else [datatype] * self.col_count[0]
)
self.type = type
values = {
"str": "",
"number": 0,
"bool": False,
"date": "01/01/1970",
"markdown": "",
"html": "",
}
column_dtypes = (
[datatype] * self.col_count[0] if isinstance(datatype, str) else datatype
@ -2417,7 +2423,10 @@ class Dataframe(Changeable, IOComponent):
self.test_input = [
[values[c] for c in column_dtypes] for _ in range(self.row_count[0])
]
self.value = value if value is not None else self.test_input
self.value = self.__process_markdown(self.value, datatype)
self.max_rows = max_rows
self.max_cols = max_cols
self.overflow_row_behaviour = overflow_row_behaviour
@ -2518,16 +2527,24 @@ class Dataframe(Changeable, IOComponent):
if y is None:
return y
if isinstance(y, str):
y = pd.read_csv(str)
return {"headers": list(y.columns), "data": y.values.tolist()}
y = pd.read_csv(y)
return {
"headers": list(y.columns),
"data": Dataframe.__process_markdown(y.values.tolist(), self.datatype),
}
if isinstance(y, pd.DataFrame):
return {"headers": list(y.columns), "data": y.values.tolist()}
return {
"headers": list(y.columns),
"data": Dataframe.__process_markdown(y.values.tolist(), self.datatype),
}
if isinstance(y, (np.ndarray, list)):
if isinstance(y, np.ndarray):
y = y.tolist()
if len(y) == 0 or not isinstance(y[0], list):
y = [y]
return {"data": y}
return {
"data": Dataframe.__process_markdown(y, self.datatype),
}
raise ValueError("Cannot process value as a Dataframe")
@staticmethod
@ -2548,10 +2565,24 @@ class Dataframe(Changeable, IOComponent):
)
)
@classmethod
def __process_markdown(cls, data: List[List[Any]], datatype: List[str]):
if "markdown" not in datatype:
return data
if cls.markdown_parser is None:
cls.markdown_parser = MarkdownIt()
for i in range(len(data)):
for j in range(len(data[i])):
if datatype[j] == "markdown":
data[i][j] = Dataframe.markdown_parser.render(data[i][j])
return data
def style(
self,
rounded: Optional[bool | Tuple[bool, bool, bool, bool]] = None,
border: Optional[bool | Tuple[bool, bool, bool, bool]] = None,
):
return IOComponent.style(
self,
@ -2693,7 +2724,6 @@ class Timeseries(Changeable, IOComponent):
def style(
self,
rounded: Optional[bool | Tuple[bool, bool, bool, bool]] = None,
border: Optional[bool | Tuple[bool, bool, bool, bool]] = None,
):
return IOComponent.style(
self,

View File

@ -1022,7 +1022,7 @@ class TestDataframe(unittest.TestCase):
dataframe_input.get_config(),
{
"headers": ["Name", "Age", "Member"],
"datatype": "str",
"datatype": ["str", "str", "str"],
"row_count": (3, "dynamic"),
"col_count": (3, "dynamic"),
"value": [
@ -1079,7 +1079,7 @@ class TestDataframe(unittest.TestCase):
"style": {},
"elem_id": None,
"visible": True,
"datatype": "str",
"datatype": ["str", "str", "str"],
"row_count": (3, "dynamic"),
"col_count": (3, "dynamic"),
"value": [

View File

@ -7,6 +7,7 @@
type Headers = Array<string>;
type Data = Array<Array<string | number>>;
type Datatype = "str" | "markdown" | "html" | "number" | "bool" | "date";
export let headers: Headers = [];
export let elem_id: string = "";
@ -19,6 +20,7 @@
export let style: Styles = {};
export let label: string | null = null;
export let wrap: boolean;
export let datatype: Datatype | Array<Datatype>;
$: {
if (value && !Array.isArray(value)) {
@ -60,5 +62,6 @@
editable={mode === "dynamic"}
{style}
{wrap}
{datatype}
/>
</div>

View File

@ -3,6 +3,7 @@
export let value: string | number = "";
export let el: HTMLInputElement | null;
export let header: boolean = false;
export let datatype: "str" | "markdown" | "html" | "number" | "bool" | "date";
</script>
{#if edit}
@ -24,5 +25,11 @@
role="button"
class:opacity-0={edit}
class:pointer-events-none={edit}
class="p-2 outline-none border-0 flex-1">{value}</span
class="p-2 outline-none border-0 flex-1"
>
{#if datatype === "markdown" || datatype === "html"}
{@html value}
{:else}
{value}
{/if}
</span>

View File

@ -9,6 +9,9 @@
import { Upload } from "@gradio/upload";
import EditableCell from "./EditableCell.svelte";
type Datatype = "str" | "markdown" | "html" | "number" | "bool" | "date";
export let datatype: Datatype | Array<Datatype>;
export let label: string | null = null;
export let headers: Array<string> = [];
export let values: Array<Array<string | number>> = [[]];
@ -567,6 +570,9 @@
bind:value
bind:el={els[id].input}
edit={editing === id}
datatype={Array.isArray(datatype)
? datatype[j]
: datatype}
/>
</div>
</td>