Black Lives Matter. Please consider donating to Black Girls Code today.

Issues with multiple scatter traces and hover text

I’m using plotly and dash to create and render two scatter plots, one in 3D and the other in 2D, similar to the network graph examples. I’m using updatemenus buttons for the update method to toggle visibility of the markers/lines to go between the 2D and 3D representations.

I’m having issues with the hover text. I currently have it working for the 3D plot, but it will not work for the 2D plot when it is toggled. Hovering over the points in the 3D scatter, I am able to see the hover text. However, when the 2D scatter is visible hover does not work.

Here’s a few interesting things I’ve noticed:

  • the order of the traces added to the Data element changes the hover behavior
  • without the opacity=0.99 setting I’ve put in with when defining the marker elements for the scatters, the hover doesn’t seem to work correctly: no matter which element I hover over, the hover text is only shown for one of the markers
  • using box select to zoom in the 2D mode doesn’t work either

My environment is based on Debian testing.

Python Version: 3.6.5rc1
igraph Version: 0.7.1
Plotly Version: 2.5.1
Dash Version: 0.21.1

I’ve tried loading the dash page in several browsers (chrome, firefox), with similar behaviors in each.

I’ve distilled the problem down to a simple self-contained example. Here’s the code:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
import platform
import string
import random
import igraph as ig
import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly
import plotly.plotly as py
import plotly.graph_objs as go

##############################################################################
def randStr(size=6, chars=string.ascii_uppercase + string.digits):
  return ''.join(random.choice(chars) for x in range(size))

##############################################################################
def getPlotPoints(NetGraph, ThreeDeeMode):
  if ThreeDeeMode:
    layt = NetGraph.layout('fr_3d', dim=3)
  else:
    layt = NetGraph.layout('fr', dim=2)

  vertexCount = len(NetGraph.vs)
  Xn = [layt[k][0] for k in range(vertexCount)]
  Yn = [layt[k][1] for k in range(vertexCount)]
  Xe = []
  Ye = []  
  if ThreeDeeMode:
    Zn = [layt[k][2] for k in range(vertexCount)]
    Ze = []
  for ePos in [edge.tuple for edge in NetGraph.es]:
    Xe += [layt[ePos[0]][0],layt[ePos[1]][0], None]
    Ye += [layt[ePos[0]][1],layt[ePos[1]][1], None] 
    if ThreeDeeMode:
      Ze += [layt[ePos[0]][2],layt[ePos[1]][2], None]

  if ThreeDeeMode:
    return (Xn, Yn, Zn, Xe, Ye, Ze)
  else: 
    return (Xn, Yn, Xe, Ye)

##############################################################################
def main():
  print(f"Python Version: {platform.python_version()}")
  print(f"igraph Version: {ig.__version__}")
  print(f"Plotly Version: {plotly.__version__}")
  print(f"Dash Version: {dash.__version__}")

  # graph construction
  netGraph = ig.Graph.GRG(10, 0.8)
  nodeLabels = [randStr(6) for vx in netGraph.vs]
  (flatPointsX, flatPointsY, flatLinesX, flatLinesY) = getPlotPoints(netGraph, False)
  (cubePointX, cubePointsY, cubePointsZ, cubeLinesX, cubeLinesY, cubeLinesZ) = getPlotPoints(netGraph, True)
      
  # create 3d and 2d network graphs
  netTrace1 = go.Scatter3d(name="cubeLines",
                           x=cubeLinesX,
                           y=cubeLinesY,
                           z=cubeLinesZ,
                           mode='lines',
                           line=dict(color='rgb(210,210,210)',
                                     width=2),
                           hoverinfo='none',
                           visible=True)

  netTrace2 = go.Scatter3d(name="cubePoints",
                           x=cubePointX,
                           y=cubePointsY,
                           z=cubePointsZ,
                           mode='markers',
                           marker=dict(color='rgb(144, 198, 149)',
                                       size=10,
                                       symbol='dot',
                                       line=dict(color='rgb(108, 122, 137)',
                                                 width=0.2),
                                       opacity=0.99),
                           text=nodeLabels,
                           hoverinfo='text',
                           visible=True)                

  netTrace3 = go.Scatter(name="flatLines",
                         x=flatLinesX,
                         y=flatLinesY,
                         mode='lines',
                         line=dict(color='rgb(210,210,210)',
                                   width=2),
                         hoverinfo='none',
                         visible=False)    

  netTrace4 = go.Scatter(name="flatPoints",
                         x=flatPointsX,
                         y=flatPointsY,
                         mode='markers',
                         marker=dict(color='rgb(144, 198, 149)',
                                     size=10,
                                     symbol='dot',
                                     line=dict(color='rgb(108, 122, 137)',
                                               width=0.2),
                                     opacity=0.99),
                         text=nodeLabels,
                         hoverinfo='text',
                         visible=False)    

  # layout
  updatemenus = list([
      dict(type="buttons",
           active=0,
           buttons=list([   
              dict(label = '3D',
                   method = 'update',
                   args = [{'visible': [False, True, False, True]}]),
              dict(label = '2D',
                   method = 'update',
                   args = [{'visible': [True, False, True, False]}])
          ]),
      )
  ])  

  graphWidth = 1280
  graphWeight = 960
  graphLayout = go.Layout(showlegend=False,
                          autosize=True,
                          width=graphWidth,
                          height=graphWeight,
                          xaxis = dict(visible=False),
                          yaxis = dict(visible=False),
                          scene = dict(xaxis = dict(visible=False),
                                       yaxis = dict(visible=False),
                                       zaxis = dict(visible=False)),
                          hovermode='closest',
                          updatemenus=updatemenus)

  netGraphData = go.Data([netTrace3, netTrace2, netTrace4, netTrace1])    

  netFig = go.Figure(data=netGraphData, layout=graphLayout)

  # serve HTML
  app = dash.Dash(__name__)
  server = app.server
  app.css.config.serve_locally = True
  app.scripts.config.serve_locally = True
  app.layout = html.Div([
    dcc.Graph(id='networkmap',figure = netFig)])

  app.run_server(debug=False, host='127.0.0.1')

if __name__ == '__main__':
  main()

Thanks in advance for any help or advice rendered!