Search code examples
pythonplotlyannotationsplotly-dashsuper

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


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: enter image description here

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.


Solution

  • 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'.