Search code examples
pythongraphplotly

Plotly: How to create a pointer when 2 lines close over each other


enter image description here

How do I create a pointer at the intersection between two lines?


Solution

  • The only difference between your question and Plotly: How to find and annotate the intersection point between two lines? seems to be that your case has multiple intersectoins. You can still use the very same approach described here and then take into account multiple annotations for multiple intersections with:

    for x, y in zip(x,y):
        fig.add_annotation(x=x, y=y,
                           text = 'lines intersect at x = ' + str(round(x, 2)) + ' and y = ' + str(round(y, 2)),
                           font=dict(family="sans serif", size=18, color="black"),
                            ax=0,
                            ay=-100,
                            showarrow=True,
                            arrowhead=1)
    

    enter image description here

    Complete code:

    import pandas as pd
    import plotly.graph_objects as go
    import numpy as np
    # import dash
    
    
    # sample dataframe
    df = pd.DataFrame()
    df['x'] = np.arange(4) +1
    df['y1'] = df['x']**3
    df['y2'] = [10+val**2.2 for val in df['x']]
    
    df2 = pd.DataFrame({'x':[5,6,7],
                        'y1':[60, 50, 10],
                        'y2':[26,20,19]})
    
    df = pd.concat([df, df2])
    
    # intersection stuff
    def _rect_inter_inner(x1,x2):
        n1=x1.shape[0]-1
        n2=x2.shape[0]-1
        X1=np.c_[x1[:-1],x1[1:]]
        X2=np.c_[x2[:-1],x2[1:]]
        S1=np.tile(X1.min(axis=1),(n2,1)).T
        S2=np.tile(X2.max(axis=1),(n1,1))
        S3=np.tile(X1.max(axis=1),(n2,1)).T
        S4=np.tile(X2.min(axis=1),(n1,1))
        return S1,S2,S3,S4
    
    def _rectangle_intersection_(x1,y1,x2,y2):
        S1,S2,S3,S4=_rect_inter_inner(x1,x2)
        S5,S6,S7,S8=_rect_inter_inner(y1,y2)
    
        C1=np.less_equal(S1,S2)
        C2=np.greater_equal(S3,S4)
        C3=np.less_equal(S5,S6)
        C4=np.greater_equal(S7,S8)
    
        ii,jj=np.nonzero(C1 & C2 & C3 & C4)
        return ii,jj
    
    def intersection(x1,y1,x2,y2):
    
        ii,jj=_rectangle_intersection_(x1,y1,x2,y2)
        n=len(ii)
    
        dxy1=np.diff(np.c_[x1,y1],axis=0)
        dxy2=np.diff(np.c_[x2,y2],axis=0)
    
        T=np.zeros((4,n))
        AA=np.zeros((4,4,n))
        AA[0:2,2,:]=-1
        AA[2:4,3,:]=-1
        AA[0::2,0,:]=dxy1[ii,:].T
        AA[1::2,1,:]=dxy2[jj,:].T
    
        BB=np.zeros((4,n))
        BB[0,:]=-x1[ii].ravel()
        BB[1,:]=-x2[jj].ravel()
        BB[2,:]=-y1[ii].ravel()
        BB[3,:]=-y2[jj].ravel()
    
        for i in range(n):
            try:
                T[:,i]=np.linalg.solve(AA[:,:,i],BB[:,i])
            except:
                T[:,i]=np.NaN
    
    
        in_range= (T[0,:] >=0) & (T[1,:] >=0) & (T[0,:] <=1) & (T[1,:] <=1)
    
        xy0=T[2:,in_range]
        xy0=xy0.T
        return xy0[:,0],xy0[:,1]
    
    # plotly figure
    x,y=intersection(np.array(df['x'].values.astype('float')),np.array(df['y1'].values.astype('float')),
                     np.array(df['x'].values.astype('float')),np.array(df['y2'].values.astype('float')))
    
    fig = go.Figure(data=go.Scatter(x=df['x'], y=df['y1'], mode = 'lines'))
    fig.add_traces(go.Scatter(x=df['x'], y=df['y2'], mode = 'lines'))
    fig.add_traces(go.Scatter(x=x, y=y,
                              mode = 'markers',
                              marker=dict(line=dict(color='black', width = 2),
                                          symbol = 'diamond',
                                          size = 14,
                                          color = 'rgba(255, 255, 0, 0.6)'),
                             name = 'intersect'),
                  )
    
    for x, y in zip(x,y):
        fig.add_annotation(x=x, y=y,
                           text = 'lines intersect at x = ' + str(round(x, 2)) + ' and y = ' + str(round(y, 2)),
                           font=dict(family="sans serif", size=18, color="black"),
                            ax=0,
                            ay=-100,
                            showarrow=True,
                            arrowhead=1)
    
    fig.show()