Python Bokeh:线图上的意外(断开?)缩放行为
我正在使用Bokeh+tornado构建一个实时绘图实用程序,我遇到了一个奇怪的行为,其中绘图自动缩放的行为与预期不符,特别是有三个令人费解的问题。你知道会出什么问题吗?我能调查什么 显示行为的图片问题列表:Python Bokeh:线图上的意外(断开?)缩放行为,python,plot,bokeh,real-time-data,Python,Plot,Bokeh,Real Time Data,我正在使用Bokeh+tornado构建一个实时绘图实用程序,我遇到了一个奇怪的行为,其中绘图自动缩放的行为与预期不符,特别是有三个令人费解的问题。你知道会出什么问题吗?我能调查什么 显示行为的图片问题列表: from bokeh.server.server import Server from bokeh.application import Application from bokeh.application.handlers.function import FunctionHandler
from bokeh.server.server import Server
from bokeh.application import Application
from bokeh.application.handlers.function import FunctionHandler
from bokeh.plotting import figure, ColumnDataSource
from tornado import gen
from tornado.ioloop import IOLoop
from multiprocessing import Process, Pipe
class LivePlotApp(object):
parent_conn, child_conn = Pipe()
def __init__(self, data_format, app_name='myapp', update_step_ms=1,
stream_rollover=50):
self.app_name = app_name
self.data_format = data_format
self.update_step_ms = update_step_ms
self.stream_rollover = stream_rollover
self.figs = []
self.lines = []
self.figs_n = 0
def start_process(self):
self.process =\
Process(target=self.start_io_loop,
args=(self.child_conn,),
daemon=True)
self.process.start()
# Safe update
@gen.coroutine
def locked_update(self):
try:
x = self.cc.recv() # This is blocking
except EOFError:
return
self.source.stream(x, rollover=self.stream_rollover)
def start_io_loop(self, child_conn):
self.io_loop = IOLoop.current()
self.server = Server(applications={
'/'+self.app_name: Application(FunctionHandler(self.make_document))
}, io_loop=self.io_loop, port=5001)
self.server.start()
self.server.show('/'+self.app_name)
self.cc = child_conn
self.io_loop.start()
def make_document(self, doc):
# Define data source
self.source = ColumnDataSource(self.data_format)
# Add figures and lines to plots to document
figs = []
for f in self.figs:
fig = figure(**f)
figs.append(fig)
for line in self.lines:
for fig_n, params in line.items():
figs[fig_n].line(*params[0], **params[1], source=self.source)
for f in figs:
doc.add_root(f)
###################################################################
# If you don't need/want to use add_figure, or add_line_to_figure
# you can just make a fixed layout
###################################################################
# fig1 = figure(title="FIG 1")
# fig1.line("t", "x", line_color="pink", source=self.source)
# fig1.line("t", "y", line_color="red", source=self.source)
# fig1.line("t", "z", line_color="blue", line_width=2,
# source=self.source)
# doc.add_root(fig1)
# fig2 = figure(title="FIG 2")
# fig2.line('t', 'other', source=self.source)
# doc.add_root(fig2)
###################################################################
doc.add_periodic_callback(self.locked_update, self.update_step_ms)
def add_figure(self, **kwargs):
self.figs.append(kwargs)
self.figs_n += 1
return self.figs_n-1
def add_line_to_figure(self, f, *args, **kwargs):
self.lines.append({f: (args, kwargs)})
if __name__ == '__main__':
# Example usage
import time
import random
import math
dict_data = {'t': [], 'x': [], 'y': [], 'z': [], 'other': []}
app = LivePlotApp(data_format=dict_data, app_name="Demo_app")
f1 = app.add_figure(title="Fig 1", plot_width=300, plot_height=300)
app.add_line_to_figure(f1, "t", "y", line_color="pink")
app.add_line_to_figure(f1, "t", "z", line_color="red", line_width=2)
app.add_line_to_figure(f1, "t", "x", line_color="blue")
app.add_line_to_figure(f1, "t", "x")
f2 = app.add_figure(title="Fig 2", plot_width=300, plot_height=300)
app.add_line_to_figure(f2, "t", "other", line_color="blue")
app.start_process()
i = 0
dt = 0.01 # Send data at 100Hz
t_sent = time.perf_counter()
while True:
time.sleep(0.1)
t = time.perf_counter()
dict_data['other'] = [random.randint(-50, 50)]
dict_data['t'] = [time.perf_counter()],
dict_data['x'] = [7 if i < 110 else 15]
dict_data['y'] = [i % 100]
dict_data['z'] = [10*math.sin(t*2*math.pi)]
if (t-t_sent > dt):
t_sent = time.perf_counter()
app.parent_conn.send(dict_data)
i += 1
from bokeh.server.server import Server
from bokeh.application import Application
from bokeh.application.handlers.function import FunctionHandler
from bokeh.plotting import figure, ColumnDataSource
from tornado import gen
from tornado.ioloop import IOLoop
from multiprocessing import Process, Pipe
class LivePlotApp(object):
parent_conn, child_conn = Pipe()
def __init__(self, data_format, app_name='myapp', update_step_ms=1,
stream_rollover=50):
self.app_name = app_name
self.data_format = data_format
self.update_step_ms = update_step_ms
self.stream_rollover = stream_rollover
self.figs = []
self.lines = []
self.figs_n = 0
def start_process(self):
self.process =\
Process(target=self.start_io_loop,
args=(self.child_conn,),
daemon=True)
self.process.start()
# Safe update
@gen.coroutine
def locked_update(self):
try:
x = self.cc.recv() # This is blocking
except EOFError:
return
self.source.stream(x, rollover=self.stream_rollover)
def start_io_loop(self, child_conn):
self.io_loop = IOLoop.current()
self.server = Server(applications={
'/'+self.app_name: Application(FunctionHandler(self.make_document))
}, io_loop=self.io_loop, port=5001)
self.server.start()
self.server.show('/'+self.app_name)
self.cc = child_conn
self.io_loop.start()
def make_document(self, doc):
# Define data source
self.source = ColumnDataSource(self.data_format)
# Add figures and lines to plots to document
figs = []
for f in self.figs:
fig = figure(**f)
figs.append(fig)
for line in self.lines:
for fig_n, params in line.items():
figs[fig_n].line(*params[0], **params[1], source=self.source)
for f in figs:
doc.add_root(f)
###################################################################
# If you don't need/want to use add_figure, or add_line_to_figure
# you can just make a fixed layout
###################################################################
# fig1 = figure(title="FIG 1")
# fig1.line("t", "x", line_color="pink", source=self.source)
# fig1.line("t", "y", line_color="red", source=self.source)
# fig1.line("t", "z", line_color="blue", line_width=2,
# source=self.source)
# doc.add_root(fig1)
# fig2 = figure(title="FIG 2")
# fig2.line('t', 'other', source=self.source)
# doc.add_root(fig2)
###################################################################
doc.add_periodic_callback(self.locked_update, self.update_step_ms)
def add_figure(self, **kwargs):
self.figs.append(kwargs)
self.figs_n += 1
return self.figs_n-1
def add_line_to_figure(self, f, *args, **kwargs):
self.lines.append({f: (args, kwargs)})
if __name__ == '__main__':
# Example usage
import time
import random
import math
dict_data = {'t': [], 'x': [], 'y': [], 'z': [], 'other': []}
app = LivePlotApp(data_format=dict_data, app_name="Demo_app")
f1 = app.add_figure(title="Fig 1", plot_width=300, plot_height=300)
app.add_line_to_figure(f1, "t", "y", line_color="pink")
app.add_line_to_figure(f1, "t", "z", line_color="red", line_width=2)
app.add_line_to_figure(f1, "t", "x", line_color="blue")
app.add_line_to_figure(f1, "t", "x")
f2 = app.add_figure(title="Fig 2", plot_width=300, plot_height=300)
app.add_line_to_figure(f2, "t", "other", line_color="blue")
app.start_process()
i = 0
dt = 0.01 # Send data at 100Hz
t_sent = time.perf_counter()
while True:
time.sleep(0.1)
t = time.perf_counter()
dict_data['other'] = [random.randint(-50, 50)]
dict_data['t'] = [time.perf_counter()],
dict_data['x'] = [7 if i < 110 else 15]
dict_data['y'] = [i % 100]
dict_data['z'] = [10*math.sin(t*2*math.pi)]
if (t-t_sent > dt):
t_sent = time.perf_counter()
app.parent_conn.send(dict_data)
i += 1
来自bokeh.server.server导入服务器
从bokeh.application导入应用程序
从bokeh.application.handlers.function导入FunctionHandler
从bokeh.plotting导入图,ColumnDataSource
从龙卷风进口
从tornado.ioloop导入ioloop
从多处理导入进程,管道
类LivePlotApp(对象):
父连接,子连接=管道()
定义初始化(self,数据格式,app\u name='myapp',update\u step\u ms=1,
流(滚动=50):
self.app\u name=app\u name
self.data\u format=数据\u格式
self.update\u step\u ms=update\u step\u ms
self.stream\u rollover=stream\u rollover
self.figs=[]
self.lines=[]
self.figus\u n=0
def启动_过程(自):
自我过程=\
进程(目标=self.start\u io\u循环,
args=(self.child_conn,),
daemon=True)
self.process.start()文件
#安全更新
@科罗廷将军
def锁定_更新(自):
尝试:
x=self.cc.recv()#这是阻塞
除EOFError外:
返回
self.source.stream(x,rollover=self.stream\u rollover)
def start_io_循环(自、子连接):
self.io_loop=IOLoop.current()
self.server=服务器(应用程序)={
'/'+self.app_名称:应用程序(FunctionHandler(self.make_文档))
},io_loop=self.io_loop,端口=5001)
self.server.start()
self.server.show('/'+self.app_name)
self.cc=儿童连接
self.io_loop.start()
def生成文档(自我、文档):
#定义数据源
self.source=ColumnDataSource(self.data\u格式)
#将图形和线条添加到绘图到文档中
无花果=[]
对于f,在self.figs中:
图=图(**f)
图附加(图)
对于self.line中的行:
对于图n,第行中的参数。items():
图[fig_n]。行(*params[0],**params[1],source=self.source)
对于图中的f:
文件添加根(f)
###################################################################
#如果您不需要/不想使用“添加图形”或“将线添加到图形”
#你可以做一个固定的布局
###################################################################
#图1=图(title=“图1”)
#图1.line(“t”、“x”,line_color=“pink”,source=self.source)
#图1.线条(“t”,“y”,线条颜色=“红色”,来源=自身来源)
#图1.线条(“t”,“z”,线条颜色=“蓝色”,线条宽度=2,
#source=self.source)
#文件添加根目录(图1)
#图2=图(title=“图2”)
#图2.行('t','other',source=self.source)
#添加文档根(图2)
###################################################################
文档添加定期回调(自锁更新、自更新步骤)
def add_图(自身,**kwargs):
自身图附加(kwargs)
自选图n+=1
返回自我。图n-1
def将线添加到图形(self、f、*args、**kwargs):
self.lines.append({f:(args,kwargs)})
如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu':
#示例用法
导入时间
随机输入
输入数学
dict_data={'t':[],'x':[],'y':[],'z':[],'other':[]
app=LivePlotApp(数据格式=dict\u数据,app\u name=“Demo\u app”)
f1=应用添加图形(title=“图1”,绘图宽度=300,绘图高度=300)
应用程序。在图形中添加线条(f1,“t”,“y”,线条颜色=“粉色”)
应用。将线添加到图中(f1,“t”,“z”,线颜色=“红色”,线宽度=2)
应用。将线添加到图中(f1,“t”,“x”,线颜色=“蓝色”)
附录.将线添加到图中(f1,“t”,“x”)
f2=应用添加图(title=“图2”,绘图宽度=300,绘图高度=300)
在图中添加线条(f2,“t”,“其他”,线条颜色=“蓝色”)
app.start_进程()
i=0
dt=0.01#以100Hz的频率发送数据
t_sent=time.perf_计数器()
尽管如此:
睡眠时间(0.1)
t=时间。性能计数器()
dict_data['other']=[random.randint(-50,50)]
dict_data['t']=[time.perf_counter()],
dict_data['x']=[7如果i<110,则为15]
dict_数据['y']=[i%100]
dict_data['z']=[10*math.sin(t*2*math.pi)]
如果(t-t_发送>dt):
t_sent=time.perf_计数器()
应用程序父连接发送(dict数据)
i+=1
这一行后面有一个逗号:
dict_data['t']=[time.perf_counter()],
它用一个列表创建一个元组,结果把事情搞砸了。你说得对!!在Python中,事情可能会以微妙的方式“失败”,而不是真正失败,这总是让我感到惊讶:)谢谢你发现了它!我选择Clojure的众多原因之一