Поиск настроек на холсте (присоединение к div и поддержание их отношений)
Я ищу библиотеку javascript или нестандартное решение, в котором я могу свободно перетаскивать перетаскиваемые компоненты и поддерживать связь между ними (например, какой узел подключен к чему и свободно перемещать узлы, куда я хочу)
Под поддержанием отношений я подразумеваю, что различные компоненты должны поддерживать свой поток взаимосвязей (как блок-схема). После их рисования мне нужно получить данные JSON об их отношениях.
Ниже приведен пример того, о чем я говорю
На рисунке выше, как вы можете видеть, у меня есть разные узлы, которые связаны между собой. Как я могу достичь этих целей с помощью библиотеки или индивидуального решения?
Приведенное выше изображение взято из библиотеки реакций strom-Reaction-Diagrmas. Я пробовал это, но он использует SVG и не хватает много настроек, которые я хочу.
Я также пробовал rete.js, но не смог настроить его в соответствии со своими потребностями (настройка форм и т. Д.).
Я также думаю о создании решения с нуля, единственная проблема, с которой я сталкиваюсь, состоит в том, как мне соединить два или несколько элементов div на холсте, поддерживая его взаимосвязь?
Обратите внимание, почему я это делаю?
- Моя цель состоит в том, чтобы я хотел создать визуальный редактор, в котором нетехнический специалист мог бы спроектировать поток, а затем я хочу экспортировать JSON, чтобы соответствующим образом сохранить его в моей базе данных.
- Когда я снова загружу холст этого потока, я смогу снова визуализировать поток взаимосвязи вместе со связанными узлами на основе данных JSON, которые у меня будут.
Можете ли вы предложить мне что-нибудь, если вы столкнулись с такой ситуацией? Любая помощь от вас, ребята, очень ценится.
3 ответа
Мне бы хотелось узнать больше о макете, который вы имеете в виду.
Это демо, где вы можете нажать на серые точки. Когда щелкают 2 точки, соединение между двумя точками рисуется на холсте svg.
В HTML у вас есть все ваши элементы внутри #wrap
элемент. Под элементами div находится элемент svg того же размера, что и #wrap
, Дивы расположены absolute
с верхним и левым атрибутами в процентах. Холст svg имеет viewBox="0 0 100 100"
а также preserveAspectRatio="none"
для того, чтобы адаптировать рисунок к размеру #wrap
, Соединители - это пути, нарисованные на SVG с fill:none
а также vector-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>
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>0 || node.inputs.size>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>
В результате вы можете настроить узлы, соединения и фон без ограничений
Вы можете использовать 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>
Привет, ребята, я решил начать свой собственный открытый проект блок-схемы с помощью ReactJs, но если вам нужно, вы можете адаптировать его к чистому javascript, пожалуйста, не стесняйтесь вносить свой вклад.