Observable Plot — 你说想看哪两列的关系,库自己画图
是什么
Observable Plot 是一个 JS 画图库,你只告诉它”我有这些数据,请把 A 列放横轴、B 列放纵轴、按 C 列染色”,它自己算坐标轴、自己选颜色、自己画刻度。日常类比:像点菜——你说”我要辣的、要清淡的、要海鲜”,厨师自己决定油盐糖怎么配。
它由 D3.js 的作者 Mike Bostock 在 2021 年带队开源。同一个人,同一群用户,两条路:D3 给你画笔和颜料(什么都能画但什么都得自己画),Plot 给你菜单(图表类型多、默认美观、几乎不用调)。
import * as Plot from "@observablehq/plot"
Plot.plot({ marks: [Plot.dot(data, {x: "克拉数", y: "价格", fill: "切工"})]})四行代码 → 一张带坐标轴、刻度、图例的散点图。没问你 scale 怎么选、轴怎么画、颜色怎么映射。
为什么重要
不理解 Plot,下面这些事都没法解释:
- 为什么 D3 火了 12 年后,作者亲自做了一个反 D3 的库
- 为什么数据科学家在 R 里用 ggplot2 用得很爽,到 JS 里就抱怨”画图好难”——Plot 就是回应
- 为什么 LLM 自动生成可视化(“画一个 X vs Y 的散点图”)越来越好用——声明式 API 比命令式好生成 10 倍
- 为什么”默认美观”也是一种核心竞争力——前端工程师可能不在乎,数据分析师非常在乎
核心要点
Plot 的整个 API 折叠成 四个概念:
-
Mark(标记):你想画什么形状?点、线、柱、面、文字……Plot 提供 35+ 种 mark。类比:菜单上的菜名(“红烧肉”、“清炒时蔬”)。
-
Channel(通道):把数据列接到视觉变量上。
x: "克拉数"意思”横轴接克拉数列”,fill: "切工"意思”颜色接切工列”。类比:把电源插头接到电器(数据是电、视觉是电器)。 -
Scale(尺度):把数据值(数字、字符串、日期)转换成屏幕坐标 / 颜色 / 大小。Plot 自动从数据类型推断(连续数字 → 线性轴,日期 → 时间轴,字符串 → 类别轴)。
-
Transform(变换):在数据进 mark 前预处理(分桶、分组、归一化、堆叠)。类比:菜送上桌前的”切丝/切丁/打成泥”。
写过 D3 的人对这四个概念会感慨”原来这些都该库自己做”。
实践案例
案例 1:散点图叠拟合线(grammar 的层叠思想)
Plot.plot({ marks: [ Plot.dot(diamonds, {x: "carat", y: "price", fill: "cut"}), Plot.linearRegressionY(diamonds, {x: "carat", y: "price"}) ]})逐部分解释:
marks: [...]是图层数组,顺序就是叠放顺序(后面的盖前面)- 第一个 mark 画散点(颜色按
cut切工分组) - 第二个 mark 画线性回归拟合线(自动算斜率截距)
- 横纵轴共享:两个 mark 都映射到同一对
x/y,scale 只算一次
这就是 grammar of graphics 的核心:多 mark 叠加 = 多语言句子组合。
案例 2:facet 分面(ggplot2 招牌移植到 JS)
Plot.plot({ marks: [Plot.dot(diamonds, {x: "carat", y: "price"})], facet: {data: diamonds, x: "cut"}})facet.x: "cut" → 按切工值拆成 N 张子图,每张子图共享 y scale 但 x 各自独立。
为什么有用:传统 dashboard 要画 5 张子图你得手写 5 遍循环 + 5 个 SVG 容器 + 5 套 axis。Plot 一行 facet,库自己拆。
案例 3:bin 变换 → 直方图
Plot.plot({ marks: [ Plot.rectY(diamonds, Plot.binX({y: "count"}, {x: "carat"})) ]})逐部分解释:
Plot.binX({y: "count"}, {x: "carat"})是 transform:把carat列分桶,每桶算个数- 套在
Plot.rectY外面:rect mark 画柱子,柱子的高度是桶里的个数 - transform 与 mark 解耦:同一个 binX 可以套到 dot / line / area 任何 mark 上
这就是”先把数据揉一揉,再交给画图”的声明式管道。
踩过的坑
-
完全声明式无逃生舱口:grammar 表达不了的图(如力导向图、3D 网格)必须退回 D3,不能在 Plot.plot 内部插自己写的 d3 代码——只能写完整的自定义 mark。
-
SVG-only 大数据卡死:≤10k 数据点流畅,>10k 浏览器明显卡顿。作者在 Twitter 公开说”暂不计划 Canvas 后端”——金融实时图表 / 科学计算大数据场景请用 D3 + Canvas 或 deck.gl。
-
与 React 集成不优雅:Plot 是命令式 vanilla DOM API,不是 React 组件。每次 data 变就要
useEffect → remove → append,整张图重画,tooltip 状态 / 鼠标位置都丢。 -
transform 链顺序看嵌套不看数组:
Plot.binX({y: "count"}, Plot.normalizeY({basis: "sum"}, {x: "carat"}))里 normalizeY 先执行(内层先),binX 后执行。新手以为是数组顺序常踩坑。
适用 vs 不适用场景
适用:
- 数据探索性分析(EDA):写 5 行就出散点 / 直方 / 箱线图,比 matplotlib 还快
- Observable notebook 内的可视化(Plot 的”原生家园”)
- 需要 ggplot2 风格 facet 分面 / mark 叠加的场景
- 静态站点 SSR 出图(构建期算完,运行期零 JS)
不适用:
- 需要 React 组件式 API → 用 recharts / visx
- 需要 >10k 数据点流畅交互 → 用 d3 + Canvas,或 deck.gl
- 需要丰富内置图表 + 配置式 API → 用 echarts
- 需要复杂动画时序(缓动、关键帧)→ 用 gsap 配合
历史小故事(可跳过)
- 1999 年:统计学家 Leland Wilkinson 出版《The Grammar of Graphics》,第一次用”图表语法”思维系统化所有统计图,纯理论无实现。
- 2005 年:Hadley Wickham 在 R 实现 ggplot2,让 grammar of graphics 走进数据科学日常,成为 R 数据分析的可视化标准。
- 2011 年:Mike Bostock 发布 D3.js,给 JS 社区底层武器,能画一切但学习曲线极陡。
- 2017 年:Bostock 离开纽约时报,创立 Observable 公司做 reactive notebook(JS 版 Jupyter)。
- 2021 年 4 月:Plot 0.1 发布,把 ggplot2 思想搬进 JS,10 个 mark 起步。
- 2023 年:Plot 0.6 加 transition 动画 + 35+ marks,成为 Observable notebook 默认引擎。
学到什么
- 隐藏底层 vs 暴露底层 是设计哲学分野:Plot 隐藏 D3 让团队效率高,visx 暴露 D3 让用户进阶
- “默认美观”是核心卖点——Bostock 把 typography 与 spacing 调到工业级,这是 D3 默认产物的弱项
- 声明式 API 在 LLM 时代价值放大:自然语言”画一个 X vs Y 的散点图”能直接生成 Plot spec,比让 LLM 写 D3 代码可靠得多
- 目标用户错位也是一种风险:Plot 想给数据科学家,但 JS 主用户是前端工程师;前端用 Recharts 多于 Plot
延伸阅读
- 官方文档:observablehq.com/plot(最佳入口,含数百实例)
- 官方教程:Plot Cheatsheets(一图速览所有 mark)
- 设计哲学:Plot’s Design Principles(Bostock 亲自写)
- 经典书:Hadley Wickham《ggplot2: Elegant Graphics for Data Analysis》(grammar of graphics 在 R 的标准教程)
- d3 —— Plot 内部依赖的底层库,反向哲学
- echarts —— 配置式可视化库,与 Plot 声明式同一阵营但思路不同
关联
- d3 —— Plot 内部依赖 + 哲学反向(D3 命令式底层,Plot 声明式高层)
- echarts —— 配置式可视化库,与 Plot 声明式同一阵营
- visx —— React-first + 暴露 D3,Plot 的反例
- recharts —— 完全 React 组件 + JSX,Plot 的另一反例
- gsap —— 动画引擎,Plot 没有内建动画系统的对照
- dnd-kit —— 拖拽 toolkit,与 Plot 同样选了”声明式 + 命令式 vanilla”两栖路线
反向链接
- antv-f2 —— AntV F2 — 移动端 Canvas 图表,G2 同语法的轻量子集
- antv-g2 —— AntV G2 — 把 Grammar of Graphics 写成 JavaScript
- apexcharts —— ApexCharts — 自带响应式与注解的 SVG 图表库
- chart-js —— Chart.js — Canvas 渲染入门级图表
- chartist —— Chartist — 极简 SVG 图表
- d3 —— D3.js — 不是图表库,是写图表库的乐高
- dnd-kit —— dnd-kit — React 现代拖拽 toolkit
- echarts —— Apache ECharts — 给一个 JSON 就能画图的可视化库
- gsap —— GSAP — GreenSock 高性能动画
- matplotlib —— matplotlib — Python 绘图基石
- observable-framework —— Observable Framework — 编译期跑数据,浏览器只看结果
- react-hook-form —— react-hook-form — input 不进 React state 也能写表单
- recharts —— Recharts — 用 JSX 直接拼出图表的 React 组件库
- regl —— regl — 函数式 WebGL 封装
- vega —— Vega — 整张图就是一棵 JSON
- visx —— visx — 把 d3 拆成 30 块乐高的 React 可视化原语