如何在Vega-Lite中实现季度环比(QoQ)叠加折线图?
Vega-Lite 季度同期对比双折线图配置方案
由于季度天数不固定,无法通过固定窗口偏移实现上一季度同期数据匹配,我们可以通过日期逻辑计算+关联匹配的方式解决,核心思路是为每个数据点计算对应的上一季度同期日期,再通过lookup获取对应数值。
核心实现步骤
- 解析日期并提取年、季度信息
- 计算当前日期在所属季度内的相对天数
- 推导上一季度的年份、季度及起始日期
- 根据相对天数匹配上一季度的对应日期(处理跨年度、季度天数差异问题)
- 通过lookup关联上一季度的数值
- 绘制双折线图
完整配置示例
长格式数据方案(推荐,便于统一管理图例)
{ "$schema": "https://vega.github.io/schema/vega-lite/v5.json", "data": { "values": [ {"date": "2024-01-05", "value": 100}, {"date": "2024-01-12", "value": 120}, {"date": "2024-01-19", "value": 110}, {"date": "2024-02-02", "value": 130}, {"date": "2024-02-09", "value": 140}, {"date": "2024-02-16", "value": 135}, {"date": "2024-03-01", "value": 150}, {"date": "2024-03-08", "value": 160}, {"date": "2024-03-15", "value": 155}, {"date": "2024-04-05", "value": 170}, {"date": "2024-04-12", "value": 180}, {"date": "2024-04-19", "value": 175}, {"date": "2024-05-03", "value": 190}, {"date": "2024-05-10", "value": 200}, {"date": "2024-05-17", "value": 195}, {"date": "2024-06-07", "value": 210}, {"date": "2024-06-14", "value": 220}, {"date": "2024-06-21", "value": 215} ] }, "transform": [ // 解析日期,提取年、季度 {"calculate": "datetime(datum.date)", "as": "datetime"}, {"calculate": "year(datum.datetime)", "as": "year"}, {"calculate": "quarter(datum.datetime)", "as": "quarter"}, // 计算本季度起始日期 {"calculate": "datetime(datum.year, (datum.quarter - 1) * 3, 1)", "as": "quarter_start"}, // 计算当前日期在本季度的天数偏移 {"calculate": "floor((datum.datetime - datum.quarter_start) / (1000 * 60 * 60 * 24))", "as": "day_in_quarter"}, // 推导上一季度的年份和季度(处理Q1对应去年Q4的情况) {"calculate": "datum.quarter === 1 ? datum.year - 1 : datum.year", "as": "prev_quarter_year"}, {"calculate": "datum.quarter === 1 ? 4 : datum.quarter - 1", "as": "prev_quarter"}, // 计算上一季度起始日期 {"calculate": "datetime(datum.prev_quarter_year, (datum.prev_quarter - 1) * 3, 1)", "as": "prev_quarter_start"}, // 计算上一季度总天数,避免无效日期 {"calculate": "floor((datetime(datum.prev_quarter_year, datum.prev_quarter * 3, 1) - datum.prev_quarter_start) / (1000 * 60 * 60 * 24))", "as": "prev_quarter_total_days"}, // 计算上一季度对应日期:若当前天数超过上一季度总天数,取上一季度最后一天 {"calculate": "datum.day_in_quarter < datum.prev_quarter_total_days ? datum.prev_quarter_start + datum.day_in_quarter * 1000 * 60 * 60 * 24 : datetime(datum.prev_quarter_year, datum.prev_quarter * 3, 0)", "as": "prev_quarter_date"}, // 格式化为统一字符串用于匹配 {"calculate": "timeFormat(datum.prev_quarter_date, '%Y-%m-%d')", "as": "prev_quarter_date_str"}, // 关联上一季度同期数值 { "lookup": "prev_quarter_date_str", "from": {"data": {"values": [/* 替换为你的原始数据,或引用外部数据源 */]}, "key": "date", "fields": ["value"]}, "as": "prev_quarter_value" }, // 转换为长格式,方便绘制双折线 {"fold": ["value", "prev_quarter_value"], "as": ["series", "value"]} ], "mark": "line", "encoding": { "x": {"field": "datetime", "type": "temporal", "title": "日期"}, "y": {"field": "value", "type": "quantitative", "title": "数值"}, "color": { "field": "series", "type": "nominal", "title": "数据类型", "scale": {"domain": ["value", "prev_quarter_value"], "range": ["#1f77b4", "#ff7f0e"]}, "legend": {"labelExpr": "datum.value === 'value' ? '实际数据' : '上一季度同期'"} } } }
层叠折线方案(适合自定义每条线样式)
{ "$schema": "https://vega.github.io/schema/vega-lite/v5.json", "data": {"values": [/* 你的原始数据 */]}, "transform": [ {"calculate": "datetime(datum.date)", "as": "datetime"}, {"calculate": "year(datum.datetime)", "as": "year"}, {"calculate": "quarter(datum.datetime)", "as": "quarter"}, {"calculate": "datetime(datum.year, (datum.quarter - 1) * 3, 1)", "as": "quarter_start"}, {"calculate": "floor((datum.datetime - datum.quarter_start) / (1000 * 60 * 60 * 24))", "as": "day_in_quarter"}, {"calculate": "datum.quarter === 1 ? datum.year - 1 : datum.year", "as": "prev_quarter_year"}, {"calculate": "datum.quarter === 1 ? 4 : datum.quarter - 1", "as": "prev_quarter"}, {"calculate": "datetime(datum.prev_quarter_year, (datum.prev_quarter - 1) * 3, 1)", "as": "prev_quarter_start"}, {"calculate": "floor((datetime(datum.prev_quarter_year, datum.prev_quarter * 3, 1) - datum.prev_quarter_start) / (1000 * 60 * 60 * 24))", "as": "prev_quarter_total_days"}, {"calculate": "datum.day_in_quarter < datum.prev_quarter_total_days ? datum.prev_quarter_start + datum.day_in_quarter * 1000 * 60 * 60 * 24 : datetime(datum.prev_quarter_year, datum.prev_quarter * 3, 0)", "as": "prev_quarter_date"}, {"calculate": "timeFormat(datum.prev_quarter_date, '%Y-%m-%d')", "as": "prev_quarter_date_str"}, { "lookup": "prev_quarter_date_str", "from": {"data": {"values": [/* 你的原始数据 */]}, "key": "date", "fields": ["value"]}, "as": "prev_quarter_value" } ], "layer": [ { "mark": {"type": "line", "color": "#1f77b4", "strokeWidth": 2}, "encoding": { "x": {"field": "datetime", "type": "temporal"}, "y": {"field": "value", "type": "quantitative"} } }, { "mark": {"type": "line", "color": "#ff7f0e", "strokeWidth": 2, "strokeDash": [5, 5]}, "encoding": { "x": {"field": "datetime", "type": "temporal"}, "y": {"field": "prev_quarter_value", "type": "quantitative"} } } ], "resolve": {"scale": {"y": "shared"}}, "legend": { "title": "数据类型", "symbolType": "line", "entries": [ {"name": "实际数据", "symbolColor": "#1f77b4", "symbolStrokeWidth": 2}, {"name": "上一季度同期", "symbolColor": "#ff7f0e", "symbolStrokeWidth": 2, "symbolStrokeDash": [5, 5]} ] } }
关键细节说明
- 跨年度处理:针对Q1的上一季度是去年Q4的情况,通过判断季度值自动调整年份。
- 无效日期规避:计算上一季度总天数,若当前日期在本季度的天数超过上一季度总天数,自动取上一季度最后一天作为匹配日期。
- 数据源适配:如果使用外部数据源(如CSV),将
data.values替换为data.url,并确保lookup的from.data引用同一数据源。 - 空值处理:若某数据点无对应上一季度数据,
prev_quarter_value会为null,可通过calculate添加默认值(如{"calculate": "datum.prev_quarter_value ?? 0", "as": "prev_quarter_value"})。
内容的提问来源于stack exchange,提问作者Raghav Mittal
相关产品推荐
相关产品推荐

