Search code examples
bordervega

Why does the inner rectangle overflow the outer rectangle in Vega spec


This is the Vega spec, it is intended to contain two equally-sized groups in one row:

{
  "$schema": "https://vega.github.io/schema/vega/v5.json",
  "autosize": "none",
  "config": {"group": {"stroke": "black"}},
  "signals": [
    {
      "name": "target_height",
      "value": 200},
    {
      "name": "target_width",
      "update": "5/4 * target_height"},
    {
      "name": "level_0_padding",
      "update": "1/16 * target_height"},
    {
      "name": "level_1_padding",
      "update": "1/2 * 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": "1/2 * (level_0_width - level_0_padding)"},
    {
      "name": "level_1_height",
      "update": "level_0_height"}],
  "width": {"signal": "level_0_width"},
  "height": {"signal": "level_0_height"},
  "padding": {"signal": "level_0_padding"},
  "layout": {
    "padding": {"signal": "level_0_padding"},
    "align": "all",
    "center": true},
  "marks": [
    {
      "type": "group",
      "encode": {"update": {
        "width": {"signal": "level_1_width"},
        "height": {"signal": "level_1_height"},
        "padding": {"value": 0},
        "stroke": {"value": "red"},
        "strokeOpacity": {"value": 0.5}}}},
    {
      "type": "group",
      "encode": {"update": {
        "width": {"signal": "level_1_width"},
        "height": {"signal": "level_1_height"},
        "padding": {"value": 0},
        "stroke": {"value": "red"},
        "strokeOpacity": {"value": 0.5}}}}]}

For some reason, however, the second group seems to overflow the global plot space: overflowing bounding boxes

It seems that the overflow is always about a few pixels wide, independently of the values of target_width, target_height or level_0_padding.

Open the Chart in the Vega Editor

Note that the issue remains if the signal update expressions are rewritten like in the following example, to exclude user floating-point error as the cause of the issue:

  "signals": [
    {
      "name": "target_height",
      "value": 200},
    {
      "name": "target_width",
      "update": "5/4 * target_height"},
    {
      "name": "level_0_padding",
      "update": "1/16 * target_height"},
    {
      "name": "level_1_padding",
      "update": "1/32 * target_height"},
    {
      "name": "level_0_height",
      "update": "7/8 * target_height"},
    {
      "name": "level_0_width",
      "update": "9/8 * target_height"},
    {
      "name": "level_1_width",
      "update": "17/32 * target_height"},
    {
      "name": "level_1_height",
      "update": "level_0_height"}],

In fact, the overflow happens even if all of the signals have integer values.

The same issue, but with a column instead of a row

overflow on bottom

Open the Chart in the Vega Editor

With a more complex layout

Trying to do the same thing, but with a slightly more complicated layout results in accumulated overflows and, even worse, the two red boxes/groups are misaligned:

horrible

Open the Chart in the Vega Editor

After playing around with this example, I conclude that the misalignment is actually a separate issue, as it can be fixed by specifying "align": "none" instead of "align": "each" or "align": "all"


Solution

  • You need "bounds": "flush".

    enter image description here

    {
      "$schema": "https://vega.github.io/schema/vega/v5.json",
      "autosize": "none",
      "config": {"group": {"stroke": "black"}},
      "signals": [
        
        {"name": "level_0_padding", "value": 16},
        {"name": "level_1_padding", "update": "1/2 * level_0_padding"},
        {"name": "level_0_height", "update": "168"},
        {"name": "level_0_width", "update": "168"},
        {
          "name": "level_1_width",
          "update": "1/2 * (level_0_width - level_0_padding)"
        },
        {"name": "level_1_height", "update": "level_0_height"}
      ],
      "width": {"signal": "168"},
      "height": {"signal": "168"},
      "padding": {"signal": "30"},
      "layout": {"padding": {"signal": "level_0_padding"}, "bounds": "flush"},
      "marks": [
        {
          "type": "group",
          "encode": {
            "update": {
              "width": {"signal": "level_1_width"},
              "height": {"signal": "level_1_height"},
              "padding": {"value": 0},
              "stroke": {"value": "red"},
              "strokeOpacity": {"value": 0.5}
            }
          }
        },
        {
          "type": "group",
          "encode": {
            "update": {
              "width": {"signal": "level_1_width"},
              "height": {"signal": "level_1_height"},
              "padding": {"value": 0},
              "stroke": {"value": "red"},
              "strokeOpacity": {"value": 0.5}
            }
          }
        }
      ]
    }