filtered = data.filter(function(penguin) {
return bill_length_min < penguin.bill_length_mm &&
islands.includes(penguin.island);
})
使用 Observable
概述
Quarto 原生支持 Observable JS,这是由 Mike Bostock(也是 D3 的作者)创建的一组对原生 JavaScript 的增强功能。Observable JS 以其 响应式运行时 为特点,特别适合交互式数据探索和分析。
Observable JS 的创建者(Observable, Inc.)在 https://observablehq.com/ 上运行一个托管服务,你可以在那里创建和发布笔记本。此外,你可以通过其 核心库 在独立的文档和网站中使用 Observable JS(“OJS”)。Quarto 使用这些库以及一个在渲染时运行的 编译器 来实现在 Quarto 文档中使用 OJS。
OJS 在任何 Quarto 文档中都可以工作(纯 Markdown 以及 Jupyter 和 Knitr 文档)。只需在 {ojs}
可执行代码块中包含你的代码。本文的其余部分将解释如何使用 OJS 与 Quarto 的基本知识。
示例
我们将从一个基于 Allison Horst 的 Palmer Penguins 数据集的简单示例开始。这里我们看一下企鹅的体重如何随性别和物种变化(使用提供的输入按喙长和岛屿过滤数据集):
让我们来看看这个示例的源代码。首先我们创建一个 {ojs}
单元格,使用 FileAttachment 从 CSV 文件中读取一些数据:
上面的示例并没有绘制所有的数据,而是绘制了一个过滤后的子集。为了创建我们的过滤器,我们需要一些输入,并且我们希望能够在我们过滤函数中使用这些输入的值。为此,我们使用 viewof
关键字和一些标准的 Inputs:
```{ojs}
viewof bill_length_min = Inputs.range(
[32, 50],
{value: 35, step: 1, label: "喙长(最小值):"}
)
viewof islands = Inputs.checkbox(
["Torgersen", "Biscoe", "Dream"],
{ value: ["Torgersen", "Biscoe"],
label: "岛屿:"
}
)
```
现在我们编写过滤函数,该函数将使用 bill_length_min
和 island
的值对从 CSV 读取的 data
进行转换。
```{ojs}
filtered = data.filter(function(penguin) {
return bill_length_min < penguin.bill_length_mm &&
islands.includes(penguin.island);
})
```
这里我们可以看到响应式的作用:我们不需要任何特殊语法来引用动态输入值,它们“直接可用”,并且当输入变化时,过滤代码会自动重新运行。这与你在更新单元格时,其他引用它的单元格会重新计算的工作方式非常相似。
最后,我们将使用 Observable Plot(一个用于快速可视化表格数据的开源 JavaScript 库)绘制过滤后的数据:
```{ojs}
Plot.rectY(filtered,
Plot.binX(
{y: "count"},
{x: "body_mass_g", fill: "species", thresholds: 20}
))
.plot({
facet: {
data: filtered,
x: "sex",
y: "species",
marginRight: 80
},
marks: [
Plot.frame(),
]
}
)
```
请注意,与我们输入的方式一样,我们在没有任何特殊语法的情况下引用 filtered
变量——绘图代码将在 filtered
变化时自动重新运行(而这又是每当输入变化时更新的)。
这涵盖了 OJS 的基本端到端使用(有关完整的源代码,请参阅 Penguins 示例)。
如果你查看 Penguins 代码,你会注意到一些有趣的地方:输入和绘图代码是在数据处理代码之前定义的。这展示了 OJS 单元格执行与传统笔记本之间的一个重要区别:单元格不需要按特定顺序定义。 由于执行是完全响应式的,运行时会根据单元格之间的引用关系自动按正确顺序执行单元格。这更像是一个电子表格,而不是传统的线性单元格执行的笔记本。
库
我们上面的示例使用了几个标准库,包括:
Observable stdlib —— DOM操作、文件处理、代码导入等的核心原语。
Observable Inputs —— 标准的输入控件,包括滑块、下拉菜单、表格、复选框等。
Observable Plot —— 用于探索性数据可视化的高级绘图库。
这些库有些特殊,因为它们在https://observablehq.com上的笔记本以及Quarto文档中的{ojs}
单元格中自动可用。
使用其他JavaScript库也很简单,只需明确导入即可。例如,这里我们使用require函数导入一些库(该函数从jsDelivr加载NPM模块):
```{ojs}
d3 = require("d3@7")
topojson = require("topojson")
```
有关使用标准库和第三方库的更多信息,请参阅Libraries文章。
数据源
在我们最初的示例中,我们使用了一个FileAttachment作为数据源。文件附件支持多种格式,包括CSV、TSV、JSON、Arrow(未压缩)和SQLite,因此是读取已准备好进行分析的数据集的便捷方式。
通常,您需要在进行可视化之前使用Python或R对数据进行预处理。使用Quarto,您可以在文档渲染期间进行此预处理,然后将结果提供给OJS。
使用Python或R中的ojs_define()
函数定义您希望在JavaScript中使用的变量。例如,要在Python中重现简单的CSV读取,您可以这样做:
```{python}
import pandas as pd
penguins = pd.read_csv("palmer-penguins.csv")
ojs_define(data = penguins)
```
调用ojs_define(data = penguins)
表示我们希望将名为data
的变量(其值为penguins
数据框)提供给OJS。
根据您使用的可视化库,可能需要一个额外的步骤来从JavaScript中消费数据。在这种情况下,Plot
函数期望按行而不是按列的数据,因此我们在过滤之前进行transpose()
:
```{ojs}
filtered = transpose(data).filter(function(penguin) {
return bill_length_min < penguin.bill_length_mm &&
islands.includes(penguin.island);
})
```
有关准备和读取数据的更多方式,请参阅Data Sources文章。
OJS单元格
有许多选项可用于自定义{ojs}
代码单元格的行为,包括显示、隐藏和折叠代码,以及控制输出的可见性和布局。
最重要的单元格选项是echo
选项,它控制是否显示源代码。根据您是将可视化嵌入文章中还是创建笔记本或完整的教程,您会有不同的偏好。
默认情况下,{ojs}
单元格中的代码会被显示。要阻止整个文档的代码显示,请在YAML元数据中设置echo: false
选项:
---
title: "My Document"
execute:
echo: false
---
您也可以在每个单元格基础上指定此选项。例如:
```{ojs}
//| echo: false
data = FileAttachment("palmer-penguins.csv").csv({ typed: true })
```
要了解所有可用选项,请参阅OJS Cells文章。
除了中断markdown流程的OJS单元格,您还可以包含内联代码。更多关于内联代码的信息,请参阅Inline Code文章。
学习更多
这些文章更深入地介绍了在Quarto文档中使用OJS:
Libraries 涵盖了使用标准库和外部JavaScript库。
Data Sources 概述了读取和预处理数据的各种方式。
OJS Cells 更深入地介绍了单元格执行、输出和布局。
Shiny Reactives 描述了如何将Shiny与OJS集成。
Code Reuse 深入探讨了在多个文档中重用OJS代码的方式。
如果您想了解更多关于响应式底层机制的信息,请查看Mike Bostock的这些笔记本: - 五分钟入门