Dash: how to control graph style via CSS? - python

I have a simple Dash application and I would like to set fonts and colours of my plots via CSS. Here is what my app.py looks like:
import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objects as go
def generate_plot():
fig = go.Figure()
fig.add_trace(go.Scatter(x=[1, 2, 3], y=[1, 2, 3]))
return fig
app = dash.Dash(__name__)
app.layout = html.Div(children=[
html.H1(children="title", className="title"),
dcc.Graph(figure=generate_plot(), class="plot")
])
I also have a file assets/style.css and i deally, I would like to extend this file with content that describes how my dcc.Graph objects should look. Is this possible? If it is, then how would I do it? I would like to be able to set fonts, background colours, line/marker colours, etc. Unfortunately, something like .plot { background-color: aqua; } in CSS has no effect. Also, html, body {font-family: serif; } has no effect too.

I have addressed some of these problems, on a development-only basis, via the following:
I host the CSS file on a different webserver than the dash Flask server. This seems reasonable for production architecture, but in a dev environment it's messy because the webserver address has to be specified in the url in order to use a different port than the dev Flask server (e.g., the :8050 default).
app.css.append_css(dict(external_url='http://localhost/style.css'))
using this css file I can set some style-based elements of the Dash page, such as
html { background-color: aqua;}
I haven't been able to find or set CSS styles in all of the auto-generated components yet, but there are hooks to set many of the style elements directly. See https://plotly.com/python/reference/#scatter for more. Splicing in code that works for me into your example, this should work:
fig.add_trace(go.Scatter(x=[1, 2, 3], y=[1, 2, 3], textfont={'family': 'serif'}))

Related

Serve TailwindCSS with django_plotly_dash

I have a Dash app in Django being served via django-plotly-dash and I'm using Tailwind for the styling across the site. Tailwind seems to be working everywhere except for the Dash app, where it is kind of working, but seems to be overwritten by the Bootstrap at some points.
I can see the Tailwind styling without any issues if I run the Dash app on its own, but not when embedded in Django.
Here's the view inside Django (and the code for this basic example):
And here it is (with garish colors to see the difference) while running Dash and Tailwind without Django:
Some of the Tailwind styling is being applied, such as the container mx-auto bit of the Dash layout, but others (e.g. coloring) are being dropped.
Here's the code for the Dash app, which is split into layout.py, callbacks.py, and dashboard.py:
layout.py:
from dash import dcc, html
layout = html.Div(
className="bg-green-100 container mx-auto my-auto px-15 py-5",
children=[
html.Div(
className="bg-red-100 py-5",
children=[
dcc.Dropdown(
id="symbol-input",
options=[
{"label": "Apple", "value": "AAPL"},
{"label": "Tesla", "value": "TSLA"},
{"label": "Meta", "value": "META"},
{"label": "Amazon", "value": "AMZN"}
],
searchable=True,
value="AAPL",
)
]),
html.Div(
className="max-w-full shadow-2xl rounded-lg border-3",
id="price-chart"
)
]
)
callbacks.py:
from dash import dcc, html
from dash.dependencies import Input, Output
import yfinance as yf
import plotly.express as px
def register_callbacks(app):
#app.callback(
Output("price-chart", "children"),
Input("symbol-input", "value"),
)
def get_data(symbol):
df = yf.Ticker(symbol).history()
fig = px.line(
x=df.index,
y=df.Close,
title=f"Price for {symbol}",
labels={
"x": "Date",
"y": "Price ($)",
}
)
return dcc.Graph(
id="price-chart-1",
figure=fig
)
dashboard.py:
from django_plotly_dash import DjangoDash
from .layout import layout
from .callbacks import register_callbacks
app = DjangoDash("Dashboard")
app.css.append_css({"external_url": "/static/css/output.css"})
app.layout = layout
register_callbacks(app)
The Tailwind CSS is in /static/css/output.css and is linked as the stylesheet in the base.html. To ensure it's working correctly in Django, I put a simple homepage up and copied code from Tailwind's site to confirm that it works. Again, it's partially coming through in the Dash app, but seems to get overwritten.
After viewing your repository, I think the problem is not that the Bootstrap CSS overrides the tailwind's one, the problem here is that the classes that you defined are simply not scanned by Tailwindcss. I'm going to assume that you generate the output.css using this command:
> npx tailwindcss -i ./static/css/input.css -o ./static/css/output.css --watch
If that's what you did to generate the CSS, then I can understand what's going on here. That's simply because of your tailwind.config.js file looks like this:
...
content: [
"./static/css/*.html",
"./templates/*.html",
"./static/css/*.js",
],
...
You said that container, mx-auto classes are applied, but not the color classes (e.g. bg-green-100, bg-red-100), that's simply because container, mx-auto classes are defined in one of "./static/css/*.html", "./templates/*.html", "./static/css/*.js", while bg-green-100, bg-red-100 are defined in other directory than those directories (it's defined in apps\dashboard\layout.py).
The easiest fix is to add the directories in which CSS classes need to be applied to the tailwind.config.js file, e.g.:
/** #type {import('tailwindcss').Config} */
module.exports = {
content: [
"./static/css/*.html",
"./templates/*.html",
"./static/css/*.js",
"./apps/**" // add this line
],
theme: {
extend: {},
},
plugins: [],
}
This will add all classes from any files or any files in ./apps directory or subdirectories to the tailwindcss build process. Don't forget to run the tailwindcss cli command (the one I mentioned earlier) every time you run the server though.

Using Plotly-Dash for multiple option select graph using dropdown menu and downloading it as standalone html file

Have lot of case studies for which plots needs to be drawn and for this using Dropdown in plotly -dash for interactivity. But unable to include the html download option.
I took example file for html download:
app = dash.Dash(__name__)
buffer = io.StringIO()
df = px.data.iris() # replace with your own data source
fig = px.scatter(
df, x="sepal_width", y="sepal_length",
color="species")
fig.write_html(buffer)
html_bytes = buffer.getvalue().encode()
encoded = b64encode(html_bytes).decode()
app.layout = html.Div([
html.H4('Simple plot export options'),
html.P("↓↓↓ try downloading the plot as PNG ↓↓↓", style={"text-align": "right", "font-weight": "bold"}),
dcc.Graph(id="graph", figure=fig),
html.A(
html.Button("Download as HTML"),
id="download",
href="data:text/html;base64," + encoded,
download="plotly_graph.html"
)
])
app.run_server(debug=True,use_reloader=False)
How to include the download as html for multiple figure options? Is it in app.callback?
Tried plotting various data with app.callback() using options and changing in dash app. But unable to have an interactive html version with dash multidropdown.

Is there a way to render spaCy's NER output on a Dash dashboard?

I am trying to incorporate spaCy's NER pre-trained model into my Dash Dashboard. I know Dash currently does not have the ability to render raw html so I am looking for a solution. I have search the online extensively without finding a solution.
Currently, I have a dashboard that looks like this:
[Dashboard]
Where I would like the SpaCy's NER output to show underneath if possible. As an example please see the image below:
[NER Example Output]
If anyone has managed to find a solution that works with Dash, please let me know. If it isn't possible then it isn't the end of the world. I know it can be done in Flask albeit it is harder to code in HTML!
Many thanks!
it's not possible to render it in one line through displacy. However, you should be able to abstract the html through python functions and manually render the results. Here's an example app:
import dash
import dash_html_components as html
import spacy
from spacy.displacy.render import DEFAULT_LABEL_COLORS
# Initialize the application
app = dash.Dash(__name__)
def entname(name):
return html.Span(name, style={
"font-size": "0.8em",
"font-weight": "bold",
"line-height": "1",
"border-radius": "0.35em",
"text-transform": "uppercase",
"vertical-align": "middle",
"margin-left": "0.5rem"
})
def entbox(children, color):
return html.Mark(children, style={
"background": color,
"padding": "0.45em 0.6em",
"margin": "0 0.25em",
"line-height": "1",
"border-radius": "0.35em",
})
def entity(children, name):
if type(children) is str:
children = [children]
children.append(entname(name))
color = DEFAULT_LABEL_COLORS[name]
return entbox(children, color)
def render(doc):
children = []
last_idx = 0
for ent in doc.ents:
children.append(doc.text[last_idx:ent.start_char])
children.append(
entity(doc.text[ent.start_char:ent.end_char], ent.label_))
last_idx = ent.end_char
children.append(doc.text[last_idx:])
return children
text = "When Sebastian Thrun started working on self-driving cars at Google in 2007, few people outside of the company took him seriously."
nlp = spacy.load("en_core_web_sm")
doc = nlp(text)
print("Entities:", doc.ents)
# define de app
app.layout = html.Div(
children=render(doc)
)
# Run the app
if __name__ == "__main__":
app.run_server(debug=True)
This produces the following result:
In the example above, the entname and entbox functions will respectively generate an html.Span and html.Mark with the style copied from the output html in displacy. Then, the function entity abstracts the previous two functions to easily generate an entity box. Finally, the render function will take spacy's Doc object and convert it into a list of Dash html components, which can be used inside the Dash layout.

Dash Sort editable table, but hide sort options from users

I have a dash table. Table allows edit. I want to sort the table by column, so that if user input data, the table is resorted right away. I achieve this like on the page https://dash.plotly.com/datatable/callbacks. The sort is already set when the page loads. I got stuck on the last step, where I want to hide the sort option from user. Is that possible?
Example on the image. I want to delete the arrows marked yellow, but keep sort by column 'pop'.
edited code example from https://dash.plotly.com/datatable/callbacks:
import dash
from dash.dependencies import Input, Output
import dash_table
import pandas as pd
app = dash.Dash(__name__)
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv')
PAGE_SIZE = 5
app.layout = dash_table.DataTable(
id='table-multicol-sorting',
columns=[
{"name": i, "id": i} for i in sorted(df.columns)
],
data=df.to_dict('records'),
page_size=PAGE_SIZE,
sort_action='native',
sort_mode='multi',
sort_as_null=['', 'No'],
sort_by=[{'column_id': 'pop', 'direction': 'asc'}],
editable=True,
)
if __name__ == '__main__':
app.run_server(debug=True)
You can target the sort element and hide it using css like this:
span.column-header--sort {
display: none;
}
So you can put that code in a css file in your assets directory for example. See the documentation here for more information about ways to include styles in a dash app.
I am able to do it by sort_action='none' in Dash v1.16.2

Dash testing dcc.upload with dash.testing

When writing production ready code we want to be able to automatically test our webapp everytime we update the code. Dash for python allows this through dash.testing. However in my app I upload an excel file utilizing the dcc.Upload() component.
How do I write a test that can send the upload link to this component?
The dcc.Upload component does not allow you to put an id on the that stores the upload link.
It is easy to work around this by inspecting the upload button/field that you have created with web developer tools. look for the line that contains "<input type=file ... >". in the elements tab.
Right click it and press copy xpath and it should give you a relative path like //*[#id="upload-data"]/div/input
The test case would look like this
from dash.testing.application_runners import import_app
def test_xxxx001_upload(dash_duo):
# get app from app.py
app = import_app("src.app")
dash_duo.start_server(app)
# find element that contains input link. Utilize the web driver to get the element
element = dash_duo.driver.find_element_by_xpath('//*[#id="upload-data"]/div/input')
element.send_keys("C:\\path\\to\\testData.xlsx")
folder structure
myapp
--src
--app.py
--server.py
--run.py
--tests
--test_app
the use of the dcc.Upload component to create an upload button
import dash_core_components as dcc
import dash_html_components as html
html.Div(
id="file-drop",
children=[
dcc.Upload(
id="upload-data",
children=html.Div(
["Drag and Drop or ", html.A("Select File"),],
id="select-file",
),
multiple=False,
),
html.Div(id="output-data-upload"),
],
)

Categories