如何在ObservableHQ中基于用户分组数据制作分面单元格图表?
问题描述
我有如下对象数组:
[{ "userid": 1, "groupid": 66123, "date_joined": "2023-01-01", "d0": "10", "d1": "6", "d2": "0", "d3": "1", "d4": "0", "d5": "0", "d6": "0", "d7": "0", "d8": "0", "d9": "0", "d10": "0", "d11": "0", "d12": "0", "d13": "0", "d14": "0" }, { "userid": 2, "groupid": 66123, "date_joined": "2023-01-01", "d0": "0", "d1": "0", "d2": "2", "d3": "0", "d4": "0", "d5": "0", "d6": "0", "d7": "0", "d8": "0", "d9": "0", "d10": "0", "d11": "0", "d12": "0", "d13": "0", "d14": "0" }, { "userid": 3, "groupid": 68100, "date_joined": "2023-01-01", "d0": "0", "d1": "0", "d2": "0", "d3": "0", "d4": "0", "d5": "0", "d6": "0", "d7": "0", "d8": "0", "d9": "0", "d10": "0", "d11": "0", "d12": "0", "d13": "0", "d14": "0" }]
我希望制作分面单元格图表,要求:
- 每个分面由
groupid决定 - 分面中的每一行对应一个
userid - 每行包含15个单元格,分别对应
d0至d14列
示例:第一个分面对应groupid"66123",包含两行:userid"1"和userid"2",每行有15个单元格对应d0至d14列。
参考了一段Observable Plot的示例代码,但不确定如何修改其中的x: d => d3.utcWeek.count(d3.utcYear(d.Date), d.Date),部分来实现需求:
Plot.plot({ height: 1400, x: { axis: null, padding: 0, }, y: { padding: 0, tickFormat: Plot.formatWeekday("en", "narrow"), tickSize: 0 }, fy: { reverse: true }, facet: { data: DJI, y: d => d.Date.getUTCFullYear() }, color: { type: "diverging", scheme: "PiYG" }, marks: [ Plot.cell(DJI, { x: d => d3.utcWeek.count(d3.utcYear(d.Date), d.Date), y: d => d.Date.getUTCDay(), fill: (d, i) => i > 0 ? (d.Close - DJI[i - 1].Close) / DJI[i - 1].Close : NaN, title: (d, i) => i > 0 ? ((d.Close - DJI[i - 1].Close) / DJI[i - 1].Close * 100).toFixed(1) : NaN, inset: 0.5 }) ] })
解决方案
核心是先把宽格式数据转成适合单元格图的长格式,再调整坐标轴与分面的映射逻辑:
1. 转换数据格式
将每个用户的d0-d14列拆分为单独的行,每行包含分组、用户、天数和对应数值:
// 把原宽格式数据转为长格式 const longData = data.flatMap(user => { return Array.from({length:15}, (_, i) => ({ groupid: user.groupid, userid: user.userid, day: i, // d0对应day=0,d1对应day=1,以此类推 value: +user[`d${i}`] // 转为数字类型用于颜色映射 })); });
2. 修改Plot配置代码
调整分面、坐标轴映射及单元格填充逻辑:
Plot.plot({ height: 400, // 根据实际用户数量调整高度 x: { axis: null, padding: 0, domain: [0,14] // 对应d0到d14的15个单元格 }, y: { padding: 0, tickFormat: d => `用户${d}`, // y轴显示用户ID tickSize: 0 }, facet: { data: longData, y: d => d.groupid, // 按groupid分面 label: d => `分组 ${d}` }, fy: { reverse: true }, color: { type: "linear", // 数值为非正负对比,用线性配色即可 scheme: "Blues", // 可替换为你需要的配色方案 domain: [0,10] // 匹配数据中的数值范围,可按需调整 }, marks: [ Plot.cell(longData, { x: d => d.day, // x轴对应d0-d14的序号(0-14) y: d => d.userid, // y轴对应用户ID,每个用户一行 fill: d => d.value, // 用d列的数值决定单元格颜色 title: d => `第${d.day}天: ${d.value}`, // 悬停提示信息 inset: 0.5 // 单元格之间的间距 }) ] })
关键修改说明
- 替换原时间相关的
x映射:原代码计算时间周数,现在直接用转换后的day字段(0-14)对应x轴位置 - 分面字段改为
groupid:通过facet.y指定按分组生成分面 - y轴映射为
userid:每个用户对应一行单元格 - 数据转长格式是核心:Observable Plot的cell标记更适合处理单条数据对应单个单元格的长格式结构
内容的提问来源于stack exchange,提问作者Aditya Mohile
相关产品推荐
相关产品推荐

