Plotly: Adding custom markers and events to xaxis points

Viewed 70

We have a plotly timeseries graph and want to add markers near the bottom of the xaxis that displays graph data at the point on the xaxis. I know we can't add custom markers to xaxis but looking for work arounds to achieve something similar to the image below.

1

2

1 Answers

This is a python suggestion since I simply did not notice the lack of a python tag. I'll let the suggestion stand for now since the approach may be applicable for a javascript solution as well.


As you point out yourself, there's no direct way to do this. But if you'd only like to display a series of events along the dates on the x-axis you can add an extra trace and set:

hovermode="x unified"

This will activate a vertical line like in your image:

Option 1 -Vertical line, highlighted event dates, hoverinfo on all traces

enter image description here

A potential downside is that you'll also show all information for all traces on every date.So the behavior of the event markers aren't special in any regard. If you'd like to apply extra emphasis to the "event" dates, you can deactivate hoverinfo for all other dates and display collected information for all traces by hovering over the events only. This will take a bit more tweaking, but is very much possible. The complete snippet below contains all details. I'm more than willing to explain the details if this is something you can use.

Option 2 - Hoverinfo only on dates with highlighted events

enter image description here

Option 2 - Complete code

# imports
import pandas as pd
import plotly.express as px
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots

fig = make_subplots(specs=[[{"secondary_y": True}]])

# data
df = px.data.stocks()
df = df.drop('AMZN', axis = 1)
colors = px.colors.qualitative.T10

# plotly
fig.add_traces(px.line(df, 
                 x = 'date',
                 y = [c for c in [*df] if c != 'date'],
                 color_discrete_sequence = colors,
                 title = 'Stocks', 
             )._data)

fig.update_layout(template = 'plotly_dark')

# get some info about the data to ma
# globalMin = df[df.columns[1:-1]].min().min()

# placeholders for markerpositions on yaxis2
df['events'] = np.nan
df['events'].iat[50] = 0
df['events'].iat[75] = 0
df['events'].iat[100] = 0

# clear hovertemplate for all traces
fig.update_traces(hovertemplate=None)

# collect info from all traces to display in a new trace
htemp = ''.join(str(x) + ' : %{customdata[' + str(i) +']:.3f}<br>' for i, x in enumerate([*df][1:-1]))
htemp = htemp[:-4]  + "<extra></extra>"

# add a new trace with collective hoverinfo
fig.add_trace(go.Scatter(x=df.date, y = df.events,
                         mode = 'markers',
                         # marker_symbol = 'diamond-tall',
                         marker_symbol = 'line-ns',
                         marker_color = 'rgba(0,0,0,0)',
                         marker_line_color = 'yellow',
                         marker_line_width = 4,
                         marker_size = 14,
                         customdata = df[[*df][1:-1]],
                         hovertemplate = htemp,
                         showlegend = True,
                         name  = 'events',
                        ), secondary_y = True)

# layout adjustments for yaxis2
fig.update_layout(yaxis2_range= [0,1],
                  yaxis2_showgrid = False,
                  yaxis2_zeroline = False,
                  yaxis2_tickfont_color = 'rgba(0,0,0,0)')

# enable the events trace to show collective information
# and display line on hover
fig.update_layout(hovermode="x unified")

# add some space between xaxis and ticklabels
fig.update_xaxes(tickprefix = "  <br>")

# edit appearance of hoverlabel
fig.update_layout(hoverlabel=dict(bgcolor='rgba(0,0,0,0.5)', 
                                  bordercolor = 'rgba(0,0,0,0)',
                                  font=dict(color='rgba(250,250,250,0.5)'),
                                  align  = 'right',                                  
                                 ))

# deactivate hoverinfo for all other traces than the event trace
fig.for_each_trace(lambda t: t.update(hoverinfo = 'skip') if t.name != 'events' else ())

# # tests with annotations that
# e = 1
# for n, c in df.events.iteritems():
#     if pd.notnull(c):
#         fig.add_annotation(x = df.date.iloc[n], y = df.events.iloc[n],
#                            text="event " + str(e),
#                            showarrow=True,
#                            ax = 0,
#                            ayref = 'pixel',
#                            ay = 150,
#                            standoff = 10,
#                            arrowhead=1)
#         e += 1
# f = fig.full_figure_for_development(warn=False)


fig.show()
Related