You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 22:18:08