visualizationvega

Dynamic column size in Vega data table visualization


I have a Vega visualization that displays data in a table built using rect and text marks: Table Screenshot

I can define the x_step signal to define the width in pixel of each column (but with the same value for all columns). Now, since my first and third column have much smaller text content than the second, I'd like to use one column width for the second column and another for the first and third. I cannot find a way to do it since it seems I cannot use datum in signals.

Please note that I want to change the axis width, not only the rect width.

Also, I need to use Vega v5.

Here's my JSON spec:

{
  "$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\")))))",
          "as": "delta_str"
        },
        {
          "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"}}
}

Solution

  • Here you go

    enter image description here

    {
      "$schema": "https://vega.github.io/schema/vega/v5.json",
      "autosize": "none",
      "background": "white",
      "width": 300,
      "height": 100,
      "padding": {"top": 60}, 
      
      "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\")))))",
              "as": "delta_str"
            },
            {
              "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?'yes':'no'",
              "as": "color"
            }
          ]
        }
      ],
      "signals": [
        {"name": "x_step", "value": 250},
        {"name": "width", "update": "containerSize()[0]"},
        {"name": "y_step", "value": 20},
        {"name": "height", "update": "containerSize()[1]"}
      ],
      "marks": [
        {
          "type": "group",
          "name": "columnHolder",
          "style": "cell",
          "layout": {"padding": 0, "bounds": "flush", "align": "each"},
          "encode": {
            "enter": {
              "x": {"signal": "0"},
              "stroke": {"value": "transparent"},
              "width": {"signal": "width"},
              "height": {"signal": "height"}
            }
          },
          "marks": [
            {
              "type": "group",
              "name": "firstColumn",
              "style": "cell",
              "title": {
                "text": "Key",
                "anchor": "start",
                "frame": "group",
                "align": "left"
              },
              "encode": {
                "enter": {
                  "stroke": {"value": "transparent"},
                  "width": {"value": 40},
                  "height": {"signal": "height"}
                }
              },
              "marks": [
                {
                  "type": "rect",
                  "clip": true,
                  "from": {"data": "source_0"},
                  "encode": {
                    "update": {
                      "fill": {"scale": "color", "field": "color"},
                      "y": {"scale": "y", "field": "key", "band": 0},
                      "height": {"scale": "y", "band": 1},
                      "x": {"value": 0},
                      "width": {"value": 40}
                    }
                  }
                },
                {
                  "type": "text",
                  "style": "col",
                  "clip": true,
                  "from": {"data": "source_0"},
                  "encode": {
                    "update": {
                      "fontSize": {"value": 12},
                      "fill": {"value": "#eeeeee"},
                      "y": {"scale": "y", "field": "key", "band": 0.5},
                      "text": {"field": "key"}
                    }
                  }
                }
              ]
            },
            {
              "type": "group",
              "name": "secondColumn",
              "style": "cell",
              "title": {
                "text": "Date",
                "anchor": "start",
                "frame": "group",
                "align": "left"
              },
              "encode": {
                "enter": {
                  "stroke": {"value": "transparent"},
                  "width": {"value": 180},
                  "height": {"signal": "height"}
                }
              },
              "marks": [
                {
                  "type": "rect",
                  "clip": true,
                  "from": {"data": "source_0"},
                  "encode": {
                    "update": {
                      "fill": {"scale": "color", "field": "color"},
                      "y": {"scale": "y", "field": "key", "band": 0},
                      "height": {"scale": "y", "band": 1},
                      "x": {"value": 0},
                      "width": {"value": 180}
                    }
                  }
                },
                {
                  "type": "text",
                  "style": "col",
                  "clip": true,
                  "from": {"data": "source_0"},
                  "encode": {
                    "update": {
                      "fontSize": {"value": 12},
                      "fill": {"value": "#eeeeee"},
                      "y": {"scale": "y", "field": "key", "band": 0.5},
                      "text": {"field": "date"}
                    }
                  }
                }
              ]
            },
            {
              "type": "group",
              "name": "thirdColumn",
              "style": "cell",
              "title": {
                "text": "Ago",
                "anchor": "start",
                "frame": "group",
                "align": "left"
              },
              "encode": {
                "enter": {
                  "stroke": {"value": "transparent"},
                  "width": {"value": 60},
                  "height": {"signal": "height"}
                }
              },
              "marks": [
                {
                  "type": "rect",
                  "clip": true,
                  "from": {"data": "source_0"},
                  "encode": {
                    "update": {
                      "fill": {"scale": "color", "field": "color"},
                      "y": {"scale": "y", "field": "key", "band": 0},
                      "height": {"scale": "y", "band": 1},
                      "x": {"value": 0},
                      "width": {"value": 60}
                    }
                  }
                },
                {
                  "type": "text",
                  "style": "col",
                  "clip": true,
                  "from": {"data": "source_0"},
                  "encode": {
                    "update": {
                      "fontSize": {"value": 12},
                      "fill": {"value": "#eeeeee"},
                      "y": {"scale": "y", "field": "key", "band": 0.5},
                      "text": {"field": "Ago"}
                    }
                  }
                }
              ]
            }
          ]
        }
      ],
      "scales": [
        {
          "name": "y",
          "type": "band",
          "domain": {"data": "source_0", "field": "key", "sort": true},
          "range": {"step": {"signal": "y_step"}},
          "paddingInner": 0,
          "paddingOuter": 0
        },
        {
          "name": "color",
          "type": "ordinal",
          "domain": ["yes", "no"],
          "range": ["#19aa6e", "#788291"]
        }
      ],
      "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"}}
    }