gojs
Version:
Interactive diagrams, charts, and graphs, such as trees, flowcharts, orgcharts, UML, BPMN, or business diagrams
388 lines (372 loc) • 17.4 kB
JavaScript
/*
* Copyright (C) 1998-2020 by Northwoods Software Corporation
* All Rights Reserved.
*
* FLOOR PLANNER CODE: TEMPLATES - GENERAL
* General GraphObject templates used in the Floor Planner sample
* Includes Context Menu, Diagram, Default Group, AngleNode, DimensionLink, PointNode
*/
/*
* Dependencies for Context Menu:
* Make Selection Group, Ungroup Selection, Clear Empty Groups
*/
// Make the selection a group
function makeSelectionGroup(floorplan) {
floorplan.startTransaction("group selection");
// ungroup all selected nodes; then group them; if one of the selected nodes is a group, ungroup all its nodes
var sel = floorplan.selection; var nodes = [];
sel.iterator.each(function (n) {
if (n instanceof go.Group) n.memberParts.iterator.each(function (part) { nodes.push(part); })
else nodes.push(n);
});
for (var i = 0; i < nodes.length; i++) nodes[i].isSelected = true;
ungroupSelection(floorplan);
floorplan.commandHandler.groupSelection();
var group = floorplan.selection.first(); // after grouping, the new group will be the only thing selected
floorplan.model.setDataProperty(group.data, "caption", "Group");
floorplan.model.setDataProperty(group.data, "notes", "");
clearEmptyGroups(floorplan);
// unselect / reselect group so data appears properly in Selection Info Window
floorplan.clearSelection();
floorplan.select(group);
floorplan.commitTransaction("group selection");
}
// Ungroup selected nodes; if the selection is a group, ungroup all it's memberParts
function ungroupSelection(floorplan) {
floorplan.startTransaction('ungroup selection');
// helper function to ungroup nodes
function ungroupNode(node) {
var group = node.containingGroup;
node.containingGroup = null;
if (group != null) {
if (group.memberParts.count === 0) floorplan.remove(group);
else if (group.memberParts.count === 1) group.memberParts.first().containingGroup = null;
}
}
// ungroup any selected nodes; remember groups that are selected
var sel = floorplan.selection; var groups = [];
sel.iterator.each(function (n) {
if (!(n instanceof go.Group)) ungroupNode(n);
else groups.push(n);
});
// go through selected groups, and ungroup their memberparts too
var nodes = [];
for (var i = 0; i < groups.length; i++) groups[i].memberParts.iterator.each(function (n) { nodes.push(n); });
for (var i = 0; i < nodes.length; i++) ungroupNode(nodes[i]);
clearEmptyGroups(floorplan);
floorplan.commitTransaction('ungroup selection');
}
// Clear all the groups that have no nodes
function clearEmptyGroups(floorplan) {
var nodes = floorplan.nodes; var arr = [];
nodes.iterator.each(function (node) { if (node instanceof go.Group && node.memberParts.count === 0 && node.category !== "WallGroup") { arr.push(node); } });
for (i = 0; i < arr.length; i++) { floorplan.remove(arr[i]); }
}
/*
* General Group Dependencies:
* Group Tool Tip
*/
// Group Tool Tip
function makeGroupToolTip() {
var $ = go.GraphObject.make;
return $(go.Adornment, "Auto",
$(go.Shape, { fill: "#FFFFCC" }),
$(go.TextBlock, { margin: 4 },
new go.Binding("text", "", function (text, obj) {
var data = obj.part.adornedObject.data;
var name = (obj.part.adornedObject.category === "MultiPurposeNode") ? data.text : data.caption;
return "Name: " + name + "\nNotes: " + data.notes + '\nMembers: ' + obj.part.adornedObject.memberParts.count;
}).ofObject())
);
}
/*
* General Templates:
* Context Menu, Default Group
*/
// Context Menu -- referenced by Node, Diagram and Group Templates
function makeContextMenu() {
var $ = go.GraphObject.make
return $(go.Adornment, "Vertical",
// Make Selection Group Button
$("ContextMenuButton",
$(go.TextBlock, "Make Group"),
{ click: function (e, obj) { makeSelectionGroup(obj.part.diagram); } },
new go.Binding("visible", "visible", function (v, obj) {
var floorplan = obj.part.diagram;
if (floorplan.selection.count <= 1) return false;
var flag = true;
floorplan.selection.iterator.each(function (node) {
if (node.category === "WallGroup" || node.category === "WindowNode" || node.category === "DoorNode") flag = false;
});
return flag;
}).ofObject()
),
// Ungroup Selection Button
$("ContextMenuButton",
$(go.TextBlock, "Ungroup"),
{ click: function (e, obj) { ungroupSelection(obj.part.diagram); } },
new go.Binding("visible", "", function (v, obj) {
var floorplan = obj.part.diagram;
if (floorplan !== null) {
var node = floorplan.selection.first();
return ((node instanceof go.Node && node.containingGroup != null && node.containingGroup.category != 'WallGroup') ||
(node instanceof go.Group && node.category === ''));
} return false;
}).ofObject()
),
// Copy Button
$("ContextMenuButton",
$(go.TextBlock, "Copy"),
{ click: function (e, obj) { obj.part.diagram.commandHandler.copySelection() } },
new go.Binding("visible", "", function (v, obj) {
if (obj.part.diagram !== null) {
return obj.part.diagram.selection.count > 0;
} return false;
}).ofObject()
),
// Cut Button
$("ContextMenuButton",
$(go.TextBlock, "Cut"),
{ click: function (e, obj) { obj.part.diagram.commandHandler.cutSelection() } },
new go.Binding("visible", "", function (v, obj) {
if (obj.part.diagram !== null) {
return obj.part.diagram.selection.count > 0;
} return false;
}).ofObject()
),
// Delete Button
$("ContextMenuButton",
$(go.TextBlock, "Delete"),
{ click: function (e, obj) { obj.part.diagram.commandHandler.deleteSelection() } },
new go.Binding("visible", "", function (v, obj) {
if (obj.part.diagram !== null) {
return obj.part.diagram.selection.count > 0;
} return false;
}).ofObject()
),
// Paste Button
$("ContextMenuButton",
$(go.TextBlock, "Paste"),
{ click: function (e, obj) { obj.part.diagram.commandHandler.pasteSelection(obj.part.diagram.toolManager.contextMenuTool.mouseDownPoint) } }
),
// Show Selection Info Button (only available when selection count > 0)
$("ContextMenuButton",
$(go.TextBlock, "Show Selection Info"),
{
click: function (e, obj) {
if (e.diagram.floorplanUI) {
var selectionInfoWindow = document.getElementById(e.diagram.floorplanUI.state.windows.selectionInfoWindow.id);
if (selectionInfoWindow.style.visibility !== 'visible') e.diagram.floorplanUI.hideShow('selectionInfoWindow');
}
}
},
new go.Binding("visible", "", function (v, obj) {
if (obj.part.diagram !== null) {
return obj.part.diagram.selection.count > 0;
} return false;
}).ofObject()
),
// Flip Dimension Side Button (only available when selection contains Wall Group(s))
$("ContextMenuButton",
$(go.TextBlock, "Flip Dimension Side"),
{
click: function (e, obj) {
var floorplan = obj.part.diagram;
if (floorplan !== null) {
floorplan.startTransaction("flip dimension link side");
var walls = [];
floorplan.selection.iterator.each(function (part) {
if (part.category === "WallGroup") walls.push(part);
});
for (var i = 0; i < walls.length; i++) {
var wall = walls[i];
var sPt = wall.data.startpoint.copy();
var ePt = wall.data.endpoint.copy();
floorplan.model.setDataProperty(wall.data, "startpoint", ePt);
floorplan.model.setDataProperty(wall.data, "endpoint", sPt);
floorplan.updateWall(wall);
}
floorplan.commitTransaction("flip dimension link side");
}
}
},
new go.Binding("visible", "", function (v, obj) {
if (obj.part.diagram !== null) {
var sel = obj.part.diagram.selection;
if (sel.count === 0) return false;
var flag = false;
sel.iterator.each(function (part) {
if (part.category === "WallGroup") flag = true;
});
return flag;
} return false;
}).ofObject()
)
);
}
// Default Group
function makeDefaultGroup() {
var $ = go.GraphObject.make;
return $(go.Group, "Vertical",
{
contextMenu: makeContextMenu(),
doubleClick: function (e) {
if (e.diagram.floorplanUI) e.diagram.floorplanUI.hideShow("selectionInfoWindow");
},
toolTip: makeGroupToolTip()
},
new go.Binding("location", "loc"),
$(go.Panel, "Auto",
$(go.Shape, "RoundedRectangle", { fill: "rgba(128,128,128,0.15)", stroke: 'rgba(128, 128, 128, .05)', name: 'SHAPE', strokeCap: 'square' },
new go.Binding("fill", "isSelected", function (s, obj) {
return s ? "rgba(128, 128, 128, .15)" : "rgba(128, 128, 128, 0.10)";
}).ofObject()
),
$(go.Placeholder, { padding: 5 }) // extra padding around group members
)
)
}
/*
* Dependencies for Angle Nodes:
* Make Arc
*/
// Return arc geometry for Angle Nodes
function makeArc(node) {
var ang = node.data.angle;
var sweep = node.data.sweep;
var rad = Math.min(30, node.data.maxRadius);
if (typeof sweep === "number" && sweep > 0) {
var start = new go.Point(rad, 0).rotate(ang);
// this is much more efficient than calling go.GraphObject.make:
return new go.Geometry()
.add(new go.PathFigure(start.x + rad, start.y + rad) // start point
.add(new go.PathSegment(go.PathSegment.Arc,
ang, sweep, // angles
rad, rad, // center
rad, rad) // radius
))
.add(new go.PathFigure(0, 0))
.add(new go.PathFigure(2 * rad, 2 * rad));
} else { // make sure this arc always occupies the same circular area of RAD radius
return new go.Geometry()
.add(new go.PathFigure(0, 0))
.add(new go.PathFigure(2 * rad, 2 * rad));
}
}
/*
* Dependencies for Dimension Links
* Make Point Node
*/
// Return a Point Node (used for Dimension Links)
function makePointNode() {
var $ = go.GraphObject.make
return $(go.Node, "Position", new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify));
}
/*
* Dynamically appearing parts:
* Angle Node, Dimension Link
*/
// Return an Angle Node (for each angle ndeeded in the diagram, one angle node is made)
function makeAngleNode() {
var $ = go.GraphObject.make;
return $(go.Node, "Spot",
{ locationSpot: go.Spot.Center, locationObjectName: "SHAPE", selectionAdorned: false },
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
$(go.Shape, "Circle", // placed where walls intersect, is invisible
{ name: "SHAPE", height: 0, width: 0 }),
$(go.Shape, // arc
{ strokeWidth: 1.5, fill: null },
new go.Binding("geometry", "", makeArc).ofObject(),
new go.Binding("stroke", "sweep", function (sweep) {
return (sweep % 45 < 1 || sweep % 45 > 44) ? "dodgerblue" : "lightblue";
})),
// Arc label panel
$(go.Panel, "Auto",
{ name: "ARCLABEL" },
// position the label in the center of the arc
new go.Binding("alignment", "sweep", function (sweep, panel) {
var rad = Math.min(30, panel.part.data.maxRadius);
var angle = panel.part.data.angle;
var cntr = new go.Point(rad, 0).rotate(angle + sweep / 2);
return new go.Spot(0.5, 0.5, cntr.x, cntr.y);
}),
// rectangle containing angle text
$(go.Shape,
{ fill: "white" },
new go.Binding("stroke", "sweep", function (sweep) {
return (sweep % 45 < 1 || sweep % 45 > 44) ? "dodgerblue" : "lightblue";
})),
// angle text
$(go.TextBlock,
{ font: "7pt sans-serif", margin: 2 },
new go.Binding("text", "sweep", function (sweep) {
return sweep.toFixed(2) + String.fromCharCode(176);
}))
)
);
}
// Returns a Dimension Link
function makeDimensionLink() {
var $ = go.GraphObject.make
return $(go.Link,
// link itself
$(go.Shape,
{ stroke: "gray", strokeWidth: 2, name: 'SHAPE' }),
// to arrow shape
$(go.Shape,
{ toArrow: "OpenTriangle", stroke: "gray", strokeWidth: 2 }),
$(go.Shape,
// from arrow shape
{ fromArrow: "BackwardOpenTriangle", stroke: "gray", strokeWidth: 2 }),
// dimension link text
$(go.TextBlock,
{ text: 'sometext', segmentOffset: new go.Point(0, -10), font: "13px sans-serif" },
new go.Binding("text", "", function (link) {
var floorplan = link.diagram;
if (floorplan) {
var fromPtNode = null; var toPtNode = null;
floorplan.pointNodes.iterator.each(function (node) {
if (node.data.key === link.data.from) fromPtNode = node;
if (node.data.key === link.data.to) toPtNode = node;
});
if (fromPtNode !== null) {
var fromPt = fromPtNode.location;
var toPt = toPtNode.location;
return floorplan.convertPixelsToUnits(Math.sqrt(fromPt.distanceSquaredPoint(toPt))).toFixed(2) + floorplan.model.modelData.unitsAbbreviation;
} return null;
} return null;
}).ofObject(),
// bind angle of textblock to angle of link -- always make text rightside up and readable
new go.Binding("angle", "angle", function (angle, link) {
if (angle > 90 && angle < 270) return (angle + 180) % 360;
return angle;
}),
// default poisiton text above / below dimension link based on angle
new go.Binding("segmentOffset", "angle", function (angle, textblock) {
var floorplan = textblock.part.diagram;
if (floorplan) {
var wall = floorplan.findPartForKey(textblock.part.data.wall);
if (wall.rotateObject.angle > 135 && wall.rotateObject.angle < 315) return new go.Point(0, 10);
return new go.Point(0, -10);
} return new go.Point(0, 0);
}).ofObject(),
// scale font size according to the length of the link
new go.Binding("font", "", function (link) {
var floorplan = link.diagram;
var fromPtNode = null; var toPtNode = null;
floorplan.pointNodes.iterator.each(function (node) {
if (node.data.key === link.data.from) fromPtNode = node;
if (node.data.key === link.data.to) toPtNode = node;
});
if (fromPtNode !== null) {
var fromPt = fromPtNode.location;
var toPt = toPtNode.location;
var distance = Math.sqrt(fromPt.distanceSquaredPoint(toPt));
if (distance > 40) return "13px sans-serif";
if (distance <= 40 && distance >= 20) return "11px sans-serif";
else return "9px sans-serif";
} return "13px sans-serif";
}).ofObject()
)
)
}