Plotly-Dash how to add an identifier to an Annotation? - python

Context
Suppose one has multiple sets of annotations, which are merged into 1 large List of annotations in the fig.update_layout of the go.Figure object in plotly/Dash. Since the annotations are created at different places, it may be somewhat tedious to keep track of the indices based on list index. So I thought, if I add an identifier to the annotation, I am sure I am updating the right annotation each time. Especially as the annotations may contain duplicate properties or possibly be complete duplicates (without identifier).
MWE
A trivial MWE is included:
import plotly.graph_objects as go
import numpy as np
t = np.linspace(0, 4*np.pi, 50)
t2 = np.pi * np.arange(5)
fig = go.Figure(go.Scatter(x=t, y=np.sin(t), mode='lines'))
fig.add_trace(go.Scatter(x=t2, y=np.sin(t2), mode='markers'))
first_annotations=[
go.layout.Annotation(
x=point,
y=np.sin(point),
xref="x",
yref="y",
text="dict Text",
align='center',
showarrow=False,
yanchor='bottom',
textangle=90) for point in t2]
second_annotations=[
go.layout.Annotation(
x=point,
y=np.cos(point),
xref="x",
yref="y",
text="Other dict Text",
align='center',
showarrow=False,
yanchor='bottom',
textangle=90) for point in t2]
first_annotations.extend(second_annotations)
fig.update_layout(annotations=first_annotations
)
fig.show()
Output
Example with 2 sets of annotations:
Question
How can one add an identifier to an Annotation object in plotly dash?
Approach
I looked through the documentation of plotly.graph_objs.layout.Annotation: however, I did not find an "identifier" (like) property.

You can create a super class called NamedAnnotation with the desired name property (as well as a getName convenience function if you like):
from plotly.graph_objs.layout import Annotation
class NamedAnnotation(Annotation):
def __init__(self, name, **kwargs):
super().__init__(self, **kwargs)
self.name = name
def getName(self):
return self.name
Then you can use NamedAnnotations instead of go.Layout.Annotation and pass the new parameter name:
first_names = [f"first_name_{i}" for i in range(len(t2))]
second_names = [f"second_name_{i}" for i in range(len(t2))]
first_annotations=[
NamedAnnotation(
name=name, # тна new parameter
x=point,
y=np.sin(point),
xref="x",
yref="y",
text="dict Text",
align='center',
showarrow=False,
yanchor='bottom',
textangle=90) for name,point in zip(first_names,t2)]
second_annotations=[
NamedAnnotation(
name=name,
x=point,
y=np.cos(point),
xref="x",
yref="y",
text="Other dict Text",
align='center',
showarrow=False,
yanchor='bottom',
textangle=90) for name,point in zip(second_names,t2)]
first_annotations.extend(second_annotations)
If you need to get the name of any of your annotations at a later point in the program, you can do something like:
first_annotations[0].getName()
And this will return 'first_name_0'.

Related

seaborn: 'rows' and 'x_vars' at the same time

I want a seaborn multiplot that varies the x-axis variable by column, but varies the subset of data shown by row. I can use PairGrid to vary the variables graphed, and I can use FacetGrid to vary the subsets graphed, but I don't see any facility to do both at once, even though it seems like a natural extension.
Is there a way to do this in seaborn currently? Or is this something that would need a feature request?
Here's a mockup of what I'm trying to do:
label:A
y:Y
(plot M vs Y where label == A)
(plot N vs Y where label == A)
label:B
y:Y
(plot M vs Y where label == B)
(plot N vs Y where label == B)
x:M
x:N
I'd also take the transpose of this scheme :)
This is not a feature that directly exists in seaborn (though it is likely to become one at some point).
That said, FacetGrid and PairGrid just instantiate different mappings between a dataframe and a figure (modulo the diagonal plots in PairGrid and a few features here and there). So a plot that is naturally expressed using one tool can generally made with the other, given a data reshaping.
So you could do something like
x_var = "body_mass_g"
col_var = "sex"
hue_var = "species"
y_vars = ["bill_length_mm", "bill_depth_mm"]
(
df
.melt([x_var, col_var, hue_var], y_vars)
.pipe(
(sns.relplot, "data"),
x=x_var,
y="value",
hue=hue_var,
col=col_var,
row="variable",
facet_kws=dict(sharey="row"),
height=3.5,
)
)
There's your plot, basically, but the labels are a little confusing. Let's improve that:
g = (
df
.melt([x_var, col_var, hue_var], y_vars)
.pipe(
(sns.relplot, "data"),
x=x_var,
y="value",
hue=hue_var,
col=col_var,
row="variable",
facet_kws=dict(sharey="row", margin_titles=True),
height=3.5,
)
.set_titles(col_template="{col_var} = {col_name}", row_template="")
)
for (row_name, _), ax in g.axes_dict.items():
ax.set_ylabel(row_name)
A little more cumbersome, but also not so hard to wrap up into a pretty general function:
def paired_column_facets(
data: pd.DataFrame, y_vars: list[str], other_vars: dict[str, str], **kwargs
) -> FacetGrid:
g = (
df
.melt(list(other_vars.values()), y_vars)
.pipe(
(sns.relplot, "data"),
**other_vars,
y="value",
row="variable",
facet_kws=dict(sharey="row", margin_titles=True),
**kwargs,
)
.set_titles(col_template="{col_var} = {col_name}", row_template="")
)
for (row_name, _), ax in g.axes_dict.items():
ax.set_ylabel(row_name)
return g

I can't figure out how to make my Plotly charts show whole numbers only

image of plotly chart
Hello, I'm really struggling to figure out how to format the axes on this chart. I've gone through the documentation and tried all sorts of different formatting suggestions from here and elsewhere but really not getting it. As you can see, the bottom chart has a .5 number, I want that to be skipped altogether and only have whole numbers along the axis.
I've seen ,d as a tickformat option to do this in about every answer, but I can't get that to work or I'm not seeing how to apply it to the second chart.
Can anyone with some Plotly charting experience help me out?
Here's the pertinent code:
def create_chart():
#Put data together into an interactive chart
fig.update_layout(height=500, width=800, yaxis_tickprefix = '$', hovermode='x unified', xaxis_tickformat =',d',
template=symbol_template, separators=".", title_text=(df.columns[DATA_COL_1]) + " & Units 2015-2019"
)
I believe what is happening is that the xaxis_tickformat parameter is affecting only the first subplot, but not the second one. To modify the formatting for each subplot, you can pass a dictionary with the tickformat parameter to yaxis, yaxis2, .... and so on for however many subplots you have (in your case, you only have 2 subplots).
import pandas as pd
from plotly.subplots import make_subplots
import plotly.graph_objects as go
## recreate the df
df = pd.DataFrame({'Year':[2015,2016,2017,2018,2019],
'Sales':[8.8*10**7,8.2*10**7,8.5*10**7,9.1*10**7,9.6*10**7],
'Units':[36200,36500,36900,37300,37700]})
def create_chart():
#Put data together into an interactive chart
fig = make_subplots(rows=2, cols=1)
fig.add_trace(go.Scatter(
x=df.Year,
y=df.Sales,
name='Sales',
mode='lines+markers'
), row=1, col=1)
fig.add_trace(go.Scatter(
x=df.Year,
y=df.Units,
name='Units',
mode='lines+markers'
), row=2, col=1)
fig.update_layout(
title_x=0.5,
height=500,
width=800,
yaxis_tickprefix = '$',
hovermode='x unified',
xaxis_tickformat =',d',
## this will change the formatting for BOTH subplots
yaxis=dict(tickformat ='d'),
yaxis2=dict(tickformat ='d'),
# template=symbol_template,
separators=".",
title={
'text':"MCD Sales & Units 2015-2019",
'x':0.5
}
)
fig.show()
create_chart()

Plotly: How to re-position ticktext close to the zero-line?

Plotly seems to place the x and y ticks on the periphery of a graph by default however I have a graph which its y zero-line is in the middle. I'd like for the tick to be right beside the y axis instead but I fail to find a command to make that happen. Is it at all possible?
To my knowledge there is no direct way to do this at the moment. But if you use the correct combination of xaxis zeroline and annotations for the y-values, you can get this:
The code snippet below takes the max and min for the y-values, builds a list of that interval with a defined number of steps, and inserts annotations for those steps. It's not very elegant, it fails a bit when you try to zoom, but it works. At least now you don't have to try a similar approach yourself.
Complete code:
import plotly.graph_objects as go
import numpy as np
x = np.arange(-4,5)
y=x**3
yticks=list(range(y.min(), y.max(), 14))
#yticks.append(y.max())9
fig = go.Figure(data=go.Scatter(x=x, y=y))
fig.update_layout(xaxis = dict(zeroline=True,
zerolinecolor='firebrick',
title_text = "x zeroline",
title_standoff = 4))
fig.update_layout(yaxis = dict(zeroline=True,
showgrid=False,
title_font = {"size": 20},
tickfont= dict(color='rgba(0,0,0,0)')))
for i, m in enumerate(yticks):
fig.add_annotation(dict(font=dict(color='firebrick',size=12),
x=0.066,
y=yticks[i],
showarrow=False,
text=str(yticks[i])+' -',
textangle=0,
xanchor='right',
xref="x",
yref="y"))
fig.show()

plotly: How to add text to existing figure?

Is it possible to add some text on the same html file as my plotly graph?
For example :
This is the code that generates a graph :
data = pd.read_csv('file.csv')
data.columns = ['price', 'place', 'date']
fig = px.scatter(data, x = "place", y = "price", )
fig.write_html("done.html")
This graph will generate a pyplot graph in an html file and I want to add some simple text (such as a conclusion line explaning the graph) under the graph.
This is an example of the output I would like:
ly
You can use fig.update_layout(margin=dict()) to make room for an explanation, and then fig.add_annotation() to insert any text you'd like below the figure utself to get this:
Complete code:
import plotly.graph_objects as go
import numpy as np
x = np.arange(-4,5)
y=x**3
yticks=list(range(y.min(), y.max(), 14))
#yticks.append(y.max())9
# build figure
fig = go.Figure(data=go.Scatter(x=x, y=y))
# make space for explanation / annotation
fig.update_layout(margin=dict(l=20, r=20, t=20, b=60),paper_bgcolor="LightSteelBlue")
# add annotation
fig.add_annotation(dict(font=dict(color='yellow',size=15),
x=0,
y=-0.12,
showarrow=False,
text="A very clear explanation",
textangle=0,
xanchor='left',
xref="paper",
yref="paper"))
fig.show()

Python matplotlib changing the major unit scale of the x-axis

I have a figure that isn't showing the correct dates on the x-axis. Possibly because there are too many observations to show and it results in not showing any. I am not sure if I should use the set_xticks function, but it gives me the attribute error
AttributeError: module 'matplotlib.pyplot' has no attribute 'set_xticks'
My current code is this:
def plot_w(dataframe,ticker,benchmark):
# a. Printing highest observed values and corresponding date
max1 = data_df.loc[:, ticker].max()
max2 = data_df.loc[:, benchmark].max()
max1_date = data_df[data_df[ticker] == max1]['Date'].values[0]
max2_date = data_df[data_df[benchmark] == max2]['Date'].values[0]
print("The highest adjusted close price observed at: \n", ticker, ":", max1.round(2), "USD on the date ", max1_date,
"\n", benchmark, ":", max2.round(2), "USD on the date", max2_date)
# b. Setting up plot based on dropdown input
I = data_df.columns == ticker
mpl_figure = dataframe.loc[:, ['Date',ticker,benchmark]]
mpl_figure.plot(x='Date', y=[ticker,benchmark], style=['-b','-k'], figsize=(10, 5), fontsize=11, legend='true', linestyle = '-')
plt.ylabel("USD",labelpad=5)
plt.locator_params(axis='x', nbins=20)
title = "Adjusted close prices for " + ticker + " and " + benchmark
plt.title(title)
plt.set_xticks(data_df['Date'].values) # Code fails here
# c. Creating the widget for the plot
widgets.interact(plot_w,
dataframe = widgets.fixed(data_df),
ticker = widgets.Dropdown(
options=data_df.columns,
value='ATVI',
description='Company 1:',
disabled=False,
),
benchmark = widgets.Dropdown(
options=data_df.columns,
value='AAPL',
description='Company 2:',
disabled=False,
)
)
The figure looks like this:
set_xticks is for an axis, if you are setting ticks for a figure it's
plt.xticks(data_df['Date'].values)
Alternatively, you can try the following inside your function working on an axis object. The idea is to first create an axis instance ax, and then pass it to the plot command. Later use it to set the x-ticks.
I = data_df.columns == ticker
fig, ax = plt.subplots(figsize=(10, 5))
mpl_figure = dataframe.loc[:, ['Date',ticker,benchmark]]
mpl_figure.plot(x='Date', y=[ticker,benchmark], style=['-b','-k'], ax=ax, fontsize=11, legend='true', linestyle = '-')
plt.ylabel("USD",labelpad=5)
plt.locator_params(axis='x', nbins=20)
title = "Adjusted close prices for " + ticker + " and " + benchmark
plt.title(title)
ax.set_xticks(data_df['Date'].values)
The module matplotlib.pyplot has no set_xticks function, but an xticks function instead. (link to doc). However, the class object matplotlib.axes.Axes does have set_xticks (link to doc).
The easiest fix in your code is thus
plt.xticks(data_df['Date'].values)
Side note: I am not entirely sure why matplotlib.pyplot keeps around two functions with (almost) the same effect but different names. I guess allowing it to be called from either the module or the object is an imitation of MATLAB, but in MATLAB the function name is the same.

Categories

Resources