2

我正在寻找一个 javascript 库或自定义解决方案,我可以在其中自由拖放组件并维护它们之间的关系(例如哪个节点连接到什么节点并在任何我想要的地方自由移动节点)

通过保持关系,我的意思是不同的组件应该保持它们的互连流程(如流程图)。绘制它们后,我需要获取它们关系的 JSON 数据。

下面是我正在谈论的内容的示例一瞥

我开发的 Strom React 图

在上图中,您可以看到我有不同的节点相互连接。如何通过库或自定义解决方案实现这些目标?

上图来自strom-react-diagrmas反应库。我已经尝试过了,但它使用 SVG 并且缺少很多我想要的自定义。

我也尝试过rete.js但无法根据我的需要自定义它(自定义形状等)。

雷特js

我也在考虑从头开始构建解决方案,我面临的唯一问题是如何在画布上加入两个或多个 div 以保持其关系?

注意我为什么要这样做?

  1. 我这样做的目标是创建一个可视化编辑器,非技术人员可以在其中设计流程,然后我想导出 JSON 以将其相应地存储在我的数据库中。
  2. 当我再次加载此流的画布时,我应该能够根据我将拥有的 JSON 数据再次呈现互连流以及连接的节点。

如果您遇到这种情况,您能给我一些建议吗?非常感谢你们的任何帮助。

4

4 回答 4

5

我很想知道更多关于你心目中的布局。

这是一个演示,您可以在其中单击灰点。单击 2 个点时,会在 svg 画布上绘制 2 个点之间的连接。

在 HTML 中,所有元素都包含在一个#wrap元素中。在 div 下方有一个 svg 元素,大小与#wrap. divabsolute以百分比的形式使用 top 和 left 属性进行定位。svg 画布有一个viewBox="0 0 100 100"preserveAspectRatio="none"为了使绘图适应#wrap. 连接器是在 svg 上绘制的路径fill:nonevector-effect: non-scaling-stroke;用于在拉伸或压扁的画布上进行统一的笔划。

最后,您可以保存数据的点数组。

我希望这可以让您了解您需要做什么。

const SVG_NS = 'http://www.w3.org/2000/svg';
let mainBox = wrap.getBoundingClientRect();

let dots = Array.from(document.querySelectorAll(".dot"))

let points = [];
let count = 0;

dots.forEach(d=>{
  d.addEventListener("click",(e)=>{
    
    let bcr = d.getBoundingClientRect();
    mainBox = wrap.getBoundingClientRect()
    // calculate the x and y coordinates for the connectors as a number from 0 to 100 
    let x = map(bcr.left - mainBox.left + bcr.width/2, mainBox.left, mainBox.left + mainBox.width, 0, 100);
    let y = map(bcr.top - mainBox.top + bcr.height/2, mainBox.top, mainBox.top + mainBox.height, 0, 100);
    
    points.push({x,y})
    if(count % 2 == 1){
      // connects the last 2 dots in the array
      drawConnector(points[points.length-1],points[points.length-2])
    }    
    count++;
  })
})

function map(n, a, b, _a, _b) {
  let d = b - a;
  let _d = _b - _a;
  let u = _d / d;
  return _a + n * u;
}


function drawConnector(a,b){
  let path = document.createElementNS(SVG_NS, 'path');
  let d = `M${a.x},${a.y} C50,${a.y} 50 ${b.y} ${b.x} ${b.y}`;
  path.setAttributeNS(null,"d",d);
  svg.appendChild(path)
}
* {
  box-sizing: border-box;
}
.box {
  width: 20%;
  height: 100px;
  border: 1px solid #bbb;
  border-radius: 10px;
  position: absolute;
  background: #efefef;
}

#wrap {
  position: absolute;
  margin:auto;
  top:0;bottom:0;left:0;right:0;
  width: 60%;
  height: 350px;
  border: 1px solid;
  min-width: 350px;
}

svg {
  position: absolute;
  width: 100%;
  height: 100%;
  background: rgba(0, 100, 250, 0.25);
}

.dot {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 1px solid #999;
  background: #d9d9d9;
  position: relative;
  left: calc(100% - 10px);
}

.dot:hover {
  border-color: tomato;
}

path {
  fill: none;
  stroke: black;
  vector-effect: non-scaling-stroke;
  stroke-width: 1px;
  stroke: #555;
}
<div id="wrap">
<svg id="svg" viewBox="0 0 100 100" preserveAspectRatio="none"></svg>  
  
<div class="box" id="a" style="top: 10%; left: 10%;">
  <div class="dot" style="top:20px" ></div>
  <div class="dot" style="top:40px" ></div>
</div>
<div class="box" id="b" style="top: 60%; left: 10%;">
  <div class="dot" style="top:20px" ></div>
  <div class="dot" style="top:40px" ></div>
</div>
<div class="box"  id="c" style="top: 30%; left: 65%; ">
  <div class="dot" style="top:20px; left:-10px" ></div>
  <div class="dot" style="top:40px; left:-10px" ></div>  
</div>
  
</div>

于 2019-02-20T17:39:30.400 回答
4

Rete.js可以通过自定义 Vue.js 组件进行定制。

例子

框架的视觉部分由渲染插件之一表示:vue 或 stage0。我更喜欢 Vue.js,所以我基于它开发了插件。

创建自定义套接字和节点


var CustomSocket = {
  template: `<div class="socket"
    :class="[type, socket.name, used()?'used':''] | kebab"
    :title="socket.name+'\\n'+socket.hint"></div>`,
  props: ['type', 'socket', 'used']
}


var CustomNode = {
  template,
  mixins: [VueRenderPlugin.mixin],
  methods:{
    used(io){
      return io.connections.length;
    }
  },
  components: {
    Socket: /*VueRenderPlugin.Socket*/CustomSocket
  }
}


class NumComponent extends Rete.Component {

    constructor(){
        super("Number");
        this.data.component = CustomNode;
    }
...

模板:

  <div class="node" :class="[selected(), node.name] | kebab">
  <div class="title">{{node.name}}</div>
  <div class="content">
    <div class="col" v-if="node.controls.size&gt;0 || node.inputs.size&gt;0">
      <div class="input" v-for="input in inputs()" :key="input.key" style="text-align: left">
        <Socket v-socket:input="input" type="input" :socket="input.socket" :used="() => input.connections.length"></Socket>
        <div class="input-title" v-show="!input.showControl()">{{input.name}}</div>
        <div class="input-control" v-show="input.showControl()" v-control="input.control"></div>
     </div>
     <div class="control" v-for="control in controls()" v-control="control"></div>
    </div>
    <div class="col">
      <div class="output" v-for="output in outputs()" :key="output.key">
        <div class="output-title">{{output.name}}</div>
        <Socket v-socket:output="output" type="output" :socket="output.socket" :used="() => output.connections.length"></Socket>
      </div>
    </div>
  </div> 
</div>

因此,您可以不受限制地自定义节点、连接和背景

于 2019-02-23T21:40:11.070 回答
3

您可以使用GOJS

这是商业项目的绝佳解决方案。它的设置很灵活,可以很容易地做出令人惊奇的事情。

官网示例。

function init() {
  if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
  var $ = go.GraphObject.make; // for conciseness in defining templates

  myDiagram =
    $(go.Diagram, "myDiagramDiv", {
      validCycle: go.Diagram.CycleNotDirected, // don't allow loops
      // For this sample, automatically show the state of the diagram's model on the page
      "undoManager.isEnabled": true
    });

  // This template is a Panel that is used to represent each item in a Panel.itemArray.
  // The Panel is data bound to the item object.
  var fieldTemplate =
    $(go.Panel, "TableRow", // this Panel is a row in the containing Table
      new go.Binding("portId", "name"), // this Panel is a "port"
      {
        background: "transparent", // so this port's background can be picked by the mouse
        fromSpot: go.Spot.Right, // links only go from the right side to the left side
        toSpot: go.Spot.Left,
        // allow drawing links from or to this port:
        fromLinkable: true,
        toLinkable: true
      },
      $(go.Shape, {
          width: 12,
          height: 12,
          column: 0,
          strokeWidth: 2,
          margin: 4,
          // but disallow drawing links from or to this shape:
          fromLinkable: false,
          toLinkable: false
        },
        new go.Binding("figure", "figure"),
        new go.Binding("fill", "color")),
      $(go.TextBlock, {
          margin: new go.Margin(0, 5),
          column: 1,
          font: "bold 13px sans-serif",
          alignment: go.Spot.Left,
          // and disallow drawing links from or to this text:
          fromLinkable: false,
          toLinkable: false
        },
        new go.Binding("text", "name")),
      $(go.TextBlock, {
          margin: new go.Margin(0, 5),
          column: 2,
          font: "13px sans-serif",
          alignment: go.Spot.Left
        },
        new go.Binding("text", "info"))
    );

  // This template represents a whole "record".
  myDiagram.nodeTemplate =
    $(go.Node, "Auto", {
        copyable: false,
        deletable: false
      },
      new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
      // this rectangular shape surrounds the content of the node
      $(go.Shape, {
        fill: "#EEEEEE"
      }),
      // the content consists of a header and a list of items
      $(go.Panel, "Vertical",
        // this is the header for the whole node
        $(go.Panel, "Auto", {
            stretch: go.GraphObject.Horizontal
          }, // as wide as the whole node
          $(go.Shape, {
            fill: "#1570A6",
            stroke: null
          }),
          $(go.TextBlock, {
              alignment: go.Spot.Center,
              margin: 3,
              stroke: "white",
              textAlign: "center",
              font: "bold 12pt sans-serif"
            },
            new go.Binding("text", "key"))),
        // this Panel holds a Panel for each item object in the itemArray;
        // each item Panel is defined by the itemTemplate to be a TableRow in this Table
        $(go.Panel, "Table", {
            padding: 2,
            minSize: new go.Size(100, 10),
            defaultStretch: go.GraphObject.Horizontal,
            itemTemplate: fieldTemplate
          },
          new go.Binding("itemArray", "fields")
        ) // end Table Panel of items
      ) // end Vertical Panel
    ); // end Node

  myDiagram.linkTemplate =
    $(go.Link, {
        relinkableFrom: true,
        relinkableTo: true, // let user reconnect links
        toShortLength: 4,
        fromShortLength: 2
      },
      $(go.Shape, {
        strokeWidth: 1.5
      }),
      $(go.Shape, {
        toArrow: "Standard",
        stroke: null
      })
    );

  myDiagram.model =
    $(go.GraphLinksModel, {
      copiesArrays: true,
      copiesArrayObjects: true,
      linkFromPortIdProperty: "fromPort",
      linkToPortIdProperty: "toPort",
      nodeDataArray: [{
          key: "Record1",
          fields: [{
              name: "field1",
              info: "",
              color: "#F7B84B",
              figure: "Ellipse"
            },
            {
              name: "field2",
              info: "the second one",
              color: "#F25022",
              figure: "Ellipse"
            },
            {
              name: "fieldThree",
              info: "3rd",
              color: "#00BCF2"
            }
          ],
          loc: "0 0"
        },
        {
          key: "Record2",
          fields: [{
              name: "fieldA",
              info: "",
              color: "#FFB900",
              figure: "Diamond"
            },
            {
              name: "fieldB",
              info: "",
              color: "#F25022",
              figure: "Rectangle"
            },
            {
              name: "fieldC",
              info: "",
              color: "#7FBA00",
              figure: "Diamond"
            },
            {
              name: "fieldD",
              info: "fourth",
              color: "#00BCF2",
              figure: "Rectangle"
            }
          ],
          loc: "280 0"
        }
      ],
      linkDataArray: [{
          from: "Record1",
          fromPort: "field1",
          to: "Record2",
          toPort: "fieldA"
        },
        {
          from: "Record1",
          fromPort: "field2",
          to: "Record2",
          toPort: "fieldD"
        },
        {
          from: "Record1",
          fromPort: "fieldThree",
          to: "Record2",
          toPort: "fieldB"
        }
      ]
    });
}

init();
<script src="https://cdnjs.cloudflare.com/ajax/libs/gojs/2.0.3/go.js"></script>
<div id="sample">
  <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:300px"></div>
</div>

于 2019-02-21T10:39:22.213 回答
1

大家好,我决定用 ReactJs 开始我自己的流程图开放项目,但是如果你需要,你可以将它改编成纯 javascript,请随时贡献。

在此处输入图像描述 https://github.com/lmoraobando/lmDiagram

于 2019-09-11T04:15:58.317 回答