You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
NewEduCoderBuild/knowledgegraph/blockEditor.html

652 lines
28 KiB

<!DOCTYPE html>
<html lang="en">
<body>
<style>
* {
margin: 0;
padding: 0;
}
button{
position: absolute;
left: 100px;
top: 50px;
}
body{
width: 100vw;
height: 100vh;
overflow: hidden;
}
</style>
<script src="/knowledgegraph/go.js"></script>
<script src="/js/go/Figures.js"></script>
<script src="/js/go/DrawCommandHandler.js"></script>
<script id="code">
const initData = "{ \"class\": \"GraphLinksModel\",\n \"nodeDataArray\": [\n{\"text\":\"Find Problem\",\"key\":-9,\"loc\":\"-20 -140\",\"color\":\"#3358ff\",\"fill\":\"white\",\"figure\":\"Procedure\",\"thickness\":3},\n{\"text\":\"What do we want?\",\"key\":-10,\"loc\":\"-65 -324.305\",\"group\":-16,\"figure\":\"Ellipse\",\"fill\":\"white\"},\n{\"text\":\"What do our users want?\",\"key\":-11,\"loc\":\"105 -334.305\",\"group\":-20,\"figure\":\"Ellipse\",\"fill\":\"#ffffff\",\"color\":\"black\"},\n{\"text\":\"Meetings\",\"key\":-12,\"loc\":\"-65 -444.305\",\"group\":-16,\"figure\":\"TriangleDown\",\"fill\":\"#ffffff\"},\n{\"text\":\"Reviews\",\"key\":-13,\"loc\":\"105 -454.305\",\"group\":-20,\"figure\":\"TriangleDown\",\"fill\":\"#ffffff\",\"color\":\"black\"},\n{\"text\":\"Can we solve it?\",\"key\":-14,\"loc\":\"190 -140\",\"color\":\"#7d33ff\",\"fill\":\"#ffffff\",\"figure\":\"Diamond\",\"size\":\"140 80\",\"thickness\":3},\n{\"isGroup\":true,\"text\":\"Internal\",\"key\":-16,\"loc\":\"-65 -384.305\",\"fill\":\"#d5ebff\",\"dash\":null,\"thickness\":1,\"group\":-22},\n{\"isGroup\":true,\"text\":\"External\",\"key\":-20,\"loc\":\"105 -394.305\",\"fill\":\"#d5ebff\",\"dash\":null,\"thickness\":1,\"group\":-22},\n{\"isGroup\":true,\"text\":\"Sources\",\"key\":-22,\"loc\":\"20 -400\",\"fill\":\"#a5d2fa\",\"dash\":[4,4],\"color\":\"#3358ff\"}\n],\n \"linkDataArray\": [\n{\"from\":-12,\"to\":-10,\"points\":[-65,-414.305,-65,-404.305,-65,-384.305,-65,-384.305,-65,-364.305,-65,-354.305],\"dash\":null,\"dir\":1},\n{\"from\":-13,\"to\":-11,\"points\":[105,-424.305,105,-414.305,105,-394.305,105,-394.305,105,-374.305,105,-364.305],\"dash\":null,\"color\":\"#000000\",\"dir\":1},\n{\"from\":-10,\"to\":-9,\"points\":[-65,-294.305,-65,-284.305,-65,-232.1525,-40,-232.1525,-40,-180,-40,-170],\"dir\":2,\"dash\":[4,4]},\n{\"from\":-11,\"to\":-9,\"points\":[105,-304.305,105,-294.305,105,-237.1525,0,-237.1525,0,-180,0,-170],\"dash\":[4,4],\"dir\":2},\n{\"from\":-9,\"to\":-14,\"points\":[40,-150,58,-150,80,-150,80,-153.33333333333331,102,-153.33333333333331,120,-153.33333333333331],\"dir\":1,\"color\":\"#3358ff\"},\n{\"from\":-14,\"to\":-9,\"points\":[190,-100,190,-90,-20,-90,-20,-95,-20,-100,-20,-110],\"fromSpot\":\"BottomSide\",\"toSpot\":\"BottomSide\",\"text\":\"No\",\"color\":\"#ff3333\",\"thickness\":2,\"dir\":1},\n{\"from\":-9,\"to\":-14,\"points\":[40,-130,58,-130,80,-130,80,-126.66666666666666,102,-126.66666666666666,120,-126.66666666666666]}\n]}"
function init() {
const $ = go.GraphObject.make;
const colors = {
red: "#ff3333",
blue: "#3358ff",
green: "#25ad23",
magenta: "#d533ff",
purple: "#7d33ff",
orange: "#ff6233",
brown: "#8e571e",
white: "#ffffff",
black: "#000000",
beige: "#fffcd5",
extralightblue: "#d5ebff",
extralightred: "#f2dfe0",
lightblue: "#a5d2fa",
lightgray: "#cccccc",
lightgreen: "#b3e6b3",
lightred: "#fcbbbd",
}
myDiagram =
new go.Diagram("myDiagramDiv",
{
padding: 20, // extra space when scrolled all the way
grid: $(go.Panel, "Grid", // a simple 10x10 grid
$(go.Shape, "LineH", { stroke: "lightgray", strokeWidth: 0.5 }),
$(go.Shape, "LineV", { stroke: "lightgray", strokeWidth: 0.5 })
),
"draggingTool.isGridSnapEnabled": true,
handlesDragDropForTopLevelParts: true,
mouseDrop: e => {
var ok = e.diagram.commandHandler.addTopLevelParts(e.diagram.selection, true);
if (!ok) e.diagram.currentTool.doCancel();
},
commandHandler: $(DrawCommandHandler), // support offset copy-and-paste
"clickCreatingTool.archetypeNodeData": { text: "NEW NODE" }, // create a new node by double-clicking in background
"PartCreated": e => {
var node = e.subject; // the newly inserted Node -- now need to snap its location to the grid
node.location = node.location.copy().snapToGridPoint(e.diagram.grid.gridOrigin, e.diagram.grid.gridCellSize);
setTimeout(() => { // and have the user start editing its text
e.diagram.commandHandler.editTextBlock();
}, 20);
},
"commandHandler.archetypeGroupData": { isGroup: true, text: "NEW GROUP" },
"SelectionGrouped": e => {
var group = e.subject;
setTimeout(() => { // and have the user start editing its text
e.diagram.commandHandler.editTextBlock();
})
},
"LinkRelinked": e => {
// re-spread the connections of other links connected with both old and new nodes
var oldnode = e.parameter.part;
oldnode.invalidateConnectedLinks();
var link = e.subject;
if (e.diagram.toolManager.linkingTool.isForwards) {
link.toNode.invalidateConnectedLinks();
} else {
link.fromNode.invalidateConnectedLinks();
}
},
"undoManager.isEnabled": true
});
// Node template
myDiagram.nodeTemplate =
$(go.Node, "Auto",
{
locationSpot: go.Spot.Center, locationObjectName: "SHAPE",
desiredSize: new go.Size(120, 60), minSize: new go.Size(40, 40),
resizable: true, resizeCellSize: new go.Size(20, 20)
},
// these Bindings are TwoWay because the DraggingTool and ResizingTool modify the target properties
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify),
$(go.Shape,
{ // the border
name: "SHAPE", fill: colors.white, cursor: "pointer",
portId: "",
fromLinkable: true, toLinkable: true,
fromLinkableDuplicates: true, toLinkableDuplicates: true,
fromSpot: go.Spot.AllSides, toSpot: go.Spot.AllSides
},
new go.Binding("figure"),
new go.Binding("fill"),
new go.Binding("stroke", "color"),
new go.Binding("strokeWidth", "thickness"),
new go.Binding("strokeDashArray", "dash")),
// this Shape prevents mouse events from reaching the middle of the port
$(go.Shape, { width: 100, height: 40, strokeWidth: 0, fill: "transparent" }),
$(go.TextBlock,
{ margin: 1, textAlign: "center", overflow: go.TextBlock.OverflowEllipsis, editable: true },
// this Binding is TwoWay due to the user editing the text with the TextEditingTool
new go.Binding("text").makeTwoWay(),
new go.Binding("stroke", "color")),
);
myDiagram.nodeTemplate.toolTip =
$("ToolTip", // show some detailed information
$(go.Panel, "Vertical",
{ maxSize: new go.Size(200, NaN) }, // limit width but not height
$(go.TextBlock,
{ font: "bold 10pt sans-serif", textAlign: "center" },
new go.Binding("text")),
$(go.TextBlock,
{ font: "10pt sans-serif", textAlign: "center" },
new go.Binding("text", "details"))
)
);
// Node selection adornment
// Include four large triangular buttons so that the user can easily make a copy
// of the node, move it to be in that direction relative to the original node,
// and add a link to the new node.
function makeArrowButton(spot, fig) {
var maker = (e, shape) => {
e.handled = true;
e.diagram.model.commit(m => {
var selnode = shape.part.adornedPart;
// create a new node in the direction of the spot
var p = new go.Point().setRectSpot(selnode.actualBounds, spot);
p.subtract(selnode.location);
p.scale(2, 2);
p.x += Math.sign(p.x) * 30;
p.y += Math.sign(p.y) * 30;
p.add(selnode.location);
p.snapToGridPoint(e.diagram.grid.gridOrigin, e.diagram.grid.gridCellSize);
// make the new node a copy of the selected node
var nodedata = m.copyNodeData(selnode.data);
// add to same group as selected node
m.setGroupKeyForNodeData(nodedata, m.getGroupKeyForNodeData(selnode.data));
m.addNodeData(nodedata); // add to model
// create a link from the selected node to the new node
var linkdata = { from: selnode.key, to: m.getKeyForNodeData(nodedata) };
m.addLinkData(linkdata); // add to model
// move the new node to the computed location, select it, and start to edit it
var newnode = e.diagram.findNodeForData(nodedata);
newnode.location = p;
e.diagram.select(newnode);
setTimeout(() => {
e.diagram.commandHandler.editTextBlock();
}, 20);
});
};
return $(go.Shape,
{
figure: fig,
alignment: spot, alignmentFocus: spot.opposite(),
width: (spot.equals(go.Spot.Top) || spot.equals(go.Spot.Bottom)) ? 25 : 18,
height: (spot.equals(go.Spot.Top) || spot.equals(go.Spot.Bottom)) ? 18 : 25,
fill: "orange", stroke: colors.white, strokeWidth: 4,
mouseEnter: (e, shape) => shape.fill = "dodgerblue",
mouseLeave: (e, shape) => shape.fill = "orange",
isActionable: true, // needed because it's in an Adornment
click: maker, contextClick: maker
});
}
// create a button that brings up the context menu
function CMButton(options) {
return $(go.Shape,
{
fill: "orange", stroke: "rgba(0, 0, 0, 0)", strokeWidth: 15, background: "transparent",
geometryString: "F1 M0 0 b 0 360 -4 0 4 z M10 0 b 0 360 -4 0 4 z M20 0 b 0 360 -4 0 4",// M10 0 A2 2 0 1 0 14 10 M20 0 A2 2 0 1 0 24 10,
isActionable: true, cursor: "context-menu",
mouseEnter: (e, shape) => shape.fill = "dodgerblue",
mouseLeave: (e, shape) => shape.fill = "orange",
click: (e, shape) => {
e.diagram.commandHandler.showContextMenu(shape.part.adornedPart);
}
},
options || {});
}
myDiagram.nodeTemplate.selectionAdornmentTemplate =
$(go.Adornment, "Spot",
$(go.Placeholder, { padding: 10 }),
makeArrowButton(go.Spot.Top, "TriangleUp"),
makeArrowButton(go.Spot.Left, "TriangleLeft"),
makeArrowButton(go.Spot.Right, "TriangleRight"),
makeArrowButton(go.Spot.Bottom, "TriangleDown"),
CMButton({ alignment: new go.Spot(0.75, 0) })
);
// Common context menu button definitions
// All buttons in context menu work on both click and contextClick,
// in case the user context-clicks on the button.
// All buttons modify the node data, not the Node, so the Bindings need not be TwoWay.
// A button-defining helper function that returns a click event handler.
// PROPNAME is the name of the data property that should be set to the given VALUE.
function ClickFunction(propname, value) {
return (e, obj) => {
e.handled = true; // don't let the click bubble up
e.diagram.model.commit(m => {
m.set(obj.part.adornedPart.data, propname, value);
});
};
}
// Create a context menu button for setting a data property with a color value.
function ColorButton(color, propname) {
if (!propname) propname = "color";
return $(go.Shape,
{
width: 16, height: 16, stroke: "lightgray", fill: color,
margin: 1, background: "transparent",
mouseEnter: (e, shape) => shape.stroke = "dodgerblue",
mouseLeave: (e, shape) => shape.stroke = "lightgray",
click: ClickFunction(propname, color), contextClick: ClickFunction(propname, color)
});
}
function LightFillButtons() { // used by multiple context menus
return [
$("ContextMenuButton",
$(go.Panel, "Horizontal",
ColorButton(colors.white, "fill"), ColorButton(colors.beige, "fill"), ColorButton(colors.extralightblue, "fill"), ColorButton(colors.extralightred, "fill")
)
),
$("ContextMenuButton",
$(go.Panel, "Horizontal",
ColorButton(colors.lightgray, "fill"), ColorButton(colors.lightgreen, "fill"), ColorButton(colors.lightblue, "fill"), ColorButton(colors.lightred, "fill")
)
)
];
}
function DarkColorButtons() { // used by multiple context menus
return [
$("ContextMenuButton",
$(go.Panel, "Horizontal",
ColorButton(colors.black), ColorButton(colors.green), ColorButton(colors.blue), ColorButton(colors.red)
)
),
$("ContextMenuButton",
$(go.Panel, "Horizontal",
ColorButton(colors.white), ColorButton(colors.magenta), ColorButton(colors.purple), ColorButton(colors.orange)
)
)
];
}
// Create a context menu button for setting a data property with a stroke width value.
function ThicknessButton(sw, propname) {
if (!propname) propname = "thickness";
return $(go.Shape, "LineH",
{
width: 16, height: 16, strokeWidth: sw,
margin: 1, background: "transparent",
mouseEnter: (e, shape) => shape.background = "dodgerblue",
mouseLeave: (e, shape) => shape.background = "transparent",
click: ClickFunction(propname, sw), contextClick: ClickFunction(propname, sw)
});
}
// Create a context menu button for setting a data property with a stroke dash Array value.
function DashButton(dash, propname) {
if (!propname) propname = "dash";
return $(go.Shape, "LineH",
{
width: 24, height: 16, strokeWidth: 2,
strokeDashArray: dash,
margin: 1, background: "transparent",
mouseEnter: (e, shape) => shape.background = "dodgerblue",
mouseLeave: (e, shape) => shape.background = "transparent",
click: ClickFunction(propname, dash), contextClick: ClickFunction(propname, dash)
});
}
function StrokeOptionsButtons() { // used by multiple context menus
return [
$("ContextMenuButton",
$(go.Panel, "Horizontal",
ThicknessButton(1), ThicknessButton(2), ThicknessButton(3), ThicknessButton(4)
)
),
$("ContextMenuButton",
$(go.Panel, "Horizontal",
DashButton(null), DashButton([2, 4]), DashButton([4, 4])
)
)
];
}
// Node context menu
function FigureButton(fig, propname) {
if (!propname) propname = "figure";
return $(go.Shape,
{
width: 32, height: 32, scale: 0.5, fill: "lightgray", figure: fig,
margin: 1, background: "transparent",
mouseEnter: (e, shape) => shape.fill = "dodgerblue",
mouseLeave: (e, shape) => shape.fill = "lightgray",
click: ClickFunction(propname, fig), contextClick: ClickFunction(propname, fig)
});
}
myDiagram.nodeTemplate.contextMenu =
$("ContextMenu",
$("ContextMenuButton",
$(go.Panel, "Horizontal",
FigureButton("Rectangle"), FigureButton("RoundedRectangle"), FigureButton("Ellipse"), FigureButton("Diamond")
)
),
$("ContextMenuButton",
$(go.Panel, "Horizontal",
FigureButton("Parallelogram2"), FigureButton("ManualOperation"), FigureButton("Procedure"), FigureButton("Cylinder1")
)
),
$("ContextMenuButton",
$(go.Panel, "Horizontal",
FigureButton("Terminator"), FigureButton("CreateRequest"), FigureButton("Document"), FigureButton("TriangleDown")
)
),
LightFillButtons(),
DarkColorButtons(),
StrokeOptionsButtons()
);
// Group template
myDiagram.groupTemplate =
$(go.Group, "Spot",
{
layerName: "Background",
ungroupable: true,
locationSpot: go.Spot.Center,
selectionObjectName: "BODY",
computesBoundsAfterDrag: true, // allow dragging out of a Group that uses a Placeholder
handlesDragDropForMembers: true, // don't need to define handlers on Nodes and Links
mouseDrop: (e, grp) => { // add dropped nodes as members of the group
var ok = grp.addMembers(grp.diagram.selection, true);
if (!ok) grp.diagram.currentTool.doCancel();
},
avoidable: false
},
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
$(go.Panel, "Auto",
{ name: "BODY" },
$(go.Shape,
{
parameter1: 10,
fill: colors.white, strokeWidth: 2, cursor: "pointer",
fromLinkable: true, toLinkable: true,
fromLinkableDuplicates: true, toLinkableDuplicates: true,
fromSpot: go.Spot.AllSides, toSpot: go.Spot.AllSides
},
new go.Binding("fill"),
new go.Binding("stroke", "color"),
new go.Binding("strokeWidth", "thickness"),
new go.Binding("strokeDashArray", "dash")),
$(go.Placeholder,
{ background: "transparent", margin: 20 })
),
$(go.TextBlock,
{
alignment: go.Spot.Top, alignmentFocus: go.Spot.Bottom,
font: "bold 12pt sans-serif", editable: true
},
new go.Binding("text"),
new go.Binding("stroke", "color"))
);
myDiagram.groupTemplate.selectionAdornmentTemplate =
$(go.Adornment, "Spot",
$(go.Panel, "Auto",
$(go.Shape, { fill: null, stroke: "dodgerblue", strokeWidth: 3 }),
$(go.Placeholder, { margin: 1.5 })
),
CMButton({ alignment: go.Spot.TopRight, alignmentFocus: go.Spot.BottomRight })
);
myDiagram.groupTemplate.contextMenu =
$("ContextMenu",
LightFillButtons(),
DarkColorButtons(),
StrokeOptionsButtons()
);
// Link template
myDiagram.linkTemplate =
$(go.Link,
{
layerName: "Foreground",
routing: go.Link.AvoidsNodes, corner: 10,
fromShortLength: 10, toShortLength: 15, // assume arrowhead at "to" end, need to avoid bad appearance when path is thick
relinkableFrom: true, relinkableTo: true,
reshapable: true, resegmentable: true
},
new go.Binding("fromSpot", "fromSpot", go.Spot.parse),
new go.Binding("toSpot", "toSpot", go.Spot.parse),
new go.Binding("fromShortLength", "dir", dir => dir >= 1 ? 10 : 0),
new go.Binding("toShortLength", "dir", dir => dir >= 1 ? 10 : 0),
new go.Binding("points").makeTwoWay(), // TwoWay due to user reshaping with LinkReshapingTool
$(go.Shape, { strokeWidth: 2 },
new go.Binding("stroke", "color"),
new go.Binding("strokeWidth", "thickness"),
new go.Binding("strokeDashArray", "dash")),
$(go.Shape, // custom arrowheads to create the lifted effect
{
segmentIndex: 0, segmentOffset: new go.Point(15, 0),
segmentOrientation: go.Link.OrientAlong,
alignmentFocus: go.Spot.Right,
figure: "circle", width: 10, strokeWidth: 0
},
new go.Binding("fill", "color"),
new go.Binding("visible", "dir", dir => dir === 1)),
$(go.Shape,
{
segmentIndex: -1, segmentOffset: new go.Point(-10, 6),
segmentOrientation: go.Link.OrientPlus90,
alignmentFocus: go.Spot.Right,
figure: "triangle",
width: 12, height: 12, strokeWidth: 0
},
new go.Binding("fill", "color"),
new go.Binding("visible", "dir", dir => dir >= 1),
new go.Binding("width", "thickness", t => 7 + (3 * t)), // custom arrowhead must scale with the size of the while
new go.Binding("height", "thickness", t => 7 + (3 * t)), // while remaining centered on line
new go.Binding("segmentOffset", "thickness", t => new go.Point(-15, 4 + (1.5 * t)))
),
$(go.Shape,
{
segmentIndex: 0, segmentOffset: new go.Point(15, -6),
segmentOrientation: go.Link.OrientMinus90,
alignmentFocus: go.Spot.Right,
figure: "triangle",
width: 12, height: 12, strokeWidth: 0
},
new go.Binding("fill", "color"),
new go.Binding("visible", "dir", dir => dir === 2),
new go.Binding("width", "thickness", t => 7 + (3 * t)),
new go.Binding("height", "thickness", t => 7 + (3 * t)),
new go.Binding("segmentOffset", "thickness", t => new go.Point(-15, 4 + (1.5 * t)))
),
$(go.TextBlock,
{ alignmentFocus: new go.Spot(0, 1, -4, 0), editable: true },
new go.Binding("text").makeTwoWay(), // TwoWay due to user editing with TextEditingTool
new go.Binding("stroke", "color"))
);
myDiagram.linkTemplate.selectionAdornmentTemplate =
$(go.Adornment, // use a special selection Adornment that does not obscure the link path itself
$(go.Shape,
{ // this uses a pathPattern with a gap in it, in order to avoid drawing on top of the link path Shape
isPanelMain: true,
stroke: "transparent", strokeWidth: 6,
pathPattern: makeAdornmentPathPattern(2) // == thickness or strokeWidth
},
new go.Binding("pathPattern", "thickness", makeAdornmentPathPattern)),
CMButton({ alignmentFocus: new go.Spot(0, 0, -6, -4) })
);
function makeAdornmentPathPattern(w) {
return $(go.Shape,
{
stroke: "dodgerblue", strokeWidth: 2, strokeCap: "square",
geometryString: "M0 0 M4 2 H3 M4 " + (w + 4).toString() + " H3"
});
}
// Link context menu
// All buttons in context menu work on both click and contextClick,
// in case the user context-clicks on the button.
// All buttons modify the link data, not the Link, so the Bindings need not be TwoWay.
function ArrowButton(num) {
var geo = "M0 0 M8 16 M0 8 L16 8 M12 11 L16 8 L12 5";
if (num === 0) {
geo = "M0 0 M16 16 M0 8 L16 8";
} else if (num === 2) {
geo = "M0 0 M16 16 M0 8 L16 8 M12 11 L16 8 L12 5 M4 11 L0 8 L4 5";
}
return $(go.Shape,
{
geometryString: geo,
margin: 2, background: "transparent",
mouseEnter: (e, shape) => shape.background = "dodgerblue",
mouseLeave: (e, shape) => shape.background = "transparent",
click: ClickFunction("dir", num), contextClick: ClickFunction("dir", num)
});
}
function AllSidesButton(to) {
var setter = (e, shape) => {
e.handled = true;
e.diagram.model.commit(m => {
var link = shape.part.adornedPart;
m.set(link.data, (to ? "toSpot" : "fromSpot"), go.Spot.stringify(go.Spot.AllSides));
// re-spread the connections of other links connected with the node
(to ? link.toNode : link.fromNode).invalidateConnectedLinks();
});
};
return $(go.Shape,
{
width: 12, height: 12, fill: "transparent",
mouseEnter: (e, shape) => shape.background = "dodgerblue",
mouseLeave: (e, shape) => shape.background = "transparent",
click: setter, contextClick: setter
});
}
function SpotButton(spot, to) {
var ang = 0;
var side = go.Spot.RightSide;
if (spot.equals(go.Spot.Top)) { ang = 270; side = go.Spot.TopSide; }
else if (spot.equals(go.Spot.Left)) { ang = 180; side = go.Spot.LeftSide; }
else if (spot.equals(go.Spot.Bottom)) { ang = 90; side = go.Spot.BottomSide; }
if (!to) ang -= 180;
var setter = (e, shape) => {
e.handled = true;
e.diagram.model.commit(m => {
var link = shape.part.adornedPart;
m.set(link.data, (to ? "toSpot" : "fromSpot"), go.Spot.stringify(side));
// re-spread the connections of other links connected with the node
(to ? link.toNode : link.fromNode).invalidateConnectedLinks();
});
};
return $(go.Shape,
{
alignment: spot, alignmentFocus: spot.opposite(),
geometryString: "M0 0 M12 12 M12 6 L1 6 L4 4 M1 6 L4 8",
angle: ang,
background: "transparent",
mouseEnter: (e, shape) => shape.background = "dodgerblue",
mouseLeave: (e, shape) => shape.background = "transparent",
click: setter, contextClick: setter
});
}
myDiagram.linkTemplate.contextMenu =
$("ContextMenu",
DarkColorButtons(),
StrokeOptionsButtons(),
$("ContextMenuButton",
$(go.Panel, "Horizontal",
ArrowButton(0), ArrowButton(1), ArrowButton(2)
)
),
$("ContextMenuButton",
$(go.Panel, "Horizontal",
$(go.Panel, "Spot",
AllSidesButton(false),
SpotButton(go.Spot.Top, false), SpotButton(go.Spot.Left, false), SpotButton(go.Spot.Right, false), SpotButton(go.Spot.Bottom, false)
),
$(go.Panel, "Spot",
{ margin: new go.Margin(0, 0, 0, 2) },
AllSidesButton(true),
SpotButton(go.Spot.Top, true), SpotButton(go.Spot.Left, true), SpotButton(go.Spot.Right, true), SpotButton(go.Spot.Bottom, true)
)
)
)
);
load();
}
function myCallback(blob) {
var url = window.URL.createObjectURL(blob);
var filename = "myBlobFile.png";
var a = document.createElement("a");
a.style = "display: none";
a.href = url;
a.download = filename;
// IE 11
if (window.navigator.msSaveBlob !== undefined) {
window.navigator.msSaveBlob(blob, filename);
return;
}
// debugger
document.body.appendChild(a);
requestAnimationFrame(() => {
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
});
}
// Show the diagram's model in JSON format
function save() {
console.log(JSON.stringify(myDiagram.model.toJson()))
myDiagram.isModified = false;
// 将图表保存为PNG图像
function saveAsPng() {
var imgs = myDiagram.makeImage();
document.body.appendChild(imgs)
return
}
// 调用保存函数
saveAsPng();
}
function load() {
myDiagram.model = go.Model.fromJson(initData);
}
window.addEventListener('DOMContentLoaded', init);
</script>
<div id="myDiagramDiv"
style="border: 1px solid black; width: 100%; height: 100vh; position: relative; -webkit-tap-highlight-color: rgba(255, 255, 255, 0); cursor: auto; font: 13px sans-serif;">
<canvas tabindex="0" width="1215" height="598"
style="position: absolute; top: 0px; left: 0px; z-index: 2; user-select: none; touch-action: none; width: 1215px; height: 598px; cursor: auto;"></canvas>
<div style="position: absolute; overflow: auto; width: 1232px; height: 598px; z-index: 1;">
<div style="position: absolute; width: 1px; height: 604.075px;"></div>
</div>
</div>
<button onclick="save()">保存图片</button>
</body>
</html>