Redering HTML/Markup in DataTable

Hi,

I am tring to build a Dash app showing a Table with

  • a column of text fields which contain Displacy-style NER-markings for entities
  • and neighboring columns displaying the found entities.

For example, the source text column would contain free text and the columns would have the extracted names, orgs, dates, etc. Here’s a scap of Displacy-like markup in a Dash app:

image

From stackoverflow.com/spacys-ner-output-on-a-dash-dashboard

I wanted to use the DataTable for a few reasons:

  • displaying multiple rows at the same time
  • dynamic (re)sizing of cells
  • editable text in other columns,
    • these cols have values (the NER spans) derived from the source text cell
  • ability to output the (updated) columns into a new data table.

None of this seems easily doable.

Some workarounds:

  1. Do everything in a DataTable and use Markdown to emulate the Displacy-style markings
    • This seems to be the option that i want, however, it is limited in what can be displayed;
      • does HTML render in DataTable?
    • i’ve just learned of this now and will try it out
  2. display a single row at a time, selecting them with a dropdown
    • drawbacks:
      • only single-row is visible;
      • updating the new table would be (maybe) difficult
  3. create a grid of Divs and Input-components, with a “Save” button, to emulate a DataTable
    • drawbacks:
      • many and dynamically created callbacks for updating the inputs (tedious but not impossible)
      • tinkering with formatting

Does anyone have any ideas or whether any of the above is a good idea? Does anyone have any alternatives?

This could be handled by pattern-matching callbacks if you wanted to go this route. :slight_smile:

I havent seen displacy-style, could you post an image or something to which you are trying to attain?

Thanks for the tip in pattern matching callbacks! I should look into this…

1 Like

@hansaster

spaCy is pretty cool, and it’s easy to use with Dash!

As you have probably discovered, you can display it in a data_table by setting the column(s) to “presentation”: “markdown”
and adding markdown_options={"html": True}

dash_table.DataTable(        
        columns=[
            {'name': 'Enter text to analyze', 'id': 'input-data'},
            {'name': 'Entities', 'id': 'output-data', "presentation": "markdown"}
        ],        
        markdown_options={"html": True},       
    ),

It’s possible to do this in a data_table, but the issue is that larger blocks of text are difficult to edit in the table.

Here is an example of using text areas and pattern matching callbacks. It’s adapted from this app in the Dash Example Index

spacy

import dash
from dash import Dash, dcc, html, Input, Output, State, MATCH

import dash_bootstrap_components as dbc
import spacy
from spacy import displacy

nlp = spacy.load("en_core_web_sm")

app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])

app.layout = dbc.Container(
    dbc.Row(
        dbc.Col(
            [
                html.H3(
                    "Natural Language Processing with spaCy",
                    className="text-center bg-primary text-white p-2 mb-4",
                ),
                dbc.Button(
                    "Add Card",
                    id="pattern-match-add-card",
                    n_clicks=0,
                    className="mb-3",
                ),
                html.Div(id="pattern-match-container", children=[], className="mt-4"),
            ]
        )
    ),
    fluid=True,
)


def get_entities(input_text):
    # new lines mess up the displacy renderer
    input_text = input_text.replace("\n", " ")

    doc = nlp(input_text)
    return displacy.render(doc.ents, style="ent")


def make_card(n_clicks):
    return dbc.Card(
        [
            dbc.CardHeader(
                [
                    f"Text Input {n_clicks + 1} ",
                    html.Div(
                        dbc.Button(
                            "X",
                            id={"type": "delete-card", "index": n_clicks},
                            n_clicks=0,
                            color="secondary",
                        ),
                        className="ms-auto",
                    ),
                ],
                className="hstack",
            ),
            dbc.Row(
                [
                    dbc.Col(
                        dcc.Textarea(
                            id={"type": "text-input", "index": n_clicks},
                            className="w-100 h-100",
                        ),
                    ),
                    dbc.Col(
                        dcc.Markdown(
                            id={"type": "entities", "index": n_clicks},
                            dangerously_allow_html=True,
                        ),
                    ),
                ]
            ),
        ],
        className="m-1",
        id={"type": "card", "index": n_clicks},
    )


@app.callback(
    Output("pattern-match-container", "children"),
    Input("pattern-match-add-card", "n_clicks"),
    State("pattern-match-container", "children"),
)
def add_card(
    n_clicks,
    cards,
):
    new_card = make_card(n_clicks)
    cards.append(new_card)
    return cards


@app.callback(
    Output({"type": "card", "index": MATCH}, "style"),
    Input({"type": "delete-card", "index": MATCH}, "n_clicks"),
    prevent_initial_call=True,
)
def remove_card(_):
    return {"display": "none"}


@app.callback(
    Output({"type": "entities", "index": MATCH}, "children"),
    Input({"type": "text-input", "index": MATCH}, "value"),
)
def update_figure(text_input):
    if text_input is None:
        return dash.no_update
    return get_entities(text_input)


if __name__ == "__main__":
    app.run_server(debug=True)

1 Like