pjw
pjw

Reputation: 2325

Bokeh second legend showing scatter radius

I have a Bokeh scatter plot (bokeh==1.0.4) using a polar projection. The radius of the points corresponds to RadiusSize in the ColumnDataSource, and the color corresponds to DepthClass.

I created a second "dummy" scatter plot to get a legend that corresponds to fill_color (depth_legend). I would like a second legend corresponding to the radius. The RadiusSize data is integers ranging 1 through 5. I am looking for a second legend very similar to this matplotlib legend (see accepted answer).

Here is my relevant plotting code:

def make_scatter(event_data):
  '''
  Create a polar scatter figure

  Parameters
  ----------
  event_data: Pandas dataframe

  Returns
  -------
  p: Bokeh Figure object
  '''

  cds = create_cds(event_data)

  p = figure(
      title="",
      name="scatter_fig",
      width=600, height=600,
      tools='',
  )

  # Project data into polar coordinates
  # customjs from bryevdv (https://github.com/bokeh/bokeh/issues/657)
  polarx = CustomJSTransform(args=dict(source=cds), v_func='''
  const new_xs = new Array(source.data.elev_project_vectors.length)
  for(var i = 0; i < new_xs.length; i++) {
      new_xs[i] = source.data.elev_project_vectors[i] * Math.sin(source.data.elev_angles[i] )
  }
  return new_xs
  ''')

  polary = CustomJSTransform(args=dict(source=cds), v_func='''
  const new_ys = new Array(source.data.elev_project_vectors.length)
  for(var i = 0; i < new_ys.length; i++) {
      new_ys[i] = source.data.elev_project_vectors[i] * Math.cos(source.data.elev_angles[i] )
  }
  return new_ys
  ''')

  event_scatter = p.scatter(
    x=transform('elev_project_vectors', polarx),
    y=transform('elev_project_vectors', polary),
    radius='RadiusSize',
    fill_color='DepthClass', fill_alpha=1.0,
    name='event_scatter',
    source=cds)

  # This is a dummy glyph just to have consistent colors for a custom legend
  event_scatter_dummy = p.scatter(
    x=[1,2,3],
    y=[1,2,3],
    radius=0,
    fill_color=['green','yellow','red'], fill_alpha=1.0,
    name='event_scatter_dummy',
    )

  depth_legend = Legend(items=[
    LegendItem(label='crown < 12"', renderers=[event_scatter_dummy], index=0),
    LegendItem(label='crown 12-44"', renderers=[event_scatter_dummy], index=1),
    LegendItem(label='crown > 44"', renderers=[event_scatter_dummy], index=2),
    ])
  p.add_layout(depth_legend)

  return p

Here is a screenshot of the current Bokeh figure with the single legend corresponding to fill_color: enter image description here Ideally, the second legend will have labels (R1,R2,R3,R4,R5) and a corresponding range of increasing radius circles. How can I get this second legend?

Upvotes: 2

Views: 1255

Answers (1)

pjw
pjw

Reputation: 2325

I was able to hack this together by creating a dummy invisible circle, then creating and stacking together five separate Legend instances that render the dummy circle. Each legend requires custom positioning using location and label_standoff to line up correctly. I then cycle through the circle glyphs and adjust their size so they correspond to the radius of the plotted data.

Not an ideal solution since the legend is not tied to the actual data, but it gets the job done visually.

Here is the Bokeh code:

  event_radius_dummy_1 = p.circle(
    1,1,
    radius=0,
    fill_alpha=0.0, line_color='black', 
    name='event_radius_dummy_1'
    )

  event_legend1 = Legend(items=[
    LegendItem(label='R1', renderers=[event_radius_dummy_1])],
    location=(20,554), label_standoff=10, label_height=3)

  event_legend2 = Legend(items=[
    LegendItem(label='R2', renderers=[event_radius_dummy_1])],
    location=(14,532), label_standoff=5)

  event_legend3 = Legend(items=[
    LegendItem(label='R3', renderers=[event_radius_dummy_1])],
    location=(8,507), label_standoff=0)

  event_legend4 = Legend(items=[
    LegendItem(label='R4', renderers=[event_radius_dummy_1])],
    location=(2,479), label_standoff=-5)

  event_legend5 = Legend(items=[
    LegendItem(label='R5', renderers=[event_radius_dummy_1])],
    location=(-4,447), label_standoff=-10)

  event_legend_list = [event_legend1,event_legend2,event_legend3,event_legend4,event_legend5]
  for legend in event_legend_list:
    p.add_layout(legend)

  size_list = [15,26,37,48,59]
  index_list = [1,2,3,4,5]

  for index, size in zip(index_list, size_list):
    p.legend[index].glyph_height = size
    p.legend[index].glyph_width = size
    p.legend[index].padding = 0
    p.legend[index].margin = 0
    p.legend[index].border_line_alpha = 0
    p.legend[index].background_fill_alpha = 0

Resulting figure: enter image description here

Upvotes: 3

Related Questions