You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vega v5表格可视化:配置非统一动态列宽

Vega v5 表格可视化自定义列宽度解决方案

我用Vega v5构建了一个基于rect和text标记的数据表格可视化(截图如下):
表格截图

目前通过x_step信号统一设置所有列的像素宽度,但第一、三列文本内容远少于第二列,希望为第二列设置单独宽度,其余两列使用另一宽度。尝试后发现无法在信号中使用datum,且需要修改的是轴宽度而非仅矩形宽度,现寻求解决方案。

原JSON配置如下:

{"$schema": "https://vega.github.io/schema/vega/v5.json","autosize": "none","background": "white","padding": {"top": 60},"style": "cell","data": [{"name": "source_0","values": [{"date": "2025-03-24T12:00:00+0100","key": "2210"},{"date": "2025-03-24T13:00:00+0100","key": "2211"},{"date": "2025-03-24T12:30:00+0100","key": "2212"},{"date": "2025-03-24T12:50:00+0100","key": "2213"}],"transform": [{"type": "formula","expr": "(now() - time(datum.date)) / 1000","as": "delta_sec"},{"type": "formula","expr": "if(datum.delta_sec >= 31536000, floor(datum.delta_sec / 31536000) + \" year\", if(datum.delta_sec >= 2592000, floor(datum.delta_sec / 2592000) + \" month\", if(datum.delta_sec >= 86400, floor(datum.delta_sec / 86400) + \" day\", if(datum.delta_sec >= 3600, floor(datum.delta_sec / 3600) + \" hour\", if(datum.delta_sec >= 60, floor(datum.delta_sec / 60) + \" minute\", floor(datum.delta_sec) + \" second\")))))"},{"type": "formula","expr": "if(parseInt(split(datum.delta_str, \" \", 1)[0]) > 1, datum.delta_str + \"s\", datum.delta_str)","as": "Ago"},{"type": "formula","expr": "datum.delta_sec < 3600","as": "color"},{"type": "window","params": [null],"as": ["row_num"],"ops": ["row_number"],"fields": [null],"sort": {"field": [],"order": []}},{"type": "formula","expr": "datum.key","as": "Key"},{"type": "formula","expr": "datum.date","as": "Date"},{"type": "fold","fields": ["Key","Date","Ago"],"as": ["key","value"]},{"type": "formula","expr": "datum[\"key\"]===\"Key\" ? 0 : datum[\"key\"]===\"Date\" ? 1 : datum[\"key\"]===\"Ago\" ? 2 : 3","as": "x_key_sort_index"}]}],"signals": [{"name": "x_step","value": 250},{"name": "width","update": "containerSize()[0]"},{"name": "y_step","value": 20},{"name": "height","update": "containerSize()[1]"}],"marks": [{"name": "layer_0_marks","type": "rect","style": ["rect"],"from": {"data": "source_0"},"encode": {"update": {"fill": {"scale": "color","field": "color"},"description": {"signal": "\"row_num: \" + (isValid(datum[\"row_num\"]) ? datum[\"row_num\"] : \"\"+datum[\"row_num\"]) + \"; key: \" + (isValid(datum[\"key\"]) ? datum[\"key\"] : \"\"+datum[\"key\"]) + \"; color: \" + (isValid(datum[\"color\"]) ? datum[\"color\"] : \"\"+datum[\"color\"])","x": {"scale": "x","field": "key"},"width": {"scale": "x","band": 1},"y": {"scale": "y","field": "row_num"},"height": {"scale": "y","band": 1}}}}},{"name": "layer_1_marks","type": "text","style": ["text"],"from": {"data": "source_0"},"encode": {"update": {"fontSize": {"value": 12},"fill": {"value": "#eeeeee"},"description": {"signal": "\"row_num: \" + (isValid(datum[\"row_num\"]) ? datum[\"row_num\"] : \"\"+datum[\"row_num\"]) + \"; key: \" + (isValid(datum[\"key\"]) ? datum[\"key\"] : \"\"+datum[\"key\"]) + \"; value: \" + (isValid(datum[\"value\"]) ? datum[\"value\"] : \"\"+datum[\"value\"])","x": {"scale": "x","field": "key","band": 0.5},"y": {"scale": "y","field": "row_num","band": 0.5},"text": {"signal": "isValid(datum[\"value\"]) ? datum[\"value\"] : \"\"+datum[\"value\"]"},"align": {"value": "center"},"baseline": {"value": "middle"}}}}]}],"scales": [{"name": "x","type": "band","domain": {"data": "source_0","field": "key","sort": {"op": "min","field": "x_key_sort_index"}},"range": {"step": {"signal": "x_step"}},"paddingInner": 0,"paddingOuter": 0},{"name": "y","type": "band","domain": {"data": "source_0","field": "row_num","sort": true},"range": {"step": {"signal": "y_step"}},"paddingInner": 0,"paddingOuter": 0},{"name": "color","type": "ordinal","domain": ["true","false"],"range": ["#19aa6e","#788291"]}],"axes": [{"scale": "x","orient": "top","gridScale": "y","grid": true,"domain": false,"labels": false,"aria": false,"maxExtent": 0,"minExtent": 0,"ticks": false,"zindex": 1},{"scale": "x","orient": "top","grid": false,"labelAngle": 0,"labelFontSize": 15,"ticks": false,"labelBaseline": "bottom","zindex": 1}],"legends": [{"labelFontSize": 12,"labelLimit": 300,"orient": "top","fill": "color","direction": "horizontal","symbolType": "square","encode": {"labels": {"update": {"text": {"signal": "if(datum.value == 'true', 'Less than 60 min ago', 'More than 60 min ago')"}}}}}],"config": {"axis": {"grid": true,"tickBand": "extent"}}}

解决方案

原配置使用的band类型x轴scale无法单独设置列宽,需改用ordinal类型并手动计算列的起始位置与宽度,具体修改如下:

  1. 替换x轴scale配置
    将原scales中的x scale修改为:
{
  "name": "x",
  "type": "ordinal",
  "domain": {"data": "source_0", "field": "key", "sort": {"op": "min", "field": "x_key_sort_index"}},
  "range": {
    "signal": "let colWidths = {'Key': 100, 'Date': 300, 'Ago': 120}; let domain = scale('x').domain(); let pos = 0; return domain.map(key => {let w = colWidths[key]; let currentPos = pos; pos += w; return currentPos;})"
  },
  "paddingInner": 0,
  "paddingOuter": 0
}

这里通过colWidths对象定义每列的宽度,同时计算出每列的起始x坐标作为scale的range。

  1. 修改矩形标记的宽度
    在layer_0_marks的encode.update.width中,替换为根据列名获取宽度:
"width": {"signal": "{'Key': 100, 'Date': 300, 'Ago': 120}[datum.key]"}
  1. 调整文本标记的位置
    在layer_1_marks的encode.update.x中,计算文本的居中位置:
"x": {"signal": "scale('x', datum.key) + {'Key': 100, 'Date': 300, 'Ago': 120}[datum.key]/2"}
  1. 移除无用信号
    原x_step信号已不再使用,可从signals数组中删除。

修改后的完整配置可直接运行,实现第二列(Date)单独宽、其他两列窄的效果,同时轴的位置也会自动适配列宽。

内容的提问来源于stack exchange,提问作者mckbrd

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 13:02:02