Modifying the Width of a Symbol Mark in Vega

Viewed 72

I want to label the symbols in my Vega graph. Some of the labels are long, extending beyond the boundaries of my symbols. How do I extend the width of a symbol to handle long labels?

Update: Is there a way of increasing the width of a symbol mark?

This is a link to my code.

N.B: I tried using rect marks, whose widths are easier to modify. But the aesthetics of the rect mark do not work for my use case. My use case is a Force Transform force-directed graph.

3 Answers

Here is a working example using your Vega spec with Force transform and text box using reactive geometry as suggested by David.

View in Vega online editor

enter image description here

{
  "$schema": "https://vega.github.io/schema/vega/v5.json",
  "description": "A node-link diagram with force-directed layout, depicting character co-occurrence in the novel Les Misérables.",
  "width": 500,
  "height": 200,
  "autosize": "pad",
  "signals": [
    {
      "name": "nodeRadius",
      "value": 50,
      "bind": {"input": "range", "min": 1, "max": 100, "step": 1}
    },
    {
      "name": "nodeCharge",
      "value": -100,
      "bind": {"input": "range", "min": -100, "max": 10, "step": 1}
    },
    {
      "name": "linkDistance",
      "value": 66,
      "bind": {"input": "range", "min": 5, "max": 100, "step": 1}
    },
    {
      "name": "textPadding", 
      "value": 5,
      "bind": {"input": "range", "min": 0, "max": 20, "step": 1}
      },
    {
      "name": "cornerRadius", 
      "value": 10,
      "bind": {"input": "range", "min": 0, "max": 20, "step": 1}
    },
    { "name": "showSymbol", "value": false, "bind": {"input": "checkbox"} },

  ],
  "data": [
    {
      "name": "node_data",
      "values": [
        {"name": "A Name", "type": "node", "1d": 0},
        {"name": "A Very Long Name", "type": "node", "1d": 1}
      ]
    },
    {"name": "edge_data", "values": [{"source": 0, "target": 1}]}
  ],
  "marks": [
   {
      "type": "symbol",
      "name": "nodes",
      "from": {"data": "node_data"},
      "encode": {
        "update": {
          "fill": {"value": "grey"},
          "opacity": {"signal": "showSymbol ? 0.5 : 0"},
          "size": {"signal": "PI * nodeRadius * nodeRadius"},
          "shape": {"value": "circle"}
        }
      },
      "transform": [
        {
          "type": "force",
          "iterations": 300,
          "static": {"signal": "false"},
          "signal": "force",
          "forces": [
            {
              "force": "center",
              "x": {"signal": "width/2"},
              "y": {"signal": "height/2"}
            },
            {"force": "collide", "radius": {"signal": "nodeRadius"}},
            {"force": "nbody", "strength": {"signal": "nodeCharge * 10"}},
            {
              "force": "link",
              "links": "edge_data",
              "distance": {"signal": "linkDistance"}
            }
          ]
        }
      ]
    },
    {
      "type": "path",
      "from": {"data": "edge_data"},
      "interactive": false,
      "encode": {
        "update": {"stroke": {"value": "#ccc"}, "strokeWidth": {"value": 0.5}}
      },
      "transform": [
        {
          "type": "linkpath",
          "require": {"signal": "force"},
          "shape": "line",
          "sourceX": "datum.source.x",
          "sourceY": "datum.source.y",
          "targetX": "datum.target.x",
          "targetY": "datum.target.y"
        }
      ]
    },
    {
      "type": "text",
      "name": "textmark",
      "from": {"data": "nodes"},
      "interactive": false,
      
      "encode": {
        "enter": {"fill": {"value": "black"}, "fontSize": {"value": 16}},
        "update": {
          "y": {"field": "y", "offset": {"signal": "nodeRadius * -0.1"}},
          "x": {"field": "x"},
          "text": {"field": "datum.name"},
          "align": {"value": "center"},
          "baseline": {"value": "middle"}
        }
      },
       "zindex": 1
    },
    {
      "name": "rectmark",
      "type": "rect",
      "from": {"data": "textmark"},
      "encode": {
        "update": {
          "x": {"field": "bounds.x1", "round": true,  "offset": {"signal": "-textPadding"}},
          "x2": {"field": "bounds.x2", "round": true, "offset": {"signal": "textPadding"}},
          "y": {"field": "bounds.y1", "round": true, "offset": {"signal": "-textPadding"}},
          "y2": {"field": "bounds.y2", "round": true, "offset": {"signal": "textPadding"}},
          "cornerRadius": {"signal": "cornerRadius"},
          "fill": {"value": "aliceblue"},
          "stroke": {"value": "steelblue"}
        }
      }
    }
  ]
}

It isn't clear what you're asking. Do you mean just increase the size of the symbol statically like this?

enter image description here

If so, just put a value in the size property.

{
  "$schema": "https://vega.github.io/schema/vega/v5.json",
  "description": "A node-link diagram with force-directed layout, depicting character co-occurrence in the novel Les Misérables.",
  "width": 700,
  "height": 500,
  "autosize": "pad",
  "signals": [
    {
      "name": "nodeRadius",
      "value": 50,
      "bind": {"input": "range", "min": 1, "max": 50, "step": 1}
    },
    {
      "name": "nodeCharge",
      "value": -100,
      "bind": {"input": "range", "min": -100, "max": 10, "step": 1}
    },
    {
      "name": "linkDistance",
      "value": 66,
      "bind": {"input": "range", "min": 5, "max": 100, "step": 1}
    }
  ],
  "data": [
    {
      "name": "node_data",
      "values": [
        {"name": "A Name", "type": "node", "1d": 0},
        {"name": "A Very Long Name", "type": "node", "1d": 1}
      ]
    },
    {"name": "edge_data", "values": [{"source": 0, "target": 1}]}
  ],
  "marks": [
    {
      "type": "symbol",
      "name": "nodes",
      "from": {"data": "node_data"},
      "encode": {
        "enter": {
          "fill": {"value": "steelblue"},
          "size": {"value": 10000},
          "tooltip": {"field": "name"}
        }
      },
      "transform": [
        {
          "type": "force",
          "iterations": 300,
          "static": {"signal": "false"},
          "signal": "force",
          "forces": [
            {
              "force": "center",
              "x": {"signal": "width/2"},
              "y": {"signal": "height/2"}
            },
            {"force": "collide", "radius": {"signal": "nodeRadius"}},
            {"force": "nbody", "strength": {"signal": "nodeCharge * 10"}},
            {
              "force": "link",
              "links": "edge_data",
              "distance": {"signal": "linkDistance"}
            }
          ]
        }
      ]
    },
    {
      "type": "path",
      "from": {"data": "edge_data"},
      "interactive": false,
      "encode": {
        "update": {"stroke": {"value": "#ccc"}, "strokeWidth": {"value": 0.5}}
      },
      "transform": [
        {
          "type": "linkpath",
          "require": {"signal": "force"},
          "shape": "line",
          "sourceX": "datum.source.x",
          "sourceY": "datum.source.y",
          "targetX": "datum.target.x",
          "targetY": "datum.target.y"
        }
      ]
    },
    {
      "type": "text",
      "from": {"data": "nodes"},
      "interactive": false,
      "enter": {"fill": {"value": "black"}, "fontSize": {"value": 10}},
      "encode": {
        "update": {
          "y": {"field": "y", "offset": {"signal": "nodeRadius * -0.1"}},
          "x": {"field": "x"},
          "text": {"field": "datum.name"},
          "align": {"value": "center"}
        }
      }
    }
  ]
}

Or you can split the word like this.

enter image description here

{
  "$schema": "https://vega.github.io/schema/vega/v5.json",
  "description": "A node-link diagram with force-directed layout, depicting character co-occurrence in the novel Les Misérables.",
  "width": 700,
  "height": 500,
  "autosize": "pad",
  "signals": [
    {
      "name": "nodeRadius",
      "value": 50,
      "bind": {"input": "range", "min": 1, "max": 50, "step": 1}
    },
    {
      "name": "nodeCharge",
      "value": -100,
      "bind": {"input": "range", "min": -100, "max": 10, "step": 1}
    },
    {
      "name": "linkDistance",
      "value": 66,
      "bind": {"input": "range", "min": 5, "max": 100, "step": 1}
    }
  ],
  "data": [
    {
      "name": "node_data",
      "values": [
        {"name": "A Name", "type": "node", "1d": 0},
        {"name": ["A Very", "Long Name"], "type": "node", "1d": 1}
      ]
    },
    {"name": "edge_data", "values": [{"source": 0, "target": 1}]}
  ],
  "marks": [
    {
      "type": "symbol",
      "name": "nodes",
      "from": {"data": "node_data"},
      "encode": {
        "enter": {
          "fill": {"value": "steelblue"},
          "size": {"value": 10000},
          "tooltip": {"field": "name"}
        }
      },
      "transform": [
        {
          "type": "force",
          "iterations": 300,
          "static": {"signal": "false"},
          "signal": "force",
          "forces": [
            {
              "force": "center",
              "x": {"signal": "width/2"},
              "y": {"signal": "height/2"}
            },
            {"force": "collide", "radius": {"signal": "nodeRadius"}},
            {"force": "nbody", "strength": {"signal": "nodeCharge * 10"}},
            {
              "force": "link",
              "links": "edge_data",
              "distance": {"signal": "linkDistance"}
            }
          ]
        }
      ]
    },
    {
      "type": "path",
      "from": {"data": "edge_data"},
      "interactive": false,
      "encode": {
        "update": {"stroke": {"value": "#ccc"}, "strokeWidth": {"value": 0.5}}
      },
      "transform": [
        {
          "type": "linkpath",
          "require": {"signal": "force"},
          "shape": "line",
          "sourceX": "datum.source.x",
          "sourceY": "datum.source.y",
          "targetX": "datum.target.x",
          "targetY": "datum.target.y"
        }
      ]
    },
    {
      "type": "text",
      "from": {"data": "nodes"},
      "interactive": false,
      "enter": {"fill": {"value": "black"}, "fontSize": {"value": 10}},
      "encode": {
        "update": {
          "y": {"field": "y", "offset": {"signal": "nodeRadius * -0.1"}},
          "x": {"field": "x"},
          "text": {"field": "datum.name"},
          "align": {"value": "center"}
        }
      }
    }
  ]
}

If you want something dynamic to size the node to the label extent, you have to use reactive geometry.

Using reactive geometry for you. This example is from jheer on GitHub. This sets the rectangles width and height according to the text mark.

{
  "$schema": "https://vega.github.io/schema/vega/v5.json",
  "width": 100,
  "height": 100,

  "data": [
    {"name": "labels", "values": [{"label": "Text"}]}
  ],

  "marks": [
    {
      "type": "group",
      "signals": [
        {"name": "bgoffset", "value": 3}
      ],
      "marks": [        
        {
          "name": "textmark",
          "type": "text",
          "from": {"data": "labels"},
          "encode": {
            "enter": {
              "text": {"field": "label"},
              "x": {"value": 50},
              "y": {"value": 50},
              "fontSize": {"value": 14},
              "align": {"value": "center"},
              "baseline": {"value": "middle"}
            }
          },
          "zindex": 1
        },
        {
          "name": "rectmark",
          "type": "rect",
          "from": {"data": "textmark"},
          "encode": {
            "enter": {
              "x": {"field": "bounds.x1", "round": true, "offset": {"signal": "-bgoffset"}},
              "x2": {"field": "bounds.x2", "round": true, "offset": {"signal": "bgoffset"}},
              "y": {"field": "bounds.y1", "round": true, "offset": {"signal": "-bgoffset"}},
              "y2": {"field": "bounds.y2", "round": true, "offset": {"signal": "bgoffset"}},
              "fill": {"value": "aliceblue"},
              "stroke": {"value": "steelblue"}
            }
          }
        }
      ]
    }
  ]

}
Related