Issues with Paper-Positioned Annotations

I need to position some custom text on my charts. These notes aren’t tied to a specific trace or point, but to the chart as a whole. These may, for example, need to be positioned in the upper right and bottom left of the entire chart.

After scouring the docs, I found the paper-relative annotation positioning options. However, these positions (0, 0.5, 1, etc.) are all relative to the central plot area, and don’t take the size of the legend, axis labels, axis titles, etc. into account.

This makes it hard (impossible?) to reliably position text on the chart as a whole, since the size of the legend, axis labels, etc. is not constant. I can manually tweak the numbers for a specific known chart (e.g. make the annotation x/y values things like 1.09 or -.06, etc.), but this isn’t an option for me since we have arbitrary sets of dynamically generated charts.

Is there some trick to positioning things which take into account the variable sizes of things beyond the plot area? Or, if not, is there some trick to determining the relative size of the entire chart vs. the plot area, which can be used to dynamically adjust the positions of paper annotations?

2 Likes

Hey there,
Instead of using the autogenerated space you could reduce or remove the margin (https://plot.ly/javascript/reference/#layout-margin) or set the domain of the x and y axes: https://plot.ly/javascript/reference/#layout-xaxis-domain to be consistent across the charts and thereby reduce some of that autosized variability. Let us know if a sizing example would be helpful for you.

1 Like

Thanks Chelsea, but I don’t think either of those are options for me:

First, omitting the margin leaves it to Plotly to try and size the plot, and Plotly’s basic “auto margin” logic doesn’t do a very good job of using the available space.

Second, the axis domain doesn’t seem to help either, since all it seems to do is let you reduce the width and height of the axes (the range is only between 0 and 1).

Finally, neither of these really help with taking into account the size of the axis titles, plot title, or the size of the legend.

I have a JSFiddle set up where I was testing out these various options at http://jsfiddle.net/brian428/p5rvsm7n/. If you can think of anything that would help, please let me know. Again, please keep in mind that we have multiple dynamically created charts, so manually tweaking the annotation position isn’t an option either. I really need a way to position them that works regardless of the size of the titles or legend on any specific chart. Let me know what you think.

Thanks.

1 Like

I know this thread is old but I’m finding this to still be a nightmare in the latest plotly/dash releases. Has anyone developed a method for fixing annotations at constant positions in the overall chart space? The best I’ve been able to do so far is set a variable y position based on the string length of my xaxis labels.

y = -0.05 * data[xaxis_column].astype(str).str.len().max()

Seems like an “ideal” solution would be another reference system like xref/yref=‘figure’ from [0, 1] for the entire figure area regardless of margins. It would be nice to be able to specify margins in that space as well, eg. margin_b=0.05 vs. absolute pixel values.

I too would love to find a way to easily position annotations and images relative to the edges of the entire figure, not just the ‘paper’ region. Here’s a simple example that illustrates why I’d love this option.

Creating a simulated dataset:

import pandas as pd
import plotly.express as px
df_placeholder_data = pd.DataFrame(
    {'X_Val': {0: 'A', 1: 'B', 2: 'C', 3: 'D'},
 'E': {0: 1, 1: 2, 2: 3, 3: 4},
 'Y_Val_1': {0: 73, 1: 24, 2: 39, 3: 85},
 'Y_Val_2': {0: 3, 1: 6, 2: 9, 3: 12},
 'G': {0: 4, 1: 8, 2: 12, 3: 16}})

Let’s say that I want to place an annotation at the bottom right corner of this chart. I can do so by manually setting the x and y coordinates when calling add_annotation():

fig = px.bar(df_placeholder_data, x = 'X_Val', y = 'Y_Val_1',
            width = 700, height = 450)
fig.update_layout(margin_pad = 3,
                  yaxis_ticksuffix = '%',
                 yaxis_showticksuffix = 'last')
fig.add_annotation(text = '<b>Sample Annotation</b>', x = 1.13, y = -0.2,
                   yanchor = 'top',
                   xanchor = 'right',
                  showarrow = False,
                  xref = 'paper',
                  yref = 'paper')
fig

The x coordinate of 1.13 allows that annotation to be placed right at the bottom right of the chart. However, the following chart has a legend, which makes the space between the right side of the chart and the edge of the figure a little larger. As a result, the annotation is no longer right at the bottom right:

fig = px.bar(df_placeholder_data, x = 'X_Val', y = ['Y_Val_1', 'Y_Val_2'],
            width = 700, height = 450,
fig.update_layout(margin_pad = 3,
                  yaxis_ticksuffix = '%',
                 yaxis_showticksuffix = 'last')
fig.add_annotation(text = '<b>Sample Annotation</b>', x = 1.13, y = -0.2,
                   yanchor = 'top',
                   xanchor = 'right',
                  showarrow = False,
                  xref = 'paper',
                  yref = 'paper')
fig            barmode = 'group')

If there were some way to draw the annotation relative to the bottom right corner of the figure, this problem would disappear.

I’m not 100% sure, but doesn’t using…

xref=f"x domain", yref=f"y domain"

… solve this problem?

The bit of sample code below uses this with add_shape, but I think it should work with add_annotation as well.

import plotly.graph_objects as go
from plotly.subplots import make_subplots
nrows, ncols = 2,3
fig = make_subplots(rows=nrows, cols=ncols)
for col in range(1, ncols + 1):
    for row in range(1, nrows + 1):
        fig.add_trace(go.Line(x=[0.0, 10.0], y=(2.0, 11.0)), row=row, col=col)
        index = ncols*(row-1) + col  if row > 1 or col > 1 else ""
        fig.add_shape(type="line", 
            x0=0.7, y0=0.0, x1=0.7, y1=1.0,
            xref=f"x{index} domain", yref=f"y{index} domain",
        )
        fig.add_shape(type="circle", 
            x0=0.5, y0=0.5, x1=0.6, y1=0.6,
            xref=f"x{index} domain", yref=f"y{index} domain",
        )
       
fig.show()

@davidharris Unfortunately, I’m getting similar results when using ‘x domain’ and ‘y domain’ in place of ‘paper.’ Here’s how the two charts got rendered after I replaced ‘paper’ with the domain references:


I think there is no way with paper or domain since they only use the area of ​​the graph as a reference for 100% (0-1), for this case you would have to create a conversion function that uses the entire area of ​​the container using a simple rule of three, the dimensions of the container are stored in the width and height properties of _fullLayout or by obtaining the container properties through the DOM

1 Like