Plotly / Dash: Multiple filters - python

I would like to implement a few data filters to preselect the data by certain criteria. These filters should be diagrams itself, i.e. a pie chart (e.g. where one can select a continent) and a time line (e.g. where one can select a time-span). Most importantly, I need to apply multiple filters from mutliple diagrams without them resetting every time I filter by selecting another diagram.
However, I do not know how to implement this. I found something old using dash.dependencies.Events, but that is not supported anymore.
Whenever I filter by a criterion in diagram A and then want to filter by another criterion from diagram B, diagram A gets reset.
Since this is probably a situation encountered by many people, and since dash does not seem to support this natively, I wanted to ask whether anyone has a workaround on this?
//edit: Here is a simple example. I can filter by clicking on a datapoint on the bar graph above. But whenever I click on a point on the line graph below, it resets the settings from the bar graph. I want to keep both.
import datetime
import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly
from dash.dependencies import Input, Output
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div([
dcc.Graph(id='graph')
])
# Multiple components can update everytime interval gets fired.
#app.callback(Output('graph', 'figure'),
[Input('graph', 'selectedData')])
def update_graph_live(input):
print(input)
data = {
'x': [1,2,3,4,5],
'y': [1,2,3,4,5],
'a': [0,-1,-2],
'b': [100,101,102]
}
# Create the graph with subplots
fig = plotly.tools.make_subplots(rows=2, cols=1, vertical_spacing=0.2)
fig['layout']['margin'] = {
'l': 30, 'r': 10, 'b': 30, 't': 10
}
fig['layout']['legend'] = {'x': 0, 'y': 1, 'xanchor': 'left'}
fig['layout']['clickmode'] = 'event+select'
fig.append_trace({
'x': data['x'],
'y': data['y'],
'name': 'xy',
'type': 'bar',
}, 1, 1)
fig.append_trace({
'x': data['a'],
'y': data['b'],
'name': 'ab',
'mode': 'lines+markers',
'type': 'scatter'
}, 2, 1)
return fig
if __name__ == '__main__':
app.run_server(debug=True)

Right now it seems your problem is that selecting data in any of the graphs in the figure of the graph component, i.e. the output of your function, triggers the Input(graph,'selectedData') to that same function!
So what you need to do is separate the graphs into separate dcc.Graph things and use dash.dependencies.State to listen to and maintain each graph's selectedData property.

Thank you for your answers. I managed to find a workaround.
First of all, as #russellr mentioned, dash.dependencies.State can be passed, but it will not trigger a callback. I would like callbacks to be triggered on multiple filters without them resetting each other.
Now, for the good people at Dash, disabling this reset would enable endless loops, so it there is a lot of sense in disabling it.
The way I did it is that I introduced Dropdown lists for filtering, and callbacks only go from the value of the Dropdown to the figure of the graph.
I select multiple conditions on the dropdowns, get the (non-interactive) visualisations from it. It might be not as pretty, but the app still got good useability feedback.

Related

How To Make Dash DataTables Responsive [Height & Weight] dynamically to fit in any device?

I'm going through the Dash Tables design to create a webpage with data preview, But the problem I'm facing is it's not responsive to browser, Though Column wise it's responsive enough but the height it's not helping at all. I'm using the bellow python code for the same.
from dash import Dash, dash_table
import pandas as pd
from collections import OrderedDict
app = Dash(__name__)
df = pd.DataFrame(OrderedDict(
[
[
'Column {}'.format(i + 1), list(range(30))
] for i in range(15)
]
))
app.layout = dash_table.DataTable(
data=df.to_dict('records'),
columns=[{'id': c, 'name': c} for c in df.columns],
virtualization=True,
fixed_rows={'headers': True},
style_cell={'minWidth': 95, 'width': 95, 'maxWidth': 95},
style_table={"overflowY":"auto",'maxHeight':'75vh'} # default is 500
)
if __name__ == '__main__':
app.run_server(debug=True)
I'm not getting any proper config to make it responsive height wise. Few ref. doc's I've came across are as follows:
https://dash.plotly.com/datatable/height
https://dash.plotly.com/datatable/style
https://community.plotly.com/t/dash-data-table-not-displayed-on-full-page/68332/14
Adjusting the MaxHeight of a Table in Dash Tables Python
Can someone help here? How can I make this table fully responsive for any screen size ?

Plotly Dash - not fully understanding the extendData feature with stacked barplot

I've created a stacked barplot in a dash app that updates the number of data points in a trace according to input from a slider, as shown:
Two different states:
Unfortunately, the updates on the image are painfully slow (note that the update mode is 'drag' and not the default. I want fluid action on the slider as it's moved. I've tried to implement a clientside callback to update the data, as shown on this post: Plotly/Dash display real time data in smooth animation (thanks to emher for that great response), but I can't seem to get it working (I feel it's extremely complicated to understand the interactions between inputs/outputs without more complex examples and I'm not even sure if this is the best solution or if it works with my plot type). I think it has to do with the several go.Bar() calls I have in the main go.Figure() call, but I'm honestly not too sure and have a really hard time finding documentation using the extendData or clientside callback functions. I'm attaching a very basic working example of the structure of my plots - it doesn't run exceptionally slow because the amount of data is small, but ideally I would like to have full smooth responsiveness as the user drags the slider around. I would really appreciate some help in making clientside callbacks work here or any other method of allowing this functionality instead of having to ping the aws container all the time for re-renders. (On the JS side, I'm not sure if I should be re-rendering the plot in Plotly.js, or only updating the data, or how to use dcc.Store with all of this.) Thanks for any guidance.
import datetime
import dash
import dash_html_components as html
from dash.dependencies import Input, Output
import dash_core_components as dcc
import plotly.graph_objects as go
trace1 = [1, 2, 3, 2, 3, 4, 5, 2]
trace2 = [3, 4, 2, 5, 3, 1, 3, 2]
dates = [datetime.date(2019, 1, 1), datetime.date(2019, 1, 2), datetime.date(2019, 1, 3), datetime.date(2019, 1, 4),
datetime.date(2019, 1, 5), datetime.date(2019, 1, 6), datetime.date(2019, 1, 7), datetime.date(2019, 1, 8)]
def serve_layout():
return html.Div(children=[
dcc.Graph(id="bar-chart"),
html.Div(" Select the starting date:"),
html.Br(),
html.Div(dcc.Slider(
id='slider',
min=0,
max=len(dates) - 1,
value=0,
updatemode='drag'))
])
app = dash.Dash(__name__)
#app.callback(
Output("bar-chart", "figure"),
[Input("slider", "value")])
def update_bar_chart(day):
fig = go.Figure(data=[
go.Bar(name='Trace 1', x=dates[day:], y=trace1[day:], marker_color='blue'),
go.Bar(name='Trace 2', x=dates[day:], y=trace2[day:], marker_color='red')])
fig.update_layout({'barmode': 'stack'})
return fig
app.layout = serve_layout
if __name__ == '__main__':
app.run_server()

Animating TripsLayer in deck.gl with python

I have seen the TripsLayer example in the deck.gl website (this one) and it looks really cool. I would like to accomplish the same but using pydeck, the python bindings for deck.gl. The example in pydeck's webpage (this one) is not animated and I am not sure how should I do it to get a smooth animation as shown in the javascript example. I have tried multiple things (passing lists, functions, variables with changing value etc.) but non of them have worked and I can't find any example with pydeck.
Thanks!
It's true that the example should include more trips. Here is how to achieve the animation of multiple trips in a jupyter notebook.
import time
import pandas as pd
import pydeck as pdk
data = '[{"agent_id":0,"path":[[-0.63968,50.83091,0.0],[-0.78175,50.83205,0.0]],"time":[65100,65520],"color":[228,87,86]},{"agent_id":1,"path":[[-0.63968,50.83091,0.0],[-0.78175,50.83205,0.0]],"time":[65940,66420],"color":[178,121,162]},{"agent_id":2,"path":[[-0.63968,50.83091,0.0],[-0.37617,50.8185,0.0]],"time":[65340,66360],"color":[157,117,93]},{"agent_id":3,"path":[[-0.63968,50.83091,0.0],[-0.78175,50.83205,0.0]],"time":[65940,66420],"color":[238,202,59]},{"agent_id":4,"path":[[-0.63968,50.83091,0.0],[-0.78175,50.83205,0.0]],"time":[67740,68160],"color":[157,117,93]}]'
df = pd.read_json(data)
view = {"bearing": 0, "latitude": 50.85, "longitude": -0.16, "pitch": 0, "zoom": 9}
time_min = 65_000
time_max = 80_000
layer = pdk.Layer(
"TripsLayer",
df,
get_path='path',
get_timestamps='time',
get_color='color',
opacity=0.8,
width_min_pixels=3,
rounded=True,
trail_length=900,
current_time=0
)
# Render
r = pdk.Deck(layers=[layer], initial_view_state=view, map_style='dark_no_labels')
r.show()
# Animate
for ct in range(time_min, time_max, 100):
layer.current_time = ct
r.update()
time.sleep(0.1)

Tooltips in Altair line charts

When specifying a tooltip for a line chart, the tooltip only appears when hovering over points along a line, but not when hovering anywhere else along a line. This is especially problematic when using a non-linear interpolation... Is there way to explicitly set tooltips on the lines themselves?
import altair as alt
from vega_datasets import data
source = data.jobs.url
alt.Chart(source).mark_line(interpolate="basis").encode(
alt.X('year:O'),
alt.Y('perc:Q', axis=alt.Axis(format='%')),
color='sex:N',
tooltip='sex:N'
).properties(
title='Percent of work-force working as Welders'
).transform_filter(
alt.datum.job == 'Welder'
)
Extending from #Philipp_Kats's answer and #dominik's comment (and for anyone else who stumbled upon this thread and wish to see the Altair code example), the current way of achieving a "tooltip" effect along the lines is to:
Create the line (mark_line())
Create a selection that chooses the nearest point & selects based on x-value
Snap some transparent selectors across the line, informing the x-value across different positions of the line
Layer (mark_text()) on top of 1 - 3 above
A real example is this line chart on a simple Flask app I made. Only difference was that I didn't make the selectors transparent (opacity=alt.value(0)) but otherwise it's a line chart with tooltips snapped on it.
Here's a reproducible example using OP's original dataset:
# Step 1: create the line
line = alt.Chart().mark_line(interpolate="basis").encode(
x=alt.X("year:O"),
y=alt.Y("perc:Q", axis=alt.Axis(format='%')),
color='sex:N'
).transform_filter(
alt.datum.job == 'Welder'
)
# Step 2: Selection that chooses nearest point based on value on x-axis
nearest = alt.selection(type='single', nearest=True, on='mouseover',
fields=['year'])
# Step 3: Transparent selectors across the chart. This is what tells us
# the x-value of the cursor
selectors = alt.Chart().mark_point().encode(
x="year:O",
opacity=alt.value(0),
).add_selection(
nearest
)
# Step 4: Add text, show values in Sex column when it's the nearest point to
# mouseover, else show blank
text = line.mark_text(align='left', dx=3, dy=-3).encode(
text=alt.condition(nearest, 'sex:N', alt.value(' '))
)
# Layer them all together
chart = alt.layer(line, selectors, text, data=source, width=300)
chart
Resulting plot:
I doubt that there is a direct technical solution at the moment :-(
One workaround solution is to explicitly add points on top of lines so it is easier to hover. I usually make them relatively large, but hide until the hover event, like here As a cherry on the top, one could use Voronoi to show the closest point at any given point, as they do in this tutorial
Let me know if you need Altair code example, I used raw vega, but implementing Altair version should be relatively trivial
As of March 2022, a workaround for this without complicating your spec too much with selectors and Voronoi tesselation: Use a thick transparent line in the background (opacity should not be exactly 0, because then it is not rendered) and create a layer chart.
base = (
alt.Chart(
pd.DataFrame(
[{"x": 1, "y": 1}, {"x": 2, "y": 2}, {"x": 3, "y": 1}, {"x": 4, "y": 4}]
)
)
.mark_line()
.encode(x="x:Q", y="y:Q", tooltip="tt:N")
.transform_calculate(tt="datum.x+' value'")
)
tt = base.mark_line(strokeWidth=30, opacity=0.01)
base + tt

plotly - multiple traces using a shared slider variable

As the title hints, I'm struggling to create a plotly chart that has multiple lines that are functions of the same slider variable.
I hacked something together using bits and pieces from the documentation: https://pastebin.com/eBixANqA. This works for one line.
Now I want to add more lines to the same chart, but this is where I'm struggling. https://pastebin.com/qZCMGeAa.
I'm getting a PlotlyListEntryError: Invalid entry found in 'data' at index, '0'
Path To Error: ['data'][0]
Can someone please help?
It looks like you were using https://plot.ly/python/sliders/ as a reference, unfortunately I don't have time to test with your code, but this should be easily adaptable. If you create each trace you want to plot in the same way that you have been:
trace1 = [dict(
type='scatter',
visible = False,
name = "trace title",
mode = 'markers+lines',
x = x[0:step],
y = y[0:step]) for step in range(len(x))]
where I note in my example my data is coming from pre-defined lists, where you are using a function, that's probably the only change you'll really need to make besides your own step size etc.
If you create a second trace in the same way, for example
trace2 = [dict(
type='scatter',
visible = False,
name = "trace title",
mode = 'markers+lines',
x = x2[0:step],
y = y2[0:step]) for step in range(len(x2))]`
Then you can put all your data together with the following
all_traces = trace1 + trace2
then you can just go ahead and plot it provided you have your layout set up correctly (it should remain unchanged from your single trace example):
fig = py.graph_objs.Figure(data=all_traces, layout=layout)
py.offline.iplot(fig)
Your slider should control both traces provided you were following https://plot.ly/python/sliders/ to get the slider working. You can combine multiple data dictionaries this way in order to have multiple plots controlled by the same slider.
I do note that if your lists of dictionaries containing data are of different length, that this gets topsy-turvy.

Categories