基本概念

  1. Figure 对象

    • 绘图的容器
    • 其中可以存在多种图形处理工具 renderer(GlyPhRenderer)
    • 处理器包含图形 glyph(Line)
  2. 图形处理器 renderer
  3. glyph

    • 各种形状本身的特性,不包括图例(legend)
    • line 直线
    • vbar 柱状图
    • 每一个图形的属性

      • x
      • y
      • line_color
  4. 图形属性 glyph properties

    • x
    • y
    • 颜色
  5. 属性类型

区域大小问题

在 min_border 满足绘图需求时,有以下计算公式

width  = min_border_left + frame_width  + min_boder_right
height = min_border_left + frame_height + min_boder_right

主绘图区域大小

figure() 函数中的 frame_widthframe_height, 他们不包括 title, axis, border pading 等

注意:

  1. frame_width, frame_height 和 width, height 不能同时使用
  2. 同时使用时, 以 frame_width 和 frame_height 为主

边界大小

axis_label, title, hovertool 等所占的区域

  • min_boder
  • min_border_left
  • min_border_right
  • min_border_top
  • min_border_bottom

整个图(canvas)大小

figure() 函数中的 withheight

FAQ

如何隐藏(删除) 工具栏中的 bokeh 图标(logo)

参考:

方法:

  • 使用参数 toolbar_options=dict(logo=None)

例子:

1
2
3
4
5
6
7
8
from bokeh.plotting import show
from bokeh.models.tools import PanTool, SaveTool

p = figure()
p.line([1, 2, 3, 4],[1, 4, 3, 0])
p.toolbar.logo = None
p.tools = [SaveTool(), PanTool()]
show(p)

如何区域着色

参考:

特点:

  • 类似 matplotlib.pyplot.fill 的多边形(polygon)区域着色

方法:

  • 使用 PatchPatchs

如何添加在任意位置添加文本注释标签

参考:

方法:

  • 单个文本标签使用 Label 工具
  • 多个文本标签使用 LabelSet 工具
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# importing the modules
from bokeh.plotting import figure, output_file, show
from bokeh.models import Label

p = figure()

p.patch([1, 2, 2, 1], [1, 1, 2, 2], color='yellow')

l = Label(x= 1.5, y=1.5, text="H2O")
p.add_layout(l)

show(p)

如何使用 latex 公式

参考:

方法:

  • 直接在文本的中添加 $$your-latex-formula$$

坐标轴设置

参考:

坐标轴范围

  1. 使用 figure 函数

    • figure(x_range=(1,100), y_range_name=(1,20))
  2. 使用 Figure 对象的属性

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    from bokeh.plotting import figure, show
    from bokeh.models import Range1d
    import numpy as np
    
    
    p = figure()
    
    x= np.arange(1, 10, 0.5)
    p.line(x, x**2)
    
    # 手动设置
    p.x_range = Range1d(1, 10)
    p.y_range = Range1d(1, 100)
    
    show(p)

坐标轴名称

  1. 使用 figure 函数

    • figure(x_axis_label='X', y_axis_label='Y')
  2. 使用 Figure 对象属性

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    from bokeh.plotting import figure, show
    from bokeh.models import Range1d
    import numpy as np
    
    
    p = figure()
    
    x= np.arange(1, 10, 0.5)
    p.line(x, x**2)
    
    # 手动设置
    p.xaxis.axis_label = 'X'
    p.yaxis.axis_label = 'Y'
    
    show(p)

第二坐标轴设置

1
2
3
from bokeh.models import Range1d, LinearAxis
p.extra_y_ranges = {"probability": Range1d(start=0, end=1)}
p.add_layout(LinearAxis(y_range_name="probability"), 'right')

坐标轴标签 axis_label 设置

1
2
3
4
plot.xaxis.axis_label="xaxis_name" # label 名称
plot.xaxis.axis_label_text_font_size = "25pt" # label 字体大小
plot.xaxis.axis_label_text_font = "times"
plot.xaxis.axis_label_text_color = "black"

着标轴 ticks 设置

1
2
3
4
5
6
7
8
9
# major tick 的标签
major_label_text_color
major_label_text_font_size

# major tick 的刻度线
major_tick_line_color

# minor tick 的刻度线
minor_tick_line_color

鼠标悬浮显示坐标

参考:

方法:

  • 使用 HoverTool 工具

举例:

1
2
3
4
5
6
7
8
p = figure(tooltips=[("x,y", "$x, $y")])

p.line(x, x**2 -2*x)
l = Label(x= 1.5, y=1.5, text="H2O", text_color=Category20[3][0])
p.add_layout(l)


show(p)

坐标显示设置

任意位置坐标,data_unit
tooltips=[("x,y", "$x, $y")]
任意位置坐标,screen_unit
tooltips=[("x,y", "$sx, $sy")]
绘图元素(Glyph)上的点
tooltips=[("x,y", "@x, @y")]
设置显示格式
tooltips=[('x,y', '(@x{0,0.000},@y{0,0.000})')]

为什么 hovertool 显示多个相同显示框的

因为,不同的图形有公共点,这样就造成同一个点被重新绘制了多次

避免方法:

  • 单独设置 hovertool.renderers

    • 这是因为,默认情况下,bokeh 会把所有的图形(glyph)都添加到 hovertool.renderers 中
  • 把这些需要 hovertool 显示的点,单独加入到 hovertool.renderers 即可

设定那些点会触发 hovertool

即设定哪个形状上的点会触发 HoverTool

1
2
3
4
5
6
other_scatter = plt.scatter(other_points[0], other_points[1], alpha=0, size=3)
hover = HoverTool(
    tooltips=[("x,y", "(@x{0,0.000},@y{0,0.000})")],
    renderers=[scatter, other_scatter],
    mode="mouse",
)

解说:

  • 这里把会触发数据点单独放到图形 scatterother_scatter
  • 传递给 HoverTool.renderers 属性

pareto 图绘制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import math
import numpy as np
import pandas as pd


# 制作bar chart使用的pareto分布随机伪数据
np.random.seed(0)
raw_data = np.ceil(np.random.pareto(1, 10)*100).astype('uint32')
raw_data = raw_data/np.sum(raw_data)*100
df = pd.DataFrame({'yield': raw_data,
                   'bin': [f'bin_log-7{i}-F-X{"SLH"[i%3]}H' for i in range(1, len(raw_data) + 1)]})
df = df.sort_values(by='yield', ascending=False)

df


from bokeh.models import ColumnDataSource, Range1d, LinearAxis
from bokeh.plotting import figure, show

def show_pareto(df, use_cum_prob=False):
    """使用vbar 绘制pareto图

    参数 use_cum_prob:是否绘制yield 积累概率分布曲线
    """

    source = ColumnDataSource(df.copy())

    x = np.arange(len(df)) + 0.5

    p = figure(x_range=df['bin'],
              title="yield by bin Pareto Chart",
              width=500, height=550)
    if use_cum_prob is True:
        p.extra_y_ranges = {"probability": Range1d(start=0, end=1)}
        p.add_layout(LinearAxis(y_range_name="probability"), 'right')

    p.vbar(x=x, top=df['yield'], width=0.9)

    # 设置x轴坐标ticks 标签文字倾斜角度
    p.xaxis.major_label_orientation = math.pi/3

    p.yaxis.axis_label = 'Yield'

    if use_cum_prob is True:
        cum_probability = np.cumsum(df['yield'])/df['yield'].sum()
        p.line(x=x, y=cum_probability, y_range_name='probability', line_color='#f46d43', line_width=3, line_alpha=0.8)

    show(p)


show_pareto(df, use_cum_prob=False)


show_pareto(df, use_cum_prob=True)

vbar_stack 图绘制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
from bokeh.palettes import Category10_10

wafer_id_len = 9
type_len = 6


# 创建wafer_id标签,类型标签
wafer_ids = [f'wafer_{i:02d}' for i in range(wafer_id_len)]
types = [f'bin_{i:02d}-F-E' for i in range(type_len)]
# 设定不同类型标签在vbar_stack中的颜色
colors = Category10_10[:type_len]


# 构造绘图使用的pareto分布随机伪数据
np.random.seed(0)
raw_data = []
for _id in wafer_ids:
    tmp = np.ceil(np.random.pareto(1, (type_len, ))*100)
    # 同一wafer_id不同类型的数据做归一化(100%)
    tmp = tmp/np.sum(tmp)*100
    raw_data.append(tmp)
raw_df = pd.DataFrame(np.array(raw_data).T, columns=wafer_ids, index=types)


raw_df


# 把数据表pd.DataFrame 转换成bokeh 绘图使用数据格式
plot_data = {'wafer_ids': wafer_ids}
for i in types:
    plot_data[i] = raw_df.transpose()[i].tolist()


from bokeh.plotting import figure, show
from bokeh.models import Legend

p = figure(x_range=wafer_ids, width=600, height=550, toolbar_location=None,
           title="yield by wafer_id")
vbar_stack = p.vbar_stack(types, x='wafer_ids', width=0.8, color=colors, source=plot_data)


p.x_range.range_padding = 0.03
p.y_range.range_padding = 0.03
p.xaxis.axis_label = 'Wafer ID'
p.xaxis.major_label_orientation = math.pi/3
p.yaxis.axis_label = 'Yield'

# 图例设置
legend = Legend(items=[(types[i], [vbar_stack[i]]) for i in range(type_len)])
p.add_layout(legend, 'right')

show(p)

嵌入 bokeh 绘图结果到 html 前段页面

主绘图区域大小

参考:

手动设定法:

  1. 使用 figure() 的 frame_width 和 frame_height 属性
  2. 问题

    • 这样整个 canvas 的大小就是变动的了,需要配合 min_border 设定整个 canvas 的大小

间接设定法:

  1. 设定整个绘图区域(坐标方框)的大小

    • figure(with=..., height=...), 设置的是整个 convas 绘图区域的大小,包括 title 和 axis_label 等
    • 参数:

      • width
      • height
  2. 设定边界区域最小大小

    • 通过 figure() 的 min_border 设定

      • 这是最小边界大小,如果内容如 title 太大,可能 title 所占的区域大小多于 min_boder 设置
    • 把边界区域大小固定后,剩余的是主绘图区域

Label 对齐问题

label 的 x,y 默认是左下角的位置