
Bokeh 不支持在同一图表中直接混合使用线性与对数 X 轴(因 x_range_name 关联的范围类型必须与轴类型一致),核心问题在于对数轴不能包含零或负值,且 Bokeh 的 LogAxis 无法自动适配非对数范围;需手动转换数据或采用双图联动方案。
bokeh 不支持在同一图表中直接混合使用线性与对数 x 轴(因 `x_range_name` 关联的范围类型必须与轴类型一致),核心问题在于对数轴不能包含零或负值,且 bokeh 的 `logaxis` 无法自动适配非对数范围;需手动转换数据或采用双图联动方案。
在 Bokeh 中为同一图表添加「双 X 轴」——一个线性、一个对数刻度——看似可行,实则受限于其底层坐标系统设计。你遇到的“对数轴无刻度、散点仍按线性绘制”问题,并非代码疏漏,而是 Bokeh 的明确限制:LogAxis 必须搭配 LogTicker 和 LogScale,且其所绑定的 x_range_name 必须使用 Range1d 的对数值范围(即 start > 0),同时所有映射到该 x_range_name 的 glyph(如 scatter)会自动按对数尺度解析其 X 坐标值。但关键矛盾在于:Bokeh 不允许多个 X 轴共享同一 figure 的 x_range,也不支持为不同 x_range_name 指定不同坐标系类型(如一个 linear、一个 log)。
因此,以下两种专业级解决方案可真正落地:
✅ 方案一:数据预处理 + 单图双轴(推荐)
将对数数据预先取 log10(),再用线性轴显示,并自定义对数标签:
import pandas as pd
import numpy as np
from bokeh.plotting import figure, show
from bokeh.models import LinearAxis, Range1d, LabelSet, ColumnDataSource
from bokeh.layouts import gridplot
df = pd.DataFrame([
['2024-06-21 06:22:38', 605.968389, 0.994548, 3],
['2024-06-21 06:22:39', 616.009398, 0.983443, 4],
# ...(其余数据同上)
], columns=['time', 'Altitude', 'log_data', 'linear_data'])
# 关键:将 log_data 转换为 log10 值(避开 0!)
df['log_data_log10'] = np.log10(df['log_data'].replace(0, np.nan)).fillna(-1) # 或设最小正值
source = ColumnDataSource(df)
p = figure(
title="Linear & Log-Scaled Data (Log Data Pre-Transformed)",
x_axis_label="Linear Data",
y_axis_label="Altitude / km",
frame_height=900,
frame_width=650,
tools="pan,wheel_zoom,box_zoom,reset"
)
# 主线性数据(红色折线)
p.line('linear_data', 'Altitude', line_color='red', line_width=2, source=source)
# 对数数据:使用转换后的 log10 值绘图,但标注原始对数值
p.scatter('log_data_log10', 'Altitude', size=8, color='blue', alpha=0.8, source=source)
# 添加第二 X 轴(线性,但代表 log10 值)
p.add_layout(LinearAxis(
axis_label="Log Data (base 10)",
axis_label_text_color='blue',
major_label_orientation="horizontal"
), 'below')
# ⚠️ 重要:设置合理的对数范围(避免 0)
p.x_range = Range1d(start=-1.5, end=np.log10(115)) # 对应原始 [0.03, 115]
# 可选:添加自定义标签显示原始对数值(如 0.1, 1, 10, 100)
log_ticks = [0.1, 1, 10, 100]
p.extra_x_ranges = {"log_axis": Range1d(start=-1, end=2)}
p.add_layout(LinearAxis(x_range_name="log_axis",
major_label_overrides={np.log10(v): str(v) for v in log_ticks}),
'above')
show(p)
✅ 优势:单图直观、性能好、交互统一;✅ 注意:务必确保
log_data > 0,否则log10报错或产生NaN;建议用np.log10(np.clip(df['log_data'], 1e-5, None))安全处理。
❌ 方案二:双图同步(备选,适合复杂需求)
若需严格保留原始刻度语义(如底部显示 1, 10, 100 等对数主刻度),推荐创建两个独立 figure 并通过 CustomJS 同步缩放/平移:
from bokeh.models import CustomJS
# 创建主图(线性)
p_linear = figure(..., x_axis_label="Linear Data")
p_linear.line('linear_data', 'Altitude', source=source)
# 创建对数图(单独 figure,x_axis_type='log')
p_log = figure(..., x_axis_type='log', x_axis_label="Log Data")
p_log.scatter('log_data', 'Altitude', source=source)
# 同步 X 范围(JavaScript)
callback = CustomJS(args=dict(p1=p_linear, p2=p_log), code="""
p2.x_range.start = Math.pow(10, p1.x_range.start);
p2.x_range.end = Math.pow(10, p1.x_range.end);
""")
p_linear.x_range.js_on_change('start', callback)
p_linear.x_range.js_on_change('end', callback)
layout = gridplot([[p_linear], [p_log]], ncols=1, width=700, height=450)
show(layout)
? 总结
-
Bokeh 原生不支持混合坐标系双 X 轴,强行使用
LogAxis+ 非对数Range1d(含 0)必然失败; -
首选方案是数据端转换:对输入数据取
log10,用线性轴可视化,再通过major_label_overrides显示原始对数值; - 所有对数操作前必须
df['col'] = df['col'].clip(lower=1e-8)防0/负值; - 若需极致刻度控制,双图 +
CustomJS是唯一健壮解法,但开发成本更高。
遵循以上原则,即可稳定实现专业级线性/对数双标度可视化。










