Why do groups not respect the parent's padding, except at the top level?

Viewed 38

What exactly is the behavior of padding for group marks in Vega? At the top-most level the children groups respect the top-level padding, but this doesn't seem the case for the children's children, they don't respect their parent's padding.

For example, here I would expect to get a rectangle centered in a rectangle centered in another rectangle:

Open the Chart in the Vega Editor

Instead each rectangle seems to be anchored at the origin of the top-level coordinate system.

Note that replacing "padding": {"signal": "level_2_padding"} with "padding": {"value": 0} doesn't seem to have any effect, so I'm not even sure if inner groups can have padding?

How can I best implement nested groups that respect the parent's padding?

2 Answers

There is no padding property on a Group mark. Instead, you can access group properties using Field Values. Something like the following should work.

Editor

enter image description here

{
  "$schema": "https://vega.github.io/schema/vega/v5.json",
  "autosize": "none",
  "config": {"group": {"stroke": "black"}},
  "signals": [
    {"name": "target_height", "value": 400},
    {"name": "target_width", "value": 300},
    {"name": "level_0_padding", "value": 64},
    {"name": "level_1_padding", "update": "1/2 * level_0_padding"},
    {"name": "level_2_padding", "update": "1/4 * level_0_padding"},
    {"name": "level_0_height", "update": "target_height - 2*level_0_padding"},
    {"name": "level_0_width", "update": "target_width - 2*level_0_padding"},
    {"name": "level_1_width", "update": "level_0_width - 2*level_1_padding"},
    {"name": "level_1_height", "update": "level_0_height - 2*level_1_padding"}
  ],
  "width": {"signal": "level_0_width"},
  "height": {"signal": "level_0_height"},
  "padding": {"signal": "level_0_padding"},
  "marks": [
    {
      "type": "group",
      "signals": [
        {
          "name": "level_2_width",
          "update": "level_1_width - 2*level_2_padding"
        },
        {
          "name": "level_2_height",
          "update": "level_1_height - 2*level_2_padding"
        }
      ],
      "encode": {
        "update": {
          "width": {"signal": "level_1_width"},
          "height": {"signal": "level_1_height"},
          "x": {"signal": "level_0_width-level_1_width - level_1_padding"},
          "y": {"signal": "level_0_height-level_1_height - level_1_padding"},
          "stroke": {"value": "red"},
          "strokeOpacity": {"value": 0.5}
        }
      },
      "marks": [
        {
          "type": "group",
          "encode": {
            "update": {
              "width": {"signal": "level_2_width"},
              "height": {"signal": "level_2_height"},
              "x": {
                "field": {"group": "width"},
                "mult": 0.5,
                "offset": {"signal": "-level_2_width/2"}
              },
              "y": {
                "field": {"group": "height"},
                "mult": 0.5,
                "offset": {"signal": "-level_2_height/2"}
              },
              "stroke": {"value": "blue"},
              "strokeOpacity": {"value": 0.5}
            }
          }
        }
      ]
    }
  ]
}

I'll accept David's answer, but also post my own to complement David's.

Here's an alternative solution specification, like David's spec it uses the "x" and "y" group properties, but I think it's a bit simpler and closer to what I need: Open the Chart in the Vega Editor

An important point that I have to mention is that using layout prevents x and y from working, that is: groups directly contained in a layout/grid may not be offset using x or y.

Related