☰Read-only archive⌕
Looking for help with Bokeh?

This is a read-only archive. Ask questions and join the conversation on GitHub.

Visit Community Support on GitHub →
Archived discussions · Captured October 28, 2026

Legend with two fields

Community Support
sbassi
sbassi

I am trying to adapt an old code to a new version of Bokeh. The original one, published in Python for Bioinformatics is like this:

from bokeh.charts import Scatter, output_file, show
from pandas import DataFrame

df = DataFrame.from_csv('../../samples/fishdata.csv')

scatter = Scatter(df, x='PC1', y='PC2', color='feeds',
        marker='species', title=
        'Metabolic variations based on 1H NMR profiling of fishes',
        xlabel='Principal Component 1: 35.8%',
        ylabel='Principal Component 2: 15.1%')
scatter.legend.background_fill_alpha = 0.3
output_file('scatter.html')
show(scatter)

That produces the following plot:

[External image unavailable in archive]

Since bokeh.charts is deprecated, I had to modify the code to get the same result, here is the new code:

That produces a similar plot:

I wonder how to put a legend with both fields, as in the original image. Legend accepts only a string with the name one field, but I need to enter two. What can I do?

⌁   Link to post
p-himik
p-himik

It seems to be this one:

@sbassi What looks like two values is just a string created by something like str(('a', 'b')). What you want cannot be done directly ([FEATURE] Reflect bivariate scatter styles in legend · Issue #9867 · bokeh/bokeh · GitHub seems relevant), but there are two workarounds that I can see:

  • Create a CDS column feeds_and_species that just combines the feeds and species column values the way you want them to be displayed on the legend. Then just pass legend_field='feeds_and_species' into p.scatter
  • Create the whole legend manually. It will allow you to avoid having to create a special column, but it will require you to create bogus markers for each row. If you’re not sure how to do this, then definitely go with the other option
⌁   Link to post
sbassi
sbassi

Hello, thanks for your post and for posting the picture (I was not allowed to post it since it triggered an anti spam property of this forum since it was my first post).
been trying to implement first option, with no success. I am no familiar with ColumnDataSource. You said “Create a CDS column…” and “Then just pass…”, but I understand that this CDS column should replace the source I am using now, that is the DataFrame, isn’t it?
Would you give me another advise?
Here is the date source: Py4Bio/fishdata.csv at master · Serulab/Py4Bio · GitHub
Best,
SB

⌁   Link to post
sbassi
sbassi

Since it was my first post, the system didn’t allowed me to post it. Now I get: “You can’t post a link to that host”

Will post it no as a link, so bypass the filter (remove whitespace and add https):
git hub . com/ Serulab/ Py4Bio/ blob/ master/ samples/scatter.png

⌁   Link to post
p-himik
p-himik

In your example, it would be something like

ds['feeds_and_species'] = df['feeds'] + ', ' + df['species']
...
p.scatter(..., legend_field='feeds_and_species')

In this case you’re not dealing with ColumnDataSource. But that’s only because Bokeh converts Pandas’ DataFrame to Bokeh’s ColumnDataSource implicitly when you pass it as source=ds.

⌁   Link to post
sbassi
sbassi

Here is the new code with all the changes:

from bokeh.plotting import figure, show, output_file
from bokeh.models.markers import marker_types
from bokeh.transform import factor_cmap, factor_mark
from pandas import read_csv

df = read_csv('../samples/fishdata.csv')
df['feeds_and_species'] = df['feeds'] + ', ' + df['species']

all_markers = [mt for mt in marker_types]
SPECIES = list(set(df['species']))
MARKERS = all_markers[:len(SPECIES)]
feeds = list(set(df['feeds']))
ttl = 'Metabolic variations based on 1H NMR profiling of fishes'
p = figure(plot_height=600, plot_width=700, title = ttl)
p.xaxis.axis_label = 'Principal Component 1: 35.8%'
p.yaxis.axis_label = 'Principal Component 2: 15.1%'
p.scatter('PC1', 'PC2', source=df, size=12, fill_alpha=0.3, 
          marker=factor_mark('species', MARKERS, SPECIES),
          color=factor_cmap('feeds', 'Category10_3', feeds),
          legend_field='feeds_and_species')
p.legend.location = 'top_left'
p.legend.click_policy = 'hide'
output_file('scatter.html')
show(p)
⌁   Link to post

End of archived discussion · Replies are disabled