Add custom legend (markers color) to plotly python

I try to visualize trading algo with plotly. I have two dataframes: one with close prices and another one with transactions, that my algo performs. Green dots should be for buy action, red for sell. What I also want is to have a menu to be able to choose what stock to analyse. For now everything works by drawing actions over the close price.

The question is: how to add a legend about dot color (green for buy, red for sell) in a certain position?

My code:

from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
import plotly.graph_objs as go
import pandas as pd
init_notebook_mode(connected=True)

feed = pd.DataFrame({'AAPL': [100, 120, 130, 120, 150],
                     'MSFT': [80, 70, 90, 100, 99]}, index = pd.date_range('2018-01-01', '2018-01-05'))

trans = pd.DataFrame({'Symbol': ['AAPL', 'AAPL', 'MSFT', 'MSFT'],
                        'Amount': [2, 1, 3, 1],
                        'Action': ['buy', 'sell', 'buy', 'sell'],
                        'Price': [120, 150, 70, 100],
                        'Date': [pd.to_datetime(i) for i in ['2018-01-02', '2018-01-05', '2018-01-02', '2018-01-04']]
                       })

data = []
all_but = dict(label = 'All',
               method = 'update',
                  args = [{'visible': [True] * len(feed.columns.tolist())},
                          {'title': 'all'}
                           ])
buttonlist = [all_but]
for col in feed.columns.tolist():
    stock = go.Scatter(x=feed.index,
                       y=feed.loc[:, col],
                       name=col
                      )
    #actions
    success = trans.loc[trans.Symbol == col]
    actions = go.Scatter(x=success.Date,
                         y=success.Price,
                         name='',
                         mode = 'markers',
                         text = ['Amount ' + str(amount) for amount in success.Amount],
                         hoverinfo = 'text',
                         showlegend=False,
                         marker = dict(
                             size=10,
                             color = ['green' if i == 'buy' else 'red' for i in success.Action]

                         )
                        )
    data.append(stock)
    data.append(actions)
    button = dict(label = col,
                  method = 'update',
                  args = [{'visible': [col == i for i in feed.columns.tolist() for _ in range(2)]},
                          {'title': 'title' + col}
                           ])
    buttonlist.append(button)

updatemenus = list([
    dict(active=-1,
         buttons=buttonlist,
    )
])
layout = dict(title='Feed', showlegend=True,
              updatemenus=updatemenus)

fig = dict(data=data, layout=layout)
iplot(fig)

P.S.: I will appreciate advices on simpler ways to do the same as I am new to plotly. (same on stackoverflow: https://stackoverflow.com/questions/54705261/add-custom-legend-markers-color-to-plotly-python )

1 Like

Hi @qpoToH,

This idea of displaying multiple legends for different characteristics of the traces isn’t something that plotly.js support natively. In most cases, legends entries and traces have a 1 to 1 mapping. With that in mind, here’s an approach/work-around to getting what I think you’re looking for.

from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
import plotly.graph_objs as go
import pandas as pd
init_notebook_mode(connected=True)

feed = pd.DataFrame({'AAPL': [100, 120, 130, 120, 150],
                     'MSFT': [80, 70, 90, 100, 99]}, index = pd.date_range('2018-01-01', '2018-01-05'))

trans = pd.DataFrame({'Symbol': ['AAPL', 'AAPL', 'MSFT', 'MSFT'],
                        'Amount': [2, 1, 3, 1],
                        'Action': ['buy', 'sell', 'buy', 'sell'],
                        'Price': [120, 150, 70, 100],
                        'Date': [pd.to_datetime(i) for i in ['2018-01-02', '2018-01-05', '2018-01-02', '2018-01-04']]
                       })

data = []

# Add "All" button
all_but = dict(label = 'All',
               method = 'update',
                  args = [{'visible': [True]},
                          {'title': 'all'}
                           ])

buttonlist = [all_but]
for i, col in enumerate(feed.columns.tolist()):
    stock = go.Scatter(x=feed.index,
                       y=feed.loc[:, col],
                       name=col,
                       legendgroup='stock'
                      )
    # actions
    success = trans.loc[trans.Symbol == col]
    
    # Buy
    success_buy = success[success.Action == 'buy']
    action_buy = go.Scatter(x=success_buy.Date,
                         y=success_buy.Price,
                         name='Buy',
                         mode = 'markers',
                         text = ['Amount ' + str(amount) for amount in success_buy.Amount],
                         hoverinfo = 'text',
                         showlegend=False,
                         marker = dict(
                             size=10,
                             color = 'green'
                         ),
                         legendgroup='Buy',
                        )
    
    # Sell
    success_sell = success[success.Action == 'sell']
    action_sell = go.Scatter(x=success_sell.Date,
                         y=success_sell.Price,
                         name='Sell',
                         mode = 'markers',
                         text = ['Amount ' + str(amount) for amount in success_sell.Amount],
                         hoverinfo = 'text',
                         showlegend=False,
                         marker = dict(
                             size=10,
                             color = 'red'
                         ),
                         legendgroup='Sell',
                        )
    data.append(stock)
    data.append(action_buy)
    data.append(action_sell)
    button = dict(label = col,
                  method = 'update',
                  args = [{'visible': [col == i for i in feed.columns.tolist() for _ in range(3)] + [True, True]},
                          {'title': 'title' + col}
                           ])
    buttonlist.append(button)

# Add single buy and sell traces for legend
data.append(go.Scatter(x=[None], y=[None], mode='markers',
                       marker=dict(size=10, color='green'),
                       legendgroup='Buy', showlegend=True, name='Buy'))

data.append(go.Scatter(x=[None], y=[None], mode='markers',
                       marker=dict(size=10, color='red'),
                       legendgroup='Sell', showlegend=True, name='Sell'))
    
updatemenus = list([
    dict(active=-1,
         buttons=buttonlist,
    )
])
layout = dict(title='Feed', showlegend=True,
              updatemenus=updatemenus)

fig = dict(data=data, layout=layout)
iplot(fig)

A couple of notes:

  1. I split the buy and sell markers into two traces for each stock, that way they can show up separately on the legend.
  2. I placed all of the “Buy” traces in a buy legendgroup and all of the “Sell” traces in a sell legendgroup. This makes sure that when you click on the legend entries to hide/show, all of the “Buy”/“Sell” traces show and hide together.
  3. I added two traces at the very end with x and y of [None], one for buy and one for sell, added them to the corresponding legend group, and gave them showlegend=True. These traces don’t show up on the plot, but they do show up on the legend. And you can think of them as representing all of the other traces in the legend group in the legend.

Hope that helps!
-Jon

7 Likes