Я задал вопрос о StackOverflow ( Ищу библиотеку Javascript для отображения и редактирования сетей узлов и ребер ) и указал на пример соединения gojs.
Это проделало мне долгий путь, так что спасибо за ответ, но я наткнулся на кирпичную стену, пытаясь добиться желаемого поведения.
Приложение, которое я пытаюсь создать, - это редактирование границ на карте:
- узел = место, где встречаются три или границы
- ссылка = участок границы между двумя узлами.
Следовательно, узлы и ссылки не имеют маркировки (узлы - это просто маленькие кружки, ссылки - просто полилинии).
Я попытался адаптировать образец сращивания (https://gojs.net/temp/splicing.html) соответствующим образом. Основные характеристики, которые мне нужны, помимо того, что делает образец:
- выбор места размещения нового узла на связи между существующими
- сохранение формы полилиний.
(Существующий образец размещает новый узел на равном расстоянии между существующими и использует прямые ссылки.)
Пользовательский опыт, который я пытался создать, заключается в следующем: во-первых, вы выбираете ссылку, чтобы она получала свои обычные украшения; затем вы щелкаете по одному из украшений в точке на полилинии, и эта точка становится новым узлом.
Я пытался сделать это путем переопределения методов LinkReshapingTool с использованием механизма расширения, описанного в https://gojs.net/latest/intro/extensions.html (вместо создания подкласса).
Что бы я ни пытался, я не могу заставить полилинии остаться. Изучив модель данных диаграммы в отладчике Chrome DevTools после запуска моего кода, он показывает , что он правильный (т.е. я вижу правильный массив точек в ссылках). Однако, когда я затем разрешаю продолжить выполнение, ссылки не отображаются должным образом (они прямые), а если я впоследствии посмотрю на модель данных, то несколько точек исчезнут, и у каждой ссылки будет только начало и конец.
Я пробовал разные вещи, но безуспешно, например:
- отсрочка сращивания до завершения работы инструмента
- передача точек в измененные ссылки различными способами (массив v список v строка)
- помещая обработку в различные переопределенные методы.
Мой текущий код указан ниже. Прошу прощения за грубые стилистические ошибки - я не опытный программист JavaScript.
<!DOCTYPE html> <!-- HTML5 document type -->
<!--
Adapted from splicing example from gojs.net
-->
<html>
<head>
<!-- use go-debug.js when developing and go.js when deploying -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gojs/1.8.28/go-debug.js"></script>
</head>
<body>
<div id="myDiagramDiv"
style="width:400px; height:300px; background-color: #DAE4E4;"></div>
<script>
var $ = go.GraphObject.make;
// state variables to remember what to do after reshaping tool aborted
var doSpliceNode = false;
var doSpliceIntoLink = null;
var doSplicePoint = null;
var doSpliceIndex = -1;
// diagram
var myDiagram = $(go.Diagram, "myDiagramDiv",
{
"undoManager.isEnabled": true
});
var tool = myDiagram.toolManager.linkReshapingTool;
// Override doMouseDown on linkreshapingtool. If user clicks on a handle with SHIFT pressed, want to insert
// a new node at that point rather than use the default behaviour to (further) reshape the link, and moreover
// want to retain the points in the link. I.e. turn one of the points in the link into a new node.
// (Existing gojs splicing example does not do this: it just puts a new node at the midpoint of the existing
// link, with no regard to the points along the link.)
tool.doMouseDown = function() {
console.log("mousedown at (" + this.Fp.M + "," + this.Fp.N + ")");
console.log(" on link from " + this.adornedLink.fromNode + " to " + this.adornedLink.toNode);
console.log(" with shift pressed? " + myDiagram.lastInput.shift);
var spliced = false;
if (myDiagram.lastInput.shift)
{
// work out which of the points on the link was clicked
var link = this.adornedLink;
var numpts = link.pointsCount;
var i;
var x = this.Fp.M; // @@TODO - by inspection in debugger this contains the X coord, but what's documented place to get this?
var y = this.Fp.N; // @@TODO - ditto for Y coord
for (i = 1; !spliced && (i < numpts - 1); i++)
{
if ((link.getPoint(i).x == x) && (link.getPoint(i).y == y))
{
console.log(" .. at point " + i);
// Store off what to do. (This used to be done inline - deferred to after as one of the things
// to try to make it work.)
doSpliceNode = true;
doSpliceIntoLink = link;
doSplicePoint = new go.Point(x, y);
doSpliceIndex = i;
spliced = true;
}
}
}
//if (!doSpliceNode)
{
console.log(".. call base class doMouseDown");
go.LinkReshapingTool.prototype.doMouseDown.call(tool);
}
}
// Override doMouseUp as well. If we had decided during mousedown to do the splice, then stop the tool now.
tool.doMouseUp = function()
{
// First call base class
go.LinkReshapingTool.prototype.doMouseUp.call(tool);
if (doSpliceNode)
{
// Doing splice - stop tool
console.log("STOP TOOL");
this.stopTool();
this.doDeactivate();
}
}
// Finally, override doStop to actually do the splice
tool.doStop = function() {
console.log("doStop");
// First call base class
go.LinkReshapingTool.prototype.doStop.call(tool);
if (doSpliceNode)
{
// now splice the node
console.log("splice node");
spliceNewNodeIntoLink2(doSpliceIntoLink, doSplicePoint, doSpliceIndex); // @@TODO make it respect points in existing link before and after
}
// Reset everything
doSpliceNode = false;
doSpliceIntoLink = null;
doSplicePoint = null;
doSpliceIndex = -1;
}
// Debug variable for inspecting later - not functional
var debugLastLink = null;
// Model, node and links for this application. Based heavily on https://gojs.net/temp/splicing.html and adapted as needed.
var myModel = $(go.GraphLinksModel);
myDiagram.nodeTemplate = $(go.Node,
"Auto",
new go.Binding("location", "location", go.Point.parse).makeTwoWay(go.Point.stringify),
$(go.Shape, "Circle", { width: 6, height: 6, strokeWidth: 0 }));
myDiagram.linkTemplate =
$(go.Link,
{
relinkableFrom: true, relinkableTo: true,
reshapable: true, resegmentable: true,
/* selectionAdornmentTemplate: @@ COMMENT OUT - NOT NEEDED
$(go.Adornment,
$(go.Shape, { isPanelMain: true, stroke: "dodgerblue", strokeWidth: 2 }),
$(go.Shape, "PlusLine",
{
isActionable: true, // so that click works in an Adornment
width: 16, height: 16, stroke: "green", strokeWidth: 4, background: "transparent",
segmentOffset: new go.Point(8, 0),
click: function(e, shape) {
alert(e);
var link = shape.part.adornedPart;
var p0 = link.getPoint(0);
var p1 = link.getPoint(link.pointsCount - 1);
var pt = new go.Point((p0.x + p1.x) / 2, (p0.y + p1.y) / 2);
// @@TODO - instead, find the position where the mouse was clicked and place the node there
// ... need to work out which segment of polyline this was in so as to calculate new lines
// @@TODO - handle drag of node so that it just affects segments of lines immediately into it, rather than
// blatting over top of them
// @@TODO - what is object e and its components
spliceNewNodeIntoLink(link, pt);
},
cursor: "pointer"
})
), */
toShortLength: 1
},
new go.Binding("points").makeTwoWay(), // Use the points information from the linkDataArray initializer
$(go.Shape, { strokeWidth: 2 })
);
/* function spliceNewNodeIntoLink(link, pt) { // @@ original version no longer called
link.diagram.commit(function(diag) {
var tokey = link.toNode.key;
// add a new node
var newnodedata = { text: "on link", location: go.Point.stringify(pt) };
diag.model.addNodeData(newnodedata);
// and splice it in by changing the existing link to refer to the new node
diag.model.setToKeyForLinkData(link.data, newnodedata.key);
// and by adding a new link from the new node to the original "toNode"
diag.model.addLinkData({ from: newnodedata.key, to: tokey });
// optional: select the new node
diag.select(diag.findNodeForData(newnodedata));
}, "spliced in node on a link");
} */
// Utility function used in one attempt to get this to work. Initializers in nodeDataArray do it via an array of numbers,
// so try that here.
function toArray(nodelist)
{
var returnarray = new Array();
var i;
for (i = 0; i < nodelist.size; i++)
{
var pt = nodelist.elt(i);
returnarray.push(pt.x);
returnarray.push(pt.y);
}
return returnarray;
}
// Function to splice the new node into the link. Parameters are
// - link: the link to splice into
// - pt: the point within the link to turn into a node
// - index: index into existing polyline of that point
function spliceNewNodeIntoLink2(link, pt, index) {
link.diagram.commit(function(diag) {
var oldlinkpointslist = link.points;
var link1pointslist = new go.List(go.Point);
var link2pointslist = new go.List(go.Point);
var i;
// Create new points list, from "from" node to new node to be added
for (i = 0; i <= index; i++)
{
var point = new go.Point(link.getPoint(i).x, link.getPoint(i).y);
link1pointslist.add(point);
}
console.log(link1pointslist);
// .. and from new node to "to" node
for (i = index; i < link.pointsCount; i++)
{
var point = new go.Point(link.getPoint(i).x, link.getPoint(i).y);
link2pointslist.add(point);
}
console.log(link2pointslist);
var tokey = link.toNode.key;
// add a new node
var newnodedata = { text: "on link", location: go.Point.stringify(pt) };
diag.model.addNodeData(newnodedata);
// and splice it in by changing the existing link to refer to the new node
diag.model.setToKeyForLinkData(link.data, newnodedata.key);
// ** NEW CODE
// Code this was based on re-used the existing link, re-purposing it to go from "from" node
// to new node, so do the same, but give it a new points list.
link.points = link1pointslist; // @@TODO find out why this doesn't work
// ... actually it does, but something ditches the points later ...
// so maybe I need to move this code to after the tool has really finished operating
// by saving off the info and calling it in an override of the last tool method that
// gets called (perhaps not - did this and it didn't work)
debugLastLink = link; // @@TEMP
// and by adding a new link from the new node to the original "toNode"
// ** UPDATED to include the second new point list
diag.model.addLinkData({ from: newnodedata.key, to: tokey, points: toArray(link2pointslist) });
// optional: select the new node
diag.select(diag.findNodeForData(newnodedata));
}, "spliced in node on a link");
}
// not called at present
function maySpliceOutNode(node) {
return node.findLinksInto().count === 1 &&
node.findLinksOutOf().count === 1 &&
node.findLinksInto().first() !== node.findLinksOutOf().first();
}
// not called at present
function spliceNodeOutFromLinkChain(node) {
if (maySpliceOutNode(node)) {
node.diagram.commit(function(diag) {
var inlink = node.findLinksInto().first();
var outlink = node.findLinksOutOf().first();
// reconnect the existing incoming link
inlink.toNode = outlink.toNode;
// remove the node and the outgoing link
diag.removeParts([node, outlink], false);
// optional: select the original link
diag.select(inlink);
}, "spliced out node from chain of links");
}
}
// Initialize modeldi
myModel.nodeDataArray = [
{ key: "1" , "location": "30 30" },
{ key: "2" , "location": "130 30" },
{ key: "3" , "location": "30 130" }
];
myModel.linkDataArray = [
{ from: "1", to: "2", "points": [ 30,30, 70,20, 100,40, 130,30 ] },
{ from: "2", to: "3", "points": [ 130,30, 100,80, 70,90, 30,130 ] },
{ from: "3", to: "1", "points": [ 30,130, 20,100, 40,70, 30,30 ] }
];
myDiagram.model = myModel;
</script>
</body>
</html>