
数据可视化库Observable Plot 源码深度解析一认识 Observable Plot配套代码仓库https://github.com/observablehq/plot即observablehq/plotv0.6.17 源码本体图表渲染说明本文档内嵌 mermaid流程图/时序图/状态图/概念关系/类结构代码块在 Typora、Obsidian、VS Code 插件、GitHub 等平台均可自动渲染。写在前面主要目的这份解析主要是解决下面几个问题自底向上理解一张图是怎么画出来的从Plot.plot({...})调用开始沿着源码真正走一遍数据 → 通道 → 比例尺 → 像素的全链路。理解 Plot 与 D3 的真实分工知道 Plot 用 D3 的哪些模块、各自负责什么、什么时候该绕开 Plot 直接用 D3。有能力自己实现一个迷你图表库第 13 章给出一个最简可用的声明式图表库骨架参考 Plot 的抽象。可以扩展新图表类型知道注册自定义 Mark、Scale、Transform 的入口与约束。图例图类型用途渲染要求mermaid概念关系、流程图、时序图、状态图、模块依赖架构图mermaid 引擎第 1 章 认识 Observable Plot1.1 一个最小示例import*asPlotfromhttps://cdn.jsdelivr.net/npm/observablehq/plot0.6/esm;// 数据12 个城市的气温constcities[{name:北京,temp:12},{name:上海,temp:17},{name:广州,temp:23},{name:深圳,temp:23},{name:杭州,temp:16},{name:成都,temp:15},{name:武汉,temp:16},{name:西安,temp:13},{name:南京,temp:15},{name:重庆,temp:18},{name:天津,temp:12},{name:苏州,temp:16}];// 一行代码水平条形图constchartPlot.barX(cities,{y:name,x:temp});Plot.barX(cities, {y: name, x: temp})调用返回的是 SVGg元素。包裹到 DOMdocument.querySelector(#app).append(chart);//document.body.append(chart);就得到一张图。Plot 没有图表容器没有 axes 配置没有 margin 配置没有 xScale、yScale 的代码——全部由 Plot 根据你的{y: name, x: temp}推断出来。完整的在HTML代码为!DOCTYPEhtmldiv idapp/divscript typemoduleimport*asPlotfromhttps://cdn.jsdelivr.net/npm/observablehq/plot0.6/esm;// 数据12 个城市的气温constcities[{name:北京,temp:12},{name:上海,temp:17},{name:广州,temp:23},{name:深圳,temp:23},{name:杭州,temp:16},{name:成都,temp:15},{name:武汉,temp:16},{name:西安,temp:13},{name:南京,temp:15},{name:重庆,temp:18},{name:天津,temp:12},{name:苏州,temp:16}];// 一行代码水平条形图constchartPlot.barX(cities,{y:name,x:temp}).plot();constcontainerdocument.querySelector(#app);container.append(chart);/script结果如图当然最佳探索该图表库的地方是https://observablehq.com/1.2 Plot 要解决的痛点如果用 D3 命令式手写同样的柱状图至少要做这些事基于d3v7!DOCTYPEhtmldiv idapp/divscript typemoduleimport*asd3fromhttps://cdn.jsdelivr.net/npm/d37/esm;constcities[{name:北京,temp:12},{name:上海,temp:17},{name:广州,temp:23},{name:深圳,temp:23},{name:杭州,temp:16},{name:成都,temp:15},{name:武汉,temp:16},{name:西安,temp:13},{name:南京,temp:15},{name:重庆,temp:18},{name:天津,temp:12},{name:苏州,temp:16}];// Plot默认进行了排序constsortedData[...cities].sort((a,b)a.nameb.name?-1:1);constmargin{top:20,right:30,bottom:45,left:60};constwidth800-margin.left-margin.right;constheight460-margin.top-margin.bottom;// 画布属性constsvgd3.select(#app).append(svg).attr(width,widthmargin.leftmargin.right).attr(height,heightmargin.topmargin.bottom).append(g).attr(transform,translate(${margin.left},${margin.top}));//y比例尺constyScaled3.scaleBand().domain(sortedData.map(dd.name)).range([0,height])// barX Y轴反转.padding(0.15);//x比例尺constxScaled3.scaleLinear().domain([0,24]).range([0,width]);//柱状图的条svg.selectAll(rect).data(sortedData).enter().append(rect).attr(y,dyScale(d.name)).attr(height,yScale.bandwidth()).attr(x,0).attr(width,dxScale(d.temp)).attr(fill,#000000);//画y轴/x轴svg.append(g).call(d3.axisLeft(yScale));svg.append(g).attr(transform,translate(0,${height})).call(d3.axisBottom(xScale));// 添加轴标题svg.append(text).attr(x,width).attr(y,height32).attr(text-anchor,end).text(temp →);svg.append(text).attr(transform,rotate(-90)).attr(x,-height/2).attr(y,-42).attr(text-anchor,middle).text(name);/scriptD3 这段代码量明显大于Plot 差距来自维度D3命令式Plot声明式选元素selectAll自动绑数据.data()data参数算比例尺手动 domain/range自动推断算坐标手动 attr自动 scale 应用画轴axisBottom手动隐式推断边距手动 translate自动 margin1.3 Plot 的定位Plot 不是 D3 的替代品而是 D3 之上的声明式语法层。Plot 的 package.json 一行说清楚了依赖关系// package.json (line 80)dependencies:{d3:^7.9.0,interval-tree-1d:^1.0.0,isoformat:^0.2.0,rimraf:^6.1.3}D3 7.9 是唯一核心运行时依赖。其他只是辅助interval-tree-1d 用于二维区间查询、isoformat 用于日期格式、rimraf 用于构建。设计哲学Plot 用最少的代码完成常见的探索式数据可视化把 D3 的装配语言地位保留给精细控制场景。——Mike Bostock1.4 总结Plot 是声明式的你描述图长什么样不描述怎么画。Plot 是组合式的marks数组里堆叠多个标记。Plot 是可推导的比例尺、坐标轴、图例、边距都能从你的数据 选项自动算出。Plot 是 D3 7.9 之上的高层语法层底层还是 D3。