Bokeh蜡烛图无法渲染求助:代码仅弹出空白窗口无图表
Bokeh蜡烛图空白问题排查与修复
问题描述
编写的Bokeh蜡烛图代码无法正常渲染图表,仅弹出空白窗口。代码逻辑为下载股票数据后处理并绘制蜡烛图,怀疑与figure参数有关,但不确定具体原因。
原代码如下:
import pandas as pd from bokeh.plotting import figure from bokeh.io import output_file, save, show from bokeh.layouts import column from bokeh.models import ColumnDataSource, CDSView, BooleanFilter, \ HoverTool, LinearAxis, NumeralTickFormatter, Range1d, RangeTool stock_url = 'https://docs.google.com/spreadsheets/d/e/2PACX-1vTiM1scE44za7xyuheW_FrUkdSdOKipDgDOWa_03ixmJCWK_ReSqhjzax66nNHyDKARXWIXgFI_EW9X/pub?gid=1661368486&single=true&output=csv' stock = pd.read_csv(stock_url) metrics_url = 'https://docs.google.com/spreadsheets/d/e/2PACX-1vRDaf4y17OWjQqxODuxA4q4hsvXRkSqN0na1KtTIpvOZUdc7xHbrkhcygFfDIyVQWI2UbC3YcKUbser/pub?gid=981872466&single=true&output=csv' metrics = pd.read_csv(metrics_url) stock['Date'] = pd.to_datetime(stock['Date']) metrics['Quarter Ended'] = pd.to_datetime(metrics['Quarter Ended']) def create_candlestick_chart(symbol='AAPL'): source = ColumnDataSource(data=dict( date=stock[stock['Symbol'] == symbol]['Date'], high=stock[stock['Symbol'] == symbol]['High'], low=stock[stock['Symbol'] == symbol]['Low'], open=stock[stock['Symbol'] == symbol]['Open'], close=stock[stock['Symbol'] == symbol]['Close'], volume=stock[stock['Symbol'] == symbol]['Volume'], )) p = figure( width=800, height=400, title=symbol, x_range=(min(stock[stock['Symbol'] == symbol]['Date']), max(stock[stock['Symbol'] == symbol]['Date'])), x_axis_type='datetime', x_axis_location='above', background_fill_color='#fbfbfb', tools='pan,wheel_zoom,box_zoom,reset,save', toolbar_location='right', ) p.xgrid.grid_line_color = '#e5e5e5' p.ygrid.grid_line_alpha = 0.5 p.xaxis.major_label_text_font_size = '10px' p.yaxis.axis_label = 'Stock Price in USD' p.yaxis.formatter = NumeralTickFormatter(format='$0.00') p.y_range.start = min(stock[stock['Symbol'] == symbol]['Low']) * 0.9 p.y_range.end = max(stock[stock['Symbol'] == symbol]['High']) * 1.1 inc = source.data['close'] > source.data['open'] dec = source.data['open'] > source.data['close'] inc_view = CDSView(source=source, filters=[BooleanFilter(inc)]) dec_view = CDSView(source=source, filters=[BooleanFilter(dec)]) w = 12 * 60 * 60 * 1000 stock_segment = p.segment( x0='date', y0='high', x1='date', y1='low', color='black', source=source ) stock_inc = p.vbar( x='date', top='close', bottom='open', width=w, fill_color='#D5E1DD', line_color='black', view=inc_view, source=source ) stock_dec = p.vbar( x='date', top='open', bottom='close', width=w, fill_color='#F2583E', line_color='black', view=dec_view, source=source ) y_volume = figure( y_range=p.y_range, width=80, height=p.height, tools='', toolbar_location=None, background_fill_color='#fbfbfb' ) p.extra_y_ranges['volume'] = Range1d( start=0, end=max(source.data['volume']) * 2 ) y_volume.vbar( x='date', top='volume', source=source, width=0.5, alpha=0.5, color='navy', y_range_name='volume' ) y_volume.axis.major_label_text_font_size = '10px' y_volume.axis.axis_label = 'Volume' y_volume.xaxis.visible = False y_volume.yaxis.formatter = NumeralTickFormatter(format='0.00a') y_volume_axis = LinearAxis( y_range_name='volume', axis_label='Volume', formatter=NumeralTickFormatter(format='0.00a') ) p.add_layout(y_volume_axis, 'right') stock_volume = p.vbar( x='date', top='volume', source=source, width=0.5, alpha=0.5, color='navy', y_range_name='volume', view=inc_view, name='inc_volume' ) stock_volume = p.vbar( x='date', top='volume', source=source, width=0.5, alpha=0.5, color='red', y_range_name='volume', view=dec_view, name='dec_volume' ) hover_stock = HoverTool() hover_stock.tooltips = [ ('Date', '@date{%F}'), ('Open', '@open{$0.00}'), ('Close', '@close{$0.00}'), ('High', '@high{$0.00}'), ('Low', '@low{$0.00}'), ('Volume', '@volume{0,0}'), ] hover_stock.formatters = { '@date': 'datetime', } hover_stock.renderers = [ stock_inc, stock_dec, ] p.add_tools(hover_stock) p.output_backend = 'svg' return p p = create_candlestick_chart() show(p)
问题根源
- 数据源格式不兼容:直接传入pandas Series到ColumnDataSource,日期列的DatetimeIndex特性导致Bokeh无法正确解析
- x_range与图层冗余:重复筛选数据导致效率低下,且创建了未加入布局的独立y_volume figure,造成资源浪费
- 宽度单位不匹配:成交量vbar使用数值单位0.5,与蜡烛图的毫秒宽度不兼容,无法在datetime轴上正确显示
- 空数据无防护:未处理筛选后数据为空的情况,可能引发计算错误
修复后的代码
import pandas as pd from bokeh.plotting import figure from bokeh.io import show from bokeh.models import ColumnDataSource, CDSView, BooleanFilter, \ HoverTool, LinearAxis, NumeralTickFormatter, Range1d stock_url = 'https://docs.google.com/spreadsheets/d/e/2PACX-1vTiM1scE44za7xyuheW_FrUkdSdOKipDgDOWa_03ixmJCWK_ReSqhjzax66nNHyDKARXWIXgFI_EW9X/pub?gid=1661368486&single=true&output=csv' stock = pd.read_csv(stock_url) metrics_url = 'https://docs.google.com/spreadsheets/d/e/2PACX-1vRDaf4y17OWjQqxODuxA4q4hsvXRkSqN0na1KtTIpvOZUdc7xHbrkhcygFfDIyVQWI2UbC3YcKUbser/pub?gid=981872466&single=true&output=csv' metrics = pd.read_csv(metrics_url) stock['Date'] = pd.to_datetime(stock['Date']) metrics['Quarter Ended'] = pd.to_datetime(metrics['Quarter Ended']) def create_candlestick_chart(symbol='AAPL'): # 筛选数据并转为列表,避免pandas格式兼容问题 df = stock[stock['Symbol'] == symbol].copy() source = ColumnDataSource(data=dict( date=df['Date'].tolist(), high=df['High'].tolist(), low=df['Low'].tolist(), open=df['Open'].tolist(), close=df['Close'].tolist(), volume=df['Volume'].tolist(), )) # 直接用筛选后的数据初始化x范围 p = figure( width=800, height=400, title=symbol, x_range=(df['Date'].min(), df['Date'].max()), x_axis_type='datetime', x_axis_location='above', background_fill_color='#fbfbfb', tools='pan,wheel_zoom,box_zoom,reset,save', toolbar_location='right', ) p.xgrid.grid_line_color = '#e5e5e5' p.ygrid.grid_line_alpha = 0.5 p.xaxis.major_label_text_font_size = '10px' p.yaxis.axis_label = 'Stock Price in USD' p.yaxis.formatter = NumeralTickFormatter(format='$0.00') # 处理空数据情况,避免计算错误 if not df.empty: p.y_range.start = df['Low'].min() * 0.9 p.y_range.end = df['High'].max() * 1.1 # 提前计算涨跌状态,避免操作数据源 inc = df['Close'] > df['Open'] dec = df['Open'] > df['Close'] inc_view = CDSView(source=source, filters=[BooleanFilter(inc.tolist())]) dec_view = CDSView(source=source, filters=[BooleanFilter(dec.tolist())]) # 设置蜡烛图宽度为单日的80%,适配datetime轴 w = 0.8 * 24 * 60 * 60 * 1000 # 绘制高低点连线 p.segment( x0='date', y0='high', x1='date', y1='low', color='black', source=source ) # 绘制上涨蜡烛 stock_inc = p.vbar( x='date', top='close', bottom='open', width=w, fill_color='#D5E1DD', line_color='black', view=inc_view, source=source ) # 绘制下跌蜡烛 stock_dec = p.vbar( x='date', top='open', bottom='close', width=w, fill_color='#F2583E', line_color='black', view=dec_view, source=source ) # 添加成交量副轴 p.extra_y_ranges['volume'] = Range1d( start=0, end=df['Volume'].max() * 2 if not df.empty else 1000 ) # 绘制成交量柱形,使用与蜡烛图一致的宽度 p.vbar( x='date', top='volume', source=source, width=w, alpha=0.3, color='#D5E1DD', y_range_name='volume', view=inc_view ) p.vbar( x='date', top='volume', source=source, width=w, alpha=0.3, color='#F2583E', y_range_name='volume', view=dec_view ) # 添加成交量轴 y_volume_axis = LinearAxis( y_range_name='volume', axis_label='Volume', formatter=NumeralTickFormatter(format='0.00a') ) p.add_layout(y_volume_axis, 'right') # 简化HoverTool定义 hover_stock = HoverTool( tooltips=[ ('Date', '@date{%F}'), ('Open', '@open{$0.00}'), ('Close', '@close{$0.00}'), ('High', '@high{$0.00}'), ('Low', '@low{$0.00}'), ('Volume', '@volume{0,0}'), ], formatters={'@date': 'datetime'}, renderers=[stock_inc, stock_dec] ) p.add_tools(hover_stock) p.output_backend = 'svg' return p p = create_candlestick_chart() show(p)
关键修复点
- 数据源转换:将pandas Series通过
.tolist()转为列表,确保Bokeh能正确解析数据类型 - 简化数据操作:提前筛选数据并复用,避免重复查询提升效率
- 统一宽度单位:成交量柱形使用与蜡烛图一致的毫秒宽度,适配datetime轴
- 移除冗余代码:删除未使用的y_volume figure,简化图层结构
- 空数据防护:添加空数据判断,避免异常计算导致图表崩溃
内容的提问来源于stack exchange,提问作者olu
相关产品推荐
相关产品推荐

