Creating a Chart with Two Different Y-Axis Ranges in Bokeh
Last Updated :
23 Jul, 2025
Bokeh is a powerful visualization library in Python that allows for the creation of interactive plots and dashboards. One of the more advanced features of Bokeh is the ability to create a chart with two different Y-axis ranges. This can be particularly useful when dealing with datasets that have vastly different scales. In this article, we will delve into how to implement this feature in Bokeh, addressing common challenges and providing a step-by-step guide.
Understanding the Need for Dual Y-Axis
In data visualization, there are instances where you need to plot two datasets that share the same X-axis but have different Y-axis ranges. For example, you might want to plot temperature and precipitation on the same chart. Temperature might range from -10 to 40 degrees Celsius, while precipitation could range from 0 to 200 mm. Plotting these on the same Y-axis would make one of the datasets unreadable due to scale differences.
Challenges with Dual Y-Axis
Using a dual Y-axis can introduce complexity in interpreting the chart:
- The primary challenge is ensuring that the data is not misrepresented due to the scaling of the axes.
- Additionally, Bokeh's default behavior might not automatically accommodate the dynamic nature of data ranges, especially when the data changes over time or with user interaction.
Creating a Dual Y-Axis Plot using Bokeh
Key Steps in the Implementation:
- Data Preparation: Use ColumnDataSource to manage the data for each dataset. This allows for efficient updates and interactions.
- Primary Y-Axis: Plot the first dataset using the default Y-axis.
- Secondary Y-Axis:
- Define an additional Y-axis range using Range1d.
- Add this range to the plot using extra_y_ranges.
- Attach a new LinearAxis to the plot, specifying the y_range_name.
- Plotting: Use the line method to plot each dataset, specifying the y_range_name for the second dataset to ensure it uses the secondary Y-axis.
Example 1: Creating a Basic Plot with Dual Y-Axis
Let's start by creating a basic plot in Bokeh. We'll use two datasets with different Y-axis ranges.
Python
from bokeh.plotting import figure, show, output_notebook
from bokeh.models import ColumnDataSource, Range1d, LinearAxis
output_notebook()
x = [1, 2, 3, 4, 5]
y1 = [10, 20, 30, 40, 50] # Dataset 1
y2 = [100, 200, 300, 400, 500] # Dataset 2
# Creating a ColumnDataSource
source1 = ColumnDataSource(data=dict(x=x, y=y1))
source2 = ColumnDataSource(data=dict(x=x, y=y2))
# Create a new plot
p = figure(title="Dual Y-Axis Example", x_axis_label='X-Axis', y_axis_label='Y1-Axis')
# Add line glyph for the first dataset
p.line('x', 'y', source=source1, line_width=2, color='blue', legend_label='Dataset 1')
# Configure the second y-axis range
p.extra_y_ranges = {"y2": Range1d(start=0, end=600)}
# Add the second y-axis to the plot
p.add_layout(LinearAxis(y_range_name="y2", axis_label='Y2-Axis'), 'right')
# Add line glyph for the second dataset
p.line('x', 'y', source=source2, line_width=2, color='green', y_range_name="y2", legend_label='Dataset 2')
show(p)
Output:
Dual Y-axis Plot in BokehExample 2: Dual Y-Axis with Real-World Data
Consider a scenario where you want to plot stock prices and trading volumes on the same chart. Stock prices might range from $100 to $150, while trading volumes could range from 1,000 to 10,000 shares.
Python
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, Range1d, LinearAxis
dates = ['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-04', '2023-01-05']
prices = [120, 125, 130, 128, 132] # Stock prices
volumes = [5000, 7000, 8000, 6000, 9000] # Trading volumes
# Creating a ColumnDataSource
source1 = ColumnDataSource(data=dict(dates=dates, prices=prices))
source2 = ColumnDataSource(data=dict(dates=dates, volumes=volumes))
# Create a new plot
p = figure(x_range=dates, title="Stock Prices and Trading Volumes", x_axis_label='Date', y_axis_label='Price')
# Add line glyph for stock prices
p.line('dates', 'prices', source=source1, line_width=2, color='blue', legend_label='Price')
# Configure the second y-axis range for volumes
p.extra_y_ranges = {"volumes": Range1d(start=0, end=10000)}
# Add the second y-axis to the plot
p.add_layout(LinearAxis(y_range_name="volumes", axis_label='Volume'), 'right')
# Add bar glyph for trading volumes
p.vbar(x='dates', top='volumes', source=source2, width=0.4, color='green', y_range_name="volumes", legend_label='Volume')
show(p)
Output:
Dual Y-Axis with Real-World DataHandling Dynamic Data in Bokeh
When dealing with dynamic data where the Y-axis ranges can change, it's crucial to ensure the axes adjust accordingly. Bokeh provides DataRange1d which can automatically adjust based on the data being plotted. This is particularly useful for real-time data visualization.
Python
from bokeh.plotting import figure, show, output_notebook, curdoc
from bokeh.models import ColumnDataSource, LinearAxis, DataRange1d
from bokeh.layouts import column
from bokeh.io import push_notebook
from bokeh.driving import linear
import numpy as np
output_notebook()
x = np.arange(1, 6)
y1 = np.random.randint(10, 50, size=len(x)) # Dataset 1 with random data
y2 = np.random.randint(100, 500, size=len(x)) # Dataset 2 with random data
# Creating a ColumnDataSource for dynamic updates
source1 = ColumnDataSource(data=dict(x=x, y=y1))
source2 = ColumnDataSource(data=dict(x=x, y=y2))
# Create a new plot
p = figure(title="Dual Y-Axis Example with Dynamic Data", x_axis_label='X-Axis')
# Set dynamic ranges for y-axes
p.y_range = DataRange1d() # Dynamic range for the first y-axis
p.extra_y_ranges = {"y2": DataRange1d()} # Dynamic range for the second y-axis
# Add line glyph for the first dataset
line1 = p.line('x', 'y', source=source1, line_width=2, color='blue', legend_label='Dataset 1')
# Add the second y-axis to the plot
p.add_layout(LinearAxis(y_range_name="y2", axis_label='Y2-Axis'), 'right')
# Add line glyph for the second dataset
line2 = p.line('x', 'y', source=source2, line_width=2, color='green', y_range_name="y2", legend_label='Dataset 2')
handle = show(p, notebook_handle=True)
# Function to update data dynamically
def update():
new_y1 = np.random.randint(10, 50, size=len(x)) # New random data for y1
new_y2 = np.random.randint(100, 500, size=len(x)) # New random data for y2
# Update the data source with new data
source1.data = dict(x=x, y=new_y1)
source2.data = dict(x=x, y=new_y2)
# Push updates to the plot in the notebook
push_notebook(handle=handle)
# Simulate dynamic data updates
from time import sleep
for _ in range(10): # Update 10 times
update()
sleep(1) # Pause for 1 second between updates
Output:
Handling Dynamic Data in BokehConsiderations and Best Practices
- Avoid Misinterpretation: Ensure that the scaling of both Y-axes does not mislead the viewer about the data's relationship.
- User Interaction: Consider adding tools like zoom and pan to allow users to explore the data more interactively.
- Alternatives: If dual Y-axes lead to confusion, consider alternative visualizations like facet plots or separate charts.
Conclusion
Creating a chart with two different Y-axis ranges in Bokeh is a powerful way to visualize datasets with varying scales. By carefully managing the axes and ensuring clear labeling, you can create informative and interactive visualizations. While dual Y-axes can be complex, Bokeh's flexibility makes it possible to implement them effectively. Always consider the end-user's ability to interpret the data accurately when designing such visualizations.