feat: 后端全量更新 - 含所有本次需求
- Contract.php: 返回合约账户余额(balance_contract) - My.php: 地址管理增加BTC/ETH - AppContract.php: 一键平仓(closeall) - AppProxy.php: 代理专属注册链接 + 分级权限(L1/L2) - site.php: 手续费减半(0.018→0.009) - agent_permission_setup.sql: 代理权限SQL - crypto_news_crawler.py: 新闻自动采集脚本
This commit is contained in:
+47
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Arranging Layout of the Class Hierarchy</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="Arrange disconnected circular subgraphs in a circle and put disconnected nodes in a grid underneath." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../samples/assets/require.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
function init() {
|
||||
require(["ArrangingScript"], function(app) {
|
||||
app.init();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:800px; min-width: 200px"></div>
|
||||
<p>
|
||||
This sample demonstrates a custom Layout, <a>ArrangingLayout</a>, that provides layouts of layouts.
|
||||
It assumes the graph should be split up and laid out by potentially three separate Layouts.
|
||||
</p>
|
||||
<p>
|
||||
The first step of ArrangingLayout is that all unconnected nodes are separated out to be laid out later by
|
||||
the <a>ArrangingLayout.sideLayout</a>, which by default is a <a>GridLayout</a>.
|
||||
</p>
|
||||
<p>
|
||||
The remaining nodes and links are partitioned into separate subgraphs with no links between subgraphs.
|
||||
The <a>ArrangingLayout.primaryLayout</a> is performed on each subgraph.
|
||||
</p>
|
||||
<p>
|
||||
If there is more than one subgraph, those subgraphs are treated as if they were individual nodes and are
|
||||
laid out by the <a>ArrangingLayout.arrangingLayout</a>.
|
||||
</p>
|
||||
<p>
|
||||
Finally the unconnected nodes are laid out by <a>ArrangingLayout.sideLayout</a> and they are all positioned
|
||||
at the <a>ArrangingLayout.side</a> Spot relative to the main body of nodes and links.
|
||||
</p>
|
||||
<p>
|
||||
This extension layout is defined in its own file, as <a href="ArrangingLayout.js">ArrangingLayout.js</a>.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
+424
@@ -0,0 +1,424 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ArrangingLayout = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
/**
|
||||
* A custom Layout that provides one way to have a layout of layouts.
|
||||
* It partitions nodes and links into separate subgraphs, applies a primary
|
||||
* layout to each subgraph, and then arranges those results by an
|
||||
* arranging layout. Any disconnected nodes are laid out later by a
|
||||
* side layout, by default in a grid underneath the main body of subgraphs.
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/Arranging.html">Arranging Layout</a> sample.
|
||||
*
|
||||
* This layout uses three separate Layouts.
|
||||
*
|
||||
* One is used for laying out nodes and links that are connected together: {@link #primaryLayout}.
|
||||
* This defaults to null and must be set to an instance of a {@link Layout},
|
||||
* such as a {@link TreeLayout} or a {@link ForceDirectedLayout} or a custom Layout.
|
||||
*
|
||||
* One is used to arrange separate subnetworks of the main graph: {@link #arrangingLayout}.
|
||||
* This defaults to an instance of {@link GridLayout}.
|
||||
*
|
||||
* One is used for laying out the additional nodes along one of the sides of the main graph: {@link #sideLayout}.
|
||||
* This also defaults to an instance of {@link GridLayout}.
|
||||
* A filter predicate, {@link #filter}, splits up the collection of nodes and links into two subsets,
|
||||
* one for the main layout and one for the side layout.
|
||||
* By default, when there is no filter, it puts all nodes that have no link connections into the
|
||||
* subset to be processed by the side layout.
|
||||
*
|
||||
* If all pairs of nodes in the main graph can be reached by some path of undirected links,
|
||||
* there are no separate subnetworks, so the {@link #arrangingLayout} need not be used and
|
||||
* the {@link #primaryLayout} would apply to all of those nodes and links.
|
||||
*
|
||||
* But if there are disconnected subnetworks, the {@link #primaryLayout} is applied to each subnetwork,
|
||||
* and then all of those results are arranged by the {@link #arrangingLayout}.
|
||||
*
|
||||
* In either case if there are any nodes in the side graph, those are arranged by the {@link #sideLayout}
|
||||
* to be on the side of the arrangement of the main graph of nodes and links.
|
||||
*
|
||||
* Note: if you do not want to have singleton nodes be arranged by {@link #sideLayout},
|
||||
* set {@link #filter} to <code>function(part) { return true; }</code>.
|
||||
* That will cause all singleton nodes to be arranged by {@link #arrangingLayout} as if they
|
||||
* were each their own subgraph.
|
||||
*
|
||||
* If you both don't want to use {@link #sideLayout} and you don't want to use {@link #arrangingLayout}
|
||||
* to lay out connected subgraphs, don't use this ArrangingLayout at all --
|
||||
* just use whatever Layout you would have assigned to {@link #primaryLayout}.
|
||||
*
|
||||
* @category Layout Extension
|
||||
*/
|
||||
var ArrangingLayout = /** @class */ (function (_super) {
|
||||
__extends(ArrangingLayout, _super);
|
||||
function ArrangingLayout() {
|
||||
var _this = _super.call(this) || this;
|
||||
_this._filter = null;
|
||||
_this._side = go.Spot.BottomSide;
|
||||
_this._spacing = new go.Size(20, 20);
|
||||
var play = new go.GridLayout();
|
||||
play.cellSize = new go.Size(1, 1);
|
||||
_this._primaryLayout = play;
|
||||
var alay = new go.GridLayout();
|
||||
alay.cellSize = new go.Size(1, 1);
|
||||
_this._arrangingLayout = alay;
|
||||
var slay = new go.GridLayout();
|
||||
slay.cellSize = new go.Size(1, 1);
|
||||
_this._sideLayout = slay;
|
||||
return _this;
|
||||
}
|
||||
/**
|
||||
* @ignore @hidden @internal
|
||||
* Copies properties to a cloned Layout.
|
||||
*/
|
||||
ArrangingLayout.prototype.cloneProtected = function (copy) {
|
||||
_super.prototype.cloneProtected.call(this, copy);
|
||||
copy._filter = this._filter;
|
||||
if (this._primaryLayout !== null)
|
||||
copy._primaryLayout = this._primaryLayout.copy();
|
||||
if (this._arrangingLayout !== null)
|
||||
copy._arrangingLayout = this._arrangingLayout.copy();
|
||||
if (this._sideLayout !== null)
|
||||
copy._sideLayout = this._sideLayout.copy();
|
||||
copy._side = this._side.copy();
|
||||
copy._spacing = this._spacing.copy();
|
||||
};
|
||||
;
|
||||
/**
|
||||
* @hidden @internal
|
||||
* @param {Diagram|Group|Iterable} coll the collection of Parts to layout.
|
||||
*/
|
||||
ArrangingLayout.prototype.doLayout = function (coll) {
|
||||
var coll2 = this.collectParts(coll);
|
||||
var diagram = this.diagram;
|
||||
if (diagram === null)
|
||||
throw new Error("No Diagram for this Layout");
|
||||
// implementations of doLayout that do not make use of a LayoutNetwork
|
||||
// need to perform their own transactions
|
||||
diagram.startTransaction("Arranging Layout");
|
||||
var maincoll = new go.Set();
|
||||
var sidecoll = new go.Set();
|
||||
this.splitParts(coll2, maincoll, sidecoll);
|
||||
var mainnet = null;
|
||||
var subnets = null;
|
||||
if (this.arrangingLayout !== null) {
|
||||
mainnet = this.makeNetwork(maincoll);
|
||||
subnets = mainnet.splitIntoSubNetworks();
|
||||
}
|
||||
var bounds = null;
|
||||
if (this.arrangingLayout !== null && mainnet !== null && subnets !== null && subnets.count > 1) {
|
||||
var groups = new go.Map();
|
||||
var it = subnets.iterator;
|
||||
while (it.next()) {
|
||||
var net = it.value;
|
||||
var subcoll = net.findAllParts();
|
||||
this.preparePrimaryLayout(this.primaryLayout, subcoll);
|
||||
this.primaryLayout.doLayout(subcoll);
|
||||
this._addMainNode(groups, subcoll, diagram);
|
||||
}
|
||||
var mit = mainnet.vertexes.iterator;
|
||||
while (mit.next()) {
|
||||
var v = mit.value;
|
||||
if (v.node) {
|
||||
var subcoll = new go.Set();
|
||||
subcoll.add(v.node);
|
||||
this.preparePrimaryLayout(this.primaryLayout, subcoll);
|
||||
this.primaryLayout.doLayout(subcoll);
|
||||
this._addMainNode(groups, subcoll, diagram);
|
||||
}
|
||||
}
|
||||
this.arrangingLayout.doLayout(groups.toKeySet());
|
||||
var git = groups.iterator;
|
||||
while (git.next()) {
|
||||
var grp = git.key;
|
||||
var ginfo = git.value;
|
||||
this.moveSubgraph(ginfo.parts, ginfo.bounds, new go.Rect(grp.position, grp.desiredSize));
|
||||
}
|
||||
bounds = diagram.computePartsBounds(groups.toKeySet()); // not maincoll due to links without real bounds
|
||||
}
|
||||
else { // no this.arrangingLayout
|
||||
this.preparePrimaryLayout(this.primaryLayout, maincoll);
|
||||
this.primaryLayout.doLayout(maincoll);
|
||||
bounds = diagram.computePartsBounds(maincoll);
|
||||
this.moveSubgraph(maincoll, bounds, bounds);
|
||||
}
|
||||
if (!bounds.isReal())
|
||||
bounds = new go.Rect(0, 0, 0, 0);
|
||||
this.prepareSideLayout(this.sideLayout, sidecoll, bounds);
|
||||
if (sidecoll.count > 0) {
|
||||
this.sideLayout.doLayout(sidecoll);
|
||||
var sidebounds = diagram.computePartsBounds(sidecoll);
|
||||
if (!sidebounds.isReal())
|
||||
sidebounds = new go.Rect(0, 0, 0, 0);
|
||||
this.moveSideCollection(sidecoll, bounds, sidebounds);
|
||||
}
|
||||
diagram.commitTransaction("Arranging Layout");
|
||||
};
|
||||
;
|
||||
/**
|
||||
* @hidden @internal
|
||||
* @param {*} subcoll
|
||||
*/
|
||||
ArrangingLayout.prototype._addMainNode = function (groups, subcoll, diagram) {
|
||||
var grp = new go.Node();
|
||||
grp.locationSpot = go.Spot.Center;
|
||||
var grpb = diagram.computePartsBounds(subcoll);
|
||||
grp.desiredSize = grpb.size;
|
||||
grp.position = grpb.position;
|
||||
groups.add(grp, { parts: subcoll, bounds: grpb });
|
||||
};
|
||||
/**
|
||||
* Assign all of the Parts in the given collection into either the
|
||||
* set of Nodes and Links for the main graph or the set of Nodes and Links
|
||||
* for the side graph.
|
||||
*
|
||||
* By default this just calls the {@link #filter} on each non-Link to decide,
|
||||
* and then looks at each Link's connected Nodes to decide.
|
||||
*
|
||||
* A null filter assigns all Nodes that have connected Links to the main graph, and
|
||||
* all Links will be assigned to the main graph, and the side graph will only contain
|
||||
* Parts with no connected Links.
|
||||
* @param {Set} coll
|
||||
* @param {Set} maincoll
|
||||
* @param {Set} sidecoll
|
||||
*/
|
||||
ArrangingLayout.prototype.splitParts = function (coll, maincoll, sidecoll) {
|
||||
// first consider all Nodes
|
||||
var pred = this.filter;
|
||||
coll.each(function (p) {
|
||||
if (p instanceof go.Link)
|
||||
return;
|
||||
var main;
|
||||
if (pred)
|
||||
main = pred(p);
|
||||
else if (p instanceof go.Node)
|
||||
main = (p.linksConnected.count > 0);
|
||||
else
|
||||
main = (p instanceof go.Link);
|
||||
if (main) {
|
||||
maincoll.add(p);
|
||||
}
|
||||
else {
|
||||
sidecoll.add(p);
|
||||
}
|
||||
});
|
||||
// now assign Links based on which Nodes they connect with
|
||||
coll.each(function (p) {
|
||||
if (p instanceof go.Link) {
|
||||
if (!p.fromNode || !p.toNode)
|
||||
return;
|
||||
if (maincoll.contains(p.fromNode) && maincoll.contains(p.toNode)) {
|
||||
maincoll.add(p);
|
||||
}
|
||||
else if (sidecoll.contains(p.fromNode) && sidecoll.contains(p.toNode)) {
|
||||
sidecoll.add(p);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
/**
|
||||
* This method is called just before the primaryLayout is performed so that
|
||||
* there can be adjustments made to the primaryLayout, if desired.
|
||||
* By default this method makes no adjustments to the primaryLayout.
|
||||
* @param {Layout} primaryLayout the sideLayout that may be modified for the results of the primaryLayout
|
||||
* @param {Set} mainColl the Nodes and Links to be laid out by primaryLayout after being separated into subnetworks
|
||||
*/
|
||||
ArrangingLayout.prototype.preparePrimaryLayout = function (primaryLayout, mainColl) {
|
||||
// by default this is a no-op
|
||||
};
|
||||
/**
|
||||
* Move a Set of Nodes and Links to the given area.
|
||||
* @param {Set} subColl the Set of Nodes and Links that form a separate connected subgraph
|
||||
* @param {Rect} subbounds the area occupied by the subColl
|
||||
* @param {Rect} bounds the area where they should be moved according to the arrangingLayout
|
||||
*/
|
||||
ArrangingLayout.prototype.moveSubgraph = function (subColl, subbounds, bounds) {
|
||||
var diagram = this.diagram;
|
||||
if (!diagram)
|
||||
return;
|
||||
diagram.moveParts(subColl, bounds.position.subtract(subbounds.position), false);
|
||||
};
|
||||
/**
|
||||
* This method is called just after the main layouts (the primaryLayouts and arrangingLayout)
|
||||
* have been performed and just before the sideLayout is performed so that there can be
|
||||
* adjustments made to the sideLayout, if desired.
|
||||
* By default this method makes no adjustments to the sideLayout.
|
||||
* @param {Layout} sideLayout the sideLayout that may be modified for the results of the main layouts
|
||||
* @param {Set} sideColl the Nodes and Links filtered out to be laid out by sideLayout
|
||||
* @param {Rect} mainBounds the area occupied by the nodes and links of the main layout, after it was performed
|
||||
*/
|
||||
ArrangingLayout.prototype.prepareSideLayout = function (sideLayout, sideColl, mainBounds) {
|
||||
// by default this is a no-op
|
||||
};
|
||||
/**
|
||||
* This method is called just after the sideLayout has been performed in order to move
|
||||
* its parts to the desired area relative to the results of the main layouts.
|
||||
* By default this calls {@link Diagram#moveParts} on the sidecoll collection to the {@link #side} of the mainbounds.
|
||||
* This won't get called if there are no Parts in the sidecoll collection.
|
||||
* @param {Set} sidecoll a collection of Parts that were laid out by the sideLayout
|
||||
* @param {Rect} mainbounds the area occupied by the results of the main layouts
|
||||
* @param {Rect} sidebounds the area occupied by the results of the sideLayout
|
||||
*/
|
||||
ArrangingLayout.prototype.moveSideCollection = function (sidecoll, mainbounds, sidebounds) {
|
||||
var diagram = this.diagram;
|
||||
if (!diagram)
|
||||
return;
|
||||
if (this.side.includesSide(go.Spot.BottomSide)) {
|
||||
diagram.moveParts(sidecoll, new go.Point(mainbounds.x - sidebounds.x, mainbounds.y + mainbounds.height + this.spacing.height - sidebounds.y), false);
|
||||
}
|
||||
else if (this.side.includesSide(go.Spot.RightSide)) {
|
||||
diagram.moveParts(sidecoll, new go.Point(mainbounds.x + mainbounds.width + this.spacing.width - sidebounds.x, mainbounds.y - sidebounds.y), false);
|
||||
}
|
||||
else if (this.side.includesSide(go.Spot.TopSide)) {
|
||||
diagram.moveParts(sidecoll, new go.Point(mainbounds.x - sidebounds.x, mainbounds.y - sidebounds.height - this.spacing.height - sidebounds.y), false);
|
||||
}
|
||||
else if (this.side.includesSide(go.Spot.LeftSide)) {
|
||||
diagram.moveParts(sidecoll, new go.Point(mainbounds.x - sidebounds.width - this.spacing.width - sidebounds.x, mainbounds.y - sidebounds.y), false);
|
||||
}
|
||||
};
|
||||
Object.defineProperty(ArrangingLayout.prototype, "filter", {
|
||||
// Public properties
|
||||
/**
|
||||
* Gets or sets the predicate function to call on each non-Link.
|
||||
* If the predicate returns true, the part will be laid out by the main layouts,
|
||||
* the primaryLayouts and the arrangingLayout, otherwise by the sideLayout.
|
||||
* The default value is a function that is true when there are any links connecting with the node.
|
||||
* Such default behavior will have the sideLayout position all of the singleton nodes.
|
||||
*/
|
||||
get: function () { return this._filter; },
|
||||
set: function (val) {
|
||||
if (val && typeof val !== 'function')
|
||||
throw new Error("new value for ArrangingLayout.filter must be a function, not: " + val);
|
||||
if (this._filter !== val) {
|
||||
this._filter = val;
|
||||
this.invalidateLayout();
|
||||
}
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(ArrangingLayout.prototype, "side", {
|
||||
/**
|
||||
* Gets or sets the side {@link Spot} where the side nodes and links should be laid out,
|
||||
* relative to the results of the main Layout.
|
||||
* The default value is Spot.BottomSide.
|
||||
* Currently only handles a single side.
|
||||
* @name ArrangingLayout#side
|
||||
* @return {Spot}
|
||||
*/
|
||||
get: function () { return this._side; },
|
||||
set: function (val) {
|
||||
if (!(val instanceof go.Spot) || !val.isSide()) {
|
||||
throw new Error("new value for ArrangingLayout.side must be a side Spot, not: " + val);
|
||||
}
|
||||
if (!this._side.equals(val)) {
|
||||
this._side = val.copy();
|
||||
this.invalidateLayout();
|
||||
}
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(ArrangingLayout.prototype, "spacing", {
|
||||
/**
|
||||
* Gets or sets the space between the main layout and the side layout.
|
||||
* The default value is Size(20, 20).
|
||||
* @name ArrangingLayout#spacing
|
||||
* @return {Size}
|
||||
*/
|
||||
get: function () { return this._spacing; },
|
||||
set: function (val) {
|
||||
if (!(val instanceof go.Size))
|
||||
throw new Error("new value for ArrangingLayout.spacing must be a Size, not: " + val);
|
||||
if (!this._spacing.equals(val)) {
|
||||
this._spacing = val.copy();
|
||||
this.invalidateLayout();
|
||||
}
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(ArrangingLayout.prototype, "primaryLayout", {
|
||||
/**
|
||||
* Gets or sets the Layout used for the main part of the diagram.
|
||||
* The default value is an instance of GridLayout.
|
||||
* Any new value must not be null.
|
||||
*/
|
||||
get: function () { return this._primaryLayout; },
|
||||
set: function (val) {
|
||||
if (!(val instanceof go.Layout))
|
||||
throw new Error("layout does not inherit from go.Layout: " + val);
|
||||
this._primaryLayout = val;
|
||||
this.invalidateLayout();
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(ArrangingLayout.prototype, "arrangingLayout", {
|
||||
/**
|
||||
* Gets or sets the Layout used to arrange multiple separate connected subgraphs of the main graph.
|
||||
* The default value is an instance of GridLayout.
|
||||
* Set this property to null in order to get the default behavior of the @{link #primaryLayout}
|
||||
* when dealing with multiple connected graphs as a whole.
|
||||
*/
|
||||
get: function () { return this._arrangingLayout; },
|
||||
set: function (val) {
|
||||
if (val && !(val instanceof go.Layout))
|
||||
throw new Error("layout does not inherit from go.Layout: " + val);
|
||||
this._arrangingLayout = val;
|
||||
this.invalidateLayout();
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(ArrangingLayout.prototype, "sideLayout", {
|
||||
/**
|
||||
* Gets or sets the Layout used to arrange the "side" nodes and links -- those outside of the main layout.
|
||||
* The default value is an instance of GridLayout.
|
||||
* Any new value must not be null.
|
||||
*/
|
||||
get: function () { return this._sideLayout; },
|
||||
set: function (val) {
|
||||
if (!(val instanceof go.Layout))
|
||||
throw new Error("layout does not inherit from go.Layout: " + val);
|
||||
this._sideLayout = val;
|
||||
this.invalidateLayout();
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
return ArrangingLayout;
|
||||
}(go.Layout));
|
||||
exports.ArrangingLayout = ArrangingLayout;
|
||||
});
|
||||
+374
@@ -0,0 +1,374 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
|
||||
/**
|
||||
* A custom Layout that provides one way to have a layout of layouts.
|
||||
* It partitions nodes and links into separate subgraphs, applies a primary
|
||||
* layout to each subgraph, and then arranges those results by an
|
||||
* arranging layout. Any disconnected nodes are laid out later by a
|
||||
* side layout, by default in a grid underneath the main body of subgraphs.
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/Arranging.html">Arranging Layout</a> sample.
|
||||
*
|
||||
* This layout uses three separate Layouts.
|
||||
*
|
||||
* One is used for laying out nodes and links that are connected together: {@link #primaryLayout}.
|
||||
* This defaults to null and must be set to an instance of a {@link Layout},
|
||||
* such as a {@link TreeLayout} or a {@link ForceDirectedLayout} or a custom Layout.
|
||||
*
|
||||
* One is used to arrange separate subnetworks of the main graph: {@link #arrangingLayout}.
|
||||
* This defaults to an instance of {@link GridLayout}.
|
||||
*
|
||||
* One is used for laying out the additional nodes along one of the sides of the main graph: {@link #sideLayout}.
|
||||
* This also defaults to an instance of {@link GridLayout}.
|
||||
* A filter predicate, {@link #filter}, splits up the collection of nodes and links into two subsets,
|
||||
* one for the main layout and one for the side layout.
|
||||
* By default, when there is no filter, it puts all nodes that have no link connections into the
|
||||
* subset to be processed by the side layout.
|
||||
*
|
||||
* If all pairs of nodes in the main graph can be reached by some path of undirected links,
|
||||
* there are no separate subnetworks, so the {@link #arrangingLayout} need not be used and
|
||||
* the {@link #primaryLayout} would apply to all of those nodes and links.
|
||||
*
|
||||
* But if there are disconnected subnetworks, the {@link #primaryLayout} is applied to each subnetwork,
|
||||
* and then all of those results are arranged by the {@link #arrangingLayout}.
|
||||
*
|
||||
* In either case if there are any nodes in the side graph, those are arranged by the {@link #sideLayout}
|
||||
* to be on the side of the arrangement of the main graph of nodes and links.
|
||||
*
|
||||
* Note: if you do not want to have singleton nodes be arranged by {@link #sideLayout},
|
||||
* set {@link #filter} to <code>function(part) { return true; }</code>.
|
||||
* That will cause all singleton nodes to be arranged by {@link #arrangingLayout} as if they
|
||||
* were each their own subgraph.
|
||||
*
|
||||
* If you both don't want to use {@link #sideLayout} and you don't want to use {@link #arrangingLayout}
|
||||
* to lay out connected subgraphs, don't use this ArrangingLayout at all --
|
||||
* just use whatever Layout you would have assigned to {@link #primaryLayout}.
|
||||
*
|
||||
* @category Layout Extension
|
||||
*/
|
||||
export class ArrangingLayout extends go.Layout {
|
||||
private _filter: ((part: go.Part) => boolean) | null = null;
|
||||
private _primaryLayout: go.Layout;
|
||||
private _arrangingLayout: go.Layout;
|
||||
private _sideLayout: go.Layout;
|
||||
private _side: go.Spot = go.Spot.BottomSide;
|
||||
private _spacing: go.Size = new go.Size(20, 20);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const play = new go.GridLayout();
|
||||
play.cellSize = new go.Size(1, 1);
|
||||
this._primaryLayout = play;
|
||||
const alay = new go.GridLayout();
|
||||
alay.cellSize = new go.Size(1, 1);
|
||||
this._arrangingLayout = alay;
|
||||
const slay = new go.GridLayout();
|
||||
slay.cellSize = new go.Size(1, 1);
|
||||
this._sideLayout = slay;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore @hidden @internal
|
||||
* Copies properties to a cloned Layout.
|
||||
*/
|
||||
cloneProtected(copy: this): void {
|
||||
super.cloneProtected(copy);
|
||||
copy._filter = this._filter;
|
||||
if (this._primaryLayout !== null) copy._primaryLayout = this._primaryLayout.copy();
|
||||
if (this._arrangingLayout !== null) copy._arrangingLayout = this._arrangingLayout.copy();
|
||||
if (this._sideLayout !== null) copy._sideLayout = this._sideLayout.copy();
|
||||
copy._side = this._side.copy();
|
||||
copy._spacing = this._spacing.copy();
|
||||
};
|
||||
|
||||
/**
|
||||
* @hidden @internal
|
||||
* @param {Diagram|Group|Iterable} coll the collection of Parts to layout.
|
||||
*/
|
||||
doLayout(coll: go.Diagram | go.Group | go.Iterable<go.Part>) {
|
||||
const coll2 = this.collectParts(coll);
|
||||
|
||||
const diagram = this.diagram;
|
||||
if (diagram === null) throw new Error("No Diagram for this Layout");
|
||||
|
||||
// implementations of doLayout that do not make use of a LayoutNetwork
|
||||
// need to perform their own transactions
|
||||
diagram.startTransaction("Arranging Layout");
|
||||
|
||||
const maincoll = new go.Set<go.Part>();
|
||||
const sidecoll = new go.Set<go.Part>();
|
||||
this.splitParts(coll2, maincoll, sidecoll);
|
||||
|
||||
let mainnet = null;
|
||||
let subnets = null;
|
||||
if (this.arrangingLayout !== null) {
|
||||
mainnet = this.makeNetwork(maincoll);
|
||||
subnets = mainnet.splitIntoSubNetworks();
|
||||
}
|
||||
let bounds = null;
|
||||
if (this.arrangingLayout !== null && mainnet !== null && subnets !== null && subnets.count > 1) {
|
||||
const groups = new go.Map<go.Part, { parts: go.Set<go.Part>, bounds: go.Rect }>();
|
||||
const it = subnets.iterator;
|
||||
while (it.next()) {
|
||||
const net = it.value;
|
||||
const subcoll = net.findAllParts();
|
||||
this.preparePrimaryLayout(this.primaryLayout, subcoll);
|
||||
this.primaryLayout.doLayout(subcoll);
|
||||
this._addMainNode(groups, subcoll, diagram);
|
||||
}
|
||||
const mit = mainnet.vertexes.iterator;
|
||||
while (mit.next()) {
|
||||
const v = mit.value;
|
||||
if (v.node) {
|
||||
const subcoll = new go.Set<go.Part>();
|
||||
subcoll.add(v.node);
|
||||
this.preparePrimaryLayout(this.primaryLayout, subcoll);
|
||||
this.primaryLayout.doLayout(subcoll);
|
||||
this._addMainNode(groups, subcoll, diagram);
|
||||
}
|
||||
}
|
||||
|
||||
this.arrangingLayout.doLayout(groups.toKeySet());
|
||||
const git = groups.iterator;
|
||||
while (git.next()) {
|
||||
const grp = git.key;
|
||||
const ginfo = git.value;
|
||||
this.moveSubgraph(ginfo.parts, ginfo.bounds, new go.Rect(grp.position, grp.desiredSize));
|
||||
}
|
||||
bounds = diagram.computePartsBounds(groups.toKeySet()); // not maincoll due to links without real bounds
|
||||
} else { // no this.arrangingLayout
|
||||
this.preparePrimaryLayout(this.primaryLayout, maincoll);
|
||||
this.primaryLayout.doLayout(maincoll);
|
||||
bounds = diagram.computePartsBounds(maincoll);
|
||||
this.moveSubgraph(maincoll, bounds, bounds);
|
||||
}
|
||||
if (!bounds.isReal()) bounds = new go.Rect(0, 0, 0, 0);
|
||||
|
||||
this.prepareSideLayout(this.sideLayout, sidecoll, bounds);
|
||||
if (sidecoll.count > 0) {
|
||||
this.sideLayout.doLayout(sidecoll);
|
||||
let sidebounds = diagram.computePartsBounds(sidecoll);
|
||||
if (!sidebounds.isReal()) sidebounds = new go.Rect(0, 0, 0, 0);
|
||||
|
||||
this.moveSideCollection(sidecoll, bounds, sidebounds);
|
||||
}
|
||||
|
||||
diagram.commitTransaction("Arranging Layout");
|
||||
};
|
||||
|
||||
/**
|
||||
* @hidden @internal
|
||||
* @param {*} subcoll
|
||||
*/
|
||||
_addMainNode(groups: go.Map<go.Part, { parts: go.Set<go.Part>, bounds: go.Rect }>, subcoll: go.Set<go.Part>, diagram: go.Diagram) {
|
||||
const grp = new go.Node();
|
||||
grp.locationSpot = go.Spot.Center;
|
||||
const grpb = diagram.computePartsBounds(subcoll);
|
||||
grp.desiredSize = grpb.size;
|
||||
grp.position = grpb.position;
|
||||
groups.add(grp, { parts: subcoll, bounds: grpb });
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign all of the Parts in the given collection into either the
|
||||
* set of Nodes and Links for the main graph or the set of Nodes and Links
|
||||
* for the side graph.
|
||||
*
|
||||
* By default this just calls the {@link #filter} on each non-Link to decide,
|
||||
* and then looks at each Link's connected Nodes to decide.
|
||||
*
|
||||
* A null filter assigns all Nodes that have connected Links to the main graph, and
|
||||
* all Links will be assigned to the main graph, and the side graph will only contain
|
||||
* Parts with no connected Links.
|
||||
* @param {Set} coll
|
||||
* @param {Set} maincoll
|
||||
* @param {Set} sidecoll
|
||||
*/
|
||||
splitParts(coll: go.Set<go.Part>, maincoll: go.Set<go.Part>, sidecoll: go.Set<go.Part>) {
|
||||
// first consider all Nodes
|
||||
const pred = this.filter;
|
||||
coll.each(function(p) {
|
||||
if (p instanceof go.Link) return;
|
||||
let main;
|
||||
if (pred) main = pred(p);
|
||||
else if (p instanceof go.Node) main = (p.linksConnected.count > 0);
|
||||
else main = (p instanceof go.Link);
|
||||
if (main) {
|
||||
maincoll.add(p);
|
||||
} else {
|
||||
sidecoll.add(p);
|
||||
}
|
||||
});
|
||||
// now assign Links based on which Nodes they connect with
|
||||
coll.each(function(p) {
|
||||
if (p instanceof go.Link) {
|
||||
if (!p.fromNode || !p.toNode) return;
|
||||
if (maincoll.contains(p.fromNode) && maincoll.contains(p.toNode)) {
|
||||
maincoll.add(p);
|
||||
} else if (sidecoll.contains(p.fromNode) && sidecoll.contains(p.toNode)) {
|
||||
sidecoll.add(p);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called just before the primaryLayout is performed so that
|
||||
* there can be adjustments made to the primaryLayout, if desired.
|
||||
* By default this method makes no adjustments to the primaryLayout.
|
||||
* @param {Layout} primaryLayout the sideLayout that may be modified for the results of the primaryLayout
|
||||
* @param {Set} mainColl the Nodes and Links to be laid out by primaryLayout after being separated into subnetworks
|
||||
*/
|
||||
preparePrimaryLayout(primaryLayout: go.Layout, mainColl: go.Set<go.Part>) {
|
||||
// by default this is a no-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a Set of Nodes and Links to the given area.
|
||||
* @param {Set} subColl the Set of Nodes and Links that form a separate connected subgraph
|
||||
* @param {Rect} subbounds the area occupied by the subColl
|
||||
* @param {Rect} bounds the area where they should be moved according to the arrangingLayout
|
||||
*/
|
||||
moveSubgraph(subColl: go.Set<go.Part>, subbounds: go.Rect, bounds: go.Rect) {
|
||||
const diagram = this.diagram;
|
||||
if (!diagram) return;
|
||||
diagram.moveParts(subColl, bounds.position.subtract(subbounds.position), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called just after the main layouts (the primaryLayouts and arrangingLayout)
|
||||
* have been performed and just before the sideLayout is performed so that there can be
|
||||
* adjustments made to the sideLayout, if desired.
|
||||
* By default this method makes no adjustments to the sideLayout.
|
||||
* @param {Layout} sideLayout the sideLayout that may be modified for the results of the main layouts
|
||||
* @param {Set} sideColl the Nodes and Links filtered out to be laid out by sideLayout
|
||||
* @param {Rect} mainBounds the area occupied by the nodes and links of the main layout, after it was performed
|
||||
*/
|
||||
prepareSideLayout(sideLayout: go.Layout, sideColl: go.Set<go.Part>, mainBounds: go.Rect) {
|
||||
// by default this is a no-op
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called just after the sideLayout has been performed in order to move
|
||||
* its parts to the desired area relative to the results of the main layouts.
|
||||
* By default this calls {@link Diagram#moveParts} on the sidecoll collection to the {@link #side} of the mainbounds.
|
||||
* This won't get called if there are no Parts in the sidecoll collection.
|
||||
* @param {Set} sidecoll a collection of Parts that were laid out by the sideLayout
|
||||
* @param {Rect} mainbounds the area occupied by the results of the main layouts
|
||||
* @param {Rect} sidebounds the area occupied by the results of the sideLayout
|
||||
*/
|
||||
moveSideCollection(sidecoll: go.Set<go.Part>, mainbounds: go.Rect, sidebounds: go.Rect) {
|
||||
const diagram = this.diagram;
|
||||
if (!diagram) return;
|
||||
if (this.side.includesSide(go.Spot.BottomSide)) {
|
||||
diagram.moveParts(sidecoll, new go.Point(mainbounds.x - sidebounds.x, mainbounds.y + mainbounds.height + this.spacing.height - sidebounds.y), false);
|
||||
} else if (this.side.includesSide(go.Spot.RightSide)) {
|
||||
diagram.moveParts(sidecoll, new go.Point(mainbounds.x + mainbounds.width + this.spacing.width - sidebounds.x, mainbounds.y - sidebounds.y), false);
|
||||
} else if (this.side.includesSide(go.Spot.TopSide)) {
|
||||
diagram.moveParts(sidecoll, new go.Point(mainbounds.x - sidebounds.x, mainbounds.y - sidebounds.height - this.spacing.height - sidebounds.y), false);
|
||||
} else if (this.side.includesSide(go.Spot.LeftSide)) {
|
||||
diagram.moveParts(sidecoll, new go.Point(mainbounds.x - sidebounds.width - this.spacing.width - sidebounds.x, mainbounds.y - sidebounds.y), false);
|
||||
}
|
||||
}
|
||||
|
||||
// Public properties
|
||||
|
||||
/**
|
||||
* Gets or sets the predicate function to call on each non-Link.
|
||||
* If the predicate returns true, the part will be laid out by the main layouts,
|
||||
* the primaryLayouts and the arrangingLayout, otherwise by the sideLayout.
|
||||
* The default value is a function that is true when there are any links connecting with the node.
|
||||
* Such default behavior will have the sideLayout position all of the singleton nodes.
|
||||
*/
|
||||
get filter(): ((part: go.Part) => boolean) | null { return this._filter; }
|
||||
set filter(val: ((part: go.Part) => boolean) | null) {
|
||||
if (val && typeof val !== 'function') throw new Error("new value for ArrangingLayout.filter must be a function, not: " + val);
|
||||
if (this._filter !== val) {
|
||||
this._filter = val;
|
||||
this.invalidateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the side {@link Spot} where the side nodes and links should be laid out,
|
||||
* relative to the results of the main Layout.
|
||||
* The default value is Spot.BottomSide.
|
||||
* Currently only handles a single side.
|
||||
* @name ArrangingLayout#side
|
||||
* @return {Spot}
|
||||
*/
|
||||
get side(): go.Spot { return this._side; }
|
||||
set side(val: go.Spot) {
|
||||
if (!(val instanceof go.Spot) || !val.isSide()) {
|
||||
throw new Error("new value for ArrangingLayout.side must be a side Spot, not: " + val);
|
||||
}
|
||||
if (!this._side.equals(val)) {
|
||||
this._side = val.copy();
|
||||
this.invalidateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the space between the main layout and the side layout.
|
||||
* The default value is Size(20, 20).
|
||||
* @name ArrangingLayout#spacing
|
||||
* @return {Size}
|
||||
*/
|
||||
get spacing(): go.Size { return this._spacing; }
|
||||
set spacing(val: go.Size) {
|
||||
if (!(val instanceof go.Size)) throw new Error("new value for ArrangingLayout.spacing must be a Size, not: " + val);
|
||||
if (!this._spacing.equals(val)) {
|
||||
this._spacing = val.copy();
|
||||
this.invalidateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the Layout used for the main part of the diagram.
|
||||
* The default value is an instance of GridLayout.
|
||||
* Any new value must not be null.
|
||||
*/
|
||||
get primaryLayout(): go.Layout { return this._primaryLayout; }
|
||||
set primaryLayout(val: go.Layout) {
|
||||
if (!(val instanceof go.Layout)) throw new Error("layout does not inherit from go.Layout: " + val);
|
||||
this._primaryLayout = val;
|
||||
this.invalidateLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the Layout used to arrange multiple separate connected subgraphs of the main graph.
|
||||
* The default value is an instance of GridLayout.
|
||||
* Set this property to null in order to get the default behavior of the @{link #primaryLayout}
|
||||
* when dealing with multiple connected graphs as a whole.
|
||||
*/
|
||||
get arrangingLayout(): go.Layout { return this._arrangingLayout; }
|
||||
set arrangingLayout(val: go.Layout) {
|
||||
if (val && !(val instanceof go.Layout)) throw new Error("layout does not inherit from go.Layout: " + val);
|
||||
this._arrangingLayout = val;
|
||||
this.invalidateLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the Layout used to arrange the "side" nodes and links -- those outside of the main layout.
|
||||
* The default value is an instance of GridLayout.
|
||||
* Any new value must not be null.
|
||||
*/
|
||||
get sideLayout(): go.Layout { return this._sideLayout; }
|
||||
set sideLayout(val: go.Layout) {
|
||||
if (!(val instanceof go.Layout)) throw new Error("layout does not inherit from go.Layout: " + val);
|
||||
this._sideLayout = val;
|
||||
this.invalidateLayout();
|
||||
}
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js", "./ArrangingLayout.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.init = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
var ArrangingLayout_js_1 = require("./ArrangingLayout.js");
|
||||
function init() {
|
||||
if (window.goSamples())
|
||||
window.goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make;
|
||||
var myDiagram = $(go.Diagram, "myDiagramDiv", // create a Diagram for the DIV HTML element
|
||||
{
|
||||
initialAutoScale: go.Diagram.Uniform,
|
||||
layout: $(ArrangingLayout_js_1.ArrangingLayout, {
|
||||
primaryLayout: $(go.CircularLayout),
|
||||
arrangingLayout: $(go.CircularLayout, { nodeDiameterFormula: go.CircularLayout.Circular, spacing: 30 }),
|
||||
// Uncommenting this filter will force all of the nodes and links to go into the main subset and thus
|
||||
// will cause all those nodes to be arranged by this.arrangingLayout, here a CircularLayout,
|
||||
// rather than by the this.sideLayout, which by default is a GridLayout.
|
||||
//filter: function(part: go.Part) { return true; },
|
||||
// additional custom properties for use by preparePrimaryLayout
|
||||
_colors: ["red", "orange", "yellow", "lime", "cyan"],
|
||||
_colorIndex: 0,
|
||||
// called for each separate connected subgraph
|
||||
preparePrimaryLayout: function (lay, coll) {
|
||||
var self = this;
|
||||
var root = null; // find the root node in this subgraph
|
||||
coll.each(function (node) {
|
||||
if (node instanceof go.Node && node.findLinksInto().count === 0)
|
||||
root = node;
|
||||
});
|
||||
var color = "white"; // determine the color for the nodes in this subgraph
|
||||
if (root !== null) {
|
||||
// root.key will be the name of the class that this node represents
|
||||
// Special case: "LayoutNetwork", "LayoutVertex", and "LayoutEdge" classes are "violet"
|
||||
if (root.key.indexOf("Layout") === 0 && root.key.length > "Layout".length) {
|
||||
color = "violet";
|
||||
}
|
||||
else { // otherwise cycle through the Array of colors
|
||||
var ca = self._colors;
|
||||
color = ca[self._colorIndex++ % ca.length];
|
||||
}
|
||||
}
|
||||
coll.each(function (node) {
|
||||
if (node instanceof go.Node) {
|
||||
var shape = node.findObject("SHAPE");
|
||||
if (shape !== null)
|
||||
shape.fill = color;
|
||||
}
|
||||
});
|
||||
},
|
||||
prepareSideLayout: function (lay, coll, b) {
|
||||
// adjust how wide the GridLayout lays out
|
||||
var self = this;
|
||||
if (self.diagram)
|
||||
lay.wrappingWidth = Math.max(b.width, self.diagram.viewportBounds.width);
|
||||
}
|
||||
})
|
||||
});
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, go.Panel.Auto, $(go.Shape, { name: "SHAPE", figure: "RoundedRectangle", fill: "lightgray" }, new go.Binding("fill", "color")), $(go.TextBlock, { margin: 2, textAlign: "center" }, new go.Binding("text", "key", function (s) {
|
||||
// insert newlines between lowercase followed by uppercase characters
|
||||
var arr = s.split("");
|
||||
for (var i = 1; i < arr.length - 1; i++) {
|
||||
var a = arr[i - 1];
|
||||
var b = arr[i];
|
||||
if (a === a.toLowerCase() && b === b.toUpperCase()) {
|
||||
arr.splice(i, 0, "\n");
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
return arr.join("");
|
||||
})));
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link, { layerName: "Background" }, $(go.Shape));
|
||||
// Collect all of the data for the model of the class hierarchy
|
||||
var nodeDataArray = [];
|
||||
// Iterate over all of the classes in "go"
|
||||
for (var k in go) {
|
||||
var cls = go[k];
|
||||
if (!cls)
|
||||
continue;
|
||||
var proto = cls.prototype;
|
||||
if (!proto)
|
||||
continue;
|
||||
proto.constructor.className = k; // remember name
|
||||
// find base class constructor
|
||||
var base = Object.getPrototypeOf(proto).constructor;
|
||||
if (base === Object) { // "root" node?
|
||||
nodeDataArray.push({ key: k });
|
||||
}
|
||||
else {
|
||||
// add a node for this class and a tree-parent reference to the base class name
|
||||
nodeDataArray.push({ key: k, parent: base.className });
|
||||
}
|
||||
}
|
||||
// Create the model for the hierarchy diagram
|
||||
myDiagram.model = new go.TreeModel(nodeDataArray);
|
||||
// Attach to the window for console manipulation
|
||||
window.myDiagram = myDiagram;
|
||||
}
|
||||
exports.init = init;
|
||||
});
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from "../release/go.js";
|
||||
import { ArrangingLayout } from "./ArrangingLayout.js";
|
||||
|
||||
export function init() {
|
||||
if ((window as any).goSamples()) (window as any).goSamples(); // init for these samples -- you don't need to call this
|
||||
|
||||
var $ = go.GraphObject.make;
|
||||
|
||||
const myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv", // create a Diagram for the DIV HTML element
|
||||
{
|
||||
initialAutoScale: go.Diagram.Uniform,
|
||||
layout:
|
||||
$(ArrangingLayout,
|
||||
{ // create a circular arrangement of circular layouts
|
||||
primaryLayout: $(go.CircularLayout), // must specify the primaryLayout
|
||||
arrangingLayout: $(go.CircularLayout, { nodeDiameterFormula: go.CircularLayout.Circular, spacing: 30 }),
|
||||
|
||||
// Uncommenting this filter will force all of the nodes and links to go into the main subset and thus
|
||||
// will cause all those nodes to be arranged by this.arrangingLayout, here a CircularLayout,
|
||||
// rather than by the this.sideLayout, which by default is a GridLayout.
|
||||
//filter: function(part: go.Part) { return true; },
|
||||
|
||||
// additional custom properties for use by preparePrimaryLayout
|
||||
_colors: ["red", "orange", "yellow", "lime", "cyan"], // possible node colors
|
||||
_colorIndex: 0, // cycle through the given colors
|
||||
|
||||
// called for each separate connected subgraph
|
||||
preparePrimaryLayout: function(lay: go.Layout, coll: go.Set<go.Part>) { // color all of the nodes in each subgraph
|
||||
const self = this as any;
|
||||
let root: go.Node | null = null; // find the root node in this subgraph
|
||||
coll.each(function(node) {
|
||||
if (node instanceof go.Node && node.findLinksInto().count === 0) root = node;
|
||||
});
|
||||
let color = "white"; // determine the color for the nodes in this subgraph
|
||||
if (root !== null) {
|
||||
// root.key will be the name of the class that this node represents
|
||||
// Special case: "LayoutNetwork", "LayoutVertex", and "LayoutEdge" classes are "violet"
|
||||
if (((root as go.Node).key as string).indexOf("Layout") === 0 && ((root as go.Node).key as string).length > "Layout".length) {
|
||||
color = "violet";
|
||||
} else { // otherwise cycle through the Array of colors
|
||||
const ca = self._colors as Array<string>;
|
||||
color = ca[(self._colorIndex as number)++ % ca.length];
|
||||
}
|
||||
}
|
||||
coll.each(function(node) { // assign the fill color for all of the nodes in the subgraph
|
||||
if (node instanceof go.Node) {
|
||||
var shape = node.findObject("SHAPE") as go.Shape;
|
||||
if (shape !== null) shape.fill = color;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
prepareSideLayout: function(lay: go.GridLayout, coll: go.Set<go.Part>, b: go.Rect) { // called once for the sideLayout
|
||||
// adjust how wide the GridLayout lays out
|
||||
const self = this as any;
|
||||
if (self.diagram) lay.wrappingWidth = Math.max(b.width, self.diagram.viewportBounds.width);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, go.Panel.Auto,
|
||||
$(go.Shape, { name: "SHAPE", figure: "RoundedRectangle", fill: "lightgray" },
|
||||
new go.Binding("fill", "color")),
|
||||
$(go.TextBlock, { margin: 2, textAlign: "center" },
|
||||
new go.Binding("text", "key", function(s) {
|
||||
// insert newlines between lowercase followed by uppercase characters
|
||||
var arr = s.split("");
|
||||
for (let i = 1; i < arr.length-1; i++) {
|
||||
var a = arr[i-1];
|
||||
var b = arr[i];
|
||||
if (a === a.toLowerCase() && b === b.toUpperCase()) {
|
||||
arr.splice(i, 0, "\n");
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
return arr.join("");
|
||||
})));
|
||||
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link,
|
||||
{ layerName: "Background" },
|
||||
$(go.Shape));
|
||||
|
||||
// Collect all of the data for the model of the class hierarchy
|
||||
var nodeDataArray = [];
|
||||
|
||||
// Iterate over all of the classes in "go"
|
||||
for (var k in go) {
|
||||
var cls = (go as any)[k];
|
||||
if (!cls) continue;
|
||||
var proto = cls.prototype;
|
||||
if (!proto) continue;
|
||||
proto.constructor.className = k; // remember name
|
||||
// find base class constructor
|
||||
var base = Object.getPrototypeOf(proto).constructor;
|
||||
if (base === Object) { // "root" node?
|
||||
nodeDataArray.push({ key: k });
|
||||
} else {
|
||||
// add a node for this class and a tree-parent reference to the base class name
|
||||
nodeDataArray.push({ key: k, parent: base.className });
|
||||
}
|
||||
}
|
||||
|
||||
// Create the model for the hierarchy diagram
|
||||
myDiagram.model = new go.TreeModel(nodeDataArray);
|
||||
|
||||
// Attach to the window for console manipulation
|
||||
(window as any).myDiagram = myDiagram;
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
// These are the definitions for all of the predefined arrowheads.
|
||||
// You do not need to load this file in order to use arrowheads.
|
||||
// Typical custom definition:
|
||||
// go.Shape.defineArrowheadGeometry("Zigzag", "M0,4 L1,8 3,0 5,8 7,0 8,4");
|
||||
// Typical usage in a link template:
|
||||
// myDiagram.linkTemplate =
|
||||
// $(go.Link,
|
||||
// $(go.Shape),
|
||||
// $(go.Shape, { toArrow: "Zigzag" })
|
||||
// );
|
||||
var go = require("../release/go.js");
|
||||
go.Shape.defineArrowheadGeometry('Standard', 'F1 m 0,0 l 8,4 -8,4 2,-4 z');
|
||||
go.Shape.defineArrowheadGeometry('Backward', 'F1 m 8,0 l -2,4 2,4 -8,-4 z');
|
||||
go.Shape.defineArrowheadGeometry('Triangle', 'F1 m 0,0 l 8,4.62 -8,4.62 z');
|
||||
go.Shape.defineArrowheadGeometry('BackwardTriangle', 'F1 m 8,4 l 0,4 -8,-4 8,-4 0,4 z');
|
||||
go.Shape.defineArrowheadGeometry('Boomerang', 'F1 m 0,0 l 8,4 -8,4 4,-4 -4,-4 z');
|
||||
go.Shape.defineArrowheadGeometry('BackwardBoomerang', 'F1 m 8,0 l -8,4 8,4 -4,-4 4,-4 z');
|
||||
go.Shape.defineArrowheadGeometry('SidewaysV', 'm 0,0 l 8,4 -8,4 0,-1 6,-3 -6,-3 0,-1 z');
|
||||
go.Shape.defineArrowheadGeometry('BackwardV', 'm 8,0 l -8,4 8,4 0,-1 -6,-3 6,-3 0,-1 z');
|
||||
go.Shape.defineArrowheadGeometry('OpenTriangle', 'm 0,0 l 8,4 -8,4');
|
||||
go.Shape.defineArrowheadGeometry('BackwardOpenTriangle', 'm 8,0 l -8,4 8,4');
|
||||
go.Shape.defineArrowheadGeometry('OpenTriangleLine', 'm 0,0 l 8,4 -8,4 m 8.5,0 l 0,-8');
|
||||
go.Shape.defineArrowheadGeometry('BackwardOpenTriangleLine', 'm 8,0 l -8,4 8,4 m -8.5,0 l 0,-8');
|
||||
go.Shape.defineArrowheadGeometry('OpenTriangleTop', 'm 0,0 l 8,4 m 0,4');
|
||||
go.Shape.defineArrowheadGeometry('BackwardOpenTriangleTop', 'm 8,0 l -8,4 m 0,4');
|
||||
go.Shape.defineArrowheadGeometry('OpenTriangleBottom', 'm 0,8 l 8,-4');
|
||||
go.Shape.defineArrowheadGeometry('BackwardOpenTriangleBottom', 'm 0,4 l 8,4');
|
||||
go.Shape.defineArrowheadGeometry('HalfTriangleTop', 'F1 m 0,0 l 0,4 8,0 z m 0,8');
|
||||
go.Shape.defineArrowheadGeometry('BackwardHalfTriangleTop', 'F1 m 8,0 l 0,4 -8,0 z m 0,8');
|
||||
go.Shape.defineArrowheadGeometry('HalfTriangleBottom', 'F1 m 0,4 l 0,4 8,-4 z');
|
||||
go.Shape.defineArrowheadGeometry('BackwardHalfTriangleBottom', 'F1 m 8,4 l 0,4 -8,-4 z');
|
||||
go.Shape.defineArrowheadGeometry('ForwardSemiCircle', 'm 4,0 b 270 180 0 4 4');
|
||||
go.Shape.defineArrowheadGeometry('BackwardSemiCircle', 'm 4,8 b 90 180 0 -4 4');
|
||||
go.Shape.defineArrowheadGeometry('Feather', 'm 0,0 l 3,4 -3,4');
|
||||
go.Shape.defineArrowheadGeometry('BackwardFeather', 'm 3,0 l -3,4 3,4');
|
||||
go.Shape.defineArrowheadGeometry('DoubleFeathers', 'm 0,0 l 3,4 -3,4 m 3,-8 l 3,4 -3,4');
|
||||
go.Shape.defineArrowheadGeometry('BackwardDoubleFeathers', 'm 3,0 l -3,4 3,4 m 3,-8 l -3,4 3,4');
|
||||
go.Shape.defineArrowheadGeometry('TripleFeathers', 'm 0,0 l 3,4 -3,4 m 3,-8 l 3,4 -3,4 m 3,-8 l 3,4 -3,4');
|
||||
go.Shape.defineArrowheadGeometry('BackwardTripleFeathers', 'm 3,0 l -3,4 3,4 m 3,-8 l -3,4 3,4 m 3,-8 l -3,4 3,4');
|
||||
go.Shape.defineArrowheadGeometry('ForwardSlash', 'm 0,8 l 5,-8');
|
||||
go.Shape.defineArrowheadGeometry('BackSlash', 'm 0,0 l 5,8');
|
||||
go.Shape.defineArrowheadGeometry('DoubleForwardSlash', 'm 0,8 l 4,-8 m -2,8 l 4,-8');
|
||||
go.Shape.defineArrowheadGeometry('DoubleBackSlash', 'm 0,0 l 4,8 m -2,-8 l 4,8');
|
||||
go.Shape.defineArrowheadGeometry('TripleForwardSlash', 'm 0,8 l 4,-8 m -2,8 l 4,-8 m -2,8 l 4,-8');
|
||||
go.Shape.defineArrowheadGeometry('TripleBackSlash', 'm 0,0 l 4,8 m -2,-8 l 4,8 m -2,-8 l 4,8');
|
||||
go.Shape.defineArrowheadGeometry('Fork', 'm 0,4 l 8,0 m -8,0 l 8,-4 m -8,4 l 8,4');
|
||||
go.Shape.defineArrowheadGeometry('BackwardFork', 'm 8,4 l -8,0 m 8,0 l -8,-4 m 8,4 l -8,4');
|
||||
go.Shape.defineArrowheadGeometry('LineFork', 'm 0,0 l 0,8 m 0,-4 l 8,0 m -8,0 l 8,-4 m -8,4 l 8,4');
|
||||
go.Shape.defineArrowheadGeometry('BackwardLineFork', 'm 8,4 l -8,0 m 8,0 l -8,-4 m 8,4 l -8,4 m 8,-8 l 0,8');
|
||||
go.Shape.defineArrowheadGeometry('CircleFork', 'F1 m 6,4 b 0 360 -3 0 3 z m 0,0 l 6,0 m -6,0 l 6,-4 m -6,4 l 6,4');
|
||||
go.Shape.defineArrowheadGeometry('BackwardCircleFork', 'F1 m 0,4 l 6,0 m -6,-4 l 6,4 m -6,4 l 6,-4 m 6,0 b 0 360 -3 0 3');
|
||||
go.Shape.defineArrowheadGeometry('CircleLineFork', 'F1 m 6,4 b 0 360 -3 0 3 z m 1,-4 l 0,8 m 0,-4 l 6,0 m -6,0 l 6,-4 m -6,4 l 6,4');
|
||||
go.Shape.defineArrowheadGeometry('BackwardCircleLineFork', 'F1 m 0,4 l 6,0 m -6,-4 l 6,4 m -6,4 l 6,-4 m 0,-4 l 0,8 m 7,-4 b 0 360 -3 0 3');
|
||||
go.Shape.defineArrowheadGeometry('Circle', 'F1 m 8,4 b 0 360 -4 0 4 z');
|
||||
go.Shape.defineArrowheadGeometry('Block', 'F1 m 0,0 l 0,8 8,0 0,-8 z');
|
||||
go.Shape.defineArrowheadGeometry('StretchedDiamond', 'F1 m 0,3 l 5,-3 5,3 -5,3 -5,-3 z');
|
||||
go.Shape.defineArrowheadGeometry('Diamond', 'F1 m 0,4 l 4,-4 4,4 -4,4 -4,-4 z');
|
||||
go.Shape.defineArrowheadGeometry('Chevron', 'F1 m 0,0 l 5,0 3,4 -3,4 -5,0 3,-4 -3,-4 z');
|
||||
go.Shape.defineArrowheadGeometry('StretchedChevron', 'F1 m 0,0 l 8,0 3,4 -3,4 -8,0 3,-4 -3,-4 z');
|
||||
go.Shape.defineArrowheadGeometry('NormalArrow', 'F1 m 0,2 l 4,0 0,-2 4,4 -4,4 0,-2 -4,0 z');
|
||||
go.Shape.defineArrowheadGeometry('X', 'm 0,0 l 8,8 m 0,-8 l -8,8');
|
||||
go.Shape.defineArrowheadGeometry('TailedNormalArrow', 'F1 m 0,0 l 2,0 1,2 3,0 0,-2 2,4 -2,4 0,-2 -3,0 -1,2 -2,0 1,-4 -1,-4 z');
|
||||
go.Shape.defineArrowheadGeometry('DoubleTriangle', 'F1 m 0,0 l 4,4 -4,4 0,-8 z m 4,0 l 4,4 -4,4 0,-8 z');
|
||||
go.Shape.defineArrowheadGeometry('BigEndArrow', 'F1 m 0,0 l 5,2 0,-2 3,4 -3,4 0,-2 -5,2 0,-8 z');
|
||||
go.Shape.defineArrowheadGeometry('ConcaveTailArrow', 'F1 m 0,2 h 4 v -2 l 4,4 -4,4 v -2 h -4 l 2,-2 -2,-2 z');
|
||||
go.Shape.defineArrowheadGeometry('RoundedTriangle', 'F1 m 0,1 a 1,1 0 0 1 1,-1 l 7,3 a 0.5,1 0 0 1 0,2 l -7,3 a 1,1 0 0 1 -1,-1 l 0,-6 z');
|
||||
go.Shape.defineArrowheadGeometry('SimpleArrow', 'F1 m 1,2 l -1,-2 2,0 1,2 -1,2 -2,0 1,-2 5,0 0,-2 2,2 -2,2 0,-2 z');
|
||||
go.Shape.defineArrowheadGeometry('AccelerationArrow', 'F1 m 0,0 l 0,8 0.2,0 0,-8 -0.2,0 z m 2,0 l 0,8 1,0 0,-8 -1,0 z m 3,0 l 2,0 2,4 -2,4 -2,0 0,-8 z');
|
||||
go.Shape.defineArrowheadGeometry('BoxArrow', 'F1 m 0,0 l 4,0 0,2 2,0 0,-2 2,4 -2,4 0,-2 -2,0 0,2 -4,0 0,-8 z');
|
||||
go.Shape.defineArrowheadGeometry('TriangleLine', 'F1 m 8,4 l -8,-4 0,8 8,-4 z m 0.5,4 l 0,-8');
|
||||
go.Shape.defineArrowheadGeometry('CircleEndedArrow', 'F1 m 10,4 l -2,-3 0,2 -2,0 0,2 2,0 0,2 2,-3 z m -4,0 b 0 360 -3 0 3 z');
|
||||
go.Shape.defineArrowheadGeometry('DynamicWidthArrow', 'F1 m 0,3 l 2,0 2,-1 2,-2 2,4 -2,4 -2,-2 -2,-1 -2,0 0,-2 z');
|
||||
go.Shape.defineArrowheadGeometry('EquilibriumArrow', 'm 0,3 l 8,0 -3,-3 m 3,5 l -8,0 3,3');
|
||||
go.Shape.defineArrowheadGeometry('FastForward', 'F1 m 0,0 l 3.5,4 0,-4 3.5,4 0,-4 1,0 0,8 -1,0 0,-4 -3.5,4 0,-4 -3.5,4 0,-8 z');
|
||||
go.Shape.defineArrowheadGeometry('Kite', 'F1 m 0,4 l 2,-4 6,4 -6,4 -2,-4 z');
|
||||
go.Shape.defineArrowheadGeometry('HalfArrowTop', 'F1 m 0,0 l 4,4 4,0 -8,-4 z m 0,8');
|
||||
go.Shape.defineArrowheadGeometry('HalfArrowBottom', 'F1 m 0,8 l 4,-4 4,0 -8,4 z');
|
||||
go.Shape.defineArrowheadGeometry('OpposingDirectionDoubleArrow', 'F1 m 0,4 l 2,-4 0,2 4,0 0,-2 2,4 -2,4 0,-2 -4,0 0,2 -2,-4 z');
|
||||
go.Shape.defineArrowheadGeometry('PartialDoubleTriangle', 'F1 m 0,0 4,3 0,-3 4,4 -4,4 0,-3 -4,3 0,-8 z');
|
||||
go.Shape.defineArrowheadGeometry('LineCircle', 'F1 m 0,0 l 0,8 m 7 -4 b 0 360 -3 0 3 z');
|
||||
go.Shape.defineArrowheadGeometry('DoubleLineCircle', 'F1 m 0,0 l 0,8 m 2,-8 l 0,8 m 7 -4 b 0 360 -3 0 3 z');
|
||||
go.Shape.defineArrowheadGeometry('TripleLineCircle', 'F1 m 0,0 l 0,8 m 2,-8 l 0,8 m 2,-8 l 0,8 m 7 -4 b 0 360 -3 0 3 z');
|
||||
go.Shape.defineArrowheadGeometry('CircleLine', 'F1 m 6 4 b 0 360 -3 0 3 z m 1,-4 l 0,8');
|
||||
go.Shape.defineArrowheadGeometry('DiamondCircle', 'F1 m 8,4 l -4,4 -4,-4 4,-4 4,4 m 8,0 b 0 360 -4 0 4 z');
|
||||
go.Shape.defineArrowheadGeometry('PlusCircle', 'F1 m 8,4 b 0 360 -4 0 4 l -8 0 z m -4 -4 l 0 8');
|
||||
go.Shape.defineArrowheadGeometry('OpenRightTriangleTop', 'm 8,0 l 0,4 -8,0 m 0,4');
|
||||
go.Shape.defineArrowheadGeometry('OpenRightTriangleBottom', 'm 8,8 l 0,-4 -8,0');
|
||||
go.Shape.defineArrowheadGeometry('Line', 'm 0,0 l 0,8');
|
||||
go.Shape.defineArrowheadGeometry('DoubleLine', 'm 0,0 l 0,8 m 2,0 l 0,-8');
|
||||
go.Shape.defineArrowheadGeometry('TripleLine', 'm 0,0 l 0,8 m 2,0 l 0,-8 m 2,0 l 0,8');
|
||||
go.Shape.defineArrowheadGeometry('PentagonArrow', 'F1 m 8,4 l -4,-4 -4,0 0,8 4,0 4,-4 z');
|
||||
});
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
// These are the definitions for all of the predefined arrowheads.
|
||||
// You do not need to load this file in order to use arrowheads.
|
||||
|
||||
// Typical custom definition:
|
||||
// go.Shape.defineArrowheadGeometry("Zigzag", "M0,4 L1,8 3,0 5,8 7,0 8,4");
|
||||
|
||||
// Typical usage in a link template:
|
||||
// myDiagram.linkTemplate =
|
||||
// $(go.Link,
|
||||
// $(go.Shape),
|
||||
// $(go.Shape, { toArrow: "Zigzag" })
|
||||
// );
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
|
||||
go.Shape.defineArrowheadGeometry('Standard', 'F1 m 0,0 l 8,4 -8,4 2,-4 z');
|
||||
go.Shape.defineArrowheadGeometry('Backward', 'F1 m 8,0 l -2,4 2,4 -8,-4 z');
|
||||
go.Shape.defineArrowheadGeometry('Triangle', 'F1 m 0,0 l 8,4.62 -8,4.62 z');
|
||||
go.Shape.defineArrowheadGeometry('BackwardTriangle', 'F1 m 8,4 l 0,4 -8,-4 8,-4 0,4 z');
|
||||
go.Shape.defineArrowheadGeometry('Boomerang', 'F1 m 0,0 l 8,4 -8,4 4,-4 -4,-4 z');
|
||||
go.Shape.defineArrowheadGeometry('BackwardBoomerang', 'F1 m 8,0 l -8,4 8,4 -4,-4 4,-4 z');
|
||||
go.Shape.defineArrowheadGeometry('SidewaysV', 'm 0,0 l 8,4 -8,4 0,-1 6,-3 -6,-3 0,-1 z');
|
||||
go.Shape.defineArrowheadGeometry('BackwardV', 'm 8,0 l -8,4 8,4 0,-1 -6,-3 6,-3 0,-1 z');
|
||||
|
||||
go.Shape.defineArrowheadGeometry('OpenTriangle', 'm 0,0 l 8,4 -8,4');
|
||||
go.Shape.defineArrowheadGeometry('BackwardOpenTriangle', 'm 8,0 l -8,4 8,4');
|
||||
go.Shape.defineArrowheadGeometry('OpenTriangleLine', 'm 0,0 l 8,4 -8,4 m 8.5,0 l 0,-8');
|
||||
go.Shape.defineArrowheadGeometry('BackwardOpenTriangleLine', 'm 8,0 l -8,4 8,4 m -8.5,0 l 0,-8');
|
||||
|
||||
go.Shape.defineArrowheadGeometry('OpenTriangleTop', 'm 0,0 l 8,4 m 0,4');
|
||||
go.Shape.defineArrowheadGeometry('BackwardOpenTriangleTop', 'm 8,0 l -8,4 m 0,4');
|
||||
go.Shape.defineArrowheadGeometry('OpenTriangleBottom', 'm 0,8 l 8,-4');
|
||||
go.Shape.defineArrowheadGeometry('BackwardOpenTriangleBottom', 'm 0,4 l 8,4');
|
||||
|
||||
go.Shape.defineArrowheadGeometry('HalfTriangleTop', 'F1 m 0,0 l 0,4 8,0 z m 0,8');
|
||||
go.Shape.defineArrowheadGeometry('BackwardHalfTriangleTop', 'F1 m 8,0 l 0,4 -8,0 z m 0,8');
|
||||
go.Shape.defineArrowheadGeometry('HalfTriangleBottom', 'F1 m 0,4 l 0,4 8,-4 z');
|
||||
go.Shape.defineArrowheadGeometry('BackwardHalfTriangleBottom', 'F1 m 8,4 l 0,4 -8,-4 z');
|
||||
|
||||
go.Shape.defineArrowheadGeometry('ForwardSemiCircle', 'm 4,0 b 270 180 0 4 4');
|
||||
go.Shape.defineArrowheadGeometry('BackwardSemiCircle', 'm 4,8 b 90 180 0 -4 4');
|
||||
|
||||
go.Shape.defineArrowheadGeometry('Feather', 'm 0,0 l 3,4 -3,4');
|
||||
go.Shape.defineArrowheadGeometry('BackwardFeather', 'm 3,0 l -3,4 3,4');
|
||||
go.Shape.defineArrowheadGeometry('DoubleFeathers', 'm 0,0 l 3,4 -3,4 m 3,-8 l 3,4 -3,4');
|
||||
go.Shape.defineArrowheadGeometry('BackwardDoubleFeathers', 'm 3,0 l -3,4 3,4 m 3,-8 l -3,4 3,4');
|
||||
go.Shape.defineArrowheadGeometry('TripleFeathers', 'm 0,0 l 3,4 -3,4 m 3,-8 l 3,4 -3,4 m 3,-8 l 3,4 -3,4');
|
||||
go.Shape.defineArrowheadGeometry('BackwardTripleFeathers', 'm 3,0 l -3,4 3,4 m 3,-8 l -3,4 3,4 m 3,-8 l -3,4 3,4');
|
||||
|
||||
go.Shape.defineArrowheadGeometry('ForwardSlash', 'm 0,8 l 5,-8');
|
||||
go.Shape.defineArrowheadGeometry('BackSlash', 'm 0,0 l 5,8');
|
||||
go.Shape.defineArrowheadGeometry('DoubleForwardSlash', 'm 0,8 l 4,-8 m -2,8 l 4,-8');
|
||||
go.Shape.defineArrowheadGeometry('DoubleBackSlash', 'm 0,0 l 4,8 m -2,-8 l 4,8');
|
||||
go.Shape.defineArrowheadGeometry('TripleForwardSlash', 'm 0,8 l 4,-8 m -2,8 l 4,-8 m -2,8 l 4,-8');
|
||||
go.Shape.defineArrowheadGeometry('TripleBackSlash', 'm 0,0 l 4,8 m -2,-8 l 4,8 m -2,-8 l 4,8');
|
||||
|
||||
go.Shape.defineArrowheadGeometry('Fork', 'm 0,4 l 8,0 m -8,0 l 8,-4 m -8,4 l 8,4');
|
||||
go.Shape.defineArrowheadGeometry('BackwardFork', 'm 8,4 l -8,0 m 8,0 l -8,-4 m 8,4 l -8,4');
|
||||
go.Shape.defineArrowheadGeometry('LineFork', 'm 0,0 l 0,8 m 0,-4 l 8,0 m -8,0 l 8,-4 m -8,4 l 8,4');
|
||||
go.Shape.defineArrowheadGeometry('BackwardLineFork', 'm 8,4 l -8,0 m 8,0 l -8,-4 m 8,4 l -8,4 m 8,-8 l 0,8');
|
||||
go.Shape.defineArrowheadGeometry('CircleFork', 'F1 m 6,4 b 0 360 -3 0 3 z m 0,0 l 6,0 m -6,0 l 6,-4 m -6,4 l 6,4');
|
||||
go.Shape.defineArrowheadGeometry('BackwardCircleFork', 'F1 m 0,4 l 6,0 m -6,-4 l 6,4 m -6,4 l 6,-4 m 6,0 b 0 360 -3 0 3');
|
||||
go.Shape.defineArrowheadGeometry('CircleLineFork', 'F1 m 6,4 b 0 360 -3 0 3 z m 1,-4 l 0,8 m 0,-4 l 6,0 m -6,0 l 6,-4 m -6,4 l 6,4');
|
||||
go.Shape.defineArrowheadGeometry('BackwardCircleLineFork', 'F1 m 0,4 l 6,0 m -6,-4 l 6,4 m -6,4 l 6,-4 m 0,-4 l 0,8 m 7,-4 b 0 360 -3 0 3');
|
||||
|
||||
go.Shape.defineArrowheadGeometry('Circle', 'F1 m 8,4 b 0 360 -4 0 4 z');
|
||||
go.Shape.defineArrowheadGeometry('Block', 'F1 m 0,0 l 0,8 8,0 0,-8 z');
|
||||
go.Shape.defineArrowheadGeometry('StretchedDiamond', 'F1 m 0,3 l 5,-3 5,3 -5,3 -5,-3 z');
|
||||
go.Shape.defineArrowheadGeometry('Diamond', 'F1 m 0,4 l 4,-4 4,4 -4,4 -4,-4 z');
|
||||
go.Shape.defineArrowheadGeometry('Chevron', 'F1 m 0,0 l 5,0 3,4 -3,4 -5,0 3,-4 -3,-4 z');
|
||||
go.Shape.defineArrowheadGeometry('StretchedChevron', 'F1 m 0,0 l 8,0 3,4 -3,4 -8,0 3,-4 -3,-4 z');
|
||||
|
||||
go.Shape.defineArrowheadGeometry('NormalArrow', 'F1 m 0,2 l 4,0 0,-2 4,4 -4,4 0,-2 -4,0 z');
|
||||
go.Shape.defineArrowheadGeometry('X', 'm 0,0 l 8,8 m 0,-8 l -8,8');
|
||||
go.Shape.defineArrowheadGeometry('TailedNormalArrow', 'F1 m 0,0 l 2,0 1,2 3,0 0,-2 2,4 -2,4 0,-2 -3,0 -1,2 -2,0 1,-4 -1,-4 z');
|
||||
go.Shape.defineArrowheadGeometry('DoubleTriangle', 'F1 m 0,0 l 4,4 -4,4 0,-8 z m 4,0 l 4,4 -4,4 0,-8 z');
|
||||
go.Shape.defineArrowheadGeometry('BigEndArrow', 'F1 m 0,0 l 5,2 0,-2 3,4 -3,4 0,-2 -5,2 0,-8 z');
|
||||
go.Shape.defineArrowheadGeometry('ConcaveTailArrow', 'F1 m 0,2 h 4 v -2 l 4,4 -4,4 v -2 h -4 l 2,-2 -2,-2 z');
|
||||
go.Shape.defineArrowheadGeometry('RoundedTriangle', 'F1 m 0,1 a 1,1 0 0 1 1,-1 l 7,3 a 0.5,1 0 0 1 0,2 l -7,3 a 1,1 0 0 1 -1,-1 l 0,-6 z');
|
||||
go.Shape.defineArrowheadGeometry('SimpleArrow', 'F1 m 1,2 l -1,-2 2,0 1,2 -1,2 -2,0 1,-2 5,0 0,-2 2,2 -2,2 0,-2 z');
|
||||
go.Shape.defineArrowheadGeometry('AccelerationArrow', 'F1 m 0,0 l 0,8 0.2,0 0,-8 -0.2,0 z m 2,0 l 0,8 1,0 0,-8 -1,0 z m 3,0 l 2,0 2,4 -2,4 -2,0 0,-8 z');
|
||||
go.Shape.defineArrowheadGeometry('BoxArrow', 'F1 m 0,0 l 4,0 0,2 2,0 0,-2 2,4 -2,4 0,-2 -2,0 0,2 -4,0 0,-8 z');
|
||||
go.Shape.defineArrowheadGeometry('TriangleLine', 'F1 m 8,4 l -8,-4 0,8 8,-4 z m 0.5,4 l 0,-8');
|
||||
|
||||
go.Shape.defineArrowheadGeometry('CircleEndedArrow', 'F1 m 10,4 l -2,-3 0,2 -2,0 0,2 2,0 0,2 2,-3 z m -4,0 b 0 360 -3 0 3 z');
|
||||
|
||||
go.Shape.defineArrowheadGeometry('DynamicWidthArrow', 'F1 m 0,3 l 2,0 2,-1 2,-2 2,4 -2,4 -2,-2 -2,-1 -2,0 0,-2 z');
|
||||
go.Shape.defineArrowheadGeometry('EquilibriumArrow', 'm 0,3 l 8,0 -3,-3 m 3,5 l -8,0 3,3');
|
||||
go.Shape.defineArrowheadGeometry('FastForward', 'F1 m 0,0 l 3.5,4 0,-4 3.5,4 0,-4 1,0 0,8 -1,0 0,-4 -3.5,4 0,-4 -3.5,4 0,-8 z');
|
||||
go.Shape.defineArrowheadGeometry('Kite', 'F1 m 0,4 l 2,-4 6,4 -6,4 -2,-4 z');
|
||||
go.Shape.defineArrowheadGeometry('HalfArrowTop', 'F1 m 0,0 l 4,4 4,0 -8,-4 z m 0,8');
|
||||
go.Shape.defineArrowheadGeometry('HalfArrowBottom', 'F1 m 0,8 l 4,-4 4,0 -8,4 z');
|
||||
go.Shape.defineArrowheadGeometry('OpposingDirectionDoubleArrow', 'F1 m 0,4 l 2,-4 0,2 4,0 0,-2 2,4 -2,4 0,-2 -4,0 0,2 -2,-4 z');
|
||||
go.Shape.defineArrowheadGeometry('PartialDoubleTriangle', 'F1 m 0,0 4,3 0,-3 4,4 -4,4 0,-3 -4,3 0,-8 z');
|
||||
go.Shape.defineArrowheadGeometry('LineCircle', 'F1 m 0,0 l 0,8 m 7 -4 b 0 360 -3 0 3 z');
|
||||
go.Shape.defineArrowheadGeometry('DoubleLineCircle', 'F1 m 0,0 l 0,8 m 2,-8 l 0,8 m 7 -4 b 0 360 -3 0 3 z');
|
||||
go.Shape.defineArrowheadGeometry('TripleLineCircle', 'F1 m 0,0 l 0,8 m 2,-8 l 0,8 m 2,-8 l 0,8 m 7 -4 b 0 360 -3 0 3 z');
|
||||
go.Shape.defineArrowheadGeometry('CircleLine', 'F1 m 6 4 b 0 360 -3 0 3 z m 1,-4 l 0,8');
|
||||
go.Shape.defineArrowheadGeometry('DiamondCircle', 'F1 m 8,4 l -4,4 -4,-4 4,-4 4,4 m 8,0 b 0 360 -4 0 4 z');
|
||||
go.Shape.defineArrowheadGeometry('PlusCircle', 'F1 m 8,4 b 0 360 -4 0 4 l -8 0 z m -4 -4 l 0 8');
|
||||
go.Shape.defineArrowheadGeometry('OpenRightTriangleTop', 'm 8,0 l 0,4 -8,0 m 0,4');
|
||||
go.Shape.defineArrowheadGeometry('OpenRightTriangleBottom', 'm 8,8 l 0,-4 -8,0');
|
||||
go.Shape.defineArrowheadGeometry('Line', 'm 0,0 l 0,8');
|
||||
go.Shape.defineArrowheadGeometry('DoubleLine', 'm 0,0 l 0,8 m 2,0 l 0,-8');
|
||||
go.Shape.defineArrowheadGeometry('TripleLine', 'm 0,0 l 0,8 m 2,0 l 0,-8 m 2,0 l 0,8');
|
||||
go.Shape.defineArrowheadGeometry('PentagonArrow', 'F1 m 8,4 l -4,-4 -4,0 0,8 4,0 4,-4 z');
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Balloon Link</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="TypeScript: A demonstration of the BalloonLink extension for implementing word balloons or speech bubbles as comments in diagrams about particular objects." />
|
||||
|
||||
<script src="../samples/assets/require.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
function init() {
|
||||
require(["BalloonLinkScript"], function(app) {
|
||||
app.init();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:300px; height:300px"></div>
|
||||
<p>
|
||||
A <b>BalloonLink</b> is a custom <a>Link</a> that draws a "balloon" shape around the Link.fromNode. It will create
|
||||
a triangular shape with the base at the fromNode and the other point at the toNode. It is defined in its own file,
|
||||
as <a href="BalloonLink.ts">BalloonLink.ts</a>.
|
||||
</p>
|
||||
<p>
|
||||
Usage can also be seen in the <a href="../samples/comments.html">Comments</a> sample.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.BalloonLink = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
/**
|
||||
* This custom {@link Link} class customizes its {@link Shape} to surround the comment node (the from node).
|
||||
* If the Shape is filled, it will obscure the comment itself unless the Link is behind the comment node.
|
||||
* Thus the default layer for BalloonLinks is "Background".
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/BalloonLink.html">Balloon Links</a> sample.
|
||||
* @category Part Extension
|
||||
*/
|
||||
var BalloonLink = /** @class */ (function (_super) {
|
||||
__extends(BalloonLink, _super);
|
||||
/**
|
||||
* Constructs a BalloonLink and sets the {@link Part#layerName} property to "Background".
|
||||
*/
|
||||
function BalloonLink() {
|
||||
var _this = _super.call(this) || this;
|
||||
_this._base = 10;
|
||||
_this.layerName = 'Background';
|
||||
return _this;
|
||||
}
|
||||
/**
|
||||
* Copies properties to a cloned BalloonLink.
|
||||
*/
|
||||
BalloonLink.prototype.cloneProtected = function (copy) {
|
||||
_super.prototype.cloneProtected.call(this, copy);
|
||||
copy._base = this._base;
|
||||
};
|
||||
Object.defineProperty(BalloonLink.prototype, "base", {
|
||||
/**
|
||||
* Gets or sets width of the base of the triangle at the center point of the {@link Link#fromNode}.
|
||||
*
|
||||
* The default value is 10.
|
||||
*/
|
||||
get: function () { return this._base; },
|
||||
set: function (value) { this._base = value; },
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
/**
|
||||
* Produce a Geometry from the Link's route that draws a "balloon" shape around the {@link Link#fromNode}
|
||||
* and has a triangular shape with the base at the fromNode and the top at the toNode.
|
||||
*/
|
||||
BalloonLink.prototype.makeGeometry = function () {
|
||||
var fromnode = this.fromNode;
|
||||
var tonode = this.toNode;
|
||||
if (fromnode === null || tonode === null)
|
||||
return _super.prototype.makeGeometry.call(this);
|
||||
// assume the fromNode is the comment and the toNode is the commented-upon node
|
||||
var bb = fromnode.actualBounds;
|
||||
var nb = tonode.actualBounds;
|
||||
var p0 = bb.center;
|
||||
var pn = this.getPoint(this.pointsCount - 1);
|
||||
if (bb.intersectsRect(nb)) {
|
||||
pn = nb.center;
|
||||
}
|
||||
var pos = this.routeBounds;
|
||||
// compute the intersection points for the triangular arrow
|
||||
var ang = pn.directionPoint(p0);
|
||||
var L = new go.Point(this.base, 0).rotate(ang - 90).add(p0);
|
||||
var R = new go.Point(this.base, 0).rotate(ang + 90).add(p0);
|
||||
this.getLinkPointFromPoint(fromnode, fromnode, L, pn, true, L);
|
||||
this.getLinkPointFromPoint(fromnode, fromnode, R, pn, true, R);
|
||||
// form a triangular arrow from the comment to the commented node
|
||||
var fig = new go.PathFigure(pn.x - pos.x, pn.y - pos.y, true); // filled; start at arrow point at commented node
|
||||
fig.add(new go.PathSegment(go.PathSegment.Line, R.x - pos.x, R.y - pos.y)); // a triangle base point on comment's edge
|
||||
var side = 0;
|
||||
if (L.y >= bb.bottom || R.y >= bb.bottom)
|
||||
side = 2;
|
||||
else if (L.x <= bb.x && R.x <= bb.x)
|
||||
side = 1;
|
||||
else if (L.x >= bb.right && R.x >= bb.right)
|
||||
side = 3;
|
||||
this.pathToCorner(side, bb, fig, pos, L, R);
|
||||
this.pathToCorner(side + 1, bb, fig, pos, L, R);
|
||||
this.pathToCorner(side + 2, bb, fig, pos, L, R);
|
||||
this.pathToCorner(side + 3, bb, fig, pos, L, R);
|
||||
fig.add(new go.PathSegment(go.PathSegment.Line, L.x - pos.x, L.y - pos.y).close()); // the other triangle base point on comment's edge
|
||||
// return a Geometry
|
||||
return new go.Geometry().add(fig);
|
||||
};
|
||||
/**
|
||||
* Draw a line to a corner, but not if the comment arrow encompasses that corner.
|
||||
*/
|
||||
BalloonLink.prototype.pathToCorner = function (side, bb, fig, pos, L, R) {
|
||||
switch (side % 4) {
|
||||
case 0:
|
||||
if (!(L.y <= bb.y && R.x <= bb.x))
|
||||
fig.add(new go.PathSegment(go.PathSegment.Line, bb.x - pos.x, bb.y - pos.y));
|
||||
break;
|
||||
case 1:
|
||||
if (!(L.x <= bb.x && R.y >= bb.bottom))
|
||||
fig.add(new go.PathSegment(go.PathSegment.Line, bb.x - pos.x, bb.bottom - pos.y));
|
||||
break;
|
||||
case 2:
|
||||
if (!(L.y >= bb.bottom && R.x >= bb.right))
|
||||
fig.add(new go.PathSegment(go.PathSegment.Line, bb.right - pos.x, bb.bottom - pos.y));
|
||||
break;
|
||||
case 3:
|
||||
if (!(L.x >= bb.right && R.y <= bb.y))
|
||||
fig.add(new go.PathSegment(go.PathSegment.Line, bb.right - pos.x, bb.y - pos.y));
|
||||
break;
|
||||
}
|
||||
};
|
||||
return BalloonLink;
|
||||
}(go.Link));
|
||||
exports.BalloonLink = BalloonLink;
|
||||
});
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
|
||||
/**
|
||||
* This custom {@link Link} class customizes its {@link Shape} to surround the comment node (the from node).
|
||||
* If the Shape is filled, it will obscure the comment itself unless the Link is behind the comment node.
|
||||
* Thus the default layer for BalloonLinks is "Background".
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/BalloonLink.html">Balloon Links</a> sample.
|
||||
* @category Part Extension
|
||||
*/
|
||||
export class BalloonLink extends go.Link {
|
||||
private _base: number = 10;
|
||||
|
||||
/**
|
||||
* Constructs a BalloonLink and sets the {@link Part#layerName} property to "Background".
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
this.layerName = 'Background';
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies properties to a cloned BalloonLink.
|
||||
*/
|
||||
protected cloneProtected(copy: this): void {
|
||||
super.cloneProtected(copy);
|
||||
copy._base = this._base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets width of the base of the triangle at the center point of the {@link Link#fromNode}.
|
||||
*
|
||||
* The default value is 10.
|
||||
*/
|
||||
get base(): number { return this._base; }
|
||||
set base(value: number) { this._base = value; }
|
||||
|
||||
/**
|
||||
* Produce a Geometry from the Link's route that draws a "balloon" shape around the {@link Link#fromNode}
|
||||
* and has a triangular shape with the base at the fromNode and the top at the toNode.
|
||||
*/
|
||||
public makeGeometry(): go.Geometry {
|
||||
const fromnode = this.fromNode;
|
||||
const tonode = this.toNode;
|
||||
if (fromnode === null || tonode === null) return super.makeGeometry();
|
||||
// assume the fromNode is the comment and the toNode is the commented-upon node
|
||||
const bb = fromnode.actualBounds;
|
||||
const nb = tonode.actualBounds;
|
||||
|
||||
const p0 = bb.center;
|
||||
let pn = this.getPoint(this.pointsCount - 1);
|
||||
if (bb.intersectsRect(nb)) {
|
||||
pn = nb.center;
|
||||
}
|
||||
const pos = this.routeBounds;
|
||||
|
||||
// compute the intersection points for the triangular arrow
|
||||
const ang = pn.directionPoint(p0);
|
||||
const L = new go.Point(this.base, 0).rotate(ang - 90).add(p0);
|
||||
const R = new go.Point(this.base, 0).rotate(ang + 90).add(p0);
|
||||
this.getLinkPointFromPoint(fromnode, fromnode, L, pn, true, L);
|
||||
this.getLinkPointFromPoint(fromnode, fromnode, R, pn, true, R);
|
||||
|
||||
// form a triangular arrow from the comment to the commented node
|
||||
const fig = new go.PathFigure(pn.x - pos.x, pn.y - pos.y, true); // filled; start at arrow point at commented node
|
||||
fig.add(new go.PathSegment(go.PathSegment.Line, R.x - pos.x, R.y - pos.y)); // a triangle base point on comment's edge
|
||||
let side = 0;
|
||||
if (L.y >= bb.bottom || R.y >= bb.bottom) side = 2;
|
||||
else if (L.x <= bb.x && R.x <= bb.x) side = 1;
|
||||
else if (L.x >= bb.right && R.x >= bb.right) side = 3;
|
||||
|
||||
this.pathToCorner(side, bb, fig, pos, L, R);
|
||||
this.pathToCorner(side + 1, bb, fig, pos, L, R);
|
||||
this.pathToCorner(side + 2, bb, fig, pos, L, R);
|
||||
this.pathToCorner(side + 3, bb, fig, pos, L, R);
|
||||
fig.add(new go.PathSegment(go.PathSegment.Line, L.x - pos.x, L.y - pos.y).close()); // the other triangle base point on comment's edge
|
||||
|
||||
// return a Geometry
|
||||
return new go.Geometry().add(fig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a line to a corner, but not if the comment arrow encompasses that corner.
|
||||
*/
|
||||
public pathToCorner(side: number, bb: go.Rect, fig: go.PathFigure, pos: go.Rect, L: go.Point, R: go.Point): void {
|
||||
switch (side % 4) {
|
||||
case 0: if (!(L.y <= bb.y && R.x <= bb.x)) fig.add(new go.PathSegment(go.PathSegment.Line, bb.x - pos.x, bb.y - pos.y)); break;
|
||||
case 1: if (!(L.x <= bb.x && R.y >= bb.bottom)) fig.add(new go.PathSegment(go.PathSegment.Line, bb.x - pos.x, bb.bottom - pos.y)); break;
|
||||
case 2: if (!(L.y >= bb.bottom && R.x >= bb.right)) fig.add(new go.PathSegment(go.PathSegment.Line, bb.right - pos.x, bb.bottom - pos.y)); break;
|
||||
case 3: if (!(L.x >= bb.right && R.y <= bb.y)) fig.add(new go.PathSegment(go.PathSegment.Line, bb.right - pos.x, bb.y - pos.y)); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js", "./BalloonLink.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.init = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
var BalloonLink_js_1 = require("./BalloonLink.js");
|
||||
function init() {
|
||||
if (window.goSamples())
|
||||
window.goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make; // for conciseness in defining templates
|
||||
var myDiagram = $(go.Diagram, 'myDiagramDiv', // create a Diagram for the DIV HTML element
|
||||
{
|
||||
'undoManager.isEnabled': true // enable undo & redo
|
||||
});
|
||||
// define a simple Node template
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, 'Auto', // the Shape will go around the TextBlock
|
||||
$(go.Shape, 'Rectangle', { strokeWidth: 0 },
|
||||
// Shape.fill is bound to Node.data.color
|
||||
new go.Binding('fill', 'color')), $(go.TextBlock, { margin: 8 }, // some room around the text
|
||||
// TextBlock.text is bound to Node.data.key
|
||||
new go.Binding('text', 'key')));
|
||||
myDiagram.linkTemplate =
|
||||
$(BalloonLink_js_1.BalloonLink, $(go.Shape, { stroke: 'limegreen', strokeWidth: 3, fill: 'limegreen' }));
|
||||
// create the model data that will be represented by Nodes and Links
|
||||
myDiagram.model = new go.GraphLinksModel([
|
||||
{ key: 'Alpha', color: 'lightblue' },
|
||||
{ key: 'Beta', color: 'orange' }
|
||||
], [
|
||||
{ from: 'Alpha', to: 'Beta' }
|
||||
]);
|
||||
// Attach to the window for console manipulation
|
||||
window.myDiagram = myDiagram;
|
||||
}
|
||||
exports.init = init;
|
||||
});
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
import { BalloonLink } from './BalloonLink.js';
|
||||
|
||||
export function init() {
|
||||
if ((window as any).goSamples()) (window as any).goSamples(); // init for these samples -- you don't need to call this
|
||||
|
||||
const $ = go.GraphObject.make; // for conciseness in defining templates
|
||||
|
||||
const myDiagram = $(go.Diagram, 'myDiagramDiv', // create a Diagram for the DIV HTML element
|
||||
{
|
||||
'undoManager.isEnabled': true // enable undo & redo
|
||||
});
|
||||
|
||||
// define a simple Node template
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, 'Auto', // the Shape will go around the TextBlock
|
||||
$(go.Shape, 'Rectangle', { strokeWidth: 0 },
|
||||
// Shape.fill is bound to Node.data.color
|
||||
new go.Binding('fill', 'color')),
|
||||
$(go.TextBlock,
|
||||
{ margin: 8 }, // some room around the text
|
||||
// TextBlock.text is bound to Node.data.key
|
||||
new go.Binding('text', 'key'))
|
||||
);
|
||||
|
||||
myDiagram.linkTemplate =
|
||||
$(BalloonLink,
|
||||
$(go.Shape,
|
||||
{ stroke: 'limegreen', strokeWidth: 3, fill: 'limegreen' })
|
||||
);
|
||||
// create the model data that will be represented by Nodes and Links
|
||||
myDiagram.model = new go.GraphLinksModel(
|
||||
[
|
||||
{ key: 'Alpha', color: 'lightblue' },
|
||||
{ key: 'Beta', color: 'orange' }
|
||||
],
|
||||
[
|
||||
{ from: 'Alpha', to: 'Beta' }
|
||||
]);
|
||||
|
||||
// Attach to the window for console manipulation
|
||||
(window as any).myDiagram = myDiagram;
|
||||
}
|
||||
Executable
+534
@@ -0,0 +1,534 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
// These are the definitions for all of the predefined buttons.
|
||||
// You do not need to load this file in order to use buttons.
|
||||
// A 'Button' is a Panel that has a Shape surrounding some content
|
||||
// and that has mouseEnter/mouseLeave behavior to highlight the button.
|
||||
// The content of the button, whether a TextBlock or a Picture or a complicated Panel,
|
||||
// must be supplied by the caller.
|
||||
// The caller must also provide a click event handler.
|
||||
// Typical usage:
|
||||
// $('Button',
|
||||
// $(go.TextBlock, 'Click me!'), // the content is just the text label
|
||||
// { click: function(e, obj) { alert('I was clicked'); } }
|
||||
// )
|
||||
// Note that a button click event handler is not invoked upon a click if isEnabledObject() returns false.
|
||||
go.GraphObject.defineBuilder('Button', function (args) {
|
||||
// default colors for 'Button' shape
|
||||
var buttonFillNormal = '#F5F5F5';
|
||||
var buttonStrokeNormal = '#BDBDBD';
|
||||
var buttonFillOver = '#E0E0E0';
|
||||
var buttonStrokeOver = '#9E9E9E';
|
||||
var buttonFillPressed = '#BDBDBD'; // set to null for no button pressed effects
|
||||
var buttonStrokePressed = '#9E9E9E';
|
||||
var buttonFillDisabled = '#E5E5E5';
|
||||
// padding inside the ButtonBorder to match sizing from previous versions
|
||||
var paddingHorizontal = 2.76142374915397;
|
||||
var paddingVertical = 2.761423749153969;
|
||||
var button = /** @type {Panel} */ (go.GraphObject.make(go.Panel, 'Auto', {
|
||||
isActionable: true,
|
||||
enabledChanged: function (btn, enabled) {
|
||||
if (btn instanceof go.Panel) {
|
||||
var shape = btn.findObject('ButtonBorder');
|
||||
if (shape !== null) {
|
||||
shape.fill = enabled ? btn['_buttonFillNormal'] : btn['_buttonFillDisabled'];
|
||||
}
|
||||
}
|
||||
},
|
||||
cursor: 'pointer',
|
||||
// save these values for the mouseEnter and mouseLeave event handlers
|
||||
'_buttonFillNormal': buttonFillNormal,
|
||||
'_buttonStrokeNormal': buttonStrokeNormal,
|
||||
'_buttonFillOver': buttonFillOver,
|
||||
'_buttonStrokeOver': buttonStrokeOver,
|
||||
'_buttonFillPressed': buttonFillPressed,
|
||||
'_buttonStrokePressed': buttonStrokePressed,
|
||||
'_buttonFillDisabled': buttonFillDisabled
|
||||
}, go.GraphObject.make(go.Shape, // the border
|
||||
{
|
||||
name: 'ButtonBorder',
|
||||
figure: 'RoundedRectangle',
|
||||
spot1: new go.Spot(0, 0, paddingHorizontal, paddingVertical),
|
||||
spot2: new go.Spot(1, 1, -paddingHorizontal, -paddingVertical),
|
||||
parameter1: 2,
|
||||
parameter2: 2,
|
||||
fill: buttonFillNormal,
|
||||
stroke: buttonStrokeNormal
|
||||
})));
|
||||
// There's no GraphObject inside the button shape -- it must be added as part of the button definition.
|
||||
// This way the object could be a TextBlock or a Shape or a Picture or arbitrarily complex Panel.
|
||||
// mouse-over behavior
|
||||
button.mouseEnter = function (e, btn, prev) {
|
||||
if (!btn.isEnabledObject())
|
||||
return;
|
||||
if (!(btn instanceof go.Panel))
|
||||
return;
|
||||
var shape = btn.findObject('ButtonBorder'); // the border Shape
|
||||
if (shape instanceof go.Shape) {
|
||||
var brush = btn['_buttonFillOver'];
|
||||
btn['_buttonFillNormal'] = shape.fill;
|
||||
shape.fill = brush;
|
||||
brush = btn['_buttonStrokeOver'];
|
||||
btn['_buttonStrokeNormal'] = shape.stroke;
|
||||
shape.stroke = brush;
|
||||
}
|
||||
};
|
||||
button.mouseLeave = function (e, btn, prev) {
|
||||
if (!btn.isEnabledObject())
|
||||
return;
|
||||
if (!(btn instanceof go.Panel))
|
||||
return;
|
||||
var shape = btn.findObject('ButtonBorder'); // the border Shape
|
||||
if (shape instanceof go.Shape) {
|
||||
shape.fill = btn['_buttonFillNormal'];
|
||||
shape.stroke = btn['_buttonStrokeNormal'];
|
||||
}
|
||||
};
|
||||
button.actionDown = function (e, btn) {
|
||||
if (!btn.isEnabledObject())
|
||||
return;
|
||||
if (!(btn instanceof go.Panel))
|
||||
return;
|
||||
if (btn['_buttonFillPressed'] === null)
|
||||
return;
|
||||
if (e.button !== 0)
|
||||
return;
|
||||
var shape = btn.findObject('ButtonBorder'); // the border Shape
|
||||
if (shape instanceof go.Shape) {
|
||||
var diagram = e.diagram;
|
||||
var oldskip = diagram.skipsUndoManager;
|
||||
diagram.skipsUndoManager = true;
|
||||
var brush = btn['_buttonFillPressed'];
|
||||
btn['_buttonFillOver'] = shape.fill;
|
||||
shape.fill = brush;
|
||||
brush = btn['_buttonStrokePressed'];
|
||||
btn['_buttonStrokeOver'] = shape.stroke;
|
||||
shape.stroke = brush;
|
||||
diagram.skipsUndoManager = oldskip;
|
||||
}
|
||||
};
|
||||
button.actionUp = function (e, btn) {
|
||||
if (!btn.isEnabledObject())
|
||||
return;
|
||||
if (!(btn instanceof go.Panel))
|
||||
return;
|
||||
if (btn['_buttonFillPressed'] === null)
|
||||
return;
|
||||
if (e.button !== 0)
|
||||
return;
|
||||
var shape = btn.findObject('ButtonBorder'); // the border Shape
|
||||
if (shape instanceof go.Shape) {
|
||||
var diagram = e.diagram;
|
||||
var oldskip = diagram.skipsUndoManager;
|
||||
diagram.skipsUndoManager = true;
|
||||
if (overButton(e, btn)) {
|
||||
shape.fill = btn['_buttonFillOver'];
|
||||
shape.stroke = btn['_buttonStrokeOver'];
|
||||
}
|
||||
else {
|
||||
shape.fill = btn['_buttonFillNormal'];
|
||||
shape.stroke = btn['_buttonStrokeNormal'];
|
||||
}
|
||||
diagram.skipsUndoManager = oldskip;
|
||||
}
|
||||
};
|
||||
button.actionCancel = function (e, btn) {
|
||||
if (!btn.isEnabledObject())
|
||||
return;
|
||||
if (!(btn instanceof go.Panel))
|
||||
return;
|
||||
if (btn['_buttonFillPressed'] === null)
|
||||
return;
|
||||
var shape = btn.findObject('ButtonBorder'); // the border Shape
|
||||
if (shape instanceof go.Shape) {
|
||||
var diagram = e.diagram;
|
||||
var oldskip = diagram.skipsUndoManager;
|
||||
diagram.skipsUndoManager = true;
|
||||
if (overButton(e, btn)) {
|
||||
shape.fill = btn['_buttonFillOver'];
|
||||
shape.stroke = btn['_buttonStrokeOver'];
|
||||
}
|
||||
else {
|
||||
shape.fill = btn['_buttonFillNormal'];
|
||||
shape.stroke = btn['_buttonStrokeNormal'];
|
||||
}
|
||||
diagram.skipsUndoManager = oldskip;
|
||||
}
|
||||
};
|
||||
button.actionMove = function (e, btn) {
|
||||
if (!btn.isEnabledObject())
|
||||
return;
|
||||
if (!(btn instanceof go.Panel))
|
||||
return;
|
||||
if (btn['_buttonFillPressed'] === null)
|
||||
return;
|
||||
var diagram = e.diagram;
|
||||
if (diagram.firstInput.button !== 0)
|
||||
return;
|
||||
diagram.currentTool.standardMouseOver();
|
||||
if (overButton(e, btn)) {
|
||||
var shape = btn.findObject('ButtonBorder');
|
||||
if (shape instanceof go.Shape) {
|
||||
var oldskip = diagram.skipsUndoManager;
|
||||
diagram.skipsUndoManager = true;
|
||||
var brush = btn['_buttonFillPressed'];
|
||||
if (shape.fill !== brush)
|
||||
shape.fill = brush;
|
||||
brush = btn['_buttonStrokePressed'];
|
||||
if (shape.stroke !== brush)
|
||||
shape.stroke = brush;
|
||||
diagram.skipsUndoManager = oldskip;
|
||||
}
|
||||
}
|
||||
};
|
||||
var overButton = function (e, btn) {
|
||||
var over = e.diagram.findObjectAt(e.documentPoint, function (x) {
|
||||
while (x.panel !== null) {
|
||||
if (x.isActionable)
|
||||
return x;
|
||||
x = x.panel;
|
||||
}
|
||||
return x;
|
||||
}, function (x) { return x === btn; });
|
||||
return over !== null;
|
||||
};
|
||||
return button;
|
||||
});
|
||||
// This is a complete Button that you can have in a Node template
|
||||
// to allow the user to collapse/expand the subtree beginning at that Node.
|
||||
// Typical usage within a Node template:
|
||||
// $('TreeExpanderButton')
|
||||
go.GraphObject.defineBuilder('TreeExpanderButton', function (args) {
|
||||
var button = /** @type {Panel} */ (go.GraphObject.make('Button', {
|
||||
'_treeExpandedFigure': 'MinusLine',
|
||||
'_treeCollapsedFigure': 'PlusLine'
|
||||
}, go.GraphObject.make(go.Shape, // the icon
|
||||
{
|
||||
name: 'ButtonIcon',
|
||||
figure: 'MinusLine',
|
||||
stroke: '#424242',
|
||||
strokeWidth: 2,
|
||||
desiredSize: new go.Size(8, 8)
|
||||
},
|
||||
// bind the Shape.figure to the Node.isTreeExpanded value using this converter:
|
||||
new go.Binding('figure', 'isTreeExpanded', function (exp, shape) {
|
||||
var but = shape.panel;
|
||||
return exp ? but['_treeExpandedFigure'] : but['_treeCollapsedFigure'];
|
||||
}).ofObject()),
|
||||
// assume initially not visible because there are no links coming out
|
||||
{ visible: false },
|
||||
// bind the button visibility to whether it's not a leaf node
|
||||
new go.Binding('visible', 'isTreeLeaf', function (leaf) { return !leaf; }).ofObject()));
|
||||
// tree expand/collapse behavior
|
||||
button.click = function (e, btn) {
|
||||
var node = btn.part;
|
||||
if (node instanceof go.Adornment)
|
||||
node = node.adornedPart;
|
||||
if (!(node instanceof go.Node))
|
||||
return;
|
||||
var diagram = node.diagram;
|
||||
if (diagram === null)
|
||||
return;
|
||||
var cmd = diagram.commandHandler;
|
||||
if (node.isTreeExpanded) {
|
||||
if (!cmd.canCollapseTree(node))
|
||||
return;
|
||||
}
|
||||
else {
|
||||
if (!cmd.canExpandTree(node))
|
||||
return;
|
||||
}
|
||||
e.handled = true;
|
||||
if (node.isTreeExpanded) {
|
||||
cmd.collapseTree(node);
|
||||
}
|
||||
else {
|
||||
cmd.expandTree(node);
|
||||
}
|
||||
};
|
||||
return button;
|
||||
});
|
||||
// This is a complete Button that you can have in a Group template
|
||||
// to allow the user to collapse/expand the subgraph that the Group holds.
|
||||
// Typical usage within a Group template:
|
||||
// $('SubGraphExpanderButton')
|
||||
go.GraphObject.defineBuilder('SubGraphExpanderButton', function (args) {
|
||||
var button = /** @type {Panel} */ (go.GraphObject.make('Button', {
|
||||
'_subGraphExpandedFigure': 'MinusLine',
|
||||
'_subGraphCollapsedFigure': 'PlusLine'
|
||||
}, go.GraphObject.make(go.Shape, // the icon
|
||||
{
|
||||
name: 'ButtonIcon',
|
||||
figure: 'MinusLine',
|
||||
stroke: '#424242',
|
||||
strokeWidth: 2,
|
||||
desiredSize: new go.Size(8, 8)
|
||||
},
|
||||
// bind the Shape.figure to the Group.isSubGraphExpanded value using this converter:
|
||||
new go.Binding('figure', 'isSubGraphExpanded', function (exp, shape) {
|
||||
var but = shape.panel;
|
||||
return exp ? but['_subGraphExpandedFigure'] : but['_subGraphCollapsedFigure'];
|
||||
}).ofObject())));
|
||||
// subgraph expand/collapse behavior
|
||||
button.click = function (e, btn) {
|
||||
var group = btn.part;
|
||||
if (group instanceof go.Adornment)
|
||||
group = group.adornedPart;
|
||||
if (!(group instanceof go.Group))
|
||||
return;
|
||||
var diagram = group.diagram;
|
||||
if (diagram === null)
|
||||
return;
|
||||
var cmd = diagram.commandHandler;
|
||||
if (group.isSubGraphExpanded) {
|
||||
if (!cmd.canCollapseSubGraph(group))
|
||||
return;
|
||||
}
|
||||
else {
|
||||
if (!cmd.canExpandSubGraph(group))
|
||||
return;
|
||||
}
|
||||
e.handled = true;
|
||||
if (group.isSubGraphExpanded) {
|
||||
cmd.collapseSubGraph(group);
|
||||
}
|
||||
else {
|
||||
cmd.expandSubGraph(group);
|
||||
}
|
||||
};
|
||||
return button;
|
||||
});
|
||||
// This is just an "Auto" Adornment that can hold some contents within a light gray, shadowed box.
|
||||
// Typical usage:
|
||||
// toolTip:
|
||||
// $("ToolTip",
|
||||
// $(go.TextBlock, . . .)
|
||||
// )
|
||||
go.GraphObject.defineBuilder('ToolTip', function (args) {
|
||||
var ad = go.GraphObject.make(go.Adornment, 'Auto', {
|
||||
isShadowed: true,
|
||||
shadowColor: 'rgba(0, 0, 0, .4)',
|
||||
shadowOffset: new go.Point(0, 3),
|
||||
shadowBlur: 5
|
||||
}, go.GraphObject.make(go.Shape, {
|
||||
name: 'Border',
|
||||
figure: 'RoundedRectangle',
|
||||
parameter1: 1,
|
||||
parameter2: 1,
|
||||
fill: '#F5F5F5',
|
||||
stroke: '#F0F0F0',
|
||||
spot1: new go.Spot(0, 0, 4, 6),
|
||||
spot2: new go.Spot(1, 1, -4, -4)
|
||||
}));
|
||||
return ad;
|
||||
});
|
||||
// This is just a "Vertical" Adornment that can hold some "ContextMenuButton"s.
|
||||
// Typical usage:
|
||||
// contextMenu:
|
||||
// $("ContextMenu",
|
||||
// $("ContextMenuButton",
|
||||
// $(go.TextBlock, . . .),
|
||||
// { click: . . .}
|
||||
// ),
|
||||
// $("ContextMenuButton", . . .)
|
||||
// )
|
||||
go.GraphObject.defineBuilder('ContextMenu', function (args) {
|
||||
var ad = go.GraphObject.make(go.Adornment, 'Vertical', {
|
||||
background: '#F5F5F5',
|
||||
isShadowed: true,
|
||||
shadowColor: 'rgba(0, 0, 0, .4)',
|
||||
shadowOffset: new go.Point(0, 3),
|
||||
shadowBlur: 5
|
||||
},
|
||||
// don't set the background if the ContextMenu is adorning something and there's a Placeholder
|
||||
new go.Binding('background', '', function (obj) {
|
||||
var part = obj.adornedPart;
|
||||
if (part !== null && obj.placeholder !== null)
|
||||
return null;
|
||||
return '#F5F5F5';
|
||||
}));
|
||||
return ad;
|
||||
});
|
||||
// This just holds the 'ButtonBorder' Shape that acts as the border
|
||||
// around the button contents, which must be supplied by the caller.
|
||||
// The button contents are usually a TextBlock or Panel consisting of a Shape and a TextBlock.
|
||||
// Typical usage within an Adornment that is either a GraphObject.contextMenu or a Diagram.contextMenu:
|
||||
// $('ContextMenuButton',
|
||||
// $(go.TextBlock, text),
|
||||
// { click: function(e, obj) { alert('Command for ' + obj.part.adornedPart); } },
|
||||
// new go.Binding('visible', '', function(data) { return ...OK to perform Command...; })
|
||||
// )
|
||||
go.GraphObject.defineBuilder('ContextMenuButton', function (args) {
|
||||
var button = /** @type {Panel} */ (go.GraphObject.make('Button'));
|
||||
button.stretch = go.GraphObject.Horizontal;
|
||||
var border = button.findObject('ButtonBorder');
|
||||
if (border instanceof go.Shape) {
|
||||
border.figure = 'Rectangle';
|
||||
border.spot1 = new go.Spot(0, 0, 2, 3);
|
||||
border.spot2 = new go.Spot(1, 1, -2, -2);
|
||||
}
|
||||
return button;
|
||||
});
|
||||
// This button is used to toggle the visibility of a GraphObject named
|
||||
// by the second argument to GraphObject.make. If the second argument is not present
|
||||
// or if it is not a string, this assumes that the element name is 'COLLAPSIBLE'.
|
||||
// You can only control the visibility of one element in a Part at a time,
|
||||
// although that element might be an arbitrarily complex Panel.
|
||||
// Typical usage:
|
||||
// $(go.Panel, . . .,
|
||||
// $('PanelExpanderButton', 'COLLAPSIBLE'),
|
||||
// . . .,
|
||||
// $(go.Panel, . . .,
|
||||
// { name: 'COLLAPSIBLE' },
|
||||
// . . . stuff to be hidden or shown as the PanelExpanderButton is clicked . . .
|
||||
// ),
|
||||
// . . .
|
||||
// )
|
||||
go.GraphObject.defineBuilder('PanelExpanderButton', function (args) {
|
||||
var eltname = (go.GraphObject.takeBuilderArgument(args, 'COLLAPSIBLE'));
|
||||
var button = (go.GraphObject.make('Button', {
|
||||
'_buttonExpandedFigure': 'M0 0 M0 6 L4 2 8 6 M8 8',
|
||||
'_buttonCollapsedFigure': 'M0 0 M0 2 L4 6 8 2 M8 8',
|
||||
'_buttonFillNormal': 'rgba(0, 0, 0, 0)',
|
||||
'_buttonStrokeNormal': null,
|
||||
'_buttonFillOver': 'rgba(0, 0, 0, .2)',
|
||||
'_buttonStrokeOver': null,
|
||||
'_buttonFillPressed': 'rgba(0, 0, 0, .4)',
|
||||
'_buttonStrokePressed': null
|
||||
}, go.GraphObject.make(go.Shape, { name: 'ButtonIcon', strokeWidth: 2 }, new go.Binding('geometryString', 'visible', function (vis) { return vis ? button['_buttonExpandedFigure'] : button['_buttonCollapsedFigure']; }).ofObject(eltname))));
|
||||
var border = button.findObject('ButtonBorder');
|
||||
if (border instanceof go.Shape) {
|
||||
border.stroke = null;
|
||||
border.fill = 'rgba(0, 0, 0, 0)';
|
||||
}
|
||||
button.click = function (e, btn) {
|
||||
if (!(btn instanceof go.Panel))
|
||||
return;
|
||||
var diagram = btn.diagram;
|
||||
if (diagram === null)
|
||||
return;
|
||||
if (diagram.isReadOnly)
|
||||
return;
|
||||
var elt = btn.findTemplateBinder();
|
||||
if (elt === null)
|
||||
elt = btn.part;
|
||||
if (elt !== null) {
|
||||
var pan = elt.findObject(eltname);
|
||||
if (pan !== null) {
|
||||
e.handled = true;
|
||||
diagram.startTransaction('Collapse/Expand Panel');
|
||||
pan.visible = !pan.visible;
|
||||
diagram.commitTransaction('Collapse/Expand Panel');
|
||||
}
|
||||
}
|
||||
};
|
||||
return button;
|
||||
});
|
||||
// Define a common checkbox button; the first argument is the name of the data property
|
||||
// to which the state of this checkbox is data bound. If the first argument is not a string,
|
||||
// it raises an error. If no data binding of the checked state is desired,
|
||||
// pass an empty string as the first argument.
|
||||
// Examples:
|
||||
// $('CheckBoxButton', 'dataPropertyName', ...)
|
||||
// or:
|
||||
// $('CheckBoxButton', '', { '_doClick': function(e, obj) { alert('clicked!'); } })
|
||||
go.GraphObject.defineBuilder('CheckBoxButton', function (args) {
|
||||
// process the one required string argument for this kind of button
|
||||
var propname = /** @type {string} */ (go.GraphObject.takeBuilderArgument(args));
|
||||
var button = /** @type {Panel} */ (go.GraphObject.make('Button', { desiredSize: new go.Size(14, 14) }, go.GraphObject.make(go.Shape, {
|
||||
name: 'ButtonIcon',
|
||||
geometryString: 'M0 0 M0 8.85 L4.9 13.75 16.2 2.45 M16.2 16.2',
|
||||
strokeWidth: 2,
|
||||
stretch: go.GraphObject.Fill,
|
||||
geometryStretch: go.GraphObject.Uniform,
|
||||
visible: false // visible set to false: not checked, unless data.PROPNAME is true
|
||||
},
|
||||
// create a data Binding only if PROPNAME is supplied and not the empty string
|
||||
(propname !== '' ? new go.Binding('visible', propname).makeTwoWay() : []))));
|
||||
button.click = function (e, btn) {
|
||||
var diagram = e.diagram;
|
||||
if (diagram === null || diagram.isReadOnly)
|
||||
return;
|
||||
if (propname !== '' && diagram.model.isReadOnly)
|
||||
return;
|
||||
e.handled = true;
|
||||
var shape = btn.findObject('ButtonIcon');
|
||||
diagram.startTransaction('checkbox');
|
||||
if (shape !== null)
|
||||
shape.visible = !shape.visible; // this toggles data.checked due to TwoWay Binding
|
||||
// support extra side-effects without clobbering the click event handler:
|
||||
if (typeof btn['_doClick'] === 'function')
|
||||
btn['_doClick'](e, btn);
|
||||
diagram.commitTransaction('checkbox');
|
||||
};
|
||||
return button;
|
||||
});
|
||||
// This defines a whole check-box -- including both a 'CheckBoxButton' and whatever you want as the check box label.
|
||||
// Note that mouseEnter/mouseLeave/click events apply to everything in the panel, not just in the 'CheckBoxButton'.
|
||||
// Examples:
|
||||
// $('CheckBox', 'aBooleanDataProperty', $(go.TextBlock, 'the checkbox label'))
|
||||
// or
|
||||
// $('CheckBox', 'someProperty', $(go.TextBlock, 'A choice'),
|
||||
// { '_doClick': function(e, obj) { ... perform extra side-effects ... } })
|
||||
go.GraphObject.defineBuilder('CheckBox', function (args) {
|
||||
// process the one required string argument for this kind of button
|
||||
var propname = /** @type {string} */ (go.GraphObject.takeBuilderArgument(args));
|
||||
var button = /** @type {Panel} */ (go.GraphObject.make('CheckBoxButton', propname, // bound to this data property
|
||||
{
|
||||
name: 'Button',
|
||||
isActionable: false,
|
||||
margin: new go.Margin(0, 1, 0, 0)
|
||||
}));
|
||||
var box = /** @type {Panel} */ (go.GraphObject.make(go.Panel, 'Horizontal', button, {
|
||||
isActionable: true,
|
||||
cursor: button.cursor,
|
||||
margin: 1,
|
||||
// transfer CheckBoxButton properties over to this new CheckBox panel
|
||||
'_buttonFillNormal': button['_buttonFillNormal'],
|
||||
'_buttonStrokeNormal': button['_buttonStrokeNormal'],
|
||||
'_buttonFillOver': button['_buttonFillOver'],
|
||||
'_buttonStrokeOver': button['_buttonStrokeOver'],
|
||||
'_buttonFillPressed': button['_buttonFillPressed'],
|
||||
'_buttonStrokePressed': button['_buttonStrokePressed'],
|
||||
'_buttonFillDisabled': button['_buttonFillDisabled'],
|
||||
mouseEnter: button.mouseEnter,
|
||||
mouseLeave: button.mouseLeave,
|
||||
actionDown: button.actionDown,
|
||||
actionUp: button.actionUp,
|
||||
actionCancel: button.actionCancel,
|
||||
actionMove: button.actionMove,
|
||||
click: button.click,
|
||||
// also save original Button behavior, for potential use in a Panel.click event handler
|
||||
'_buttonClick': button.click
|
||||
}));
|
||||
// avoid potentially conflicting event handlers on the 'CheckBoxButton'
|
||||
button.mouseEnter = null;
|
||||
button.mouseLeave = null;
|
||||
button.actionDown = null;
|
||||
button.actionUp = null;
|
||||
button.actionCancel = null;
|
||||
button.actionMove = null;
|
||||
button.click = null;
|
||||
return box;
|
||||
});
|
||||
});
|
||||
Executable
+596
@@ -0,0 +1,596 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
|
||||
// These are the definitions for all of the predefined buttons.
|
||||
// You do not need to load this file in order to use buttons.
|
||||
|
||||
// A 'Button' is a Panel that has a Shape surrounding some content
|
||||
// and that has mouseEnter/mouseLeave behavior to highlight the button.
|
||||
// The content of the button, whether a TextBlock or a Picture or a complicated Panel,
|
||||
// must be supplied by the caller.
|
||||
// The caller must also provide a click event handler.
|
||||
|
||||
// Typical usage:
|
||||
// $('Button',
|
||||
// $(go.TextBlock, 'Click me!'), // the content is just the text label
|
||||
// { click: function(e, obj) { alert('I was clicked'); } }
|
||||
// )
|
||||
|
||||
// Note that a button click event handler is not invoked upon a click if isEnabledObject() returns false.
|
||||
|
||||
go.GraphObject.defineBuilder('Button', (args: any): go.Panel => {
|
||||
// default colors for 'Button' shape
|
||||
const buttonFillNormal = '#F5F5F5';
|
||||
const buttonStrokeNormal = '#BDBDBD';
|
||||
const buttonFillOver = '#E0E0E0';
|
||||
const buttonStrokeOver = '#9E9E9E';
|
||||
const buttonFillPressed = '#BDBDBD'; // set to null for no button pressed effects
|
||||
const buttonStrokePressed = '#9E9E9E';
|
||||
const buttonFillDisabled = '#E5E5E5';
|
||||
|
||||
// padding inside the ButtonBorder to match sizing from previous versions
|
||||
const paddingHorizontal = 2.76142374915397;
|
||||
const paddingVertical = 2.761423749153969;
|
||||
|
||||
const button = /** @type {Panel} */ (
|
||||
go.GraphObject.make(go.Panel, 'Auto',
|
||||
{
|
||||
isActionable: true, // needed so that the ActionTool intercepts mouse events
|
||||
enabledChanged: (btn: go.GraphObject, enabled: boolean): void => {
|
||||
if (btn instanceof go.Panel) {
|
||||
const shape = btn.findObject('ButtonBorder') as go.Shape;
|
||||
if (shape !== null) {
|
||||
shape.fill = enabled ? (btn as any)['_buttonFillNormal'] : (btn as any)['_buttonFillDisabled'];
|
||||
}
|
||||
}
|
||||
},
|
||||
cursor: 'pointer',
|
||||
// save these values for the mouseEnter and mouseLeave event handlers
|
||||
'_buttonFillNormal': buttonFillNormal,
|
||||
'_buttonStrokeNormal': buttonStrokeNormal,
|
||||
'_buttonFillOver': buttonFillOver,
|
||||
'_buttonStrokeOver': buttonStrokeOver,
|
||||
'_buttonFillPressed': buttonFillPressed,
|
||||
'_buttonStrokePressed': buttonStrokePressed,
|
||||
'_buttonFillDisabled': buttonFillDisabled
|
||||
},
|
||||
go.GraphObject.make(go.Shape, // the border
|
||||
{
|
||||
name: 'ButtonBorder',
|
||||
figure: 'RoundedRectangle',
|
||||
spot1: new go.Spot(0, 0, paddingHorizontal, paddingVertical),
|
||||
spot2: new go.Spot(1, 1, -paddingHorizontal, -paddingVertical),
|
||||
parameter1: 2,
|
||||
parameter2: 2,
|
||||
fill: buttonFillNormal,
|
||||
stroke: buttonStrokeNormal
|
||||
}
|
||||
)
|
||||
)
|
||||
) as go.Panel;
|
||||
|
||||
// There's no GraphObject inside the button shape -- it must be added as part of the button definition.
|
||||
// This way the object could be a TextBlock or a Shape or a Picture or arbitrarily complex Panel.
|
||||
|
||||
// mouse-over behavior
|
||||
button.mouseEnter = (e: go.InputEvent, btn: go.GraphObject, prev: go.GraphObject): void => {
|
||||
if (!btn.isEnabledObject()) return;
|
||||
if (!(btn instanceof go.Panel)) return;
|
||||
const shape = btn.findObject('ButtonBorder'); // the border Shape
|
||||
if (shape instanceof go.Shape) {
|
||||
let brush = (btn as any)['_buttonFillOver'];
|
||||
(btn as any)['_buttonFillNormal'] = shape.fill;
|
||||
shape.fill = brush;
|
||||
brush = (btn as any)['_buttonStrokeOver'];
|
||||
(btn as any)['_buttonStrokeNormal'] = shape.stroke;
|
||||
shape.stroke = brush;
|
||||
}
|
||||
};
|
||||
|
||||
button.mouseLeave = (e: go.InputEvent, btn: go.GraphObject, prev: go.GraphObject): void => {
|
||||
if (!btn.isEnabledObject()) return;
|
||||
if (!(btn instanceof go.Panel)) return;
|
||||
const shape = btn.findObject('ButtonBorder'); // the border Shape
|
||||
if (shape instanceof go.Shape) {
|
||||
shape.fill = (btn as any)['_buttonFillNormal'];
|
||||
shape.stroke = (btn as any)['_buttonStrokeNormal'];
|
||||
}
|
||||
};
|
||||
|
||||
button.actionDown = (e: go.InputEvent, btn: go.GraphObject): void => {
|
||||
if (!btn.isEnabledObject()) return;
|
||||
if (!(btn instanceof go.Panel)) return;
|
||||
if ((btn as any)['_buttonFillPressed'] === null) return;
|
||||
if (e.button !== 0) return;
|
||||
const shape = btn.findObject('ButtonBorder'); // the border Shape
|
||||
if (shape instanceof go.Shape) {
|
||||
const diagram = e.diagram;
|
||||
const oldskip = diagram.skipsUndoManager;
|
||||
diagram.skipsUndoManager = true;
|
||||
let brush = (btn as any)['_buttonFillPressed'];
|
||||
(btn as any)['_buttonFillOver'] = shape.fill;
|
||||
shape.fill = brush;
|
||||
brush = (btn as any)['_buttonStrokePressed'];
|
||||
(btn as any)['_buttonStrokeOver'] = shape.stroke;
|
||||
shape.stroke = brush;
|
||||
diagram.skipsUndoManager = oldskip;
|
||||
}
|
||||
};
|
||||
|
||||
button.actionUp = (e: go.InputEvent, btn: go.GraphObject): void => {
|
||||
if (!btn.isEnabledObject()) return;
|
||||
if (!(btn instanceof go.Panel)) return;
|
||||
if ((btn as any)['_buttonFillPressed'] === null) return;
|
||||
if (e.button !== 0) return;
|
||||
const shape = btn.findObject('ButtonBorder'); // the border Shape
|
||||
if (shape instanceof go.Shape) {
|
||||
const diagram = e.diagram;
|
||||
const oldskip = diagram.skipsUndoManager;
|
||||
diagram.skipsUndoManager = true;
|
||||
if (overButton(e, btn)) {
|
||||
shape.fill = (btn as any)['_buttonFillOver'];
|
||||
shape.stroke = (btn as any)['_buttonStrokeOver'];
|
||||
} else {
|
||||
shape.fill = (btn as any)['_buttonFillNormal'];
|
||||
shape.stroke = (btn as any)['_buttonStrokeNormal'];
|
||||
}
|
||||
diagram.skipsUndoManager = oldskip;
|
||||
}
|
||||
};
|
||||
|
||||
button.actionCancel = (e: go.InputEvent, btn: go.GraphObject): void => {
|
||||
if (!btn.isEnabledObject()) return;
|
||||
if (!(btn instanceof go.Panel)) return;
|
||||
if ((btn as any)['_buttonFillPressed'] === null) return;
|
||||
const shape = btn.findObject('ButtonBorder'); // the border Shape
|
||||
if (shape instanceof go.Shape) {
|
||||
const diagram = e.diagram;
|
||||
const oldskip = diagram.skipsUndoManager;
|
||||
diagram.skipsUndoManager = true;
|
||||
if (overButton(e, btn)) {
|
||||
shape.fill = (btn as any)['_buttonFillOver'];
|
||||
shape.stroke = (btn as any)['_buttonStrokeOver'];
|
||||
} else {
|
||||
shape.fill = (btn as any)['_buttonFillNormal'];
|
||||
shape.stroke = (btn as any)['_buttonStrokeNormal'];
|
||||
}
|
||||
diagram.skipsUndoManager = oldskip;
|
||||
}
|
||||
};
|
||||
|
||||
button.actionMove = (e: go.InputEvent, btn: go.GraphObject): void => {
|
||||
if (!btn.isEnabledObject()) return;
|
||||
if (!(btn instanceof go.Panel)) return;
|
||||
if ((btn as any)['_buttonFillPressed'] === null) return;
|
||||
const diagram = e.diagram;
|
||||
if (diagram.firstInput.button !== 0) return;
|
||||
diagram.currentTool.standardMouseOver();
|
||||
if (overButton(e, btn)) {
|
||||
const shape = btn.findObject('ButtonBorder');
|
||||
if (shape instanceof go.Shape) {
|
||||
const oldskip = diagram.skipsUndoManager;
|
||||
diagram.skipsUndoManager = true;
|
||||
let brush = (btn as any)['_buttonFillPressed'];
|
||||
if (shape.fill !== brush) shape.fill = brush;
|
||||
brush = (btn as any)['_buttonStrokePressed'];
|
||||
if (shape.stroke !== brush) shape.stroke = brush;
|
||||
diagram.skipsUndoManager = oldskip;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const overButton = (e: go.InputEvent, btn: go.Panel): boolean => {
|
||||
const over = e.diagram.findObjectAt(
|
||||
e.documentPoint,
|
||||
(x: go.GraphObject): go.GraphObject => {
|
||||
while (x.panel !== null) {
|
||||
if (x.isActionable) return x;
|
||||
x = x.panel;
|
||||
}
|
||||
return x;
|
||||
},
|
||||
(x: go.GraphObject): boolean => x === btn
|
||||
);
|
||||
return over !== null;
|
||||
};
|
||||
|
||||
return button;
|
||||
});
|
||||
|
||||
|
||||
// This is a complete Button that you can have in a Node template
|
||||
// to allow the user to collapse/expand the subtree beginning at that Node.
|
||||
|
||||
// Typical usage within a Node template:
|
||||
// $('TreeExpanderButton')
|
||||
|
||||
go.GraphObject.defineBuilder('TreeExpanderButton', (args: any): go.Panel => {
|
||||
const button = /** @type {Panel} */ (
|
||||
go.GraphObject.make('Button',
|
||||
{ // set these values for the isTreeExpanded binding conversion
|
||||
'_treeExpandedFigure': 'MinusLine',
|
||||
'_treeCollapsedFigure': 'PlusLine'
|
||||
},
|
||||
go.GraphObject.make(go.Shape, // the icon
|
||||
{
|
||||
name: 'ButtonIcon',
|
||||
figure: 'MinusLine', // default value for isTreeExpanded is true
|
||||
stroke: '#424242',
|
||||
strokeWidth: 2,
|
||||
desiredSize: new go.Size(8, 8)
|
||||
},
|
||||
// bind the Shape.figure to the Node.isTreeExpanded value using this converter:
|
||||
new go.Binding('figure', 'isTreeExpanded',
|
||||
(exp: boolean, shape: go.Shape): string => {
|
||||
const but = shape.panel;
|
||||
return exp ? (but as any)['_treeExpandedFigure'] : (but as any)['_treeCollapsedFigure'];
|
||||
}
|
||||
).ofObject()
|
||||
),
|
||||
// assume initially not visible because there are no links coming out
|
||||
{ visible: false },
|
||||
// bind the button visibility to whether it's not a leaf node
|
||||
new go.Binding('visible', 'isTreeLeaf',
|
||||
(leaf: boolean): boolean => !leaf
|
||||
).ofObject()
|
||||
)
|
||||
) as go.Panel;
|
||||
|
||||
// tree expand/collapse behavior
|
||||
button.click = (e: go.InputEvent, btn: go.GraphObject): void => {
|
||||
let node = btn.part;
|
||||
if (node instanceof go.Adornment) node = node.adornedPart;
|
||||
if (!(node instanceof go.Node)) return;
|
||||
const diagram = node.diagram;
|
||||
if (diagram === null) return;
|
||||
const cmd = diagram.commandHandler;
|
||||
if (node.isTreeExpanded) {
|
||||
if (!cmd.canCollapseTree(node)) return;
|
||||
} else {
|
||||
if (!cmd.canExpandTree(node)) return;
|
||||
}
|
||||
e.handled = true;
|
||||
if (node.isTreeExpanded) {
|
||||
cmd.collapseTree(node);
|
||||
} else {
|
||||
cmd.expandTree(node);
|
||||
}
|
||||
};
|
||||
|
||||
return button;
|
||||
});
|
||||
|
||||
|
||||
// This is a complete Button that you can have in a Group template
|
||||
// to allow the user to collapse/expand the subgraph that the Group holds.
|
||||
|
||||
// Typical usage within a Group template:
|
||||
// $('SubGraphExpanderButton')
|
||||
|
||||
go.GraphObject.defineBuilder('SubGraphExpanderButton', (args: any): go.Panel => {
|
||||
const button = /** @type {Panel} */ (
|
||||
go.GraphObject.make('Button',
|
||||
{ // set these values for the isSubGraphExpanded binding conversion
|
||||
'_subGraphExpandedFigure': 'MinusLine',
|
||||
'_subGraphCollapsedFigure': 'PlusLine'
|
||||
},
|
||||
go.GraphObject.make(go.Shape, // the icon
|
||||
{
|
||||
name: 'ButtonIcon',
|
||||
figure: 'MinusLine', // default value for isSubGraphExpanded is true
|
||||
stroke: '#424242',
|
||||
strokeWidth: 2,
|
||||
desiredSize: new go.Size(8, 8)
|
||||
},
|
||||
// bind the Shape.figure to the Group.isSubGraphExpanded value using this converter:
|
||||
new go.Binding('figure', 'isSubGraphExpanded',
|
||||
(exp: boolean, shape: go.Shape): string => {
|
||||
const but = shape.panel;
|
||||
return exp ? (but as any)['_subGraphExpandedFigure'] : (but as any)['_subGraphCollapsedFigure'];
|
||||
}
|
||||
).ofObject()
|
||||
)
|
||||
)
|
||||
) as go.Panel;
|
||||
|
||||
// subgraph expand/collapse behavior
|
||||
button.click = (e: go.InputEvent, btn: go.GraphObject): void => {
|
||||
let group = btn.part;
|
||||
if (group instanceof go.Adornment) group = group.adornedPart;
|
||||
if (!(group instanceof go.Group)) return;
|
||||
const diagram = group.diagram;
|
||||
if (diagram === null) return;
|
||||
const cmd = diagram.commandHandler;
|
||||
if (group.isSubGraphExpanded) {
|
||||
if (!cmd.canCollapseSubGraph(group)) return;
|
||||
} else {
|
||||
if (!cmd.canExpandSubGraph(group)) return;
|
||||
}
|
||||
e.handled = true;
|
||||
if (group.isSubGraphExpanded) {
|
||||
cmd.collapseSubGraph(group);
|
||||
} else {
|
||||
cmd.expandSubGraph(group);
|
||||
}
|
||||
};
|
||||
|
||||
return button;
|
||||
});
|
||||
|
||||
|
||||
// This is just an "Auto" Adornment that can hold some contents within a light gray, shadowed box.
|
||||
|
||||
// Typical usage:
|
||||
// toolTip:
|
||||
// $("ToolTip",
|
||||
// $(go.TextBlock, . . .)
|
||||
// )
|
||||
go.GraphObject.defineBuilder('ToolTip', (args: any): go.Adornment => {
|
||||
const ad = go.GraphObject.make(go.Adornment, 'Auto',
|
||||
{
|
||||
isShadowed: true,
|
||||
shadowColor: 'rgba(0, 0, 0, .4)',
|
||||
shadowOffset: new go.Point(0, 3),
|
||||
shadowBlur: 5
|
||||
},
|
||||
go.GraphObject.make(go.Shape,
|
||||
{
|
||||
name: 'Border',
|
||||
figure: 'RoundedRectangle',
|
||||
parameter1: 1,
|
||||
parameter2: 1,
|
||||
fill: '#F5F5F5',
|
||||
stroke: '#F0F0F0',
|
||||
spot1: new go.Spot(0, 0, 4, 6),
|
||||
spot2: new go.Spot(1, 1, -4, -4)
|
||||
}
|
||||
)
|
||||
);
|
||||
return ad;
|
||||
});
|
||||
|
||||
|
||||
// This is just a "Vertical" Adornment that can hold some "ContextMenuButton"s.
|
||||
|
||||
// Typical usage:
|
||||
// contextMenu:
|
||||
// $("ContextMenu",
|
||||
// $("ContextMenuButton",
|
||||
// $(go.TextBlock, . . .),
|
||||
// { click: . . .}
|
||||
// ),
|
||||
// $("ContextMenuButton", . . .)
|
||||
// )
|
||||
go.GraphObject.defineBuilder('ContextMenu', (args: any): go.Adornment => {
|
||||
const ad = go.GraphObject.make(go.Adornment, 'Vertical',
|
||||
{
|
||||
background: '#F5F5F5',
|
||||
isShadowed: true,
|
||||
shadowColor: 'rgba(0, 0, 0, .4)',
|
||||
shadowOffset: new go.Point(0, 3),
|
||||
shadowBlur: 5
|
||||
},
|
||||
// don't set the background if the ContextMenu is adorning something and there's a Placeholder
|
||||
new go.Binding('background', '', (obj: go.Adornment) => {
|
||||
const part = obj.adornedPart;
|
||||
if (part !== null && obj.placeholder !== null) return null;
|
||||
return '#F5F5F5';
|
||||
})
|
||||
);
|
||||
return ad;
|
||||
});
|
||||
|
||||
|
||||
// This just holds the 'ButtonBorder' Shape that acts as the border
|
||||
// around the button contents, which must be supplied by the caller.
|
||||
// The button contents are usually a TextBlock or Panel consisting of a Shape and a TextBlock.
|
||||
|
||||
// Typical usage within an Adornment that is either a GraphObject.contextMenu or a Diagram.contextMenu:
|
||||
// $('ContextMenuButton',
|
||||
// $(go.TextBlock, text),
|
||||
// { click: function(e, obj) { alert('Command for ' + obj.part.adornedPart); } },
|
||||
// new go.Binding('visible', '', function(data) { return ...OK to perform Command...; })
|
||||
// )
|
||||
|
||||
go.GraphObject.defineBuilder('ContextMenuButton', (args: any): go.Panel => {
|
||||
const button = /** @type {Panel} */ (go.GraphObject.make('Button')) as go.Panel;
|
||||
button.stretch = go.GraphObject.Horizontal;
|
||||
const border = button.findObject('ButtonBorder');
|
||||
if (border instanceof go.Shape) {
|
||||
border.figure = 'Rectangle';
|
||||
border.spot1 = new go.Spot(0, 0, 2, 3);
|
||||
border.spot2 = new go.Spot(1, 1, -2, -2);
|
||||
}
|
||||
return button;
|
||||
});
|
||||
|
||||
|
||||
// This button is used to toggle the visibility of a GraphObject named
|
||||
// by the second argument to GraphObject.make. If the second argument is not present
|
||||
// or if it is not a string, this assumes that the element name is 'COLLAPSIBLE'.
|
||||
// You can only control the visibility of one element in a Part at a time,
|
||||
// although that element might be an arbitrarily complex Panel.
|
||||
|
||||
// Typical usage:
|
||||
// $(go.Panel, . . .,
|
||||
// $('PanelExpanderButton', 'COLLAPSIBLE'),
|
||||
// . . .,
|
||||
// $(go.Panel, . . .,
|
||||
// { name: 'COLLAPSIBLE' },
|
||||
// . . . stuff to be hidden or shown as the PanelExpanderButton is clicked . . .
|
||||
// ),
|
||||
// . . .
|
||||
// )
|
||||
|
||||
go.GraphObject.defineBuilder('PanelExpanderButton', (args: any): go.Panel => {
|
||||
const eltname: string = /** @type {string} */ (go.GraphObject.takeBuilderArgument(args, 'COLLAPSIBLE'));
|
||||
|
||||
const button: go.Panel = /** @type {Panel} */ (
|
||||
go.GraphObject.make('Button',
|
||||
{ // set these values for the button's look
|
||||
'_buttonExpandedFigure': 'M0 0 M0 6 L4 2 8 6 M8 8',
|
||||
'_buttonCollapsedFigure': 'M0 0 M0 2 L4 6 8 2 M8 8',
|
||||
'_buttonFillNormal': 'rgba(0, 0, 0, 0)',
|
||||
'_buttonStrokeNormal': null,
|
||||
'_buttonFillOver': 'rgba(0, 0, 0, .2)',
|
||||
'_buttonStrokeOver': null,
|
||||
'_buttonFillPressed': 'rgba(0, 0, 0, .4)',
|
||||
'_buttonStrokePressed': null
|
||||
},
|
||||
go.GraphObject.make(go.Shape,
|
||||
{ name: 'ButtonIcon', strokeWidth: 2 },
|
||||
new go.Binding('geometryString', 'visible',
|
||||
(vis: boolean): string => vis ? (button as any)['_buttonExpandedFigure'] : (button as any)['_buttonCollapsedFigure']
|
||||
).ofObject(eltname)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const border = button.findObject('ButtonBorder');
|
||||
if (border instanceof go.Shape) {
|
||||
border.stroke = null;
|
||||
border.fill = 'rgba(0, 0, 0, 0)';
|
||||
}
|
||||
|
||||
button.click = (e: go.InputEvent, btn: go.GraphObject): void => {
|
||||
if (!(btn instanceof go.Panel)) return;
|
||||
const diagram = btn.diagram;
|
||||
if (diagram === null) return;
|
||||
if (diagram.isReadOnly) return;
|
||||
let elt = btn.findTemplateBinder();
|
||||
if (elt === null) elt = btn.part;
|
||||
if (elt !== null) {
|
||||
const pan = elt.findObject(eltname);
|
||||
if (pan !== null) {
|
||||
e.handled = true;
|
||||
diagram.startTransaction('Collapse/Expand Panel');
|
||||
pan.visible = !pan.visible;
|
||||
diagram.commitTransaction('Collapse/Expand Panel');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return button;
|
||||
});
|
||||
|
||||
|
||||
// Define a common checkbox button; the first argument is the name of the data property
|
||||
// to which the state of this checkbox is data bound. If the first argument is not a string,
|
||||
// it raises an error. If no data binding of the checked state is desired,
|
||||
// pass an empty string as the first argument.
|
||||
|
||||
// Examples:
|
||||
// $('CheckBoxButton', 'dataPropertyName', ...)
|
||||
// or:
|
||||
// $('CheckBoxButton', '', { '_doClick': function(e, obj) { alert('clicked!'); } })
|
||||
|
||||
go.GraphObject.defineBuilder('CheckBoxButton', (args: any): go.Panel => {
|
||||
// process the one required string argument for this kind of button
|
||||
const propname = /** @type {string} */ (go.GraphObject.takeBuilderArgument(args));
|
||||
|
||||
const button = /** @type {Panel} */ (
|
||||
go.GraphObject.make('Button',
|
||||
{ desiredSize: new go.Size(14, 14) },
|
||||
go.GraphObject.make(go.Shape,
|
||||
{
|
||||
name: 'ButtonIcon',
|
||||
geometryString: 'M0 0 M0 8.85 L4.9 13.75 16.2 2.45 M16.2 16.2', // a 'check' mark
|
||||
strokeWidth: 2,
|
||||
stretch: go.GraphObject.Fill, // this Shape expands to fill the Button
|
||||
geometryStretch: go.GraphObject.Uniform, // the check mark fills the Shape without distortion
|
||||
visible: false // visible set to false: not checked, unless data.PROPNAME is true
|
||||
},
|
||||
// create a data Binding only if PROPNAME is supplied and not the empty string
|
||||
(propname !== '' ? new go.Binding('visible', propname).makeTwoWay() : [])
|
||||
)
|
||||
)
|
||||
) as go.Panel;
|
||||
|
||||
button.click = (e: go.InputEvent, btn: go.GraphObject): void => {
|
||||
const diagram = e.diagram;
|
||||
if (diagram === null || diagram.isReadOnly) return;
|
||||
if (propname !== '' && diagram.model.isReadOnly) return;
|
||||
e.handled = true;
|
||||
const shape = (btn as go.Panel).findObject('ButtonIcon');
|
||||
diagram.startTransaction('checkbox');
|
||||
if (shape !== null) shape.visible = !shape.visible; // this toggles data.checked due to TwoWay Binding
|
||||
// support extra side-effects without clobbering the click event handler:
|
||||
if (typeof (btn as any)['_doClick'] === 'function') (btn as any)['_doClick'](e, btn);
|
||||
diagram.commitTransaction('checkbox');
|
||||
};
|
||||
|
||||
return button;
|
||||
});
|
||||
|
||||
|
||||
// This defines a whole check-box -- including both a 'CheckBoxButton' and whatever you want as the check box label.
|
||||
// Note that mouseEnter/mouseLeave/click events apply to everything in the panel, not just in the 'CheckBoxButton'.
|
||||
|
||||
// Examples:
|
||||
// $('CheckBox', 'aBooleanDataProperty', $(go.TextBlock, 'the checkbox label'))
|
||||
// or
|
||||
// $('CheckBox', 'someProperty', $(go.TextBlock, 'A choice'),
|
||||
// { '_doClick': function(e, obj) { ... perform extra side-effects ... } })
|
||||
|
||||
go.GraphObject.defineBuilder('CheckBox', (args: any): go.Panel => {
|
||||
// process the one required string argument for this kind of button
|
||||
const propname = /** @type {string} */ (go.GraphObject.takeBuilderArgument(args));
|
||||
|
||||
const button = /** @type {Panel} */ (
|
||||
go.GraphObject.make('CheckBoxButton', propname, // bound to this data property
|
||||
{
|
||||
name: 'Button',
|
||||
isActionable: false, // actionable is set on the whole horizontal panel
|
||||
margin: new go.Margin(0, 1, 0, 0)
|
||||
}
|
||||
)
|
||||
) as go.Panel;
|
||||
|
||||
const box = /** @type {Panel} */ (
|
||||
go.GraphObject.make(go.Panel, 'Horizontal',
|
||||
button,
|
||||
{
|
||||
isActionable: true,
|
||||
cursor: button.cursor,
|
||||
margin: 1,
|
||||
// transfer CheckBoxButton properties over to this new CheckBox panel
|
||||
'_buttonFillNormal': (button as any)['_buttonFillNormal'],
|
||||
'_buttonStrokeNormal': (button as any)['_buttonStrokeNormal'],
|
||||
'_buttonFillOver': (button as any)['_buttonFillOver'],
|
||||
'_buttonStrokeOver': (button as any)['_buttonStrokeOver'],
|
||||
'_buttonFillPressed': (button as any)['_buttonFillPressed'],
|
||||
'_buttonStrokePressed': (button as any)['_buttonStrokePressed'],
|
||||
'_buttonFillDisabled': (button as any)['_buttonFillDisabled'],
|
||||
mouseEnter: button.mouseEnter,
|
||||
mouseLeave: button.mouseLeave,
|
||||
actionDown: button.actionDown,
|
||||
actionUp: button.actionUp,
|
||||
actionCancel: button.actionCancel,
|
||||
actionMove: button.actionMove,
|
||||
click: button.click,
|
||||
// also save original Button behavior, for potential use in a Panel.click event handler
|
||||
'_buttonClick': button.click
|
||||
}
|
||||
)
|
||||
) as go.Panel;
|
||||
// avoid potentially conflicting event handlers on the 'CheckBoxButton'
|
||||
button.mouseEnter = null;
|
||||
button.mouseLeave = null;
|
||||
button.actionDown = null;
|
||||
button.actionUp = null;
|
||||
button.actionCancel = null;
|
||||
button.actionMove = null;
|
||||
button.click = null;
|
||||
return box;
|
||||
});
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CheckBoxes</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="TypeScript: An implementation of CheckBoxes as GoJS objects to show and edit a boolean data property." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../samples/assets/require.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
function init() {
|
||||
require(["CheckBoxesScript"], function(app) {
|
||||
app.init();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:500px; height:500px"></div>
|
||||
<p>
|
||||
Various uses of CheckBoxes. These are predefined in the library.
|
||||
You can see how they are defined in <a href="Buttons.ts">Buttons.ts</a>.
|
||||
</p>
|
||||
<textarea id="mySavedModel" style="width:100%;height:300px"></textarea>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.init = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
function init() {
|
||||
if (window.goSamples)
|
||||
window.goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make; // for conciseness in defining templates
|
||||
var myDiagram = $(go.Diagram, 'myDiagramDiv', // create a Diagram for the DIV HTML element
|
||||
{
|
||||
'undoManager.isEnabled': true // enable undo & redo
|
||||
});
|
||||
// this template includes a lot of CheckBoxes, each configured in different manners
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, 'Auto', // the Shape will go around the whole table
|
||||
$(go.Shape, { strokeWidth: 0 }, // no border
|
||||
new go.Binding('fill', 'color')), $(go.Panel, 'Table', { padding: 3 }, $(go.TextBlock, { row: 0, column: 0, columnSpan: 2 }, { margin: 3, font: 'bold 10pt sans-serif' }, // some room around the bold text
|
||||
new go.Binding('text', 'key')),
|
||||
// the first column has an assortment of CheckBoxes
|
||||
$(go.Panel, 'Vertical', { row: 1, column: 0, defaultAlignment: go.Spot.Left }, $('CheckBox', 'choice1', $(go.TextBlock, 'default')), $('CheckBox', 'choice2', { 'ButtonIcon.stroke': 'green' }, $(go.TextBlock, 'green')), $('CheckBox', 'choice3', { 'ButtonIcon.stroke': 'red', 'ButtonIcon.figure': 'XLine' }, $(go.TextBlock, 'red X')), $('CheckBox', 'choice4', { '_buttonFillOver': 'pink', '_buttonStrokeOver': 'red' }, $(go.TextBlock, 'pink over')), $('CheckBox', 'choice5', { 'Button.width': 32, 'Button.height': 32 }, $(go.TextBlock, 'BIG', { font: 'bold 12pt sans-serif' })), $('CheckBox', 'choice6', {
|
||||
'Button.width': 20, 'Button.height': 20,
|
||||
'ButtonBorder.figure': 'Circle', 'ButtonBorder.stroke': 'blue',
|
||||
'ButtonIcon.figure': 'Circle', 'ButtonIcon.fill': 'blue',
|
||||
'ButtonIcon.strokeWidth': 0, 'ButtonIcon.desiredSize': new go.Size(10, 10)
|
||||
}, $(go.TextBlock, 'blue circle')), $('CheckBox', 'choice7', go.Panel.Vertical, $(go.TextBlock, 'vertical'))),
|
||||
// the second column is a list of CheckBoxes
|
||||
$(go.Panel, 'Table', {
|
||||
row: 1, column: 1,
|
||||
alignment: go.Spot.Top,
|
||||
minSize: new go.Size(50, NaN),
|
||||
itemTemplate: $('CheckBox', 'checked', go.Panel.TableRow, $(go.TextBlock, // align text towards the right, next to the Button
|
||||
{ column: 0, alignment: go.Spot.Right }, new go.Binding('text', 'name')), { 'Button.column': 1 } // put Button in second column, to the right of text
|
||||
)
|
||||
}, new go.Binding('itemArray', 'items')),
|
||||
// now a checkbox at the bottom of the whole table
|
||||
$('CheckBox', '', // not data bound
|
||||
{ row: 3, columnSpan: 2, alignment: go.Spot.Left },
|
||||
// this checkbox is not bound to model data, but it does toggle the Part.movable
|
||||
// property of the Node that this is in
|
||||
$(go.TextBlock, 'Node is not movable'), {
|
||||
'_doClick': function (e, obj) {
|
||||
if (obj.part !== null)
|
||||
obj.part.movable = !obj.part.movable; // toggle the Part.movable flag
|
||||
}
|
||||
})));
|
||||
// but use the default Link template, by not setting Diagram.linkTemplate
|
||||
// create the model data that will be represented by Nodes and Links
|
||||
myDiagram.model =
|
||||
$(go.GraphLinksModel, {
|
||||
copiesArrays: true,
|
||||
copiesArrayObjects: true,
|
||||
'Changed': function (e) {
|
||||
if (e.isTransactionFinished) {
|
||||
var elt = document.getElementById('mySavedModel');
|
||||
if (elt !== null)
|
||||
elt.textContent = myDiagram.model.toJson();
|
||||
}
|
||||
},
|
||||
nodeDataArray: [
|
||||
{
|
||||
key: 'Alpha', color: 'lightblue', choice1: true, choice2: true, choice3: true, choice4: true, choice5: true, choice6: true, choice7: true,
|
||||
items: [{ name: 'item 0' },
|
||||
{ name: 'item 1' },
|
||||
{ name: 'item 2' }]
|
||||
},
|
||||
{
|
||||
key: 'Beta', color: 'orange',
|
||||
items: [{ name: 'B1', checked: false },
|
||||
{ name: 'Bee2', checked: true }]
|
||||
},
|
||||
{
|
||||
key: 'Gamma', color: 'lightgreen',
|
||||
items: [{ name: 'C-one', checked: true },
|
||||
{ name: 'C-two', checked: true },
|
||||
{ name: 'C-three' }]
|
||||
},
|
||||
{
|
||||
key: 'Delta', color: 'pink', choice1: true, choice2: false,
|
||||
items: []
|
||||
}
|
||||
],
|
||||
linkDataArray: [
|
||||
{ from: 'Alpha', to: 'Beta' },
|
||||
{ from: 'Alpha', to: 'Gamma' },
|
||||
{ from: 'Gamma', to: 'Delta' },
|
||||
{ from: 'Delta', to: 'Alpha' }
|
||||
]
|
||||
});
|
||||
// Attach to the window for console manipulation
|
||||
window.myDiagram = myDiagram;
|
||||
}
|
||||
exports.init = init;
|
||||
});
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
|
||||
export function init() {
|
||||
if ((window as any).goSamples) (window as any).goSamples(); // init for these samples -- you don't need to call this
|
||||
|
||||
const $ = go.GraphObject.make; // for conciseness in defining templates
|
||||
|
||||
const myDiagram = $(go.Diagram, 'myDiagramDiv', // create a Diagram for the DIV HTML element
|
||||
{
|
||||
'undoManager.isEnabled': true // enable undo & redo
|
||||
});
|
||||
|
||||
// this template includes a lot of CheckBoxes, each configured in different manners
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, 'Auto', // the Shape will go around the whole table
|
||||
$(go.Shape, { strokeWidth: 0 }, // no border
|
||||
new go.Binding('fill', 'color')),
|
||||
$(go.Panel, 'Table',
|
||||
{ padding: 3 },
|
||||
$(go.TextBlock,
|
||||
{ row: 0, column: 0, columnSpan: 2 },
|
||||
{ margin: 3, font: 'bold 10pt sans-serif' }, // some room around the bold text
|
||||
new go.Binding('text', 'key')),
|
||||
// the first column has an assortment of CheckBoxes
|
||||
$(go.Panel, 'Vertical',
|
||||
{ row: 1, column: 0, defaultAlignment: go.Spot.Left },
|
||||
$('CheckBox', 'choice1',
|
||||
$(go.TextBlock, 'default')
|
||||
),
|
||||
$('CheckBox', 'choice2',
|
||||
{ 'ButtonIcon.stroke': 'green' },
|
||||
$(go.TextBlock, 'green')
|
||||
),
|
||||
$('CheckBox', 'choice3',
|
||||
{ 'ButtonIcon.stroke': 'red', 'ButtonIcon.figure': 'XLine' },
|
||||
$(go.TextBlock, 'red X')
|
||||
),
|
||||
$('CheckBox', 'choice4',
|
||||
{ '_buttonFillOver': 'pink', '_buttonStrokeOver': 'red' },
|
||||
$(go.TextBlock, 'pink over')
|
||||
),
|
||||
$('CheckBox', 'choice5',
|
||||
{ 'Button.width': 32, 'Button.height': 32 },
|
||||
$(go.TextBlock, 'BIG',
|
||||
{ font: 'bold 12pt sans-serif' })
|
||||
),
|
||||
$('CheckBox', 'choice6',
|
||||
{
|
||||
'Button.width': 20, 'Button.height': 20,
|
||||
'ButtonBorder.figure': 'Circle', 'ButtonBorder.stroke': 'blue',
|
||||
'ButtonIcon.figure': 'Circle', 'ButtonIcon.fill': 'blue',
|
||||
'ButtonIcon.strokeWidth': 0, 'ButtonIcon.desiredSize': new go.Size(10, 10)
|
||||
},
|
||||
$(go.TextBlock, 'blue circle')
|
||||
),
|
||||
$('CheckBox', 'choice7', go.Panel.Vertical,
|
||||
$(go.TextBlock, 'vertical')
|
||||
)
|
||||
),
|
||||
// the second column is a list of CheckBoxes
|
||||
$(go.Panel, 'Table',
|
||||
{
|
||||
row: 1, column: 1,
|
||||
alignment: go.Spot.Top,
|
||||
minSize: new go.Size(50, NaN),
|
||||
itemTemplate:
|
||||
$('CheckBox', 'checked', go.Panel.TableRow,
|
||||
$(go.TextBlock, // align text towards the right, next to the Button
|
||||
{ column: 0, alignment: go.Spot.Right },
|
||||
new go.Binding('text', 'name')),
|
||||
{ 'Button.column': 1 } // put Button in second column, to the right of text
|
||||
)
|
||||
},
|
||||
new go.Binding('itemArray', 'items')
|
||||
),
|
||||
// now a checkbox at the bottom of the whole table
|
||||
$('CheckBox', '', // not data bound
|
||||
{ row: 3, columnSpan: 2, alignment: go.Spot.Left },
|
||||
// this checkbox is not bound to model data, but it does toggle the Part.movable
|
||||
// property of the Node that this is in
|
||||
$(go.TextBlock, 'Node is not movable'),
|
||||
{ // _doClick is executed within a transaction by the CheckBoxButton click function
|
||||
'_doClick': function (e: go.DiagramEvent, obj: go.GraphObject) {
|
||||
if (obj.part !== null) obj.part.movable = !obj.part.movable; // toggle the Part.movable flag
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// but use the default Link template, by not setting Diagram.linkTemplate
|
||||
|
||||
// create the model data that will be represented by Nodes and Links
|
||||
myDiagram.model =
|
||||
$(go.GraphLinksModel,
|
||||
{
|
||||
copiesArrays: true,
|
||||
copiesArrayObjects: true,
|
||||
'Changed': function (e: go.ChangedEvent) {
|
||||
if (e.isTransactionFinished) {
|
||||
const elt = document.getElementById('mySavedModel');
|
||||
if (elt !== null) elt.textContent = myDiagram.model.toJson();
|
||||
}
|
||||
},
|
||||
nodeDataArray:
|
||||
[
|
||||
{
|
||||
key: 'Alpha', color: 'lightblue', choice1: true, choice2: true, choice3: true, choice4: true, choice5: true, choice6: true, choice7: true,
|
||||
items: [{ name: 'item 0' },
|
||||
{ name: 'item 1' },
|
||||
{ name: 'item 2' }]
|
||||
},
|
||||
{
|
||||
key: 'Beta', color: 'orange',
|
||||
items: [{ name: 'B1', checked: false },
|
||||
{ name: 'Bee2', checked: true }]
|
||||
},
|
||||
{
|
||||
key: 'Gamma', color: 'lightgreen',
|
||||
items: [{ name: 'C-one', checked: true },
|
||||
{ name: 'C-two', checked: true },
|
||||
{ name: 'C-three' }]
|
||||
},
|
||||
{
|
||||
key: 'Delta', color: 'pink', choice1: true, choice2: false,
|
||||
items: []
|
||||
}
|
||||
],
|
||||
linkDataArray:
|
||||
[
|
||||
{ from: 'Alpha', to: 'Beta' },
|
||||
{ from: 'Alpha', to: 'Gamma' },
|
||||
{ from: 'Gamma', to: 'Delta' },
|
||||
{ from: 'Delta', to: 'Alpha' }
|
||||
]
|
||||
});
|
||||
|
||||
// Attach to the window for console manipulation
|
||||
(window as any).myDiagram = myDiagram;
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Resizing Rows and Columns in a Table Panel</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="TypeScript: Using the RowResizingTool and ColumnResizingTool to allow the user to change the size of rows and columns in a Table Panel." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../samples/assets/require.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
function init() {
|
||||
require(["ColumnResizingScript"], function(app) {
|
||||
app.init();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:400px"></div>
|
||||
<p>
|
||||
This makes use of two tools, defined in their own files: <a href="ColumnResizingTool.ts">ColumnResizingTool.ts</a> and <a href="RowResizingTool.ts">RowResizingTool.ts</a>.
|
||||
Each tool adds an <a>Adornment</a> to a selected node that has a resize handle for each column or each row of a "Table" <a>Panel</a>.
|
||||
While resizing, you can press the Tab or the Delete key in order to stop the tool and restore the column or row to its natural size.
|
||||
</p>
|
||||
<p>
|
||||
This sample also adds TwoWay Bindings to the <a>RowColumnDefinition.width</a> property for the columns.
|
||||
Each column width is stored in the corresponding index of the node data's "widths" property, which must be an Array of numbers.
|
||||
The default value is NaN, allowing the column to occupy its natural width.
|
||||
Note that there are <b>no</b> Bindings for the row heights.
|
||||
</p>
|
||||
<p>The model data, automatically updated after each change or undo or redo:</p>
|
||||
<textarea id="mySavedModel" style="width:100%;height:300px"></textarea>
|
||||
<p>See also the <a href="../samples/addRemoveColumns.html">Add & Remove Rows & Columns</a> sample.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js", "./ColumnResizingTool.js", "./RowResizingTool.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.init = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
var ColumnResizingTool_js_1 = require("./ColumnResizingTool.js");
|
||||
var RowResizingTool_js_1 = require("./RowResizingTool.js");
|
||||
function init() {
|
||||
if (window.goSamples)
|
||||
window.goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make; // for conciseness in defining templates
|
||||
var myDiagram = $(go.Diagram, 'myDiagramDiv', {
|
||||
validCycle: go.Diagram.CycleNotDirected,
|
||||
'undoManager.isEnabled': true
|
||||
});
|
||||
myDiagram.toolManager.mouseDownTools.add(new RowResizingTool_js_1.RowResizingTool());
|
||||
myDiagram.toolManager.mouseDownTools.add(new ColumnResizingTool_js_1.ColumnResizingTool());
|
||||
// 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',
|
||||
fromSpot: go.Spot.Right,
|
||||
toSpot: go.Spot.Left,
|
||||
// allow drawing links from or to this port:
|
||||
fromLinkable: true, toLinkable: true
|
||||
}, $(go.Shape, {
|
||||
column: 0,
|
||||
width: 12, height: 12, 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, {
|
||||
column: 1,
|
||||
margin: new go.Margin(0, 2),
|
||||
stretch: go.GraphObject.Horizontal,
|
||||
font: 'bold 13px sans-serif',
|
||||
wrap: go.TextBlock.None,
|
||||
overflow: go.TextBlock.OverflowEllipsis,
|
||||
// and disallow drawing links from or to this text:
|
||||
fromLinkable: false, toLinkable: false
|
||||
}, new go.Binding('text', 'name')), $(go.TextBlock, {
|
||||
column: 2,
|
||||
margin: new go.Margin(0, 2),
|
||||
stretch: go.GraphObject.Horizontal,
|
||||
font: '13px sans-serif',
|
||||
maxLines: 3,
|
||||
overflow: go.TextBlock.OverflowEllipsis,
|
||||
editable: true
|
||||
}, new go.Binding('text', 'info').makeTwoWay()));
|
||||
// Return initialization for a RowColumnDefinition, specifying a particular column
|
||||
// and adding a Binding of RowColumnDefinition.width to the IDX'th number in the data.widths Array
|
||||
function makeWidthBinding(idx) {
|
||||
// These two conversion functions are closed over the IDX variable.
|
||||
// This source-to-target conversion extracts a number from the Array at the given index.
|
||||
function getColumnWidth(arr) {
|
||||
if (Array.isArray(arr) && idx < arr.length)
|
||||
return arr[idx];
|
||||
return NaN;
|
||||
}
|
||||
// This target-to-source conversion sets a number in the Array at the given index.
|
||||
function setColumnWidth(w, data) {
|
||||
var arr = data.widths;
|
||||
if (!arr)
|
||||
arr = [];
|
||||
if (idx >= arr.length) {
|
||||
for (var i = arr.length; i <= idx; i++)
|
||||
arr[i] = NaN; // default to NaN
|
||||
}
|
||||
arr[idx] = w;
|
||||
return arr; // need to return the Array (as the value of data.widths)
|
||||
}
|
||||
return [
|
||||
{ column: idx },
|
||||
new go.Binding('width', 'widths', getColumnWidth).makeTwoWay(setColumnWidth)
|
||||
];
|
||||
}
|
||||
// This template represents a whole "record".
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, 'Auto', 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', { stretch: go.GraphObject.Horizontal, alignment: go.Spot.TopLeft },
|
||||
// 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', {
|
||||
name: 'TABLE', stretch: go.GraphObject.Horizontal,
|
||||
minSize: new go.Size(100, 10),
|
||||
defaultAlignment: go.Spot.Left,
|
||||
defaultStretch: go.GraphObject.Horizontal,
|
||||
defaultColumnSeparatorStroke: 'gray',
|
||||
defaultRowSeparatorStroke: 'gray',
|
||||
itemTemplate: fieldTemplate
|
||||
}, $(go.RowColumnDefinition, makeWidthBinding(0)), $(go.RowColumnDefinition, makeWidthBinding(1)), $(go.RowColumnDefinition, makeWidthBinding(2)), new go.Binding('itemArray', 'fields')) // end Table Panel of items
|
||||
) // end Vertical Panel
|
||||
); // end Node
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link, { relinkableFrom: true, relinkableTo: true, toShortLength: 4 }, // let user reconnect links
|
||||
$(go.Shape, { strokeWidth: 1.5 }), $(go.Shape, { toArrow: 'Standard', stroke: null }));
|
||||
myDiagram.model =
|
||||
$(go.GraphLinksModel, {
|
||||
linkFromPortIdProperty: 'fromPort',
|
||||
linkToPortIdProperty: 'toPort',
|
||||
// automatically update the model that is shown on this page
|
||||
'Changed': function (e) {
|
||||
if (e.isTransactionFinished)
|
||||
showModel();
|
||||
},
|
||||
nodeDataArray: [
|
||||
{
|
||||
key: 'Record1',
|
||||
widths: [NaN, NaN, 60],
|
||||
fields: [
|
||||
{ name: 'field1', info: 'first field', 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',
|
||||
widths: [NaN, NaN, NaN],
|
||||
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: '250 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' }
|
||||
]
|
||||
});
|
||||
showModel(); // show the diagram's initial model
|
||||
function showModel() {
|
||||
var elt = document.getElementById('mySavedModel');
|
||||
if (elt !== null)
|
||||
elt.textContent = myDiagram.model.toJson();
|
||||
}
|
||||
// Attach to the window for console manipulation
|
||||
window.myDiagram = myDiagram;
|
||||
}
|
||||
exports.init = init;
|
||||
});
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
import { ColumnResizingTool } from './ColumnResizingTool.js';
|
||||
import { RowResizingTool } from './RowResizingTool.js';
|
||||
|
||||
export function init() {
|
||||
if ((window as any).goSamples) (window as any).goSamples(); // init for these samples -- you don't need to call this
|
||||
|
||||
const $ = go.GraphObject.make; // for conciseness in defining templates
|
||||
|
||||
const myDiagram =
|
||||
$(go.Diagram, 'myDiagramDiv',
|
||||
{
|
||||
validCycle: go.Diagram.CycleNotDirected, // don't allow loops
|
||||
'undoManager.isEnabled': true
|
||||
});
|
||||
|
||||
myDiagram.toolManager.mouseDownTools.add(new RowResizingTool());
|
||||
myDiagram.toolManager.mouseDownTools.add(new ColumnResizingTool());
|
||||
|
||||
// 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.
|
||||
const 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,
|
||||
{
|
||||
column: 0,
|
||||
width: 12, height: 12, 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,
|
||||
{
|
||||
column: 1,
|
||||
margin: new go.Margin(0, 2),
|
||||
stretch: go.GraphObject.Horizontal,
|
||||
font: 'bold 13px sans-serif',
|
||||
wrap: go.TextBlock.None,
|
||||
overflow: go.TextBlock.OverflowEllipsis,
|
||||
// and disallow drawing links from or to this text:
|
||||
fromLinkable: false, toLinkable: false
|
||||
},
|
||||
new go.Binding('text', 'name')),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
column: 2,
|
||||
margin: new go.Margin(0, 2),
|
||||
stretch: go.GraphObject.Horizontal,
|
||||
font: '13px sans-serif',
|
||||
maxLines: 3,
|
||||
overflow: go.TextBlock.OverflowEllipsis,
|
||||
editable: true
|
||||
},
|
||||
new go.Binding('text', 'info').makeTwoWay())
|
||||
);
|
||||
|
||||
// Return initialization for a RowColumnDefinition, specifying a particular column
|
||||
// and adding a Binding of RowColumnDefinition.width to the IDX'th number in the data.widths Array
|
||||
function makeWidthBinding(idx: number) {
|
||||
// These two conversion functions are closed over the IDX variable.
|
||||
// This source-to-target conversion extracts a number from the Array at the given index.
|
||||
function getColumnWidth(arr: Array<number>) {
|
||||
if (Array.isArray(arr) && idx < arr.length) return arr[idx];
|
||||
return NaN;
|
||||
}
|
||||
// This target-to-source conversion sets a number in the Array at the given index.
|
||||
function setColumnWidth(w: number, data: any): any {
|
||||
let arr = data.widths;
|
||||
if (!arr) arr = [];
|
||||
if (idx >= arr.length) {
|
||||
for (let i = arr.length; i <= idx; i++) arr[i] = NaN; // default to NaN
|
||||
}
|
||||
arr[idx] = w;
|
||||
return arr; // need to return the Array (as the value of data.widths)
|
||||
}
|
||||
return [
|
||||
{ column: idx },
|
||||
new go.Binding('width', 'widths', getColumnWidth).makeTwoWay(setColumnWidth)
|
||||
];
|
||||
}
|
||||
|
||||
// This template represents a whole "record".
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, 'Auto',
|
||||
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',
|
||||
{ stretch: go.GraphObject.Horizontal, alignment: go.Spot.TopLeft },
|
||||
// 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',
|
||||
{
|
||||
name: 'TABLE', stretch: go.GraphObject.Horizontal,
|
||||
minSize: new go.Size(100, 10),
|
||||
defaultAlignment: go.Spot.Left,
|
||||
defaultStretch: go.GraphObject.Horizontal,
|
||||
defaultColumnSeparatorStroke: 'gray',
|
||||
defaultRowSeparatorStroke: 'gray',
|
||||
itemTemplate: fieldTemplate
|
||||
},
|
||||
$(go.RowColumnDefinition, makeWidthBinding(0)),
|
||||
$(go.RowColumnDefinition, makeWidthBinding(1)),
|
||||
$(go.RowColumnDefinition, makeWidthBinding(2)),
|
||||
new go.Binding('itemArray', 'fields')
|
||||
) // end Table Panel of items
|
||||
) // end Vertical Panel
|
||||
); // end Node
|
||||
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link,
|
||||
{ relinkableFrom: true, relinkableTo: true, toShortLength: 4 }, // let user reconnect links
|
||||
$(go.Shape, { strokeWidth: 1.5 }),
|
||||
$(go.Shape, { toArrow: 'Standard', stroke: null })
|
||||
);
|
||||
|
||||
myDiagram.model =
|
||||
$(go.GraphLinksModel,
|
||||
{
|
||||
linkFromPortIdProperty: 'fromPort',
|
||||
linkToPortIdProperty: 'toPort',
|
||||
// automatically update the model that is shown on this page
|
||||
'Changed': function (e: go.ChangedEvent) {
|
||||
if (e.isTransactionFinished) showModel();
|
||||
},
|
||||
nodeDataArray: [
|
||||
{
|
||||
key: 'Record1',
|
||||
widths: [NaN, NaN, 60],
|
||||
fields: [
|
||||
{ name: 'field1', info: 'first field', 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',
|
||||
widths: [NaN, NaN, NaN],
|
||||
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: '250 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' }
|
||||
]
|
||||
});
|
||||
|
||||
showModel(); // show the diagram's initial model
|
||||
|
||||
function showModel() {
|
||||
const elt = document.getElementById('mySavedModel');
|
||||
if (elt !== null) elt.textContent = myDiagram.model.toJson();
|
||||
}
|
||||
|
||||
// Attach to the window for console manipulation
|
||||
(window as any).myDiagram = myDiagram;
|
||||
}
|
||||
+324
@@ -0,0 +1,324 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ColumnResizingTool = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
/**
|
||||
* The ColumnResizingTool class lets the user resize each column of a named Table Panel in a selected Part.
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/ColumnResizing.html">Column Resizing</a> sample.
|
||||
* @category Tool Extension
|
||||
*/
|
||||
var ColumnResizingTool = /** @class */ (function (_super) {
|
||||
__extends(ColumnResizingTool, _super);
|
||||
/**
|
||||
* Constructs a ColumnResizingTool and sets the handle and name of the tool.
|
||||
*/
|
||||
function ColumnResizingTool() {
|
||||
var _this = _super.call(this) || this;
|
||||
_this._tableName = 'TABLE';
|
||||
// internal state
|
||||
_this._handle = null;
|
||||
_this._adornedTable = null;
|
||||
var h = new go.Shape();
|
||||
h.geometryString = 'M0 0 V14 M2 0 V14';
|
||||
h.desiredSize = new go.Size(2, 14);
|
||||
h.cursor = 'col-resize';
|
||||
h.geometryStretch = go.GraphObject.None;
|
||||
h.background = 'rgba(255,255,255,0.5)';
|
||||
h.stroke = 'rgba(30,144,255,0.5)';
|
||||
_this._handleArchetype = h;
|
||||
_this.name = 'ColumnResizing';
|
||||
return _this;
|
||||
}
|
||||
Object.defineProperty(ColumnResizingTool.prototype, "handleArchetype", {
|
||||
/**
|
||||
* Gets or sets small GraphObject that is copied as a resize handle for each column.
|
||||
* This tool expects that this object's {@link GraphObject#desiredSize} (a.k.a width and height) has been set to real numbers.
|
||||
*
|
||||
* The default value is a {@link Shape} that is a narrow rectangle.
|
||||
*/
|
||||
get: function () { return this._handleArchetype; },
|
||||
set: function (val) { this._handleArchetype = val; },
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(ColumnResizingTool.prototype, "tableName", {
|
||||
/**
|
||||
* Gets or sets the name of the Table Panel to be resized.
|
||||
*
|
||||
* The default value is the name "TABLE".
|
||||
*/
|
||||
get: function () { return this._tableName; },
|
||||
set: function (val) { this._tableName = val; },
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(ColumnResizingTool.prototype, "handle", {
|
||||
/**
|
||||
* This read-only property returns the {@link GraphObject} that is the tool handle being dragged by the user.
|
||||
* This will be contained by an {@link Adornment} whose category is "ColumnResizing".
|
||||
* Its {@link Adornment#adornedObject} is the same as the {@link #adornedTable}.
|
||||
*/
|
||||
get: function () { return this._handle; },
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(ColumnResizingTool.prototype, "adornedTable", {
|
||||
/**
|
||||
* This read-only property returns the {@link Panel} of type {@link Panel.Table} whose columns are being resized.
|
||||
* This must be contained within the selected {@link Part}.
|
||||
*/
|
||||
get: function () { return this._adornedTable; },
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
/**
|
||||
* Show an {@link Adornment} with a resize handle at each column.
|
||||
* Don't show anything if {@link #tableName} doesn't identify a {@link Panel}
|
||||
* that has a {@link Panel#type} of type {@link Panel.Table}.
|
||||
*/
|
||||
ColumnResizingTool.prototype.updateAdornments = function (part) {
|
||||
if (part === null || part instanceof go.Link)
|
||||
return; // this tool never applies to Links
|
||||
if (part.isSelected && !this.diagram.isReadOnly) {
|
||||
var selelt = part.findObject(this.tableName);
|
||||
if (selelt instanceof go.Panel && selelt.actualBounds.isReal() && selelt.isVisibleObject() &&
|
||||
part.actualBounds.isReal() && part.isVisible() &&
|
||||
selelt.type === go.Panel.Table) {
|
||||
var table_1 = selelt;
|
||||
var adornment = part.findAdornment(this.name);
|
||||
if (adornment === null) {
|
||||
adornment = this.makeAdornment(table_1);
|
||||
part.addAdornment(this.name, adornment);
|
||||
}
|
||||
if (adornment !== null) {
|
||||
var pad_1 = table_1.padding;
|
||||
var numcols_1 = table_1.columnCount;
|
||||
// update the position/alignment of each handle
|
||||
adornment.elements.each(function (h) {
|
||||
if (!h.pickable)
|
||||
return;
|
||||
var coldef = table_1.getColumnDefinition(h.column);
|
||||
var wid = coldef.actual;
|
||||
if (wid > 0)
|
||||
wid = coldef.total;
|
||||
var sep = 0;
|
||||
// find next non-zero-width column's separatorStrokeWidth
|
||||
var idx = h.column + 1;
|
||||
while (idx < numcols_1 && table_1.getColumnDefinition(idx).actual === 0)
|
||||
idx++;
|
||||
if (idx < numcols_1) {
|
||||
sep = table_1.getColumnDefinition(idx).separatorStrokeWidth;
|
||||
if (isNaN(sep))
|
||||
sep = table_1.defaultColumnSeparatorStrokeWidth;
|
||||
}
|
||||
h.alignment = new go.Spot(0, 0, pad_1.left + coldef.position + wid + sep / 2, pad_1.top + h.height / 2);
|
||||
});
|
||||
adornment.locationObject.desiredSize = table_1.actualBounds.size;
|
||||
adornment.location = table_1.getDocumentPoint(adornment.locationSpot);
|
||||
adornment.angle = table_1.getDocumentAngle();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
part.removeAdornment(this.name);
|
||||
};
|
||||
/**
|
||||
* @hidden @internal
|
||||
* @param {Panel} table the Table Panel whose columns may be resized
|
||||
* @return {Adornment}
|
||||
*/
|
||||
ColumnResizingTool.prototype.makeAdornment = function (table) {
|
||||
// the Adornment is a Spot Panel holding resize handles
|
||||
var adornment = new go.Adornment();
|
||||
adornment.category = this.name;
|
||||
adornment.adornedObject = table;
|
||||
adornment.type = go.Panel.Spot;
|
||||
adornment.locationObjectName = 'BLOCK';
|
||||
// create the "main" element of the Spot Panel
|
||||
var block = new go.TextBlock(); // doesn't matter much what this is
|
||||
block.name = 'BLOCK';
|
||||
block.pickable = false; // it's transparent and not pickable
|
||||
adornment.add(block);
|
||||
// now add resize handles for each column
|
||||
for (var i = 0; i < table.columnCount; i++) {
|
||||
var coldef = table.getColumnDefinition(i);
|
||||
var h = this.makeHandle(table, coldef);
|
||||
if (h !== null)
|
||||
adornment.add(h);
|
||||
}
|
||||
return adornment;
|
||||
};
|
||||
/**
|
||||
* @hidden @internal
|
||||
* @param {Panel} table the Table Panel whose columns may be resized
|
||||
* @param {RowColumnDefinition} coldef the column definition to be resized
|
||||
* @return a copy of the {@link #handleArchetype}
|
||||
*/
|
||||
ColumnResizingTool.prototype.makeHandle = function (table, coldef) {
|
||||
var h = this.handleArchetype;
|
||||
if (h === null)
|
||||
return null;
|
||||
var c = h.copy();
|
||||
c.column = coldef.index;
|
||||
return c;
|
||||
};
|
||||
/**
|
||||
* This tool may run when there is a mouse-down event on a "ColumnResizing" handle,
|
||||
* the diagram is not read-only, the left mouse button is being used,
|
||||
* and this tool's adornment's resize handle is at the current mouse point.
|
||||
*/
|
||||
ColumnResizingTool.prototype.canStart = function () {
|
||||
if (!this.isEnabled)
|
||||
return false;
|
||||
var diagram = this.diagram;
|
||||
if (diagram.isReadOnly)
|
||||
return false;
|
||||
if (!diagram.lastInput.left)
|
||||
return false;
|
||||
var h = this.findToolHandleAt(diagram.firstInput.documentPoint, this.name);
|
||||
return (h !== null);
|
||||
};
|
||||
/**
|
||||
* Find the {@link #handle}, ensure type {@link Panel.Table}, capture the mouse, and start a transaction.
|
||||
*
|
||||
* If the call to {@link Tool#findToolHandleAt} finds no "ColumnResizing" tool handle, this method returns without activating this tool.
|
||||
*/
|
||||
ColumnResizingTool.prototype.doActivate = function () {
|
||||
var diagram = this.diagram;
|
||||
this._handle = this.findToolHandleAt(diagram.firstInput.documentPoint, this.name);
|
||||
if (this.handle === null)
|
||||
return;
|
||||
var panel = this.handle.part.adornedObject;
|
||||
if (!panel || panel.type !== go.Panel.Table)
|
||||
return;
|
||||
this._adornedTable = panel;
|
||||
diagram.isMouseCaptured = true;
|
||||
this.startTransaction(this.name);
|
||||
this.isActive = true;
|
||||
};
|
||||
/**
|
||||
* Stop the current transaction and release the mouse.
|
||||
*/
|
||||
ColumnResizingTool.prototype.doDeactivate = function () {
|
||||
this.stopTransaction();
|
||||
this._handle = null;
|
||||
this._adornedTable = null;
|
||||
var diagram = this.diagram;
|
||||
diagram.isMouseCaptured = false;
|
||||
this.isActive = false;
|
||||
};
|
||||
/**
|
||||
* Call {@link #resize} with a new size determined by the current mouse point.
|
||||
* This determines the new bounds by calling {@link #computeResize}.
|
||||
*/
|
||||
ColumnResizingTool.prototype.doMouseMove = function () {
|
||||
var diagram = this.diagram;
|
||||
if (this.isActive) {
|
||||
var newpt = this.computeResize(diagram.lastInput.documentPoint);
|
||||
this.resize(newpt);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Call {@link #resize} with the final bounds based on the most recent mouse point, and commit the transaction.
|
||||
* This determines the new bounds by calling {@link #computeResize}.
|
||||
*/
|
||||
ColumnResizingTool.prototype.doMouseUp = function () {
|
||||
var diagram = this.diagram;
|
||||
if (this.isActive) {
|
||||
var newpt = this.computeResize(diagram.lastInput.documentPoint);
|
||||
this.resize(newpt);
|
||||
this.transactionResult = this.name; // success
|
||||
}
|
||||
this.stopTool();
|
||||
};
|
||||
/**
|
||||
* Change the {@link RowColumnDefinition#width} of the column being resized
|
||||
* to a value corresponding to the given mouse point.
|
||||
* @param {Point} newPoint the value returned by the call to {@link #computeResize}
|
||||
*/
|
||||
ColumnResizingTool.prototype.resize = function (newPoint) {
|
||||
var table = this.adornedTable;
|
||||
if (table === null)
|
||||
return;
|
||||
var h = this.handle;
|
||||
if (h === null)
|
||||
return;
|
||||
var pad = table.padding;
|
||||
var numcols = table.columnCount;
|
||||
var locpt = table.getLocalPoint(newPoint);
|
||||
var coldef = table.getColumnDefinition(h.column);
|
||||
var sep = 0;
|
||||
var idx = h.column + 1;
|
||||
while (idx < numcols && table.getColumnDefinition(idx).actual === 0)
|
||||
idx++;
|
||||
if (idx < numcols) {
|
||||
sep = table.getColumnDefinition(idx).separatorStrokeWidth;
|
||||
if (isNaN(sep))
|
||||
sep = table.defaultColumnSeparatorStrokeWidth;
|
||||
}
|
||||
coldef.width = Math.max(0, locpt.x - pad.left - coldef.position - (coldef.total - coldef.actual) - sep / 2);
|
||||
};
|
||||
/**
|
||||
* This can be overridden in order to customize the resizing process.
|
||||
* @expose
|
||||
* @param {Point} p the point where the handle is being dragged
|
||||
* @return {Point}
|
||||
*/
|
||||
ColumnResizingTool.prototype.computeResize = function (p) {
|
||||
return p;
|
||||
};
|
||||
/**
|
||||
* Pressing the Delete key removes any column width setting and stops this tool.
|
||||
*/
|
||||
ColumnResizingTool.prototype.doKeyDown = function () {
|
||||
if (!this.isActive)
|
||||
return;
|
||||
var e = this.diagram.lastInput;
|
||||
if (e.key === 'Del' || e.key === '\t') { // remove width setting
|
||||
if (this.adornedTable !== null && this.handle !== null) {
|
||||
var coldef = this.adornedTable.getColumnDefinition(this.handle.column);
|
||||
coldef.width = NaN;
|
||||
this.transactionResult = this.name; // success
|
||||
this.stopTool();
|
||||
return;
|
||||
}
|
||||
}
|
||||
_super.prototype.doKeyDown.call(this);
|
||||
};
|
||||
return ColumnResizingTool;
|
||||
}(go.Tool));
|
||||
exports.ColumnResizingTool = ColumnResizingTool;
|
||||
});
|
||||
+285
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
|
||||
/**
|
||||
* The ColumnResizingTool class lets the user resize each column of a named Table Panel in a selected Part.
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/ColumnResizing.html">Column Resizing</a> sample.
|
||||
* @category Tool Extension
|
||||
*/
|
||||
export class ColumnResizingTool extends go.Tool {
|
||||
private _handleArchetype: go.Shape;
|
||||
private _tableName: string = 'TABLE';
|
||||
|
||||
// internal state
|
||||
private _handle: go.GraphObject | null = null;
|
||||
private _adornedTable: go.Panel | null = null;
|
||||
|
||||
/**
|
||||
* Constructs a ColumnResizingTool and sets the handle and name of the tool.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
const h: go.Shape = new go.Shape();
|
||||
h.geometryString = 'M0 0 V14 M2 0 V14';
|
||||
h.desiredSize = new go.Size(2, 14);
|
||||
h.cursor = 'col-resize';
|
||||
h.geometryStretch = go.GraphObject.None;
|
||||
h.background = 'rgba(255,255,255,0.5)';
|
||||
h.stroke = 'rgba(30,144,255,0.5)';
|
||||
this._handleArchetype = h;
|
||||
this.name = 'ColumnResizing';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets small GraphObject that is copied as a resize handle for each column.
|
||||
* This tool expects that this object's {@link GraphObject#desiredSize} (a.k.a width and height) has been set to real numbers.
|
||||
*
|
||||
* The default value is a {@link Shape} that is a narrow rectangle.
|
||||
*/
|
||||
get handleArchetype(): go.Shape { return this._handleArchetype; }
|
||||
set handleArchetype(val: go.Shape) { this._handleArchetype = val; }
|
||||
|
||||
/**
|
||||
* Gets or sets the name of the Table Panel to be resized.
|
||||
*
|
||||
* The default value is the name "TABLE".
|
||||
*/
|
||||
get tableName(): string { return this._tableName; }
|
||||
set tableName(val: string) { this._tableName = val; }
|
||||
|
||||
/**
|
||||
* This read-only property returns the {@link GraphObject} that is the tool handle being dragged by the user.
|
||||
* This will be contained by an {@link Adornment} whose category is "ColumnResizing".
|
||||
* Its {@link Adornment#adornedObject} is the same as the {@link #adornedTable}.
|
||||
*/
|
||||
get handle(): go.GraphObject | null { return this._handle; }
|
||||
|
||||
/**
|
||||
* This read-only property returns the {@link Panel} of type {@link Panel.Table} whose columns are being resized.
|
||||
* This must be contained within the selected {@link Part}.
|
||||
*/
|
||||
get adornedTable(): go.Panel | null { return this._adornedTable; }
|
||||
|
||||
/**
|
||||
* Show an {@link Adornment} with a resize handle at each column.
|
||||
* Don't show anything if {@link #tableName} doesn't identify a {@link Panel}
|
||||
* that has a {@link Panel#type} of type {@link Panel.Table}.
|
||||
*/
|
||||
public updateAdornments(part: go.Part): void {
|
||||
if (part === null || part instanceof go.Link) return; // this tool never applies to Links
|
||||
if (part.isSelected && !this.diagram.isReadOnly) {
|
||||
const selelt = part.findObject(this.tableName);
|
||||
if (selelt instanceof go.Panel && selelt.actualBounds.isReal() && selelt.isVisibleObject() &&
|
||||
part.actualBounds.isReal() && part.isVisible() &&
|
||||
selelt.type === go.Panel.Table) {
|
||||
const table = selelt;
|
||||
let adornment = part.findAdornment(this.name);
|
||||
if (adornment === null) {
|
||||
adornment = this.makeAdornment(table);
|
||||
part.addAdornment(this.name, adornment);
|
||||
}
|
||||
if (adornment !== null) {
|
||||
const pad = table.padding as go.Margin;
|
||||
const numcols = table.columnCount;
|
||||
// update the position/alignment of each handle
|
||||
adornment.elements.each((h: go.GraphObject) => {
|
||||
if (!h.pickable) return;
|
||||
const coldef = table.getColumnDefinition(h.column);
|
||||
let wid = coldef.actual;
|
||||
if (wid > 0) wid = coldef.total;
|
||||
let sep = 0;
|
||||
// find next non-zero-width column's separatorStrokeWidth
|
||||
let idx = h.column + 1;
|
||||
while (idx < numcols && table.getColumnDefinition(idx).actual === 0) idx++;
|
||||
if (idx < numcols) {
|
||||
sep = table.getColumnDefinition(idx).separatorStrokeWidth;
|
||||
if (isNaN(sep)) sep = table.defaultColumnSeparatorStrokeWidth;
|
||||
}
|
||||
h.alignment = new go.Spot(0, 0, pad.left + coldef.position + wid + sep / 2, pad.top + h.height / 2);
|
||||
});
|
||||
adornment.locationObject.desiredSize = table.actualBounds.size;
|
||||
adornment.location = table.getDocumentPoint(adornment.locationSpot);
|
||||
adornment.angle = table.getDocumentAngle();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
part.removeAdornment(this.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden @internal
|
||||
* @param {Panel} table the Table Panel whose columns may be resized
|
||||
* @return {Adornment}
|
||||
*/
|
||||
public makeAdornment(table: go.Panel): go.Adornment {
|
||||
// the Adornment is a Spot Panel holding resize handles
|
||||
const adornment = new go.Adornment();
|
||||
adornment.category = this.name;
|
||||
adornment.adornedObject = table;
|
||||
adornment.type = go.Panel.Spot;
|
||||
adornment.locationObjectName = 'BLOCK';
|
||||
// create the "main" element of the Spot Panel
|
||||
const block = new go.TextBlock(); // doesn't matter much what this is
|
||||
block.name = 'BLOCK';
|
||||
block.pickable = false; // it's transparent and not pickable
|
||||
adornment.add(block);
|
||||
// now add resize handles for each column
|
||||
for (let i = 0; i < table.columnCount; i++) {
|
||||
const coldef = table.getColumnDefinition(i);
|
||||
const h = this.makeHandle(table, coldef);
|
||||
if (h !== null) adornment.add(h);
|
||||
}
|
||||
return adornment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden @internal
|
||||
* @param {Panel} table the Table Panel whose columns may be resized
|
||||
* @param {RowColumnDefinition} coldef the column definition to be resized
|
||||
* @return a copy of the {@link #handleArchetype}
|
||||
*/
|
||||
public makeHandle(table: go.Panel, coldef: go.RowColumnDefinition): go.GraphObject | null {
|
||||
const h = this.handleArchetype;
|
||||
if (h === null) return null;
|
||||
const c = h.copy();
|
||||
c.column = coldef.index;
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This tool may run when there is a mouse-down event on a "ColumnResizing" handle,
|
||||
* the diagram is not read-only, the left mouse button is being used,
|
||||
* and this tool's adornment's resize handle is at the current mouse point.
|
||||
*/
|
||||
public canStart(): boolean {
|
||||
if (!this.isEnabled) return false;
|
||||
|
||||
const diagram = this.diagram;
|
||||
if (diagram.isReadOnly) return false;
|
||||
if (!diagram.lastInput.left) return false;
|
||||
const h = this.findToolHandleAt(diagram.firstInput.documentPoint, this.name);
|
||||
return (h !== null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the {@link #handle}, ensure type {@link Panel.Table}, capture the mouse, and start a transaction.
|
||||
*
|
||||
* If the call to {@link Tool#findToolHandleAt} finds no "ColumnResizing" tool handle, this method returns without activating this tool.
|
||||
*/
|
||||
public doActivate(): void {
|
||||
const diagram = this.diagram;
|
||||
this._handle = this.findToolHandleAt(diagram.firstInput.documentPoint, this.name);
|
||||
if (this.handle === null) return;
|
||||
const panel = (this.handle.part as go.Adornment).adornedObject as go.Adornment;
|
||||
if (!panel || panel.type !== go.Panel.Table) return;
|
||||
this._adornedTable = panel;
|
||||
diagram.isMouseCaptured = true;
|
||||
this.startTransaction(this.name);
|
||||
this.isActive = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the current transaction and release the mouse.
|
||||
*/
|
||||
public doDeactivate(): void {
|
||||
this.stopTransaction();
|
||||
this._handle = null;
|
||||
this._adornedTable = null;
|
||||
const diagram = this.diagram;
|
||||
diagram.isMouseCaptured = false;
|
||||
this.isActive = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call {@link #resize} with a new size determined by the current mouse point.
|
||||
* This determines the new bounds by calling {@link #computeResize}.
|
||||
*/
|
||||
public doMouseMove(): void {
|
||||
const diagram = this.diagram;
|
||||
if (this.isActive) {
|
||||
const newpt = this.computeResize(diagram.lastInput.documentPoint);
|
||||
this.resize(newpt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call {@link #resize} with the final bounds based on the most recent mouse point, and commit the transaction.
|
||||
* This determines the new bounds by calling {@link #computeResize}.
|
||||
*/
|
||||
public doMouseUp(): void {
|
||||
const diagram = this.diagram;
|
||||
if (this.isActive) {
|
||||
const newpt = this.computeResize(diagram.lastInput.documentPoint);
|
||||
this.resize(newpt);
|
||||
this.transactionResult = this.name; // success
|
||||
}
|
||||
this.stopTool();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the {@link RowColumnDefinition#width} of the column being resized
|
||||
* to a value corresponding to the given mouse point.
|
||||
* @param {Point} newPoint the value returned by the call to {@link #computeResize}
|
||||
*/
|
||||
public resize(newPoint: go.Point): void {
|
||||
const table = this.adornedTable;
|
||||
if (table === null) return;
|
||||
const h = this.handle;
|
||||
if (h === null) return;
|
||||
const pad = table.padding as go.Margin;
|
||||
const numcols = table.columnCount;
|
||||
const locpt = table.getLocalPoint(newPoint);
|
||||
const coldef = table.getColumnDefinition(h.column);
|
||||
let sep = 0;
|
||||
let idx = h.column + 1;
|
||||
while (idx < numcols && table.getColumnDefinition(idx).actual === 0) idx++;
|
||||
if (idx < numcols) {
|
||||
sep = table.getColumnDefinition(idx).separatorStrokeWidth;
|
||||
if (isNaN(sep)) sep = table.defaultColumnSeparatorStrokeWidth;
|
||||
}
|
||||
coldef.width = Math.max(0, locpt.x - pad.left - coldef.position - (coldef.total - coldef.actual) - sep / 2);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This can be overridden in order to customize the resizing process.
|
||||
* @expose
|
||||
* @param {Point} p the point where the handle is being dragged
|
||||
* @return {Point}
|
||||
*/
|
||||
public computeResize(p: go.Point): go.Point {
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pressing the Delete key removes any column width setting and stops this tool.
|
||||
*/
|
||||
public doKeyDown(): void {
|
||||
if (!this.isActive) return;
|
||||
const e = this.diagram.lastInput;
|
||||
if (e.key === 'Del' || e.key === '\t') { // remove width setting
|
||||
if (this.adornedTable !== null && this.handle !== null) {
|
||||
const coldef = this.adornedTable.getColumnDefinition(this.handle.column);
|
||||
coldef.width = NaN;
|
||||
this.transactionResult = this.name; // success
|
||||
this.stopTool();
|
||||
return;
|
||||
}
|
||||
}
|
||||
super.doKeyDown();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>State Chart with Simple Curved Link Reshaping</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="TypeScript: Changing the curviness of a link using a single reshaping handle." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!-- /* Copyright 1998-2020 by Northwoods Software Corporation. */ -->
|
||||
|
||||
<script src="../samples/assets/require.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
function init() {
|
||||
require(["CurvedLinkReshapingScript"], function(app) {
|
||||
app.init();
|
||||
document.getElementById("SaveButton").onclick = app.save;
|
||||
document.getElementById("LoadButton").onclick = app.load;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="background-color: whitesmoke; border: solid 1px black; width: 100%; height: 400px"></div>
|
||||
<p>
|
||||
This sample is a modification of the <a href="../samples/stateChart.html">State Chart</a> sample that makes use of
|
||||
the CurvedLinkReshapingTool that is defined in its own file, as <a href="CurvedLinkReshapingTool.ts">CurvedLinkReshapingTool.ts</a>.
|
||||
</p>
|
||||
<p>
|
||||
Note that unlike the standard case of a Bezier-curved Link that is <a>Part.reshapable</a>, there is only one reshape
|
||||
handle When the user drags that handle, the value of <a>Link.curviness</a> is modified, causing the link to be curved
|
||||
differently. This sample also defines a TwoWay <a>Binding</a> on that property, thereby saving the curviness to the
|
||||
model data. Unlike the regular State Chart sample, there is no Binding on <a>Link.points</a>, which is no longer needed
|
||||
when the curviness is the only modified property.
|
||||
</p>
|
||||
<button id="SaveButton">Save</button>
|
||||
<button id="LoadButton">Load</button> Diagram Model saved in JSON format:
|
||||
<br />
|
||||
<textarea id="mySavedModel" style="width:100%;height:300px">
|
||||
{ "nodeKeyProperty": "id",
|
||||
"nodeDataArray": [
|
||||
{ "id": 0, "loc": "120 120", "text": "Initial" },
|
||||
{ "id": 1, "loc": "330 120", "text": "First down" },
|
||||
{ "id": 2, "loc": "226 376", "text": "First up" },
|
||||
{ "id": 3, "loc": "60 276", "text": "Second down" },
|
||||
{ "id": 4, "loc": "226 226", "text": "Wait" }
|
||||
],
|
||||
"linkDataArray": [
|
||||
{ "from": 0, "to": 0, "text": "up or timer", "curviness": -20 },
|
||||
{ "from": 0, "to": 1, "text": "down", "curviness": 20 },
|
||||
{ "from": 1, "to": 0, "text": "up (moved)\nPOST", "curviness": 20 },
|
||||
{ "from": 1, "to": 1, "text": "down", "curviness": -20 },
|
||||
{ "from": 1, "to": 2, "text": "up (no move)" },
|
||||
{ "from": 1, "to": 4, "text": "timer" },
|
||||
{ "from": 2, "to": 0, "text": "timer\nPOST" },
|
||||
{ "from": 2, "to": 3, "text": "down" },
|
||||
{ "from": 3, "to": 0, "text": "up\nPOST\n(dblclick\nif no move)" },
|
||||
{ "from": 3, "to": 3, "text": "down or timer", "curviness": 20 },
|
||||
{ "from": 4, "to": 0, "text": "up\nPOST" },
|
||||
{ "from": 4, "to": 4, "text": "down" }
|
||||
]
|
||||
}
|
||||
</textarea>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js", "./CurvedLinkReshapingTool.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.load = exports.save = exports.init = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
var CurvedLinkReshapingTool_js_1 = require("./CurvedLinkReshapingTool.js");
|
||||
var myDiagram;
|
||||
function init() {
|
||||
if (window.goSamples)
|
||||
window.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', // must name or refer to the DIV HTML element
|
||||
{
|
||||
// have mouse wheel events zoom in and out instead of scroll up and down
|
||||
'toolManager.mouseWheelBehavior': go.ToolManager.WheelZoom,
|
||||
// support double-click in background creating a new node
|
||||
'clickCreatingTool.archetypeNodeData': { text: 'new node' },
|
||||
'linkReshapingTool': new CurvedLinkReshapingTool_js_1.CurvedLinkReshapingTool(),
|
||||
// enable undo & redo
|
||||
'undoManager.isEnabled': true
|
||||
});
|
||||
// when the document is modified, add a "*" to the title and enable the "Save" button
|
||||
myDiagram.addDiagramListener('Modified', function (e) {
|
||||
var button = document.getElementById('SaveButton');
|
||||
if (button)
|
||||
button.disabled = !myDiagram.isModified;
|
||||
var idx = document.title.indexOf('*');
|
||||
if (myDiagram.isModified) {
|
||||
if (idx < 0)
|
||||
document.title += '*';
|
||||
}
|
||||
else {
|
||||
if (idx >= 0)
|
||||
document.title = document.title.substr(0, idx);
|
||||
}
|
||||
});
|
||||
// define the Node template
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, 'Auto', new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
// define the node's outer shape, which will surround the TextBlock
|
||||
$(go.Shape, 'RoundedRectangle', {
|
||||
parameter1: 20,
|
||||
fill: $(go.Brush, 'Linear', { 0: 'rgb(254, 201, 0)', 1: 'rgb(254, 162, 0)' }),
|
||||
stroke: 'black',
|
||||
portId: '',
|
||||
fromLinkable: true,
|
||||
fromLinkableSelfNode: true,
|
||||
fromLinkableDuplicates: true,
|
||||
toLinkable: true,
|
||||
toLinkableSelfNode: true,
|
||||
toLinkableDuplicates: true,
|
||||
cursor: 'pointer'
|
||||
}), $(go.TextBlock, {
|
||||
font: 'bold 11pt helvetica, bold arial, sans-serif',
|
||||
editable: true // editing the text automatically updates the model data
|
||||
}, new go.Binding('text', 'text').makeTwoWay()));
|
||||
// unlike the normal selection Adornment, this one includes a Button
|
||||
myDiagram.nodeTemplate.selectionAdornmentTemplate =
|
||||
$(go.Adornment, 'Spot', $(go.Panel, 'Auto', $(go.Shape, { fill: null, stroke: 'blue', strokeWidth: 2 }), $(go.Placeholder) // this represents the selected Node
|
||||
),
|
||||
// the button to create a "next" node, at the top-right corner
|
||||
$('Button', {
|
||||
alignment: go.Spot.TopRight,
|
||||
click: addNodeAndLink // this function is defined below
|
||||
}, $(go.Shape, 'PlusLine', { desiredSize: new go.Size(6, 6) })) // end button
|
||||
); // end Adornment
|
||||
// clicking the button inserts a new node to the right of the selected node,
|
||||
// and adds a link to that new node
|
||||
function addNodeAndLink(e, obj) {
|
||||
var adorn = obj.part;
|
||||
var fromNode = adorn.adornedPart;
|
||||
if (fromNode === null)
|
||||
return;
|
||||
e.handled = true;
|
||||
var diagram = e.diagram;
|
||||
diagram.startTransaction('Add State');
|
||||
// get the node data for which the user clicked the button
|
||||
var fromData = fromNode.data;
|
||||
// create a new "State" data object, positioned off to the right of the adorned Node
|
||||
var toData = { text: 'new' };
|
||||
var p = fromNode.location.copy();
|
||||
p.x += 200;
|
||||
toData.loc = go.Point.stringify(p); // the "loc" property is a string, not a Point object
|
||||
// add the new node data to the model
|
||||
var model = diagram.model;
|
||||
model.addNodeData(toData);
|
||||
// create a link data from the old node data to the new node data
|
||||
var linkdata = {
|
||||
from: model.getKeyForNodeData(fromData),
|
||||
to: model.getKeyForNodeData(toData),
|
||||
text: 'transition'
|
||||
};
|
||||
// and add the link data to the model
|
||||
model.addLinkData(linkdata);
|
||||
// select the new Node
|
||||
var newnode = diagram.findNodeForData(toData);
|
||||
diagram.select(newnode);
|
||||
diagram.commitTransaction('Add State');
|
||||
// if the new node is off-screen, scroll the diagram to show the new node
|
||||
if (newnode !== null)
|
||||
diagram.scrollToRect(newnode.actualBounds);
|
||||
}
|
||||
// replace the default Link template in the linkTemplateMap
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link, // the whole link panel
|
||||
{ curve: go.Link.Bezier, reshapable: true },
|
||||
// don't need to save Link.points, so don't need TwoWay Binding on "points"
|
||||
new go.Binding('curviness', 'curviness').makeTwoWay(), // but save "curviness" automatically
|
||||
$(go.Shape, // the link shape
|
||||
{ strokeWidth: 1.5 }), $(go.Shape, // the arrowhead
|
||||
{ toArrow: 'standard', stroke: null }), $(go.Panel, 'Auto', $(go.Shape, // the label background, which becomes transparent around the edges
|
||||
{
|
||||
fill: $(go.Brush, 'Radial', { 0: 'rgb(240, 240, 240)', 0.3: 'rgb(240, 240, 240)', 1: 'rgba(240, 240, 240, 0)' }),
|
||||
stroke: null
|
||||
}), $(go.TextBlock, 'transition', // the label text
|
||||
{
|
||||
textAlign: 'center',
|
||||
font: '10pt helvetica, arial, sans-serif',
|
||||
stroke: 'black',
|
||||
margin: 4,
|
||||
editable: true // editing the text automatically updates the model data
|
||||
}, new go.Binding('text', 'text').makeTwoWay())));
|
||||
// read in the JSON-format data from the "mySavedModel" element
|
||||
load();
|
||||
// Attach to the window for console manipulation
|
||||
window.myDiagram = myDiagram;
|
||||
}
|
||||
exports.init = init;
|
||||
// Show the diagram's model in JSON format
|
||||
function save() {
|
||||
document.getElementById('mySavedModel').value = myDiagram.model.toJson();
|
||||
myDiagram.isModified = false;
|
||||
}
|
||||
exports.save = save;
|
||||
function load() {
|
||||
myDiagram.model = go.Model.fromJson(document.getElementById('mySavedModel').value);
|
||||
}
|
||||
exports.load = load;
|
||||
});
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
import { CurvedLinkReshapingTool } from './CurvedLinkReshapingTool.js';
|
||||
|
||||
let myDiagram: go.Diagram;
|
||||
|
||||
export function init() {
|
||||
if ((window as any).goSamples) (window as any).goSamples(); // init for these samples -- you don't need to call this
|
||||
|
||||
const $ = go.GraphObject.make; // for conciseness in defining templates
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, 'myDiagramDiv', // must name or refer to the DIV HTML element
|
||||
{
|
||||
// have mouse wheel events zoom in and out instead of scroll up and down
|
||||
'toolManager.mouseWheelBehavior': go.ToolManager.WheelZoom,
|
||||
// support double-click in background creating a new node
|
||||
'clickCreatingTool.archetypeNodeData': { text: 'new node' },
|
||||
'linkReshapingTool': new CurvedLinkReshapingTool(),
|
||||
// enable undo & redo
|
||||
'undoManager.isEnabled': true
|
||||
});
|
||||
|
||||
// when the document is modified, add a "*" to the title and enable the "Save" button
|
||||
myDiagram.addDiagramListener('Modified', (e) => {
|
||||
const button = (document.getElementById('SaveButton') as any);
|
||||
if (button) button.disabled = !myDiagram.isModified;
|
||||
const idx = document.title.indexOf('*');
|
||||
if (myDiagram.isModified) {
|
||||
if (idx < 0) document.title += '*';
|
||||
} else {
|
||||
if (idx >= 0) document.title = document.title.substr(0, idx);
|
||||
}
|
||||
});
|
||||
|
||||
// define the Node template
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, 'Auto',
|
||||
new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
// define the node's outer shape, which will surround the TextBlock
|
||||
$(go.Shape, 'RoundedRectangle',
|
||||
{
|
||||
parameter1: 20, // the corner has a large radius
|
||||
fill: $(go.Brush, 'Linear', { 0: 'rgb(254, 201, 0)', 1: 'rgb(254, 162, 0)' }),
|
||||
stroke: 'black',
|
||||
portId: '',
|
||||
fromLinkable: true,
|
||||
fromLinkableSelfNode: true,
|
||||
fromLinkableDuplicates: true,
|
||||
toLinkable: true,
|
||||
toLinkableSelfNode: true,
|
||||
toLinkableDuplicates: true,
|
||||
cursor: 'pointer'
|
||||
}),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
font: 'bold 11pt helvetica, bold arial, sans-serif',
|
||||
editable: true // editing the text automatically updates the model data
|
||||
},
|
||||
new go.Binding('text', 'text').makeTwoWay())
|
||||
);
|
||||
|
||||
// unlike the normal selection Adornment, this one includes a Button
|
||||
myDiagram.nodeTemplate.selectionAdornmentTemplate =
|
||||
$(go.Adornment, 'Spot',
|
||||
$(go.Panel, 'Auto',
|
||||
$(go.Shape, { fill: null, stroke: 'blue', strokeWidth: 2 }),
|
||||
$(go.Placeholder) // this represents the selected Node
|
||||
),
|
||||
// the button to create a "next" node, at the top-right corner
|
||||
$('Button',
|
||||
{
|
||||
alignment: go.Spot.TopRight,
|
||||
click: addNodeAndLink // this function is defined below
|
||||
},
|
||||
$(go.Shape, 'PlusLine', { desiredSize: new go.Size(6, 6) })
|
||||
) // end button
|
||||
); // end Adornment
|
||||
|
||||
// clicking the button inserts a new node to the right of the selected node,
|
||||
// and adds a link to that new node
|
||||
function addNodeAndLink(e: go.InputEvent, obj: go.GraphObject) {
|
||||
const adorn = obj.part as go.Adornment;
|
||||
const fromNode = adorn.adornedPart;
|
||||
if (fromNode === null) return;
|
||||
|
||||
e.handled = true;
|
||||
const diagram = e.diagram;
|
||||
diagram.startTransaction('Add State');
|
||||
|
||||
// get the node data for which the user clicked the button
|
||||
const fromData = fromNode.data;
|
||||
// create a new "State" data object, positioned off to the right of the adorned Node
|
||||
const toData: any = { text: 'new' };
|
||||
const p = fromNode.location.copy();
|
||||
p.x += 200;
|
||||
toData.loc = go.Point.stringify(p); // the "loc" property is a string, not a Point object
|
||||
// add the new node data to the model
|
||||
const model = diagram.model as go.GraphLinksModel;
|
||||
model.addNodeData(toData);
|
||||
|
||||
// create a link data from the old node data to the new node data
|
||||
const linkdata = {
|
||||
from: model.getKeyForNodeData(fromData), // or just: fromData.id
|
||||
to: model.getKeyForNodeData(toData),
|
||||
text: 'transition'
|
||||
};
|
||||
// and add the link data to the model
|
||||
model.addLinkData(linkdata);
|
||||
|
||||
// select the new Node
|
||||
const newnode = diagram.findNodeForData(toData);
|
||||
diagram.select(newnode);
|
||||
|
||||
diagram.commitTransaction('Add State');
|
||||
|
||||
// if the new node is off-screen, scroll the diagram to show the new node
|
||||
if (newnode !== null) diagram.scrollToRect(newnode.actualBounds);
|
||||
}
|
||||
|
||||
// replace the default Link template in the linkTemplateMap
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link, // the whole link panel
|
||||
{ curve: go.Link.Bezier, reshapable: true },
|
||||
// don't need to save Link.points, so don't need TwoWay Binding on "points"
|
||||
new go.Binding('curviness', 'curviness').makeTwoWay(), // but save "curviness" automatically
|
||||
$(go.Shape, // the link shape
|
||||
{ strokeWidth: 1.5 }),
|
||||
$(go.Shape, // the arrowhead
|
||||
{ toArrow: 'standard', stroke: null }),
|
||||
$(go.Panel, 'Auto',
|
||||
$(go.Shape, // the label background, which becomes transparent around the edges
|
||||
{
|
||||
fill: $(go.Brush, 'Radial',
|
||||
{ 0: 'rgb(240, 240, 240)', 0.3: 'rgb(240, 240, 240)', 1: 'rgba(240, 240, 240, 0)' }),
|
||||
stroke: null
|
||||
}),
|
||||
$(go.TextBlock, 'transition', // the label text
|
||||
{
|
||||
textAlign: 'center',
|
||||
font: '10pt helvetica, arial, sans-serif',
|
||||
stroke: 'black',
|
||||
margin: 4,
|
||||
editable: true // editing the text automatically updates the model data
|
||||
},
|
||||
new go.Binding('text', 'text').makeTwoWay())
|
||||
)
|
||||
);
|
||||
|
||||
// read in the JSON-format data from the "mySavedModel" element
|
||||
load();
|
||||
|
||||
// Attach to the window for console manipulation
|
||||
(window as any).myDiagram = myDiagram;
|
||||
}
|
||||
|
||||
// Show the diagram's model in JSON format
|
||||
export function save() {
|
||||
(document.getElementById('mySavedModel') as any).value = myDiagram.model.toJson();
|
||||
myDiagram.isModified = false;
|
||||
}
|
||||
export function load() {
|
||||
myDiagram.model = go.Model.fromJson((document.getElementById('mySavedModel') as any).value);
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.CurvedLinkReshapingTool = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
/**
|
||||
* This CurvedLinkReshapingTool class allows for a {@link Link}'s path to be modified by the user
|
||||
* via the dragging of a single tool handle at the middle of the link.
|
||||
* Dragging the handle changes the value of {@link Link#curviness}.
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/CurvedLinkReshaping.html">Curved Link Reshaping</a> sample.
|
||||
* @category Tool Extension
|
||||
*/
|
||||
var CurvedLinkReshapingTool = /** @class */ (function (_super) {
|
||||
__extends(CurvedLinkReshapingTool, _super);
|
||||
function CurvedLinkReshapingTool() {
|
||||
var _this = _super !== null && _super.apply(this, arguments) || this;
|
||||
_this._originalCurviness = NaN;
|
||||
return _this;
|
||||
}
|
||||
/**
|
||||
* @hidden @internal
|
||||
*/
|
||||
CurvedLinkReshapingTool.prototype.makeAdornment = function (pathshape) {
|
||||
var link = pathshape.part;
|
||||
if (link !== null && link.curve === go.Link.Bezier && link.pointsCount === 4) {
|
||||
var adornment = new go.Adornment();
|
||||
adornment.type = go.Panel.Link;
|
||||
var h = this.makeHandle(pathshape, 0);
|
||||
this.setReshapingBehavior(h, go.LinkReshapingTool.All);
|
||||
h.cursor = 'move';
|
||||
adornment.add(h);
|
||||
adornment.category = this.name;
|
||||
adornment.adornedObject = pathshape;
|
||||
return adornment;
|
||||
}
|
||||
else {
|
||||
return _super.prototype.makeAdornment.call(this, pathshape);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Start reshaping, if {@link #findToolHandleAt} finds a reshape handle at the mouse down point.
|
||||
*
|
||||
* If successful this sets {@link #handle} to be the reshape handle that it finds
|
||||
* and {@link #adornedLink} to be the {@link Link} being routed.
|
||||
* It also remembers the original link route (a list of Points) and curviness in case this tool is cancelled.
|
||||
* And it starts a transaction.
|
||||
*/
|
||||
CurvedLinkReshapingTool.prototype.doActivate = function () {
|
||||
_super.prototype.doActivate.call(this);
|
||||
if (this.adornedLink !== null)
|
||||
this._originalCurviness = this.adornedLink.curviness;
|
||||
};
|
||||
/**
|
||||
* Restore the link route to be the original points and curviness and stop this tool.
|
||||
*/
|
||||
CurvedLinkReshapingTool.prototype.doCancel = function () {
|
||||
if (this.adornedLink !== null)
|
||||
this.adornedLink.curviness = this._originalCurviness;
|
||||
_super.prototype.doCancel.call(this);
|
||||
};
|
||||
/**
|
||||
* Change the route of the {@link #adornedLink} by moving the point corresponding to the current
|
||||
* {@link #handle} to be at the given {@link Point}.
|
||||
* This is called by {@link #doMouseMove} and {@link #doMouseUp} with the result of calling
|
||||
* {@link #computeReshape} to constrain the input point.
|
||||
* @param {Point} newpt the value of the call to {@link #computeReshape}.
|
||||
*/
|
||||
CurvedLinkReshapingTool.prototype.reshape = function (newpt) {
|
||||
var link = this.adornedLink;
|
||||
if (link !== null && link.curve === go.Link.Bezier && link.pointsCount === 4) {
|
||||
var start = link.getPoint(0);
|
||||
var end = link.getPoint(3);
|
||||
var ang = start.directionPoint(end);
|
||||
var mid = new go.Point((start.x + end.x) / 2, (start.y + end.y) / 2);
|
||||
var a = new go.Point(9999, 0).rotate(ang + 90).add(mid);
|
||||
var b = new go.Point(9999, 0).rotate(ang - 90).add(mid);
|
||||
var q = newpt.copy().projectOntoLineSegmentPoint(a, b);
|
||||
var curviness = Math.sqrt(mid.distanceSquaredPoint(q));
|
||||
var port = link.fromPort;
|
||||
if (port === link.toPort && port !== null) {
|
||||
if (newpt.y < port.getDocumentPoint(go.Spot.Center).y)
|
||||
curviness = -curviness;
|
||||
}
|
||||
else {
|
||||
var diff = mid.directionPoint(q) - ang;
|
||||
if ((diff > 0 && diff < 180) || (diff < -180))
|
||||
curviness = -curviness;
|
||||
}
|
||||
link.curviness = curviness;
|
||||
}
|
||||
else {
|
||||
_super.prototype.reshape.call(this, newpt);
|
||||
}
|
||||
};
|
||||
return CurvedLinkReshapingTool;
|
||||
}(go.LinkReshapingTool));
|
||||
exports.CurvedLinkReshapingTool = CurvedLinkReshapingTool;
|
||||
});
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
|
||||
/**
|
||||
* This CurvedLinkReshapingTool class allows for a {@link Link}'s path to be modified by the user
|
||||
* via the dragging of a single tool handle at the middle of the link.
|
||||
* Dragging the handle changes the value of {@link Link#curviness}.
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/CurvedLinkReshaping.html">Curved Link Reshaping</a> sample.
|
||||
* @category Tool Extension
|
||||
*/
|
||||
export class CurvedLinkReshapingTool extends go.LinkReshapingTool {
|
||||
private _originalCurviness: number = NaN;
|
||||
|
||||
/**
|
||||
* @hidden @internal
|
||||
*/
|
||||
public makeAdornment(pathshape: go.GraphObject): go.Adornment {
|
||||
const link = pathshape.part as go.Link;
|
||||
if (link !== null && link.curve === go.Link.Bezier && link.pointsCount === 4) {
|
||||
const adornment = new go.Adornment();
|
||||
adornment.type = go.Panel.Link;
|
||||
const h = this.makeHandle(pathshape, 0);
|
||||
this.setReshapingBehavior(h, go.LinkReshapingTool.All);
|
||||
h.cursor = 'move';
|
||||
adornment.add(h);
|
||||
adornment.category = this.name;
|
||||
adornment.adornedObject = pathshape;
|
||||
return adornment;
|
||||
} else {
|
||||
return super.makeAdornment(pathshape);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start reshaping, if {@link #findToolHandleAt} finds a reshape handle at the mouse down point.
|
||||
*
|
||||
* If successful this sets {@link #handle} to be the reshape handle that it finds
|
||||
* and {@link #adornedLink} to be the {@link Link} being routed.
|
||||
* It also remembers the original link route (a list of Points) and curviness in case this tool is cancelled.
|
||||
* And it starts a transaction.
|
||||
*/
|
||||
public doActivate(): void {
|
||||
super.doActivate();
|
||||
if (this.adornedLink !== null) this._originalCurviness = this.adornedLink.curviness;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the link route to be the original points and curviness and stop this tool.
|
||||
*/
|
||||
public doCancel(): void {
|
||||
if (this.adornedLink !== null) this.adornedLink.curviness = this._originalCurviness;
|
||||
super.doCancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the route of the {@link #adornedLink} by moving the point corresponding to the current
|
||||
* {@link #handle} to be at the given {@link Point}.
|
||||
* This is called by {@link #doMouseMove} and {@link #doMouseUp} with the result of calling
|
||||
* {@link #computeReshape} to constrain the input point.
|
||||
* @param {Point} newpt the value of the call to {@link #computeReshape}.
|
||||
*/
|
||||
public reshape(newpt: go.Point): void {
|
||||
const link = this.adornedLink;
|
||||
if (link !== null && link.curve === go.Link.Bezier && link.pointsCount === 4) {
|
||||
const start = link.getPoint(0);
|
||||
const end = link.getPoint(3);
|
||||
const ang = start.directionPoint(end);
|
||||
const mid = new go.Point((start.x + end.x) / 2, (start.y + end.y) / 2);
|
||||
const a = new go.Point(9999, 0).rotate(ang + 90).add(mid);
|
||||
const b = new go.Point(9999, 0).rotate(ang - 90).add(mid);
|
||||
const q = newpt.copy().projectOntoLineSegmentPoint(a, b);
|
||||
let curviness = Math.sqrt(mid.distanceSquaredPoint(q));
|
||||
const port = link.fromPort;
|
||||
if (port === link.toPort && port !== null) {
|
||||
if (newpt.y < port.getDocumentPoint(go.Spot.Center).y) curviness = -curviness;
|
||||
} else {
|
||||
const diff = mid.directionPoint(q) - ang;
|
||||
if ((diff > 0 && diff < 180) || (diff < -180)) curviness = -curviness;
|
||||
}
|
||||
link.curviness = curviness;
|
||||
} else {
|
||||
super.reshape(newpt);
|
||||
}
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
Default CSS for the Data inspector
|
||||
see also: DataInspector.js, DataInspector.html
|
||||
*/
|
||||
|
||||
/*
|
||||
Grey color palette
|
||||
https://www.google.com/design/spec/style/color.html
|
||||
/* #FAFAFA; /* Grey 50 */
|
||||
/* #F5F5F5; /* Grey 100 */
|
||||
/* #EEEEEE; /* Grey 200 */
|
||||
/* #E0E0E0; /* Grey 300 */
|
||||
/* #BDBDBD; /* Grey 400 */
|
||||
/* #9E9E9E; /* Grey 500 */
|
||||
/* #757575; /* Grey 600 */
|
||||
/* #616161; /* Grey 700 */
|
||||
/* #424242; /* Grey 800 */
|
||||
/* #212121; /* Grey 900 */
|
||||
|
||||
.inspector {
|
||||
display: inline-block;
|
||||
font: bold 14px helvetica, sans-serif;
|
||||
background-color: #212121; /* Grey 900 */
|
||||
color: #F5F5F5; /* Grey 100 */
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.inspector table {
|
||||
border-collapse: separate;
|
||||
border-spacing: 2px;
|
||||
}
|
||||
|
||||
.inspector td, th {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.inspector input {
|
||||
background-color: #424242; /* Grey 800 */
|
||||
color: #F5F5F5; /* Grey 100 */
|
||||
font: bold 12px helvetica, sans-serif;
|
||||
border: 0px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.inspector input:disabled {
|
||||
background-color: #BDBDBD; /* Grey 400 */
|
||||
color: #616161; /* Grey 700 */
|
||||
}
|
||||
|
||||
.inspector select {
|
||||
background-color: #424242;
|
||||
}
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Data Inspector</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="TypeScript: An HTML panel that displays the properties of some model data and allows the user to edit their values." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../samples/assets/require.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script>
|
||||
<!-- this is only for the GoJS Samples framework -->
|
||||
<link rel='stylesheet' href='DataInspector.css' />
|
||||
<script id='code'>
|
||||
require(["DataInspectorScript"], function(app) {
|
||||
app.init();
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="sample">
|
||||
<span style="display: inline-block; vertical-align: top;">
|
||||
<div style="margin-left: 10px;">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:400px; height:400px;"></div>
|
||||
</div>
|
||||
</span>
|
||||
<span style="display: inline-block; vertical-align: top;">
|
||||
Selected Part:<br/>
|
||||
<div id="myInspectorDiv1" class="inspector"> </div><br/>
|
||||
First Node's data:<br />
|
||||
<div id="myInspectorDiv2" class="inspector"> </div><br />
|
||||
Model.modelData:<br />
|
||||
<div id="myInspectorDiv3" class="inspector"> </div><br />
|
||||
</span>
|
||||
<div>
|
||||
<p>An HTML-based inspector that displays and allows editing of data for the selected Part (if any), or for a particular JavaScript
|
||||
object, or for the shared <a>Model.modelData</a> object, which exists even if there are no nodes or links.
|
||||
<p>The inspector code lies in <a href="DataInspector.ts">DataInspector.ts</a> and <a href="DataInspector.css">DataInspector.css</a>.
|
||||
This code is meant to be a starting point for making your own model data inspector.
|
||||
<p>On browsers that support it, color types display a color picker. There are various plugins and polyfills for this functionaltiy
|
||||
if you wish to extend the data inspector.
|
||||
<p>This shows the contents of the model after each transaction:
|
||||
<pre id="savedModel" />
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
+961
@@ -0,0 +1,961 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Inspector = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
/**
|
||||
* This class implements an inspector for GoJS model data objects.
|
||||
* The constructor takes three arguments:
|
||||
* - `divid` ***string*** a string referencing the HTML ID of the to-be inspector's div
|
||||
* - `diagram` ***Diagram*** a reference to a GoJS Diagram
|
||||
* - `options` ***Object*** an optional JS Object describing options for the inspector
|
||||
*
|
||||
* Options:
|
||||
* - `inspectSelection` ***boolean*** see {@link #inspectSelection}
|
||||
* - `includesOwnProperties` ***boolean*** see {@link #includesOwnProperties}
|
||||
* - `properties` ***Object*** see {@link #properties}
|
||||
* - `propertyModified` ***function(propertyName, newValue, inspector)*** see {@link #propertyModified}
|
||||
* - `multipleSelection` ***boolean*** see {@link #multipleSelection}
|
||||
* - `showUnionProperties` ***boolean*** see {@link #showUnionProperties}
|
||||
* - `showLimit` ***number*** see {@link #showLimit}
|
||||
*
|
||||
* Options for properties:
|
||||
* - `show` ***boolean | function*** a boolean value to show or hide the property from the inspector, or a predicate function to show conditionally.
|
||||
* - `readOnly` ***boolean | function*** whether or not the property is read-only
|
||||
* - `type` ***string*** a string describing the data type. Supported values: "string|number|boolean|color|arrayofnumber|point|rect|size|spot|margin|select"
|
||||
* - `defaultValue` ***any*** a default value for the property. Defaults to the empty string.
|
||||
* - `choices` ***Array | function*** when type === "select", the Array of choices to use or a function that returns the Array of choices.
|
||||
*
|
||||
* Example usage of Inspector:
|
||||
* ```js
|
||||
* var inspector = new Inspector("myInspector", myDiagram,
|
||||
* {
|
||||
* includesOwnProperties: false,
|
||||
* properties: {
|
||||
* "key": { show: Inspector.showIfPresent, readOnly: true },
|
||||
* "comments": { show: Inspector.showIfNode },
|
||||
* "LinkComments": { show: Inspector.showIfLink },
|
||||
* "chosen": { show: Inspector.showIfNode, type: "checkbox" },
|
||||
* "state": { show: Inspector.showIfNode, type: "select", choices: ["Stopped", "Parked", "Moving"] }
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This is the basic HTML Structure that the Inspector creates within the given DIV element:
|
||||
* ```html
|
||||
* <div id="divid" class="inspector">
|
||||
* <tr>
|
||||
* <td>propertyName</td>
|
||||
* <td><input value=propertyValue /></td>
|
||||
* </tr>
|
||||
* ...
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/DataInspector.html">Data Inspector</a> sample.
|
||||
* @category Extension
|
||||
*/
|
||||
var Inspector = /** @class */ (function () {
|
||||
/**
|
||||
* Constructs an Inspector and sets up properties based on the options provided.
|
||||
* Also sets up change listeners on the Diagram so the Inspector stays up-to-date.
|
||||
* @param {string} divid a string referencing the HTML ID of the to-be Inspector's div
|
||||
* @param {Diagram} diagram a reference to a GoJS Diagram
|
||||
* @param {Object=} options an optional JS Object describing options for the inspector
|
||||
*/
|
||||
function Inspector(divid, diagram, options) {
|
||||
this._inspectedObject = null;
|
||||
// Inspector options defaults:
|
||||
this._inspectSelection = true;
|
||||
this._includesOwnProperties = true;
|
||||
this._properties = {};
|
||||
this._propertyModified = null;
|
||||
this._multipleSelection = false;
|
||||
this._showUnionProperties = false;
|
||||
this._showLimit = 0;
|
||||
// Private variables used to keep track of internal state
|
||||
this.inspectedProperties = {};
|
||||
this.multipleProperties = {};
|
||||
var mainDiv = document.getElementById(divid);
|
||||
mainDiv.className = 'inspector';
|
||||
mainDiv.innerHTML = '';
|
||||
this._div = mainDiv;
|
||||
this._diagram = diagram;
|
||||
this.tabIndex = 0;
|
||||
// Set properties based on options
|
||||
if (options !== undefined) {
|
||||
if (options.inspectSelection !== undefined)
|
||||
this._inspectSelection = options.inspectSelection;
|
||||
if (options.includesOwnProperties !== undefined)
|
||||
this._includesOwnProperties = options.includesOwnProperties;
|
||||
if (options.properties !== undefined)
|
||||
this._properties = options.properties;
|
||||
if (options.propertyModified !== undefined)
|
||||
this._propertyModified = options.propertyModified;
|
||||
if (options.multipleSelection !== undefined)
|
||||
this._multipleSelection = options.multipleSelection;
|
||||
if (options.showUnionProperties !== undefined)
|
||||
this._showUnionProperties = options.showUnionProperties;
|
||||
if (options.showLimit !== undefined)
|
||||
this._showLimit = options.showLimit;
|
||||
}
|
||||
// Prepare change listeners
|
||||
var self = this;
|
||||
this.inspectOnModelChanged = function (e) {
|
||||
if (e.isTransactionFinished)
|
||||
self.inspectObject();
|
||||
};
|
||||
this.inspectOnSelectionChanged = function (e) { self.inspectObject(); };
|
||||
this._diagram.addModelChangedListener(this.inspectOnModelChanged);
|
||||
if (this._inspectSelection) {
|
||||
this._diagram.addDiagramListener('ChangedSelection', this.inspectOnSelectionChanged);
|
||||
}
|
||||
}
|
||||
Object.defineProperty(Inspector.prototype, "div", {
|
||||
/**
|
||||
* This read-only property returns the HTMLElement containing the Inspector.
|
||||
*/
|
||||
get: function () { return this._div; },
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(Inspector.prototype, "diagram", {
|
||||
/**
|
||||
* Gets or sets the {@link Diagram} associated with this Inspector.
|
||||
*/
|
||||
get: function () { return this._diagram; },
|
||||
set: function (val) {
|
||||
if (val !== this._diagram) {
|
||||
// First, unassociate change listeners with current inspected diagram
|
||||
this._diagram.removeModelChangedListener(this.inspectOnModelChanged);
|
||||
this._diagram.removeDiagramListener('ChangedSelection', this.inspectOnSelectionChanged);
|
||||
// Now set the diagram and add the necessary change listeners
|
||||
this._diagram = val;
|
||||
this._diagram.addModelChangedListener(this.inspectOnModelChanged);
|
||||
if (this._inspectSelection) {
|
||||
this._diagram.addDiagramListener('ChangedSelection', this.inspectOnSelectionChanged);
|
||||
this.inspectObject();
|
||||
}
|
||||
else {
|
||||
this.inspectObject(null);
|
||||
}
|
||||
}
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(Inspector.prototype, "inspectedObject", {
|
||||
/**
|
||||
* This read-only property returns the object currently being inspected.
|
||||
*
|
||||
* To set the inspected object, call {@link #inspectObject}.
|
||||
*/
|
||||
get: function () { return this._inspectedObject; },
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(Inspector.prototype, "inspectSelection", {
|
||||
/**
|
||||
* Gets or sets whether the Inspector automatically inspects the associated Diagram's selection.
|
||||
* When set to false, the Inspector won't show anything until {@link #inspectObject} is called.
|
||||
*
|
||||
* The default value is true.
|
||||
*/
|
||||
get: function () { return this._inspectSelection; },
|
||||
set: function (val) {
|
||||
if (val !== this._inspectSelection) {
|
||||
this._inspectSelection = val;
|
||||
if (this._inspectSelection) {
|
||||
this._diagram.addDiagramListener('ChangedSelection', this.inspectOnSelectionChanged);
|
||||
this.inspectObject();
|
||||
}
|
||||
else {
|
||||
this._diagram.removeDiagramListener('ChangedSelection', this.inspectOnSelectionChanged);
|
||||
this.inspectObject(null);
|
||||
}
|
||||
}
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(Inspector.prototype, "includesOwnProperties", {
|
||||
/**
|
||||
* Gets or sets whether the Inspector includes all properties currently on the inspected object.
|
||||
*
|
||||
* The default value is true.
|
||||
*/
|
||||
get: function () { return this._includesOwnProperties; },
|
||||
set: function (val) {
|
||||
if (val !== this._includesOwnProperties) {
|
||||
this._includesOwnProperties = val;
|
||||
this.inspectObject();
|
||||
}
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(Inspector.prototype, "properties", {
|
||||
/**
|
||||
* Gets or sets the properties that the Inspector will inspect, maybe setting options for those properties.
|
||||
* The object should contain string: Object pairs represnting propertyName: propertyOptions.
|
||||
* Can be used to include or exclude additional properties.
|
||||
*
|
||||
* The default value is an empty object.
|
||||
*/
|
||||
get: function () { return this._properties; },
|
||||
set: function (val) {
|
||||
if (val !== this._properties) {
|
||||
this._properties = val;
|
||||
this.inspectObject();
|
||||
}
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(Inspector.prototype, "propertyModified", {
|
||||
/**
|
||||
* Gets or sets the function to be called when a property is modified by the Inspector.
|
||||
* The first paremeter will be the property name, the second will be the new value, and the third will be a reference to this Inspector.
|
||||
*
|
||||
* The default value is null, meaning nothing will be done.
|
||||
*/
|
||||
get: function () { return this._propertyModified; },
|
||||
set: function (val) {
|
||||
if (val !== this._propertyModified) {
|
||||
this._propertyModified = val;
|
||||
}
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(Inspector.prototype, "multipleSelection", {
|
||||
/**
|
||||
* Gets or sets whether the Inspector displays properties for multiple selected objects or just the first.
|
||||
*
|
||||
* The default value is false, meaning only the first item in the {@link Diagram#selection} is inspected.
|
||||
*/
|
||||
get: function () { return this._multipleSelection; },
|
||||
set: function (val) {
|
||||
if (val !== this._multipleSelection) {
|
||||
this._multipleSelection = val;
|
||||
this.inspectObject();
|
||||
}
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(Inspector.prototype, "showUnionProperties", {
|
||||
/**
|
||||
* Gets or sets whether the Inspector displays the union or intersection of properties for multiple selected objects.
|
||||
*
|
||||
* The default value is false, meaning the intersection of properties is inspected.
|
||||
*/
|
||||
get: function () { return this._showUnionProperties; },
|
||||
set: function (val) {
|
||||
if (val !== this._showUnionProperties) {
|
||||
this._showUnionProperties = val;
|
||||
this.inspectObject();
|
||||
}
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(Inspector.prototype, "showLimit", {
|
||||
/**
|
||||
* Gets or sets how many objects will be displayed when {@link #multipleSelection} is true.
|
||||
*
|
||||
* The default value is 0, meaning all selected objects will be displayed for a given property.
|
||||
*/
|
||||
get: function () { return this._showLimit; },
|
||||
set: function (val) {
|
||||
if (val !== this._showLimit) {
|
||||
this._showLimit = val;
|
||||
this.inspectObject();
|
||||
}
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
/**
|
||||
* This predicate function can be used as a value for the `show` option for properties.
|
||||
* When used, the property will only be shown when inspecting a {@link Node}.
|
||||
* @param {Part} part the Part being inspected
|
||||
* @return {boolean}
|
||||
*/
|
||||
Inspector.showIfNode = function (part) { return part instanceof go.Node; };
|
||||
/**
|
||||
* This predicate function can be used as a value for the `show` option for properties.
|
||||
* When used, the property will only be shown when inspecting a {@link Link}.
|
||||
* @param {Part} part the Part being inspected
|
||||
* @return {boolean}
|
||||
*/
|
||||
Inspector.showIfLink = function (part) { return part instanceof go.Link; };
|
||||
/**
|
||||
* This predicate function can be used as a value for the `show` option for properties.
|
||||
* When used, the property will only be shown when inspecting a {@link Group}.
|
||||
* @param {Part} part the Part being inspected
|
||||
* @return {boolean}
|
||||
*/
|
||||
Inspector.showIfGroup = function (part) { return part instanceof go.Group; };
|
||||
/**
|
||||
* This predicate function can be used as a value for the `show` option for properties.
|
||||
* When used, the property will only be shown if present.
|
||||
* Useful for properties such as `key`, which will be shown on Nodes and Groups, but normally not on Links
|
||||
* @param {Part|null} part the Part being inspected
|
||||
* @param {string} propname the property to check presence of
|
||||
* @return {boolean}
|
||||
*/
|
||||
Inspector.showIfPresent = function (data, propname) {
|
||||
if (data instanceof go.Part)
|
||||
data = data.data;
|
||||
return typeof data === 'object' && data[propname] !== undefined;
|
||||
};
|
||||
/**
|
||||
* Update the HTML state of this Inspector with the given object.
|
||||
*
|
||||
* If passed an object, the Inspector will inspect that object.
|
||||
* If passed null, this will do nothing.
|
||||
* If no parameter is supplied, the {@link #inspectedObject} will be set based on the value of {@link #inspectSelection}.
|
||||
* @param {Object=} object an optional argument, used when {@link #inspectSelection} is false to
|
||||
* set {@link #inspectedObject} and show and edit that object's properties.
|
||||
*/
|
||||
Inspector.prototype.inspectObject = function (object) {
|
||||
var inspectedObject = null;
|
||||
var inspectedObjects = null;
|
||||
if (object === null)
|
||||
return;
|
||||
if (object === undefined) {
|
||||
if (this._inspectSelection) {
|
||||
if (this._multipleSelection) { // gets the selection if multiple selection is true
|
||||
inspectedObjects = this._diagram.selection;
|
||||
}
|
||||
else { // otherwise grab the first object
|
||||
inspectedObject = this._diagram.selection.first();
|
||||
}
|
||||
}
|
||||
else { // if there is a single inspected object
|
||||
inspectedObject = this._inspectedObject;
|
||||
}
|
||||
}
|
||||
else { // if object was passed in as a parameter
|
||||
inspectedObject = object;
|
||||
}
|
||||
if (!inspectedObjects && inspectedObject) {
|
||||
inspectedObjects = new go.Set();
|
||||
inspectedObjects.add(inspectedObject);
|
||||
}
|
||||
if (!inspectedObjects || inspectedObjects.count < 1) { // if nothing is selected
|
||||
this.updateAllHTML();
|
||||
return;
|
||||
}
|
||||
if (inspectedObjects) {
|
||||
var mainDiv = this._div;
|
||||
mainDiv.innerHTML = '';
|
||||
var shared = new go.Map(); // for properties that the nodes have in common
|
||||
var properties = new go.Map(); // for adding properties
|
||||
var all = new go.Map(); // used later to prevent changing properties when unneeded
|
||||
var it = inspectedObjects.iterator;
|
||||
var nodecount = 2;
|
||||
// Build table:
|
||||
var table = document.createElement('table');
|
||||
var tbody = document.createElement('tbody');
|
||||
this.inspectedProperties = {};
|
||||
this.tabIndex = 0;
|
||||
var declaredProperties = this._properties;
|
||||
it.next();
|
||||
inspectedObject = it.value;
|
||||
this._inspectedObject = inspectedObject;
|
||||
var data = (inspectedObject instanceof go.Part) ? inspectedObject.data : inspectedObject;
|
||||
if (data) { // initial pass to set shared and all
|
||||
// Go through all the properties passed in to the inspector and add them to the map, if appropriate:
|
||||
for (var name_1 in declaredProperties) {
|
||||
var desc = declaredProperties[name_1];
|
||||
if (!this.canShowProperty(name_1, desc, inspectedObject))
|
||||
continue;
|
||||
var val = this.findValue(name_1, desc, data);
|
||||
if (val === '' && this._properties[name_1] && this._properties[name_1].type === 'checkbox') {
|
||||
shared.add(name_1, false);
|
||||
all.add(name_1, false);
|
||||
}
|
||||
else {
|
||||
shared.add(name_1, val);
|
||||
all.add(name_1, val);
|
||||
}
|
||||
}
|
||||
// Go through all the properties on the model data and add them to the map, if appropriate:
|
||||
if (this._includesOwnProperties) {
|
||||
for (var k in data) {
|
||||
if (k === '__gohashid')
|
||||
continue; // skip internal GoJS hash property
|
||||
if (this.inspectedProperties[k])
|
||||
continue; // already exists
|
||||
if (declaredProperties[k] && !this.canShowProperty(k, declaredProperties[k], inspectedObject))
|
||||
continue;
|
||||
shared.add(k, data[k]);
|
||||
all.add(k, data[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (it.next() && (this._showLimit < 1 || nodecount <= this._showLimit)) { // grabs all the properties from the other selected objects
|
||||
properties.clear();
|
||||
inspectedObject = it.value;
|
||||
if (inspectedObject) {
|
||||
// use either the Part.data or the object itself (for model.modelData)
|
||||
data = (inspectedObject instanceof go.Part) ? inspectedObject.data : inspectedObject;
|
||||
if (data) {
|
||||
// Go through all the properties passed in to the inspector and add them to properties to add, if appropriate:
|
||||
for (var name_2 in declaredProperties) {
|
||||
var desc = declaredProperties[name_2];
|
||||
if (!this.canShowProperty(name_2, desc, inspectedObject))
|
||||
continue;
|
||||
var val = this.findValue(name_2, desc, data);
|
||||
if (val === '' && this._properties[name_2] && this._properties[name_2].type === 'checkbox') {
|
||||
properties.add(name_2, false);
|
||||
}
|
||||
else {
|
||||
properties.add(name_2, val);
|
||||
}
|
||||
}
|
||||
// Go through all the properties on the model data and add them to properties to add, if appropriate:
|
||||
if (this._includesOwnProperties) {
|
||||
for (var k in data) {
|
||||
if (k === '__gohashid')
|
||||
continue; // skip internal GoJS hash property
|
||||
if (this.inspectedProperties[k])
|
||||
continue; // already exists
|
||||
if (declaredProperties[k] && !this.canShowProperty(k, declaredProperties[k], inspectedObject))
|
||||
continue;
|
||||
properties.add(k, data[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!this._showUnionProperties) {
|
||||
// Cleans up shared map with properties that aren't shared between the selected objects
|
||||
// Also adds properties to the add and shared maps if applicable
|
||||
var addIt = shared.iterator;
|
||||
var toRemove = [];
|
||||
while (addIt.next()) {
|
||||
if (properties.has(addIt.key)) {
|
||||
var newVal = all.get(addIt.key) + '|' + properties.get(addIt.key);
|
||||
all.set(addIt.key, newVal);
|
||||
if ((declaredProperties[addIt.key] && declaredProperties[addIt.key].type !== 'color'
|
||||
&& declaredProperties[addIt.key].type !== 'checkbox' && declaredProperties[addIt.key].type !== 'select')
|
||||
|| !declaredProperties[addIt.key]) { // for non-string properties i.e color
|
||||
newVal = shared.get(addIt.key) + '|' + properties.get(addIt.key);
|
||||
shared.set(addIt.key, newVal);
|
||||
}
|
||||
}
|
||||
else { // toRemove array since addIt is still iterating
|
||||
toRemove.push(addIt.key);
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < toRemove.length; i++) { // removes anything that doesn't showUnionProperties
|
||||
shared.remove(toRemove[i]);
|
||||
all.remove(toRemove[i]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Adds missing properties to all with the correct amount of seperators
|
||||
var addIt = properties.iterator;
|
||||
while (addIt.next()) {
|
||||
if (all.has(addIt.key)) {
|
||||
if ((declaredProperties[addIt.key] && declaredProperties[addIt.key].type !== 'color'
|
||||
&& declaredProperties[addIt.key].type !== 'checkbox' && declaredProperties[addIt.key].type !== 'select')
|
||||
|| !declaredProperties[addIt.key]) { // for non-string properties i.e color
|
||||
var newVal = all.get(addIt.key) + '|' + properties.get(addIt.key);
|
||||
all.set(addIt.key, newVal);
|
||||
}
|
||||
}
|
||||
else {
|
||||
var newVal = '';
|
||||
for (var i = 0; i < nodecount - 1; i++)
|
||||
newVal += '|';
|
||||
newVal += properties.get(addIt.key);
|
||||
all.set(addIt.key, newVal);
|
||||
}
|
||||
}
|
||||
// Adds bars in case properties is not in all
|
||||
addIt = all.iterator;
|
||||
while (addIt.next()) {
|
||||
if (!properties.has(addIt.key)) {
|
||||
if ((declaredProperties[addIt.key] && declaredProperties[addIt.key].type !== 'color'
|
||||
&& declaredProperties[addIt.key].type !== 'checkbox' && declaredProperties[addIt.key].type !== 'select')
|
||||
|| !declaredProperties[addIt.key]) { // for non-string properties i.e color
|
||||
var newVal = all.get(addIt.key) + '|';
|
||||
all.set(addIt.key, newVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
nodecount++;
|
||||
}
|
||||
// builds the table property rows and sets multipleProperties to help with updateall
|
||||
var mapIt = void 0;
|
||||
if (!this._showUnionProperties)
|
||||
mapIt = shared.iterator;
|
||||
else
|
||||
mapIt = all.iterator;
|
||||
while (mapIt.next()) {
|
||||
tbody.appendChild(this.buildPropertyRow(mapIt.key, mapIt.value)); // shows the properties that are allowed
|
||||
}
|
||||
table.appendChild(tbody);
|
||||
mainDiv.appendChild(table);
|
||||
var allIt = all.iterator;
|
||||
while (allIt.next()) {
|
||||
this.multipleProperties[allIt.key] = allIt.value; // used for updateall to know which properties to change
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* This predicate should be false if the given property should not be shown.
|
||||
* Normally it only checks the value of "show" on the property descriptor.
|
||||
*
|
||||
* The default value is true.
|
||||
* @param {string} propertyName the property name
|
||||
* @param {Object} propertyDesc the property descriptor
|
||||
* @param {Object} inspectedObject the data object
|
||||
* @return {boolean} whether a particular property should be shown in this Inspector
|
||||
*/
|
||||
Inspector.prototype.canShowProperty = function (propertyName, propertyDesc, inspectedObject) {
|
||||
var prop = propertyDesc;
|
||||
if (prop.show === false)
|
||||
return false;
|
||||
// if "show" is a predicate, make sure it passes or do not show this property
|
||||
if (typeof prop.show === 'function')
|
||||
return prop.show(inspectedObject, propertyName);
|
||||
return true;
|
||||
};
|
||||
/**
|
||||
* This predicate should be false if the given property should not be editable by the user.
|
||||
* Normally it only checks the value of "readOnly" on the property descriptor.
|
||||
*
|
||||
* The default value is true.
|
||||
* @param {string} propertyName the property name
|
||||
* @param {Object} propertyDesc the property descriptor
|
||||
* @param {Object} inspectedObject the data object
|
||||
* @return {boolean} whether a particular property should be shown in this Inspector
|
||||
*/
|
||||
Inspector.prototype.canEditProperty = function (propertyName, propertyDesc, inspectedObject) {
|
||||
if (this._diagram.isReadOnly || this._diagram.isModelReadOnly)
|
||||
return false;
|
||||
if (inspectedObject === null)
|
||||
return false;
|
||||
// assume property values that are functions of Objects cannot be edited
|
||||
var data = (inspectedObject instanceof go.Part) ? inspectedObject.data : inspectedObject;
|
||||
var valtype = typeof data[propertyName];
|
||||
if (valtype === 'function')
|
||||
return false;
|
||||
if (propertyDesc) {
|
||||
var prop = propertyDesc;
|
||||
if (prop.readOnly === true)
|
||||
return false;
|
||||
// if "readOnly" is a predicate, make sure it passes or do not show this property
|
||||
if (typeof prop.readOnly === 'function')
|
||||
return !prop.readOnly(inspectedObject, propertyName);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
/**
|
||||
* @ignore
|
||||
* @param propName
|
||||
* @param propDesc
|
||||
* @param data
|
||||
*/
|
||||
Inspector.prototype.findValue = function (propName, propDesc, data) {
|
||||
var val = '';
|
||||
if (propDesc && propDesc.defaultValue !== undefined)
|
||||
val = propDesc.defaultValue;
|
||||
if (data[propName] !== undefined)
|
||||
val = data[propName];
|
||||
if (val === undefined)
|
||||
return '';
|
||||
return val;
|
||||
};
|
||||
/**
|
||||
* This sets `inspectedProperties[propertyName]` and creates the HTML table row for a given property:
|
||||
* ```html
|
||||
* <tr>
|
||||
* <td>propertyName</td>
|
||||
* <td><input value=propertyValue /></td>
|
||||
* </tr>
|
||||
* ```
|
||||
*
|
||||
* This method can be customized to change how an Inspector row is rendered.
|
||||
* @param {string} propertyName the property name
|
||||
* @param {*} propertyValue the property value
|
||||
* @return {HTMLTableRowElement} the table row
|
||||
*/
|
||||
Inspector.prototype.buildPropertyRow = function (propertyName, propertyValue) {
|
||||
var tr = document.createElement('tr');
|
||||
var td1 = document.createElement('td');
|
||||
var displayName;
|
||||
if (this._properties[propertyName] && this._properties[propertyName].name !== undefined) { // name changes the dispaly name shown on inspector
|
||||
displayName = this._properties[propertyName].name;
|
||||
}
|
||||
else {
|
||||
displayName = propertyName;
|
||||
}
|
||||
td1.textContent = displayName;
|
||||
tr.appendChild(td1);
|
||||
var td2 = document.createElement('td');
|
||||
var decProp = this._properties[propertyName];
|
||||
var input = null;
|
||||
var self = this;
|
||||
function updateall() {
|
||||
if (self._diagram.selection.count === 1 || !self.multipleSelection) {
|
||||
self.updateAllProperties();
|
||||
}
|
||||
else {
|
||||
self.updateAllObjectsProperties();
|
||||
}
|
||||
}
|
||||
if (decProp && decProp.type === 'select') {
|
||||
input = document.createElement('select');
|
||||
this.updateSelect(decProp, input, propertyName, propertyValue);
|
||||
input.addEventListener('change', updateall);
|
||||
}
|
||||
else {
|
||||
input = document.createElement('input');
|
||||
input.value = this.convertToString(propertyValue);
|
||||
if (decProp) {
|
||||
var t = decProp.type;
|
||||
if (t !== 'string' && t !== 'number' && t !== 'boolean' &&
|
||||
t !== 'arrayofnumber' && t !== 'point' && t !== 'size' &&
|
||||
t !== 'rect' && t !== 'spot' && t !== 'margin') {
|
||||
input.setAttribute('type', decProp.type);
|
||||
}
|
||||
if (decProp.type === 'color') {
|
||||
if (input.type === 'color') {
|
||||
input.value = this.convertToColor(propertyValue);
|
||||
// input.addEventListener('input', updateall); // removed with multi select
|
||||
input.addEventListener('change', updateall);
|
||||
}
|
||||
}
|
||||
if (decProp.type === 'checkbox') {
|
||||
input.checked = !!propertyValue;
|
||||
input.addEventListener('change', updateall);
|
||||
}
|
||||
}
|
||||
if (input.type !== 'color')
|
||||
input.addEventListener('blur', updateall);
|
||||
}
|
||||
if (input) {
|
||||
input.tabIndex = this.tabIndex++;
|
||||
input.disabled = !this.canEditProperty(propertyName, decProp, this._inspectedObject);
|
||||
td2.appendChild(input);
|
||||
}
|
||||
tr.appendChild(td2);
|
||||
this.inspectedProperties[propertyName] = input;
|
||||
return tr;
|
||||
};
|
||||
/**
|
||||
* @hidden @ignore
|
||||
* HTML5 color input will only take hex,
|
||||
* so let HTML5 canvas convert the color into hex format.
|
||||
* This converts "rgb(255, 0, 0)" into "#FF0000", etc.
|
||||
*/
|
||||
Inspector.prototype.convertToColor = function (propertyValue) {
|
||||
var ctx = document.createElement('canvas').getContext('2d');
|
||||
if (ctx === null)
|
||||
return '#000000';
|
||||
ctx.fillStyle = propertyValue;
|
||||
return ctx.fillStyle;
|
||||
};
|
||||
/**
|
||||
* @hidden @ignore
|
||||
*/
|
||||
Inspector.prototype.convertToArrayOfNumber = function (propertyValue) {
|
||||
if (propertyValue === 'null')
|
||||
return null;
|
||||
var split = propertyValue.split(' ');
|
||||
var arr = [];
|
||||
for (var i = 0; i < split.length; i++) {
|
||||
var str = split[i];
|
||||
if (!str)
|
||||
continue;
|
||||
arr.push(parseFloat(str));
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
/**
|
||||
* @hidden @ignore
|
||||
*/
|
||||
Inspector.prototype.convertToString = function (x) {
|
||||
if (x === undefined)
|
||||
return 'undefined';
|
||||
if (x === null)
|
||||
return 'null';
|
||||
if (x instanceof go.Point)
|
||||
return go.Point.stringify(x);
|
||||
if (x instanceof go.Size)
|
||||
return go.Size.stringify(x);
|
||||
if (x instanceof go.Rect)
|
||||
return go.Rect.stringify(x);
|
||||
if (x instanceof go.Spot)
|
||||
return go.Spot.stringify(x);
|
||||
if (x instanceof go.Margin)
|
||||
return go.Margin.stringify(x);
|
||||
if (x instanceof go.List)
|
||||
return this.convertToString(x.toArray());
|
||||
if (Array.isArray(x)) {
|
||||
var str = '';
|
||||
for (var i = 0; i < x.length; i++) {
|
||||
if (i > 0)
|
||||
str += ' ';
|
||||
var v = x[i];
|
||||
str += this.convertToString(v);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
return x.toString();
|
||||
};
|
||||
/**
|
||||
* @hidden @ignore
|
||||
* Update all of the HTML in this Inspector.
|
||||
*/
|
||||
Inspector.prototype.updateAllHTML = function () {
|
||||
var inspectedProps = this.inspectedProperties;
|
||||
var isPart = this._inspectedObject instanceof go.Part;
|
||||
var data = isPart ? this._inspectedObject.data : this._inspectedObject;
|
||||
if (!data) { // clear out all of the fields
|
||||
for (var name_3 in inspectedProps) {
|
||||
var input = inspectedProps[name_3];
|
||||
if (input instanceof HTMLSelectElement) {
|
||||
input.innerHTML = '';
|
||||
}
|
||||
else if (input.type === 'color') {
|
||||
input.value = '#000000';
|
||||
}
|
||||
else if (input.type === 'checkbox') {
|
||||
input.checked = false;
|
||||
}
|
||||
else {
|
||||
input.value = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (var name_4 in inspectedProps) {
|
||||
var input = inspectedProps[name_4];
|
||||
var propertyValue = data[name_4];
|
||||
if (input instanceof HTMLSelectElement) {
|
||||
var decProp = this._properties[name_4];
|
||||
this.updateSelect(decProp, input, name_4, propertyValue);
|
||||
}
|
||||
else if (input.type === 'color') {
|
||||
input.value = this.convertToColor(propertyValue);
|
||||
}
|
||||
else if (input.type === 'checkbox') {
|
||||
input.checked = !!propertyValue;
|
||||
}
|
||||
else {
|
||||
input.value = this.convertToString(propertyValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @hidden @ignore
|
||||
* Update an HTMLSelectElement with an appropriate list of choices, given the propertyName
|
||||
*/
|
||||
Inspector.prototype.updateSelect = function (decProp, select, propertyName, propertyValue) {
|
||||
select.innerHTML = ''; // clear out anything that was there
|
||||
var choices = decProp.choices;
|
||||
if (typeof choices === 'function')
|
||||
choices = choices(this._inspectedObject, propertyName);
|
||||
if (!Array.isArray(choices))
|
||||
choices = [];
|
||||
decProp.choicesArray = choices; // remember list of actual choice values (not strings)
|
||||
for (var i = 0; i < choices.length; i++) {
|
||||
var choice = choices[i];
|
||||
var opt = document.createElement('option');
|
||||
opt.text = this.convertToString(choice);
|
||||
select.add(opt);
|
||||
}
|
||||
select.value = this.convertToString(propertyValue);
|
||||
};
|
||||
Inspector.prototype.parseValue = function (decProp, value, input, oldval) {
|
||||
// If it's a boolean, or if its previous value was boolean,
|
||||
// parse the value to be a boolean and then update the input.value to match
|
||||
var type = '';
|
||||
if (decProp !== undefined && decProp.type !== undefined) {
|
||||
type = decProp.type;
|
||||
}
|
||||
if (type === '') {
|
||||
if (typeof oldval === 'boolean')
|
||||
type = 'boolean'; // infer boolean
|
||||
else if (typeof oldval === 'number')
|
||||
type = 'number';
|
||||
else if (oldval instanceof go.Point)
|
||||
type = 'point';
|
||||
else if (oldval instanceof go.Size)
|
||||
type = 'size';
|
||||
else if (oldval instanceof go.Rect)
|
||||
type = 'rect';
|
||||
else if (oldval instanceof go.Spot)
|
||||
type = 'spot';
|
||||
else if (oldval instanceof go.Margin)
|
||||
type = 'margin';
|
||||
}
|
||||
// convert to specific type, if needed
|
||||
switch (type) {
|
||||
case 'boolean':
|
||||
value = !(value === false || value === 'false' || value === '0');
|
||||
break;
|
||||
case 'number':
|
||||
value = parseFloat(value);
|
||||
break;
|
||||
case 'arrayofnumber':
|
||||
value = this.convertToArrayOfNumber(value);
|
||||
break;
|
||||
case 'point':
|
||||
value = go.Point.parse(value);
|
||||
break;
|
||||
case 'size':
|
||||
value = go.Size.parse(value);
|
||||
break;
|
||||
case 'rect':
|
||||
value = go.Rect.parse(value);
|
||||
break;
|
||||
case 'spot':
|
||||
value = go.Spot.parse(value);
|
||||
break;
|
||||
case 'margin':
|
||||
value = go.Margin.parse(value);
|
||||
break;
|
||||
case 'checkbox':
|
||||
value = input.checked;
|
||||
break;
|
||||
case 'select':
|
||||
value = decProp.choicesArray[input.selectedIndex];
|
||||
break;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
/**
|
||||
* @hidden @ignore
|
||||
* Update all of the data properties of all the objects in {@link #inspectedObjects} according to the
|
||||
* current values held in the HTML input elements.
|
||||
*/
|
||||
Inspector.prototype.updateAllObjectsProperties = function () {
|
||||
var inspectedProps = this.inspectedProperties;
|
||||
var diagram = this._diagram;
|
||||
diagram.startTransaction('set all properties');
|
||||
for (var name_5 in inspectedProps) {
|
||||
var input = inspectedProps[name_5];
|
||||
var value = input.value;
|
||||
var arr1 = value.split('|');
|
||||
var arr2 = [];
|
||||
if (this.multipleProperties[name_5]) {
|
||||
// don't split if it is union and its checkbox type
|
||||
if (this._properties[name_5] && this._properties[name_5].type === 'checkbox' && this._showUnionProperties) {
|
||||
arr2.push(this.multipleProperties[name_5]);
|
||||
}
|
||||
else if (this._properties[name_5]) {
|
||||
arr2 = this.multipleProperties[name_5].toString().split('|');
|
||||
}
|
||||
}
|
||||
var it = diagram.selection.iterator;
|
||||
var change = false;
|
||||
if (this._properties[name_5] && this._properties[name_5].type === 'checkbox')
|
||||
change = true; // always change checkbox
|
||||
if (arr1.length < arr2.length // i.e Alpha|Beta -> Alpha procs the change
|
||||
&& (!this._properties[name_5] // from and to links
|
||||
|| !(this._properties[name_5] // do not change color checkbox and choices due to them always having less
|
||||
&& (this._properties[name_5].type === 'color' || this._properties[name_5].type === 'checkbox' || this._properties[name_5].type === 'choices')))) {
|
||||
change = true;
|
||||
}
|
||||
else { // standard detection in change in properties
|
||||
for (var j = 0; j < arr1.length && j < arr2.length; j++) {
|
||||
if (!(arr1[j] === arr2[j])
|
||||
&& !(this._properties[name_5] && this._properties[name_5].type === 'color' && arr1[j].toLowerCase() === arr2[j].toLowerCase())) {
|
||||
change = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (change) { // only change properties it needs to change instead all of them
|
||||
for (var i = 0; i < diagram.selection.count; i++) {
|
||||
it.next();
|
||||
var isPart = it.value instanceof go.Part;
|
||||
var data = isPart ? it.value.data : it.value;
|
||||
if (data) { // ignores the selected node if there is no data
|
||||
if (i < arr1.length)
|
||||
value = arr1[i];
|
||||
else
|
||||
value = arr1[0];
|
||||
// don't update "readOnly" data properties
|
||||
var decProp = this._properties[name_5];
|
||||
if (!this.canEditProperty(name_5, decProp, it.value))
|
||||
continue;
|
||||
var oldval = data[name_5];
|
||||
value = this.parseValue(decProp, value, input, oldval);
|
||||
// in case parsed to be different, such as in the case of boolean values,
|
||||
// the value shown should match the actual value
|
||||
input.value = value;
|
||||
// modify the data object in an undo-able fashion
|
||||
diagram.model.setDataProperty(data, name_5, value);
|
||||
// notify any listener
|
||||
if (this.propertyModified !== null)
|
||||
this.propertyModified(name_5, value, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
diagram.commitTransaction('set all properties');
|
||||
};
|
||||
/**
|
||||
* @hidden @ignore
|
||||
* Update all of the data properties of {@link #inspectedObject} according to the
|
||||
* current values held in the HTML input elements.
|
||||
*/
|
||||
Inspector.prototype.updateAllProperties = function () {
|
||||
var inspectedProps = this.inspectedProperties;
|
||||
var diagram = this._diagram;
|
||||
var isPart = this.inspectedObject instanceof go.Part;
|
||||
var data = isPart ? this.inspectedObject.data : this.inspectedObject;
|
||||
if (!data)
|
||||
return; // must not try to update data when there's no data!
|
||||
diagram.startTransaction('set all properties');
|
||||
for (var name_6 in inspectedProps) {
|
||||
var input = inspectedProps[name_6];
|
||||
var value = input.value;
|
||||
// don't update "readOnly" data properties
|
||||
var decProp = this._properties[name_6];
|
||||
if (!this.canEditProperty(name_6, decProp, this.inspectedObject))
|
||||
continue;
|
||||
var oldval = data[name_6];
|
||||
value = this.parseValue(decProp, value, input, oldval);
|
||||
// in case parsed to be different, such as in the case of boolean values,
|
||||
// the value shown should match the actual value
|
||||
input.value = value;
|
||||
// modify the data object in an undo-able fashion
|
||||
diagram.model.setDataProperty(data, name_6, value);
|
||||
// notify any listener
|
||||
if (this.propertyModified !== null)
|
||||
this.propertyModified(name_6, value, this);
|
||||
}
|
||||
diagram.commitTransaction('set all properties');
|
||||
};
|
||||
return Inspector;
|
||||
}());
|
||||
exports.Inspector = Inspector;
|
||||
});
|
||||
+862
@@ -0,0 +1,862 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
|
||||
/**
|
||||
* This class implements an inspector for GoJS model data objects.
|
||||
* The constructor takes three arguments:
|
||||
* - `divid` ***string*** a string referencing the HTML ID of the to-be inspector's div
|
||||
* - `diagram` ***Diagram*** a reference to a GoJS Diagram
|
||||
* - `options` ***Object*** an optional JS Object describing options for the inspector
|
||||
*
|
||||
* Options:
|
||||
* - `inspectSelection` ***boolean*** see {@link #inspectSelection}
|
||||
* - `includesOwnProperties` ***boolean*** see {@link #includesOwnProperties}
|
||||
* - `properties` ***Object*** see {@link #properties}
|
||||
* - `propertyModified` ***function(propertyName, newValue, inspector)*** see {@link #propertyModified}
|
||||
* - `multipleSelection` ***boolean*** see {@link #multipleSelection}
|
||||
* - `showUnionProperties` ***boolean*** see {@link #showUnionProperties}
|
||||
* - `showLimit` ***number*** see {@link #showLimit}
|
||||
*
|
||||
* Options for properties:
|
||||
* - `show` ***boolean | function*** a boolean value to show or hide the property from the inspector, or a predicate function to show conditionally.
|
||||
* - `readOnly` ***boolean | function*** whether or not the property is read-only
|
||||
* - `type` ***string*** a string describing the data type. Supported values: "string|number|boolean|color|arrayofnumber|point|rect|size|spot|margin|select"
|
||||
* - `defaultValue` ***any*** a default value for the property. Defaults to the empty string.
|
||||
* - `choices` ***Array | function*** when type === "select", the Array of choices to use or a function that returns the Array of choices.
|
||||
*
|
||||
* Example usage of Inspector:
|
||||
* ```js
|
||||
* var inspector = new Inspector("myInspector", myDiagram,
|
||||
* {
|
||||
* includesOwnProperties: false,
|
||||
* properties: {
|
||||
* "key": { show: Inspector.showIfPresent, readOnly: true },
|
||||
* "comments": { show: Inspector.showIfNode },
|
||||
* "LinkComments": { show: Inspector.showIfLink },
|
||||
* "chosen": { show: Inspector.showIfNode, type: "checkbox" },
|
||||
* "state": { show: Inspector.showIfNode, type: "select", choices: ["Stopped", "Parked", "Moving"] }
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This is the basic HTML Structure that the Inspector creates within the given DIV element:
|
||||
* ```html
|
||||
* <div id="divid" class="inspector">
|
||||
* <tr>
|
||||
* <td>propertyName</td>
|
||||
* <td><input value=propertyValue /></td>
|
||||
* </tr>
|
||||
* ...
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/DataInspector.html">Data Inspector</a> sample.
|
||||
* @category Extension
|
||||
*/
|
||||
export class Inspector {
|
||||
private _div: HTMLDivElement;
|
||||
private _diagram: go.Diagram;
|
||||
private _inspectedObject: go.ObjectData | null = null;
|
||||
// Inspector options defaults:
|
||||
private _inspectSelection: boolean = true;
|
||||
private _includesOwnProperties: boolean = true;
|
||||
private _properties: { [index: string]: any } = {};
|
||||
private _propertyModified: ((a: string, b: string, c: Inspector) => void) | null = null;
|
||||
private _multipleSelection: boolean = false;
|
||||
private _showUnionProperties: boolean = false;
|
||||
private _showLimit: number = 0;
|
||||
|
||||
// Private variables used to keep track of internal state
|
||||
private inspectedProperties: { [index: string]: any } = {};
|
||||
private multipleProperties: { [index: string]: any } = {};
|
||||
private tabIndex: number;
|
||||
// Functions used to keep the Inspector up-to-date
|
||||
private inspectOnModelChanged: ((e: go.ChangedEvent) => void);
|
||||
private inspectOnSelectionChanged: ((e: go.DiagramEvent) => void);
|
||||
|
||||
/**
|
||||
* Constructs an Inspector and sets up properties based on the options provided.
|
||||
* Also sets up change listeners on the Diagram so the Inspector stays up-to-date.
|
||||
* @param {string} divid a string referencing the HTML ID of the to-be Inspector's div
|
||||
* @param {Diagram} diagram a reference to a GoJS Diagram
|
||||
* @param {Object=} options an optional JS Object describing options for the inspector
|
||||
*/
|
||||
constructor(divid: string, diagram: go.Diagram, options?: { [index: string]: any }) {
|
||||
const mainDiv = document.getElementById(divid) as HTMLDivElement;
|
||||
mainDiv.className = 'inspector';
|
||||
mainDiv.innerHTML = '';
|
||||
this._div = mainDiv;
|
||||
this._diagram = diagram;
|
||||
this.tabIndex = 0;
|
||||
// Set properties based on options
|
||||
if (options !== undefined) {
|
||||
if (options.inspectSelection !== undefined) this._inspectSelection = options.inspectSelection;
|
||||
if (options.includesOwnProperties !== undefined) this._includesOwnProperties = options.includesOwnProperties;
|
||||
if (options.properties !== undefined) this._properties = options.properties;
|
||||
if (options.propertyModified !== undefined) this._propertyModified = options.propertyModified;
|
||||
if (options.multipleSelection !== undefined) this._multipleSelection = options.multipleSelection;
|
||||
if (options.showUnionProperties !== undefined) this._showUnionProperties = options.showUnionProperties;
|
||||
if (options.showLimit !== undefined) this._showLimit = options.showLimit;
|
||||
}
|
||||
// Prepare change listeners
|
||||
const self = this;
|
||||
this.inspectOnModelChanged = (e: go.ChangedEvent) => {
|
||||
if (e.isTransactionFinished) self.inspectObject();
|
||||
};
|
||||
this.inspectOnSelectionChanged = (e: go.DiagramEvent) => { self.inspectObject(); };
|
||||
this._diagram.addModelChangedListener(this.inspectOnModelChanged);
|
||||
if (this._inspectSelection) {
|
||||
this._diagram.addDiagramListener('ChangedSelection', this.inspectOnSelectionChanged);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This read-only property returns the HTMLElement containing the Inspector.
|
||||
*/
|
||||
get div(): HTMLDivElement { return this._div; }
|
||||
|
||||
/**
|
||||
* Gets or sets the {@link Diagram} associated with this Inspector.
|
||||
*/
|
||||
get diagram(): go.Diagram { return this._diagram; }
|
||||
set diagram(val: go.Diagram) {
|
||||
if (val !== this._diagram) {
|
||||
// First, unassociate change listeners with current inspected diagram
|
||||
this._diagram.removeModelChangedListener(this.inspectOnModelChanged);
|
||||
this._diagram.removeDiagramListener('ChangedSelection', this.inspectOnSelectionChanged);
|
||||
// Now set the diagram and add the necessary change listeners
|
||||
this._diagram = val;
|
||||
this._diagram.addModelChangedListener(this.inspectOnModelChanged);
|
||||
if (this._inspectSelection) {
|
||||
this._diagram.addDiagramListener('ChangedSelection', this.inspectOnSelectionChanged);
|
||||
this.inspectObject();
|
||||
} else {
|
||||
this.inspectObject(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This read-only property returns the object currently being inspected.
|
||||
*
|
||||
* To set the inspected object, call {@link #inspectObject}.
|
||||
*/
|
||||
get inspectedObject(): go.ObjectData | null { return this._inspectedObject; }
|
||||
|
||||
/**
|
||||
* Gets or sets whether the Inspector automatically inspects the associated Diagram's selection.
|
||||
* When set to false, the Inspector won't show anything until {@link #inspectObject} is called.
|
||||
*
|
||||
* The default value is true.
|
||||
*/
|
||||
get inspectSelection(): boolean { return this._inspectSelection; }
|
||||
set inspectSelection(val: boolean) {
|
||||
if (val !== this._inspectSelection) {
|
||||
this._inspectSelection = val;
|
||||
if (this._inspectSelection) {
|
||||
this._diagram.addDiagramListener('ChangedSelection', this.inspectOnSelectionChanged);
|
||||
this.inspectObject();
|
||||
} else {
|
||||
this._diagram.removeDiagramListener('ChangedSelection', this.inspectOnSelectionChanged);
|
||||
this.inspectObject(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets whether the Inspector includes all properties currently on the inspected object.
|
||||
*
|
||||
* The default value is true.
|
||||
*/
|
||||
get includesOwnProperties(): boolean { return this._includesOwnProperties; }
|
||||
set includesOwnProperties(val: boolean) {
|
||||
if (val !== this._includesOwnProperties) {
|
||||
this._includesOwnProperties = val;
|
||||
this.inspectObject();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the properties that the Inspector will inspect, maybe setting options for those properties.
|
||||
* The object should contain string: Object pairs represnting propertyName: propertyOptions.
|
||||
* Can be used to include or exclude additional properties.
|
||||
*
|
||||
* The default value is an empty object.
|
||||
*/
|
||||
get properties(): go.ObjectData { return this._properties; }
|
||||
set properties(val: go.ObjectData) {
|
||||
if (val !== this._properties) {
|
||||
this._properties = val;
|
||||
this.inspectObject();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the function to be called when a property is modified by the Inspector.
|
||||
* The first paremeter will be the property name, the second will be the new value, and the third will be a reference to this Inspector.
|
||||
*
|
||||
* The default value is null, meaning nothing will be done.
|
||||
*/
|
||||
get propertyModified(): ((a: string, b: string, c: Inspector) => void) | null { return this._propertyModified; }
|
||||
set propertyModified(val: ((a: string, b: string, c: Inspector) => void) | null) {
|
||||
if (val !== this._propertyModified) {
|
||||
this._propertyModified = val;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets whether the Inspector displays properties for multiple selected objects or just the first.
|
||||
*
|
||||
* The default value is false, meaning only the first item in the {@link Diagram#selection} is inspected.
|
||||
*/
|
||||
get multipleSelection(): boolean { return this._multipleSelection; }
|
||||
set multipleSelection(val: boolean) {
|
||||
if (val !== this._multipleSelection) {
|
||||
this._multipleSelection = val;
|
||||
this.inspectObject();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets whether the Inspector displays the union or intersection of properties for multiple selected objects.
|
||||
*
|
||||
* The default value is false, meaning the intersection of properties is inspected.
|
||||
*/
|
||||
get showUnionProperties(): boolean { return this._showUnionProperties; }
|
||||
set showUnionProperties(val: boolean) {
|
||||
if (val !== this._showUnionProperties) {
|
||||
this._showUnionProperties = val;
|
||||
this.inspectObject();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets how many objects will be displayed when {@link #multipleSelection} is true.
|
||||
*
|
||||
* The default value is 0, meaning all selected objects will be displayed for a given property.
|
||||
*/
|
||||
get showLimit(): number { return this._showLimit; }
|
||||
set showLimit(val: number) {
|
||||
if (val !== this._showLimit) {
|
||||
this._showLimit = val;
|
||||
this.inspectObject();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This predicate function can be used as a value for the `show` option for properties.
|
||||
* When used, the property will only be shown when inspecting a {@link Node}.
|
||||
* @param {Part} part the Part being inspected
|
||||
* @return {boolean}
|
||||
*/
|
||||
public static showIfNode(part: go.Part): boolean { return part instanceof go.Node; }
|
||||
|
||||
/**
|
||||
* This predicate function can be used as a value for the `show` option for properties.
|
||||
* When used, the property will only be shown when inspecting a {@link Link}.
|
||||
* @param {Part} part the Part being inspected
|
||||
* @return {boolean}
|
||||
*/
|
||||
public static showIfLink(part: go.Part): boolean { return part instanceof go.Link; }
|
||||
|
||||
/**
|
||||
* This predicate function can be used as a value for the `show` option for properties.
|
||||
* When used, the property will only be shown when inspecting a {@link Group}.
|
||||
* @param {Part} part the Part being inspected
|
||||
* @return {boolean}
|
||||
*/
|
||||
public static showIfGroup(part: go.Part): boolean { return part instanceof go.Group; }
|
||||
|
||||
/**
|
||||
* This predicate function can be used as a value for the `show` option for properties.
|
||||
* When used, the property will only be shown if present.
|
||||
* Useful for properties such as `key`, which will be shown on Nodes and Groups, but normally not on Links
|
||||
* @param {Part|null} part the Part being inspected
|
||||
* @param {string} propname the property to check presence of
|
||||
* @return {boolean}
|
||||
*/
|
||||
public static showIfPresent(data: go.Part | null, propname: string): boolean {
|
||||
if (data instanceof go.Part) data = data.data;
|
||||
return typeof data === 'object' && (data as any)[propname] !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the HTML state of this Inspector with the given object.
|
||||
*
|
||||
* If passed an object, the Inspector will inspect that object.
|
||||
* If passed null, this will do nothing.
|
||||
* If no parameter is supplied, the {@link #inspectedObject} will be set based on the value of {@link #inspectSelection}.
|
||||
* @param {Object=} object an optional argument, used when {@link #inspectSelection} is false to
|
||||
* set {@link #inspectedObject} and show and edit that object's properties.
|
||||
*/
|
||||
public inspectObject(object?: go.ObjectData | null): void {
|
||||
let inspectedObject: go.ObjectData | null = null;
|
||||
let inspectedObjects: go.Set<go.ObjectData> | null = null;
|
||||
if (object === null) return;
|
||||
if (object === undefined) {
|
||||
if (this._inspectSelection) {
|
||||
if (this._multipleSelection) { // gets the selection if multiple selection is true
|
||||
inspectedObjects = this._diagram.selection;
|
||||
} else { // otherwise grab the first object
|
||||
inspectedObject = this._diagram.selection.first();
|
||||
}
|
||||
} else { // if there is a single inspected object
|
||||
inspectedObject = this._inspectedObject;
|
||||
}
|
||||
} else { // if object was passed in as a parameter
|
||||
inspectedObject = object;
|
||||
}
|
||||
if (!inspectedObjects && inspectedObject) {
|
||||
inspectedObjects = new go.Set<go.ObjectData>();
|
||||
inspectedObjects.add(inspectedObject);
|
||||
}
|
||||
if (!inspectedObjects || inspectedObjects.count < 1) { // if nothing is selected
|
||||
this.updateAllHTML();
|
||||
return;
|
||||
}
|
||||
|
||||
if (inspectedObjects) {
|
||||
const mainDiv = this._div;
|
||||
mainDiv.innerHTML = '';
|
||||
const shared: go.Map<string, any> = new go.Map<string, any>(); // for properties that the nodes have in common
|
||||
const properties: go.Map<string, any> = new go.Map<string, any>(); // for adding properties
|
||||
const all: go.Map<string, any> = new go.Map<string, any>(); // used later to prevent changing properties when unneeded
|
||||
const it = inspectedObjects.iterator;
|
||||
let nodecount = 2;
|
||||
// Build table:
|
||||
const table = document.createElement('table');
|
||||
const tbody = document.createElement('tbody');
|
||||
this.inspectedProperties = {};
|
||||
this.tabIndex = 0;
|
||||
const declaredProperties = this._properties;
|
||||
it.next();
|
||||
inspectedObject = it.value;
|
||||
this._inspectedObject = inspectedObject;
|
||||
let data = (inspectedObject instanceof go.Part) ? inspectedObject.data : inspectedObject;
|
||||
if (data) { // initial pass to set shared and all
|
||||
// Go through all the properties passed in to the inspector and add them to the map, if appropriate:
|
||||
for (const name in declaredProperties) {
|
||||
const desc = declaredProperties[name];
|
||||
if (!this.canShowProperty(name, desc, inspectedObject)) continue;
|
||||
const val = this.findValue(name, desc, data);
|
||||
if (val === '' && this._properties[name] && this._properties[name].type === 'checkbox') {
|
||||
shared.add(name, false);
|
||||
all.add(name, false);
|
||||
} else {
|
||||
shared.add(name, val);
|
||||
all.add(name, val);
|
||||
}
|
||||
}
|
||||
// Go through all the properties on the model data and add them to the map, if appropriate:
|
||||
if (this._includesOwnProperties) {
|
||||
for (const k in data) {
|
||||
if (k === '__gohashid') continue; // skip internal GoJS hash property
|
||||
if (this.inspectedProperties[k]) continue; // already exists
|
||||
if (declaredProperties[k] && !this.canShowProperty(k, declaredProperties[k], inspectedObject)) continue;
|
||||
shared.add(k, data[k]);
|
||||
all.add(k, data[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (it.next() && (this._showLimit < 1 || nodecount <= this._showLimit)) { // grabs all the properties from the other selected objects
|
||||
properties.clear();
|
||||
inspectedObject = it.value;
|
||||
if (inspectedObject) {
|
||||
// use either the Part.data or the object itself (for model.modelData)
|
||||
data = (inspectedObject instanceof go.Part) ? inspectedObject.data : inspectedObject;
|
||||
if (data) {
|
||||
// Go through all the properties passed in to the inspector and add them to properties to add, if appropriate:
|
||||
for (const name in declaredProperties) {
|
||||
const desc = declaredProperties[name];
|
||||
if (!this.canShowProperty(name, desc, inspectedObject)) continue;
|
||||
const val = this.findValue(name, desc, data);
|
||||
if (val === '' && this._properties[name] && this._properties[name].type === 'checkbox') {
|
||||
properties.add(name, false);
|
||||
} else {
|
||||
properties.add(name, val);
|
||||
}
|
||||
}
|
||||
// Go through all the properties on the model data and add them to properties to add, if appropriate:
|
||||
if (this._includesOwnProperties) {
|
||||
for (const k in data) {
|
||||
if (k === '__gohashid') continue; // skip internal GoJS hash property
|
||||
if (this.inspectedProperties[k]) continue; // already exists
|
||||
if (declaredProperties[k] && !this.canShowProperty(k, declaredProperties[k], inspectedObject)) continue;
|
||||
properties.add(k, data[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!this._showUnionProperties) {
|
||||
// Cleans up shared map with properties that aren't shared between the selected objects
|
||||
// Also adds properties to the add and shared maps if applicable
|
||||
const addIt = shared.iterator;
|
||||
const toRemove: Array<string> = [];
|
||||
while (addIt.next()) {
|
||||
if (properties.has(addIt.key)) {
|
||||
let newVal = all.get(addIt.key) + '|' + properties.get(addIt.key);
|
||||
all.set(addIt.key, newVal);
|
||||
if ((declaredProperties[addIt.key] && declaredProperties[addIt.key].type !== 'color'
|
||||
&& declaredProperties[addIt.key].type !== 'checkbox' && declaredProperties[addIt.key].type !== 'select')
|
||||
|| !declaredProperties[addIt.key]) { // for non-string properties i.e color
|
||||
newVal = shared.get(addIt.key) + '|' + properties.get(addIt.key);
|
||||
shared.set(addIt.key, newVal);
|
||||
}
|
||||
} else { // toRemove array since addIt is still iterating
|
||||
toRemove.push(addIt.key);
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < toRemove.length; i++) { // removes anything that doesn't showUnionProperties
|
||||
shared.remove(toRemove[i]);
|
||||
all.remove(toRemove[i]);
|
||||
}
|
||||
} else {
|
||||
// Adds missing properties to all with the correct amount of seperators
|
||||
let addIt = properties.iterator;
|
||||
while (addIt.next()) {
|
||||
if (all.has(addIt.key)) {
|
||||
if ((declaredProperties[addIt.key] && declaredProperties[addIt.key].type !== 'color'
|
||||
&& declaredProperties[addIt.key].type !== 'checkbox' && declaredProperties[addIt.key].type !== 'select')
|
||||
|| !declaredProperties[addIt.key]) { // for non-string properties i.e color
|
||||
const newVal = all.get(addIt.key) + '|' + properties.get(addIt.key);
|
||||
all.set(addIt.key, newVal);
|
||||
}
|
||||
} else {
|
||||
let newVal = '';
|
||||
for (let i = 0; i < nodecount - 1; i++) newVal += '|';
|
||||
newVal += properties.get(addIt.key);
|
||||
all.set(addIt.key, newVal);
|
||||
}
|
||||
}
|
||||
// Adds bars in case properties is not in all
|
||||
addIt = all.iterator;
|
||||
while (addIt.next()) {
|
||||
if (!properties.has(addIt.key)) {
|
||||
if ((declaredProperties[addIt.key] && declaredProperties[addIt.key].type !== 'color'
|
||||
&& declaredProperties[addIt.key].type !== 'checkbox' && declaredProperties[addIt.key].type !== 'select')
|
||||
|| !declaredProperties[addIt.key]) { // for non-string properties i.e color
|
||||
const newVal = all.get(addIt.key) + '|';
|
||||
all.set(addIt.key, newVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
nodecount++;
|
||||
}
|
||||
// builds the table property rows and sets multipleProperties to help with updateall
|
||||
let mapIt;
|
||||
if (!this._showUnionProperties) mapIt = shared.iterator;
|
||||
else mapIt = all.iterator;
|
||||
while (mapIt.next()) {
|
||||
tbody.appendChild(this.buildPropertyRow(mapIt.key, mapIt.value)); // shows the properties that are allowed
|
||||
}
|
||||
table.appendChild(tbody);
|
||||
mainDiv.appendChild(table);
|
||||
const allIt = all.iterator;
|
||||
while (allIt.next()) {
|
||||
this.multipleProperties[allIt.key] = allIt.value; // used for updateall to know which properties to change
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This predicate should be false if the given property should not be shown.
|
||||
* Normally it only checks the value of "show" on the property descriptor.
|
||||
*
|
||||
* The default value is true.
|
||||
* @param {string} propertyName the property name
|
||||
* @param {Object} propertyDesc the property descriptor
|
||||
* @param {Object} inspectedObject the data object
|
||||
* @return {boolean} whether a particular property should be shown in this Inspector
|
||||
*/
|
||||
public canShowProperty(propertyName: string, propertyDesc: go.ObjectData, inspectedObject: go.ObjectData): boolean {
|
||||
const prop = propertyDesc as any;
|
||||
if (prop.show === false) return false;
|
||||
// if "show" is a predicate, make sure it passes or do not show this property
|
||||
if (typeof prop.show === 'function') return prop.show(inspectedObject, propertyName);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This predicate should be false if the given property should not be editable by the user.
|
||||
* Normally it only checks the value of "readOnly" on the property descriptor.
|
||||
*
|
||||
* The default value is true.
|
||||
* @param {string} propertyName the property name
|
||||
* @param {Object} propertyDesc the property descriptor
|
||||
* @param {Object} inspectedObject the data object
|
||||
* @return {boolean} whether a particular property should be shown in this Inspector
|
||||
*/
|
||||
public canEditProperty(propertyName: string, propertyDesc: go.ObjectData, inspectedObject: go.ObjectData | null): boolean {
|
||||
if (this._diagram.isReadOnly || this._diagram.isModelReadOnly) return false;
|
||||
if (inspectedObject === null) return false;
|
||||
// assume property values that are functions of Objects cannot be edited
|
||||
const data = (inspectedObject instanceof go.Part) ? inspectedObject.data : inspectedObject;
|
||||
const valtype = typeof data[propertyName];
|
||||
if (valtype === 'function') return false;
|
||||
if (propertyDesc) {
|
||||
const prop = propertyDesc as any;
|
||||
if (prop.readOnly === true) return false;
|
||||
// if "readOnly" is a predicate, make sure it passes or do not show this property
|
||||
if (typeof prop.readOnly === 'function') return !prop.readOnly(inspectedObject, propertyName);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
* @param propName
|
||||
* @param propDesc
|
||||
* @param data
|
||||
*/
|
||||
private findValue(propName: string, propDesc: any, data: any): any {
|
||||
let val = '';
|
||||
if (propDesc && propDesc.defaultValue !== undefined) val = propDesc.defaultValue;
|
||||
if (data[propName] !== undefined) val = data[propName];
|
||||
if (val === undefined) return '';
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets `inspectedProperties[propertyName]` and creates the HTML table row for a given property:
|
||||
* ```html
|
||||
* <tr>
|
||||
* <td>propertyName</td>
|
||||
* <td><input value=propertyValue /></td>
|
||||
* </tr>
|
||||
* ```
|
||||
*
|
||||
* This method can be customized to change how an Inspector row is rendered.
|
||||
* @param {string} propertyName the property name
|
||||
* @param {*} propertyValue the property value
|
||||
* @return {HTMLTableRowElement} the table row
|
||||
*/
|
||||
public buildPropertyRow(propertyName: string, propertyValue: any): HTMLTableRowElement {
|
||||
const tr = document.createElement('tr');
|
||||
|
||||
const td1 = document.createElement('td');
|
||||
let displayName;
|
||||
if (this._properties[propertyName] && this._properties[propertyName].name !== undefined) { // name changes the dispaly name shown on inspector
|
||||
displayName = this._properties[propertyName].name;
|
||||
} else {
|
||||
displayName = propertyName;
|
||||
}
|
||||
td1.textContent = displayName;
|
||||
|
||||
tr.appendChild(td1);
|
||||
|
||||
const td2 = document.createElement('td');
|
||||
const decProp = this._properties[propertyName];
|
||||
let input = null;
|
||||
const self = this;
|
||||
function updateall() {
|
||||
if (self._diagram.selection.count === 1 || !self.multipleSelection) {
|
||||
self.updateAllProperties();
|
||||
} else {
|
||||
self.updateAllObjectsProperties();
|
||||
}
|
||||
}
|
||||
|
||||
if (decProp && decProp.type === 'select') {
|
||||
input = document.createElement('select');
|
||||
this.updateSelect(decProp, input, propertyName, propertyValue);
|
||||
input.addEventListener('change', updateall);
|
||||
} else {
|
||||
input = document.createElement('input');
|
||||
|
||||
input.value = this.convertToString(propertyValue);
|
||||
if (decProp) {
|
||||
const t = decProp.type;
|
||||
if (t !== 'string' && t !== 'number' && t !== 'boolean' &&
|
||||
t !== 'arrayofnumber' && t !== 'point' && t !== 'size' &&
|
||||
t !== 'rect' && t !== 'spot' && t !== 'margin') {
|
||||
input.setAttribute('type', decProp.type);
|
||||
}
|
||||
if (decProp.type === 'color') {
|
||||
if (input.type === 'color') {
|
||||
input.value = this.convertToColor(propertyValue);
|
||||
// input.addEventListener('input', updateall); // removed with multi select
|
||||
input.addEventListener('change', updateall);
|
||||
}
|
||||
} if (decProp.type === 'checkbox') {
|
||||
input.checked = !!propertyValue;
|
||||
input.addEventListener('change', updateall);
|
||||
}
|
||||
}
|
||||
if (input.type !== 'color') input.addEventListener('blur', updateall);
|
||||
}
|
||||
|
||||
if (input) {
|
||||
input.tabIndex = this.tabIndex++;
|
||||
input.disabled = !this.canEditProperty(propertyName, decProp, this._inspectedObject);
|
||||
td2.appendChild(input);
|
||||
}
|
||||
tr.appendChild(td2);
|
||||
|
||||
this.inspectedProperties[propertyName] = input;
|
||||
return tr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden @ignore
|
||||
* HTML5 color input will only take hex,
|
||||
* so let HTML5 canvas convert the color into hex format.
|
||||
* This converts "rgb(255, 0, 0)" into "#FF0000", etc.
|
||||
*/
|
||||
public convertToColor(propertyValue: string): string {
|
||||
const ctx: CanvasRenderingContext2D | null = document.createElement('canvas').getContext('2d');
|
||||
if (ctx === null) return '#000000';
|
||||
ctx.fillStyle = propertyValue;
|
||||
return ctx.fillStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden @ignore
|
||||
*/
|
||||
public convertToArrayOfNumber(propertyValue: string): Array<number> | null {
|
||||
if (propertyValue === 'null') return null;
|
||||
const split = propertyValue.split(' ');
|
||||
const arr = [];
|
||||
for (let i = 0; i < split.length; i++) {
|
||||
const str = split[i];
|
||||
if (!str) continue;
|
||||
arr.push(parseFloat(str));
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden @ignore
|
||||
*/
|
||||
public convertToString(x: any): string {
|
||||
if (x === undefined) return 'undefined';
|
||||
if (x === null) return 'null';
|
||||
if (x instanceof go.Point) return go.Point.stringify(x);
|
||||
if (x instanceof go.Size) return go.Size.stringify(x);
|
||||
if (x instanceof go.Rect) return go.Rect.stringify(x);
|
||||
if (x instanceof go.Spot) return go.Spot.stringify(x);
|
||||
if (x instanceof go.Margin) return go.Margin.stringify(x);
|
||||
if (x instanceof go.List) return this.convertToString(x.toArray());
|
||||
if (Array.isArray(x)) {
|
||||
let str = '';
|
||||
for (let i = 0; i < x.length; i++) {
|
||||
if (i > 0) str += ' ';
|
||||
const v = x[i];
|
||||
str += this.convertToString(v);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
return x.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden @ignore
|
||||
* Update all of the HTML in this Inspector.
|
||||
*/
|
||||
public updateAllHTML(): void {
|
||||
const inspectedProps = this.inspectedProperties;
|
||||
const isPart = this._inspectedObject instanceof go.Part;
|
||||
const data = isPart ? (this._inspectedObject as any).data : this._inspectedObject;
|
||||
if (!data) { // clear out all of the fields
|
||||
for (const name in inspectedProps) {
|
||||
const input = inspectedProps[name];
|
||||
if (input instanceof HTMLSelectElement) {
|
||||
input.innerHTML = '';
|
||||
} else if (input.type === 'color') {
|
||||
input.value = '#000000';
|
||||
} else if (input.type === 'checkbox') {
|
||||
input.checked = false;
|
||||
} else {
|
||||
input.value = '';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const name in inspectedProps) {
|
||||
const input = inspectedProps[name];
|
||||
const propertyValue = data[name];
|
||||
if (input instanceof HTMLSelectElement) {
|
||||
const decProp = this._properties[name];
|
||||
this.updateSelect(decProp, input, name, propertyValue);
|
||||
} else if (input.type === 'color') {
|
||||
input.value = this.convertToColor(propertyValue);
|
||||
} else if (input.type === 'checkbox') {
|
||||
input.checked = !!propertyValue;
|
||||
} else {
|
||||
input.value = this.convertToString(propertyValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden @ignore
|
||||
* Update an HTMLSelectElement with an appropriate list of choices, given the propertyName
|
||||
*/
|
||||
public updateSelect(decProp: any, select: HTMLSelectElement, propertyName: string, propertyValue: any): void {
|
||||
select.innerHTML = ''; // clear out anything that was there
|
||||
let choices = decProp.choices;
|
||||
if (typeof choices === 'function') choices = choices(this._inspectedObject, propertyName);
|
||||
if (!Array.isArray(choices)) choices = [];
|
||||
decProp.choicesArray = choices; // remember list of actual choice values (not strings)
|
||||
for (let i = 0; i < choices.length; i++) {
|
||||
const choice = choices[i];
|
||||
const opt = document.createElement('option');
|
||||
opt.text = this.convertToString(choice);
|
||||
select.add(opt);
|
||||
}
|
||||
select.value = this.convertToString(propertyValue);
|
||||
}
|
||||
|
||||
private parseValue(decProp: any, value: any, input: any, oldval: any) {
|
||||
// If it's a boolean, or if its previous value was boolean,
|
||||
// parse the value to be a boolean and then update the input.value to match
|
||||
let type = '';
|
||||
if (decProp !== undefined && decProp.type !== undefined) {
|
||||
type = decProp.type;
|
||||
}
|
||||
if (type === '') {
|
||||
if (typeof oldval === 'boolean') type = 'boolean'; // infer boolean
|
||||
else if (typeof oldval === 'number') type = 'number';
|
||||
else if (oldval instanceof go.Point) type = 'point';
|
||||
else if (oldval instanceof go.Size) type = 'size';
|
||||
else if (oldval instanceof go.Rect) type = 'rect';
|
||||
else if (oldval instanceof go.Spot) type = 'spot';
|
||||
else if (oldval instanceof go.Margin) type = 'margin';
|
||||
}
|
||||
|
||||
// convert to specific type, if needed
|
||||
switch (type) {
|
||||
case 'boolean': value = !(value === false || value === 'false' || value === '0'); break;
|
||||
case 'number': value = parseFloat(value); break;
|
||||
case 'arrayofnumber': value = this.convertToArrayOfNumber(value); break;
|
||||
case 'point': value = go.Point.parse(value); break;
|
||||
case 'size': value = go.Size.parse(value); break;
|
||||
case 'rect': value = go.Rect.parse(value); break;
|
||||
case 'spot': value = go.Spot.parse(value); break;
|
||||
case 'margin': value = go.Margin.parse(value); break;
|
||||
case 'checkbox': value = input.checked; break;
|
||||
case 'select': value = decProp.choicesArray[input.selectedIndex]; break;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden @ignore
|
||||
* Update all of the data properties of all the objects in {@link #inspectedObjects} according to the
|
||||
* current values held in the HTML input elements.
|
||||
*/
|
||||
private updateAllObjectsProperties() {
|
||||
const inspectedProps = this.inspectedProperties;
|
||||
const diagram = this._diagram;
|
||||
diagram.startTransaction('set all properties');
|
||||
for (const name in inspectedProps) {
|
||||
const input = inspectedProps[name];
|
||||
let value = input.value;
|
||||
const arr1: Array<string> = value.split('|');
|
||||
let arr2: Array<string> = [];
|
||||
if (this.multipleProperties[name]) {
|
||||
// don't split if it is union and its checkbox type
|
||||
if (this._properties[name] && this._properties[name].type === 'checkbox' && this._showUnionProperties) {
|
||||
arr2.push(this.multipleProperties[name]);
|
||||
} else if (this._properties[name]) {
|
||||
arr2 = this.multipleProperties[name].toString().split('|');
|
||||
}
|
||||
}
|
||||
const it = diagram.selection.iterator;
|
||||
let change = false;
|
||||
if (this._properties[name] && this._properties[name].type === 'checkbox') change = true; // always change checkbox
|
||||
if (arr1.length < arr2.length // i.e Alpha|Beta -> Alpha procs the change
|
||||
&& (!this._properties[name] // from and to links
|
||||
|| !(this._properties[name] // do not change color checkbox and choices due to them always having less
|
||||
&& (this._properties[name].type === 'color' || this._properties[name].type === 'checkbox' || this._properties[name].type === 'choices')))) {
|
||||
change = true;
|
||||
} else { // standard detection in change in properties
|
||||
for (let j = 0; j < arr1.length && j < arr2.length; j++) {
|
||||
if (!(arr1[j] === arr2[j])
|
||||
&& !(this._properties[name] && this._properties[name].type === 'color' && arr1[j].toLowerCase() === arr2[j].toLowerCase())) {
|
||||
change = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (change) { // only change properties it needs to change instead all of them
|
||||
for (let i = 0; i < diagram.selection.count; i++) {
|
||||
it.next();
|
||||
const isPart = it.value instanceof go.Part;
|
||||
const data = isPart ? it.value.data : it.value;
|
||||
|
||||
if (data) { // ignores the selected node if there is no data
|
||||
if (i < arr1.length) value = arr1[i];
|
||||
else value = arr1[0];
|
||||
|
||||
// don't update "readOnly" data properties
|
||||
const decProp = this._properties[name];
|
||||
if (!this.canEditProperty(name, decProp, it.value)) continue;
|
||||
|
||||
const oldval = data[name];
|
||||
value = this.parseValue(decProp, value, input, oldval);
|
||||
|
||||
// in case parsed to be different, such as in the case of boolean values,
|
||||
// the value shown should match the actual value
|
||||
input.value = value;
|
||||
|
||||
// modify the data object in an undo-able fashion
|
||||
diagram.model.setDataProperty(data, name, value);
|
||||
|
||||
// notify any listener
|
||||
if (this.propertyModified !== null) this.propertyModified(name, value, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
diagram.commitTransaction('set all properties');
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden @ignore
|
||||
* Update all of the data properties of {@link #inspectedObject} according to the
|
||||
* current values held in the HTML input elements.
|
||||
*/
|
||||
private updateAllProperties() {
|
||||
const inspectedProps = this.inspectedProperties;
|
||||
const diagram = this._diagram;
|
||||
const isPart = this.inspectedObject instanceof go.Part;
|
||||
const data = isPart ? (this.inspectedObject as any).data : this.inspectedObject;
|
||||
if (!data) return; // must not try to update data when there's no data!
|
||||
|
||||
diagram.startTransaction('set all properties');
|
||||
for (const name in inspectedProps) {
|
||||
const input = inspectedProps[name];
|
||||
let value = input.value;
|
||||
|
||||
// don't update "readOnly" data properties
|
||||
const decProp = this._properties[name];
|
||||
if (!this.canEditProperty(name, decProp, this.inspectedObject)) continue;
|
||||
|
||||
const oldval = data[name];
|
||||
value = this.parseValue(decProp, value, input, oldval);
|
||||
|
||||
// in case parsed to be different, such as in the case of boolean values,
|
||||
// the value shown should match the actual value
|
||||
input.value = value;
|
||||
|
||||
// modify the data object in an undo-able fashion
|
||||
diagram.model.setDataProperty(data, name, value);
|
||||
|
||||
// notify any listener
|
||||
if (this.propertyModified !== null) this.propertyModified(name, value, this);
|
||||
}
|
||||
diagram.commitTransaction('set all properties');
|
||||
}
|
||||
}
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js", "./DataInspector.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.init = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
var DataInspector_js_1 = require("./DataInspector.js");
|
||||
function init() {
|
||||
if (window.goSamples)
|
||||
window.goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make; // for conciseness in defining templates
|
||||
var myDiagram = $(go.Diagram, 'myDiagramDiv', // create a Diagram for the DIV HTML element
|
||||
{
|
||||
'animationManager.isEnabled': false,
|
||||
// allow double-click in background to create a new node
|
||||
'clickCreatingTool.archetypeNodeData': { text: 'Node', color: 'white' },
|
||||
// allow Ctrl-G to call groupSelection()
|
||||
'commandHandler.archetypeGroupData': { text: 'Group', isGroup: true, color: 'blue' },
|
||||
// enable undo & redo
|
||||
'undoManager.isEnabled': true,
|
||||
// automatically show the state of the diagram's model on the page
|
||||
'ModelChanged': function (e) {
|
||||
if (e.isTransactionFinished) {
|
||||
var elt = document.getElementById('savedModel');
|
||||
if (elt !== null)
|
||||
elt.textContent = myDiagram.model.toJson();
|
||||
}
|
||||
}
|
||||
});
|
||||
// These nodes have text surrounded by a rounded rectangle
|
||||
// whose fill color is bound to the node data.
|
||||
// The user can drag a node by dragging its TextBlock label.
|
||||
// Dragging from the Shape will start drawing a new link.
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, 'Auto', { locationSpot: go.Spot.Center }, new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify), $(go.Shape, 'Rectangle', {
|
||||
stroke: null, strokeWidth: 0,
|
||||
fill: 'white',
|
||||
portId: '', cursor: 'pointer',
|
||||
// allow all kinds of links from and to this port
|
||||
fromLinkable: true, fromLinkableSelfNode: true, fromLinkableDuplicates: true,
|
||||
toLinkable: true, toLinkableSelfNode: true, toLinkableDuplicates: true
|
||||
}, new go.Binding('fill', 'color')), $(go.TextBlock, {
|
||||
font: 'bold 18px sans-serif',
|
||||
stroke: '#111',
|
||||
margin: 8,
|
||||
isMultiline: false,
|
||||
editable: true // allow in-place editing by user
|
||||
}, new go.Binding('text', 'text').makeTwoWay()));
|
||||
// The link shape and arrowhead have their stroke brush data bound to the "color" property
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link, { toShortLength: 3, relinkableFrom: true, relinkableTo: true }, // allow the user to relink existing links
|
||||
$(go.Shape, { strokeWidth: 2 }, new go.Binding('stroke', 'color')), $(go.Shape, { toArrow: 'Standard', stroke: null }, new go.Binding('fill', 'color')));
|
||||
// Groups consist of a title in the color given by the group node data
|
||||
// above a translucent gray rectangle surrounding the member parts
|
||||
myDiagram.groupTemplate =
|
||||
$(go.Group, 'Vertical', {
|
||||
selectionObjectName: 'PANEL',
|
||||
ungroupable: true
|
||||
}, // enable Ctrl-Shift-G to ungroup a selected Group
|
||||
$(go.TextBlock, {
|
||||
font: 'bold 19px sans-serif',
|
||||
isMultiline: false,
|
||||
editable: true // allow in-place editing by user
|
||||
}, new go.Binding('text', 'text').makeTwoWay(), new go.Binding('stroke', 'color')), $(go.Panel, 'Auto', { name: 'PANEL' }, $(go.Shape, 'Rectangle', // the rectangular shape around the members
|
||||
{ fill: 'rgba(128,128,128,0.2)', stroke: 'gray', strokeWidth: 3 }), $(go.Placeholder, { padding: 10 }) // represents where the members are
|
||||
));
|
||||
// Create the Diagram's Model:
|
||||
var nodeDataArray = [
|
||||
{ key: 1, text: 'Alpha', color: '#B2DFDB', state: 'one' },
|
||||
{ key: 2, text: 'Beta', color: '#B2B2DB', state: 'two', password: '1234' },
|
||||
{ key: 3, text: 'Gamma', color: '#1DE9B6', state: 2, group: 5, flag: false, choices: [1, 2, 3, 4, 5] },
|
||||
{ key: 4, text: 'Delta', color: '#00BFA5', state: 'three', group: 5, flag: true },
|
||||
{ key: 5, text: 'Epsilon', color: '#00BFA5', isGroup: true }
|
||||
];
|
||||
var linkDataArray = [
|
||||
{ from: 1, to: 2, color: '#5E35B1' },
|
||||
{ from: 2, to: 2, color: '#5E35B1' },
|
||||
{ from: 3, to: 4, color: '#6200EA' },
|
||||
{ from: 3, to: 1, color: '#6200EA' }
|
||||
];
|
||||
myDiagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
|
||||
// myDiagram.model = go.Model.fromJson((document.getElementById('mySavedModel') as any).value);
|
||||
// some shared model data
|
||||
myDiagram.model.modelData = { test: true, hello: 'world', version: 42 };
|
||||
// select a Node, so that the first Inspector shows something
|
||||
myDiagram.select(myDiagram.nodes.first());
|
||||
// Declare which properties to show and how.
|
||||
// By default, all properties on the model data objects are shown unless the inspector option "includesOwnProperties" is set to false.
|
||||
// Show the primary selection's data, or blanks if no Part is selected:
|
||||
var inspector1 = new DataInspector_js_1.Inspector('myInspectorDiv1', myDiagram, {
|
||||
// allows for multiple nodes to be inspected at once
|
||||
multipleSelection: true,
|
||||
// max number of node properties will be shown when multiple selection is true
|
||||
showLimit: 4,
|
||||
// when multipleSelection is true, when showUnionProperties is true it takes the union of properties
|
||||
// otherwise it takes the intersection of properties
|
||||
showUnionProperties: true,
|
||||
// uncomment this line to only inspect the named properties below instead of all properties on each object:
|
||||
// includesOwnProperties: false,
|
||||
properties: {
|
||||
'text': {},
|
||||
// key would be automatically added for nodes, but we want to declare it read-only also:
|
||||
'key': { readOnly: true, show: DataInspector_js_1.Inspector.showIfPresent },
|
||||
// color would be automatically added for nodes, but we want to declare it a color also:
|
||||
'color': { show: DataInspector_js_1.Inspector.showIfPresent, type: 'color' },
|
||||
// Comments and LinkComments are not in any node or link data (yet), so we add them here:
|
||||
'Comments': { show: DataInspector_js_1.Inspector.showIfNode },
|
||||
'LinkComments': { show: DataInspector_js_1.Inspector.showIfLink },
|
||||
'isGroup': { readOnly: true, show: DataInspector_js_1.Inspector.showIfPresent },
|
||||
'flag': { show: DataInspector_js_1.Inspector.showIfNode, type: 'checkbox' },
|
||||
'state': {
|
||||
show: DataInspector_js_1.Inspector.showIfNode,
|
||||
type: 'select',
|
||||
choices: function (node, propName) {
|
||||
if (Array.isArray(node.data.choices))
|
||||
return node.data.choices;
|
||||
return ['one', 'two', 'three', 'four', 'five'];
|
||||
}
|
||||
},
|
||||
'choices': { show: false },
|
||||
// an example of specifying the <input> type
|
||||
'password': { show: DataInspector_js_1.Inspector.showIfPresent, type: 'password' }
|
||||
}
|
||||
});
|
||||
// Always show the first Node:
|
||||
var inspector2 = new DataInspector_js_1.Inspector('myInspectorDiv2', myDiagram, {
|
||||
// By default the inspector works on the Diagram selection.
|
||||
// This property lets us inspect a specific object by calling Inspector.inspectObject(object)
|
||||
inspectSelection: false,
|
||||
properties: {
|
||||
'text': {},
|
||||
// This property we want to declare as a color, to show a color-picker:
|
||||
'color': { type: 'color' },
|
||||
// key would be automatically added for node data, but we want to declare it read-only also:
|
||||
'key': { readOnly: true, show: DataInspector_js_1.Inspector.showIfPresent(myDiagram.selection.first(), 'key') }
|
||||
}
|
||||
});
|
||||
// If not inspecting a selection, you can programatically decide what to inspect (a Part, or a JavaScript object)
|
||||
// Here, we inspect the first node, if available
|
||||
var firstnode = myDiagram.nodes.first();
|
||||
if (firstnode !== null)
|
||||
inspector2.inspectObject(firstnode.data);
|
||||
// Always show the model.modelData:
|
||||
var inspector3 = new DataInspector_js_1.Inspector('myInspectorDiv3', myDiagram, {
|
||||
inspectSelection: false
|
||||
});
|
||||
inspector3.inspectObject(myDiagram.model.modelData);
|
||||
// Attach to the window for console manipulation
|
||||
window.myDiagram = myDiagram;
|
||||
window.inspector1 = inspector1;
|
||||
window.inspector2 = inspector2;
|
||||
window.inspector3 = inspector3;
|
||||
}
|
||||
exports.init = init;
|
||||
});
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
import { Inspector } from './DataInspector.js';
|
||||
|
||||
export function init() {
|
||||
if ((window as any).goSamples) (window as any).goSamples(); // init for these samples -- you don't need to call this
|
||||
|
||||
const $ = go.GraphObject.make; // for conciseness in defining templates
|
||||
|
||||
const myDiagram: go.Diagram =
|
||||
$(go.Diagram, 'myDiagramDiv', // create a Diagram for the DIV HTML element
|
||||
{
|
||||
'animationManager.isEnabled': false,
|
||||
// allow double-click in background to create a new node
|
||||
'clickCreatingTool.archetypeNodeData': { text: 'Node', color: 'white' },
|
||||
// allow Ctrl-G to call groupSelection()
|
||||
'commandHandler.archetypeGroupData': { text: 'Group', isGroup: true, color: 'blue' },
|
||||
// enable undo & redo
|
||||
'undoManager.isEnabled': true,
|
||||
// automatically show the state of the diagram's model on the page
|
||||
'ModelChanged': function (e: go.ChangedEvent) {
|
||||
if (e.isTransactionFinished) {
|
||||
const elt = document.getElementById('savedModel');
|
||||
if (elt !== null) elt.textContent = myDiagram.model.toJson();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// These nodes have text surrounded by a rounded rectangle
|
||||
// whose fill color is bound to the node data.
|
||||
// The user can drag a node by dragging its TextBlock label.
|
||||
// Dragging from the Shape will start drawing a new link.
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, 'Auto',
|
||||
{ locationSpot: go.Spot.Center },
|
||||
new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
$(go.Shape, 'Rectangle',
|
||||
{
|
||||
stroke: null, strokeWidth: 0,
|
||||
fill: 'white', // the default fill, if there is no data-binding
|
||||
portId: '', cursor: 'pointer', // the Shape is the port, not the whole Node
|
||||
// allow all kinds of links from and to this port
|
||||
fromLinkable: true, fromLinkableSelfNode: true, fromLinkableDuplicates: true,
|
||||
toLinkable: true, toLinkableSelfNode: true, toLinkableDuplicates: true
|
||||
},
|
||||
new go.Binding('fill', 'color')),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
font: 'bold 18px sans-serif',
|
||||
stroke: '#111',
|
||||
margin: 8, // make some extra space for the shape around the text
|
||||
isMultiline: false, // don't allow newlines in text
|
||||
editable: true // allow in-place editing by user
|
||||
},
|
||||
new go.Binding('text', 'text').makeTwoWay())
|
||||
);
|
||||
|
||||
// The link shape and arrowhead have their stroke brush data bound to the "color" property
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link,
|
||||
{ toShortLength: 3, relinkableFrom: true, relinkableTo: true }, // allow the user to relink existing links
|
||||
$(go.Shape,
|
||||
{ strokeWidth: 2 },
|
||||
new go.Binding('stroke', 'color')),
|
||||
$(go.Shape,
|
||||
{ toArrow: 'Standard', stroke: null },
|
||||
new go.Binding('fill', 'color'))
|
||||
);
|
||||
|
||||
// Groups consist of a title in the color given by the group node data
|
||||
// above a translucent gray rectangle surrounding the member parts
|
||||
myDiagram.groupTemplate =
|
||||
$(go.Group, 'Vertical',
|
||||
{
|
||||
selectionObjectName: 'PANEL', // selection handle goes around shape, not label
|
||||
ungroupable: true
|
||||
}, // enable Ctrl-Shift-G to ungroup a selected Group
|
||||
$(go.TextBlock,
|
||||
{
|
||||
font: 'bold 19px sans-serif',
|
||||
isMultiline: false, // don't allow newlines in text
|
||||
editable: true // allow in-place editing by user
|
||||
},
|
||||
new go.Binding('text', 'text').makeTwoWay(),
|
||||
new go.Binding('stroke', 'color')),
|
||||
$(go.Panel, 'Auto',
|
||||
{ name: 'PANEL' },
|
||||
$(go.Shape, 'Rectangle', // the rectangular shape around the members
|
||||
{ fill: 'rgba(128,128,128,0.2)', stroke: 'gray', strokeWidth: 3 }),
|
||||
$(go.Placeholder, { padding: 10 }) // represents where the members are
|
||||
)
|
||||
);
|
||||
|
||||
// Create the Diagram's Model:
|
||||
const nodeDataArray = [
|
||||
{ key: 1, text: 'Alpha', color: '#B2DFDB', state: 'one' },
|
||||
{ key: 2, text: 'Beta', color: '#B2B2DB', state: 'two', password: '1234' },
|
||||
{ key: 3, text: 'Gamma', color: '#1DE9B6', state: 2, group: 5, flag: false, choices: [1, 2, 3, 4, 5] },
|
||||
{ key: 4, text: 'Delta', color: '#00BFA5', state: 'three', group: 5, flag: true },
|
||||
{ key: 5, text: 'Epsilon', color: '#00BFA5', isGroup: true }
|
||||
];
|
||||
const linkDataArray = [
|
||||
{ from: 1, to: 2, color: '#5E35B1' },
|
||||
{ from: 2, to: 2, color: '#5E35B1' },
|
||||
{ from: 3, to: 4, color: '#6200EA' },
|
||||
{ from: 3, to: 1, color: '#6200EA' }
|
||||
];
|
||||
myDiagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
|
||||
// myDiagram.model = go.Model.fromJson((document.getElementById('mySavedModel') as any).value);
|
||||
|
||||
// some shared model data
|
||||
myDiagram.model.modelData = { test: true, hello: 'world', version: 42 };
|
||||
|
||||
// select a Node, so that the first Inspector shows something
|
||||
myDiagram.select(myDiagram.nodes.first());
|
||||
|
||||
|
||||
// Declare which properties to show and how.
|
||||
// By default, all properties on the model data objects are shown unless the inspector option "includesOwnProperties" is set to false.
|
||||
|
||||
// Show the primary selection's data, or blanks if no Part is selected:
|
||||
const inspector1 = new Inspector('myInspectorDiv1', myDiagram,
|
||||
{
|
||||
// allows for multiple nodes to be inspected at once
|
||||
multipleSelection: true,
|
||||
// max number of node properties will be shown when multiple selection is true
|
||||
showLimit: 4,
|
||||
// when multipleSelection is true, when showUnionProperties is true it takes the union of properties
|
||||
// otherwise it takes the intersection of properties
|
||||
showUnionProperties: true,
|
||||
|
||||
// uncomment this line to only inspect the named properties below instead of all properties on each object:
|
||||
// includesOwnProperties: false,
|
||||
properties: {
|
||||
'text': {},
|
||||
// key would be automatically added for nodes, but we want to declare it read-only also:
|
||||
'key': { readOnly: true, show: Inspector.showIfPresent },
|
||||
// color would be automatically added for nodes, but we want to declare it a color also:
|
||||
'color': { show: Inspector.showIfPresent, type: 'color' },
|
||||
// Comments and LinkComments are not in any node or link data (yet), so we add them here:
|
||||
'Comments': { show: Inspector.showIfNode },
|
||||
'LinkComments': { show: Inspector.showIfLink },
|
||||
'isGroup': { readOnly: true, show: Inspector.showIfPresent },
|
||||
'flag': { show: Inspector.showIfNode, type: 'checkbox' },
|
||||
'state': {
|
||||
show: Inspector.showIfNode,
|
||||
type: 'select',
|
||||
choices: function (node: go.Node, propName: string) {
|
||||
if (Array.isArray(node.data.choices)) return node.data.choices;
|
||||
return ['one', 'two', 'three', 'four', 'five'];
|
||||
}
|
||||
},
|
||||
'choices': { show: false }, // must not be shown at all
|
||||
// an example of specifying the <input> type
|
||||
'password': { show: Inspector.showIfPresent, type: 'password' }
|
||||
}
|
||||
});
|
||||
|
||||
// Always show the first Node:
|
||||
const inspector2 = new Inspector('myInspectorDiv2', myDiagram,
|
||||
{
|
||||
// By default the inspector works on the Diagram selection.
|
||||
// This property lets us inspect a specific object by calling Inspector.inspectObject(object)
|
||||
inspectSelection: false,
|
||||
properties: {
|
||||
'text': {},
|
||||
// This property we want to declare as a color, to show a color-picker:
|
||||
'color': { type: 'color' },
|
||||
// key would be automatically added for node data, but we want to declare it read-only also:
|
||||
'key': { readOnly: true, show: Inspector.showIfPresent(myDiagram.selection.first(), 'key') }
|
||||
}
|
||||
});
|
||||
// If not inspecting a selection, you can programatically decide what to inspect (a Part, or a JavaScript object)
|
||||
// Here, we inspect the first node, if available
|
||||
const firstnode = myDiagram.nodes.first();
|
||||
if (firstnode !== null) inspector2.inspectObject(firstnode.data);
|
||||
|
||||
// Always show the model.modelData:
|
||||
const inspector3 = new Inspector('myInspectorDiv3', myDiagram,
|
||||
{
|
||||
inspectSelection: false
|
||||
});
|
||||
inspector3.inspectObject(myDiagram.model.modelData);
|
||||
|
||||
// Attach to the window for console manipulation
|
||||
(window as any).myDiagram = myDiagram;
|
||||
(window as any).inspector1 = inspector1;
|
||||
(window as any).inspector2 = inspector2;
|
||||
(window as any).inspector3 = inspector3;
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Using Dimensioning Links</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="TypeScript: Dimensioning Links show the distance from a spot on a node to another spot on a node." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../samples/assets/require.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
function init() {
|
||||
require(["DimensioningScript"], function(app) {
|
||||
app.init();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:400px"></div>
|
||||
<p>
|
||||
This sample makes use of the DimensioningLink class, which inherits from the <a>Link</a> class. That class is defined
|
||||
at <a href="../extensions/DimensioningLink.ts">DimensioningLink.ts</a>.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
+243
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.DimensioningLink = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
/**
|
||||
* A custom routed {@link Link} for showing the distances between a point on one node and a point on another node.
|
||||
*
|
||||
* Note that because this is a Link, the points being measured must be on {@link Node}s, not simple {@link Part}s.
|
||||
* The exact point on each Node is determined by the {@link Link#fromSpot} and {@link Link#toSpot}.
|
||||
*
|
||||
* Several properties of the DimensioningLink customize the appearance of the dimensioning:
|
||||
* {@link #direction}, for orientation of the dimension line and which side it is on,
|
||||
* {@link #extension}, for how far the dimension line is from the measured points,
|
||||
* {@link #inset}, for leaving room for a text label, and
|
||||
* {@link #gap}, for distance that the extension line starts from the measured points.
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/Dimensioning.html">Dimensioning</a> sample.
|
||||
* @category Part Extension
|
||||
*/
|
||||
var DimensioningLink = /** @class */ (function (_super) {
|
||||
__extends(DimensioningLink, _super);
|
||||
/**
|
||||
* Constructs a DimensioningLink and sets the following properties:
|
||||
* - {@link #isLayoutPositioned} = false
|
||||
* - {@link #isTreeLink} = false
|
||||
* - {@link #routing} = {@link Link.Orthogonal}
|
||||
*/
|
||||
function DimensioningLink() {
|
||||
var _this = _super.call(this) || this;
|
||||
_this._direction = 0;
|
||||
_this._extension = 30;
|
||||
_this._inset = 10;
|
||||
_this._gap = 10;
|
||||
_this.isLayoutPositioned = false;
|
||||
_this.isTreeLink = false;
|
||||
_this.routing = go.Link.Orthogonal;
|
||||
return _this;
|
||||
}
|
||||
/**
|
||||
* Copies properties to a cloned DimensioningLink.
|
||||
*/
|
||||
DimensioningLink.prototype.cloneProtected = function (copy) {
|
||||
_super.prototype.cloneProtected.call(this, copy);
|
||||
copy._direction = this._direction;
|
||||
copy._extension = this._extension;
|
||||
copy._inset = this._inset;
|
||||
copy._gap = this._gap;
|
||||
};
|
||||
Object.defineProperty(DimensioningLink.prototype, "direction", {
|
||||
/**
|
||||
* The general angle at which the measurement should be made.
|
||||
*
|
||||
* The default value is 0, meaning to go measure only along the X axis,
|
||||
* with the dimension line and label above the two nodes (at lower Y coordinates).
|
||||
* New values must be one of: 0, 90, 180, 270, or NaN.
|
||||
* The value NaN indicates that the measurement is point-to-point and not orthogonal.
|
||||
*/
|
||||
get: function () { return this._direction; },
|
||||
set: function (val) {
|
||||
if (isNaN(val) || val === 0 || val === 90 || val === 180 || val === 270) {
|
||||
this._direction = val;
|
||||
}
|
||||
else {
|
||||
throw new Error('DimensioningLink: invalid new direction: ' + val);
|
||||
}
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(DimensioningLink.prototype, "extension", {
|
||||
/**
|
||||
* The distance at which the dimension line should be from the points being measured.
|
||||
*
|
||||
* The default value is 30.
|
||||
* Larger values mean further away from the nodes.
|
||||
* The new value must be greater than or equal to zero.
|
||||
*/
|
||||
get: function () { return this._extension; },
|
||||
set: function (val) { this._extension = val; },
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(DimensioningLink.prototype, "inset", {
|
||||
/**
|
||||
* The distance that the dimension line should be "indented" from the ends of the
|
||||
* extension lines that are orthogonal to the dimension line.
|
||||
*
|
||||
* The default value is 10.
|
||||
*/
|
||||
get: function () { return this._inset; },
|
||||
set: function (val) {
|
||||
if (val >= 0) {
|
||||
this._inset = val;
|
||||
}
|
||||
else {
|
||||
throw new Error('DimensionLink: invalid new inset: ' + val);
|
||||
}
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(DimensioningLink.prototype, "gap", {
|
||||
/**
|
||||
* The distance that the extension lines should come short of the measured points.
|
||||
*
|
||||
* The default value is 10.
|
||||
*/
|
||||
get: function () { return this._gap; },
|
||||
set: function (val) {
|
||||
if (val >= 0) {
|
||||
this._gap = val;
|
||||
}
|
||||
else {
|
||||
throw new Error('DimensionLink: invalid new gap: ' + val);
|
||||
}
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
/**
|
||||
* Constructs the link's route by modifying {@link #points}.
|
||||
* @return {boolean} true if it computed a route of points
|
||||
*/
|
||||
DimensioningLink.prototype.computePoints = function () {
|
||||
var fromnode = this.fromNode;
|
||||
if (!fromnode)
|
||||
return false;
|
||||
var fromport = this.fromPort;
|
||||
var fromspot = this.computeSpot(true);
|
||||
var tonode = this.toNode;
|
||||
if (!tonode)
|
||||
return false;
|
||||
var toport = this.toPort;
|
||||
var tospot = this.computeSpot(false);
|
||||
var frompoint = this.getLinkPoint(fromnode, fromport, fromspot, true, true, tonode, toport);
|
||||
if (!frompoint.isReal())
|
||||
return false;
|
||||
var topoint = this.getLinkPoint(tonode, toport, tospot, false, true, fromnode, fromport);
|
||||
if (!topoint.isReal())
|
||||
return false;
|
||||
this.clearPoints();
|
||||
var ang = this.direction;
|
||||
if (isNaN(ang)) {
|
||||
ang = frompoint.directionPoint(topoint);
|
||||
var p = new go.Point(this.extension, 0);
|
||||
p.rotate(ang + 90);
|
||||
var q = new go.Point(this.extension - this.inset, 0);
|
||||
q.rotate(ang + 90);
|
||||
var g = new go.Point(this.gap, 0);
|
||||
g.rotate(ang + 90);
|
||||
this.addPointAt(frompoint.x + g.x, frompoint.y + g.y);
|
||||
this.addPointAt(frompoint.x + p.x, frompoint.y + p.y);
|
||||
this.addPointAt(frompoint.x + q.x, frompoint.y + q.y);
|
||||
this.addPointAt(topoint.x + q.x, topoint.y + q.y);
|
||||
this.addPointAt(topoint.x + p.x, topoint.y + p.y);
|
||||
this.addPointAt(topoint.x + g.x, topoint.y + g.y);
|
||||
}
|
||||
else {
|
||||
var dist = this.extension;
|
||||
var r = 0.0;
|
||||
var s = 0.0;
|
||||
var t0 = 0.0;
|
||||
var t1 = 0.0;
|
||||
if (ang === 0 || ang === 180) {
|
||||
if (ang === 0) {
|
||||
r = Math.min(frompoint.y, topoint.y) - this.extension;
|
||||
s = r + this.inset;
|
||||
t0 = frompoint.y - this.gap;
|
||||
t1 = topoint.y - this.gap;
|
||||
}
|
||||
else {
|
||||
r = Math.max(frompoint.y, topoint.y) + this.extension;
|
||||
s = r - this.inset;
|
||||
t0 = frompoint.y + this.gap;
|
||||
t1 = topoint.y + this.gap;
|
||||
}
|
||||
this.addPointAt(frompoint.x, t0);
|
||||
this.addPointAt(frompoint.x + 0.01, r);
|
||||
this.addPointAt(frompoint.x, s);
|
||||
this.addPointAt(topoint.x, s);
|
||||
this.addPointAt(topoint.x - 0.01, r);
|
||||
this.addPointAt(topoint.x, t1);
|
||||
}
|
||||
else if (ang === 90 || ang === 270) {
|
||||
if (ang === 90) {
|
||||
r = Math.max(frompoint.x, topoint.x) + this.extension;
|
||||
s = r - this.inset;
|
||||
t0 = frompoint.x + this.gap;
|
||||
t1 = topoint.x + this.gap;
|
||||
}
|
||||
else {
|
||||
r = Math.min(frompoint.x, topoint.x) - this.extension;
|
||||
s = r + this.inset;
|
||||
t0 = frompoint.x - this.gap;
|
||||
t1 = topoint.x - this.gap;
|
||||
}
|
||||
this.addPointAt(t0, frompoint.y);
|
||||
this.addPointAt(r, frompoint.y + 0.01);
|
||||
this.addPointAt(s, frompoint.y);
|
||||
this.addPointAt(s, topoint.y);
|
||||
this.addPointAt(r, topoint.y - 0.01);
|
||||
this.addPointAt(t1, topoint.y);
|
||||
}
|
||||
}
|
||||
this.updateTargetBindings();
|
||||
return true;
|
||||
};
|
||||
return DimensioningLink;
|
||||
}(go.Link));
|
||||
exports.DimensioningLink = DimensioningLink;
|
||||
});
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
|
||||
/**
|
||||
* A custom routed {@link Link} for showing the distances between a point on one node and a point on another node.
|
||||
*
|
||||
* Note that because this is a Link, the points being measured must be on {@link Node}s, not simple {@link Part}s.
|
||||
* The exact point on each Node is determined by the {@link Link#fromSpot} and {@link Link#toSpot}.
|
||||
*
|
||||
* Several properties of the DimensioningLink customize the appearance of the dimensioning:
|
||||
* {@link #direction}, for orientation of the dimension line and which side it is on,
|
||||
* {@link #extension}, for how far the dimension line is from the measured points,
|
||||
* {@link #inset}, for leaving room for a text label, and
|
||||
* {@link #gap}, for distance that the extension line starts from the measured points.
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/Dimensioning.html">Dimensioning</a> sample.
|
||||
* @category Part Extension
|
||||
*/
|
||||
export class DimensioningLink extends go.Link {
|
||||
private _direction: number = 0;
|
||||
private _extension: number = 30;
|
||||
private _inset: number = 10;
|
||||
private _gap: number = 10;
|
||||
|
||||
/**
|
||||
* Constructs a DimensioningLink and sets the following properties:
|
||||
* - {@link #isLayoutPositioned} = false
|
||||
* - {@link #isTreeLink} = false
|
||||
* - {@link #routing} = {@link Link.Orthogonal}
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
this.isLayoutPositioned = false;
|
||||
this.isTreeLink = false;
|
||||
this.routing = go.Link.Orthogonal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies properties to a cloned DimensioningLink.
|
||||
*/
|
||||
public cloneProtected(copy: this): void {
|
||||
super.cloneProtected(copy);
|
||||
copy._direction = this._direction;
|
||||
copy._extension = this._extension;
|
||||
copy._inset = this._inset;
|
||||
copy._gap = this._gap;
|
||||
}
|
||||
|
||||
/**
|
||||
* The general angle at which the measurement should be made.
|
||||
*
|
||||
* The default value is 0, meaning to go measure only along the X axis,
|
||||
* with the dimension line and label above the two nodes (at lower Y coordinates).
|
||||
* New values must be one of: 0, 90, 180, 270, or NaN.
|
||||
* The value NaN indicates that the measurement is point-to-point and not orthogonal.
|
||||
*/
|
||||
get direction(): number { return this._direction; }
|
||||
set direction(val: number) {
|
||||
if (isNaN(val) || val === 0 || val === 90 || val === 180 || val === 270) {
|
||||
this._direction = val;
|
||||
} else {
|
||||
throw new Error('DimensioningLink: invalid new direction: ' + val);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The distance at which the dimension line should be from the points being measured.
|
||||
*
|
||||
* The default value is 30.
|
||||
* Larger values mean further away from the nodes.
|
||||
* The new value must be greater than or equal to zero.
|
||||
*/
|
||||
get extension(): number { return this._extension; }
|
||||
set extension(val: number) { this._extension = val; }
|
||||
|
||||
/**
|
||||
* The distance that the dimension line should be "indented" from the ends of the
|
||||
* extension lines that are orthogonal to the dimension line.
|
||||
*
|
||||
* The default value is 10.
|
||||
*/
|
||||
get inset(): number { return this._inset; }
|
||||
set inset(val: number) {
|
||||
if (val >= 0) {
|
||||
this._inset = val;
|
||||
} else {
|
||||
throw new Error('DimensionLink: invalid new inset: ' + val);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The distance that the extension lines should come short of the measured points.
|
||||
*
|
||||
* The default value is 10.
|
||||
*/
|
||||
get gap(): number { return this._gap; }
|
||||
set gap(val: number) {
|
||||
if (val >= 0) {
|
||||
this._gap = val;
|
||||
} else {
|
||||
throw new Error('DimensionLink: invalid new gap: ' + val);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the link's route by modifying {@link #points}.
|
||||
* @return {boolean} true if it computed a route of points
|
||||
*/
|
||||
public computePoints(): boolean {
|
||||
const fromnode = this.fromNode;
|
||||
if (!fromnode) return false;
|
||||
const fromport = this.fromPort;
|
||||
const fromspot = this.computeSpot(true);
|
||||
const tonode = this.toNode;
|
||||
if (!tonode) return false;
|
||||
const toport = this.toPort;
|
||||
const tospot = this.computeSpot(false);
|
||||
const frompoint = this.getLinkPoint(fromnode, fromport, fromspot, true, true, tonode, toport);
|
||||
if (!frompoint.isReal()) return false;
|
||||
const topoint = this.getLinkPoint(tonode, toport, tospot, false, true, fromnode, fromport);
|
||||
if (!topoint.isReal()) return false;
|
||||
|
||||
this.clearPoints();
|
||||
|
||||
let ang = this.direction;
|
||||
if (isNaN(ang)) {
|
||||
ang = frompoint.directionPoint(topoint);
|
||||
const p = new go.Point(this.extension, 0);
|
||||
p.rotate(ang + 90);
|
||||
const q = new go.Point(this.extension - this.inset, 0);
|
||||
q.rotate(ang + 90);
|
||||
const g = new go.Point(this.gap, 0);
|
||||
g.rotate(ang + 90);
|
||||
this.addPointAt(frompoint.x + g.x, frompoint.y + g.y);
|
||||
this.addPointAt(frompoint.x + p.x, frompoint.y + p.y);
|
||||
this.addPointAt(frompoint.x + q.x, frompoint.y + q.y);
|
||||
this.addPointAt(topoint.x + q.x, topoint.y + q.y);
|
||||
this.addPointAt(topoint.x + p.x, topoint.y + p.y);
|
||||
this.addPointAt(topoint.x + g.x, topoint.y + g.y);
|
||||
} else {
|
||||
const dist = this.extension;
|
||||
let r = 0.0;
|
||||
let s = 0.0;
|
||||
let t0 = 0.0;
|
||||
let t1 = 0.0;
|
||||
if (ang === 0 || ang === 180) {
|
||||
if (ang === 0) {
|
||||
r = Math.min(frompoint.y, topoint.y) - this.extension;
|
||||
s = r + this.inset;
|
||||
t0 = frompoint.y - this.gap;
|
||||
t1 = topoint.y - this.gap;
|
||||
} else {
|
||||
r = Math.max(frompoint.y, topoint.y) + this.extension;
|
||||
s = r - this.inset;
|
||||
t0 = frompoint.y + this.gap;
|
||||
t1 = topoint.y + this.gap;
|
||||
}
|
||||
this.addPointAt(frompoint.x, t0);
|
||||
this.addPointAt(frompoint.x + 0.01, r);
|
||||
this.addPointAt(frompoint.x, s);
|
||||
this.addPointAt(topoint.x, s);
|
||||
this.addPointAt(topoint.x - 0.01, r);
|
||||
this.addPointAt(topoint.x, t1);
|
||||
} else if (ang === 90 || ang === 270) {
|
||||
if (ang === 90) {
|
||||
r = Math.max(frompoint.x, topoint.x) + this.extension;
|
||||
s = r - this.inset;
|
||||
t0 = frompoint.x + this.gap;
|
||||
t1 = topoint.x + this.gap;
|
||||
} else {
|
||||
r = Math.min(frompoint.x, topoint.x) - this.extension;
|
||||
s = r + this.inset;
|
||||
t0 = frompoint.x - this.gap;
|
||||
t1 = topoint.x - this.gap;
|
||||
}
|
||||
this.addPointAt(t0, frompoint.y);
|
||||
this.addPointAt(r, frompoint.y + 0.01);
|
||||
this.addPointAt(s, frompoint.y);
|
||||
this.addPointAt(s, topoint.y);
|
||||
this.addPointAt(r, topoint.y - 0.01);
|
||||
this.addPointAt(t1, topoint.y);
|
||||
}
|
||||
}
|
||||
|
||||
this.updateTargetBindings();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js", "./DimensioningLink.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.init = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
var DimensioningLink_js_1 = require("./DimensioningLink.js");
|
||||
function init() {
|
||||
if (window.goSamples)
|
||||
window.goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make;
|
||||
var myDiagram = $(go.Diagram, 'myDiagramDiv', {
|
||||
'undoManager.isEnabled': true
|
||||
});
|
||||
// A simple resizable node
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, 'Auto', { locationSpot: go.Spot.Center }, new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify), { resizable: true }, $(go.Shape, { strokeWidth: 0, fill: 'lightgray' }, new go.Binding('fill', 'color')), $(go.TextBlock, { margin: 10 }, new go.Binding('text', 'key')));
|
||||
// A generalized example template using a DimensioningLink.
|
||||
// Most usage might not have so many Bindings.
|
||||
myDiagram.linkTemplateMap.add('Dimensioning', $(DimensioningLink_js_1.DimensioningLink, new go.Binding('fromSpot', 'fromSpot', go.Spot.parse), new go.Binding('toSpot', 'toSpot', go.Spot.parse), new go.Binding('direction'), new go.Binding('extension'), new go.Binding('inset'), $(go.Shape, { stroke: 'gray' }, new go.Binding('stroke', 'color')), $(go.Shape, { fromArrow: 'BackwardOpenTriangle', segmentIndex: 2, stroke: 'gray' }, new go.Binding('stroke', 'color')), $(go.Shape, { toArrow: 'OpenTriangle', segmentIndex: -3, stroke: 'gray' }, new go.Binding('stroke', 'color')), $(go.TextBlock, {
|
||||
segmentIndex: 2,
|
||||
segmentFraction: 0.5,
|
||||
segmentOrientation: go.Link.OrientUpright,
|
||||
alignmentFocus: go.Spot.Bottom,
|
||||
stroke: 'gray',
|
||||
font: '8pt sans-serif'
|
||||
}, new go.Binding('text', '', showDistance).ofObject(), new go.Binding('stroke', 'color'))));
|
||||
// Return a string representing the distance between the two points.
|
||||
// This is the cartesian distance if this.direction is NaN;
|
||||
// otherwise it is the orthogonal distance along that axis.
|
||||
function showDistance(link) {
|
||||
var numpts = link.pointsCount;
|
||||
if (numpts < 2)
|
||||
return '';
|
||||
var p0 = link.getPoint(0);
|
||||
var pn = link.getPoint(numpts - 1);
|
||||
var ang = link.direction;
|
||||
if (isNaN(ang))
|
||||
return Math.floor(Math.sqrt(p0.distanceSquaredPoint(pn))) + '';
|
||||
var rad = ang * Math.PI / 180;
|
||||
return Math.floor(Math.abs(Math.cos(rad) * (p0.x - pn.x)) +
|
||||
Math.abs(Math.sin(rad) * (p0.y - pn.y))) + '';
|
||||
}
|
||||
myDiagram.model = new go.GraphLinksModel([
|
||||
{ key: 'Alpha', loc: '0 50' },
|
||||
{ key: 'Beta', loc: '150 0' },
|
||||
{ key: 'Gamma', loc: '100 150' }
|
||||
], [
|
||||
{
|
||||
from: 'Alpha', to: 'Beta', category: 'Dimensioning',
|
||||
fromSpot: 'TopRight', toSpot: 'TopLeft'
|
||||
},
|
||||
{
|
||||
from: 'Alpha', to: 'Beta', category: 'Dimensioning',
|
||||
fromSpot: 'TopLeft', toSpot: 'TopRight', extension: 50, color: 'blue'
|
||||
},
|
||||
{
|
||||
from: 'Alpha', to: 'Beta', category: 'Dimensioning',
|
||||
fromSpot: 'TopLeft', toSpot: 'TopLeft', direction: 270, color: 'green'
|
||||
},
|
||||
{
|
||||
from: 'Alpha', to: 'Beta', category: 'Dimensioning',
|
||||
fromSpot: 'BottomRight', toSpot: 'BottomRight', direction: 90, color: 'purple'
|
||||
},
|
||||
{
|
||||
from: 'Alpha', to: 'Beta', category: 'Dimensioning',
|
||||
fromSpot: 'Center', toSpot: 'Center', extension: 50, direction: NaN, color: 'red'
|
||||
},
|
||||
{
|
||||
from: 'Gamma', to: 'Gamma', category: 'Dimensioning',
|
||||
fromSpot: 'TopLeft', toSpot: 'TopRight', direction: 0
|
||||
},
|
||||
{
|
||||
from: 'Gamma', to: 'Gamma', category: 'Dimensioning',
|
||||
fromSpot: 'TopRight', toSpot: 'BottomRight', direction: 90
|
||||
},
|
||||
{
|
||||
from: 'Gamma', to: 'Gamma', category: 'Dimensioning',
|
||||
fromSpot: 'BottomRight', toSpot: 'BottomLeft', direction: 180
|
||||
},
|
||||
{
|
||||
from: 'Gamma', to: 'Gamma', category: 'Dimensioning',
|
||||
fromSpot: 'BottomLeft', toSpot: 'TopLeft', direction: 270
|
||||
}
|
||||
]);
|
||||
// Attach to the window for console manipulation
|
||||
window.myDiagram = myDiagram;
|
||||
}
|
||||
exports.init = init;
|
||||
});
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
import { DimensioningLink } from './DimensioningLink.js';
|
||||
|
||||
export function init() {
|
||||
if ((window as any).goSamples) (window as any).goSamples(); // init for these samples -- you don't need to call this
|
||||
|
||||
const $ = go.GraphObject.make;
|
||||
|
||||
const myDiagram =
|
||||
$(go.Diagram, 'myDiagramDiv',
|
||||
{
|
||||
'undoManager.isEnabled': true
|
||||
});
|
||||
|
||||
// A simple resizable node
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, 'Auto',
|
||||
{ locationSpot: go.Spot.Center },
|
||||
new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
{ resizable: true },
|
||||
$(go.Shape, { strokeWidth: 0, fill: 'lightgray' },
|
||||
new go.Binding('fill', 'color')),
|
||||
$(go.TextBlock, { margin: 10 },
|
||||
new go.Binding('text', 'key'))
|
||||
);
|
||||
|
||||
// A generalized example template using a DimensioningLink.
|
||||
// Most usage might not have so many Bindings.
|
||||
myDiagram.linkTemplateMap.add('Dimensioning',
|
||||
$(DimensioningLink,
|
||||
new go.Binding('fromSpot', 'fromSpot', go.Spot.parse),
|
||||
new go.Binding('toSpot', 'toSpot', go.Spot.parse),
|
||||
new go.Binding('direction'),
|
||||
new go.Binding('extension'),
|
||||
new go.Binding('inset'),
|
||||
$(go.Shape, { stroke: 'gray' },
|
||||
new go.Binding('stroke', 'color')),
|
||||
$(go.Shape, { fromArrow: 'BackwardOpenTriangle', segmentIndex: 2, stroke: 'gray' },
|
||||
new go.Binding('stroke', 'color')),
|
||||
$(go.Shape, { toArrow: 'OpenTriangle', segmentIndex: -3, stroke: 'gray' },
|
||||
new go.Binding('stroke', 'color')),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
segmentIndex: 2,
|
||||
segmentFraction: 0.5,
|
||||
segmentOrientation: go.Link.OrientUpright,
|
||||
alignmentFocus: go.Spot.Bottom,
|
||||
stroke: 'gray',
|
||||
font: '8pt sans-serif'
|
||||
},
|
||||
new go.Binding('text', '', showDistance).ofObject(),
|
||||
new go.Binding('stroke', 'color'))
|
||||
));
|
||||
|
||||
// Return a string representing the distance between the two points.
|
||||
// This is the cartesian distance if this.direction is NaN;
|
||||
// otherwise it is the orthogonal distance along that axis.
|
||||
function showDistance(link: go.Link) {
|
||||
const numpts = link.pointsCount;
|
||||
if (numpts < 2) return '';
|
||||
const p0 = link.getPoint(0);
|
||||
const pn = link.getPoint(numpts - 1);
|
||||
const ang = (link as any).direction;
|
||||
if (isNaN(ang)) return Math.floor(Math.sqrt(p0.distanceSquaredPoint(pn))) + '';
|
||||
const rad = ang * Math.PI / 180;
|
||||
return Math.floor(Math.abs(Math.cos(rad) * (p0.x - pn.x)) +
|
||||
Math.abs(Math.sin(rad) * (p0.y - pn.y))) + '';
|
||||
}
|
||||
|
||||
myDiagram.model = new go.GraphLinksModel([
|
||||
{ key: 'Alpha', loc: '0 50' },
|
||||
{ key: 'Beta', loc: '150 0' },
|
||||
{ key: 'Gamma', loc: '100 150' }
|
||||
], [
|
||||
{
|
||||
from: 'Alpha', to: 'Beta', category: 'Dimensioning',
|
||||
fromSpot: 'TopRight', toSpot: 'TopLeft'
|
||||
},
|
||||
{
|
||||
from: 'Alpha', to: 'Beta', category: 'Dimensioning',
|
||||
fromSpot: 'TopLeft', toSpot: 'TopRight', extension: 50, color: 'blue'
|
||||
},
|
||||
{
|
||||
from: 'Alpha', to: 'Beta', category: 'Dimensioning',
|
||||
fromSpot: 'TopLeft', toSpot: 'TopLeft', direction: 270, color: 'green'
|
||||
},
|
||||
{
|
||||
from: 'Alpha', to: 'Beta', category: 'Dimensioning',
|
||||
fromSpot: 'BottomRight', toSpot: 'BottomRight', direction: 90, color: 'purple'
|
||||
},
|
||||
{
|
||||
from: 'Alpha', to: 'Beta', category: 'Dimensioning',
|
||||
fromSpot: 'Center', toSpot: 'Center', extension: 50, direction: NaN, color: 'red'
|
||||
},
|
||||
|
||||
{
|
||||
from: 'Gamma', to: 'Gamma', category: 'Dimensioning',
|
||||
fromSpot: 'TopLeft', toSpot: 'TopRight', direction: 0
|
||||
},
|
||||
{
|
||||
from: 'Gamma', to: 'Gamma', category: 'Dimensioning',
|
||||
fromSpot: 'TopRight', toSpot: 'BottomRight', direction: 90
|
||||
},
|
||||
{
|
||||
from: 'Gamma', to: 'Gamma', category: 'Dimensioning',
|
||||
fromSpot: 'BottomRight', toSpot: 'BottomLeft', direction: 180
|
||||
},
|
||||
{
|
||||
from: 'Gamma', to: 'Gamma', category: 'Dimensioning',
|
||||
fromSpot: 'BottomLeft', toSpot: 'TopLeft', direction: 270
|
||||
}
|
||||
]);
|
||||
|
||||
// Attach to the window for console manipulation
|
||||
(window as any).myDiagram = myDiagram;
|
||||
}
|
||||
+272
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.DoubleTreeLayout = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
/**
|
||||
* Perform two TreeLayouts, one going rightwards and one going leftwards.
|
||||
* The choice of direction is determined by the mandatory predicate {@link #directionFunction},
|
||||
* which is called on each child Node of the root Node.
|
||||
*
|
||||
* You can also set {@link #vertical} to true if you want the DoubleTreeLayout to
|
||||
* perform TreeLayouts both downwards and upwards.
|
||||
*
|
||||
* Normally there should be a single root node. Hoewver if there are multiple root nodes
|
||||
* found in the nodes and links that this layout is responsible for, this will pretend that
|
||||
* there is a real root node and make all of the apparent root nodes children of that pretend root.
|
||||
*
|
||||
* If there is no root node, all nodes are involved in cycles, so the first given node is chosen.
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../samples/doubleTree.html">Double Tree</a> sample.
|
||||
* @category Layout Extension
|
||||
*/
|
||||
var DoubleTreeLayout = /** @class */ (function (_super) {
|
||||
__extends(DoubleTreeLayout, _super);
|
||||
function DoubleTreeLayout() {
|
||||
var _this = _super !== null && _super.apply(this, arguments) || this;
|
||||
_this._vertical = false;
|
||||
_this._directionFunction = function (node) { return true; };
|
||||
_this._bottomRightOptions = null;
|
||||
_this._topLeftOptions = null;
|
||||
return _this;
|
||||
}
|
||||
Object.defineProperty(DoubleTreeLayout.prototype, "vertical", {
|
||||
/**
|
||||
* When false, the layout should grow towards the left and towards the right;
|
||||
* when true, the layout show grow upwards and downwards.
|
||||
* The default value is false.
|
||||
*/
|
||||
get: function () { return this._vertical; },
|
||||
set: function (value) {
|
||||
if (typeof value !== "boolean")
|
||||
throw new Error("new value for DoubleTreeLayout.vertical must be a boolean value.");
|
||||
if (this._vertical !== value) {
|
||||
this._vertical = value;
|
||||
this.invalidateLayout();
|
||||
}
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(DoubleTreeLayout.prototype, "directionFunction", {
|
||||
/**
|
||||
* This function is called on each child node of the root node
|
||||
* in order to determine whether the subtree starting from that child node
|
||||
* will grow towards larger coordinates or towards smaller ones.
|
||||
* The value must be a function and must not be null.
|
||||
* It must return true if {@link #isPositiveDirection} should return true; otherwise it should return false.
|
||||
*/
|
||||
get: function () { return this._directionFunction; },
|
||||
set: function (value) {
|
||||
if (typeof value !== "function") {
|
||||
throw new Error("new value for DoubleTreeLayout.directionFunction must be a function taking a node data object and returning a boolean.");
|
||||
}
|
||||
if (this._directionFunction !== value) {
|
||||
this._directionFunction = value;
|
||||
this.invalidateLayout();
|
||||
}
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(DoubleTreeLayout.prototype, "bottomRightOptions", {
|
||||
/**
|
||||
* Gets or sets the options to be applied to a {@link TreeLayout}.
|
||||
* By default this is null -- no properties are set on the TreeLayout
|
||||
* other than the {@link TreeLayout#angle}, depending on {@link #vertical} and
|
||||
* the result of calling {@link #directionFunction}.
|
||||
*/
|
||||
get: function () { return this._bottomRightOptions; },
|
||||
set: function (value) {
|
||||
if (this._bottomRightOptions !== value) {
|
||||
this._bottomRightOptions = value;
|
||||
this.invalidateLayout();
|
||||
}
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(DoubleTreeLayout.prototype, "topLeftOptions", {
|
||||
/**
|
||||
* Gets or sets the options to be applied to a {@link TreeLayout}.
|
||||
* By default this is null -- no properties are set on the TreeLayout
|
||||
* other than the {@link TreeLayout#angle}, depending on {@link #vertical} and
|
||||
* the result of calling {@link #directionFunction}.
|
||||
*/
|
||||
get: function () { return this._topLeftOptions; },
|
||||
set: function (value) {
|
||||
if (this._topLeftOptions !== value) {
|
||||
this._topLeftOptions = value;
|
||||
this.invalidateLayout();
|
||||
}
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
/**
|
||||
* @ignore
|
||||
* Copies properties to a cloned Layout.
|
||||
*/
|
||||
DoubleTreeLayout.prototype.cloneProtected = function (copy) {
|
||||
_super.prototype.cloneProtected.call(this, copy);
|
||||
copy._vertical = this._vertical;
|
||||
copy._directionFunction = this._directionFunction;
|
||||
copy._bottomRightOptions = this._bottomRightOptions;
|
||||
copy._topLeftOptions = this._topLeftOptions;
|
||||
};
|
||||
/**
|
||||
* Perform two {@link TreeLayout}s by splitting the collection of Parts
|
||||
* into two separate subsets but sharing only a single root Node.
|
||||
* @param coll
|
||||
*/
|
||||
DoubleTreeLayout.prototype.doLayout = function (coll) {
|
||||
var coll2 = this.collectParts(coll);
|
||||
if (coll2.count === 0)
|
||||
return;
|
||||
var diagram = this.diagram;
|
||||
if (diagram !== null)
|
||||
diagram.startTransaction("Double Tree Layout");
|
||||
// split the nodes and links into two Sets, depending on direction
|
||||
var leftParts = new go.Set();
|
||||
var rightParts = new go.Set();
|
||||
this.separatePartsForLayout(coll2, leftParts, rightParts);
|
||||
// but the ROOT node will be in both collections
|
||||
// create and perform two TreeLayouts, one in each direction,
|
||||
// without moving the ROOT node, on the different subsets of nodes and links
|
||||
var layout1 = this.createTreeLayout(false);
|
||||
layout1.angle = this.vertical ? 270 : 180;
|
||||
layout1.arrangement = go.TreeLayout.ArrangementFixedRoots;
|
||||
var layout2 = this.createTreeLayout(true);
|
||||
layout2.angle = this.vertical ? 90 : 0;
|
||||
layout2.arrangement = go.TreeLayout.ArrangementFixedRoots;
|
||||
layout1.doLayout(leftParts);
|
||||
layout2.doLayout(rightParts);
|
||||
if (diagram !== null)
|
||||
diagram.commitTransaction("Double Tree Layout");
|
||||
};
|
||||
/**
|
||||
* This just returns an instance of {@link TreeLayout}.
|
||||
* The caller will set the {@link TreeLayout#angle}.
|
||||
* @param {boolean} positive true for growth downward or rightward
|
||||
* @return {TreeLayout}
|
||||
*/
|
||||
DoubleTreeLayout.prototype.createTreeLayout = function (positive) {
|
||||
var lay = new go.TreeLayout();
|
||||
var opts = this.topLeftOptions;
|
||||
if (positive)
|
||||
opts = this.bottomRightOptions;
|
||||
if (opts)
|
||||
for (var p in opts) {
|
||||
lay[p] = opts[p];
|
||||
}
|
||||
return lay;
|
||||
};
|
||||
/**
|
||||
* This is called by {@link #doLayout} to split the collection of Nodes and Links into two Sets,
|
||||
* one for the subtrees growing towards the left or upwards, and one for the subtrees
|
||||
* growing towards the right or downwards.
|
||||
*/
|
||||
DoubleTreeLayout.prototype.separatePartsForLayout = function (coll, leftParts, rightParts) {
|
||||
var root = null; // the one root
|
||||
var roots = new go.Set(); // in case there are multiple roots
|
||||
coll.each(function (node) {
|
||||
if (node instanceof go.Node && node.findTreeParentNode() === null)
|
||||
roots.add(node);
|
||||
});
|
||||
if (roots.count === 0) { // just choose the first node as the root
|
||||
var it = coll.iterator;
|
||||
while (it.next()) {
|
||||
if (it.value instanceof go.Node) {
|
||||
root = it.value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (roots.count === 1) { // normal case: just one root node
|
||||
root = roots.first();
|
||||
}
|
||||
else { // multiple root nodes -- create a dummy node to be the one real root
|
||||
root = new go.Node(); // the new root node
|
||||
root.location = new go.Point(0, 0);
|
||||
var forwards_1 = (this.diagram ? this.diagram.isTreePathToChildren : true);
|
||||
// now make dummy links from the one root node to each node
|
||||
roots.each(function (child) {
|
||||
var link = new go.Link();
|
||||
if (forwards_1) {
|
||||
link.fromNode = root;
|
||||
link.toNode = child;
|
||||
}
|
||||
else {
|
||||
link.fromNode = child;
|
||||
link.toNode = root;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (root === null)
|
||||
return;
|
||||
// the ROOT node is shared by both subtrees
|
||||
leftParts.add(root);
|
||||
rightParts.add(root);
|
||||
var lay = this;
|
||||
// look at all of the immediate children of the ROOT node
|
||||
root.findTreeChildrenNodes().each(function (child) {
|
||||
// in what direction is this child growing?
|
||||
var bottomright = lay.isPositiveDirection(child);
|
||||
var parts = bottomright ? rightParts : leftParts;
|
||||
// add the whole subtree starting with this child node
|
||||
parts.addAll(child.findTreeParts());
|
||||
// and also add the link from the ROOT node to this child node
|
||||
var plink = child.findTreeParentLink();
|
||||
if (plink !== null)
|
||||
parts.add(plink);
|
||||
});
|
||||
};
|
||||
/**
|
||||
* This predicate is called on each child node of the root node,
|
||||
* and only on immediate children of the root.
|
||||
* It should return true if this child node is the root of a subtree that should grow
|
||||
* rightwards or downwards, or false otherwise.
|
||||
* @param {Node} child
|
||||
* @returns {boolean} true if grows towards right or towards bottom; false otherwise
|
||||
*/
|
||||
DoubleTreeLayout.prototype.isPositiveDirection = function (child) {
|
||||
var f = this.directionFunction;
|
||||
if (!f)
|
||||
throw new Error("No DoubleTreeLayout.directionFunction supplied on the layout");
|
||||
return f(child);
|
||||
};
|
||||
return DoubleTreeLayout;
|
||||
}(go.Layout));
|
||||
exports.DoubleTreeLayout = DoubleTreeLayout;
|
||||
});
|
||||
+226
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
|
||||
/**
|
||||
* Perform two TreeLayouts, one going rightwards and one going leftwards.
|
||||
* The choice of direction is determined by the mandatory predicate {@link #directionFunction},
|
||||
* which is called on each child Node of the root Node.
|
||||
*
|
||||
* You can also set {@link #vertical} to true if you want the DoubleTreeLayout to
|
||||
* perform TreeLayouts both downwards and upwards.
|
||||
*
|
||||
* Normally there should be a single root node. Hoewver if there are multiple root nodes
|
||||
* found in the nodes and links that this layout is responsible for, this will pretend that
|
||||
* there is a real root node and make all of the apparent root nodes children of that pretend root.
|
||||
*
|
||||
* If there is no root node, all nodes are involved in cycles, so the first given node is chosen.
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../samples/doubleTree.html">Double Tree</a> sample.
|
||||
* @category Layout Extension
|
||||
*/
|
||||
export class DoubleTreeLayout extends go.Layout {
|
||||
private _vertical: boolean = false;
|
||||
private _directionFunction: ((node: go.Node) => boolean) = function(node: go.Node): boolean { return true; };
|
||||
private _bottomRightOptions: Partial<go.TreeLayout> | null = null;
|
||||
private _topLeftOptions: Partial<go.TreeLayout> | null = null;
|
||||
|
||||
/**
|
||||
* When false, the layout should grow towards the left and towards the right;
|
||||
* when true, the layout show grow upwards and downwards.
|
||||
* The default value is false.
|
||||
*/
|
||||
get vertical(): boolean { return this._vertical; }
|
||||
set vertical(value: boolean) {
|
||||
if (typeof value !== "boolean") throw new Error("new value for DoubleTreeLayout.vertical must be a boolean value.");
|
||||
if (this._vertical !== value) {
|
||||
this._vertical = value;
|
||||
this.invalidateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called on each child node of the root node
|
||||
* in order to determine whether the subtree starting from that child node
|
||||
* will grow towards larger coordinates or towards smaller ones.
|
||||
* The value must be a function and must not be null.
|
||||
* It must return true if {@link #isPositiveDirection} should return true; otherwise it should return false.
|
||||
*/
|
||||
get directionFunction(): ((node: go.Node) => boolean) { return this._directionFunction; }
|
||||
set directionFunction(value: ((node: go.Node) => boolean)) {
|
||||
if (typeof value !== "function") {
|
||||
throw new Error("new value for DoubleTreeLayout.directionFunction must be a function taking a node data object and returning a boolean.");
|
||||
}
|
||||
if (this._directionFunction !== value) {
|
||||
this._directionFunction = value;
|
||||
this.invalidateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the options to be applied to a {@link TreeLayout}.
|
||||
* By default this is null -- no properties are set on the TreeLayout
|
||||
* other than the {@link TreeLayout#angle}, depending on {@link #vertical} and
|
||||
* the result of calling {@link #directionFunction}.
|
||||
*/
|
||||
get bottomRightOptions(): Partial<go.TreeLayout> | null { return this._bottomRightOptions; }
|
||||
set bottomRightOptions(value: Partial<go.TreeLayout> | null) {
|
||||
if (this._bottomRightOptions !== value) {
|
||||
this._bottomRightOptions = value;
|
||||
this.invalidateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the options to be applied to a {@link TreeLayout}.
|
||||
* By default this is null -- no properties are set on the TreeLayout
|
||||
* other than the {@link TreeLayout#angle}, depending on {@link #vertical} and
|
||||
* the result of calling {@link #directionFunction}.
|
||||
*/
|
||||
get topLeftOptions(): Partial<go.TreeLayout> | null { return this._topLeftOptions; }
|
||||
set topLeftOptions(value: Partial<go.TreeLayout> | null) {
|
||||
if (this._topLeftOptions !== value) {
|
||||
this._topLeftOptions = value;
|
||||
this.invalidateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
* Copies properties to a cloned Layout.
|
||||
*/
|
||||
protected cloneProtected(copy: this): void {
|
||||
super.cloneProtected(copy);
|
||||
copy._vertical = this._vertical;
|
||||
copy._directionFunction = this._directionFunction;
|
||||
copy._bottomRightOptions = this._bottomRightOptions;
|
||||
copy._topLeftOptions = this._topLeftOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform two {@link TreeLayout}s by splitting the collection of Parts
|
||||
* into two separate subsets but sharing only a single root Node.
|
||||
* @param coll
|
||||
*/
|
||||
public doLayout(coll: (go.Diagram | go.Group | go.Iterable<go.Part>)): void {
|
||||
const coll2: go.Set<go.Part> = this.collectParts(coll);
|
||||
if (coll2.count === 0) return;
|
||||
const diagram = this.diagram;
|
||||
if (diagram !== null) diagram.startTransaction("Double Tree Layout");
|
||||
|
||||
// split the nodes and links into two Sets, depending on direction
|
||||
const leftParts = new go.Set<go.Part>();
|
||||
const rightParts = new go.Set<go.Part>();
|
||||
this.separatePartsForLayout(coll2, leftParts, rightParts);
|
||||
// but the ROOT node will be in both collections
|
||||
|
||||
// create and perform two TreeLayouts, one in each direction,
|
||||
// without moving the ROOT node, on the different subsets of nodes and links
|
||||
const layout1 = this.createTreeLayout(false);
|
||||
layout1.angle = this.vertical ? 270 : 180;
|
||||
layout1.arrangement = go.TreeLayout.ArrangementFixedRoots;
|
||||
|
||||
const layout2 = this.createTreeLayout(true);
|
||||
layout2.angle = this.vertical ? 90 : 0;
|
||||
layout2.arrangement = go.TreeLayout.ArrangementFixedRoots;
|
||||
|
||||
layout1.doLayout(leftParts);
|
||||
layout2.doLayout(rightParts);
|
||||
|
||||
if (diagram !== null) diagram.commitTransaction("Double Tree Layout");
|
||||
}
|
||||
|
||||
/**
|
||||
* This just returns an instance of {@link TreeLayout}.
|
||||
* The caller will set the {@link TreeLayout#angle}.
|
||||
* @param {boolean} positive true for growth downward or rightward
|
||||
* @return {TreeLayout}
|
||||
*/
|
||||
protected createTreeLayout(positive: boolean): go.TreeLayout {
|
||||
const lay = new go.TreeLayout();
|
||||
let opts = this.topLeftOptions;
|
||||
if (positive) opts = this.bottomRightOptions;
|
||||
if (opts) for (const p in opts) { (<any>lay)[p] = (<any>opts)[p]; }
|
||||
return lay;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called by {@link #doLayout} to split the collection of Nodes and Links into two Sets,
|
||||
* one for the subtrees growing towards the left or upwards, and one for the subtrees
|
||||
* growing towards the right or downwards.
|
||||
*/
|
||||
protected separatePartsForLayout(coll: go.Set<go.Part>, leftParts: go.Set<go.Part>, rightParts: go.Set<go.Part>): void {
|
||||
let root: go.Node | null = null; // the one root
|
||||
const roots = new go.Set<go.Node>(); // in case there are multiple roots
|
||||
coll.each(function(node: go.Part) {
|
||||
if (node instanceof go.Node && node.findTreeParentNode() === null) roots.add(node);
|
||||
});
|
||||
if (roots.count === 0) { // just choose the first node as the root
|
||||
const it = coll.iterator;
|
||||
while (it.next()) {
|
||||
if (it.value instanceof go.Node) {
|
||||
root = it.value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (roots.count === 1) { // normal case: just one root node
|
||||
root = roots.first();
|
||||
} else { // multiple root nodes -- create a dummy node to be the one real root
|
||||
root = new go.Node(); // the new root node
|
||||
root.location = new go.Point(0, 0);
|
||||
const forwards = (this.diagram ? this.diagram.isTreePathToChildren : true);
|
||||
// now make dummy links from the one root node to each node
|
||||
roots.each(function(child) {
|
||||
const link = new go.Link();
|
||||
if (forwards) {
|
||||
link.fromNode = root;
|
||||
link.toNode = child;
|
||||
} else {
|
||||
link.fromNode = child;
|
||||
link.toNode = root;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (root === null) return;
|
||||
|
||||
// the ROOT node is shared by both subtrees
|
||||
leftParts.add(root);
|
||||
rightParts.add(root);
|
||||
const lay = this;
|
||||
// look at all of the immediate children of the ROOT node
|
||||
root.findTreeChildrenNodes().each(function(child) {
|
||||
// in what direction is this child growing?
|
||||
const bottomright = lay.isPositiveDirection(child);
|
||||
const parts = bottomright ? rightParts : leftParts;
|
||||
// add the whole subtree starting with this child node
|
||||
parts.addAll(child.findTreeParts());
|
||||
// and also add the link from the ROOT node to this child node
|
||||
const plink = child.findTreeParentLink();
|
||||
if (plink !== null) parts.add(plink);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This predicate is called on each child node of the root node,
|
||||
* and only on immediate children of the root.
|
||||
* It should return true if this child node is the root of a subtree that should grow
|
||||
* rightwards or downwards, or false otherwise.
|
||||
* @param {Node} child
|
||||
* @returns {boolean} true if grows towards right or towards bottom; false otherwise
|
||||
*/
|
||||
protected isPositiveDirection(child: go.Node): boolean {
|
||||
const f = this.directionFunction;
|
||||
if (!f) throw new Error("No DoubleTreeLayout.directionFunction supplied on the layout");
|
||||
return f(child);
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Drag Creating Tool</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="TypeScript: Create nodes by dragging, thereby specifying their initial size." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../samples/assets/require.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
function init() {
|
||||
require(["DragCreatingScript"], function(app) {
|
||||
app.init();
|
||||
document.getElementById("ToolEnabled").onclick = app.toolEnabled;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="background-color: white; border: solid 1px black; width: 100%;height: 800px"></div>
|
||||
<label><input id="ToolEnabled" type="checkbox" checked="checked"/>DragCreatingTool enabled</label>
|
||||
<p>
|
||||
This sample demonstrates the DragCreatingTool, which replaces the standard DragSelectingTool. It is defined in its own file,
|
||||
as <a href="DragCreatingTool.ts">DragCreatingTool.ts</a>.
|
||||
</p>
|
||||
<p>
|
||||
Press in the background and then drag to show the area to be occupied by the new node. The mouse-up event will add a copy
|
||||
of the DragCreatingTool.archetypeNodeData object, causing a new node to be created. The tool will assign its <a>GraphObject.position</a> and <a>GraphObject.desiredSize</a>.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js", "./DragCreatingTool.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.toolEnabled = exports.init = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
var DragCreatingTool_js_1 = require("./DragCreatingTool.js");
|
||||
var myDiagram;
|
||||
function init() {
|
||||
if (window.goSamples)
|
||||
window.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', {
|
||||
// Define the template for Nodes, just some text inside a colored rectangle
|
||||
nodeTemplate: $(go.Node, 'Auto', { minSize: new go.Size(60, 20), resizable: true }, new go.Binding('desiredSize', 'size', go.Size.parse).makeTwoWay(go.Size.stringify), new go.Binding('position', 'pos', go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
// temporarily put selected nodes in ForegFround layer
|
||||
new go.Binding('layerName', 'isSelected', function (s) { return s ? 'Foreground' : ''; }).ofObject(), $(go.Shape, 'Rectangle', new go.Binding('fill', 'color')), $(go.TextBlock, { margin: 2 }, new go.Binding('text', 'color'))),
|
||||
'undoManager.isEnabled': true
|
||||
});
|
||||
myDiagram.add($(go.Part, { layerName: 'Grid', location: new go.Point(0, 0) }, $(go.TextBlock, 'Mouse-down and then drag in the background\nto add a Node there with the drawn size.', { stroke: 'brown' })));
|
||||
var CustomDragCreatingTool = /** @class */ (function (_super) {
|
||||
__extends(CustomDragCreatingTool, _super);
|
||||
function CustomDragCreatingTool() {
|
||||
return _super !== null && _super.apply(this, arguments) || this;
|
||||
}
|
||||
CustomDragCreatingTool.prototype.insertPart = function (bounds) {
|
||||
if (this.archetypeNodeData === null)
|
||||
return null;
|
||||
// use a different color each time
|
||||
this.archetypeNodeData.color = go.Brush.randomColor();
|
||||
// call the base method to do normal behavior and return its result
|
||||
return DragCreatingTool_js_1.DragCreatingTool.prototype.insertPart.call(this, bounds);
|
||||
};
|
||||
return CustomDragCreatingTool;
|
||||
}(DragCreatingTool_js_1.DragCreatingTool));
|
||||
// Add an instance of the custom tool defined in DragCreatingTool.js.
|
||||
// This needs to be inserted before the standard DragSelectingTool,
|
||||
// which is normally the third Tool in the ToolManager.mouseMoveTools list.
|
||||
// Note that if you do not set the DragCreatingTool.delay, the default value will
|
||||
// require a wait after the mouse down event. Not waiting will allow the DragSelectingTool
|
||||
// and the PanningTool to be able to run instead of the DragCreatingTool, depending on the delay.
|
||||
myDiagram.toolManager.mouseMoveTools.insertAt(2, $(CustomDragCreatingTool, {
|
||||
isEnabled: true,
|
||||
delay: 0,
|
||||
box: $(go.Part, { layerName: 'Tool' }, $(go.Shape, { name: 'SHAPE', fill: null, stroke: 'cyan', strokeWidth: 2 })),
|
||||
archetypeNodeData: { color: 'white' } // initial properties shared by all nodes
|
||||
}));
|
||||
// Attach to the window for console manipulation
|
||||
window.myDiagram = myDiagram;
|
||||
}
|
||||
exports.init = init;
|
||||
function toolEnabled() {
|
||||
var enable = document.getElementById('ToolEnabled').checked;
|
||||
var tool = myDiagram.toolManager.findTool('DragCreating');
|
||||
if (tool !== null)
|
||||
tool.isEnabled = enable;
|
||||
}
|
||||
exports.toolEnabled = toolEnabled;
|
||||
});
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
import { DragCreatingTool } from './DragCreatingTool.js';
|
||||
|
||||
let myDiagram: go.Diagram;
|
||||
|
||||
export function init() {
|
||||
if ((window as any).goSamples) (window as any).goSamples(); // init for these samples -- you don't need to call this
|
||||
|
||||
const $ = go.GraphObject.make; // for conciseness in defining templates
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, 'myDiagramDiv',
|
||||
{
|
||||
// Define the template for Nodes, just some text inside a colored rectangle
|
||||
nodeTemplate:
|
||||
$(go.Node, 'Auto',
|
||||
{ minSize: new go.Size(60, 20), resizable: true },
|
||||
new go.Binding('desiredSize', 'size', go.Size.parse).makeTwoWay(go.Size.stringify),
|
||||
new go.Binding('position', 'pos', go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
// temporarily put selected nodes in ForegFround layer
|
||||
new go.Binding('layerName', 'isSelected', (s) => s ? 'Foreground' : '').ofObject(),
|
||||
$(go.Shape, 'Rectangle',
|
||||
new go.Binding('fill', 'color')),
|
||||
$(go.TextBlock,
|
||||
{ margin: 2 },
|
||||
new go.Binding('text', 'color'))),
|
||||
'undoManager.isEnabled': true
|
||||
});
|
||||
|
||||
myDiagram.add(
|
||||
$(go.Part,
|
||||
{ layerName: 'Grid', location: new go.Point(0, 0) },
|
||||
$(go.TextBlock, 'Mouse-down and then drag in the background\nto add a Node there with the drawn size.',
|
||||
{ stroke: 'brown' })
|
||||
));
|
||||
|
||||
class CustomDragCreatingTool extends DragCreatingTool {
|
||||
insertPart(bounds: go.Rect): go.Part | null { // override DragCreatingTool.insertPart
|
||||
if (this.archetypeNodeData === null) return null;
|
||||
// use a different color each time
|
||||
this.archetypeNodeData.color = go.Brush.randomColor();
|
||||
// call the base method to do normal behavior and return its result
|
||||
return DragCreatingTool.prototype.insertPart.call(this, bounds);
|
||||
}
|
||||
}
|
||||
|
||||
// Add an instance of the custom tool defined in DragCreatingTool.js.
|
||||
// This needs to be inserted before the standard DragSelectingTool,
|
||||
// which is normally the third Tool in the ToolManager.mouseMoveTools list.
|
||||
// Note that if you do not set the DragCreatingTool.delay, the default value will
|
||||
// require a wait after the mouse down event. Not waiting will allow the DragSelectingTool
|
||||
// and the PanningTool to be able to run instead of the DragCreatingTool, depending on the delay.
|
||||
myDiagram.toolManager.mouseMoveTools.insertAt(2,
|
||||
$(CustomDragCreatingTool,
|
||||
{
|
||||
isEnabled: true, // disabled by the checkbox
|
||||
delay: 0, // always canStart(), so PanningTool never gets the chance to run
|
||||
box: $(go.Part,
|
||||
{ layerName: 'Tool' },
|
||||
$(go.Shape,
|
||||
{ name: 'SHAPE', fill: null, stroke: 'cyan', strokeWidth: 2 })
|
||||
),
|
||||
archetypeNodeData: { color: 'white' } // initial properties shared by all nodes
|
||||
}));
|
||||
|
||||
// Attach to the window for console manipulation
|
||||
(window as any).myDiagram = myDiagram;
|
||||
}
|
||||
|
||||
export function toolEnabled() {
|
||||
const enable = (document.getElementById('ToolEnabled') as any).checked;
|
||||
const tool = myDiagram.toolManager.findTool('DragCreating');
|
||||
if (tool !== null) tool.isEnabled = enable;
|
||||
}
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.DragCreatingTool = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
/**
|
||||
* The DragCreatingTool lets the user create a new node by dragging in the background
|
||||
* to indicate its size and position.
|
||||
*
|
||||
* The default drag selection box is a magenta rectangle.
|
||||
* You can modify the {@link #box} to customize its appearance.
|
||||
*
|
||||
* This tool will not be able to start running unless you have set the
|
||||
* {@link #archetypeNodeData} property to an object that can be copied and added to the diagram's model.
|
||||
*
|
||||
* You can use this tool in a modal manner by executing:
|
||||
* ```js
|
||||
* diagram.currentTool = new DragCreatingTool();
|
||||
* ```
|
||||
*
|
||||
* Use this tool in a mode-less manner by executing:
|
||||
* ```js
|
||||
* myDiagram.toolManager.mouseMoveTools.insertAt(2, new DragCreatingTool());
|
||||
* ```
|
||||
*
|
||||
* However when used mode-lessly as a mouse-move tool, in {@link ToolManager#mouseMoveTools},
|
||||
* this cannot start running unless there has been a motionless delay
|
||||
* after the mouse-down event of at least {@link #delay} milliseconds.
|
||||
*
|
||||
* This tool does not utilize any {@link Adornment}s or tool handles,
|
||||
* but it does temporarily add the {@link #box} Part to the diagram.
|
||||
* This tool does conduct a transaction when inserting the new node.
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/DragCreating.html">Drag Creating</a> sample.
|
||||
* @category Tool Extension
|
||||
*/
|
||||
var DragCreatingTool = /** @class */ (function (_super) {
|
||||
__extends(DragCreatingTool, _super);
|
||||
/**
|
||||
* Constructs a DragCreatingTool, sets {@link #box} to a magenta rectangle, and sets name of the tool.
|
||||
*/
|
||||
function DragCreatingTool() {
|
||||
var _this = _super.call(this) || this;
|
||||
_this._archetypeNodeData = null;
|
||||
_this._delay = 175;
|
||||
var b = new go.Part();
|
||||
var r = new go.Shape();
|
||||
b.layerName = 'Tool';
|
||||
b.selectable = false;
|
||||
r.name = 'SHAPE';
|
||||
r.figure = 'Rectangle';
|
||||
r.fill = null;
|
||||
r.stroke = 'magenta';
|
||||
r.position = new go.Point(0, 0);
|
||||
b.add(r);
|
||||
_this._box = b;
|
||||
_this.name = 'DragCreating';
|
||||
return _this;
|
||||
}
|
||||
Object.defineProperty(DragCreatingTool.prototype, "box", {
|
||||
/**
|
||||
* Gets or sets the {@link Part} used as the "rubber-band box"
|
||||
* that is stretched to follow the mouse, as feedback for what area will
|
||||
* be passed to {@link #insertPart} upon a mouse-up.
|
||||
*
|
||||
* Initially this is a {@link Part} containing only a simple magenta rectangular {@link Shape}.
|
||||
* The object to be resized should be named "SHAPE".
|
||||
* Setting this property does not raise any events.
|
||||
*
|
||||
* Modifying this property while this tool {@link Tool#isActive} might have no effect.
|
||||
*/
|
||||
get: function () { return this._box; },
|
||||
set: function (val) { this._box = val; },
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(DragCreatingTool.prototype, "delay", {
|
||||
/**
|
||||
* Gets or sets the time in milliseconds for which the mouse must be stationary
|
||||
* before this tool can be started.
|
||||
*
|
||||
* The default value is 175 milliseconds.
|
||||
* A value of zero will allow this tool to run without any wait after the mouse down.
|
||||
* Setting this property does not raise any events.
|
||||
*/
|
||||
get: function () { return this._delay; },
|
||||
set: function (val) { this._delay = val; },
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(DragCreatingTool.prototype, "archetypeNodeData", {
|
||||
/**
|
||||
* Gets or sets a data object that will be copied and added to the diagram's model each time this tool executes.
|
||||
*
|
||||
* The default value is null.
|
||||
* The value must be non-null for this tool to be able to run.
|
||||
* Setting this property does not raise any events.
|
||||
*/
|
||||
get: function () { return this._archetypeNodeData; },
|
||||
set: function (val) { this._archetypeNodeData = val; },
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
/**
|
||||
* This tool can run when there has been a mouse-drag, far enough away not to be a click,
|
||||
* and there has been delay of at least {@link #delay} milliseconds
|
||||
* after the mouse-down before a mouse-move.
|
||||
*/
|
||||
DragCreatingTool.prototype.canStart = function () {
|
||||
if (!this.isEnabled)
|
||||
return false;
|
||||
// gotta have some node data that can be copied
|
||||
if (this.archetypeNodeData === null)
|
||||
return false;
|
||||
var diagram = this.diagram;
|
||||
// heed IsReadOnly & AllowInsert
|
||||
if (diagram.isReadOnly || diagram.isModelReadOnly)
|
||||
return false;
|
||||
if (!diagram.allowInsert)
|
||||
return false;
|
||||
var e = diagram.lastInput;
|
||||
// require left button & that it has moved far enough away from the mouse down point, so it isn't a click
|
||||
if (!e.left)
|
||||
return false;
|
||||
// don't include the following checks when this tool is running modally
|
||||
if (diagram.currentTool !== this) {
|
||||
if (!this.isBeyondDragSize())
|
||||
return false;
|
||||
// must wait for "delay" milliseconds before that tool can run
|
||||
if (e.timestamp - diagram.firstInput.timestamp < this.delay)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
/**
|
||||
* Capture the mouse and show the {@link #box}.
|
||||
*/
|
||||
DragCreatingTool.prototype.doActivate = function () {
|
||||
var diagram = this.diagram;
|
||||
this.isActive = true;
|
||||
diagram.isMouseCaptured = true;
|
||||
diagram.add(this.box);
|
||||
this.doMouseMove();
|
||||
};
|
||||
/**
|
||||
* Release the mouse and remove any {@link #box}.
|
||||
*/
|
||||
DragCreatingTool.prototype.doDeactivate = function () {
|
||||
var diagram = this.diagram;
|
||||
diagram.remove(this.box);
|
||||
diagram.isMouseCaptured = false;
|
||||
this.isActive = false;
|
||||
};
|
||||
/**
|
||||
* Update the {@link #box}'s position and size according to the value
|
||||
* of {@link #computeBoxBounds}.
|
||||
*/
|
||||
DragCreatingTool.prototype.doMouseMove = function () {
|
||||
if (this.isActive && this.box !== null) {
|
||||
var r = this.computeBoxBounds();
|
||||
var shape = this.box.findObject('SHAPE');
|
||||
if (shape === null)
|
||||
shape = this.box.findMainElement();
|
||||
if (shape !== null)
|
||||
shape.desiredSize = r.size;
|
||||
this.box.position = r.position;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Call {@link #insertPart} with the value of a call to {@link #computeBoxBounds}.
|
||||
*/
|
||||
DragCreatingTool.prototype.doMouseUp = function () {
|
||||
if (this.isActive) {
|
||||
var diagram = this.diagram;
|
||||
diagram.remove(this.box);
|
||||
try {
|
||||
diagram.currentCursor = 'wait';
|
||||
this.insertPart(this.computeBoxBounds());
|
||||
}
|
||||
finally {
|
||||
diagram.currentCursor = '';
|
||||
}
|
||||
}
|
||||
this.stopTool();
|
||||
};
|
||||
/**
|
||||
* This just returns a {@link Rect} stretching from the mouse-down point to the current mouse point.
|
||||
* @return {Rect} a {@link Rect} in document coordinates.
|
||||
*/
|
||||
DragCreatingTool.prototype.computeBoxBounds = function () {
|
||||
var diagram = this.diagram;
|
||||
var start = diagram.firstInput.documentPoint;
|
||||
var latest = diagram.lastInput.documentPoint;
|
||||
return new go.Rect(start, latest);
|
||||
};
|
||||
/**
|
||||
* Create a node by adding a copy of the {@link #archetypeNodeData} object
|
||||
* to the diagram's model, assign its {@link GraphObject#position} and {@link GraphObject#desiredSize}
|
||||
* according to the given bounds, and select the new part.
|
||||
*
|
||||
* The actual part that is added to the diagram may be a {@link Part}, a {@link Node},
|
||||
* or even a {@link Group}, depending on the properties of the {@link #archetypeNodeData}
|
||||
* and the type of the template that is copied to create the part.
|
||||
* @param {Rect} bounds a Point in document coordinates.
|
||||
* @return {Part} the newly created Part, or null if it failed.
|
||||
*/
|
||||
DragCreatingTool.prototype.insertPart = function (bounds) {
|
||||
var diagram = this.diagram;
|
||||
var arch = this.archetypeNodeData;
|
||||
if (arch === null)
|
||||
return null;
|
||||
diagram.raiseDiagramEvent('ChangingSelection', diagram.selection);
|
||||
this.startTransaction(this.name);
|
||||
var part = null;
|
||||
if (arch !== null) {
|
||||
var data = diagram.model.copyNodeData(arch);
|
||||
if (data) {
|
||||
diagram.model.addNodeData(data);
|
||||
part = diagram.findPartForData(data);
|
||||
}
|
||||
}
|
||||
if (part !== null) {
|
||||
part.position = bounds.position;
|
||||
part.resizeObject.desiredSize = bounds.size;
|
||||
if (diagram.allowSelect) {
|
||||
diagram.clearSelection();
|
||||
part.isSelected = true;
|
||||
}
|
||||
}
|
||||
// set the TransactionResult before raising event, in case it changes the result or cancels the tool
|
||||
this.transactionResult = this.name;
|
||||
this.stopTransaction();
|
||||
diagram.raiseDiagramEvent('ChangedSelection', diagram.selection);
|
||||
return part;
|
||||
};
|
||||
return DragCreatingTool;
|
||||
}(go.Tool));
|
||||
exports.DragCreatingTool = DragCreatingTool;
|
||||
});
|
||||
+237
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
|
||||
/**
|
||||
* The DragCreatingTool lets the user create a new node by dragging in the background
|
||||
* to indicate its size and position.
|
||||
*
|
||||
* The default drag selection box is a magenta rectangle.
|
||||
* You can modify the {@link #box} to customize its appearance.
|
||||
*
|
||||
* This tool will not be able to start running unless you have set the
|
||||
* {@link #archetypeNodeData} property to an object that can be copied and added to the diagram's model.
|
||||
*
|
||||
* You can use this tool in a modal manner by executing:
|
||||
* ```js
|
||||
* diagram.currentTool = new DragCreatingTool();
|
||||
* ```
|
||||
*
|
||||
* Use this tool in a mode-less manner by executing:
|
||||
* ```js
|
||||
* myDiagram.toolManager.mouseMoveTools.insertAt(2, new DragCreatingTool());
|
||||
* ```
|
||||
*
|
||||
* However when used mode-lessly as a mouse-move tool, in {@link ToolManager#mouseMoveTools},
|
||||
* this cannot start running unless there has been a motionless delay
|
||||
* after the mouse-down event of at least {@link #delay} milliseconds.
|
||||
*
|
||||
* This tool does not utilize any {@link Adornment}s or tool handles,
|
||||
* but it does temporarily add the {@link #box} Part to the diagram.
|
||||
* This tool does conduct a transaction when inserting the new node.
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/DragCreating.html">Drag Creating</a> sample.
|
||||
* @category Tool Extension
|
||||
*/
|
||||
export class DragCreatingTool extends go.Tool {
|
||||
private _box: go.Part;
|
||||
private _archetypeNodeData: go.ObjectData | null = null;
|
||||
private _delay: number = 175;
|
||||
|
||||
/**
|
||||
* Constructs a DragCreatingTool, sets {@link #box} to a magenta rectangle, and sets name of the tool.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
const b: go.Part = new go.Part();
|
||||
const r: go.Shape = new go.Shape();
|
||||
b.layerName = 'Tool';
|
||||
b.selectable = false;
|
||||
r.name = 'SHAPE';
|
||||
r.figure = 'Rectangle';
|
||||
r.fill = null;
|
||||
r.stroke = 'magenta';
|
||||
r.position = new go.Point(0, 0);
|
||||
b.add(r);
|
||||
this._box = b;
|
||||
this.name = 'DragCreating';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the {@link Part} used as the "rubber-band box"
|
||||
* that is stretched to follow the mouse, as feedback for what area will
|
||||
* be passed to {@link #insertPart} upon a mouse-up.
|
||||
*
|
||||
* Initially this is a {@link Part} containing only a simple magenta rectangular {@link Shape}.
|
||||
* The object to be resized should be named "SHAPE".
|
||||
* Setting this property does not raise any events.
|
||||
*
|
||||
* Modifying this property while this tool {@link Tool#isActive} might have no effect.
|
||||
*/
|
||||
get box(): go.Part { return this._box; }
|
||||
set box(val: go.Part) { this._box = val; }
|
||||
|
||||
/**
|
||||
* Gets or sets the time in milliseconds for which the mouse must be stationary
|
||||
* before this tool can be started.
|
||||
*
|
||||
* The default value is 175 milliseconds.
|
||||
* A value of zero will allow this tool to run without any wait after the mouse down.
|
||||
* Setting this property does not raise any events.
|
||||
*/
|
||||
get delay(): number { return this._delay; }
|
||||
set delay(val: number) { this._delay = val; }
|
||||
|
||||
/**
|
||||
* Gets or sets a data object that will be copied and added to the diagram's model each time this tool executes.
|
||||
*
|
||||
* The default value is null.
|
||||
* The value must be non-null for this tool to be able to run.
|
||||
* Setting this property does not raise any events.
|
||||
*/
|
||||
get archetypeNodeData(): go.ObjectData | null { return this._archetypeNodeData; }
|
||||
set archetypeNodeData(val: go.ObjectData | null) { this._archetypeNodeData = val; }
|
||||
|
||||
/**
|
||||
* This tool can run when there has been a mouse-drag, far enough away not to be a click,
|
||||
* and there has been delay of at least {@link #delay} milliseconds
|
||||
* after the mouse-down before a mouse-move.
|
||||
*/
|
||||
public canStart(): boolean {
|
||||
if (!this.isEnabled) return false;
|
||||
|
||||
// gotta have some node data that can be copied
|
||||
if (this.archetypeNodeData === null) return false;
|
||||
|
||||
const diagram = this.diagram;
|
||||
// heed IsReadOnly & AllowInsert
|
||||
if (diagram.isReadOnly || diagram.isModelReadOnly) return false;
|
||||
if (!diagram.allowInsert) return false;
|
||||
|
||||
const e = diagram.lastInput;
|
||||
// require left button & that it has moved far enough away from the mouse down point, so it isn't a click
|
||||
if (!e.left) return false;
|
||||
// don't include the following checks when this tool is running modally
|
||||
if (diagram.currentTool !== this) {
|
||||
if (!this.isBeyondDragSize()) return false;
|
||||
// must wait for "delay" milliseconds before that tool can run
|
||||
if (e.timestamp - diagram.firstInput.timestamp < this.delay) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Capture the mouse and show the {@link #box}.
|
||||
*/
|
||||
public doActivate(): void {
|
||||
const diagram = this.diagram;
|
||||
this.isActive = true;
|
||||
diagram.isMouseCaptured = true;
|
||||
diagram.add(this.box);
|
||||
this.doMouseMove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the mouse and remove any {@link #box}.
|
||||
*/
|
||||
public doDeactivate(): void {
|
||||
const diagram = this.diagram;
|
||||
diagram.remove(this.box);
|
||||
diagram.isMouseCaptured = false;
|
||||
this.isActive = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the {@link #box}'s position and size according to the value
|
||||
* of {@link #computeBoxBounds}.
|
||||
*/
|
||||
public doMouseMove(): void {
|
||||
if (this.isActive && this.box !== null) {
|
||||
const r = this.computeBoxBounds();
|
||||
let shape = this.box.findObject('SHAPE');
|
||||
if (shape === null) shape = this.box.findMainElement();
|
||||
if (shape !== null) shape.desiredSize = r.size;
|
||||
this.box.position = r.position;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call {@link #insertPart} with the value of a call to {@link #computeBoxBounds}.
|
||||
*/
|
||||
public doMouseUp(): void {
|
||||
if (this.isActive) {
|
||||
const diagram = this.diagram;
|
||||
diagram.remove(this.box);
|
||||
try {
|
||||
diagram.currentCursor = 'wait';
|
||||
this.insertPart(this.computeBoxBounds());
|
||||
} finally {
|
||||
diagram.currentCursor = '';
|
||||
}
|
||||
}
|
||||
this.stopTool();
|
||||
}
|
||||
|
||||
/**
|
||||
* This just returns a {@link Rect} stretching from the mouse-down point to the current mouse point.
|
||||
* @return {Rect} a {@link Rect} in document coordinates.
|
||||
*/
|
||||
public computeBoxBounds(): go.Rect {
|
||||
const diagram = this.diagram;
|
||||
const start = diagram.firstInput.documentPoint;
|
||||
const latest = diagram.lastInput.documentPoint;
|
||||
return new go.Rect(start, latest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a node by adding a copy of the {@link #archetypeNodeData} object
|
||||
* to the diagram's model, assign its {@link GraphObject#position} and {@link GraphObject#desiredSize}
|
||||
* according to the given bounds, and select the new part.
|
||||
*
|
||||
* The actual part that is added to the diagram may be a {@link Part}, a {@link Node},
|
||||
* or even a {@link Group}, depending on the properties of the {@link #archetypeNodeData}
|
||||
* and the type of the template that is copied to create the part.
|
||||
* @param {Rect} bounds a Point in document coordinates.
|
||||
* @return {Part} the newly created Part, or null if it failed.
|
||||
*/
|
||||
public insertPart(bounds: go.Rect): go.Part | null {
|
||||
const diagram = this.diagram;
|
||||
const arch = this.archetypeNodeData;
|
||||
if (arch === null) return null;
|
||||
|
||||
diagram.raiseDiagramEvent('ChangingSelection', diagram.selection);
|
||||
this.startTransaction(this.name);
|
||||
let part = null;
|
||||
if (arch !== null) {
|
||||
const data = diagram.model.copyNodeData(arch);
|
||||
if (data) {
|
||||
diagram.model.addNodeData(data);
|
||||
part = diagram.findPartForData(data);
|
||||
}
|
||||
}
|
||||
if (part !== null) {
|
||||
part.position = bounds.position;
|
||||
part.resizeObject.desiredSize = bounds.size;
|
||||
if (diagram.allowSelect) {
|
||||
diagram.clearSelection();
|
||||
part.isSelected = true;
|
||||
}
|
||||
}
|
||||
|
||||
// set the TransactionResult before raising event, in case it changes the result or cancels the tool
|
||||
this.transactionResult = this.name;
|
||||
this.stopTransaction();
|
||||
diagram.raiseDiagramEvent('ChangedSelection', diagram.selection);
|
||||
return part;
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Drag Zooming Tool</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="TypeScript: Users can zoom into and out of a diagram by drawing a rectangle showing what part of the document should be shown by the new viewport." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../samples/assets/require.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
function init() {
|
||||
require(["DragZoomingScript"], function(app) {
|
||||
app.init();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="background-color: white; border: solid 1px black; width: 100%;height: 800px"></div>
|
||||
<p>
|
||||
This sample demonstrates the DragZoomingTool, which replaces the standard DragSelectingTool. It is defined in its own file, as <a href="DragZoomingTool.ts">DragZoomingTool.ts</a>.
|
||||
</p>
|
||||
<p>
|
||||
Press in the background, wait briefly, and then drag to zoom in to show the area of the drawn rectangle.
|
||||
Hold down the Shift key to zoom out.
|
||||
The rectangle always has the same aspect ratio as the viewport of the diagram.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js", "./DragZoomingTool.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.init = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
var DragZoomingTool_js_1 = require("./DragZoomingTool.js");
|
||||
var myDiagram;
|
||||
var myLoading;
|
||||
function init() {
|
||||
if (window.goSamples)
|
||||
window.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', {
|
||||
initialDocumentSpot: go.Spot.Center,
|
||||
initialViewportSpot: go.Spot.Center,
|
||||
// Define the template for Nodes, just some text inside a colored rectangle
|
||||
nodeTemplate: $(go.Node, 'Spot', { width: 70, height: 20 }, $(go.Shape, 'Rectangle', new go.Binding('fill', 'c')), $(go.TextBlock, { margin: 2 }, new go.Binding('text', 'c'))),
|
||||
// Define the template for Links, just a simple line
|
||||
linkTemplate: $(go.Link, $(go.Shape, { stroke: 'black' })),
|
||||
layout: $(go.TreeLayout, {
|
||||
angle: 90,
|
||||
nodeSpacing: 4,
|
||||
compaction: go.TreeLayout.CompactionNone
|
||||
}),
|
||||
model: $(go.TreeModel, {
|
||||
nodeKeyProperty: 'k',
|
||||
nodeParentKeyProperty: 'p'
|
||||
})
|
||||
});
|
||||
// Add an instance of the custom tool defined in DragZoomingTool.js.
|
||||
// This needs to be inserted before the standard DragSelectingTool,
|
||||
// which is normally the third Tool in the ToolManager.mouseMoveTools list.
|
||||
myDiagram.toolManager.mouseMoveTools.insertAt(2, new DragZoomingTool_js_1.DragZoomingTool());
|
||||
// This is a status message
|
||||
myLoading =
|
||||
$(go.Part, { selectable: false, location: new go.Point(0, 0) }, $(go.TextBlock, 'loading...', { stroke: 'red', font: '20pt sans-serif' }));
|
||||
// temporarily add the status indicator
|
||||
myDiagram.add(myLoading);
|
||||
// allow the myLoading indicator to be shown now,
|
||||
// but allow objects added in loadTree to also be considered part of the initial Diagram
|
||||
myDiagram.delayInitialization(loadTree);
|
||||
}
|
||||
exports.init = init;
|
||||
function loadTree() {
|
||||
// create some tree data
|
||||
var total = 99;
|
||||
var treedata = [];
|
||||
for (var i = 0; i < total; i++) {
|
||||
// these property names are also specified when creating the TreeModel
|
||||
var d = {
|
||||
k: i,
|
||||
c: go.Brush.randomColor(),
|
||||
p: (i > 0 ? Math.floor(Math.random() * i / 2) : undefined) // the random parent's key
|
||||
};
|
||||
treedata.push(d);
|
||||
}
|
||||
// give the Diagram's model all the data
|
||||
myDiagram.model.nodeDataArray = treedata;
|
||||
// remove the status indicator
|
||||
myDiagram.remove(myLoading);
|
||||
// Attach to the window for console manipulation
|
||||
window.myDiagram = myDiagram;
|
||||
}
|
||||
});
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
import { DragZoomingTool } from './DragZoomingTool.js';
|
||||
|
||||
let myDiagram: go.Diagram;
|
||||
let myLoading: go.Part;
|
||||
|
||||
export function init() {
|
||||
if ((window as any).goSamples) (window as any).goSamples(); // init for these samples -- you don't need to call this
|
||||
|
||||
const $ = go.GraphObject.make; // for conciseness in defining templates
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, 'myDiagramDiv',
|
||||
{
|
||||
initialDocumentSpot: go.Spot.Center,
|
||||
initialViewportSpot: go.Spot.Center,
|
||||
|
||||
// Define the template for Nodes, just some text inside a colored rectangle
|
||||
nodeTemplate:
|
||||
$(go.Node, 'Spot',
|
||||
{ width: 70, height: 20 },
|
||||
$(go.Shape, 'Rectangle',
|
||||
new go.Binding('fill', 'c')),
|
||||
$(go.TextBlock,
|
||||
{ margin: 2 },
|
||||
new go.Binding('text', 'c'))),
|
||||
|
||||
// Define the template for Links, just a simple line
|
||||
linkTemplate:
|
||||
$(go.Link,
|
||||
$(go.Shape, { stroke: 'black' })),
|
||||
|
||||
layout:
|
||||
$(go.TreeLayout,
|
||||
{
|
||||
angle: 90,
|
||||
nodeSpacing: 4,
|
||||
compaction: go.TreeLayout.CompactionNone
|
||||
}),
|
||||
|
||||
model:
|
||||
$(go.TreeModel,
|
||||
{ // we use single character property names, to save space if rendered as JSON
|
||||
nodeKeyProperty: 'k',
|
||||
nodeParentKeyProperty: 'p'
|
||||
})
|
||||
});
|
||||
|
||||
// Add an instance of the custom tool defined in DragZoomingTool.js.
|
||||
// This needs to be inserted before the standard DragSelectingTool,
|
||||
// which is normally the third Tool in the ToolManager.mouseMoveTools list.
|
||||
myDiagram.toolManager.mouseMoveTools.insertAt(2, new DragZoomingTool());
|
||||
|
||||
// This is a status message
|
||||
myLoading =
|
||||
$(go.Part,
|
||||
{ selectable: false, location: new go.Point(0, 0) },
|
||||
$(go.TextBlock, 'loading...',
|
||||
{ stroke: 'red', font: '20pt sans-serif' }));
|
||||
|
||||
// temporarily add the status indicator
|
||||
myDiagram.add(myLoading);
|
||||
|
||||
// allow the myLoading indicator to be shown now,
|
||||
// but allow objects added in loadTree to also be considered part of the initial Diagram
|
||||
myDiagram.delayInitialization(loadTree);
|
||||
}
|
||||
|
||||
function loadTree() {
|
||||
// create some tree data
|
||||
const total = 99;
|
||||
const treedata = [];
|
||||
for (let i = 0; i < total; i++) {
|
||||
// these property names are also specified when creating the TreeModel
|
||||
const d = {
|
||||
k: i, // this node data's key
|
||||
c: go.Brush.randomColor(), // the node's color
|
||||
p: (i > 0 ? Math.floor(Math.random() * i / 2) : undefined) // the random parent's key
|
||||
};
|
||||
treedata.push(d);
|
||||
}
|
||||
|
||||
// give the Diagram's model all the data
|
||||
myDiagram.model.nodeDataArray = treedata;
|
||||
|
||||
// remove the status indicator
|
||||
myDiagram.remove(myLoading);
|
||||
|
||||
// Attach to the window for console manipulation
|
||||
(window as any).myDiagram = myDiagram;
|
||||
}
|
||||
+274
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.DragZoomingTool = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
/**
|
||||
* The DragZoomingTool lets the user zoom into a diagram by stretching a box
|
||||
* to indicate the new contents of the diagram's viewport (the area of the
|
||||
* model shown by the Diagram).
|
||||
* Hold down the Shift key in order to zoom out.
|
||||
*
|
||||
* The default drag selection box is a magenta rectangle.
|
||||
* You can modify the {@link #box} to customize its appearance.
|
||||
*
|
||||
* The diagram that is zoomed by this tool is specified by the {@link #zoomedDiagram} property.
|
||||
* If the value is null, the tool zooms its own {@link Tool#diagram}.
|
||||
*
|
||||
* You can use this tool in a modal manner by executing:
|
||||
* ```js
|
||||
* diagram.currentTool = new DragZoomingTool();
|
||||
* ```
|
||||
*
|
||||
* Use this tool in a mode-less manner by executing:
|
||||
* ```js
|
||||
* myDiagram.toolManager.mouseMoveTools.insertAt(2, new DragZoomingTool());
|
||||
* ```
|
||||
*
|
||||
* However when used mode-lessly as a mouse-move tool, in {@link ToolManager#mouseMoveTools},
|
||||
* this cannot start running unless there has been a motionless delay
|
||||
* after the mouse-down event of at least {@link #delay} milliseconds.
|
||||
*
|
||||
* This tool does not utilize any {@link Adornment}s or tool handles,
|
||||
* but it does temporarily add the {@link #box} part to the diagram.
|
||||
* This tool does not modify the model or conduct any transaction.
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/DragZooming.html">Drag Zooming</a> sample.
|
||||
* @category Tool Extension
|
||||
*/
|
||||
var DragZoomingTool = /** @class */ (function (_super) {
|
||||
__extends(DragZoomingTool, _super);
|
||||
/**
|
||||
* Constructs a DragZoomingTool, sets {@link #box} to a magenta rectangle, and sets name of the tool.
|
||||
*/
|
||||
function DragZoomingTool() {
|
||||
var _this = _super.call(this) || this;
|
||||
_this._delay = 175;
|
||||
_this._zoomedDiagram = null;
|
||||
var b = new go.Part();
|
||||
var r = new go.Shape();
|
||||
b.layerName = 'Tool';
|
||||
b.selectable = false;
|
||||
r.name = 'SHAPE';
|
||||
r.figure = 'Rectangle';
|
||||
r.fill = null;
|
||||
r.stroke = 'magenta';
|
||||
r.position = new go.Point(0, 0);
|
||||
b.add(r);
|
||||
_this._box = b;
|
||||
_this.name = 'DragZooming';
|
||||
return _this;
|
||||
}
|
||||
Object.defineProperty(DragZoomingTool.prototype, "box", {
|
||||
/**
|
||||
* Gets or sets the {@link Part} used as the "rubber-band zoom box"
|
||||
* that is stretched to follow the mouse, as feedback for what area will
|
||||
* be passed to {@link #zoomToRect} upon a mouse-up.
|
||||
*
|
||||
* Initially this is a {@link Part} containing only a simple magenta rectangular {@link Shape}.
|
||||
* The object to be resized should be named "SHAPE".
|
||||
* Setting this property does not raise any events.
|
||||
*
|
||||
* Modifying this property while this tool {@link Tool#isActive} might have no effect.
|
||||
*/
|
||||
get: function () { return this._box; },
|
||||
set: function (val) { this._box = val; },
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(DragZoomingTool.prototype, "delay", {
|
||||
/**
|
||||
* Gets or sets the time in milliseconds for which the mouse must be stationary
|
||||
* before this tool can be started.
|
||||
*
|
||||
* The default value is 175 milliseconds.
|
||||
* Setting this property does not raise any events.
|
||||
*/
|
||||
get: function () { return this._delay; },
|
||||
set: function (val) { this._delay = val; },
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(DragZoomingTool.prototype, "zoomedDiagram", {
|
||||
/**
|
||||
* Gets or sets the {@link Diagram} whose {@link Diagram#position} and {@link Diagram#scale}
|
||||
* should be set to display the drawn {@link #box} rectangular bounds.
|
||||
*
|
||||
* The default value is null, which causes {@link #zoomToRect} to modify this tool's {@link Tool#diagram}.
|
||||
* Setting this property does not raise any events.
|
||||
*/
|
||||
get: function () { return this._zoomedDiagram; },
|
||||
set: function (val) { this._zoomedDiagram = val; },
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
/**
|
||||
* This tool can run when there has been a mouse-drag, far enough away not to be a click,
|
||||
* and there has been delay of at least {@link #delay} milliseconds
|
||||
* after the mouse-down before a mouse-move.
|
||||
*/
|
||||
DragZoomingTool.prototype.canStart = function () {
|
||||
if (!this.isEnabled)
|
||||
return false;
|
||||
var diagram = this.diagram;
|
||||
var e = diagram.lastInput;
|
||||
// require left button & that it has moved far enough away from the mouse down point, so it isn't a click
|
||||
if (!e.left)
|
||||
return false;
|
||||
// don't include the following checks when this tool is running modally
|
||||
if (diagram.currentTool !== this) {
|
||||
if (!this.isBeyondDragSize())
|
||||
return false;
|
||||
// must wait for "delay" milliseconds before that tool can run
|
||||
if (e.timestamp - diagram.firstInput.timestamp < this.delay)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
/**
|
||||
* Capture the mouse and show the {@link #box}.
|
||||
*/
|
||||
DragZoomingTool.prototype.doActivate = function () {
|
||||
var diagram = this.diagram;
|
||||
this.isActive = true;
|
||||
diagram.isMouseCaptured = true;
|
||||
diagram.skipsUndoManager = true;
|
||||
diagram.add(this.box);
|
||||
this.doMouseMove();
|
||||
};
|
||||
/**
|
||||
* Release the mouse and remove any {@link #box}.
|
||||
*/
|
||||
DragZoomingTool.prototype.doDeactivate = function () {
|
||||
var diagram = this.diagram;
|
||||
diagram.remove(this.box);
|
||||
diagram.skipsUndoManager = false;
|
||||
diagram.isMouseCaptured = false;
|
||||
this.isActive = false;
|
||||
};
|
||||
/**
|
||||
* Update the {@link #box}'s position and size according to the value
|
||||
* of {@link #computeBoxBounds}.
|
||||
*/
|
||||
DragZoomingTool.prototype.doMouseMove = function () {
|
||||
var diagram = this.diagram;
|
||||
if (this.isActive && this.box !== null) {
|
||||
var r = this.computeBoxBounds();
|
||||
var shape = this.box.findObject('SHAPE');
|
||||
if (shape === null)
|
||||
shape = this.box.findMainElement();
|
||||
if (shape !== null)
|
||||
shape.desiredSize = r.size;
|
||||
this.box.position = r.position;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Call {@link #zoomToRect} with the value of a call to {@link #computeBoxBounds}.
|
||||
*/
|
||||
DragZoomingTool.prototype.doMouseUp = function () {
|
||||
if (this.isActive) {
|
||||
var diagram = this.diagram;
|
||||
diagram.remove(this.box);
|
||||
try {
|
||||
diagram.currentCursor = 'wait';
|
||||
this.zoomToRect(this.computeBoxBounds());
|
||||
}
|
||||
finally {
|
||||
diagram.currentCursor = '';
|
||||
}
|
||||
}
|
||||
this.stopTool();
|
||||
};
|
||||
/**
|
||||
* This just returns a {@link Rect} stretching from the mouse-down point to the current mouse point
|
||||
* while maintaining the aspect ratio of the {@link #zoomedDiagram}.
|
||||
* @return {Rect} a {@link Rect} in document coordinates.
|
||||
*/
|
||||
DragZoomingTool.prototype.computeBoxBounds = function () {
|
||||
var diagram = this.diagram;
|
||||
var start = diagram.firstInput.documentPoint;
|
||||
var latest = diagram.lastInput.documentPoint;
|
||||
var adx = latest.x - start.x;
|
||||
var ady = latest.y - start.y;
|
||||
var observed = this.zoomedDiagram;
|
||||
if (observed === null)
|
||||
observed = diagram;
|
||||
if (observed === null) {
|
||||
return new go.Rect(start, latest);
|
||||
}
|
||||
var vrect = observed.viewportBounds;
|
||||
if (vrect.height === 0 || ady === 0) {
|
||||
return new go.Rect(start, latest);
|
||||
}
|
||||
var vratio = vrect.width / vrect.height;
|
||||
var lx;
|
||||
var ly;
|
||||
if (Math.abs(adx / ady) < vratio) {
|
||||
lx = start.x + adx;
|
||||
ly = start.y + Math.ceil(Math.abs(adx) / vratio) * (ady < 0 ? -1 : 1);
|
||||
}
|
||||
else {
|
||||
lx = start.x + Math.ceil(Math.abs(ady) * vratio) * (adx < 0 ? -1 : 1);
|
||||
ly = start.y + ady;
|
||||
}
|
||||
return new go.Rect(start, new go.Point(lx, ly));
|
||||
};
|
||||
/**
|
||||
* This method is called to change the {@link #zoomedDiagram}'s viewport to match the given rectangle.
|
||||
* @param {Rect} r a rectangular bounds in document coordinates.
|
||||
*/
|
||||
DragZoomingTool.prototype.zoomToRect = function (r) {
|
||||
if (r.width < 0.1)
|
||||
return;
|
||||
var diagram = this.diagram;
|
||||
var observed = this.zoomedDiagram;
|
||||
if (observed === null)
|
||||
observed = diagram;
|
||||
if (observed === null)
|
||||
return;
|
||||
// zoom out when using the Shift modifier
|
||||
if (diagram.lastInput.shift) {
|
||||
observed.scale = Math.max(observed.scale * r.width / observed.viewportBounds.width, observed.minScale);
|
||||
observed.centerRect(r);
|
||||
}
|
||||
else {
|
||||
// do scale first, so the Diagram's position normalization isn't constrained unduly when increasing scale
|
||||
observed.scale = Math.min(observed.viewportBounds.width * observed.scale / r.width, observed.maxScale);
|
||||
observed.position = new go.Point(r.x, r.y);
|
||||
}
|
||||
};
|
||||
return DragZoomingTool;
|
||||
}(go.Tool));
|
||||
exports.DragZoomingTool = DragZoomingTool;
|
||||
});
|
||||
+237
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
|
||||
/**
|
||||
* The DragZoomingTool lets the user zoom into a diagram by stretching a box
|
||||
* to indicate the new contents of the diagram's viewport (the area of the
|
||||
* model shown by the Diagram).
|
||||
* Hold down the Shift key in order to zoom out.
|
||||
*
|
||||
* The default drag selection box is a magenta rectangle.
|
||||
* You can modify the {@link #box} to customize its appearance.
|
||||
*
|
||||
* The diagram that is zoomed by this tool is specified by the {@link #zoomedDiagram} property.
|
||||
* If the value is null, the tool zooms its own {@link Tool#diagram}.
|
||||
*
|
||||
* You can use this tool in a modal manner by executing:
|
||||
* ```js
|
||||
* diagram.currentTool = new DragZoomingTool();
|
||||
* ```
|
||||
*
|
||||
* Use this tool in a mode-less manner by executing:
|
||||
* ```js
|
||||
* myDiagram.toolManager.mouseMoveTools.insertAt(2, new DragZoomingTool());
|
||||
* ```
|
||||
*
|
||||
* However when used mode-lessly as a mouse-move tool, in {@link ToolManager#mouseMoveTools},
|
||||
* this cannot start running unless there has been a motionless delay
|
||||
* after the mouse-down event of at least {@link #delay} milliseconds.
|
||||
*
|
||||
* This tool does not utilize any {@link Adornment}s or tool handles,
|
||||
* but it does temporarily add the {@link #box} part to the diagram.
|
||||
* This tool does not modify the model or conduct any transaction.
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/DragZooming.html">Drag Zooming</a> sample.
|
||||
* @category Tool Extension
|
||||
*/
|
||||
export class DragZoomingTool extends go.Tool {
|
||||
private _box: go.Part;
|
||||
private _delay: number = 175;
|
||||
private _zoomedDiagram: go.Diagram | null = null;
|
||||
|
||||
/**
|
||||
* Constructs a DragZoomingTool, sets {@link #box} to a magenta rectangle, and sets name of the tool.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
const b: go.Part = new go.Part();
|
||||
const r: go.Shape = new go.Shape();
|
||||
b.layerName = 'Tool';
|
||||
b.selectable = false;
|
||||
r.name = 'SHAPE';
|
||||
r.figure = 'Rectangle';
|
||||
r.fill = null;
|
||||
r.stroke = 'magenta';
|
||||
r.position = new go.Point(0, 0);
|
||||
b.add(r);
|
||||
this._box = b;
|
||||
this.name = 'DragZooming';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the {@link Part} used as the "rubber-band zoom box"
|
||||
* that is stretched to follow the mouse, as feedback for what area will
|
||||
* be passed to {@link #zoomToRect} upon a mouse-up.
|
||||
*
|
||||
* Initially this is a {@link Part} containing only a simple magenta rectangular {@link Shape}.
|
||||
* The object to be resized should be named "SHAPE".
|
||||
* Setting this property does not raise any events.
|
||||
*
|
||||
* Modifying this property while this tool {@link Tool#isActive} might have no effect.
|
||||
*/
|
||||
get box(): go.Part { return this._box; }
|
||||
set box(val: go.Part) { this._box = val; }
|
||||
|
||||
/**
|
||||
* Gets or sets the time in milliseconds for which the mouse must be stationary
|
||||
* before this tool can be started.
|
||||
*
|
||||
* The default value is 175 milliseconds.
|
||||
* Setting this property does not raise any events.
|
||||
*/
|
||||
get delay(): number { return this._delay; }
|
||||
set delay(val: number) { this._delay = val; }
|
||||
|
||||
/**
|
||||
* Gets or sets the {@link Diagram} whose {@link Diagram#position} and {@link Diagram#scale}
|
||||
* should be set to display the drawn {@link #box} rectangular bounds.
|
||||
*
|
||||
* The default value is null, which causes {@link #zoomToRect} to modify this tool's {@link Tool#diagram}.
|
||||
* Setting this property does not raise any events.
|
||||
*/
|
||||
get zoomedDiagram(): go.Diagram | null { return this._zoomedDiagram; }
|
||||
set zoomedDiagram(val: go.Diagram | null) { this._zoomedDiagram = val; }
|
||||
|
||||
/**
|
||||
* This tool can run when there has been a mouse-drag, far enough away not to be a click,
|
||||
* and there has been delay of at least {@link #delay} milliseconds
|
||||
* after the mouse-down before a mouse-move.
|
||||
*/
|
||||
public canStart(): boolean {
|
||||
if (!this.isEnabled) return false;
|
||||
const diagram = this.diagram;
|
||||
const e = diagram.lastInput;
|
||||
// require left button & that it has moved far enough away from the mouse down point, so it isn't a click
|
||||
if (!e.left) return false;
|
||||
// don't include the following checks when this tool is running modally
|
||||
if (diagram.currentTool !== this) {
|
||||
if (!this.isBeyondDragSize()) return false;
|
||||
// must wait for "delay" milliseconds before that tool can run
|
||||
if (e.timestamp - diagram.firstInput.timestamp < this.delay) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Capture the mouse and show the {@link #box}.
|
||||
*/
|
||||
public doActivate(): void {
|
||||
const diagram = this.diagram;
|
||||
this.isActive = true;
|
||||
diagram.isMouseCaptured = true;
|
||||
diagram.skipsUndoManager = true;
|
||||
diagram.add(this.box);
|
||||
this.doMouseMove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the mouse and remove any {@link #box}.
|
||||
*/
|
||||
public doDeactivate(): void {
|
||||
const diagram = this.diagram;
|
||||
diagram.remove(this.box);
|
||||
diagram.skipsUndoManager = false;
|
||||
diagram.isMouseCaptured = false;
|
||||
this.isActive = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the {@link #box}'s position and size according to the value
|
||||
* of {@link #computeBoxBounds}.
|
||||
*/
|
||||
public doMouseMove(): void {
|
||||
const diagram = this.diagram;
|
||||
if (this.isActive && this.box !== null) {
|
||||
const r = this.computeBoxBounds();
|
||||
let shape = this.box.findObject('SHAPE');
|
||||
if (shape === null) shape = this.box.findMainElement();
|
||||
if (shape !== null) shape.desiredSize = r.size;
|
||||
this.box.position = r.position;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call {@link #zoomToRect} with the value of a call to {@link #computeBoxBounds}.
|
||||
*/
|
||||
public doMouseUp(): void {
|
||||
if (this.isActive) {
|
||||
const diagram = this.diagram;
|
||||
diagram.remove(this.box);
|
||||
try {
|
||||
diagram.currentCursor = 'wait';
|
||||
this.zoomToRect(this.computeBoxBounds());
|
||||
} finally {
|
||||
diagram.currentCursor = '';
|
||||
}
|
||||
}
|
||||
this.stopTool();
|
||||
}
|
||||
|
||||
/**
|
||||
* This just returns a {@link Rect} stretching from the mouse-down point to the current mouse point
|
||||
* while maintaining the aspect ratio of the {@link #zoomedDiagram}.
|
||||
* @return {Rect} a {@link Rect} in document coordinates.
|
||||
*/
|
||||
public computeBoxBounds(): go.Rect {
|
||||
const diagram = this.diagram;
|
||||
const start = diagram.firstInput.documentPoint;
|
||||
const latest = diagram.lastInput.documentPoint;
|
||||
const adx = latest.x - start.x;
|
||||
const ady = latest.y - start.y;
|
||||
|
||||
let observed = this.zoomedDiagram;
|
||||
if (observed === null) observed = diagram;
|
||||
if (observed === null) {
|
||||
return new go.Rect(start, latest);
|
||||
}
|
||||
const vrect = observed.viewportBounds;
|
||||
if (vrect.height === 0 || ady === 0) {
|
||||
return new go.Rect(start, latest);
|
||||
}
|
||||
|
||||
const vratio = vrect.width / vrect.height;
|
||||
let lx;
|
||||
let ly;
|
||||
if (Math.abs(adx / ady) < vratio) {
|
||||
lx = start.x + adx;
|
||||
ly = start.y + Math.ceil(Math.abs(adx) / vratio) * (ady < 0 ? -1 : 1);
|
||||
} else {
|
||||
lx = start.x + Math.ceil(Math.abs(ady) * vratio) * (adx < 0 ? -1 : 1);
|
||||
ly = start.y + ady;
|
||||
}
|
||||
return new go.Rect(start, new go.Point(lx, ly));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called to change the {@link #zoomedDiagram}'s viewport to match the given rectangle.
|
||||
* @param {Rect} r a rectangular bounds in document coordinates.
|
||||
*/
|
||||
public zoomToRect(r: go.Rect): void {
|
||||
if (r.width < 0.1) return;
|
||||
const diagram = this.diagram;
|
||||
let observed = this.zoomedDiagram;
|
||||
if (observed === null) observed = diagram;
|
||||
if (observed === null) return;
|
||||
|
||||
// zoom out when using the Shift modifier
|
||||
if (diagram.lastInput.shift) {
|
||||
observed.scale = Math.max(observed.scale * r.width / observed.viewportBounds.width, observed.minScale);
|
||||
observed.centerRect(r);
|
||||
} else {
|
||||
// do scale first, so the Diagram's position normalization isn't constrained unduly when increasing scale
|
||||
observed.scale = Math.min(observed.viewportBounds.width * observed.scale / r.width, observed.maxScale);
|
||||
observed.position = new go.Point(r.x, r.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Drawing Commands</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="TypeScript: The DrawCommandHandler extension implements various commands for aligning and rotating objects and for handling arrow keys to select or shift." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../samples/assets/require.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
function init() {
|
||||
require(["DrawCommandHandlerScript"], function(app) {
|
||||
app.init();
|
||||
document.getElementById("leftsides").onclick = app.lefts;
|
||||
document.getElementById("rightsides").onclick = app.rights;
|
||||
document.getElementById("tops").onclick = app.tops;
|
||||
document.getElementById("bottoms").onclick = app.bottoms;
|
||||
document.getElementById("cenX").onclick = app.cenX;
|
||||
document.getElementById("cenY").onclick = app.cenY;
|
||||
document.getElementById("row").onclick = app.row;
|
||||
document.getElementById("column").onclick = app.column;
|
||||
document.getElementById("45").onclick = app.rotate45;
|
||||
document.getElementById("-45").onclick = app.rotateNeg45;
|
||||
document.getElementById("90").onclick = app.rotate90;
|
||||
document.getElementById("-90").onclick = app.rotateNeg90;
|
||||
document.getElementById("180").onclick = app.rotate180;
|
||||
document.getElementById("front").onclick = app.front;
|
||||
document.getElementById("back").onclick = app.back;
|
||||
document.getElementById("move").onclick = app.arrowMode;
|
||||
document.getElementById("select").onclick = app.arrowMode;
|
||||
document.getElementById("scroll").onclick = app.arrowMode;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<!-- The DIV for the Diagram needs an explicit size or else we won't see anything.
|
||||
Also add a border to help see the edges. -->
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:400px; height:400px"></div>
|
||||
<p>
|
||||
Align:
|
||||
<button id="leftsides">Left Sides</button>
|
||||
<button id="rightsides">Right Sides</button>
|
||||
<button id="tops">Tops</button>
|
||||
<button id="bottoms">Bottoms</button>
|
||||
<button id="cenX">Center X</button>
|
||||
<button id="cenY">Center Y</button>
|
||||
<button id="row">Row</button>
|
||||
<button id="column">Column</button>
|
||||
</br>
|
||||
Rotate:
|
||||
<button id="45">45°</button>
|
||||
<button id="-45">-45°</button>
|
||||
<button id="90">90°</button>
|
||||
<button id="-90">-90°</button>
|
||||
<button id="180">180°</button>
|
||||
</br>
|
||||
Z-Order:
|
||||
<button id="front">Pull to Front</button>
|
||||
<button id="back">Push to Back</button>
|
||||
</br>
|
||||
Arrow Mode:
|
||||
<input type="radio" name="arrow" id="move" checked="checked">Move</input>
|
||||
<input type="radio" name="arrow" id="select">Select</input>
|
||||
<input type="radio" name="arrow" id="scroll">Scroll</input>
|
||||
</p>
|
||||
<p>
|
||||
This example demonstrates a custom <a>CommandHandler</a>.
|
||||
It allows the user to position selected Parts in a diagram relative to each other,
|
||||
overrides <a>CommandHandler.doKeyDown</a> to allow handling the arrow keys in additional manners,
|
||||
and uses a "paste offset" so that pasting objects will cascade them rather than place them on top of one another.
|
||||
It is defined in its own file, as <a href="DrawCommandHandler.ts">DrawCommandHandler.ts</a>.
|
||||
</p>
|
||||
<p>
|
||||
The above buttons can be used to align Parts, rotate Parts, or change the behavior of the arrow keys.
|
||||
</p>
|
||||
<p>
|
||||
Usage can also be seen in the <a href="../projects/bpmn/BPMN.html">BPMN Editor</a> sample.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
+572
@@ -0,0 +1,572 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.DrawCommandHandler = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
/**
|
||||
* This CommandHandler class allows the user to position selected Parts in a diagram
|
||||
* relative to the first part selected, in addition to overriding the doKeyDown method
|
||||
* of the CommandHandler for handling the arrow keys in additional manners.
|
||||
*
|
||||
* Typical usage:
|
||||
* ```js
|
||||
* $(go.Diagram, "myDiagramDiv",
|
||||
* {
|
||||
* commandHandler: $(DrawCommandHandler),
|
||||
* . . .
|
||||
* }
|
||||
* )
|
||||
* ```
|
||||
* or:
|
||||
* ```js
|
||||
* myDiagram.commandHandler = new DrawCommandHandler();
|
||||
* ```
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/DrawCommandHandler.html">Drawing Commands</a> sample.
|
||||
* @category Extension
|
||||
*/
|
||||
var DrawCommandHandler = /** @class */ (function (_super) {
|
||||
__extends(DrawCommandHandler, _super);
|
||||
function DrawCommandHandler() {
|
||||
var _this = _super !== null && _super.apply(this, arguments) || this;
|
||||
_this._arrowKeyBehavior = 'move';
|
||||
_this._pasteOffset = new go.Point(10, 10);
|
||||
_this._lastPasteOffset = new go.Point(0, 0);
|
||||
return _this;
|
||||
}
|
||||
Object.defineProperty(DrawCommandHandler.prototype, "arrowKeyBehavior", {
|
||||
/**
|
||||
* Gets or sets the arrow key behavior. Possible values are "move", "select", and "scroll".
|
||||
*
|
||||
* The default value is "move".
|
||||
*/
|
||||
get: function () { return this._arrowKeyBehavior; },
|
||||
set: function (val) {
|
||||
if (val !== 'move' && val !== 'select' && val !== 'scroll' && val !== 'none') {
|
||||
throw new Error('DrawCommandHandler.arrowKeyBehavior must be either "move", "select", "scroll", or "none", not: ' + val);
|
||||
}
|
||||
this._arrowKeyBehavior = val;
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(DrawCommandHandler.prototype, "pasteOffset", {
|
||||
/**
|
||||
* Gets or sets the offset at which each repeated {@link #pasteSelection} puts the new copied parts from the clipboard.
|
||||
*/
|
||||
get: function () { return this._pasteOffset; },
|
||||
set: function (val) {
|
||||
if (!(val instanceof go.Point))
|
||||
throw new Error('DrawCommandHandler.pasteOffset must be a Point, not: ' + val);
|
||||
this._pasteOffset.set(val);
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
/**
|
||||
* This controls whether or not the user can invoke the {@link #alignLeft}, {@link #alignRight},
|
||||
* {@link #alignTop}, {@link #alignBottom}, {@link #alignCenterX}, {@link #alignCenterY} commands.
|
||||
* @return {boolean} This returns true:
|
||||
* if the diagram is not {@link Diagram#isReadOnly},
|
||||
* if the model is not {@link Model#isReadOnly}, and
|
||||
* if there are at least two selected {@link Part}s.
|
||||
*/
|
||||
DrawCommandHandler.prototype.canAlignSelection = function () {
|
||||
var diagram = this.diagram;
|
||||
if (diagram.isReadOnly || diagram.isModelReadOnly)
|
||||
return false;
|
||||
if (diagram.selection.count < 2)
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
/**
|
||||
* Aligns selected parts along the left-most edge of the left-most part.
|
||||
*/
|
||||
DrawCommandHandler.prototype.alignLeft = function () {
|
||||
var diagram = this.diagram;
|
||||
diagram.startTransaction('aligning left');
|
||||
var minPosition = Infinity;
|
||||
diagram.selection.each(function (current) {
|
||||
if (current instanceof go.Link)
|
||||
return; // skips over go.Link
|
||||
minPosition = Math.min(current.position.x, minPosition);
|
||||
});
|
||||
diagram.selection.each(function (current) {
|
||||
if (current instanceof go.Link)
|
||||
return; // skips over go.Link
|
||||
current.move(new go.Point(minPosition, current.position.y));
|
||||
});
|
||||
diagram.commitTransaction('aligning left');
|
||||
};
|
||||
/**
|
||||
* Aligns selected parts at the right-most edge of the right-most part.
|
||||
*/
|
||||
DrawCommandHandler.prototype.alignRight = function () {
|
||||
var diagram = this.diagram;
|
||||
diagram.startTransaction('aligning right');
|
||||
var maxPosition = -Infinity;
|
||||
diagram.selection.each(function (current) {
|
||||
if (current instanceof go.Link)
|
||||
return; // skips over go.Link
|
||||
var rightSideLoc = current.actualBounds.x + current.actualBounds.width;
|
||||
maxPosition = Math.max(rightSideLoc, maxPosition);
|
||||
});
|
||||
diagram.selection.each(function (current) {
|
||||
if (current instanceof go.Link)
|
||||
return; // skips over go.Link
|
||||
current.move(new go.Point(maxPosition - current.actualBounds.width, current.position.y));
|
||||
});
|
||||
diagram.commitTransaction('aligning right');
|
||||
};
|
||||
/**
|
||||
* Aligns selected parts at the top-most edge of the top-most part.
|
||||
*/
|
||||
DrawCommandHandler.prototype.alignTop = function () {
|
||||
var diagram = this.diagram;
|
||||
diagram.startTransaction('alignTop');
|
||||
var minPosition = Infinity;
|
||||
diagram.selection.each(function (current) {
|
||||
if (current instanceof go.Link)
|
||||
return; // skips over go.Link
|
||||
minPosition = Math.min(current.position.y, minPosition);
|
||||
});
|
||||
diagram.selection.each(function (current) {
|
||||
if (current instanceof go.Link)
|
||||
return; // skips over go.Link
|
||||
current.move(new go.Point(current.position.x, minPosition));
|
||||
});
|
||||
diagram.commitTransaction('alignTop');
|
||||
};
|
||||
/**
|
||||
* Aligns selected parts at the bottom-most edge of the bottom-most part.
|
||||
*/
|
||||
DrawCommandHandler.prototype.alignBottom = function () {
|
||||
var diagram = this.diagram;
|
||||
diagram.startTransaction('aligning bottom');
|
||||
var maxPosition = -Infinity;
|
||||
diagram.selection.each(function (current) {
|
||||
if (current instanceof go.Link)
|
||||
return; // skips over go.Link
|
||||
var bottomSideLoc = current.actualBounds.y + current.actualBounds.height;
|
||||
maxPosition = Math.max(bottomSideLoc, maxPosition);
|
||||
});
|
||||
diagram.selection.each(function (current) {
|
||||
if (current instanceof go.Link)
|
||||
return; // skips over go.Link
|
||||
current.move(new go.Point(current.actualBounds.x, maxPosition - current.actualBounds.height));
|
||||
});
|
||||
diagram.commitTransaction('aligning bottom');
|
||||
};
|
||||
/**
|
||||
* Aligns selected parts at the x-value of the center point of the first selected part.
|
||||
*/
|
||||
DrawCommandHandler.prototype.alignCenterX = function () {
|
||||
var diagram = this.diagram;
|
||||
var firstSelection = diagram.selection.first();
|
||||
if (!firstSelection)
|
||||
return;
|
||||
diagram.startTransaction('aligning Center X');
|
||||
var centerX = firstSelection.actualBounds.x + firstSelection.actualBounds.width / 2;
|
||||
diagram.selection.each(function (current) {
|
||||
if (current instanceof go.Link)
|
||||
return; // skips over go.Link
|
||||
current.move(new go.Point(centerX - current.actualBounds.width / 2, current.actualBounds.y));
|
||||
});
|
||||
diagram.commitTransaction('aligning Center X');
|
||||
};
|
||||
/**
|
||||
* Aligns selected parts at the y-value of the center point of the first selected part.
|
||||
*/
|
||||
DrawCommandHandler.prototype.alignCenterY = function () {
|
||||
var diagram = this.diagram;
|
||||
var firstSelection = diagram.selection.first();
|
||||
if (!firstSelection)
|
||||
return;
|
||||
diagram.startTransaction('aligning Center Y');
|
||||
var centerY = firstSelection.actualBounds.y + firstSelection.actualBounds.height / 2;
|
||||
diagram.selection.each(function (current) {
|
||||
if (current instanceof go.Link)
|
||||
return; // skips over go.Link
|
||||
current.move(new go.Point(current.actualBounds.x, centerY - current.actualBounds.height / 2));
|
||||
});
|
||||
diagram.commitTransaction('aligning Center Y');
|
||||
};
|
||||
/**
|
||||
* Aligns selected parts top-to-bottom in order of the order selected.
|
||||
* Distance between parts can be specified. Default distance is 0.
|
||||
*/
|
||||
DrawCommandHandler.prototype.alignColumn = function (distance) {
|
||||
var diagram = this.diagram;
|
||||
diagram.startTransaction('align Column');
|
||||
if (distance === undefined)
|
||||
distance = 0; // for aligning edge to edge
|
||||
distance = parseFloat(distance.toString());
|
||||
var selectedParts = new Array();
|
||||
diagram.selection.each(function (current) {
|
||||
if (current instanceof go.Link)
|
||||
return; // skips over go.Link
|
||||
selectedParts.push(current);
|
||||
});
|
||||
for (var i = 0; i < selectedParts.length - 1; i++) {
|
||||
var current = selectedParts[i];
|
||||
// adds distance specified between parts
|
||||
var curBottomSideLoc = current.actualBounds.y + current.actualBounds.height + distance;
|
||||
var next = selectedParts[i + 1];
|
||||
next.move(new go.Point(current.actualBounds.x, curBottomSideLoc));
|
||||
}
|
||||
diagram.commitTransaction('align Column');
|
||||
};
|
||||
/**
|
||||
* Aligns selected parts left-to-right in order of the order selected.
|
||||
* Distance between parts can be specified. Default distance is 0.
|
||||
*/
|
||||
DrawCommandHandler.prototype.alignRow = function (distance) {
|
||||
if (distance === undefined)
|
||||
distance = 0; // for aligning edge to edge
|
||||
distance = parseFloat(distance.toString());
|
||||
var diagram = this.diagram;
|
||||
diagram.startTransaction('align Row');
|
||||
var selectedParts = new Array();
|
||||
diagram.selection.each(function (current) {
|
||||
if (current instanceof go.Link)
|
||||
return; // skips over go.Link
|
||||
selectedParts.push(current);
|
||||
});
|
||||
for (var i = 0; i < selectedParts.length - 1; i++) {
|
||||
var current = selectedParts[i];
|
||||
// adds distance specified between parts
|
||||
var curRightSideLoc = current.actualBounds.x + current.actualBounds.width + distance;
|
||||
var next = selectedParts[i + 1];
|
||||
next.move(new go.Point(curRightSideLoc, current.actualBounds.y));
|
||||
}
|
||||
diagram.commitTransaction('align Row');
|
||||
};
|
||||
/**
|
||||
* This controls whether or not the user can invoke the {@link #rotate} command.
|
||||
* @return {boolean} This returns true:
|
||||
* if the diagram is not {@link Diagram#isReadOnly},
|
||||
* if the model is not {@link Model#isReadOnly}, and
|
||||
* if there is at least one selected {@link Part}.
|
||||
*/
|
||||
DrawCommandHandler.prototype.canRotate = function () {
|
||||
var diagram = this.diagram;
|
||||
if (diagram.isReadOnly || diagram.isModelReadOnly)
|
||||
return false;
|
||||
if (diagram.selection.count < 1)
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
/**
|
||||
* Change the angle of the parts connected with the given part. This is in the command handler
|
||||
* so it can be easily accessed for the purpose of creating commands that change the rotation of a part.
|
||||
* @param {number} angle the positive (clockwise) or negative (counter-clockwise) change in the rotation angle of each Part, in degrees.
|
||||
*/
|
||||
DrawCommandHandler.prototype.rotate = function (angle) {
|
||||
if (angle === undefined)
|
||||
angle = 90;
|
||||
var diagram = this.diagram;
|
||||
diagram.startTransaction('rotate ' + angle.toString());
|
||||
diagram.selection.each(function (current) {
|
||||
if (current instanceof go.Link || current instanceof go.Group)
|
||||
return; // skips over Links and Groups
|
||||
current.angle += angle;
|
||||
});
|
||||
diagram.commitTransaction('rotate ' + angle.toString());
|
||||
};
|
||||
/**
|
||||
* Change the z-ordering of selected parts to pull them forward, in front of all other parts
|
||||
* in their respective layers.
|
||||
* All unselected parts in each layer with a selected Part with a non-numeric {@link Part#zOrder} will get a zOrder of zero.
|
||||
* @this {DrawCommandHandler}
|
||||
*/
|
||||
DrawCommandHandler.prototype.pullToFront = function () {
|
||||
var diagram = this.diagram;
|
||||
diagram.startTransaction("pullToFront");
|
||||
// find the affected Layers
|
||||
var layers = new go.Map();
|
||||
diagram.selection.each(function (part) {
|
||||
if (part.layer !== null)
|
||||
layers.set(part.layer, 0);
|
||||
});
|
||||
// find the maximum zOrder in each Layer
|
||||
layers.iteratorKeys.each(function (layer) {
|
||||
var max = 0;
|
||||
layer.parts.each(function (part) {
|
||||
if (part.isSelected)
|
||||
return;
|
||||
var z = part.zOrder;
|
||||
if (isNaN(z)) {
|
||||
part.zOrder = 0;
|
||||
}
|
||||
else {
|
||||
max = Math.max(max, z);
|
||||
}
|
||||
});
|
||||
layers.set(layer, max);
|
||||
});
|
||||
// assign each selected Part.zOrder to the computed value for each Layer
|
||||
diagram.selection.each(function (part) {
|
||||
var z = layers.get(part.layer) || 0;
|
||||
DrawCommandHandler._assignZOrder(part, z + 1);
|
||||
});
|
||||
diagram.commitTransaction("pullToFront");
|
||||
};
|
||||
/**
|
||||
* Change the z-ordering of selected parts to push them backward, behind of all other parts
|
||||
* in their respective layers.
|
||||
* All unselected parts in each layer with a selected Part with a non-numeric {@link Part#zOrder} will get a zOrder of zero.
|
||||
* @this {DrawCommandHandler}
|
||||
*/
|
||||
DrawCommandHandler.prototype.pushToBack = function () {
|
||||
var diagram = this.diagram;
|
||||
diagram.startTransaction("pushToBack");
|
||||
// find the affected Layers
|
||||
var layers = new go.Map();
|
||||
diagram.selection.each(function (part) {
|
||||
if (part.layer !== null)
|
||||
layers.set(part.layer, 0);
|
||||
});
|
||||
// find the minimum zOrder in each Layer
|
||||
layers.iteratorKeys.each(function (layer) {
|
||||
var min = 0;
|
||||
layer.parts.each(function (part) {
|
||||
if (part.isSelected)
|
||||
return;
|
||||
var z = part.zOrder;
|
||||
if (isNaN(z)) {
|
||||
part.zOrder = 0;
|
||||
}
|
||||
else {
|
||||
min = Math.min(min, z);
|
||||
}
|
||||
});
|
||||
layers.set(layer, min);
|
||||
});
|
||||
// assign each selected Part.zOrder to the computed value for each Layer
|
||||
diagram.selection.each(function (part) {
|
||||
var z = layers.get(part.layer) || 0;
|
||||
DrawCommandHandler._assignZOrder(part,
|
||||
// make sure a group's nested nodes are also behind everything else
|
||||
z - 1 - DrawCommandHandler._findGroupDepth(part));
|
||||
});
|
||||
diagram.commitTransaction("pushToBack");
|
||||
};
|
||||
DrawCommandHandler._assignZOrder = function (part, z, root) {
|
||||
if (root === undefined)
|
||||
root = part;
|
||||
if (part.layer === root.layer)
|
||||
part.zOrder = z;
|
||||
if (part instanceof go.Group) {
|
||||
part.memberParts.each(function (m) {
|
||||
DrawCommandHandler._assignZOrder(m, z + 1, root);
|
||||
});
|
||||
}
|
||||
};
|
||||
DrawCommandHandler._findGroupDepth = function (part) {
|
||||
if (part instanceof go.Group) {
|
||||
var d_1 = 0;
|
||||
part.memberParts.each(function (m) {
|
||||
d_1 = Math.max(d_1, DrawCommandHandler._findGroupDepth(m));
|
||||
});
|
||||
return d_1 + 1;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* This implements custom behaviors for arrow key keyboard events.
|
||||
* Set {@link #arrowKeyBehavior} to "select", "move" (the default), "scroll" (the standard behavior), or "none"
|
||||
* to affect the behavior when the user types an arrow key.
|
||||
*/
|
||||
DrawCommandHandler.prototype.doKeyDown = function () {
|
||||
var diagram = this.diagram;
|
||||
var e = diagram.lastInput;
|
||||
// determines the function of the arrow keys
|
||||
if (e.key === 'Up' || e.key === 'Down' || e.key === 'Left' || e.key === 'Right') {
|
||||
var behavior = this.arrowKeyBehavior;
|
||||
if (behavior === 'none') {
|
||||
// no-op
|
||||
return;
|
||||
}
|
||||
else if (behavior === 'select') {
|
||||
this._arrowKeySelect();
|
||||
return;
|
||||
}
|
||||
else if (behavior === 'move') {
|
||||
this._arrowKeyMove();
|
||||
return;
|
||||
}
|
||||
// otherwise drop through to get the default scrolling behavior
|
||||
}
|
||||
// otherwise still does all standard commands
|
||||
_super.prototype.doKeyDown.call(this);
|
||||
};
|
||||
/**
|
||||
* Collects in an Array all of the non-Link Parts currently in the Diagram.
|
||||
*/
|
||||
DrawCommandHandler.prototype._getAllParts = function () {
|
||||
var allParts = new Array();
|
||||
this.diagram.nodes.each(function (node) { allParts.push(node); });
|
||||
this.diagram.parts.each(function (part) { allParts.push(part); });
|
||||
// note that this ignores Links
|
||||
return allParts;
|
||||
};
|
||||
/**
|
||||
* To be called when arrow keys should move the Diagram.selection.
|
||||
*/
|
||||
DrawCommandHandler.prototype._arrowKeyMove = function () {
|
||||
var diagram = this.diagram;
|
||||
var e = diagram.lastInput;
|
||||
// moves all selected parts in the specified direction
|
||||
var vdistance = 0;
|
||||
var hdistance = 0;
|
||||
// if control is being held down, move pixel by pixel. Else, moves by grid cell size
|
||||
if (e.control || e.meta) {
|
||||
vdistance = 1;
|
||||
hdistance = 1;
|
||||
}
|
||||
else if (diagram.grid !== null) {
|
||||
var cellsize = diagram.grid.gridCellSize;
|
||||
hdistance = cellsize.width;
|
||||
vdistance = cellsize.height;
|
||||
}
|
||||
diagram.startTransaction('arrowKeyMove');
|
||||
diagram.selection.each(function (part) {
|
||||
if (e.key === 'Up') {
|
||||
part.move(new go.Point(part.actualBounds.x, part.actualBounds.y - vdistance));
|
||||
}
|
||||
else if (e.key === 'Down') {
|
||||
part.move(new go.Point(part.actualBounds.x, part.actualBounds.y + vdistance));
|
||||
}
|
||||
else if (e.key === 'Left') {
|
||||
part.move(new go.Point(part.actualBounds.x - hdistance, part.actualBounds.y));
|
||||
}
|
||||
else if (e.key === 'Right') {
|
||||
part.move(new go.Point(part.actualBounds.x + hdistance, part.actualBounds.y));
|
||||
}
|
||||
});
|
||||
diagram.commitTransaction('arrowKeyMove');
|
||||
};
|
||||
/**
|
||||
* To be called when arrow keys should change selection.
|
||||
*/
|
||||
DrawCommandHandler.prototype._arrowKeySelect = function () {
|
||||
var diagram = this.diagram;
|
||||
var e = diagram.lastInput;
|
||||
// with a part selected, arrow keys change the selection
|
||||
// arrow keys + shift selects the additional part in the specified direction
|
||||
// arrow keys + control toggles the selection of the additional part
|
||||
var nextPart = null;
|
||||
if (e.key === 'Up') {
|
||||
nextPart = this._findNearestPartTowards(270);
|
||||
}
|
||||
else if (e.key === 'Down') {
|
||||
nextPart = this._findNearestPartTowards(90);
|
||||
}
|
||||
else if (e.key === 'Left') {
|
||||
nextPart = this._findNearestPartTowards(180);
|
||||
}
|
||||
else if (e.key === 'Right') {
|
||||
nextPart = this._findNearestPartTowards(0);
|
||||
}
|
||||
if (nextPart !== null) {
|
||||
if (e.shift) {
|
||||
nextPart.isSelected = true;
|
||||
}
|
||||
else if (e.control || e.meta) {
|
||||
nextPart.isSelected = !nextPart.isSelected;
|
||||
}
|
||||
else {
|
||||
diagram.select(nextPart);
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Finds the nearest Part in the specified direction, based on their center points.
|
||||
* if it doesn't find anything, it just returns the current Part.
|
||||
* @param {number} dir the direction, in degrees
|
||||
* @return {Part} the closest Part found in the given direction
|
||||
*/
|
||||
DrawCommandHandler.prototype._findNearestPartTowards = function (dir) {
|
||||
var originalPart = this.diagram.selection.first();
|
||||
if (originalPart === null)
|
||||
return null;
|
||||
var originalPoint = originalPart.actualBounds.center;
|
||||
var allParts = this._getAllParts();
|
||||
var closestDistance = Infinity;
|
||||
var closest = originalPart; // if no parts meet the criteria, the same part remains selected
|
||||
for (var i = 0; i < allParts.length; i++) {
|
||||
var nextPart = allParts[i];
|
||||
if (nextPart === originalPart)
|
||||
continue; // skips over currently selected part
|
||||
var nextPoint = nextPart.actualBounds.center;
|
||||
var angle = originalPoint.directionPoint(nextPoint);
|
||||
var anglediff = this._angleCloseness(angle, dir);
|
||||
if (anglediff <= 45) { // if this part's center is within the desired direction's sector,
|
||||
var distance = originalPoint.distanceSquaredPoint(nextPoint);
|
||||
distance *= 1 + Math.sin(anglediff * Math.PI / 180); // the more different from the intended angle, the further it is
|
||||
if (distance < closestDistance) { // and if it's closer than any other part,
|
||||
closestDistance = distance; // remember it as a better choice
|
||||
closest = nextPart;
|
||||
}
|
||||
}
|
||||
}
|
||||
return closest;
|
||||
};
|
||||
DrawCommandHandler.prototype._angleCloseness = function (a, dir) {
|
||||
return Math.min(Math.abs(dir - a), Math.min(Math.abs(dir + 360 - a), Math.abs(dir - 360 - a)));
|
||||
};
|
||||
/**
|
||||
* Reset the last offset for pasting.
|
||||
* @param {Iterable.<Part>} coll a collection of {@link Part}s.
|
||||
*/
|
||||
DrawCommandHandler.prototype.copyToClipboard = function (coll) {
|
||||
_super.prototype.copyToClipboard.call(this, coll);
|
||||
this._lastPasteOffset.set(this.pasteOffset);
|
||||
};
|
||||
/**
|
||||
* Paste from the clipboard with an offset incremented on each paste, and reset when copied.
|
||||
* @return {Set.<Part>} a collection of newly pasted {@link Part}s
|
||||
*/
|
||||
DrawCommandHandler.prototype.pasteFromClipboard = function () {
|
||||
var coll = _super.prototype.pasteFromClipboard.call(this);
|
||||
this.diagram.moveParts(coll, this._lastPasteOffset, false);
|
||||
this._lastPasteOffset.add(this.pasteOffset);
|
||||
return coll;
|
||||
};
|
||||
return DrawCommandHandler;
|
||||
}(go.CommandHandler));
|
||||
exports.DrawCommandHandler = DrawCommandHandler;
|
||||
});
|
||||
+524
@@ -0,0 +1,524 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
|
||||
/**
|
||||
* This CommandHandler class allows the user to position selected Parts in a diagram
|
||||
* relative to the first part selected, in addition to overriding the doKeyDown method
|
||||
* of the CommandHandler for handling the arrow keys in additional manners.
|
||||
*
|
||||
* Typical usage:
|
||||
* ```js
|
||||
* $(go.Diagram, "myDiagramDiv",
|
||||
* {
|
||||
* commandHandler: $(DrawCommandHandler),
|
||||
* . . .
|
||||
* }
|
||||
* )
|
||||
* ```
|
||||
* or:
|
||||
* ```js
|
||||
* myDiagram.commandHandler = new DrawCommandHandler();
|
||||
* ```
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/DrawCommandHandler.html">Drawing Commands</a> sample.
|
||||
* @category Extension
|
||||
*/
|
||||
export class DrawCommandHandler extends go.CommandHandler {
|
||||
private _arrowKeyBehavior: string = 'move';
|
||||
private _pasteOffset: go.Point = new go.Point(10, 10);
|
||||
private _lastPasteOffset: go.Point = new go.Point(0, 0);
|
||||
|
||||
/**
|
||||
* Gets or sets the arrow key behavior. Possible values are "move", "select", and "scroll".
|
||||
*
|
||||
* The default value is "move".
|
||||
*/
|
||||
get arrowKeyBehavior(): string { return this._arrowKeyBehavior; }
|
||||
set arrowKeyBehavior(val: string) {
|
||||
if (val !== 'move' && val !== 'select' && val !== 'scroll' && val !== 'none') {
|
||||
throw new Error('DrawCommandHandler.arrowKeyBehavior must be either "move", "select", "scroll", or "none", not: ' + val);
|
||||
}
|
||||
this._arrowKeyBehavior = val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the offset at which each repeated {@link #pasteSelection} puts the new copied parts from the clipboard.
|
||||
*/
|
||||
get pasteOffset(): go.Point { return this._pasteOffset; }
|
||||
set pasteOffset(val: go.Point) {
|
||||
if (!(val instanceof go.Point)) throw new Error('DrawCommandHandler.pasteOffset must be a Point, not: ' + val);
|
||||
this._pasteOffset.set(val);
|
||||
}
|
||||
|
||||
/**
|
||||
* This controls whether or not the user can invoke the {@link #alignLeft}, {@link #alignRight},
|
||||
* {@link #alignTop}, {@link #alignBottom}, {@link #alignCenterX}, {@link #alignCenterY} commands.
|
||||
* @return {boolean} This returns true:
|
||||
* if the diagram is not {@link Diagram#isReadOnly},
|
||||
* if the model is not {@link Model#isReadOnly}, and
|
||||
* if there are at least two selected {@link Part}s.
|
||||
*/
|
||||
public canAlignSelection(): boolean {
|
||||
const diagram = this.diagram;
|
||||
if (diagram.isReadOnly || diagram.isModelReadOnly) return false;
|
||||
if (diagram.selection.count < 2) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aligns selected parts along the left-most edge of the left-most part.
|
||||
*/
|
||||
public alignLeft(): void {
|
||||
const diagram = this.diagram;
|
||||
diagram.startTransaction('aligning left');
|
||||
let minPosition = Infinity;
|
||||
diagram.selection.each((current) => {
|
||||
if (current instanceof go.Link) return; // skips over go.Link
|
||||
minPosition = Math.min(current.position.x, minPosition);
|
||||
});
|
||||
diagram.selection.each((current) => {
|
||||
if (current instanceof go.Link) return; // skips over go.Link
|
||||
current.move(new go.Point(minPosition, current.position.y));
|
||||
});
|
||||
diagram.commitTransaction('aligning left');
|
||||
}
|
||||
|
||||
/**
|
||||
* Aligns selected parts at the right-most edge of the right-most part.
|
||||
*/
|
||||
public alignRight(): void {
|
||||
const diagram = this.diagram;
|
||||
diagram.startTransaction('aligning right');
|
||||
let maxPosition = -Infinity;
|
||||
diagram.selection.each((current) => {
|
||||
if (current instanceof go.Link) return; // skips over go.Link
|
||||
const rightSideLoc = current.actualBounds.x + current.actualBounds.width;
|
||||
maxPosition = Math.max(rightSideLoc, maxPosition);
|
||||
});
|
||||
diagram.selection.each((current) => {
|
||||
if (current instanceof go.Link) return; // skips over go.Link
|
||||
current.move(new go.Point(maxPosition - current.actualBounds.width, current.position.y));
|
||||
});
|
||||
diagram.commitTransaction('aligning right');
|
||||
}
|
||||
|
||||
/**
|
||||
* Aligns selected parts at the top-most edge of the top-most part.
|
||||
*/
|
||||
public alignTop(): void {
|
||||
const diagram = this.diagram;
|
||||
diagram.startTransaction('alignTop');
|
||||
let minPosition = Infinity;
|
||||
diagram.selection.each((current) => {
|
||||
if (current instanceof go.Link) return; // skips over go.Link
|
||||
minPosition = Math.min(current.position.y, minPosition);
|
||||
});
|
||||
diagram.selection.each((current) => {
|
||||
if (current instanceof go.Link) return; // skips over go.Link
|
||||
current.move(new go.Point(current.position.x, minPosition));
|
||||
});
|
||||
diagram.commitTransaction('alignTop');
|
||||
}
|
||||
|
||||
/**
|
||||
* Aligns selected parts at the bottom-most edge of the bottom-most part.
|
||||
*/
|
||||
public alignBottom(): void {
|
||||
const diagram = this.diagram;
|
||||
diagram.startTransaction('aligning bottom');
|
||||
let maxPosition = -Infinity;
|
||||
diagram.selection.each((current) => {
|
||||
if (current instanceof go.Link) return; // skips over go.Link
|
||||
const bottomSideLoc = current.actualBounds.y + current.actualBounds.height;
|
||||
maxPosition = Math.max(bottomSideLoc, maxPosition);
|
||||
});
|
||||
diagram.selection.each((current) => {
|
||||
if (current instanceof go.Link) return; // skips over go.Link
|
||||
current.move(new go.Point(current.actualBounds.x, maxPosition - current.actualBounds.height));
|
||||
});
|
||||
diagram.commitTransaction('aligning bottom');
|
||||
}
|
||||
|
||||
/**
|
||||
* Aligns selected parts at the x-value of the center point of the first selected part.
|
||||
*/
|
||||
public alignCenterX(): void {
|
||||
const diagram = this.diagram;
|
||||
const firstSelection = diagram.selection.first();
|
||||
if (!firstSelection) return;
|
||||
diagram.startTransaction('aligning Center X');
|
||||
const centerX = firstSelection.actualBounds.x + firstSelection.actualBounds.width / 2;
|
||||
diagram.selection.each((current) => {
|
||||
if (current instanceof go.Link) return; // skips over go.Link
|
||||
current.move(new go.Point(centerX - current.actualBounds.width / 2, current.actualBounds.y));
|
||||
});
|
||||
diagram.commitTransaction('aligning Center X');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Aligns selected parts at the y-value of the center point of the first selected part.
|
||||
*/
|
||||
public alignCenterY(): void {
|
||||
const diagram = this.diagram;
|
||||
const firstSelection = diagram.selection.first();
|
||||
if (!firstSelection) return;
|
||||
diagram.startTransaction('aligning Center Y');
|
||||
const centerY = firstSelection.actualBounds.y + firstSelection.actualBounds.height / 2;
|
||||
diagram.selection.each((current) => {
|
||||
if (current instanceof go.Link) return; // skips over go.Link
|
||||
current.move(new go.Point(current.actualBounds.x, centerY - current.actualBounds.height / 2));
|
||||
});
|
||||
diagram.commitTransaction('aligning Center Y');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Aligns selected parts top-to-bottom in order of the order selected.
|
||||
* Distance between parts can be specified. Default distance is 0.
|
||||
*/
|
||||
public alignColumn(distance: number): void {
|
||||
const diagram = this.diagram;
|
||||
diagram.startTransaction('align Column');
|
||||
if (distance === undefined) distance = 0; // for aligning edge to edge
|
||||
distance = parseFloat(distance.toString());
|
||||
const selectedParts = new Array();
|
||||
diagram.selection.each((current) => {
|
||||
if (current instanceof go.Link) return; // skips over go.Link
|
||||
selectedParts.push(current);
|
||||
});
|
||||
for (let i = 0; i < selectedParts.length - 1; i++) {
|
||||
const current = selectedParts[i];
|
||||
// adds distance specified between parts
|
||||
const curBottomSideLoc = current.actualBounds.y + current.actualBounds.height + distance;
|
||||
const next = selectedParts[i + 1];
|
||||
next.move(new go.Point(current.actualBounds.x, curBottomSideLoc));
|
||||
}
|
||||
diagram.commitTransaction('align Column');
|
||||
}
|
||||
|
||||
/**
|
||||
* Aligns selected parts left-to-right in order of the order selected.
|
||||
* Distance between parts can be specified. Default distance is 0.
|
||||
*/
|
||||
public alignRow(distance: number): void {
|
||||
if (distance === undefined) distance = 0; // for aligning edge to edge
|
||||
distance = parseFloat(distance.toString());
|
||||
const diagram = this.diagram;
|
||||
diagram.startTransaction('align Row');
|
||||
const selectedParts = new Array();
|
||||
diagram.selection.each((current) => {
|
||||
if (current instanceof go.Link) return; // skips over go.Link
|
||||
selectedParts.push(current);
|
||||
});
|
||||
for (let i = 0; i < selectedParts.length - 1; i++) {
|
||||
const current = selectedParts[i];
|
||||
// adds distance specified between parts
|
||||
const curRightSideLoc = current.actualBounds.x + current.actualBounds.width + distance;
|
||||
const next = selectedParts[i + 1];
|
||||
next.move(new go.Point(curRightSideLoc, current.actualBounds.y));
|
||||
}
|
||||
diagram.commitTransaction('align Row');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This controls whether or not the user can invoke the {@link #rotate} command.
|
||||
* @return {boolean} This returns true:
|
||||
* if the diagram is not {@link Diagram#isReadOnly},
|
||||
* if the model is not {@link Model#isReadOnly}, and
|
||||
* if there is at least one selected {@link Part}.
|
||||
*/
|
||||
public canRotate(): boolean {
|
||||
const diagram = this.diagram;
|
||||
if (diagram.isReadOnly || diagram.isModelReadOnly) return false;
|
||||
if (diagram.selection.count < 1) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the angle of the parts connected with the given part. This is in the command handler
|
||||
* so it can be easily accessed for the purpose of creating commands that change the rotation of a part.
|
||||
* @param {number} angle the positive (clockwise) or negative (counter-clockwise) change in the rotation angle of each Part, in degrees.
|
||||
*/
|
||||
public rotate(angle: number): void {
|
||||
if (angle === undefined) angle = 90;
|
||||
const diagram = this.diagram;
|
||||
diagram.startTransaction('rotate ' + angle.toString());
|
||||
diagram.selection.each((current) => {
|
||||
if (current instanceof go.Link || current instanceof go.Group) return; // skips over Links and Groups
|
||||
current.angle += angle;
|
||||
});
|
||||
diagram.commitTransaction('rotate ' + angle.toString());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Change the z-ordering of selected parts to pull them forward, in front of all other parts
|
||||
* in their respective layers.
|
||||
* All unselected parts in each layer with a selected Part with a non-numeric {@link Part#zOrder} will get a zOrder of zero.
|
||||
* @this {DrawCommandHandler}
|
||||
*/
|
||||
public pullToFront(): void {
|
||||
const diagram = this.diagram;
|
||||
diagram.startTransaction("pullToFront");
|
||||
// find the affected Layers
|
||||
const layers = new go.Map<go.Layer, number>();
|
||||
diagram.selection.each(function(part) {
|
||||
if (part.layer !== null) layers.set(part.layer, 0);
|
||||
});
|
||||
// find the maximum zOrder in each Layer
|
||||
layers.iteratorKeys.each(function(layer) {
|
||||
let max = 0;
|
||||
layer.parts.each(function(part) {
|
||||
if (part.isSelected) return;
|
||||
const z = part.zOrder;
|
||||
if (isNaN(z)) {
|
||||
part.zOrder = 0;
|
||||
} else {
|
||||
max = Math.max(max, z);
|
||||
}
|
||||
});
|
||||
layers.set(layer, max);
|
||||
});
|
||||
// assign each selected Part.zOrder to the computed value for each Layer
|
||||
diagram.selection.each(function(part) {
|
||||
const z = layers.get(part.layer as go.Layer) || 0;
|
||||
DrawCommandHandler._assignZOrder(part, z + 1);
|
||||
});
|
||||
diagram.commitTransaction("pullToFront");
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the z-ordering of selected parts to push them backward, behind of all other parts
|
||||
* in their respective layers.
|
||||
* All unselected parts in each layer with a selected Part with a non-numeric {@link Part#zOrder} will get a zOrder of zero.
|
||||
* @this {DrawCommandHandler}
|
||||
*/
|
||||
public pushToBack(): void {
|
||||
const diagram = this.diagram;
|
||||
diagram.startTransaction("pushToBack");
|
||||
// find the affected Layers
|
||||
const layers = new go.Map<go.Layer, number>();
|
||||
diagram.selection.each(function(part) {
|
||||
if (part.layer !== null) layers.set(part.layer, 0);
|
||||
});
|
||||
// find the minimum zOrder in each Layer
|
||||
layers.iteratorKeys.each(function(layer) {
|
||||
let min = 0;
|
||||
layer.parts.each(function(part) {
|
||||
if (part.isSelected) return;
|
||||
const z = part.zOrder;
|
||||
if (isNaN(z)) {
|
||||
part.zOrder = 0;
|
||||
} else {
|
||||
min = Math.min(min, z);
|
||||
}
|
||||
});
|
||||
layers.set(layer, min);
|
||||
});
|
||||
// assign each selected Part.zOrder to the computed value for each Layer
|
||||
diagram.selection.each(function(part) {
|
||||
const z = layers.get(part.layer as go.Layer) || 0;
|
||||
DrawCommandHandler._assignZOrder(part,
|
||||
// make sure a group's nested nodes are also behind everything else
|
||||
z - 1 - DrawCommandHandler._findGroupDepth(part));
|
||||
});
|
||||
diagram.commitTransaction("pushToBack");
|
||||
}
|
||||
|
||||
private static _assignZOrder(part: go.Part, z: number, root?: go.Part): void {
|
||||
if (root === undefined) root = part;
|
||||
if (part.layer === root.layer) part.zOrder = z;
|
||||
if (part instanceof go.Group) {
|
||||
part.memberParts.each(function(m) {
|
||||
DrawCommandHandler._assignZOrder(m, z+1, root);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static _findGroupDepth(part: go.Part): number {
|
||||
if (part instanceof go.Group) {
|
||||
let d = 0;
|
||||
part.memberParts.each(function(m) {
|
||||
d = Math.max(d, DrawCommandHandler._findGroupDepth(m));
|
||||
});
|
||||
return d+1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This implements custom behaviors for arrow key keyboard events.
|
||||
* Set {@link #arrowKeyBehavior} to "select", "move" (the default), "scroll" (the standard behavior), or "none"
|
||||
* to affect the behavior when the user types an arrow key.
|
||||
*/
|
||||
public doKeyDown(): void {
|
||||
const diagram = this.diagram;
|
||||
const e = diagram.lastInput;
|
||||
|
||||
// determines the function of the arrow keys
|
||||
if (e.key === 'Up' || e.key === 'Down' || e.key === 'Left' || e.key === 'Right') {
|
||||
const behavior = this.arrowKeyBehavior;
|
||||
if (behavior === 'none') {
|
||||
// no-op
|
||||
return;
|
||||
} else if (behavior === 'select') {
|
||||
this._arrowKeySelect();
|
||||
return;
|
||||
} else if (behavior === 'move') {
|
||||
this._arrowKeyMove();
|
||||
return;
|
||||
}
|
||||
// otherwise drop through to get the default scrolling behavior
|
||||
}
|
||||
|
||||
// otherwise still does all standard commands
|
||||
super.doKeyDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects in an Array all of the non-Link Parts currently in the Diagram.
|
||||
*/
|
||||
private _getAllParts(): Array<any> {
|
||||
const allParts = new Array();
|
||||
this.diagram.nodes.each((node) => { allParts.push(node); });
|
||||
this.diagram.parts.each((part) => { allParts.push(part); });
|
||||
// note that this ignores Links
|
||||
return allParts;
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called when arrow keys should move the Diagram.selection.
|
||||
*/
|
||||
private _arrowKeyMove(): void {
|
||||
const diagram = this.diagram;
|
||||
const e = diagram.lastInput;
|
||||
// moves all selected parts in the specified direction
|
||||
let vdistance = 0;
|
||||
let hdistance = 0;
|
||||
// if control is being held down, move pixel by pixel. Else, moves by grid cell size
|
||||
if (e.control || e.meta) {
|
||||
vdistance = 1;
|
||||
hdistance = 1;
|
||||
} else if (diagram.grid !== null) {
|
||||
const cellsize = diagram.grid.gridCellSize;
|
||||
hdistance = cellsize.width;
|
||||
vdistance = cellsize.height;
|
||||
}
|
||||
diagram.startTransaction('arrowKeyMove');
|
||||
diagram.selection.each((part) => {
|
||||
if (e.key === 'Up') {
|
||||
part.move(new go.Point(part.actualBounds.x, part.actualBounds.y - vdistance));
|
||||
} else if (e.key === 'Down') {
|
||||
part.move(new go.Point(part.actualBounds.x, part.actualBounds.y + vdistance));
|
||||
} else if (e.key === 'Left') {
|
||||
part.move(new go.Point(part.actualBounds.x - hdistance, part.actualBounds.y));
|
||||
} else if (e.key === 'Right') {
|
||||
part.move(new go.Point(part.actualBounds.x + hdistance, part.actualBounds.y));
|
||||
}
|
||||
});
|
||||
diagram.commitTransaction('arrowKeyMove');
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called when arrow keys should change selection.
|
||||
*/
|
||||
private _arrowKeySelect(): void {
|
||||
const diagram = this.diagram;
|
||||
const e = diagram.lastInput;
|
||||
// with a part selected, arrow keys change the selection
|
||||
// arrow keys + shift selects the additional part in the specified direction
|
||||
// arrow keys + control toggles the selection of the additional part
|
||||
let nextPart = null;
|
||||
if (e.key === 'Up') {
|
||||
nextPart = this._findNearestPartTowards(270);
|
||||
} else if (e.key === 'Down') {
|
||||
nextPart = this._findNearestPartTowards(90);
|
||||
} else if (e.key === 'Left') {
|
||||
nextPart = this._findNearestPartTowards(180);
|
||||
} else if (e.key === 'Right') {
|
||||
nextPart = this._findNearestPartTowards(0);
|
||||
}
|
||||
if (nextPart !== null) {
|
||||
if (e.shift) {
|
||||
nextPart.isSelected = true;
|
||||
} else if (e.control || e.meta) {
|
||||
nextPart.isSelected = !nextPart.isSelected;
|
||||
} else {
|
||||
diagram.select(nextPart);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the nearest Part in the specified direction, based on their center points.
|
||||
* if it doesn't find anything, it just returns the current Part.
|
||||
* @param {number} dir the direction, in degrees
|
||||
* @return {Part} the closest Part found in the given direction
|
||||
*/
|
||||
private _findNearestPartTowards(dir: number): go.Part | null {
|
||||
const originalPart = this.diagram.selection.first();
|
||||
if (originalPart === null) return null;
|
||||
const originalPoint = originalPart.actualBounds.center;
|
||||
const allParts = this._getAllParts();
|
||||
let closestDistance = Infinity;
|
||||
let closest = originalPart; // if no parts meet the criteria, the same part remains selected
|
||||
|
||||
for (let i = 0; i < allParts.length; i++) {
|
||||
const nextPart = allParts[i];
|
||||
if (nextPart === originalPart) continue; // skips over currently selected part
|
||||
const nextPoint = nextPart.actualBounds.center;
|
||||
const angle = originalPoint.directionPoint(nextPoint);
|
||||
const anglediff = this._angleCloseness(angle, dir);
|
||||
if (anglediff <= 45) { // if this part's center is within the desired direction's sector,
|
||||
let distance = originalPoint.distanceSquaredPoint(nextPoint);
|
||||
distance *= 1 + Math.sin(anglediff * Math.PI / 180); // the more different from the intended angle, the further it is
|
||||
if (distance < closestDistance) { // and if it's closer than any other part,
|
||||
closestDistance = distance; // remember it as a better choice
|
||||
closest = nextPart;
|
||||
}
|
||||
}
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
private _angleCloseness(a: number, dir: number): number {
|
||||
return Math.min(Math.abs(dir - a), Math.min(Math.abs(dir + 360 - a), Math.abs(dir - 360 - a)));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reset the last offset for pasting.
|
||||
* @param {Iterable.<Part>} coll a collection of {@link Part}s.
|
||||
*/
|
||||
public copyToClipboard(coll: go.Iterable<go.Part>): void {
|
||||
super.copyToClipboard(coll);
|
||||
this._lastPasteOffset.set(this.pasteOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Paste from the clipboard with an offset incremented on each paste, and reset when copied.
|
||||
* @return {Set.<Part>} a collection of newly pasted {@link Part}s
|
||||
*/
|
||||
public pasteFromClipboard(): go.Set<go.Part> {
|
||||
const coll = super.pasteFromClipboard();
|
||||
this.diagram.moveParts(coll, this._lastPasteOffset, false);
|
||||
this._lastPasteOffset.add(this.pasteOffset);
|
||||
return coll;
|
||||
}
|
||||
}
|
||||
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js", "./DrawCommandHandler.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.back = exports.front = exports.rotate180 = exports.rotateNeg90 = exports.rotate90 = exports.rotateNeg45 = exports.rotate45 = exports.column = exports.row = exports.cenY = exports.cenX = exports.bottoms = exports.tops = exports.rights = exports.lefts = exports.arrowMode = exports.askSpace = exports.init = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
var DrawCommandHandler_js_1 = require("./DrawCommandHandler.js");
|
||||
var myDiagram;
|
||||
function init() {
|
||||
if (window.goSamples)
|
||||
window.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', // create a Diagram for the DIV HTML element
|
||||
{
|
||||
commandHandler: new DrawCommandHandler_js_1.DrawCommandHandler(),
|
||||
"commandHandler.archetypeGroupData": { isGroup: true },
|
||||
'undoManager.isEnabled': true // enable undo & redo
|
||||
});
|
||||
// define a simple Node template
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, 'Auto', // the Shape will go around the TextBlock
|
||||
{ locationSpot: go.Spot.Center }, $(go.Shape, 'RoundedRectangle', { strokeWidth: 0 },
|
||||
// Shape.fill is bound to Node.data.color
|
||||
new go.Binding('fill', 'color')), $(go.TextBlock, { margin: 8 }, // some room around the text
|
||||
// TextBlock.text is bound to Node.data.key
|
||||
new go.Binding('text', 'key')));
|
||||
// but use the default Link template, by not setting Diagram.linkTemplate
|
||||
// create the model data that will be represented by Nodes and Links
|
||||
myDiagram.model = new go.GraphLinksModel([
|
||||
{ key: 'Alpha', color: 'lightblue' },
|
||||
{ key: 'Beta', color: 'orange' },
|
||||
{ key: 'Gamma', color: 'lightgreen' },
|
||||
{ key: 'Delta', color: 'pink' }
|
||||
], [
|
||||
{ from: 'Alpha', to: 'Beta' },
|
||||
{ from: 'Alpha', to: 'Gamma' },
|
||||
{ from: 'Beta', to: 'Beta' },
|
||||
{ from: 'Gamma', to: 'Delta' },
|
||||
{ from: 'Delta', to: 'Alpha' }
|
||||
]);
|
||||
// Attach to the window for console manipulation
|
||||
window.myDiagram = myDiagram;
|
||||
}
|
||||
exports.init = init;
|
||||
function askSpace() {
|
||||
var space = parseInt(prompt('Desired space between nodes (in pixels):') || '0');
|
||||
return space;
|
||||
}
|
||||
exports.askSpace = askSpace;
|
||||
// update arrowkey function
|
||||
function arrowMode() {
|
||||
// no transaction needed, because we are modifying the CommandHandler for future use
|
||||
var move = document.getElementById('move');
|
||||
var select = document.getElementById('select');
|
||||
var scroll = document.getElementById('scroll');
|
||||
if (move.checked === true) {
|
||||
myDiagram.commandHandler.arrowKeyBehavior = 'move';
|
||||
}
|
||||
else if (select.checked === true) {
|
||||
myDiagram.commandHandler.arrowKeyBehavior = 'select';
|
||||
}
|
||||
else if (scroll.checked === true) {
|
||||
myDiagram.commandHandler.arrowKeyBehavior = 'scroll';
|
||||
}
|
||||
}
|
||||
exports.arrowMode = arrowMode;
|
||||
function lefts() { myDiagram.commandHandler.alignLeft(); }
|
||||
exports.lefts = lefts;
|
||||
function rights() { myDiagram.commandHandler.alignRight(); }
|
||||
exports.rights = rights;
|
||||
function tops() { myDiagram.commandHandler.alignTop(); }
|
||||
exports.tops = tops;
|
||||
function bottoms() { myDiagram.commandHandler.alignBottom(); }
|
||||
exports.bottoms = bottoms;
|
||||
function cenX() { myDiagram.commandHandler.alignCenterX(); }
|
||||
exports.cenX = cenX;
|
||||
function cenY() { myDiagram.commandHandler.alignCenterY(); }
|
||||
exports.cenY = cenY;
|
||||
function row() { myDiagram.commandHandler.alignRow(askSpace()); }
|
||||
exports.row = row;
|
||||
function column() { myDiagram.commandHandler.alignColumn(askSpace()); }
|
||||
exports.column = column;
|
||||
function rotate45() { myDiagram.commandHandler.rotate(45); }
|
||||
exports.rotate45 = rotate45;
|
||||
function rotateNeg45() { myDiagram.commandHandler.rotate(-45); }
|
||||
exports.rotateNeg45 = rotateNeg45;
|
||||
function rotate90() { myDiagram.commandHandler.rotate(90); }
|
||||
exports.rotate90 = rotate90;
|
||||
function rotateNeg90() { myDiagram.commandHandler.rotate(-90); }
|
||||
exports.rotateNeg90 = rotateNeg90;
|
||||
function rotate180() { myDiagram.commandHandler.rotate(180); }
|
||||
exports.rotate180 = rotate180;
|
||||
function front() { myDiagram.commandHandler.pullToFront(); }
|
||||
exports.front = front;
|
||||
function back() { myDiagram.commandHandler.pushToBack(); }
|
||||
exports.back = back;
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
import { DrawCommandHandler } from './DrawCommandHandler.js';
|
||||
|
||||
let myDiagram: go.Diagram;
|
||||
|
||||
export function init() {
|
||||
if ((window as any).goSamples) (window as any).goSamples(); // init for these samples -- you don't need to call this
|
||||
|
||||
const $ = go.GraphObject.make; // for conciseness in defining templates
|
||||
|
||||
myDiagram = $(go.Diagram, 'myDiagramDiv', // create a Diagram for the DIV HTML element
|
||||
{
|
||||
commandHandler: new DrawCommandHandler(), // defined in DrawCommandHandler.js
|
||||
"commandHandler.archetypeGroupData": { isGroup: true },
|
||||
'undoManager.isEnabled': true // enable undo & redo
|
||||
});
|
||||
|
||||
// define a simple Node template
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, 'Auto', // the Shape will go around the TextBlock
|
||||
{ locationSpot: go.Spot.Center },
|
||||
$(go.Shape, 'RoundedRectangle', { strokeWidth: 0 },
|
||||
// Shape.fill is bound to Node.data.color
|
||||
new go.Binding('fill', 'color')),
|
||||
$(go.TextBlock,
|
||||
{ margin: 8 }, // some room around the text
|
||||
// TextBlock.text is bound to Node.data.key
|
||||
new go.Binding('text', 'key'))
|
||||
);
|
||||
|
||||
// but use the default Link template, by not setting Diagram.linkTemplate
|
||||
|
||||
// create the model data that will be represented by Nodes and Links
|
||||
myDiagram.model = new go.GraphLinksModel(
|
||||
[
|
||||
{ key: 'Alpha', color: 'lightblue' },
|
||||
{ key: 'Beta', color: 'orange' },
|
||||
{ key: 'Gamma', color: 'lightgreen' },
|
||||
{ key: 'Delta', color: 'pink' }
|
||||
],
|
||||
[
|
||||
{ from: 'Alpha', to: 'Beta' },
|
||||
{ from: 'Alpha', to: 'Gamma' },
|
||||
{ from: 'Beta', to: 'Beta' },
|
||||
{ from: 'Gamma', to: 'Delta' },
|
||||
{ from: 'Delta', to: 'Alpha' }
|
||||
]);
|
||||
|
||||
// Attach to the window for console manipulation
|
||||
(window as any).myDiagram = myDiagram;
|
||||
}
|
||||
|
||||
export function askSpace() {
|
||||
const space: number = parseInt(prompt('Desired space between nodes (in pixels):') || '0');
|
||||
return space;
|
||||
}
|
||||
|
||||
// update arrowkey function
|
||||
export function arrowMode() {
|
||||
// no transaction needed, because we are modifying the CommandHandler for future use
|
||||
const move = (document.getElementById('move') as any);
|
||||
const select = (document.getElementById('select') as any);
|
||||
const scroll = (document.getElementById('scroll') as any);
|
||||
if (move.checked === true) {
|
||||
(myDiagram.commandHandler as DrawCommandHandler).arrowKeyBehavior = 'move';
|
||||
} else if (select.checked === true) {
|
||||
(myDiagram.commandHandler as DrawCommandHandler).arrowKeyBehavior = 'select';
|
||||
} else if (scroll.checked === true) {
|
||||
(myDiagram.commandHandler as DrawCommandHandler).arrowKeyBehavior = 'scroll';
|
||||
}
|
||||
}
|
||||
|
||||
export function lefts() { (myDiagram.commandHandler as DrawCommandHandler).alignLeft(); }
|
||||
export function rights() { (myDiagram.commandHandler as DrawCommandHandler).alignRight(); }
|
||||
export function tops() { (myDiagram.commandHandler as DrawCommandHandler).alignTop(); }
|
||||
export function bottoms() { (myDiagram.commandHandler as DrawCommandHandler).alignBottom(); }
|
||||
export function cenX() { (myDiagram.commandHandler as DrawCommandHandler).alignCenterX(); }
|
||||
export function cenY() { (myDiagram.commandHandler as DrawCommandHandler).alignCenterY(); }
|
||||
export function row() { (myDiagram.commandHandler as DrawCommandHandler).alignRow(askSpace()); }
|
||||
export function column() { (myDiagram.commandHandler as DrawCommandHandler).alignColumn(askSpace()); }
|
||||
export function rotate45() { (myDiagram.commandHandler as DrawCommandHandler).rotate(45); }
|
||||
export function rotateNeg45() { (myDiagram.commandHandler as DrawCommandHandler).rotate(-45); }
|
||||
export function rotate90() { (myDiagram.commandHandler as DrawCommandHandler).rotate(90); }
|
||||
export function rotateNeg90() { (myDiagram.commandHandler as DrawCommandHandler).rotate(-90); }
|
||||
export function rotate180() { (myDiagram.commandHandler as DrawCommandHandler).rotate(180); }
|
||||
export function front() { (myDiagram.commandHandler as DrawCommandHandler).pullToFront(); }
|
||||
export function back() { (myDiagram.commandHandler as DrawCommandHandler).pushToBack(); }
|
||||
Executable
+5095
File diff suppressed because it is too large
Load Diff
Executable
+5919
File diff suppressed because it is too large
Load Diff
+41
@@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Fishbone Layout</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="TypeScript: Cause-and-effect diagrams using FishboneLayout, also known as Ishikawa or herringbone diagrams." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../samples/assets/require.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
function init() {
|
||||
require(["FishboneScript"], function(app) {
|
||||
app.init();
|
||||
document.getElementById("fishbone").onclick = app.layoutFishbone;
|
||||
document.getElementById("branching").onclick = app.layoutBranching;
|
||||
document.getElementById("normal").onclick = app.layoutNormal;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="height:550px;width:100%;border:1px solid black;"></div>
|
||||
<div id="buttons">
|
||||
<label>Layout:</label>
|
||||
<button id="fishbone">Fishbone</button>
|
||||
<button id="branching">Branching</button>
|
||||
<button id="normal">Normal</button>
|
||||
</div>
|
||||
<p>
|
||||
This sample shows a "fishbone" layout of a tree model of cause-and-effect relationships. This type of layout is often seen
|
||||
in root cause analysis, or RCA. The layout is defined in its own file, as <a href="FishboneLayout.ts">FishboneLayout.ts</a>.
|
||||
When using FishboneLayout the diagram uses FishboneLink in order to get custom routing for the links.
|
||||
</p>
|
||||
<p>
|
||||
The buttons each set the <a>Diagram.layout</a> within a transaction.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
+345
@@ -0,0 +1,345 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.FishboneLink = exports.FishboneLayout = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
/**
|
||||
* FishboneLayout is a custom {@link Layout} derived from {@link TreeLayout} for creating "fishbone" diagrams.
|
||||
* A fishbone diagram also requires a {@link Link} class that implements custom routing, {@link FishboneLink}.
|
||||
*
|
||||
* This only works for angle === 0 or angle === 180.
|
||||
*
|
||||
* This layout assumes Links are automatically routed in the way needed by fishbone diagrams,
|
||||
* by using the FishboneLink class instead of go.Link.
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/Fishbone.html">Fishbone Layout</a> sample.
|
||||
* @category Layout Extension
|
||||
*/
|
||||
var FishboneLayout = /** @class */ (function (_super) {
|
||||
__extends(FishboneLayout, _super);
|
||||
/**
|
||||
* Constructs a FishboneLayout and sets the following properties:
|
||||
* - {@link #alignment} = {@link TreeLayout.AlignmentBusBranching}
|
||||
* - {@link #setsPortSpot} = false
|
||||
* - {@link #setsChildPortSpot} = false
|
||||
*/
|
||||
function FishboneLayout() {
|
||||
var _this = _super.call(this) || this;
|
||||
_this.alignment = go.TreeLayout.AlignmentBusBranching;
|
||||
_this.setsPortSpot = false;
|
||||
_this.setsChildPortSpot = false;
|
||||
return _this;
|
||||
}
|
||||
/**
|
||||
* Create and initialize a {@link LayoutNetwork} with the given nodes and links.
|
||||
* This override creates dummy vertexes, when necessary, to allow for proper positioning within the fishbone.
|
||||
* @param {Diagram|Group|Iterable.<Part>} coll A {@link Diagram} or a {@link Group} or a collection of {@link Part}s.
|
||||
* @return {LayoutNetwork}
|
||||
*/
|
||||
FishboneLayout.prototype.makeNetwork = function (coll) {
|
||||
// assert(this.angle === 0 || this.angle === 180);
|
||||
// assert(this.alignment === go.TreeLayout.AlignmentBusBranching);
|
||||
// assert(this.path !== go.TreeLayout.PathSource);
|
||||
// call base method for standard behavior
|
||||
var net = _super.prototype.makeNetwork.call(this, coll);
|
||||
// make a copy of the collection of TreeVertexes
|
||||
// because we will be modifying the TreeNetwork.vertexes collection in the loop
|
||||
var verts = new go.List().addAll(net.vertexes.iterator);
|
||||
verts.each(function (v) {
|
||||
// ignore leaves of tree
|
||||
if (v.destinationEdges.count === 0)
|
||||
return;
|
||||
if (v.destinationEdges.count % 2 === 1) {
|
||||
// if there's an odd number of real children, add two dummies
|
||||
var dummy = net.createVertex();
|
||||
dummy.bounds = new go.Rect();
|
||||
dummy.focus = new go.Point();
|
||||
net.addVertex(dummy);
|
||||
net.linkVertexes(v, dummy, null);
|
||||
}
|
||||
// make sure there's an odd number of children, including at least one dummy;
|
||||
// commitNodes will move the parent node to where this dummy child node is placed
|
||||
var dummy2 = net.createVertex();
|
||||
dummy2.bounds = v.bounds;
|
||||
dummy2.focus = v.focus;
|
||||
net.addVertex(dummy2);
|
||||
net.linkVertexes(v, dummy2, null);
|
||||
});
|
||||
return net;
|
||||
};
|
||||
/**
|
||||
* Add a direction property to each vertex and modify {@link TreeVertex#layerSpacing}.
|
||||
*/
|
||||
FishboneLayout.prototype.assignTreeVertexValues = function (v) {
|
||||
_super.prototype.assignTreeVertexValues.call(this, v);
|
||||
v['_direction'] = 0; // add this property to each TreeVertex
|
||||
if (v.parent !== null) {
|
||||
// The parent node will be moved to where the last dummy will be;
|
||||
// reduce the space to account for the future hole.
|
||||
if (v.angle === 0 || v.angle === 180) {
|
||||
v.layerSpacing -= v.bounds.width;
|
||||
}
|
||||
else {
|
||||
v.layerSpacing -= v.bounds.height;
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Assigns {@link Link#fromSpot}s and {@link Link#toSpot}s based on branching and angle
|
||||
* and moves vertexes based on dummy locations.
|
||||
*/
|
||||
FishboneLayout.prototype.commitNodes = function () {
|
||||
if (this.network === null)
|
||||
return;
|
||||
// vertex Angle is set by BusBranching "inheritance";
|
||||
// assign spots assuming overall Angle === 0 or 180
|
||||
// and links are always connecting horizontal with vertical
|
||||
this.network.edges.each(function (e) {
|
||||
var link = e.link;
|
||||
if (link === null)
|
||||
return;
|
||||
link.fromSpot = go.Spot.None;
|
||||
link.toSpot = go.Spot.None;
|
||||
var v = e.fromVertex;
|
||||
var w = e.toVertex;
|
||||
if (v.angle === 0) {
|
||||
link.fromSpot = go.Spot.Left;
|
||||
}
|
||||
else if (v.angle === 180) {
|
||||
link.fromSpot = go.Spot.Right;
|
||||
}
|
||||
if (w.angle === 0) {
|
||||
link.toSpot = go.Spot.Left;
|
||||
}
|
||||
else if (w.angle === 180) {
|
||||
link.toSpot = go.Spot.Right;
|
||||
}
|
||||
});
|
||||
// move the parent node to the location of the last dummy
|
||||
var vit = this.network.vertexes.iterator;
|
||||
while (vit.next()) {
|
||||
var v = vit.value;
|
||||
var len = v.children.length;
|
||||
if (len === 0)
|
||||
continue; // ignore leaf nodes
|
||||
if (v.parent === null)
|
||||
continue; // don't move root node
|
||||
var dummy2 = v.children[len - 1];
|
||||
v.centerX = dummy2.centerX;
|
||||
v.centerY = dummy2.centerY;
|
||||
}
|
||||
var layout = this;
|
||||
vit = this.network.vertexes.iterator;
|
||||
while (vit.next()) {
|
||||
var v = vit.value;
|
||||
if (v.parent === null) {
|
||||
layout.shift(v);
|
||||
}
|
||||
}
|
||||
// now actually change the Node.location of all nodes
|
||||
_super.prototype.commitNodes.call(this);
|
||||
};
|
||||
/**
|
||||
* This override stops links from being committed since the work is done by the {@link FishboneLink} class.
|
||||
*/
|
||||
FishboneLayout.prototype.commitLinks = function () { };
|
||||
/**
|
||||
* Shifts subtrees within the fishbone based on angle and node spacing.
|
||||
*/
|
||||
FishboneLayout.prototype.shift = function (v) {
|
||||
var p = v.parent;
|
||||
if (p !== null && (v.angle === 90 || v.angle === 270)) {
|
||||
var g = p.parent;
|
||||
if (g !== null) {
|
||||
var shift = v.nodeSpacing;
|
||||
if (g['_direction'] > 0) {
|
||||
if (g.angle === 90) {
|
||||
if (p.angle === 0) {
|
||||
v['_direction'] = 1;
|
||||
if (v.angle === 270)
|
||||
this.shiftAll(2, -shift, p, v);
|
||||
}
|
||||
else if (p.angle === 180) {
|
||||
v['_direction'] = -1;
|
||||
if (v.angle === 90)
|
||||
this.shiftAll(-2, shift, p, v);
|
||||
}
|
||||
}
|
||||
else if (g.angle === 270) {
|
||||
if (p.angle === 0) {
|
||||
v['_direction'] = 1;
|
||||
if (v.angle === 90)
|
||||
this.shiftAll(2, -shift, p, v);
|
||||
}
|
||||
else if (p.angle === 180) {
|
||||
v['_direction'] = -1;
|
||||
if (v.angle === 270)
|
||||
this.shiftAll(-2, shift, p, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (g['_direction'] < 0) {
|
||||
if (g.angle === 90) {
|
||||
if (p.angle === 0) {
|
||||
v['_direction'] = 1;
|
||||
if (v.angle === 90)
|
||||
this.shiftAll(2, -shift, p, v);
|
||||
}
|
||||
else if (p.angle === 180) {
|
||||
v['_direction'] = -1;
|
||||
if (v.angle === 270)
|
||||
this.shiftAll(-2, shift, p, v);
|
||||
}
|
||||
}
|
||||
else if (g.angle === 270) {
|
||||
if (p.angle === 0) {
|
||||
v['_direction'] = 1;
|
||||
if (v.angle === 270)
|
||||
this.shiftAll(2, -shift, p, v);
|
||||
}
|
||||
else if (p.angle === 180) {
|
||||
v['_direction'] = -1;
|
||||
if (v.angle === 90)
|
||||
this.shiftAll(-2, shift, p, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // g === null: V is a child of the tree ROOT
|
||||
var dir = ((p.angle === 0) ? 1 : -1);
|
||||
v['_direction'] = dir;
|
||||
this.shiftAll(dir, 0, p, v);
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < v.children.length; i++) {
|
||||
var c = v.children[i];
|
||||
this.shift(c);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Shifts a subtree.
|
||||
*/
|
||||
FishboneLayout.prototype.shiftAll = function (direction, absolute, root, v) {
|
||||
// assert(root.angle === 0 || root.angle === 180);
|
||||
var locx = v.centerX;
|
||||
locx += direction * Math.abs(root.centerY - v.centerY) / 2;
|
||||
locx += absolute;
|
||||
v.centerX = locx;
|
||||
for (var i = 0; i < v.children.length; i++) {
|
||||
var c = v.children[i];
|
||||
this.shiftAll(direction, absolute, root, c);
|
||||
}
|
||||
};
|
||||
return FishboneLayout;
|
||||
}(go.TreeLayout));
|
||||
exports.FishboneLayout = FishboneLayout;
|
||||
/**
|
||||
* Custom {@link Link} class for {@link FishboneLayout}.
|
||||
* @category Part Extension
|
||||
*/
|
||||
var FishboneLink = /** @class */ (function (_super) {
|
||||
__extends(FishboneLink, _super);
|
||||
function FishboneLink() {
|
||||
return _super !== null && _super.apply(this, arguments) || this;
|
||||
}
|
||||
FishboneLink.prototype.computeAdjusting = function () { return this.adjusting; };
|
||||
/**
|
||||
* Determines the points for this link based on spots and maintains horizontal lines.
|
||||
*/
|
||||
FishboneLink.prototype.computePoints = function () {
|
||||
var result = _super.prototype.computePoints.call(this);
|
||||
if (result) {
|
||||
// insert middle point to maintain horizontal lines
|
||||
if (this.fromSpot.equals(go.Spot.Right) || this.fromSpot.equals(go.Spot.Left)) {
|
||||
var p1 = void 0;
|
||||
// deal with root node being on the "wrong" side
|
||||
var fromnode = this.fromNode;
|
||||
var fromport = this.fromPort;
|
||||
if (fromnode !== null && fromport !== null && fromnode.findLinksInto().count === 0) {
|
||||
// pretend the link is coming from the opposite direction than the declared FromSpot
|
||||
var fromctr = fromport.getDocumentPoint(go.Spot.Center);
|
||||
var fromfar = fromctr.copy();
|
||||
fromfar.x += (this.fromSpot.equals(go.Spot.Left) ? 99999 : -99999);
|
||||
p1 = this.getLinkPointFromPoint(fromnode, fromport, fromctr, fromfar, true).copy();
|
||||
// update the route points
|
||||
this.setPoint(0, p1);
|
||||
var endseg = this.fromEndSegmentLength;
|
||||
if (isNaN(endseg))
|
||||
endseg = fromport.fromEndSegmentLength;
|
||||
p1.x += (this.fromSpot.equals(go.Spot.Left)) ? endseg : -endseg;
|
||||
this.setPoint(1, p1);
|
||||
}
|
||||
else {
|
||||
p1 = this.getPoint(1); // points 0 & 1 should be OK already
|
||||
}
|
||||
var tonode = this.toNode;
|
||||
var toport = this.toPort;
|
||||
if (tonode !== null && toport !== null) {
|
||||
var toctr = toport.getDocumentPoint(go.Spot.Center);
|
||||
var far = toctr.copy();
|
||||
far.x += (this.fromSpot.equals(go.Spot.Left)) ? -99999 / 2 : 99999 / 2;
|
||||
far.y += (toctr.y < p1.y) ? 99999 : -99999;
|
||||
var p2 = this.getLinkPointFromPoint(tonode, toport, toctr, far, false);
|
||||
this.setPoint(2, p2);
|
||||
var dx = Math.abs(p2.y - p1.y) / 2;
|
||||
if (this.fromSpot.equals(go.Spot.Left))
|
||||
dx = -dx;
|
||||
this.insertPoint(2, new go.Point(p2.x + dx, p1.y));
|
||||
}
|
||||
}
|
||||
else if (this.toSpot.equals(go.Spot.Right) || this.toSpot.equals(go.Spot.Left)) {
|
||||
var p1 = this.getPoint(1); // points 1 & 2 should be OK already
|
||||
var fromnode = this.fromNode;
|
||||
var fromport = this.fromPort;
|
||||
if (fromnode !== null && fromport !== null) {
|
||||
var parentlink = fromnode.findLinksInto().first();
|
||||
var fromctr = fromport.getDocumentPoint(go.Spot.Center);
|
||||
var far = fromctr.copy();
|
||||
far.x += (parentlink !== null && parentlink.fromSpot.equals(go.Spot.Left)) ? -99999 / 2 : 99999 / 2;
|
||||
far.y += (fromctr.y < p1.y) ? 99999 : -99999;
|
||||
var p0 = this.getLinkPointFromPoint(fromnode, fromport, fromctr, far, true);
|
||||
this.setPoint(0, p0);
|
||||
var dx = Math.abs(p1.y - p0.y) / 2;
|
||||
if (parentlink !== null && parentlink.fromSpot.equals(go.Spot.Left))
|
||||
dx = -dx;
|
||||
this.insertPoint(1, new go.Point(p0.x + dx, p1.y));
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
return FishboneLink;
|
||||
}(go.Link));
|
||||
exports.FishboneLink = FishboneLink;
|
||||
});
|
||||
+298
@@ -0,0 +1,298 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
|
||||
/**
|
||||
* FishboneLayout is a custom {@link Layout} derived from {@link TreeLayout} for creating "fishbone" diagrams.
|
||||
* A fishbone diagram also requires a {@link Link} class that implements custom routing, {@link FishboneLink}.
|
||||
*
|
||||
* This only works for angle === 0 or angle === 180.
|
||||
*
|
||||
* This layout assumes Links are automatically routed in the way needed by fishbone diagrams,
|
||||
* by using the FishboneLink class instead of go.Link.
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/Fishbone.html">Fishbone Layout</a> sample.
|
||||
* @category Layout Extension
|
||||
*/
|
||||
export class FishboneLayout extends go.TreeLayout {
|
||||
/**
|
||||
* Constructs a FishboneLayout and sets the following properties:
|
||||
* - {@link #alignment} = {@link TreeLayout.AlignmentBusBranching}
|
||||
* - {@link #setsPortSpot} = false
|
||||
* - {@link #setsChildPortSpot} = false
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
this.alignment = go.TreeLayout.AlignmentBusBranching;
|
||||
this.setsPortSpot = false;
|
||||
this.setsChildPortSpot = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and initialize a {@link LayoutNetwork} with the given nodes and links.
|
||||
* This override creates dummy vertexes, when necessary, to allow for proper positioning within the fishbone.
|
||||
* @param {Diagram|Group|Iterable.<Part>} coll A {@link Diagram} or a {@link Group} or a collection of {@link Part}s.
|
||||
* @return {LayoutNetwork}
|
||||
*/
|
||||
public makeNetwork(coll: go.Diagram | go.Group | go.Iterable<go.Part>): go.LayoutNetwork {
|
||||
// assert(this.angle === 0 || this.angle === 180);
|
||||
// assert(this.alignment === go.TreeLayout.AlignmentBusBranching);
|
||||
// assert(this.path !== go.TreeLayout.PathSource);
|
||||
|
||||
// call base method for standard behavior
|
||||
const net = super.makeNetwork(coll);
|
||||
// make a copy of the collection of TreeVertexes
|
||||
// because we will be modifying the TreeNetwork.vertexes collection in the loop
|
||||
const verts = new go.List<go.TreeVertex>().addAll(net.vertexes.iterator as go.Iterator<go.TreeVertex>);
|
||||
verts.each(function(v: go.TreeVertex) {
|
||||
// ignore leaves of tree
|
||||
if (v.destinationEdges.count === 0) return;
|
||||
if (v.destinationEdges.count % 2 === 1) {
|
||||
// if there's an odd number of real children, add two dummies
|
||||
const dummy = net.createVertex();
|
||||
dummy.bounds = new go.Rect();
|
||||
dummy.focus = new go.Point();
|
||||
net.addVertex(dummy);
|
||||
net.linkVertexes(v, dummy, null);
|
||||
}
|
||||
// make sure there's an odd number of children, including at least one dummy;
|
||||
// commitNodes will move the parent node to where this dummy child node is placed
|
||||
const dummy2 = net.createVertex();
|
||||
dummy2.bounds = v.bounds;
|
||||
dummy2.focus = v.focus;
|
||||
net.addVertex(dummy2);
|
||||
net.linkVertexes(v, dummy2, null);
|
||||
});
|
||||
return net;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a direction property to each vertex and modify {@link TreeVertex#layerSpacing}.
|
||||
*/
|
||||
public assignTreeVertexValues(v: go.TreeVertex): void {
|
||||
super.assignTreeVertexValues(v);
|
||||
(v as any)['_direction'] = 0; // add this property to each TreeVertex
|
||||
if (v.parent !== null) {
|
||||
// The parent node will be moved to where the last dummy will be;
|
||||
// reduce the space to account for the future hole.
|
||||
if (v.angle === 0 || v.angle === 180) {
|
||||
v.layerSpacing -= v.bounds.width;
|
||||
} else {
|
||||
v.layerSpacing -= v.bounds.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns {@link Link#fromSpot}s and {@link Link#toSpot}s based on branching and angle
|
||||
* and moves vertexes based on dummy locations.
|
||||
*/
|
||||
public commitNodes(): void {
|
||||
if (this.network === null) return;
|
||||
// vertex Angle is set by BusBranching "inheritance";
|
||||
// assign spots assuming overall Angle === 0 or 180
|
||||
// and links are always connecting horizontal with vertical
|
||||
this.network.edges.each(function(e) {
|
||||
const link = e.link;
|
||||
if (link === null) return;
|
||||
link.fromSpot = go.Spot.None;
|
||||
link.toSpot = go.Spot.None;
|
||||
|
||||
const v: go.TreeVertex = e.fromVertex as go.TreeVertex;
|
||||
const w: go.TreeVertex = e.toVertex as go.TreeVertex;
|
||||
|
||||
if (v.angle === 0) {
|
||||
link.fromSpot = go.Spot.Left;
|
||||
} else if (v.angle === 180) {
|
||||
link.fromSpot = go.Spot.Right;
|
||||
}
|
||||
|
||||
if (w.angle === 0) {
|
||||
link.toSpot = go.Spot.Left;
|
||||
} else if (w.angle === 180) {
|
||||
link.toSpot = go.Spot.Right;
|
||||
}
|
||||
});
|
||||
|
||||
// move the parent node to the location of the last dummy
|
||||
let vit = this.network.vertexes.iterator;
|
||||
while (vit.next()) {
|
||||
const v = vit.value as go.TreeVertex;
|
||||
const len = v.children.length;
|
||||
if (len === 0) continue; // ignore leaf nodes
|
||||
if (v.parent === null) continue; // don't move root node
|
||||
const dummy2 = v.children[len - 1];
|
||||
v.centerX = dummy2.centerX;
|
||||
v.centerY = dummy2.centerY;
|
||||
}
|
||||
|
||||
const layout = this;
|
||||
vit = this.network.vertexes.iterator;
|
||||
while (vit.next()) {
|
||||
const v = vit.value as go.TreeVertex;
|
||||
if (v.parent === null) {
|
||||
layout.shift(v);
|
||||
}
|
||||
}
|
||||
|
||||
// now actually change the Node.location of all nodes
|
||||
super.commitNodes();
|
||||
}
|
||||
|
||||
/**
|
||||
* This override stops links from being committed since the work is done by the {@link FishboneLink} class.
|
||||
*/
|
||||
public commitLinks(): void { }
|
||||
|
||||
/**
|
||||
* Shifts subtrees within the fishbone based on angle and node spacing.
|
||||
*/
|
||||
public shift(v: go.TreeVertex): void {
|
||||
const p = v.parent;
|
||||
if (p !== null && (v.angle === 90 || v.angle === 270)) {
|
||||
const g = p.parent;
|
||||
if (g !== null) {
|
||||
const shift = v.nodeSpacing;
|
||||
if ((g as any)['_direction'] > 0) {
|
||||
if (g.angle === 90) {
|
||||
if (p.angle === 0) {
|
||||
(v as any)['_direction'] = 1;
|
||||
if (v.angle === 270) this.shiftAll(2, -shift, p, v);
|
||||
} else if (p.angle === 180) {
|
||||
(v as any)['_direction'] = -1;
|
||||
if (v.angle === 90) this.shiftAll(-2, shift, p, v);
|
||||
}
|
||||
} else if (g.angle === 270) {
|
||||
if (p.angle === 0) {
|
||||
(v as any)['_direction'] = 1;
|
||||
if (v.angle === 90) this.shiftAll(2, -shift, p, v);
|
||||
} else if (p.angle === 180) {
|
||||
(v as any)['_direction'] = -1;
|
||||
if (v.angle === 270) this.shiftAll(-2, shift, p, v);
|
||||
}
|
||||
}
|
||||
} else if ((g as any)['_direction'] < 0) {
|
||||
if (g.angle === 90) {
|
||||
if (p.angle === 0) {
|
||||
(v as any)['_direction'] = 1;
|
||||
if (v.angle === 90) this.shiftAll(2, -shift, p, v);
|
||||
} else if (p.angle === 180) {
|
||||
(v as any)['_direction'] = -1;
|
||||
if (v.angle === 270) this.shiftAll(-2, shift, p, v);
|
||||
}
|
||||
} else if (g.angle === 270) {
|
||||
if (p.angle === 0) {
|
||||
(v as any)['_direction'] = 1;
|
||||
if (v.angle === 270) this.shiftAll(2, -shift, p, v);
|
||||
} else if (p.angle === 180) {
|
||||
(v as any)['_direction'] = -1;
|
||||
if (v.angle === 90) this.shiftAll(-2, shift, p, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // g === null: V is a child of the tree ROOT
|
||||
const dir = ((p.angle === 0) ? 1 : -1);
|
||||
(v as any)['_direction'] = dir;
|
||||
this.shiftAll(dir, 0, p, v);
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < v.children.length; i++) {
|
||||
const c = v.children[i];
|
||||
this.shift(c);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shifts a subtree.
|
||||
*/
|
||||
public shiftAll(direction: number, absolute: number, root: go.TreeVertex, v: go.TreeVertex): void {
|
||||
// assert(root.angle === 0 || root.angle === 180);
|
||||
let locx = v.centerX;
|
||||
locx += direction * Math.abs(root.centerY - v.centerY) / 2;
|
||||
locx += absolute;
|
||||
v.centerX = locx;
|
||||
for (let i = 0; i < v.children.length; i++) {
|
||||
const c = v.children[i];
|
||||
this.shiftAll(direction, absolute, root, c);
|
||||
}
|
||||
}
|
||||
// end FishboneLayout
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom {@link Link} class for {@link FishboneLayout}.
|
||||
* @category Part Extension
|
||||
*/
|
||||
export class FishboneLink extends go.Link {
|
||||
public computeAdjusting(): go.EnumValue { return this.adjusting; }
|
||||
/**
|
||||
* Determines the points for this link based on spots and maintains horizontal lines.
|
||||
*/
|
||||
public computePoints(): boolean {
|
||||
const result = super.computePoints();
|
||||
if (result) {
|
||||
// insert middle point to maintain horizontal lines
|
||||
if (this.fromSpot.equals(go.Spot.Right) || this.fromSpot.equals(go.Spot.Left)) {
|
||||
let p1: go.Point;
|
||||
// deal with root node being on the "wrong" side
|
||||
const fromnode = this.fromNode;
|
||||
const fromport = this.fromPort;
|
||||
if (fromnode !== null && fromport !== null && fromnode.findLinksInto().count === 0) {
|
||||
// pretend the link is coming from the opposite direction than the declared FromSpot
|
||||
const fromctr = fromport.getDocumentPoint(go.Spot.Center);
|
||||
const fromfar = fromctr.copy();
|
||||
fromfar.x += (this.fromSpot.equals(go.Spot.Left) ? 99999 : -99999);
|
||||
p1 = this.getLinkPointFromPoint(fromnode, fromport, fromctr, fromfar, true).copy();
|
||||
// update the route points
|
||||
this.setPoint(0, p1);
|
||||
let endseg = this.fromEndSegmentLength;
|
||||
if (isNaN(endseg)) endseg = fromport.fromEndSegmentLength;
|
||||
p1.x += (this.fromSpot.equals(go.Spot.Left)) ? endseg : -endseg;
|
||||
this.setPoint(1, p1);
|
||||
} else {
|
||||
p1 = this.getPoint(1); // points 0 & 1 should be OK already
|
||||
}
|
||||
const tonode = this.toNode;
|
||||
const toport = this.toPort;
|
||||
if (tonode !== null && toport !== null) {
|
||||
const toctr = toport.getDocumentPoint(go.Spot.Center);
|
||||
const far = toctr.copy();
|
||||
far.x += (this.fromSpot.equals(go.Spot.Left)) ? -99999 / 2 : 99999 / 2;
|
||||
far.y += (toctr.y < p1.y) ? 99999 : -99999;
|
||||
const p2 = this.getLinkPointFromPoint(tonode, toport, toctr, far, false);
|
||||
this.setPoint(2, p2);
|
||||
let dx = Math.abs(p2.y - p1.y) / 2;
|
||||
if (this.fromSpot.equals(go.Spot.Left)) dx = -dx;
|
||||
this.insertPoint(2, new go.Point(p2.x + dx, p1.y));
|
||||
}
|
||||
} else if (this.toSpot.equals(go.Spot.Right) || this.toSpot.equals(go.Spot.Left)) {
|
||||
const p1: go.Point = this.getPoint(1); // points 1 & 2 should be OK already
|
||||
const fromnode = this.fromNode;
|
||||
const fromport = this.fromPort;
|
||||
if (fromnode !== null && fromport !== null) {
|
||||
const parentlink = fromnode.findLinksInto().first();
|
||||
const fromctr = fromport.getDocumentPoint(go.Spot.Center);
|
||||
const far = fromctr.copy();
|
||||
far.x += (parentlink !== null && parentlink.fromSpot.equals(go.Spot.Left)) ? -99999 / 2 : 99999 / 2;
|
||||
far.y += (fromctr.y < p1.y) ? 99999 : -99999;
|
||||
const p0 = this.getLinkPointFromPoint(fromnode, fromport, fromctr, far, true);
|
||||
this.setPoint(0, p0);
|
||||
let dx = Math.abs(p1.y - p0.y) / 2;
|
||||
if (parentlink !== null && parentlink.fromSpot.equals(go.Spot.Left)) dx = -dx;
|
||||
this.insertPoint(1, new go.Point(p0.x + dx, p1.y));
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js", "./FishboneLayout.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.layoutNormal = exports.layoutBranching = exports.layoutFishbone = exports.init = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
var FishboneLayout_js_1 = require("./FishboneLayout.js");
|
||||
var myDiagram;
|
||||
function init() {
|
||||
if (window.goSamples)
|
||||
window.goSamples(); // init for these samples -- you don't need to call this F
|
||||
var $ = go.GraphObject.make; // for conciseness in defining templates
|
||||
myDiagram =
|
||||
$(go.Diagram, 'myDiagramDiv', // refers to its DIV HTML element by id
|
||||
{ isReadOnly: true }); // do not allow the user to modify the diagram
|
||||
// define the normal node template, just some text
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, $(go.TextBlock, new go.Binding('text'), new go.Binding('font', '', convertFont)));
|
||||
function convertFont(data) {
|
||||
var size = data.size;
|
||||
if (size === undefined)
|
||||
size = 13;
|
||||
var weight = data.weight;
|
||||
if (weight === undefined)
|
||||
weight = '';
|
||||
return weight + ' ' + size + 'px sans-serif';
|
||||
}
|
||||
// This demo switches the Diagram.linkTemplate between the "normal" and the "fishbone" templates.
|
||||
// If you are only doing a FishboneLayout, you could just set Diagram.linkTemplate
|
||||
// to the template named "fishbone" here, and not switch templates dynamically.
|
||||
// define the non-fishbone link template
|
||||
myDiagram.linkTemplateMap.add('normal', $(go.Link, { routing: go.Link.Orthogonal, corner: 4 }, $(go.Shape)));
|
||||
// use this link template for fishbone layouts
|
||||
myDiagram.linkTemplateMap.add('fishbone', $(FishboneLayout_js_1.FishboneLink, // defined above
|
||||
$(go.Shape)));
|
||||
// here is the structured data used to build the model
|
||||
var json = {
|
||||
'text': 'Incorrect Deliveries', 'size': 18, 'weight': 'Bold', 'causes': [
|
||||
{
|
||||
'text': 'Skills', 'size': 14, 'weight': 'Bold', 'causes': [
|
||||
{
|
||||
'text': 'knowledge', 'weight': 'Bold', 'causes': [
|
||||
{
|
||||
'text': 'procedures', 'causes': [
|
||||
{ 'text': 'documentation' }
|
||||
]
|
||||
},
|
||||
{ 'text': 'products' }
|
||||
]
|
||||
},
|
||||
{ 'text': 'literacy', 'weight': 'Bold' }
|
||||
]
|
||||
},
|
||||
{
|
||||
'text': 'Procedures', 'size': 14, 'weight': 'Bold', 'causes': [
|
||||
{
|
||||
'text': 'manual', 'weight': 'Bold', 'causes': [
|
||||
{ 'text': 'consistency' }
|
||||
]
|
||||
},
|
||||
{
|
||||
'text': 'automated', 'weight': 'Bold', 'causes': [
|
||||
{ 'text': 'correctness' },
|
||||
{ 'text': 'reliability' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'text': 'Communication', 'size': 14, 'weight': 'Bold', 'causes': [
|
||||
{ 'text': 'ambiguity', 'weight': 'Bold' },
|
||||
{
|
||||
'text': 'sales staff', 'weight': 'Bold', 'causes': [
|
||||
{
|
||||
'text': 'order details', 'causes': [
|
||||
{ 'text': 'lack of knowledge' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'text': 'telephone orders', 'weight': 'Bold', 'causes': [
|
||||
{ 'text': 'lack of information' }
|
||||
]
|
||||
},
|
||||
{
|
||||
'text': 'picking slips', 'weight': 'Bold', 'causes': [
|
||||
{ 'text': 'details' },
|
||||
{ 'text': 'legibility' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'text': 'Transport', 'size': 14, 'weight': 'Bold', 'causes': [
|
||||
{
|
||||
'text': 'information', 'weight': 'Bold', 'causes': [
|
||||
{ 'text': 'incorrect person' },
|
||||
{
|
||||
'text': 'incorrect addresses', 'causes': [
|
||||
{
|
||||
'text': 'customer data base', 'causes': [
|
||||
{ 'text': 'not up-to-date' },
|
||||
{ 'text': 'incorrect program' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{ 'text': 'incorrect dept' }
|
||||
]
|
||||
},
|
||||
{
|
||||
'text': 'carriers', 'weight': 'Bold', 'causes': [
|
||||
{ 'text': 'efficiency' },
|
||||
{ 'text': 'methods' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
function walkJson(obj, arr) {
|
||||
var key = arr.length;
|
||||
obj.key = key;
|
||||
arr.push(obj);
|
||||
var children = obj.causes;
|
||||
if (children) {
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
var o = children[i];
|
||||
o.parent = key; // reference to parent node data
|
||||
walkJson(o, arr);
|
||||
}
|
||||
}
|
||||
}
|
||||
// build the tree model
|
||||
var nodeDataArray = [];
|
||||
walkJson(json, nodeDataArray);
|
||||
myDiagram.model = new go.TreeModel(nodeDataArray);
|
||||
layoutFishbone();
|
||||
// Attach to the window for console manipulation
|
||||
window.myDiagram = myDiagram;
|
||||
}
|
||||
exports.init = init;
|
||||
// use FishboneLayout and FishboneLink
|
||||
function layoutFishbone() {
|
||||
myDiagram.startTransaction('fishbone layout');
|
||||
myDiagram.linkTemplate = myDiagram.linkTemplateMap.getValue('fishbone');
|
||||
myDiagram.layout = go.GraphObject.make(FishboneLayout_js_1.FishboneLayout, {
|
||||
angle: 180,
|
||||
layerSpacing: 10,
|
||||
nodeSpacing: 20,
|
||||
rowSpacing: 10
|
||||
});
|
||||
myDiagram.commitTransaction('fishbone layout');
|
||||
}
|
||||
exports.layoutFishbone = layoutFishbone;
|
||||
// make the layout a branching tree layout and use a normal link template
|
||||
function layoutBranching() {
|
||||
myDiagram.startTransaction('branching layout');
|
||||
myDiagram.linkTemplate = myDiagram.linkTemplateMap.getValue('normal');
|
||||
myDiagram.layout = go.GraphObject.make(go.TreeLayout, {
|
||||
angle: 180,
|
||||
layerSpacing: 20,
|
||||
alignment: go.TreeLayout.AlignmentBusBranching
|
||||
});
|
||||
myDiagram.commitTransaction('branching layout');
|
||||
}
|
||||
exports.layoutBranching = layoutBranching;
|
||||
// make the layout a basic tree layout and use a normal link template
|
||||
function layoutNormal() {
|
||||
myDiagram.startTransaction('normal layout');
|
||||
myDiagram.linkTemplate = myDiagram.linkTemplateMap.getValue('normal');
|
||||
myDiagram.layout = go.GraphObject.make(go.TreeLayout, {
|
||||
angle: 180,
|
||||
breadthLimit: 1000,
|
||||
alignment: go.TreeLayout.AlignmentStart
|
||||
});
|
||||
myDiagram.commitTransaction('normal layout');
|
||||
}
|
||||
exports.layoutNormal = layoutNormal;
|
||||
});
|
||||
+208
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
import { FishboneLayout, FishboneLink } from './FishboneLayout.js';
|
||||
|
||||
let myDiagram: go.Diagram;
|
||||
|
||||
export function init() {
|
||||
if ((window as any).goSamples) (window as any).goSamples(); // init for these samples -- you don't need to call this F
|
||||
|
||||
const $ = go.GraphObject.make; // for conciseness in defining templates
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, 'myDiagramDiv', // refers to its DIV HTML element by id
|
||||
{ isReadOnly: true }); // do not allow the user to modify the diagram
|
||||
|
||||
// define the normal node template, just some text
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node,
|
||||
$(go.TextBlock,
|
||||
new go.Binding('text'),
|
||||
new go.Binding('font', '', convertFont))
|
||||
);
|
||||
|
||||
function convertFont(data: any) {
|
||||
let size = data.size;
|
||||
if (size === undefined) size = 13;
|
||||
let weight = data.weight;
|
||||
if (weight === undefined) weight = '';
|
||||
return weight + ' ' + size + 'px sans-serif';
|
||||
}
|
||||
|
||||
// This demo switches the Diagram.linkTemplate between the "normal" and the "fishbone" templates.
|
||||
// If you are only doing a FishboneLayout, you could just set Diagram.linkTemplate
|
||||
// to the template named "fishbone" here, and not switch templates dynamically.
|
||||
|
||||
// define the non-fishbone link template
|
||||
myDiagram.linkTemplateMap.add('normal',
|
||||
$(go.Link,
|
||||
{ routing: go.Link.Orthogonal, corner: 4 },
|
||||
$(go.Shape)
|
||||
));
|
||||
|
||||
// use this link template for fishbone layouts
|
||||
myDiagram.linkTemplateMap.add('fishbone',
|
||||
$(FishboneLink, // defined above
|
||||
$(go.Shape)
|
||||
));
|
||||
|
||||
// here is the structured data used to build the model
|
||||
const json = {
|
||||
'text': 'Incorrect Deliveries', 'size': 18, 'weight': 'Bold', 'causes': [
|
||||
{
|
||||
'text': 'Skills', 'size': 14, 'weight': 'Bold', 'causes': [
|
||||
{
|
||||
'text': 'knowledge', 'weight': 'Bold', 'causes': [
|
||||
{
|
||||
'text': 'procedures', 'causes': [
|
||||
{ 'text': 'documentation' }
|
||||
]
|
||||
},
|
||||
{ 'text': 'products' }
|
||||
]
|
||||
},
|
||||
{ 'text': 'literacy', 'weight': 'Bold' }
|
||||
]
|
||||
},
|
||||
{
|
||||
'text': 'Procedures', 'size': 14, 'weight': 'Bold', 'causes': [
|
||||
{
|
||||
'text': 'manual', 'weight': 'Bold', 'causes': [
|
||||
{ 'text': 'consistency' }
|
||||
]
|
||||
},
|
||||
{
|
||||
'text': 'automated', 'weight': 'Bold', 'causes': [
|
||||
{ 'text': 'correctness' },
|
||||
{ 'text': 'reliability' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'text': 'Communication', 'size': 14, 'weight': 'Bold', 'causes': [
|
||||
{ 'text': 'ambiguity', 'weight': 'Bold' },
|
||||
{
|
||||
'text': 'sales staff', 'weight': 'Bold', 'causes': [
|
||||
{
|
||||
'text': 'order details', 'causes': [
|
||||
{ 'text': 'lack of knowledge' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'text': 'telephone orders', 'weight': 'Bold', 'causes': [
|
||||
{ 'text': 'lack of information' }
|
||||
]
|
||||
},
|
||||
{
|
||||
'text': 'picking slips', 'weight': 'Bold', 'causes': [
|
||||
{ 'text': 'details' },
|
||||
{ 'text': 'legibility' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'text': 'Transport', 'size': 14, 'weight': 'Bold', 'causes': [
|
||||
{
|
||||
'text': 'information', 'weight': 'Bold', 'causes': [
|
||||
{ 'text': 'incorrect person' },
|
||||
{
|
||||
'text': 'incorrect addresses', 'causes': [
|
||||
{
|
||||
'text': 'customer data base', 'causes': [
|
||||
{ 'text': 'not up-to-date' },
|
||||
{ 'text': 'incorrect program' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{ 'text': 'incorrect dept' }
|
||||
]
|
||||
},
|
||||
{
|
||||
'text': 'carriers', 'weight': 'Bold', 'causes': [
|
||||
{ 'text': 'efficiency' },
|
||||
{ 'text': 'methods' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
function walkJson(obj: any, arr: any) {
|
||||
const key = arr.length;
|
||||
obj.key = key;
|
||||
arr.push(obj);
|
||||
|
||||
const children = obj.causes;
|
||||
if (children) {
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const o = children[i];
|
||||
o.parent = key; // reference to parent node data
|
||||
walkJson(o, arr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// build the tree model
|
||||
const nodeDataArray: Array<Object> = [];
|
||||
walkJson(json, nodeDataArray);
|
||||
myDiagram.model = new go.TreeModel(nodeDataArray);
|
||||
|
||||
layoutFishbone();
|
||||
|
||||
// Attach to the window for console manipulation
|
||||
(window as any).myDiagram = myDiagram;
|
||||
}
|
||||
|
||||
// use FishboneLayout and FishboneLink
|
||||
export function layoutFishbone() {
|
||||
myDiagram.startTransaction('fishbone layout');
|
||||
myDiagram.linkTemplate = myDiagram.linkTemplateMap.getValue('fishbone') as go.Link;
|
||||
myDiagram.layout = go.GraphObject.make(FishboneLayout, { // defined above
|
||||
angle: 180,
|
||||
layerSpacing: 10,
|
||||
nodeSpacing: 20,
|
||||
rowSpacing: 10
|
||||
});
|
||||
myDiagram.commitTransaction('fishbone layout');
|
||||
}
|
||||
|
||||
// make the layout a branching tree layout and use a normal link template
|
||||
export function layoutBranching() {
|
||||
myDiagram.startTransaction('branching layout');
|
||||
myDiagram.linkTemplate = myDiagram.linkTemplateMap.getValue('normal') as go.Link;
|
||||
myDiagram.layout = go.GraphObject.make(go.TreeLayout, {
|
||||
angle: 180,
|
||||
layerSpacing: 20,
|
||||
alignment: go.TreeLayout.AlignmentBusBranching
|
||||
});
|
||||
myDiagram.commitTransaction('branching layout');
|
||||
}
|
||||
|
||||
// make the layout a basic tree layout and use a normal link template
|
||||
export function layoutNormal() {
|
||||
myDiagram.startTransaction('normal layout');
|
||||
myDiagram.linkTemplate = myDiagram.linkTemplateMap.getValue('normal') as go.Link;
|
||||
myDiagram.layout = go.GraphObject.make(go.TreeLayout, {
|
||||
angle: 180,
|
||||
breadthLimit: 1000,
|
||||
alignment: go.TreeLayout.AlignmentStart
|
||||
});
|
||||
myDiagram.commitTransaction('normal layout');
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Freehand Drawing Tool</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="TypeScript: Let the user draw a Shape using the mouse or finger without any constraints." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../samples/assets/require.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script><!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
function init() {
|
||||
require(["FreehandDrawingScript"], function(app) {
|
||||
app.init();
|
||||
document.getElementById("select").onclick = app.select;
|
||||
document.getElementById("drawMode").onclick = app.drawMode;
|
||||
document.getElementById("save").onclick = app.save;
|
||||
document.getElementById("load").onclick = app.load;
|
||||
document.getElementById("allowResizing").onclick = app.allowResizing;
|
||||
document.getElementById("allowReshaping").onclick = app.allowReshaping;
|
||||
document.getElementById("allowRotating").onclick = app.allowRotating;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width: 100%; height: 600px"></div>
|
||||
<div id="buttons">
|
||||
<button id="select">Select</button>
|
||||
<button id="drawMode">Draw Mode</button>
|
||||
<button id="save">Save</button>
|
||||
<button id="load">Load</button>
|
||||
<label><input type="checkbox" id = "allowResizing" checked="checked" />Allow Resizing</label>
|
||||
<label><input type="checkbox" id = "allowReshaping" checked="checked" />Allow Reshaping</label>
|
||||
<label><input type="checkbox" id = "allowRotating" checked="checked" />Allow Rotating</label>
|
||||
</div>
|
||||
<p>
|
||||
This sample demonstrates the FreehandDrawingTool. It is defined in its own file, as <a href="FreehandDrawingTool.ts">FreehandDrawingTool.ts</a>.
|
||||
It also demonstrates the GeometryReshapingTool, another custom tool, defined in <a href="GeometryReshapingTool.ts">GeometryReshapingTool.ts</a>.
|
||||
</p>
|
||||
<p>
|
||||
Press and drag to draw a line.
|
||||
</p>
|
||||
<p>
|
||||
Click the "Select" button to switch back to the normal selection behavior, so that you can select, resize, and rotate the
|
||||
shapes. The checkboxes control whether you can resize, reshape, and/or rotate selected shapes.
|
||||
</p>
|
||||
<textarea id="mySavedDiagram" style="width:100%;height:300px">
|
||||
{ "position": "0 0",
|
||||
"model": { "class": "go.GraphLinksModel",
|
||||
"nodeDataArray": [ {"loc":"301 143", "category":"FreehandDrawing", "geo":"M0 70 L1 70 L2 70 L3 70 L5 70 L7 70 L8 70 L11 70 L13 70 L18 69 L21 69 L25 68 L29 67 L34 67 L38 67 L42 67 L47 66 L50 66 L53 66 L55 66 L57 66 L60 66 L63 66 L64 66 L66 66 L68 66 L70 66 L72 66 L74 66 L76 66 L78 65 L81 65 L83 65 L85 65 L88 65 L90 65 L92 65 L95 65 L98 65 L100 65 L102 65 L104 65 L106 65 L109 65 L110 65 L111 65 L112 65 L113 65 L114 65 L115 65 L116 65 L118 65 L119 65 L120 65 L121 65 L122 65 L123 65 L124 65 L125 65 L126 65 L127 65 L128 65 L129 65 L131 65 L131 64 L132 64 L133 64 L134 64 L135 64 L137 64 L138 64 L139 64 L140 64 L141 64 L140 64 L139 64 L138 64 L137 64 L135 65 L134 65 L132 66 L130 67 L129 67 L126 68 L123 70 L121 71 L119 72 L116 73 L114 74 L111 76 L109 77 L106 78 L104 79 L99 81 L96 84 L94 84 L90 87 L89 87 L87 88 L86 89 L84 89 L83 90 L81 91 L80 92 L79 92 L77 93 L76 94 L74 94 L74 95 L73 96 L71 96 L70 97 L68 98 L67 98 L66 99 L64 99 L64 100 L62 100 L61 101 L60 102 L59 102 L58 103 L57 103 L57 104 L56 104 L56 105 L54 105 L54 107 L53 107 L52 108 L51 108 L51 109 L49 110 L48 111 L47 112 L47 113 L46 113 L45 114 L44 114 L44 115 L44 116 L43 116 L43 117 L42 117 L42 119 L41 119 L41 120 L40 120 L40 121 L39 122 L39 123 L38 124 L37 125 L36 126 L36 127 L35 128 L34 128 L34 129 L33 130 L33 131 L32 131 L32 130 L32 129 L33 128 L34 125 L35 122 L37 119 L39 115 L41 111 L41 106 L43 103 L45 98 L47 93 L48 90 L49 86 L51 83 L52 81 L54 78 L55 74 L55 71 L56 68 L57 65 L58 62 L58 59 L58 55 L58 53 L58 51 L58 49 L58 48 L59 45 L60 44 L60 42 L61 40 L61 38 L62 36 L64 32 L64 30 L65 29 L66 27 L66 26 L66 25 L66 24 L67 23 L67 22 L67 21 L67 20 L67 19 L68 19 L68 18 L68 17 L69 16 L69 15 L69 14 L69 12 L69 11 L69 10 L70 9 L70 8 L70 7 L70 6 L71 5 L71 4 L71 3 L71 2 L71 1 L71 0 L71 1 L71 2 L71 5 L71 6 L71 8 L71 9 L71 12 L71 14 L72 16 L72 18 L73 20 L73 23 L74 25 L74 27 L75 29 L76 32 L77 34 L78 35 L79 38 L79 39 L81 42 L81 43 L82 45 L83 46 L83 47 L83 49 L85 50 L86 52 L86 55 L86 58 L88 60 L89 62 L89 64 L90 66 L91 67 L91 69 L92 70 L92 71 L93 72 L94 73 L94 74 L94 75 L95 76 L96 77 L96 79 L97 81 L98 82 L98 83 L98 84 L99 85 L99 86 L100 87 L100 90 L101 91 L102 92 L102 93 L103 95 L103 96 L103 97 L104 98 L104 100 L104 101 L105 102 L106 103 L106 104 L107 106 L108 108 L109 110 L110 111 L111 113 L112 114 L113 115 L113 117 L115 119 L116 121 L116 123 L118 124 L119 126 L120 127 L121 129 L121 130 L122 131 L123 131 L123 132 L124 132 L124 133 L125 134 L125 135 L126 135 L127 136 L128 137 L129 138 L130 139 L131 140 L130 139 L129 138 L128 138 L126 136 L124 136 L123 134 L121 133 L118 131 L116 130 L114 128 L111 127 L107 123 L105 122 L102 120 L100 119 L98 117 L95 116 L93 114 L90 113 L88 111 L85 110 L83 108 L81 106 L78 105 L77 104 L75 103 L72 102 L71 101 L70 100 L68 99 L67 98 L65 98 L64 97 L62 96 L60 96 L58 95 L57 94 L54 93 L53 93 L51 92 L49 91 L48 91 L47 91 L45 90 L44 90 L43 89 L42 89 L41 89 L40 88 L39 87 L37 87 L36 86 L35 85 L33 85 L32 84 L31 84 L30 83 L29 83 L28 82 L26 81 L25 81 L24 80 L22 80 L21 79 L21 78 L20 78 L19 78 L18 77 L17 77 L16 76 L15 76 L15 75 L14 75 L13 75 L12 74 L11 74 L10 74 L9 74 L7 73 L5 72 L4 72 L3 72 L2 71", "key":-1} ],
|
||||
"linkDataArray": [ ]
|
||||
} }
|
||||
</textarea>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js", "./FreehandDrawingTool.js", "./GeometryReshapingTool.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.allowRotating = exports.allowReshaping = exports.allowResizing = exports.drawMode = exports.select = exports.load = exports.save = exports.updateAllAdornments = exports.mode = exports.init = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
var FreehandDrawingTool_js_1 = require("./FreehandDrawingTool.js");
|
||||
var GeometryReshapingTool_js_1 = require("./GeometryReshapingTool.js");
|
||||
var myDiagram;
|
||||
function init() {
|
||||
if (window.goSamples)
|
||||
window.goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make;
|
||||
myDiagram =
|
||||
$(go.Diagram, 'myDiagramDiv');
|
||||
myDiagram.toolManager.mouseDownTools.insertAt(3, new GeometryReshapingTool_js_1.GeometryReshapingTool());
|
||||
myDiagram.nodeTemplateMap.add('FreehandDrawing', $(go.Part, { locationSpot: go.Spot.Center, isLayoutPositioned: false }, new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify), {
|
||||
selectionAdorned: true, selectionObjectName: 'SHAPE',
|
||||
selectionAdornmentTemplate: // custom selection adornment: a blue rectangle
|
||||
$(go.Adornment, 'Auto', $(go.Shape, { stroke: 'dodgerblue', fill: null }), $(go.Placeholder, { margin: -1 }))
|
||||
}, { resizable: true, resizeObjectName: 'SHAPE' }, { rotatable: true, rotateObjectName: 'SHAPE' }, { reshapable: true }, // GeometryReshapingTool assumes nonexistent Part.reshapeObjectName would be "SHAPE"
|
||||
$(go.Shape, { name: 'SHAPE', fill: null, strokeWidth: 1.5 }, new go.Binding('desiredSize', 'size', go.Size.parse).makeTwoWay(go.Size.stringify), new go.Binding('angle').makeTwoWay(), new go.Binding('geometryString', 'geo').makeTwoWay(), new go.Binding('fill'), new go.Binding('stroke'), new go.Binding('strokeWidth'))));
|
||||
// create drawing tool for myDiagram, defined in FreehandDrawingTool.js
|
||||
var tool = new FreehandDrawingTool_js_1.FreehandDrawingTool();
|
||||
// provide the default JavaScript object for a new polygon in the model
|
||||
tool.archetypePartData = { stroke: 'green', strokeWidth: 3, category: 'FreehandDrawing' };
|
||||
// allow the tool to start on top of an existing Part
|
||||
tool.isBackgroundOnly = false;
|
||||
// install as first mouse-move-tool
|
||||
myDiagram.toolManager.mouseMoveTools.insertAt(0, tool);
|
||||
load(); // load a simple diagram from the textarea
|
||||
// Attach to the window for console manipulation
|
||||
window.myDiagram = myDiagram;
|
||||
}
|
||||
exports.init = init;
|
||||
function mode(draw) {
|
||||
var tool = myDiagram.toolManager.findTool('FreehandDrawing');
|
||||
if (tool !== null)
|
||||
tool.isEnabled = draw;
|
||||
}
|
||||
exports.mode = mode;
|
||||
function updateAllAdornments() {
|
||||
myDiagram.selection.each(function (p) { p.updateAdornments(); });
|
||||
}
|
||||
exports.updateAllAdornments = updateAllAdornments;
|
||||
// save a model to and load a model from Json text, displayed below the Diagram
|
||||
function save() {
|
||||
var str = '{ "position": "' + go.Point.stringify(myDiagram.position) + '",\n "model": ' + myDiagram.model.toJson() + ' }';
|
||||
document.getElementById('mySavedDiagram').value = str;
|
||||
}
|
||||
exports.save = save;
|
||||
function load() {
|
||||
var str = document.getElementById('mySavedDiagram').value;
|
||||
try {
|
||||
var json = JSON.parse(str);
|
||||
myDiagram.initialPosition = go.Point.parse(json.position || '0 0');
|
||||
myDiagram.model = go.Model.fromJson(json.model);
|
||||
myDiagram.model.undoManager.isEnabled = true;
|
||||
}
|
||||
catch (ex) {
|
||||
alert(ex);
|
||||
}
|
||||
}
|
||||
exports.load = load;
|
||||
function select() {
|
||||
mode(false);
|
||||
}
|
||||
exports.select = select;
|
||||
function drawMode() {
|
||||
mode(true);
|
||||
}
|
||||
exports.drawMode = drawMode;
|
||||
function allowResizing() {
|
||||
myDiagram.allowResize = !myDiagram.allowResize;
|
||||
updateAllAdornments();
|
||||
}
|
||||
exports.allowResizing = allowResizing;
|
||||
function allowReshaping() {
|
||||
myDiagram.allowReshape = !myDiagram.allowReshape;
|
||||
updateAllAdornments();
|
||||
}
|
||||
exports.allowReshaping = allowReshaping;
|
||||
function allowRotating() {
|
||||
myDiagram.allowRotate = !myDiagram.allowRotate;
|
||||
updateAllAdornments();
|
||||
}
|
||||
exports.allowRotating = allowRotating;
|
||||
});
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
import { FreehandDrawingTool } from './FreehandDrawingTool.js';
|
||||
import { GeometryReshapingTool } from './GeometryReshapingTool.js';
|
||||
|
||||
let myDiagram: go.Diagram;
|
||||
|
||||
export function init() {
|
||||
if ((window as any).goSamples) (window as any).goSamples(); // init for these samples -- you don't need to call this
|
||||
|
||||
const $ = go.GraphObject.make;
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, 'myDiagramDiv');
|
||||
|
||||
myDiagram.toolManager.mouseDownTools.insertAt(3, new GeometryReshapingTool());
|
||||
|
||||
myDiagram.nodeTemplateMap.add('FreehandDrawing',
|
||||
$(go.Part,
|
||||
{ locationSpot: go.Spot.Center, isLayoutPositioned: false },
|
||||
new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
{
|
||||
selectionAdorned: true, selectionObjectName: 'SHAPE',
|
||||
selectionAdornmentTemplate: // custom selection adornment: a blue rectangle
|
||||
$(go.Adornment, 'Auto',
|
||||
$(go.Shape, { stroke: 'dodgerblue', fill: null }),
|
||||
$(go.Placeholder, { margin: -1 }))
|
||||
},
|
||||
{ resizable: true, resizeObjectName: 'SHAPE' },
|
||||
{ rotatable: true, rotateObjectName: 'SHAPE' },
|
||||
{ reshapable: true }, // GeometryReshapingTool assumes nonexistent Part.reshapeObjectName would be "SHAPE"
|
||||
$(go.Shape,
|
||||
{ name: 'SHAPE', fill: null, strokeWidth: 1.5 },
|
||||
new go.Binding('desiredSize', 'size', go.Size.parse).makeTwoWay(go.Size.stringify),
|
||||
new go.Binding('angle').makeTwoWay(),
|
||||
new go.Binding('geometryString', 'geo').makeTwoWay(),
|
||||
new go.Binding('fill'),
|
||||
new go.Binding('stroke'),
|
||||
new go.Binding('strokeWidth'))
|
||||
));
|
||||
|
||||
// create drawing tool for myDiagram, defined in FreehandDrawingTool.js
|
||||
const tool = new FreehandDrawingTool();
|
||||
// provide the default JavaScript object for a new polygon in the model
|
||||
tool.archetypePartData = { stroke: 'green', strokeWidth: 3, category: 'FreehandDrawing' };
|
||||
// allow the tool to start on top of an existing Part
|
||||
tool.isBackgroundOnly = false;
|
||||
// install as first mouse-move-tool
|
||||
myDiagram.toolManager.mouseMoveTools.insertAt(0, tool);
|
||||
|
||||
load(); // load a simple diagram from the textarea
|
||||
|
||||
// Attach to the window for console manipulation
|
||||
(window as any).myDiagram = myDiagram;
|
||||
}
|
||||
|
||||
export function mode(draw: boolean) {
|
||||
const tool = myDiagram.toolManager.findTool('FreehandDrawing');
|
||||
if (tool !== null) tool.isEnabled = draw;
|
||||
}
|
||||
|
||||
export function updateAllAdornments() { // called after checkboxes change Diagram.allow...
|
||||
myDiagram.selection.each(function (p) { p.updateAdornments(); });
|
||||
}
|
||||
|
||||
// save a model to and load a model from Json text, displayed below the Diagram
|
||||
export function save() {
|
||||
const str = '{ "position": "' + go.Point.stringify(myDiagram.position) + '",\n "model": ' + myDiagram.model.toJson() + ' }';
|
||||
(document.getElementById('mySavedDiagram') as any).value = str;
|
||||
}
|
||||
export function load() {
|
||||
const str = (document.getElementById('mySavedDiagram') as any).value;
|
||||
try {
|
||||
const json = JSON.parse(str);
|
||||
myDiagram.initialPosition = go.Point.parse(json.position || '0 0');
|
||||
myDiagram.model = go.Model.fromJson(json.model);
|
||||
myDiagram.model.undoManager.isEnabled = true;
|
||||
} catch (ex) {
|
||||
alert(ex);
|
||||
}
|
||||
}
|
||||
|
||||
export function select() {
|
||||
mode(false);
|
||||
}
|
||||
export function drawMode() {
|
||||
mode(true);
|
||||
}
|
||||
export function allowResizing() {
|
||||
myDiagram.allowResize = !myDiagram.allowResize; updateAllAdornments();
|
||||
}
|
||||
export function allowReshaping() {
|
||||
myDiagram.allowReshape = !myDiagram.allowReshape; updateAllAdornments();
|
||||
}
|
||||
export function allowRotating() {
|
||||
myDiagram.allowRotate = !myDiagram.allowRotate; updateAllAdornments();
|
||||
}
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.FreehandDrawingTool = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
/**
|
||||
* The FreehandDrawingTool allows the user to draw a shape using the mouse.
|
||||
* It collects all of the points from a mouse-down, all mouse-moves, until a mouse-up,
|
||||
* and puts all of those points in a {@link Geometry} used by a {@link Shape}.
|
||||
* It then adds a node data object to the diagram's model.
|
||||
*
|
||||
* This tool may be installed as the first mouse down tool:
|
||||
* ```js
|
||||
* myDiagram.toolManager.mouseDownTools.insertAt(0, new FreehandDrawingTool());
|
||||
* ```
|
||||
*
|
||||
* The Shape used during the drawing operation can be customized by setting {@link #temporaryShape}.
|
||||
* The node data added to the model can be customized by setting {@link #archetypePartData}.
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/FreehandDrawing.html">Freehand Drawing</a> sample.
|
||||
* @category Tool Extension
|
||||
*/
|
||||
var FreehandDrawingTool = /** @class */ (function (_super) {
|
||||
__extends(FreehandDrawingTool, _super);
|
||||
function FreehandDrawingTool() {
|
||||
var _this = _super.call(this) || this;
|
||||
// this is the Shape that is shown during a drawing operation
|
||||
_this._temporaryShape = go.GraphObject.make(go.Shape, { name: 'SHAPE', fill: null, strokeWidth: 1.5 });
|
||||
_this._archetypePartData = {}; // the data to copy for a new polyline Part
|
||||
_this._isBackgroundOnly = true; // affects canStart()
|
||||
// the Shape has to be inside a temporary Part that is used during the drawing operation
|
||||
_this.temp = go.GraphObject.make(go.Part, { layerName: 'Tool' }, _this._temporaryShape);
|
||||
_this.name = 'FreehandDrawing';
|
||||
return _this;
|
||||
}
|
||||
Object.defineProperty(FreehandDrawingTool.prototype, "temporaryShape", {
|
||||
/**
|
||||
* Gets or sets the Shape that is used to hold the line as it is being drawn.
|
||||
*
|
||||
* The default value is a simple Shape drawing an unfilled open thin black line.
|
||||
*/
|
||||
get: function () { return this._temporaryShape; },
|
||||
set: function (val) {
|
||||
if (this._temporaryShape !== val && val !== null) {
|
||||
val.name = 'SHAPE';
|
||||
var panel = this._temporaryShape.panel;
|
||||
if (panel !== null) {
|
||||
panel.remove(this._temporaryShape);
|
||||
this._temporaryShape = val;
|
||||
panel.add(this._temporaryShape);
|
||||
}
|
||||
}
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(FreehandDrawingTool.prototype, "archetypePartData", {
|
||||
/**
|
||||
* Gets or sets the node data object that is copied and added to the model
|
||||
* when the freehand drawing operation completes.
|
||||
*/
|
||||
get: function () { return this._archetypePartData; },
|
||||
set: function (val) { this._archetypePartData = val; },
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(FreehandDrawingTool.prototype, "isBackgroundOnly", {
|
||||
/**
|
||||
* Gets or sets whether this tool can only run if the user starts in the diagram's background
|
||||
* rather than on top of an existing Part.
|
||||
*
|
||||
* The default value is true.
|
||||
*/
|
||||
get: function () { return this._isBackgroundOnly; },
|
||||
set: function (val) { this._isBackgroundOnly = val; },
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
/**
|
||||
* Only start if the diagram is modifiable and allows insertions.
|
||||
* OPTIONAL: if the user is starting in the diagram's background, not over an existing Part.
|
||||
*/
|
||||
FreehandDrawingTool.prototype.canStart = function () {
|
||||
if (!this.isEnabled)
|
||||
return false;
|
||||
var diagram = this.diagram;
|
||||
if (diagram.isReadOnly || diagram.isModelReadOnly)
|
||||
return false;
|
||||
if (!diagram.allowInsert)
|
||||
return false;
|
||||
// don't include the following check when this tool is running modally
|
||||
if (diagram.currentTool !== this && this.isBackgroundOnly) {
|
||||
// only operates in the background, not on some Part
|
||||
var part = diagram.findPartAt(diagram.lastInput.documentPoint, true);
|
||||
if (part !== null)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
/**
|
||||
* Capture the mouse and use a "crosshair" cursor.
|
||||
*/
|
||||
FreehandDrawingTool.prototype.doActivate = function () {
|
||||
_super.prototype.doActivate.call(this);
|
||||
this.diagram.isMouseCaptured = true;
|
||||
this.diagram.currentCursor = 'crosshair';
|
||||
};
|
||||
/**
|
||||
* Release the mouse and reset the cursor.
|
||||
*/
|
||||
FreehandDrawingTool.prototype.doDeactivate = function () {
|
||||
_super.prototype.doDeactivate.call(this);
|
||||
if (this.temporaryShape !== null && this.temporaryShape.part !== null) {
|
||||
this.diagram.remove(this.temporaryShape.part);
|
||||
}
|
||||
this.diagram.currentCursor = '';
|
||||
this.diagram.isMouseCaptured = false;
|
||||
};
|
||||
/**
|
||||
* This adds a Point to the {@link #temporaryShape}'s geometry.
|
||||
*
|
||||
* If the Shape is not yet in the Diagram, its geometry is initialized and
|
||||
* its parent Part is added to the Diagram.
|
||||
*
|
||||
* If the point is less than half a pixel away from the previous point, it is ignored.
|
||||
*/
|
||||
FreehandDrawingTool.prototype.addPoint = function (p) {
|
||||
var shape = this.temporaryShape;
|
||||
if (shape === null)
|
||||
return;
|
||||
var part = shape.part;
|
||||
if (part === null)
|
||||
return;
|
||||
// for the temporary Shape, normalize the geometry to be in the viewport
|
||||
var viewpt = this.diagram.viewportBounds.position;
|
||||
var q = new go.Point(p.x - viewpt.x, p.y - viewpt.y);
|
||||
if (part.diagram === null) {
|
||||
var f = new go.PathFigure(q.x, q.y, true); // possibly filled, depending on Shape.fill
|
||||
var g = new go.Geometry().add(f); // the Shape.geometry consists of a single PathFigure
|
||||
shape.geometry = g;
|
||||
// position the Shape's Part, accounting for the strokeWidth
|
||||
part.position = new go.Point(viewpt.x - shape.strokeWidth / 2, viewpt.y - shape.strokeWidth / 2);
|
||||
this.diagram.add(part);
|
||||
}
|
||||
// only add a point if it isn't too close to the last one
|
||||
var geo = shape.geometry;
|
||||
if (geo !== null) {
|
||||
var fig = geo.figures.first();
|
||||
if (fig !== null) {
|
||||
var segs = fig.segments;
|
||||
var idx = segs.count - 1;
|
||||
if (idx >= 0) {
|
||||
var last = segs.elt(idx);
|
||||
if (Math.abs(q.x - last.endX) < 0.5 && Math.abs(q.y - last.endY) < 0.5)
|
||||
return;
|
||||
}
|
||||
// must copy whole Geometry in order to add a PathSegment
|
||||
var geo2 = geo.copy();
|
||||
var fig2 = geo2.figures.first();
|
||||
if (fig2 !== null) {
|
||||
fig2.add(new go.PathSegment(go.PathSegment.Line, q.x, q.y));
|
||||
shape.geometry = geo2;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Start drawing the line by starting to accumulate points in the {@link #temporaryShape}'s geometry.
|
||||
*/
|
||||
FreehandDrawingTool.prototype.doMouseDown = function () {
|
||||
if (!this.isActive) {
|
||||
this.doActivate();
|
||||
// the first point
|
||||
this.addPoint(this.diagram.lastInput.documentPoint);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Keep accumulating points in the {@link #temporaryShape}'s geometry.
|
||||
*/
|
||||
FreehandDrawingTool.prototype.doMouseMove = function () {
|
||||
if (this.isActive) {
|
||||
this.addPoint(this.diagram.lastInput.documentPoint);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Finish drawing the line by adding a node data object holding the
|
||||
* geometry string and the node position that the node template can bind to.
|
||||
* This copies the {@link #archetypePartData} and adds it to the model.
|
||||
*/
|
||||
FreehandDrawingTool.prototype.doMouseUp = function () {
|
||||
var diagram = this.diagram;
|
||||
var started = false;
|
||||
if (this.isActive) {
|
||||
started = true;
|
||||
// the last point
|
||||
this.addPoint(diagram.lastInput.documentPoint);
|
||||
// normalize geometry and node position
|
||||
var viewpt = diagram.viewportBounds.position;
|
||||
if (this.temporaryShape.geometry !== null) {
|
||||
var geo = this.temporaryShape.geometry.copy();
|
||||
var pos = geo.normalize();
|
||||
pos.x = viewpt.x - pos.x;
|
||||
pos.y = viewpt.y - pos.y;
|
||||
diagram.startTransaction(this.name);
|
||||
// create the node data for the model
|
||||
var d = diagram.model.copyNodeData(this.archetypePartData);
|
||||
if (d !== null) {
|
||||
// adding data to model creates the actual Part
|
||||
diagram.model.addNodeData(d);
|
||||
var part = diagram.findPartForData(d);
|
||||
if (part !== null) {
|
||||
// assign the location
|
||||
part.location = new go.Point(pos.x + geo.bounds.width / 2, pos.y + geo.bounds.height / 2);
|
||||
// assign the Shape.geometry
|
||||
var shape = part.findObject('SHAPE');
|
||||
if (shape !== null)
|
||||
shape.geometry = geo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.stopTool();
|
||||
if (started)
|
||||
diagram.commitTransaction(this.name);
|
||||
};
|
||||
return FreehandDrawingTool;
|
||||
}(go.Tool));
|
||||
exports.FreehandDrawingTool = FreehandDrawingTool;
|
||||
});
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
|
||||
/**
|
||||
* The FreehandDrawingTool allows the user to draw a shape using the mouse.
|
||||
* It collects all of the points from a mouse-down, all mouse-moves, until a mouse-up,
|
||||
* and puts all of those points in a {@link Geometry} used by a {@link Shape}.
|
||||
* It then adds a node data object to the diagram's model.
|
||||
*
|
||||
* This tool may be installed as the first mouse down tool:
|
||||
* ```js
|
||||
* myDiagram.toolManager.mouseDownTools.insertAt(0, new FreehandDrawingTool());
|
||||
* ```
|
||||
*
|
||||
* The Shape used during the drawing operation can be customized by setting {@link #temporaryShape}.
|
||||
* The node data added to the model can be customized by setting {@link #archetypePartData}.
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/FreehandDrawing.html">Freehand Drawing</a> sample.
|
||||
* @category Tool Extension
|
||||
*/
|
||||
export class FreehandDrawingTool extends go.Tool {
|
||||
// this is the Shape that is shown during a drawing operation
|
||||
private _temporaryShape: go.GraphObject = go.GraphObject.make(go.Shape, { name: 'SHAPE', fill: null, strokeWidth: 1.5 });
|
||||
private _archetypePartData: go.ObjectData = {}; // the data to copy for a new polyline Part
|
||||
private _isBackgroundOnly: boolean = true; // affects canStart()
|
||||
|
||||
// the Shape has to be inside a temporary Part that is used during the drawing operation
|
||||
private temp: go.GraphObject = go.GraphObject.make(go.Part, { layerName: 'Tool' }, this._temporaryShape);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.name = 'FreehandDrawing';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the Shape that is used to hold the line as it is being drawn.
|
||||
*
|
||||
* The default value is a simple Shape drawing an unfilled open thin black line.
|
||||
*/
|
||||
get temporaryShape(): go.Shape { return this._temporaryShape as go.Shape; }
|
||||
set temporaryShape(val: go.Shape) {
|
||||
if (this._temporaryShape !== val && val !== null) {
|
||||
val.name = 'SHAPE';
|
||||
const panel = this._temporaryShape.panel;
|
||||
if (panel !== null) {
|
||||
panel.remove(this._temporaryShape);
|
||||
this._temporaryShape = val;
|
||||
panel.add(this._temporaryShape);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the node data object that is copied and added to the model
|
||||
* when the freehand drawing operation completes.
|
||||
*/
|
||||
get archetypePartData(): go.ObjectData { return this._archetypePartData; }
|
||||
set archetypePartData(val: go.ObjectData) { this._archetypePartData = val; }
|
||||
|
||||
/**
|
||||
* Gets or sets whether this tool can only run if the user starts in the diagram's background
|
||||
* rather than on top of an existing Part.
|
||||
*
|
||||
* The default value is true.
|
||||
*/
|
||||
get isBackgroundOnly(): boolean { return this._isBackgroundOnly; }
|
||||
set isBackgroundOnly(val: boolean) { this._isBackgroundOnly = val; }
|
||||
|
||||
/**
|
||||
* Only start if the diagram is modifiable and allows insertions.
|
||||
* OPTIONAL: if the user is starting in the diagram's background, not over an existing Part.
|
||||
*/
|
||||
public canStart(): boolean {
|
||||
if (!this.isEnabled) return false;
|
||||
const diagram = this.diagram;
|
||||
if (diagram.isReadOnly || diagram.isModelReadOnly) return false;
|
||||
if (!diagram.allowInsert) return false;
|
||||
// don't include the following check when this tool is running modally
|
||||
if (diagram.currentTool !== this && this.isBackgroundOnly) {
|
||||
// only operates in the background, not on some Part
|
||||
const part = diagram.findPartAt(diagram.lastInput.documentPoint, true);
|
||||
if (part !== null) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Capture the mouse and use a "crosshair" cursor.
|
||||
*/
|
||||
public doActivate(): void {
|
||||
super.doActivate();
|
||||
this.diagram.isMouseCaptured = true;
|
||||
this.diagram.currentCursor = 'crosshair';
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the mouse and reset the cursor.
|
||||
*/
|
||||
public doDeactivate(): void {
|
||||
super.doDeactivate();
|
||||
if (this.temporaryShape !== null && this.temporaryShape.part !== null) {
|
||||
this.diagram.remove(this.temporaryShape.part);
|
||||
}
|
||||
this.diagram.currentCursor = '';
|
||||
this.diagram.isMouseCaptured = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This adds a Point to the {@link #temporaryShape}'s geometry.
|
||||
*
|
||||
* If the Shape is not yet in the Diagram, its geometry is initialized and
|
||||
* its parent Part is added to the Diagram.
|
||||
*
|
||||
* If the point is less than half a pixel away from the previous point, it is ignored.
|
||||
*/
|
||||
public addPoint(p: go.Point): void {
|
||||
const shape = this.temporaryShape;
|
||||
if (shape === null) return;
|
||||
const part = shape.part;
|
||||
if (part === null) return;
|
||||
|
||||
// for the temporary Shape, normalize the geometry to be in the viewport
|
||||
const viewpt = this.diagram.viewportBounds.position;
|
||||
const q = new go.Point(p.x - viewpt.x, p.y - viewpt.y);
|
||||
|
||||
if (part.diagram === null) {
|
||||
const f = new go.PathFigure(q.x, q.y, true); // possibly filled, depending on Shape.fill
|
||||
const g = new go.Geometry().add(f); // the Shape.geometry consists of a single PathFigure
|
||||
shape.geometry = g;
|
||||
// position the Shape's Part, accounting for the strokeWidth
|
||||
part.position = new go.Point(viewpt.x - shape.strokeWidth / 2, viewpt.y - shape.strokeWidth / 2);
|
||||
this.diagram.add(part);
|
||||
}
|
||||
|
||||
// only add a point if it isn't too close to the last one
|
||||
const geo = shape.geometry;
|
||||
if (geo !== null) {
|
||||
const fig = geo.figures.first();
|
||||
if (fig !== null) {
|
||||
const segs = fig.segments;
|
||||
const idx = segs.count - 1;
|
||||
if (idx >= 0) {
|
||||
const last = segs.elt(idx);
|
||||
if (Math.abs(q.x - last.endX) < 0.5 && Math.abs(q.y - last.endY) < 0.5) return;
|
||||
}
|
||||
|
||||
// must copy whole Geometry in order to add a PathSegment
|
||||
const geo2 = geo.copy();
|
||||
const fig2 = geo2.figures.first();
|
||||
if (fig2 !== null) {
|
||||
fig2.add(new go.PathSegment(go.PathSegment.Line, q.x, q.y));
|
||||
shape.geometry = geo2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start drawing the line by starting to accumulate points in the {@link #temporaryShape}'s geometry.
|
||||
*/
|
||||
public doMouseDown(): void {
|
||||
if (!this.isActive) {
|
||||
this.doActivate();
|
||||
// the first point
|
||||
this.addPoint(this.diagram.lastInput.documentPoint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep accumulating points in the {@link #temporaryShape}'s geometry.
|
||||
*/
|
||||
public doMouseMove(): void {
|
||||
if (this.isActive) {
|
||||
this.addPoint(this.diagram.lastInput.documentPoint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish drawing the line by adding a node data object holding the
|
||||
* geometry string and the node position that the node template can bind to.
|
||||
* This copies the {@link #archetypePartData} and adds it to the model.
|
||||
*/
|
||||
public doMouseUp(): void {
|
||||
const diagram = this.diagram;
|
||||
let started = false;
|
||||
if (this.isActive) {
|
||||
started = true;
|
||||
// the last point
|
||||
this.addPoint(diagram.lastInput.documentPoint);
|
||||
// normalize geometry and node position
|
||||
const viewpt = diagram.viewportBounds.position;
|
||||
if (this.temporaryShape.geometry !== null) {
|
||||
const geo = this.temporaryShape.geometry.copy();
|
||||
const pos = geo.normalize();
|
||||
pos.x = viewpt.x - pos.x;
|
||||
pos.y = viewpt.y - pos.y;
|
||||
|
||||
diagram.startTransaction(this.name);
|
||||
// create the node data for the model
|
||||
const d = diagram.model.copyNodeData(this.archetypePartData);
|
||||
if (d !== null) {
|
||||
// adding data to model creates the actual Part
|
||||
diagram.model.addNodeData(d);
|
||||
const part = diagram.findPartForData(d);
|
||||
if (part !== null) {
|
||||
// assign the location
|
||||
part.location = new go.Point(pos.x + geo.bounds.width / 2, pos.y + geo.bounds.height / 2);
|
||||
// assign the Shape.geometry
|
||||
const shape = part.findObject('SHAPE') as go.Shape;
|
||||
if (shape !== null) shape.geometry = geo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.stopTool();
|
||||
if (started) diagram.commitTransaction(this.name);
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Geometry Reshaping</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="TypeScript: Allow the user to change a Shape by dragging a handle at a point of the Shape's Geometry." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../samples/assets/require.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
function init() {
|
||||
require(["GeometryReshapingScript"], function(app) {
|
||||
app.init();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width: 100%; height: 350px"></div>
|
||||
<p>
|
||||
The GeometryReshapingTool class allows for a Shape's Geometry to be modified by the user via the dragging of tool handles.
|
||||
Reshape handles are drawn as Adornments at each point in the geometry. It is defined in its own file, as <a href="GeometryReshapingTool.ts">GeometryReshapingTool.ts</a>.
|
||||
</p>
|
||||
<p>
|
||||
Usage can also be seen in the <a href="FreehandDrawing.html">Freehand Drawing</a> and <a href="PolygonDrawing.html">Polygon Drawing</a> samples.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js", "./GeometryReshapingTool.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.init = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
var GeometryReshapingTool_js_1 = require("./GeometryReshapingTool.js");
|
||||
function init() {
|
||||
if (window.goSamples)
|
||||
window.goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make;
|
||||
var myDiagram = $(go.Diagram, 'myDiagramDiv', // create a Diagram for the DIV HTML element
|
||||
{
|
||||
'undoManager.isEnabled': true // enable undo & redo
|
||||
});
|
||||
myDiagram.toolManager.mouseDownTools.insertAt(3, new GeometryReshapingTool_js_1.GeometryReshapingTool());
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, { reshapable: true }, // GeometryReshapingTool assumes nonexistent Part.reshapeObjectName would be "SHAPE"
|
||||
$(go.Shape, { name: 'SHAPE', fill: 'lightgray', strokeWidth: 1.5 }, new go.Binding('geometryString', 'geo').makeTwoWay()));
|
||||
myDiagram.model = new go.GraphLinksModel([{ geo: 'F M0 145 L75 2 L131 87 L195 0 L249 143z', key: -1 }], []);
|
||||
// Attach to the window for console manipulation
|
||||
window.myDiagram = myDiagram;
|
||||
}
|
||||
exports.init = init;
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
import { GeometryReshapingTool } from './GeometryReshapingTool.js';
|
||||
|
||||
export function init() {
|
||||
if ((window as any).goSamples) (window as any).goSamples(); // init for these samples -- you don't need to call this
|
||||
|
||||
const $ = go.GraphObject.make;
|
||||
|
||||
const myDiagram = $(go.Diagram, 'myDiagramDiv', // create a Diagram for the DIV HTML element
|
||||
{
|
||||
'undoManager.isEnabled': true // enable undo & redo
|
||||
});
|
||||
|
||||
myDiagram.toolManager.mouseDownTools.insertAt(3, new GeometryReshapingTool());
|
||||
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node,
|
||||
{ reshapable: true }, // GeometryReshapingTool assumes nonexistent Part.reshapeObjectName would be "SHAPE"
|
||||
$(go.Shape,
|
||||
{ name: 'SHAPE', fill: 'lightgray', strokeWidth: 1.5 },
|
||||
new go.Binding('geometryString', 'geo').makeTwoWay()
|
||||
)
|
||||
);
|
||||
|
||||
myDiagram.model = new go.GraphLinksModel([{ geo: 'F M0 145 L75 2 L131 87 L195 0 L249 143z', key: -1 }], []);
|
||||
|
||||
// Attach to the window for console manipulation
|
||||
(window as any).myDiagram = myDiagram;
|
||||
}
|
||||
+400
@@ -0,0 +1,400 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.GeometryReshapingTool = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
/**
|
||||
* The GeometryReshapingTool class allows for a Shape's Geometry to be modified by the user
|
||||
* via the dragging of tool handles.
|
||||
* This does not handle Links, whose routes should be reshaped by the LinkReshapingTool.
|
||||
* The {@link #reshapeObjectName} needs to identify the named {@link Shape} within the
|
||||
* selected {@link Part}.
|
||||
* If the shape cannot be found or if its {@link Shape#geometry} is not of type {@link Geometry.Path},
|
||||
* this will not show any GeometryReshaping {@link Adornment}.
|
||||
* At the current time this tool does not support adding or removing {@link PathSegment}s to the Geometry.
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/GeometryReshaping.html">Geometry Reshaping</a> sample.
|
||||
* @category Tool Extension
|
||||
*/
|
||||
var GeometryReshapingTool = /** @class */ (function (_super) {
|
||||
__extends(GeometryReshapingTool, _super);
|
||||
/**
|
||||
* Constructs a GeometryReshapingTool and sets the handle and name of the tool.
|
||||
*/
|
||||
function GeometryReshapingTool() {
|
||||
var _this = _super.call(this) || this;
|
||||
_this._reshapeObjectName = 'SHAPE'; // ??? can't add Part.reshapeObjectName property
|
||||
// there's no Part.reshapeAdornmentTemplate either
|
||||
// internal state
|
||||
_this._handle = null;
|
||||
_this._adornedShape = null;
|
||||
_this._originalGeometry = null; // in case the tool is cancelled and the UndoManager is not enabled
|
||||
var h = new go.Shape();
|
||||
h.figure = 'Diamond';
|
||||
h.desiredSize = new go.Size(7, 7);
|
||||
h.fill = 'lightblue';
|
||||
h.stroke = 'dodgerblue';
|
||||
h.cursor = 'move';
|
||||
_this._handleArchetype = h;
|
||||
_this.name = 'GeometryReshaping';
|
||||
return _this;
|
||||
}
|
||||
Object.defineProperty(GeometryReshapingTool.prototype, "handleArchetype", {
|
||||
/**
|
||||
* A small GraphObject used as a reshape handle for each segment.
|
||||
* The default GraphObject is a small blue diamond.
|
||||
*/
|
||||
get: function () { return this._handleArchetype; },
|
||||
set: function (value) { this._handleArchetype = value; },
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(GeometryReshapingTool.prototype, "reshapeObjectName", {
|
||||
/**
|
||||
* The name of the GraphObject to be reshaped.
|
||||
*/
|
||||
get: function () { return this._reshapeObjectName; },
|
||||
set: function (value) { this._reshapeObjectName = value; },
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(GeometryReshapingTool.prototype, "handle", {
|
||||
/**
|
||||
* This read-only property returns the {@link GraphObject} that is the tool handle being dragged by the user.
|
||||
* This will be contained by an {@link Adornment} whose category is "GeometryReshaping".
|
||||
* Its {@link Adornment#adornedObject} is the same as the {@link #adornedShape}.
|
||||
*/
|
||||
get: function () { return this._handle; },
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(GeometryReshapingTool.prototype, "adornedShape", {
|
||||
/**
|
||||
* Gets the {@link Shape} that is being reshaped.
|
||||
* This must be contained within the selected Part.
|
||||
*/
|
||||
get: function () { return this._adornedShape; },
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(GeometryReshapingTool.prototype, "originalGeometry", {
|
||||
/**
|
||||
* This read-only property remembers the original value for {@link Shape#geometry},
|
||||
* so that it can be restored if this tool is cancelled.
|
||||
*/
|
||||
get: function () { return this._originalGeometry; },
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
/**
|
||||
* Show an {@link Adornment} with a reshape handle at each point of the geometry.
|
||||
* Don't show anything if {@link #reshapeObjectName} doesn't identify a {@link Shape}
|
||||
* that has a {@link Shape#geometry} of type {@link Geometry.Path}.
|
||||
*/
|
||||
GeometryReshapingTool.prototype.updateAdornments = function (part) {
|
||||
if (part === null || part instanceof go.Link)
|
||||
return; // this tool never applies to Links
|
||||
if (part.isSelected && !this.diagram.isReadOnly) {
|
||||
var selelt = part.findObject(this.reshapeObjectName);
|
||||
if (selelt instanceof go.Shape && selelt.geometry !== null &&
|
||||
selelt.actualBounds.isReal() && selelt.isVisibleObject() &&
|
||||
part.canReshape() && part.actualBounds.isReal() && part.isVisible() &&
|
||||
selelt.geometry.type === go.Geometry.Path) {
|
||||
var adornment = part.findAdornment(this.name);
|
||||
if (adornment === null) {
|
||||
adornment = this.makeAdornment(selelt);
|
||||
}
|
||||
if (adornment !== null) {
|
||||
// update the position/alignment of each handle
|
||||
var geo_1 = selelt.geometry;
|
||||
var b_1 = geo_1.bounds;
|
||||
// update the size of the adornment
|
||||
var body = adornment.findObject('BODY');
|
||||
if (body !== null)
|
||||
body.desiredSize = b_1.size;
|
||||
adornment.elements.each(function (h) {
|
||||
if (h._typ === undefined)
|
||||
return;
|
||||
var fig = geo_1.figures.elt(h._fig);
|
||||
var seg = fig.segments.elt(h._seg);
|
||||
var x = 0;
|
||||
var y = 0;
|
||||
switch (h._typ) {
|
||||
case 0:
|
||||
x = fig.startX;
|
||||
y = fig.startY;
|
||||
break;
|
||||
case 1:
|
||||
x = seg.endX;
|
||||
y = seg.endY;
|
||||
break;
|
||||
case 2:
|
||||
x = seg.point1X;
|
||||
y = seg.point1Y;
|
||||
break;
|
||||
case 3:
|
||||
x = seg.point2X;
|
||||
y = seg.point2Y;
|
||||
break;
|
||||
}
|
||||
h.alignment = new go.Spot(0, 0, x - b_1.x, y - b_1.y);
|
||||
});
|
||||
part.addAdornment(this.name, adornment);
|
||||
adornment.location = selelt.getDocumentPoint(go.Spot.TopLeft);
|
||||
adornment.angle = selelt.getDocumentAngle();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
part.removeAdornment(this.name);
|
||||
};
|
||||
/**
|
||||
* @hidden @internal
|
||||
*/
|
||||
GeometryReshapingTool.prototype.makeAdornment = function (selelt) {
|
||||
var adornment = new go.Adornment();
|
||||
adornment.type = go.Panel.Spot;
|
||||
adornment.locationObjectName = 'BODY';
|
||||
adornment.locationSpot = new go.Spot(0, 0, -selelt.strokeWidth / 2, -selelt.strokeWidth / 2);
|
||||
var h = new go.Shape();
|
||||
h.name = 'BODY';
|
||||
h.fill = null;
|
||||
h.stroke = null;
|
||||
h.strokeWidth = 0;
|
||||
adornment.add(h);
|
||||
var geo = selelt.geometry;
|
||||
if (geo !== null) {
|
||||
// requires Path Geometry, checked above in updateAdornments
|
||||
for (var f = 0; f < geo.figures.count; f++) {
|
||||
var fig = geo.figures.elt(f);
|
||||
for (var g = 0; g < fig.segments.count; g++) {
|
||||
var seg = fig.segments.elt(g);
|
||||
if (g === 0) {
|
||||
h = this.makeHandle(selelt, fig, seg);
|
||||
if (h !== null) {
|
||||
h._typ = 0;
|
||||
h._fig = f;
|
||||
h._seg = g;
|
||||
adornment.add(h);
|
||||
}
|
||||
}
|
||||
h = this.makeHandle(selelt, fig, seg);
|
||||
if (h !== null) {
|
||||
h._typ = 1;
|
||||
h._fig = f;
|
||||
h._seg = g;
|
||||
adornment.add(h);
|
||||
}
|
||||
if (seg.type === go.PathSegment.QuadraticBezier || seg.type === go.PathSegment.Bezier) {
|
||||
h = this.makeHandle(selelt, fig, seg);
|
||||
if (h !== null) {
|
||||
h._typ = 2;
|
||||
h._fig = f;
|
||||
h._seg = g;
|
||||
adornment.add(h);
|
||||
}
|
||||
if (seg.type === go.PathSegment.Bezier) {
|
||||
h = this.makeHandle(selelt, fig, seg);
|
||||
if (h !== null) {
|
||||
h._typ = 3;
|
||||
h._fig = f;
|
||||
h._seg = g;
|
||||
adornment.add(h);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
adornment.category = this.name;
|
||||
adornment.adornedObject = selelt;
|
||||
return adornment;
|
||||
};
|
||||
/**
|
||||
* @hidden @internal
|
||||
*/
|
||||
GeometryReshapingTool.prototype.makeHandle = function (selelt, fig, seg) {
|
||||
var h = this.handleArchetype;
|
||||
if (h === null)
|
||||
return null;
|
||||
return h.copy();
|
||||
};
|
||||
/**
|
||||
* This tool may run when there is a mouse-down event on a reshape handle.
|
||||
*/
|
||||
GeometryReshapingTool.prototype.canStart = function () {
|
||||
if (!this.isEnabled)
|
||||
return false;
|
||||
var diagram = this.diagram;
|
||||
if (diagram.isReadOnly)
|
||||
return false;
|
||||
if (!diagram.allowReshape)
|
||||
return false;
|
||||
if (!diagram.lastInput.left)
|
||||
return false;
|
||||
var h = this.findToolHandleAt(diagram.firstInput.documentPoint, this.name);
|
||||
return (h !== null);
|
||||
};
|
||||
/**
|
||||
* Start reshaping, if {@link #findToolHandleAt} finds a reshape handle at the mouse down point.
|
||||
*
|
||||
* If successful this sets {@link #handle} to be the reshape handle that it finds
|
||||
* and {@link #adornedShape} to be the {@link Shape} being reshaped.
|
||||
* It also remembers the original geometry in case this tool is cancelled.
|
||||
* And it starts a transaction.
|
||||
*/
|
||||
GeometryReshapingTool.prototype.doActivate = function () {
|
||||
var diagram = this.diagram;
|
||||
this._handle = this.findToolHandleAt(diagram.firstInput.documentPoint, this.name);
|
||||
if (this._handle === null)
|
||||
return;
|
||||
var shape = this._handle.part.adornedObject;
|
||||
if (!shape)
|
||||
return;
|
||||
this._adornedShape = shape;
|
||||
diagram.isMouseCaptured = true;
|
||||
this.startTransaction(this.name);
|
||||
this._originalGeometry = shape.geometry;
|
||||
this.isActive = true;
|
||||
};
|
||||
/**
|
||||
* This stops the current reshaping operation with the Shape as it is.
|
||||
*/
|
||||
GeometryReshapingTool.prototype.doDeactivate = function () {
|
||||
this.stopTransaction();
|
||||
this._handle = null;
|
||||
this._adornedShape = null;
|
||||
var diagram = this.diagram;
|
||||
diagram.isMouseCaptured = false;
|
||||
this.isActive = false;
|
||||
};
|
||||
/**
|
||||
* Restore the shape to be the original geometry and stop this tool.
|
||||
*/
|
||||
GeometryReshapingTool.prototype.doCancel = function () {
|
||||
var shape = this._adornedShape;
|
||||
if (shape !== null) {
|
||||
// explicitly restore the original route, in case !UndoManager.isEnabled
|
||||
shape.geometry = this._originalGeometry;
|
||||
}
|
||||
this.stopTool();
|
||||
};
|
||||
/**
|
||||
* Call {@link #reshape} with a new point determined by the mouse
|
||||
* to change the geometry of the {@link #adornedShape}.
|
||||
*/
|
||||
GeometryReshapingTool.prototype.doMouseMove = function () {
|
||||
var diagram = this.diagram;
|
||||
if (this.isActive) {
|
||||
var newpt = this.computeReshape(diagram.lastInput.documentPoint);
|
||||
this.reshape(newpt);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Reshape the Shape's geometry with a point based on the most recent mouse point by calling {@link #reshape},
|
||||
* and then stop this tool.
|
||||
*/
|
||||
GeometryReshapingTool.prototype.doMouseUp = function () {
|
||||
var diagram = this.diagram;
|
||||
if (this.isActive) {
|
||||
var newpt = this.computeReshape(diagram.lastInput.documentPoint);
|
||||
this.reshape(newpt);
|
||||
this.transactionResult = this.name; // success
|
||||
}
|
||||
this.stopTool();
|
||||
};
|
||||
/**
|
||||
* Change the geometry of the {@link #adornedShape} by moving the point corresponding to the current
|
||||
* {@link #handle} to be at the given {@link Point}.
|
||||
* This is called by {@link #doMouseMove} and {@link #doMouseUp} with the result of calling
|
||||
* {@link #computeReshape} to constrain the input point.
|
||||
* @param {Point} newPoint the value of the call to {@link #computeReshape}.
|
||||
*/
|
||||
GeometryReshapingTool.prototype.reshape = function (newPoint) {
|
||||
var shape = this.adornedShape;
|
||||
if (shape === null || shape.geometry === null)
|
||||
return;
|
||||
var locpt = shape.getLocalPoint(newPoint);
|
||||
var geo = shape.geometry.copy();
|
||||
var type = this.handle._typ;
|
||||
if (type === undefined)
|
||||
return;
|
||||
var fig = geo.figures.elt(this.handle._fig);
|
||||
var seg = fig.segments.elt(this.handle._seg);
|
||||
switch (type) {
|
||||
case 0:
|
||||
fig.startX = locpt.x;
|
||||
fig.startY = locpt.y;
|
||||
break;
|
||||
case 1:
|
||||
seg.endX = locpt.x;
|
||||
seg.endY = locpt.y;
|
||||
break;
|
||||
case 2:
|
||||
seg.point1X = locpt.x;
|
||||
seg.point1Y = locpt.y;
|
||||
break;
|
||||
case 3:
|
||||
seg.point2X = locpt.x;
|
||||
seg.point2Y = locpt.y;
|
||||
break;
|
||||
}
|
||||
var offset = geo.normalize(); // avoid any negative coordinates in the geometry
|
||||
shape.desiredSize = new go.Size(NaN, NaN); // clear the desiredSize so Geometry can determine size
|
||||
shape.geometry = geo; // modify the Shape
|
||||
var part = shape.part; // move the Part holding the Shape
|
||||
if (part === null)
|
||||
return;
|
||||
part.ensureBounds();
|
||||
if (part.locationObject !== shape && !part.locationSpot.equals(go.Spot.Center)) { // but only if the locationSpot isn't Center
|
||||
// support the whole Node being rotated
|
||||
part.move(part.position.copy().subtract(offset.rotate(part.angle)));
|
||||
}
|
||||
this.updateAdornments(part); // update any Adornments of the Part
|
||||
this.diagram.maybeUpdate(); // force more frequent drawing for smoother looking behavior
|
||||
};
|
||||
/**
|
||||
* This is called by {@link #doMouseMove} and {@link #doMouseUp} to limit the input point
|
||||
* before calling {@link #reshape}.
|
||||
* By default, this doesn't limit the input point.
|
||||
* @param {Point} p the point where the handle is being dragged.
|
||||
* @return {Point}
|
||||
*/
|
||||
GeometryReshapingTool.prototype.computeReshape = function (p) {
|
||||
return p; // no constraints on the points
|
||||
};
|
||||
return GeometryReshapingTool;
|
||||
}(go.Tool));
|
||||
exports.GeometryReshapingTool = GeometryReshapingTool;
|
||||
});
|
||||
+340
@@ -0,0 +1,340 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
|
||||
/**
|
||||
* The GeometryReshapingTool class allows for a Shape's Geometry to be modified by the user
|
||||
* via the dragging of tool handles.
|
||||
* This does not handle Links, whose routes should be reshaped by the LinkReshapingTool.
|
||||
* The {@link #reshapeObjectName} needs to identify the named {@link Shape} within the
|
||||
* selected {@link Part}.
|
||||
* If the shape cannot be found or if its {@link Shape#geometry} is not of type {@link Geometry.Path},
|
||||
* this will not show any GeometryReshaping {@link Adornment}.
|
||||
* At the current time this tool does not support adding or removing {@link PathSegment}s to the Geometry.
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/GeometryReshaping.html">Geometry Reshaping</a> sample.
|
||||
* @category Tool Extension
|
||||
*/
|
||||
export class GeometryReshapingTool extends go.Tool {
|
||||
|
||||
private _handleArchetype: go.GraphObject;
|
||||
private _reshapeObjectName = 'SHAPE'; // ??? can't add Part.reshapeObjectName property
|
||||
// there's no Part.reshapeAdornmentTemplate either
|
||||
|
||||
// internal state
|
||||
private _handle: go.GraphObject | null = null;
|
||||
private _adornedShape: go.Shape | null = null;
|
||||
private _originalGeometry: go.Geometry | null = null; // in case the tool is cancelled and the UndoManager is not enabled
|
||||
|
||||
/**
|
||||
* Constructs a GeometryReshapingTool and sets the handle and name of the tool.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
const h: go.Shape = new go.Shape();
|
||||
h.figure = 'Diamond';
|
||||
h.desiredSize = new go.Size(7, 7);
|
||||
h.fill = 'lightblue';
|
||||
h.stroke = 'dodgerblue';
|
||||
h.cursor = 'move';
|
||||
this._handleArchetype = h;
|
||||
this.name = 'GeometryReshaping';
|
||||
}
|
||||
|
||||
/**
|
||||
* A small GraphObject used as a reshape handle for each segment.
|
||||
* The default GraphObject is a small blue diamond.
|
||||
*/
|
||||
get handleArchetype(): go.GraphObject { return this._handleArchetype; }
|
||||
set handleArchetype(value: go.GraphObject) { this._handleArchetype = value; }
|
||||
|
||||
/**
|
||||
* The name of the GraphObject to be reshaped.
|
||||
*/
|
||||
get reshapeObjectName(): string { return this._reshapeObjectName; }
|
||||
set reshapeObjectName(value: string) { this._reshapeObjectName = value; }
|
||||
|
||||
/**
|
||||
* This read-only property returns the {@link GraphObject} that is the tool handle being dragged by the user.
|
||||
* This will be contained by an {@link Adornment} whose category is "GeometryReshaping".
|
||||
* Its {@link Adornment#adornedObject} is the same as the {@link #adornedShape}.
|
||||
*/
|
||||
get handle(): go.GraphObject | null { return this._handle; }
|
||||
|
||||
/**
|
||||
* Gets the {@link Shape} that is being reshaped.
|
||||
* This must be contained within the selected Part.
|
||||
*/
|
||||
get adornedShape(): go.Shape | null { return this._adornedShape; }
|
||||
|
||||
/**
|
||||
* This read-only property remembers the original value for {@link Shape#geometry},
|
||||
* so that it can be restored if this tool is cancelled.
|
||||
*/
|
||||
get originalGeometry(): go.Geometry | null { return this._originalGeometry; }
|
||||
|
||||
/**
|
||||
* Show an {@link Adornment} with a reshape handle at each point of the geometry.
|
||||
* Don't show anything if {@link #reshapeObjectName} doesn't identify a {@link Shape}
|
||||
* that has a {@link Shape#geometry} of type {@link Geometry.Path}.
|
||||
*/
|
||||
public updateAdornments(part: go.Part): void {
|
||||
if (part === null || part instanceof go.Link) return; // this tool never applies to Links
|
||||
if (part.isSelected && !this.diagram.isReadOnly) {
|
||||
const selelt = part.findObject(this.reshapeObjectName);
|
||||
if (selelt instanceof go.Shape && selelt.geometry !== null &&
|
||||
selelt.actualBounds.isReal() && selelt.isVisibleObject() &&
|
||||
part.canReshape() && part.actualBounds.isReal() && part.isVisible() &&
|
||||
selelt.geometry.type === go.Geometry.Path) {
|
||||
let adornment = part.findAdornment(this.name);
|
||||
if (adornment === null) {
|
||||
adornment = this.makeAdornment(selelt);
|
||||
}
|
||||
if (adornment !== null) {
|
||||
// update the position/alignment of each handle
|
||||
const geo = selelt.geometry;
|
||||
const b = geo.bounds;
|
||||
// update the size of the adornment
|
||||
const body = adornment.findObject('BODY');
|
||||
if (body !== null) body.desiredSize = b.size;
|
||||
adornment.elements.each(function(h: any) {
|
||||
if (h._typ === undefined) return;
|
||||
const fig = geo.figures.elt(h._fig);
|
||||
const seg = fig.segments.elt(h._seg);
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
switch (h._typ) {
|
||||
case 0: x = fig.startX; y = fig.startY; break;
|
||||
case 1: x = seg.endX; y = seg.endY; break;
|
||||
case 2: x = seg.point1X; y = seg.point1Y; break;
|
||||
case 3: x = seg.point2X; y = seg.point2Y; break;
|
||||
}
|
||||
h.alignment = new go.Spot(0, 0, x - b.x, y - b.y);
|
||||
});
|
||||
|
||||
part.addAdornment(this.name, adornment);
|
||||
adornment.location = selelt.getDocumentPoint(go.Spot.TopLeft);
|
||||
adornment.angle = selelt.getDocumentAngle();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
part.removeAdornment(this.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden @internal
|
||||
*/
|
||||
public makeAdornment(selelt: go.Shape): go.Adornment {
|
||||
const adornment = new go.Adornment();
|
||||
adornment.type = go.Panel.Spot;
|
||||
adornment.locationObjectName = 'BODY';
|
||||
adornment.locationSpot = new go.Spot(0, 0, -selelt.strokeWidth / 2, -selelt.strokeWidth / 2);
|
||||
let h: any = new go.Shape();
|
||||
h.name = 'BODY';
|
||||
h.fill = null;
|
||||
h.stroke = null;
|
||||
h.strokeWidth = 0;
|
||||
adornment.add(h);
|
||||
|
||||
const geo = selelt.geometry;
|
||||
if (geo !== null) {
|
||||
// requires Path Geometry, checked above in updateAdornments
|
||||
for (let f = 0; f < geo.figures.count; f++) {
|
||||
const fig = geo.figures.elt(f);
|
||||
for (let g = 0; g < fig.segments.count; g++) {
|
||||
const seg = fig.segments.elt(g);
|
||||
if (g === 0) {
|
||||
h = this.makeHandle(selelt, fig, seg);
|
||||
if (h !== null) {
|
||||
h._typ = 0;
|
||||
h._fig = f;
|
||||
h._seg = g;
|
||||
adornment.add(h);
|
||||
}
|
||||
}
|
||||
h = this.makeHandle(selelt, fig, seg);
|
||||
if (h !== null) {
|
||||
h._typ = 1;
|
||||
h._fig = f;
|
||||
h._seg = g;
|
||||
adornment.add(h);
|
||||
}
|
||||
if (seg.type === go.PathSegment.QuadraticBezier || seg.type === go.PathSegment.Bezier) {
|
||||
h = this.makeHandle(selelt, fig, seg);
|
||||
if (h !== null) {
|
||||
h._typ = 2;
|
||||
h._fig = f;
|
||||
h._seg = g;
|
||||
adornment.add(h);
|
||||
}
|
||||
if (seg.type === go.PathSegment.Bezier) {
|
||||
h = this.makeHandle(selelt, fig, seg);
|
||||
if (h !== null) {
|
||||
h._typ = 3;
|
||||
h._fig = f;
|
||||
h._seg = g;
|
||||
adornment.add(h);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
adornment.category = this.name;
|
||||
adornment.adornedObject = selelt;
|
||||
return adornment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden @internal
|
||||
*/
|
||||
public makeHandle(selelt: go.GraphObject, fig: go.PathFigure, seg: go.PathSegment): go.GraphObject | null {
|
||||
const h = this.handleArchetype;
|
||||
if (h === null) return null;
|
||||
return h.copy();
|
||||
}
|
||||
|
||||
/**
|
||||
* This tool may run when there is a mouse-down event on a reshape handle.
|
||||
*/
|
||||
public canStart(): boolean {
|
||||
if (!this.isEnabled) return false;
|
||||
|
||||
const diagram = this.diagram;
|
||||
if (diagram.isReadOnly) return false;
|
||||
if (!diagram.allowReshape) return false;
|
||||
if (!diagram.lastInput.left) return false;
|
||||
const h = this.findToolHandleAt(diagram.firstInput.documentPoint, this.name);
|
||||
return (h !== null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start reshaping, if {@link #findToolHandleAt} finds a reshape handle at the mouse down point.
|
||||
*
|
||||
* If successful this sets {@link #handle} to be the reshape handle that it finds
|
||||
* and {@link #adornedShape} to be the {@link Shape} being reshaped.
|
||||
* It also remembers the original geometry in case this tool is cancelled.
|
||||
* And it starts a transaction.
|
||||
*/
|
||||
public doActivate(): void {
|
||||
const diagram = this.diagram;
|
||||
this._handle = this.findToolHandleAt(diagram.firstInput.documentPoint, this.name);
|
||||
if (this._handle === null) return;
|
||||
const shape = (this._handle.part as go.Adornment).adornedObject as go.Shape;
|
||||
if (!shape) return;
|
||||
this._adornedShape = shape;
|
||||
diagram.isMouseCaptured = true;
|
||||
this.startTransaction(this.name);
|
||||
this._originalGeometry = shape.geometry;
|
||||
this.isActive = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This stops the current reshaping operation with the Shape as it is.
|
||||
*/
|
||||
public doDeactivate(): void {
|
||||
this.stopTransaction();
|
||||
|
||||
this._handle = null;
|
||||
this._adornedShape = null;
|
||||
const diagram = this.diagram;
|
||||
diagram.isMouseCaptured = false;
|
||||
this.isActive = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the shape to be the original geometry and stop this tool.
|
||||
*/
|
||||
public doCancel(): void {
|
||||
const shape = this._adornedShape;
|
||||
if (shape !== null) {
|
||||
// explicitly restore the original route, in case !UndoManager.isEnabled
|
||||
shape.geometry = this._originalGeometry;
|
||||
}
|
||||
this.stopTool();
|
||||
}
|
||||
|
||||
/**
|
||||
* Call {@link #reshape} with a new point determined by the mouse
|
||||
* to change the geometry of the {@link #adornedShape}.
|
||||
*/
|
||||
public doMouseMove(): void {
|
||||
const diagram = this.diagram;
|
||||
if (this.isActive) {
|
||||
const newpt = this.computeReshape(diagram.lastInput.documentPoint);
|
||||
this.reshape(newpt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reshape the Shape's geometry with a point based on the most recent mouse point by calling {@link #reshape},
|
||||
* and then stop this tool.
|
||||
*/
|
||||
public doMouseUp(): void {
|
||||
const diagram = this.diagram;
|
||||
if (this.isActive) {
|
||||
const newpt = this.computeReshape(diagram.lastInput.documentPoint);
|
||||
this.reshape(newpt);
|
||||
this.transactionResult = this.name; // success
|
||||
}
|
||||
this.stopTool();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the geometry of the {@link #adornedShape} by moving the point corresponding to the current
|
||||
* {@link #handle} to be at the given {@link Point}.
|
||||
* This is called by {@link #doMouseMove} and {@link #doMouseUp} with the result of calling
|
||||
* {@link #computeReshape} to constrain the input point.
|
||||
* @param {Point} newPoint the value of the call to {@link #computeReshape}.
|
||||
*/
|
||||
public reshape(newPoint: go.Point): void {
|
||||
const shape = this.adornedShape;
|
||||
if (shape === null || shape.geometry === null) return;
|
||||
const locpt = shape.getLocalPoint(newPoint);
|
||||
const geo = shape.geometry.copy();
|
||||
const type = (this.handle as any)._typ;
|
||||
if (type === undefined) return;
|
||||
const fig = geo.figures.elt((this.handle as any)._fig);
|
||||
const seg = fig.segments.elt((this.handle as any)._seg);
|
||||
switch (type) {
|
||||
case 0: fig.startX = locpt.x; fig.startY = locpt.y; break;
|
||||
case 1: seg.endX = locpt.x; seg.endY = locpt.y; break;
|
||||
case 2: seg.point1X = locpt.x; seg.point1Y = locpt.y; break;
|
||||
case 3: seg.point2X = locpt.x; seg.point2Y = locpt.y; break;
|
||||
}
|
||||
const offset = geo.normalize(); // avoid any negative coordinates in the geometry
|
||||
shape.desiredSize = new go.Size(NaN, NaN); // clear the desiredSize so Geometry can determine size
|
||||
shape.geometry = geo; // modify the Shape
|
||||
const part = shape.part; // move the Part holding the Shape
|
||||
if (part === null) return;
|
||||
part.ensureBounds();
|
||||
if (part.locationObject !== shape && !part.locationSpot.equals(go.Spot.Center)) { // but only if the locationSpot isn't Center
|
||||
// support the whole Node being rotated
|
||||
part.move(part.position.copy().subtract(offset.rotate(part.angle)));
|
||||
}
|
||||
this.updateAdornments(part); // update any Adornments of the Part
|
||||
this.diagram.maybeUpdate(); // force more frequent drawing for smoother looking behavior
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called by {@link #doMouseMove} and {@link #doMouseUp} to limit the input point
|
||||
* before calling {@link #reshape}.
|
||||
* By default, this doesn't limit the input point.
|
||||
* @param {Point} p the point where the handle is being dragged.
|
||||
* @return {Point}
|
||||
*/
|
||||
public computeReshape(p: go.Point): go.Point {
|
||||
return p; // no constraints on the points
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Guided Dragging</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="TypeScript: A demonstration of the GuidedDraggingTool extension." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../samples/assets/require.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
function init() {
|
||||
require(["GuidedDraggingScript"], function(app) {
|
||||
app.init();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<!-- The DIV for the Diagram needs an explicit size or else we won't see anything.
|
||||
Also add a border to help see the edges. -->
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:400px; height:400px"></div>
|
||||
<p>
|
||||
This custom <a>DraggingTool</a> class makes guidelines visible as a Part is dragged around a Diagram and is nearly
|
||||
aligned with another Part.
|
||||
If a locationObjectName is set, then this aligns <a>Part.locationObject</a>s instead.
|
||||
The tool is defined in its own file, as <a href="GuidedDraggingTool.ts">GuidedDraggingTool.ts</a>.
|
||||
</p>
|
||||
<p>
|
||||
Usage can also be seen in the <a href="FloorPlanEditor.html">Floor Plan Editor</a> sample.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js", "./GuidedDraggingTool.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.init = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
var GuidedDraggingTool_js_1 = require("./GuidedDraggingTool.js");
|
||||
function init() {
|
||||
if (window.goSamples)
|
||||
window.goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make; // for conciseness in defining templates
|
||||
var myDiagram = $(go.Diagram, 'myDiagramDiv', // create a Diagram for the DIV HTML element
|
||||
{
|
||||
draggingTool: new GuidedDraggingTool_js_1.GuidedDraggingTool(),
|
||||
'draggingTool.horizontalGuidelineColor': 'blue',
|
||||
'draggingTool.verticalGuidelineColor': 'blue',
|
||||
'draggingTool.centerGuidelineColor': 'green',
|
||||
'draggingTool.guidelineWidth': 1,
|
||||
'undoManager.isEnabled': true // enable undo & redo
|
||||
});
|
||||
// define a simple Node template
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, 'Auto', // the Shape will go around the TextBlock
|
||||
$(go.Shape, 'RoundedRectangle', { strokeWidth: 0 },
|
||||
// Shape.fill is bound to Node.data.color
|
||||
new go.Binding('fill', 'color')), $(go.TextBlock, { margin: 8 }, // some room around the text
|
||||
// TextBlock.text is bound to Node.data.key
|
||||
new go.Binding('text', 'key')));
|
||||
// but use the default Link template, by not setting Diagram.linkTemplate
|
||||
// create the model data that will be represented by Nodes and Links
|
||||
myDiagram.model = new go.GraphLinksModel([
|
||||
{ key: 'Alpha', color: 'lightblue' },
|
||||
{ key: 'Beta', color: 'orange' },
|
||||
{ key: 'Gamma', color: 'lightgreen' },
|
||||
{ key: 'Delta', color: 'pink' }
|
||||
], [
|
||||
{ from: 'Alpha', to: 'Beta' },
|
||||
{ from: 'Alpha', to: 'Gamma' },
|
||||
{ from: 'Beta', to: 'Beta' },
|
||||
{ from: 'Gamma', to: 'Delta' },
|
||||
{ from: 'Delta', to: 'Alpha' }
|
||||
]);
|
||||
// Attach to the window for console manipulation
|
||||
window.myDiagram = myDiagram;
|
||||
}
|
||||
exports.init = init;
|
||||
});
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
import { GuidedDraggingTool } from './GuidedDraggingTool.js';
|
||||
|
||||
export function init() {
|
||||
if ((window as any).goSamples) (window as any).goSamples(); // init for these samples -- you don't need to call this
|
||||
|
||||
const $ = go.GraphObject.make; // for conciseness in defining templates
|
||||
|
||||
const myDiagram = $(go.Diagram, 'myDiagramDiv', // create a Diagram for the DIV HTML element
|
||||
{
|
||||
draggingTool: new GuidedDraggingTool(), // defined in GuidedDraggingTool.js
|
||||
'draggingTool.horizontalGuidelineColor': 'blue',
|
||||
'draggingTool.verticalGuidelineColor': 'blue',
|
||||
'draggingTool.centerGuidelineColor': 'green',
|
||||
'draggingTool.guidelineWidth': 1,
|
||||
'undoManager.isEnabled': true // enable undo & redo
|
||||
});
|
||||
|
||||
// define a simple Node template
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, 'Auto', // the Shape will go around the TextBlock
|
||||
$(go.Shape, 'RoundedRectangle', { strokeWidth: 0 },
|
||||
// Shape.fill is bound to Node.data.color
|
||||
new go.Binding('fill', 'color')),
|
||||
$(go.TextBlock,
|
||||
{ margin: 8 }, // some room around the text
|
||||
// TextBlock.text is bound to Node.data.key
|
||||
new go.Binding('text', 'key'))
|
||||
);
|
||||
|
||||
// but use the default Link template, by not setting Diagram.linkTemplate
|
||||
|
||||
// create the model data that will be represented by Nodes and Links
|
||||
myDiagram.model = new go.GraphLinksModel(
|
||||
[
|
||||
{ key: 'Alpha', color: 'lightblue' },
|
||||
{ key: 'Beta', color: 'orange' },
|
||||
{ key: 'Gamma', color: 'lightgreen' },
|
||||
{ key: 'Delta', color: 'pink' }
|
||||
],
|
||||
[
|
||||
{ from: 'Alpha', to: 'Beta' },
|
||||
{ from: 'Alpha', to: 'Gamma' },
|
||||
{ from: 'Beta', to: 'Beta' },
|
||||
{ from: 'Gamma', to: 'Delta' },
|
||||
{ from: 'Delta', to: 'Alpha' }
|
||||
]);
|
||||
|
||||
// Attach to the window for console manipulation
|
||||
(window as any).myDiagram = myDiagram;
|
||||
}
|
||||
+526
@@ -0,0 +1,526 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.GuidedDraggingTool = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
/**
|
||||
* The GuidedDraggingTool class makes guidelines visible as the parts are dragged around a diagram
|
||||
* when the selected part is nearly aligned with another part.
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/GuidedDragging.html">Guided Dragging</a> sample.
|
||||
* @category Tool Extension
|
||||
*/
|
||||
var GuidedDraggingTool = /** @class */ (function (_super) {
|
||||
__extends(GuidedDraggingTool, _super);
|
||||
/**
|
||||
* Constructs a GuidedDraggingTool and sets up the temporary guideline parts.
|
||||
*/
|
||||
function GuidedDraggingTool() {
|
||||
var _this = _super.call(this) || this;
|
||||
// properties that the programmer can modify
|
||||
_this._guidelineSnapDistance = 6;
|
||||
_this._isGuidelineEnabled = true;
|
||||
_this._horizontalGuidelineColor = 'gray';
|
||||
_this._verticalGuidelineColor = 'gray';
|
||||
_this._centerGuidelineColor = 'gray';
|
||||
_this._guidelineWidth = 1;
|
||||
_this._searchDistance = 1000;
|
||||
_this._isGuidelineSnapEnabled = true;
|
||||
var partProperties = { layerName: 'Tool', isInDocumentBounds: false };
|
||||
var shapeProperties = { stroke: 'gray', isGeometryPositioned: true };
|
||||
var $ = go.GraphObject.make;
|
||||
// temporary parts for horizonal guidelines
|
||||
_this.guidelineHtop =
|
||||
$(go.Part, partProperties, $(go.Shape, shapeProperties, { geometryString: 'M0 0 100 0' }));
|
||||
_this.guidelineHbottom =
|
||||
$(go.Part, partProperties, $(go.Shape, shapeProperties, { geometryString: 'M0 0 100 0' }));
|
||||
_this.guidelineHcenter =
|
||||
$(go.Part, partProperties, $(go.Shape, shapeProperties, { geometryString: 'M0 0 100 0' }));
|
||||
// temporary parts for vertical guidelines
|
||||
_this.guidelineVleft =
|
||||
$(go.Part, partProperties, $(go.Shape, shapeProperties, { geometryString: 'M0 0 0 100' }));
|
||||
_this.guidelineVright =
|
||||
$(go.Part, partProperties, $(go.Shape, shapeProperties, { geometryString: 'M0 0 0 100' }));
|
||||
_this.guidelineVcenter =
|
||||
$(go.Part, partProperties, $(go.Shape, shapeProperties, { geometryString: 'M0 0 0 100' }));
|
||||
return _this;
|
||||
}
|
||||
Object.defineProperty(GuidedDraggingTool.prototype, "guidelineSnapDistance", {
|
||||
/**
|
||||
* Gets or sets the margin of error for which guidelines show up.
|
||||
*
|
||||
* The default value is 6.
|
||||
* Guidelines will show up when the aligned nods are ± 6px away from perfect alignment.
|
||||
*/
|
||||
get: function () { return this._guidelineSnapDistance; },
|
||||
set: function (val) {
|
||||
if (typeof val !== 'number' || isNaN(val) || val < 0)
|
||||
throw new Error('new value for GuideddraggingTool.guidelineSnapDistance must be a non-negative number');
|
||||
if (this._guidelineSnapDistance !== val) {
|
||||
this._guidelineSnapDistance = val;
|
||||
}
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(GuidedDraggingTool.prototype, "isGuidelineEnabled", {
|
||||
/**
|
||||
* Gets or sets whether the guidelines are enabled or disable.
|
||||
*
|
||||
* The default value is true.
|
||||
*/
|
||||
get: function () { return this._isGuidelineEnabled; },
|
||||
set: function (val) {
|
||||
if (typeof val !== 'boolean')
|
||||
throw new Error('new value for GuidedDraggingTool.isGuidelineEnabled must be a boolean value.');
|
||||
if (this._isGuidelineEnabled !== val) {
|
||||
this._isGuidelineEnabled = val;
|
||||
}
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(GuidedDraggingTool.prototype, "horizontalGuidelineColor", {
|
||||
/**
|
||||
* Gets or sets the color of horizontal guidelines.
|
||||
*
|
||||
* The default value is "gray".
|
||||
*/
|
||||
get: function () { return this._horizontalGuidelineColor; },
|
||||
set: function (val) {
|
||||
if (this._horizontalGuidelineColor !== val) {
|
||||
this._horizontalGuidelineColor = val;
|
||||
this.guidelineHbottom.elements.first().stroke = this._horizontalGuidelineColor;
|
||||
this.guidelineHtop.elements.first().stroke = this._horizontalGuidelineColor;
|
||||
}
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(GuidedDraggingTool.prototype, "verticalGuidelineColor", {
|
||||
/**
|
||||
* Gets or sets the color of vertical guidelines.
|
||||
*
|
||||
* The default value is "gray".
|
||||
*/
|
||||
get: function () { return this._verticalGuidelineColor; },
|
||||
set: function (val) {
|
||||
if (this._verticalGuidelineColor !== val) {
|
||||
this._verticalGuidelineColor = val;
|
||||
this.guidelineVleft.elements.first().stroke = this._verticalGuidelineColor;
|
||||
this.guidelineVright.elements.first().stroke = this._verticalGuidelineColor;
|
||||
}
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(GuidedDraggingTool.prototype, "centerGuidelineColor", {
|
||||
/**
|
||||
* Gets or sets the color of center guidelines.
|
||||
*
|
||||
* The default value is "gray".
|
||||
*/
|
||||
get: function () { return this._centerGuidelineColor; },
|
||||
set: function (val) {
|
||||
if (this._centerGuidelineColor !== val) {
|
||||
this._centerGuidelineColor = val;
|
||||
this.guidelineVcenter.elements.first().stroke = this._centerGuidelineColor;
|
||||
this.guidelineHcenter.elements.first().stroke = this._centerGuidelineColor;
|
||||
}
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(GuidedDraggingTool.prototype, "guidelineWidth", {
|
||||
/**
|
||||
* Gets or sets the width guidelines.
|
||||
*
|
||||
* The default value is 1.
|
||||
*/
|
||||
get: function () { return this._guidelineWidth; },
|
||||
set: function (val) {
|
||||
if (typeof val !== 'number' || isNaN(val) || val < 0)
|
||||
throw new Error('New value for GuidedDraggingTool.guidelineWidth must be a non-negative number.');
|
||||
if (this._guidelineWidth !== val) {
|
||||
this._guidelineWidth = val;
|
||||
this.guidelineVcenter.elements.first().strokeWidth = val;
|
||||
this.guidelineHcenter.elements.first().strokeWidth = val;
|
||||
this.guidelineVleft.elements.first().strokeWidth = val;
|
||||
this.guidelineVright.elements.first().strokeWidth = val;
|
||||
this.guidelineHbottom.elements.first().strokeWidth = val;
|
||||
this.guidelineHtop.elements.first().strokeWidth = val;
|
||||
}
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(GuidedDraggingTool.prototype, "searchDistance", {
|
||||
/**
|
||||
* Gets or sets the distance around the selected part to search for aligned parts.
|
||||
*
|
||||
* The default value is 1000.
|
||||
* Set this to Infinity if you want to search the entire diagram no matter how far away.
|
||||
*/
|
||||
get: function () { return this._searchDistance; },
|
||||
set: function (val) {
|
||||
if (typeof val !== 'number' || isNaN(val) || val <= 0)
|
||||
throw new Error('new value for GuidedDraggingTool.searchDistance must be a positive number.');
|
||||
if (this._searchDistance !== val) {
|
||||
this._searchDistance = val;
|
||||
}
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(GuidedDraggingTool.prototype, "isGuidelineSnapEnabled", {
|
||||
/**
|
||||
* Gets or sets whether snapping to guidelines is enabled.
|
||||
*
|
||||
* The default value is true.
|
||||
*/
|
||||
get: function () { return this._isGuidelineSnapEnabled; },
|
||||
set: function (val) {
|
||||
if (typeof val !== 'boolean')
|
||||
throw new Error('new value for GuidedDraggingTool.isGuidelineSnapEnabled must be a boolean.');
|
||||
if (this._isGuidelineSnapEnabled !== val) {
|
||||
this._isGuidelineSnapEnabled = val;
|
||||
}
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
/**
|
||||
* Removes all of the guidelines from the grid.
|
||||
*/
|
||||
GuidedDraggingTool.prototype.clearGuidelines = function () {
|
||||
this.diagram.remove(this.guidelineHbottom);
|
||||
this.diagram.remove(this.guidelineHcenter);
|
||||
this.diagram.remove(this.guidelineHtop);
|
||||
this.diagram.remove(this.guidelineVleft);
|
||||
this.diagram.remove(this.guidelineVright);
|
||||
this.diagram.remove(this.guidelineVcenter);
|
||||
};
|
||||
/**
|
||||
* Calls the base method and removes the guidelines from the graph.
|
||||
*/
|
||||
GuidedDraggingTool.prototype.doDeactivate = function () {
|
||||
_super.prototype.doDeactivate.call(this);
|
||||
// clear any guidelines when dragging is done
|
||||
this.clearGuidelines();
|
||||
};
|
||||
/**
|
||||
* Shows vertical and horizontal guidelines for the dragged part.
|
||||
*/
|
||||
GuidedDraggingTool.prototype.doDragOver = function (pt, obj) {
|
||||
// clear all existing guidelines in case either show... method decides to show a guideline
|
||||
this.clearGuidelines();
|
||||
// gets the selected part
|
||||
var draggingParts = this.copiedParts || this.draggedParts;
|
||||
if (draggingParts === null)
|
||||
return;
|
||||
var partItr = draggingParts.iterator;
|
||||
if (partItr.next()) {
|
||||
var part = partItr.key;
|
||||
this.showHorizontalMatches(part, this.isGuidelineEnabled, false);
|
||||
this.showVerticalMatches(part, this.isGuidelineEnabled, false);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* On a mouse-up, snaps the selected part to the nearest guideline.
|
||||
* If not snapping, the part remains at its position.
|
||||
*/
|
||||
GuidedDraggingTool.prototype.doDropOnto = function (pt, obj) {
|
||||
this.clearGuidelines();
|
||||
// gets the selected (perhaps copied) Part
|
||||
var draggingParts = this.copiedParts || this.draggedParts;
|
||||
if (draggingParts === null)
|
||||
return;
|
||||
var partItr = draggingParts.iterator;
|
||||
if (partItr.next()) {
|
||||
var part = partItr.key;
|
||||
// snaps only when the mouse is released without shift modifier
|
||||
var e = this.diagram.lastInput;
|
||||
var snap = this.isGuidelineSnapEnabled && !e.shift;
|
||||
this.showHorizontalMatches(part, false, snap); // false means don't show guidelines
|
||||
this.showVerticalMatches(part, false, snap);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* When nodes are shifted due to being guided upon a drop, make sure all connected link routes are invalidated,
|
||||
* since the node is likely to have moved a different amount than all its connected links in the regular
|
||||
* operation of the DraggingTool.
|
||||
*/
|
||||
GuidedDraggingTool.prototype.invalidateLinks = function (node) {
|
||||
if (node instanceof go.Node)
|
||||
node.invalidateConnectedLinks();
|
||||
};
|
||||
/**
|
||||
* This predicate decides whether or not the given Part should guide the dragged part.
|
||||
* @param {Part} part a stationary Part to which the dragged part might be aligned
|
||||
* @param {Part} guidedpart the Part being dragged
|
||||
*/
|
||||
GuidedDraggingTool.prototype.isGuiding = function (part, guidedpart) {
|
||||
return part instanceof go.Part &&
|
||||
!part.isSelected &&
|
||||
!(part instanceof go.Link) &&
|
||||
guidedpart instanceof go.Part &&
|
||||
part.containingGroup === guidedpart.containingGroup &&
|
||||
part.layer !== null && !part.layer.isTemporary;
|
||||
};
|
||||
/**
|
||||
* This finds parts that are aligned near the selected part along horizontal lines. It compares the selected
|
||||
* part to all parts within a rectangle approximately twice the {@link #searchDistance} wide.
|
||||
* The guidelines appear when a part is aligned within a margin-of-error equal to {@link #guidelineSnapDistance}.
|
||||
* @param {Node} part
|
||||
* @param {boolean} guideline if true, show guideline
|
||||
* @param {boolean} snap if true, snap the part to where the guideline would be
|
||||
*/
|
||||
GuidedDraggingTool.prototype.showHorizontalMatches = function (part, guideline, snap) {
|
||||
var _this = this;
|
||||
var objBounds = part.locationObject.getDocumentBounds();
|
||||
var p0 = objBounds.y;
|
||||
var p1 = objBounds.y + objBounds.height / 2;
|
||||
var p2 = objBounds.y + objBounds.height;
|
||||
var marginOfError = this.guidelineSnapDistance;
|
||||
var distance = this.searchDistance;
|
||||
// compares with parts within narrow vertical area
|
||||
var area = objBounds.copy();
|
||||
area.inflate(distance, marginOfError + 1);
|
||||
var otherObjs = this.diagram.findObjectsIn(area, function (obj) { return obj.part; }, function (p) { return _this.isGuiding(p, part); }, true);
|
||||
var bestDiff = marginOfError;
|
||||
var bestObj = null; // TS 2.6 won't let this be go.Part | null
|
||||
var bestSpot = go.Spot.Default;
|
||||
var bestOtherSpot = go.Spot.Default;
|
||||
// horizontal line -- comparing y-values
|
||||
otherObjs.each(function (other) {
|
||||
if (other === part)
|
||||
return; // ignore itself
|
||||
var otherBounds = other.locationObject.getDocumentBounds();
|
||||
var q0 = otherBounds.y;
|
||||
var q1 = otherBounds.y + otherBounds.height / 2;
|
||||
var q2 = otherBounds.y + otherBounds.height;
|
||||
// compare center with center of OTHER part
|
||||
if (Math.abs(p1 - q1) < bestDiff) {
|
||||
bestDiff = Math.abs(p1 - q1);
|
||||
bestObj = other;
|
||||
bestSpot = go.Spot.Center;
|
||||
bestOtherSpot = go.Spot.Center;
|
||||
}
|
||||
// compare top side with top and bottom sides of OTHER part
|
||||
if (Math.abs(p0 - q0) < bestDiff) {
|
||||
bestDiff = Math.abs(p0 - q0);
|
||||
bestObj = other;
|
||||
bestSpot = go.Spot.Top;
|
||||
bestOtherSpot = go.Spot.Top;
|
||||
}
|
||||
else if (Math.abs(p0 - q2) < bestDiff) {
|
||||
bestDiff = Math.abs(p0 - q2);
|
||||
bestObj = other;
|
||||
bestSpot = go.Spot.Top;
|
||||
bestOtherSpot = go.Spot.Bottom;
|
||||
}
|
||||
// compare bottom side with top and bottom sides of OTHER part
|
||||
if (Math.abs(p2 - q0) < bestDiff) {
|
||||
bestDiff = Math.abs(p2 - q0);
|
||||
bestObj = other;
|
||||
bestSpot = go.Spot.Bottom;
|
||||
bestOtherSpot = go.Spot.Top;
|
||||
}
|
||||
else if (Math.abs(p2 - q2) < bestDiff) {
|
||||
bestDiff = Math.abs(p2 - q2);
|
||||
bestObj = other;
|
||||
bestSpot = go.Spot.Bottom;
|
||||
bestOtherSpot = go.Spot.Bottom;
|
||||
}
|
||||
});
|
||||
if (bestObj !== null) {
|
||||
var offsetX = objBounds.x - part.actualBounds.x;
|
||||
var offsetY = objBounds.y - part.actualBounds.y;
|
||||
var bestBounds = bestObj.locationObject.getDocumentBounds();
|
||||
// line extends from x0 to x2
|
||||
var x0 = Math.min(objBounds.x, bestBounds.x) - 10;
|
||||
var x2 = Math.max(objBounds.x + objBounds.width, bestBounds.x + bestBounds.width) + 10;
|
||||
// find bestObj's desired Y
|
||||
var bestPoint = new go.Point().setRectSpot(bestBounds, bestOtherSpot);
|
||||
if (bestSpot === go.Spot.Center) {
|
||||
if (snap) {
|
||||
// call Part.move in order to automatically move member Parts of Groups
|
||||
part.move(new go.Point(objBounds.x - offsetX, bestPoint.y - objBounds.height / 2 - offsetY));
|
||||
this.invalidateLinks(part);
|
||||
}
|
||||
if (guideline) {
|
||||
this.guidelineHcenter.position = new go.Point(x0, bestPoint.y);
|
||||
this.guidelineHcenter.elt(0).width = x2 - x0;
|
||||
this.diagram.add(this.guidelineHcenter);
|
||||
}
|
||||
}
|
||||
else if (bestSpot === go.Spot.Top) {
|
||||
if (snap) {
|
||||
part.move(new go.Point(objBounds.x - offsetX, bestPoint.y - offsetY));
|
||||
this.invalidateLinks(part);
|
||||
}
|
||||
if (guideline) {
|
||||
this.guidelineHtop.position = new go.Point(x0, bestPoint.y);
|
||||
this.guidelineHtop.elt(0).width = x2 - x0;
|
||||
this.diagram.add(this.guidelineHtop);
|
||||
}
|
||||
}
|
||||
else if (bestSpot === go.Spot.Bottom) {
|
||||
if (snap) {
|
||||
part.move(new go.Point(objBounds.x - offsetX, bestPoint.y - objBounds.height - offsetY));
|
||||
this.invalidateLinks(part);
|
||||
}
|
||||
if (guideline) {
|
||||
this.guidelineHbottom.position = new go.Point(x0, bestPoint.y);
|
||||
this.guidelineHbottom.elt(0).width = x2 - x0;
|
||||
this.diagram.add(this.guidelineHbottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* This finds parts that are aligned near the selected part along vertical lines. It compares the selected
|
||||
* part to all parts within a rectangle approximately twice the {@link #searchDistance} tall.
|
||||
* The guidelines appear when a part is aligned within a margin-of-error equal to {@link #guidelineSnapDistance}.
|
||||
* @param {Part} part
|
||||
* @param {boolean} guideline if true, show guideline
|
||||
* @param {boolean} snap if true, don't show guidelines but just snap the part to where the guideline would be
|
||||
*/
|
||||
GuidedDraggingTool.prototype.showVerticalMatches = function (part, guideline, snap) {
|
||||
var _this = this;
|
||||
var objBounds = part.locationObject.getDocumentBounds();
|
||||
var p0 = objBounds.x;
|
||||
var p1 = objBounds.x + objBounds.width / 2;
|
||||
var p2 = objBounds.x + objBounds.width;
|
||||
var marginOfError = this.guidelineSnapDistance;
|
||||
var distance = this.searchDistance;
|
||||
// compares with parts within narrow vertical area
|
||||
var area = objBounds.copy();
|
||||
area.inflate(marginOfError + 1, distance);
|
||||
var otherObjs = this.diagram.findObjectsIn(area, function (obj) { return obj.part; }, function (p) { return _this.isGuiding(p, part); }, true);
|
||||
var bestDiff = marginOfError;
|
||||
var bestObj = null; // TS 2.6 won't let this be go.Part | null
|
||||
var bestSpot = go.Spot.Default;
|
||||
var bestOtherSpot = go.Spot.Default;
|
||||
// vertical line -- comparing x-values
|
||||
otherObjs.each(function (other) {
|
||||
if (other === part)
|
||||
return; // ignore itself
|
||||
var otherBounds = other.locationObject.getDocumentBounds();
|
||||
var q0 = otherBounds.x;
|
||||
var q1 = otherBounds.x + otherBounds.width / 2;
|
||||
var q2 = otherBounds.x + otherBounds.width;
|
||||
// compare center with center of OTHER part
|
||||
if (Math.abs(p1 - q1) < bestDiff) {
|
||||
bestDiff = Math.abs(p1 - q1);
|
||||
bestObj = other;
|
||||
bestSpot = go.Spot.Center;
|
||||
bestOtherSpot = go.Spot.Center;
|
||||
}
|
||||
// compare left side with left and right sides of OTHER part
|
||||
if (Math.abs(p0 - q0) < bestDiff) {
|
||||
bestDiff = Math.abs(p0 - q0);
|
||||
bestObj = other;
|
||||
bestSpot = go.Spot.Left;
|
||||
bestOtherSpot = go.Spot.Left;
|
||||
}
|
||||
else if (Math.abs(p0 - q2) < bestDiff) {
|
||||
bestDiff = Math.abs(p0 - q2);
|
||||
bestObj = other;
|
||||
bestSpot = go.Spot.Left;
|
||||
bestOtherSpot = go.Spot.Right;
|
||||
}
|
||||
// compare right side with left and right sides of OTHER part
|
||||
if (Math.abs(p2 - q0) < bestDiff) {
|
||||
bestDiff = Math.abs(p2 - q0);
|
||||
bestObj = other;
|
||||
bestSpot = go.Spot.Right;
|
||||
bestOtherSpot = go.Spot.Left;
|
||||
}
|
||||
else if (Math.abs(p2 - q2) < bestDiff) {
|
||||
bestDiff = Math.abs(p2 - q2);
|
||||
bestObj = other;
|
||||
bestSpot = go.Spot.Right;
|
||||
bestOtherSpot = go.Spot.Right;
|
||||
}
|
||||
});
|
||||
if (bestObj !== null) {
|
||||
var offsetX = objBounds.x - part.actualBounds.x;
|
||||
var offsetY = objBounds.y - part.actualBounds.y;
|
||||
var bestBounds = bestObj.locationObject.getDocumentBounds();
|
||||
// line extends from y0 to y2
|
||||
var y0 = Math.min(objBounds.y, bestBounds.y) - 10;
|
||||
var y2 = Math.max(objBounds.y + objBounds.height, bestBounds.y + bestBounds.height) + 10;
|
||||
// find bestObj's desired X
|
||||
var bestPoint = new go.Point().setRectSpot(bestBounds, bestOtherSpot);
|
||||
if (bestSpot === go.Spot.Center) {
|
||||
if (snap) {
|
||||
// call Part.move in order to automatically move member Parts of Groups
|
||||
part.move(new go.Point(bestPoint.x - objBounds.width / 2 - offsetX, objBounds.y - offsetY));
|
||||
this.invalidateLinks(part);
|
||||
}
|
||||
if (guideline) {
|
||||
this.guidelineVcenter.position = new go.Point(bestPoint.x, y0);
|
||||
this.guidelineVcenter.elt(0).height = y2 - y0;
|
||||
this.diagram.add(this.guidelineVcenter);
|
||||
}
|
||||
}
|
||||
else if (bestSpot === go.Spot.Left) {
|
||||
if (snap) {
|
||||
part.move(new go.Point(bestPoint.x - offsetX, objBounds.y - offsetY));
|
||||
this.invalidateLinks(part);
|
||||
}
|
||||
if (guideline) {
|
||||
this.guidelineVleft.position = new go.Point(bestPoint.x, y0);
|
||||
this.guidelineVleft.elt(0).height = y2 - y0;
|
||||
this.diagram.add(this.guidelineVleft);
|
||||
}
|
||||
}
|
||||
else if (bestSpot === go.Spot.Right) {
|
||||
if (snap) {
|
||||
part.move(new go.Point(bestPoint.x - objBounds.width - offsetX, objBounds.y - offsetY));
|
||||
this.invalidateLinks(part);
|
||||
}
|
||||
if (guideline) {
|
||||
this.guidelineVright.position = new go.Point(bestPoint.x, y0);
|
||||
this.guidelineVright.elt(0).height = y2 - y0;
|
||||
this.diagram.add(this.guidelineVright);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return GuidedDraggingTool;
|
||||
}(go.DraggingTool));
|
||||
exports.GuidedDraggingTool = GuidedDraggingTool;
|
||||
});
|
||||
+502
@@ -0,0 +1,502 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
|
||||
/**
|
||||
* The GuidedDraggingTool class makes guidelines visible as the parts are dragged around a diagram
|
||||
* when the selected part is nearly aligned with another part.
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/GuidedDragging.html">Guided Dragging</a> sample.
|
||||
* @category Tool Extension
|
||||
*/
|
||||
export class GuidedDraggingTool extends go.DraggingTool {
|
||||
// horizontal guidelines
|
||||
private guidelineHtop: go.Part;
|
||||
private guidelineHbottom: go.Part;
|
||||
private guidelineHcenter: go.Part;
|
||||
// vertical guidelines
|
||||
private guidelineVleft: go.Part;
|
||||
private guidelineVright: go.Part;
|
||||
private guidelineVcenter: go.Part;
|
||||
|
||||
// properties that the programmer can modify
|
||||
private _guidelineSnapDistance: number = 6;
|
||||
private _isGuidelineEnabled: boolean = true;
|
||||
private _horizontalGuidelineColor: string = 'gray';
|
||||
private _verticalGuidelineColor: string = 'gray';
|
||||
private _centerGuidelineColor: string = 'gray';
|
||||
private _guidelineWidth: number = 1;
|
||||
private _searchDistance: number = 1000;
|
||||
private _isGuidelineSnapEnabled: boolean = true;
|
||||
|
||||
/**
|
||||
* Constructs a GuidedDraggingTool and sets up the temporary guideline parts.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const partProperties = { layerName: 'Tool', isInDocumentBounds: false };
|
||||
const shapeProperties = { stroke: 'gray', isGeometryPositioned: true };
|
||||
|
||||
const $ = go.GraphObject.make;
|
||||
// temporary parts for horizonal guidelines
|
||||
this.guidelineHtop =
|
||||
$(go.Part, partProperties,
|
||||
$(go.Shape, shapeProperties, { geometryString: 'M0 0 100 0' }));
|
||||
this.guidelineHbottom =
|
||||
$(go.Part, partProperties,
|
||||
$(go.Shape, shapeProperties, { geometryString: 'M0 0 100 0' }));
|
||||
this.guidelineHcenter =
|
||||
$(go.Part, partProperties,
|
||||
$(go.Shape, shapeProperties, { geometryString: 'M0 0 100 0' }));
|
||||
// temporary parts for vertical guidelines
|
||||
this.guidelineVleft =
|
||||
$(go.Part, partProperties,
|
||||
$(go.Shape, shapeProperties, { geometryString: 'M0 0 0 100' }));
|
||||
this.guidelineVright =
|
||||
$(go.Part, partProperties,
|
||||
$(go.Shape, shapeProperties, { geometryString: 'M0 0 0 100' }));
|
||||
this.guidelineVcenter =
|
||||
$(go.Part, partProperties,
|
||||
$(go.Shape, shapeProperties, { geometryString: 'M0 0 0 100' }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the margin of error for which guidelines show up.
|
||||
*
|
||||
* The default value is 6.
|
||||
* Guidelines will show up when the aligned nods are ± 6px away from perfect alignment.
|
||||
*/
|
||||
get guidelineSnapDistance(): number { return this._guidelineSnapDistance; }
|
||||
set guidelineSnapDistance(val: number) {
|
||||
if (typeof val !== 'number' || isNaN(val) || val < 0) throw new Error('new value for GuideddraggingTool.guidelineSnapDistance must be a non-negative number');
|
||||
if (this._guidelineSnapDistance !== val) {
|
||||
this._guidelineSnapDistance = val;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets whether the guidelines are enabled or disable.
|
||||
*
|
||||
* The default value is true.
|
||||
*/
|
||||
get isGuidelineEnabled(): boolean { return this._isGuidelineEnabled; }
|
||||
set isGuidelineEnabled(val: boolean) {
|
||||
if (typeof val !== 'boolean') throw new Error('new value for GuidedDraggingTool.isGuidelineEnabled must be a boolean value.');
|
||||
if (this._isGuidelineEnabled !== val) {
|
||||
this._isGuidelineEnabled = val;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the color of horizontal guidelines.
|
||||
*
|
||||
* The default value is "gray".
|
||||
*/
|
||||
get horizontalGuidelineColor(): string { return this._horizontalGuidelineColor; }
|
||||
set horizontalGuidelineColor(val: string) {
|
||||
if (this._horizontalGuidelineColor !== val) {
|
||||
this._horizontalGuidelineColor = val;
|
||||
(this.guidelineHbottom.elements.first() as go.Shape).stroke = this._horizontalGuidelineColor;
|
||||
(this.guidelineHtop.elements.first() as go.Shape).stroke = this._horizontalGuidelineColor;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the color of vertical guidelines.
|
||||
*
|
||||
* The default value is "gray".
|
||||
*/
|
||||
get verticalGuidelineColor(): string { return this._verticalGuidelineColor; }
|
||||
set verticalGuidelineColor(val: string) {
|
||||
if (this._verticalGuidelineColor !== val) {
|
||||
this._verticalGuidelineColor = val;
|
||||
(this.guidelineVleft.elements.first() as go.Shape).stroke = this._verticalGuidelineColor;
|
||||
(this.guidelineVright.elements.first() as go.Shape).stroke = this._verticalGuidelineColor;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the color of center guidelines.
|
||||
*
|
||||
* The default value is "gray".
|
||||
*/
|
||||
get centerGuidelineColor(): string { return this._centerGuidelineColor; }
|
||||
set centerGuidelineColor(val: string) {
|
||||
if (this._centerGuidelineColor !== val) {
|
||||
this._centerGuidelineColor = val;
|
||||
(this.guidelineVcenter.elements.first() as go.Shape).stroke = this._centerGuidelineColor;
|
||||
(this.guidelineHcenter.elements.first() as go.Shape).stroke = this._centerGuidelineColor;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the width guidelines.
|
||||
*
|
||||
* The default value is 1.
|
||||
*/
|
||||
get guidelineWidth(): number { return this._guidelineWidth; }
|
||||
set guidelineWidth(val: number) {
|
||||
if (typeof val !== 'number' || isNaN(val) || val < 0) throw new Error('New value for GuidedDraggingTool.guidelineWidth must be a non-negative number.');
|
||||
if (this._guidelineWidth !== val) {
|
||||
this._guidelineWidth = val;
|
||||
(this.guidelineVcenter.elements.first() as go.Shape).strokeWidth = val;
|
||||
(this.guidelineHcenter.elements.first() as go.Shape).strokeWidth = val;
|
||||
(this.guidelineVleft.elements.first() as go.Shape).strokeWidth = val;
|
||||
(this.guidelineVright.elements.first() as go.Shape).strokeWidth = val;
|
||||
(this.guidelineHbottom.elements.first() as go.Shape).strokeWidth = val;
|
||||
(this.guidelineHtop.elements.first() as go.Shape).strokeWidth = val;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the distance around the selected part to search for aligned parts.
|
||||
*
|
||||
* The default value is 1000.
|
||||
* Set this to Infinity if you want to search the entire diagram no matter how far away.
|
||||
*/
|
||||
get searchDistance(): number { return this._searchDistance; }
|
||||
set searchDistance(val: number) {
|
||||
if (typeof val !== 'number' || isNaN(val) || val <= 0) throw new Error('new value for GuidedDraggingTool.searchDistance must be a positive number.');
|
||||
if (this._searchDistance !== val) {
|
||||
this._searchDistance = val;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets whether snapping to guidelines is enabled.
|
||||
*
|
||||
* The default value is true.
|
||||
*/
|
||||
get isGuidelineSnapEnabled(): boolean { return this._isGuidelineSnapEnabled; }
|
||||
set isGuidelineSnapEnabled(val: boolean) {
|
||||
if (typeof val !== 'boolean') throw new Error('new value for GuidedDraggingTool.isGuidelineSnapEnabled must be a boolean.');
|
||||
if (this._isGuidelineSnapEnabled !== val) {
|
||||
this._isGuidelineSnapEnabled = val;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all of the guidelines from the grid.
|
||||
*/
|
||||
public clearGuidelines(): void {
|
||||
this.diagram.remove(this.guidelineHbottom);
|
||||
this.diagram.remove(this.guidelineHcenter);
|
||||
this.diagram.remove(this.guidelineHtop);
|
||||
this.diagram.remove(this.guidelineVleft);
|
||||
this.diagram.remove(this.guidelineVright);
|
||||
this.diagram.remove(this.guidelineVcenter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the base method and removes the guidelines from the graph.
|
||||
*/
|
||||
public doDeactivate(): void {
|
||||
super.doDeactivate();
|
||||
// clear any guidelines when dragging is done
|
||||
this.clearGuidelines();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows vertical and horizontal guidelines for the dragged part.
|
||||
*/
|
||||
public doDragOver(pt: go.Point, obj: go.GraphObject): void {
|
||||
// clear all existing guidelines in case either show... method decides to show a guideline
|
||||
this.clearGuidelines();
|
||||
|
||||
// gets the selected part
|
||||
const draggingParts = this.copiedParts || this.draggedParts;
|
||||
if (draggingParts === null) return;
|
||||
const partItr = draggingParts.iterator;
|
||||
if (partItr.next()) {
|
||||
const part = partItr.key;
|
||||
|
||||
this.showHorizontalMatches(part, this.isGuidelineEnabled, false);
|
||||
this.showVerticalMatches(part, this.isGuidelineEnabled, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On a mouse-up, snaps the selected part to the nearest guideline.
|
||||
* If not snapping, the part remains at its position.
|
||||
*/
|
||||
public doDropOnto(pt: go.Point, obj: go.GraphObject): void {
|
||||
this.clearGuidelines();
|
||||
|
||||
// gets the selected (perhaps copied) Part
|
||||
const draggingParts = this.copiedParts || this.draggedParts;
|
||||
if (draggingParts === null) return;
|
||||
const partItr = draggingParts.iterator;
|
||||
if (partItr.next()) {
|
||||
const part = partItr.key;
|
||||
|
||||
// snaps only when the mouse is released without shift modifier
|
||||
const e = this.diagram.lastInput;
|
||||
const snap = this.isGuidelineSnapEnabled && !e.shift;
|
||||
|
||||
this.showHorizontalMatches(part, false, snap); // false means don't show guidelines
|
||||
this.showVerticalMatches(part, false, snap);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When nodes are shifted due to being guided upon a drop, make sure all connected link routes are invalidated,
|
||||
* since the node is likely to have moved a different amount than all its connected links in the regular
|
||||
* operation of the DraggingTool.
|
||||
*/
|
||||
public invalidateLinks(node: go.Part): void {
|
||||
if (node instanceof go.Node) node.invalidateConnectedLinks();
|
||||
}
|
||||
|
||||
/**
|
||||
* This predicate decides whether or not the given Part should guide the dragged part.
|
||||
* @param {Part} part a stationary Part to which the dragged part might be aligned
|
||||
* @param {Part} guidedpart the Part being dragged
|
||||
*/
|
||||
protected isGuiding(part: go.Part, guidedpart: go.Part): boolean {
|
||||
return part instanceof go.Part &&
|
||||
!part.isSelected &&
|
||||
!(part instanceof go.Link) &&
|
||||
guidedpart instanceof go.Part &&
|
||||
part.containingGroup === guidedpart.containingGroup &&
|
||||
part.layer !== null && !part.layer.isTemporary;
|
||||
}
|
||||
|
||||
/**
|
||||
* This finds parts that are aligned near the selected part along horizontal lines. It compares the selected
|
||||
* part to all parts within a rectangle approximately twice the {@link #searchDistance} wide.
|
||||
* The guidelines appear when a part is aligned within a margin-of-error equal to {@link #guidelineSnapDistance}.
|
||||
* @param {Node} part
|
||||
* @param {boolean} guideline if true, show guideline
|
||||
* @param {boolean} snap if true, snap the part to where the guideline would be
|
||||
*/
|
||||
public showHorizontalMatches(part: go.Part, guideline: boolean, snap: boolean): void {
|
||||
const objBounds = part.locationObject.getDocumentBounds();
|
||||
const p0 = objBounds.y;
|
||||
const p1 = objBounds.y + objBounds.height / 2;
|
||||
const p2 = objBounds.y + objBounds.height;
|
||||
|
||||
const marginOfError = this.guidelineSnapDistance;
|
||||
const distance = this.searchDistance;
|
||||
// compares with parts within narrow vertical area
|
||||
const area = objBounds.copy();
|
||||
area.inflate(distance, marginOfError + 1);
|
||||
const otherObjs = this.diagram.findObjectsIn(area,
|
||||
(obj) => obj.part as go.Part,
|
||||
(p) => this.isGuiding(p as go.Part, part),
|
||||
true) as go.Set<go.Part>;
|
||||
|
||||
let bestDiff: number = marginOfError;
|
||||
let bestObj: any = null; // TS 2.6 won't let this be go.Part | null
|
||||
let bestSpot: go.Spot = go.Spot.Default;
|
||||
let bestOtherSpot: go.Spot = go.Spot.Default;
|
||||
// horizontal line -- comparing y-values
|
||||
otherObjs.each((other) => {
|
||||
if (other === part) return; // ignore itself
|
||||
|
||||
const otherBounds = other.locationObject.getDocumentBounds();
|
||||
const q0 = otherBounds.y;
|
||||
const q1 = otherBounds.y + otherBounds.height / 2;
|
||||
const q2 = otherBounds.y + otherBounds.height;
|
||||
|
||||
// compare center with center of OTHER part
|
||||
if (Math.abs(p1 - q1) < bestDiff) {
|
||||
bestDiff = Math.abs(p1 - q1);
|
||||
bestObj = other;
|
||||
bestSpot = go.Spot.Center;
|
||||
bestOtherSpot = go.Spot.Center;
|
||||
}
|
||||
// compare top side with top and bottom sides of OTHER part
|
||||
if (Math.abs(p0 - q0) < bestDiff) {
|
||||
bestDiff = Math.abs(p0 - q0);
|
||||
bestObj = other;
|
||||
bestSpot = go.Spot.Top;
|
||||
bestOtherSpot = go.Spot.Top;
|
||||
} else if (Math.abs(p0 - q2) < bestDiff) {
|
||||
bestDiff = Math.abs(p0 - q2);
|
||||
bestObj = other;
|
||||
bestSpot = go.Spot.Top;
|
||||
bestOtherSpot = go.Spot.Bottom;
|
||||
}
|
||||
// compare bottom side with top and bottom sides of OTHER part
|
||||
if (Math.abs(p2 - q0) < bestDiff) {
|
||||
bestDiff = Math.abs(p2 - q0);
|
||||
bestObj = other;
|
||||
bestSpot = go.Spot.Bottom;
|
||||
bestOtherSpot = go.Spot.Top;
|
||||
} else if (Math.abs(p2 - q2) < bestDiff) {
|
||||
bestDiff = Math.abs(p2 - q2);
|
||||
bestObj = other;
|
||||
bestSpot = go.Spot.Bottom;
|
||||
bestOtherSpot = go.Spot.Bottom;
|
||||
}
|
||||
});
|
||||
|
||||
if (bestObj !== null) {
|
||||
const offsetX = objBounds.x - part.actualBounds.x;
|
||||
const offsetY = objBounds.y - part.actualBounds.y;
|
||||
const bestBounds = bestObj.locationObject.getDocumentBounds();
|
||||
// line extends from x0 to x2
|
||||
const x0 = Math.min(objBounds.x, bestBounds.x) - 10;
|
||||
const x2 = Math.max(objBounds.x + objBounds.width, bestBounds.x + bestBounds.width) + 10;
|
||||
// find bestObj's desired Y
|
||||
const bestPoint = new go.Point().setRectSpot(bestBounds, bestOtherSpot);
|
||||
if (bestSpot === go.Spot.Center) {
|
||||
if (snap) {
|
||||
// call Part.move in order to automatically move member Parts of Groups
|
||||
part.move(new go.Point(objBounds.x - offsetX, bestPoint.y - objBounds.height / 2 - offsetY));
|
||||
this.invalidateLinks(part);
|
||||
}
|
||||
if (guideline) {
|
||||
this.guidelineHcenter.position = new go.Point(x0, bestPoint.y);
|
||||
this.guidelineHcenter.elt(0).width = x2 - x0;
|
||||
this.diagram.add(this.guidelineHcenter);
|
||||
}
|
||||
} else if (bestSpot === go.Spot.Top) {
|
||||
if (snap) {
|
||||
part.move(new go.Point(objBounds.x - offsetX, bestPoint.y - offsetY));
|
||||
this.invalidateLinks(part);
|
||||
}
|
||||
if (guideline) {
|
||||
this.guidelineHtop.position = new go.Point(x0, bestPoint.y);
|
||||
this.guidelineHtop.elt(0).width = x2 - x0;
|
||||
this.diagram.add(this.guidelineHtop);
|
||||
}
|
||||
} else if (bestSpot === go.Spot.Bottom) {
|
||||
if (snap) {
|
||||
part.move(new go.Point(objBounds.x - offsetX, bestPoint.y - objBounds.height - offsetY));
|
||||
this.invalidateLinks(part);
|
||||
}
|
||||
if (guideline) {
|
||||
this.guidelineHbottom.position = new go.Point(x0, bestPoint.y);
|
||||
this.guidelineHbottom.elt(0).width = x2 - x0;
|
||||
this.diagram.add(this.guidelineHbottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This finds parts that are aligned near the selected part along vertical lines. It compares the selected
|
||||
* part to all parts within a rectangle approximately twice the {@link #searchDistance} tall.
|
||||
* The guidelines appear when a part is aligned within a margin-of-error equal to {@link #guidelineSnapDistance}.
|
||||
* @param {Part} part
|
||||
* @param {boolean} guideline if true, show guideline
|
||||
* @param {boolean} snap if true, don't show guidelines but just snap the part to where the guideline would be
|
||||
*/
|
||||
public showVerticalMatches(part: go.Part, guideline: boolean, snap: boolean): void {
|
||||
const objBounds = part.locationObject.getDocumentBounds();
|
||||
const p0 = objBounds.x;
|
||||
const p1 = objBounds.x + objBounds.width / 2;
|
||||
const p2 = objBounds.x + objBounds.width;
|
||||
|
||||
const marginOfError = this.guidelineSnapDistance;
|
||||
const distance = this.searchDistance;
|
||||
// compares with parts within narrow vertical area
|
||||
const area = objBounds.copy();
|
||||
area.inflate(marginOfError + 1, distance);
|
||||
const otherObjs = this.diagram.findObjectsIn(area,
|
||||
(obj) => obj.part as go.Part,
|
||||
(p) => this.isGuiding(p as go.Part, part),
|
||||
true) as go.Set<go.Part>;
|
||||
|
||||
let bestDiff: number = marginOfError;
|
||||
let bestObj: any = null; // TS 2.6 won't let this be go.Part | null
|
||||
let bestSpot: go.Spot = go.Spot.Default;
|
||||
let bestOtherSpot: go.Spot = go.Spot.Default;
|
||||
// vertical line -- comparing x-values
|
||||
otherObjs.each((other) => {
|
||||
if (other === part) return; // ignore itself
|
||||
|
||||
const otherBounds = other.locationObject.getDocumentBounds();
|
||||
const q0 = otherBounds.x;
|
||||
const q1 = otherBounds.x + otherBounds.width / 2;
|
||||
const q2 = otherBounds.x + otherBounds.width;
|
||||
|
||||
// compare center with center of OTHER part
|
||||
if (Math.abs(p1 - q1) < bestDiff) {
|
||||
bestDiff = Math.abs(p1 - q1);
|
||||
bestObj = other;
|
||||
bestSpot = go.Spot.Center;
|
||||
bestOtherSpot = go.Spot.Center;
|
||||
}
|
||||
// compare left side with left and right sides of OTHER part
|
||||
if (Math.abs(p0 - q0) < bestDiff) {
|
||||
bestDiff = Math.abs(p0 - q0);
|
||||
bestObj = other;
|
||||
bestSpot = go.Spot.Left;
|
||||
bestOtherSpot = go.Spot.Left;
|
||||
} else if (Math.abs(p0 - q2) < bestDiff) {
|
||||
bestDiff = Math.abs(p0 - q2);
|
||||
bestObj = other;
|
||||
bestSpot = go.Spot.Left;
|
||||
bestOtherSpot = go.Spot.Right;
|
||||
}
|
||||
// compare right side with left and right sides of OTHER part
|
||||
if (Math.abs(p2 - q0) < bestDiff) {
|
||||
bestDiff = Math.abs(p2 - q0);
|
||||
bestObj = other;
|
||||
bestSpot = go.Spot.Right;
|
||||
bestOtherSpot = go.Spot.Left;
|
||||
} else if (Math.abs(p2 - q2) < bestDiff) {
|
||||
bestDiff = Math.abs(p2 - q2);
|
||||
bestObj = other;
|
||||
bestSpot = go.Spot.Right;
|
||||
bestOtherSpot = go.Spot.Right;
|
||||
}
|
||||
});
|
||||
|
||||
if (bestObj !== null) {
|
||||
const offsetX = objBounds.x - part.actualBounds.x;
|
||||
const offsetY = objBounds.y - part.actualBounds.y;
|
||||
const bestBounds = bestObj.locationObject.getDocumentBounds();
|
||||
// line extends from y0 to y2
|
||||
const y0 = Math.min(objBounds.y, bestBounds.y) - 10;
|
||||
const y2 = Math.max(objBounds.y + objBounds.height, bestBounds.y + bestBounds.height) + 10;
|
||||
// find bestObj's desired X
|
||||
const bestPoint = new go.Point().setRectSpot(bestBounds, bestOtherSpot);
|
||||
if (bestSpot === go.Spot.Center) {
|
||||
if (snap) {
|
||||
// call Part.move in order to automatically move member Parts of Groups
|
||||
part.move(new go.Point(bestPoint.x - objBounds.width / 2 - offsetX, objBounds.y - offsetY));
|
||||
this.invalidateLinks(part);
|
||||
}
|
||||
if (guideline) {
|
||||
this.guidelineVcenter.position = new go.Point(bestPoint.x, y0);
|
||||
this.guidelineVcenter.elt(0).height = y2 - y0;
|
||||
this.diagram.add(this.guidelineVcenter);
|
||||
}
|
||||
} else if (bestSpot === go.Spot.Left) {
|
||||
if (snap) {
|
||||
part.move(new go.Point(bestPoint.x - offsetX, objBounds.y - offsetY));
|
||||
this.invalidateLinks(part);
|
||||
}
|
||||
if (guideline) {
|
||||
this.guidelineVleft.position = new go.Point(bestPoint.x, y0);
|
||||
this.guidelineVleft.elt(0).height = y2 - y0;
|
||||
this.diagram.add(this.guidelineVleft);
|
||||
}
|
||||
} else if (bestSpot === go.Spot.Right) {
|
||||
if (snap) {
|
||||
part.move(new go.Point(bestPoint.x - objBounds.width - offsetX, objBounds.y - offsetY));
|
||||
this.invalidateLinks(part);
|
||||
}
|
||||
if (guideline) {
|
||||
this.guidelineVright.position = new go.Point(bestPoint.x, y0);
|
||||
this.guidelineVright.elt(0).height = y2 - y0;
|
||||
this.diagram.add(this.guidelineVright);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Demo of HyperlinkText Builder</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="TypeScript: Implement a 'hyperlink' that allows the user to open a page by clicking." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../samples/assets/require.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
function init() {
|
||||
require(["HyperlinkScript", "HyperlinkText"], function(app) {
|
||||
app.init();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:400px"></div>
|
||||
<p>
|
||||
This uses the "HyperlinkText" builder defined in <a href="HyperlinkText.ts">HyperlinkText.ts</a>.
|
||||
</p>
|
||||
<p>
|
||||
Click on the text to open a window to a computed URL. A mouse-over on the text will underline the text. Hover on the text
|
||||
and you will see a tooltip showing the destination URL.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.init = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
function init() {
|
||||
if (window.goSamples)
|
||||
window.goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make;
|
||||
var myDiagram = $(go.Diagram, 'myDiagramDiv');
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, 'Auto', $(go.Shape, 'Ellipse', { fill: 'lightskyblue' }), $('HyperlinkText', function (node) { return 'https://gojs.net/' + node.data.version; }, function (node) { return 'Visit GoJS ' + node.data.version; }, { margin: 10 }));
|
||||
myDiagram.model = new go.GraphLinksModel([
|
||||
{ key: 1, version: 'beta' },
|
||||
{ key: 2, version: 'latest' }
|
||||
], [
|
||||
{ from: 1, to: 2 }
|
||||
]);
|
||||
// Attach to the window for console manipulation
|
||||
window.myDiagram = myDiagram;
|
||||
}
|
||||
exports.init = init;
|
||||
});
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
|
||||
export function init() {
|
||||
if ((window as any).goSamples) (window as any).goSamples(); // init for these samples -- you don't need to call this
|
||||
|
||||
const $ = go.GraphObject.make;
|
||||
|
||||
const myDiagram =
|
||||
$(go.Diagram, 'myDiagramDiv');
|
||||
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, 'Auto',
|
||||
$(go.Shape, 'Ellipse', { fill: 'lightskyblue' }),
|
||||
$('HyperlinkText',
|
||||
(node: go.Node) => 'https://gojs.net/' + node.data.version,
|
||||
(node: go.Node) => 'Visit GoJS ' + node.data.version,
|
||||
{ margin: 10 }
|
||||
)
|
||||
);
|
||||
|
||||
myDiagram.model = new go.GraphLinksModel([
|
||||
{ key: 1, version: 'beta' },
|
||||
{ key: 2, version: 'latest' }
|
||||
], [
|
||||
{ from: 1, to: 2 }
|
||||
]);
|
||||
|
||||
// Attach to the window for console manipulation
|
||||
(window as any).myDiagram = myDiagram;
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
// A "HyperlinkText" is either a TextBlock or a Panel containing a TextBlock that when clicked
|
||||
// opens a new browser window with a given or computed URL.
|
||||
// When the user's mouse passes over a "HyperlinkText", the text is underlined.
|
||||
// When the mouse hovers over a "HyperlinkText", it shows a tooltip that displays the URL.
|
||||
// This "HyperlinkText" builder is not pre-defined in the GoJS library, so you will need to load this definition.
|
||||
// Typical usages:
|
||||
// $("HyperlinkText", "https://gojs.net", "Visit GoJS")
|
||||
//
|
||||
// $("HyperlinkText",
|
||||
// function(node) { return "https://gojs.net/" + node.data.version; },
|
||||
// function(node) { return "Visit GoJS version " + node.data.version; })
|
||||
//
|
||||
// $("HyperlinkText",
|
||||
// function(node) { return "https://gojs.net/" + node.data.version; },
|
||||
// $(go.Panel, "Auto",
|
||||
// $(go.Shape, ...),
|
||||
// $(go.TextBlock, ...)
|
||||
// )
|
||||
// )
|
||||
// The first argument to the "HyperlinkText" builder should be either the URL string or a function
|
||||
// that takes the data-bound Panel and returns the URL string.
|
||||
// If the URL string is empty or if the function returns an empty string,
|
||||
// the text will not be underlined on a mouse-over and a click has no effect.
|
||||
// The second argument to the "HyperlinkText" builder may be either a string to display in a TextBlock,
|
||||
// or a function that takes the data-bound Panel and returns the string to display in a TextBlock.
|
||||
// If no text string or function is provided, it assumes all of the arguments are used to
|
||||
// define the visual tree for the "HyperlinkText", in the normal fashion for a Panel.
|
||||
// The result is either a TextBlock or a Panel.
|
||||
go.GraphObject.defineBuilder('HyperlinkText', function (args) {
|
||||
// the URL is required as the first argument, either a string or a side-effect-free function returning a string
|
||||
var url = go.GraphObject.takeBuilderArgument(args, undefined, function (x) { return typeof x === 'string' || typeof x === 'function'; });
|
||||
// the text for the HyperlinkText is the optional second argument, either a string or a side-effect-free function returning a string
|
||||
var text = go.GraphObject.takeBuilderArgument(args, null, function (x) { return typeof x === 'string' || typeof x === 'function'; });
|
||||
// see if the visual tree is supplied in the arguments to the "HyperlinkText"
|
||||
var anyGraphObjects = false;
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
var a = args[i];
|
||||
if (a && a instanceof go.GraphObject)
|
||||
anyGraphObjects = true;
|
||||
}
|
||||
// define the click behavior
|
||||
var click = function (e, obj) {
|
||||
var u = obj._url;
|
||||
if (typeof u === 'function')
|
||||
u = u(obj.findTemplateBinder());
|
||||
if (u)
|
||||
window.open(u, '_blank');
|
||||
};
|
||||
// define the tooltip
|
||||
var tooltip = go.GraphObject.make('ToolTip', go.GraphObject.make(go.TextBlock, { name: 'TB', margin: 4 }, new go.Binding('text', '', function (obj) {
|
||||
// here OBJ will be in the Adornment, need to get the HyperlinkText/TextBlock
|
||||
obj = obj.part.adornedObject;
|
||||
var u = obj._url;
|
||||
if (typeof u === 'function')
|
||||
u = u(obj.findTemplateBinder());
|
||||
return u;
|
||||
}).ofObject()), new go.Binding('visible', 'text', function (t) { return !!t; }).ofObject('TB'));
|
||||
// if the text is provided, use a new TextBlock; otherwise assume the TextBlock is provided
|
||||
if (typeof (text) === 'string' || typeof (text) === 'function' || !anyGraphObjects) {
|
||||
if (text === null && typeof (url) === 'string')
|
||||
text = url;
|
||||
var tb = go.GraphObject.make(go.TextBlock, {
|
||||
'_url': url,
|
||||
cursor: 'pointer',
|
||||
mouseEnter: function (e, obj) {
|
||||
var u = obj._url;
|
||||
if (typeof u === 'function')
|
||||
u = u(obj.findTemplateBinder());
|
||||
if (u && obj instanceof go.TextBlock)
|
||||
obj.isUnderline = true;
|
||||
},
|
||||
mouseLeave: function (e, obj) { if (obj instanceof go.TextBlock)
|
||||
obj.isUnderline = false; },
|
||||
click: click,
|
||||
toolTip: tooltip // shared by all HyperlinkText textblocks
|
||||
});
|
||||
if (typeof (text) === 'string') {
|
||||
tb.text = text;
|
||||
}
|
||||
else if (typeof (text) === 'function') {
|
||||
tb.bind(new go.Binding('text', '', text).ofObject());
|
||||
}
|
||||
else if (typeof (url) === 'function') {
|
||||
tb.bind(new go.Binding('text', '', url).ofObject());
|
||||
}
|
||||
return tb;
|
||||
}
|
||||
else {
|
||||
var findTextBlock_1 = function (obj) {
|
||||
if (obj instanceof go.TextBlock)
|
||||
return obj;
|
||||
if (obj instanceof go.Panel) {
|
||||
var it = obj.elements;
|
||||
while (it.next()) {
|
||||
var result = findTextBlock_1(it.value);
|
||||
if (result !== null)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
return go.GraphObject.make(go.Panel, {
|
||||
'_url': url,
|
||||
cursor: 'pointer',
|
||||
mouseEnter: function (e, panel) {
|
||||
var tb = findTextBlock_1(panel);
|
||||
var u = panel._url;
|
||||
if (typeof u === 'function')
|
||||
u = u(panel.findTemplateBinder());
|
||||
if (tb !== null && u)
|
||||
tb.isUnderline = true;
|
||||
},
|
||||
mouseLeave: function (e, panel) {
|
||||
var tb = findTextBlock_1(panel);
|
||||
if (tb !== null)
|
||||
tb.isUnderline = false;
|
||||
},
|
||||
click: click,
|
||||
toolTip: tooltip // shared by all HyperlinkText panels
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
|
||||
// A "HyperlinkText" is either a TextBlock or a Panel containing a TextBlock that when clicked
|
||||
// opens a new browser window with a given or computed URL.
|
||||
// When the user's mouse passes over a "HyperlinkText", the text is underlined.
|
||||
// When the mouse hovers over a "HyperlinkText", it shows a tooltip that displays the URL.
|
||||
|
||||
// This "HyperlinkText" builder is not pre-defined in the GoJS library, so you will need to load this definition.
|
||||
|
||||
// Typical usages:
|
||||
// $("HyperlinkText", "https://gojs.net", "Visit GoJS")
|
||||
//
|
||||
// $("HyperlinkText",
|
||||
// function(node) { return "https://gojs.net/" + node.data.version; },
|
||||
// function(node) { return "Visit GoJS version " + node.data.version; })
|
||||
//
|
||||
// $("HyperlinkText",
|
||||
// function(node) { return "https://gojs.net/" + node.data.version; },
|
||||
// $(go.Panel, "Auto",
|
||||
// $(go.Shape, ...),
|
||||
// $(go.TextBlock, ...)
|
||||
// )
|
||||
// )
|
||||
|
||||
// The first argument to the "HyperlinkText" builder should be either the URL string or a function
|
||||
// that takes the data-bound Panel and returns the URL string.
|
||||
// If the URL string is empty or if the function returns an empty string,
|
||||
// the text will not be underlined on a mouse-over and a click has no effect.
|
||||
|
||||
// The second argument to the "HyperlinkText" builder may be either a string to display in a TextBlock,
|
||||
// or a function that takes the data-bound Panel and returns the string to display in a TextBlock.
|
||||
// If no text string or function is provided, it assumes all of the arguments are used to
|
||||
// define the visual tree for the "HyperlinkText", in the normal fashion for a Panel.
|
||||
|
||||
// The result is either a TextBlock or a Panel.
|
||||
|
||||
go.GraphObject.defineBuilder('HyperlinkText', (args) => {
|
||||
// the URL is required as the first argument, either a string or a side-effect-free function returning a string
|
||||
const url = go.GraphObject.takeBuilderArgument(args, undefined, (x) => typeof x === 'string' || typeof x === 'function');
|
||||
// the text for the HyperlinkText is the optional second argument, either a string or a side-effect-free function returning a string
|
||||
let text = go.GraphObject.takeBuilderArgument(args, null, (x) => typeof x === 'string' || typeof x === 'function');
|
||||
|
||||
// see if the visual tree is supplied in the arguments to the "HyperlinkText"
|
||||
let anyGraphObjects = false;
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const a = args[i];
|
||||
if (a && a instanceof go.GraphObject) anyGraphObjects = true;
|
||||
}
|
||||
|
||||
// define the click behavior
|
||||
const click =
|
||||
(e: go.InputEvent, obj: go.GraphObject) => {
|
||||
let u = (obj as any)._url;
|
||||
if (typeof u === 'function') u = u(obj.findTemplateBinder());
|
||||
if (u) window.open(u, '_blank');
|
||||
};
|
||||
|
||||
// define the tooltip
|
||||
const tooltip =
|
||||
go.GraphObject.make<go.Adornment>('ToolTip',
|
||||
go.GraphObject.make(go.TextBlock,
|
||||
{ name: 'TB', margin: 4 },
|
||||
new go.Binding('text', '', function(obj) {
|
||||
// here OBJ will be in the Adornment, need to get the HyperlinkText/TextBlock
|
||||
obj = obj.part.adornedObject;
|
||||
let u = obj._url;
|
||||
if (typeof u === 'function') u = u(obj.findTemplateBinder());
|
||||
return u;
|
||||
}).ofObject()
|
||||
),
|
||||
new go.Binding('visible', 'text', function(t) { return !!t; }).ofObject('TB')
|
||||
);
|
||||
|
||||
// if the text is provided, use a new TextBlock; otherwise assume the TextBlock is provided
|
||||
if (typeof (text) === 'string' || typeof (text) === 'function' || !anyGraphObjects) {
|
||||
if (text === null && typeof (url) === 'string') text = url;
|
||||
const tb = go.GraphObject.make(go.TextBlock,
|
||||
{
|
||||
'_url': url,
|
||||
cursor: 'pointer',
|
||||
mouseEnter: function(e: go.InputEvent, obj: go.GraphObject) {
|
||||
let u = (obj as any)._url;
|
||||
if (typeof u === 'function') u = u(obj.findTemplateBinder());
|
||||
if (u && obj instanceof go.TextBlock) obj.isUnderline = true;
|
||||
},
|
||||
mouseLeave: (e: go.InputEvent, obj: go.GraphObject) => { if (obj instanceof go.TextBlock) obj.isUnderline = false; },
|
||||
click: click, // defined above
|
||||
toolTip: tooltip // shared by all HyperlinkText textblocks
|
||||
}
|
||||
);
|
||||
if (typeof (text) === 'string') {
|
||||
tb.text = text;
|
||||
} else if (typeof (text) === 'function') {
|
||||
tb.bind(new go.Binding('text', '', text).ofObject());
|
||||
} else if (typeof (url) === 'function') {
|
||||
tb.bind(new go.Binding('text', '', url).ofObject());
|
||||
}
|
||||
return tb;
|
||||
} else {
|
||||
const findTextBlock = function(obj: go.GraphObject): go.TextBlock | null {
|
||||
if (obj instanceof go.TextBlock) return obj;
|
||||
if (obj instanceof go.Panel) {
|
||||
const it = obj.elements;
|
||||
while (it.next()) {
|
||||
const result = findTextBlock(it.value);
|
||||
if (result !== null) return result;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
return go.GraphObject.make(go.Panel,
|
||||
{
|
||||
'_url': url,
|
||||
cursor: 'pointer',
|
||||
mouseEnter: (e: go.InputEvent, panel: go.GraphObject) => {
|
||||
const tb = findTextBlock(panel);
|
||||
let u = (panel as any)._url;
|
||||
if (typeof u === 'function') u = u(panel.findTemplateBinder());
|
||||
if (tb !== null && u) tb.isUnderline = true;
|
||||
},
|
||||
mouseLeave: (e: go.InputEvent, panel: go.GraphObject) => {
|
||||
const tb = findTextBlock(panel);
|
||||
if (tb !== null) tb.isUnderline = false;
|
||||
},
|
||||
click: click, // defined above
|
||||
toolTip: tooltip // shared by all HyperlinkText panels
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/* CSS for the lightbox context menu */
|
||||
/* see also LightBoxContextMenu.js and samples/htmlLightBoxContextMenu.html */
|
||||
#contextMenuDIV {
|
||||
|
||||
}
|
||||
|
||||
#cmLight {
|
||||
top: 0px;
|
||||
z-index:10002;
|
||||
position: fixed;
|
||||
text-align: center;
|
||||
left: 25%;
|
||||
width: 50%;
|
||||
background-color: #F5F5F5;
|
||||
padding: 16px;
|
||||
border: 16px solid #444;
|
||||
border-radius: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#cmDark {
|
||||
z-index:10001;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: black;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
#cmLight ul { list-style: none; }
|
||||
#cmLight li {
|
||||
font:700 1.5em Helvetica, Arial, sans-serif;
|
||||
position: relative;
|
||||
min-width: 60px; }
|
||||
#cmLight a {
|
||||
color: #444;
|
||||
display: inline-block;
|
||||
padding: 4px;
|
||||
text-decoration: none;
|
||||
margin: 2px;
|
||||
border: 1px solid gray;
|
||||
border-radius: 10px;
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
// HTML + JavaScript context menu, made with HTMLInfo
|
||||
// This file exposes one instance of HTMLInfo, window.myHTMLLightBox
|
||||
// see also LightBoxContextMenu.css and /samples/htmlLightBoxContextMenu.html
|
||||
(function (window) {
|
||||
/* HTML for context menu:
|
||||
<div id="contextMenuDIV">
|
||||
<div id="cmLight"></div>
|
||||
<div id="cmDark"></div>
|
||||
</div>
|
||||
*/
|
||||
var contextMenuDIV = document.createElement('div');
|
||||
contextMenuDIV.id = 'contextMenuDIV';
|
||||
// This is the actual HTML LightBox-style context menu, composed of buttons and a background:
|
||||
var cmLight = document.createElement('div');
|
||||
cmLight.id = 'cmLight';
|
||||
cmLight.className = 'goCXforeground';
|
||||
var cmDark = document.createElement('div');
|
||||
cmDark.id = 'cmDark';
|
||||
cmDark.className = 'goCXbackground';
|
||||
contextMenuDIV.appendChild(cmLight);
|
||||
contextMenuDIV.appendChild(cmDark);
|
||||
var cxMenuButtons = [
|
||||
{
|
||||
text: 'Copy',
|
||||
command: function (diagram) { diagram.commandHandler.copySelection(); },
|
||||
isVisible: function (diagram) { return diagram.commandHandler.canCopySelection(); }
|
||||
}, {
|
||||
text: 'Cut',
|
||||
command: function (diagram) { diagram.commandHandler.cutSelection(); },
|
||||
isVisible: function (diagram) { return diagram.commandHandler.canCutSelection(); }
|
||||
}, {
|
||||
text: 'Delete',
|
||||
command: function (diagram) { diagram.commandHandler.deleteSelection(); },
|
||||
isVisible: function (diagram) { return diagram.commandHandler.canDeleteSelection(); }
|
||||
}, {
|
||||
text: 'Paste',
|
||||
command: function (diagram) { diagram.commandHandler.pasteSelection(diagram.toolManager.contextMenuTool.mouseDownPoint); },
|
||||
isVisible: function (diagram) { return diagram.commandHandler.canPasteSelection(diagram.toolManager.contextMenuTool.mouseDownPoint); }
|
||||
}, {
|
||||
text: 'Select All',
|
||||
command: function (diagram) { diagram.commandHandler.selectAll(); },
|
||||
isVisible: function (diagram) { return diagram.commandHandler.canSelectAll(); }
|
||||
}, {
|
||||
text: 'Undo',
|
||||
command: function (diagram) { diagram.commandHandler.undo(); },
|
||||
isVisible: function (diagram) { return diagram.commandHandler.canUndo(); }
|
||||
}, {
|
||||
text: 'Redo',
|
||||
command: function (diagram) { diagram.commandHandler.redo(); },
|
||||
isVisible: function (diagram) { return diagram.commandHandler.canRedo(); }
|
||||
}, {
|
||||
text: 'Scroll To Part',
|
||||
command: function (diagram) { diagram.commandHandler.scrollToPart(); },
|
||||
isVisible: function (diagram) { return diagram.commandHandler.canScrollToPart(); }
|
||||
}, {
|
||||
text: 'Zoom To Fit',
|
||||
command: function (diagram) { diagram.commandHandler.zoomToFit(); },
|
||||
isVisible: function (diagram) { return diagram.commandHandler.canZoomToFit(); }
|
||||
}, {
|
||||
text: 'Reset Zoom',
|
||||
command: function (diagram) { diagram.commandHandler.resetZoom(); },
|
||||
isVisible: function (diagram) { return diagram.commandHandler.canResetZoom(); }
|
||||
}, {
|
||||
text: 'Group Selection',
|
||||
command: function (diagram) { diagram.commandHandler.groupSelection(); },
|
||||
isVisible: function (diagram) { return diagram.commandHandler.canGroupSelection(); }
|
||||
}, {
|
||||
text: 'Ungroup Selection',
|
||||
command: function (diagram) { diagram.commandHandler.ungroupSelection(); },
|
||||
isVisible: function (diagram) { return diagram.commandHandler.canUngroupSelection(); }
|
||||
}, {
|
||||
text: 'Edit Text',
|
||||
command: function (diagram) { diagram.commandHandler.editTextBlock(); },
|
||||
isVisible: function (diagram) { return diagram.commandHandler.canEditTextBlock(); }
|
||||
}
|
||||
];
|
||||
var $ = go.GraphObject.make;
|
||||
var myContextMenu = $(go.HTMLInfo, {
|
||||
show: showContextMenu,
|
||||
hide: hideContextMenu
|
||||
});
|
||||
var firstTime = true;
|
||||
function showContextMenu(obj, diagram, tool) {
|
||||
if (firstTime) {
|
||||
// We don't want the div acting as a context menu to have a (browser) context menu!
|
||||
cmLight.addEventListener('contextmenu', function (e) { e.preventDefault(); return false; }, false);
|
||||
cmLight.addEventListener('selectstart', function (e) { e.preventDefault(); return false; }, false);
|
||||
contextMenuDIV.addEventListener('contextmenu', function (e) { e.preventDefault(); return false; }, false);
|
||||
// Stop the context menu tool if you click on the dark part:
|
||||
contextMenuDIV.addEventListener('click', function (e) { diagram.currentTool.stopTool(); return false; }, false);
|
||||
firstTime = false;
|
||||
}
|
||||
// Empty the context menu and only show buttons that are relevant
|
||||
cmLight.innerHTML = '';
|
||||
var ul = document.createElement('ul');
|
||||
cmLight.appendChild(ul);
|
||||
var _loop_1 = function (i) {
|
||||
var button = cxMenuButtons[i];
|
||||
var command = button.command;
|
||||
var isVisible = button.isVisible;
|
||||
if (!(typeof command === 'function'))
|
||||
return "continue";
|
||||
// Only show buttons that have isVisible = true
|
||||
if (typeof isVisible === 'function' && !isVisible(diagram))
|
||||
return "continue";
|
||||
var li = document.createElement('li');
|
||||
var ahref = document.createElement('a');
|
||||
ahref.href = '#';
|
||||
ahref._command = button.command;
|
||||
ahref.addEventListener('click', function (e) {
|
||||
ahref._command(diagram);
|
||||
tool.stopTool();
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}, false);
|
||||
ahref.textContent = button.text;
|
||||
li.appendChild(ahref);
|
||||
ul.appendChild(li);
|
||||
};
|
||||
for (var i = 0; i < cxMenuButtons.length; i++) {
|
||||
_loop_1(i);
|
||||
}
|
||||
// show the whole LightBox context menu
|
||||
document.body.appendChild(contextMenuDIV);
|
||||
}
|
||||
function hideContextMenu(diagram, tool) {
|
||||
document.body.removeChild(contextMenuDIV);
|
||||
}
|
||||
window.myHTMLLightBox = myContextMenu;
|
||||
})(window);
|
||||
});
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
|
||||
// HTML + JavaScript context menu, made with HTMLInfo
|
||||
// This file exposes one instance of HTMLInfo, window.myHTMLLightBox
|
||||
// see also LightBoxContextMenu.css and /samples/htmlLightBoxContextMenu.html
|
||||
(function(window) {
|
||||
/* HTML for context menu:
|
||||
<div id="contextMenuDIV">
|
||||
<div id="cmLight"></div>
|
||||
<div id="cmDark"></div>
|
||||
</div>
|
||||
*/
|
||||
const contextMenuDIV = document.createElement('div');
|
||||
contextMenuDIV.id = 'contextMenuDIV';
|
||||
// This is the actual HTML LightBox-style context menu, composed of buttons and a background:
|
||||
const cmLight = document.createElement('div');
|
||||
cmLight.id = 'cmLight';
|
||||
cmLight.className = 'goCXforeground';
|
||||
const cmDark = document.createElement('div');
|
||||
cmDark.id = 'cmDark';
|
||||
cmDark.className = 'goCXbackground';
|
||||
contextMenuDIV.appendChild(cmLight);
|
||||
contextMenuDIV.appendChild(cmDark);
|
||||
|
||||
const cxMenuButtons = [
|
||||
{
|
||||
text: 'Copy',
|
||||
command: (diagram: go.Diagram) => { diagram.commandHandler.copySelection(); },
|
||||
isVisible: (diagram: go.Diagram) => diagram.commandHandler.canCopySelection()
|
||||
}, {
|
||||
text: 'Cut',
|
||||
command: (diagram: go.Diagram) => { diagram.commandHandler.cutSelection(); },
|
||||
isVisible: (diagram: go.Diagram) => diagram.commandHandler.canCutSelection()
|
||||
}, {
|
||||
text: 'Delete',
|
||||
command: (diagram: go.Diagram) => { diagram.commandHandler.deleteSelection(); },
|
||||
isVisible: (diagram: go.Diagram) => diagram.commandHandler.canDeleteSelection()
|
||||
}, {
|
||||
text: 'Paste',
|
||||
command: (diagram: go.Diagram) => { diagram.commandHandler.pasteSelection(diagram.toolManager.contextMenuTool.mouseDownPoint); },
|
||||
isVisible: (diagram: go.Diagram) => diagram.commandHandler.canPasteSelection(diagram.toolManager.contextMenuTool.mouseDownPoint)
|
||||
}, {
|
||||
text: 'Select All',
|
||||
command: (diagram: go.Diagram) => { diagram.commandHandler.selectAll(); },
|
||||
isVisible: (diagram: go.Diagram) => diagram.commandHandler.canSelectAll()
|
||||
}, {
|
||||
text: 'Undo',
|
||||
command: (diagram: go.Diagram) => { diagram.commandHandler.undo(); },
|
||||
isVisible: (diagram: go.Diagram) => diagram.commandHandler.canUndo()
|
||||
}, {
|
||||
text: 'Redo',
|
||||
command: (diagram: go.Diagram) => { diagram.commandHandler.redo(); },
|
||||
isVisible: (diagram: go.Diagram) => diagram.commandHandler.canRedo()
|
||||
}, {
|
||||
text: 'Scroll To Part',
|
||||
command: (diagram: go.Diagram) => { diagram.commandHandler.scrollToPart(); },
|
||||
isVisible: (diagram: go.Diagram) => diagram.commandHandler.canScrollToPart()
|
||||
}, {
|
||||
text: 'Zoom To Fit',
|
||||
command: (diagram: go.Diagram) => { diagram.commandHandler.zoomToFit(); },
|
||||
isVisible: (diagram: go.Diagram) => diagram.commandHandler.canZoomToFit()
|
||||
}, {
|
||||
text: 'Reset Zoom',
|
||||
command: (diagram: go.Diagram) => { diagram.commandHandler.resetZoom(); },
|
||||
isVisible: (diagram: go.Diagram) => diagram.commandHandler.canResetZoom()
|
||||
}, {
|
||||
text: 'Group Selection',
|
||||
command: (diagram: go.Diagram) => { diagram.commandHandler.groupSelection(); },
|
||||
isVisible: (diagram: go.Diagram) => diagram.commandHandler.canGroupSelection()
|
||||
}, {
|
||||
text: 'Ungroup Selection',
|
||||
command: (diagram: go.Diagram) => { diagram.commandHandler.ungroupSelection(); },
|
||||
isVisible: (diagram: go.Diagram) => diagram.commandHandler.canUngroupSelection()
|
||||
}, {
|
||||
text: 'Edit Text',
|
||||
command: (diagram: go.Diagram) => { diagram.commandHandler.editTextBlock(); },
|
||||
isVisible: (diagram: go.Diagram) => diagram.commandHandler.canEditTextBlock()
|
||||
}
|
||||
];
|
||||
|
||||
const $ = go.GraphObject.make;
|
||||
const myContextMenu = $(go.HTMLInfo, {
|
||||
show: showContextMenu,
|
||||
hide: hideContextMenu
|
||||
});
|
||||
|
||||
let firstTime = true;
|
||||
|
||||
function showContextMenu(obj: go.GraphObject, diagram: go.Diagram, tool: go.Tool) {
|
||||
if (firstTime) {
|
||||
// We don't want the div acting as a context menu to have a (browser) context menu!
|
||||
cmLight.addEventListener('contextmenu', (e) => { e.preventDefault(); return false; }, false);
|
||||
cmLight.addEventListener('selectstart', (e) => { e.preventDefault(); return false; }, false);
|
||||
contextMenuDIV.addEventListener('contextmenu', (e) => { e.preventDefault(); return false; }, false);
|
||||
// Stop the context menu tool if you click on the dark part:
|
||||
contextMenuDIV.addEventListener('click', (e) => { diagram.currentTool.stopTool(); return false; }, false);
|
||||
firstTime = false;
|
||||
}
|
||||
|
||||
// Empty the context menu and only show buttons that are relevant
|
||||
cmLight.innerHTML = '';
|
||||
|
||||
const ul = document.createElement('ul');
|
||||
cmLight.appendChild(ul);
|
||||
|
||||
for (let i = 0; i < cxMenuButtons.length; i++) {
|
||||
const button = cxMenuButtons[i];
|
||||
const command = button.command;
|
||||
const isVisible = button.isVisible;
|
||||
|
||||
if (!(typeof command === 'function')) continue;
|
||||
// Only show buttons that have isVisible = true
|
||||
if (typeof isVisible === 'function' && !isVisible(diagram)) continue;
|
||||
const li = document.createElement('li');
|
||||
const ahref = document.createElement('a');
|
||||
ahref.href = '#';
|
||||
(ahref as any)._command = button.command;
|
||||
ahref.addEventListener('click', (e) => {
|
||||
(ahref as any)._command(diagram);
|
||||
tool.stopTool();
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}, false);
|
||||
ahref.textContent = button.text;
|
||||
li.appendChild(ahref);
|
||||
ul.appendChild(li);
|
||||
}
|
||||
|
||||
// show the whole LightBox context menu
|
||||
document.body.appendChild(contextMenuDIV);
|
||||
}
|
||||
|
||||
function hideContextMenu(diagram: go.Diagram, tool: go.Tool) {
|
||||
document.body.removeChild(contextMenuDIV);
|
||||
}
|
||||
|
||||
(window as any).myHTMLLightBox = myContextMenu;
|
||||
})(window);
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>State Chart with Draggable Link Labels</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="TypeScript: Allow the user to shift link labels; useful when the user wants to adjust the position of the label relative to the link path to avoid overlapping the link path or any nodes." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../samples/assets/require.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
function init() {
|
||||
require(["LinkLabelDraggingScript"], function(app) {
|
||||
app.init();
|
||||
document.getElementById("SaveButton").onclick = app.save;
|
||||
document.getElementById("LoadButton").onclick = app.load;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="background-color: whitesmoke; border: solid 1px black; width: 100%; height: 400px"></div>
|
||||
<p>
|
||||
This sample is a modification of the <a href="../samples/stateChart.html">State Chart</a> sample that makes use of
|
||||
the LinkLabelDraggingTool that is defined in its own file, as <a href="LinkLabelDraggingTool.ts">LinkLabelDraggingTool.ts</a>.
|
||||
</p>
|
||||
<p>
|
||||
Note that after dragging a link label you can move a node connected by that link and the label maintains the same position
|
||||
relative to the link route. That relative position is specified by the <a>GraphObject.segmentOffset</a> property.
|
||||
This sample also saves any changes to that property by means of a TwoWay <a>Binding</a>.
|
||||
</p>
|
||||
<p>
|
||||
See also the similar <a href="LinkLabelOnPathDragging.html" target="_blank">Link Label On Path Dragging sample</a>,
|
||||
where the label is constrained to remain on the path of the link.
|
||||
</p>
|
||||
<button id="SaveButton">Save</button>
|
||||
<button id="LoadButton">Load</button> Diagram Model saved in JSON format:
|
||||
<br />
|
||||
<textarea id="mySavedModel" style="width:100%;height:300px">
|
||||
{ "nodeKeyProperty": "id",
|
||||
"nodeDataArray": [
|
||||
{ "id": 0, "loc": "120 120", "text": "Initial" },
|
||||
{ "id": 1, "loc": "330 120", "text": "First down" },
|
||||
{ "id": 2, "loc": "226 376", "text": "First up" },
|
||||
{ "id": 3, "loc": "60 276", "text": "Second down" },
|
||||
{ "id": 4, "loc": "226 226", "text": "Wait" }
|
||||
],
|
||||
"linkDataArray": [
|
||||
{ "from": 0, "to": 0, "text": "up or timer", "curviness": -20 },
|
||||
{ "from": 0, "to": 1, "text": "down", "curviness": 20 },
|
||||
{ "from": 1, "to": 0, "text": "up (moved)\nPOST", "curviness": 20 },
|
||||
{ "from": 1, "to": 1, "text": "down", "curviness": -20 },
|
||||
{ "from": 1, "to": 2, "text": "up (no move)" },
|
||||
{ "from": 1, "to": 4, "text": "timer" },
|
||||
{ "from": 2, "to": 0, "text": "timer\nPOST" },
|
||||
{ "from": 2, "to": 3, "text": "down" },
|
||||
{ "from": 3, "to": 0, "text": "up\nPOST\n(dblclick\nif no move)" },
|
||||
{ "from": 3, "to": 3, "text": "down or timer", "curviness": 20 },
|
||||
{ "from": 4, "to": 0, "text": "up\nPOST" },
|
||||
{ "from": 4, "to": 4, "text": "down" }
|
||||
]
|
||||
}
|
||||
</textarea>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js", "./LinkLabelDraggingTool.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.load = exports.save = exports.init = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
var LinkLabelDraggingTool_js_1 = require("./LinkLabelDraggingTool.js");
|
||||
var myDiagram;
|
||||
function init() {
|
||||
if (window.goSamples)
|
||||
window.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', // must name or refer to the DIV HTML element
|
||||
{
|
||||
// have mouse wheel events zoom in and out instead of scroll up and down
|
||||
'toolManager.mouseWheelBehavior': go.ToolManager.WheelZoom,
|
||||
// support double-click in background creating a new node
|
||||
'clickCreatingTool.archetypeNodeData': { text: 'new node' },
|
||||
// enable undo & redo
|
||||
'undoManager.isEnabled': true
|
||||
});
|
||||
// install the LinkLabelDraggingTool as a "mouse move" tool
|
||||
myDiagram.toolManager.mouseMoveTools.insertAt(0, new LinkLabelDraggingTool_js_1.LinkLabelDraggingTool());
|
||||
// when the document is modified, add a "*" to the title and enable the "Save" button
|
||||
myDiagram.addDiagramListener('Modified', function (e) {
|
||||
var button = document.getElementById('SaveButton');
|
||||
if (button)
|
||||
button.disabled = !myDiagram.isModified;
|
||||
var idx = document.title.indexOf('*');
|
||||
if (myDiagram.isModified) {
|
||||
if (idx < 0)
|
||||
document.title += '*';
|
||||
}
|
||||
else {
|
||||
if (idx >= 0)
|
||||
document.title = document.title.substr(0, idx);
|
||||
}
|
||||
});
|
||||
// define the Node template
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, 'Auto', new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
// define the node's outer shape, which will surround the TextBlock
|
||||
$(go.Shape, 'RoundedRectangle', {
|
||||
parameter1: 20,
|
||||
fill: $(go.Brush, 'Linear', { 0: 'rgb(254, 201, 0)', 1: 'rgb(254, 162, 0)' }),
|
||||
stroke: 'black',
|
||||
portId: '',
|
||||
fromLinkable: true,
|
||||
fromLinkableSelfNode: true,
|
||||
fromLinkableDuplicates: true,
|
||||
toLinkable: true,
|
||||
toLinkableSelfNode: true,
|
||||
toLinkableDuplicates: true,
|
||||
cursor: 'pointer'
|
||||
}), $(go.TextBlock, {
|
||||
font: 'bold 11pt helvetica, bold arial, sans-serif',
|
||||
editable: true // editing the text automatically updates the model data
|
||||
}, new go.Binding('text', 'text').makeTwoWay()));
|
||||
// unlike the normal selection Adornment, this one includes a Button
|
||||
myDiagram.nodeTemplate.selectionAdornmentTemplate =
|
||||
$(go.Adornment, 'Spot', $(go.Panel, 'Auto', $(go.Shape, { fill: null, stroke: 'blue', strokeWidth: 2 }), $(go.Placeholder) // this represents the selected Node
|
||||
),
|
||||
// the button to create a "next" node, at the top-right corner
|
||||
$('Button', {
|
||||
alignment: go.Spot.TopRight,
|
||||
click: addNodeAndLink // this function is defined below
|
||||
}, $(go.Shape, 'PlusLine', { desiredSize: new go.Size(6, 6) })) // end button
|
||||
); // end Adornment
|
||||
// clicking the button inserts a new node to the right of the selected node,
|
||||
// and adds a link to that new node
|
||||
function addNodeAndLink(e, obj) {
|
||||
var adorn = obj.part;
|
||||
var fromNode = adorn.adornedPart;
|
||||
if (fromNode === null)
|
||||
return;
|
||||
e.handled = true;
|
||||
var diagram = e.diagram;
|
||||
diagram.startTransaction('Add State');
|
||||
// get the node data for which the user clicked the button
|
||||
var fromData = fromNode.data;
|
||||
// create a new "State" data object, positioned off to the right of the adorned Node
|
||||
var toData = { text: 'new' };
|
||||
var p = fromNode.location.copy();
|
||||
p.x += 200;
|
||||
toData.loc = go.Point.stringify(p); // the "loc" property is a string, not a Point object
|
||||
// add the new node data to the model
|
||||
var model = diagram.model;
|
||||
model.addNodeData(toData);
|
||||
// create a link data from the old node data to the new node data
|
||||
var linkdata = {
|
||||
from: model.getKeyForNodeData(fromData),
|
||||
to: model.getKeyForNodeData(toData),
|
||||
text: 'transition'
|
||||
};
|
||||
// and add the link data to the model
|
||||
model.addLinkData(linkdata);
|
||||
// select the new Node
|
||||
var newnode = diagram.findNodeForData(toData);
|
||||
diagram.select(newnode);
|
||||
diagram.commitTransaction('Add State');
|
||||
// if the new node is off-screen, scroll the diagram to show the new node
|
||||
if (newnode !== null)
|
||||
diagram.scrollToRect(newnode.actualBounds);
|
||||
}
|
||||
// replace the default Link template in the linkTemplateMap
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link, // the whole link panel
|
||||
{ curve: go.Link.Bezier, adjusting: go.Link.Stretch, reshapable: true }, new go.Binding('points').makeTwoWay(), new go.Binding('curviness', 'curviness'), $(go.Shape, // the link shape
|
||||
{ strokeWidth: 1.5 }), $(go.Shape, // the arrowhead
|
||||
{ toArrow: 'standard', stroke: null }), $(go.Panel, 'Auto', { cursor: 'move' }, // visual hint that the user can do something with this link label
|
||||
$(go.Shape, // the label background, which becomes transparent around the edges
|
||||
{
|
||||
fill: $(go.Brush, 'Radial', { 0: 'rgb(240, 240, 240)', 0.3: 'rgb(240, 240, 240)', 1: 'rgba(240, 240, 240, 0)' }),
|
||||
stroke: null
|
||||
}), $(go.TextBlock, 'transition', // the label text
|
||||
{
|
||||
textAlign: 'center',
|
||||
font: '10pt helvetica, arial, sans-serif',
|
||||
stroke: 'black',
|
||||
margin: 4,
|
||||
editable: true // editing the text automatically updates the model data
|
||||
}, new go.Binding('text', 'text').makeTwoWay()),
|
||||
// The GraphObject.segmentOffset property is what the LinkLabelDraggingTool modifies.
|
||||
// This TwoWay binding saves any changes to the same named property on the link data.
|
||||
new go.Binding('segmentOffset', 'segmentOffset', go.Point.parse).makeTwoWay(go.Point.stringify)));
|
||||
// read in the JSON-format data from the "mySavedModel" element
|
||||
load();
|
||||
// Attach to the window for console manipulation
|
||||
window.myDiagram = myDiagram;
|
||||
}
|
||||
exports.init = init;
|
||||
// Show the diagram's model in JSON format
|
||||
function save() {
|
||||
document.getElementById('mySavedModel').value = myDiagram.model.toJson();
|
||||
myDiagram.isModified = false;
|
||||
}
|
||||
exports.save = save;
|
||||
function load() {
|
||||
myDiagram.model = go.Model.fromJson(document.getElementById('mySavedModel').value);
|
||||
}
|
||||
exports.load = load;
|
||||
});
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
import { LinkLabelDraggingTool } from './LinkLabelDraggingTool.js';
|
||||
|
||||
let myDiagram: go.Diagram;
|
||||
|
||||
export function init() {
|
||||
if ((window as any).goSamples) (window as any).goSamples(); // init for these samples -- you don't need to call this
|
||||
|
||||
const $ = go.GraphObject.make; // for conciseness in defining templates
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, 'myDiagramDiv', // must name or refer to the DIV HTML element
|
||||
{
|
||||
// have mouse wheel events zoom in and out instead of scroll up and down
|
||||
'toolManager.mouseWheelBehavior': go.ToolManager.WheelZoom,
|
||||
// support double-click in background creating a new node
|
||||
'clickCreatingTool.archetypeNodeData': { text: 'new node' },
|
||||
// enable undo & redo
|
||||
'undoManager.isEnabled': true
|
||||
});
|
||||
|
||||
// install the LinkLabelDraggingTool as a "mouse move" tool
|
||||
myDiagram.toolManager.mouseMoveTools.insertAt(0, new LinkLabelDraggingTool());
|
||||
|
||||
// when the document is modified, add a "*" to the title and enable the "Save" button
|
||||
myDiagram.addDiagramListener('Modified', (e: go.DiagramEvent) => {
|
||||
const button = (document.getElementById('SaveButton') as any);
|
||||
if (button) button.disabled = !myDiagram.isModified;
|
||||
const idx = document.title.indexOf('*');
|
||||
if (myDiagram.isModified) {
|
||||
if (idx < 0) document.title += '*';
|
||||
} else {
|
||||
if (idx >= 0) document.title = document.title.substr(0, idx);
|
||||
}
|
||||
});
|
||||
|
||||
// define the Node template
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, 'Auto',
|
||||
new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
// define the node's outer shape, which will surround the TextBlock
|
||||
$(go.Shape, 'RoundedRectangle',
|
||||
{
|
||||
parameter1: 20, // the corner has a large radius
|
||||
fill: $(go.Brush, 'Linear', { 0: 'rgb(254, 201, 0)', 1: 'rgb(254, 162, 0)' }),
|
||||
stroke: 'black',
|
||||
portId: '',
|
||||
fromLinkable: true,
|
||||
fromLinkableSelfNode: true,
|
||||
fromLinkableDuplicates: true,
|
||||
toLinkable: true,
|
||||
toLinkableSelfNode: true,
|
||||
toLinkableDuplicates: true,
|
||||
cursor: 'pointer'
|
||||
}),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
font: 'bold 11pt helvetica, bold arial, sans-serif',
|
||||
editable: true // editing the text automatically updates the model data
|
||||
},
|
||||
new go.Binding('text', 'text').makeTwoWay())
|
||||
);
|
||||
|
||||
// unlike the normal selection Adornment, this one includes a Button
|
||||
myDiagram.nodeTemplate.selectionAdornmentTemplate =
|
||||
$(go.Adornment, 'Spot',
|
||||
$(go.Panel, 'Auto',
|
||||
$(go.Shape, { fill: null, stroke: 'blue', strokeWidth: 2 }),
|
||||
$(go.Placeholder) // this represents the selected Node
|
||||
),
|
||||
// the button to create a "next" node, at the top-right corner
|
||||
$('Button',
|
||||
{
|
||||
alignment: go.Spot.TopRight,
|
||||
click: addNodeAndLink // this function is defined below
|
||||
},
|
||||
$(go.Shape, 'PlusLine', { desiredSize: new go.Size(6, 6) })
|
||||
) // end button
|
||||
); // end Adornment
|
||||
|
||||
// clicking the button inserts a new node to the right of the selected node,
|
||||
// and adds a link to that new node
|
||||
function addNodeAndLink(e: go.InputEvent, obj: go.GraphObject) {
|
||||
const adorn = obj.part as go.Adornment;
|
||||
const fromNode = adorn.adornedPart;
|
||||
if (fromNode === null) return;
|
||||
|
||||
e.handled = true;
|
||||
const diagram = e.diagram;
|
||||
diagram.startTransaction('Add State');
|
||||
|
||||
// get the node data for which the user clicked the button
|
||||
const fromData = fromNode.data;
|
||||
// create a new "State" data object, positioned off to the right of the adorned Node
|
||||
const toData: any = { text: 'new' };
|
||||
const p = fromNode.location.copy();
|
||||
p.x += 200;
|
||||
toData.loc = go.Point.stringify(p); // the "loc" property is a string, not a Point object
|
||||
// add the new node data to the model
|
||||
const model = diagram.model as go.GraphLinksModel;
|
||||
model.addNodeData(toData);
|
||||
|
||||
// create a link data from the old node data to the new node data
|
||||
const linkdata = {
|
||||
from: model.getKeyForNodeData(fromData), // or just: fromData.id
|
||||
to: model.getKeyForNodeData(toData),
|
||||
text: 'transition'
|
||||
};
|
||||
// and add the link data to the model
|
||||
model.addLinkData(linkdata);
|
||||
|
||||
// select the new Node
|
||||
const newnode = diagram.findNodeForData(toData);
|
||||
diagram.select(newnode);
|
||||
|
||||
diagram.commitTransaction('Add State');
|
||||
|
||||
// if the new node is off-screen, scroll the diagram to show the new node
|
||||
if (newnode !== null) diagram.scrollToRect(newnode.actualBounds);
|
||||
}
|
||||
|
||||
// replace the default Link template in the linkTemplateMap
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link, // the whole link panel
|
||||
{ curve: go.Link.Bezier, adjusting: go.Link.Stretch, reshapable: true },
|
||||
new go.Binding('points').makeTwoWay(),
|
||||
new go.Binding('curviness', 'curviness'),
|
||||
$(go.Shape, // the link shape
|
||||
{ strokeWidth: 1.5 }),
|
||||
$(go.Shape, // the arrowhead
|
||||
{ toArrow: 'standard', stroke: null }),
|
||||
$(go.Panel, 'Auto',
|
||||
{ cursor: 'move' }, // visual hint that the user can do something with this link label
|
||||
$(go.Shape, // the label background, which becomes transparent around the edges
|
||||
{
|
||||
fill: $(go.Brush, 'Radial',
|
||||
{ 0: 'rgb(240, 240, 240)', 0.3: 'rgb(240, 240, 240)', 1: 'rgba(240, 240, 240, 0)' }),
|
||||
stroke: null
|
||||
}),
|
||||
$(go.TextBlock, 'transition', // the label text
|
||||
{
|
||||
textAlign: 'center',
|
||||
font: '10pt helvetica, arial, sans-serif',
|
||||
stroke: 'black',
|
||||
margin: 4,
|
||||
editable: true // editing the text automatically updates the model data
|
||||
},
|
||||
new go.Binding('text', 'text').makeTwoWay()),
|
||||
// The GraphObject.segmentOffset property is what the LinkLabelDraggingTool modifies.
|
||||
// This TwoWay binding saves any changes to the same named property on the link data.
|
||||
new go.Binding('segmentOffset', 'segmentOffset', go.Point.parse).makeTwoWay(go.Point.stringify)
|
||||
)
|
||||
);
|
||||
|
||||
// read in the JSON-format data from the "mySavedModel" element
|
||||
load();
|
||||
|
||||
// Attach to the window for console manipulation
|
||||
(window as any).myDiagram = myDiagram;
|
||||
}
|
||||
|
||||
// Show the diagram's model in JSON format
|
||||
export function save() {
|
||||
(document.getElementById('mySavedModel') as any).value = myDiagram.model.toJson();
|
||||
myDiagram.isModified = false;
|
||||
}
|
||||
export function load() {
|
||||
myDiagram.model = go.Model.fromJson((document.getElementById('mySavedModel') as any).value);
|
||||
}
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.LinkLabelDraggingTool = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
/**
|
||||
* The LinkLabelDraggingTool class lets the user move a label on a {@link Link}.
|
||||
*
|
||||
* This tool only works when the Link has a label
|
||||
* that is positioned at the {@link Link#midPoint} plus some offset.
|
||||
* It does not work for labels that have a particular {@link GraphObject#segmentIndex}.
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/LinkLabelDragging.html">Link Label Dragging</a> sample.
|
||||
* @category Tool Extension
|
||||
*/
|
||||
var LinkLabelDraggingTool = /** @class */ (function (_super) {
|
||||
__extends(LinkLabelDraggingTool, _super);
|
||||
/**
|
||||
* Constructs a LinkLabelDraggingTool and sets the name for the tool.
|
||||
*/
|
||||
function LinkLabelDraggingTool() {
|
||||
var _this = _super.call(this) || this;
|
||||
/**
|
||||
* The label being dragged.
|
||||
*/
|
||||
_this.label = null;
|
||||
_this._offset = new go.Point(); // of the mouse relative to the center of the label object
|
||||
_this._originalOffset = null;
|
||||
_this.name = 'LinkLabelDragging';
|
||||
return _this;
|
||||
}
|
||||
/**
|
||||
* From the GraphObject at the mouse point, search up the visual tree until we get to
|
||||
* an object that is a label of a Link.
|
||||
* @return {GraphObject} This returns null if no such label is at the mouse down point.
|
||||
*/
|
||||
LinkLabelDraggingTool.prototype.findLabel = function () {
|
||||
var diagram = this.diagram;
|
||||
var e = diagram.lastInput;
|
||||
var elt = diagram.findObjectAt(e.documentPoint, null, null);
|
||||
if (elt === null || !(elt.part instanceof go.Link))
|
||||
return null;
|
||||
while (elt !== null && elt.panel !== elt.part) {
|
||||
elt = elt.panel;
|
||||
}
|
||||
// If it's at an arrowhead segment index, don't consider it a label:
|
||||
if (elt !== null && (elt.segmentIndex === 0 || elt.segmentIndex === -1))
|
||||
return null;
|
||||
return elt;
|
||||
};
|
||||
/**
|
||||
* This tool can only start if the mouse has moved enough so that it is not a click,
|
||||
* and if the mouse down point is on a GraphObject "label" in a Link Panel,
|
||||
* as determined by {@link #findLabel}.
|
||||
*/
|
||||
LinkLabelDraggingTool.prototype.canStart = function () {
|
||||
if (!_super.prototype.canStart.call(this))
|
||||
return false;
|
||||
var diagram = this.diagram;
|
||||
// require left button & that it has moved far enough away from the mouse down point, so it isn't a click
|
||||
var e = diagram.lastInput;
|
||||
if (!e.left)
|
||||
return false;
|
||||
if (!this.isBeyondDragSize())
|
||||
return false;
|
||||
return this.findLabel() !== null;
|
||||
};
|
||||
/**
|
||||
* Start a transaction, call {@link #findLabel} and remember it as the "label" property,
|
||||
* and remember the original value for the label's {@link GraphObject#segmentOffset} property.
|
||||
*/
|
||||
LinkLabelDraggingTool.prototype.doActivate = function () {
|
||||
this.startTransaction('Shifted Label');
|
||||
this.label = this.findLabel();
|
||||
if (this.label !== null) {
|
||||
// compute the offset of the mouse-down point relative to the center of the label
|
||||
this._offset = this.diagram.firstInput.documentPoint.copy().subtract(this.label.getDocumentPoint(go.Spot.Center));
|
||||
this._originalOffset = this.label.segmentOffset.copy();
|
||||
}
|
||||
_super.prototype.doActivate.call(this);
|
||||
};
|
||||
/**
|
||||
* Stop any ongoing transaction.
|
||||
*/
|
||||
LinkLabelDraggingTool.prototype.doDeactivate = function () {
|
||||
_super.prototype.doDeactivate.call(this);
|
||||
this.stopTransaction();
|
||||
};
|
||||
/**
|
||||
* Clear any reference to a label element.
|
||||
*/
|
||||
LinkLabelDraggingTool.prototype.doStop = function () {
|
||||
this.label = null;
|
||||
_super.prototype.doStop.call(this);
|
||||
};
|
||||
/**
|
||||
* Restore the label's original value for {@link GraphObject#segmentOffset}.
|
||||
*/
|
||||
LinkLabelDraggingTool.prototype.doCancel = function () {
|
||||
if (this.label !== null && this._originalOffset !== null) {
|
||||
this.label.segmentOffset = this._originalOffset;
|
||||
}
|
||||
_super.prototype.doCancel.call(this);
|
||||
};
|
||||
/**
|
||||
* During the drag, call {@link #updateSegmentOffset} in order to set
|
||||
* the {@link GraphObject#segmentOffset} of the label.
|
||||
*/
|
||||
LinkLabelDraggingTool.prototype.doMouseMove = function () {
|
||||
if (!this.isActive)
|
||||
return;
|
||||
this.updateSegmentOffset();
|
||||
};
|
||||
/**
|
||||
* At the end of the drag, update the segment offset of the label and finish the tool,
|
||||
* completing a transaction.
|
||||
*/
|
||||
LinkLabelDraggingTool.prototype.doMouseUp = function () {
|
||||
if (!this.isActive)
|
||||
return;
|
||||
this.updateSegmentOffset();
|
||||
this.transactionResult = 'Shifted Label';
|
||||
this.stopTool();
|
||||
};
|
||||
/**
|
||||
* Save the label's {@link GraphObject#segmentOffset} as a rotated offset from the midpoint of the
|
||||
* Link that the label is in.
|
||||
*/
|
||||
LinkLabelDraggingTool.prototype.updateSegmentOffset = function () {
|
||||
var lab = this.label;
|
||||
if (lab === null)
|
||||
return;
|
||||
var link = lab.part;
|
||||
if (!(link instanceof go.Link))
|
||||
return;
|
||||
var last = this.diagram.lastInput.documentPoint;
|
||||
var idx = lab.segmentIndex;
|
||||
var numpts = link.pointsCount;
|
||||
// if the label is a "mid" label, assume it is positioned differently from a label at a particular segment
|
||||
if (idx < -numpts || idx >= numpts) {
|
||||
var mid = link.midPoint;
|
||||
// need to rotate this point to account for the angle of the link segment at the mid-point
|
||||
var p = new go.Point(last.x - this._offset.x - mid.x, last.y - this._offset.y - mid.y);
|
||||
lab.segmentOffset = p.rotate(-link.midAngle);
|
||||
}
|
||||
else { // handle the label point being on a partiular segment with a given fraction
|
||||
var frac = lab.segmentFraction;
|
||||
var a = void 0;
|
||||
var b = void 0;
|
||||
if (idx >= 0) { // indexing forwards
|
||||
a = link.getPoint(idx);
|
||||
b = (idx < numpts - 1) ? link.getPoint(idx + 1) : a;
|
||||
}
|
||||
else { // or backwards if segmentIndex is negative
|
||||
var i = numpts + idx;
|
||||
a = link.getPoint(i);
|
||||
b = (i > 0) ? link.getPoint(i - 1) : a;
|
||||
}
|
||||
var labx = a.x + (b.x - a.x) * frac;
|
||||
var laby = a.y + (b.y - a.y) * frac;
|
||||
var p = new go.Point(last.x - this._offset.x - labx, last.y - this._offset.y - laby);
|
||||
var segangle = (idx >= 0) ? a.directionPoint(b) : b.directionPoint(a);
|
||||
lab.segmentOffset = p.rotate(-segangle);
|
||||
}
|
||||
};
|
||||
return LinkLabelDraggingTool;
|
||||
}(go.Tool));
|
||||
exports.LinkLabelDraggingTool = LinkLabelDraggingTool;
|
||||
});
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
|
||||
/**
|
||||
* The LinkLabelDraggingTool class lets the user move a label on a {@link Link}.
|
||||
*
|
||||
* This tool only works when the Link has a label
|
||||
* that is positioned at the {@link Link#midPoint} plus some offset.
|
||||
* It does not work for labels that have a particular {@link GraphObject#segmentIndex}.
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/LinkLabelDragging.html">Link Label Dragging</a> sample.
|
||||
* @category Tool Extension
|
||||
*/
|
||||
export class LinkLabelDraggingTool extends go.Tool {
|
||||
/**
|
||||
* The label being dragged.
|
||||
*/
|
||||
public label: go.GraphObject | null = null;
|
||||
private _offset: go.Point = new go.Point(); // of the mouse relative to the center of the label object
|
||||
private _originalOffset: go.Point | null = null;
|
||||
|
||||
/**
|
||||
* Constructs a LinkLabelDraggingTool and sets the name for the tool.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
this.name = 'LinkLabelDragging';
|
||||
}
|
||||
|
||||
/**
|
||||
* From the GraphObject at the mouse point, search up the visual tree until we get to
|
||||
* an object that is a label of a Link.
|
||||
* @return {GraphObject} This returns null if no such label is at the mouse down point.
|
||||
*/
|
||||
public findLabel(): go.GraphObject | null {
|
||||
const diagram = this.diagram;
|
||||
const e = diagram.lastInput;
|
||||
let elt = diagram.findObjectAt(e.documentPoint, null, null);
|
||||
|
||||
if (elt === null || !(elt.part instanceof go.Link)) return null;
|
||||
while (elt !== null && elt.panel !== elt.part) {
|
||||
elt = elt.panel;
|
||||
}
|
||||
// If it's at an arrowhead segment index, don't consider it a label:
|
||||
if (elt !== null && (elt.segmentIndex === 0 || elt.segmentIndex === -1)) return null;
|
||||
return elt;
|
||||
}
|
||||
|
||||
/**
|
||||
* This tool can only start if the mouse has moved enough so that it is not a click,
|
||||
* and if the mouse down point is on a GraphObject "label" in a Link Panel,
|
||||
* as determined by {@link #findLabel}.
|
||||
*/
|
||||
public canStart(): boolean {
|
||||
if (!super.canStart()) return false;
|
||||
const diagram = this.diagram;
|
||||
// require left button & that it has moved far enough away from the mouse down point, so it isn't a click
|
||||
const e = diagram.lastInput;
|
||||
if (!e.left) return false;
|
||||
if (!this.isBeyondDragSize()) return false;
|
||||
|
||||
return this.findLabel() !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a transaction, call {@link #findLabel} and remember it as the "label" property,
|
||||
* and remember the original value for the label's {@link GraphObject#segmentOffset} property.
|
||||
*/
|
||||
public doActivate(): void {
|
||||
this.startTransaction('Shifted Label');
|
||||
this.label = this.findLabel();
|
||||
if (this.label !== null) {
|
||||
// compute the offset of the mouse-down point relative to the center of the label
|
||||
this._offset = this.diagram.firstInput.documentPoint.copy().subtract(this.label.getDocumentPoint(go.Spot.Center));
|
||||
this._originalOffset = this.label.segmentOffset.copy();
|
||||
}
|
||||
super.doActivate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop any ongoing transaction.
|
||||
*/
|
||||
public doDeactivate(): void {
|
||||
super.doDeactivate();
|
||||
this.stopTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear any reference to a label element.
|
||||
*/
|
||||
public doStop(): void {
|
||||
this.label = null;
|
||||
super.doStop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the label's original value for {@link GraphObject#segmentOffset}.
|
||||
*/
|
||||
public doCancel(): void {
|
||||
if (this.label !== null && this._originalOffset !== null) {
|
||||
this.label.segmentOffset = this._originalOffset;
|
||||
}
|
||||
super.doCancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* During the drag, call {@link #updateSegmentOffset} in order to set
|
||||
* the {@link GraphObject#segmentOffset} of the label.
|
||||
*/
|
||||
public doMouseMove(): void {
|
||||
if (!this.isActive) return;
|
||||
this.updateSegmentOffset();
|
||||
}
|
||||
|
||||
/**
|
||||
* At the end of the drag, update the segment offset of the label and finish the tool,
|
||||
* completing a transaction.
|
||||
*/
|
||||
public doMouseUp(): void {
|
||||
if (!this.isActive) return;
|
||||
this.updateSegmentOffset();
|
||||
this.transactionResult = 'Shifted Label';
|
||||
this.stopTool();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the label's {@link GraphObject#segmentOffset} as a rotated offset from the midpoint of the
|
||||
* Link that the label is in.
|
||||
*/
|
||||
public updateSegmentOffset(): void {
|
||||
const lab = this.label;
|
||||
if (lab === null) return;
|
||||
const link = lab.part;
|
||||
if (!(link instanceof go.Link)) return;
|
||||
|
||||
const last = this.diagram.lastInput.documentPoint;
|
||||
const idx = lab.segmentIndex;
|
||||
const numpts = link.pointsCount;
|
||||
// if the label is a "mid" label, assume it is positioned differently from a label at a particular segment
|
||||
if (idx < -numpts || idx >= numpts) {
|
||||
const mid = link.midPoint;
|
||||
// need to rotate this point to account for the angle of the link segment at the mid-point
|
||||
const p = new go.Point(last.x - this._offset.x - mid.x, last.y - this._offset.y - mid.y);
|
||||
lab.segmentOffset = p.rotate(-link.midAngle);
|
||||
} else { // handle the label point being on a partiular segment with a given fraction
|
||||
const frac = lab.segmentFraction;
|
||||
let a: go.Point;
|
||||
let b: go.Point;
|
||||
if (idx >= 0) { // indexing forwards
|
||||
a = link.getPoint(idx);
|
||||
b = (idx < numpts - 1) ? link.getPoint(idx + 1) : a;
|
||||
} else { // or backwards if segmentIndex is negative
|
||||
const i = numpts + idx;
|
||||
a = link.getPoint(i);
|
||||
b = (i > 0) ? link.getPoint(i - 1) : a;
|
||||
}
|
||||
const labx = a.x + (b.x - a.x) * frac;
|
||||
const laby = a.y + (b.y - a.y) * frac;
|
||||
const p = new go.Point(last.x - this._offset.x - labx, last.y - this._offset.y - laby);
|
||||
const segangle = (idx >= 0) ? a.directionPoint(b) : b.directionPoint(a);
|
||||
lab.segmentOffset = p.rotate(-segangle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Draggable Link Labels That Stay On Path</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="TypeScript: This variation on the LinkLabelDraggingTool extension restricts link labels to stay on the link's route while the user is dragging it." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../samples/assets/require.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script>
|
||||
<!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
function init() {
|
||||
require(["LinkLabelOnPathDraggingScript"], function(app) {
|
||||
app.init();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="background-color: whitesmoke; border: solid 1px black; width: 100%; height: 600px"></div>
|
||||
<p>
|
||||
This sample demonstrates a custom Tool, LinkLabelOnPathDraggingTool, that allows the user to drag the label of a Link, but
|
||||
that keeps the label exactly on the path of the link. The tool is defined at <a href="LinkLabelOnPathDraggingTool.ts">LinkLabelOnPathDraggingTool.ts</a>.
|
||||
</p>
|
||||
<p>
|
||||
The label on the link can be any arbitrarily complex object.
|
||||
It is positioned by the <a>GraphObject.segmentIndex</a> and <a>GraphObject.segmentFraction</a> properties.
|
||||
The segmentIndex is set to NaN such that the whole link path acts as the segment, and the segmentFraction is set by the LinkLabelOnPathDraggingTool.
|
||||
A two-way data binding on segmentFraction automatically remembers any modified value on the link data object in the model.
|
||||
</p>
|
||||
<p>
|
||||
The tool is derived from a similar tool, <a href="LinkLabelDraggingTool.ts">LinkLabelDraggingTool.ts</a>, that allows
|
||||
the user to drag the label in any direction from the mid-point of the Link path.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js", "./LinkLabelOnPathDraggingTool.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.init = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
var LinkLabelOnPathDraggingTool_js_1 = require("./LinkLabelOnPathDraggingTool.js");
|
||||
function init() {
|
||||
if (window.goSamples)
|
||||
window.goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make;
|
||||
var myDiagram = $(go.Diagram, 'myDiagramDiv', // the ID of the DIV HTML element
|
||||
{
|
||||
layout: $(go.ForceDirectedLayout, { defaultSpringLength: 50, defaultElectricalCharge: 50 }),
|
||||
'undoManager.isEnabled': true
|
||||
});
|
||||
// install the LinkLabelDraggingTool as a "mouse move" tool
|
||||
myDiagram.toolManager.mouseMoveTools.insertAt(0, new LinkLabelOnPathDraggingTool_js_1.LinkLabelOnPathDraggingTool());
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, go.Panel.Auto, { locationSpot: go.Spot.Center }, $(go.Shape, {
|
||||
fill: 'orange',
|
||||
portId: '',
|
||||
fromLinkable: true,
|
||||
fromSpot: go.Spot.AllSides,
|
||||
toLinkable: true,
|
||||
toSpot: go.Spot.AllSides,
|
||||
cursor: 'pointer'
|
||||
}, new go.Binding('fill', 'color')), $(go.TextBlock, { margin: 10, font: 'bold 12pt sans-serif' }, new go.Binding('text')));
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link, {
|
||||
routing: go.Link.AvoidsNodes,
|
||||
corner: 5,
|
||||
relinkableFrom: true,
|
||||
relinkableTo: true,
|
||||
reshapable: true,
|
||||
resegmentable: true
|
||||
}, $(go.Shape), $(go.Shape, { toArrow: 'OpenTriangle' }), $(go.Panel, 'Auto',
|
||||
// mark this Panel as being a draggable label, and set default segment props
|
||||
{ _isLinkLabel: true, segmentIndex: NaN, segmentFraction: .5 }, $(go.Shape, { fill: 'white' }), $(go.TextBlock, '?', { margin: 3 }, new go.Binding('text', 'color')),
|
||||
// remember any modified segment properties in the link data object
|
||||
new go.Binding('segmentIndex').makeTwoWay(), new go.Binding('segmentFraction').makeTwoWay()));
|
||||
// create a few nodes and links
|
||||
myDiagram.model = new go.GraphLinksModel([
|
||||
{ key: 1, text: 'one', color: 'lightyellow' },
|
||||
{ key: 2, text: 'two', color: 'brown' },
|
||||
{ key: 3, text: 'three', color: 'green' },
|
||||
{ key: 4, text: 'four', color: 'slateblue' },
|
||||
{ key: 5, text: 'five', color: 'aquamarine' },
|
||||
{ key: 6, text: 'six', color: 'lightgreen' },
|
||||
{ key: 7, text: 'seven' }
|
||||
], [
|
||||
{ from: 5, to: 6, color: 'orange' },
|
||||
{ from: 1, to: 2, color: 'red' },
|
||||
{ from: 1, to: 3, color: 'blue' },
|
||||
{ from: 1, to: 4, color: 'goldenrod' },
|
||||
{ from: 2, to: 5, color: 'fuchsia' },
|
||||
{ from: 3, to: 5, color: 'green' },
|
||||
{ from: 4, to: 5, color: 'black' },
|
||||
{ from: 6, to: 7 }
|
||||
]);
|
||||
// Attach to the window for console manipulation
|
||||
window.myDiagram = myDiagram;
|
||||
}
|
||||
exports.init = init;
|
||||
});
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
import { LinkLabelOnPathDraggingTool } from './LinkLabelOnPathDraggingTool.js';
|
||||
|
||||
export function init() {
|
||||
if ((window as any).goSamples) (window as any).goSamples(); // init for these samples -- you don't need to call this
|
||||
|
||||
const $ = go.GraphObject.make;
|
||||
|
||||
const myDiagram =
|
||||
$(go.Diagram, 'myDiagramDiv', // the ID of the DIV HTML element
|
||||
{
|
||||
layout: $(go.ForceDirectedLayout,
|
||||
{ defaultSpringLength: 50, defaultElectricalCharge: 50 }),
|
||||
'undoManager.isEnabled': true
|
||||
});
|
||||
|
||||
// install the LinkLabelDraggingTool as a "mouse move" tool
|
||||
myDiagram.toolManager.mouseMoveTools.insertAt(0, new LinkLabelOnPathDraggingTool());
|
||||
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, go.Panel.Auto,
|
||||
{ locationSpot: go.Spot.Center },
|
||||
$(go.Shape,
|
||||
{
|
||||
fill: 'orange', // default fill color
|
||||
portId: '',
|
||||
fromLinkable: true,
|
||||
fromSpot: go.Spot.AllSides,
|
||||
toLinkable: true,
|
||||
toSpot: go.Spot.AllSides,
|
||||
cursor: 'pointer'
|
||||
},
|
||||
new go.Binding('fill', 'color')),
|
||||
$(go.TextBlock,
|
||||
{ margin: 10, font: 'bold 12pt sans-serif' },
|
||||
new go.Binding('text'))
|
||||
);
|
||||
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link,
|
||||
{
|
||||
routing: go.Link.AvoidsNodes,
|
||||
corner: 5,
|
||||
relinkableFrom: true,
|
||||
relinkableTo: true,
|
||||
reshapable: true,
|
||||
resegmentable: true
|
||||
},
|
||||
$(go.Shape),
|
||||
$(go.Shape, { toArrow: 'OpenTriangle' }),
|
||||
$(go.Panel, 'Auto',
|
||||
// mark this Panel as being a draggable label, and set default segment props
|
||||
{ _isLinkLabel: true, segmentIndex: NaN, segmentFraction: .5 },
|
||||
$(go.Shape, { fill: 'white' }),
|
||||
$(go.TextBlock, '?', { margin: 3 },
|
||||
new go.Binding('text', 'color')),
|
||||
// remember any modified segment properties in the link data object
|
||||
new go.Binding('segmentIndex').makeTwoWay(),
|
||||
new go.Binding('segmentFraction').makeTwoWay()
|
||||
)
|
||||
);
|
||||
|
||||
// create a few nodes and links
|
||||
myDiagram.model = new go.GraphLinksModel([
|
||||
{ key: 1, text: 'one', color: 'lightyellow' },
|
||||
{ key: 2, text: 'two', color: 'brown' },
|
||||
{ key: 3, text: 'three', color: 'green' },
|
||||
{ key: 4, text: 'four', color: 'slateblue' },
|
||||
{ key: 5, text: 'five', color: 'aquamarine' },
|
||||
{ key: 6, text: 'six', color: 'lightgreen' },
|
||||
{ key: 7, text: 'seven' }
|
||||
], [
|
||||
{ from: 5, to: 6, color: 'orange' },
|
||||
{ from: 1, to: 2, color: 'red' },
|
||||
{ from: 1, to: 3, color: 'blue' },
|
||||
{ from: 1, to: 4, color: 'goldenrod' },
|
||||
{ from: 2, to: 5, color: 'fuchsia' },
|
||||
{ from: 3, to: 5, color: 'green' },
|
||||
{ from: 4, to: 5, color: 'black' },
|
||||
{ from: 6, to: 7 }
|
||||
]);
|
||||
|
||||
// Attach to the window for console manipulation
|
||||
(window as any).myDiagram = myDiagram;
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.LinkLabelOnPathDraggingTool = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
/**
|
||||
* The LinkLabelOnPathDraggingTool class lets the user move a label on a {@link Link} while keeping the label on the link's path.
|
||||
* This tool only works when the Link has a label marked by the "_isLinkLabel" property.
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/LinkLabelOnPathDragging.html">Link Label On Path Dragging</a> sample.
|
||||
* @category Tool Extension
|
||||
*/
|
||||
var LinkLabelOnPathDraggingTool = /** @class */ (function (_super) {
|
||||
__extends(LinkLabelOnPathDraggingTool, _super);
|
||||
/**
|
||||
* Constructs a LinkLabelOnPathDraggingTool and sets the name for the tool.
|
||||
*/
|
||||
function LinkLabelOnPathDraggingTool() {
|
||||
var _this = _super.call(this) || this;
|
||||
/**
|
||||
* The label being dragged.
|
||||
*/
|
||||
_this.label = null;
|
||||
_this._originalFraction = 0.0;
|
||||
_this.name = 'LinkLabelOnPathDragging';
|
||||
return _this;
|
||||
}
|
||||
/**
|
||||
* From the GraphObject at the mouse point, search up the visual tree until we get to
|
||||
* an object that has the "_isLinkLabel" property set to true and that is an immediate child of a Link Panel.
|
||||
* @return {GraphObject} This returns null if no such label is at the mouse down point.
|
||||
*/
|
||||
LinkLabelOnPathDraggingTool.prototype.findLabel = function () {
|
||||
var diagram = this.diagram;
|
||||
var e = diagram.lastInput;
|
||||
var elt = diagram.findObjectAt(e.documentPoint, null, null);
|
||||
if (elt === null || !(elt.part instanceof go.Link))
|
||||
return null;
|
||||
while (elt !== null && elt.panel !== elt.part) {
|
||||
elt = elt.panel;
|
||||
}
|
||||
// If it's not marked as "_isLinkLabel", don't consider it a label:
|
||||
if (!elt['_isLinkLabel'])
|
||||
return null;
|
||||
return elt;
|
||||
};
|
||||
/**
|
||||
* This tool can only start if the mouse has moved enough so that it is not a click,
|
||||
* and if the mouse down point is on a GraphObject "label" in a Link Panel,
|
||||
* as determined by {@link #findLabel}.
|
||||
*/
|
||||
LinkLabelOnPathDraggingTool.prototype.canStart = function () {
|
||||
if (!_super.prototype.canStart.call(this))
|
||||
return false;
|
||||
var diagram = this.diagram;
|
||||
// require left button & that it has moved far enough away from the mouse down point, so it isn't a click
|
||||
var e = diagram.lastInput;
|
||||
if (!e.left)
|
||||
return false;
|
||||
if (!this.isBeyondDragSize())
|
||||
return false;
|
||||
return this.findLabel() !== null;
|
||||
};
|
||||
/**
|
||||
* Start a transaction, call findLabel and remember it as the "label" property,
|
||||
* and remember the original values for the label's segment properties.
|
||||
*/
|
||||
LinkLabelOnPathDraggingTool.prototype.doActivate = function () {
|
||||
this.startTransaction('Shifted Label');
|
||||
this.label = this.findLabel();
|
||||
if (this.label !== null) {
|
||||
this._originalFraction = this.label.segmentFraction;
|
||||
}
|
||||
_super.prototype.doActivate.call(this);
|
||||
};
|
||||
/**
|
||||
* Stop any ongoing transaction.
|
||||
*/
|
||||
LinkLabelOnPathDraggingTool.prototype.doDeactivate = function () {
|
||||
_super.prototype.doDeactivate.call(this);
|
||||
this.stopTransaction();
|
||||
};
|
||||
/**
|
||||
* Clear any reference to a label element.
|
||||
*/
|
||||
LinkLabelOnPathDraggingTool.prototype.doStop = function () {
|
||||
this.label = null;
|
||||
_super.prototype.doStop.call(this);
|
||||
};
|
||||
/**
|
||||
* Restore the label's original value for GraphObject.segment... properties.
|
||||
*/
|
||||
LinkLabelOnPathDraggingTool.prototype.doCancel = function () {
|
||||
if (this.label !== null) {
|
||||
this.label.segmentFraction = this._originalFraction;
|
||||
}
|
||||
_super.prototype.doCancel.call(this);
|
||||
};
|
||||
/**
|
||||
* During the drag, call {@link #updateSegmentOffset} in order to set the segment... properties of the label.
|
||||
*/
|
||||
LinkLabelOnPathDraggingTool.prototype.doMouseMove = function () {
|
||||
if (!this.isActive)
|
||||
return;
|
||||
this.updateSegmentOffset();
|
||||
};
|
||||
/**
|
||||
* At the end of the drag, update the segment properties of the label and finish the tool,
|
||||
* completing a transaction.
|
||||
*/
|
||||
LinkLabelOnPathDraggingTool.prototype.doMouseUp = function () {
|
||||
if (!this.isActive)
|
||||
return;
|
||||
this.updateSegmentOffset();
|
||||
this.transactionResult = 'Shifted Label';
|
||||
this.stopTool();
|
||||
};
|
||||
/**
|
||||
* Save the label's {@link GraphObject#segmentFraction}
|
||||
* at the closest point to the mouse.
|
||||
*/
|
||||
LinkLabelOnPathDraggingTool.prototype.updateSegmentOffset = function () {
|
||||
var lab = this.label;
|
||||
if (lab === null)
|
||||
return;
|
||||
var link = lab.part;
|
||||
if (!(link instanceof go.Link) || link.path === null)
|
||||
return;
|
||||
var last = this.diagram.lastInput.documentPoint;
|
||||
// find the fractional distance along the link path closest to this point
|
||||
var path = link.path;
|
||||
if (path.geometry === null)
|
||||
return;
|
||||
var localpt = path.getLocalPoint(last);
|
||||
lab.segmentFraction = path.geometry.getFractionForPoint(localpt);
|
||||
};
|
||||
return LinkLabelOnPathDraggingTool;
|
||||
}(go.Tool));
|
||||
exports.LinkLabelOnPathDraggingTool = LinkLabelOnPathDraggingTool;
|
||||
});
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
|
||||
/**
|
||||
* The LinkLabelOnPathDraggingTool class lets the user move a label on a {@link Link} while keeping the label on the link's path.
|
||||
* This tool only works when the Link has a label marked by the "_isLinkLabel" property.
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/LinkLabelOnPathDragging.html">Link Label On Path Dragging</a> sample.
|
||||
* @category Tool Extension
|
||||
*/
|
||||
export class LinkLabelOnPathDraggingTool extends go.Tool {
|
||||
/**
|
||||
* The label being dragged.
|
||||
*/
|
||||
public label: go.GraphObject | null = null;
|
||||
private _originalFraction: number = 0.0;
|
||||
|
||||
/**
|
||||
* Constructs a LinkLabelOnPathDraggingTool and sets the name for the tool.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
this.name = 'LinkLabelOnPathDragging';
|
||||
}
|
||||
|
||||
/**
|
||||
* From the GraphObject at the mouse point, search up the visual tree until we get to
|
||||
* an object that has the "_isLinkLabel" property set to true and that is an immediate child of a Link Panel.
|
||||
* @return {GraphObject} This returns null if no such label is at the mouse down point.
|
||||
*/
|
||||
public findLabel(): go.GraphObject | null {
|
||||
const diagram = this.diagram;
|
||||
const e = diagram.lastInput;
|
||||
let elt = diagram.findObjectAt(e.documentPoint, null, null);
|
||||
|
||||
if (elt === null || !(elt.part instanceof go.Link)) return null;
|
||||
while (elt !== null && elt.panel !== elt.part) {
|
||||
elt = elt.panel;
|
||||
}
|
||||
// If it's not marked as "_isLinkLabel", don't consider it a label:
|
||||
if (!(elt as any)['_isLinkLabel']) return null;
|
||||
return elt;
|
||||
}
|
||||
|
||||
/**
|
||||
* This tool can only start if the mouse has moved enough so that it is not a click,
|
||||
* and if the mouse down point is on a GraphObject "label" in a Link Panel,
|
||||
* as determined by {@link #findLabel}.
|
||||
*/
|
||||
public canStart(): boolean {
|
||||
if (!super.canStart()) return false;
|
||||
const diagram = this.diagram;
|
||||
// require left button & that it has moved far enough away from the mouse down point, so it isn't a click
|
||||
const e = diagram.lastInput;
|
||||
if (!e.left) return false;
|
||||
if (!this.isBeyondDragSize()) return false;
|
||||
|
||||
return this.findLabel() !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a transaction, call findLabel and remember it as the "label" property,
|
||||
* and remember the original values for the label's segment properties.
|
||||
*/
|
||||
public doActivate(): void {
|
||||
this.startTransaction('Shifted Label');
|
||||
this.label = this.findLabel();
|
||||
if (this.label !== null) {
|
||||
this._originalFraction = this.label.segmentFraction;
|
||||
}
|
||||
super.doActivate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop any ongoing transaction.
|
||||
*/
|
||||
public doDeactivate(): void {
|
||||
super.doDeactivate();
|
||||
this.stopTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear any reference to a label element.
|
||||
*/
|
||||
public doStop(): void {
|
||||
this.label = null;
|
||||
super.doStop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the label's original value for GraphObject.segment... properties.
|
||||
*/
|
||||
public doCancel(): void {
|
||||
if (this.label !== null) {
|
||||
this.label.segmentFraction = this._originalFraction;
|
||||
}
|
||||
super.doCancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* During the drag, call {@link #updateSegmentOffset} in order to set the segment... properties of the label.
|
||||
*/
|
||||
public doMouseMove(): void {
|
||||
if (!this.isActive) return;
|
||||
this.updateSegmentOffset();
|
||||
}
|
||||
|
||||
/**
|
||||
* At the end of the drag, update the segment properties of the label and finish the tool,
|
||||
* completing a transaction.
|
||||
*/
|
||||
public doMouseUp(): void {
|
||||
if (!this.isActive) return;
|
||||
this.updateSegmentOffset();
|
||||
this.transactionResult = 'Shifted Label';
|
||||
this.stopTool();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the label's {@link GraphObject#segmentFraction}
|
||||
* at the closest point to the mouse.
|
||||
*/
|
||||
public updateSegmentOffset(): void {
|
||||
const lab = this.label;
|
||||
if (lab === null) return;
|
||||
const link = lab.part;
|
||||
if (!(link instanceof go.Link) || link.path === null) return;
|
||||
|
||||
const last = this.diagram.lastInput.documentPoint;
|
||||
// find the fractional distance along the link path closest to this point
|
||||
const path = link.path;
|
||||
if (path.geometry === null) return;
|
||||
const localpt = path.getLocalPoint(last);
|
||||
lab.segmentFraction = path.geometry.getFractionForPoint(localpt);
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Link Shifting Tool</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="TypeScript: Allow the user to shift the end of a link that is connected with a rectangular node." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../samples/assets/require.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
function init() {
|
||||
require(["LinkShiftingScript"], function(app) {
|
||||
app.init();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
|
||||
<p>
|
||||
This sample demonstrates the LinkShiftingTool, which is an extra tool that can be installed in the ToolManager to allow users
|
||||
to shift the end point of the link to be anywhere along the sides of the port with which it remains connected.
|
||||
</p>
|
||||
<p>
|
||||
This only looks good for ports that occupy the whole of a rectangular node. If you want to restrict the user's permitted
|
||||
sides, you can adapt the
|
||||
<code>LinkShiftingTool.doReshape</code> method to do what you want.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js", "./LinkShiftingTool.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.init = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
var LinkShiftingTool_js_1 = require("./LinkShiftingTool.js");
|
||||
function init() {
|
||||
if (window.goSamples)
|
||||
window.goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make;
|
||||
var myDiagram = $(go.Diagram, 'myDiagramDiv', {
|
||||
'undoManager.isEnabled': true
|
||||
});
|
||||
myDiagram.toolManager.mouseDownTools.add($(LinkShiftingTool_js_1.LinkShiftingTool));
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, 'Auto', {
|
||||
fromSpot: go.Spot.AllSides, toSpot: go.Spot.AllSides,
|
||||
fromLinkable: true, toLinkable: true,
|
||||
locationSpot: go.Spot.Center
|
||||
}, new go.Binding('location', 'location', go.Point.parse).makeTwoWay(go.Point.stringify), $(go.Shape, { fill: 'lightgray' }), $(go.TextBlock, { margin: 10 }, { fromLinkable: false, toLinkable: false }, new go.Binding('text', 'key')));
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link, {
|
||||
reshapable: true, resegmentable: true,
|
||||
relinkableFrom: true, relinkableTo: true,
|
||||
adjusting: go.Link.Stretch
|
||||
},
|
||||
// remember the (potentially) user-modified route
|
||||
new go.Binding('points').makeTwoWay(),
|
||||
// remember any spots modified by LinkShiftingTool
|
||||
new go.Binding('fromSpot', 'fromSpot', go.Spot.parse).makeTwoWay(go.Spot.stringify), new go.Binding('toSpot', 'toSpot', go.Spot.parse).makeTwoWay(go.Spot.stringify), $(go.Shape), $(go.Shape, { toArrow: 'Standard' }));
|
||||
myDiagram.model = new go.GraphLinksModel([
|
||||
{ key: 'Alpha', location: '0 0' },
|
||||
{ key: 'Beta', location: '0 100' }
|
||||
], [
|
||||
{ from: 'Alpha', to: 'Beta' }
|
||||
]);
|
||||
myDiagram.addDiagramListener('InitialLayoutCompleted', function (e) {
|
||||
// select the Link in order to show its two additional Adornments, for shifting the ends
|
||||
var firstlink = myDiagram.links.first();
|
||||
if (firstlink !== null)
|
||||
firstlink.isSelected = true;
|
||||
});
|
||||
// Attach to the window for console manipulation
|
||||
window.myDiagram = myDiagram;
|
||||
}
|
||||
exports.init = init;
|
||||
});
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
import { LinkShiftingTool } from './LinkShiftingTool.js';
|
||||
|
||||
export function init() {
|
||||
if ((window as any).goSamples) (window as any).goSamples(); // init for these samples -- you don't need to call this
|
||||
|
||||
const $ = go.GraphObject.make;
|
||||
|
||||
const myDiagram =
|
||||
$(go.Diagram, 'myDiagramDiv',
|
||||
{
|
||||
'undoManager.isEnabled': true
|
||||
});
|
||||
myDiagram.toolManager.mouseDownTools.add($(LinkShiftingTool));
|
||||
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, 'Auto',
|
||||
{
|
||||
fromSpot: go.Spot.AllSides, toSpot: go.Spot.AllSides,
|
||||
fromLinkable: true, toLinkable: true,
|
||||
locationSpot: go.Spot.Center
|
||||
},
|
||||
new go.Binding('location', 'location', go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
$(go.Shape, { fill: 'lightgray' }),
|
||||
$(go.TextBlock, { margin: 10 },
|
||||
{ fromLinkable: false, toLinkable: false },
|
||||
new go.Binding('text', 'key'))
|
||||
);
|
||||
|
||||
myDiagram.linkTemplate =
|
||||
$(go.Link,
|
||||
{
|
||||
reshapable: true, resegmentable: true,
|
||||
relinkableFrom: true, relinkableTo: true,
|
||||
adjusting: go.Link.Stretch
|
||||
},
|
||||
// remember the (potentially) user-modified route
|
||||
new go.Binding('points').makeTwoWay(),
|
||||
// remember any spots modified by LinkShiftingTool
|
||||
new go.Binding('fromSpot', 'fromSpot', go.Spot.parse).makeTwoWay(go.Spot.stringify),
|
||||
new go.Binding('toSpot', 'toSpot', go.Spot.parse).makeTwoWay(go.Spot.stringify),
|
||||
$(go.Shape),
|
||||
$(go.Shape, { toArrow: 'Standard' })
|
||||
);
|
||||
|
||||
myDiagram.model = new go.GraphLinksModel([
|
||||
{ key: 'Alpha', location: '0 0' },
|
||||
{ key: 'Beta', location: '0 100' }
|
||||
], [
|
||||
{ from: 'Alpha', to: 'Beta' }
|
||||
]);
|
||||
|
||||
myDiagram.addDiagramListener('InitialLayoutCompleted', function (e: go.DiagramEvent) {
|
||||
// select the Link in order to show its two additional Adornments, for shifting the ends
|
||||
const firstlink = myDiagram.links.first();
|
||||
if (firstlink !== null) firstlink.isSelected = true;
|
||||
});
|
||||
|
||||
// Attach to the window for console manipulation
|
||||
(window as any).myDiagram = myDiagram;
|
||||
}
|
||||
+301
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
(function (factory) {
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
var v = factory(require, exports);
|
||||
if (v !== undefined) module.exports = v;
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
define(["require", "exports", "../release/go.js"], factory);
|
||||
}
|
||||
})(function (require, exports) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.LinkShiftingTool = void 0;
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
var go = require("../release/go.js");
|
||||
/**
|
||||
* The LinkShiftingTool class lets the user shift the end of a link to be anywhere along the edges of the port;
|
||||
* use it in a diagram.toolManager.mouseDownTools list:
|
||||
* ```js
|
||||
* myDiagram.toolManager.mouseDownTools.add(new LinkShiftingTool());
|
||||
* ```
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/LinkShifting.html">Link Shifting</a> sample.
|
||||
* @category Tool Extension
|
||||
*/
|
||||
var LinkShiftingTool = /** @class */ (function (_super) {
|
||||
__extends(LinkShiftingTool, _super);
|
||||
/**
|
||||
* Constructs a LinkShiftingTool and sets the handles and name of the tool.
|
||||
*/
|
||||
function LinkShiftingTool() {
|
||||
var _this = _super.call(this) || this;
|
||||
// transient state
|
||||
_this._handle = null;
|
||||
var h = new go.Shape();
|
||||
h.geometryString = 'F1 M0 0 L8 0 M8 4 L0 4';
|
||||
h.fill = null;
|
||||
h.stroke = 'dodgerblue';
|
||||
h.background = 'lightblue';
|
||||
h.cursor = 'pointer';
|
||||
h.segmentIndex = 0;
|
||||
h.segmentFraction = 1;
|
||||
h.segmentOrientation = go.Link.OrientAlong;
|
||||
var g = new go.Shape();
|
||||
g.geometryString = 'F1 M0 0 L8 0 M8 4 L0 4';
|
||||
g.fill = null;
|
||||
g.stroke = 'dodgerblue';
|
||||
g.background = 'lightblue';
|
||||
g.cursor = 'pointer';
|
||||
g.segmentIndex = -1;
|
||||
g.segmentFraction = 1;
|
||||
g.segmentOrientation = go.Link.OrientAlong;
|
||||
_this._fromHandleArchetype = h;
|
||||
_this._toHandleArchetype = g;
|
||||
_this._originalPoints = null;
|
||||
_this.name = 'LinkShifting';
|
||||
return _this;
|
||||
}
|
||||
Object.defineProperty(LinkShiftingTool.prototype, "fromHandleArchetype", {
|
||||
/**
|
||||
* A small GraphObject used as a shifting handle.
|
||||
*/
|
||||
get: function () { return this._fromHandleArchetype; },
|
||||
set: function (value) { this._fromHandleArchetype = value; },
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(LinkShiftingTool.prototype, "toHandleArchetype", {
|
||||
/**
|
||||
* A small GraphObject used as a shifting handle.
|
||||
*/
|
||||
get: function () { return this._toHandleArchetype; },
|
||||
set: function (value) { this._toHandleArchetype = value; },
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
/**
|
||||
* Show an {@link Adornment} with a reshape handle at each end of the link which allows for shifting of the end points.
|
||||
*/
|
||||
LinkShiftingTool.prototype.updateAdornments = function (part) {
|
||||
if (part === null || !(part instanceof go.Link))
|
||||
return; // this tool only applies to Links
|
||||
var link = part;
|
||||
// show handles if link is selected, remove them if no longer selected
|
||||
var category = 'LinkShiftingFrom';
|
||||
var adornment = null;
|
||||
if (link.isSelected && !this.diagram.isReadOnly) {
|
||||
var selelt = link.selectionObject;
|
||||
if (selelt !== null && link.actualBounds.isReal() && link.isVisible() &&
|
||||
selelt.actualBounds.isReal() && selelt.isVisibleObject()) {
|
||||
var spot = link.computeSpot(true);
|
||||
if (spot.isSide() || spot.isSpot()) {
|
||||
adornment = link.findAdornment(category);
|
||||
if (adornment === null) {
|
||||
adornment = this.makeAdornment(selelt, false);
|
||||
adornment.category = category;
|
||||
link.addAdornment(category, adornment);
|
||||
}
|
||||
else {
|
||||
// This is just to invalidate the measure, so it recomputes itself based on the adorned link
|
||||
adornment.segmentFraction = Math.random();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (adornment === null)
|
||||
link.removeAdornment(category);
|
||||
category = 'LinkShiftingTo';
|
||||
adornment = null;
|
||||
if (link.isSelected && !this.diagram.isReadOnly) {
|
||||
var selelt = link.selectionObject;
|
||||
if (selelt !== null && link.actualBounds.isReal() && link.isVisible() &&
|
||||
selelt.actualBounds.isReal() && selelt.isVisibleObject()) {
|
||||
var spot = link.computeSpot(false);
|
||||
if (spot.isSide() || spot.isSpot()) {
|
||||
adornment = link.findAdornment(category);
|
||||
if (adornment === null) {
|
||||
adornment = this.makeAdornment(selelt, true);
|
||||
adornment.category = category;
|
||||
link.addAdornment(category, adornment);
|
||||
}
|
||||
else {
|
||||
// This is just to invalidate the measure, so it recomputes itself based on the adorned link
|
||||
adornment.segmentFraction = Math.random();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (adornment === null)
|
||||
link.removeAdornment(category);
|
||||
};
|
||||
/**
|
||||
* @hidden @internal
|
||||
* @param {GraphObject} selelt the {@link GraphObject} of the {@link Link} being shifted.
|
||||
* @param {boolean} toend
|
||||
* @return {Adornment}
|
||||
*/
|
||||
LinkShiftingTool.prototype.makeAdornment = function (selelt, toend) {
|
||||
var adornment = new go.Adornment();
|
||||
adornment.type = go.Panel.Link;
|
||||
var h = (toend ? this.toHandleArchetype : this.fromHandleArchetype);
|
||||
if (h !== null) {
|
||||
// add a single handle for shifting at one end
|
||||
adornment.add(h.copy());
|
||||
}
|
||||
adornment.adornedObject = selelt;
|
||||
return adornment;
|
||||
};
|
||||
/**
|
||||
* This tool may run when there is a mouse-down event on a reshaping handle.
|
||||
*/
|
||||
LinkShiftingTool.prototype.canStart = function () {
|
||||
if (!this.isEnabled)
|
||||
return false;
|
||||
var diagram = this.diagram;
|
||||
if (diagram.isReadOnly || diagram.isModelReadOnly)
|
||||
return false;
|
||||
if (!diagram.lastInput.left)
|
||||
return false;
|
||||
var h = this.findToolHandleAt(diagram.firstInput.documentPoint, 'LinkShiftingFrom');
|
||||
if (h === null)
|
||||
h = this.findToolHandleAt(diagram.firstInput.documentPoint, 'LinkShiftingTo');
|
||||
return (h !== null);
|
||||
};
|
||||
/**
|
||||
* Start shifting, if {@link #findToolHandleAt} finds a reshaping handle at the mouse down point.
|
||||
*
|
||||
* If successful this sets the handle to be the reshape handle that it finds.
|
||||
* It also remembers the original points in case this tool is cancelled.
|
||||
* And it starts a transaction.
|
||||
*/
|
||||
LinkShiftingTool.prototype.doActivate = function () {
|
||||
var diagram = this.diagram;
|
||||
var h = this.findToolHandleAt(diagram.firstInput.documentPoint, 'LinkShiftingFrom');
|
||||
if (h === null)
|
||||
h = this.findToolHandleAt(diagram.firstInput.documentPoint, 'LinkShiftingTo');
|
||||
if (h === null)
|
||||
return;
|
||||
var ad = h.part;
|
||||
if (ad === null || ad.adornedObject === null)
|
||||
return;
|
||||
var link = ad.adornedObject.part;
|
||||
if (!(link instanceof go.Link))
|
||||
return;
|
||||
this._handle = h;
|
||||
this._originalPoints = link.points.copy();
|
||||
this.startTransaction(this.name);
|
||||
diagram.isMouseCaptured = true;
|
||||
diagram.currentCursor = 'pointer';
|
||||
this.isActive = true;
|
||||
};
|
||||
/**
|
||||
* This stops the current shifting operation with the link as it is.
|
||||
*/
|
||||
LinkShiftingTool.prototype.doDeactivate = function () {
|
||||
this.isActive = false;
|
||||
var diagram = this.diagram;
|
||||
diagram.isMouseCaptured = false;
|
||||
diagram.currentCursor = '';
|
||||
this.stopTransaction();
|
||||
};
|
||||
/**
|
||||
* Perform cleanup of tool state.
|
||||
*/
|
||||
LinkShiftingTool.prototype.doStop = function () {
|
||||
this._handle = null;
|
||||
this._originalPoints = null;
|
||||
};
|
||||
/**
|
||||
* Restore the link route to be the original points and stop this tool.
|
||||
*/
|
||||
LinkShiftingTool.prototype.doCancel = function () {
|
||||
if (this._handle !== null) {
|
||||
var ad = this._handle.part;
|
||||
if (ad.adornedObject === null)
|
||||
return;
|
||||
var link = ad.adornedObject.part;
|
||||
if (this._originalPoints !== null)
|
||||
link.points = this._originalPoints;
|
||||
}
|
||||
this.stopTool();
|
||||
};
|
||||
/**
|
||||
* Call {@link #doReshape} with a new point determined by the mouse
|
||||
* to change the end point of the link.
|
||||
*/
|
||||
LinkShiftingTool.prototype.doMouseMove = function () {
|
||||
if (this.isActive) {
|
||||
this.doReshape(this.diagram.lastInput.documentPoint);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Reshape the link's end with a point based on the most recent mouse point by calling {@link #doReshape},
|
||||
* and then stop this tool.
|
||||
*/
|
||||
LinkShiftingTool.prototype.doMouseUp = function () {
|
||||
if (this.isActive) {
|
||||
this.doReshape(this.diagram.lastInput.documentPoint);
|
||||
this.transactionResult = this.name;
|
||||
}
|
||||
this.stopTool();
|
||||
};
|
||||
/**
|
||||
* Find the closest point along the edge of the link's port and shift the end of the link to that point.
|
||||
*/
|
||||
LinkShiftingTool.prototype.doReshape = function (pt) {
|
||||
if (this._handle === null)
|
||||
return;
|
||||
var ad = this._handle.part;
|
||||
if (ad.adornedObject === null)
|
||||
return;
|
||||
var link = ad.adornedObject.part;
|
||||
var fromend = ad.category === 'LinkShiftingFrom';
|
||||
var port = null;
|
||||
if (fromend) {
|
||||
port = link.fromPort;
|
||||
}
|
||||
else {
|
||||
port = link.toPort;
|
||||
}
|
||||
if (port === null)
|
||||
return;
|
||||
// support rotated ports
|
||||
var portang = port.getDocumentAngle();
|
||||
var center = port.getDocumentPoint(go.Spot.Center);
|
||||
var portb = new go.Rect(port.getDocumentPoint(go.Spot.TopLeft).subtract(center).rotate(-portang).add(center), port.getDocumentPoint(go.Spot.BottomRight).subtract(center).rotate(-portang).add(center));
|
||||
var lp = link.getLinkPointFromPoint(port.part, port, center, pt, fromend);
|
||||
lp = lp.copy().subtract(center).rotate(-portang).add(center);
|
||||
var spot = new go.Spot(Math.max(0, Math.min(1, (lp.x - portb.x) / (portb.width || 1))), Math.max(0, Math.min(1, (lp.y - portb.y) / (portb.height || 1))));
|
||||
if (fromend) {
|
||||
link.fromSpot = spot;
|
||||
}
|
||||
else {
|
||||
link.toSpot = spot;
|
||||
}
|
||||
};
|
||||
return LinkShiftingTool;
|
||||
}(go.Tool));
|
||||
exports.LinkShiftingTool = LinkShiftingTool;
|
||||
});
|
||||
+269
@@ -0,0 +1,269 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an extension and not part of the main GoJS library.
|
||||
* Note that the API for this class may change with any version, even point releases.
|
||||
* If you intend to use an extension in production, you should copy the code to your own source directory.
|
||||
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
||||
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
||||
*/
|
||||
|
||||
import * as go from '../release/go.js';
|
||||
|
||||
/**
|
||||
* The LinkShiftingTool class lets the user shift the end of a link to be anywhere along the edges of the port;
|
||||
* use it in a diagram.toolManager.mouseDownTools list:
|
||||
* ```js
|
||||
* myDiagram.toolManager.mouseDownTools.add(new LinkShiftingTool());
|
||||
* ```
|
||||
*
|
||||
* If you want to experiment with this extension, try the <a href="../../extensionsTS/LinkShifting.html">Link Shifting</a> sample.
|
||||
* @category Tool Extension
|
||||
*/
|
||||
export class LinkShiftingTool extends go.Tool {
|
||||
// these are archetypes for the two shift handles, one at each end of the Link:
|
||||
private _fromHandleArchetype: go.GraphObject | null;
|
||||
private _toHandleArchetype: go.GraphObject | null;
|
||||
|
||||
// transient state
|
||||
private _handle: go.GraphObject | null = null;
|
||||
private _originalPoints: go.List<go.Point> | null;
|
||||
|
||||
/**
|
||||
* Constructs a LinkShiftingTool and sets the handles and name of the tool.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
const h: go.Shape = new go.Shape();
|
||||
h.geometryString = 'F1 M0 0 L8 0 M8 4 L0 4';
|
||||
h.fill = null;
|
||||
h.stroke = 'dodgerblue';
|
||||
h.background = 'lightblue';
|
||||
h.cursor = 'pointer';
|
||||
h.segmentIndex = 0;
|
||||
h.segmentFraction = 1;
|
||||
h.segmentOrientation = go.Link.OrientAlong;
|
||||
const g: go.Shape = new go.Shape();
|
||||
g.geometryString = 'F1 M0 0 L8 0 M8 4 L0 4';
|
||||
g.fill = null;
|
||||
g.stroke = 'dodgerblue';
|
||||
g.background = 'lightblue';
|
||||
g.cursor = 'pointer';
|
||||
g.segmentIndex = -1;
|
||||
g.segmentFraction = 1;
|
||||
g.segmentOrientation = go.Link.OrientAlong;
|
||||
|
||||
this._fromHandleArchetype = h;
|
||||
this._toHandleArchetype = g;
|
||||
this._originalPoints = null;
|
||||
this.name = 'LinkShifting';
|
||||
}
|
||||
|
||||
/**
|
||||
* A small GraphObject used as a shifting handle.
|
||||
*/
|
||||
get fromHandleArchetype(): go.GraphObject | null { return this._fromHandleArchetype; }
|
||||
set fromHandleArchetype(value: go.GraphObject | null) { this._fromHandleArchetype = value; }
|
||||
|
||||
/**
|
||||
* A small GraphObject used as a shifting handle.
|
||||
*/
|
||||
get toHandleArchetype(): go.GraphObject | null { return this._toHandleArchetype; }
|
||||
set toHandleArchetype(value: go.GraphObject | null) { this._toHandleArchetype = value; }
|
||||
|
||||
/**
|
||||
* Show an {@link Adornment} with a reshape handle at each end of the link which allows for shifting of the end points.
|
||||
*/
|
||||
public updateAdornments(part: go.Part): void {
|
||||
if (part === null || !(part instanceof go.Link)) return; // this tool only applies to Links
|
||||
const link: go.Link = part;
|
||||
// show handles if link is selected, remove them if no longer selected
|
||||
let category = 'LinkShiftingFrom';
|
||||
let adornment = null;
|
||||
if (link.isSelected && !this.diagram.isReadOnly) {
|
||||
const selelt = link.selectionObject;
|
||||
if (selelt !== null && link.actualBounds.isReal() && link.isVisible() &&
|
||||
selelt.actualBounds.isReal() && selelt.isVisibleObject()) {
|
||||
const spot = (link as any).computeSpot(true);
|
||||
if (spot.isSide() || spot.isSpot()) {
|
||||
adornment = link.findAdornment(category);
|
||||
if (adornment === null) {
|
||||
adornment = this.makeAdornment(selelt, false);
|
||||
adornment.category = category;
|
||||
link.addAdornment(category, adornment);
|
||||
} else {
|
||||
// This is just to invalidate the measure, so it recomputes itself based on the adorned link
|
||||
adornment.segmentFraction = Math.random();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (adornment === null) link.removeAdornment(category);
|
||||
|
||||
category = 'LinkShiftingTo';
|
||||
adornment = null;
|
||||
if (link.isSelected && !this.diagram.isReadOnly) {
|
||||
const selelt = link.selectionObject;
|
||||
if (selelt !== null && link.actualBounds.isReal() && link.isVisible() &&
|
||||
selelt.actualBounds.isReal() && selelt.isVisibleObject()) {
|
||||
const spot = (link as any).computeSpot(false);
|
||||
if (spot.isSide() || spot.isSpot()) {
|
||||
adornment = link.findAdornment(category);
|
||||
if (adornment === null) {
|
||||
adornment = this.makeAdornment(selelt, true);
|
||||
adornment.category = category;
|
||||
link.addAdornment(category, adornment);
|
||||
} else {
|
||||
// This is just to invalidate the measure, so it recomputes itself based on the adorned link
|
||||
adornment.segmentFraction = Math.random();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (adornment === null) link.removeAdornment(category);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden @internal
|
||||
* @param {GraphObject} selelt the {@link GraphObject} of the {@link Link} being shifted.
|
||||
* @param {boolean} toend
|
||||
* @return {Adornment}
|
||||
*/
|
||||
public makeAdornment(selelt: go.GraphObject, toend: boolean): go.Adornment {
|
||||
const adornment = new go.Adornment();
|
||||
adornment.type = go.Panel.Link;
|
||||
const h = (toend ? this.toHandleArchetype : this.fromHandleArchetype);
|
||||
if (h !== null) {
|
||||
// add a single handle for shifting at one end
|
||||
adornment.add(h.copy());
|
||||
}
|
||||
adornment.adornedObject = selelt;
|
||||
return adornment;
|
||||
}
|
||||
|
||||
/**
|
||||
* This tool may run when there is a mouse-down event on a reshaping handle.
|
||||
*/
|
||||
public canStart(): boolean {
|
||||
if (!this.isEnabled) return false;
|
||||
const diagram = this.diagram;
|
||||
if (diagram.isReadOnly || diagram.isModelReadOnly) return false;
|
||||
if (!diagram.lastInput.left) return false;
|
||||
let h = this.findToolHandleAt(diagram.firstInput.documentPoint, 'LinkShiftingFrom');
|
||||
if (h === null) h = this.findToolHandleAt(diagram.firstInput.documentPoint, 'LinkShiftingTo');
|
||||
return (h !== null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start shifting, if {@link #findToolHandleAt} finds a reshaping handle at the mouse down point.
|
||||
*
|
||||
* If successful this sets the handle to be the reshape handle that it finds.
|
||||
* It also remembers the original points in case this tool is cancelled.
|
||||
* And it starts a transaction.
|
||||
*/
|
||||
public doActivate(): void {
|
||||
const diagram = this.diagram;
|
||||
let h = this.findToolHandleAt(diagram.firstInput.documentPoint, 'LinkShiftingFrom');
|
||||
if (h === null) h = this.findToolHandleAt(diagram.firstInput.documentPoint, 'LinkShiftingTo');
|
||||
if (h === null) return;
|
||||
const ad = h.part as go.Adornment;
|
||||
if (ad === null || ad.adornedObject === null) return;
|
||||
const link = ad.adornedObject.part;
|
||||
if (!(link instanceof go.Link)) return;
|
||||
|
||||
this._handle = h;
|
||||
this._originalPoints = link.points.copy();
|
||||
this.startTransaction(this.name);
|
||||
diagram.isMouseCaptured = true;
|
||||
diagram.currentCursor = 'pointer';
|
||||
this.isActive = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This stops the current shifting operation with the link as it is.
|
||||
*/
|
||||
public doDeactivate(): void {
|
||||
this.isActive = false;
|
||||
const diagram = this.diagram;
|
||||
diagram.isMouseCaptured = false;
|
||||
diagram.currentCursor = '';
|
||||
this.stopTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform cleanup of tool state.
|
||||
*/
|
||||
public doStop(): void {
|
||||
this._handle = null;
|
||||
this._originalPoints = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the link route to be the original points and stop this tool.
|
||||
*/
|
||||
public doCancel(): void {
|
||||
if (this._handle !== null) {
|
||||
const ad = this._handle.part as go.Adornment;
|
||||
if (ad.adornedObject === null) return;
|
||||
const link = ad.adornedObject.part as go.Link;
|
||||
if (this._originalPoints !== null) link.points = this._originalPoints;
|
||||
}
|
||||
this.stopTool();
|
||||
}
|
||||
|
||||
/**
|
||||
* Call {@link #doReshape} with a new point determined by the mouse
|
||||
* to change the end point of the link.
|
||||
*/
|
||||
public doMouseMove(): void {
|
||||
if (this.isActive) {
|
||||
this.doReshape(this.diagram.lastInput.documentPoint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reshape the link's end with a point based on the most recent mouse point by calling {@link #doReshape},
|
||||
* and then stop this tool.
|
||||
*/
|
||||
public doMouseUp(): void {
|
||||
if (this.isActive) {
|
||||
this.doReshape(this.diagram.lastInput.documentPoint);
|
||||
this.transactionResult = this.name;
|
||||
}
|
||||
this.stopTool();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the closest point along the edge of the link's port and shift the end of the link to that point.
|
||||
*/
|
||||
public doReshape(pt: go.Point): void {
|
||||
if (this._handle === null) return;
|
||||
const ad = this._handle.part as go.Adornment;
|
||||
if (ad.adornedObject === null) return;
|
||||
const link = ad.adornedObject.part as go.Link;
|
||||
const fromend = ad.category === 'LinkShiftingFrom';
|
||||
let port = null;
|
||||
if (fromend) {
|
||||
port = link.fromPort;
|
||||
} else {
|
||||
port = link.toPort;
|
||||
}
|
||||
if (port === null) return;
|
||||
// support rotated ports
|
||||
const portang = port.getDocumentAngle();
|
||||
const center = port.getDocumentPoint(go.Spot.Center);
|
||||
const portb = new go.Rect(port.getDocumentPoint(go.Spot.TopLeft).subtract(center).rotate(-portang).add(center),
|
||||
port.getDocumentPoint(go.Spot.BottomRight).subtract(center).rotate(-portang).add(center));
|
||||
let lp = link.getLinkPointFromPoint(port.part as go.Node, port, center, pt, fromend);
|
||||
lp = lp.copy().subtract(center).rotate(-portang).add(center);
|
||||
const spot = new go.Spot(Math.max(0, Math.min(1, (lp.x - portb.x) / (portb.width || 1))),
|
||||
Math.max(0, Math.min(1, (lp.y - portb.y) / (portb.height || 1))));
|
||||
if (fromend) {
|
||||
link.fromSpot = spot;
|
||||
} else {
|
||||
link.toSpot = spot;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user