2

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"}}
}
3
  • You cannot do this using a band scale. Are you open to other approaches? Commented Apr 8 at 13:10
  • 1
    If you see the Gantt on this page, there is a table structure on the left of the viz. You can reuse that approach if you want me to show you. github.com/PBI-David/Deneb-Showcase Commented Apr 8 at 13:11
  • @davidebacci Yes, I'm open to any approach that works with Vega v5. The table on the left of the Gantt indeed seems to do what I expect Commented Apr 9 at 7:04

1 Answer 1

2
+50

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"}}
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.