7 图形
- 第7章向您展示了如何向绘图添加直接交互以及如何显示以其他方式生成的图像。
我们在第 2 章中简要讨论了 renderPlot() ;它是在应用程序中显示图形的强大工具。本章将向您展示如何充分利用它来创建交互式绘图,即响应鼠标事件的绘图。您还将学习一些其他有用的技术,包括制作具有动态宽度和高度的绘图以及使用 renderImage() 显示图像。
在本章中,我们将需要 ggplot2 和 Shiny,因为我们将其用于大多数图形的绘制。
1 | library(shiny) |
7.1 交互性
plotOutput() 最酷的一点是,它不仅可以作为显示绘图的输出,还可以作为响应指针事件的输入。这允许您创建交互式图形,用户直接与绘图上的数据进行交互。交互式图形是一种强大的工具,具有广泛的应用范围。我没有空间向你展示所有的可能性,所以在这里我将重点介绍基础知识,然后向你指出资源以了解更多信息。
7.1.1 基础知识
绘图可以响应四种不同的鼠标事件: click
、 dblclick
(双击)、 hover
(当鼠标在同一位置停留一段时间时)和 brush
(矩形选择工具)。要将这些事件转换为Shiny的输入,您需要为相应的 plotOutput()
参数提供一个字符串,例如 plotOutput("plot", click = "plot_click")
。这将创建一个 input$plot_click
,您可以使用它来处理鼠标在绘图上的单击。
下面是一个非常简单的处理鼠标单击的示例。我们注册 plot_click
输入,然后使用鼠标单击的坐标更新输出。图 7.1 显示了结果。
1 | ui <- fluidPage( |
(请注意使用 req() ,以确保应用在第一次单击之前不会执行任何操作,且坐标以wt
和mpg
变量表示。
以下各节更详细地介绍了这些事件。我们将从点击事件开始,然后简要讨论密切相关的 dblclick
和 hover
。然后,您将了解 brush
事件,该事件提供了一个矩形brush
,由其四个边( xmin
, xmax
, ymin
和 ymax
定义。然后,我将举几个示例,使用动作结果更新绘图,然后讨论 Shiny 中交互式图形的一些局限性。
7.1.2 点击
点事件返回一个相对丰富的列表,其中包含大量信息。最重要的组件是 x
和 y
,它们在数据坐标中给出事件的位置。但我不打算谈论这种数据结构,因为你只在相对罕见的情况下需要(如果你确实想要细节,请在 Shiny 图库中使用这个应用程序)。相反,您将使用 nearPoints() 帮助程序,该帮助程序返回一个数据框,其中包含单击附近的行,并处理一堆繁琐的细节。
下面是 nearPoints()
运行的简单示例,其中显示了有关事件附近点的数据表。图 7.2 显示了该应用的屏幕截图。
1 | ui <- fluidPage( |
您可能想知道 nearPoints()
究竟返回了什么。这是使用 browser() 的好地方,我们在 Section 5.2.3 中讨论过:
1 | ... |
现在,在我启动应用程序并单击一个点后,我被放入交互式调试器,在那里我可以运行 nearPoints()
并查看它返回的内容:
1 | nearPoints(mtcars, input$plot_click) |
使用 nearPoints()
的另一种方法是使用 allRows = TRUE
和 addDist = TRUE
。这将返回包含两个新列的原始数据框:
dist_
给出行和事件之间的距离(以像素为单位)。selected_
表示它是否靠近 click 事件(即它是否是allRows = FALSE
) 时返回的行。
我们稍后会看到一个例子。
7.1.3 其他点事件
同样的方法对 click
、dblclick
和 hover
同样有效:只需更改参数的名称。如果需要,可以通过提供 clickOps()、dblclickOps() 或 hoverOps() 而不是给出输入 id 的字符串来获得对事件的额外控制。这些很少需要,所以我这里不讨论它们;请参阅文档以获取详细信息。
您可以在一个图上使用多种交互类型。只需确保向用户解释他们可以做什么:使用鼠标事件与应用程序交互的一个缺点是它们不能立即被发现。
7.1.4 刷涂(Brushing)
在绘图上选择点的另一种方法是使用画笔,画笔是由四个边定义的矩形选择。在 Shiny 中,一旦掌握了 click
和 nearPoints()
,使用画笔就非常简单:只需切换到 brush
参数和 brushedPoints() 辅助函数。
这是另一个简单的例子,显示了画笔选择了哪些点。图7.3显示了结果。
1 | ui <- fluidPage( |
使用 brushOptions() 控制颜色(填充和描边),或将刷涂限制为direction = "x"
或 "y"
的单个维度(例如,用于刷涂时间序列)。
7.1.5 修改绘图
到目前为止,我们已经展示了交互的结果。但是,交互的真正魅力在于,当你显示你正在交互的同一图中的变化时。不幸的是,这需要你尚未学过的先进反应技术:reactiveVal()。我们将在第16章中回到reactiveVal()
,但我想在这里展示它,因为它是一种非常有用的技术。你可能需要在阅读了那章之后重新阅读这一节,但即使没有所有的理论,你也会对潜在的应用有所了解。
正如你可能从名字中猜到的那样,reactiveVal()
非常类似于 reactive()
。通过调用带有初始值的 reactiveVal()
来创建反应性值,并以与反应性相同的方式检索该值:
1 | val <- reactiveVal(10) |
最大的区别是,你也可以更新一个响应式值,并且引用该值的所有反应式使用者都将重新计算。响应式值使用特殊的语法进行更新——你像调用函数一样调用它,第一个参数是新的值:
1 | val(20) |
这意味着使用当前值更新反应值看起来像这样:
1 | val(val() + 1) |
不幸的是,如果你真的想在控制台中运行这段代码,你会得到一个错误,因为它必须在响应式环境中运行。这使得实验和调试更具挑战性,因为你需要 browser() 或类似方法来暂停 shinyApp() 调用中的执行。这是我们将在第 16 章后面讨论的挑战之一。
现在,让我们把学习 reactiveVal()
的挑战放在一边,告诉你为什么你可能会烦恼。想象一下,您想要可视化单击与图上的点之间的距离。在下面的应用程序中,我们首先创建一个反应值来存储这些距离,并使用一个常量对其进行初始化,该常量将在我们单击任何内容之前使用。然后,我们使用 observeEvent() 来更新单击鼠标时的反应值,并使用 ggplot
可视化具有点大小的距离。总而言之,这看起来像下面的代码,结果如图 7.4 所示。
1 | set.seed(1014) |
这里有两个重要的ggplot2技术需要注意:
- 在绘制之前,我将距离添加到数据框中。我认为在可视化之前,将相关变量放在数据框中是一种很好的做法。
- 我为 scale_size_area() 设置了限制,以确保点击之间的尺寸具有可比性。为了找到正确的范围,我做了一些交互式实验,但如有需要,您可以计算出确切的细节(参见本章末尾的练习)。
有一个更复杂的想法。我想用画笔逐步添加点到一个选择。在这里,我使用不同的颜色显示选择,但你可以想象许多其他的应用程序。为了使这个工作,我将reactiveVal()
初始化为一个FALSE
的向量,然后使用brushedPoints()和|将画笔下的任何点添加到选择。为了给用户一种重新开始的方式,我双击重置选择。图7.5显示了运行应用程序的几个截图。
1 | ui <- fluidPage( |
再次,我设定了比例的限制,以确保图例(和颜色)在第一次点击后不会改变。
7.1.6 互动限制
在我们继续之前,了解交互式图中的基本数据流以了解其局限性非常重要。基本流程是这样的:
- JavaScript捕获鼠标事件。
- Shiny将鼠标事件数据发送回R,告诉应用程序输入已过时。
- 重新计算所有下游反应式使用者。
plotOutput()
生成一个新的PNG并将其发送到浏览器。
对于本地应用程序,瓶颈往往是绘制图表所花费的时间。根据图表的复杂程度,这可能需要几分之一秒的时间。但对于托管应用程序,您还必须考虑将事件从浏览器传输到R所需的时间,然后将渲染的图表从R传输回浏览器。
一般来说,这意味着不可能创建动作和响应被视为即时的Shiny应用程序(即绘图似乎与您的操作同时更新)。如果您需要这种速度,您必须在JavaScript中执行更多的计算。一种方法是使用一个封装了JavaScript图形库的R包。现在,在我写这本书的时候,我认为你会得到plotly
包的最佳体验,正如Carson Sievert在《使用R、plotly和shiny进行基于Web的交互式数据可视化》一书中所述。
7.2 动态高度和宽度
本章的其余部分不如交互式图形那么令人兴奋,但包含了一些重要的内容。
首先,可以使绘图大小具有反应性,因此宽度和高度会根据用户操作而变化。为此,向renderPlot()
的width
和height
参数提供零参数函数,这些参数现在必须在server中定义,而不是在UI中定义,因为它们可以更改。这些函数不应有参数,并返回所需的大小(以像素为单位)。它们在反应式环境中进行评估,以便您可以动态地调整绘图的大小。
以下应用程序演示了基本思想。它提供了两个直接控制绘图大小的滑块。图 7.6 中显示了几个示例屏幕截图。请注意,当您调整绘图大小时,数据保持不变:您不会获得新的随机数。
1 | ui <- fluidPage( |
图 7.6 您可以使绘图大小动态化,以便它响应用户操作。该图显示了更改宽度的效果。 | 在 https://hadley.shinyapps.io/ms-resize 上查看演示。 |
在实际应用中,您将在 width 和 height 函数中使用更复杂的表达式。例如,如果您在 ggplot2 中使用分面图,则可以使用它来增加图的大小,以保持各个分面大小大致相同。 |
7.3 图片
如果要显示现有图像(而不是绘图),则可以使用 renderImage() 。例如,您可能有一个要向用户显示的照片目录。以下应用通过展示可爱的小狗照片来说明 renderImage()
的基础知识。这些照片来自 https://unsplash.com ,我最喜欢的免版税库存照片来源。
1 | puppies <- tibble::tribble( |
renderImage()
需要返回一个列表。唯一关键的参数是 src
,一个指向图像文件的本地路径。此外,您还可以提供:
content-type
,它定义了图像的 MIME 类型。如果没有提供,Shiny 将从文件扩展名中猜测,因此只有在图像没有扩展名时才需要提供此内容。图像的
width
和height
(如果已知)。任何其他参数,如
class
或alt
,都将作为属性添加到 HTML 中的<img>
标记中。
您还必须提供deleteFile
参数。不幸的是,renderImage()
最初是设计用于处理临时文件的,因此在渲染后会自动删除图像。这显然是非常危险的,因此在Shiny 1.5.0中改变了行为。现在Shiny不再删除图像,而是强制您明确选择想要的行为。
你可以在
https://shiny.rstudio.com/articles/images.html
了解更多关于 renderImage()
的信息,并查看你可能使用它的其他方式。
7.4 总结
可视化是数据交流的强大方式,本章为您提供了几种高级技术来增强您的Shiny应用程序。接下来,您将学习向用户提供有关应用程序中正在发生的事情的反馈的技术,这对于需要花费大量时间的操作尤为重要。
加关注
关注公众号“生信之巅”。
敬告:使用文中脚本请引用本文网址,请尊重本人的劳动成果,谢谢!