Repository: gradio-app/gradio

Issue:
Dataframe allow Markdown or HTML content
- [X] I have searched to see if a similar issue already exists.


**Is your feature request related to a problem? Please describe.**

Simple example here: https://huggingface.co/spaces/Gradio-Blocks/Leaderboard. Currently it's not possible to create hyperlinks inside a Dataframe table. I can see other use cases beyond `<a>` elements, maybe images or simple video previews?  I'm thinking that would be handy to preview rich content Dataframes outputs. I understand the performance issues with large tables, but I guess it needs lazy loading anyway.



Discussion:
+1!! This would be hugely useful

cc @pngwn for his thoughts

Resolving patch:
diff --git a/demo/blocks_outputs/run.py b/demo/blocks_outputs/run.py
--- a/demo/blocks_outputs/run.py
+++ b/demo/blocks_outputs/run.py
@@ -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 @@
         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__":
diff --git a/gradio/components.py b/gradio/components.py
--- a/gradio/components.py
+++ b/gradio/components.py
@@ -2356,6 +2356,8 @@ class Dataframe(Changeable, IOComponent):
     Demos: filter_records, matrix_transpose, tax_calculator
     """
 
+    markdown_parser = None
+
     def __init__(
         self,
         value: Optional[List[List[Any]]] = None,
@@ -2405,13 +2407,17 @@ def __init__(
         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
@@ -2419,7 +2425,10 @@ def __init__(
         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
@@ -2520,16 +2529,24 @@ def postprocess(self, y):
         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
@@ -2550,10 +2567,24 @@ def __validate_headers(headers: List[str] | None, col_count: int):
                 )
             )
 
+    @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,
@@ -2695,7 +2726,6 @@ def postprocess(self, y):
     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,


Test patch:
diff --git a/test/test_components.py b/test/test_components.py
--- a/test/test_components.py
+++ b/test/test_components.py
@@ -1022,7 +1022,7 @@ def test_component_functions(self):
             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 @@ def test_component_functions(self):
                 "style": {},
                 "elem_id": None,
                 "visible": True,
-                "datatype": "str",
+                "datatype": ["str", "str", "str"],
                 "row_count": (3, "dynamic"),
                 "col_count": (3, "dynamic"),
                 "value": [