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

如何在Vega-Lite多Y轴分层图表中对齐零点?

问题:Vega-Lite动态数据下多Y轴零点对齐实现

在Vega-Lite中,我希望对齐多Y轴的数值,使所有Y轴的零点处于同一位置,负值位于X轴下方。由于传入图表的数据是动态变化的,无法硬编码定义域(domain)范围,请问是否可以实现该需求?

原代码

{"$schema":"https://vega.github.io/schema/vega-lite/v5.json","width":300,"data":{"values":[{"key":"Type A","sum1":"1000","sum2":"2000","ratio":"15"},{"key":"Type B","sum1":"1500","sum2":"1500","ratio":"-5"},{"key":"Type C","sum1":"2000","sum2":"1000","ratio":"7"}]},"layer":[{"mark":{"type":"bar","size":15,"xOffset":0},"encoding":{"y":{"field":"sum1","type":"quantitative","axis":{"title":null,"ticks":true,"orient":"left"}},"x":{"field":"key","type":"nominal","axis":{"title":null,"labelAngle":-45,"labelLimit":1000}},"color":{"datum":"Sum1"}}},{"mark":{"type":"bar","size":15,"xOffset":15},"encoding":{"y":{"field":"sum2","type":"quantitative","axis":{"domain":false,"title":null,"labels":false,"ticks":false}},"x":{"field":"key","type":"nominal","axis":{"title":null,"labelAngle":-45,"labelLimit":1000}},"color":{"datum":"Sum2"}}},{"mark":{"type":"line","interpolate":"monotone","clip":true},"encoding":{"y":{"type":"quantitative","field":"ratio","axis":{"title":null,"ticks":true,"orient":"right"}},"x":{"field":"key","type":"nominal","axis":{"title":null,"labelAngle":-45,"labelLimit":1000}},"color":{"datum":"Ratio"}}}]},"resolve":{"scale":{"y":"independent"}},"config":{"legend":{"orient":"bottom","columns":3,"labelLimit":500,"layout":{"bottom":{"anchor":"middle"}}}}}"

解决方案

可以通过动态计算各Y轴数据的极值比例实现零点对齐,无需硬编码定义域。核心逻辑是基于零点平衡正负区间的缩放比例,确保所有轴的零点处于同一水平线。

修改后的完整代码

{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
  "width": 300,
  "data": {
    "values": [
      {"key": "Type A", "sum1": "1000", "sum2": "2000", "ratio": "15"},
      {"key": "Type B", "sum1": "1500", "sum2": "1500", "ratio": "-5"},
      {"key": "Type C", "sum1": "2000", "sum2": "1000", "ratio": "7"}
    ]
  },
  "transform": [
    {"calculate": "max(datum.sum1, 0)", "as": "sum1_pos"},
    {"calculate": "min(datum.sum1, 0)", "as": "sum1_neg"},
    {"calculate": "max(datum.sum2, 0)", "as": "sum2_pos"},
    {"calculate": "min(datum.sum2, 0)", "as": "sum2_neg"},
    {"calculate": "max(datum.ratio, 0)", "as": "ratio_pos"},
    {"calculate": "min(datum.ratio, 0)", "as": "ratio_neg"}
  ],
  "layer": [
    {
      "mark": {"type": "bar", "size": 15, "xOffset": 0},
      "encoding": {
        "y": {
          "field": "sum1",
          "type": "quantitative",
          "axis": {"title": null, "ticks": true, "orient": "left"},
          "scale": {
            "domain": [
              {"signal": "-max(abs(aggregate('sum1_neg', 'min')), aggregate('sum1_pos', 'max') * abs(aggregate('sum1_neg', 'min'))/aggregate('sum1_pos', 'max'))"},
              {"signal": "max(aggregate('sum1_pos', 'max'), aggregate('sum1_neg', 'min') * -aggregate('sum1_pos', 'max')/aggregate('sum1_neg', 'min'))"}
            ]
          }
        },
        "x": {"field": "key", "type": "nominal", "axis": {"title": null, "labelAngle": -45, "labelLimit": 1000}},
        "color": {"datum": "Sum1"}
      }
    },
    {
      "mark": {"type": "bar", "size": 15, "xOffset": 15},
      "encoding": {
        "y": {
          "field": "sum2",
          "type": "quantitative",
          "axis": {"domain": false, "title": null, "labels": false, "ticks": false},
          "scale": {
            "domain": [
              {"signal": "-max(abs(aggregate('sum2_neg', 'min')), aggregate('sum2_pos', 'max') * abs(aggregate('sum2_neg', 'min'))/aggregate('sum2_pos', 'max'))"},
              {"signal": "max(aggregate('sum2_pos', 'max'), aggregate('sum2_neg', 'min') * -aggregate('sum2_pos', 'max')/aggregate('sum2_neg', 'min'))"}
            ]
          }
        },
        "x": {"field": "key", "type": "nominal", "axis": {"title": null, "labelAngle": -45, "labelLimit": 1000}},
        "color": {"datum": "Sum2"}
      }
    },
    {
      "mark": {"type": "line", "interpolate": "monotone", "clip": true},
      "encoding": {
        "y": {
          "field": "ratio",
          "type": "quantitative",
          "axis": {"title": null, "ticks": true, "orient": "right"},
          "scale": {
            "domain": [
              {"signal": "-max(abs(aggregate('ratio_neg', 'min')), aggregate('ratio_pos', 'max') * abs(aggregate('ratio_neg', 'min'))/aggregate('ratio_pos', 'max'))"},
              {"signal": "max(aggregate('ratio_pos', 'max'), aggregate('ratio_neg', 'min') * -aggregate('ratio_pos', 'max')/aggregate('ratio_neg', 'min'))"}
            ]
          }
        },
        "x": {"field": "key", "type": "nominal", "axis": {"title": null, "labelAngle": -45, "labelLimit": 1000}},
        "color": {"datum": "Ratio"}
      }
    }
  ],
  "resolve": {"scale": {"y": "independent"}},
  "config": {
    "legend": {"orient": "bottom", "columns": 3, "labelLimit": 500, "layout": {"bottom": {"anchor": "middle"}}}
  }
}

关键说明

  1. 数据预处理:通过transform计算每个字段的正负极值,为动态定义域计算提供基础
  2. 动态定义域:每个Y轴的domain使用信号动态生成,以零点为中心,按正负区间的最大比例缩放,确保零点对齐
  3. 动态适配:所有计算基于数据聚合结果,无需硬编码数值,可自动适配任意动态输入数据

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 14:20:40