public void paintBox(YLabel label, Graphics2D gfx, double x, double y, double width, double height) {

    // calculate the bubble
    Shape shape = new RoundRectangle2D.Double(x, y, width, height, Math.min(width / 3, 10), Math.min(height / 3, 10));

    double cx = x + width * 0.5d;
    double cy = y + height * 0.5d;

    if (label instanceof NodeLabel) {
      // calculate a wedge connecting the node and the rounded rectangle around the label text
      NodeRealizer labelRealizer = ((NodeLabel) label).getRealizer();
      Node node = ((NodeLabel) label).getNode();
      Graph2D graph2D = ((Graph2D) node.getGraph());
      NodeRealizer nodeRealizer = graph2D.getRealizer(node);

      double tx = graph2D.getCenterX(node);
      double ty = graph2D.getCenterY(node);

      // calculate an offset for the tip of the wedge
      if(!nodeRealizer.contains(cx, cy)) {
        double dirX = cx - labelRealizer.getCenterX();
        double dirY = cy - labelRealizer.getCenterY();
        Point2D result = new Point2D.Double();
        nodeRealizer.findIntersection(tx, ty, cx, cy, result);
        double l0 = Math.sqrt(dirX * dirX + dirY * dirY);
        if(l0 > 0) {
          double halfNodeWidth = nodeRealizer.getWidth() * 0.5 + 5;
          halfNodeWidth = (dirX > 0) ? halfNodeWidth : -1.0 * halfNodeWidth;
          tx = result.getX() + 5 * dirX / l0;
          ty = result.getY() + 5 * dirY / l0;
        }
      }

      // add the wedge to the bubble shape
      double dx = cx - tx;
      double dy = cy - ty;
      double l = Math.sqrt(dx * dx + dy * dy);
      if (l > 0) {
        double size = Math.min(width, height) * 0.25;
        GeneralPath p = new GeneralPath();
        p.moveTo((float) tx, (float) ty);
        p.lineTo((float) (cx + dy * size / l), (float) (cy - dx * size / l));
        p.lineTo((float) (cx - dy * size / l), (float) (cy + dx * size / l));
        p.closePath();
        Area area = new Area(shape);
        area.add(new Area(p));
        shape = area;
      }

    } else if (label instanceof EdgeLabel) {
      // calculate an anchor line connecting the edge and the rounded rectangle around the label text
      Edge edge = ((EdgeLabel) label).getEdge();
      Graph2D graph2D = ((Graph2D) edge.getGraph());
      EdgeRealizer edgeRealizer = graph2D.getRealizer(edge);
      GeneralPath path = edgeRealizer.getPath();
      double[] result = PointPathProjector.calculateClosestPathPoint(path, cx, cy);
      double dx = cx - result[0];
      double dy = cy - result[1];
      double l = Math.sqrt(dx * dx + dy * dy);

      // draw the anchor line with an offset to the edge
      if (l > 0) {
        double tx = result[0] + 5 * dx / l;
        double ty = result[1] + 5 * dy / l;
        Line2D line = new Line2D.Double(cx, cy, tx, ty);
        gfx.setColor(new Color(0, 0, 0, 64));
        gfx.draw(line);
      }
    }

    // paint the bubble using the colors of the label
    Color backgroundColor = label.getBackgroundColor();
    if (backgroundColor != null) {
      // shadow
      gfx.setColor(new Color(0, 0, 0, 64));
      gfx.translate(5, 5);
      gfx.fill(shape);
      gfx.translate(-5, -5);
      // and background
      gfx.setColor(backgroundColor);
      gfx.fill(shape);
    }

    // line
    Color lineColor = label.getLineColor();
    if (lineColor != null) {
      gfx.setColor(lineColor);
      gfx.draw(shape);
    }
  }