如何使用 JointJS 在不与端口重叠的情况下在矩形中添加 header/title?

How to add a header/title in a rectangle using JointJS without overlapping with the ports?

我有一个有多个端口的矩形。我想在上面添加一个标题来命名矩形,就像这样。

本质上,我正在制作和 ERD-like 设计,每个端口代表一个 table 列。我使用了 docs 中的标头矩形,但问题是端口与 header.

重叠

有没有什么办法可以动态调整端口的位置,使其正好在header的下方,不重叠?这是我在 JSFiddle

中的代码

HTML

<html>
  <body>
    <button id="btnAdd">Add Table</button>
    <div id="dbLookupCanvas"></div>
  </body>
</html>

JS

$(document).ready(function() {
  $('#btnAdd').on('click', function() {
    AddTable();
  });

  InitializeCanvas();

  // Adding of two sample tables on first load
  AddTable(50, 50);
  AddTable(250, 50);
});

var graph;
var paper
var selectedElement;
var namespace;

function InitializeCanvas() {
  let canvasContainer = $('#dbLookupCanvas').parent();

  namespace = joint.shapes;

  graph = new joint.dia.Graph({}, {
    cellNamespace: namespace
  });

  paper = new joint.dia.Paper({
    el: document.getElementById('dbLookupCanvas'),
    model: graph,
    width: canvasContainer.width(),
    height: 500,
    gridSize: 10,
    drawGrid: true,
    cellViewNamespace: namespace,
    validateConnection: function(cellViewS, magnetS, cellViewT, magnetT, end, linkView) {
      return (magnetS !== magnetT);
    },
    snapLinks: {
      radius: 20
    }
  });

  //Dragging navigation on canvas
  var dragStartPosition;
  paper.on('blank:pointerdown',
    function(event, x, y) {
      dragStartPosition = {
        x: x,
        y: y
      };
    }
  );

  paper.on('cell:pointerup blank:pointerup', function(cellView, x, y) {
    dragStartPosition = null;
  });

  $("#dbLookupCanvas")
    .mousemove(function(event) {
      if (dragStartPosition)
        paper.translate(
          event.offsetX - dragStartPosition.x,
          event.offsetY - dragStartPosition.y);
    });

  // Remove links not connected to anything
  paper.model.on('batch:stop', function() {
    var links = paper.model.getLinks();
    _.each(links, function(link) {
      var source = link.get('source');
      var target = link.get('target');
      if (source.id === undefined || target.id === undefined) {
        link.remove();
      }
    });
  });

  paper.on('cell:pointerdown', function(elementView) {
    resetAll(this);
    let isElement = elementView.model.isElement();

    if (isElement) {
      var currentElement = elementView.model;
      currentElement.attr('body/stroke', 'orange');
      selectedElement = elementView.model;
    } else
      selectedElement = null;
  });

  paper.on('blank:pointerdown', function(elementView) {
    resetAll(this);
  });

  $('#dbLookupCanvas')
    .attr('tabindex', 0)
    .on('mouseover', function() {
      this.focus();
    })
    .on('keydown', function(e) {
      if (e.keyCode == 46)
        if (selectedElement) selectedElement.remove();
    });
}

function AddTable(xCoord = undefined, yCoord = undefined) {
  // This is a sample database data here
  let data = [{
      columnName: "radomData1"
    },
    {
      columnName: "radomData2"
    },
    {
      columnName: "radomData3"
    }
  ];

  if (xCoord == undefined && yCoord == undefined) {
    xCoord = 50;
    yCoord = 50;
  }

  const rect = new joint.shapes.standard.HeaderedRectangle({
    position: {
      x: xCoord,
      y: yCoord
    },
    size: {
      width: 150,
      height: 200
    },
    ports: {
      groups: {
        'a': {},
        'b': {}
      }
    }
  });

  rect.attr('root/title', 'joint.shapes.standard.HeaderedRectangle');
  rect.attr('headerText/text', 'Sample Header');

  $.each(data, (i, v) => {
    const port = {
      group: 'a',
      args: {}, // Extra arguments for the port layout function, see `layout.Port` section
      label: {
        position: {
          name: 'right',
          args: {
            y: 6
          } // Extra arguments for the label layout function, see `layout.PortLabel` section
        },
        markup: [{
          tagName: 'text',
          selector: 'label'
        }]
      },
      attrs: {
        body: {
          magnet: true,
          width: 16,
          height: 16,
          x: -8,
          y: -4,
          stroke: 'red',
          fill: 'gray'
        },
        label: {
          text: v.columnName,
          fill: 'black'
        }
      },
      markup: [{
        tagName: 'rect',
        selector: 'body'
      }]
    };

    rect.addPort(port);
  });

  rect.resize(150, data.length * 40);

  graph.addCell(rect);
}

function resetAll(paper) {
  paper.drawBackground({
    color: 'white'
  });

  var elements = paper.model.getElements();
  for (var i = 0, ii = elements.length; i < ii; i++) {
    var currentElement = elements[i];
    currentElement.attr('body/stroke', 'black');
  }

  var links = paper.model.getLinks();
  for (var j = 0, jj = links.length; j < jj; j++) {
    var currentLink = links[j];
    currentLink.attr('line/stroke', 'black');
    currentLink.label(0, {
      attrs: {
        body: {
          stroke: 'black'
        }
      }
    });
  }
}

如有任何帮助,我们将不胜感激。谢谢!

您可以利用端口布局。我认为 line 布局可以解决这个问题。 layout.Port documentation reference

在您的 JSFiddle 中,类似以下的内容似乎运行良好:

 const rect = new joint.shapes.standard.HeaderedRectangle({
    position: {
      x: xCoord,
      y: yCoord
    },
    size: {
      width: 150,
      height: 200
    },
    ports: {
      groups: {
        'a': {
            position: {
             name: 'line',
             args: {
               start: { x: 0, y: 30 },
               end: { x:0, y: 110 }
             }
          }
        },
        'b': {}
      }
    }
  });