Python Bokeh:线图上的意外(断开?)缩放行为

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

我正在使用Bokeh+tornado构建一个实时绘图实用程序,我遇到了一个奇怪的行为,其中绘图自动缩放的行为与预期不符,特别是有三个令人费解的问题。你知道会出什么问题吗?我能调查什么

显示行为的图片问题列表:

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
  • 线形图仅对正Y轴值自动缩放(预期图显示的是-50和50之间的随机值,但问题是Y轴的缩放比例不低于0):
  • 滚动实时绘图在特定时刻突然停止跟踪数据,进行疯狂缩放并翻转X轴,然后在一段时间后恢复(这种情况总是发生一次,并且在t=10时,两个绘图突然改变并变成这样。然后在几秒钟后恢复正常绘图):
  • 直线图仅根据一条直线自动缩放,而忽略了其余的直线(此处向上的直线将“跟随”自动缩放,这将增加最小Y,使其他直线不再可见):
  • 这是重现行为的代码:

    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的众多原因之一