我正在尝试做一些我通常认为微不足道的事情但在散景中似乎非常困难:在绘图中添加垂直颜色条然后使用颜色条的标题(也就是颜色映射背后的变量)到颜色条的一侧,但从水平方向顺时针旋转90度。
从我所知的bokeh ColorBar()接口(查看文档并使用python解释器的help()函数来获取此元素),这是不可能的。无奈之下,我添加了自己的基于Label()的注释。这可以工作,但是在散景服务情况下部署时显示奇怪的行为 - 绘图上数据窗口的宽度与标题颜色条的标题字符串的长度成反比。
下面我已经包含了散景服务器mpg示例的修改版本。对其复杂性表示歉意,但我觉得这是使用散景附带的基础设施/数据来说明问题的最佳方式。对于那些不熟悉bokeh服务的人来说,这个代码片段需要保存到一个名为main.py的文件中,该文件位于一个目录中 - 为了参数,让我们说CrossFilter2 - 并且在CrossFilter2的父目录中需要调用命令
bokeh serve --show CrossFilter2
这将显示在浏览器窗口(localhost:5006 / CrossFilter2)中,如果您使用颜色选择小部件,您将看到我的意思,即短变量名称,如'hp'或'mpg'导致比较长的变量名称(例如'accel'或'weight')更宽的数据显示窗口。我怀疑标签元素的大小可能存在错误 - 它们的x和y维度是交换的 - 并且散景还没有理解标签元素已被旋转。
我的问题是:
如果我必须解决您在示例代码中可以看到的麻烦,还有其他方法可以避免数据窗口宽度问题吗?
import numpy as np
import pandas as pd
from bokeh.layouts import row, widgetbox
from bokeh.models import Select
from bokeh.models import HoverTool, ColorBar, LinearColorMapper, Label
from bokeh.palettes import Spectral5
from bokeh.plotting import curdoc, figure, ColumnDataSource
from bokeh.sampledata.autompg import autompg_clean as df
df = df.copy()
SIZES = list(range(6, 22, 3))
COLORS = Spectral5
# data cleanup
df.cyl = df.cyl.astype(str)
df.yr = df.yr.astype(str)
columns = sorted(df.columns)
discrete = [x for x in columns if df[x].dtype == object]
continuous = [x for x in columns if x not in discrete]
quantileable = [x for x in continuous if len(df[x].unique()) > 20]
def create_figure():
xs = df[x.value].tolist()
ys = df[y.value].tolist()
x_title = x.value.title()
y_title = y.value.title()
name = df['name'].tolist()
kw = dict()
if x.value in discrete:
kw['x_range'] = sorted(set(xs))
if y.value in discrete:
kw['y_range'] = sorted(set(ys))
kw['title'] = "%s vs %s" % (y_title, x_title)
p = figure(plot_height=600, plot_width=800,
tools='pan,box_zoom,wheel_zoom,lasso_select,reset,save',
toolbar_location='above', **kw)
p.xaxis.axis_label = x_title
p.yaxis.axis_label = y_title
if x.value in discrete:
p.xaxis.major_label_orientation = pd.np.pi / 4
if size.value != 'None':
groups = pd.qcut(df[size.value].values, len(SIZES))
sz = [SIZES[xx] for xx in groups.codes]
else:
sz = [9] * len(xs)
if color.value != 'None':
coloring = df[color.value].tolist()
cv_95 = np.percentile(np.asarray(coloring), 95)
mapper = LinearColorMapper(palette=Spectral5,
low=cv_min, high=cv_95)
mapper.low_color = 'blue'
mapper.high_color = 'red'
add_color_bar = True
ninety_degrees = pd.np.pi / 2.
color_bar = ColorBar(color_mapper=mapper, title='',
#title=color.value.title(),
title_text_font_style='bold',
title_text_font_size='20px',
title_text_align='center',
orientation='vertical',
major_label_text_font_size='16px',
major_label_text_font_style='bold',
label_standoff=8,
major_tick_line_color='black',
major_tick_line_width=3,
major_tick_in=12,
location=(0,0))
else:
c = ['#31AADE'] * len(xs)
add_color_bar = False
if add_color_bar:
source = ColumnDataSource(data=dict(x=xs, y=ys,
c=coloring, size=sz, name=name))
else:
source = ColumnDataSource(data=dict(x=xs, y=ys, color=c,
size=sz, name=name))
if add_color_bar:
p.circle('x', 'y', fill_color={'field': 'c',
'transform': mapper},
line_color=None, size='size', source=source)
else:
p.circle('x', 'y', color='color', size='size', source=source)
p.add_tools(HoverTool(tooltips=[('x', '@x'), ('y', '@y'),
('desc', '@name')]))
if add_color_bar:
color_bar_label = Label(text=color.value.title(),
angle=ninety_degrees,
text_color='black',
text_font_style='bold',
text_font_size='20px',
x=25, y=300,
x_units='screen', y_units='screen')
p.add_layout(color_bar, 'right')
p.add_layout(color_bar_label, 'right')
return p
def update(attr, old, new):
layout.children[1] = create_figure()
x = Select(title='X-Axis', value='mpg', options=columns)
x.on_change('value', update)
y = Select(title='Y-Axis', value='hp', options=columns)
y.on_change('value', update)
size = Select(title='Size', value='None',
options=['None'] + quantileable)
size.on_change('value', update)
color = Select(title='Color', value='None',
options=['None'] + quantileable)
color.on_change('value', update)
controls = widgetbox([x, y, color, size], width=200)
layout = row(controls, create_figure())
curdoc().add_root(layout)
curdoc().title = "Crossfilter"
答案 0 :(得分:4)
您可以通过在单独的轴上绘制并向此轴添加标题来向Colorbar添加垂直标签。为了说明这一点,这里是Bokeh标准Colorbar示例的修改版本(找到here):
import numpy as np
from bokeh.plotting import figure, output_file, show
from bokeh.models import LogColorMapper, LogTicker, ColorBar
from bokeh.layouts import row
plot_height = 500
plot_width = 500
color_bar_height = plot_height + 11
color_bar_width = 180
output_file('color_bar.html')
def normal2d(X, Y, sigx=1.0, sigy=1.0, mux=0.0, muy=0.0):
z = (X-mux)**2 / sigx**2 + (Y-muy)**2 / sigy**2
return np.exp(-z/2) / (2 * np.pi * sigx * sigy)
X, Y = np.mgrid[-3:3:100j, -2:2:100j]
Z = normal2d(X, Y, 0.1, 0.2, 1.0, 1.0) + 0.1*normal2d(X, Y, 1.0, 1.0)
image = Z * 1e6
color_mapper = LogColorMapper(palette="Viridis256", low=1, high=1e7)
plot = figure(x_range=(0,1), y_range=(0,1), toolbar_location=None,
width=plot_width, height=plot_height)
plot.image(image=[image], color_mapper=color_mapper,
dh=[1.0], dw=[1.0], x=[0], y=[0])
现在,要制作Colorbar,创建一个单独的虚拟绘图,将Colorbar添加到虚拟绘图并将其放在主绘图旁边。添加Colorbar标签作为虚拟图的标题并适当地居中。
color_bar = ColorBar(color_mapper=color_mapper, ticker=LogTicker(),
label_standoff=12, border_line_color=None, location=(0,0))
color_bar_plot = figure(title="My color bar title", title_location="right",
height=color_bar_height, width=color_bar_width,
toolbar_location=None, min_border=0,
outline_line_color=None)
color_bar_plot.add_layout(color_bar, 'right')
color_bar_plot.title.align="center"
color_bar_plot.title.text_font_size = '12pt'
layout = row(plot, color_bar_plot)
show(layout)
这给出了以下输出图像:
要注意的一点是,color_bar_width设置得足够宽,可以同时包含Colorbar,它的轴标签和Colorbar标签。如果宽度设置得太小,则会出现错误,并且不会渲染绘图。
答案 1 :(得分:1)
从Bokeh 0.12.10
开始,没有可用于彩条的内置标签。除了你的方法或类似的东西,另一种可能性是custom extension,虽然这同样不是微不足道的。
另外,一个colobar标签看起来似乎是一个合理的考虑因素。关于应该可以轻易获得的概念,如果您对所有用户进行了他们认为应该可以轻易获得的内容的评论,那么将会有成千上万的不同的建议来确定优先级。正如在OSS世界中经常出现的那样,有更多可能的事情要做,而不是有人去做(在这种情况下少于3个)。因此,首先会建议GitHub Issue请求该功能,其次,如果您有能力,请自愿帮助实施该功能。您的贡献将是有价值的,并为许多人所赞赏。