te')); return $arr; } /* 遍历用户所有主题 * @param $uid 用户ID * @param int $page 页数 * @param int $pagesize 每页记录条数 * @param bool $desc 排序方式 TRUE降序 FALSE升序 * @param string $key 返回的数组用那一列的值作为 key * @param array $col 查询哪些列 */ function thread_tid_find_by_uid($uid, $page = 1, $pagesize = 1000, $desc = TRUE, $key = 'tid', $col = array()) { if (empty($uid)) return array(); $orderby = TRUE == $desc ? -1 : 1; $arr = thread_tid__find($cond = array('uid' => $uid), array('tid' => $orderby), $page, $pagesize, $key, $col); return $arr; } // 遍历栏目下tid 支持数组 $fid = array(1,2,3) function thread_tid_find_by_fid($fid, $page = 1, $pagesize = 1000, $desc = TRUE) { if (empty($fid)) return array(); $orderby = TRUE == $desc ? -1 : 1; $arr = thread_tid__find($cond = array('fid' => $fid), array('tid' => $orderby), $page, $pagesize, 'tid', array('tid', 'verify_date')); return $arr; } function thread_tid_delete($tid) { if (empty($tid)) return FALSE; $r = thread_tid__delete(array('tid' => $tid)); return $r; } function thread_tid_count() { $n = thread_tid__count(); return $n; } // 统计用户主题数 大数量下严谨使用非主键统计 function thread_uid_count($uid) { $n = thread_tid__count(array('uid' => $uid)); return $n; } // 统计栏目主题数 大数量下严谨使用非主键统计 function thread_fid_count($fid) { $n = thread_tid__count(array('fid' => $fid)); return $n; } ?>javascript - Looking for customization on canvas (joining div's and maintaining it's relationship) - Stack Overf
最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Looking for customization on canvas (joining div's and maintaining it's relationship) - Stack Overf

programmeradmin3浏览0评论

I'm looking for a javascript library or custom solution where I can freely drag drop ponents and maintain the relationship between them (like which node is connected to what & move the nodes freely wherever I want)

By maintaining the relationship I mean the different ponents should maintain their interconnection flow(like a flowchart). After drawing them I need to get the JSON data of their relationship.

Below is a sample glimpse of what I'm talking about

In the above picture as you can see I have different nodes which are interconnected. How can I achieve these things by library or custom solution?

The above image is from the strom-react-diagrmas react library. I have tried this but it uses SVG and lacks a lot of customization that I want.

I've also tried rete.js but unable to customize it as per my need(customizing shapes etc).

I'm also thinking of building a solution from scratch, the only problem I'm facing is how do I join two or multiple divs on canvas maintaining its relationship?

Note why am I doing this?

  1. My goal behind doing this is I want to create a visual editor where a non-technical person can design the flow and then I want to export the JSON to store it accordingly in my database.
  2. When I load the canvas of this flow again I should be able to render the interconnection flow along with connected nodes again based on the JSON data that I will have.

Can you suggest me something if you have e across such a situation? Any help from you guys is really appreciated.

I'm looking for a javascript library or custom solution where I can freely drag drop ponents and maintain the relationship between them (like which node is connected to what & move the nodes freely wherever I want)

By maintaining the relationship I mean the different ponents should maintain their interconnection flow(like a flowchart). After drawing them I need to get the JSON data of their relationship.

Below is a sample glimpse of what I'm talking about

In the above picture as you can see I have different nodes which are interconnected. How can I achieve these things by library or custom solution?

The above image is from the strom-react-diagrmas react library. I have tried this but it uses SVG and lacks a lot of customization that I want.

I've also tried rete.js but unable to customize it as per my need(customizing shapes etc).

I'm also thinking of building a solution from scratch, the only problem I'm facing is how do I join two or multiple divs on canvas maintaining its relationship?

Note why am I doing this?

  1. My goal behind doing this is I want to create a visual editor where a non-technical person can design the flow and then I want to export the JSON to store it accordingly in my database.
  2. When I load the canvas of this flow again I should be able to render the interconnection flow along with connected nodes again based on the JSON data that I will have.

Can you suggest me something if you have e across such a situation? Any help from you guys is really appreciated.

Share Improve this question asked Feb 19, 2019 at 17:30 Harsh MakadiaHarsh Makadia 3,4436 gold badges34 silver badges42 bronze badges 2
  • Instead of using canvas I would use svg to draw the connectors – enxaneta Commented Feb 19, 2019 at 20:55
  • @enxaneta - I'm ok with that too using SVG. Do you know any specific library for the same? – Harsh Makadia Commented Feb 20, 2019 at 4:15
Add a ment  | 

4 Answers 4

Reset to default 6

Rete.js can be customized via custom Vue.js ponents.

Example

Visual part of the framework is represented by one of the plugins for rendering: vue or stage0. I'm prefer Vue.js so I have developed the plugin based on it.

Create custom socket and node


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;
    }
  },
  ponents: {
    Socket: /*VueRenderPlugin.Socket*/CustomSocket
  }
}


class NumComponent extends Rete.Component {

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

Template:

  <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>

As a result, you can customize nodes, connections and background without restrictions

I would have loved to know more about the layout you have in mind.

This is a demo where you can click the grey dots. When 2 dots are clicked a connection between the 2 dots is drawn on the svg canvas.

In the HTML you have all your elements inside a #wrap element. Underneath the divs there is an svg element, the same size as the #wrap. The divs are positioned absolute with top and left attributes in percentages. The svg canvas has a viewBox="0 0 100 100" and preserveAspectRatio="none" in order to adapt the drawing to the size of the #wrap. The connectors are paths drawn on the svg with fill:none and vector-effect: non-scaling-stroke; for an uniform stroke on a stretched or squished canvas.

In the end you can save the points array for the data.

I hope this can give you an idea about what you need to do.

const SVG_NS = 'http://www.w3/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>

You can use a GOJS.

This is a great solution for a mercial project. It is flexible in settings and makes it quite easy to do amazing things.

Example on official website.

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./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>

Hello guys I have decided to start my own flowchart open project with ReactJs, but if you need, you can adapt it to pure javascript, please feel free to contribute.

https://github./lmoraobando/lmDiagram

发布评论

评论列表(0)

  1. 暂无评论