Plotly Dash和OmniSciDB用于实时数据可视化

在本文中,我们讨论了如何使用Plotly Dash和OmniSciDB创建具有实时数据可视化的仪表板。

在过去的一年中,OmniSci F1 Demo遍历了美国各地的会议和聚会。通过允许参与者在F1视频游戏中行驶几圈并查看流向遥测结果的流媒体,OmniSciDB,社区和活动团队可以以高度参与的方式演示OmniSci平台。

在本文中,我将逐步介绍如何使用Plotly Dash创建实时仪表板,并概述将OmniSciDB用作自定义应用程序的数据源时要记住的一些注意事项。

F1 Demo GitHub存储库中提供了此博客文章中解释的所有代码。

Flask + Bootstrap + React.js =破折号

Python社区并没有像R社区和Shiny那样将单个开源项目合并在一起,但是Dash在设计和目标上感到相似。通过Dash Bootstrap组件可以使用诸如模板化和网格布局选项之类的用户友好功能,并且由于Dash是基于React.js构建的,因此默认情况下可以进行交互。对我而言,选择Dash的最大优势是它基于我已经有经验的Python Web框架Flask构建。

布置F1仪表板仅需几行代码,类似于普通的Flask应用程序:


Plotly Dash和OmniSciDB用于实时数据可视化

F1仪表板可以认为是两个部分:

  1. 导航栏,其中包含应用程序标题和公司品牌以及
  2. 主体由两行组成,每行都有两列以保存可视化内容。

我选择对导航栏和正文使用占位符变量,而不是将实际的小部件构建到布局中。这使我可以分别处理这四个部分,而不是将所有Dash代码嵌套在同一app.layout作业中。


Plotly Dash和OmniSciDB用于实时数据可视化

调用OmniSciDB填充可视化

Dash提供了用于在仪表板上创建和布置可视化效果的所有功能,而该应用程序的计算密集型部分则由OmniSciDB处理。为仪表板提供动力的主要查询有四个,每个小组件一个(不包括Metric 静态填充的下拉小组件):

  • 最快10圈进入排行榜。
  • 获取轨道位置(散点图)。
  • 获取车辆遥测(折线图)。
  • 填充参考单圈下拉列表(最快的50个单圈)。

这些查询中的每一个都被编写为Python函数,使用pymapd与OmniSciDB后端进行通信。让我们探索get_telemetry_data():


Plotly Dash和OmniSciDB用于实时数据可视化

该功能有几个输入,有助于从特定圈中唯一地识别和检索遥测数据。在函数主体中,您会注意到我将pymapd.connect()调用嵌入到函数内部,而不是将Connection 对象作为参数传递给函数。虽然每隔几秒钟对数据库进行一次新连接锤击并不是最佳的开发实践,但它确实可以确保每次提交新查询时都保持“新鲜”连接。

在后面的文章中,我将更多地讨论整个应用程序的性能注意事项,但是对于将应用程序作为活动展台中的单个显示器的预期用途,在每个查询上创建新的连接都不够高效。

使用Dash回调进行反应式编程

F1仪表板的交互功能是使用Dash中的回调创建的,这些回调被实现为Python装饰器。装饰器允许使用其他行为来修改Python函数,而无需实际更改底层Python函数。Dash回调装饰器具有输入和输出;更改输入(通常是菜单元素)将重新运行Python函数,从而修改页面上的特定元素(也定义为回调的一部分)。

按需回调

在仪表板上使用回调的第一种方式是下拉菜单,用于设置Reference Lap 与当前行驶性能进行比较的,以及Metric 选择要监视的遥测指标。您可以在build_telemetry_chart()函数中看到两种输入的工作方式。为了避免过多的说明,我们在这里仅查看装饰器和函数签名:


Plotly Dash和OmniSciDB用于实时数据可视化


Plotly Dash和OmniSciDB用于实时数据可视化

上面的代码段的6至28行定义了下拉菜单。有关这些行的重要注意事项是id字段。这些标签定义了整个Dash应用程序将观察状态变化的内容。当这些下拉菜单中的任何一个更改其value字段时(在用户与界面进行交互时发生),它们都会发送一个信号,build_telemetry_chart()提示需要重新运行。

当build_telemetry_chart()完成运行,它返回每回调(这对于本实施例中是id定义的输出位置它的输出telemetry-graph 在telgraph Python对象)。了解Dash中的回调的一个重要概念是,该id 值与HTML中的id概念相同,HTML是Web页面中的唯一标识符。尽管Dash是通过Python运行的,并且telgraph 是我们的Python对象,但id的回调引用是对React.js的传递,以告诉它要更新网页的哪一部分。

基于时间的回调

按需回调增加了仪表板的交互性,但是要制作实时更新的仪表板,我们需要定期刷新支持图表的数据,而无需用户干预。使用Dash Core Components库中的Interval类,我们可以设置选择触发回调的任何时间段:


Plotly Dash和OmniSciDB用于实时数据可视化

在trackgraph Python对象中,我添加了一个Interval ,每7,000毫秒更新一次,以更新track-interval ID。您会在上面的回调嵌入代码段中注意到,Input数组中的第一个值是Input('track-interval', 'n_intervals');。每7秒钟,该dcc.Interval() 代码就会触发一次,它告诉build_telemetry_chart() 函数它需要运行,然后更新telemetry-graph ID。

用这种方式控制UI元素可能会造成混乱,因为JavaScript比Python多得多!但是,回调使我们能够在应用程序中构建以下逻辑:“如果1)更改了参考间隔和/或2)更改了度量标准和/或3)7秒钟,则更新遥测图。” 因此,尽管Dash中的回调可能令人困惑,但回调非常强大,值得花时间学习。

控制Dash CSS的加载顺序

有了仪表板的布局并增加了交互性之后,仪表板小部件中实际显示的内容就是您要讲述的故事和设计敏感性的问题。我不会介绍我所做的每个设计决策,也不会介绍如何修改Darkly主题以具有OmniSci品牌色彩,但是Dash文档和用户论坛应该提供您所需要的所有答案。

当我尝试为仪表盘设置样式时,令我震惊的一件事是,我的CSS更改一直被Dash加载的React组件中的基本CSS样式覆盖。我克服这个问题的方法是用来app.index_string 控制文件的加载顺序:


Plotly Dash和OmniSciDB用于实时数据可视化

通过将{%css%} 标签放在页脚的最后,我能够确保CSS更改不会被Dash的任何默认CSS类覆盖。我的自定义CSS(作为asset / external.css文件的一部分)现在是页面上加载的最后一件事,因此,这些CSS定义就是应用程序中显示的内容。

从Flask Dev Server迁移到生产服务器

在完成Dash应用程序后,我决定留意Flask警告,不要将开发服务器用于生产工作。我使用了gunicorn和8个线程,但是由于该应用程序一次仅在一个位置(即活动展位)运行,因此我使用的次数可能更少。

如何运行Dask / Flask应用程序的安装步骤超出了本文的范围,但是Flask的官方文档以及DigitalOcean Flask指南使该过程非常轻松。

对于该特定的Dash应用程序而言,至关重要的一项决定是将该应用程序与OmniSciDB在同一服务器上运行,并使用多个GPU。通过使用更大的GPU服务器(Microsoft Azure上的ND24s实例,448GB CPU RAM / 96GB GPU RAM)并从同一服务器运行Dash应用程序,它既消除了网络延迟,又为OmniSciDB提供了可观的GPU内存缓冲区以保留数据“热”。

无论用户探索了参考圈和遥测指标的哪种组合,该应用程序使用的最大GPU RAM都约为30GB,随着额外的圈扩展了遥测数据集,剩下了足够的服务器容量。

让我们来看看您的自定义仪表盘!

在我的两个OmniSci F1 Demo帖子中,我已经展示了可以使用StreamSets将实时数据流插入OmniSciDB,还可以将OmniSciDB与Dash一起使用以创建实时仪表板。由于OmniSciDB的体系结构,因此数据工程和实时仪表板用例都是可能的,该体系结构不需要创建索引即可获得高性能的查询。一旦将数据提取到OmniSciDB中,并且可以将数据从CPU RAM传输到GPU RAM,就可以使用该数据。

对于OmniSciDB开源用户,像Dash这样的框架可以提供接近Immerse的分析体验。您唯一可能会错过的是Immerse中的一些高级功能,例如图表与后端渲染之间的自动交叉过滤(当然还有构建仪表板的无代码性质)。


分享到:


相關文章: