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:
li
2026-03-30 20:16:32 +08:00
commit 1b24994e74
6721 changed files with 1308571 additions and 0 deletions
+314
View File
@@ -0,0 +1,314 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS HTML Interaction -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>HTML Interaction</h1>
<p>
This intro page explains how to use GoJS Diagrams alongside other HTML elements in a webapp.
<p>
For custom Text Editors, Context Menus, and ToolTips, which are invoked and hidden via GoJS tool operations, it is best to use the <a>HTMLInfo</a> class. <code>HTMLInfo</code> is described in the second section of this page.
<h2 id="UsingHTMLAlongsideGoJS">Using HTML Alongside GoJS</h2>
<h3 id="EditingPartsWithHTMLDataInspector">Editing Parts with the HTML Data Inspector</h3>
<p>
Generally, GoJS can interact with the rest of the page via JavaScript that programatically moves and modifies GoJS objects and the Diagram. If you have not read about programatically interacting with Parts and the Model, there is a <a href="../learn/graphobject.html">GraphObject Manipulation tutorial</a> for this purpose.
<p>
To help programmers get started with HTML controls we have implemented a simple <a href="../extensions/DataInspector.html">Data Inspector Extension</a>, an HTML-based property editor that displays and allows editing of data for the selected Part.
<p>
The Data Inspector chiefly works via a <code>"ChangedSelection"</code> <a href="events.html">Diagram Listener</a>. When triggered, it populates HTML Fields. Editing those fields and clicking away then update the selected Part by calling <code>diagram.model.setDataProperty</code> to update the model.
<h3 id="JQueryAndGoJS">jQuery and GoJS</h3>
<p>
GoJS does not depend on jQuery, but the two can be used together. The <a href="../samples/tabs.html">Tabs Sample</a> shows how to use GoJS inside a jQuery tab. The <a href="../samples/htmlInteraction.html">HTML Interaction Sample</a> places a GoJS Palette inside of a jQuery movable window, and a data inspector that modifies the current selected node inside another.
<p>
jQuery normally sets the <code>$</code> variable. If you are copying code from our samples or documentation, be aware that we usually do this:
<code>var $ = go.GraphObject.make;</code> so that uses of <code>$</code> in our examples will build <a>GraphObject</a>s and other GoJS objects.
Caution: calling jQuery when trying to build <b>GoJS</b> objects will cause unusual and cryptic errors.
So you should locally assign the <code>$</code> variable or use a different variable for building <b>GoJS</b> objects.
<h3 id="HTMLFocusOnDiagrams">HTML Focus on Diagrams</h3>
<p>
When a browser element gets focus, some browsers scroll that element into view as much as possible.
Because this behavior may be unwelcome in some web apps, the <a>Diagram.scrollsPageOnFocus</a> property defaults to false.
However you may want to set this property to true in order to get the standard behavior.
<p>
You can remove the outline while the Diagram is in focus. This is a CSS effect, not a GoJS effect, and can be removed by removing the CSS outline from all HTML elements inside the Diagram div:
<pre class="lang-css">/* affect all elements inside myDiagramDiv */
#myDiagramDiv * {
outline: none;
-webkit-tap-highlight-color: rgba(255, 255, 255, 0); /* mobile webkit */
}
</pre>
<h2 id="HTMLInfoClass">The HTMLInfo Class</h2>
<p>
Use the <a>HTMLInfo</a> class to display custom HTML page elements, such as a context menu, tooltip, or text editor made of HTML.
<p>
Properties that can be set to an instance of <code>HTMLInfo</code> include:
<ul>
<li><a>TextEditingTool.defaultTextEditor</a>
<li><a>TextBlock.textEditor</a>
<li><a>GraphObject.contextMenu</a>
<li><a>Diagram.contextMenu</a>
<li><a>GraphObject.toolTip</a>
<li><a>Diagram.toolTip</a>
</ul>
<h3 id="Usage">Usage</h3>
<p>
When replacing GoJS functionality with custom functionality, the main concern is when to show and hide the custom content. <code>HTMLInfo</code> does this with two settable functions defined by the programmer and called by GoJS:
<ul>
<li><a>HTMLInfo.show</a>, called by GoJS when custom information should be displayed, for example when activating a ToolTip, ContextMenuTool, or TextEditingTool.
<li><a>HTMLInfo.hide</a>, called by GoJS when custom information is finished, and should no longer be displayed, for example when ending these tools.
</ul>
<p>
In lieu of setting <a>HTMLInfo.hide</a>, you can set the <a>HTMLInfo.mainElement</a> property to the primary HTML Element that you are showing/hiding, and HTMLInfo will automatically hide the provided element by calling:
<pre class="lang-js">mainElement.style.display = "none";</pre>
<h3 id="HTMLInfoSamples">HTMLInfo samples</h3>
<ul>
<li>Text Editors: <a href="../samples/customTextEditingTool.html">Custom Text Editors sample</a> and <a href="../extensions/TextEditor.html">Re-implementation of the default Text Editor</a>
<li>Context Menus: <a href="../samples/customContextMenu.html">Custom Context Menu</a> and <a href="../samples/htmlLightBoxContextMenu.html">HTML Lightbox Context Menu</a> (a re-implementation of the default touch context menu)
<li>Tooltips: <a href="../samples/dataVisualization.html">Data Visualization Tooltip</a>
</ul>
<h3 id="Tooltips">Tooltips</h3>
<p>
For tooltips, if a <a>GraphObject.toolTip</a> or <a>Diagram.toolTip</a> is set to an instance of <code>HTMLInfo</code>, GoJS calls <code>HTMLInfo.show</code> in <a>ToolManager.showToolTip</a>. After the tooltip delay, GoJS will call <code>HTMLInfo.hide</code> in <a>ToolManager.hideToolTip</a>.
<p>
What follows is an example using <code>HTMLInfo.show</code> and <code>HTMLInfo.hide</code>, but the <code>HTMLInfo.hide</code> is simple enough that setting the <code>HTMLInfo.mainElement</code> to the tooltip div instead would be sufficient.
<div id="diagramParent" style="position: relative;">
<div id="toolTipDIV" style="position: absolute; background: white; z-index: 1000; display: none;">
<p id="toolTipParagraph">Tooltip
</div>
</div>
<pre class="lang-js" id="toolTipExample">
function showToolTip(obj, diagram, tool) {
var toolTipDIV = document.getElementById('toolTipDIV');
var pt = diagram.lastInput.viewPoint;
toolTipDIV.style.left = (pt.x + 10) + "px";
toolTipDIV.style.top = (pt.y + 10) + "px";
document.getElementById('toolTipParagraph').textContent = "Tooltip for: " + obj.data.key;
toolTipDIV.style.display = "block";
}
function hideToolTip(diagram, tool) {
var toolTipDIV = document.getElementById('toolTipDIV');
toolTipDIV.style.display = "none";
}
var myToolTip = $(go.HTMLInfo, {
show: showToolTip,
hide: hideToolTip
/*
since hideToolTip is very simple,
we could have set mainElement instead of setting hide:
mainElement: document.getElementById('toolTipDIV')
*/
});
diagram.nodeTemplate =
$(go.Node, "Auto",
{
toolTip: myToolTip
},
$(go.Shape, "RoundedRectangle", { strokeWidth: 0},
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 8 },
new go.Binding("text", "key"))
);
diagram.model = new go.GraphLinksModel(
[
{ key: "Alpha", color: "lightblue" },
{ key: "Beta", color: "orange" },
{ key: "Gamma", color: "lightgreen" },
{ key: "Delta", color: "pink" }
]);
</pre>
<pre class="lang-html">
&lt;!-- this must be added as a sibling of the Diagram --&gt;
&lt;div id="toolTipDIV" style="position: absolute; background: white; z-index: 1000; display: none;"&gt;
&lt;p id="toolTipParagraph"&gt;Tooltip&lt;/p&gt;
&lt;/div&gt;
</pre>
<script>goCode("toolTipExample", 600, 160, go.Diagram, "diagramParent")</script>
<h3 id="ContextMenus">Context Menus</h3>
<p>
For context menus, <a>ContextMenuTool.showContextMenu</a> will call <code>HTMLInfo.show</code>. <a>ContextMenuTool.hideContextMenu</a> will call <code>HTMLInfo.hide</code>.
<pre class="lang-js">// Assign an HTMLInfo to the Diagram:
myDiagram.contextMenu = $(go.HTMLInfo, {
show: showContextMenu,
hide: hideContextMenu
});
function showContextMenu(obj, diagram, tool) {
// Show the context menu HTML element:
SomeDOMElement.style.display = "block";
// Also show relevant buttons given the current state
// and the GraphObject obj; if null, the context menu is for the whole Diagram
}
function hideContextMenu() {
SomeDOMElement.style.display = "none";
}
function buttonClick() {
// do some action when a context menu button is clicked
// then:
myDiagram.currentTool.stopTool();
}
</pre>
<h3 id="TextEditors">Text Editors</h3>
<p>
For custom text editors, <a>TextEditingTool.doActivate</a> will call <code>HTMLInfo.show</code>. <a>TextEditingTool.doDeactivate</a> will call <code>HTMLInfo.hide</code>.
<p>
HTMLInfos used as text editors must also define a <a>HTMLInfo.valueFunction</a>. When <a>TextEditingTool.acceptText</a> is called, GoJS will call <code>HTMLInfo.valueFunction</code> and use the return value as the value for the TextEditingTool completion.
<p>
The example below constructs an HTMLInfo that uses <code>HTMLInfo.show</code> and <code>HTMLInfo.hide</code> to dynamically add, populate, and remove HTML elements from the page.
<div id="diagramParent2" style="position: relative;">
</div>
<pre class="lang-js" id="textEditorExample">
// Diagram setup. The HTMLInfo is set at the end of this code block.
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle", { strokeWidth: 0},
new go.Binding("fill", "color")),
$(go.TextBlock,
{ editable: true, margin: 8, choices: ['Alpha', 'Beta', 'Gamma', 'Delta'] },
new go.Binding("text"))
);
diagram.model = new go.GraphLinksModel(
[
{ text: "Alpha", color: "lightblue" },
{ text: "Beta", color: "orange" },
{ text: "Gamma", color: "lightgreen" },
{ text: "Delta", color: "pink" }
]);
// Create an HTMLInfo and dynamically create some HTML to show/hide
var customEditor = new go.HTMLInfo();
var customSelectBox = document.createElement("select");
customEditor.show = function(textBlock, diagram, tool) {
if (!(textBlock instanceof go.TextBlock)) return;
// Populate the select box:
customSelectBox.innerHTML = "";
// this sample assumes textBlock.choices is not null
var list = textBlock.choices;
for (var i = 0; i < list.length; i++) {
var op = document.createElement("option");
op.text = list[i];
op.value = list[i];
customSelectBox.add(op, null);
}
// After the list is populated, set the value:
customSelectBox.value = textBlock.text;
// Do a few different things when a user presses a key
customSelectBox.addEventListener("keydown", function(e) {
var keynum = e.which;
if (keynum == 13) { // Accept on Enter
tool.acceptText(go.TextEditingTool.Enter);
return;
} else if (keynum == 9) { // Accept on Tab
tool.acceptText(go.TextEditingTool.Tab);
e.preventDefault();
return false;
} else if (keynum === 27) { // Cancel on Esc
tool.doCancel();
if (tool.diagram) tool.diagram.focus();
}
}, false);
var loc = textBlock.getDocumentPoint(go.Spot.TopLeft);
var pos = diagram.transformDocToView(loc);
customSelectBox.style.left = pos.x + "px";
customSelectBox.style.top = pos.y + "px";
customSelectBox.style.position = 'absolute';
customSelectBox.style.zIndex = 100; // place it in front of the Diagram
diagram.div.appendChild(customSelectBox);
}
customEditor.hide = function(diagram, tool) {
diagram.div.removeChild(customSelectBox);
}
// This is necessary for HTMLInfo instances that are used as text editors
customEditor.valueFunction = function() { return customSelectBox.value; }
// Set the HTMLInfo:
diagram.toolManager.textEditingTool.defaultTextEditor = customEditor;
</pre>
<script>goCode("textEditorExample", 600, 160, go.Diagram, "diagramParent2")</script>
</div>
</div>
</body>
</html>
+404
View File
@@ -0,0 +1,404 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS and Angular -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Using GoJS with Angular</h1>
<p class="box" style="background-color: lightgoldenrodyellow;">
Examples of most of the topics discussed on this page can be found in the <a href="https://github.com/NorthwoodsSoftware/gojs-angular-basic"
target="_blank">gojs-angular-basic</a>
project,
which serves as a simple starter project.
</p>
<p>
If you are new to GoJS, it may be helpful to first visit the <a href="../learn/index.html" target="_blank">Getting
Started Tutorial</a>.
</p>
<p>
The easiest way to get a component set up for a GoJS Diagram is to use the <a href="#TODO" target="_blank">gojs-angular</a>
package,
which exports Angular Components for GoJS Diagrams, Palettes, and Overviews.
More information about the package, including the various props it takes, can be found on the
<a href="https://npmjs.com/gojs-react" target="_blank">NPM</a> page. Our examples will be using a <a>GraphLinksModel</a>,
but any model can be used.
</p>
<p>
You can see a sample project using all GoJS / Angular Components <a href="https://github.com/NorthwoodsSoftware/gojs-angular-basic">here</a>.
</p>
<h2 id="GeneralInformation">General Information</h2>
<h3 id="Installation">Installation</h3>
<p>
To use the published components, make sure you install GoJS and gojs-angular: <code>npm install gojs gojs-angular</code>.
</p>
<h3 id="AboutComponentStyling">About Component Styling</h3>
<p>
Whether you are using the published Diagram, Palette, or Overview Angular / GoJS Components, you will probably
want to style them.
First, you'll need to style a CSS class for the div of your GoJS Diagram / Palette / Overview such as:
</p>
<pre class="lang-css">
/* app.component.css */
.myDiagramDiv {
background: whitesmoke;
width: 800px;
height: 300px;
border: 1px solid black;
}
</pre>
<p> To style the GoJS Diagram / Palette / Overivew div, which will reside in the Angular / GoJS
Component(s) you are using, make sure you set <code>encapsulation: ViewEncapsulation.None</code> in the <code>@Component</code>
decorator
of the component holding your Angular / GoJS Component(s). Without this, your styling will not effect the
component divs.
Read more about Angular view encapsulation <a href="https://angular.io/api/core/ViewEncapsulation">here</a>.
</p>
<p>Your <code>@Component</code> decorator for the component holding the your GoJS / Angular Component(s) should
look something
like: </p>
<pre class="lang-ts">
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
encapsulation: ViewEncapsulation.None
})
</pre>
<h3 id="DataSyncService">The DataSyncService</h3>
<p>
The <code>gojs-angular</code> package comes with an Angular <a href="https://angular.io/tutorial/toh-pt4">service</a>
called DataSyncService (demonstrated later), which is used to
easily merge changes (a <a href="https://gojs.net/latest/api/symbols/IncrementalData.html">go.IncrementalData</a>
instance). </p>
<p>This service has three static functions: </p>
<ul>
<li><code>syncNodeData(changes, array)</code> - Merges any node data changes in a go.IncrementalData object
with a given array of node data, then returns the new array</li>
<li><code>syncLinkData(changes, array)</code> - Merges any link data changes in a go.IncrementalData object
with a given array of link data, then returns the new array. <strong>Note</strong>: Ensure you set the <a
href="https://gojs.net/latest/api/symbols/GraphLinksModel.html#linkKeyProperty">linkKeyProperty</a>
if you are using GraphLinksModel, so data merging is possible.</li>
<li><code>syncModelData(changes, object)</code> - Merges any modelData changes in a go.IncrementalData object
with a given modelData object, then returns the new object</li>
</ul>
<p>These functions should allow you to keep your data synced up as needed, without needing to write lots of
code.</p>
<h3 id="ListeningForModelChanges">Listening for Model Changes</h3>
<p>
It is common practice to want to listen for when data changes in a Diagram or Palette, then do something with
those changes on an application-level (such as data syncing). That's why, for both the DiagramComponent and
PaletteComponent, there is a <code>modelChange</code> @Input property function (more info below).
</p>
<p>
Note that the UndoManager should always be enabled to allow for transactions to take place, but the
UndoManager.maxHistoryLength can be set to 0 to prevent undo and redo.
</p>
<br>
<h2 id="UsingDiagramComponent">Using the Diagram Component</h2>
<p>
Diagram Component accepts several <code>@Input()</code> Angular properties, some of which
are
optional. They are:
<ul>
<li><code>initDiagram</code> - A function that must return a GoJS Diagram. You may define your Diagram's
Node
and Link templates here.</li>
<li><code>divClassName</code> - A class name for your Diagram div</li>
<li><code>nodeDataArray</code> - An array containing data objects for your nodes </li>
<li><code>linkDataArray</code> - An array containing data objects for your links </li>
<li><code>modelData</code> - A data object,
containing your diagram's <a href="https://gojs.net/latest/api/symbols/Model.html#modelData">model.modelData</a>.
This property is optional.
<li><code>skipsDiagramUpdate</code> - A boolean flag, specifying whether the component should skip updating,
often set when updating state from a GoJS model change.</li>
<li><code>modelChange</code> - A function, which accepts a <a href="">go.IncrementalData</a> object.
This function will fire when your Diagram's model changes, allowing you to decide what to do with those
changes. A common practice is to sync your app-level data to reflect the changes in the diagram model,
which
is made simple using the DataSyncService <code>gojs-angular</code> ships with. </li>
</ul>
</p>
<p>For example, these properties may look something like this: </p>
<pre class="lang-ts">
public initDiagram(): go.Diagram {
const $ = go.GraphObject.make;
const dia = $(go.Diagram, {
'undoManager.isEnabled': true, // must be set to allow for model change listening
// 'undoManager.maxHistoryLength': 0, // uncomment disable undo/redo functionality
model: $(go.GraphLinksModel,
{
linkKeyProperty: 'key' // IMPORTANT! must be defined for merges and data sync when using GraphLinksModel
}
)
});
// define the Node template
dia.nodeTemplate =
$(go.Node, 'Auto',
{
toLinkable: true, fromLinkable: true
},
$(go.Shape, 'RoundedRectangle', { stroke: null },
new go.Binding('fill', 'color')
),
$(go.TextBlock, { margin: 8 },
new go.Binding('text', 'key'))
);
return dia;
}
public diagramNodeData: Array<go.ObjectData> = [
{ key: 'Alpha', color: 'lightblue' },
{ key: 'Beta', color: 'orange' },
{ key: 'Gamma', color: 'lightgreen' },
{ key: 'Delta', color: 'pink' }
];
public diagramLinkData: Array<go.ObjectData> = [
{ key: -1, from: 'Alpha', to: 'Beta' },
{ key: -2, from: 'Alpha', to: 'Gamma' },
{ key: -3, from: 'Beta', to: 'Beta' },
{ key: -4, from: 'Gamma', to: 'Delta' },
{ key: -5, from: 'Delta', to: 'Alpha' }
];
public diagramDivClassName: string = 'myDiagramDiv';
public diagramModelData = { prop: 'value' };
public skipsDiagramUpdate = false;
// When the diagram model changes, update app data to reflect those changes
public diagramModelChange = function(changes: go.IncrementalData) {
// when setting state here, be sure to set skipsDiagramUpdate: true since GoJS already has this update
// (since this is a GoJS model changed listener event function)
// this way, we don't log an unneeded transaction in the Diagram's undoManager history
this.skipsDiagramUpdate = true;
this.diagramNodeData = DataSyncService.syncNodeData(changes, this.diagramNodeData);
this.diagramLinkData = DataSyncService.syncLinkData(changes, this.diagramLinkData);
this.diagramModelData = DataSyncService.syncModelData(changes, this.diagramModelData);
};
</pre>
<p>
Once you've defined your <code>@Input</code> properties for your DiagramComponent, pass these properties
to your DiagramComponent in your template, like so:
</p>
<pre class="lang-html">
&lt;gojs-diagram
[initDiagram]='initDiagram'
[nodeDataArray]='diagramNodeData'
[linkDataArray]='diagramLinkData'
[modelData]='diagramModelData'
[skipsDiagramUpdate]='skipsDiagramUpdate'
(modelChange)='diagramModelChange($event)'
[divClassName]='diagramDivClassName'&gt;
&lt;/gojs-diagram&gt;
</pre>
<p>
You will now have a GoJS Diagram working in your Angular application.
</p>
<br>
<h2 id="UsingPaletteComponent">Using the Palette Component</h2>
The Palette Component accepts the following Angular <code>@Input()</code> properties.
<ul>
<li><code>initPalette</code> - A function that must return a GoJS Palette. You may define your Palette's Node
and Link templates here.</li>
<li><code>divClassName</code> - A class name for the div your Palette div</li>
<li><code>nodeDataArray</code> - An array containing data objects for your nodes </li>
<li><code>linkDataArray</code> - An array containing data objects for your links </li>
<li><code>modelData</code> - A data object,
containing your palette's <a href="https://gojs.net/latest/api/symbols/Model.html#modelData">model.modelData</a>.
This property is optional.
<li><code>modelChange</code> - A function, which accepts a <a href="">go.IncrementalData</a> object.
This function will fire when your Palette's model changes, allowing you to decide what to do with those
changes. A common practice is to sync your app-level data to reflect the changes in the palette model,
which
is made simple using the DataSyncService <code>gojs-angular</code> ships with. </li>
</ul>
<p>
Define these properties in your component that will hold that Palette Component, such as:
</p>
<pre class="lang-ts">
public initPalette(): go.Palette {
const $ = go.GraphObject.make;
const palette = $(go.Palette);
// define the Node template
palette.nodeTemplate =
$(go.Node, 'Auto',
$(go.Shape, 'RoundedRectangle',
{
stroke: null
},
new go.Binding('fill', 'color')
),
$(go.TextBlock, { margin: 8 },
new go.Binding('text', 'key'))
);
palette.model = $(go.GraphLinksModel,
{
linkKeyProperty: 'key' // IMPORTANT! must be defined for merges and data sync when using GraphLinksModel
});
return palette;
}
public paletteNodeData: Array<go.ObjectData> = [
{ key: 'PaletteNode1', color: 'firebrick' },
{ key: 'PaletteNode2', color: 'blueviolet' }
];
public paletteLinkData: Array<go.ObjectData> = [
{ from: 'PaletteNode1', to: 'PaletteNode2' }
];
public paletteModelData = { prop: 'val' };
public paletteDivClassName = 'myPaletteDiv';
public paletteModelChange = function(changes: go.IncrementalData) {
this.paletteNodeData = DataSyncService.syncNodeData(changes, this.paletteNodeData);
this.paletteLinkData = DataSyncService.syncLinkData(changes, this.paletteLinkData);
this.paletteModelData = DataSyncService.syncModelData(changes, this.paletteModelData);
};
</pre>
<p>
Then pass these properties to your Palette Component in your template, like:
</p>
<pre class="lang-html">
&lt;gojs-palette
[initPalette]='initPalette'
[nodeDataArray]='paletteNodeData'
[linkDataArray]='paletteLinkData'
[modelData]='paletteModelData'
(modelChange)='paletteModelChange($event)'
[divClassName]='paletteDivClassName'&gt;
&lt;/gojs-palette&gt;</pre>
<p>
You should now have a GoJS Palette Component working in your Angular application.
</p>
<br>
<h2 id="UsingOverviewComponent">Using the Overview Component</h2>
<p>
The Overview Component accepts the following Angular <code>@Input()</code> properties.
</p>
<ul>
<li><code>initOverview</code> - A function that must return a GoJS Overview.</li>
<li><code>divClassName</code> - A class name for your Overview div</li>
<li><code>observedDiagram</code> - The GoJS Diagram this Overview observes</li>
</ul>
<p>
Define these properties in the component that will hold your Overview Component, like:
</p>
<pre class="lang-ts">
public oDivClassName = 'myOverviewDiv';
public initOverview(): go.Overview {
const $ = go.GraphObject.make;
const overview = $(go.Overview);
return overview;
}
public observedDiagram = null;</pre>
<p>
Then pass these properties to your Overview Component in your template, like:
</p>
<pre class="lang-html">
&lt;gojs-overview
[initOverview]='initOverview'
[divClassName]='oDivClassName'
[observedDiagram]='observedDiagram'&gt;
&lt;/gojs-overview&gt;</pre>
<p>
But, we're not done yet. <code>observedDiagram</code> is null, so the Overview will observe anything.
To assign your Overview a Diagram to observe, you will have to reassign the <code>observedDiagram</code>
property after initialization. To do so,
reassign the bound <code>observedDiagram</code> property in your component holding your Overview
Component in the <code>ngAfterViewInit</code> lifecycle hook.
</p>
<p>
<strong>Note</strong>: To avoid a <code>ExpressionChangedAfterItHasBeenCheckedError</code>, you must inform
Angular
to then detect changes.
This can be done with the <a href="https://angular.io/api/core/ChangeDetectorRef">ChangeDetectorRef</a>.detectChanges()
method. You can inject a ChangeDetectorRef instance
into your wrapper Component constructor, and use that after you alter <code>observedDiagram</code> to call
detectChanges(). Like so:
</p>
<pre class="lang-ts">
constructor(private cdr: ChangeDetectorRef) { }
public ngAfterViewInit() {
if (this.observedDiagram) return;
// in this snippet, this.myDiagramComponent is a reference to a GoJS/Angular Diagram Component
// that has a valid GoJS Diagram
this.observedDiagram = this.myDiagramComponent.diagram;
// IMPORTANT: without this, Angular will throw ExpressionChangedAfterItHasBeenCheckedError (dev mode only)
this.cdr.detectChanges();
}
</pre>
<p>
Now, after initialization, your Overview should display appropriately.
</p>
<br>
<h2 id="UpdatingPropertiesBasedOnAppState">Updating Properties Based on App State</h2>
<p>You may have some app-level properties you want to effect the behavior / appearance of your Diagram, Palette,
or Overview. You could subclass their respective components and add <code>@Input</code> bindings with specific
setter methods, or, more simply, you can have an <code>ngOnChanges</code> function in your app-level component
that updates various Diagram / Palette / Component properties based on your app state.</p>
<p>For example, say you have an app-level property called <code>showGrid</code>. When <code>showGrid</code> is
true, your Diagram's grid should be visible -- when false, it should be invisible. In your AppComponent, you
could do something like: </p>
<pre class="lang-ts">
// myDiagramComponent is a reference to your DiagramComponent
@ViewChild('myDiagram', { static: true }) public myDiagramComponent: DiagramComponent;
public ngDoCheck() {
// whenever showGrid changes, update the diagram.grid.visible in the child DiagramComponent
if (this.myDiagramComponent && this.myDiagramComponent.diagram instanceof go.Diagram) {
this.myDiagramComponent.diagram.grid.visible = this.showGrid;
}
}
</pre>
</div>
</div>
</body>
</html>
+625
View File
@@ -0,0 +1,625 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Animation -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>GoJS Animation</h1>
<p>
<b>GoJS</b> offers several built-in animations, enabled by default, as well as the ability to create arbitrary animations.
</p>
<p>
The <a>Diagram.animationManager</a> handles animations within a <a>Diagram</a>.
The <a>AnimationManager</a> automatically sets up and dispatches default animations, and has properties to customize and disable them.
Custom animations are possible by creating instances of <a>Animation</a> or <a>AnimationTrigger</a>.
</p>
<div class="box" style="background-color: lightgoldenrodyellow; padding: 10px;">
<p>This introduction page details the different classes used for GoJS animation.</p>
<p>To see more demonstrations of custom animations, visit the <a href="../samples/customAnimations.html">Custom Animations extension sample</a>.</p>
</div>
<h2 id="DefaultAnimations">Default Animations</h2>
<p>
By default, the AnimationManager creates and runs several animations for its Diagram using a single
instance of Animation, the <a>AnimationManager.defaultAnimation</a>.
These animations occur on various commands, by the <a>Diagram.model</a> setter, and upon layouts.
Unlike other Animations, they will be stopped if a new transaction is started during animation.
</p>
<p>
GoJS will begin an animation automatically for these reasons:
</p>
<p>Invoked by <a>CommandHandler</a>:</p>
<ul>
<li>"Collapse SubGraph" - Animates the collapsing nodes by "disappearing" them, animating their scales and positions into their group.
<li>"Expand SubGraph" - Expands groups by animating the scales and positions of nodes starting within the collapsed group.
<li>"Collapse Tree" - Animates the collapsing nodes by "disappearing" them, animating their scales and positions into the root node.
<li>"Expand Tree" - Expands subtrees by animating the scales and positions of descendant nodes starting within the collapsed root node.
<li>"Scroll To Part" - Animates the <a>Diagram.position</a> and may expand groups or expand subtrees to make the node visible.
<li>"Zoom To Fit" - Animates the <a>Diagram.position</a> and <a>Diagram.scale</a>.
</ul>
<p>Invoked by <a>Diagram</a>:</p>
<ul>
<li>"Model" - Animates all node positions when a new model is set.
<li>"Layout" - Animates all changed node positions on a layout.
</ul>
<p>Invoked by <a>AnimationTrigger</a>s, if any are declared:</p>
<ul>
<li>"Trigger" - Animates the change of a defined <a>GraphObject</a> property.
</ul>
<p>
The above quoted names are strings passed to <a>AnimationManager.canStart</a>
This method can be overridden to return <code>false</code> if you wish to stop specific automatic animations.
</p>
<h3 id="DefaultAnimations">Default Initial Animation</h3>
<p>
As of GoJS 2.1, the default initial animation fades the diagram upwards into view. Prior versions animated Part locations separately.
To control the initial animation behavior, there now exists <a>AnimationManager.initialAnimationStyle</a>, which is set to <a>AnimationManager,Default</a>
by default, but can be set to <a>AnimationManager,AnimateLocations</a> to use the animation style from GoJS 2.0.
You can also set this property to <a>AnimationManager,None</a> and define your own initial animation using the <code>"InitialAnimationStarting"</code> <a>DiagramEvent</a>.
</p>
<p>
Here is an example with buttons which set <a>AnimationManager.initialAnimationStyle</a> to the three different values, then reload the Diagram.
A fourth button illustrates how one might use the <code>"InitialAnimationStarting"</code> <a>DiagramEvent</a> to make a custom "zoom in" animation.
</p>
<pre id="animationStyles">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle", { strokeWidth: 0, fill: "lightblue" }),
$(go.TextBlock,
{ margin: 8, font: "bold 14px sans-serif", stroke: '#333' },
new go.Binding("text", "key"))
);
diagram.model = new go.GraphLinksModel([{ key: 'Alpha' }, { key: 'Beta' }, { key: 'Delta' }, { key: 'Gamma' }]);
// only needed for this demonstration, this flag is used to stop
// the "InitialAnimationStarting" listener when other buttons are pressed
window.custom = false;
window.animateDefault = function() {
window.custom = false;
diagram.animationManager.initialAnimationStyle = go.AnimationManager.Default;
diagram.model = go.Model.fromJSON(diagram.model.toJSON());
}
window.animateLocations = function() {
window.custom = false;
diagram.animationManager.initialAnimationStyle = go.AnimationManager.AnimateLocations;
diagram.model = go.Model.fromJSON(diagram.model.toJSON());
}
window.animateNone = function() {
window.custom = false;
diagram.animationManager.initialAnimationStyle = go.AnimationManager.None;
diagram.model = go.Model.fromJSON(diagram.model.toJSON());
}
window.animateCustom = function() {
window.custom = true;
diagram.animationManager.initialAnimationStyle = go.AnimationManager.None;
// Customer listener zooms-in the Diagram on load:
diagram.addDiagramListener("InitialAnimationStarting", function(e) {
var animation = e.subject.defaultAnimation;
if (window.custom === false) {
// a different button was pressed, restore default values on the default animation:
animation.easing = go.Animation.EaseInOutQuad;
animation.duration = NaN;
return;
}
animation.easing = go.Animation.EaseOutExpo;
animation.duration = 1500;
animation.add(e.diagram, 'scale', 0.1, 1);
animation.add(e.diagram, 'opacity', 0, 1);
})
diagram.model = go.Model.fromJSON(diagram.model.toJSON());
}
</pre>
<script>goCode("animationStyles", 600, 200)</script>
<p>
<input id="Animate Colors" type="button" onclick="animateDefault()" value="animationStyle: Default" />
<input id="Animate Colors" type="button" onclick="animateLocations()" value="animationStyle: AnimateLocations" />
<input id="Animate Colors" type="button" onclick="animateNone()" value="animationStyle: None (does nothing!)" />
<input id="Animate Colors" type="button" onclick="animateCustom()" value="animationStyle: None + Custom Animation" />
<h3 id="Limitations">Limitations of Default Animations</h3>
<p>
The AnimationManager can be turned off by setting <a>AnimationManager.isEnabled</a> to <code>false</code>.
Specific default animations can be turned off or modified by overriding <a>AnimationManager.canStart</a>
and potentially returning <code>false</code>.
</p>
<p>
The default animation will be stopped if a new transaction begins during the animation.
The same is not true of other <a>Animation</a>s, which are not stopped by new transactions, and can continue indefinitely.
</p>
<h2 id="AnimatableProperties">Animatable Properties</h2>
<p>
By default, <a>AnimationTriggers</a> and <a>Animation</a>s can animate these properties of GraphObjects:
</p>
<ul>
<li><code>position</code>
<li><code>location</code> (on Parts)
<li><code>scale</code>
<li><code>opacity</code>
<li><code>angle</code>
<li><code>desiredSize</code>
<li><code>width</code>
<li><code>height</code>
<li><code>background</code> (for solid string colors only)
<li><code>areaBackground</code> (for solid string colors only)
<li><code>fill</code> (on Shapes, for solid string colors only)
<li><code>strokeWidth</code> (on Shapes)
<li><code>strokeDashOffset</code> (on Shapes)
<li><code>stroke</code> (on Shapes, TextBlocks, for solid string colors only)
</ul>
<p>
Additionally <a>Animation</a>s (but not <a>AnimationTriggers</a>) can animate these properties of Diagram:
</p>
<ul>
<li><code>position</code>
<li><code>scale</code>
<li><code>opacity</code>
</ul>
<p>
It is possible to animate other properties if they are defined by the programmer -- see the section "Custom Animation Effects" below.
</p>
<h2 id="AnimationTriggerClass">The AnimationTrigger Class</h2>
<p class="box" style="background-color: lightgoldenrodyellow;">
<em>New in 2.1</em>
</p>
<p>
An <a>AnimationTrigger</a> is used to declare GraphObject properties to animate when their value has changed.
When a trigger is defined, changes to the target property will animate from the old value to the new value.
In templates, triggers are defined in a similar fashion to Bindings:
</p>
<pre class="lang-js">
// In this shape definition, two triggers are defined on a Shape.
// These will cause all changes to Shape.stroke and Shape.fill to animate
// from their old values to their new values.
$(go.Shape, "Rectangle",
{ strokeWidth: 12, stroke: 'black', fill: 'white' },
new go.AnimationTrigger('stroke'),
new go.AnimationTrigger('fill')
)
</pre>
<p>
Here is an example, with an HTML button that sets the Shape's <code>stroke</code> and <code>fill</code> to new random values:
</p>
<pre id="animateTrigger1">
diagram.nodeTemplate =
$(go.Node,
$(go.Shape, "Rectangle",
{ strokeWidth: 12, stroke: 'black', fill: 'white' },
new go.AnimationTrigger('stroke'),
new go.AnimationTrigger('fill')
)
);
diagram.model = new go.GraphLinksModel([{ key: 'Alpha' }]); // One node
// attach this Diagram to the window to use a button
window.animateTrigger1 = function() {
diagram.commit(function(diag) {
var node = diag.nodes.first();
node.elt(0).stroke = go.Brush.randomColor();
node.elt(0).fill = go.Brush.randomColor();
});
}
</pre>
<script>goCode("animateTrigger1", 600, 200)</script>
<p><input id="Animate Colors" type="button" onclick="animateTrigger1()" value="Animate Colors" />
<p>
AnimationTriggers can invoke an animation immediately, starting a new animation with each property of each GraphObject that has been modified,
or they can (much more efficiently) be bundled together into the default
animation (<a>AnimationManager.defaultAnimation</a>) and begin at the end of the next transaction.
These behaviors can be set with <a>AnimationTrigger.startCondition</a> by the values
<a>AnimationTrigger,Immediate</a> and <a>AnimationTrigger,Bundled</a>, respectively.
The default value, <a>AnimationTrigger,Default</a>, attempts to infer which is best.
It will start immediately if there is no ongoing transaction or if <a>Diagram.skipsUndoManager</a> is true.
</p>
<p>
AnimationTriggers are only definable in templates, on GraphObjects, and cannot be used on RowColumnDefinitions or Diagrams.
</p>
<h2 id="AnimationClass">The Animation Class</h2>
<p class="box" style="background-color: lightgoldenrodyellow">
<em>New in 2.1</em>
</p>
<p>
General animation of GraphObject and Diagram properties is possible by creating one or more instances of the <a>Animation</a> class.
</p>
<pre class="lang-js">
var animation = new go.Animation();
// Animate the node's angle from its current value to a random value between 0 and 150 degrees
animation.add(node, "angle", node.angle, Math.random() * 150);
animation.duration = 1000; // Animate over 1 second, instead of the default 600 milliseconds
animation.start(); // starts the animation immediately
</pre>
<p>
<a>Animation.add</a> is used to specify which objects should animate, which properties, and their starting and ending values:
</p>
<pre class="lang-js">
animation.add(GraphObjectOrDiagram, "EffectName", StartingValue, EndingValue);
</pre>
<p>
Here's the above animation in an example, where each node is animated by an HTML button.
Note carefully that each node is added to the same animation. The same effect would be had with one animation per node,
but it is always more efficient to group the properties you are animating into a single animation, if possible
(for instance, it is possible if they are all going to start at the same time and have the same duration).
</p>
<pre id="animate1">
// define a simple Node template
diagram.nodeTemplate =
$(go.Node, "Spot",
{ locationSpot: go.Spot.Center },
new go.Binding("angle"),
$(go.Shape, "Diamond", { strokeWidth: 0, width: 75, height: 75 },
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 8, font: 'bold 12pt sans-serif' },
new go.Binding("text", "key"))
);
diagram.model = new go.GraphLinksModel(
[
{ key: "Alpha", color: "lightblue" },
{ key: "Beta", color: "orange" },
{ key: "Gamma", group: 'G1', color: "lightgreen" },
{ key: "Delta", group: 'G1', color: "pink", angle: 45 }
],
[
{ from: "Alpha", to: "Beta" },
{ from: "Gamma", to: "Delta" }
]);
window.animate1 = function() {
var animation = new go.Animation();
diagram.nodes.each(function(node) {
// Animate the node's angle from its current value to a random value between 0 and 150 degrees
animation.add(node, "angle", node.angle, Math.random() * 150);
});
animation.duration = 1000; // Animate over 1 second, instead of the default 600 milliseconds
animation.start(); // starts the animation immediately
}
</pre>
<script>goCode("animate1", 600, 250)</script>
<p><input id="Animate Nodes" type="button" onclick="animate1()" value="Animate Node Angles" />
<p>
Animating the Diagram is possible by passing it as the object to be animated:
</p>
<pre class="lang-js">
animation.add(myDiagram, "position", myDiagram.position, myDiagram.position.copy().offset(200, 15));
...
animation.add(myDiagram, "scale", myDiagram.scale, 0.2);
</pre>
<p>
Animations can also be reversed, as is common with animations that are intended to be cosmetic in nature, by setting <a>Animation.reversible</a> to true.
This doubles the effective duration of the Animation.
</p>
<p>
Below are several example Animations, all with <a>Animation.reversible</a> set to true. The first animates Nodes, the other three animate Diagram position and scale.
</p>
<pre id="animate2">
// define a simple Node template
diagram.nodeTemplate =
$(go.Node, "Spot",
{ locationSpot: go.Spot.Center },
new go.Binding("angle"),
$(go.Shape, "Diamond", { strokeWidth: 0, width: 75, height: 75 },
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 8, font: 'bold 12pt sans-serif' },
new go.Binding("text", "key"))
);
diagram.model = new go.GraphLinksModel(
[
{ key: "Alpha", color: "lightblue" },
{ key: "Beta", color: "orange" },
{ key: "Gamma", group: 'G1', color: "lightgreen" },
{ key: "Delta", group: 'G1', color: "pink" }
],
[
{ from: "Alpha", to: "Beta" },
{ from: "Gamma", to: "Delta" }
]);
function protectedAnimation(f) { // return a button event handler to start an animation
return function() {
// Stop any currently running animations
diagram.animationManager.stopAnimation(true);
var animation = new go.Animation();
animation.reversible = true; // reverse the animation at the end, doubling its total time
f(animation); // initialize the Animation
animation.start(); // start the animation immediately
};
}
window.animateAngleReverse =
protectedAnimation(function(animation) {
diagram.nodes.each(function(node) {
// Animate the node's angle from its current value a random value between 0 and 90
animation.add(node, "angle", node.angle, Math.random() * 90);
});
});
window.animateDiagramPosition =
protectedAnimation(function(animation) {
// shift the diagram contents towards the right and then back
animation.add(diagram, "position", diagram.position, diagram.position.copy().offset(200, 15));
animation.duration = 700;
});
window.animateZoomOut =
protectedAnimation(function(animation) {
animation.add(diagram, "scale", diagram.scale, 0.2);
});
window.animateZoomIn =
protectedAnimation(function(animation) {
animation.add(diagram, "scale", diagram.scale, 4);
});
</pre>
<script>goCode("animate2", 600, 250)</script>
<p>
<input id="AnimateAngleReverse" type="button" onclick="animateAngleReverse()" value="Animate Angles (reverse)" />
<input id="AnimateDiagramPosition" type="button" onclick="animateDiagramPosition()" value="Animate Diagram Position (reverse)" />
<input id="AnimateZoomOut" type="button" onclick="animateZoomOut()" value="Zoom Out (reverse)" />
<input id="AnimateZoomIn" type="button" onclick="animateZoomIn()" value="Zoom In (reverse)" />
</p>
<p>
Without the call to <a>AnimationManager.stopAnimation</a> to protect against rapid button clicks,
you would notice that if you clicked Zoom Out, and then during the animation clicked the same button again,
the Diagram's scale would not return to its initial value of 1.0.
This is because the Animation animates from the <em>current</em> Diagram scale value, to its final value, and back again,
but the current value is also what's being changed due to the ongoing animation.
</p>
<h3 id="CustomAnimationEffects">Custom Animation Effects</h3>
<p>
It is sometimes helpful to add custom ways to modify one or more properties during an animation.
You can register new animatable effects with <a>AnimationManager,defineAnimationEffect</a>.
The name passed is an arbitrary string, but often reflects a property of a GraphObject class.
The body of the function passed determines what property or properties are animated.
</p>
<p>
</p>
<p>
Here is an example, creating an <code>"fraction"</code> Animation effect to animate the value of <a>GraphObject.segmentFraction</a>,
which will give the appearance of a Link label moving along its path.
</p>
<pre>
// This presumes the object to be animated is a label within a Link
go.AnimationManager.defineAnimationEffect('fraction',
function(obj, startValue, endValue, easing, currentTime, duration, animation) {
obj.segmentFraction = easing(currentTime, startValue, endValue - startValue, duration);
});
</pre>
<p>
After defining this, we can use it as a property name in an Animation.
The following example sets up an indefinite (<a>Animation.runCount</a> = <code>Infinity</code>) and reversible
animation, where each link is assigned a random duration to cycle the fill color and segmentFraction of its label.
This produces labels that appear to move along their path while pulsating colors. The setting of <a>Animation.reversible</a>
causes them to go backwards once finished, to start from their beginning again.
</p>
<pre>
function animateColorAndFraction() {
// create one Animation for each link, so that they have independent durations
myDiagram.links.each(function(node) {
var animation = new go.Animation()
animation.add(node.elt(1), "fill", node.elt(0).fill, go.Brush.randomColor());
animation.add(node.elt(1), "fraction", 0, 1);
animation.duration = 1000 + (Math.random()*2000);
animation.reversible = true; // Re-run backwards
animation.runCount = Infinity; // Animate forever
animation.start();
});
}
</pre>
<pre id="animate3" style="display: none;">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle", { strokeWidth: 0 },
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 8 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
$(go.Shape, { strokeWidth: 2 }),
// The label
$(go.Shape, "Circle", { segmentIndex: 0, width: 15, height: 15, fill: 'red', strokeWidth: 2 })
);
diagram.model = new go.GraphLinksModel(
[
{ key: "Alpha", color: "lightblue" },
{ key: "Gamma", color: "lightgreen" },
{ key: "Delta", color: "pink" }
],
[
{ from: "Alpha", to: "Gamma" },
{ from: "Gamma", to: "Delta" },
{ from: "Delta", to: "Alpha" }
]);
// This presumes the object to be animated is a label within a Link
go.AnimationManager.defineAnimationEffect('fraction',
function(obj, startValue, endValue, easing, currentTime, duration, animation) {
obj.segmentFraction = easing(currentTime, startValue, endValue - startValue, duration);
});
window.animateColorAndFraction = function() {
// create one Animation for each link, so that they have independent durations
diagram.links.each(function(node) {
var animation = new go.Animation()
animation.add(node.elt(1), "fill", node.elt(0).fill, go.Brush.randomColor());
animation.add(node.elt(1), "fraction", 0, 1);
animation.duration = 1000 + (Math.random()*2000);
animation.reversible = true; // Re-run backwards
animation.runCount = Infinity; // Animate forever
animation.start();
});
}
</pre>
<script>goCode("animate3", 600, 200)</script>
<p><input id="animateColorAndFraction" type="button" onclick="animateColorAndFraction()" value="Animate Color and Segment Fraction" />
<p>Since <a>Animation.runCount</a> was set to <code>Infinity</code>, this Animation wil run indefinitely.</p>
<h3 id="AnimatingDeletion">Animating Deletion</h3>
<p>
Parts to be deleted can be animated, but since they will no longer exist in the Diagram after removal,
a copy must be added to the Animation so that there is an object to animate.
This can be done with <a>Animation.addTemporaryPart</a>.
The part can then have its deletion animated using <a>Animation.add</a>.
This temporary part will be the object that animates,
and will automatically appear when animation begins and be removed when animaton completes.
It is typical for deletion animations to shrink the mock Part, move it off-screen, reduce its opacity to zero,
or otherwise show it disappearing in some way.
</p>
<p>
In this example, each Part being deleted will be scaled to an imperceptible size (by animating scale to 0.01)
and spun around (by animating angle), to give the appearance of swirling away. There are other example
deletion (and creation) effects in the <a href="../samples/customAnimations.html">Custom Animations extension sample</a>.
</p>
<pre class="lang-js">
myDiagram.addDiagramListener('SelectionDeleting', function(e) {
// the DiagramEvent.subject is the collection of Parts about to be deleted
e.subject.each(function(part) {
if (!(part instanceof go.Node)) return; // only animate Nodes
var animation = new go.Animation();
var deletePart = part.copy();
animation.add(deletePart, "scale", deletePart.scale, 0.01);
animation.add(deletePart, "angle", deletePart.angle, 360);
animation.addTemporaryPart(deletePart, myDiagram);
animation.start();
});
});
</pre>
<pre id="animate4" style="display: none;">
// define a simple Node template
diagram.nodeTemplate =
$(go.Node, "Spot",
{ locationSpot: go.Spot.Center },
new go.Binding("angle"),
$(go.Shape, "Ellipse",
{
fill: "hsl(" + ((Math.random()*360)|0) + ", 100%, 80%)",
strokeWidth: 4, strokeDashArray: [8, 8],
width: 55, height: 55
}),
$(go.TextBlock,
new go.Binding("text", "key"))
);
diagram.model = new go.GraphLinksModel(
[
{ key: "Alpha" },
{ key: "Beta" },
{ key: "Gamma" },
{ key: "Delta" }
],
[ ]);
diagram.addDiagramListener('SelectionDeleting', function(e) {
// the DiagramEvent.subject is the collection of Parts about to be deleted
e.subject.each(function(part) {
if (!(part instanceof go.Node)) return; // only animate Nodes
var animation = new go.Animation();
var deletePart = part.copy();
animation.add(deletePart, "scale", deletePart.scale, 0.01);
animation.add(deletePart, "angle", deletePart.angle, 360);
animation.addTemporaryPart(deletePart, diagram);
animation.start();
});
});
window.deleteNode = function() {
if (diagram.selection.count === 0) diagram.select(diagram.nodes.first());
diagram.commandHandler.deleteSelection();
}
</pre>
<script>goCode("animate4", 600, 200)</script>
<p><input type="button" onclick="deleteNode()" value="Delete a Node" />
<h3 id="AnimationExamples">Animation Examples</h3>
<p>
To see more examples of custom animations, visit the <a href="../samples/customAnimations.html">Custom Animations extension sample</a>.
It demonstrates a number of Node creation/deletion animations, linking animations, and more.
There are also several samples which contain animation:
</p>
<ul>
<li><a href="../samples/animatedFocus.html" target="_blank">Animated Focus</a> - Scroll to center a Node in the viewport, while zooming a copy of that node for attention.</li>
<li><a href="../samples/treeLoadAnimation.html" target="_blank">Tree Load Animation</a> - Recursive animation upon model load.</li>
<li><a href="../samples/dataVisualization.html" target="_blank">Data Visualization</a> - Nodes now move using an <a>AnimationTrigger</a>.</li>
<li><a href="../samples/kittenMonitor.html" target="_blank">Kitten Monitor</a> - Kittens now move using an <a>AnimationTrigger</a>.</li>
<li><a href="../samples/processFlow.html" target="_blank">Process Flow</a> - Custom animation defined to animate the Link Shape's strokeDashArray.</li>
<li><a href="../samples/belts.html" target="_blank">Belts and Rollers</a> - Apparent movement of the belts by animating the strokeDashArray.</li>
<li><a href="../samples/shopFloorMonitor.html" target="_blank">Shop Floor Monitor</a> - Link color changes now use an <a>AnimationTrigger</a>.</li>
<li><a href="../samples/flowchart.html" target="_blank">Flowchart</a> - In the Palette only, initial animation is disabled in favor of a custom fade-in animation.</li>
<li><a href="../samples/stateChart.html" target="_blank">State Chart</a> - Initial animation is disabled in favor of a custom zoom fade-in animation.</li>
</ul>
</div>
</div>
</body>
</html>
+439
View File
@@ -0,0 +1,439 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Brushes -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
<script src="../extensions/Figures.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>GoJS Brushes</h1>
<p>
A <a>Brush</a> holds color information and describes how to draw the inside of a Shape or the stroke of a shape or a TextBlock or the background of any GraphObject.
<p>
A Brush must not be modified once it has been assigned to a GraphObject, such as the <a>Shape.fill</a> or <a>TextBlock.stroke</a> or <a>GraphObject.background</a>. However, a Brush may be shared by multiple GraphObjects.
<h2 id="SolidBrushes">Solid Brushes</h2>
The simplest brushes are defined by a single solid color. Because they are so simple, anywhere you want a single-color brush you can subsitute a valid CSS color string.
<pre class="lang-js" id="simpleBrushes">
diagram.add($(go.Part,
$(go.Shape, "Circle", {
fill: $(go.Brush, { color: "palegreen" })
})
));
diagram.add($(go.Part,
$(go.Shape, "Circle", {
fill: "palegreen"
})
));
</pre>
<script>goCode("simpleBrushes", 600, 120)</script>
<p>Many CSS color strings are valid, including named colors, hex values, RGB values, and RGBA values.
<pre class="lang-js" id="simpleBrushes2">
diagram.layout = $(go.GridLayout);
diagram.add($(go.Part,
$(go.Shape, "Circle", {
fill: "#DFAD83"
})
));
diagram.add($(go.Part,
$(go.Shape, "Circle", {
fill: "rgba(0,255,0,.3)" // semi transparent green
})
));
diagram.add($(go.Part,
$(go.Shape, "Circle", {
fill: "rgba(0,255,0,.3)",
stroke: '#DFBB00',
strokeWidth: 4,
background: 'coral'
})
));
</pre>
<script>goCode("simpleBrushes2", 600, 120)</script>
<h2 id="GradientBrushes">Gradient Brushes</h2>
<p>Gradient brushes are defined by setting the type and adding a number of color stops to the Brush.</p>
<pre class="lang-js">
// constructs a Linear gradient brush
var brush = new go.Brush(go.Brush.Linear);
brush.addColorStop(0, "blue");
brush.addColorStop(1, "red");
</pre>
<p>To simplify the syntax, you can use go.GraphObject.make (see <a href="buildingObjects.html">building objects</a>):
<pre class="lang-js">
// constructs the same Brush
var brush = $(go.Brush, "Linear", { 0.0: "blue", 1.0: "red" });
</pre>
<p>Some examples follow:</p>
<pre class="lang-js" id="gradients1">
diagram.add(
$(go.Part, "Table",
$(go.Shape, { row: 0, column: 0,
figure: "Circle", width: 100, height: 100, margin: 5,
// A linear gradient brush from blue to red, going from top to bottom (default)
fill: $(go.Brush, "Linear", { 0.0: "blue", 1.0: "red" })
}),
$(go.Shape, { row: 0, column: 1,
figure: "Circle", width: 100, height: 100, margin: 5,
// A linear gradient brush from blue to red, going from bottom to top
// by defining start and end spots
fill: $(go.Brush, "Linear", { 0.0: "blue", 1.0: "red", start: go.Spot.Bottom, end: go.Spot.Top })
})
));
</pre>
<script>goCode("gradients1", 600, 120)</script>
<p>Brushes can have any number of color stops:</p>
<pre class="lang-js" id="gradients2">
diagram.add(
$(go.Part, "Table",
$(go.Shape, { row: 0, column: 0,
figure: "Rectangle", width: 100, height: 100, margin: 5,
// A rainbow linear gradient brush:
fill: $(go.Brush, "Linear", {
0.0: "rgba(255, 0, 0, 1)",
0.15: "rgba(255, 255, 0, 1)",
0.30: "rgba(0, 255, 0, 1)",
0.50: "rgba(0, 255, 255, 1)",
0.65: "rgba(0, 0, 255, 1)",
0.80: "rgba(255, 0, 255, 1)",
1: "rgba(255, 0, 0, 1)"
})
}),
$(go.Shape, { row: 0, column: 1,
figure: "Rectangle", width: 100, height: 100, margin: 5,
// A rainbow radial gradient brush:
fill: $(go.Brush, "Radial", {
0.0: "rgba(255, 0, 0, 1)",
0.15: "rgba(255, 255, 0, 1)",
0.30: "rgba(0, 255, 0, 1)",
0.50: "rgba(0, 255, 255, 1)",
0.65: "rgba(0, 0, 255, 1)",
0.80: "rgba(255, 0, 255, 1)",
1: "rgba(255, 0, 0, 1)"
})
})
));
</pre>
<script>goCode("gradients2", 600, 120)</script>
<p>Radial gradient brushes can be controlled with <a>Brsuh.startRadius</a> and <a>Brush.endRadius</a>, which default to zero and NaN, respectively, meaning the gradient begins at the very center and goes to the farthest measured edge of the object.
<pre class="lang-js" id="gradients21">
diagram.layout = $(go.GridLayout);
diagram.add(
$(go.Part,
$(go.Shape, {
figure: "Rectangle", width: 100, height: 100, margin: 5,
// A rainbow radial gradient brush:
fill: $(go.Brush, "Radial", {
0.0: "red", 1: "black"
})
})
));
diagram.add(
$(go.Part,
$(go.Shape, {
figure: "Rectangle", width: 100, height: 100, margin: 5,
// A rainbow radial gradient brush:
fill: $(go.Brush, "Radial", {
startRadius: 30, 0.0: "red", 1: "black"
})
})
));
diagram.add(
$(go.Part,
$(go.Shape, {
figure: "Rectangle", width: 100, height: 100, margin: 5,
// A rainbow radial gradient brush:
fill: $(go.Brush, "Radial", {
startRadius: 30, endRadius: 40, 0.0: "red", 1: "black"
})
})
));
</pre>
<script>goCode("gradients21", 600, 120)</script>
<p>Several GraphObjects can share the same Brush:
<pre class="lang-js" id="gradients3">
diagram.layout = $(go.GridLayout);
// Create one brush for several GraphObjects to share:
var rainbow = $(go.Brush, "Linear", {
0.0: "rgba(255, 0, 0, 1)",
0.15: "rgba(255, 255, 0, 1)",
0.30: "rgba(0, 255, 0, 1)",
0.50: "rgba(0, 255, 255, 1)",
0.65: "rgba(0, 0, 255, 1)",
0.80: "rgba(255, 0, 255, 1)",
1: "rgba(255, 0, 0, 1)"
});
diagram.add(
$(go.Part,
$(go.Shape, { figure: "Rectangle", width: 100, height: 100, fill: rainbow })
));
diagram.add(
$(go.Part,
$(go.Shape, { figure: "Fragile", width: 50, height: 50, angle: 45, fill: rainbow })
));
diagram.add(
$(go.Part, "Auto",
$(go.Shape, { figure: "Rectangle", fill: rainbow }),
$(go.TextBlock, "text", { font: 'bold 32pt sans-serif', stroke: rainbow, angle: 90 })
));
diagram.add(
$(go.Part,
$(go.Shape, { figure: "Circle", width: 70, height: 70, angle: 180, fill: null, strokeWidth: 10, stroke: rainbow })
));
</pre>
<script>goCode("gradients3", 600, 120)</script>
<h2 id="PatternBrushes">Pattern Brushes</h2>
<p>The following example sets up two Pattern brushes, one using an HTML Canvas with content drawn to it, which looks like this:
<div id="patternCanvas"></canvas>
<script type="text/javascript">
// set up an 40x40 HTML Canvas and draw on it to create a repeating "tile" to use as a pattern
var patternCanvas = document.createElement('canvas');
patternCanvas.width = 40;
patternCanvas.height = 40;
var pctx = patternCanvas.getContext('2d');
// This creates a shape similar to a diamond leaf
pctx.beginPath();
pctx.moveTo(0.0, 40.0);
pctx.lineTo(26.9, 36.0);
pctx.bezierCurveTo(31.7, 36.0, 36.0, 32.1, 36.0, 27.3);
pctx.lineTo(40.0, 0.0);
pctx.lineTo(11.8, 3.0);
pctx.bezierCurveTo(7.0, 3.0, 3.0, 6.9, 3.0, 11.7);
pctx.lineTo(0.0, 40.0);
pctx.closePath();
pctx.fillStyle = "rgb(188, 222, 178)";
pctx.fill();
pctx.lineWidth = 0.8;
pctx.strokeStyle = "rgb(0, 156, 86)";
pctx.lineJoin = "miter";
pctx.miterLimit = 4.0;
pctx.stroke();
document.getElementById('patternCanvas').appendChild(patternCanvas);
</script>
<p>The other Pattern Brush uses this image:</p>
<p><img src="images/pattern.jpg"/></p>
<pre class="lang-js" id="diagramPre">
// set up an 40x40 HTML Canvas and draw on it to create a repeating "tile" to use as a pattern
function makePattern() {
var patternCanvas = document.createElement('canvas');
patternCanvas.width = 40;
patternCanvas.height = 40;
var pctx = patternCanvas.getContext('2d');
// This creates a shape similar to a diamond leaf
pctx.beginPath();
pctx.moveTo(0.0, 40.0);
pctx.lineTo(26.9, 36.0);
pctx.bezierCurveTo(31.7, 36.0, 36.0, 32.1, 36.0, 27.3);
pctx.lineTo(40.0, 0.0);
pctx.lineTo(11.8, 3.0);
pctx.bezierCurveTo(7.0, 3.0, 3.0, 6.9, 3.0, 11.7);
pctx.lineTo(0.0, 40.0);
pctx.closePath();
pctx.fillStyle = "rgb(188, 222, 178)";
pctx.fill();
pctx.lineWidth = 0.8;
pctx.strokeStyle = "rgb(0, 156, 86)";
pctx.lineJoin = "miter";
pctx.miterLimit = 4.0;
pctx.stroke();
return patternCanvas;
}
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
var $ = go.GraphObject.make; // for conciseness in defining templates
diagram.nodeTemplate =
$(go.Node, "Spot",
{ resizable: true, resizeObjectName: 'SHAPE' },
$(go.Shape, "Rectangle",
{ name: 'SHAPE', strokeWidth: 0, stroke: null },
new go.Binding("fill")),
$(go.TextBlock,
{ margin: 10, font: "bold 18px Verdana" },
new go.Binding("text", "key"))
);
var img = new Image();
img.src = 'images/pattern.jpg';
// Use an image as a pattern
var patternBrush = $(go.Brush, "Pattern", { pattern: img });
// use a reference to an HTML Canvas (with renderings on it) as a pattern:
var patternBrush2 = $(go.Brush, "Pattern", { pattern: makePattern() });
diagram.model = new go.GraphLinksModel(
[
{ key: "Alpha", fill: patternBrush },
{ key: "Beta", fill: patternBrush2 }
],
[
]);
</pre>
<div style="width:100%">
<span id="diagramSpan" style="display: inline-block; vertical-align: top">
<p><b>The result:</b></p>
</span>
</div>
<script>goCode("diagramPre", 500, 300, go.Diagram, "diagramSpan");</script>
<h2 id="BrushFunctions">Brush Functions</h2>
<p>
There are some functions available for generating different colors or modifying Brush colors:
</p>
<ul>
<li>
<a>Brush,randomColor</a> - returns a random hexadecimal color value
<li>
<a>Brush,lightenBy</a> and <a>Brush,darkenBy</a> - return lightened or darkened colors/brushes; there are both instance
and static versions
</li>
<li>
<a>Brush,lighten</a> and <a>Brush,darken</a> - convenience static functions which return lightened or darkened colors
</li>
<li>
<a>Brush,mix</a> - mixes two colors together
</li>
<li>
<a>Brush,isDark</a> - determines whether a color is dark, often used in bindings; there are both instance and static versions
</li>
</ul>
<p>
In the following example, parts use the lighten and darken functions to get suitable colors for their stroke/fill.
</p>
<pre class="lang-js" id="functions">
diagram.layout = $(go.GridLayout);
var color1 = "rgb(80, 130, 210)";
var color2 = go.Brush.randomColor(192, 224);
var gradBrush = $(go.Brush, "Linear", { 0: color1, 1: color2 });
function shapeStyle() {
return [ "Ellipse", { width: 120, height: 80, strokeWidth: 4 } ];
}
// static Brush methods
diagram.add($(go.Part, "Auto",
$(go.Shape, shapeStyle(),
{ fill: color1, stroke: go.Brush.darken(color1) }),
$(go.TextBlock, "dark stroke")));
diagram.add($(go.Part, "Auto",
$(go.Shape, shapeStyle(),
{ fill: color1, stroke: go.Brush.darkenBy(color1, .4) }),
$(go.TextBlock, "darker stroke")));
diagram.add($(go.Part, "Auto",
$(go.Shape, shapeStyle(),
{ fill: go.Brush.lighten(color1), stroke: color1 }),
$(go.TextBlock, "light fill")));
diagram.add($(go.Part, "Auto",
$(go.Shape, shapeStyle(),
{ fill: go.Brush.lightenBy(color1, .4), stroke: color1 }),
$(go.TextBlock, "lighter fill")));
// instance Brush methods
diagram.add($(go.Part, "Auto",
$(go.Shape, shapeStyle(),
{ fill: gradBrush.copy().lightenBy(.2) }),
$(go.TextBlock, "lighter")));
diagram.add($(go.Part, "Auto",
$(go.Shape, shapeStyle(),
{ fill: gradBrush }),
$(go.TextBlock, "normal")));
diagram.add($(go.Part, "Auto",
$(go.Shape, shapeStyle(),
{ fill: gradBrush.copy().darkenBy(.2) }),
$(go.TextBlock, "darker")));
</pre>
<script>goCode("functions", 600, 200)</script>
<p>
In the following example, the color of text is determined by whether the background shape is dark.
</p>
<pre class="lang-js" id="functions2">
diagram.layout = $(go.GridLayout);
diagram.nodeTemplate =
$(go.Node, "Auto",
{ desiredSize: new go.Size(80, 40) },
$(go.Shape, "RoundedRectangle", { strokeWidth: 0 },
new go.Binding("fill", "color")),
$(go.TextBlock, { margin: 8 },
new go.Binding("stroke", "color",
// dark nodes use white text, light nodes use dark text
function (c) { return go.Brush.isDark(c) ? "white" : "black"; }),
new go.Binding("text", "key")
)
);
diagram.model = new go.Model(
[
{ key: "Alpha", color: "white" },
{ key: "Beta", color: "black" },
{ key: "Gamma", color: "darkblue" },
{ key: "Delta", color: "lightblue" },
{ key: "Epsilon", color: "darkgreen" },
{ key: "Zeta", color: "lightgreen" },
{ key: "Eta", color: "darkred" },
{ key: "Theta", color: "lightcoral" }
]
)
</pre>
<script>goCode("functions2", 600, 200)</script>
</div>
</div>
</body>
</html>
+424
View File
@@ -0,0 +1,424 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Building GraphObjects -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="../extensions/Figures.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Building Parts with GraphObjects</h1>
<p>
You can construct a <a>Node</a> or other kind of <a>Part</a> in traditional JavaScript code.
<b>GoJS</b> also offers a more declarative-looking manner of building parts that has several advantages over code.
</p>
<p>
The following pages will discuss the basic kinds of objects you can use to build a node.
These pages build up a diagram by explicitly creating and adding nodes and links.
Later pages will show how to build diagrams using models rather than using such code.
</p>
<h2 id="VisualStructureOfNodesAndLinks">The Visual Structure of Nodes and Links</h2>
<p>
First, look at a diagram that includes comments about the GraphObjects used to build some example nodes and links:
</p>
<pre class="lang-js" id="commented" style="display:none">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ scale: 2 }, // make it easier to see
new go.Binding("location", "loc", go.Point.parse),
{ locationSpot: go.Spot.Center, portId: "Node" },
$(go.Shape, "RoundedRectangle",
{ fill: "white", portId: "Shape" },
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 4, stroke: "blue", portId: "TextBlock" },
new go.Binding("text"))
);
diagram.linkTemplate =
$(go.Link, // make it easier to see
$(go.Shape, { strokeWidth: 3 }),
$(go.Shape, { scale: 2, toArrow: "Standard" })
);
// define several shared Brushes
var bluegrad = $(go.Brush, "Linear", { 0: "rgb(150, 150, 250)", 0.5: "rgb(86, 86, 186)", 1: "rgb(86, 86, 186)" });
var yellowgrad = $(go.Brush, "Linear", { 0: "rgb(254, 221, 50)", 1: "rgb(254, 182, 50)" });
var lightgrad = $(go.Brush, "Linear", { 1: "#E6E6FA", 0: "#FFFAF0" });
// the template for each attribute in a node's array of item data
var itemTempl =
$(go.Panel, "TableRow",
new go.Binding("portId", "name", function(n) { return n + "ITEMPANEL"; }),
new go.Binding("background", "row", function(i) { return (i === 2) ? "lightgreen" : "transparent"; }).ofObject(),
$(go.Shape,
new go.Binding("portId", "name", function(n) { return n + "SHAPE"; }),
{ column: 0, desiredSize: new go.Size(10, 10) },
new go.Binding("figure", "figure"),
new go.Binding("fill", "color")),
$(go.TextBlock,
{
column: 1,
stroke: "#333333",
font: "bold 14px sans-serif"
},
new go.Binding("text", "name"),
new go.Binding("portId", "name", function(n) { return n + "TEXTBLOCK"; }))
);
// define the Node template, representing an entity
diagram.nodeTemplateMap.add("Complex",
$(go.Node, "Auto", // the whole node panel
{
locationSpot: go.Spot.Center,
scale: 1.5,
selectionAdorned: true,
fromSpot: go.Spot.AllSides,
toSpot: go.Spot.AllSides,
isShadowed: true,
shadowColor: "#C5C1AA"
},
new go.Binding("location", "loc", go.Point.parse),
// define the node's outer shape, which will surround the Table
$(go.Shape, "Rectangle",
{ portId: "RECTANGLE" },
{ fill: lightgrad, stroke: "#756875", strokeWidth: 3 }),
$(go.Panel, "Table",
{ margin: 8, stretch: go.GraphObject.Fill },
$(go.RowColumnDefinition, { row: 0, sizing: go.RowColumnDefinition.None }),
// the table header
$(go.TextBlock,
{ portId: "HEADER" },
{
row: 0, alignment: go.Spot.Center,
margin: new go.Margin(0, 14, 0, 2), // leave room for Button
font: "bold 16px sans-serif"
},
new go.Binding("text", "key")),
// the collapse/expand button
$("Button",
{ portId: "BUTTON" },
{
row: 0, alignment: go.Spot.TopRight,
"ButtonBorder.stroke": null,
click: function(e, but) {
var list = but.part.findObject("LIST");
if (list !== null) {
list.diagram.startTransaction("collapse/expand");
list.visible = !list.visible;
var shape = but.findObject("SHAPE");
if (shape !== null) shape.figure = (list.visible ? "TriangleUp" : "TriangleDown");
list.diagram.commitTransaction("collapse/expand");
}
}
},
$(go.Shape, "TriangleUp",
{ name: "SHAPE", width: 6, height: 4 })),
// the list of Panels, each showing an attribute
$(go.Panel, "Table",
{
name: "LIST", background: "pink", portId: "LIST",
row: 1,
padding: 3,
alignment: go.Spot.TopLeft,
defaultAlignment: go.Spot.Left,
itemTemplate: itemTempl
},
new go.Binding("itemArray", "items"))
) // end Table Panel
)); // end Node
// annotations -- brown text
diagram.nodeTemplateMap.add("Comment",
$(go.Node,
new go.Binding("location", "loc", go.Point.parse),
{ locationSpot: go.Spot.Center },
$(go.TextBlock,
{ stroke: "brown", textAlign: "center" },
new go.Binding("text"),
new go.Binding("font", "bold", function(b) { return b ? "bold 10pt sans-serif" : "10pt sans-serif"; }))
));
// so that comments can point at any named GraphObject in a Link
diagram.nodeTemplateMap.add("LinkLabel",
$(go.Node,
new go.Binding("segmentIndex"),
new go.Binding("segmentOffset")
));
// brown curved links connecting with a Comment node
diagram.linkTemplateMap.add("Comment",
$(go.Link,
{ curve: go.Link.Bezier },
new go.Binding("curviness"),
$(go.Shape, { stroke: "brown" }),
$(go.Shape, { toArrow: "OpenTriangle", stroke: "brown" })
));
var model = new go.GraphLinksModel();
model.linkToPortIdProperty = "pid";
model.linkLabelKeysProperty = "labs";
model.nodeDataArray = [
{ key: 1, text: "Alpha", color: "lightblue", loc: "0 0" },
{ key: 2, text: "Beta", color: "lightgreen", loc: "200 0" },
{ key: -1, text: "two Nodes", category: "Comment", bold: true, loc: "100 -60" },
{ key: -2, text: "a Shape of figure\n'RoundedRectangle',\nwith black stroke\nand lightblue fill", category: "Comment", loc: "-140 0" },
{ key: -3, text: "a TextBlock with blue stroke\nand no background\nshowing the string 'Alpha'", category: "Comment", loc: "-50 70" },
{ key: -4, text: "a Link's\nmain path\nShape", category: "Comment", loc: "100 40" },
{ key: -41, category: "LinkLabel" },
{ key: -5, text: "a Link's\narrowhead\nShape", category: "Comment", loc: "170 80" },
{ key: -51, category: "LinkLabel", segmentIndex: -1, segmentOffset: new go.Point(-8, 4) },
{ key: -6, text: "a Link", category: "Comment", bold: true, loc: "100 -30" },
{ key: -7, text: "this Node Panel\nalso acts as the\nNode's only port", category: "Comment", loc: "320 0" },
{ key: 11, category: "Complex", loc: "0 230",
items: [{ name: "SupplierID", iskey: true, figure: "Decision", color: yellowgrad },
{ name: "CompanyName", iskey: false, figure: "Cube1", color: bluegrad },
{ name: "ContactName", iskey: false, figure: "Cube1", color: bluegrad },
{ name: "Address", iskey: false, figure: "Cube1", color: bluegrad }]
},
{ key: -11, text: "a Rectangle Shape", category: "Comment", loc: "-70 120" },
{ key: -12, text: "a TextBlock\nacting as a header", category: "Comment", loc: "70 120" },
{ key: -13, text: "a Button Panel consisting\nof two Shapes", category: "Comment", loc: "200 150" },
{ key: -14, text: "a Vertical items Panel\nwith pink background,\nholding 4 Panels,\none per item", category: "Comment", loc: "200 220" },
{ key: -15, text: "a TextBlock\nin a Panel for item #3", category: "Comment", loc: "50 340" },
{ key: -16, text: "a Shape\nin a Panel for item #3", category: "Comment", loc: "-140 320" },
{ key: -17, text: "a TableRow Panel\nfor item #2\nwith lightgreen\nbackground", category: "Comment", loc: "-200 250" }
];
model.linkDataArray = [
{ from: 1, to: 2, labs: [-41, -51] },
{ from: -1, category: "Comment", to: 1, pid: "Node", curviness: -10 },
{ from: -1, category: "Comment", to: 2, pid: "Node" },
{ from: -2, category: "Comment", to: 1, pid: "Shape" },
{ from: -3, category: "Comment", to: 1, pid: "TextBlock", curviness: -10 },
{ from: -4, category: "Comment", to: -41, curviness: 0 },
{ from: -5, category: "Comment", to: -51, curviness: 5 },
{ from: -6, category: "Comment", to: -41, curviness: -5 },
{ from: -7, category: "Comment", to: 2, pic: "Node", curviness: -10 },
{ from: -11, category: "Comment", to: 11, pid: "RECTANGLE", curviness: 0 },
{ from: -12, category: "Comment", to: 11, pid: "HEADER" },
{ from: -13, category: "Comment", to: 11, pid: "BUTTON" },
{ from: -14, category: "Comment", to: 11, pid: "LIST" },
{ from: -15, category: "Comment", to: 11, pid: "AddressTEXTBLOCK" },
{ from: -16, category: "Comment", to: 11, pid: "AddressSHAPE" },
{ from: -17, category: "Comment", to: 11, pid: "ContactNameITEMPANEL" }
];
diagram.model = model;
</pre>
<script>goCode("commented", 650, 450)</script>
<p>
As you can see, a node or a link can be composed of many GraphObjects, including <a>Panel</a>s that may be nested.
You can drag around any comment in order to see the area covered by the GraphObject at the end of the comment link to it,
except for the GraphObjects within the Link itself.
</p>
<h2 id="BuildingWithCode">Building with Code</h2>
<p>
A <a>GraphObject</a> is a JavaScript object that can be constructed and initialized
in the same manner as any other object.
A <a>Node</a> is a <a>GraphObject</a> that contains <a>GraphObject</a>s such as <a>TextBlock</a>s, <a>Shape</a>s,
<a>Picture</a>s, and <a>Panel</a>s that may contain yet more GraphObjects.
</p>
<p>
A very simple Node might consist of a Shape and a TextBlock.
You can build such a visual tree of GraphObjects using code such as:
</p>
<pre class="lang-js" id="simpleCode">
var node = new go.Node(go.Panel.Auto);
var shape = new go.Shape();
shape.figure = "RoundedRectangle";
shape.fill = "lightblue";
node.add(shape);
var textblock = new go.TextBlock();
textblock.text = "Hello!";
textblock.margin = 5;
node.add(textblock);
diagram.add(node);
</pre>
<p>
This code produces the following diagram. It is a "live" diagram, not a screenshot image,
so you can click on the node to select it and then drag it around.
</p>
<script>goCode("simpleCode", 250, 150)</script>
<p>
Although building a node in this manner will work, as the nodes get more complicated
the code will become more complicated to read and to maintain.
Fortunately <b>GoJS</b> has a better way to make Parts out of GraphObjects.
</p>
<p>
Furthermore, later sections will discuss how Nodes and Links should be created automatically using models, templates, and data-binding.
Until that time, these pages will create Nodes explicitly and add them to Diagrams directly.
</p>
<h2 id="BuildingWithMake">Building with <b>GraphObject.make</b></h2>
<p>
<b>GoJS</b> defines a static function, <a>GraphObject,make</a>, that is very useful in
constructing GraphObjects without having to think of and keep track of temporary variable names.
This static function also supports building objects in a nested fashion,
where the indentation gives you a clue about depth in the visual tree,
unlike the simple linear code shown above.
</p>
<p>
<a>GraphObject,make</a> is a function whose first argument must be a class type,
typically a subclass of <a>GraphObject</a>.
</p>
<p>
Additional arguments to <a>GraphObject,make</a> may be of several types:
</p>
<ul>
<li>a plain JavaScript object with property/value pairs -- these property values are set on the object being constructed</li>
<li>a <a>GraphObject</a>, which is added as an element to the <a>Panel</a> that is being constructed</li>
<li>a <b>GoJS</b> enumerated value constant, which is used as the value of the unique property of the object being constructed that can accept such a value</li>
<li>a string, which sets the <a>TextBlock.text</a>, <a>Shape.figure</a>, <a>Picture.source</a>, or <a>Panel.type</a> property of the object that is being constructed</li>
<li>a <a>RowColumnDefinition</a>, for describing rows or columns in Table <a>Panel</a>s</li>
<li>a JavaScript Array, holding arguments to <a>GraphObject,make</a>, useful when returning more than one argument from a function</li>
<li>other specialized objects that are used in the appropriate manner for the object being constructed</li>
</ul>
<p>
We can rewrite the code above with <b>go.GraphObject.make</b> to produce exactly the same results:
</p>
<pre class="lang-js" id="simpleJSAML">
var $ = go.GraphObject.make;
diagram.add(
$(go.Node, go.Panel.Auto,
$(go.Shape,
{ figure: "RoundedRectangle",
fill: "lightblue" }),
$(go.TextBlock,
{ text: "Hello!",
margin: 5 })
));
</pre>
<script>goCode("simpleJSAML", 250, 150)</script>
<p>
This can be simplified a bit by using string arguments:
</p>
<pre class="lang-js" id="simpleJSAML2">
var $ = go.GraphObject.make;
diagram.add(
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle", { fill: "lightblue" }),
$(go.TextBlock, "Hello!", { margin: 5 })
));
</pre>
<script>goCode("simpleJSAML2", 250, 150)</script>
<p>
Notice how we set the <a>Panel.type</a>, <a>Shape.figure</a>, and <a>TextBlock.text</a> properties by just using the string value.
</p>
<p>
The use of <b>$</b> as an abbreviation for <b>go.GraphObject.make</b> is so handy
that we will assume its use from now on.
Having the call to <b>go.GraphObject.make</b> be minimized into a single character
helps remove clutter from the code and lets the indentation match the nesting of
<a>GraphObject</a>s in the visual tree that is being constructed.
</p>
<p>
Some other JavaScript libraries automatically define "$" to be a handy-to-type function name,
assuming that they are the only library that matters.
But you cannot have the same symbol have two different meanings at the same time in the same scope, of course.
So you may want to choose to use a different short name, such as "$$" or "GO" when using <b>GoJS</b>.
The <b>GoJS</b> documentation and samples make use of "$" because it makes the resulting code most clear.
</p>
<p class="box bg-info">
Another advantage of using <a>GraphObject,make</a> is that it will make sure that any
properties that you set are defined properties on the class.
If you have a typo in the name of the property, it will throw an error, for which you can see a message in the console log.
</p>
<p>
<a>GraphObject,make</a> also works to build <b>GoJS</b> classes other than ones inheriting from <a>GraphObject</a>.
Here is an example of using <b>go.GraphObject.make</b> to build a <a>Brush</a>
rather than a <a>GraphObject</a> subclass.
</p>
<pre class="lang-js" id="gradientJSAML">
diagram.add(
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle",
{ fill: $(go.Brush, "Linear",
{ 0.0: "Violet", 1.0: "Lavender" }) }),
$(go.TextBlock, "Hello!",
{ margin: 5 })
));
</pre>
<script>goCode("gradientJSAML", 250, 150)</script>
<p>
It is also common to use <a>GraphObject,make</a> to build a <a>Diagram</a>.
In such a use a string argument, which if provided must be the second argument, will name the DIV HTML element that the Diagram should use.
Equivalently you can pass a direct reference to the DIV element as the second argument.
</p>
<p>
Also, when setting properties on a Diagram, you can use property names that are strings consisting of two identifiers separated by a period.
The name before the period is used as the name of a property on the Diagram or on the <a>Diagram.toolManager</a> that returns an object whose property is to be set.
The name after the period is the name of the property that is set.
Note that because there is an embedded period, JavaScript property syntax requires that you use quotes.
</p>
<p>
You can also declare <a>DiagramEvent</a> listeners, as if calling <a>Diagram.addDiagramListener</a>,
by pretending to set a Diagram property that is actually the name of a DiagramEvent.
Because all DiagramEvents have names that are capitalized, the names will not conflict with any Diagram property names.
</p>
<p>
Here is a moderately extensive usage of GraphObject.make to build a Diagram:
</p>
<pre class="lang-js">
var myDiagram =
$(go.Diagram, "myDiagramDiv", // must name or refer to the DIV HTML element
{
// don't initialize some properties until after a new model has been loaded
"InitialLayoutCompleted": loadDiagramProperties, // a DiagramEvent listener
// have mouse wheel events zoom in and out instead of scroll up and down
"toolManager.mouseWheelBehavior": go.ToolManager.WheelZoom,
// specify a data object to copy for each new Node that is created by clicking
"clickCreatingTool.archetypeNodeData": { text: "new node" }
});
// the DiagramEvent listener for "InitialLayoutCompleted"
function loadDiagramProperties(e) { . . . }
</pre>
<p>
All of this initialization using <a>GraphObject,make</a> is still JavaScript code, so we can call functions
and easily share objects such as brushes:
</p>
<pre class="lang-js" id="codeJSAML">
var violetbrush = $(go.Brush, "Linear", { 0.0: "Violet", 1.0: "Lavender" });
diagram.add(
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle",
{ fill: violetbrush }),
$(go.TextBlock, "Hello!",
{ margin: 5 })
));
diagram.add(
$(go.Node, "Auto",
$(go.Shape, "Ellipse",
{ fill: violetbrush }),
$(go.TextBlock, "Goodbye!",
{ margin: 5 })
));
</pre>
<script>goCode("codeJSAML", 250, 150)</script>
<p>
<a>Brush</a>es and <a>Geometry</a> objects may be shared, but <a>GraphObject</a>s may not be shared.
</p>
<p>
The following pages will provide more details about the basic building block classes,
<a>TextBlock</a>, <a>Shape</a>, and <a>Picture</a>, and about ways of aggregating them with the <a>Panel</a> class.
</p>
</div>
</div>
</body>
</html>
+250
View File
@@ -0,0 +1,250 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Buttons -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Buttons</h1>
<p>
For your convenience we have defined several <a>Panel</a>s for common uses.
These include "Button", "TreeExpanderButton", "SubGraphExpanderButton", "PanelExpanderButton", and "ContextMenuButton".
</p>
<p>
These predefined panels can be used as if they were <a>Panel</a>-derived classes in calls to <a>GraphObject,make</a>.
They are implemented as simple visual trees of <a>GraphObject</a>s in <a>Panel</a>s,
with pre-set properties and event handlers.
</p>
<p>
You can see a copy of their definitions in this file:
<a href="../extensions/Buttons.js">Buttons.js</a>.
</p>
<p>
See samples that make use of buttons in the <a href="../samples/index.html#buttons">samples index</a>.
In addition, see the <a href="../extensions/Checkboxes.html">Checkboxes</a> extension for an example of using "CheckBoxButton".
</p>
<h2 id="GeneralButtons">General Buttons</h2>
<p>
The most general kind of predefined <a>Panel</a> is "Button".
</p>
<pre class="lang-js" id="button">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ locationSpot: go.Spot.Center },
$(go.Shape, "Rectangle",
{ fill: "gold" }),
$(go.Panel, "Vertical",
{ margin: 3 },
$("Button",
{ margin: 2,
click: incrementCounter },
$(go.TextBlock, "Click me!")),
$(go.TextBlock,
new go.Binding("text", "clickCount",
function(c) { return "Clicked " + c + " times."; }))
)
);
function incrementCounter(e, obj) {
var node = obj.part;
var data = node.data;
if (data && typeof(data.clickCount) === "number") {
node.diagram.model.commit(function(m) {
m.set(data, "clickCount", data.clickCount + 1);
}, "clicked");
}
}
diagram.model = new go.GraphLinksModel(
[ { clickCount: 0 } ]);
</pre>
<script>goCode("button", 600, 150)</script>
<h2 id="TreeExpanderButtons">TreeExpanderButtons</h2>
<p>
It is common to want to expand and collapse subtrees.
It is easy to let the user control this by adding an instance of the "TreeExpanderButton" to your node template.
</p>
<pre class="lang-js" id="treeExpanderButton">
diagram.nodeTemplate =
$(go.Node, "Spot",
$(go.Panel, "Auto",
$(go.Shape, "Rectangle",
{ fill: "gold" }),
$(go.TextBlock, "Click small button\nto collapse/expand subtree",
{ margin: 5 })
),
$("TreeExpanderButton",
{ alignment: go.Spot.Bottom, alignmentFocus: go.Spot.Top },
{ visible: true })
);
diagram.layout = $(go.TreeLayout, { angle: 90 });
diagram.model = new go.GraphLinksModel(
[ { key: 1 },
{ key: 2 } ],
[ { from: 1, to: 2 } ] );
</pre>
<script>goCode("treeExpanderButton", 600, 200)</script>
<h2 id="SubGraphExpanderButtons">SubGraphExpanderButtons</h2>
<p>
It is also common to want to expand and collapse groups containing subgraphs.
You can let the user control this by adding an instance of the "SubGraphExpanderButton" to your group template.
</p>
<pre class="lang-js" id="subgraphExpanderButton">
diagram.groupTemplate =
$(go.Group, "Auto",
$(go.Shape, "Rectangle",
{ fill: "gold" }),
$(go.Panel, "Vertical",
{ margin: 5,
defaultAlignment: go.Spot.Left },
$(go.Panel, "Horizontal",
$("SubGraphExpanderButton",
{ margin: new go.Margin(0, 3, 5, 0) }),
$(go.TextBlock, "Group")
),
$(go.Placeholder)
)
);
diagram.model = new go.GraphLinksModel(
[ { key: 0, isGroup: true },
{ key: 1, group: 0 },
{ key: 2, group: 0 },
{ key: 3, group: 0 } ] );
</pre>
<script>goCode("subgraphExpanderButton", 600, 150)</script>
<h2 id="PanelExpanderButtons">PanelExpanderButtons</h2>
<p>
It is common to want to expand and collapse a piece of a node,
thereby showing or hiding details that are sometimes not needed.
It is easy to let the user control this by adding an instance of the "PanelExpanderButton" to your node template.
The second argument to <a>GraphObject,make</a> should be a string that names the element in the node whose
<a>GraphObject.visible</a> property you want the button to toggle.
</p>
<pre class="lang-js" id="panelExpanderButton">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape,
{ fill: "gold" }),
$(go.Panel, "Table",
{ defaultAlignment: go.Spot.Top, defaultColumnSeparatorStroke: "black" },
$(go.Panel, "Table",
{ column: 0 },
$(go.TextBlock, "List 1",
{ column: 0, margin: new go.Margin(3, 3, 0, 3),
font: "bold 12pt sans-serif" }),
$("PanelExpanderButton", "LIST1",
{ column: 1 }),
$(go.Panel, "Vertical",
{ name: "LIST1", row: 1, column: 0, columnSpan: 2 },
new go.Binding("itemArray", "list1"))
),
$(go.Panel, "Table",
{ column: 1 },
$(go.TextBlock, "List 2",
{ column: 0, margin: new go.Margin(3, 3, 0, 3),
font: "bold 12pt sans-serif" }),
$("PanelExpanderButton", "LIST2",
{ column: 1 }),
$(go.Panel, "Vertical",
{ name: "LIST2", row: 1, column: 0, columnSpan: 2 },
new go.Binding("itemArray", "list2"))
)
)
);
diagram.model = new go.GraphLinksModel([
{
key: 1,
list1: [ "one", "two", "three", "four", "five" ],
list2: [ "first", "second", "third", "fourth" ]
}
]);
</pre>
<script>goCode("panelExpanderButton", 600, 200)</script>
<h2 id="ContextMenuButtons">ContextMenuButtons</h2>
<p>
Although you can implement context menus in any way you choose, it is common to use the predefined "ContextMenuButton".
</p>
<pre class="lang-js" id="contextMenuButtons">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "Rectangle",
{ fill: "gold" }),
$(go.TextBlock, "Use ContextMenu!",
{ margin: 5 })
);
diagram.nodeTemplate.contextMenu =
$("ContextMenu",
$("ContextMenuButton",
$(go.TextBlock, "Shift Left"),
{ click: function(e, obj) { shiftNode(obj, -20); } }),
$("ContextMenuButton",
$(go.TextBlock, "Shift Right"),
{ click: function(e, obj) { shiftNode(obj, +20); } })
);
function shiftNode(obj, dist) {
var adorn = obj.part;
var node = adorn.adornedPart;
node.diagram.commit(function(d) {
var pos = node.location.copy();
pos.x += dist;
node.location = pos;
}, "Shift");
}
diagram.model = new go.GraphLinksModel(
[ { key: 1 } ] );
</pre>
<script>goCode("contextMenuButtons", 600, 150)</script>
<p>
For an example of defining context menus using HTML, see the <a href="../samples/customContextMenu.html">Custom ContextMenu sample</a>.
</p>
<h2 id="ButtonDefinitions">Button Definitions</h2>
<p>
The implementation of all predefined buttons is provided in <a href="../extensions/Buttons.js">Buttons.js</a>
in the Extensions directory.
You may wish to copy and adapt these definitions when creating your own buttons.
</p>
<p>
Those definitions might not be an up-to-date description
of the actual standard button implementations that are in <b>GoJS</b> and used by <a>GraphObject,make</a>.
</p>
<p>
Note that the definitions of those buttons makes use of the <a>GraphObject.defineBuilder</a> static function.
That extends the behavior of <a>GraphObject,make</a> to allow the creation of fairly complex visual trees by name with optional arguments.
You can find the definitions of various kinds of controls throughout the samples and extensions, such as at:
<ul>
<li><a href="../extensions/Buttons.js">Buttons.js</a></li>
<li><a href="../extensions/HyperlinkText.js">HyperlinkText.js</a></li>
<li><a href="../extensions/ScrollingTable.js">ScrollingTable.js</a></li>
<li><a href="../extensions/ScrollingTable.js">AutoRepeatButton</a></li>
</ul>
</p>
</div>
</div>
</body>
</html>
+280
View File
@@ -0,0 +1,280 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Changed Events -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Changed Events</h1>
<p>
There are three basic kinds of events that <b>GoJS</b> generates:
<a>DiagramEvent</a>s, <a>InputEvent</a>s, and <a>ChangedEvent</a>s.
This page talks about the latter, which are generated as <a>Diagram</a>s, <a>GraphObject</a>s,
<a>Model</a>s, or Model data objects are modified.
See the page <a href="events.html">Events</a> for the former two kinds of events.
</p>
<p>
<a>ChangedEvent</a>s in <b>GoJS</b> are notifications of state changes, mostly object property changes.
The ChangedEvent records the kind of change that occurred and enough information to be able to undo and redo them.
</p>
<p>
Changed events are produced by both <a>Model</a> and <a>Diagram</a>.
They are multicast events, so you can call <a>Model.addChangedListener</a> and <a>Diagram.addChangedListener</a>,
as well as the corresponding removeChangedListener methods.
For convenience you can also specify a Model change listener on a Diagram: <a>Diagram.addModelChangedListener</a>.
<a>ChangedEvent</a>s received by a Model change listener will have a non-null value for <a>ChangedEvent.model</a>.
Similarly, ChangedEvents received by a Diagram change listener will have non-null value for <a>ChangedEvent.diagram</a>.
</p>
<p>
A <a>Diagram</a> always registers itself as a listener on its <a>Model</a>, so that it can automatically
notice changes to the model and update its Parts accordingly.
Furthermore the <a>UndoManager</a>, if enabled, automatically listens to changes to both the model and the diagram,
so that it can record the change history and perform undo and redo.
</p>
<h2 id="ModelAndDataChanges">Model and Data changes</h2>
<h3 id="ModelPropertyChanges">Model property changes</h3>
<p>
Model ChangedEvents record state changes either to data in a model or to the <a>Model</a> itself.
ChangedEvents for models are generated by calls to <a>Model.setDataProperty</a> and by <a>Model</a> property setters.
</p>
<p>
For property changes, that information includes the <a>ChangedEvent.object</a> that was modified,
the <a>ChangedEvent.propertyName</a>, and the <a>ChangedEvent.oldValue</a> and <a>ChangedEvent.newValue</a> values
for that property.
Property changes are identified by the <a>ChangedEvent.change</a> property value being <a>ChangedEvent,Property</a>.
</p>
<p>
Some changes represent structural changes to the model, not just simple model data changes.
"Structural" changes are the insertion, modification, or removal of relationships that the model is responsible for maintaining.
In such cases the <a>ChangedEvent.modelChange</a> property will be a non-empty string naming the kind of change.
The following names for Property <a>ChangedEvent</a>s correspond to structural model data changes:
</p>
<ul>
<li>"<b>nodeDataArray</b>", when the <a>Model.nodeDataArray</a> Array has been replaced</li>
<li>"<b>nodeCategory</b>", due to a call to <a>Model.setCategoryForNodeData</a></li>
<li>"<b>nodeGroupKey</b>", due to a call to <a>GraphLinksModel.setGroupKeyForNodeData</a></li>
<li>"<b>linkDataArray</b>", when the <a>GraphLinksModel.linkDataArray</a> Array has been replaced</li>
<li>"<b>linkFromKey</b>", due to a call to <a>GraphLinksModel.setFromKeyForLinkData</a></li>
<li>"<b>linkToKey</b>", due to a call to <a>GraphLinksModel.setToKeyForLinkData</a></li>
<li>"<b>linkFromPortId</b>", due to a call to <a>GraphLinksModel.setFromPortIdForLinkData</a></li>
<li>"<b>linkToPortId</b>", due to a call to <a>GraphLinksModel.setToPortIdForLinkData</a></li>
<li>"<b>linkLabelKeys</b>", due to a call to <a>GraphLinksModel.setLabelKeysForLinkData</a></li>
<li>"<b>linkCategory</b>", due to a call to <a>GraphLinksModel.setCategoryForLinkData</a></li>
<li>"<b>nodeParentKey</b>", due to a call to <a>TreeModel.setParentKeyForNodeData</a></li>
<li>"<b>parentLinkCategory</b>", due to a call to <a>TreeModel.setParentLinkCategoryForNodeData</a></li>
</ul>
<p>
The value of <a>ChangedEvent.modelChange</a> will be one of these strings.
The value of <a>ChangedEvent.propertyName</a> depends on the name of the actual data property that was modified.
For example, for the model property change "linkFromKey", the actual property name defaults to "from".
But you might be using a different property name by having set <a>GraphLinksModel.linkFromKeyProperty</a> to some other data property name.
</p>
<p>
Any property can be changed on a node data or link data object, by calling <a>Model.setDataProperty</a>.
Such a call will result in the property name to be recorded as the <a>ChangedEvent.propertyName</a>.
These cases are treated as normal property changes, not structural model changes,
so <a>ChangedEvent.modelChange</a> will be the empty string.
The value of <a>ChangedEvent.object</a> will of course be the JavaScript object that was modified.
</p>
<p>
Some changes may happen temporarily because some code, such as in a Tool, might want to use temporary objects for their own purposes.
However your change listener might not be interested in such <a>ChangedEvent</a>s.
If that is the case, you may want to ignore the ChangedEvent if <a>Model.skipsUndoManager</a> (or <a>Diagram.skipsUndoManager</a>) is true.
</p>
<p>
Finally, there are property changes on the model itself.
For a listing of such properties, see the documentation for <a>Model</a>, <a>GraphLinksModel</a>, and <a>TreeModel</a>.
These cases are also treated as normal property changes, so <a>ChangedEvent.modelChange</a> will be the empty string.
Both <a>ChangedEvent.model</a> and <a>ChangedEvent.object</a> will be the model itself.
</p>
<h3 id="ModelCollectionChanges">Model collection changes</h3>
<p>
Other kinds of changed events include <a>ChangedEvent,Insert</a> and <a>ChangedEvent,Remove</a>.
In addition to all of the previously mentioned ChangedEvent properties used to record a property change,
the <a>ChangedEvent.oldParam</a> and <a>ChangedEvent.newParam</a> provide the "index" information
needed to be able to properly undo and redo the change.
</p>
<p>
The following names for Insert and Remove <a>ChangedEvent</a>s correspond to model changes to collections:
</p>
<ul>
<li>"<b>nodeDataArray</b>", due to a call to <a>Model.addNodeData</a> or <a>Model.removeNodeData</a></li>
<li>"<b>linkDataArray</b>", due to a call to <a>GraphLinksModel.addLinkData</a> or <a>GraphLinksModel.removeLinkData</a></li>
<li>"<b>linkLabelKeys</b>", due to a call to <a>GraphLinksModel.addLabelKeyForLinkData</a>
or <a>GraphLinksModel.removeLabelKeyForLinkData</a></li>
</ul>
<h3 id="Transactions">Transactions</h3>
<p>
The final kind of model changed event is <a>ChangedEvent,Transaction</a>.
These are not strictly object changes in the normal sense, but they do notify when a transaction starts or finishes,
or when an undo or redo starts or finishes.
</p>
<p>
The following values of <a>ChangedEvent.propertyName</a> describe the kind of transaction-related event that just occurred:
</p>
<ul>
<li>"<b>StartingFirstTransaction</b>"</li>
<li>"<b>StartedTransaction</b>"</li>
<li>"<b>CommittingTransaction</b>"</li>
<li>"<b>CommittedTransaction</b>"</li>
<li>"<b>RolledBackTransaction</b>"</li>
<li>"<b>StartingUndo</b>"</li>
<li>"<b>FinishedUndo</b>"</li>
<li>"<b>StartingRedo</b>"</li>
<li>"<b>FinishedRedo</b>"</li>
</ul>
<p>
In each case the <a>ChangedEvent.object</a> is the <a>Transaction</a> holding a sequence of <a>ChangedEvent</a>s.
The <a>ChangedEvent.oldValue</a> is the name of the transaction --
the string passed to <a>UndoManager.startTransaction</a> or <a>UndoManager.commitTransaction</a>.
The various standard commands and tools that perform transactions document the transaction name(s) that they employ.
But your code can employ as many transaction names as you like.
</p>
<p class="box bg-danger">
As a general rule, you should not make any changes to the model or any of its data in a listener
for any Transaction ChangedEvent.
</p>
<h3 id="SavingModelWhenTransactionsComplete">Saving the Model when Transactions Complete</h3>
<p>
It is commonplace to want to update a server database when a transaction has finished.
Use the <a>ChangedEvent.isTransactionFinished</a> read-only property to detect that case.
You'll want to implement a Changed listener as follows:
</p>
<pre class="lang-js">
// notice whenever a transaction or undo/redo has occurred
diagram.addModelChangedListener(function(evt) {
if (evt.isTransactionFinished) saveModel(evt.model);
});
</pre>
<p>
The value of <a>Transaction.changes</a> will be a List of <a>ChangedEvent</a>s, in the order that they were recorded.
Those ChangedEvents represent changes both to the <a>Model</a> and to the <a>Diagram</a> or its <a>GraphObject</a>s.
Model changes will have <code>e.model !== null</code>; diagram changes will have <code>e.diagram !== null</code>.
</p>
<h3 id="IncrementallySavingChangesToModel">Incrementally Saving Changes to the Model</h3>
<p>
If you do not want to save the whole model at the end of each transaction, but only certain changes to the model,
you can iterate over the list of changes to pick out the ones that you care about.
For example, here is a listener that logs a message only when node data is added to or removed from the <a>Model.nodeDataArray</a>.
</p>
<pre class="lang-js">
diagram.addModelChangedListener(function(evt) {
// ignore unimportant Transaction events
if (!evt.isTransactionFinished) return;
var txn = evt.object; // a Transaction
if (txn === null) return;
// iterate over all of the actual ChangedEvents of the Transaction
txn.changes.each(function(e) {
// ignore any kind of change other than adding/removing a node
if (e.modelChange !== "nodeDataArray") return;
// record node insertions and removals
if (e.change === go.ChangedEvent.Insert) {
console.log(evt.propertyName + " added node with key: " + e.newValue.key);
} else if (e.change === go.ChangedEvent.Remove) {
console.log(evt.propertyName + " removed node with key: " + e.oldValue.key);
}
});
});
</pre>
<p>
The above listener will put out messages as the user adds nodes (including by copying) and deletes nodes.
The <a>ChangedEvent.propertyName</a> of the Transaction event (i.e. <i>evt</i> in the code above)
will be either "CommittedTransaction", "FinishedUndo", or "FinishedRedo".
Note that a "FinishedUndo" of the removal of a node is really adding the node,
just as the undo of the insertion of a node actually removes it.
</p>
<p>
Similarly, here is an example of noticing when links are connected, reconnected, or disconnected.
This not only checks for insertions to and removals from <a>GraphLinksModel.linkDataArray</a>,
but also changes to the "from" and the "to" properties of the link data.
</p>
<pre class="lang-js">
diagram.addModelChangedListener(function(evt) {
// ignore unimportant Transaction events
if (!evt.isTransactionFinished) return;
var txn = evt.object; // a Transaction
if (txn === null) return;
// iterate over all of the actual ChangedEvents of the Transaction
txn.changes.each(function(e) {
// record node insertions and removals
if (e.change === go.ChangedEvent.Property) {
if (e.modelChange === "linkFromKey") {
console.log(evt.propertyName + " changed From key of link: " +
e.object + " from: " + e.oldValue + " to: " + e.newValue);
} else if (e.modelChange === "linkToKey") {
console.log(evt.propertyName + " changed To key of link: " +
e.object + " from: " + e.oldValue + " to: " + e.newValue);
}
} else if (e.change === go.ChangedEvent.Insert && e.modelChange === "linkDataArray") {
console.log(evt.propertyName + " added link: " + e.newValue);
} else if (e.change === go.ChangedEvent.Remove && e.modelChange === "linkDataArray") {
console.log(evt.propertyName + " removed link: " + e.oldValue);
}
});
});
</pre>
<p>
Note: the above code only works for a <a>GraphLinksModel</a>, where the link data are separate JavaScript objects.
</p>
<p>
Look at the <a href="../samples/UpdateDemo.html">Update Demo</a> for a demonstration of how you can keep
track of changes to a model when a transaction is committed or when an undo or redo is finished.
The common pattern is to iterate over the ChangedEvents of the current Transaction
in order to decide what to record in a database.
</p>
<p>
It is also possible to send incremental updates to a database using <a>Model.toIncrementalJson</a> or <a>Model.toIncrementalData</a>,
which iterate over the changes in a transaction and group them into a JSON-formatted string or an object representing any updates.
</p>
<pre class="lang-js">
diagram.addModelChangedListener(function(e) {
// ignore unimportant Transaction events
if (!evt.isTransactionFinished) return;
var json = e.model.toIncrementalJson(e);
var data = e.model.toIncrementalData(e);
... send to server/database ...
});
</pre>
<h2 id="DiagramAndGraphObjectChanges">Diagram and GraphObject changes</h2>
<p>
Diagram ChangedEvents record state changes to <a>GraphObject</a>s or <a>RowColumnDefinition</a>s in a diagram,
or to a <a>Layer</a> in a diagram, or to the <a>Diagram</a> itself.
For such events, <a>ChangedEvent.diagram</a> will be non-null.
</p>
<p>
Most ChangedEvents for diagrams record property changes, such as when some code sets the <a>TextBlock.text</a> property
or the <a>Part.location</a> property.
There are a few places which generate ChangedEvents recording insertions into or removals from collections,
such as <a>Panel.insertAt</a>.
There are never any ChangedEvents for diagrams that are <a>ChangedEvent,Transaction</a>.
</p>
<p>
Although ChangedEvents for diagrams are important for undo/redo in order to retain visual fidelity,
one normally ignores them when saving models. Only ChangedEvents for models record state changes to model data.
So for saving to a database, you will want to consider only those ChangedEvents for which <a>ChangedEvent.model</a> is non-null.
</p>
</div>
</div>
</body>
</html>
+318
View File
@@ -0,0 +1,318 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Collections -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Collections</h1>
<p>
<b>GoJS</b> provides its own collection classes: <a>List</a>, <a>Set</a>, and <a>Map</a>.
You can iterate over a collection by using an <a>Iterator</a>.
</p>
<p>
These collection classes have several advantages over using JavaScript arrays as lists or objects as maps.
They raise an error when trying to get the next item of an iterator if the collection has been modified since getting the iterator.
They can be made read-only to avoid unexpected modifications.
They offer methods not found on simple arrays or objects, such as <a>Iterator.any</a>, <a>Iterator.all</a>, and <a>Iterator.each</a>.
If you are writing in TypeScript, they optionally enforce compile-time type checking of the item types.
</p>
<p>
In <b>GoJS</b> most of the properties and methods that return collections describing the structure of the diagram return an <a>Iterator</a>.
That is because the implementation of the collections are internal -- you only need to know how to iterate over the result collection.
Other methods or properties will allow you to modify the diagram.
An example is <a>Diagram.nodes</a>, which returns the current collection of <a>Node</a>s and <a>Group</a>s in the diagram as an <a>Iterator</a>.
The collection is automatically modified as the programmer adds or removes node data in the model or by direct calls to
<a>Diagram.add</a> or <a>Diagram.remove</a>.
</p>
<p>
However there are a few properties that return collections that are allowed to be modified.
Examples include collections on classes that are usually frozen after initialization:
<a>Geometry.figures</a>, <a>PathFigure.segments</a>, and <a>Brush.colorStops</a>.
Other examples include collections that are modified very infrequently, usually only upon diagram initialization:
<a>ToolManager.mouseDownTools</a> (and the other lists of tools) and <a>Diagram.nodeTemplateMap</a>
(and other template maps).
</p>
<p>
See samples that make use of collections in the <a href="../samples/index.html#collections">samples index</a>.
</p>
<h2 id="List">List</h2>
<p>
A <a>List</a> is an ordered collection of values that are indexed by integers from zero to one less than the count.
</p>
<pre class="lang-js">
var l = new go.List();
l.add("A");
l.add("B");
l.add("C");
assert(l.count === 3);
assert(l.elt(0) === "A");
assert(l.has("B"));
assert(l.indexOf("B") === 1);
l.setElt(1, "z"); // replace an item
assert(l.elt(1) === "z");
l.removeAt(1); // remove an item
assert(l.count === 2);
assert(l.elt(1) === "C");
</pre>
<p>
In 2.0, the optional argument to the <a>List</a> constructor has been removed.
However, if you are writing in TypeScript, GoJS collections classes (<code>List</code>, <code>Map</code>, <code>Set</code>) are now generic, and will help you enforce types:
</p>
<pre class="lang-ts">
// TypeScript:
var l = new go.List&lt;string&gt;(); // Create a list of only strings
l.add("A");
l.add(23); // throws an error during compilation or in an IDE
l.add({}); // throws an error during compilation or in an IDE
</pre>
<p>
To iterate over a <a>List</a>, get its <a>List.iterator</a> and call <a>Iterator.next</a>
on it to advance its position in the list. Its <a>Iterator.value</a> will be a list item;
its <a>Iterator.key</a> will be the corresponding index in the list.
</p>
<pre class="lang-js">
var l = new go.List();
l.add("A");
l.add("B");
l.add("C");
var it = l.iterator;
while (it.next()) {
console.log(it.key + ": " + it.value);
}
// This outputs:
// 0: A
// 1: B
// 2: C
</pre>
<h2 id="Set">Set</h2>
<p>
A <a>Set</a> is an unordered collection of values that does not allow duplicate values.
This class is similar to the <code>Set</code> object that is defined in ECMAScript 2015 (ES6).
</p>
<p>
The optional argument to the <a>Set</a> constructor specifies the type of the items that may be added to the set.
</p>
<pre class="lang-js">
var s = new go.Set();
s.add("A");
s.add("B");
s.add("C");
s.add("B"); // duplicate is ignored
assert(s.count === 3);
assert(s.has("B"));
s.remove("B"); // remove an item
assert(s.count === 2);
assert(!s.has("B"));
</pre>
<p>
As with <code>List</code> and <code>Map</code>, in 2.0 the optional argument to the <a>Set</a> constructor has been removed, but it is now a generic class in TypeScript and can enforce types:
</p>
<pre class="lang-ts">
// TypeScript:
var s = new go.Set&lt;string&gt;(); // Create a set of only strings
s.add("A");
s.add(23); // throws an error during compilation or in an IDE
s.add({}); // throws an error during compilation or in an IDE
</pre>
<p>
Iterating over the items in a <a>Set</a> is just like iterating over a <a>List</a>,
except that the order of the items may vary.
</p>
<pre class="lang-js">
var s = new go.Set();
s.add("A");
s.add("B");
s.add("C");
s.add("B"); // duplicate is ignored
var it = s.iterator;
while (it.next()) {
console.log(it.value);
}
// This might output, perhaps in different order:
// A
// B
// C
</pre>
<h2 id="Map">Map</h2>
<p>
A <a>Map</a> is an unordered collection of key-value pairs that are indexed by the keys.
This class is similar to the <code>Map</code> object that is defined in ECMAScript 2015 (ES6).
</p>
<p>
The two optional arguments to the <a>Map</a> constructor specifies the types of the keys and the types of the item values that may be added to the map.
</p>
<pre class="lang-js">
var m = new go.Map();
m.add("A", 1); // associate "A" with 1
m.add("B", 2);
m.add("C", 3);
assert(s.count === 3);
assert(s.has("B"));
assert(s.get("B") === 2);
m.add("B", 222); // replace the value for "B"
assert(s.get("B") === 222);
s.remove("B"); // remove an item
assert(s.count === 2);
assert(!s.has("B"));
assert(s.get("B") === null);
</pre>
<p>
As with <code>List</code> and <code>Set</code>, in 2.0 the optional arguments to the <a>Map</a> constructor have been removed, but it is now a generic class in TypeScript and can enforce types:
</p>
<pre class="lang-ts">
// TypeScript:
var m = new go.Map&lt;string, number&gt;(); // Create a map of strings to numbers
m.add("A", 1);
m.add(23, 23); // throws an error during compilation or in an IDE
m.add({}, 23); // throws an error during compilation or in an IDE
</pre>
<p>
Iterating over the items in a <a>Map</a> is just like iterating over a <a>List</a>,
but offering access to both the keys and the values.
As with <a>Set</a>s the order of the items may vary.
</p>
<pre class="lang-js">
var m = new go.Map();
m.add("A", 1); // associate "A" with 1
m.add("B", 2);
m.add("C", 3);
m.add("B", 222); // replace the value for "B"
// Normal iteration lets you get both the key and its corresponding value:
var it = m.iterator;
while (it.next()) {
console.log(it.key + ": " + it.value);
}
// This might output, perhaps in different order:
// A: 1
// B: 222
// C: 3
// To get a collection of the keys, use Map.iteratorKeys:
var kit = m.iteratorKeys;
while (kit.next()) {
console.log(kit.value);
}
// This might output, perhaps in different order:
// A
// B
// C
// To get a collection of the values, use Map.iteratorValues:
var vit = m.iteratorValues;
while (vit.next()) {
console.log(vit.value);
}
// This might output, perhaps in different order:
// 1
// 222
// 3
</pre>
<p>
Typically one uses <a>Map.iteratorKeys</a> or <a>Map.iteratorValues</a>
when needing to pass a collection on to other methods that take an <a>Iterator</a>.
</p>
<h2 id="MoreIterationExamples">More Iteration Examples</h2>
<p>
It is commonplace to iterate over the selected <a>Part</a>s of a <a>Diagram</a>:
<pre class="lang-js">
for (var it = diagram.selection.iterator; it.next(); ) {
var part = it.value; // part is now a Node or a Group or a Link or maybe a simple Part
if (part instanceof go.Node) { . . . }
else if (part instanceof go.Link) { . . . }
}
</pre>
Alternatively:
<pre class="lang-js">
diagram.selection.each(function(part) {
// part is now a Node or a Group or a Link or maybe a simple Part
if (part instanceof go.Node) { . . . }
else if (part instanceof go.Link) { . . . }
});
</pre>
</p>
<p>
Sometimes one needs to iterate over the <a>Node</a>s in a <a>Diagram</a>:
<pre class="lang-js">
for (var it = diagram.nodes; it.next(); ) {
var n = it.value; // n is now a Node or a Group
if (n.category === "Special") { . . . }
}
</pre>
</p>
<p>
You can also iterate over the port elements in a <a>Node</a>, or the <a>Link</a>s connected to a port element:
<pre class="lang-js">
for (var pit = node.ports; pit.next(); ) {
var port = pit.value; // port is now a GraphObject within the node
for (var lit = node.findLinksConnected(port.portId); lit.next(); ) {
var link = lit.value; // link is now a Link connected with the port
if (link.data.xyz === 17) { . . . }
}
}
</pre>
</p>
<p>
Or perhaps you need to iterate over the elements of a <a>Panel</a>:
<pre class="lang-js">
for (var it = panel.elements; it.next(); ) {
var elt = it.value; // elt is now a GraphObject that is an immediate child of the Panel
if (elt instanceof go.TextBlock) { . . . }
else if (elt instanceof go.Panel) { . . . recurse . . . }
}
</pre>
</p>
<p>
If you want to find <a>Node</a>s that are immediate members of a <a>Group</a>:
<pre class="lang-js">
for (var mit = group.memberParts; mit.next(); ) {
var part = mit.value; // part is now a Part within the Group
if (part instanceof go.Node) { . . . maybe work with part.data . . . }
}
</pre>
</p>
</div>
</div>
</body>
</html>
+254
View File
@@ -0,0 +1,254 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Commands -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Commands</h1>
<p>
Commands such as <b>Delete</b> or <b>Paste</b> or <b>Undo</b> are implemented by the <a>CommandHandler</a> class.
</p>
<p>
Keyboard events, like mouse and touch events, always go to the <a>Diagram.currentTool</a>.
The current tool, when the user is not performing some gesture, is the same as the <a>Diagram.defaultTool</a>,
which normally is the <a>Diagram.toolManager</a>.
The <a>ToolManager</a> handles keyboard events by delegating them to the <a>Diagram.commandHandler</a>.
</p>
<p>
Basically, the diagram handles a keyboard event, creates an <a>InputEvent</a> describing it,
and then calls <a>ToolManager.doKeyDown</a>. That in turn just calls <a>CommandHandler.doKeyDown</a>.
The same sequence happens for key-up events.
</p>
<p>
Please note that the handling of keyboard commands depends on the diagram getting focus and then getting keyboard events.
Do not apply any styling such as <pre class="lang-css">canvas:focus { display: none; }</pre>.
</p>
<h2 id="KeyboardCommandBindings">Keyboard command bindings</h2>
<p>
The <a>CommandHandler</a> implements the following command bindings for keyboard input:
</p>
<ul>
<li>Del &amp; Backspace invoke <a>CommandHandler.deleteSelection</a></li>
<li>Ctrl-X &amp; Shift-Del invoke <a>CommandHandler.cutSelection</a></li>
<li>Ctrl-C &amp; Ctrl-Insert invoke <a>CommandHandler.copySelection</a></li>
<li>Ctrl-V &amp; Shift-Insert invoke <a>CommandHandler.pasteSelection</a></li>
<li>Ctrl-A invokes <a>CommandHandler.selectAll</a></li>
<li>Ctrl-Z &amp; Alt-Backspace invoke <a>CommandHandler.undo</a></li>
<li>Ctrl-Y &amp; Alt-Shift-Backspace invoke <a>CommandHandler.redo</a></li>
<li>Up &amp; Down &amp; Left &amp; Right (arrow keys) call <a>Diagram.scroll</a></li>
<li>PageUp &amp; PageDown call <a>Diagram.scroll</a></li>
<li>Home &amp; End call <a>Diagram.scroll</a></li>
<li>Space invokes <a>CommandHandler.scrollToPart</a></li>
<li>Keypad-- (minus) invokes <a>CommandHandler.decreaseZoom</a></li>
<li>Keypad-+ (plus) invokes <a>CommandHandler.increaseZoom</a></li>
<li>Ctrl-0 invokes <a>CommandHandler.resetZoom</a></li>
<li>Shift-Z invokes <a>CommandHandler.zoomToFit</a>; repeat to return to the original scale and position</li>
<li>Ctrl-G invokes <a>CommandHandler.groupSelection</a></li>
<li>Ctrl-Shift-G invokes <a>CommandHandler.ungroupSelection</a></li>
<li>F2 invokes <a>CommandHandler.editTextBlock</a></li>
<li>Menu Key invokes <a>CommandHandler.showContextMenu</a></li>
<li>Esc invokes <a>CommandHandler.stopCommand</a></li>
</ul>
<p>
On a Mac the Command key is used as the modifier instead of the Control key.
</p>
<p>
At the current time there are no keyboard bindings for commands such as <a>CommandHandler.collapseSubGraph</a>,
<a>CommandHandler.collapseTree</a>, <a>CommandHandler.expandSubGraph</a>, or <a>CommandHandler.expandTree</a>.
</p>
<p>
If you want to have a different behavior for the arrow keys, consider using the sample class extended from <a>CommandHandler</a>:
<a href="../extensions/DrawCommandHandler.js">DrawCommandHandler</a>, which implements options for having
the arrow keys move the selection or change the selection.
</p>
<p>
That DrawCommandHandler extension also demonstrates a customization of the <b>Copy</b> and <b>Paste</b> commands
to automatically shift the location of pasted copies.
</p>
<h2 id="CommandHandler">CommandHandler</h2>
<p>
The <a>CommandHandler</a> class implements pairs of methods:
a method to execute a command and a predicate that is true when the command may be executed.
For example, for the <b>Copy</b> command, there is a <a>CommandHandler.copySelection</a> method
and a <a>CommandHandler.canCopySelection</a> method.
</p>
<p>
Keyboard event handling always calls the "can..." predicate first.
Only if that returns true does it actually call the method to execute the command.
</p>
<p>
There are a number of properties that you can set to affect the CommandHandler's standard behavior.
For example, if you want to allow the user to group selected parts together with the <a>CommandHandler.groupSelection</a>,
you will need to set <a>CommandHandler.archetypeGroupData</a> to a group node data object:
</p>
<pre class="lang-js">
diagram.commandHandler.archetypeGroupData =
{ key: "Group", isGroup: true, color: "blue" };
</pre>
<p>
That data object is copied and added to the model as the new group data object by <a>CommandHandler.groupSelection</a>.
</p>
<p>
If you want to add your own keyboard bindings, you can override the <a>CommandHandler.doKeyDown</a> method.
For example, to support using the "T" key to collapse or expand the currently selected <a>Group</a>:
</p>
<pre class="lang-js">
myDiagram.commandHandler.doKeyDown = function() {
var e = myDiagram.lastInput;
var cmd = myDiagram.commandHandler;
if (e.key === "T") { // could also check for e.control or e.shift
if (cmd.canCollapseSubGraph()) {
cmd.collapseSubGraph();
} else if (cmd.canExpandSubGraph()) {
cmd.expandSubGraph();
}
} else {
// call base method with no arguments
go.CommandHandler.prototype.doKeyDown.call(cmd);
}
};
</pre>
<p>
Do not forget to call the base method in order to handle all of the keys that your method does not handle.
</p>
<p class="box bg-info">
Note that calling the base method involves getting the base class's prototype's method.
If the base method takes arguments, be sure to pass arguments to the call to the base method.
</p>
<h2 id="UpdatingCommandUI">Updating command UI</h2>
<p>
It is common to have HTML elements outside of the diagram that invoke commands.
You can use the <a>CommandHandler</a>'s "can..." predicates to enable or disable UI that would invoke the command.
</p>
<pre class="lang-js" id="commands">
// allow the group command to execute
diagram.commandHandler.archetypeGroupData =
{ key: "Group", isGroup: true, color: "blue" };
// modify the default group template to allow ungrouping
diagram.groupTemplate.ungroupable = true;
var nodeDataArray = [
{ key: "Alpha" },
{ key: "Beta" },
{ key: "Delta", group: "Epsilon" },
{ key: "Gamma", group: "Epsilon" },
{ key: "Epsilon", isGroup: true }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" },
{ from: "Beta", to: "Beta" },
{ from: "Gamma", to: "Delta" },
{ from: "Delta", to: "Alpha" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
// enable or disable a particular button
function enable(name, ok) {
var button = document.getElementById(name);
if (button) button.disabled = !ok;
}
// enable or disable all command buttons
function enableAll() {
var cmdhnd = diagram.commandHandler;
enable("SelectAll", cmdhnd.canSelectAll());
enable("Cut", cmdhnd.canCutSelection());
enable("Copy", cmdhnd.canCopySelection());
enable("Paste", cmdhnd.canPasteSelection());
enable("Delete", cmdhnd.canDeleteSelection());
enable("Group", cmdhnd.canGroupSelection());
enable("Ungroup", cmdhnd.canUngroupSelection());
enable("Undo", cmdhnd.canUndo());
enable("Redo", cmdhnd.canRedo());
}
// notice whenever the selection may have changed
diagram.addDiagramListener("ChangedSelection", function(e) {
enableAll();
});
// notice when the Paste command may need to be reenabled
diagram.addDiagramListener("ClipboardChanged", function(e) {
enableAll();
});
// notice whenever a transaction or undo/redo has occurred
diagram.addModelChangedListener(function(e) {
if (e.isTransactionFinished) enableAll();
});
// perform initial enablements after everything has settled down
setTimeout(enableAll, 1);
myDiagram = diagram; // make the diagram accessible to button onclick handlers
</pre>
<script>goCode("commands", 600, 150)</script>
<input id="SelectAll" type="button" onclick="myDiagram.commandHandler.selectAll()" value="Select All" />
<input id="Cut" type="button" onclick="myDiagram.commandHandler.cutSelection()" value="Cut" />
<input id="Copy" type="button" onclick="myDiagram.commandHandler.copySelection()" value="Copy" />
<input id="Paste" type="button" onclick="myDiagram.commandHandler.pasteSelection()" value="Paste" />
<input id="Delete" type="button" onclick="myDiagram.commandHandler.deleteSelection()" value="Delete" />
<input id="Group" type="button" onclick="myDiagram.commandHandler.groupSelection()" value="Group" />
<input id="Ungroup" type="button" onclick="myDiagram.commandHandler.ungroupSelection()" value="Ungroup" />
<input id="Undo" type="button" onclick="myDiagram.commandHandler.undo()" value="Undo" />
<input id="Redo" type="button" onclick="myDiagram.commandHandler.redo()" value="Redo" />
<script>
// once the buttons are defined and have IDs, we can update them all
myDiagram.undoManager.isEnabled = true; // calls enableAll() due to Model Changed listener
</script>
<p>
Each button is implemented in the following fashion:
</p>
<pre class="lang-html">
&lt;input id="SelectAll" type="button"
onclick="myDiagram.commandHandler.selectAll()" value="Select All" /&gt;
</pre>
<p>
Whenever the selection changes or whenever a transaction or undo or redo occurs,
the enableAll function is called to update the "disabled" property of each of the buttons.
</p>
<h2 id="Accessibility">Accessibility</h2>
<p>
Since <b>GoJS</b> is based on the HTML Canvas element,
making an app that is accessible to screen-readers or other accessibility devices
is a matter of generating fallback content outside of GoJS,
just as you would generate fallback content separate from any HTML Canvas application.
</p>
<p>
Although much of the predefined functionality of the <a>CommandHandler</a> is accessible
with keyboard commands or the default context menu, not all of it is,
and the functionality of the <a>Tool</a>s mostly depends on mouse or touch events.
We recommend that you implement alternative mechanisms specific to your application
for those tools that you want your users to access without a pointing device.
</p>
<h2 id="MoreCommandHandlerOverrideExamples">More CommandHandler override examples</h2>
<p>
Stop CTRL+Z/CTRL+Y from doing an undo/redo, but still allow <a>CommandHandler.undo</a> and <a>CommandHandler.redo</a> to be called programatically:
</p>
<pre class="lang-js">
myDiagram.commandHandler.doKeyDown = function() {
var e = myDiagram.lastInput;
// The meta (Command) key substitutes for "control" for Mac commands
var control = e.control || e.meta;
var key = e.key;
// Quit on any undo/redo key combination:
if (control && (key === 'Z' || key === 'Y')) return;
// call base method with no arguments (default functionality)
go.CommandHandler.prototype.doKeyDown.call(this);
};
</pre>
</div>
</div>
</body>
</html>
+409
View File
@@ -0,0 +1,409 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Link Connection Points on Nodes -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="../extensions/Figures.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Link Connection Points on Nodes</h1>
<p>
There is flexibility in controlling exactly how and where a link connects to a node.
In the previous examples the link has always ended at the edge of the node.
But you can specify the <a>Spot</a> on a node at which a link terminates.
</p>
<h2 id="NonRectangularNodes">Non-rectangular Nodes</h2>
<p>
When a <a>Node</a> does not have a rectangular shape, by default links will end
where the line toward the center of the node intersects with the edge of the node.
</p>
<p>
Here is a demonstration of that -- drag one of the nodes around and watch how the link always
connects to the nearest intersection or to the center of the node.
This example includes arrowheads at both ends of the link, to make it clear that the link route
really ends right at the edge of the node.
</p>
<pre class="lang-js" id="nonRectangular">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ width: 90, height: 90,
selectionAdorned: false },
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "FivePointedStar", { fill: "lightgray" }),
$(go.TextBlock,
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
$(go.Shape),
$(go.Shape, // the "from" end arrowhead
{ fromArrow: "Chevron" }),
$(go.Shape, // the "to" end arrowhead
{ toArrow: "StretchedDiamond", fill: "red" })
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "100 50" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("nonRectangular", 600, 150)</script>
<h2 id="ToSpotAndFromSpot">ToSpot and FromSpot</h2>
<p>
You can easily require links to end at a particular point within the bounds of the node,
rather than at the nearest edge intersection.
Set the <a>GraphObject.toSpot</a> to a <a>Spot</a> value other than <a>Spot,None</a>
to cause links coming into the node to end at that spot within the node, with a direction
that is appropriate for the side that the spot is at.
Similarly, set the <a>GraphObject.fromSpot</a> for the ends of links coming out of the node.
</p>
<p>
The following examples all display the same graph but use different templates
to demonstrate how links can connect to nodes.
They all call this common function to define some nodes and links.
</p>
<pre class="lang-js" id="makeGraph">
function makeGraph(diagram) {
var $ = go.GraphObject.make;
diagram.layout =
$(go.LayeredDigraphLayout, // this will be discussed in a later section
{ columnSpacing: 5,
setsPortSpots: false });
var nodeDataArray = [
{ key: "Alpha" }, { key: "Beta" }, { key: "Gamma" }, { key: "Delta" },
{ key: "Epsilon" }, { key: "Zeta" }, { key: "Eta" }, { key: "Theta" }
];
var linkDataArray = [
{ from: "Beta", to: "Alpha" },
{ from: "Gamma", to: "Alpha" },
{ from: "Delta", to: "Alpha" },
{ from: "Alpha", to: "Epsilon" },
{ from: "Alpha", to: "Zeta" },
{ from: "Alpha", to: "Eta" },
{ from: "Alpha", to: "Theta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
}
</pre>
<script>eval(document.getElementById("makeGraph").textContent)</script>
<p>
Let us specify that links coming into a node connect at the middle of the left side, and that links going out
of a node connect at the middle of the right side. Such a convention is appropriate for diagrams that have
a general sense of direction to them, such as the following one which goes from left to right.
</p>
<pre class="lang-js" id="leftright">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ fromSpot: go.Spot.Right, // coming out from middle-right
toSpot: go.Spot.Left }, // going into at middle-left
$(go.Shape, "Rectangle", { fill: "lightgray" }),
$(go.TextBlock,
{ margin: 5},
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
$(go.Shape),
$(go.Shape, { toArrow: "Standard" })
);
makeGraph(diagram);
</pre>
<script>goCode("leftright", 600, 150)</script>
<p>
You can also specify that the links go into a node not at a single spot but spread out along one side.
Instead of <a>Spot,Right</a> use <a>Spot,RightSide</a>, and similarly for the left side.
</p>
<pre class="lang-js" id="leftrightSides">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ fromSpot: go.Spot.RightSide, // coming out from right side
toSpot: go.Spot.LeftSide }, // going into at left side
$(go.Shape, "Rectangle", { fill: "lightgray" }),
$(go.TextBlock,
{ margin: 5},
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
$(go.Shape),
$(go.Shape, { toArrow: "Standard" })
);
makeGraph(diagram);
</pre>
<script>goCode("leftrightSides", 600, 150)</script>
<p>
Of course this only looks good when the nodes are basically rectangular.
</p>
<p>
You can use a different kind of <a>Link.routing</a>:
</p>
<pre class="lang-js" id="leftrightSidesOrthogonal">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ fromSpot: go.Spot.RightSide, // coming out from right side
toSpot: go.Spot.LeftSide }, // going into at left side
$(go.Shape, "Rectangle", { fill: "lightgray" }),
$(go.TextBlock,
{ margin: 5},
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
{ routing: go.Link.Orthogonal, // Orthogonal routing
corner: 10 }, // with rounded corners
$(go.Shape),
$(go.Shape, { toArrow: "Standard" })
);
makeGraph(diagram);
</pre>
<script>goCode("leftrightSidesOrthogonal", 600, 150)</script>
<p>
Or you can use a different kind of <a>Link.curve</a>:
</p>
<pre class="lang-js" id="leftrightSidesBezier">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ fromSpot: go.Spot.RightSide, // coming out from right side
toSpot: go.Spot.LeftSide }, // going into at left side
$(go.Shape, "Rectangle", { fill: "lightgray" }),
$(go.TextBlock,
{ margin: 5},
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
{ curve: go.Link.Bezier }, // Bezier curve
$(go.Shape),
$(go.Shape, { toArrow: "Standard" })
);
makeGraph(diagram);
</pre>
<script> goCode("leftrightSidesBezier", 600, 150)</script>
<p>
But you need to be careful to specify sensible spots for how the graph is arranged.
</p>
<pre class="lang-js" id="leftrightSidesBad">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ fromSpot: go.Spot.TopSide, // coming out from top side -- BAD!
toSpot: go.Spot.RightSide }, // going into at right side -- BAD!
$(go.Shape, "Rectangle", { fill: "lightgray" }),
$(go.TextBlock,
{ margin: 5},
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
$(go.Shape),
$(go.Shape, { toArrow: "Standard" })
);
makeGraph(diagram);
diagram.add($(go.Part, // this is just a comment
{ location: new go.Point(300, 50) },
$(go.TextBlock, "Bad Spots",
{ font: "16pt bold", stroke: "red" })
));
</pre>
<script>goCode("leftrightSidesBad", 600, 150)</script>
<h3 id="UndirectedSpots">Undirected Spots</h3>
<p>
When no spot is specified for the <a>GraphObject.fromSpot</a> or <a>GraphObject.toSpot</a>,
the route computation will compute the furthest point on the route of the link from the center of the port to the other port
that is an intersection of an edge of the port.
This was demonstrated above in <a href="#NonRectangularNodes">Non-rectangular Nodes</a> and is again demonstrated here.
</p>
<pre class="lang-js" id="noSpotFocus0">
diagram.nodeTemplate =
$(go.Node, "Vertical",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "YinYang",
{
fill: "white", portId: ""
},
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 8 },
new go.Binding("text"))
);
diagram.model = new go.GraphLinksModel(
[
{ key: 1, text: "Alpha", color: "lightblue", loc: "0 50" },
{ key: 2, text: "Beta", color: "orange", loc: "150 0" },
{ key: 3, text: "Gamma", color: "lightgreen", loc: "300 50" }
],
[
{ from: 1, to: 2 },
{ from: 2, to: 3 }
]);
</pre>
<script>goCode("noSpotFocus0", 600, 250)</script>
<p>
However it is possible to specify a focus point that is different from the center of the port.
Use a <a>Spot</a> value that has <a>Spot.x</a> and <a>Spot.y</a> equal to 0.5 but with <a>Spot.offsetX</a> and <a>Spot.offsetY</a>
values that specify where you want links to focus towards, relative to the center of the port.
</p>
<pre class="lang-js" id="noSpotFocus1">
diagram.nodeTemplate =
$(go.Node, "Vertical",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "YinYang",
{
fill: "white", portId: "",
fromSpot: new go.Spot(0.5, 0.5, 0, -25), toSpot: new go.Spot(0.5, 0.5, 0, 25)
},
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 8 },
new go.Binding("text"))
);
diagram.model = new go.GraphLinksModel(
[
{ key: 1, text: "Alpha", color: "lightblue", loc: "0 50" },
{ key: 2, text: "Beta", color: "orange", loc: "150 0" },
{ key: 3, text: "Gamma", color: "lightgreen", loc: "300 50" }
],
[
{ from: 1, to: 2 },
{ from: 2, to: 3 }
]);
</pre>
<script>goCode("noSpotFocus1", 600, 250)</script>
<p>
In this example, links always appear to be coming from the hole near the top of the "YinYang" figure
towards the dot near the bottom of the figure.
Try moving the nodes to see this behavior.
Note that the <a>Spot.x</a> and <a>Spot.y</a> values are both 0.5, with fixed offsets from the center of the port.
</p>
<p>
It is also possible to have links go directly to particular spots within a port.
Use regular <a>Spot</a> values, but set the Link's end segment length to zero,
<a>Link.fromEndSegmentLength</a> or <a>Link.toEndSegmentLength</a>.
</p>
<pre class="lang-js" id="noSpotFocus2">
diagram.nodeTemplate =
$(go.Node, "Vertical",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "YinYang",
{
fill: "white", portId: "",
fromSpot: new go.Spot(0.5, 0.25), toSpot: new go.Spot(0.5, 0.75),
fromEndSegmentLength: 0, toEndSegmentLength: 0
},
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 8 },
new go.Binding("text"))
);
diagram.model = new go.GraphLinksModel(
[
{ key: 1, text: "Alpha", color: "lightblue", loc: "0 50" },
{ key: 2, text: "Beta", color: "orange", loc: "150 0" },
{ key: 3, text: "Gamma", color: "lightgreen", loc: "300 50" }
],
[
{ from: 1, to: 2 },
{ from: 2, to: 3 }
]);
</pre>
<script>goCode("noSpotFocus2", 600, 250)</script>
<p>
Again, links always appear to be coming from the hole near the top of the "YinYang" figure
towards the dot near the bottom of the figure, but now they go all the way rather than stop at the edge.
Note that the <a>Spot.x</a> and <a>Spot.y</a> values are <i>not</i> both 0.5,
and that the Link end segment lengths are zero.
</p>
<h2 id="SpotsForIndividualLinks">Spots for Individual Links</h2>
<p>
Setting the <a>GraphObject.fromSpot</a> and <a>GraphObject.toSpot</a> properties specifies
the default link connection point for all links connected to the node.
What if you want some links to go to the middle-top spot but some other links to go to the middle-left spot of the same node?
You can achieve this by setting the <a>Link.fromSpot</a> and <a>Link.toSpot</a> properties,
which take precedence over the correspondingly named properties of what the link connects with.
</p>
<pre class="lang-js" id="customSpots">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "Rectangle", { fill: "lightgray" }),
$(go.TextBlock,
{ margin: 5},
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
// get the link spots from the link data
new go.Binding("fromSpot", "fromSpot", go.Spot.parse),
new go.Binding("toSpot", "toSpot", go.Spot.parse),
$(go.Shape),
$(go.Shape, { toArrow: "Standard" })
);
var nodeDataArray = [
{ key: "Alpha" }, { key: "Beta" }, { key: "Gamma" }, { key: "Delta" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta", fromSpot: "TopRight", toSpot: "Left" },
{ from: "Alpha", to: "Gamma", fromSpot: "Left", toSpot: "Left" },
{ from: "Alpha", to: "Delta", fromSpot: "None", toSpot: "Top" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("customSpots", 600, 150)</script>
<h3 id="SomeLayoutsSetLinkSpots">Some Layouts set Link Spots</h3>
<p>
Some of the predefined <a>Layout</a>s automatically set <a>Link.fromSpot</a> and <a>Link.toSpot</a>
when the nature of the layout implies a natural direction.
So, for example, a <a>TreeLayout</a> with a <a>TreeLayout.angle</a> <code>== 90</code> will set each Link's
fromSpot to be <a>Spot,Bottom</a> and each Link's toSpot to be <a>Spot,Top</a>.
</p>
<p>
You can disable the setting of Link spots for TreeLayout by setting <a>TreeLayout.setsPortSpot</a> and/or <a>TreeLayout.setsChildPortSpot</a> to false.
For LayeredDigraphLayout, set <a>LayeredDigraphLayout.setsPortSpots</a> to false.
For ForceDirectedLayout, set <a>ForceDirectedLayout.setsPortSpots</a> to false, although this is rarely needed.
</p>
</div>
</div>
</body>
</html>
+248
View File
@@ -0,0 +1,248 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Context Menus -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Context Menus</h1>
<p>
<b>GoJS</b> provides a mechanism for you to define context menus for any object or for the diagram background.
</p>
<p class="box" style="background-color: lightgoldenrodyellow;">
Note: GoJS context menus cannot render outside of Diagrams, because they are objects inside the Diagram and therefore drawn only on the Diagram.
If you need a context menu drawn partially or fully outside the Diagram, consider making an <a href="#HTMLContextMenus">HTML context menu</a>.
</p>
<p>
A GoJS context menu is an <a>Adornment</a> that is shown when the user context-clicks (right mouse click or long touch hold)
an object that has its <a>GraphObject.contextMenu</a> set.
The context menu is bound to the same data as the part itself.
</p>
<p>
See samples that make use of context menus in the <a href="../samples/index.html#contextmenus">samples index</a>.
</p>
<p>
It is typical to implement a context menu as a "ContextMenu" Panel containing "ContextMenuButton"s,
as you can see in the code below in the assignment of the Node's <a>GraphObject.contextMenu</a> and <a>Diagram.contextMenu</a> properties.
Each "ContextMenu" is just a "Vertical" Panel <a>Adornment</a> that is shadowed.
Each "ContextMenuButton" is a Panel on which you can set the <a>GraphObject.click</a> event handler.
In the event handler <code>obj.part</code> will be the whole context menu Adornment.
<code>obj.part.adornedPart</code> will be adorned Node or Link.
The bound data is <code>obj.part.data</code>, which will be the same as <code>obj.part.adornedPart.data</code>.
</p>
<p>
You can see how the "ContextMenu" and "ContextMenuButton" builders are defined at
<a href="../extensions/Buttons.js">Buttons.js</a>.
</p>
<p>
In this example each <a>Node</a> has its <a>GraphObject.contextMenu</a> property set to an Adornment that shows
a single button that when clicked changes the color property of the bound model data.
The diagram gets its own context menu by setting <a>Diagram.contextMenu</a>.
</p>
<pre class="lang-js" id="contextmenus">
// This method is called as a context menu button's click handler.
// Rotate the selected node's color through a predefined sequence of colors.
function changeColor(e, obj) {
diagram.commit(function(d) {
// get the context menu that holds the button that was clicked
var contextmenu = obj.part;
// get the node data to which the Node is data bound
var nodedata = contextmenu.data;
// compute the next color for the node
var newcolor = "lightblue";
switch (nodedata.color) {
case "lightblue": newcolor = "lightgreen"; break;
case "lightgreen": newcolor = "lightyellow"; break;
case "lightyellow": newcolor = "orange"; break;
case "orange": newcolor = "lightblue"; break;
}
// modify the node data
// this evaluates data Bindings and records changes in the UndoManager
d.model.set(nodedata, "color", newcolor);
}, "changed color");
}
// this is a normal Node template that also has a contextMenu defined for it
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle",
{ fill: "white" },
new go.Binding("fill", "color")),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key")),
{
contextMenu: // define a context menu for each node
$("ContextMenu", // that has one button
$("ContextMenuButton",
$(go.TextBlock, "Change Color"),
{ click: changeColor })
// more ContextMenuButtons would go here
) // end Adornment
}
);
// also define a context menu for the diagram's background
diagram.contextMenu =
$("ContextMenu",
$("ContextMenuButton",
$(go.TextBlock, "Undo"),
{ click: function(e, obj) { e.diagram.commandHandler.undo(); } },
new go.Binding("visible", "", function(o) {
return o.diagram.commandHandler.canUndo();
}).ofObject()),
$("ContextMenuButton",
$(go.TextBlock, "Redo"),
{ click: function(e, obj) { e.diagram.commandHandler.redo(); } },
new go.Binding("visible", "", function(o) {
return o.diagram.commandHandler.canRedo();
}).ofObject()),
// no binding, always visible button:
$("ContextMenuButton",
$(go.TextBlock, "New Node"),
{ click: function(e, obj) {
e.diagram.commit(function(d) {
var data = {};
d.model.addNodeData(data);
part = d.findPartForData(data); // must be same data reference, not a new {}
// set location to saved mouseDownPoint in ContextMenuTool
part.location = d.toolManager.contextMenuTool.mouseDownPoint;
}, 'new node');
} })
);
var nodeDataArray = [
{ key: "Alpha", color: "lightyellow" },
{ key: "Beta", color: "orange" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
diagram.undoManager.isEnabled = true;
</pre>
<script>goCode("contextmenus", 350, 200)</script>
<p>
Try context clicking a node and invoking the "Change Color" command a few times.
With the diagram context menu you will be able to "Undo" and/or "Redo", or you can use Control-Z and/or Control-Y.
</p>
<h2 id="Positioning">Positioning</h2>
<p>
There are two ways to customize the positioning of the context menu relative to the adorned GraphObject.
One way is to override <a>ContextMenuTool.positionContextMenu</a>.
Another way is to have the context menu <a>Adornment</a> include a <a>Placeholder</a>.
The Placeholder is positioned to have the same size and position as the adorned object.
The context menu will not to have a background, and thus will not display a shadow by default when using a Placeholder.
</p>
<pre class="lang-js" id="contextmenusplaceholder">
// this is a shared context menu button click event handler, just for demonstration
function cmCommand(e, obj) {
var node = obj.part.adornedPart; // the Node with the context menu
var buttontext = obj.elt(1); // the TextBlock
alert(buttontext.text + " command on " + node.data.key);
}
// this is a normal Node template that also has a contextMenu defined for it
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle",
{ fill: "white" },
new go.Binding("fill", "color")),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key")),
{
contextMenu: // define a context menu for each node
$("ContextMenu", "Spot", // that has several buttons around
$(go.Placeholder, { padding: 5 }), // a Placeholder object
$("ContextMenuButton", $(go.TextBlock, "Top"),
{ alignment: go.Spot.Top, alignmentFocus: go.Spot.Bottom, click: cmCommand }),
$("ContextMenuButton", $(go.TextBlock, "Right"),
{ alignment: go.Spot.Right, alignmentFocus: go.Spot.Left, click: cmCommand }),
$("ContextMenuButton", $(go.TextBlock, "Bottom"),
{ alignment: go.Spot.Bottom, alignmentFocus: go.Spot.Top, click: cmCommand }),
$("ContextMenuButton", $(go.TextBlock, "Left"),
{ alignment: go.Spot.Left, alignmentFocus: go.Spot.Right, click: cmCommand })
) // end Adornment
}
);
var nodeDataArray = [
{ key: "Alpha", color: "lightyellow" },
{ key: "Beta", color: "orange" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("contextmenusplaceholder", 350, 200)</script>
<h2 id="HTMLContextMenus">HTML Context Menus</h2>
<p>
It is possible to define custom context menus using HTML instead of Adornments using the <a>HTMLInfo</a> class.
The <a href="../samples/customContextMenu.html">Custom Context Menu sample</a> and
<a href="../samples/htmlLightBoxContextMenu.html">Lightbox Context Menu sample</a> show two such custom context menus.
</p>
<p>
HTML context menus require more effort to implement than using the default <b>GoJS</b> "ContextMenu" and "ContextMenuButton".
However you would have the full power of HTML/CSS/JavaScript to show whatever you want.
This includes creating context menus that can exist or float outside of the Diagram.
</p>
<p>
There are two primary considerations when authoring HTML and CSS for context menus.
The context menu should usually be a sibling Element of the Diagram, and should never be nested inside a Diagram DIV:
</p>
<pre class="lang-js">
&lt;div style="position: relative;"&gt;
&lt;div id="myDiagramDiv" style="border: solid 1px black; width:400px; height:400px;"&gt;&lt;/div&gt;
&lt;div id="contextMenu"&gt;
&lt;!-- ... context menu HTML --&gt;
&lt;/div&gt;
&lt;/div&gt;
</pre>
<p>
And the ContextMenu may need a z-index set to ensure it is always on top. GoJS Diagrams have z-index of 2, and some tools a z-index of 100.
</p>
<pre class="lang-css">#contextMenu {
z-index: 1000;
...
}
</pre>
<p>
See the <a href="../samples/customContextMenu.html">Custom Context Menu sample</a> and
<a href="../samples/htmlLightBoxContextMenu.html">Lightbox Context Menu sample</a> for HTML examples.
See the <a href="HTMLInteraction.html">HTMLInteraction</a> page for more discussion on HTML in GoJS.
</p>
<h2 id="DefaultContextMenuForTouchEnabledDevices">Default Context Menu for Touch-enabled devices</h2>
<p>
Touch devices are presumed to have no keyboard ability, which makes actions like copying and pasting more difficult.
Because of this, <b>GoJS</b> provides a built-in default context menu on touch devices, implemented in HTML.
The buttons on this menu are populated dynamically, depending on the target GraphObject (if any) and Diagram and their properties.
</p>
<p>
The default context menu can be disabled by setting <a>ContextMenuTool.defaultTouchContextMenu</a> to null.
The <a href="../samples/htmlLightBoxContextMenu.html">Lightbox Context Menu sample</a> contains a re-implementation of this menu if you wish to modify it.
</p>
<p>
If you define your own custom context menus, they will prevent the default context menu from appearing on touch devices.
We recommend that your custom context menus include all common commands appropriate for your app.
</p>
</div>
</div>
</body>
</html>
+796
View File
@@ -0,0 +1,796 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Data Binding -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Data Binding</h1>
<p>
Data binding is a way to extract a value from a source object and set a property on a target object.
The target objects are normally <a>GraphObject</a>s;
the source objects are usually JavaScript data objects held in a model.
</p>
<p>
You could write code that gets a desired value from the model data,
searches the <a>Diagram</a> for the appropriate <a>Part</a>, searches for the target <a>GraphObject</a>
within the visual tree of that Part, and then sets one or more properties on that GraphObject with that value,
perhaps after modifying or converting the original value in a way appropriate for the individual properties.
However data binding offers a declarative way to specify such behavior just by supplying a
<a>Binding</a> that names the properties on the source object and on the target object.
</p>
<p>
Trying to bind a non-existent property of a <a>GraphObject</a> will probably result in a warning or error
that you can see in the console log. Always check the console log for any kinds of potential exceptions that
are normally suppressed by the binding system.
</p>
<p>
Data bindings are used to keep <a>GraphObject</a> properties in sync with their <a>Part</a>'s data's properties.
They are not used to establish or maintain relationships between Parts. Each kind of <a>Model</a> has its
own methods for declaring the relationships between parts.
</p>
<h2 id="RelationshipsOfPartsAndDataAndBinding">The Relationships of Parts and Data and Binding</h2>
<p>
First, look at a diagram that includes comments about the GraphObjects used to build some example nodes and links:
</p>
<pre class="lang-js" id="commented" style="display:none">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ scale : 1.6, isShadowed: true },
new go.Binding("location", "pos", go.Point.parse),
{ locationSpot: go.Spot.Center, portId: "NODE" },
$(go.Shape, "RoundedRectangle",
{ fill: "white", portId: "SHAPE" },
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 4, portId: "TEXTBLOCK" },
new go.Binding("text", "txt"))
);
diagram.linkTemplate =
$(go.Link,
{ isShadowed: true },
$(go.Shape,
{ strokeWidth: 5, stroke: "orange" })
);
// Represents the nodeDataArray for the two nodes
diagram.nodeTemplateMap.add("dataNode",
$(go.Node, "Auto",
{
locationSpot: go.Spot.Center,
scale: 1.2,
selectionAdorned: true,
fromSpot: go.Spot.AllSides,
toSpot: go.Spot.AllSides,
shadowColor: "#C5C1AA"
},
new go.Binding("location", "pos", go.Point.parse),
$(go.Shape, "Rectangle",
{ fill: "lightgray" }),
$(go.Panel, "Vertical",
{ defaultStretch: go.GraphObject.Horizontal },
$(go.TextBlock, headerStyle(), // Header:
{ portId: "HEADER" },
new go.Binding("text", "head")),
$(go.Shape, "LineH", { height: 1, stretch: go.GraphObject.Fill }),
$(go.TextBlock, textStyle(), // Location:
{ portId: "LOCATION" },
new go.Binding("text", "loc")),
$(go.Shape, "LineH", { height: 1, stretch: go.GraphObject.Fill }),
$(go.TextBlock, textStyle(), // Fill:
{ portId: "FILL" },
new go.Binding("text", "color")),
$(go.Shape, "LineH", { height: 1, stretch: go.GraphObject.Fill }),
$(go.TextBlock, textStyle(), // Text:
{ portId: "TEXT" },
new go.Binding("text", "txt")),
$(go.Shape, "LineH", { height: 1, stretch: go.GraphObject.Fill }),
$(go.TextBlock, textStyle(), // Text:
{ portId: "PARENT" },
new go.Binding("text", "parent"))
)
)
);
diagram.linkTemplateMap.add("dataNode", // Links from dataNode to Nodes
$(go.Link,
{ routing: go.Link.Orthogonal, corner: 5 },
$(go.Shape, { stroke: "gray", strokeWidth: 2 }),
$(go.Shape, { toArrow: "Standard", stroke: "gray", fill: "gray" })
));
diagram.nodeTemplateMap.add("title",
$(go.Node, "Auto",
new go.Binding("location", "pos", go.Point.parse),
$(go.TextBlock,
{ font: "bold 25pt sans-serif", textAlign: "center"},
new go.Binding("text", "txt"))
));
diagram.nodeTemplateMap.add("nodeDataArray",
$(go.Node, "Auto",
{
locationSpot: go.Spot.Center,
scale: 1.2,
selectionAdorned: true,
fromSpot: go.Spot.AllSides,
toSpot: go.Spot.AllSides,
shadowColor: "#C5C1AA"
},
new go.Binding("location", "pos", go.Point.parse),
$(go.Shape, "Rectangle", { fill: "lightgray" }),
$(go.Panel, "Vertical",
{ defaultStretch: go.GraphObject.Horizontal },
$(go.TextBlock, headerStyle(),
{ portId: "HEADER", text: "nodeDataArray" }),
$(go.Shape, "LineH", { height: 1, stretch: go.GraphObject.Fill }),
$(go.TextBlock, textStyle(),
{ portId: "dataNode1", desiredSize: new go.Size(NaN,16) }),
$(go.Shape, "LineH", { height: 1, stretch: go.GraphObject.Fill }),
$(go.TextBlock, textStyle(),
{ portId: "dataNode2", desiredSize: new go.Size(NaN,16) })
)
));
// Comments
diagram.nodeTemplateMap.add("Comment", // Template for comment node
$(go.Node,
new go.Binding("location", "pos", go.Point.parse),
{ locationSpot: go.Spot.Center},
$(go.TextBlock,
{ stroke: "brown", textAlign: "center" },
new go.Binding("text", "txt"),
new go.Binding("font", "bold", function(b) { return b ? "bold 10pt sans-serif" : "10pt sans-serif"; }))
));
diagram.nodeTemplateMap.add("LinkLabel", // Template for comments on links
$(go.Node,
new go.Binding("segmentIndex"),
new go.Binding("segmentOffset")
));
diagram.linkTemplateMap.add("Comment", // Template for links from comments
$(go.Link,
{ curve: go.Link.Bezier },
new go.Binding("curviness"),
$(go.Shape, { stroke: "brown" }),
$(go.Shape, { toArrow: "OpenTriangle", stroke: "brown" })
));
diagram.linkTemplateMap.add("Binding",
$(go.Link,
{ curve: go.Link.Bezier },
new go.Binding("curviness"),
$(go.Shape, { stroke: "green" , strokeWidth: 2, strokeDashArray: [10, 10] }),
$(go.Shape, { toArrow: "OpenTriangle", stroke: "green", strokeWidth: 2 })
));
diagram.linkTemplateMap.add("Data",
$(go.Link,
{ curve: go.Link.Bezier },
new go.Binding("curviness"),
$(go.Shape, { stroke: "gray" , strokeWidth: 2 }),
$(go.Shape, { toArrow: "Standard", fill: "gray", stroke: "gray", strokeWidth: 2 }),
$(go.TextBlock, ".data", { font: "bold 12pt Courier", segmentOffset: new go.Point(0, -10) })
));
var model = new go.GraphLinksModel();
model.linkFromPortIdProperty = "fPID";
model.linkToPortIdProperty = "tPID"
model.linkLabelKeysProperty = "labels";
model.nodeDataArray = [
{ key: 1, txt: "Alpha", color: "lightblue", pos: "50 20"},
{ key: 2, txt: "Beta", color: "lightgreen", pos: "50 270"},
{ key: 3, category: "dataNode", pos: "300 66", head: "key: 1", txt: "text: Alpha", color: "color: lightblue", loc: "location: 50 0", parent: "parent: null"},
{ key: 4, category: "dataNode", pos: "300 316", head: "key: 2", txt: "text: Beta", color: "color: lightgreen", loc: "location: 50 250", parent: "parent: 1"},
{ key: 5, category: "nodeDataArray", pos: "500 125"},
{ key: 6, category: "title", pos: "320 -100", txt: "TreeModel,\n data"},
{ key: 7, category: "title", pos: "-50 -100", txt: "Diagram,\nNodes, Links"},
{ key: -1, category: "Comment", pos: "310 190", txt: "These two\ndata Objects are\nare held in the\nnodeDataArray."},
{ key: -2, category: "Comment", pos: "100 100", txt: "a Link", bold: true},
{ key: -21, category: "LinkLabel"},
{ key: -3, category: "Comment", pos: "0 130", txt: "two Nodes", bold: true},
{ key: -4, category: "Comment", pos: "190 180", txt: "data binding", bold: true},
{ key: -41, category: "LinkLabel", segmentOffset: new go.Point(45, 0)},
{ key: -42, category: "LinkLabel", segmentOffset: new go.Point(20, 0)},
{ key: -43, category: "LinkLabel", segmentOffset: new go.Point(-10, 0)},
{ key: -44, category: "LinkLabel", segmentOffset: new go.Point(25, 0)},
{ key: -45, category: "LinkLabel", segmentOffset: new go.Point(20, 0)},
{ key: -46, category: "LinkLabel", segmentOffset: new go.Point(-10, 0)}
];
model.linkDataArray = [
{ from: 1, to: 2, labels: [-21] },
{ from: 1, tPID: "HEADER", to: 3, category: "Data", curviness: 0},
{ from: 2, tPID: "HEADER", to: 4, category: "Data", curviness: 0},
{ from: -21, tPID: "HEADER", to: 4, category: "Data", curviness: -50},
{ from: 5, fPID: "dataNode1", to: 3, tPID: "HEADER", category: "dataNode"},
{ from: 5, fPID: "dataNode2", to: 4, tPID: "HEADER", category: "dataNode"},
{ from: -1, to: 3, category: "Comment"},
{ from: -1, to: 4, category: "Comment"},
{ from: -2, to: -21, category: "Comment", curviness: 10},
{ from: -3, to: 1, category: "Comment", curviness: 10},
{ from: -3, to: 2, category: "Comment", curviness: -10},
{ from: -4, to: -41, category: "Comment", curviness: 5},
{ from: -4, to: -42, category: "Comment", curviness: 5},
{ from: -4, to: -43, category: "Comment", curviness: 5},
{ from: -4, to: -44, category: "Comment", curviness: 5},
{ from: -4, to: -45, category: "Comment", curviness: 5},
{ from: -4, to: -46, category: "Comment", curviness: 5},
{ from: -4, to: -47, category: "Comment", curviness: 5},
{ from: 3, fPID: "LOCATION", to: 1, tPID: "NODE", category: "Binding", curviness: 10, labels: [-41]},
{ from: 3, fPID: "FILL", to: 1, tPID: "SHAPE" , category: "Binding", curviness: 30, labels: [-42]},
{ from: 3, fPID: "TEXT", to: 1, tPID: "TEXTBLOCK", category: "Binding", curviness: 50, labels: [-43]},
{ from: 4, fPID: "LOCATION", to: 2, tPID: "NODE", category: "Binding", curviness: 10, labels: [-44]},
{ from: 4, fPID: "FILL", to: 2, tPID: "SHAPE" , category: "Binding", curviness: 30, labels: [-45]},
{ from: 4, fPID: "TEXT", to: 2, tPID: "TEXTBLOCK", category: "Binding", curviness: 50, labels: [-46]}
];
diagram.model = model;
// Formatting
function headerStyle() {
return {
margin: 3,
font: "bold 12pt sans-serif",
minSize: new go.Size(16, 16),
maxSize: new go.Size(120, NaN),
textAlign: "center"
};
}
function textStyle() {
return {
margin: 2,
font: "10pt sans-serif",
minSize: new go.Size(16, 16),
maxSize: new go.Size(120, NaN),
textAlign: "center"
};
}
</pre>
<script>goCode("commented", 650, 550)</script>
<p>
The two <a>Node</a>s and one <a>Link</a> belong to the <a>Diagram</a> and are on the left side, with shadows.
The <a>TreeModel</a> and the two data objects in its <a>Model.nodeDataArray</a> are on the right side, in gray.
</p>
<p>
Each <a>Node</a> and <a>Link</a> has a <a>Panel.data</a> property that references the data object in the model.
Thus it is easy, given a Node, to refer to all of the data properties that you have put on the data in the model.
These references are drawn as gray links.
</p>
<p>
Each <a>Node</a> also has three <a>Binding</a>s, drawn with dashed green lines:
</p>
<ul>
<li>to the <a>Part.location</a> property from the <code>data.location</code> property</li>
<li>to the <a>Shape.fill</a> property from the <code>data.color</code> property</li>
<li>to the <a>TextBlock.text</a> property from the <code>data.text</code> property</li>
</ul>
<p>
The use of templates and data binding greatly simplify the information that must be stored in model data,
and allow great flexibility in representing nodes and links in various manners independent of the model data.
But not all data properties need to be used in Bindings in the template.
</p>
<p>
Note that <a>Binding</a>s are <em>not</em> references from the data to any <a>Part</a>.
The whole point of separating models from diagrams is to avoid references from data to Diagrams or Nodes or Links or Tools.
The only references from diagram to model are the <a>Diagram.model</a> property and each node or link's <a>Panel.data</a> property.
</p>
<h2 id="BindingStringAndNumberProperties">Binding string and number properties</h2>
<p>
It is easy to data bind <a>GraphObject</a> properties to data properties.
In this example we not only data bind <a>TextBlock.text</a> and <a>Shape.fill</a> in nodes to property values of node data,
but for thicker colored lines we also bind <a>Shape.stroke</a> and <a>Shape.strokeWidth</a> in links to property values of link data.
</p>
<p>
All you need to do is add to the target <a>GraphObject</a> a new <a>Binding</a> that
names the target property on the visual object and the source property on the data object.
Of course the target property must be a settable property; some GraphObject properties are not settable.
If you specify a target property name that does not exist you will get warning messages in the console.
If the source property value is undefined, the binding is not evaluated.
</p>
<pre class="lang-js" id="simpleModelWithBind">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle",
{ fill: "white" },
new go.Binding("fill", "color")), // shape.fill = data.color
$(go.TextBlock,
{ margin: 5 },
new go.Binding("text", "key")) // textblock.text = data.key
);
diagram.linkTemplate =
$(go.Link,
$(go.Shape,
new go.Binding("stroke", "color"), // shape.stroke = data.color
new go.Binding("strokeWidth", "thick")), // shape.strokeWidth = data.thick
$(go.Shape,
{ toArrow: "OpenTriangle", fill: null },
new go.Binding("stroke", "color"), // shape.stroke = data.color
new go.Binding("strokeWidth", "thick")) // shape.strokeWidth = data.thick
);
var nodeDataArray = [
{ key: "Alpha", color: "lightblue" },
{ key: "Beta", color: "pink" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta", color: "blue", thick: 2 }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("simpleModelWithBind", 250, 150)</script>
<p>
Note that there are two bindings using the "color" property of the source link data.
There is one for each target <a>Shape</a> in the <a>Link</a> template;
each binds the <a>Shape.stroke</a> property.
</p>
<h2 id="BindingObjectPropertiesSuchAsLocation">Binding object properties such as <b>Part.location</b></h2>
<p>
You can also data bind properties that have values that are objects.
For example it is common to data bind the <a>Part.location</a> property.
</p>
<p>
The value of Part.location is a <a>Point</a>, so in this example the data property must be a Point.
</p>
<pre class="lang-js" id="bindLocationPoint">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc"), // get the Node.location from the data.loc value
$(go.Shape, "RoundedRectangle",
{ fill: "white" },
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 5 },
new go.Binding("text", "key"))
);
var nodeDataArray = [
// for each node specify the location using Point values
{ key: "Alpha", color: "lightblue", loc: new go.Point(0, 0) },
{ key: "Beta", color: "pink", loc: new go.Point(100, 50) }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("bindLocationPoint", 250, 150)</script>
<p>
For conciseness the rest of these examples make use of the default <a>Diagram.linkTemplate</a>.
</p>
<h2 id="ConversionFunctions">Conversion functions</h2>
<p>
But what if you want the data property value for the location to be something other than a <a>Point</a>?
You can provide a conversion function that converts the actual data property value to the needed value type or format.
</p>
<p>
For situations like this example, the <a>Point</a> class includes a static function,
<a>Point,parse</a>, that you can use to convert a string into a Point object.
It expects two numbers to be in the input string, representing the <a>Point.x</a> and <a>Point.y</a> values.
It returns a Point object with those values.
</p>
<p>
You can pass a conversion function as the third argument to the <a>Binding</a> constructor.
In this case it is <a>Point,parse</a>.
This allows the location to be specified in the form of a string ("100 50") rather than as an expression that returns a <a>Point</a>.
For data properties on model objects, you will often want to use strings as the representation of
<a>Point</a>s, <a>Size</a>s, <a>Rect</a>s, <a>Margin</a>s, and <a>Spot</a>s, rather than references to objects of those classes.
Strings are easily read and written in JSON and XML.
Trying to read/write classes of objects would take extra space and would require additional cooperation on the part of both the writer and the reader.
</p>
<pre class="lang-js" id="bindLocationString">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse), // convert string into a Point value
$(go.Shape, "RoundedRectangle",
{ fill: "white" },
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 5 },
new go.Binding("text", "key"))
);
var nodeDataArray = [
{ key: "Alpha", color: "lightblue", loc: "0 0" }, // note string values for location
{ key: "Beta", color: "pink", loc: "100 50" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("bindLocationString", 250, 150)</script>
<p>
Conversion functions can be named or anonymous functions.
They take a data property value and return a value suitable for the property that is being set.
They should not have any side-effects.
They may get called any number of times in any order.
</p>
<p>
Here is an example that has several <a>Shape</a> properties data-bound to the same boolean data property named "highlight".
Each conversion function takes the boolean value and returns the appropriate value for the property that is data-bound.
This makes it trivial to control the appearance of each node from the data by setting the "highlight" data property to be either false or true.
</p>
<pre class="lang-js" id="bindHighlight">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle",
{ // default values if the data.highlight is undefined:
fill: "yellow", stroke: "orange", strokeWidth: 2 },
new go.Binding("fill", "highlight", function(v) { return v ? "pink" : "lightblue"; }),
new go.Binding("stroke", "highlight", function(v) { return v ? "red" : "blue"; }),
new go.Binding("strokeWidth", "highlight", function(v) { return v ? 3 : 1; })),
$(go.TextBlock,
{ margin: 5 },
new go.Binding("text", "key"))
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0", highlight: false },
{ key: "Beta", loc: "100 50", highlight: true },
{ key: "Gamma", loc: "0 100" } // highlight property undefined: use defaults
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("bindHighlight", 250, 150)</script>
<p class="box bg-danger">
Note that a conversion function can only return property values.
You cannot return GraphObjects to replace objects in the visual tree of the Part.
If you need to show different GraphObjects based on bound data,
you can bind the <a>GraphObject.visible</a> or the <a>GraphObject.opacity</a> property.
If you really want different visual structures
you can use multiple templates (<a href="templateMaps.html">Template Maps</a>).
</p>
<h2 id="ChangingDataValues">Changing data values</h2>
<p>
The examples above all depend on the data bindings being evaluated when the <a>Part</a>
has been created and its <a>Panel.data</a> property is set to refer to the corresponding
node or link data.
These actions occur automatically when the <a>Diagram</a> creates diagram parts
for the data in the model upon setting <a>Diagram.model</a>.
</p>
<p>
However, <b>GoJS</b> cannot know when the data property of an arbitrary JavaScript object has been modified.
If you want to change some data object in a model and have the diagram be automatically updated,
what you should do depends on the nature of the property that you are changing.
</p>
<p>
For most data properties, ones that the model does not treat specially but are data-bound,
you can just call <a>Model.setDataProperty</a>.
In this example we modify the value of "highlight" on a node data object.
For fun, this modification occurs about twice a second.
</p>
<pre class="lang-js" id="changeBoundValue">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ locationSpot: go.Spot.Center },
$(go.Shape, "RoundedRectangle",
{ // default values if the data.highlight is undefined:
fill: "yellow", stroke: "orange", strokeWidth: 2 },
new go.Binding("fill", "highlight", function(v) { return v ? "pink" : "lightblue"; }),
new go.Binding("stroke", "highlight", function(v) { return v ? "red" : "blue"; }),
new go.Binding("strokeWidth", "highlight", function(v) { return v ? 3 : 1; })),
$(go.TextBlock,
{ margin: 5 },
new go.Binding("text", "key"))
);
diagram.model.nodeDataArray = [
{ key: "Alpha", highlight: false } // just one node, and no links
];
function flash() {
// all model changes should happen in a transaction
diagram.model.commit(function(m) {
var data = m.nodeDataArray[0]; // get the first node data
m.set(data, "highlight", !data.highlight);
}, "flash");
}
function loop() {
setTimeout(function() { flash(); loop(); }, 500);
}
loop();
</pre>
<script>goCode("changeBoundValue", 250, 150)</script>
<h2 id="ChangingGraphStructure">Changing graph structure</h2>
<p>
Data binding is not used to establish relationships between parts.
For data properties that a particular model knows about,
such as "to" or "from" for link data in a <a>GraphLinksModel</a>,
you must call the appropriate model methods in order to modify the data property.
Modifying a data property directly without calling the appropriate model method
may cause inconsistencies or undefined behavior.
</p>
<p>
For node data, the model methods are
<a>Model.setCategoryForNodeData</a>,
<a>Model.setKeyForNodeData</a>,
<a>GraphLinksModel.setGroupKeyForNodeData</a>,
<a>TreeModel.setParentKeyForNodeData</a>, and
<a>TreeModel.setParentLinkCategoryForNodeData</a>.
For link data, the model methods are
<a>GraphLinksModel.setCategoryForLinkData</a>,
<a>GraphLinksModel.setFromKeyForLinkData</a>,
<a>GraphLinksModel.setFromPortIdForLinkData</a>,
<a>GraphLinksModel.setToKeyForLinkData</a>,
<a>GraphLinksModel.setToPortIdForLinkData</a>, and
<a>GraphLinksModel.setLabelKeysForLinkData</a>.
</p>
<p>
This example changes the "to" property of a link data, causing the link to
connect to a different node.
This example uses the default Link template, which does not have any
data bindings.
The change in the link relationship is accomplished by calling a model method,
not via a data binding.
</p>
<pre class="lang-js" id="changeLinkTo">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ locationSpot: go.Spot.Center },
$(go.Shape, "RoundedRectangle",
{ fill: "yellow", stroke: "orange", strokeWidth: 2 }),
$(go.TextBlock,
{ margin: 5 },
new go.Binding("text", "key"))
);
var nodeDataArray = [
{ key: "Alpha" },
{ key: "Beta" },
{ key: "Gamma" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
function switchTo() {
// all model changes should happen in a transaction
diagram.model.commit(function(m) {
var data = m.linkDataArray[0]; // get the first link data
if (m.getToKeyForLinkData(data) === "Beta")
m.setToKeyForLinkData(data, "Gamma");
else
m.setToKeyForLinkData(data, "Beta");
}, "reconnect link");
}
function loop() {
setTimeout(function() { switchTo(); loop(); }, 1000);
}
loop();
</pre>
<script>goCode("changeLinkTo", 250, 150)</script>
<h2 id="BindingToGraphObjectSources">Binding to <b>GraphObject</b> sources</h2>
<p>
The binding source object need not be a plain JavaScript data object held in the diagram's model.
The source object may instead be a named <a>GraphObject</a> in the same <a>Part</a>.
The source property must be a settable property of the class.
The binding is evaluated when the property is set to a new value.
</p>
<p>
One common use of such a binding is to change the appearance of a Part when the <a>Part.isSelected</a>.
Call <a>Binding.ofObject</a> to cause the Binding to use the object whose <a>GraphObject.name</a> is the given name.
Use the empty string, "", or no argument, to refer to the whole Part itself.
This is a convenience so that you do not need to name the whole Part.
"ofObject" really means "of the GraphObject named ...", as found by <a>Panel.findObject</a> when there is a string argument.
</p>
<p>
In the example below, the <a>Shape.fill</a> is bound to the <a>Part.isSelected</a> property.
When the node is selected or de-selected, the <a>Part.isSelected</a> property changes value, so the binding is evaluated.
The conversion function gets a boolean value and returns the desired brush color to be used as the shape's fill.
This example also turns off selection adornments, so that the only visual way to tell that a node is selected is by the shape's fill color.
</p>
<pre class="lang-js" id="bindingElements">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ selectionAdorned: false }, // no blue selection handle!
$(go.Shape, "RoundedRectangle",
// bind Shape.fill to Node.isSelected converted to a color
new go.Binding("fill", "isSelected", function(sel) {
return sel ? "dodgerblue" : "lightgray";
}).ofObject()), // no name means bind to the whole Part
$(go.TextBlock,
{ margin: 5 },
new go.Binding("text", "descr"))
);
diagram.model.nodeDataArray = [
{ descr: "Select me!" },
{ descr: "I turn blue when selected." }
];
</pre>
<script>goCode("bindingElements", 450, 100)</script>
<p>
Caution: do not declare cycles of binding dependencies -- that will result in undefined behavior.
<a>GraphObject</a> binding sources also require the <a>Part</a> to be bound to data (i.e. <a>Part.data</a> must be non-null).
The property on the GraphObject must be settable, so it does not work on read-only properties
such as ones that return computed values (e.g. <a>Part.isTopLevel</a>) or Iterators (e.g. <a>Node.linksConnected</a>).
</p>
<h2 id="BindingToSharedModelDataSource">Binding to the shared <b>Model.modelData</b> source</h2>
<p>
The binding source object may be a third kind of source, besides the <a>Panel.data</a> or some <a>GraphObject</a> within the panel.
It can also be the JavaScript Object that is the shared <a>Model.modelData</a> object.
This permits binding of Node or Link element properties to shared properties in the model
that will exist and may be modified even though no nodes or links exist in the model.
</p>
<p>
In the example below, the <a>Shape.fill</a> is bound to the "color" property on the <a>Model.modelData</a> object.
As you click the button the <code>changeColor</code> function modifies the <code>modelData</code> object
by calling <a>Model.setDataProperty</a>.
</p>
<pre class="lang-js" id="bindingModel">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle",
{ fill: "white" }, // the default value if there is no modelData.color property
new go.Binding("fill", "color").ofModel()), // meaning a property of Model.modelData
$(go.TextBlock,
{ margin: 5 },
new go.Binding("text"))
);
// start all nodes yellow
diagram.model.modelData.color = "yellow";
diagram.model.nodeDataArray = [
{ text: "Alpha" },
{ text: "Beta" }
];
diagram.undoManager.isEnabled = true;
changeColor = function() { // define a function named "changeColor" callable by button.onclick
diagram.model.commit(function(m) {
// alternate between lightblue and lightgreen colors
var oldcolor = m.modelData.color;
var newcolor = (oldcolor === "lightblue" ? "lightgreen" : "lightblue");
m.set(m.modelData, "color", newcolor);
}, "changed shared color");
}
</pre>
<script>goCode("bindingModel", 450, 100)</script>
<button id="changeColorButton" onclick="changeColor()">Change shared color</button>
<h2 id="TwoWayDataBinding">Two-way data binding</h2>
<p>
All of the bindings above only transfer values from the source data to target properties.
But sometimes you would like to be able to transfer values from <a>GraphObject</a>s back to the model data,
to keep the model data up-to-date with the diagram.
This is possible by using a TwoWay <a>Binding</a>, which can pass values not only from source to target,
but also from the target object back to the source data.
</p>
<pre class="lang-js" id="bindTwoWay">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ locationSpot: go.Spot.Center },
new go.Binding("location", "loc").makeTwoWay(), // TwoWay Binding
$(go.Shape, "RoundedRectangle",
{ fill: "lightblue", stroke: "blue", strokeWidth: 2 }),
$(go.TextBlock,
{ margin: 5 },
new go.Binding("text", "key"))
);
var nodeDataArray = [
{ key: "Alpha", loc: new go.Point(0, 0) }
];
diagram.model = new go.GraphLinksModel(nodeDataArray);
shiftNode = (function() { // define a function named "shiftNode" callable by button.onclick
// all model changes should happen in a transaction
diagram.commit(function(d) {
var data = d.model.nodeDataArray[0]; // get the first node data
var node = d.findNodeForData(data); // find the corresponding Node
var p = node.location.copy(); // make a copy of the location, a Point
p.x += 10;
if (p.x > 200) p.x = 0;
// changing the Node.location also changes the data.loc property due to TwoWay binding
node.location = p;
// show the updated location held by the "loc" property of the node data
document.getElementById("bindTwoWayData").textContent = data.loc.toString();
}, "shift node");
});
shiftNode(); // initialize everything
</pre>
<p>
Click on the button to move the <a>Node</a>.
The effect is basically what happens when the user drags the node.
In this example, the TwoWay <a>Binding</a> on <a>Node.location</a> will update the
"loc" property of the node data that is the Node's <a>Part.data</a>.
</p>
<input type="button" onclick="shiftNode()" value="shiftNode()" />
nodedata.loc: <code id="bindTwoWayData"></code>
<script>goCode("bindTwoWay", 250, 150)</script>
<p>
Just as you can use a conversion function when going from source to target,
you can supply a conversion function to <a>Binding.makeTwoWay</a> for going from target to source.
For example, to represent the location as a string in the model data instead of as a <a>Point</a>:
</p>
<pre class="lang-js">
// storage representation of Points/Sizes/Rects/Margins/Spots is as strings, not objects:
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify)
</pre>
<p>
However, you must not have a TwoWay binding on the node data property that is the "key" property.
(That defaults to the name "key" but is actually the value of <a>Model.nodeKeyProperty</a>.)
That property value must always be unique among all node data within the model and is known by the Model.
A TwoWay binding might change the value, causing a multitude of problems.
Similarly, the <a>Node.key</a> property is read-only, to prevent accidental changes of the key value.
</p>
<h2 id="ReasonsForTwoWayBindings">Reasons for TwoWay Bindings</h2>
<p>
The basic reason for using a TwoWay <a>Binding</a> on a settable property is to make sure that any changes to that property
will be copied to the corresponding model data.
By making sure that the <a>Model</a> is up-to-date, you can easily "save the diagram" just by saving the model
and "loading a diagram" is just a matter of loading a model into memory and setting <a>Diagram.model</a>.
If you are careful to only hold JSON-serializable data in the model data, you can just use the <a>Model.toJson</a>
and <a>Model,fromJson</a> methods for converting a model to and from a textual representation.
</p>
<p>
<em>Most bindings do not need to be TwoWay.</em>
For performance reasons you should not make a Binding be TwoWay unless you actually need to propagate changes back to the data.
Most settable properties are only set on initialization and then never change.
</p>
<p>
Settable properties only change value when some code sets them.
That code might be in code that you write as part of your app.
Or it might be in a command (see <a href="commands.html">Commands</a>) or a tool (see <a href="tools.html">Tools</a>).
Here is a list of properties for which a TwoWay Binding is plausible because one of the predefined commands or tools modify them:
</p>
<ul>
<li><a>Part.location</a>, by <a>DraggingTool</a> if it is enabled</li>
<li><a>Link.points</a>, by <a>LinkReshapingTool</a> if it is enabled</li>
<li><a>GraphObject.desiredSize</a>, by <a>ResizingTool</a> if it is enabled</li>
<li><a>GraphObject.angle</a>, by <a>RotatingTool</a> if it is enabled</li>
<li><a>TextBlock.text</a>, by <a>TextEditingTool</a> if it is enabled</li>
<li><a>Part.isSelected</a>, by many tools and commands</li>
<li><a>Node.isTreeExpanded</a> and <a>Node.wasTreeExpanded</a>, by <a>CommandHandler.collapseTree</a> and <a>CommandHandler.expandTree</a>, called by a "TreeExpanderButton"</li>
<li><a>Group.isSubGraphExpanded</a> and <a>Group.wasSubGraphExpanded</a>, by <a>CommandHandler.collapseSubGraph</a> and <a>CommandHandler.expandSubGraph</a>, called by a "SubGraphExpanderButton"</li>
</ul>
<p>
You will not need to use a TwoWay binding on a property if the Tool that modifies it cannot run,
or if the command that modifies it cannot be invoked.
You probably will not need a TwoWay binding on any other properties unless you write code to modify them.
And even then it is sometimes better to write the code to modify the model data directly by calling <a>Model.setDataProperty</a>,
depending on a OneWay Binding to update the GraphObject property.
</p>
<p>
It is also possible to use TwoWay Bindings where the source is a GraphObject rather than model data.
This is needed less frequently, when you do <em>not</em> want to have the state stored in the model,
but you do want to synchronize properties of GraphObjects within the same Part.
<em>Use TwoWay Bindings sparingly.</em>
</p>
</div>
</div>
</body>
</html>
+398
View File
@@ -0,0 +1,398 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Debugging Suggestions-- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Debugging Suggestions</h1>
<p>
Developing a diagramming app involves a lot more than just writing some JavaScript code that uses the <b>GoJS</b> library.
</p>
<ul>
<li>You will need to be familiar with HTML DOM and CSS.</li>
<li>You will need to test your app on many different devices using many different browsers.</li>
<li>You will need to be familiar with your JavaScript framework (if any).</li>
<li>You will need to know how to use each browser's development facilities, especially the console window and debugger.</li>
</ul>
<h3 id="UseGoDebugJSLibrary">Use the <code>go-debug.js</code> library</h3>
<p>
While developing your app make sure you use the debug library, <code>go-debug.js</code>, rather than the <code>go.js</code> library.
The debug library does more error checking of property values and method arguments, and it detects more unusual situations.
Most warning and errors will be written to the console window. Always check it for messages. We have tried to make them informative.
</p>
<h3 id="UseDocumentedAPI">Use the documented API</h3>
<p>
Try to limit your code to only use documented classes, properties, and methods, as listed in the
<a href="../api/index.html" target="api">API</a> reference or in the TypeScript definition file,
<a href="../release/go.d.ts" target="_blank">go.d.ts</a>.
</p>
<p>
Please do not refer to some minified property name, which will only be one or two letters long.
In another version of the library the minified names will be different, so such code would no longer work.
Basically: never use one or two letter property names
except for "x" and "y" on <a>Point</a>, <a>Rect</a>, <a>Spot</a>, and <a>LayoutVertex</a> instances
and the <a>InputEvent.up</a> property.
</p>
<p>
Do not modify the prototypes of any of the <b>GoJS</b> classes.
If you modify the built-in classes, we cannot support you.
The way to modify the behavior of the <b>GoJS</b> classes is via the techniques discussed at
<a href="extensions.html">Extensions</a>.
However most of the <b>GoJS</b> classes cannot be subclassed and most of the documented methods cannot be overridden.
Generally the <a>Tool</a> and <a>Layout</a> classes and the <a>CommandHandler</a> and <a>Link</a> classes may be subclassed;
look at the API documentation to see if a method may be overridden.
</p>
<h2 id="UsingConsoleWindow">Using the Console window</h2>
<p>
First you will need to get a reference to your <a>Diagram</a> object in the Console window or the Debugger window.
</p>
<p>
One way to do that is by remembering it in your code.
You can set a property on the <code>window</code> object to refer to the Diagram that you create.
Many of the samples do this just by leaving out the <code>var</code> declaration:
<pre class="lang-js"> myDiagram = $(go.Diagram, "myDiagramDiv", . . .);</pre>
</p>
<p>
Alternatively, in the console, if you know the name of the HTML DIV element,
you can call the static function <a>Diagram,fromDiv</a> to get the <a>Diagram</a> object:
<pre class="lang-js">> myDiagram = go.Diagram.fromDiv("myDiagramDiv");</pre>
If that DIV element is not named, perhaps you have some other way of getting a reference to the DIV element.
That may depend on the framework that you are using.
You can still call <a>Diagram,fromDiv</a> on that element to get the corresponding Diagram object.
</p>
<p>
Then in the console you can use the <code>myDiagram</code> reference to the <a>Diagram</a> object. Some examples:
</p>
<p>
<pre class="lang-js">> myDiagram.nodes.size</pre>
returns the number of <a>Node</a>s in the Diagram.
</p>
<p>
<pre class="lang-js">> myDiagram.model.nodeDataArray[0]</pre>
returns the first node data object in the diagram's model's <a>Model.nodeDataArray</a>.
</p>
<p>
<pre class="lang-js">> myDiagram.layoutDiagram(true)</pre>
forces all layouts to happen, rearranging the nodes and routing the links.
</p>
<p>
The code that you execute in the console can be more complicated too.
For example, you can find, select, and scroll to a particular node:
<pre class="lang-js">> myNode = myDiagram.findNodeForKey("Omega");
> myNode.isSelected = true
> myDiagram.commandHandler.scrollToPart(myNode)</pre>
If you don't know the key for the node that you want to see in the viewport,
perhaps you know how to find the node data object in the model.
The <a>Diagram.findNodesByExample</a> method might also be useful.
</p>
<h3 id="ExaminingSelectedNode">Examining a selected Node</h3>
<p>
<pre class="lang-js">> myDiagram.selection.first()</pre>
returns the first selected <a>Part</a>, which might be either a <a>Node</a>, a <a>Link</a>,
or null if nothing is selected.
</p>
<p>
If you remember the selected Node or Link, you can then examine it further more easily. For example:
<pre class="lang-js">> myNode = myDiagram.selection.first()
> myNode.data.key</pre>
remembers the first selected Node and returns the key of the node data.
You might want to look at all of the properties of the <pre class="lang-js">myNode.data</pre> object.
</p>
<p>
You could also look at other properties of the Node and call its methods. For example:
<pre class="lang-js">> myNode.location</pre>
returns a <a>Point</a> whose properties the debugger may show. Or call:
<pre class="lang-js">> myNode.location.toString()</pre>
to see a human-readable textual rendering of that Point object.
</p>
<p>
As another example, you can print out all of the nodes the selected node is connected to:
<pre class="lang-js">myNode.findNodesOutOf().each(function(n) { console.log(n.data.key); })</pre>
You can find more examples of iterating at <a href="collections.html#MoreIterationExamples">Collections</a>
</p>
<p>
You can also look at the structure of the visual tree of a node. With this recursive function:
<pre class="lang-js">> function walk(x, level, index) {
> console.log(level + "," + index + ": " + x.toString());
> if (!(x instanceof go.Panel)) return;
> for (var i = 0; i < x.elements.size; i++) walk(x.elt(i), level+1, i);
> }</pre>
you could call
<pre class="lang-js">> walk(myNode, 0, 0)</pre>
and in the Org Chart sample get results such as:
<pre class="lang-js">
0,0: Node#653(Kensaku Tamaki)
1,0: Shape(Rectangle)#656
1,1: Panel(Panel.Table)#657
2,0: TextBlock("Kensaku Tamaki")
2,1: Picture(https://www.nwoods.com/go/Flags/japan-flag.Png)#664
2,2: TextBlock("Title: Vice Chairman"...)</pre>
So you can see how the Node is a panel composed of Shape surrounding a nested Table Panel,
which in turn is composed of two TextBlocks and a Picture.
</p>
<h2 id="DebuggingNodePanelDesigns">Debugging Node Panel designs</h2>
<p>
When building your own node template, there may be times when the objects in the node are not sized and positioned the way that you would like.
It is important that you understand how objects may be assembled within panels. You will want to re-read:
</p>
<ul>
<li><a href="https://gojs.net/latest/intro/buildingObjects.html">Building with GraphObjects</a></li>
<li><a href="https://gojs.net/latest/intro/textBlocks.html">TextBlocks</a></li>
<li><a href="https://gojs.net/latest/intro/shapes.html">Shapes</a></li>
<li><a href="https://gojs.net/latest/intro/pictures.html">Pictures</a></li>
<li><a href="https://gojs.net/latest/intro/panels.html">Panels</a></li>
<li><a href="https://gojs.net/latest/intro/tablePanels.html">Table Panels</a></li>
<li><a href="https://gojs.net/latest/intro/sizing.html">Sizing of GraphObjects</a></li>
</ul>
<p>
Say that you want a node consisting of two TextBlocks, one above the other. You might start off with:
</p>
<pre class="lang-js" id="first">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, { fill: "white" }),
$(go.Panel, "Vertical",
{ margin: 3 },
$(go.TextBlock,
new go.Binding("text", "t1")),
$(go.TextBlock,
new go.Binding("text", "t2"))
)
);
diagram.model.nodeDataArray = [{ t1: "Top", t2: "Bottom"}];
</pre>
<script>goCode("first", 500, 140)</script>
<p>
But wait -- you want the node to be a fixed size. So you set the node's width and height:
</p>
<pre class="lang-js" id="second">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ width: 80, height: 100 },
$(go.Shape, { fill: "white" }),
$(go.Panel, "Vertical",
{ margin: 3 },
$(go.TextBlock,
new go.Binding("text", "t1")),
$(go.TextBlock,
new go.Binding("text", "t2"))
)
);
diagram.model.nodeDataArray = [{ t1: "Top", t2: "Bottom"}];
</pre>
<script>goCode("second", 500, 140)</script>
<p>
That looks better, but you are suprised that both TextBlocks are near the center. Why is that?
For debugging purposes let's change the <a>GraphObject.background</a> colors of each TextBlock and the nested Panel.
</p>
<pre class="lang-js" id="third">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ width: 80, height: 100 },
$(go.Shape, { fill: "white" }),
$(go.Panel, "Vertical", { background: "red" },
{ margin: 3 },
$(go.TextBlock, { background: "lime" },
new go.Binding("text", "t1")),
$(go.TextBlock, { background: "cyan" },
new go.Binding("text", "t2"))
)
);
diagram.model.nodeDataArray = [{ t1: "Top", t2: "Bottom"}];
</pre>
<script>goCode("third", 500, 140)</script>
<p>
It is now clear that the TextBlocks are no bigger than they need to be to hold the text,
and that the Panel is also no bigger than need be to hold the two TextBlocks.
</p>
<p>
So you think that you just need to <a>GraphObject.stretch</a> the panel.
</p>
<pre class="lang-js" id="fourth">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ width: 80, height: 100 },
$(go.Shape, { fill: "white" }),
$(go.Panel, "Vertical", { background: "red" },
{ margin: 3, stretch: go.GraphObject.Fill },
$(go.TextBlock, { background: "lime" },
new go.Binding("text", "t1")),
$(go.TextBlock, { background: "cyan" },
new go.Binding("text", "t2"))
)
);
diagram.model.nodeDataArray = [{ t1: "Top", t2: "Bottom"}];
</pre>
<script>goCode("fourth", 500, 140)</script>
<p>
Now the Panel with the red background indeed fills up the whole outer Auto Panel,
inside its main Shape acting as a border.
But the lime green and cyan blue TextBlocks are still only their natural heights.
</p>
<p>
If you want the text to be spaced evenly vertically,
you might think you only need to stretch those two TextBlocks.
</p>
<pre class="lang-js" id="fifth">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ width: 80, height: 100 },
$(go.Shape, { fill: "white" }),
$(go.Panel, "Vertical", { background: "red" },
{ margin: 3, stretch: go.GraphObject.Fill },
$(go.TextBlock, { background: "lime" },
{ stretch: go.GraphObject.Fill },
new go.Binding("text", "t1")),
$(go.TextBlock, { background: "cyan" },
{ stretch: go.GraphObject.Fill },
new go.Binding("text", "t2"))
)
);
diagram.model.nodeDataArray = [{ t1: "Top", t2: "Bottom"}];
</pre>
<script>goCode("fifth", 500, 140)</script>
<p>
Now the TextBlocks are stretching horizontally but not vertically!
The reason is that a Vertical Panel never stretches its elements vertically.
It always stacks its elements on top of each other with their natural heights.
When a Vertical Panel is taller than the stack of its elements, there is extra space at the bottom.
</p>
<p>
Instead of a Vertical Panel we should use a Table Panel.
This requires assigning the <a>GraphObject.row</a> on each element (i.e. each TextBlock).
</p>
<pre class="lang-js" id="sixth">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ width: 80, height: 100 },
$(go.Shape, { fill: "white" }),
$(go.Panel, "Table", { background: "red" },
{ margin: 3, stretch: go.GraphObject.Fill },
$(go.TextBlock, { background: "lime" },
{ row: 0 },
new go.Binding("text", "t1")),
$(go.TextBlock, { background: "cyan" },
{ row: 1 },
new go.Binding("text", "t2"))
)
);
diagram.model.nodeDataArray = [{ t1: "Top", t2: "Bottom"}];
</pre>
<script>goCode("sixth", 500, 140)</script>
<p>
Because by default elements are centered within the cells of a Table Panel, no stretching of the TextBlocks is needed.
(You could change that by setting <a>Panel.defaultAlignment</a> or <a>Panel.defaultStretch</a>.)
</p>
<p>
Are we all done? Maybe. What happens when the text changes size?
One way to test that is to create a bunch of nodes using different model data, using short and long strings.
</p>
<p>
But to demonstrate one more debugging technique, we'll make the Node <a>Part.resizable</a>.
You can interactively resize the node (the whole node because we haven't set <a>Part.resizeObjectName</a>)
so you can see how the nested Panel and the TextBlocks handle constrained sizing.
</p>
<pre class="lang-js" id="seventh">
diagram.nodeTemplate =
$(go.Node, "Auto", { resizable: true },
{ width: 80, height: 100 },
$(go.Shape, { fill: "white" }),
$(go.Panel, "Table", { background: "red" },
{ margin: 3, stretch: go.GraphObject.Fill },
$(go.TextBlock, { background: "lime" },
{ row: 0 },
new go.Binding("text", "t1")),
$(go.TextBlock, { background: "cyan" },
{ row: 1 },
new go.Binding("text", "t2"))
)
);
diagram.model.nodeDataArray = [{ t1: "Top String", t2: "Bottom String"}];
diagram.findNodeForData(diagram.model.nodeDataArray[0]).isSelected = true;
</pre>
<script>goCode("seventh", 500, 140)</script>
<p>
Note how when the node becomes narrow, it clips the text rather than make the text wrap.
Let's say that you would rather that the text wrap.
</p>
<p>
This can be implemented by stretching the TextBlocks horizontally, which will define their widths, forcing the text to wrap.
But text normally is drawn at the left side of the bounds of the TextBlock when the text direction is left-to-right.
If you want each TextBlock to be centered within its bounds, you'll need to set <a>TextBlock.textAlign</a> to "center".
</p>
<pre class="lang-js" id="eighth">
diagram.nodeTemplate =
$(go.Node, "Auto", { resizable: true },
{ width: 80, height: 100 },
$(go.Shape, { fill: "white" }),
$(go.Panel, "Table", { background: "red" },
{ margin: 3, stretch: go.GraphObject.Fill,
defaultStretch: go.GraphObject.Horizontal },
$(go.TextBlock, { background: "lime" },
{ row: 0, textAlign: "center" },
new go.Binding("text", "t1")),
$(go.TextBlock, { background: "cyan" },
{ row: 1, textAlign: "center" },
new go.Binding("text", "t2"))
)
);
diagram.model.nodeDataArray = [{ t1: "Top String", t2: "Bottom String"}];
diagram.findNodeForData(diagram.model.nodeDataArray[0]).isSelected = true;
</pre>
<script>goCode("eighth", 500, 140)</script>
<p>
The TextBlocks can be seen to stretch across the width of the available area.
Note how the text wraps as the node becomes narrow, causing the TextBlocks to become more narrow.
Of course when there's not enough room to render all of the text, the TextBlocks will be clipped.
</p>
<p>
Now we just need to get rid of the colored backgrounds and resizable-ness used for debugging
and assign the desired colors and fonts.
</p>
<pre class="lang-js" id="ninth">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ width: 80, height: 100 },
$(go.Shape, { fill: "white" }),
$(go.Panel, "Table",
{ margin: 3, stretch: go.GraphObject.Fill,
defaultStretch: go.GraphObject.Horizontal, background: "purple" },
$(go.TextBlock,
{ row: 0, textAlign: "center", stroke: "white", font: "bold 11pt sans-serif" },
new go.Binding("text", "t1")),
$(go.TextBlock,
{ row: 1, textAlign: "center", stroke: "white", font: "bold 11pt sans-serif" },
new go.Binding("text", "t2"))
)
);
diagram.model.nodeDataArray = [{ t1: "Top String", t2: "Bottom String"}];
</pre>
<script>goCode("ninth", 500, 140)</script>
</div>
</div>
</body>
</html>
+158
View File
@@ -0,0 +1,158 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Deployment -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Deployment</h1>
<p>
If you have downloaded a copy of the <b>GoJS</b> library from <a href="https://gojs.net">gojs.net</a>
for use in your own app, you have probably noticed that a watermark appears in the diagram.
For version 1.7 and beyond, you need to use a license key to remove this watermark from your domain.
For version 1.6 and older, you need to get a generated <code>go.js</code> or <code>go-debug.js</code> library that is tied to your particular domain.
</p>
<p>
When you want to deploy to your own web site your app that uses <b>GoJS</b>, you will need to make a request at:
<a href="https://www.nwoods.com/app/activate.aspx?sku=gojs" target="_blank">https://www.nwoods.com/app/activate.aspx?sku=gojs</a>.
</p>
<p>
Enter your e-mail address and the e-mail address of the person who purchased a license for <b>GoJS</b>,
the order number for that purchase, and your web site's domain name.
Please make sure that e-mail from "nwoods.com" is not caught in your corporate or personal spam filter.
If you <a href="https://www.nwoods.com/contact.html">contact us</a> for further help either via our web site
or by sending us email, please include the e-mail that our web server sent to you.
</p>
<p>
Regarding domain names, for example, if your app will be at:
<code>https://www.example.com/app/ProcessEditor.html</code>,
enter <code>example.com</code> as the domain name.
This procedure works for internal corporate web sites as well as for public web sites, with hostnames and with IP addresses.
The protocol and port number do not matter.
It will work when the HTML page is served from a subdomain of the licensed domain,
such as from <code>editors.example.com</code>.
It will also work when "localhost" is the domain, to help your debugging and testing efforts.
</p>
<p>
The GoJS library never "phones home" -- it will never initiate any network traffic other than when explicitly directed to do so,
such as for downloading image files.
</p>
<h2 id="For1.7AndLater">For 1.7 and Later</h2>
<p>
Our server will generate a GoJS license key for you, in the form of a JavaScript statement that you will need to include with your code.
It must execute after the GoJS library file has been loaded, but before you create your first <a>Diagram</a>.
</p>
<pre class="lang-js">// Must execute after loading the library and before you create your first Diagram:
go.Diagram.licenseKey = "YourKeyHere";
</pre>
Before version 2.0, you need to write:
<pre class="lang-js">// Must execute after loading the library and before you create your first Diagram:
go.licenseKey = "YourKeyHere";
</pre>
<p>
This mechanism works when using either the release library, <code>go.js</code>, or the debug library,
<code>go-debug.js</code>, but only with GoJS version 1.7 or later.
Note that this assignment is of a static property of the Diagram class: <a>Diagram,licenseKey</a>.
Of course you will need to substitute your generated license key string for <code>"YourKeyHere"</code> in the <code>go.Diagram.licenseKey</code> assignment statement.
License keys are long strings without any embedded whitespace or punctuation.
You can request license keys for as many domains as you have licensed.
</p>
<p>
Unlike the older activation method, you no longer need to get a new domain-specific <code>go.js</code> library each time you update or upgrade.
Once your key is in place, you can continue to use the same key while updating the patch version of GoJS.
For example, a license key for version 1.7.3 will work for all versions 1.7.*.
Remember to get a new license key when upgrading to a new major or minor version of GoJS.
For example, when upgrading from version 5.3.1 to version 5.4.6, you will need a new license key.
</p>
<p>
License keys only depend on the major/minor version number and the domain from which the HTML page was served.
License keys are valid forever, as long as the major and minor version number of the library do not change and
as long as the HTML page is served from the same domain.
</p>
<p>
You can download the <code>go.js</code> library from the <a href="../download.html" target="_blank">GoJS Downloads page</a>,
or you can install it via <a href="https://www.npmjs.com/package/gojs" target="_blank">Node package manager (npm)</a>
or via <a href="https://www.nuget.org/packages/Northwoods.GoJS/" target="_blank">NuGet</a>,
or you can link to a CDN such as <a href="https://unpkg.com/browse/gojs/" target="_blank">UNPKG</a> or <a href="https://www.jsdelivr.com/package/npm/gojs">JSDELIVR</a>.
Most customers will no longer need a special build of GoJS, as had been the case before version 1.7.
</p>
<h2 id="ForUnlimitedDomainsOEMCustomers">For Unlimited Domains OEM Customers</h2>
<p>
If you are an ISV and intend to distribute your app to run on many customers' web sites,
<a href="https://www.nwoods.com/contact.html">Contact sales</a> for our our Unlimited Domains option and
instructions on requesting and using a custom <code>go.js</code> library that works on any site or platform.
</p>
<p>
When building a desktop application using Electron or Cordova or NW or when hosted in a WebView as part of a desktop application,
your HTML page is not being served from a web server at a domain.
In such circumstances you will need to use the Unlimited Domains option to make sure your Diagram does not display a watermark.
You should use your organization's domain as the requested domain name.
</p>
<p>
The procedure for unlimited domains is similar to the procedure followed for the 1.6 and older versions,
but with the addition of requiring a license key.
</p>
<h2 id="For1.6AndOlder">For 1.6 and Older</h2>
<p>
Our automated web server will create <code>go.js</code> and <code>go-debug.js</code> libraries that are customized
not to show a watermark on pages from that domain and will e-mail you instructions for how to download them.
</p>
<p>
You can request libraries for as many domains as you have licensed.
If there is a problem you will receive e-mail from our web server describing the problem.
</p>
<p>
When updating or upgrading to a new version of <b>GoJS</b> less than version 1.7,
you will need to get a new domain-specific <code>go.js</code> library again using the same procedure.
(We too had to produce one for the domain "gojs.net" each time we updated our web site.)
</p>
<!--
<p>
If you are building a Windows Store JavaScript app, there is no domain name.
Instead you should use the application-specific "appId", a GUID.
To obtain the proper ID your app must first be <a href="https://msdn.microsoft.com/en-us/library/windows/apps/hh454036.aspx">packaged with the windows app store</a>,
so that the package name is the one to be used in production.
Once you have associated your app with the windows store,
you can find this GUID in your <code>package.appxmanifest</code> file as the <code>&lt;Package&gt;</code> <code>&lt;Identity&gt;</code> <code>Name</code> attribute.
</p>
-->
<h2 id="InternationalizationAndLocalization">Internationalization and Localization</h2>
<p>
<b>GoJS</b> apps can display text in non-Latin languages.
For example, see <a href="../samples/familyTreeJP.html" target="samples">Japanese Family Tree</a>.
</p>
<p>
The <b>GoJS</b> library does not manipulate currency values or date/time values or addresses,
so there are no localization issues with those data types and values.
<b>GoJS</b> does not contain any of its own icons (images) or cursors.
</p>
<p>
Nor does <b>GoJS</b> display any built-in text strings, so no translation is needed.
There are error and warning messages that may be output to the console, but
those messages are only meant for debugging by programmers, not for consumption by end users.
Reading and writing of numeric values is only performed internally
when reading and writing JSON or geometry path strings or CSS colors, which are all defined to use non-localized formats.
</p>
<p>
All user-visible text is completely under the control of the programmer.
For localizability you may find it convenient to use conversion functions in <a>Binding</a>s.
The <a>TextEditingTool</a> uses an HTML TextArea element to implement in-place text input and text editing,
thereby utilising the browser's support for input method editors.
</p>
</div>
</div>
</body>
</html>
+468
View File
@@ -0,0 +1,468 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Events -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Events</h1>
<p>
There are three basic kinds of events that <b>GoJS</b> deals with:
<a>DiagramEvent</a>s, <a>InputEvent</a>s, and <a>ChangedEvent</a>s.
This page discusses the first two; see <a href="changedEvents.html">Changed Events</a> for the last kind of event.
</p>
<h2 id="DiagramEvents">Diagram Events</h2>
<p>
<a>DiagramEvent</a>s represent general user-initiated changes to a diagram.
You can register one or more diagram event handlers by calling <a>Diagram.addDiagramListener</a>.
You can also register a diagram event handler in <a>Diagram</a> initialization when calling <a>GraphObject,make</a>.
Each kind of diagram event is distinguished by its name.
</p>
<p>
Currently defined diagram event names include:
</p>
<ul>
<li id="InitialAnimationStarting" onclick="window.location.hash = '#AnimationStarting'">
"<b>InitialAnimationStarting</b>", the initial default animation is about to start;
do not modify the diagram or its model in the event listener.
This can be useful for modifying the <a>AnimationManager.defaultAnimation</a> to make a custom initial animation.
See <a>AnimationManager.initialAnimationStyle</a> for details.
</li>
<li id="AnimationStarting" onclick="window.location.hash = '#AnimationStarting'">
"<b>AnimationStarting</b>", a default animation (<a>AnimationManager.defaultAnimation</a>) is about to start;
do not modify the diagram or its model in the event listener.
</li>
<li id="AnimationFinished" onclick="window.location.hash = '#AnimationFinished'">
"<b>AnimationFinished</b>", a default animation (<a>AnimationManager.defaultAnimation</a>) just completed;
do not modify the diagram or its model in the event listener.
</li>
<li id="BackgroundSingleClicked" onclick="window.location.hash = '#BackgroundSingleClicked'">
"<b>BackgroundSingleClicked</b>", when a mouse left-button single-click happened in the background of the Diagram, not on a Part;
if you make any changes, start and commit your own transaction.
</li>
<li id="BackgroundDoubleClicked" onclick="window.location.hash = '#BackgroundDoubleClicked'">
"<b>BackgroundDoubleClicked</b>", when a mouse left-button double-click happened in the background of the Diagram, not on a Part;
if you make any changes, start and commit your own transaction.
</li>
<li id="BackgroundContextClicked" onclick="window.location.hash = '#BackgroundContextClicked'">
"<b>BackgroundContextClicked</b>", when a mouse right-button single-click happened in the background of the Diagram, not on a Part;
if you make any changes, start and commit your own transaction.
</li>
<li id="ChangingSelection" onclick="window.location.hash = '#ChangingSelection'">
"<b>ChangingSelection</b>", an operation is about to change the <a>Diagram.selection</a> collection,
which is also the value of the <a>DiagramEvent.subject</a>;
do not make any changes to the selection or the diagram or the model in the event listener;
note that just setting <a>Part.isSelected</a> will not raise this event, but tools and commands will.
</li>
<li id="ChangedSelection" onclick="window.location.hash = '#ChangedSelection'">
"<b>ChangedSelection</b>", an operation has just changed the <a>Diagram.selection</a> collection,
which is also the value of the <a>DiagramEvent.subject</a>;
do not make any changes to the selection or the diagram or the model in the event listener;
note that just setting <a>Part.isSelected</a> will not raise this event, but tools and commands will.
</li>
<li id="ClipboardChanged" onclick="window.location.hash = '#ClipboardChanged'">
"<b>ClipboardChanged</b>", Parts have been copied to the clipboard by <a>CommandHandler.copySelection</a>;
the <a>DiagramEvent.subject</a> is the collection of Parts;
if you make any changes, start and commit your own transaction.
</li>
<li id="ClipboardPasted" onclick="window.location.hash = '#ClipboardPasted'">
"<b>ClipboardPasted</b>", Parts have been copied from the clipboard into the Diagram by <a>CommandHandler.pasteSelection</a>;
the <a>DiagramEvent.subject</a> is the <a>Diagram.selection</a>,
and this is called within a transaction, so that you do not have to start and commit your own transaction.
</li>
<li id="DocumentBoundsChanged" onclick="window.location.hash = '#DocumentBoundsChanged'">
"<b>DocumentBoundsChanged</b>", the area of the diagram's Parts, <a>Diagram.documentBounds</a>, has changed;
the <a>DiagramEvent.parameter</a> is the old Rect.
</li>
<li id="ExternalObjectsDropped" onclick="window.location.hash = '#ExternalObjectsDropped'">
"<b>ExternalObjectsDropped</b>", Parts have been copied into the Diagram by drag-and-drop from outside of the Diagram;
the <a>DiagramEvent.subject</a> is the set of Parts that were dropped (which is also the <a>Diagram.selection</a>),
the <a>DiagramEvent.parameter</a> is the source Diagram,
and this is called within a transaction, so that you do not have to start and commit your own transaction.
</li>
<li id="GainedFocus" onclick="window.location.hash = '#GainedFocus'">
"<b>GainedFocus</b>", the diagram has gained keyboard focus, such as after a call to <a>Diagram.focus</a>.
</li>
<li id="InitialLayoutCompleted" onclick="window.location.hash = '#InitialLayoutCompleted'">
"<b>InitialLayoutCompleted</b>", the whole diagram layout has updated for the first time since a major change to the Diagram,
such as replacing the Model;
if you make any changes, you do not need to perform a transaction.
</li>
<li id="LayoutCompleted" onclick="window.location.hash = '#LayoutCompleted'">
"<b>LayoutCompleted</b>", the whole diagram layout has just been updated;
if you make any changes, you do not need to perform a transaction.
</li>
<li id="LinkDrawn" onclick="window.location.hash = '#LinkDrawn'">
"<b>LinkDrawn</b>", the user has just created a new Link using <a>LinkingTool</a>;
the <a>DiagramEvent.subject</a> is the new Link,
and this is called within a transaction, so that you do not have to start and commit your own transaction..
</li>
<li id="LinkRelinked" onclick="window.location.hash = '#LinkRelinked'">
"<b>LinkRelinked</b>", the user has just reconnected an existing Link using <a>RelinkingTool</a> or <a>DraggingTool</a>;
the <a>DiagramEvent.subject</a> is the modified Link,
the <a>DiagramEvent.parameter</a> is the GraphObject port that the link was disconnected from,
and this is called within a transaction, so that you do not have to start and commit your own transaction..
</li>
<li id="LinkReshaped" onclick="window.location.hash = '#LinkReshaped'">
"<b>LinkReshaped</b>", the user has just rerouted an existing Link using <a>LinkReshapingTool</a>;
the <a>DiagramEvent.subject</a> is the modified Link,
the <a>DiagramEvent.parameter</a> is the List of Points of the link's original route,
and this is called within a transaction, so that you do not have to start and commit your own transaction..
</li>
<li id="LostFocus" onclick="window.location.hash = '#LostFocus'">
"<b>LostFocus</b>", the diagram has lost keyboard focus ("blur").
</li>
<li id="Modified" onclick="window.location.hash = '#Modified'">
"<b>Modified</b>", the <a>Diagram.isModified</a> property has been set to a new value --
useful for marking a window as having been modified since the last save;
do not modify the Diagram or its Model in the event listener.
</li>
<li id="ObjectSingleClicked" onclick="window.location.hash = '#ObjectSingleClicked'">
"<b>ObjectSingleClicked</b>", a click that occurred on a GraphObject;
the <a>DiagramEvent.subject</a> is the GraphObject;
if you make any changes, start and commit your own transaction.
</li>
<li id="ObjectDoubleClicked" onclick="window.location.hash = '#ObjectDoubleClicked'">
"<b>ObjectDoubleClicked</b>", a double-click that occurred on a GraphObject;
the <a>DiagramEvent.subject</a> is the GraphObject;
if you make any changes, start and commit your own transaction.
</li>
<li id="ObjectContextClicked" onclick="window.location.hash = '#ObjectContextClicked'">
"<b>ObjectContextClicked</b>", a context-click that occurred on a GraphObject;
the <a>DiagramEvent.subject</a> is the GraphObject;
if you make any changes, start and commit your own transaction.
</li>
<li id="PartCreated" onclick="window.location.hash = '#PartCreated'">
"<b>PartCreated</b>", the user inserted a new Part by <a>ClickCreatingTool</a>;
the <a>DiagramEvent.subject</a> is the new Part,
and this is called within a transaction, so that you do not have to start and commit your own transaction.
</li>
<li id="PartResized" onclick="window.location.hash = '#PartResized'">
"<b>PartResized</b>", the user has changed the size of a GraphObject by <a>ResizingTool</a>;
the <a>DiagramEvent.subject</a> is the GraphObject,
the <a>DiagramEvent.parameter</a> is the original Size,
and this is called within a transaction, so that you do not have to start and commit your own transaction.
</li>
<li id="PartRotated" onclick="window.location.hash = '#PartRotated'">
"<b>PartRotated</b>", the user has changed the angle of a GraphObject by <a>RotatingTool</a>;
the <a>DiagramEvent.subject</a> is the GraphObject,
the <a>DiagramEvent.parameter</a> is the original angle in degrees,
and this is called within a transaction, so that you do not have to start and commit your own transaction.
</li>
<li id="SelectionMoved" onclick="window.location.hash = '#SelectionMoved'">
"<b>SelectionMoved</b>", the user has moved selected Parts by <a>DraggingTool</a>;
the <a>DiagramEvent.subject</a> is a Set of the moved Parts,
and this is called within a transaction, so that you do not have to start and commit your own transaction.
</li>
<li id="SelectionCopied" onclick="window.location.hash = '#SelectionCopied'">
"<b>SelectionCopied</b>", the user has copied selected Parts by <a>DraggingTool</a>;
the <a>DiagramEvent.subject</a> is Set of the newly copied Parts,
and this is called within a transaction, so that you do not have to start and commit your own transaction.
</li>
<li id="SelectionDeleting" onclick="window.location.hash = '#SelectionDeleting'">
"<b>SelectionDeleting</b>", the user is about to delete selected Parts by <a>CommandHandler.deleteSelection</a>;
the <a>DiagramEvent.subject</a> is the <a>Diagram.selection</a> collection of Parts to be deleted,
and this is called within a transaction, so that you do not have to start and commit your own transaction.
</li>
<li id="SelectionDeleted" onclick="window.location.hash = '#SelectionDeleted'">
"<b>SelectionDeleted</b>", the user has deleted selected Parts by <a>CommandHandler.deleteSelection</a>;
the <a>DiagramEvent.subject</a> is the collection of Parts that were deleted,
and this is called within a transaction, so that you do not have to start and commit your own transaction.
</li>
<li id="SelectionGrouped" onclick="window.location.hash = '#SelectionGrouped'">
"<b>SelectionGrouped</b>", the user has made a new Group out of the selected Parts by <a>CommandHandler.groupSelection</a>;
the <a>DiagramEvent.subject</a> is the new Group,
and this is called within a transaction, so that you do not have to start and commit your own transaction.
</li>
<li id="SelectionUngrouped" onclick="window.location.hash = '#SelectionUngrouped'">
"<b>SelectionUngrouped</b>", the user has removed a selected Group but kept its members by <a>CommandHandler.ungroupSelection</a>;
the <a>DiagramEvent.subject</a> is the collection of Groups that were ungrouped,
the <a>DiagramEvent.parameter</a> is the collection of former member Parts that were ungrouped,
and this is called within a transaction, so that you do not have to start and commit your own transaction.
</li>
<li id="SubGraphCollapsed" onclick="window.location.hash = '#SubGraphCollapsed'">
"<b>SubGraphCollapsed</b>", the user has collapsed selected Groups by <a>CommandHandler.collapseSubGraph</a>;
the <a>DiagramEvent.subject</a> is the collection of Groups that were collapsed,
and this is called within a transaction, so that you do not have to start and commit your own transaction.
</li>
<li id="SubGraphExpanded" onclick="window.location.hash = '#SubGraphExpanded'">
"<b>SubGraphExpanded</b>", the user has expanded selected Groups by <a>CommandHandler.expandSubGraph</a>;
the <a>DiagramEvent.subject</a> is the collection of Groups that were expanded,
and this is called within a transaction, so that you do not have to start and commit your own transaction.
</li>
<li id="TextEdited" onclick="window.location.hash = '#TextEdited'">
"<b>TextEdited</b>", the user has changed the string value of a TextBlock by <a>TextEditingTool</a>;
the <a>DiagramEvent.subject</a> is the edited TextBlock,
the <a>DiagramEvent.parameter</a> is the original string,
and this is called within a transaction, so that you do not have to start and commit your own transaction.
</li>
<li id="TreeCollapsed" onclick="window.location.hash = '#TreeCollapsed'">
"<b>TreeCollapsed</b>", the user has collapsed selected Nodes with subtrees by <a>CommandHandler.collapseTree</a>;
the <a>DiagramEvent.subject</a> is the collection of Nodes that were collapsed,
and this is called within a transaction, so that you do not have to start and commit your own transaction.
</li>
<li id="TreeExpanded" onclick="window.location.hash = '#TreeExpanded'">
"<b>TreeExpanded</b>", the user has expanded selected Nodes with subtrees by <a>CommandHandler.expandTree</a>;
the <a>DiagramEvent.subject</a> is the collection of Nodes that were expanded,
and this is called within a transaction, so that you do not have to start and commit your own transaction.
</li>
<li id="ViewportBoundsChanged" onclick="window.location.hash = '#ViewportBoundsChanged'">
"<b>ViewportBoundsChanged</b>", the visible area of the Diagram, <a>Diagram.viewportBounds</a>, has changed;
the <a>DiagramEvent.subject</a> is an object whose "scale" property is the old <a>Diagram.scale</a> value,
whose "position" property is the old <a>Diagram.position</a> value,
and whose "bounds" property is the old <a>Diagram.viewportBounds</a> value;
the <a>DiagramEvent.parameter</a> is also the old viewportBounds Rect.
Do not modify the Diagram position or scale (i.e. the viewport bounds) in the listener.
</li>
</ul>
<p>
DiagramEvents do not necessarily correspond to mouse events or keyboard events or touch events.
Nor do they necessarily correspond to changes to the diagram's model --
for tracking such changes, use <a>Model.addChangedListener</a> or <a>Diagram.addModelChangedListener</a>.
DiagramEvents only occur because the user did something, perhaps indirectly.
</p>
<p>
<p>
In addition to the DiagramEvent listeners there are also circumstances where detecting such changes is common
enough to warrant having properties that are event handlers.
Because these events do not necessarily correspond to any particular input or diagram event,
these event handlers have custom arguments that are specific to the situation.
</p>
<p>
One very common such event property is <a>GraphObject.click</a>, which if non-null is a function that is called
whenever the user clicks on that object.
This is most commonly used to specify behavior for "Button"s, but it and the other "click" event properties,
"doubleClick" and "contextClick", can be useful on any GraphObject.
</p>
<p>
Another common event property is <a>Part.selectionChanged</a>,
which (if non-null) is called whenever <a>Part.isSelected</a> changes.
In this case the event hander function is passed a single argument, the Part.
There is no need for additional arguments because the function can check the current value of <a>Part.isSelected</a> to decide what to do.
</p>
</p>
<p>
Model <a>ChangedEvent</a>s are more complete and reliable than depending on <a>DiagramEvent</a>s.
For example, the "LinkDrawn" DiagramEvent is not raised when code adds a link to a diagram.
That DiagramEvent is only raised when the user draws a new link using the <a>LinkingTool</a>.
Furthermore the link has not yet been routed, so <a>Link.points</a> will not have been computed.
In fact, creating a new link may invalidate a <a>Layout</a>, so all of the nodes may be moved in the near future.
</p>
<p class="box bg-danger">
Sometimes you want to update a database as the user makes changes to a diagram.
Usually you will want to implement a <a>Model</a> <a>ChangedEvent</a> listener,
by calling <a>Model.addChangedListener</a> or <a>Diagram.addModelChangedListener</a>,
that notices the changes to the model and decides what to record in the database.
See the discussion of <a href="changedEvents.html">Changed Events</a> and the <a href="../samples/UpdateDemo.html">Update Demo</a>.
</p>
<p>
This example demonstrates handling several diagram events: <b>"ObjectSingleClicked"</b>,
<b>"BackgroundDoubleClicked"</b>, and <b>"ClipboardPasted"</b>.
</p>
<pre class="lang-js" id="diagramEvents">
function showMessage(s) {
document.getElementById("diagramEventsMsg").textContent = s;
}
diagram.addDiagramListener("ObjectSingleClicked",
function(e) {
var part = e.subject.part;
if (!(part instanceof go.Link)) showMessage("Clicked on " + part.data.key);
});
diagram.addDiagramListener("BackgroundDoubleClicked",
function(e) { showMessage("Double-clicked at " + e.diagram.lastInput.documentPoint); });
diagram.addDiagramListener("ClipboardPasted",
function(e) { showMessage("Pasted " + e.diagram.selection.count + " parts"); });
var nodeDataArray = [
{ key: "Alpha" },
{ key: "Beta", group: "Omega" },
{ key: "Gamma", group: "Omega" },
{ key: "Omega", isGroup: true },
{ key: "Delta" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }, // from outside the Group to inside it
{ from: "Beta", to: "Gamma" }, // this link is a member of the Group
{ from: "Omega", to: "Delta" } // from the Group to a Node
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("diagramEvents", 600, 200)</script>
<span id="diagramEventsMsg" style="color: red">(message)</span>
<h2 id="InputEvents">Input Events</h2>
<p>
When a low-level HTML DOM event occurs, <b>GoJS</b> canonicalizes the keyboard/mouse/touch event information
into a new <a>InputEvent</a> that can be passed to various event-handling methods and saved for later examination.
</p>
<p>
An InputEvent keeps the <a>InputEvent.key</a> for keyboard events,
the <a>InputEvent.button</a> for mouse events,
the <a>InputEvent.viewPoint</a> for mouse and touch events,
and <a>InputEvent.modifiers</a> for keyboard and mouse events.
</p>
<p>
The diagram's event handlers also record the <a>InputEvent.documentPoint</a>,
which is the <a>InputEvent.viewPoint</a> in document coordinates at the time of the mouse event,
and the <a>InputEvent.timestamp</a>, which records the time that the event occurred in milliseconds.
</p>
<p>
The InputEvent class also provides many handy properties for particular kinds of events.
Examples include <a>InputEvent.control</a> (if the control key had been pressed) and
<a>InputEvent.left</a> (if the left/primary mouse button was pressed).
</p>
<p>
Some tools find the "current" <a>GraphObject</a> at the mouse point.
This is remembered as the <a>InputEvent.targetObject</a>.
</p>
<h2 id="HigherLevelInputEvents">Higher-level input events</h2>
<p>
Some tools detect a sequence of input events to compose somewhat more abstract user events.
Examples include "click" (mouse-down-and-up very close to each other) and "hover" (motionless mouse for some time).
The tools will call an event handler (if there is any) for the current <a>GraphObject</a> at the mouse point.
The event handler is held as the value of a property on the object.
It then also "bubbles" the event up the chain of <a>GraphObject.panel</a>s until it ends with a <a>Part</a>.
This allows a "click" event handler to be declared on a <a>Panel</a> and have it apply even if the click actually happens on an element deep inside the panel.
If there is no object at the mouse point, the event occurs on the diagram.
</p>
<p>
Click-like event properties include <a>GraphObject.click</a>, <a>GraphObject.doubleClick</a>, and <a>GraphObject.contextClick</a>.
They also occur when there is no GraphObject -- the event happened in the diagram's background:
<a>Diagram.click</a>, <a>Diagram.doubleClick</a>, and <a>Diagram.contextClick</a>.
These are all properties that you can set to a function that is the event handler.
These events are caused by both mouse events and touch events.
</p>
<p>
Mouse-over-like event properties include <a>GraphObject.mouseEnter</a>, <a>GraphObject.mouseOver</a>, and <a>GraphObject.mouseLeave</a>.
But only <a>Diagram.mouseOver</a> applies to the diagram.
</p>
<p>
Hover-like event properties include <a>GraphObject.mouseHover</a> and <a>GraphObject.mouseHold</a>.
The equivalent diagram properties are <a>Diagram.mouseHover</a> and <a>Diagram.mouseHold</a>.
</p>
<p>
There are also event properties for dragging operations: <a>GraphObject.mouseDragEnter</a>, <a>GraphObject.mouseDragLeave</a>, and <a>GraphObject.mouseDrop</a>.
These apply to stationary objects, not the objects being dragged.
And they also occur when dragging by touch events, not just mouse events.
</p>
<p>
This example demonstrates handling three higher-level input events:
clicking on nodes and entering/leaving groups.
</p>
<pre class="lang-js" id="inputEvents">
function showMessage(s) {
document.getElementById("inputEventsMsg").textContent = s;
}
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "Ellipse", { fill: "white" }),
$(go.TextBlock,
new go.Binding("text", "key")),
{ click: function(e, obj) { showMessage("Clicked on " + obj.part.data.key); } }
);
diagram.groupTemplate =
$(go.Group, "Vertical",
$(go.TextBlock,
{ alignment: go.Spot.Left, font: "Bold 12pt Sans-Serif" },
new go.Binding("text", "key")),
$(go.Panel, "Auto",
$(go.Shape, "RoundedRectangle",
{ name: "SHAPE",
parameter1: 14,
fill: "rgba(128,128,128,0.33)" }),
$(go.Placeholder, { padding: 5 })
),
{ mouseEnter: function(e, obj, prev) { // change group's background brush
var shape = obj.part.findObject("SHAPE");
if (shape) shape.fill = "red";
},
mouseLeave: function(e, obj, next) { // restore to original brush
var shape = obj.part.findObject("SHAPE");
if (shape) shape.fill = "rgba(128,128,128,0.33)";
} });
var nodeDataArray = [
{ key: "Alpha" },
{ key: "Beta", group: "Omega" },
{ key: "Gamma", group: "Omega" },
{ key: "Omega", isGroup: true },
{ key: "Delta" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }, // from outside the Group to inside it
{ from: "Beta", to: "Gamma" }, // this link is a member of the Group
{ from: "Omega", to: "Delta" } // from the Group to a Node
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("inputEvents", 600, 200)</script>
<span id="inputEventsMsg" style="color: red">(message)</span>
<h2 id="ClickingAndSelecting">Clicking and Selecting</h2>
<p>
This example demonstrates both the "click" and the "selectionChanged" events:
</p>
<pre class="lang-js" id="changeMethods">
function showMessage(s) {
document.getElementById("changeMethodsMsg").textContent = s;
}
diagram.nodeTemplate =
$(go.Node, "Auto",
{ selectionAdorned: false },
$(go.Shape, "Ellipse", { fill: "white" }),
$(go.TextBlock,
new go.Binding("text", "key")),
{
click: function(e, obj) { showMessage("Clicked on " + obj.part.data.key); },
selectionChanged: function(part) {
var shape = part.elt(0);
shape.fill = part.isSelected ? "red" : "white";
}
}
);
var nodeDataArray = [
{ key: "Alpha" }, { key: "Beta" }, { key: "Gamma" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" },
{ from: "Beta", to: "Gamma" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("changeMethods", 600, 200)</script>
<span id="changeMethodsMsg" style="color: red">(message)</span>
<p>
Try Ctrl-A to select everything.
Note the distinction between the <a>GraphObject.click</a> event property and the <a>Part.selectionChanged</a> event property.
Both are methods that get called when something has happened to the node.
The <a>GraphObject.click</a> occurs when the user clicks on the node, which happens to select the node.
But the <a>Part.selectionChanged</a> occurs even when there is no click event or even any mouse event --
it was due to a property change to the node.
</p>
</div>
</div>
</body>
</html>
+368
View File
@@ -0,0 +1,368 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Extending GoJS -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Extending GoJS</h1>
<p>
<b>GoJS</b> can be extended in a variety of ways.
The most common way to change the standard behavior is to set properties on the <a>GraphObject</a>, <a>Diagram</a>, <a>CommandHandler</a>, <a>Tool</a>, or <a>Layout</a>.
But when no such properties exist, you might need to override methods of CommandHandler, Tool, Layout, Link, or Node.
Methods that you can override are documented in the API reference.
This page describes how to override methods, either by replacing a method on an instance (a feature of JavaScript) or by defining a subclass.
You should not modify the prototypes of any of the <b>GoJS</b> classes.
</p>
<p class="box bg-danger">
Do not modify the prototypes of the <b>GoJS</b> classes.<br />
Only use the properties and methods documented in the <a href="../api/index.html">API</a>.
</p>
<p class="box bg-danger">
Note that the API for extension classes 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.
</p>
<p class="box bg-info">
In addition to our samples, <b>GoJS</b> provides an <strong><a href="../extensions/">extensions gallery</a></strong>,
showcasing the creation of custom tools and layouts.
Those classes and samples have been translated into TypeScript, available at <code>../extensionsTS/</code>.
Those extension classes are UMD modules; they use the <code>../release/go.js</code> library.
The extension classes are also available as ES6 modules at <code>../extensionsJSM/</code>; these use the <code>../release/go.mjs</code> library.
We recommend that you copy the files that you need into your project, so that you can adjust how they refer to the "go.js" library
and so that you can include them into your own building and packaging procedures.
</p>
<h2 id="CommandHandler">Command Handler</h2>
<p>
Overriding the <a>CommandHandler</a> allows you to alter default functionality and create your own key bindings.
See the <a href="commands.html">intro page on Commands</a> for more.
However, the techniques shown below for overriding methods on Tools and Layouts also applies to the CommandHandler.
</p>
<h2 id="ToolsAndLayouts">Tools and Layouts</h2>
<p>
<b>GoJS</b> operates on nodes and links using many tools and layouts, all of which are subclasses of the <a>Tool</a> and <a>Layout</a> classes.
See the <a href="tools.html">intro page on Tools</a> for more about Tools, and the <a href="layouts.html">intro page on Layouts</a> for more about Layouts.
</p>
<p>
Tools can be modified, or they can be replaced in or added to the <a>Diagram.toolManager</a>.
All tools must inherit from the <a>Tool</a> class or from a class that inherits from Tool.
</p>
<p class="box bg-info">
Some of our samples, such as the <a href="../samples/pipes.html">Pipes sample</a>, contain examples of custom tools.
More custom tool examples are available in the <a href="../extensions/">extensions gallery</a>.
TypeScript versions of those classes and samples are available in <code>../extensionsTS/</code>.
Those custom tool classes are also available as ES6 modules in <code>../extensionsJSM/</code>.
</p>
<p>
Layouts can be modified, or they can be used by setting <a>Diagram.layout</a> or <a>Group.layout</a>.
All Layouts must inherit from the <a>Layout</a> class or a class that inherits from Layout.
</p>
<p class="box bg-info">
Some of our samples, such as the <a href="../samples/parseTree.html">Parse Tree sample</a>, contain examples of custom layouts.
More custom layout examples are available in the <a href="../extensions/">extensions gallery</a>.
TypeScript versions of those classes are available in <code>../extensionsTS/</code>.
Those custom layout classes are also available as ES6 modules in <code>../extensionsJSM/</code>.
</p>
<h2 id="OverridingMethodWithoutDefiningSubclass">Overriding a Method Without Defining a Subclass</h2>
<p>
Often we can avoid subclassing a Tool in its entirety and merely override a single method.
This is common when we want to make a small change to the behavior of a method.
Here we show how to override the <code>ClickSelectingTool.standardMouseSelect</code> method by modifying the tool instance of a particular Diagram.
</p>
<p>
One can override Layout methods in this manner also, but that is rarely done -- layouts are almost always subclassed.
It cannot be done for layouts that are the value of <a>Group.layout</a> because those layouts may be copied and cannot be shared.
</p>
<p>
Since we are not creating a new (sub)class, we set the method directly on the Diagram's <a>ClickSelectingTool</a>, which is referenced through its <a>ToolManager</a>.
Typical scaffolding for overriding a method in such a manner is as follows:
</p>
<pre class="lang-js">
var tool = diagram.toolManager.clickSelectingTool;
tool.standardMouseSelect = function() {
// Maybe do something else before
// ...
// Be careful about using 'this' within such functions!
// In cases where you want normal behavior, call the base functionality.
// Note the reference to the prototype
// and the call to 'call' passing it what 'this' should be.
go.ClickSelectingTool.prototype.standardMouseSelect.call(tool);
// Maybe do something else after
// ...
}
</pre>
<p>
As a concrete example, we will override <a>Tool.standardMouseSelect</a> to select only Nodes and Links that have a width and height of less than 50 diagram units.
This means that we must find the to-be-selected object using <code>diagram.findPartAt</code>, check its bounds, and quit if the bounds are too large.
Otherwise, we call the base functionality to select as we normally might.
</p>
<pre class="lang-js" id="tool">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "Rectangle",
{ fill: "white" },
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 5 },
new go.Binding("text", "key"))
);
var tool = diagram.toolManager.clickSelectingTool;
tool.standardMouseSelect = function() {
var diagram = tool.diagram;
var e = diagram.lastInput;
// to select containing Group if Part.canSelect() is false
var curobj = diagram.findPartAt(e.documentPoint, false);
if (curobj !== null) {
var bounds = curobj.actualBounds;
// End the selection process if the bounds are greater than 50 width or height
if (bounds.width > 50 || bounds.height > 50) {
// If this was a left click with no modifier, we want to act the same as if
// we are clicking on the Diagram background, so we clear the selection
if (e.left && !e.control && !e.shift) {
diagram.clearSelection();
}
// Then return, so that we do not call the base functionality
return;
}
}
// otherwise, call the base functionality
go.ClickSelectingTool.prototype.standardMouseSelect.call(tool);
}
diagram.model = new go.Model([
{ key: "Alpha", color: "lightblue" },
{ key: "Epsilon", color: "thistle" },
{ key: "Psi", color: "lightcoral" },
{ key: "Gamma", color: "lightgreen" }
]);
</pre>
<p>
Running this code, we see that the "Epsilon" and "Gamma" nodes are not selectable, because they are both wider than 50.
Note that this custom tool does not change the behavior of other tools that might select, such as the <a>DraggingTool</a> or the <a>DragSelectingTool</a>.
</p>
<script>goCode("tool", 300, 130)</script>
<h2 id="OverridingMethodsBySubclassingLayout">Overriding Methods by Subclassing a Layout</h2>
<p>
Layouts can be subclassed to create custom Layouts that inherit the properties and methods of an existing layout.
Subclassing in traditional JavaScript for <b>GoJS</b> requires a few key steps:
</p>
<ul>
<li>Create a new class (function), and call the base class constructor.
<li>Call <a>Diagram,inherit</a> with the new class and the base class.
<li>Modify the prototype of your derived class to add new functionality.
</ul>
<p>
To create a new Layout, called <code>CascadeLayout</code>, we would start with the following scaffolding:
</p>
<pre class="lang-js">
function CascadeLayout() {
// Note the direct constructor call, no use of "prototype"
go.Layout.call(this);
// new properties go here, on "this"
}
go.Diagram.inherit(CascadeLayout, go.Layout);
// Note setting the method on the prototype
CascadeLayout.prototype.doLayout = function(coll) {
// Layout logic goes here.
// You can reliably use "this" to refer to the layout instance
// on which this method was called.
}
</pre>
<p>
Note that if you are writing in modern JavaScript (ECMAScript 6) or in TypeScript, you can use newer syntax for defining classes:
</p>
<pre class="lang-js">
export class CascadeLayout extends go.Layout {
// new data properties (fields) get declared and initialized here
constructor() {
super();
// other initializations can be done here
}
// optionally, define property getters and setters here
// override or define methods
public doLayout(coll) {
// Layout logic goes here.
}
}
</pre>
<p>
Layouts commonly need additional properties that act as layout options.
To add a "offset" property to <code>CascadeLayout</code>, we will use the convention that an underscore member is private, and will set a default value in the constructor:
</p>
<pre class="lang-js">
function CascadeLayout() {
go.Layout.call(this);
this._offset = new go.Size(12, 12);
}
</pre>
<p>
Then, we use <code>Object.defineProperty</code> to make a "public" getter and setter.
Getters and setters allow us to do type checking and have side effects.
This setter makes sure the offset value is a <code>go.Size</code> object and invalidates the layout only if the value has changed.
</p>
<pre class="lang-js">
Object.defineProperty(CascadeLayout.prototype, "offset", {
get: function() { return this._offset; },
set: function(val) {
if (!(val instanceof go.Size)) {
throw new Error("new value for CascadeLayout.offset must be a Size, not: " + val);
}
if (!this._offset.equals(val)) {
this._offset = val;
this.invalidateLayout();
}
}
});
</pre>
<p>
If you are writing in ECMAScript 6 or TypeScript, you can define property getters and setters.
</p>
<pre class="lang-js">
get offset() { return this._offset; }
set offset(val) {
if (!(val instanceof go.Size)) {
throw new Error("new value for CascadeLayout.offset must be a Size, not: " + val);
}
if (!this._offset.equals(val)) {
this._offset = val;
this.invalidateLayout();
}
}
</pre>
<p>
If the layout might be used as the value of <a>Group.layout</a>,
you will need to make sure the instance that you set in the Group template can be copied correctly.
</p>
<pre class="lang-js">
CascadeLayout.prototype.cloneProtected = function(copy) {
go.Layout.prototype.cloneProtected.call(this, copy);
copy._offset = this._offset;
}
</pre>
<p>
Lastly we'll define a <a>Layout.doLayout</a>, being sure to look at the documentation and accomodate all possible input, as doLayout has one argument that can either be a <a>Diagram</a>, or a <a>Group</a>, or an <a>Iterable</a> collection.
</p>
<p>
All together, we can see the cascade layout in action:
</p>
<pre class="lang-js" id="example">
/**
* @constructor
* @extends Layout
* @class
* This layout arranges nodes in a cascade specified by the offset property
*/
function CascadeLayout() {
go.Layout.call(this);
this._offset = new go.Size(12, 12);
}
go.Diagram.inherit(CascadeLayout, go.Layout);
CascadeLayout.prototype.cloneProtected = function(copy) {
go.Layout.prototype.cloneProtected.call(this, copy);
copy._offset = this._offset;
}
Object.defineProperty(CascadeLayout.prototype, "offset", {
get: function() { return this._offset; },
set: function(val) {
if (!(val instanceof go.Size)) {
throw new Error("new value for CascadeLayout.offset must be a Size, not: " + val);
}
if (!this._offset.equals(val)) {
this._offset = val;
this.invalidateLayout();
}
}
});
/**
* This method positions all Nodes and ignores all Links.
* @this {CascadeLayout}
* @param {Diagram|Group|Iterable} coll the collection of Parts to layout.
*/
CascadeLayout.prototype.doLayout = function(coll) {
// get the Nodes and Links to be laid out
var parts = this.collectParts(coll);
// Start the layout at the arrangement origin, a property inherited from Layout
var x = this.arrangementOrigin.x;
var y = this.arrangementOrigin.y;
var offset = this.offset;
var it = parts.iterator;
while (it.next()) {
var node = it.value;
if (!(node instanceof go.Node)) continue; // ignore Links
node.move(new go.Point(x, y));
x += offset.width;
y += offset.height;
}
}
// end of CascadeLayout
// Regular diagram setup:
diagram.layout = new CascadeLayout();
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "Rectangle",
{ fill: "white" },
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 5 },
new go.Binding("text", "key"))
);
diagram.model = new go.Model([
{ key: "Alpha", color: "lightblue" },
{ key: "Beta", color: "thistle" },
{ key: "Delta", color: "lightcoral" },
{ key: "Gamma", color: "lightgreen" }
]);
</pre>
<script>goCode("example", 300, 200)</script>
</div>
</div>
</body>
</html>
+427
View File
@@ -0,0 +1,427 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Geometry Path Strings -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Geometry Path Strings</h1>
<p>
The <b>GoJS</b> <a>Geometry</a> class controls the "shape" of a <a>Shape</a>,
whereas the <a>Shape.fill</a> and <a>Shape.stroke</a> and other shape properties control the colors and appearance of the shape.
For common shape figures, there are predefined geometries that can be used by setting <a>Shape.figure</a>.
However one can also define custom geometries.
</p>
<p>
One can construct any Geometry by allocating and initializing a <a>Geometry</a> of at least one <a>PathFigure</a> holding some <a>PathSegment</a>s.
But you may find that using the string representation of a Geometry is easier to write and save in a database.
Use the static method <a>Geometry,parse</a> or the <a>Shape.geometryString</a> property to transform a geometry path string into a <a>Geometry</a> object.
</p>
<p>
See samples that make use of Geometries in the <a href="../samples/index.html#geometries">samples index</a>.
</p>
<h2 id="GeometryPathStringSyntax">Geometry Path String Syntax</h2>
<p>
The syntax for a geometry path string is an extension of the SVG path string syntax.
The string consists of a number of commands, each a single letter followed by some command-specific numeric parameters.
</p>
<p>
Below are the possible commands along with the parameters they take.
The parameter notation <code>(x y)+</code> means that the command requires exactly two parameters,
but there can be 1 or more sets of parameters for each command.
For instance, the <code>L (x y)+</code> command can be written as <code>L 10 10 20 20</code> to denote two straight line segments.
</p>
<p>
Commands written with an uppercase letter indicate absolute coordinates;
lowercase commands specify coordinates relative to the last command.
Some commands do not care about case because they do not take coordinates as arguments.
</p>
<ul style="padding-left: 0px; list-style: none;">
<li><pre class="lang-js">M (x y)+</pre> Move commands begin a new subpath in a <a>PathFigure</a>.
One is essential to begin a PathFigure and therefore must be the first segment type in the path string,
with the exception of a Fill command (<code>F</code>) that can precede it.
<p>Additional sets of parameters for a move command are automatically considered Line commands,
so <code>M 10 10 20 20</code> is identical to <code>M 10 10 L 20 20</code>.</li>
<li><pre class="lang-js">L (x y)+</pre> Line command adds a straight line segment from the previous point to the new point.</li>
<li><pre class="lang-js">H (x)+</pre> Horizontal line command specifies only an x value for a straight horizontal line.</li>
<li><pre class="lang-js">V (y)+</pre> Vertical line command specifies only a y value for a straight vertical line.</li>
<li><pre class="lang-js">Q (x1 y1 x y)+</pre> Quadratic Bezier Curves.
<code>x1</code>, <code>y1</code> is the control point.
See <a href="https://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands">SVG Quadratic Bezier command</a> for more details.</li>
<li><pre class="lang-js">T (x y)+</pre> Short-hand Quadratic Bezier Curves.
The control point is calculated based on <a href="https://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands">SVG's path rules.</a></li>
<li><pre class="lang-js">C (x1 y1 x2 y2 x y)+</pre> Cubic Bezier Curves.
<code>x1</code>, <code>y1</code> and <code>x2</code>, <code>y2</code> are the control points.
See <a href="https://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands">SVG Cubic Bezier command</a> for more details.</li>
<li><pre class="lang-js">S (x2 y2 x y)+</pre> Short-hand Cubic Bezier Curves.
The two control points are calculated based on <a href="https://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands">SVG's path rules.</a></li>
<li><pre class="lang-js">A (rx ry x-axis-rotation large-arc-flag sweep-flag x y)+</pre> Elliptical Arcs.
These follow the <a href="https://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands">SVG arc conventions</a>.</li>
<li><pre class="lang-js">Z</pre> <code>Z</code> denotes that the current segment is closed.
This is placed after the last segment of a subpath.
There are no parameters, and case does not matter with this command.</li>
</ul>
<hr />
<p>
For detailed information on the SVG path strings, see the <a href="https://www.w3.org/TR/SVG/paths.html">W3C's page on SVG Paths</a>.
<hr />
<p>
Additionally there are some tokens specific to <b>GoJS</b>:
<ul style="padding-left: 0px; list-style: none;">
<li><pre class="lang-js">B (startAngle, sweepAngle, centerX, centerY, radiusX, radiusY) </pre> Arcs following <b>GoJS</b> canvas arc convention.
These arcs create a new line from the last point in the subpath to the first point of an arc defined by the five arguments.
Unlike all other commands with parameters, multiple sets of parameters are not allowed for B-arcs.
<li><pre class="lang-js">X</pre> Used before <code>M</code> commands to denote separate PathFigures instead of a subpath.
There are no parameters, and case does not matter with this command.
Separate PathFigures are important when different fills are desired per figure component.</li>
<li><pre class="lang-js">F</pre> The existence of this command specifies whether the current PathFigure is filled (true if <code>F</code> is present).
This is placed at the beginning of a figure.
There is an optional parameter that is currently ignored.
Case does not matter with this command.</li>
<li><pre class="lang-js">U</pre> The existence of this command specifies whether the current PathFigure is shadowed (<strong>false</strong> if <code>U</code> is present.
A shadowed PathFigure is the default).
Shadows on shapes (and therefore PathFigures) only exist if <a>Part.isShadowed</a> is set to true on the containing part.
This is placed at the beginning of a figure.
Case does not matter with this command.</li>
</ul>
<h2 id="GeometryPathStringExamples">Geometry Path String Examples</h2>
<p>
Here is a simple usage of a geometry path string when initializing a <a>Shape</a> without setting <a>Shape.figure</a>:
</p>
<pre class="lang-js" id="s1">
diagram.add(
$(go.Node,
$(go.Shape,
{ geometryString: "F M120 0 L80 80 0 50z",
fill: "lightgreen" })));
</pre>
<script> goCode("s1", 600, 140)</script>
<p>
Here is a geometry path string that uses quadratic Bezier curves:
</p>
<pre class="lang-js" id="s2">
diagram.add(
$(go.Node,
$(go.Shape,
{ geometryString: "F M0 0 L100 0 Q150 50 100 100 L0 100 Q50 50 0 0z",
fill: "lightgreen" })));
</pre>
<script> goCode("s2", 600, 140)</script>
<p>
This geometry uses <b>GoJS</b> arcs:
</p>
<pre class="lang-js" id="s3">
diagram.add(
$(go.Node, "Spot",
$(go.Shape,
{ geometryString: "F M0 0 L80 0 B-90 90 80 20 20 20 L100 100 20 100 B90 90 20 80 20 20z",
fill: "lightgreen" }),
$(go.TextBlock, "custom shape")
));
</pre>
<script> goCode("s3", 600, 140)</script>
<p>
The following geometry uses <b>GoJS</b> arcs. Because the <a>Shape</a> is stretched to fit around the <a>TextBlock</a>,
and because the default value of <a>Shape.geometryStretch</a> causes the <a>Geometry</a> to be stretched too,
the custom geometry is also stretched to fit around the text.
</p>
<pre class="lang-js" id="s4">
diagram.add(
$(go.Node, "Auto",
$(go.Shape,
{ geometryString: "F M0 0 L.8 0 B-90 90 .8 .2 .2 .2 L1 1 .2 1 B90 90 .2 .8 .2 .2z",
fill: "lightgreen" }),
$(go.TextBlock, "custom shape",
{ margin: 4 })
));
</pre>
<script>goCode("s4", 600, 140)</script>
<p>
In the following Diagram we use a path string that contains three PathFigures.
Note the <code>X</code> commands separating the figures and the <code>F</code> commands denoting fill.
</p>
<pre class="lang-js" id="a">
diagram.add(
$(go.Part,
$(go.Shape,
{ geometryString:
"F M 0 0 l 30,30 10,10 35,0 0,-35 x m 50 0 l 0,-50 10,0 35,35 x" +
"f m 50 0 l 0,-50 10,0 35,35z",
strokeWidth: 10, stroke: "lightblue", fill: "gray" })
));
</pre>
<script>goCode("a", 600, 140)</script>
The first two PathFigures are open; the first and third figures are filled.
The <code>Z</code> command only closes the PathFigure that it ends.
<p>
In the following Diagram we use a path string that contains four PathFigures, two of which have a shadow.
Note that figures are shadowed by default if the containing Part has <a>Part.isShadowed</a> set to true.
To un-shadow specific path figures we use the <code>U</code> command.
</p>
<pre class="lang-js" id="a2">
diagram.add(
$(go.Part,
{ isShadowed: true, shadowOffset: new go.Point(10, 10) },
$(go.Shape,
{ geometryString:
"F M 0 0 l 30,30 10,10 35,0 0,-35 x u m 50 0 l 0,-50 10,0 35,35 x" +
"u f m 50 0 l 0,-50 10,0 35,35z x m 70 0 l 0,30 30,0 5,-35z",
strokeWidth: 8, stroke: "lightblue", fill: "lightcoral" })
));
</pre>
<script>goCode("a2", 600, 140)</script>
The first and last PathFigures are shadowed; the second and third are unshadowed.
<h3 id="GeometryParse">Geometry.parse</h3>
<p>
Use the static method <a>Geometry,parse</a> to convert a <b>GoJS</b> syntax path string into a <a>Geometry</a>.
</p>
<pre class="lang-js" id="s11">
diagram.add(
$(go.Node, "Horizontal",
$(go.TextBlock, "Custom Triangle:"),
$(go.Shape,
{ geometry: go.Geometry.parse("M120 0 L80 80 0 50z"), // Geometry is not filled
fill: "green", background: "whitesmoke",
stroke: "orange", strokeWidth: 2 })
));
</pre>
<script>goCode("s11", 600, 140)</script>
<p>
Note that even though a <a>Shape.fill</a> is specified, the shape does not appear filled.
This is because the geometry's one <a>PathFigure</a> is not declared to be filled -- there is no <code>F</code> command.
Importing SVG path strings that are filled also requires declaring that the geometry is filled.
There are several ways to do that:
</p>
<ul>
<li>
Call <a>Geometry,fillPath</a> for converting the SVG path string to <b>GoJS</b> syntax before calling <a>Geometry,parse</a>.
For literal SVG path strings it is often easiest just to prefix it with "F ".
</li>
<li>Call <a>Geometry,parse</a> with a second argument that is true.</li>
<li>Modify the <a>Geometry</a> returned by <a>Geometry,parse</a>, by setting <a>PathFigure.isFilled</a> to true on the desired PathFigures.</li>
</ul>
<p>
Here is the same example, but using a filled geometry path string.
</p>
<pre class="lang-js" id="s11a">
diagram.add(
$(go.Node, "Horizontal",
$(go.TextBlock, "Custom Triangle:"),
$(go.Shape,
{ geometry: go.Geometry.parse("F M120 0 L80 80 0 50z"), // Geometry is filled
fill: "green", background: "whitesmoke",
stroke: "orange", strokeWidth: 2 })
));
</pre>
<script>goCode("s11a", 600, 140)</script>
<p>
All Geometry objects have bounds that contain the origin,
so a geometry created with no points at x==0 or y==0 will have extra space to the left of it or above it.
Note how there is extra space in the following node, causing the shape to appear farther away from the text and shifted down:
</p>
<pre class="lang-js" id="s12">
diagram.add(
$(go.Node, "Horizontal",
$(go.TextBlock, "Custom Triangle:"),
$(go.Shape,
{ geometry: go.Geometry.parse("M120 50 L80 80 50 50z", true), // Geometry is filled
fill: "green", background: "whitesmoke",
stroke: "orange", strokeWidth: 2 })
));
</pre>
<script>goCode("s12", 600, 140)</script>
<p>
Often when importing SVG shapes created by drawing applications into <b>GoJS</b> we do not want any extra space above or to the left, so we need to normalize the geometry.
There is a function for this, <a>Geometry.normalize</a>, which modifies the Geometry's points in-place and returns a Point describing the amount they were offset.
</p>
<pre class="lang-js" id="s12a">
var geo = go.Geometry.parse("M120 50 L80 80 50 50z", true);
geo.normalize();
diagram.add(
$(go.Node, "Horizontal",
$(go.TextBlock, "Custom Triangle:"),
$(go.Shape,
{ geometry: geo, // normalized above
fill: "green", background: "whitesmoke",
stroke: "orange", strokeWidth: 2 })
));
</pre>
<script>goCode("s12a", 600, 140)</script>
<h3 id="ShapeGeometryString">Shape.geometryString</h3>
<p>
The <a>Shape.geometryString</a> property setter parses a given <b>GoJS</b> path string as a Geometry, normalizes it,
sets the <a>Shape.geometry</a> to this new Geometry, and offsets the Shape's position by the amount it was shifted in normalization.
The positioning is useful when the shape is inside a <a>Panel,Position</a> panel.
But when the shape is used in any other kind of panel (thus ignoring the <a>GraphObject.position</a>),
it is still useful to remove the extra space so that the shape fits in well with the other objects in the panel.
</p>
<p>
The example below adds three Parts with Shapes to the diagram.
The first shape uses <a>Geometry,parse</a> to set the Shape's Geometry, the second one uses <a>Geometry,parse</a> and <a>Geometry.normalize</a>.
The third uses <a>Shape.geometryString</a>.
Note the difference in size between the first Part and the other two.
</p>
<pre class="lang-js" id="b">
var pathstring = "M30 100 C 50 50, 70 20, 100 100, 110, 130, 45, 150, 65, 100";
// Just parsing the geometry
diagram.add(
$(go.Part, "Vertical",
$(go.Shape,
{ geometry: go.Geometry.parse(pathstring),
strokeWidth: 10, stroke: "lightcoral",
background: "whitesmoke" }),
$(go.TextBlock, "parse")
));
// Parsing the geometry and normalizing it
var geo = go.Geometry.parse(pathstring);
geo.normalize();
diagram.add(
$(go.Part, "Vertical",
$(go.Shape,
{ geometry: geo,
strokeWidth: 10, stroke: "lightgreen",
background: "whitesmoke" }),
$(go.TextBlock, "parse/normalize")
));
// Using geometryString to parse and normalize the geometry
diagram.add(
$(go.Part, "Vertical",
$(go.Shape,
{ geometryString: pathstring,
strokeWidth: 10, stroke: "lightblue",
background: "whitesmoke" }),
$(go.TextBlock, "geometryString")
));
diagram.layout = $(go.GridLayout);
// Select them all to more easily see their sizes
diagram.commandHandler.selectAll();
</pre>
<script>goCode("b", 600, 180)</script>
<h2 id="FlippingGeometriesHorizontallyAndVertically">Flipping Geometries Horizontally and Vertically</h2>
<p>
GoJS Geometries have several methods for modifying the geometry's points by a transformation matrix.
We can use these methods to flip or mirror the geometries if needed.
</p>
<p>
<code>geometry.scale(-1, 1)</code> will flip a geometry horizontally.
<code>geometry.scale(1, -1)</code> will flip a geometry vertically.
</p>
<pre class="lang-js" id="b2">
var pathstring = "M30 100 C 50 50, 70 20, 100 100, 110, 130, 45, 150, 65, 100";
var geo = go.Geometry.parse(pathstring);
geo.normalize();
diagram.add(
$(go.Part, "Vertical",
$(go.Shape,
{ geometry: geo,
strokeWidth: 10, stroke: "lightgreen",
background: "whitesmoke" }),
$(go.TextBlock, "geometry from string\n(normalized)")
));
var geo2 = geo.copy();
geo2.scale(-1, 1); // flips a geometry horizontally
diagram.add(
$(go.Part, "Vertical",
$(go.Shape,
{ geometry: geo2,
strokeWidth: 10, stroke: "lightgreen",
background: "whitesmoke" }),
$(go.TextBlock, "flipped horizontally")
));
var geo3 = geo.copy();
geo3.scale(1, -1); // flips a geometry vertically
diagram.add(
$(go.Part, "Vertical",
$(go.Shape,
{ geometry: geo3,
strokeWidth: 10, stroke: "lightgreen",
background: "whitesmoke" }),
$(go.TextBlock, "flipped vertically")
));
diagram.layout = $(go.GridLayout);
</pre>
<script>goCode("b2", 600, 180)</script>
<h2 id="ConvertingPathStrings">Converting Path Strings</h2>
<p>
The static method <a>Geometry,stringify</a> can be used to output a Geometry as a string.
This string will have the <b>GoJS</b> path string syntax.
You can use Geometry.stringify and Geometry.parse to data bind custom shape geometries.
<p>
<code>Geometry.parse(Geometry.stringify(myGeometry))</code> will return a geometry equal to <code>myGeometry</code>,
though if myGeometry was created from a string, the string itself is not guaranteed to be the same.
If you merely want to copy a Geometry you should use <a>Geometry.copy</a>.
<p>
<pre class="lang-js">
// These path strings represent identical geometries:
var a = "m0 0 t 50 50, q 40 20, 50 10 h 10 v -23 l 45, 5, 65, 100"
var b = "M0 0 Q0 0 50 50 Q90 70 100 60 L110 60 L110 37 L155 42 L220 142"
go.Geometry.stringify(Geometry.parse(a)); // returns the string in b
go.Geometry.stringify(Geometry.parse(b)); // returns the string in b
</pre>
<p>
Because of the additional non-SVG commands, a string generated from <a>Geometry,stringify</a> will not necessarily be a valid SVG path.
</p>
<p>
The static method <a>Geometry,fillPath</a> takes a path string of either syntax and adds <code>F</code> tokens before each PathFigure that does not have them.
Because SVG path strings are not considered to be "filled" by themselves,
if you are converting an SVG Path shape to <b>GoJS</b> you will want to call <a>Geometry,fillPath</a> on the SVG string.
</p>
<pre class="lang-js">
go.Geometry.fillPath("M0 0 L20 20 L20 0");
// returns "F M0 0 L20 20 L20 0"
</pre>
The result can then be passed to <a>Geometry,parse</a> or <a>Shape.geometryString</a>.
<h2 id="ParameterizedGeometries">Parameterized Geometries</h2>
<p>
Although individual <a>Geometry</a> objects cannot be dynamically parameterized based on the intended size or other properties,
the <a>Shape</a> class does support such parameterization via <a>Shape,defineFigureGenerator</a>.
When you set or bind the <a>Shape.figure</a> property, the shape will call the named figure generator
to generate a Geometry appropriate for the desired width and height and other Shape properties.
</p>
<p>
You can see the definitions of all of the predefined figures in the extensions file:
<a href="../extensions/Figures.js" target="_blank">Figures.js</a>.
</p>
</div>
</div>
</body>
</html>
+324
View File
@@ -0,0 +1,324 @@
/* Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved. */
// Load necessary scripts:
if (window.require) {
// declare required libraries and ensure Bootstrap's dependency on jQuery
require.config({
paths: {
"highlight": "../assets/js/highlight",
"jquery": "../assets/js/jquery.min", // 1.11.3
"bootstrap": "../assets/js/bootstrap.min"
},
shim: {
"bootstrap": ["jquery"]
}
});
require(["highlight", "jquery", "bootstrap"], function () { });
} else {
function goLoadSrc(filenames) {
var scripts = document.getElementsByTagName("script");
var script = null;
for (var i = 0; i < scripts.length; i++) {
if (scripts[i].src.indexOf("goIntro") > 0) {
script = scripts[i];
break;
}
}
for (var i = 0; i < arguments.length; i++) {
var filename = arguments[i];
if (!filename) continue;
var selt = document.createElement("script");
selt.async = false;
selt.defer = false;
selt.src = "../assets/js/" + filename;
script.parentNode.insertBefore(selt, script.nextSibling);
script = selt;
}
}
goLoadSrc("highlight.js", (window.jQuery ? "" : "jquery.min.js"), "bootstrap.min.js");
}
var head = document.getElementsByTagName("head")[0];
var link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";
link.href = "../assets/css/bootstrap.min.css";
head.appendChild(link);
link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";
link.href = "../assets/css/highlight.css";
head.appendChild(link);
link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";
link.href = "../assets/css/main.css";
head.appendChild(link);
// Create a DIV and add it to the document just after the PRE element.
// Evaluate the JavaScript text that is in the PRE element in order to initialize the Diagram.
function goCode(pre, w, h, diagramclass, parentid) {
if (diagramclass === undefined) diagramclass = go.Diagram;
if (typeof pre === "string") pre = document.getElementById(pre);
var div = document.createElement("div");
div.style.width = w + "px";
div.style.height = h + "px";
div.className = "diagramStyling";
var parent;
if (parentid === undefined) {
parent = pre.parentNode;
} else {
parent = document.getElementById(parentid);
}
parent.appendChild(div);
// temporarily bind "diagram" to the main Diagram for the DIV, and "$" to go.GraphObject.make
var f = eval("(function (diagram, $) {" + pre.textContent + "})");
f(new diagramclass(div), go.GraphObject.make);
}
// Traverse the whole document and replace <a>TYPENAME</a> with:
// <a href="../api/symbols/TYPENAME.html">TYPENAME</a>
// and <a>TYPENAME.MEMBERNAME</a> with:
// <a href="../api/symbols/TYPENAME.html#MEMBERNAME">TYPENAME.MEMBERNAME</a>
function goIntro() {
_traverseDOM(document);
// add class to main content
var content = document.getElementById('content');
content.className = "col-md-10";
// side navigation
var navindex = document.createElement('div');
navindex.id = "navindex";
navindex.className = "col-md-2";
navindex.innerHTML = myMenu;
var container = document.getElementById('container');
container.insertBefore(navindex, content);
// top navbar
var navbar = document.createElement('div');
navbar.innerHTML = myNavbar;
document.body.insertBefore(navbar, container);
// When the page loads, change the class of li's to highlight the current page
var url = window.location.href;
var lindex = url.lastIndexOf('/');
url = url.slice(lindex + 1).toLowerCase();
var lis = document.getElementById("sections").getElementsByTagName("li");
var l = lis.length;
var currentindex = -1;
for (var i = 0; i < l; i++) {
var lowerhref = lis[i].childNodes[0].href.toLowerCase();
if (lowerhref.indexOf('intro') === -1) continue;
if (lowerhref.indexOf('/' + url) !== -1) {
currentindex = i;
lis[i].childNodes[0].className = "selected";
break;
}
}
// prev & next page navigation
var pagenav = document.createElement("div");
var nav = "<div>";
if (currentindex > 0) {
var prevurl = lis[currentindex - 1].childNodes[0].href.toLowerCase();
nav += "<a href='" + prevurl + "'>&lt;Previous Intro Page</a>";
} else {
nav += "<a href='../learn/index.html'>&lt;Learn</a>";
}
if (currentindex < lis.length - 1) {
var nexturl = lis[currentindex + 1].childNodes[0].href.toLowerCase();
nav += "<a style='float:right' href='" + nexturl + "'>Next Intro Page&gt;</a>";
}
nav += "</div>";
pagenav.innerHTML = nav;
content.appendChild(pagenav);
// footer
var footer = document.createElement("div");
footer.className = "footer";
var msg = "Copyright &copy; 1998-2020 by Northwoods Software Corporation.";
if (window.go && go.version) {
msg = "GoJS&reg; version " + go.version + ". " + msg;
}
footer.innerHTML = msg;
content.appendChild(footer);
}
function _traverseDOM(node) {
if (node.nodeType === 1 && node.nodeName === "A" && !node.getAttribute("href")) {
var inner = node.innerHTML;
var text = [inner];
var isStatic = false;
if (inner.indexOf(",") > 0) {
text = inner.split(",");
isStatic = true;
node.innerHTML = inner.replace(",", ".");
} else {
text = inner.split(".");
}
if (text.length === 1) {
node.setAttribute("href", "../api/symbols/" + text[0] + ".html");
node.setAttribute("target", "api");
} else if (text.length === 2) {
node.setAttribute("href", "../api/symbols/" + text[0] + ".html" + "#" + (isStatic ? "static-" : "") + text[1]);
node.setAttribute("target", "api");
} else {
alert("Unknown API reference: " + node.innerHTML);
}
}
if (node.nodeType === 1 &&
(node.nodeName === "H2" || node.nodeName === "H3" || node.nodeName === "H4") &&
node.id) {
node.addEventListener("click", function (e) {
window.location.hash = "#" + node.id;
});
}
for (var i = 0; i < node.childNodes.length; i++) {
_traverseDOM(node.childNodes[i]);
}
}
(function (i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {
(i[r].q = i[r].q || []).push(arguments)
}, i[r].l = 1 * new Date(); a = s.createElement(o),
m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m)
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
ga('create', 'UA-1506307-5', 'auto');
ga('send', 'pageview');
//<![CDATA[
var myMenu = '\
<div class="sidebar-nav">\
<div class="navbar navbar-default" role="navigation">\
<div class="navbar-header">\
<div class="navheader-container">\
<div class="navheader-collapse" data-toggle="collapse" data-target="#DiagramNavbar">\
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#DiagramNavbar">\
<span class="sr-only">Toggle navigation</span>\
<span class="icon-bar"></span>\
<span class="icon-bar"></span>\
<span class="icon-bar"></span>\
</button>\
</div>\
<span class="navbar-brand">Introduction</span>\
</div>\
</div>\
<div id="DiagramNavbar" class="navbar-collapse collapse sidebar-navbar-collapse">\
<ul id="sections" class="classList nav navbar-nav">\
<li><a href="index.html">Basics</a></li>\
<li><a href="buildingObjects.html">Building Parts</a></li>\
<li><a href="usingModels.html">Using Models</a></li>\
<li><a href="dataBinding.html">Data Binding</a></li>\
<li><a href="react.html">GoJS with React</a></li>\
<li><a href="angular.html">GoJS with Angular</a></li>\
<li><a href="textBlocks.html">TextBlocks</a></li>\
<li><a href="shapes.html">Shapes</a></li>\
<li><a href="pictures.html">Pictures</a></li>\
<li><a href="panels.html">Panels</a></li>\
<li><a href="tablePanels.html">Table Panels</a></li>\
<li><a href="brush.html">Brushes</a></li>\
<li><a href="sizing.html">Sizing Objects</a></li>\
<li><a href="itemArrays.html">Item Arrays</a></li>\
<li><a href="changedEvents.html">Changed Events</a></li>\
<li><a href="transactions.html">Transactions</a></li>\
<li><a href="viewport.html">Coordinates</a></li>\
<li><a href="initialView.html">Initial View</a></li>\
<li><a href="collections.html">Collections</a></li>\
<li><a href="links.html">Links</a></li>\
<li><a href="linkLabels.html">Link Labels</a></li>\
<li><a href="connectionPoints.html">Link Points</a></li>\
<li><a href="ports.html">Ports</a></li>\
<li><a href="nodes.html">Nodes</a></li>\
<li><a href="debugging.html">Debugging</a></li>\
<li><a href="layouts.html">Layouts</a></li>\
<li><a href="trees.html">Trees</a></li>\
<li><a href="subtrees.html">SubTrees</a></li>\
<li><a href="groups.html">Groups</a></li>\
<li><a href="subgraphs.html">SubGraphs</a></li>\
<li><a href="sizedGroups.html">Sized Groups</a></li>\
<li><a href="selection.html">Selection</a></li>\
<li><a href="highlighting.html">Highlighting</a></li>\
<li><a href="animation.html">Animation</a></li>\
<li><a href="toolTips.html">ToolTips</a></li>\
<li><a href="contextmenus.html">Context Menus</a></li>\
<li><a href="events.html">Diagram Events</a></li>\
<li><a href="tools.html">Tools</a></li>\
<li><a href="commands.html">Commands</a></li>\
<li><a href="permissions.html">Permissions</a></li>\
<li><a href="validation.html">Validation</a></li>\
<li><a href="HTMLInteraction.html">HTML Interaction</a></li>\
<li><a href="layers.html">Layers &amp; Z-ordering</a></li>\
<li><a href="palette.html">Palette</a></li>\
<li><a href="overview.html">Overview</a></li>\
<li><a href="resizing.html">Resizing Diagrams</a></li>\
<li><a href="replacingDeleting.html">Replacing and Deleting</a></li>\
<li><a href="buttons.html">Buttons</a></li>\
<li><a href="templateMaps.html">Template Maps</a></li>\
<li><a href="legends.html">Legends and Titles</a></li>\
<li><a href="extensions.html">Extensions</a></li>\
<li><a href="geometry.html">Geometry Strings</a></li>\
<li><a href="grids.html">Grid Patterns</a></li>\
<li><a href="graduatedPanels.html">Graduated Panels</a></li>\
<li><a href="makingImages.html">Diagram Images</a></li>\
<li><a href="makingSVG.html">Diagram SVG</a></li>\
<li><a href="printing.html">Printing</a></li>\
<li><a href="serverSideImages.html">Server-side Images</a></li>\
<li><a href="nodeScript.html">GoJS in Node.js</a></li>\
<li><a href="storage.html">Storage</a></li>\
<li><a href="performance.html">Performance</a></li>\
<li><a href="source.html">Building from Source</a></li>\
<li><a href="deployment.html">Deployment</a></li>\
</ul>\
</div>\
</div>\
</div>';
//]]>
//<![CDATA[
var myNavbar = '\
<!-- non-fixed navbar -->\
<nav id="non-fixed-nav" class="navbar navbar-inverse navbar-top">\
<div class="container-fluid">\
<div class="navbar-header">\
<div class="navheader-container">\
<div class="navheader-collapse" data-toggle="collapse" data-target="#navbar">\
<a id="toplogo" class="navbar-brand" href="../index.html">GoJS</a>\
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbar">\
<span class="sr-only">Toggle navigation</span>\
<span class="icon-bar"></span>\
<span class="icon-bar"></span>\
<span class="icon-bar"></span>\
</button>\
</div>\
</div>\
</div>\
<div id="navbar" class="navbar-collapse collapse">\
<ul class="nav navbar-nav navbar-right">\
<li><a href="../index.html">Home</a></li>\
<li><a href="../learn/index.html">Learn</a></li>\
<li><a href="../samples/index.html">Samples</a></li>\
<li><a href="../intro/index.html">Intro</a></li>\
<li><a href="../api/index.html" target="api">API</a></li>\
<li><a href="https://www.nwoods.com/components/evalform.htm">Register</a></li>\
<li><a href="../download.html">Download</a></li>\
<li><a href="https://forum.nwoods.com/c/gojs">Forum</a></li>\
<li><a href="https://www.nwoods.com/contact.html" onclick="ga(\'send\',\'event\',\'Outbound Link\',\'click\',\'contact\');">Contact</a></li>\
<li class="buy"><a href="https://www.nwoods.com/sales/index.html" onclick="ga(\'send\',\'event\',\'Outbound Link\',\'click\',\'buy\');">Buy</a></li>\
<li class="activate"><a href="https://www.nwoods.com/app/activate.aspx?sku=gojs">Activate</a></li>\
</ul>\
</div><!--/.nav-collapse -->\
</div>\
</nav>';
//]]>
+669
View File
@@ -0,0 +1,669 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Graduated Panels -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
<script src="../extensions/Figures.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Graduated Panels</h1>
<p>
The "Graduated" Panel, <a>Panel,Graduated</a>,
draws regular tick marks and/or text labels along the stroke of the main child <a>Shape</a>.
Graduated Panels can be considered scales showing a range of values.
</p>
<p>
For examples of Graduated Panels see the <a href="../samples/timeline.html">Timeline</a>,
<a href="../samples/thermometer.html">Thermometer</a>,
<a href="../samples/instrumentGauge.html">Instrument Gauge</a>,
and <a href="../samples/ruleredDiagram.html">Rulered Diagram</a> samples.
</p>
<h2 id="SimpleGraduatedPanels">Simple Graduated Panels</h2>
<p>
Similar to Auto and Spot Panels, Graduated Panels should have two or more elements in them.
Elements must be either <a>Shape</a>s or <a>TextBlock</a>s.
The main Shape element may be declared by setting <a>GraphObject.isPanelMain</a> to true;
but no such setting is needed if it is the very first element of the panel.
Shapes and TextBlocks, other than the main Shape, basically act as templates for the drawing of each tick mark and label.
</p>
<p>
Tick mark <a>Shape</a>s within a Graduated Panel should have a measured size, either by setting a <a>GraphObject.desiredSize</a>
(or <code>width</code> and <code>height</code> properties), or by setting its <a>Shape.geometry</a>.
For basic tick marks drawn normal to the main Shape's path,
it is easiest to use a simple vertical line geometry string: <code>M0 0 V10</code>.
The height of the geometry will determine the length of the tick mark.
</p>
<pre class="lang-js" id="graduatedSimple">
diagram.add(
// all Parts are Panels
$(go.Part, go.Panel.Graduated, // or "Graduated"
$(go.Shape, { geometryString: "M0 0 H400" }), // the main shape, a horizontal line
$(go.Shape, { geometryString: "M0 0 V10" }) // a tick mark, a vertical line
));
</pre>
<script>goCode("graduatedSimple", 500, 100)</script>
<p>
Any shape, including custom geometries, can be used as the main Shape or as a tick mark Shape of a Graduated Panel.
</p>
<pre class="lang-js" id="graduatedCircle">
diagram.add(
$(go.Part, "Graduated",
{ background: "transparent" }, // make panel pickable
// main shape is a whole circle
$(go.Shape, "Circle",
{ fill: null, desiredSize: new go.Size(150, 150) }),
// tick shape is a double line
$(go.Shape, { geometryString: "M0 0 V10 M3 0 V10" })
));
</pre>
<script>goCode("graduatedCircle", 200, 200)</script>
<p>
Graduated Panels can also be labeled with TextBlocks denoting the values along the scale.
Often, these will be offset from the main stroke using <a>GraphObject.segmentOffset</a>, as one would
with Link labels, so that the text does not overlap the main stroke.
More detail on placing labels is in the "Appearance" section below.
</p>
<pre class="lang-js" id="graduatedLabels">
diagram.add(
$(go.Part, "Graduated",
{ background: "transparent" }, // make panel pickable
$(go.Shape, { geometryString: "M0 0 H400" }), // the main shape
$(go.TextBlock, { segmentOffset: new go.Point(0, 12) }) // tick labels
));
</pre>
<script>goCode("graduatedLabels", 500, 100)</script>
<h2 id="GraduatedPanelProperties">Graduated Panel Properties</h2>
<p>
There are a number of properties that govern the appearance of tick marks and labels.
</p>
<h3 id="TickMarkValues">Tick Mark Values</h3>
<p>
The graduated values of a Graduated Panel will range on a linear scale from the start of the
main shape's stroke to the end of the stroke.
The values and frequency of tick marks and labels are governed by a few properties:
</p>
<ul>
<li><a>Panel.graduatedMin</a>
- the minimum value represented on the scale, at the beginning of the stroke of the main shape</li>
<li><a>Panel.graduatedMax</a>
- the maximum value represented on the scale, at the end of the main shape</li>
<li><a>Panel.graduatedTickBase</a>
- the value of the "origin" tick mark, the first tick mark if it is the same as graduatedMin</li>
<li><a>Panel.graduatedTickUnit</a>
- tick marks are positioned at multiples of the graduatedTickUnit added to the graduatedTickBase</li>
<li><a>Shape.interval</a>/<a>TextBlock.interval</a>
- a multiple of the graduatedTickUnit at which to draw a tick or label</li>
</ul>
<p>
Graduated Panels can have multiple Shapes as tick marks and multiple TextBlocks as labels,
with the interval property controlling at what multiples of the <code>graduatedTickUnit</code> they are drawn.
In many of the examples below, larger ticks are drawn at intervals of 4; some have an interval of 5.
</p>
<p>
A <code>graduatedMin</code> of <code>0</code>, <code>graduatedMax</code> of <code>77</code>,
<code>graduatedTickBase</code> of <code>0</code>, <code>graduatedTickUnit</code> of <code>2.5</code>,
and intervals of 4 result in a scale that might appear as:
</p>
<pre class="lang-js" id="graduatedVals1">
diagram.add(
$(go.Part, "Graduated",
{
graduatedMin: 0, graduatedMax: 77,
graduatedTickBase: 0, graduatedTickUnit: 2.5,
background: "transparent"
},
$(go.Shape, { geometryString: "M0 0 H400" }), // the main Shape
// a short, frequent tick mark
$(go.Shape, { geometryString: "M0 0 V5" }),
// a longer tick mark every four ticks
$(go.Shape, { geometryString: "M0 0 V10", interval: 4 }),
// text label only every four ticks, with a vertical offset
$(go.TextBlock, { segmentOffset: new go.Point(0, 12), interval: 4 })
));
</pre>
<script>goCode("graduatedVals1", 500, 100)</script>
<p>
Changing <code>graduatedMin</code> to <code>-23</code> results in:
</p>
<pre class="lang-js" id="graduatedVals2">
diagram.add(
$(go.Part, "Graduated",
{
graduatedMin: -23, graduatedMax: 77,
graduatedTickBase: 0, graduatedTickUnit: 2.5,
background: "transparent"
},
$(go.Shape, { geometryString: "M0 0 H400" }), // the main Shape
$(go.Shape, { geometryString: "M0 0 V5" }), // short tick mark
$(go.Shape, { geometryString: "M0 0 V10", interval: 4 }), // long tick mark
$(go.TextBlock, { segmentOffset: new go.Point(0, 12), interval: 4 }) // labels
));
</pre>
<script>goCode("graduatedVals2", 500, 100)</script>
<p>
The range from the min to the max value (<a>Panel.graduatedRange</a>) has increased from 77 to 100,
so the tick marks are closer to each other for the same length main path.
</p>
<p>
Changing <code>graduatedTickBase</code> to <code>1.2</code> results in:
</p>
<pre class="lang-js" id="graduatedVals3">
diagram.add(
$(go.Part, "Graduated",
{
graduatedMin: -23, graduatedMax: 77,
graduatedTickBase: 1.2, graduatedTickUnit: 2.5,
background: "transparent"
},
$(go.Shape, { geometryString: "M0 0 H400" }), // the main Shape
$(go.Shape, { geometryString: "M0 0 V5" }), // short tick mark
$(go.Shape, { geometryString: "M0 0 V10", interval: 4 }), // long tick mark
$(go.TextBlock, { segmentOffset: new go.Point(0, 12), interval: 4 }) // labels
));
</pre>
<script>goCode("graduatedVals3", 500, 100)</script>
<p>
Basically, the "origin" for the scale has shifted slightly, even though the end values remain the same.
There will always be a tick mark at the <code>graduatedTickBase</code>
if that value is within the range of the graduated scale.
</p>
<p>
Doubling the <code>graduatedTickUnit</code> to <code>5</code> results in:
</p>
<pre class="lang-js" id="graduatedVals4">
diagram.add(
$(go.Part, "Graduated",
{
graduatedMin: -23, graduatedMax: 77,
graduatedTickBase: 1.2, graduatedTickUnit: 5,
background: "transparent"
},
$(go.Shape, { geometryString: "M0 0 H400" }),
$(go.Shape, { geometryString: "M0 0 V5" }), // short tick mark
$(go.Shape, { geometryString: "M0 0 V10", interval: 4 }), // long tick mark
$(go.TextBlock, { segmentOffset: new go.Point(0, 12), interval: 4 }) // labels
));
</pre>
<script>goCode("graduatedVals4", 500, 100)</script>
<p>
Doubling the tick unit halves the number of ticks for the same length path, but again the end values are unchanged.
</p>
<p>
Changing <code>graduatedTickBase</code> back to <code>0</code> and the intervals to <code>5</code> results in:
</p>
<pre class="lang-js" id="graduatedVals5">
diagram.add(
$(go.Part, "Graduated",
{
graduatedMin: -23, graduatedMax: 77,
graduatedTickBase: 0, graduatedTickUnit: 5,
background: "transparent"
},
$(go.Shape, { geometryString: "M0 0 H400" }),
$(go.Shape, { geometryString: "M0 0 V5" }), // short tick mark
$(go.Shape, { geometryString: "M0 0 V10", interval: 5 }), // long tick mark
$(go.TextBlock, { interval: 5, segmentOffset: new go.Point(0, 12) })
));
</pre>
<script>goCode("graduatedVals5", 500, 100)</script>
<p>
You can have more than one label. For example, small text that is more frequent than larger text:
</p>
<pre class="lang-js" id="graduated2Labels">
diagram.add(
$(go.Part, "Graduated",
{
graduatedMin: 0, graduatedMax: 140,
graduatedTickBase: 0, graduatedTickUnit: 5,
background: "transparent"
},
$(go.Shape, { geometryString: "M0 0 H450" }), // longer line
$(go.Shape, { geometryString: "M0 0 V5" }),
$(go.Shape, { geometryString: "M0 0 V10", interval: 4 }),
// minor label
$(go.TextBlock, { interval: 2, segmentOffset: new go.Point(0, 8),
stroke: "blue", font: "7pt sans-serif" }),
// major label
$(go.TextBlock, { interval: 4, segmentOffset: new go.Point(0, 12),
stroke: "red", font: "bold 12pt sans-serif" })
));
</pre>
<script>goCode("graduated2Labels", 500, 100)</script>
<h3 id="TickMarkAppearance">Tick Mark Appearance</h3>
<p>
The appearance of tick marks relative to the main shape path is controlled by a few properties:
</p>
<ul>
<li><a>Shape.graduatedStart</a>/<a>TextBlock.graduatedStart</a>
- the fractional distance along the main stroke at which drawing this tick or label may begin</li>
<li><a>Shape.graduatedEnd</a>/<a>TextBlock.graduatedEnd</a>
- the fractional distance along the main stroke beyond which it will not draw this tick or label</li>
<li><a>GraphObject.alignmentFocus</a>
- the spot on the tick or label to align with the calculated path points, defaulting to the top center</li>
<li><a>GraphObject.segmentOffset</a>
- how much to offset a TextBlock label from the main stroke -- the Y value specifies distance from the path</li>
<li><a>GraphObject.segmentOrientation</a>
- how to rotate a TextBlock label relative to the main stroke</li>
</ul>
<p>
Only TextBlock labels should set the <a>GraphObject.segmentOffset</a> or <a>GraphObject.segmentOrientation</a>.
They have no impact on the main shape or tick shapes.
These GraphObject properties are also commonly used to place Link labels,
as seen in the <a href="linkLabels.html">Introduction page on Link labels</a>,
and are used by Graduated Panels in a similar manner.
</p>
<p>
Setting <code>graduatedStart</code> and/or <code>graduatedEnd</code> allows for drawing ticks only along part of the main stroke:
</p>
<pre class="lang-js" id="graduatedAppr1">
diagram.add(
$(go.Part, "Graduated",
$(go.Shape, { geometryString: "M0 0 H400" }),
$(go.Shape, { geometryString: "M0 0 V10", graduatedStart: .25, graduatedEnd: .75 })
));
</pre>
<script>goCode("graduatedAppr1", 500, 100)</script>
<p>
In this case, tick marks are now only drawn in the middle half of the main shape.
</p>
<p>
Setting <code>alignmentFocus</code> to <code>go.Spot.Bottom</code> will cause the ticks to have their bottoms aligned to the main stroke:
</p>
<pre class="lang-js" id="graduatedAppr2">
diagram.add(
$(go.Part, "Graduated",
$(go.Shape, { geometryString: "M0 0 H400" }),
$(go.Shape, { geometryString: "M0 0 V10", alignmentFocus: go.Spot.Bottom })
));
</pre>
<script>goCode("graduatedAppr2", 500, 100)</script>
<p>
Setting <code>alignmentFocus</code> to <code>go.Spot.Center</code> will cause the ticks to be centered across the path:
</p>
<pre class="lang-js" id="graduatedAppr21">
diagram.add(
$(go.Part, "Graduated",
$(go.Shape, { geometryString: "M0 0 H400" }),
$(go.Shape, { geometryString: "M0 0 V10 M0 20 V30", alignmentFocus: go.Spot.Center })
));
</pre>
<script>goCode("graduatedAppr21", 500, 100)</script>
<p>
Note the gap in the geometry of the shape.
</p>
<p>
Setting <code>segmentOffset</code> for labels can make them more readable near tick marks:
</p>
<pre class="lang-js" id="graduatedAppr3">
diagram.add(
$(go.Part, "Graduated",
$(go.Shape, { geometryString: "M0 0 H400" }),
$(go.Shape, { geometryString: "M0 0 V10" }),
// offset to display below ticks
$(go.TextBlock, { segmentOffset: new go.Point(0, 12) })
));
</pre>
<script>goCode("graduatedAppr3", 500, 100)</script>
<p>
Setting <code>segmentOrientation</code> for labels can alter the angle at which they are drawn relative to the main stroke:
</p>
<pre class="lang-js" id="graduatedAppr4">
diagram.add(
$(go.Part, "Graduated",
$(go.Shape, { geometryString: "M0 0 H400" }),
$(go.Shape, { geometryString: "M0 0 V10" }),
// change the angle of the text
$(go.TextBlock, { segmentOrientation: go.Link.OrientMinus90 })
));
</pre>
<script>goCode("graduatedAppr4", 500, 100)</script>
<p>
Note that the top-center point of each label is exactly at the point along the path for that value.
</p>
<p>
Combining these two properties and re-aligning the tick marks:
</p>
<pre class="lang-js" id="graduatedAppr5">
diagram.add(
$(go.Part, "Graduated",
$(go.Shape, { geometryString: "M0 0 H400" }),
$(go.Shape, { geometryString: "M0 0 V10", alignmentFocus: go.Spot.Bottom }),
$(go.TextBlock,
{
alignmentFocus: go.Spot.Left,
segmentOffset: new go.Point(0, -12),
segmentOrientation: go.Link.OrientMinus90
}
)
));
</pre>
<script>goCode("graduatedAppr5", 500, 100)</script>
<p>
These properties behave similarly to Link labels, in that they respond to the direction of the main stroke.
For example, let us turn the main shape so that it goes diagonally down from the top-left to the bottom-right.
</p>
<pre class="lang-js" id="graduatedApprDiag">
diagram.add(
$(go.Part, "Graduated",
$(go.Shape, { geometryString: "M0 0 L285 285" }),
$(go.Shape, { geometryString: "M0 0 V10", alignmentFocus: go.Spot.Bottom }),
$(go.TextBlock,
{
alignmentFocus: go.Spot.Left,
segmentOffset: new go.Point(0, -12),
segmentOrientation: go.Link.OrientMinus90
}
)
));
</pre>
<script>goCode("graduatedApprDiag", 350, 350)</script>
<p>
Now let us try a curve:
</p>
<pre class="lang-js" id="graduatedApprCurve">
diagram.add(
$(go.Part, "Graduated",
$(go.Shape, "Curve1", { desiredSize: new go.Size(285, 285) }),
$(go.Shape, { geometryString: "M0 0 V10", alignmentFocus: go.Spot.Bottom }),
$(go.TextBlock,
{
alignmentFocus: go.Spot.Left,
segmentOffset: new go.Point(0, -12),
segmentOrientation: go.Link.OrientMinus90
}
)
));
</pre>
<script>goCode("graduatedApprCurve", 350, 350)</script>
<p>
Here's another commonplace configuration:
</p>
<pre class="lang-js" id="graduatedApprCurve2">
diagram.add(
$(go.Part, "Graduated",
$(go.Shape, { geometryString: "M0 0 A120 120 0 0 1 200 0" }), // an arc
$(go.Shape, { geometryString: "M0 0 V10" }),
$(go.TextBlock,
{
segmentOffset: new go.Point(0, 12),
segmentOrientation: go.Link.OrientAlong
}
)
));
</pre>
<script>goCode("graduatedApprCurve2", 350, 100)</script>
<p>
For vertical lines, it's not necessary to rotate the text:
</p>
<pre class="lang-js" id="graduatedApprVert">
diagram.add(
$(go.Part, "Graduated",
$(go.Shape, { geometryString: "M0 0 V400" }),
$(go.Shape, { geometryString: "M0 0 V10", alignmentFocus: go.Spot.Bottom }),
$(go.TextBlock,
{
alignmentFocus: go.Spot.Left,
segmentOffset: new go.Point(0, -12)
}
)
));
</pre>
<script>goCode("graduatedApprVert", 100, 450)</script>
<p>
We can also go from bottom to top:
</p>
<pre class="lang-js" id="graduatedApprVertUp">
diagram.add(
$(go.Part, "Graduated",
$(go.Shape, { geometryString: "M0 0 V-400" }),
$(go.Shape, { geometryString: "M0 0 V10", alignmentFocus: go.Spot.Top }),
$(go.TextBlock,
{
alignmentFocus: go.Spot.Left,
segmentOffset: new go.Point(0, 12)
}
)
));
</pre>
<script>goCode("graduatedApprVertUp", 100, 450)</script>
<p>
Note how the Geometry goes from 0,0 to 0,-400, because negative Y values are higher on the screen/page.
Note how because everything is relative to the path, the tick marks and labels would be on the opposite side,
so we have also changed the <code>alignmentFocus</code> and <code>segmentOffset</code> to have opposite values
from the previous example.
</p>
<p>
Lastly, any angle specified on a label will be respected if orientation is one of <a>Link.OrientNone</a>,
<a>Link.OrientAlong</a>, or <a>Link.OrientUpright</a>. In the case of Along and Upright, the angle will be
added to the slope of the main path at the point of the TextBlock.
</p>
<pre class="lang-js" id="graduatedApprTxtAngle">
diagram.add(
$(go.Part, "Spot",
$(go.Panel, "Graduated",
$(go.Shape, { geometryString: "M0 0 L100 0 100 100 L0 100" }),
$(go.Shape, { geometryString: "M0 0 V10" }),
$(go.TextBlock,
{
interval: 5,
angle: 45,
segmentOffset: new go.Point(0, 12)
}
)
),
$(go.TextBlock, "None")
));
diagram.add(
$(go.Part, "Spot",
$(go.Panel, "Graduated",
$(go.Shape, { geometryString: "M0 0 L100 0 100 100 L0 100" }),
$(go.Shape, { geometryString: "M0 0 V10" }),
$(go.TextBlock,
{
interval: 5,
angle: 45,
segmentOrientation: go.Link.OrientAlong,
segmentOffset: new go.Point(0, 12)
}
)
),
$(go.TextBlock, "Along")
));
diagram.add(
$(go.Part, "Spot",
$(go.Panel, "Graduated",
$(go.Shape, { geometryString: "M0 0 L100 0 100 100 L0 100" }),
$(go.Shape, { geometryString: "M0 0 V10" }),
$(go.TextBlock,
{
interval: 5,
angle: 45,
segmentOrientation: go.Link.OrientUpright,
segmentOffset: new go.Point(0, 12)
}
)
),
$(go.TextBlock, "Upright")
));
</pre>
<script>goCode("graduatedApprTxtAngle", 300, 300)</script>
<p>
With None, the labels are always 45 degrees. With Along, the labels are always 45 degrees more than the slope.
With Upright, the labels are always 45 degrees more than the slope, then rotated upright if necessary.
</p>
<h3 id="FunctionalAppearanceProperties">Functional Appearance Properties</h3>
<p>
There are also some functional properties allowing for further customization of the appearance of ticks and labels.
</p>
<ul>
<li><a>Shape.graduatedSkip</a>/<a>TextBlock.graduatedSkip</a>
- an optional function which returns true for values that should be skipped while drawing a particular tick or label</li>
<li><a>TextBlock.graduatedFunction</a>
- an optional function which converts a value to a string to be displayed at that value -- if not defined, the default returns the value rounded to at most two decimals</li>
</ul>
<p>
Setting <code>graduatedSkip</code> allows for skipping ticks where the supplied function returns true:
</p>
<pre class="lang-js" id="graduatedSkip">
diagram.add(
$(go.Part, "Graduated",
$(go.Shape, { geometryString: "M0 0 H400" }),
$(go.Shape,
{ // skip drawing tick at 30
graduatedSkip: function (v) { return v === 30; },
geometryString: "M0 0 V10"
}
),
$(go.TextBlock, { segmentOffset: new go.Point(0, 12) })
));
</pre>
<script>goCode("graduatedSkip", 500, 100)</script>
<p>
Setting <code>graduatedFunction</code> allows for changing the way labels are displayed:
</p>
<pre class="lang-js" id="graduatedFunc">
diagram.add(
$(go.Part, "Graduated",
$(go.Shape, { geometryString: "M0 0 H400" }),
$(go.Shape, { geometryString: "M0 0 V10" }),
$(go.TextBlock,
{ // always display two decimals
graduatedFunction: function(val) { return val.toFixed(2); },
segmentOffset: new go.Point(0, 12)
}
)
));
</pre>
<script>goCode("graduatedFunc", 500, 100)</script>
<h2 id="GraduatedValueComputations">Graduated Value Computations</h2>
<p>
There are some methods available for computing points along graduated paths:
</p>
<ul>
<li><a>Panel.graduatedPointForValue</a>
- returns the Point along the main shape at some value between graduatedMin and graduatedMax in Panel coordinates</li>
<li><a>Panel.graduatedValueForPoint</a>
- returns the value along the main shape nearest a given Point</li>
</ul>
<p>
In the following example, the red marker uses a <a>Part.dragComputation</a> function that
keeps it along the path of the Graduated Panel using the above functions.
</p>
<pre class="lang-js" id="graduatedPointValueCalc">
var gauge =
$(go.Part, "Auto",
{ location: new go.Point(10, 20) },
$(go.Shape, { fill: "white" }),
$(go.Panel, "Graduated",
{ name: "SCALE", margin: 10 },
$(go.Shape, { name: "PATH", geometryString: "M0 0 A120 120 0 0 1 200 0" }),
$(go.Shape, { geometryString: "M0 0 V10" }),
$(go.TextBlock,
{ segmentOffset: new go.Point(0, 12), segmentOrientation: go.Link.OrientAlong })
)
);
diagram.add(gauge);
var marker =
$(go.Part, "Spot",
{ locationSpot: go.Spot.Center, selectionAdorned: false },
$(go.Shape, "Circle", { fill: "transparent", strokeWidth: 0, cursor: "pointer" }),
$(go.Shape, "Circle", { fill: "red", strokeWidth: 0, width: 8, height: 8 }),
{
dragComputation: function(node, pt) {
var scale = gauge.findObject("SCALE");
var loc = scale.getLocalPoint(pt);
var val = scale.graduatedValueForPoint(loc);
var gpt = scale.graduatedPointForValue(val);
return scale.getDocumentPoint(gpt);
}
}
);
diagram.add(marker);
// once everything has been positioned, give the marker its location
diagram.addDiagramListener("InitialLayoutCompleted", function(e) {
var scale = gauge.findObject("SCALE");
var gpt = scale.graduatedPointForValue(0);
marker.location = scale.getDocumentPoint(gpt);
});
</pre>
<script>goCode("graduatedPointValueCalc", 350, 200)</script>
<p>
As you drag the red circle, you will notice that it always stays on the main shape's stroke.
The computation converts the point to the panel's coordinate system, computes the closest graduated value,
computes the point on the shape geometry for that value, and finally converts it back to document coordinates
for use as the marker's location.
</p>
<p>
Note that for demonstration purposes this example has the marker being a separate Part from the "gauge" Part.
A real gauge would have the marker be part of the gauge as an indicator of a particular value, optionally draggable by the user.
See some examples at <a href="../samples/controlGauges.html" target="_blank">Instrument Controls: Gauges and Meters</a>.
</p>
<h2 id="OtherConsiderations">Other Considerations</h2>
<p>
By default, only the main shape of a Graduated Panel can be used to pick the panel.
As with Grid Panels, a Graduated Panel should have a non-null <code>background</code> if the entire panel needs to be pickable.
You cannot set or bind the <a>Panel.itemArray</a> of a Graduated Panel.
You can set and bind properties on tick <a>Shape</a>s and <a>TextBlock</a> labels
as you can with any other <a>GraphObject</a> properties.
</p>
<pre class="lang-js" id="graduatedBackground">
diagram.add(
$(go.Part, "Graduated", // or "Graduated"
{ background: "white" },
$(go.Shape, { geometryString: "M0 0 H150", stroke: "blue", strokeWidth: 2 }),
$(go.Shape, { geometryString: "M0 0 V20", stroke: "blue", strokeDashArray: [2, 2] })
));
</pre>
<script>goCode("graduatedBackground", 500, 100)</script>
<p>
Events on the tick Shapes and TextBlock labels will be ignored.
Rotating the main shape will not rotate the ticks, just as rotating a Spot Panel's main element
won't rotate its children. Rotation should generally be done at the Panel level. Another similarity
to Spot Panels is that resizing of a Graduated Panel should generally be done on the main shape.
TextBlock labels cannot be edited.
</p>
</div>
</div>
</body>
</html>
+319
View File
@@ -0,0 +1,319 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Grid Patterns -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Grid Patterns</h1>
<p>
It is common to want to display a grid of lines drawn at regular intervals.
You may also want to force dragged parts to be aligned on grid points, and to resize parts to be multiples of the grid cell size.
</p>
<p>
Grids are implemented using a type of <a>Panel</a>, <a>Panel,Grid</a>.
Grid Panels, like most other types of Panels, can be used within <a>Node</a>s or any other kind of <a>Part</a>.
However when they are used as the <a>Diagram.grid</a>, they are effectively infinite in extent.
</p>
<p>
Unlike in other kinds of <a>Panel</a>s, Grid Panel elements must be <a>Shape</a>s that are only used to control how the grid lines or grid bars are drawn.
</p>
<p>
See samples that make use of grids in the <a href="../samples/index.html#grid">samples index</a>.
</p>
<h2 id="DefaultGrid">Default Grid</h2>
<p>
To display a grid pattern in the background of the diagram, you can just make the <a>Diagram.grid</a> visible:
</p>
<pre class="lang-js" id="defaultGrid">
diagram.grid.visible = true;
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "Rectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5},
new go.Binding("text", "key"))
);
var nodeDataArray = [
{ key: "Alpha" }, { key: "Beta" }, { key: "Gamma" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray);
</pre>
<script>goCode("defaultGrid", 600, 150)</script>
<h2 id="GridSnapping">Grid Snapping</h2>
<p>
The <a>DraggingTool</a> and <a>ResizingTool</a> can change their behavior based on the background grid pattern,
if you set the <a>DraggingTool.isGridSnapEnabled</a> and/or <a>ResizingTool.isGridSnapEnabled</a> properties to true.
</p>
<p>
Setting <a>DraggingTool.isGridSnapEnabled</a> to true will not affect disconnected Links,
but these can snap if you define a custom <a>Part.dragComputation</a> to do so on the Link template.
</p>
<pre class="lang-js" id="gridSnapping">
diagram.grid.visible = true;
diagram.toolManager.draggingTool.isGridSnapEnabled = true;
diagram.toolManager.resizingTool.isGridSnapEnabled = true;
diagram.nodeTemplate =
$(go.Node, "Auto",
{ resizable: true },
$(go.Shape, "Rectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5},
new go.Binding("text", "key"))
);
var nodeDataArray = [
{ key: "Alpha" }, { key: "Beta" }, { key: "Gamma" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray);
</pre>
<script>goCode("gridSnapping", 600, 150)</script>
<h2 id="SimpleGridCustomization">Simple Grid Customization</h2>
<p>
You can change the size of the grid cell by setting <a>Panel.gridCellSize</a>:
</p>
<pre class="lang-js" id="biggerGrid">
diagram.grid.visible = true;
diagram.grid.gridCellSize = new go.Size(30, 20);
diagram.toolManager.draggingTool.isGridSnapEnabled = true;
diagram.toolManager.resizingTool.isGridSnapEnabled = true;
diagram.nodeTemplate =
$(go.Node, "Auto",
{ resizable: true },
$(go.Shape, "Rectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5},
new go.Binding("text", "key"))
);
var nodeDataArray = [
{ key: "Alpha" }, { key: "Beta" }, { key: "Gamma" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray);
</pre>
<script>goCode("biggerGrid", 600, 150)</script>
<p>
The cell size used when snapping the locations of Parts during a drag need not be exactly
the same as the background grid's cell size.
The value of <a>DraggingTool.gridSnapCellSize</a> takes precedence over the <a>Panel.gridCellSize</a>.
Note that if <a>DraggingTool.gridSnapCellSize</a> is set but <a>ResizingTool.cellSize</a> is not,
Parts will use the DraggingTool.gridSnapCellSize value when resizing.
</p>
<pre class="lang-js" id="gridSnapping2">
diagram.grid.visible = true;
diagram.toolManager.draggingTool.isGridSnapEnabled = true;
diagram.toolManager.resizingTool.isGridSnapEnabled = true;
// snap to every other point both vertically and horizontally
// (the default background grid has a cell size of 10x10)
diagram.toolManager.draggingTool.gridSnapCellSize = new go.Size(20, 20);
diagram.nodeTemplate =
$(go.Node, "Auto",
{ resizable: true },
$(go.Shape, "Rectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5},
new go.Binding("text", "key"))
);
var nodeDataArray = [
{ key: "Alpha" }, { key: "Beta" }, { key: "Gamma" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray);
</pre>
<script>goCode("gridSnapping2", 600, 150)</script>
<h2 id="CustomGrids">Custom Grids</h2>
<p>
Grid patterns are implemented by the <a>Panel</a> class when its <a>Panel.type</a> is <a>Panel,Grid</a>.
The elements of a Grid Panel must be <a>Shape</a>s whose <a>Shape.figure</a> is one of a small set of known kinds of figures.
The only figures it can accept are: "LineH", "LineV", "BarH", and "BarV".
The two "Line" figures result in stroked lines separating the grid cells;
the two "Bar" figures result in filled rectangles in the grid cells.
</p>
<p>
Here is a simple grid consisting of blue horizontal lines and green vertical lines:
</p>
<pre class="lang-js" id="customBackground">
diagram.grid =
$(go.Panel, go.Panel.Grid, // or "Grid"
{ gridCellSize: new go.Size(25, 25) },
$(go.Shape, "LineH", { stroke: "blue" }),
$(go.Shape, "LineV", { stroke: "green" })
);
</pre>
<script>goCode("customBackground", 600, 150)</script>
<p>
The <a>Shape.interval</a> property is also used by a Grid Panel to determine how frequently a line should be drawn.
The value should be a positive integer specifying how many cells there are between drawings of this particular line.
So if you wanted darker blue and darker green lines every five cells:
</p>
<pre class="lang-js" id="customBackground2">
diagram.grid =
$(go.Panel, "Grid",
{ gridCellSize: new go.Size(10, 10) },
$(go.Shape, "LineH", { stroke: "lightblue" }),
$(go.Shape, "LineV", { stroke: "lightgreen" }),
$(go.Shape, "LineH", { stroke: "blue", interval: 5 }),
$(go.Shape, "LineV", { stroke: "green", interval: 5 })
);
diagram.nodeTemplate =
$(go.Node, "Auto",
{ resizable: true },
$(go.Shape, "Rectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5},
new go.Binding("text", "key"))
);
var nodeDataArray = [
{ key: "Alpha" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray);
</pre>
<script>goCode("customBackground2", 600, 150)</script>
<p>
Note that the Shapes are drawn in the order in which they appear in the Panel,
so you can see that the dark blue horizontal lines are drawn in front of the light green vertical lines,
and that the dark green vertical line crosses in front of the dark blue horizontal lines.
</p>
<p>
Here is the definition of the predefined <a>Diagram.grid</a>:
</p>
<pre class="lang-js" id="standardGrid">
diagram.grid =
$(go.Panel, "Grid",
{
name: "GRID",
visible: false,
gridCellSize: new go.Size(10, 10),
gridOrigin: new go.Point(0, 0)
},
$(go.Shape, "LineH", { stroke: "lightgray", strokeWidth: 0.5, interval: 1 }),
$(go.Shape, "LineH", { stroke: "gray", strokeWidth: 0.5, interval: 5 }),
$(go.Shape, "LineH", { stroke: "gray", strokeWidth: 1.0, interval: 10 }),
$(go.Shape, "LineV", { stroke: "lightgray", strokeWidth: 0.5, interval: 1 }),
$(go.Shape, "LineV", { stroke: "gray", strokeWidth: 0.5, interval: 5 }),
$(go.Shape, "LineV", { stroke: "gray", strokeWidth: 1.0, interval: 10 })
);
diagram.grid.visible = true; // so that this example shows the standard grid
diagram.div.style.background = "white";
</pre>
<script>goCode("standardGrid", 600, 150)</script>
<p>
You can get a green-bar pattern by using the "BarH" figure. Note the use of <a>Shape.fill</a>
instead of <a>Shape.stroke</a> and explicitly setting the <a>GraphObject.height</a>:
</p>
<pre class="lang-js" id="customBackground3">
diagram.grid =
$(go.Panel, "Grid",
{ gridCellSize: new go.Size(50, 50) },
$(go.Shape, "BarH", { fill: "lightgreen", interval: 2, height: 50 })
);
diagram.nodeTemplate =
$(go.Node, "Auto",
{
dragComputation: function(node, pt, gridpt) {
pt.y = Math.round(pt.y/100)*100;
return pt;
}
},
$(go.Shape, "Rectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5},
new go.Binding("text", "key"))
);
var nodeDataArray = [
{ key: "Alpha" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray);
</pre>
<script>goCode("customBackground3", 600, 350)</script>
<p>
This example also demonstrates how one can use the <a>Part.dragComputation</a> property to customize where
the user can drag the node. In this case the <a>Part.location</a>.y is limited to be multiples of 100,
corresponding to the rows of cells filled by the green bars.
</p>
<p>
To get a tablecloth effect, one can use both vertical and horizontal bars with a translucent color:
</p>
<pre class="lang-js" id="customBackground4">
diagram.grid =
$(go.Panel, "Grid",
{ gridCellSize: new go.Size(100, 100) },
$(go.Shape, "BarV", { fill: "rgba(255,0,0,0.1)", width: 50 }),
$(go.Shape, "BarH", { fill: "rgba(255,0,0,0.1)", height: 50 })
);
diagram.toolManager.draggingTool.isGridSnapEnabled = true;
diagram.nodeTemplate =
$(go.Node, "Auto",
{ width: 50, height: 50 },
$(go.Shape, "Rectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5},
new go.Binding("text", "key"))
);
var nodeDataArray = [
{ key: "Alpha" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray);
</pre>
<script>goCode("customBackground4", 600, 350)</script>
<p>
This example limits dragging of all nodes by setting <a>DraggingTool.isGridSnapEnabled</a> to true.
</p>
<p>
Here is an example of using a "Grid" <a>Panel</a> as a regular data bound element in a <a>Node</a>:
</p>
<pre class="lang-js" id="nodeGrid">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ resizable: true, resizeObjectName: "GRID" },
$(go.Shape, "Rectangle", { fill: "transparent" }),
$(go.Panel, "Grid",
{ name: "GRID", desiredSize: new go.Size(100, 100), gridCellSize: new go.Size(20, 20) },
new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify),
new go.Binding("gridCellSize", "cell", go.Size.parse).makeTwoWay(go.Size.stringify),
$(go.Shape, "LineV",
new go.Binding("stroke")),
$(go.Shape, "LineH",
new go.Binding("stroke"))
));
diagram.model = new go.GraphLinksModel([
{ key: "Alpha", cell: "25 25", stroke: "lightgreen" },
{ key: "Beta", size: "150 75", cell: "15 30" }
]);
</pre>
<script>goCode("nodeGrid", 600, 350)</script>
<h2 id="OtherConsiderations">Other Considerations</h2>
<p>
A Grid Panel should have a non-null <code>background</code> if it needs to be pickable.
One cannot set or bind the <a>Panel.itemArray</a> of a Grid Panel.
</p>
<p>
Events on the Shapes will be ignored.
Shapes in a Grid Panel must not be scaled or rotated.
</p>
</div>
</div>
</body>
</html>
+187
View File
@@ -0,0 +1,187 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Groups -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Groups</h1>
<p>
Use the <a>Group</a> class to treat a collection of <a>Node</a>s and <a>Link</a>s as if they were a single <a>Node</a>.
Those nodes and links are members of the group; together they constitute a subgraph.
</p>
<p>
A subgraph is <em>not</em> another <a>Diagram</a>, so there is no separate HTML Div element for the subgraph of a group.
All of the <a>Part</a>s that are members of a <a>Group</a> belong to the same Diagram as the Group.
There can be links between member nodes and nodes outside of the group as well as links between the group itself and other nodes.
There can even be links between member nodes and the containing group itself.
</p>
<p>
Groups can also be collapsed and expanded, to hide or show the member parts.
</p>
<p>
The member parts of a group are available via the <a>Group.memberParts</a> property.
Conversely, the <a>Part.containingGroup</a> property refers to the group, if the part belongs to one.
A part can be member of at most one group at a time.
You can set that property in order to add that part to a group.
However you must make sure that no group contains itself, either directly or indirectly through other groups.
</p>
<p>
Because every <a>Group</a> is a <a>Node</a>, you can have nested groups.
Although member <a>Node</a>s and <a>Link</a>s belong to the <a>Group</a> that contains them,
they are not in the visual tree of the group -- their <a>GraphObject.panel</a> is null and no member part
is in the group's <a>Panel.elements</a> collection.
No <a>Part</a> can be in the visual tree of another <a>Part</a>.
Parts normally do belong directly to one <a>Layer</a>.
</p>
<p>
See samples that make use of Groups in the <a href="../samples/index.html#groups">samples index</a>.
</p>
<h2 id="SimpleGroups">Simple Groups</h2>
<p>
In a <a>GraphLinksModel</a> the <a>Model.nodeDataArray</a> holds node data, each of which might be
represented by a <a>Group</a> rather than by a regular <a>Node</a>.
You can declare that it should be a group by setting the isGroup data property to true.
You can declare that a node data be a member of a group by referring to the group's key as
the group data property value.
</p>
<p>
Here is a group containing two nested groups as well as two regular nodes.
If you move a group, its member parts move along.
If you copy a group, its member parts are copied too.
If you delete a group, its member parts are deleted too.
If you move a member node, its containing group inflates or shrinks to cover the area occupied by all of the members.
</p>
<pre class="lang-js" id="simple">
diagram.model.nodeDataArray = [
{ key: "Alpha", isGroup: true },
{ key: "Beta", group: "Alpha" },
{ key: "Gamma", group: "Alpha", isGroup: true },
{ key: "Delta", group: "Gamma" },
{ key: "Epsilon", group: "Gamma" },
{ key: "Zeta", group: "Alpha" },
{ key: "Eta", group: "Alpha", isGroup: true},
{ key: "Theta", group: "Eta" }
];
</pre>
<script>goCode("simple", 600, 200)</script>
<h3 id="GroupsLinks">Groups and Links</h2>
<p>
Because <a>Group</a>s are <a>Node</a>s, a <a>Link</a> may connect with a group as well as with a plain node.
</p>
<p>
Here is a simple example of four regular nodes and one group node.
In this example the link from "Alpha" goes directly to the "Beta" node,
but the link to "Delta" actually comes from the "Omega" group rather than from any particular member of the group.
</p>
<pre class="lang-js" id="links">
var nodeDataArray = [
{ key: "Alpha" },
{ key: "Beta", group: "Omega" },
{ key: "Gamma", group: "Omega" },
{ key: "Omega", isGroup: true },
{ key: "Delta" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }, // from outside the Group to inside it
{ from: "Beta", to: "Gamma" }, // this link is a member of the Group
{ from: "Omega", to: "Delta" } // from the Group to a Node
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("links", 600, 150)</script>
<p>
If you drag the "Delta" node around you can see how the link from the "Omega" group appears to come from the center
of the group and start at the group's edge rather than at any member node.
This is different than for the link from "Alpha" to "Beta".
</p>
<p>
Note also how the link from "Beta" to "Gamma" is effectively owned by the "Omega" group because
both of the nodes are owned by that group. Copying the group automatically copies the link too.
</p>
<p>
This example did not set any of the following properties:
<a>Diagram.nodeTemplate</a>, <a>Diagram.groupTemplate</a>, and <a>Diagram.linkTemplate</a>,
in order to demonstrate the default templates for all kinds of node data and link data.
</p>
<h2 id="GroupTemplates">Group Templates</h2>
<p>
Here is an example of how one might define templates for nodes and for groups.
The node template is very simple: some text inside an ellipse.
The group template is different from a node template in several aspects.
</p>
<p>
First, the group template builds a go.Group, not a go.Node or go.Part.
The group can use a number of the panel types, just as a node may use various panel types.
</p>
<p>
Second, the group template includes a <a>Placeholder</a> object.
This object, of which you may have at most one within the visual tree of a group,
gets the size and position of the union of the bounds of the member parts, plus some padding.
The use of a Placeholder results in the Group surrounding the collection of group members,
no matter where the member nodes are placed.
</p>
<pre class="lang-js" id="groupTemplates">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "Ellipse", { fill: "white" }),
$(go.TextBlock,
new go.Binding("text", "key"))
);
diagram.groupTemplate =
$(go.Group, "Vertical",
$(go.Panel, "Auto",
$(go.Shape, "RoundedRectangle", // surrounds the Placeholder
{ parameter1: 14,
fill: "rgba(128,128,128,0.33)" }),
$(go.Placeholder, // represents the area of all member parts,
{ padding: 5}) // with some extra padding around them
),
$(go.TextBlock, // group title
{ alignment: go.Spot.Right, font: "Bold 12pt Sans-Serif" },
new go.Binding("text", "key"))
);
var nodeDataArray = [
{ key: "Alpha" },
{ key: "Beta", group: "Omega" },
{ key: "Gamma", group: "Omega" },
{ key: "Omega", isGroup: true },
{ key: "Delta" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }, // from outside the Group to inside it
{ from: "Beta", to: "Gamma" }, // this link is a member of the Group
{ from: "Omega", to: "Delta" } // from the Group to a Node
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("groupTemplates", 600, 200)</script>
<p>
Note how when you move the "Beta" or "Gamma" nodes the "Omega" group automatically resizes so that the
TextBlock on the group stays below and on the right side of the "RoundedRectangle" shape.
</p>
<p>
Just as a <a>Diagram</a> can have its own <a>Layout</a>, a <a>Group</a> can have its own <a>Group.layout</a>.
This is discussed in the page about <a href="subgraphs.html">SubGraphs</a>.
</p>
</div>
</div>
</body>
</html>
+305
View File
@@ -0,0 +1,305 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Highlighting -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Highlighting</h1>
<p>
It is common to make a Node (or a part of a Node or a Link) stand out by "highlighting" it in some way.
This happens with selection when a selection Adornment is shown.
However one frequently wants to highlight Parts independently of selection.
This can be done by changing the fill or stroke of a Shape, replacing a Picture source with another source, adding or removing a shadow, and so on.
</p>
<h2 id="HighlightingNodeUponMouseOver">Highlighting a Node upon Mouse Over</h2>
<p>
The most general kind of highlighting is to change appearance when an action occurs, such as mousing over a node.
This can draw attention to interactive Nodes or Links or really any GraphObject, such as buttons.
This is why <a href="buttons.html">predefined buttons in GoJS</a> highlight on mouse-over.
</p>
<p>
To achieve this effect you just need to define <a>GraphObject.mouseEnter</a> and <a>GraphObject.mouseLeave</a> event handlers.
</p>
<pre class="lang-js" id="highlighting1">
function mouseEnter(e, obj) {
var shape = obj.findObject("SHAPE");
shape.fill = "#6DAB80";
shape.stroke = "#A6E6A1";
var text = obj.findObject("TEXT");
text.stroke = "white";
};
function mouseLeave(e, obj) {
var shape = obj.findObject("SHAPE");
// Return the Shape's fill and stroke to the defaults
shape.fill = obj.data.color;
shape.stroke = null;
// Return the TextBlock's stroke to its default
var text = obj.findObject("TEXT");
text.stroke = "black";
};
diagram.nodeTemplate =
$(go.Node, "Auto",
{
mouseEnter: mouseEnter,
mouseLeave: mouseLeave
},
$(go.Shape, "Rectangle",
{ strokeWidth: 2, stroke: null, name: "SHAPE" },
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 10, font: "bold 18px Verdana", name: "TEXT" },
new go.Binding("text", "key"))
);
diagram.model = new go.GraphLinksModel(
[
{ key: "Alpha", color: "#96D6D9" },
{ key: "Beta", color: "#96D6D9" },
{ key: "Gamma", color: "#EFEBCA" },
{ key: "Delta", color: "#EFEBCA" }
],
[
{ from: "Alpha", to: "Beta" },
{ from: "Alpha", to: "Gamma" },
{ from: "Beta", to: "Beta" },
{ from: "Gamma", to: "Delta" },
{ from: "Delta", to: "Alpha" }
]);
</pre>
<script>goCode("highlighting1", 600, 150)</script>
<p>Mouse-over nodes to see them highlight.</p>
<p>
It is also commonplace to perform highlighting of stationary Parts during a drag, which is a different case of "mouse over".
This can be implemented in a manner similar to the mouseEnter/mouseLeave events by implementing
<a>GraphObject.mouseDragEnter</a> and <a>GraphObject.mouseDragLeave</a> event handlers.
Several samples demonstrate this: <a href="../samples/orgChartEditor.html">Org Chart Editor</a>,
<a href="../samples/planogram.html">Planogram</a>, <a href="../samples/regrouping.html">Regrouping</a>,
and <a href="../samples/seatingChart.html">Seating Chart</a>.
</p>
<h2 id="HighlightingNodesAndLinks">Highlighting Nodes and Links</h2>
<p>
It is common to want to show Nodes or Links that are related to a particular Node.
Unlike the mouse-over scenarios, one may want to maintain the highlighting for many Parts
independent of any mouse state or selection state.
</p>
<p>
Here is an example of highlighting all of the nodes and links that come out of a node that the user clicks.
This example uses the <a>Part.isHighlighted</a> property and data binding of visual properties to that Part.isHighlighted property.
</p>
<pre class="lang-js" id="highlighting2">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ // when the user clicks on a Node, highlight all Links coming out of the node
// and all of the Nodes at the other ends of those Links.
click: function(e, node) {
// highlight all Links and Nodes coming out of a given Node
var diagram = node.diagram;
diagram.startTransaction("highlight");
// remove any previous highlighting
diagram.clearHighlighteds();
// for each Link coming out of the Node, set Link.isHighlighted
node.findLinksOutOf().each(function(l) { l.isHighlighted = true; });
// for each Node destination for the Node, set Node.isHighlighted
node.findNodesOutOf().each(function(n) { n.isHighlighted = true; });
diagram.commitTransaction("highlight");
}
},
$(go.Shape, "Rectangle",
{ strokeWidth: 2, stroke: null },
new go.Binding("fill", "color"),
// the Shape.stroke color depends on whether Node.isHighlighted is true
new go.Binding("stroke", "isHighlighted", function(h) { return h ? "red" : "black"; })
.ofObject()),
$(go.TextBlock,
{ margin: 10, font: "bold 18px Verdana" },
new go.Binding("text", "key"))
);
// define the Link template
diagram.linkTemplate =
$(go.Link,
{ toShortLength: 4 },
$(go.Shape,
// the Shape.stroke color depends on whether Link.isHighlighted is true
new go.Binding("stroke", "isHighlighted", function(h) { return h ? "red" : "black"; })
.ofObject(),
// the Shape.strokeWidth depends on whether Link.isHighlighted is true
new go.Binding("strokeWidth", "isHighlighted", function(h) { return h ? 3 : 1; })
.ofObject()),
$(go.Shape,
{ toArrow: "Standard", strokeWidth: 0 },
// the Shape.fill color depends on whether Link.isHighlighted is true
new go.Binding("fill", "isHighlighted", function(h) { return h ? "red" : "black"; })
.ofObject())
);
// when the user clicks on the background of the Diagram, remove all highlighting
diagram.click = function(e) {
e.diagram.commit(function(d) { d.clearHighlighteds(); }, "no highlighteds");
};
diagram.model = new go.GraphLinksModel(
[
{ key: "Alpha", color: "#96D6D9" },
{ key: "Beta", color: "#96D6D9" },
{ key: "Gamma", color: "#EFEBCA" },
{ key: "Delta", color: "#EFEBCA" }
],
[
{ from: "Alpha", to: "Beta" },
{ from: "Alpha", to: "Gamma" },
{ from: "Beta", to: "Beta" },
{ from: "Gamma", to: "Delta" },
{ from: "Delta", to: "Alpha" }
]);
</pre>
<script>goCode("highlighting2", 600, 200)</script>
<p>
Click on a node to highlight outbound connected links and nodes.
Click in the diagram background to remove all highlights.
Note that the highlighting is independent of selection.
</p>
<p>
The use of data binding to modify the Shape properties allows you to avoid specifying names for each Shape
and writing code to find the Shape and modify its properties.
</p>
<p>
It is also commonplace to perform highlighting of stationary Parts during a drag, which is a different case of "mouse over".
This can be implemented in a manner similar to the mouseEnter/mouseLeave events by implementing
<a>GraphObject.mouseDragEnter</a> and <a>GraphObject.mouseDragLeave</a> event handlers.
Several samples demonstrate this: <a href="../samples/orgChartEditor.html">Org Chart Editor</a>,
<a href="../samples/planogram.html">Planogram</a>, <a href="../samples/regrouping.html">Regrouping</a>,
and <a href="../samples/seatingChart.html">Seating Chart</a>.
</p>
<h3 id="ChangingNodeSizeWhenHighlighting">Changing Node Size When Highlighting</h3>
<p>
You may want to increase the size of a node or of an element in a node in order to highlight it.
For example you could have a Binding on <a>GraphObject.scale</a> or <a>Shape.strokeWidth</a>:
</p>
<pre class="lang-js">
$(go.Node, ...
$(go.Shape, ...,
new go.Binding("strokeWidth", "isHighlighted", function(h) { return h ? 5 : 1; })),
...
)
</pre>
<p>
However, doing so will change the size of the object. That is likely to invalidate the route of any links that are connected
with that node. That might not matter in many apps, but in some cases the routes of some links may have been reshaped by the user.
Any recomputation of the route due to a connected node moving or changing size might lose that route.
</p>
<p>
If that is a consideration in your app, you might consider instead having each node hold an additional Shape that would provide
the highlighting when shown and that would be unseen otherwise.
But do not toggle the <a>GraphObject.visible</a> property, because that would cause the node to change size.
Instead toggle the <a>GraphObject.opacity</a> property between 0.0 and 1.0.
</p>
<pre class="lang-js" id="highlighting3">
diagram.nodeTemplate =
$(go.Node, "Auto",
{
locationSpot: go.Spot.Center,
// when the user clicks on a Node, highlight all Links coming out of the node
// and all of the Nodes at the other ends of those Links.
click: function(e, node) {
var diagram = node.diagram;
diagram.startTransaction("highlight");
diagram.clearHighlighteds();
node.findLinksOutOf().each(function(l) { l.isHighlighted = true; });
node.findNodesOutOf().each(function(n) { n.isHighlighted = true; });
diagram.commitTransaction("highlight");
}
},
$(go.Panel, "Auto",
$(go.Shape, "Ellipse",
{ strokeWidth: 2, portId: "" },
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 5, font: "bold 18px Verdana" },
new go.Binding("text", "key"))
),
// the highlight shape, which is always a thick red ellipse
$(go.Shape, "Ellipse",
// this shape is the "border" of the Auto Panel, but is drawn in front of the
// regular Auto Panel holding the black-bordered ellipse and text
{ isPanelMain: true, spot1: go.Spot.TopLeft, spot2: go.Spot.BottomRight },
{ strokeWidth: 6, stroke: "red", fill: null },
// only show this ellipse when Part.isHighlighted is true
new go.Binding("opacity", "isHighlighted", function(h) { return h ? 1.0 : 0.0; })
.ofObject())
);
// define the Link template
diagram.linkTemplate =
$(go.Link,
{ toShortLength: 4, reshapable: true, resegmentable: true },
$(go.Shape,
// when highlighted, draw as a thick red line
new go.Binding("stroke", "isHighlighted", function(h) { return h ? "red" : "black"; })
.ofObject(),
new go.Binding("strokeWidth", "isHighlighted", function(h) { return h ? 3 : 1; })
.ofObject()),
$(go.Shape,
{ toArrow: "Standard", strokeWidth: 0 },
new go.Binding("fill", "isHighlighted", function(h) { return h ? "red" : "black"; })
.ofObject())
);
// when the user clicks on the background of the Diagram, remove all highlighting
diagram.click = function(e) {
diagram.startTransaction("no highlighteds");
diagram.clearHighlighteds();
diagram.commitTransaction("no highlighteds");
};
diagram.model = new go.GraphLinksModel(
[
{ key: "Alpha", color: "#96D6D9" },
{ key: "Beta", color: "#96D6D9" },
{ key: "Gamma", color: "#EFEBCA" },
{ key: "Delta", color: "#EFEBCA" }
],
[
{ from: "Alpha", to: "Beta" },
{ from: "Alpha", to: "Gamma" },
{ from: "Beta", to: "Beta" },
{ from: "Gamma", to: "Delta" },
{ from: "Delta", to: "Alpha" }
]);
</pre>
<script>goCode("highlighting3", 600, 200)</script>
<p>
The highlight Shape is the outer ellipse that always has a thick red stroke. It is normally hidden by having zero opacity,
but the Binding will change its opacity to one when <a>Part.isHighlighted</a> is true.
</p>
<p>
That highlight Shape is always shown in front of the panel of the colored ellipse and text by putting it afterwards in the list of the
panel's child elements.
However since the "Auto" Panel assumes the first element acts as the border, we need to set <a>GraphObject.isPanelMain</a>
to true on the highlight Shape so that it is the border for the inner panel.
</p>
</div>
</div>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 94 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 94 KiB

+318
View File
@@ -0,0 +1,318 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Introduction -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Introduction to GoJS Diagramming Components</h1>
<p>
<b>GoJS</b> is a JavaScript library that lets you easily create interactive diagrams in modern web browsers.
<b>GoJS</b> supports graphical templates and data-binding of graphical object properties to model data.
You only need to save and restore the model, consisting of simple JavaScript objects holding
whatever properties your app needs.
Many predefined tools and commands implement the standard behaviors that most diagrams need.
Customization of appearance and behavior is mostly a matter of setting properties.
</p>
<h2 id="SimpleGoJSDiagram">A Simple GoJS Diagram</h2>
<p>
The following code defines a node template and model data, which produces a small diagram with a handful of nodes and links.
</p>
<pre class="lang-js" id="minimal">
// For conciseness. See the "Building Parts" intro page for more
var $ = go.GraphObject.make;
// the node template describes how each Node should be constructed
diagram.nodeTemplate =
$(go.Node, "Auto", // the Shape automatically fits around the TextBlock
$(go.Shape, "RoundedRectangle", // use this kind of figure for the Shape
// bind Shape.fill to Node.data.color
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 3 }, // some room around the text
// bind TextBlock.text to Node.data.key
new go.Binding("text", "key"))
);
// the Model holds only the essential information describing the diagram
diagram.model = new go.GraphLinksModel(
[ // a JavaScript Array of JavaScript objects, one per node;
// the "color" property is added specifically for this app
{ key: "Alpha", color: "lightblue" },
{ key: "Beta", color: "orange" },
{ key: "Gamma", color: "lightgreen" },
{ key: "Delta", color: "pink" }
],
[ // a JavaScript Array of JavaScript objects, one per link
{ from: "Alpha", to: "Beta" },
{ from: "Alpha", to: "Gamma" },
{ from: "Beta", to: "Beta" },
{ from: "Gamma", to: "Delta" },
{ from: "Delta", to: "Alpha" }
]);
// enable Ctrl-Z to undo and Ctrl-Y to redo
diagram.undoManager.isEnabled = true;
</pre>
<p>This creates the following Diagram:</p>
<script>goCode("minimal", 400, 150)</script>
<p>
You can interact with this diagram in many ways:
</p>
<ul>
<li>You can select a part by clicking on it.
Selected nodes are highlighted with an <a>Adornment</a> that is a blue rectangle surrounding the node.
Selected links are highlighted with a blue line following the path of the link.</li>
<li>Multiple parts may be selected at once.
Hold the Shift key down when clicking to add to the selection.
Hold the Control key down when clicking to toggle whether that part is selected.</li>
<li>Another way to multi-select is to mouse-down at a point in the background (not on a part), wait a moment, and then drag a box.
Parts that are in the box when the mouse-up occurs are selected.
The Shift and Control modifiers work then as well.</li>
<li>Ctrl-A selects all parts in the diagram.</li>
<li>Move one or more nodes by selecting them and dragging.</li>
<li>Copying selected parts works with either copy/paste (Ctrl-C/Ctrl-V) or with Ctrl-mouse-drag.</li>
<li>Delete selected parts with the Delete key.</li>
<li>If scrollbars are visible or if the whole collection of parts is smaller than the viewable area of the diagram (the "viewport"),
you can pan the diagram with a mouse-down in the background (not on a part) if you drag without waiting.</li>
<li>Use the mouse wheel to scroll up and down and Shift-mouse-wheel to scroll left and right.
Ctrl-mouse-wheel zooms in and out.</li>
</ul>
<p>
You can also pan, pinch zoom, select, copy, move, delete, undo, and redo with your fingers on a touch device.
Most commands that can be invoked from a keyboard can be invoked from the default context menu that you get by pressing your finger and holding it motionless for a moment.
</p>
<p>
What is unique about all of the examples in the documentation is that they are all "live" -- there are no screenshots!
They are actual <a>Diagram</a>s implemented by the source code shown.
You can interact with them -- some even display animation.
</p>
<p>
If you'd like to see more examples of what <b>GoJS</b> can do, see the <a href="../samples/index.html" target="samples">GoJS Samples directory</a>.
To make it easier to search the JavaScript code and documentation or to experiment by modifying the samples,
you can install the <b>GoJS</b> kit in various manners:
<ul>
<li>Download a ZIP file from <a href="../download.html">Download</a>.</li>
<li>Download us from <a href="https://github.com/NorthwoodsSoftware/GoJS">GoJS on GitHub</a>.</li>
<li>Install GoJS using <code>npm install gojs</code>.</li>
</ul>
</p>
<h2 id="GoJSConcepts">GoJS Concepts</h2>
<p>
<a>Diagram</a>s consist of <a>Part</a>s: <a>Node</a>s that may be connected by <a>Link</a>s and that may be grouped together into <a>Group</a>s.
All of these parts are gathered together in <a>Layer</a>s and are arranged by <a>Layout</a>s.
</p>
<p>
Each diagram has a <a>Model</a> that holds and interprets your application data to determine node-to-node link relationships and
group-member relationships.
Most parts are data-bound to your application data.
The diagram automatically creates a <a>Node</a> or a <a>Group</a> for each data item in the model's <a>Model.nodeDataArray</a>
and a <a>Link</a> for each data item in the model's <a>GraphLinksModel.linkDataArray</a>.
You can add whatever properties you need to each data object, but there are just a few properties that each kind of model expects.
</p>
<p>
Each <a>Node</a> or <a>Link</a> is normally defined by a template that declares its appearance and behavior.
Each template consists of <a>Panel</a>s of <a>GraphObject</a>s such as <a>TextBlock</a>s or <a>Shape</a>s.
There are default templates for all parts, but almost all applications will specify custom templates
in order to achieve the desired appearance and behavior.
Data bindings of <a>GraphObject</a> properties to model data properties make each Node or Link unique for the data.
</p>
<p>
The nodes may be positioned manually (interactively or programmatically) or may be arranged automatically by the
<a>Diagram.layout</a> and by each <a>Group.layout</a>.
Nodes are positioned either by their top-left corner point (<a>GraphObject.position</a>) or by a programmer-defined
spot in the node (<a>Part.location</a> and <a>Part.locationSpot</a>).
</p>
<p>
<a>Tool</a>s handle mouse and keyboard events. Each diagram has a number of tools that perform interactive tasks such as
selecting parts or dragging them or drawing a new link between two nodes. The <a>ToolManager</a> determines
which tool should be running, depending on the mouse events and current circumstances.
</p>
<p>
Each diagram also has a <a>CommandHandler</a> that implements various commands, such as Delete or Copy.
The CommandHandler interprets keyboard events, such as control-Z, when the ToolManager is running.
</p>
<p>
The diagram provides the ability to scroll the parts of the diagram and to zoom in or out.
The diagram also contains all of the layers, which in turn contain all of the parts (nodes and links).
The parts in turn are composed of possibly nested panels of text, shapes, and images.
This hierarchy of JavaScript objects in memory forms the "visual tree" of everything that may be drawn by the diagram.
</p>
<p>
The <a>Overview</a> class allows the user to see the whole model and to control what part of it that the diagram displays.
The <a>Palette</a> class holds parts that the user may drag-and-drop into a diagram.
</p>
<p>
You can select one or more parts in the diagram. The template implementation may change the appearance
of the node or link when it is selected. The diagram may also add <a>Adornment</a>s to indicate selection and to
support tools such as resizing a node or reconnecting a link.
Adornments are also how tooltips and context menus are implemented.
</p>
<p>
All programmatic changes to <a>Diagram</a>, <a>GraphObject</a>, <a>Model</a> or model data state should be performed
within a single transaction per user action, to make sure updating happens correctly and to support undo/redo.
All of the predefined tools and commands perform transactions, so each user action is automatically undoable
if the <a>UndoManager</a> is enabled.
<a>DiagramEvent</a>s on Diagrams, and event handlers on Diagrams and GraphObjects,
are all documented whether they are raised within a transaction or whether you need to conduct a transaction in order
to change the model or the diagram.
</p>
<h2 id="CreatingDiagram">Creating a Diagram</h2>
<b>GoJS</b> does not depend on any JavaScript library or framework, so you should be able to use it in any environment.
However it does require that the environment support modern HTML and JavaScript.
<h3 id="LoadingGoJS">Loading GoJS</h3>
<p>
Before you can execute any JavaScript code to build a Diagram, you will need to load the <b>GoJS</b> library.
When you include the library, the "<code>go</code>" JavaScript object will hold all of the <b>GoJS</b> types.
During development we recommend that you load "go-debug.js" instead of "go.js", for additional run-time error checking and debugging ability.
</p>
<p>
We recommend that you declare that your web page supports modern HTML:
</p>
<pre class="lang-html">
&lt;!DOCTYPE html&gt; &lt;!-- Declare standards mode. --&gt;
&lt;html&gt;
&lt;head&gt;
. . .
&lt;!-- Include the GoJS library. --&gt;
&lt;script src="go-debug.js"&gt;&lt;/script&gt;</pre>
<p>
If you are using <a href="http://requirejs.org" target="_blank">RequireJS</a>, <b>GoJS</b> supports UMD module definitions.
See the <a href="../samples/require.html" target="samples">Require sample</a> for an example.
Furthermore modularized versions of the extension classes are now available at <code>../extensionsTS/</code>,
where the extension classes have been translated into TypeScript and compiled into <code>.js</code> files
that can be <code>import</code>ed. or <code>require</code>d.
</p>
<p>
In ES6 (ECMAScript 2015) or TypeScript code, just import the <code>go.js</code> library:
<pre class="lang-ts">import * as go from "./path/to/gojs/release/go";</pre>
or, if depending on your npm environment:
<pre class="lang-ts">import * as go from "gojs";</pre>
</p>
<p>
If you want to use ES6 modules, use <code>go.mjs</code> in the <code>../release/</code> directory.
The extension classes are also available as ES6 modules in the <code>../extensionsJSM/</code> directory.
</p>
<pre>
import * as go from "./path/to/gojs/release/go.mjs";
import { DoubleTreeLayout } from "./path/to/gojs/extensionsJSM/DoubleTreeLayout.js";</pre>
<h3 id="HostingGoJSinaDivElement">Hosting GoJS in a Div Element</h3>
<p>
Every <a>Diagram</a> must be hosted by an HTML Div element.
<b>GoJS</b> will manage the contents of that Div element, but you may position and size and style the Div as you would any HTML element.
The diagram will add a Canvas element to that Div element that the diagram will draw in -- this is what users actually see.
The Canvas element is automatically sized to have the same size as the Div element.
</p>
<pre class="lang-html">
&lt;body&gt;
. . .
&lt;!-- The DIV for a Diagram needs an explicit size or else we won't see anything.
In this case we also add a border to help see the edges. --&gt;
&lt;div id="myDiagramDiv" style="border: solid 1px blue; width:400px; height:150px"&gt;&lt;/div&gt;</pre>
<p>
Then you can create the <a>Diagram</a> in JavaScript with a reference to that Div element.
Build the diagram by constructing plain JavaScript objects and adding them to the diagram's model.
Note that all references in JavaScript code to <b>GoJS</b> types such as <a>Diagram</a> are prefixed with "<code>go.</code>".
</p>
<pre class="lang-html">
&lt;!-- Create the Diagram in the DIV element using JavaScript. --&gt;
&lt;!-- The "go" object is the "namespace" that holds all of the GoJS types. --&gt;
&lt;script&gt;
var diagram = new go.Diagram("myDiagramDiv");
diagram.model = new go.GraphLinksModel(
[{ key: "Hello" }, // two node data, in an Array
{ key: "World!" }],
[{ from: "Hello", to: "World!"}] // one link data, in an Array
);
&lt;/script&gt;</pre>
<div id="myDiagramDiv" style="border: solid 1px blue; width:400px; height:150px"></div>
<!-- Create the Diagram using JavaScript. -->
<!-- The "go" object is the "namespace" that holds all of the GoJS types. -->
<script>
var diagram = new go.Diagram("myDiagramDiv");
diagram.model = new go.GraphLinksModel(
[{ key: "Hello" }, // two node data, in an Array
{ key: "World!" }],
[{ from: "Hello", to: "World!" }] // one link data, in an Array
);
</script>
<p>
This completes the implementation of the "Hello World!" live diagram that you see above.
</p>
<h3 id="DevelopingYourDiagram">Developing your Diagram</h3>
<p class="box bg-danger">
<b>GoJS</b> outputs error or warning messages when something goes wrong.
When developing with <b>GoJS</b>, be sure to check your browser's developer console for information.
The "go-debug.js" version of the library contains extra type-checking and error-checking code, and should be used during development.
The "go.js" version has less error checking, but is faster as a result, and should be used in production.
</p>
<p>
Your JavaScript code should only use properties and methods that are documented in the <a href="../api/index.html" target="api">API</a>.
The <b>GoJS</b> libraries are "minified", so if you look at an instance of a <b>GoJS</b> class in the debugger,
you will see many one or two letter property names. All of those are internal names that you should not use.
At the current time the only one letter property names are "x" and "y" on <a>Point</a>, <a>Rect</a>, <a>Spot</a> and <a>LayoutVertex</a>.
The only two letter property name is <a>InputEvent.up</a>.
Otherwise you should not try to use any one or two letter property names on any <b>GoJS</b>-defined objects.
</p>
<p class="box bg-danger">
Do not modify the prototypes of the <b>GoJS</b> classes.<br />
Only use the properties and methods documented in the <a href="../api/index.html" target="api">API</a>.
</p>
<p>
You can also use <a href="https://www.typescriptlang.org/">TypeScript</a> in order to get better "compile-time" type-checking.
The TypeScript definition file for <b>GoJS</b> is named "go.d.ts" and is located in the same directory as the "go.js" and "go-debug.js" libraries.
In some editors, access to the definition file also greatly improves documentation feedback while editing TypeScript code.
The extension classes have also been translated into TypeScript, available at <code>../extensionsTS/</code>.
</p>
<p>
To learn about new features and bug fixes, read the <a href="https://gojs.net/latest/changelog.html" target="_blank">Change Log</a>.
Read about getting the latest releases at <a href="../download.html">Downloads</a>.
</p>
<p>
You can see the variety of kinds of diagrams that you can build at <a href="../samples/index.html" target="samples">GoJS Samples</a>.
</p>
<p>
In the next introduction page we discuss <a href="buildingObjects.html">building <b>GoJS</b> Parts and adding them into Diagrams.</a>
</p>
</div>
</div>
</body>
</html>
+150
View File
@@ -0,0 +1,150 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Initial Viewport -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Initial, Automatic, and Programmatic Viewport Management</h1>
<p>
Once you have created and assigned a model to cause some Parts to appear in your Diagram,
you can control where your parts initially appear in the viewport, and you can control
where they are shown after the diagram has been modified.
</p>
<h2 id="InitialViewport">Initial viewport</h2>
<p>
You can very easily set the <a>Diagram.initialPosition</a> and/or <a>Diagram.initialScale</a> properties
at any time, even before assigning the model. This will cause the <a>Diagram.position</a> and/or <a>Diagram.scale</a>
properties to be set to the specified initial value(s) once all of the Parts have been created and any initial layout has been performed.
</p>
<p>
But what if you do not know how big your diagram document will be?
If you want to show a particular area of the document, you will not know what position to set.
If you want to show a big document at the largest scale that shows all of it, you will not know what scale to set.
</p>
<p>
One solution to this problem is to set the <a>Diagram.initialDocumentSpot</a> and <a>Diagram.initialViewportSpot</a> properties
to particular <a>Spot</a> values.
For example, if you are showing a tree-like diagram and you want tree to be centered horizontally but positioned vertically at the top,
you can do something like this when you create the Diagram:
</p>
<pre class="lang-js">
$(go.Diagram, "myDiagramDiv",
{
initialDocumentSpot: go.Spot.Top,
initialViewportSpot: go.Spot.Top
})
</pre>
<p>
This makes sure that after the initial layout of your diagram the middle top point of the diagram contents
is positioned to be at the middle top point of the viewport.
</p>
<p>
Another solution to this problem is to set the <a>Diagram.initialContentAlignment</a> or <a>Diagram.initialAutoScale</a> properties.
For example it is fairly common to want to make sure that small documents appear top-centered within the diagram window --
just set <a>Diagram.initialContentAlignment</a> to <a>Spot,Top</a>.
Or if you want to "zoom-to-fit" the diagram, just set <a>Diagram.initialAutoScale</a> to <a>Diagram,Uniform</a>.
</p>
<pre class="lang-js">
$(go.Diagram, "myDiagramDiv",
{
initialAutoScale: go.Diagram.Uniform
})
</pre>
<p>
More generally, you may want to try to center a particular <a>Node</a>.
Here is how you can do that:
</p>
<pre class="lang-js" id="centernode">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc"),
$(go.Shape, { fill: "lightyellow" }),
$(go.TextBlock, { margin: 5 },
// show the location as text in the node
new go.Binding("text", "loc",
function (p) { return p.x.toFixed() + ", " + p.y.toFixed(); }))
);
// initialize the model with random nodes:
var nodeDataArray = [];
for (var i = 0; i &lt; 20; i++ ) {
nodeDataArray.push({ loc: new go.Point(Math.random() * 600, Math.random() * 300) });
}
diagram.model.nodeDataArray = nodeDataArray;
// this event handler is called when the diagram is first ready
diagram.addDiagramListener("InitialLayoutCompleted", function(e) {
// pick a random node data
var data = nodeDataArray[Math.floor(Math.random()*nodeDataArray.length)];
// find the corresponding Node
var node = diagram.findNodeForData(data);
// and center it and select it
diagram.centerRect(node.actualBounds);
diagram.select(node);
});
</pre>
<script>goCode("centernode", 300, 150)</script>
<p>
Note: because by default one cannot scroll past any edge of the document plus any <a>Diagram.scrollMargin</a>,
if the selected node happens to be at or near an edge, the node cannot actually be centered in the viewport.
</p>
<h2 id="AutomaticViewportManagement">Automatic viewport management</h2>
<p>
There are also times when you will want to control the viewport (i.e. the <a>Diagram.position</a> and <a>Diagram.scale</a>)
after every change to the diagram.
For example, if you always want to keep the document centered after the user has moved or deleted or inserted nodes,
set <a>Diagram.contentAlignment</a> (rather than <a>Diagram.initialContentAlignment</a>) to <a>Spot.Center</a>.
</p>
<p>
Or if you always want to keep the document "zoomed-to-fit", set <a>Diagram.autoScale</a>
(rather than <a>Diagram.initialAutoScale</a>) to <a>Diagram,Uniform</a>.
As an example, the <a>Overview</a> diagram does this.
</p>
<h2 id="ProgrammaticViewportManagement">Programmatic viewport management</h2>
<p>
If you do not want continual repositioning or rescaling of the diagram, but you do sometimes want to change
the <a>Diagram.position</a> and/or the <a>Diagram.scale</a>, you can set those properties to whatever values you like.
However, please note that the ultimate value for <a>Diagram.position</a> is normally limited by the <a>Diagram.documentBounds</a>
and the size of the viewport and the scale of the diagram.
The <a>Diagram.scale</a> is limited by <a>Diagram.minScale</a> and <a>Diagram.maxScale</a>.
</p>
<p>
But it is more common to call a method on Diagram to achieve the results that you want.
For example, to get the effect of the <a>Diagram.initialDocumentSpot</a> and <a>Diagram.initialViewportSpot</a> properties
that are used when the "InitialLayoutCompleted" DiagramEvent occurs, call
<a>Diagram.alignDocument</a> with the two desired Spots that you want to have coincide.
</p>
<p>
As already demonstrated above, if you want to try to center a particular node in the viewport,
you can call <a>Diagram.centerRect</a> with the node's <a>GraphObject.actualBounds</a>.
</p>
<p>
If you want to make sure that a particular node is within the viewport, but not necessarily centered,
call <a>Diagram.scrollToRect</a>.
</p>
<p>
If you just want to scroll the diagram, in the same manners as the user might via a scrollbar or the mouse wheel,
call <a>Diagram.scroll</a> with arguments that specify how much to scroll and in which direction.
</p>
<p>
The just-mentioned Diagram methods do not change the <a>Diagram.scale</a>.
If you want to rescale the diagram so that the whole document bounds are shown, call <a>Diagram.zoomToFit</a>.
More generally, if you want a particular area of your diagram to be shown at whatever scale will make it fit in the viewport, call <a>Diagram.zoomToRect</a>.
</p>
</div>
</div>
</body>
</html>
+465
View File
@@ -0,0 +1,465 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Item Arrays-- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="../extensions/Figures.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Panel Item Arrays</h1>
<p>
How does one display a variable number of elements in a node by data binding to a JavaScript Array?
The answer is simple: just bind (or set) <a>Panel.itemArray</a>.
The <a>Panel</a> will contain as many elements as there are values in the bound Array.
</p>
<p>
See samples that make use of item Arrays in the <a href="../samples/index.html#itemarrays">samples index</a>.
</p>
<h2 id="SimpleItemLists">Simple item lists</h2>
<p>
Here is a very simple example demonstrating the standard way of binding <a>Panel.itemArray</a> to a data property whose value is an Array.
</p>
<pre class="lang-js" id="simple">
diagram.nodeTemplate =
$(go.Node, "Vertical",
new go.Binding("itemArray", "items"));
diagram.model =
$(go.GraphLinksModel,
{
nodeDataArray: [
{ key: 1, items: [ "Alpha", "Beta", "Gamma", "Delta" ] },
{ key: 2, items: [ "first", "second", "third" ] }
],
linkDataArray: [
{ from: 1, to: 2 }
]
});
</pre>
<script>goCode("simple", 450, 200)</script>
<p>
Note that the <a>Panel.itemArray</a> property is almost always bound to some data property that always has an Array as its value.
One does not use a literal or constructed Array as the initial value for the Panel.itemArray property in a template,
unless you expect all parts copied from the template will always have exactly the same unchanging list of items.
</p>
<p>
As with most data bindings, the name of the data property does not really matter.
In this example, the property name is "items", but you can use whatever name seems appropriate to your app.
You can also have more than one item array in a node or link.
</p>
<h2 id="ItemTemplates">Item templates</h2>
<p>
You can customize the elements created for each array item by specifying the <a>Panel.itemTemplate</a>.
The template must be an instance of <a>Panel</a>.
Each item in the bound Array will get a copy of this Panel that is added to the Panel with the <a>Panel.itemArray</a>.
The <a>Panel.data</a> will be the item in the Array, so all of the normal data binding functionality is available to customize each item Panel.
</p>
<p>
This use of templates and data binding is similar to the way <a>Node</a>s are created automatically in a <a>Diagram</a> based on an Array of node data in the model.
The value of <a>Diagram.nodeTemplate</a> must always be a <a>Node</a> or a simple <a>Part</a>;
the value of <a>Panel.itemTemplate</a> must always be a <a>Panel</a> and cannot be a <a>Part</a>.
</p>
<p>
Note that each item in the <a>Panel.itemArray</a> can be any JavaScript value, including strings and numbers.
This is different than the values held by the <a>Model.nodeDataArray</a>, which must all be JavaScript Objects.
The item <a>Panel.data</a> value may be a string, as it is in this example;
the <a>Part.data</a> value will always be an Object.
</p>
<p>
Here is a simple customization of the <a>Panel.itemTemplate</a>, working with the same model as above.
Note that the second argument to the <a>Binding</a> constructor in this case is the empty string,
because strings (and numbers) do not have many useful properties.
</p>
<pre class="lang-js" id="vertical">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle",
{ fill: "#3AA7A3" }),
$(go.Panel, "Vertical",
new go.Binding("itemArray", "items"),
{
itemTemplate:
$(go.Panel, "Auto",
{ margin: 2 },
$(go.Shape, "RoundedRectangle",
{ fill: "#91E3E0" }),
$(go.TextBlock, new go.Binding("text", ""),
{ margin: 2 })
) // end of itemTemplate
})
);
diagram.model =
$(go.GraphLinksModel,
{
nodeDataArray: [
{ key: 1, items: [ "Alpha", "Beta", "Gamma", "Delta" ] },
{ key: 2, items: [ "first", "second", "third" ] }
],
linkDataArray: [
{ from: 1, to: 2 }
]
}
);
</pre>
<script>goCode("vertical", 450, 200)</script>
<p>
However even when binding to strings or numbers one could make the use of converters to get the desired binding values.
</p>
<p>
Of course if the array items are Objects, you can refer to their properties just as you can in a <a>Diagram.nodeTemplate</a>.
As with node data, you can have as many properties on your item data as your app demands, using whatever property names you prefer.
Use data binding to automatically use those property values to customize the appearance and behavior of your item Panels.
</p>
<pre class="lang-js" id="verticalobjects">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle",
{ fill: "#3AA7A3" }),
$(go.Panel, "Vertical",
new go.Binding("itemArray", "items"),
{
itemTemplate:
$(go.Panel, "Auto",
{ margin: 2 },
$(go.Shape, "RoundedRectangle",
{ fill: "#91E3E0" },
new go.Binding("fill", "c")),
$(go.TextBlock, new go.Binding("text", "t"),
{ margin: 2 })
)
})
);
diagram.model =
$(go.GraphLinksModel,
{
nodeDataArray: [
{
key: 1,
items: [
{ t: "Alpha", c: "orange" },
{ t: "Beta" },
{ t: "Gamma", c: "green" },
{ t: "Delta", c: "yellow" }
]
},
{
key: 2,
items: [
{ t: "first", c: "red" },
{ t: "second", c: "cyan" },
{ t: "third" }
]
}
],
linkDataArray: [
{ from: 1, to: 2 }
]
}
);
</pre>
<script>goCode("verticalobjects", 450, 200)</script>
<h2 id="DifferentPanelTypes">Different Panel types</h2>
<p>
Although <a>Panel</a>s that have an item array are often of type <a>Panel,Vertical</a>,
you can use other panel types that support a variable number of elements.
The most common types are <a>Panel,Vertical</a>, <a>Panel,Horizontal</a>, <a>Panel,Table</a>, and <a>Panel,Position</a>.
It does not make sense to use a <a>Panel,Viewbox</a> panel, because that panel type only supports a single element.
</p>
<p>
If the panel type is <a>Panel,Spot</a>, <a>Panel,Auto</a>, or <a>Panel,Link</a>,
the first child element of the Panel is assumed to be the "main" object and is kept as the first child
in addition to all of the nested panels created for the values in the <a>Panel.itemArray</a>.
</p>
<p>
Here is an example of a horizontal Panel:
</p>
<pre class="lang-js" id="horizontal">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle",
{ fill: "gold" }),
$(go.Panel, "Horizontal",
{ margin: 4 },
new go.Binding("itemArray", "a"),
{
itemTemplate:
$(go.Panel, "Auto",
{ margin: 2 },
$(go.Shape, "RoundedRectangle",
{ fill: "white" }),
$(go.TextBlock, new go.Binding("text", ""),
{ margin: 2 })
) // end of itemTemplate
})
);
diagram.model =
$(go.GraphLinksModel,
{
nodeDataArray: [
{ key: "n1", a: [ 23, 17, 45, 21 ] },
{ key: "n2", a: [ 1, 2, 3, 4, 5 ] }
],
linkDataArray: [
{ from: "n1", to: "n2" }
]
}
);
</pre>
<script>goCode("horizontal", 450, 200)</script>
<p>
When using a <a>Panel</a> of type <a>Panel,Table</a> as the container, it is commonplace
to use an item template that is of type <a>Panel,TableRow</a> or <a>Panel,TableColumn</a>.
This is the only way to specify the individual column or row indexes for the elements inside the template.
</p>
<pre class="lang-js" id="table">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, { fill: "lightgray" }),
$(go.Panel, "Table",
new go.Binding("itemArray", "people"),
{ margin: 4,
defaultAlignment: go.Spot.Left,
itemTemplate:
$(go.Panel, "TableRow",
new go.Binding("background", "back"),
$(go.TextBlock, new go.Binding("text", "name"),
{ column: 0, margin: 2, font: "bold 10pt sans-serif" }),
$(go.TextBlock, new go.Binding("text", "phone"),
{ column: 1, margin: 2 }),
$(go.TextBlock, new go.Binding("text", "loc"),
{ column: 2, margin: 2 })
) // end of itemTemplate
})
);
diagram.model =
$(go.GraphLinksModel,
{
nodeDataArray: [
{ key: "group1",
people: [
{ name: "Alice", phone: "2345", loc: "C4-E18" },
{ name: "Bob", phone: "9876", loc: "E1-B34", back: "red" },
{ name: "Carol", phone: "1111", loc: "C4-E23" },
{ name: "Ted", phone: "2222", loc: "C4-E197" }
] },
{ key: "group2",
people: [
{ name: "Robert", phone: "5656", loc: "B1-A27" },
{ name: "Natalie", phone: "5698", loc: "B1-B6" }
] }
],
linkDataArray: [
{ from: "group1", to: "group2" }
]
}
);
</pre>
<script>goCode("table", 450, 200)</script>
<p>
Note in this case the item template has a data binding of the TableRow Panel's <a>Panel.background</a> property
to the item data's "back" property.
</p>
<p>
Sometimes one wants to get the row for a particular item, or one wants to have a property value depend on the row index.
You can always depend on the value of <a>Panel.itemIndex</a> to get that property.
If the item Panel is of type <a>Panel,TableRow</a>, the item Panel's <a>GraphObject.row</a> property will also be set to the zero-based row number,
so you can access it in code by finding that Panel.
The same is true for <a>GraphObject.column</a> if the itemTemplate is a <a>Panel,TableColumn</a> Panel.
</p>
<p>
Because that property is set when the item panels are created for Array item data, you can create <a>Binding</a>s where the source
is that "row" property: <code>new go.Binding("targetProperty", "row", function(i) { return ...; }).ofObject()</code>.
The following example demonstrates binding the Panel.background property to be light green if the row is even.
</p>
<pre class="lang-js" id="alternating">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, { fill: "white" }),
$(go.Panel, "Table",
new go.Binding("itemArray", "people"),
{
defaultAlignment: go.Spot.Left,
itemTemplate:
$(go.Panel, "TableRow",
new go.Binding("background", "row",
function(i) { return i%2 === 0 ? "lightgreen" : "transparent" })
.ofObject(),
$(go.TextBlock, new go.Binding("text", "name"),
{ column: 0, margin: 2, font: "bold 10pt sans-serif" }),
$(go.TextBlock, new go.Binding("text", "phone"),
{ column: 1, margin: 2 }),
$(go.TextBlock, new go.Binding("text", "loc"),
{ column: 2, margin: 2 }),
$("Button",
{
column: 3,
margin: new go.Margin(0, 1, 0, 0),
click: function(e, obj) {
// OBJ is this Button Panel;
// find the TableRow Panel containing it
var itempanel = obj.panel;
alert("Clicked on row " + itempanel.row + " for " + itempanel.data.name);
}
},
$(go.Shape, "FivePointedStar",
{ desiredSize: new go.Size(8, 8) })
)
) // end of itemTemplate
})
);
diagram.model =
$(go.GraphLinksModel,
{
nodeDataArray: [
{ key: "group1",
people: [
{ name: "Alice", phone: "2345", loc: "C4-E18" },
{ name: "Bob", phone: "9876", loc: "E1-B34" },
{ name: "Carol", phone: "1111", loc: "C4-E23" },
{ name: "Ted", phone: "2222", loc: "C4-E197" },
{ name: "Robert", phone: "5656", loc: "B1-A27" },
{ name: "Natalie", phone: "5698", loc: "B1-B6" }
] }
]
}
);
</pre>
<script>goCode("alternating", 450, 200)</script>
<p>
The "Button" Panel in the item template also demonstrates how one can get the particular row index
as well as the data to which the item panel is bound.
</p>
<p>
The natural way to have a distinct header for a Table Panel is to have the first row (i.e. the first item)
hold the data for the header, but have it be styled differently.
If you want such a behavior, you will want to use multiple templates -- see the example in <a href="templateMaps.html">Template Maps</a>.
</p>
<p>
If instead you want to have a table header that is "fixed" and not dependent on item Array data,
you can have a single "TableRow" (or "TableColumn") Panel in the "Table" Panel that is kept if <a>Panel.isPanelMain</a> is true.
</p>
<pre class="lang-js" id="header">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, { fill: "white" }),
$(go.Panel, "Table",
new go.Binding("itemArray", "people"),
{
defaultAlignment: go.Spot.Left,
defaultColumnSeparatorStroke: "black",
itemTemplate: // the row created for each item in the itemArray
$(go.Panel, "TableRow",
$(go.TextBlock, new go.Binding("text", "name"),
{ column: 0, margin: 2, font: "bold 10pt sans-serif" }),
$(go.TextBlock, new go.Binding("text", "phone"),
{ column: 1, margin: 2 }),
$(go.TextBlock, new go.Binding("text", "loc"),
{ column: 2, margin: 2 })
)
},
// define the header as a literal row in the table,
// not bound to any item, but bound to Node data
$(go.Panel, "TableRow",
{ isPanelMain: true }, // needed to keep this element when itemArray gets an Array
$(go.TextBlock, "Person",
{ column: 0, margin: new go.Margin(2, 2, 0, 2), font: "bold 10pt sans-serif" }),
$(go.TextBlock, "Phone",
{ column: 1, margin: new go.Margin(2, 2, 0, 2), font: "bold 10pt sans-serif" }),
$(go.TextBlock, "Location",
{ column: 2, margin: new go.Margin(2, 2, 0, 2), font: "bold 10pt sans-serif" })
),
$(go.RowColumnDefinition,
{ row: 0, background: "lightgray" }),
$(go.RowColumnDefinition,
{ row: 1, separatorStroke: "black" })
)
);
diagram.model =
$(go.GraphLinksModel,
{
nodeDataArray: [
{ key: "group1",
people: [
{ name: "Alice", phone: "2345", loc: "C4-E18" },
{ name: "Bob", phone: "9876", loc: "E1-B34" },
{ name: "Carol", phone: "1111", loc: "C4-E23" },
{ name: "Ted", phone: "2222", loc: "C4-E197" },
{ name: "Robert", phone: "5656", loc: "B1-A27" },
{ name: "Natalie", phone: "5698", loc: "B1-B6" }
] }
]
}
);
</pre>
<script>goCode("header", 450, 200)</script>
<p>
In such cases the constant header element, the literal "TableRow" Panel in the node template,
will have a <a>GraphObject.row</a> == 0 and a <a>Panel.itemIndex</a> that is NaN.
The "TableRow" Panel corresponding to the first item data, <code>panel.itemArray[0]</code>,
will have a <a>GraphObject.row</a> == 1, matching its position in the list of <a>Panel.elements</a>.
But it will have a <a>Panel.itemIndex</a> == 0, matching its position in the itemArray.
</p>
<h2 id="ArraysInModels">Arrays in Models</h2>
<p>
When a data-bound Part is copied, the Part's <a>Part.data</a>, which must be a JavaScript Object, is copied too.
The normal copying method, <a>Model.copyNodeData</a>, makes a shallow copy of the original data object.
</p>
<p>
However that is probably not the desired behavior for Arrays.
When you use item Arrays, you normally do <em>not</em> want to share those Arrays between copies of the Node.
If your node data is not copied correctly, unexpected behavior may occur.
So when you are using item Arrays and permit users to copy nodes, you will need to make sure such Arrays and their objects are copied.
For the simplest cases, it may be sufficient to set <a>Model.copiesArrays</a> and <a>Model.copiesArrayObjects</a> to true.
More generally you may want to implement your own node data copier function
and assign it to <a>Model.copyNodeDataFunction</a>.
</p>
<p>
This is demonstrated by the <a href="../samples/dynamicPorts.html">Dynamic Ports</a> sample,
which not only needs to copy the four item Arrays that each node data holds,
but also each Object that is in each of those Arrays.
In that sample the <a>Model.copiesArrays</a> and <a>Model.copiesArrayObjects</a> properties
are set to true in the JSON-formatted representation of the model that is loaded into the diagram.
</p>
<p>
For <a>GraphLinksModel</a>s, there is also a similar members for link data:
the <a>GraphLinksModel.copyLinkData</a> method and <a>GraphLinksModel.copyLinkDataFunction</a> property.
</p>
<p>
If you need to dynamically modify the value of a property of an item data, call <a>Model.setDataProperty</a>,
just as you would for node data or link data.
</p>
<p>
If you need to add or remove items from an item Array, call the <a>Model.insertArrayItem</a> or <a>Model.removeArrayItem</a> methods.
</p>
</div>
</div>
</body>
</html>
+179
View File
@@ -0,0 +1,179 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Layers -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Layers and Z-ordering</h1>
<p>
All <a>Part</a>s that are in a <a>Diagram</a> actually belong to a <a>Layer</a> in the diagram.
You can control the visibility, Z-order, and various user permissions for all of the parts in each layer.
</p>
<p>
Parts can be individually modified to toggle their visibility using <a>Part.visible</a> or <a>Part.opacity</a>.
Parts can be individually Z-ordered within layers using <a>Part.zOrder</a>.
</p>
<h2 id="StandardLayers">Standard Layers</h2>
<p>
Every Diagram starts off with several standard layers.
These are their <a>Layer.name</a>s, in order from furthest behind to most in front:
</p>
<ul>
<li><b>"Grid"</b>, holding the <a>Diagram.grid</a> and any other static Parts that you wish to be behind everything</li>
<li><b>"Background"</b></li>
<li><b>""</b>, the default layer</li>
<li><b>"Foreground"</b></li>
<li><b>"Adornment"</b>, holding <a>Adornment</a>s for selection and various Tools</li>
<li><b>"Tool"</b>, holding Parts used in the execution of various Tools</li>
</ul>
<p>
Each Part is placed in a Layer according to its <a>Part.layerName</a>.
The default value is the empty string.
Use <a>Diagram.findLayer</a> to find a Layer given a layer name.
Change which layer a part is in by setting <a>Part.layerName</a>.
</p>
<p>
Changes to Parts in the "Grid", "Adornment", and "Tool" Layers are automatically ignored by the <a>UndoManager</a>,
because <a>Layer.isTemporary</a> is true.
</p>
<p>
Parts in the "Grid" Layer are not selectable, because <a>Layer.allowSelect</a> is false.
This prevents the user from selecting the background grid when it is visible.
</p>
<h2 id="LayersExample">Layers Example</h2>
<p>
This example adds several <a>Layer</a>s to the diagram, each named by a color,
and then creates a bunch of colored parts at random locations.
Every <a>Part.layerName</a> is data-bound to the "color" property of the node data.
</p>
<p>
In addition there are checkboxes for each layer, controlling the visibility of the respective layer.
You can see how all of the parts of the same color appear and disappear according to the value of the checkbox.
Furthermore you can see how they all have the same depth in the Z-ordering.
</p>
<p>
Finally, each Part has a <a>Part.selectionChanged</a> function which puts the part in the "Foreground"
layer when it is selected and back in its normal color layer when it is not selected.
</p>
<pre class="lang-js" id="layers">
// These new layers come in front of the standard regular layers,
// but behind the "Foreground" layer:
var forelayer = diagram.findLayer("Foreground");
diagram.addLayerBefore($(go.Layer, { name: "blue" }), forelayer);
diagram.addLayerBefore($(go.Layer, { name: "green" }), forelayer);
diagram.addLayerBefore($(go.Layer, { name: "orange" }), forelayer);
diagram.nodeTemplate =
$(go.Part, "Spot", // no links or grouping, so can use the simpler Part class
new go.Binding("layerName", "color"),
new go.Binding("location", "loc"),
$(go.Shape,
{ width: 80, height: 80 },
new go.Binding("fill", "color")),
$(go.TextBlock,
{ stroke: "white", font: "bold 12px sans-serif" }),
{
selectionChanged: function(p) {
p.layerName = (p.isSelected ? "Foreground" : p.data.color);
},
layerChanged: function(p, oldLayer, newLayer) {
if (newLayer !== null) p.elt(1).text = newLayer.name;
}
}
);
var array = [];
for (var i = 0; i &lt; 12; i++) {
var data = { loc: new go.Point(Math.random()*520, Math.random()*200) };
switch (Math.floor(Math.random()*3)) {
case 0: data.color = "blue"; break;
case 1: data.color = "green"; break;
case 2: data.color = "orange"; break;
default: data.color = "Foreground"; break;
}
array.push(data);
}
diagram.model.nodeDataArray = array;
diagram.undoManager.isEnabled = true;
// define this function so that the checkbox event handlers can call it
toggleVisible = function(layername, e) {
diagram.commit(function(d) {
var layer = d.findLayer(layername);
if (layer !== null) layer.visible = e.currentTarget.checked;
}, 'toggle ' + layername);
};
</pre>
<script>goCode("layers", 610, 290)</script>
Layer visibility:<br />
<input type="checkbox" checked="checked" onclick="toggleVisible('blue', event)" />blue
<input type="checkbox" checked="checked" onclick="toggleVisible('green', event)" />green
<input type="checkbox" checked="checked" onclick="toggleVisible('orange', event)" />orange
<input type="checkbox" checked="checked" onclick="toggleVisible('Foreground', event)" />Foreground
<h2 id="ZOrderExample">ZOrder Example</h2>
<p>
This example adds several <a>Part</a>s to one Layer (the default) in the diagram.
Every <a>Part.zOrder</a> is data-bound to the "zOrder" property of the node data, as is its text.
</p>
<p>
Buttons on the Part can be used to modify the z-order of each.
</p>
<pre class="lang-js" id="zOrder">
function changeZOrder(amt, obj) {
diagram.commit(function(d) {
var data = obj.part.data;
d.model.set(data, "zOrder", data.zOrder + amt);
}, 'modified zOrder');
}
diagram.nodeTemplate =
$(go.Part, "Spot",
new go.Binding("layerName", "color"),
new go.Binding("location", "loc"),
new go.Binding("zOrder"),
$(go.Shape,
{ width: 100, height: 100, stroke: 'rgb(50,50,50)', fill: 'rgb(50,100,255)' }),
$(go.TextBlock,
{ font: "52px sans-serif", stroke: 'whitesmoke' },
new go.Binding("text", "zOrder")),
$("Button",
{ alignment: go.Spot.BottomLeft, alignmentFocus: go.Spot.BottomLeft,
click: function (e, obj) { changeZOrder(-1, obj); } },
$(go.Shape, "LineH", { width: 14, height: 14 })),
$("Button",
{ alignment: go.Spot.BottomRight, alignmentFocus: go.Spot.BottomRight,
click: function (e, obj) { changeZOrder(1, obj); } },
$(go.Shape, "PlusLine", { width: 14, height: 14 }))
);
var array = [];
for (var i = 0; i < 12; i++) {
var data = { loc: new go.Point(Math.random()*500, Math.random()*200) };
data.zOrder = (Math.floor(Math.random()*20))
array.push(data);
}
diagram.model.nodeDataArray = array;
diagram.undoManager.isEnabled = true;
</pre>
<script>goCode("zOrder", 610, 310)</script>
</div>
</div>
</body>
</html>
+504
View File
@@ -0,0 +1,504 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Layouts -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Diagram Layouts</h1>
<p>
In general terms, a "layout" is a way of sizing and positioning a collection of objects.
HTML has its own layouts for its HTML elements.
In <b>GoJS</b> you have already seen many examples of Panel layout, such as Auto or Table,
which sizes and positions <a>GraphObject</a>s within a <a>Panel</a>.
<b>GoJS</b> also provides Diagram layouts, which position <a>Node</a>s and route <a>Link</a>s
within a <a>Diagram</a> or a <a>Group</a>.
</p>
<p>
Naturally the principal purpose of each diagram <a>Layout</a> is to position nodes, typically by calling <a>Part.move</a>.
But layouts also may also result in custom routing of the links, by setting properties on each <a>Link</a>.
For example <a>TreeLayout</a> also ensures that links are routed in the expected direction by setting
<a>Link.fromSpot</a> and <a>Link.toSpot</a> depending on the <a>TreeLayout.angle</a>.
(However, that behavior can be disabled by setting <a>TreeLayout.setsPortSpot</a> and <a>TreeLayout.setsChildPortSpot</a>.
The same is true for some other layouts.)
</p>
<p>
Diagram layouts can be accomplished in several manners.
Manual layouts occur because the user moves nodes, thereby establishing new positions for those nodes.
Such layouts might be saved in some persistent data format and later loaded using data binding or assignments in code.
Programmatic layouts happen when some code executes to set the <a>Part</a> position or location.
Automatic layouts are programmatic layouts that are implemented by the <a>Layout</a> class or its subclasses.
</p>
<h2 id="DefaultLayout">Default Layout</h2>
<p>
The value of <a>Diagram.layout</a> defaults to an instance of <a>Layout</a>.
This kind of layout is unlike all of the other layout subclasses, in that it only sets the position of nodes
that do not already have a position -- i.e. where the X or Y of the <a>GraphObject.actualBounds</a> is NaN.
It leaves unmodified all nodes that do have a defined position, and it ignores all links.
</p>
<p>
Many of the examples you have seen so far do not set <a>Diagram.layout</a> and thus use the default layout.
Some of the examples data bind the <a>Part.location</a> or <a>GraphObject.position</a> to a data property.
Those examples are basically using manual layout, but with the node positions coming from the node data rather than from
arrangement by the user.
</p>
<p>
However many of the examples just allow the standard behavior of the <a>Layout</a> class to assign positions to the nodes
in the order in which they are seen by the layout.
Those examples are exhibiting automatic layout behavior.
</p>
<h2 id="AutomaticLayouts">Automatic Layouts</h2>
<p>
<b>GoJS</b> offers several kinds of automatic layouts, including:
</p>
<ul>
<li><a>GridLayout</a></li>
<li><a>TreeLayout</a></li>
<li><a>ForceDirectedLayout</a></li>
<li><a>LayeredDigraphLayout</a></li>
<li><a>CircularLayout</a></li>
</ul>
<p>
There are samples for each of these layouts, demonstrating the effects of setting various detailed layout properties:
</p>
<ul>
<li><a href="../samples/gLayout.html" target="samples">GridLayout Sample</a></li>
<li><a href="../samples/tLayout.html" target="samples">TreeLayout Sample</a></li>
<li><a href="../samples/fdLayout.html" target="samples">ForceDirectedLayout Sample</a></li>
<li><a href="../samples/ldLayout.html" target="samples">LayeredDigraphLayout Sample</a></li>
<li><a href="../samples/cLayout.html" target="samples">CircularLayout Sample</a></li>
</ul>
<p>
In the introduction pages and samples you will see many examples that make use of automatic layout by setting the <a>Diagram.layout</a> property. <a href="https://github.com/NorthwoodsSoftware/GoJS/search?utf8=%E2%9C%93&amp;q=%22layout%3A+%26%28go%22&amp;type=Code">Search the sources of the samples for many more examples.</a>
</p>
<h3 id="LayoutUsage">Layout Usage</h3>
<p>
You can set <a>Diagram.layout</a> in a JavaScript statement:
</p>
<pre class="lang-js">diagram.layout = new go.ForceDirectedLayout();</pre>
<p>
Or you can initialize that property using <a>GraphObject,make</a>:
</p>
<pre class="lang-js">
var diagram = $(go.Diagram, "myDiagramDiv",
{
layout: $(go.TreeLayout,
{ angle: 90, nodeSpacing: 10, layerSpacing: 30 })
});
</pre>
<p>
We recommend using <b>GraphObject.make</b> whenever you can because of the error checking that it does for property names.
</p>
<h3 id="GridLayout">Grid Layout</h3>
<p>A simple layout for placing Nodes in a grid-like arrangement.</p>
<pre class="lang-js" id="gridlayout" style="display: none;">
diagram.layout = $(go.GridLayout);
diagram.contentAlignment = go.Spot.Center;
// define a simple Node template
diagram.nodeTemplate =
$(go.Node, "Spot", // the Shape will go around the TextBlock
$(go.Shape, "Ellipse",
{ fill: 'palegreen', stroke: '#333', strokeWidth: 3, width: 40, height: 40 }
),
$(go.TextBlock,
{ margin: 3, font: 'bold 14px sans-serif', stroke: '#333' }, // some room around the text
// TextBlock.text is bound to Node.data.key
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
// Default routing is go.Link.Normal
// Default corner is 0
{ corner: 5 },
$(go.Shape, { strokeWidth: 3, stroke: "#333" })
);
// create the model data that will be represented by Nodes and Links
diagram.model = new go.GraphLinksModel(
[
{ key: "1" },
{ key: "2" },
{ key: "3" },
{ key: "4" },
{ key: "5" },
{ key: "6" },
{ key: "7" },
{ key: "8" },
{ key: "9" },
{ key: "10" },
{ key: "11" },
],
[
]);
</pre>
<script>goCode("gridlayout", 400, 120)</script>
<p>
See the <a href="../samples/gLayout.html" target="samples">GridLayout Sample</a> for a demonstration of layout options.
The <a href="../samples/swimlanes.html" target="samples">Swim Lanes</a> sample demonstrates a customization of <a>GridLayout</a>.
See more samples that make use of <a>GridLayout</a> in the <a href="../samples/index.html#gridlayout">samples index</a>.
</p>
<h id="TreeLayout">Tree Layout</h>
<p>This layout positions nodes of a tree-structured graph in layers (rows or columns).</p>
<pre class="lang-js" id="treelayout" style="display: none;">
diagram.layout = $(go.TreeLayout);
diagram.contentAlignment = go.Spot.Center;
// define a simple Node template
diagram.nodeTemplate =
$(go.Node, "Spot", // the Shape will go around the TextBlock
$(go.Shape, "Ellipse",
{ fill: 'palegreen', stroke: '#333', strokeWidth: 3, width: 40, height: 40 }
),
$(go.TextBlock,
{ margin: 3, font: 'bold 14px sans-serif', stroke: '#333' }, // some room around the text
// TextBlock.text is bound to Node.data.key
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
// Default routing is go.Link.Normal
// Default corner is 0
{ corner: 5 },
$(go.Shape, { strokeWidth: 3, stroke: "#333" })
);
// create the model data that will be represented by Nodes and Links
diagram.model = new go.GraphLinksModel(
[
{ key: "1" },
{ key: "2" },
{ key: "3" },
{ key: "4" },
{ key: "5" },
{ key: "6" }
],
[
{ from: "1", to: "2" },
{ from: "1", to: "3" },
{ from: "3", to: "4" },
{ from: "3", to: "5" },
{ from: "3", to: "6" }
]);
</pre>
<script>goCode("treelayout", 400, 200)</script>
<p>
See the <a href="../samples/tLayout.html" target="samples">TreeLayout sample</a> for a demonstration of layout options.
The <a href="../samples/orgChartEditor.html" target="samples">Org Chart Editor</a>,
<a href="../samples/parseTree.html" target="samples">Parse Tree</a>,
<a href="../samples/swimBands.html" target="samples">Layer Bands</a>, and
<a href="../samples/virtualizedTreeLayout.html" target="samples">Virtualized Tree</a>
samples demonstrate customization of <a>TreeLayout</a>.
See more samples that make use of <a>TreeLayout</a> in the <a href="../samples/index.html#treelayout">samples index</a>.
</p>
<h3 id="ForceDirectedLayout">Force-Directed Layout</h3>
<p>Force-directed layout treats the graph as if it were a system of physical bodies with forces acting on them and between them.</p>
<pre class="lang-js" id="fdlayout" style="display: none;">
diagram.layout = $(go.ForceDirectedLayout);
diagram.initialAutoScale = go.Diagram.Uniform;
diagram.contentAlignment = go.Spot.Center;
// define a simple Node template
diagram.nodeTemplate =
$(go.Node, "Spot", // the Shape will go around the TextBlock
$(go.Shape, "Ellipse",
{ fill: 'palegreen', stroke: '#333', strokeWidth: 3, width: 40, height: 40 }
),
$(go.TextBlock,
{ margin: 3, font: 'bold 14px sans-serif', stroke: '#333' }, // some room around the text
// TextBlock.text is bound to Node.data.key
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
// Default routing is go.Link.Normal
// Default corner is 0
{ corner: 5 },
$(go.Shape, { strokeWidth: 3, stroke: "#333" })
);
// create the model data that will be represented by Nodes and Links
diagram.model = new go.GraphLinksModel(
[
{ key: "1" },
{ key: "2" },
{ key: "3" },
{ key: "4" },
{ key: "5" },
{ key: "6" },
{ key: "7" },
{ key: "8" },
{ key: "9" },
{ key: "10" },
{ key: "11" },
],
[
{ from: "6", to: "2" },
{ from: "3", to: "4" },
{ from: "3", to: "5" },
{ from: "3", to: "6" },
{ from: "6", to: "1" },
{ from: "6", to: "7" },
{ from: "4", to: "8" },
{ from: "4", to: "9" },
{ from: "4", to: "10" },
{ from: "4", to: "11" },
]);
</pre>
<script>goCode("fdlayout", 400, 200)</script>
<p>
See the <a href="../samples/fdLayout.html" target="samples">ForceDirectedLayout sample</a> for a demonstration of layout options.
That sample also demonstrates a simple customization of <a>ForceDirectedLayout</a>.
The <a href="../samples/virtualizedForceLayout.html" target="samples">Virtualized Force Directed</a> sample
demonstrates a more complicated customization of <a>ForceDirectedLayout</a>.
See more samples that make use of <a>ForceDirectedLayout</a> in the <a href="../samples/index.html#forcedirectedlayout">samples index</a>.
</p>
<h3 id="LayeredDigraphLayout">Layered Digraph Layout</h3>
<p>This arranges nodes of directed graphs into layers (rows or columns).</p>
<pre class="lang-js" id="ldllayout" style="display: none;">
diagram.layout = $(go.LayeredDigraphLayout);
diagram.contentAlignment = go.Spot.Center;
// define a simple Node template
diagram.nodeTemplate =
$(go.Node, "Spot", // the Shape will go around the TextBlock
$(go.Shape, "Ellipse",
{ fill: 'palegreen', stroke: '#333', strokeWidth: 3, width: 40, height: 40 }
),
$(go.TextBlock,
{ margin: 3, font: 'bold 14px sans-serif', stroke: '#333' }, // some room around the text
// TextBlock.text is bound to Node.data.key
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
// Default routing is go.Link.Normal
// Default corner is 0
{ corner: 5 },
$(go.Shape, { strokeWidth: 3, stroke: "#333" })
);
// create the model data that will be represented by Nodes and Links
diagram.model = new go.GraphLinksModel(
[
{ key: "1" },
{ key: "2" },
{ key: "3" },
{ key: "4" },
{ key: "5" },
{ key: "6" },
{ key: "7" },
],
[
{ from: "1", to: "2" },
{ from: "1", to: "3" },
{ from: "3", to: "4" },
{ from: "3", to: "5" },
{ from: "3", to: "6" },
{ from: "2", to: "5" },
{ from: "1", to: "5" },
{ from: "1", to: "7" },
{ from: "6", to: "7" },
]);
</pre>
<script>goCode("ldllayout", 400, 300)</script>
<p>
See the <a href="../samples/ldLayout.html" target="samples">LayeredDigraphLayout sample</a> for a demonstration of layout options.
The <a href="../samples/genogram.html" target="samples">Genogram</a> sample demonstrates a complex customization of <a>LayeredDigraphLayout</a>.
See more samples that make use of <a>LayeredDigraphLayout</a> in the <a href="../samples/index.html#layereddigraphlayout">samples index</a>.
</p>
<h3 id="CircularLayout">Circular Layout</h3>
<p>This layout positions nodes in a circular or elliptical arrangement.</p>
<pre class="lang-js" id="circularLayout" style="display: none;">
diagram.layout = $(go.CircularLayout);
diagram.contentAlignment = go.Spot.Center;
// define a simple Node template
diagram.nodeTemplate =
$(go.Node, "Spot", // the Shape will go around the TextBlock
$(go.Shape, "Ellipse",
{ fill: 'palegreen', stroke: '#333', strokeWidth: 3, width: 40, height: 40 }
),
$(go.TextBlock,
{ margin: 3, font: 'bold 14px sans-serif', stroke: '#333' }, // some room around the text
// TextBlock.text is bound to Node.data.key
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
// Default routing is go.Link.Normal
// Default corner is 0
{ corner: 5 },
$(go.Shape, { strokeWidth: 3, stroke: "#333" })
);
// create the model data that will be represented by Nodes and Links
diagram.model = new go.GraphLinksModel(
[
{ key: "1" },
{ key: "2" },
{ key: "3" },
{ key: "4" },
{ key: "5" },
{ key: "6" },
{ key: "7" },
],
[
{ from: "1", to: "2" },
{ from: "1", to: "3" },
{ from: "3", to: "4" },
{ from: "3", to: "5" },
{ from: "3", to: "6" },
{ from: "2", to: "5" },
{ from: "1", to: "5" },
{ from: "1", to: "7" },
{ from: "6", to: "7" },
]);
</pre>
<script>goCode("circularLayout", 400, 200)</script>
<p>
See the <a href="../samples/cLayout.html" target="samples">CircularLayout sample</a> for a demonstration of layout options.
The <a href="../samples/friendWheel.html" target="samples">Friend Wheel</a> sample demonstrates a simple customization of <a>CircularLayout</a>.
See more samples that make use of <a>CircularLayout</a> in the <a href="../samples/index.html#circularlayout">samples index</a>.
</p>
<h3 id="CustomLayouts">Custom Layouts</h3>
<p>
GoJS allows for the creation of custom layouts.
The intro page on <a href="extensions.html">GoJS extensions</a> gives a simple example of a custom layout.
See more samples that make use of custom layouts in the <a href="../samples/index.html#customlayout">samples index</a>.
</p>
<p>
There are also many layouts that are extensions -- not predefined in the <code>go.js</code> or <code>go-debug.js</code> library,
but available as source code in one of the three extension directories, with some documentation, and with corresponding samples.
</p>
<ul>
<li><a>DoubleTreeLayout</a>: sample at <a href="../samples/doubleTree.html" target="samples">DoubleTreeLayout Sample</a>, defined in <a href="../extensions/DoubleTreeLayout.js">DoubleTreeLayout.js</a></li>
<li><a>FishboneLayout</a>: sample at <a href="../extensions/Fishbone.html" target="samples">FishboneLayout Sample</a>, defined in <a href="../extensions/FishboneLayout.js">FishboneLayout.js</a></li>
<li><a>PackedLayout</a>: sample at <a href="../extensions/PackedLayout.html" target="samples">PackedLayout Sample</a>, defined in <a href="../extensionsTS/PackedLayout.js">PackedLayout.js</a></li>
<li><a>ParallelLayout</a>: sample at <a href="../extensions/Parallel.html" target="samples">ParallelLayout Sample</a>, defined in <a href="../extensions/ParallelLayout.js">ParallelLayout.js</a></li>
<li><a>SepentineLayout</a>: sample at <a href="../extensions/Serpentine.html" target="samples">SerpentineLayout Sample</a>, defined in <a href="../extensions/SerpentineLayout.js">SerpentineLayout.js</a></li>
<li><a>SpiralLayout</a>: sample at <a href="../extensions/Spiral.html" target="samples">SpiralLayout Sample</a>, defined in <a href="../extensions/SpiralLayout.js">SpiralLayout.js</a></li>
<li><a>SwimLaneLayout</a>: sample at <a href="../extensions/SwimLaneLayout.html" target="samples">SwimLaneLayout Sample</a>, defined in <a href="../extensionsTS/SwimLaneLayout.js">SwimLaneLayout.js</a></li>
<li><a>TableLayout</a>: sample at <a href="../extensions/Table.html" target="samples">TableLayout Sample</a>, defined in <a href="../extensions/TableLayout.js">TableLayout.js</a></li>
<li><a>TreeMapLayout</a>: sample at <a href="../extensions/TreeMap.html" target="samples">TreeMapLayout Sample</a>, defined in <a href="../extensions/TreeMapLayout.js">TreeMapLayout.js</a></li>
</ul>
<h2 id="LayoutInvalidation">Layout Invalidation</h2>
<p>
A layout is considered "valid" when it has performed its positioning of its nodes and perhaps routed its links.
However some kinds of changes cause a layout to become "invalid", thereby causing it to be performed again in the near future.
Because layouts can be computationally expensive, automatic layouts are not performed as soon as a layout is invalidated.
Instead they are typically performed at the end of a transaction.
</p>
<p>
The most common reasons for a layout to be invalidated are because a node or a link has been added or removed from the collection
of nodes and links that a layout is responsible for, or because a node or a link has changed visibility, or because a node has changed size.
If you do not want an automatic layout to happen when such a change occurs, it may be easiest to set <a>Layout.isOngoing</a> to false.
</p>
<p>
Another common situation is where you have set <a>Diagram.layout</a> to some kind of layout but you want to load a diagram (model)
that contains manually positioned or adjusted node locations. The <a>Binding</a> of <a>Part.location</a> to the model data is effective,
but the locations are lost when a layout is performed immediately after loading. This situation can be avoided by setting
<a>Layout.isInitial</a> to false. After the initial layout the layout might still be invalidated by adding or removing or changing
the visibility of a node or a link or by a change in node size, unless you have also set <a>Layout.isOngoing</a> to false.
When both <a>Layout.isInitial</a> and <a>Layout.isOngoing</a> are false, you can still explicitly cause a layout to happen by either
calling <a>Layout.invalidateLayout</a> or by calling <a>Diagram.layoutDiagram</a> with a <code>true</code> argument.
</p>
<p>
For example, in editors it is commonplace to have TwoWay Bindings on <a>Node.location</a> to save manually adjusted node locations.
This means that saved models will have saved locations for all of the nodes.
But if you create a new model without all of the node data objects having real locations,
you will want a layout to be performed initially when the model is loaded.
You can accomplish this by setting <a>Layout.isInitial</a> to false
(and optionally <a>Layout.isOngoing</a> to false, if that is what you want when users add or remove nodes or links)
and then implementing an "InitialLayoutCompleted" <a>DiagramEvent</a> listener that decides whether a layout is needed.
The decision could be to look at a flag that you add to the <a>Model.modelData</a>.
Or you could look at all of the nodes to make sure their locations have real values:
</p>
<pre class="lang-js">
$(go.Diagram, . . .,
{
. . .,
layout: $(go.TreeLayout, { isInitial: false, isOngoing: false }, . . .),
"InitialLayoutCompleted": function(e) {
// if not all Nodes have real locations, force a layout to happen
if (!e.diagram.nodes.all(function(n) { return n.location.isReal(); })) {
e.diagram.layoutDiagram(true);
}
}
})
</pre>
<p>
But if you do not want a change to a particular Node or Link to cause an automatic layout, yet you do want that invalidation for other Nodes or Links,
you can set the <a>Part.layoutConditions</a> property to the combination of <a>Part</a> "Layout..." flags that suits your needs.
It is most common to not want a layout for the <a>Part,LayoutNodeSized</a> condition:
</p>
<pre class="lang-js">
$(go.Node, . . .,
{ layoutConditions: go.Part.LayoutStandard & ~go.Part.LayoutNodeSized },
. . .
)
</pre>
<p>
Parts that remain not visible or that are in layers that are <a>Layer.isTemporary</a> also never invalidate any Layout.
</p>
<p>
Finally, you can set <a>Part.isLayoutPositioned</a> to false in order for the Layout to completely ignore that Part.
But you will have to make sure that that Part does have a real <a>Part.location</a>, since no layout will set it for you.
Without a real location the part will not be visible anywhere in the diagram.
Furthermore if a node has isLayoutPositioned set to false, Layouts will not only ignore that node but also all links connecting with that node.
Because the node will not be moved by the layout, it might overlap with the laid-out nodes and links.
You can also set or bind <a>Part.isLayoutPositioned</a> to false on Links in order to have the layout ignore those links.
This is demonstrated in <a href="../samples/orgChartExtras.html" target="samples">Org Chart Extras</a>.
</p>
</div>
</div>
</body>
</html>
+176
View File
@@ -0,0 +1,176 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Legends and Titles -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Legends and Titles</h1>
<p>
Sometimes in addition to the nodes and links that are the subject of a diagram,
one also wants to display a "legend" or "key" describing the different kinds of nodes or links.
Perhaps one also wants there to be "title" for the diagram in large letters.
</p>
<h2 id="OutsideOfDiagram">Outside of Diagram</h2>
<p>
First, you must consider whether titles or legends should be part of the diagram or not.
You can create whatever you want in HTML outside of the diagram.
</p>
<script>
function setupForLegend(diagram) {
var $ = go.GraphObject.make;
diagram.layout = $(go.TreeLayout, { angle: 90 });
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle",
{ fill: "white" },
new go.Binding("fill", "color")),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
{ routing: go.Link.Orthogonal, corner: 8 },
$(go.Shape, { strokeWidth: 2 }));
diagram.model = new go.TreeModel([
{ key: "Alpha", color: "purple" },
{ key: "Beta", parent: "Alpha", color: "red" },
{ key: "Gamma", parent: "Alpha", color: "white" },
]);
}
</script>
<pre class="lang-js" style="display:none" id="diagramPre">
setupForLegend(diagram);
</pre>
<div style="width:100%">
<span id="diagramSpan" style="display: inline-block">
<span style="font: bold 16pt sans-serif; color:green">An HTML Title for a Diagram</span>
</span>
<div style="display: inline-block; vertical-align: bottom; border: 2px solid green">
<b>Color key:</b>
<table>
<tr><td><span style="color:purple; font-weight:bold">Purple</span> nodes are juicy and sweet</td></tr>
<tr><td><span style="color:red; font-weight:bold">Red</span> nodes are very angry</td></tr>
<tr><td><span style="color:white;background:gray;font-weight:bold">White</span> nodes sleep well at night</td></tr>
</table>
</div>
</div>
<script>goCode("diagramPre", 300, 200, go.Diagram, "diagramSpan");</script>
<p>
Note that anything in HTML will not automatically scroll and zoom along with the diagram's contents shown in the viewport.
But HTML elements could be positioned in front of or behind the diagram's DIV element.
</p>
<h2 id="UnmodeledParts">Unmodeled Parts</h2>
<p>
Second, you should consider whether the title or legend should be held in your data model.
Do you need to save and load that data in your database?
</p>
<p>
If you do not want to these objects to be included in your application's data model,
you can just create them as simple <a>Part</a>s and <a>Diagram.add</a> them to your diagram
at explicitly defined <a>Part.location</a>s.
</p>
<pre class="lang-js" id="unmodeled">
setupForLegend(diagram); // this creates a diagram just like the first example
diagram.add(
$(go.Part, { location: new go.Point(0, -40) },
$(go.TextBlock, "A Title", { font: "bold 24pt sans-serif", stroke: "green" })));
</pre>
<script>goCode("unmodeled", 300, 200);</script>
<p>
If you do not assign a location or position for your Parts,
and if your <a>Diagram.layout</a> (if any) does not assign any <a>Part.location</a>,
then there might not be a real location for those parts and they might not appear anywhere in the diagram.
</p>
<p class="box bg-info">
All of the predefined <a>Layout</a>s that make use of <a>LayoutNetwork</a>s, including <a>TreeLayout</a>,
do not operate on simple <a>Part</a>s but only on <a>Node</a>s and <a>Link</a>s.
If you had added a <a>Node</a> to the diagram it would have been positioned as part of this diagram's normal tree layout,
even though you explicitly set its location.
Alternatively it could still be a Node if you set its <a>Part.isLayoutPositioned</a> property to false.
</p>
<p>
You will notice that the title is selectable and movable and copyable and deletable in the diagram above.
You may want to set properties such as <a>Part.selectable</a> to false.
</p>
<p>
For an example showing a legend, see the <a href="../samples/familyTree.html" target="samples">Family Tree</a> sample.
</p>
<h3 id="ModeledParts">Modeled Parts</h3>
<p>
If on the other hand you do want to store your titles or legends in your model, you can do so using the normal mechanisms.
Typically you will use <a href="templateMaps.html">node categories and template maps</a>.
</p>
<p>
If you do not want your users to manipulate those objects, you will want to set <a>Part.selectable</a> to false.
You may want to set <a>Part.layerName</a> to "Grid", so that it is always in the background behind everything else.
(All Parts in the "Grid" Layer are automatically not selectable, because <a>Layer.allowSelect</a> is false for that <a>Layer</a>.)
</p>
<h2 id="StaticParts">Static Parts</h2>
<p>
Third, consider whether you want the title or legend to move or scale as the user scrolls or zooms the diagram.
If you want to keep such a decoration at the same position in the viewport, it might be easiest to do so by implementing
it as an HTML element that is superimposed with the diagram's DIV element.
</p>
<p>
However if you really want to implement it using a <b>GoJS</b> <a>Part</a>, you can do so by implementing a
"ViewportBoundsChanged" <a>DiagramEvent</a> listener that continually resets the position and scale to values that
make them appear not to move as the user scrolls or zooms.
</p>
<pre class="lang-js" id="static">
setupForLegend(diagram); // this creates a diagram just like the first example
diagram.add(
$(go.Part,
{
layerName: "Grid", // must be in a Layer that is Layer.isTemporary,
// to avoid being recorded by the UndoManager
_viewPosition: new go.Point(0,0) // some position in the viewport,
// not in document coordinates
},
$(go.TextBlock, "A Title", { font: "bold 24pt sans-serif", stroke: "green" })));
// Whenever the Diagram.position or Diagram.scale change,
// update the position of all simple Parts that have a _viewPosition property.
diagram.addDiagramListener("ViewportBoundsChanged", function(e) {
e.diagram.commit(function(dia) {
// only iterates through simple Parts in the diagram, not Nodes or Links
dia.parts.each(function(part) {
// and only on those that have the "_viewPosition" property set to a Point
if (part._viewPosition) {
part.position = dia.transformViewToDoc(part._viewPosition);
part.scale = 1/dia.scale;
}
})
}, "fix Parts");
});
</pre>
<script>goCode("static", 300, 200);</script>
<p>
Note that as the user pans or scrolls or zooms the diagram, the title remains at the same viewport position
at apparently the same effective size.
This example makes use of the "Grid" <a>Layer</a> (see <a href="layers.html">Intro to Layers</a>), which
is convenient for making sure the title (or legend) stays in the background and does not participate in
selection or mouse events or the <a>UndoManager</a>.
</p>
</div>
</div>
</body>
</html>
+472
View File
@@ -0,0 +1,472 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Link Labels -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="../extensions/Figures.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Labels on Links</h1>
<p>
It is common to add annotations or decorations on a link, particularly text.
</p>
<h2 id="SimpleLinkLabels">Simple Link labels</h2>
<p>
By default if you add a <a>GraphObject</a> to a <a>Link</a>, it will be positioned at the middle of the link.
In this example, we just add a <a>TextBlock</a> to the link and bind its <a>TextBlock.text</a> property
to the link data's "text" property.
</p>
<pre class="lang-js" id="simple">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
$(go.Shape), // this is the link shape (the line)
$(go.Shape, { toArrow: "Standard" }), // this is an arrowhead
$(go.TextBlock, // this is a Link label
new go.Binding("text", "text"))
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "200 50" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta", text: "a label" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("simple", 600, 100)</script>
<p>
Note that clicking on the text label results in selection of the whole Link.
</p>
<p>
Although it is commonplace to use a <a>TextBlock</a> as the link label, it can be any <a>GraphObject</a>
such as a <a>Shape</a> or an arbitrarily complex <a>Panel</a>. Here is a simple Panel label:
</p>
<pre class="lang-js" id="labels">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
$(go.Shape),
$(go.Shape, { toArrow: "Standard" }),
$(go.Panel, "Auto", // this whole Panel is a link label
$(go.Shape, "TenPointedStar", { fill: "yellow", stroke: "gray" }),
$(go.TextBlock, { margin: 3 },
new go.Binding("text", "text"))
)
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "200 50" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta", text: "hello!" } // added information for link label
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("labels", 600, 100)</script>
<p>
This also works if the link is orthogonally routed or bezier-curved.
</p>
<pre class="lang-js" id="simpleOrtho">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
{ routing: go.Link.Orthogonal },
$(go.Shape),
$(go.Shape, { toArrow: "Standard" }),
$(go.TextBlock, { textAlign: "center" }, // centered multi-line text
new go.Binding("text", "text"))
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "200 50" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta", text: "a label\non an\northo link" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("simpleOrtho", 600, 100)</script>
<p>
Although positioning the label at the middle of the link is the default behavior,
you can set <a>GraphObject</a> properties that start with "segment" to specify exactly
where and how to arrange the object along the route of the link.
</p>
<h2 id="LinkLabelSegmentIndexAndSegmentFraction">Link label segmentIndex and segmentFraction</h2>
<p>
Set the <a>GraphObject.segmentIndex</a> property in order to specify which segment of the link route
the object should be on.
Set the <a>GraphObject.segmentFraction</a> property to control how far the object should be, as a fraction
from the start of the segment (zero) to the end of the segment (one).
</p>
<p>
When setting the <a>GraphObject.segmentIndex</a> property to NaN,
the fraction will be calculated along the entire link route instead of a particular segment.
</p>
<p>
In the case of a link that comes from a node with no <a>GraphObject.fromSpot</a> (i.e. <a>Spot,None</a>)
and goes to a node with no <a>GraphObject.toSpot</a>, there may be only one segment in the link, segment number zero.
</p>
<pre class="lang-js" id="fraction">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
$(go.Shape),
$(go.Shape, { toArrow: "Standard" }),
$(go.TextBlock, "from", { segmentIndex: 0, segmentFraction: 0.2 }),
$(go.TextBlock, "mid", { segmentIndex: 0, segmentFraction: 0.5 }),
$(go.TextBlock, "to", { segmentIndex: 0, segmentFraction: 0.8 })
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "200 50" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("fraction", 600, 100)</script>
<p>
In the case of a link that has many segments in it, you will want to specify different segment numbers.
Orthogonal links, for example, typically have 6 points in the route, which means five segments numbered from 0 to 4.
</p>
<pre class="lang-js" id="fractionOrtho">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
{ routing: go.Link.Orthogonal },
$(go.Shape),
$(go.Shape, { toArrow: "Standard" }),
$(go.TextBlock, "from", { segmentIndex: 1, segmentFraction: 0.5 }),
$(go.TextBlock, "mid", { segmentIndex: 2, segmentFraction: 0.5 }),
$(go.TextBlock, "to", { segmentIndex: 3, segmentFraction: 0.5 })
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "200 50" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("fractionOrtho", 600, 100)</script>
<p>
However, you can also count segments backwards from the "to" end of the link.
-1 is the last segment, -2 is the next to last, etc.
When you use a negative segment index, the segment fraction goes from 0 closest to the "to" end
to 1 for the end of that segment that is farthest back along the route from the "to" end.
Thus a segmentIndex of -1 with a segmentFraction of 0 is the very end point of the link route.
A segmentIndex of -1 with a segmentFraction of 1 is the same point as segmentIndex -2 and segmentFraction 0.
</p>
<p>
For labels that belong near the "to" end of a link, you will normally use negative values for <a>GraphObject.segmentIndex</a>.
This convention works better when the number of segments in a link is unknown or may vary.
</p>
<p>
Lastly, one can specify a segmentIndex of NaN to have the fraction calculated along the entire link route instead of just a particular segment.
</p>
<pre class="lang-js" id="fractionNoIndex">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
{ curve: go.Link.Bezier },
$(go.Shape),
$(go.Shape, { toArrow: "Standard" }),
$(go.TextBlock, "1/3", { segmentIndex: NaN, segmentFraction: 0.33 }), // label at 1/3 of link length
$(go.TextBlock, "2/3", { segmentIndex: NaN, segmentFraction: 0.67 }) // label at 2/3 of link length
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "200 50" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("fractionNoIndex", 600, 100)</script>
<h2 id="LinkLabelSegmentOffsetAndAlignmentFocus">Link label segmentOffset and alignmentFocus</h2>
<p>
There are two ways of making small adjustments to the position of a label object given a particular point on a link segment
specified by the segment index and fractional distance.
</p>
<p>
The <a>GraphObject.segmentOffset</a> property controls where to position the object relative to the point
on a link segment determined by the <a>GraphObject.segmentIndex</a> and <a>GraphObject.segmentFraction</a> properties.
The offset is not a simple offset of the point -- it is rotated according to the angle of that link segment.
A positive value for the Y offset moves the label element towards the right side of the
link, as seen going in the direction of the link. Naturally a negative value for the Y offset moves it towards
the left side.
</p>
<pre class="lang-js" id="offset">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
$(go.Shape),
$(go.Shape, { toArrow: "Standard" }),
$(go.TextBlock, "left", { segmentOffset: new go.Point(0, -10) }),
$(go.TextBlock, "right", { segmentOffset: new go.Point(0, 10) })
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "200 50" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("offset", 600, 200)</script>
<p>
If you drag one node around in a circle around the other one,
you will see how the "left" and "right" labels are positioned.
</p>
<p>
Another way to change the effective offset is by changing the spot in the object that is being
positioned relative to the link segment point.
You can do that by setting the <a>GraphObject.alignmentFocus</a>, which as you have seen above defaults to <a>Spot.Center</a>.
(<a>GraphObject.alignmentFocus</a> is also used by other <a>Panel</a> types, which is why its name does not start with "segment".)
</p>
<pre class="lang-js" id="alignmentFocus">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
$(go.Shape),
$(go.Shape, { toArrow: "Standard" }),
$(go.TextBlock, "left", { alignmentFocus: new go.Spot(1, 0.5, 3, 0) }),
$(go.TextBlock, "right", { alignmentFocus: new go.Spot(0, 0.5, -3, 0) })
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "200 50" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("alignmentFocus", 600, 200)</script>
<p>
Yet you may instead want to control the angle of the individual labels based on the angle of the link segment.
</p>
<h2 id="LinkLabelSegmentOrientation">Link label segmentOrientation</h2>
<p>
The <a>GraphObject.segmentOrientation</a> property controls the angle of the label object relative
to the angle of the link segment.
There are several possible values that you can use.
The default orientation is <a>Link,None</a>, meaning no rotation at all.
<a>Link,OrientAlong</a> is commonly used to have the object always rotated at the same angle as the link segment.
<a>Link,OrientUpright</a> is like "OrientAlong", but is often used when there is text in the label, to make it easier to read.
</p>
<pre class="lang-js" id="orient">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
$(go.Shape),
$(go.Shape, { toArrow: "Standard" }),
$(go.TextBlock, "left",
{ segmentOffset: new go.Point(0, -10),
segmentOrientation: go.Link.OrientUpright }),
$(go.TextBlock, "middle",
{ segmentOffset: new go.Point(0, 0),
segmentOrientation: go.Link.OrientUpright }),
$(go.TextBlock, "right",
{ segmentOffset: new go.Point(0, 10),
segmentOrientation: go.Link.OrientUpright })
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "200 50" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("orient", 600, 200)</script>
<p>
Now if you move a node around you will always be able to read the label texts,
and yet each label stays on its intended side of the link, as seen going in the direction of the link.
</p>
<p>
This points out a difference between a segmentIndex/segmentFraction pair of 0/1 and 1/0.
Although they both refer to the same point, the angle associated with the first pair is the angle of the first segment (segment 0),
whereas the angle associated with the second pair is the angle of the second segment.
</p>
<h2 id="LinkLabelsNearEnds">Link labels near the ends</h2>
<p>
For labels that are near either end of a link, it may be convenient to set the <a>GraphObject.segmentOffset</a> to
Point(NaN, NaN). This causes the offset to be half the width and half the height of the label object.
</p>
<pre class="lang-js" id="nearEnds">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
$(go.Shape),
$(go.Shape, { toArrow: "Standard" }),
$(go.TextBlock, "from",
{ segmentIndex: 0, segmentOffset: new go.Point(NaN, NaN),
segmentOrientation: go.Link.OrientUpright }),
$(go.TextBlock, "to",
{ segmentIndex: -1, segmentOffset: new go.Point(NaN, NaN),
segmentOrientation: go.Link.OrientUpright })
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "200 50" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("nearEnds", 600, 200)</script>
<h2 id="Arrowheads">Arrowheads</h2>
<p>
Now that you know more about the <a>GraphObject</a> "segment..." properties for controlling the position and angle of objects in a <a>Link</a>,
it is easy to explain how arrowheads are defined. Arrowheads are just labels: <a>Shape</a>s that are initialized in a convenient manner.
</p>
<p>
You can see a copy of all of the built-in arrowhead definitions in this file: <a href="../extensions/Arrowheads.js">Arrowheads.js</a>.
</p>
<p>
Here are the equivalent settings for initializing an arrowhead <a>Shape</a> by setting <a>Shape.toArrow</a> to "Standard".
</p>
<pre class="lang-js" id="arrowheads">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
$(go.Shape),
$(go.Shape,
// the following are the same as { toArrow: "Standard" }:
{ segmentIndex: -1,
segmentOrientation: go.Link.OrientAlong,
alignmentFocus: go.Spot.Right,
geometry: go.Geometry.parse("F1 m0 0 l8 4 -8 4 2 -4 z") })
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "200 50" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("arrowheads", 600, 200)</script>
</div>
</div>
</body>
</html>
+681
View File
@@ -0,0 +1,681 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Links -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Links</h1>
<p>
Use the <a>Link</a> class to implement a visual relationship between nodes.
</p>
<p>
See samples that make use of customized Links in the <a href="../samples/index.html#links">samples index</a>.
</p>
<h2 id="CreatingLinks">Creating Links</h2>
<p>
Links are normally created by the presence of link data objects in the <a>GraphLinksModel.linkDataArray</a>
or by a parent key reference as the value of the <a>TreeModel.nodeParentKeyProperty</a> of a node data object
in a <a>TreeModel</a>.
Users can draw new links by using the <a>LinkingTool</a>: Introduction to the <a href="tools.html#LinkingToolAndRelinkingTool">Linking Tools</a>.
</p>
<p>
You can create new links programmatically by modifying the model.
It is most common to operate directly on the model by either calling <a>GraphLinksModel.addLinkData</a>
or by calling <a>TreeModel.setParentKeyForNodeData</a>.
Such changes are observed by all diagrams that are displaying the model so that they can automatically
create the corresponding <a>Link</a>s.
You can find examples of calls to <a>GraphLinksModel.addLinkData</a> in the samples.
</p>
<p>
It is also possible to create new links without detailed knowledge of the diagram's model by calling
<a>LinkingTool.insertLink</a>. That is how the user's actions to draw a new link actually create it.
That method knows how to modify the <a>GraphLinksModel</a> or the <a>TreeModel</a> appropriately,
while respecting the additional functionality offered by the <a>LinkingTool.archetypeLinkData</a>
and other properties of the <a>LinkingTool</a>.
You can find examples of calls to <a>LinkingTool.insertLink</a> in the samples.
</p>
<h2 id="NondirectionalLinks">Non-directional Links</h2>
<p>
The simplest links are those without arrowheads to indicate a visual direction.
Either the relationship really is non-directional, or the direction is implicit in the organization of the diagram.
</p>
<p>
The template just contains a <a>Shape</a> as the main element, as the line that is drawn between nodes.
After the link's route is computed the main Shape will get a <a>Geometry</a> based on the points in the route.
</p>
<pre class="lang-js" id="noArrowheads">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link, // the whole link panel
$(go.Shape) // the link shape, default black stroke
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "100 50" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("noArrowheads", 600, 100)</script>
<p>
By default the way that the model and diagram know about the node data references of a link data is
by looking at its from and to properties.
If you want to use a different properties on the link data, set <a>GraphLinksModel.linkFromKeyProperty</a> to be the name
of the property that results in the node data's key, and similarly for the <a>GraphLinksModel.linkToKeyProperty</a>.
</p>
<h2 id="Arrowheads">Arrowheads</h2>
<p>
Many links do want to indicate directionality by using arrowheads.
<b>GoJS</b> makes it easy to create common arrowheads: just add a Shape and set its <a>Shape.toArrow</a> property.
Setting that property will automatically assign a <a>Geometry</a> to the <a>Shape.geometry</a>
and will set other properties so that the arrowhead is positioned at the head of the link and is pointing in the correct direction.
Of course you can set the other Shape properties such as <a>Shape.fill</a> in order to customize the appearance of the arrowhead.
</p>
<pre class="lang-js" id="arrowheads">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
$(go.Shape), // the link shape
$(go.Shape, // the arrowhead
{ toArrow: "OpenTriangle", fill: null })
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "100 50" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("arrowheads", 600, 100)</script>
<p>
You can see all of the predefined arrowhead types in the <a href="../samples/arrowheads.html" target="samples">Arrowheads Sample</a>.
</p>
<p>
You can also have an arrowhead at the start of the link: set the <a>Shape.fromArrow</a> property.
Note that an arrowhead normally goes along the path of the link regardless of its position on the path,
so just as with a real arrow, setting <code>{ fromArrow: "TripleFeathers" }</code> has the "feathers" pointing forward.
If the link is meant to be bi-directional, the arrowhead name for the "from" end of a link
will probably want to start with the string "Backward...".
</p>
<h2 id="Routing">Routing</h2>
<p>
If you want to customize the path that each <a>Link</a> takes, you need to set properties on the link.
The property that has the most general effect on the points that the link's route follows is <a>Link.routing</a>.
</p>
<p>
This example shows the two most common routing values: <a>Link,Normal</a> (the default) and <a>Link,Orthogonal</a>.
</p>
<pre class="lang-js" id="routing">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
new go.Binding("routing", "routing"),
$(go.Shape),
$(go.Shape, { toArrow: "Standard" })
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "50 50" },
{ key: "Gamma", loc: "100 25" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta", routing: go.Link.Normal },
{ from: "Alpha", to: "Gamma", routing: go.Link.Orthogonal }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("routing", 600, 100)</script>
<p>
Note that the computed route also depends on the properties of the node, including its shape.
There are other properties, including <a>GraphObject.fromSpot</a> and <a>GraphObject.toSpot</a>, that affect the route.
For more discussion about spots, please read this Introduction page: <a href="connectionPoints.html">Link Connection Points</a>.
Furthermore some <a>Layout</a>s set properties on links to control their routing according to what the layout expects.
</p>
<p>
You can also set <a>Link.routing</a> to <a>Link,AvoidsNodes</a>:
</p>
<pre class="lang-js" id="avoidsNodes">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
{ routing: go.Link.AvoidsNodes }, // link route should avoid nodes
$(go.Shape),
$(go.Shape, { toArrow: "Standard" })
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "250 40" },
{ key: "Gamma", loc: "100 0" },
{ key: "Delta", loc: "75 50" },
{ key: "Epsilon", loc: "150 30" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("avoidsNodes", 600, 100)</script>
<p>
If you move the nodes interactively, you can see how the link's route adjusts to avoid crossing over nodes.
Notice that a small gap between nodes might not be considered wide enough for links to go through.
</p>
<p>
If a node is very close to or overlaps with either the link's <a>Link.fromNode</a> or <a>Link.toNode</a>
and would block the link's route, it ignores that node, treating it as if it were just an extension of the connected node.
Also if no node-avoiding route exists because there is a ring of nodes around one of the connected nodes,
the routing algorithm will give up and cross over some nodes anyway.
</p>
<p>
You can declare that it is OK to route through a node by setting <a>Node.avoidable</a> to false.
This is commonly done for <a>Group</a>s to allow links connecting outside of the group to route nicely within the group.
</p>
<p>
Note the the use of AvoidsNodes routing is distinctly slower than normal Orthogonal routing, especially for large diagrams.
</p>
<h3 id="EndSegmentLengths">End Segment Lengths</h3>
<p>
Another way to affect the precise route that Orthogonal and AvoidsNodes routing take is to set or bind
<a>GraphObject.fromEndSegmentLength</a> and <a>GraphObject.toEndSegmentLength</a>.
These properties determine the length of the very first segment or the very last segment, but only for orthogonally routed links.
Those properties can be set either on the port element of the node or on the link.
On the link the property value takes precedence over the corresponding property's value at the port.
</p>
<pre data-language="javascript" id="endseg">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
{ routing: go.Link.Orthogonal, fromSpot: go.Spot.Left, toSpot: go.Spot.Right },
new go.Binding("fromEndSegmentLength"),
new go.Binding("toEndSegmentLength"),
$(go.Shape),
$(go.Shape, { toArrow: "Standard" })
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "100 50" },
{ key: "Gamma", loc: "0 100" },
{ key: "Delta", loc: "100 150" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" },
{ from: "Gamma", to: "Delta", fromEndSegmentLength: 4, toEndSegmentLength: 30 },
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("endseg", 600, 200)</script>
<p>
In this example the values of the <a>Link.fromEndSegmentLength</a> and <a>Link.toEndSegmentLength</a> are bound
to the same named properties on the link data.
In both cases the link's route is force to come out of the left side of the source node and to the right side of the destination node.
In the case from "Alpha" to "Beta", you see the default behavior.
In the case from "Gamma" to "Delta", you see the results of a shorter-than-normal <code>fromEndSegmentLength</code>
and a longer-than-normal <code>toEndSegmentLength</code>.
</p>
<h2 id="CurveCurvinessCorner">Curve, Curviness, Corner</h2>
<p>
Once the <a>Link.routing</a> determines the route (i.e., the sequence of points) that the link takes,
other properties control the details of how the link shape gets its path geometry.
The first such property is <a>Link.curve</a>, which controls whether the link shape has basically straight segments
or is a big curve.
</p>
<p>
The default value for <a>Link.curve</a> is <a>Link,None</a>, which produces link shapes with straight segments
as you see above.
</p>
<p>
A value of <a>Link,Bezier</a> produces a naturally curved path for the link shape.
</p>
<pre class="lang-js" id="bezier">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
{ curve: go.Link.Bezier }, // Bezier curve
$(go.Shape),
$(go.Shape, { toArrow: "Standard" })
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "100 50" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("bezier", 600, 100)</script>
<p>
You can control how curved it is by setting the <a>Link.curviness</a> property.
The default produces a slight curve.
</p>
<p>
If there are multiple links, it will automatically compute reasonable values for the curviness of each link,
unless you assign <a>Link.curviness</a> explicitly.
</p>
<pre class="lang-js" id="beziers">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
{ curve: go.Link.Bezier },
$(go.Shape),
$(go.Shape, { toArrow: "Standard" })
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "100 50" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }, // multiple links between the same nodes
{ from: "Alpha", to: "Beta" },
{ from: "Alpha", to: "Beta" },
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("beziers", 600, 100)</script>
<p>
Another kind of curviness comes from rounded corners when the <a>Link.routing</a> is Orthogonal or AvoidsNodes.
</p>
<pre class="lang-js" id="corners">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
{ routing: go.Link.AvoidsNodes,
corner: 10 }, // rounded corners
$(go.Shape),
$(go.Shape, { toArrow: "Standard" })
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "250 40" },
{ key: "Gamma", loc: "100 0" },
{ key: "Delta", loc: "75 50" },
{ key: "Epsilon", loc: "150 30" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("corners", 600, 100)</script>
<p>
Another kind of curviness comes from setting <a>Link.curve</a> to <a>Link,JumpOver</a>.
This causes little "hops" in the path of an orthogonal link that crosses another orthogonal link
that also has a JumpOver curve.
</p>
<pre class="lang-js" id="jumpOvers">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ locationSpot: go.Spot.Center },
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
{ routing: go.Link.Orthogonal, // may be either Orthogonal or AvoidsNodes
curve: go.Link.JumpOver },
$(go.Shape),
$(go.Shape, { toArrow: "Standard" })
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 50" },
{ key: "Beta", loc: "100 50" },
{ key: "Alpha2", loc: "50 0" },
{ key: "Beta2", loc: "50 100" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }, // these two links will cross
{ from: "Alpha2", to: "Beta2" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("jumpOvers", 600, 150)</script>
<p>
Note that the use of link jumping is distinctly slower than normal links because all of the crossing
points must be computed and the geometry of the link shape will be more complex.
</p>
<p>
Another kind of curviness (or actually lack of it) comes from setting <a>Link.curve</a> to <a>Link,JumpGap</a>.
This causes little "gaps" in the path of an orthogonal link that crosses another orthogonal link
that also has a JumpGap curve.
</p>
<pre class="lang-js" id="jumpGaps">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ locationSpot: go.Spot.Center },
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
{ routing: go.Link.Orthogonal, // may be either Orthogonal or AvoidsNodes
curve: go.Link.JumpGap },
$(go.Shape),
$(go.Shape, { toArrow: "Standard" })
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 50" },
{ key: "Beta", loc: "100 50" },
{ key: "Alpha2", loc: "50 0" },
{ key: "Beta2", loc: "50 100" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }, // these two links will cross
{ from: "Alpha2", to: "Beta2" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("jumpGaps", 600, 150)</script>
<h2 id="EasierClickingOnLinks">Easier Clicking on Links</h2>
<p>
A problem that users may notice, especially when using fingers but also with the mouse,
is that it can be difficult to click on links that have a thin <a>Link.path</a>.
One could set the <a>Shape.strokeWidth</a> to a larger value, such as 8, but you may not want that appearance.
</p>
<pre class="lang-js" id="thickPath">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
$(go.Shape, { strokeWidth: 8 }), // thick path
$(go.Shape, { toArrow: "Standard" })
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "100 50" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("thickPath", 600, 100)</script>
<p>
The solution is to add a thick path Shape but not have it draw anything.
This is easily done by setting <code>{ stroke: "transparent", strokeWidth: 8 }</code>.
However if you want to keep the original path Shape, <em>both</em> Shapes need to be declared as the "main" element
for the Link by setting <a>GraphObject.isPanelMain</a> to true.
The Link panel knows that all such Shapes should get the same computed Geometry for the link path.
</p>
<pre class="lang-js" id="doublePath">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
$(go.Shape, { isPanelMain: true, stroke: "transparent", strokeWidth: 8 }), // thick undrawn path
$(go.Shape, { isPanelMain: true }), // default stroke === "black", strokeWidth === 1
$(go.Shape, { toArrow: "Standard" })
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "100 50" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("doublePath", 600, 100)</script>
<p>
In this example you will find it easier to select the link than without the second transparent link path shape.
</p>
<p>
The transparent shape can also be used for highlighting purposes.
For example, to implement the effect of highlighting the link when the mouse passes over it,
add <a>GraphObject.mouseEnter</a> and <a>GraphObject.mouseLeave</a> event handlers:
</p>
<pre class="lang-js" id="doublePathHighlight">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
$(go.Shape, { isPanelMain: true, stroke: "transparent", strokeWidth: 8 }), // thick undrawn path
$(go.Shape, { isPanelMain: true }), // default stroke === "black", strokeWidth === 1
$(go.Shape, { toArrow: "Standard" }),
{
// a mouse-over highlights the link by changing the first main path shape's stroke:
mouseEnter: function(e, link) { link.elt(0).stroke = "rgba(0,90,156,0.3)"; },
mouseLeave: function(e, link) { link.elt(0).stroke = "transparent"; }
}
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "100 50" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("doublePathHighlight", 600, 100)</script>
<p>
Pass the mouse over the link to see the effect.
Such feedback also helps the user click or context click on the link.
</p>
<h2 id="ShortLengths">Short Lengths</h2>
<p>
Note in the example above with the thick black path shape,
that the arrowhead seems to have disappeared due to the thickness of the link path.
One can avoid the problem by increasing the <a>GraphObject.scale</a> of the arrowhead, perhaps to 2.
</p>
<pre class="lang-js" id="thickPath2">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
$(go.Shape, { strokeWidth: 8 }), // thick path
$(go.Shape, { toArrow: "Standard", scale: 2 }) // bigger arrowhead
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "100 50" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("thickPath2", 600, 100)</script>
<p>
Now the arrowhead is clearly visible.
But that in turn shows that the arrowhead is still obscured at the very end of the link path,
where it is too wide to show the point of the arrowhead.
That problem can be avoided by setting <a>Link.toShortLength</a> to a value such as 8,
depending on the kind of arrowhead used.
The path geometry will be shortened by that distance so that the link path does not interfere with the arrowhead.
</p>
<pre class="lang-js" id="shortLength">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
{ toShortLength: 8 }, // shortens path to avoid interfering with arrowhead
$(go.Shape, { strokeWidth: 8 }), // thick path
$(go.Shape, { toArrow: "Standard", scale: 2 }) // bigger arrowhead
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "100 50" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("shortLength", 600, 100)</script>
<p>
There is also a <a>Link.fromShortLength</a> property, to control how far the "from" end of the link path is drawn.
If there is an end segment, the distance that it can be shortened is limited to the corresponding
<a>Link.toEndSegmentLength</a> or <a>Link.fromEndSegmentLength</a>.
Note also that the short length may be negative, which would cause the link path to be drawn longer --
into the port at which the link is connected.
</p>
<h2 id="DisconnectedLinks">Disconnected Links</h2>
<p>
The normal expectation is that one cannot have a link relationship unless it connects two nodes.
However <b>GoJS</b> does support the creation and manipulation of links that have either or both of
the <a>Link.fromNode</a> and <a>Link.toNode</a> properties with null values.
This is demonstrated by the <a href="../samples/draggableLink.html">Draggable Link</a> sample.
</p>
<p>
Both ends of the link must be connected to nodes in order for the standard link routing to operate.
If a link does not know where to start or where to end, it cannot compute a route or a position for the link.
However, you can provide a route by setting or binding <a>Link.points</a> to a list of two or more Points.
That will automatically give the link a position so that it can be seen in the diagram.
</p>
<p>
The linking tools, <a>LinkingTool</a> and <a>RelinkingTool</a>, normally do not permit the creation
or reconnection of links that connect with "nothing".
However, you can set <a>LinkingBaseTool.isUnconnectedLinkValid</a> to true to allow the user to do so,
as the Draggable Link sample demonstrates.
</p>
<p>
Links cannot normally be dragged unless they are part of a collection that includes the connected nodes.
However, you can set <a>DraggingTool.dragsLink</a> to true to allow the user to drag a solitary <a>Link</a>.
This mode allows the user to disconnect a link by dragging it away from the node(s)/port(s) to which it was attached.
It also allows the user to reconnect one or both ends of the link by dropping it so that the end(s) are at valid port(s).
This too is demonstrated by the Draggable Link sample.
</p>
</div>
</div>
</body>
</html>
+311
View File
@@ -0,0 +1,311 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Making Images -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
<style type="text/css">
.images {
border: 1px solid rgba(255,0,0,.4);
}
/* make HRs thicker to set them apart from the code section borders */
hr {
height: 3px;
background: #333;
}
</style>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Making Images</h1>
<p>
<b>GoJS</b> has two functions for creating images: <a>Diagram.makeImageData</a>, which outputs a Base64 image data string, and <a>Diagram.makeImage</a>, which is a convenience function that calls <a>Diagram.makeImageData</a> and returns a new HTMLImageElement with the image data as its source. Both functions have the same single argument, a JavaScript Object that contains several definable properties, enumerated in the documentation.
</p>
<p>
This page is almost identical to the page on <a href="makingSVG.html">Making SVG</a>, which shows how to render SVG elements instead of PNG images.
</p>
<!-- Don't bother showing this source -->
<pre class="lang-js" id="diag" style="display: none;">
// define a simple Node template (but use the default Link template)
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle",
// Shape.fill is bound to Node.data.color
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 3 }, // some room around the text
// TextBlock.text is bound to Node.data.key
new go.Binding("text", "key"))
);
// create the model data that will be represented by Nodes and Links
var nodeDataArray = [
{ key: "Alpha", color: "lightblue" },
{ key: "Beta", color: "orange" },
{ key: "Gamma", color: "lightgreen" },
{ key: "Delta", color: "pink" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" },
{ from: "Alpha", to: "Gamma" },
{ from: "Beta", to: "Beta" },
{ from: "Gamma", to: "Delta" },
{ from: "Delta", to: "Alpha" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
window.myDiagram = diagram;
window.goCode2 = function(pre) {
if (diagramclass === undefined) diagramclass = go.Diagram;
if (typeof pre === "string") pre = document.getElementById(pre);
var f = eval("(function () {window.img = " + pre.textContent + "})");
f();
img.className = "images";
pre.parentElement.insertBefore(img, pre.nextSibling)
}
window.goCode3 = function(pre) {
if (diagramclass === undefined) diagramclass = go.Diagram;
if (typeof pre === "string") pre = document.getElementById(pre);
var f = eval("(function () {" + pre.textContent + "})");
f();
}
window.addImage = function(img) {
obj = document.getElementById("severalImages");
img.className = "images";
obj.appendChild(img)
}
</pre>
<script>goCode("diag", 300, 150)</script>
<hr/>
<p>
Calling makeImage with no arguments or with an empty properties object results in an image that is the same size as the Diagram's viewport.
</p>
<pre class="lang-js" id="code0">
myDiagram.makeImage();
</pre>
<script>goCode2("code0");</script>
<hr/>
<p>
Calling makeImage with an object that has the "scale" property set to 1 results in an image that includes the whole diagram,
not just the area visible in the viewport. However, the empty areas around the document bounds are trimmed away.
</p>
<pre class="lang-js" id="codeA">
myDiagram.makeImage({
scale: 1
});
</pre>
<script>goCode2("codeA");</script>
<hr/>
<p>
Setting the scale property will create a scaled image that is precisely large enough to contain the Diagram. The following image is created with a scale of 2.
</p>
<pre class="lang-js" id="code1">
myDiagram.makeImage({
scale: 2
});
</pre>
<script>goCode2("code1");</script>
<hr/>
<p>
The following image is created by setting the size option of makeImage. Note that the canvas is scaled uniformly and any extra space is placed on the bottom or right side of the image.
</p>
<pre class="lang-js" id="code2">
myDiagram.makeImage({
size: new go.Size(100,100)
});
</pre>
<script>goCode2("code2");</script>
<hr/>
<p>The following image is also created by setting the size option of makeImage, but only the width is set. The height will be whatever size is needed to uniformly contain the Diagram.</p>
<pre class="lang-js" id="code3">
myDiagram.makeImage({
size: new go.Size(100,NaN)
});
</pre>
<script>goCode2("code3");</script>
<hr/>
<p>The parts option allows us to specify an <a>Iterable</a> collection of Parts to draw. This is useful if you only want to make an image of part of the diagram, such as a selection of nodes.</p>
<pre class="lang-js" id="code4a">
myPartsList = new go.List();
myPartsList.add(myDiagram.findNodeForKey("Beta"));
myPartsList.add(myDiagram.findNodeForKey("Delta"));
</pre>
<script>goCode3("code4a");</script>
<pre class="lang-js" id="code4">
myDiagram.makeImage({
parts: myPartsList
});
</pre>
<script>goCode2("code4");</script>
<p>Or simply drawing only the links:</p>
<pre class="lang-js" id="code4-2">
myDiagram.makeImage({
parts: myDiagram.links
});
</pre>
<script>goCode2("code4-2");</script>
<hr/>
<p>Setting both scale and size creates an image that is scaled specifically and cropped to the given size, as in the following image.</p>
<pre class="lang-js" id="code5">
myDiagram.makeImage({
scale: 1.5,
size: new go.Size(100,100)
});
</pre>
<script>goCode2("code5");</script>
<hr/>
<p>We may want a very large, scaled image that has a limit on its size, and we can use the maxSize property to constrain one or both dimensions. The following image has a very large scale applied but is limited in size horizontally, so some horizontal cropping will occur.</p>
<p>The default value for maxSize is <code>go.Size(2000, 2000)</code>, and specifying <code>go.Size(600, NaN)</code> is equivalent to specifying <code>go.Size(600, 2000)</code>. If we wanted no cropping on the height we could instead write <code>go.Size(600, Infinity)</code>.</p>
<pre class="lang-js" id="code6">
myDiagram.makeImage({
scale: 9,
maxSize: new go.Size(600, NaN)
});
</pre>
<script>goCode2("code6");</script>
<hr/>
<p>Setting both position and size creates a diagram image that is positioned specifically and cropped to the given size. When a position is set but no scale is set, the scale defaults to 1.</p>
<pre class="lang-js" id="code7">
myDiagram.makeImage({
position: new go.Point(20,20),
size: new go.Size(50,50)
});
</pre>
<script>goCode2("code7");</script>
<p>Setting the background to a CSS color string will replace the transparent Diagram background with the given color.</p>
<pre class="lang-js" id="code8">
myDiagram.makeImage({
size: new go.Size(NaN,250),
background: "rgba(0, 255, 0, 0.5)" // semi-transparent green background
});
</pre>
<script>goCode2("code8");</script>
<hr/>
<p>In the following code we use the document bounds to split the Diagram into four equal parts, making four images out of each part. In this way we can prepare images for pagination, making a gallery, or printing purposes. The four images created are shown below.</p>
<pre class="lang-js" id="manyImgCode">
var d = myDiagram.documentBounds;
var halfWidth = d.width / 2;
var halfHeight = d.height / 2;
img = myDiagram.makeImage({
position: new go.Point(d.x, d.y),
size: new go.Size(halfWidth,halfHeight)
});
addImage(img); // Adds the image to a DIV below
img = myDiagram.makeImage({
position: new go.Point(d.x + halfWidth, d.y),
size: new go.Size(halfWidth,halfHeight)
});
addImage(img);
img = myDiagram.makeImage({
position: new go.Point(d.x, d.y+ halfHeight),
size: new go.Size(halfWidth,halfHeight)
});
addImage(img);
img = myDiagram.makeImage({
position: new go.Point(d.x + halfWidth, d.y + halfHeight),
size: new go.Size(halfWidth,halfHeight)
});
addImage(img);
</pre>
<div id="severalImages"></div>
<script>goCode3("manyImgCode");</script>
<hr/>
<h2 id="ImageType">Image Type</h2>
<p>
We can set the type and details properties of the argument object in order to retrieve different kinds of images. The only widely supported type is "image/jpeg". The details for a jpeg determine its quality by using values from 0 to 1 inclusive. Jpegs are not commonly used for Diagrams because their lossy compression can render text unreadable.
</p>
<p>
The following image is an outputted jpeg. Note how the transparent background is turned black, because the jpeg format does not support alpha transparency, and the default state of the HTML5 canvas is that of fully transparent black pixels, rgba(0,0,0,0).
</p>
<pre class="lang-js" id="codea1">
myDiagram.makeImage({
scale: 1,
type: "image/jpeg"
});
</pre>
<script>
goCode2("codea1");
</script>
<hr/>
<p>
The following image is a jpeg created with an AntiqueWhite background specified.
</p>
<pre class="lang-js" id="codea2">
myDiagram.makeImage({
scale: 1,
background: "AntiqueWhite",
type: "image/jpeg"
});
</pre>
<script>
goCode2("codea2");
</script>
<hr/>
<p>
The following image is a jpeg created (with an AntiqueWhite background) and the details option, at very low quality.
</p>
<pre class="lang-js" id="codea3">
myDiagram.makeImage({
scale: 1,
background: "AntiqueWhite",
type: "image/jpeg",
details: 0.05
});
</pre>
<script>
goCode2("codea3");
</script>
<hr/>
<h2 id="DownloadingImages">Downloading Images</h2>
<p>
You do not need to involve the web server if you want the user to download an image.
See the sample <a href="../samples/minimalBlob.html" target="_blank">Minimal Blob</a>.
Note that that sample only downloads a single image.
</p>
<p>
We suggest that you use SVG for downloading an image, if that choice is acceptable to your users.
That sample is at <a href="../samples/minimalSvg.html" target="_blank">Minimal SVG</a>.
</p>
</div>
</div>
</body>
</html>
+289
View File
@@ -0,0 +1,289 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Making SVG -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
<style type="text/css">
.images {
border: 1px solid rgba(255,0,0,.4);
}
/* make HRs thicker to set them apart from the code section borders */
hr {
height: 3px;
background: #333;
}
</style>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Making SVG</h1>
<p>
<b>GoJS</b> has one function for creating SVG: <a>Diagram.makeSvg</a>, which returns a new SVGElement with a representation of a GoJS Diagram. The method has a single argument, a JavaScript Object that contains several definable properties, enumerated in the documentation.
</p>
<p>
SVG export can be useful as content for a PDF.
Most GoJS users who create PDFs do so by exporting Diagrams to SVG or images and place that content in their PDFs, on the server or elsewhere.
</p>
<p>
This page is almost identical to the page on <a href="makingImages.html">Making Images</a>, which shows how to render PNG images instead of SVG elements.
</p>
<p>
Below are several examples of using <a>Diagram.makeSvg</a> on the following diagram:
</p>
<!-- Don't bother showing this source -->
<pre class="lang-js" id="diag" style="display: none;">
// define a simple Node template (but use the default Link template)
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle",
// Shape.fill is bound to Node.data.color
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 3 }, // some room around the text
// TextBlock.text is bound to Node.data.key
new go.Binding("text", "key"))
);
// create the model data that will be represented by Nodes and Links
var nodeDataArray = [
{ key: "Alpha", color: "lightblue" },
{ key: "Beta", color: "orange" },
{ key: "Gamma", color: "lightgreen" },
{ key: "Delta", color: "pink" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" },
{ from: "Alpha", to: "Gamma" },
{ from: "Beta", to: "Beta" },
{ from: "Gamma", to: "Delta" },
{ from: "Delta", to: "Alpha" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
window.myDiagram = diagram;
window.goCode2 = function(pre) {
if (diagramclass === undefined) diagramclass = go.Diagram;
if (typeof pre === "string") pre = document.getElementById(pre);
var f = eval("(function () {window.img = " + pre.textContent + "})");
f();
pre.parentElement.insertBefore(img, pre.nextSibling)
}
window.goCode3 = function(pre) {
if (diagramclass === undefined) diagramclass = go.Diagram;
if (typeof pre === "string") pre = document.getElementById(pre);
var f = eval("(function () {" + pre.textContent + "})");
f();
}
window.addSVG = function(img) {
obj = document.getElementById("severalImages");
img.className = "images";
obj.appendChild(img)
}
</pre>
<script>goCode("diag", 300, 150)</script>
<hr/>
<p>
Calling makeSvg with no arguments or with an empty properties object results in a scene that is the same size as the Diagram's viewport.
</p>
<pre class="lang-js" id="code0">
myDiagram.makeSvg();
</pre>
<script>goCode2("code0");</script>
<hr/>
<p>
Calling makeSvg with an object that has the "scale" property set to 1 results in a scene that includes the whole diagram,
not just the area visible in the viewport. However, the empty areas around the document bounds are trimmed away.
</p>
<pre class="lang-js" id="codeA">
myDiagram.makeSvg({
scale: 1
});
</pre>
<script>goCode2("codeA");</script>
<hr/>
<p>
Setting the scale property will create a scaled SVG Scene that is precisely large enough to contain the Diagram. The following SVG is created with a scale of 2.
</p>
<pre class="lang-js" id="code1">
myDiagram.makeSvg({
scale: 2
});
</pre>
<script>goCode2("code1");</script>
<p>
<em>Note how, unlike an image, you can select the text.</em>
</p>
<hr/>
<p>
The following SVG is created by setting the size option of makeSvg. Note that the canvas is scaled uniformly and any extra space is placed on the bottom or right side of the SVG.
</p>
<pre class="lang-js" id="code2">
myDiagram.makeSvg({
size: new go.Size(100,100)
});
</pre>
<script>goCode2("code2");</script>
<hr/>
<p>The following SVG is also created by setting the size option of makeSvg, but only the width is set. The height will be whatever size is needed to uniformly contain the Diagram.</p>
<pre class="lang-js" id="code3">
myDiagram.makeSvg({
size: new go.Size(100,NaN)
});
</pre>
<script>goCode2("code3");</script>
<hr/>
<p>The parts option allows us to specify an <a>Iterable</a> collection of Parts to draw. This is useful if you only want to make an image of part of the diagram, such as a selection of nodes.</p>
<pre class="lang-js" id="code4a">
myPartsList = new go.List();
myPartsList.add(myDiagram.findNodeForKey("Beta"));
myPartsList.add(myDiagram.findNodeForKey("Delta"));
</pre>
<script>goCode3("code4a");</script>
<pre class="lang-js" id="code4">
myDiagram.makeSvg({
parts: myPartsList
});
</pre>
<script>goCode2("code4");</script>
<p>Or drawing only the links:</p>
<pre class="lang-js" id="code4-2">
myDiagram.makeSvg({
parts: myDiagram.links
});
</pre>
<script>goCode2("code4-2");</script>
<hr/>
<p>Setting both scale and size creates an image that is scaled specifically and cropped to the given size, as in the following image.</p>
<pre class="lang-js" id="code5">
myDiagram.makeSvg({
scale: 1.5,
size: new go.Size(100,100)
});
</pre>
<script>goCode2("code5");</script>
<hr/>
<p>We may want a very large, scaled image that has a limit on its size, and we can use the maxSize property to constrain one or both dimensions. The following image has a very large scale applied but is limited in size horizontally, so some horizontal cropping will occur.</p>
<p>The default value for maxSize is <code>go.Size(2000, 2000)</code>, and specifying <code>go.Size(600, NaN)</code> is equivalent to specifying <code>go.Size(600, 2000)</code>. If we wanted no cropping on the height we could instead write <code>go.Size(600, Infinity)</code>.</p>
<pre class="lang-js" id="code6">
myDiagram.makeSvg({
scale: 9,
maxSize: new go.Size(600, NaN)
});
</pre>
<script>goCode2("code6");</script>
<hr/>
<p>Setting both position and size creates a diagram image that is positioned specifically and cropped to the given size. When a position is set but no scale is set, the scale defaults to 1.</p>
<pre class="lang-js" id="code7">
myDiagram.makeSvg({
position: new go.Point(20,20),
size: new go.Size(50,50)
});
</pre>
<script>goCode2("code7");</script>
<p>Setting the background to a CSS color string will replace the transparent Diagram background with the given color.</p>
<pre class="lang-js" id="code8">
myDiagram.makeSvg({
size: new go.Size(NaN,250),
background: "rgba(0, 255, 0, 0.5)" // semi-transparent green background
});
</pre>
<script>goCode2("code8");</script>
<hr/>
<p>In the following code we use the document bounds to split the Diagram into four equal parts, making four images out of each part. In this way we can prepare images for pagination, making a gallery, or printing purposes. The four images created are shown below.</p>
<pre class="lang-js" id="manyImgCode">
var d = myDiagram.documentBounds;
var halfWidth = d.width / 2;
var halfHeight = d.height / 2;
svg = myDiagram.makeSvg({
position: new go.Point(d.x, d.y),
size: new go.Size(halfWidth,halfHeight)
});
addSVG(svg); // Adds the SVG to a DIV below
svg = myDiagram.makeSvg({
position: new go.Point(d.x + halfWidth, d.y),
size: new go.Size(halfWidth,halfHeight)
});
addSVG(svg);
svg = myDiagram.makeSvg({
position: new go.Point(d.x, d.y+ halfHeight),
size: new go.Size(halfWidth,halfHeight)
});
addSVG(svg);
svg = myDiagram.makeSvg({
position: new go.Point(d.x + halfWidth, d.y + halfHeight),
size: new go.Size(halfWidth,halfHeight)
});
addSVG(svg);
</pre>
<div id="severalImages"></div>
<script>goCode3("manyImgCode");</script>
<hr />
<p>
You can open the SVG in a new window by appending it to the DOM of a new page:
</p>
<button id="openSVG">Open SVG</button>
<pre class="lang-js" id="codea1">
var button = document.getElementById('openSVG');
button.addEventListener('click', function() {
var newWindow = window.open("","newWindow");
if (!newWindow) return;
var newDocument = newWindow.document;
var svg = myDiagram.makeSvg({
document: newDocument, // create SVG DOM in new document context
scale: 9,
maxSize: new go.Size(600, NaN)
});
newDocument.body.appendChild(svg);
}, false);
</pre>
<script>goCode("codea1");</script>
<hr/>
<h2 id="DownloadingSVGFiles">Downloading SVG Files</h2>
<p>
You do not need to involve the web server if you want the user to download an SVG file.
See the sample <a href="../samples/minimalSvg.html" target="_blank">Minimal SVG</a>.
Note that that sample only downloads a single SVG file, but that file can cover the whole document.
</p>
</div>
</div>
</body>
</html>
+151
View File
@@ -0,0 +1,151 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS with ES6 Modules -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>GoJS and ES6 Modules</h1>
<p>
The GoJS kit contains examples of using GoJS with ES6 modules.
</p>
<h2 id="GoJSDirectly">Using GoJS as an ES6 module</h2>
<p>
By default the GoJS library does not use the <code>export</code> keyword, for compatibility reasons.
So we have had to provide different libraries that are ES6 modules.
In the <code>release</code> folder, they are the <code>go.mjs</code> and <code>go-debug.mjs</code> libraries.
In addition, there is an ES6 module-specific TypeScript definition file: <code>go-module.d.ts</code>.
</p>
<p>
In <code>samplesTS</code>, the <a href="../samplesTS/minimalModule.html">minimalModule.html</a> sample uses ES6.
It references:
</p>
<pre class="lang-ts">import * as go from '../release/go.mjs';</pre>
<p>In order to load GoJS and other ES6 modules, the HTML page also uses the <code>type="module"</code> script tag:</p>
<pre class="lang-html">
&lt;!-- requires minimalModule.js, built from minimalModule.ts --&gt;
&lt;script type="module"&gt;
import { init } from "./minimalModule.js";
// lib needs: export const go = self.go;
window.onload = function() {
init();
}
&lt;/script&gt;
</pre>
<p>
Most browsers will not display resources with <code>&lt;script type="module"&gt;</code> if they are served from a local file system,
so you may need to open <code>minimalModule.html</code> from a server to see the results.
</p>
<h2 id="GoJSES6">GoJS Extensions as ES6 Modules</h2>
<p>
The extension classes can also be loaded as ES6 modules if you modify the <code>tsconfig.json</code> configuration in the <code>extensionsTS</code>
folder, and then rebuild.
</p>
<pre class="lang-json">
{
"compilerOptions": {
"target": "es6",
"strict": true
}
}</pre>
<p>
Recompiling those TypeScript classes will then produce module-friendly JS libraries.
</p>
<p>
Depending on your toolchain, you could also include compiler options directly into your project,
as is done in the <a href="https://github.com/NorthwoodsSoftware/GoJS-projects/tree/master/vue-webpack">vue-webpack GoJS project</a>.
In its <code>webpack.config.js</code> file, we specify new compiler options for the TypeScript loader so that Webpack + Vue
compiles the extensions with ES6 module support instead of the <code>extensionsTS</code> defaults.
</p>
<pre class="lang-json">
/* ... in webpack.config.ts in the vue-webpack project ... */
// files with `.ts` or `.tsx` extension will be handled by `ts-loader`
{ test: /\.tsx?$/,
loader: 'ts-loader',
options: {
// We want to override the tsconfig file currently in:
// vue-webpack\node_modules\gojs\extensionsTS
// Because it uses ES5 + umd modules and we want to use ES6 + ES6.
compilerOptions: {
"module": "ES6",
"target": "ES6",
"noImplicitAny": true
}
}</pre>
<h2 id="GoJSRequireJS">GoJS with <code>RequireJS</code></h2>
<p>
Both the <code>go.js</code> library and the <code>go-debug.js</code> library can be loaded via <a href="https://requirejs.org/">RequireJS</a>.
</p>
<p>
The <code>extensionsTS</code> directory contains all of the extension classes from the
<code>extensions</code> directory, but in TypeScript and pre-compiled as UMD modules.
This is reflected in that directory's <code>tsconfig.json</code>:
</p>
<pre class="lang-json">
{
"compilerOptions": {
"module": "umd",
"target": "es5",
"strict": true
}
}</pre>
<p>
The generated JavaScript can then be loaded as UMD modules via <code>require</code>.
</p>
<pre class="lang-html">
&lt;script src="../samples/assets/require.js"&gt;&lt;/script&gt;
&lt;script id="code"&gt;
function init() {
require(["CheckBoxesScript"], function (app) {
app.init();
});
}
&lt;/script&gt;</pre>
<h2 id="GoJSWithES6Modules">GoJS with ES6 Modules</h2>
<p>
The GoJS library is available as an ES6/JavaScript module at <code>release/go.mjs</code>.
A debug version is also available: <code>release/go-debug.mjs</code>.
</p>
<p>
Samples and extension classes as modules are in the <code>extensionsJSM</code> directory.
These include the modules directly using the <code>type="module"</code> <code>script</code> tag.
For example, in the sample <a href="../extensionsJSM/LinkLabelDragging.html">LinkLabelDragging</a>:
</p>
<pre>
&lt;script type="module" id="code"&gt;
import * as go from "../release/go.mjs";
import { LinkLabelDraggingTool } from './LinkLabelDraggingTool.js';
const $ = go.GraphObject.make;
const myDiagram =
$(go.Diagram, 'myDiagramDiv', ...);
// install the LinkLabelDraggingTool as a "mouse move" tool
myDiagram.toolManager.mouseMoveTools.insertAt(0, new LinkLabelDraggingTool());
&lt;/script&gt;
</pre>
</div >
</body >
</html >
+107
View File
@@ -0,0 +1,107 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS in Node.js -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Using GoJS with Node.js</h1>
<p>
As of 2.0, GoJS can be used in DOM-less contexts like Node.js. However there are some considerations:
<ul>
<li>
Since there is no Diagram DIV, you must instead set the <a>Diagram.viewSize</a> property.
This affects all the same values as the DIV size, like Diagram.position and layout results from layouts that are viewport-sized.
</li>
<li>Cannot measure go.Pictures, you must set a <a>GraphObject.desiredSize</a> instead.</li>
<li>Cannot measure go.TextBlocks accurately, you should set a <a>GraphObject.desiredSize</a> instead.</li></li>
</ul>
</p>
<p>
For server-side operations that need to measure Pictures or TextBlocks, you should consider using a headless browser with Node.
<a href="serverSideImages.html">Click here for examples using Node with Puppeteer (headless Chrome)</a>.
</p>
<h2 id="NodeJSExample">Node.js example</h2>
<p>
If you saved the following JavaScript as <code>nodescript.js</code> and run it with node (<code>node nodescript.js</code>),
it will output Model JSON results in the console, which include the locations of laid-out Nodes. You can use Node.js
in this way to do server-side operations like large layouts, and then send the JSON to the client.
</p>
<pre class="lang-js">
// nodescript.js
// This example loads the GoJS library, creates a Diagram with a layout and prints the JSON results.
// Load GoJS. This assumes using require and CommonJS:
const go = require("gojs");
const $ = go.GraphObject.make; // for conciseness in defining templates
const myDiagram =
$(go.Diagram, '', // No DOM, so there can be no DIV!
{
viewSize: new go.Size(400,400), // Set this property in DOM-less environments
layout: $(go.LayeredDigraphLayout)
});
myDiagram.nodeTemplate =
$(go.Node, 'Auto',
// specify the size of the node rather than measuring the size of the text
{ width: 80, height: 40 },
// automatically save the Node.location to the node's data object
new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
$(go.Shape, 'RoundedRectangle', { strokeWidth: 0},
new go.Binding('fill', 'color')),
$(go.TextBlock,
new go.Binding('text', 'key'))
);
// After the layout, output results:
myDiagram.addDiagramListener('InitialLayoutCompleted', function() {
console.log(myDiagram.model.toJson());
});
// load a model:
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: 'Gamma', to: 'Delta' },
{ from: 'Delta', to: 'Alpha' }
]);
</pre>
<p>
Alternatively, if your code is saved as <code>nodescript.mjs</code> or your project is of <code>"type": "module"</code>,
you can use GoJS as an ES6 module:
</p>
<pre class="lang-js">
// nodescript.mjs
// This example loads the GoJS library, creates a Diagram with a layout and prints the JSON results.
// Load GoJS. This assumes using import and ES6 modules:
import * as go from "gojs/release/go.mjs";
const $ = go.GraphObject.make;
. . .
</pre>
</div>
</div>
</body>
</html>
+527
View File
@@ -0,0 +1,527 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Nodes -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="../extensions/Figures.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Nodes</h1>
<p>
You can customize your nodes to have exactly the appearance and behavior that you want.
So far you have only seen very simple nodes.
But if you have seen the <a href="../samples/index.html">Sample Applications</a>,
you have seen many other kinds of nodes.
</p>
<p>
In this page we demonstrate some of the choices you can make when designing your nodes.
</p>
<h2 id="SurroundingContent">Surrounding Content</h2>
<p>
It is common to surround interesting information with a border or other background.
</p>
<h3 id="SimpleBorders">Simple borders</h3>
<p>
Many of the simplest nodes just consist of a <a>Panel</a> of type <a>Panel,Auto</a> with a <a>Shape</a>
surrounding a <a>TextBlock</a>.
</p>
<pre class="lang-js" id="border">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "Rectangle",
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 5 },
new go.Binding("text", "key"))
);
diagram.model.nodeDataArray = [
{ key: "Alpha", color: "lightblue" }
];
</pre>
<script>goCode("border", 300, 150)</script>
<h3 id="ShapedNodes">Shaped nodes</h3>
<p>
The Shape surrounding the content need not be rectangular.
This example demonstrates a number of shapes.
</p>
<pre class="lang-js" id="shapes">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape,
new go.Binding("figure", "fig"),
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 5 },
new go.Binding("text", "key"))
);
diagram.model.nodeDataArray = [
{ key: "Alpha", color: "lightblue", fig: "RoundedRectangle" },
{ key: "Beta", color: "lightblue", fig: "Ellipse" },
{ key: "Gamma", color: "lightblue", fig: "Hexagon" },
{ key: "Delta", color: "lightblue", fig: "FramedRectangle" },
{ key: "Epsilon", color: "lightblue", fig: "Cloud" },
{ key: "Zeta", color: "lightblue", fig: "Procedure" }
];
</pre>
<script>goCode("shapes", 300, 150)</script>
<p>
The surrounding/background object need not be a <a>Shape</a>.
You could use a <a>Picture</a> or even a more complex object such as a <a>Panel</a>.
</p>
<h3 id="ComplexContents">Complex contents</h3>
<p>
The content of an Auto <a>Panel</a> need not be limited to a single <a>TextBlock</a> --
you can have arbitrarily complex panels of objects.
In this example the content is a Table Panel with three rows of TextBlocks.
</p>
<pre class="lang-js" id="borderedtable">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape,
{ fill: $(go.Brush, "Linear", { 0: "white", 1: "lightblue" }),
stroke: "darkblue", strokeWidth: 2 }),
$(go.Panel, "Table",
{ defaultAlignment: go.Spot.Left, margin: 4 },
$(go.RowColumnDefinition, { column: 1, width: 4 }),
$(go.TextBlock,
{ row: 0, column: 0, columnSpan: 3, alignment: go.Spot.Center },
{ font: "bold 12pt sans-serif" },
new go.Binding("text", "key")),
$(go.TextBlock, "First: ",
{ row: 1, column: 0 }),
$(go.TextBlock,
{ row: 1, column: 2 },
new go.Binding("text", "prop1")),
$(go.TextBlock, "Second: ",
{ row: 2, column: 0 }),
$(go.TextBlock,
{ row: 2, column: 2 },
new go.Binding("text", "prop2"))
)
);
diagram.model.nodeDataArray = [
{ key: "Alpha", prop1: "value of 'prop1'", prop2: "the other property" }
];
</pre>
<script>goCode("borderedtable", 300, 150)</script>
<h3 id="FixedSizeNodes">Fixed-size nodes</h3>
<p>
The above examples have the "Auto" Panel surround some content, where the content might be of different sizes.
That results in the Nodes having different sizes.
</p>
<p>
If you want a <a>Panel</a> (and thus a Node, because <a>Node</a> inherits from <a>Part</a> which inherits from <a>Panel</a>)
to be of fixed size, set <a>GraphObject.desiredSize</a> on that panel.
(Equivalently, you can set <a>GraphObject.width</a> and <a>GraphObject.height</a>.)
That may result in the clipping of content that is too large,
or it may result in extra space if the content is smaller than the available area provided by the "Auto" Panel.
</p>
<pre class="lang-js" id="fixedsize">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ desiredSize: new go.Size(100, 50) }, // on Panel
$(go.Shape,
new go.Binding("figure", "fig"),
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 5 },
new go.Binding("text", "key"))
);
diagram.model.nodeDataArray = [
{ key: "Alpha", color: "lightblue", fig: "RoundedRectangle" },
{ key: "Beta", color: "lightblue", fig: "Ellipse" },
{ key: "Gamma", color: "lightblue", fig: "Hexagon" },
{ key: "Delta", color: "lightblue", fig: "FramedRectangle" },
{ key: "Epsilon,Epsilon,Epsilon", color: "lightblue", fig: "Cloud" },
{ key: "Z", color: "lightblue", fig: "Procedure" }
];
</pre>
<script>goCode("fixedsize", 500, 200)</script>
<p>
Note how the "Epsilon..." TextBlock is measured with the constraint of having a limited width,
as imposed by the Panel's width. That results in the text being wrapped before (maybe) being clipped.
</p>
<p>
You probably do not want to set the desiredSize of the main element, the Shape in this case above.
If you did, that would not constrain how the content elements are sized within the Panel.
</p>
<pre class="lang-js" id="fixedsize2">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape,
{ desiredSize: new go.Size(100, 50) }, // on main element, not on Panel
new go.Binding("figure", "fig"),
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 5 },
new go.Binding("text", "key"))
);
diagram.model.nodeDataArray = [
{ key: "Alpha", color: "lightblue", fig: "RoundedRectangle" },
{ key: "Beta", color: "lightblue", fig: "Ellipse" },
{ key: "Gamma", color: "lightblue", fig: "Hexagon" },
{ key: "Delta", color: "lightblue", fig: "FramedRectangle" },
{ key: "Epsilon,Epsilon,Epsilon", color: "lightblue", fig: "Cloud" },
{ key: "Z", color: "lightblue", fig: "Procedure" }
];
</pre>
<script>goCode("fixedsize2", 500, 200)</script>
<p>
Note how the TextBlock is measured without the constraint of having a limited width from the Panel.
That results in the text being treated as a single long line, which is then clipped by the Panel.
</p>
<h2 id="StackedContent">Stacked Content</h2>
<p>
Many simple nodes consist of a few objects positioned above each other or next to each other.
</p>
<h3 id="Icons">Icons</h3>
<p>
Perhaps the most commonly seen kind of node can be implemented using a Vertical <a>Panel</a>.
</p>
<pre class="lang-js" id="icons">
diagram.nodeTemplate =
$(go.Node, "Vertical",
$(go.Picture,
{ maxSize: new go.Size(50, 50) },
new go.Binding("source", "img")),
$(go.TextBlock,
{ margin: new go.Margin(3, 0, 0, 0),
maxSize: new go.Size(100, 30),
isMultiline: false },
new go.Binding("text", "text"))
);
diagram.model.nodeDataArray = [
{ text: "Jellylorum", img: "images/50x40.png" }
];
</pre>
<script>goCode("icons", 300, 150)</script>
<p>
Of course you are not limited to just two objects in a panel.
In fact you can have as many GraphObjects in a "Vertical" or a "Horizontal" Panel as you like.
</p>
<pre class="lang-js" id="icons2">
diagram.nodeTemplate =
$(go.Node, "Vertical",
$(go.TextBlock,
{ margin: new go.Margin(3, 0, 0, 0),
maxSize: new go.Size(100, 30),
isMultiline: false,
font: "bold 10pt sans-serif" },
new go.Binding("text", "head")),
$(go.Picture,
{ maxSize: new go.Size(50, 50) },
new go.Binding("source", "img")),
$(go.TextBlock,
{ margin: new go.Margin(3, 0, 0, 0),
maxSize: new go.Size(100, 30),
isMultiline: false },
new go.Binding("text", "foot"))
);
diagram.model.nodeDataArray = [
{ head: "Kitten", foot: "Tantomile", img: "images/50x40.png" }
];
</pre>
<script>goCode("icons2", 300, 150)</script>
<h3 id="SmallIcons">Small icons</h3>
<p>
Another commonly seen kind of node can be implemented using a Horizontal <a>Panel</a>.
</p>
<pre class="lang-js" id="smallicons">
diagram.nodeTemplate =
$(go.Node, "Horizontal",
$(go.Picture,
{ maxSize: new go.Size(16, 16) },
new go.Binding("source", "img")),
$(go.TextBlock,
{ margin: new go.Margin(0, 0, 0, 2) },
new go.Binding("text", "text"))
);
diagram.model.nodeDataArray = [
{ text: "Alonzo", img: "images/50x40.png" }
];
</pre>
<script>goCode("smallicons", 300, 150)</script>
<h2 id="NestedPanels">Nested Panels</h2>
<p>
Panels can be nested.
For example, here is a node consisting of an "Vertical" Panel consisting of an "Auto" Panel surrounding a "Vertical" Panel including a "Horizontal" Panel.
The outer "Vertical" Panel arranges the main stuff on top and a TextBlock on the bottom.
The "Auto" Panel supplies a border around everything but the bottom text.
The inner "Vertical" Panel places three objects vertically in a stack.
The "Horizontal" Panel which is the first element of the "Vertical" Panel places three objects horizontally in a row.
</p>
<pre class="lang-js" id="nestedpanel1">
// common styling for each indicator
function makeIndicator(propName) { // the data property name
return $(go.Shape,
"Circle",
{ width: 8, height: 8, fill: "white", strokeWidth: 0, margin: 5 },
new go.Binding("fill", propName));
}
function makeImagePath(icon) { return "../samples/images/" + icon; }
diagram.nodeTemplate =
$(go.Node, "Vertical",
$(go.Panel, "Auto",
{ background: "white" },
{ portId: "" }, // this whole panel acts as the only port for the node
$(go.Shape, // the border
{ fill: "transparent", stroke: "lightgray" }),
$(go.Panel, "Vertical", // everything within the border
$(go.Panel, "Horizontal", // the row of status indicators
makeIndicator("ind0"),
makeIndicator("ind1"),
makeIndicator("ind2")
), // end Horizontal Panel
$(go.Picture,
{ width: 32, height: 32, margin: 4 },
new go.Binding("source", "icon", makeImagePath)),
$(go.TextBlock,
{ stretch: go.GraphObject.Horizontal, textAlign: "center" },
new go.Binding("text", "number"),
new go.Binding("background", "color"))
) // end Vertical Panel
), // end Auto Panel
$(go.TextBlock,
{ margin: 4 },
new go.Binding("text"))
);
diagram.model.nodeDataArray = [
{ key: 1, text: "Device Type A", number: 17, icon: "server switch.jpg", color: "moccasin",
ind0: "red", ind1: "orange", ind2: "mediumspringgreen" },
{ key: 2, text: "Device Type B", number: 97, icon: "voice atm switch.jpg", color: "mistyrose",
ind0: "lightgray", ind1: "orange", ind2: "green" }
];
diagram.model.linkDataArray = [
{ from: 1, to: 2 }
];
</pre>
<script>goCode("nestedpanel1", 300, 150)</script>
<h2 id="DecoratedContent">Decorated Content</h2>
<p>
Sometimes you want to have a simple node that may display additional visuals
to indicate what state it is in.
</p>
<p>
One way to implement this is to use a Spot <a>Panel</a>, where the main element is itself a Panel
containing the elements that you always want to display, and there are additional objects located at spots around the main element.
The basic outline would be:
</p>
<pre>
Node, "Spot"
Panel, "Auto" // the contents with border
Shape // the border
Panel, ... // the contents
. . .
Shape // the decoration
</pre>
<p>
So the basic body of the node is in a "Vertical" or any kind of Panel,
which is surrounded by a border using an "Auto" Panel,
which gets decorations using the "Spot" Panel that is also the Node.
</p>
<p>
The same design of having the Node be a "Spot" Panel can also used for placing ports relative to the body of a node.
</p>
<pre class="lang-js" id="spotdecorations">
diagram.nodeTemplate =
$(go.Node, "Spot",
// the main content:
$(go.Panel, "Vertical",
$(go.Picture,
{ maxSize: new go.Size(50, 50) },
new go.Binding("source", "img")),
$(go.TextBlock,
{ margin: new go.Margin(3, 0, 0, 0) },
new go.Binding("text", "text"),
new go.Binding("stroke", "error", function(err) { return err ? "red" : "black" }))
),
// decorations:
$(go.Shape, "TriangleUp",
{ alignment: go.Spot.TopLeft,
fill: "yellow", width: 14, height: 14,
visible: false },
new go.Binding("visible", "info", function(i) { return i ? true : false; })),
$(go.Shape, "StopSign",
{ alignment: go.Spot.TopRight,
fill: "red", width: 14, height: 14,
visible: false },
new go.Binding("visible", "error")),
{
toolTip:
$(go.Adornment, "Auto",
$(go.Shape, { fill: "#FFFFCC" },
new go.Binding("visible", "info", function(i) { return i ? true : false; })),
$(go.TextBlock, { margin: 4 },
new go.Binding("text", "info"))
)
}
);
diagram.model.nodeDataArray = [
{ text: "Demeter", img: "images/50x40.png", info: "" },
{ text: "Copricat", img: "images/50x40.png", error: true, info: "shredded curtains" }
];
</pre>
<script>goCode("spotdecorations", 300, 150)</script>
<p>
As another example of a node decoration, this implements a "ribbon" at the top right corner of the node.
The ribbon is implemented by a <a>Panel</a> that contains both a <a>Shape</a> and a <a>TextBlock</a>,
and the panel is positioned by its <a>GraphObject.alignment</a> and <a>GraphObject.alignmentFocus</a> in
the Spot Panel that also is the <a>Node</a>.
The appearance of the ribbon is achieved by using a custom <a>Geometry</a> and binding <a>GraphObject.opacity</a>.
</p>
<pre class="lang-js" id="ribbondecorations">
diagram.nodeTemplate =
$(go.Node, "Spot",
{ locationSpot: go.Spot.Center, locationObjectName: "BODY" },
{ selectionObjectName: "BODY" },
$(go.Panel, "Auto",
{ name: "BODY", width: 150, height: 100 },
{ portId: "" },
$(go.Shape,
{ fill: "lightgray", stroke: null, strokeWidth: 0 }),
$(go.TextBlock,
new go.Binding("text"))
),
$(go.Panel, "Spot",
new go.Binding("opacity", "ribbon", function(t) { return t ? 1 : 0; }),
// note that the opacity defaults to zero (not visible),
// in case there is no "ribbon" property
{ opacity: 0,
alignment: new go.Spot(1, 0, 5, -5),
alignmentFocus: go.Spot.TopRight },
$(go.Shape, // the ribbon itself
{ geometryString: "F1 M0 0 L30 0 70 40 70 70z",
fill: "red", stroke: null, strokeWidth: 0 }),
$(go.TextBlock,
new go.Binding("text", "ribbon"),
{ alignment: new go.Spot(1, 0, -29, 29),
angle: 45, maxSize: new go.Size(100, NaN),
stroke: "white", font: "bold 13px sans-serif", textAlign: "center" })
)
);
diagram.model = new go.GraphLinksModel([
{ key: 1, text: "Alpha" },
{ key: 2, text: "Beta", ribbon: "NEWEST" }
],[
]);
</pre>
<script>goCode("ribbondecorations", 500, 150)</script>
<h2 id="PositionAndLocation">Position and Location</h2>
<p>
Nodes are positioned in document coordinates.
(For more information, read <a href="viewport.html">Coordinate Systems</a>.)
The point at which a Node resides, in document coordinates, is normally the top-left corner of the Node's <a>GraphObject.actualBounds</a>.
If you set the <a>GraphObject.position</a> of a Node, you will be modifying the <code>x</code> and <code>y</code> values of the node's <a>GraphObject.actualBounds</a>.
</p>
<p>
However there are times when it is more natural to think that the "point" of a Node is not at the top-left corner but at some other spot within.
This is especially true when you want any variably-sized text labels or occasional decorations to be ignored regarding the node's location.
That is why Nodes also have a "location" which refers to a point inside the Node.
If you set the <a>Part.location</a> of a Node, you will be lining up the location point of the node to be at that point in document coordinates.
When you move a Node you are actually changing its <a>Part.location</a>.
</p>
<p>
By default the location of a Node is the same as its position.
However you can set the <a>Part.locationSpot</a> to cause the location point to be at some spot in the node's actualBounds.
Furthermore you can set the <a>Part.locationObjectName</a> to cause the location point to be at some spot in some element within the node.
The position will always be at the top-left corner of the whole node,
but the location may be some point at some spot in some object within the node.
</p>
<pre class="lang-js" id="positionlocation">
diagram.grid.visible = true;
diagram.add(
$(go.Node, "Vertical",
{ position: new go.Point(0, 0) }, // set the Node.position
$(go.TextBlock, "position", { editable: true }),
$(go.Shape, { name: "SHAPE", width: 30, height: 30, fill: "lightgreen" })
));
diagram.add(
$(go.Node, "Vertical",
{
location: new go.Point(100, 0), // set the Node.location
locationObjectName: "SHAPE" // the location point is on the element named "SHAPE"
},
$(go.TextBlock, "location", { editable: true }),
$(go.Shape, { name: "SHAPE", width: 30, height: 30, fill: "lightgreen" })
));
</pre>
<script>goCode("positionlocation", 500, 200)</script>
<p>
In this example both nodes have the same Y-coordinate value of zero.
Note how in the above example the "position" Node has the top-left corner of the node at the grid point.
Yet the "location" Node has the top-left corner of the green square at the grid point.
If you edit the text of each node after double-clicking on the text,
note how the green square moves relative to the diagram grid for the "position" node,
but that it does not move for the "location" node.
</p>
<p>
It is common to specify the <a>Part.locationSpot</a> to be <code>go.Spot.Center</code> so that the location point
is at the center of some element in the node, rather than at the top-left corner of that element.
</p>
<pre class="lang-js" id="positionlocation2">
diagram.grid.visible = true;
diagram.add(
$(go.Node, "Vertical",
{ position: new go.Point(0, 0) }, // set the Node.position
$(go.TextBlock, "position", { editable: true }),
$(go.Panel, "Auto",
$(go.Shape, "Circle", { name: "SHAPE", width: 16, height: 16, fill: "lightgreen" }),
$(go.Shape, "Circle", { width: 6, height: 6, strokeWidth: 0 })
)
));
diagram.add(
$(go.Node, "Vertical",
{
location: new go.Point(100, 0), // set the Node.location
locationObjectName: "SHAPE", // the location point is at the center of "SHAPE"
locationSpot: go.Spot.Center
},
$(go.TextBlock, "location", { editable: true }),
$(go.Panel, "Auto",
$(go.Shape, "Circle", { name: "SHAPE", width: 16, height: 16, fill: "lightgreen" }),
$(go.Shape, "Circle", { width: 6, height: 6, strokeWidth: 0 })
)
));
</pre>
<script>goCode("positionlocation2", 500, 200)</script>
<p>
If the position or location of a Node is not <a>Point.isReal</a>, it will not be seen, because GoJS will not know where to draw the node.
In fact the default value for a node's position or location is <code>NaN, NaN</code> and it is the responsibility of either the <a>Diagram.layout</a>
or data bindings to assign real point values for each node.
</p>
</div>
</body>
</html>
+82
View File
@@ -0,0 +1,82 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Overview -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Overview Diagrams</h1>
<p>
An <a>Overview</a> is a subclass of <a>Diagram</a> that is used to display all of the <a>Part</a>s
of another diagram and to show where that diagram's viewport is relative to all of those parts.
The user can also scroll the overviewed diagram by clicking or dragging within the overview.
</p>
<p>
The initialization of an <a>Overview</a> is just a matter of setting <a>Overview.observed</a>
to refer to the <a>Diagram</a> that you want it to show. So there needs to be a DIV for your main diagram,
for which you create a Diagram in the normal manner, and a separate DIV for your overview, for which you
create the Overview in a very simple manner.
</p>
<p>
See samples that make use of <a>Overview</a>s in the <a href="../samples/index.html#overview">samples index</a>.
</p>
<p>
The code below first creates a Diagram that we want to view.
It initializes the diagram with 1000 nodes of random colors.
</p>
<p>
It then creates an <a>Overview</a> and sets <a>Overview.observed</a> to the above Diagram.
The DIV for the overview is named "myOverviewDiv".
You can, if you wish, set <a>Overview.observed</a> at a later time.
You can also set it to null in order to have the Overview stop showing any Diagram.
</p>
<pre class="lang-js" id="diagramPre">
// initialize the main Diagram
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "Rectangle",
{ fill: "white" },
new go.Binding("fill", "color")),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
// start off with a lot of nodes
var nodeDataArray = [];
for (var i = 0; i &lt; 1000; i++) {
nodeDataArray.push({ color: go.Brush.randomColor() });
}
diagram.model.nodeDataArray = nodeDataArray;
// create the Overview and initialize it to show the main Diagram
var myOverview =
$(go.Overview, "myOverviewDiv",
{ observed: diagram });
</pre>
<div style="width:100%">
<span id="overviewSpan" style="display: inline-block; vertical-align: top;">
<b>Overview:</b><br />
<div id="myOverviewDiv" style="width:150px; height: 150px" class="diagramStyling"></div>
</span>
<span id="diagramSpan" style="display: inline-block; vertical-align: top">
<b>Diagram:</b><br />
</span>
</div>
<script>goCode("diagramPre", 500, 300, go.Diagram, "diagramSpan");</script>
<p>
Animations are not shown in Overviews.
Rendering images or SVG does not work for Overviews.
</p>
</div>
</div>
</body>
</html>
+162
View File
@@ -0,0 +1,162 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Palette -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Palette Diagrams</h1>
<p>
A <a>Palette</a> is a subclass of <a>Diagram</a> that is used to display a number of <a>Part</a>s that
can be dragged into the diagram that is being modified by the user.
The initialization of a <a>Palette</a> is just like the initialization of any <a>Diagram</a>.
Like Diagrams, you can have more than one Palette on the page at the same time.
</p>
<p>
See samples that make use of <a>Palette</a>s in the <a href="../samples/index.html#palette">samples index</a>.
</p>
<p>
The following code initializes an empty Diagram on the right side, below.
Note that <a>Diagram.allowDrop</a> must be true, which it is now by default.
In this example we do not bother initializing the model with any node data.
</p>
<p>
This code also creates two <a>Palette</a>s, in the same manner as you would any Diagram.
You initialize a Palette's model in order to show nodes in that Palette.
</p>
<pre class="lang-js" id="diagramPre">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle",
{ fill: "white" },
new go.Binding("fill", "color"),
{ portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.undoManager.isEnabled = true;
// create the Palette
var myPalette =
$(go.Palette, "myPaletteDiv");
// the Palette's node template is different from the main Diagram's
myPalette.nodeTemplate =
$(go.Node, "Horizontal",
$(go.Shape,
{ width: 14, height: 14, fill: "white" },
new go.Binding("fill", "color")),
$(go.TextBlock,
new go.Binding("text", "color"))
);
// the list of data to show in the Palette
myPalette.model.nodeDataArray = [
{ key: "C", color: "cyan" },
{ key: "LC", color: "lightcyan" },
{ key: "A", color: "aquamarine" },
{ key: "T", color: "turquoise" },
{ key: "PB", color: "powderblue" },
{ key: "LB", color: "lightblue" },
{ key: "LSB", color: "lightskyblue" },
{ key: "DSB", color: "deepskyblue" }
];
// create the Palette
var myPalette2 =
$(go.Palette, "myPaletteDiv2",
{ // customize the GridLayout to align the centers of the locationObjects
layout: $(go.GridLayout, { alignment: go.GridLayout.Location })
});
// the Palette's node template is different from the main Diagram's
myPalette2.nodeTemplate =
$(go.Node, "Vertical",
{ locationObjectName: "TB", locationSpot: go.Spot.Center },
$(go.Shape,
{ width: 20, height: 20, fill: "white" },
new go.Binding("fill", "color")),
$(go.TextBlock, { name: "TB" },
new go.Binding("text", "color"))
);
// the list of data to show in the Palette
myPalette2.model.nodeDataArray = [
{ key: "IR", color: "indianred" },
{ key: "LC", color: "lightcoral" },
{ key: "S", color: "salmon" },
{ key: "DS", color: "darksalmon" },
{ key: "LS", color: "lightsalmon" }
];
</pre>
<div style="width:100%">
<span id="paletteSpan" style="display: inline-block; vertical-align: top">
<b>Palette 1 (blues):</b><br />
<div id="myPaletteDiv" style="width: 120px; height: 250px" class="diagramStyling"></div>
</span>
<span id="diagramSpan" style="display: inline-block; vertical-align: top">
<b>Diagram:</b><br />
</span>
<span id="paletteSpan2" style="display: inline-block; vertical-align: top">
<b>Palette 2 (reds):</b><br />
<div id="myPaletteDiv2" style="width: 120px; height: 250px" class="diagramStyling"></div>
</span>
</div>
<script>goCode("diagramPre", 250, 250, go.Diagram, "diagramSpan");</script>
<p>
First, notice that although both Palettes have been initialized with the same kind of model data,
the appearances of the items in the palettes are different because the two use different node templates.
</p>
<p>
Furthermore when you drag a part from the Palette on either side into the Diagram in the middle,
that the appearance changes, because the Diagram uses a third node template.
<em>What is being dragged is just the model data, not the actual <a>Node</a>s.</em>
Because each diagram can use its own templates, the same data object can be represented completely differently.
</p>
<p>
If you want the Palette to show exactly the same Nodes for the same data as your main Diagram,
you can have it share the templates of the main Diagram:
</p>
<pre class="lang-js">
myPalette.nodeTemplateMap = myDiagram.nodeTemplateMap;
</pre>
<p>
Because <a>Palette</a> inherits from <a>Diagram</a>, you can customize it in the normal manners.
You can decide to set its <a>Diagram.initialScale</a> if you want its parts to be smaller or larger than normal.
</p>
<p>
It is also commonplace to customize the ordering of the parts in the palette.
The palette's layout property is a <a>GridLayout</a>, so you can set its <a>GridLayout.sorting</a> property,
and if needed, its <a>GridLayout.comparer</a> property to a custom sorting function.
For example, if you want the Palette to show its parts in exactly the same order in which they
appear in the <code>myPalette.model.nodeDataArray</code>:
</p>
<pre class="lang-js">
myPalette.layout.sorting = go.GridLayout.Forward;
</pre>
<p>
If you wanted to sort the parts in the Palette according to some property on the model data:
</p>
<pre class="lang-js">
myPalette.layout.comparer = function(a, b) {
// A and B are Parts
var av = a.data.someProp;
var bv = b.data.someProp;
if (av < bv) return -1;
if (av > bv) return 1;
return 0;
};
</pre>
</div>
</div>
</body>
</html>
+912
View File
@@ -0,0 +1,912 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Panels -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Panels</h1>
<p>
<a>Panel</a>s are <a>GraphObject</a>s that hold other <a>GraphObject</a>s as their elements.
A Panel is responsible for sizing and positioning all of its elements.
Each Panel establishes its own coordinate system.
The elements of a panel are drawn in order, thereby establishing the Z-ordering of those elements.
</p>
<p>
Although there is only one Panel class, there are many different kinds of panels,
each with its own purpose in how it arranges its elements.
When you construct a <a>Panel</a> you usually specify its <a>Panel.type</a> as the constructor argument.
These are the kinds of panels that exist:
</p>
<ul>
<li><a>Panel,Position</a></li>
<li><a>Panel,Vertical</a></li>
<li><a>Panel,Horizontal</a></li>
<li><a>Panel,Auto</a></li>
<li><a>Panel,Spot</a></li>
<li><a>Panel,Table</a> (see the next section about <a href="tablePanels.html">Table Panels</a>)</li>
<li><a>Panel,Viewbox</a></li>
<li><a>Panel,Link</a> (see the section about <a href="linkLabels.html">Link Labels</a>)</li>
<li><a>Panel,Grid</a> (see the section about <a href="grids.html">Grid Patterns</a>)</li>
<li><a>Panel,Graduated</a> (see the section about <a href="graduatedPanels.html">Graduated Panels</a>)</li>
</ul>
<p>
In these simplistic demonstrations, the code programmatically creates a Part and adds it to the Diagram.
Once you learn about models and data binding you will generally not create parts programmatically.
</p>
<p>
Note also that one can only add <a>Part</a>s (i.e. <a>Node</a>s and <a>Link</a>s) to <a>Diagram</a>s,
and that a Part cannot be an element of a Panel.
But all Parts are Panels because the <a>Part</a> class inherits from <a>Panel</a> -- Parts are basically "top-level" Panels.
Thus these examples make use of Parts as top-level objects whereas within a Node you would use a Panel instead of a Part.
</p>
<p>
Panels have no visual elements of their own, so to display their size the
<a>GraphObject.background</a> is often used.
Panels also have <a>Panel.padding</a> in addition to <a>GraphObject.margin</a>.
Unlike margin, backgrounds cover padding. Setting a padding when the Panel is constrained
in size will reduce the total area that it has to arrange its elements. Setting a margin
will not do this -- instead the Panel will expand in size.
</p>
<h2 id="PositionPanels">Position Panels</h2>
<p>
The simplest kind of <a>Panel</a> is "Position" (<a>Panel,Position</a>).
Each element gets its normal size, whether its natural size or a specified <a>GraphObject.desiredSize</a>
(or equivalently the <a>GraphObject.width</a> and <a>GraphObject.height</a>).
</p>
<p>
Each element's position is given by the <a>GraphObject.position</a> property.
If no position is specified, the element is positioned at (0,0).
All positions are in the Panel's own coordinate system, not in the document-wide coordinate system.
Positions may include negative coordinates.
</p>
<p>
The Panel's size is just big enough to hold all of its elements.
If you want it to be a bit bigger than that, you can set the <a>Panel.padding</a> property.
</p>
<pre class="lang-js" id="positionPanels">
diagram.add(
// all Parts are Panels
$(go.Part, go.Panel.Position, // or "Position"
{ background: "lightgray" },
$(go.TextBlock, "default, at (0,0)", { background: "lightgreen" }),
$(go.TextBlock, "(100, 0)", { position: new go.Point(100, 0), background: "lightgreen" }),
$(go.TextBlock, "(0, 100)", { position: new go.Point(0, 100), background: "lightgreen" }),
$(go.TextBlock, "(55, 28)", { position: new go.Point(55, 28), background: "lightgreen" }),
$(go.TextBlock, "(33, 70)", { position: new go.Point(33, 70), background: "lightgreen" }),
$(go.TextBlock, "(100, 100)", { position: new go.Point(100, 100), background: "lightgreen" })
));
</pre>
<script>goCode("positionPanels", 600, 150)</script>
<p>
A Position Panel will always include the (0,0) origin point in its own panel coordinate system.
Thus a Position Panel that has elements whose collective bounds does not include (0,0) is always extended to include the origin.
</p>
<pre class="lang-js" id="zeroPositionPanel">
diagram.add(
$(go.Part, "Position",
{ background: "lightgray" },
$(go.TextBlock, "(-50,50)", { position: new go.Point(-50, 50), background: "lightgreen" }),
$(go.TextBlock, "(50, 50)", { position: new go.Point(50, 50), background: "lightgreen" }),
$(go.TextBlock, "(0, 100)", { position: new go.Point(0, 100), background: "lightgreen" })
));
</pre>
<script>goCode("zeroPositionPanel", 600, 140)</script>
<p>
Note that when you position <a>Shape</a>s within a Position Panel the thickness of their strokes,
<a>Shape.strokeWidth</a>, will be included. If you wish to position multiple Shapes so that their geometries
line up with each other, independent of how thick their strokes are, set
<a>Shape.isGeometryPositioned</a> to true on each of those Shapes.
</p>
<h2 id="VerticalPanels">Vertical Panels</h2>
<p>
A very common kind of <a>Panel</a> is "Vertical" (<a>Panel,Vertical</a>).
In this Panel all of the panel elements are arranged vertically from top to bottom.
Each element gets its normal height and either its normal width or, if stretched, the width of the panel.
If the element's <a>GraphObject.stretch</a> property has any vertical stretch component, it is ignored.
</p>
<p>
If the element's width does not happen to be the same as the width of the panel,
it is aligned horizontally according to its <a>GraphObject.alignment</a> property.
</p>
<p>
The following Vertical Panel shows how narrow objects are aligned horizontally
and how a narrow object may be stretched horizontally.
The width of the whole Panel is determined by the width of the widest object,
which in this case is the first element.
Note how the last element does not set the desired <a>GraphObject.width</a> property,
so that the <a>GraphObject.stretch</a> value is effective.
</p>
<pre class="lang-js" id="verticalPanels">
diagram.add(
$(go.Part, go.Panel.Vertical, // or "Vertical"
{ background: "lightgray" },
$(go.TextBlock, "a longer string", { background: "lightgreen" }),
$(go.TextBlock, "left", { background: "lightgreen", alignment: go.Spot.Left }),
$(go.TextBlock, "center", { background: "lightgreen", alignment: go.Spot.Center }),
$(go.TextBlock, "right", { background: "lightgreen", alignment: go.Spot.Right }),
$(go.TextBlock, "stretch", { background: "lightgreen", stretch: go.GraphObject.Fill })
));
</pre>
<script>goCode("verticalPanels", 600, 150)</script>
<h2 id="ConstrainedWidthVerticalPanels">Constrained Width Vertical Panels</h2>
<p>
A Vertical <a>Panel</a> normally has the width of its widest element and the height that is the sum of all of its elements.
However, you can also set the width and/or height to be larger or smaller than the natural size.
Or if there is a Panel containing this panel, it might impose size constraints on this panel.
If the width and/or height are larger than the natural size, the panel is bigger,
leaving empty space that may be filled with the background brush.
If the width and/or height are smaller than the natural size, the content elements may be clipped.
</p>
<p>
The Vertical Panel below sets the width to be 140, much wider than needed.
You can see how the last element's width is stretched.
</p>
<pre class="lang-js" id="excessWidth">
diagram.add(
$(go.Part, "Vertical",
{ background: "lightgray", width: 140 },
$(go.TextBlock, "a longer string", { background: "lightgreen" }),
$(go.TextBlock, "left", { background: "lightgreen", alignment: go.Spot.Left }),
$(go.TextBlock, "center", { background: "lightgreen", alignment: go.Spot.Center }),
$(go.TextBlock, "right", { background: "lightgreen", alignment: go.Spot.Right }),
$(go.TextBlock, "stretch", { background: "lightgreen", stretch: go.GraphObject.Fill })
));
</pre>
<script>goCode("excessWidth", 600, 150)</script>
<p>
These two Vertical Panels both have a width of 50, much less than natural.
The latter one also has a restricted height.
Note how the text is automatically wrapped to try to fit within the limited width,
because the default value for <a>TextBlock.wrap</a> is to allow wrapping.
</p>
<pre class="lang-js" id="limitedWidth">
diagram.add(
$(go.Part, "Vertical",
{ position: new go.Point(0, 0), background: "lightgray", width: 50 },
$(go.TextBlock, "a longer string", { background: "lightgreen" }),
$(go.TextBlock, "left", { background: "lightgreen", alignment: go.Spot.Left }),
$(go.TextBlock, "center", { background: "lightgreen", alignment: go.Spot.Center }),
$(go.TextBlock, "right", { background: "lightgreen", alignment: go.Spot.Right }),
$(go.TextBlock, "stretch", { background: "lightgreen", stretch: go.GraphObject.Fill })
));
diagram.add(
$(go.Part, "Vertical",
{ position: new go.Point(70, 0), background: "lightgray", width: 50, height: 65 },
$(go.TextBlock, "a longer string", { background: "lightgreen" }),
$(go.TextBlock, "left", { background: "lightgreen", alignment: go.Spot.Left }),
$(go.TextBlock, "center", { background: "lightgreen", alignment: go.Spot.Center }),
$(go.TextBlock, "right", { background: "lightgreen", alignment: go.Spot.Right }),
$(go.TextBlock, "stretch", { background: "lightgreen", stretch: go.GraphObject.Fill })
));
</pre>
<script>goCode("limitedWidth", 600, 150)</script>
<p>
Here is a Vertical Panel with a default <a>GraphObject.stretch</a> of <a>GraphObject,Horizontal</a>.
Because no width is specified for the whole panel, its width is the width of the widest element, in this case the second one.
Note how all of the <a>TextBlock</a>s have the same long width, as highlighted by the lightgreen backgrounds.
However the last TextBlock has a limited width, so it is not stretched.
One can limit the width but not the height by supplying a value of <code>NaN</code> or <code>Infinity</code> for the height.
</p>
<pre class="lang-js" id="defaultStretch">
diagram.add(
$(go.Part, "Vertical",
{ background: "lightgray", defaultStretch: go.GraphObject.Horizontal },
$(go.TextBlock, "short", { margin: 2, background: "lightgreen" }),
$(go.TextBlock, "a much longer string", { margin: 2, background: "lightgreen" }),
$(go.TextBlock, "medium length", { margin: 2, background: "lightgreen" }),
$(go.TextBlock, "short2", { margin: 2, background: "lightgreen" }),
$(go.TextBlock, "max 50", { margin: 2, background: "lightgreen", maxSize: new go.Size(50, NaN) })
));
</pre>
<script>goCode("defaultStretch", 600, 150)</script>
<p>
If you change that sample to set the <a>GraphObject.width</a> or <a>GraphObject.desiredSize</a>.width on one or more of the elements
(just the last one in this case), the panel will get a width that is equal to the maximum of the set widths.
The reduced width will cause the other, stretched, elements to be measured with the limited width (50 in this case),
which cause those <a>TextBlock</a>s to wrap to fit within the available width.
</p>
<pre class="lang-js" id="defaultStretch2">
diagram.add(
$(go.Part, "Vertical",
{ background: "lightgray", defaultStretch: go.GraphObject.Horizontal },
$(go.TextBlock, "short", { margin: 2, background: "lightgreen" }),
$(go.TextBlock, "a much longer string", { margin: 2, background: "lightgreen" }),
$(go.TextBlock, "medium length", { margin: 2, background: "lightgreen" }),
$(go.TextBlock, "short2", { margin: 2, background: "lightgreen" }),
$(go.TextBlock, "= 50", { margin: 2, background: "lightgreen", width: 50 })
));
</pre>
<script>goCode("defaultStretch2", 600, 150)</script>
<h2 id="HorizontalPanels">Horizontal Panels</h2>
<p>
Horizontal <a>Panel</a>s are just like Vertical Panels, except that the elements are arranged horizontally instead of vertically.
Elements are never stretched horizontally, but they may be stretched vertically.
Because elements are never stretched horizontally, a stretch value of <a>GraphObject,Fill</a> is the same as <a>GraphObject,Vertical</a>.
</p>
<p>
Note that the last element in both panels do not specify a desired <a>GraphObject.height</a>,
so that the <a>GraphObject.stretch</a> value may be effective.
</p>
<pre class="lang-js" id="horizontalPanels">
diagram.add(
$(go.Part, go.Panel.Horizontal, // or "Horizontal"
{ position: new go.Point(0, 0), background: "lightgray" },
$(go.Shape, { width: 30, fill: "lightgreen", height: 100 }),
$(go.Shape, { width: 30, fill: "lightgreen", height: 50, alignment: go.Spot.Top }),
$(go.Shape, { width: 30, fill: "lightgreen", height: 50, alignment: go.Spot.Center }),
$(go.Shape, { width: 30, fill: "lightgreen", height: 50, alignment: go.Spot.Bottom }),
$(go.Shape, { width: 30, fill: "lightgreen", stretch: go.GraphObject.Fill })
));
diagram.add(
$(go.Part, "Horizontal",
{ position: new go.Point(200, 0), background: "lightgray", height: 120 },
$(go.Shape, { width: 30, fill: "lightgreen", height: 50, alignment: go.Spot.Top }),
$(go.Shape, { width: 30, fill: "lightgreen", height: 50, alignment: go.Spot.Center }),
$(go.Shape, { width: 30, fill: "lightgreen", height: 50, alignment: go.Spot.Bottom }),
$(go.Shape, { width: 30, fill: "lightgreen", stretch: go.GraphObject.Fill })
));
</pre>
<script>goCode("horizontalPanels", 600, 150)</script>
<h3 id="FillingHorizontalAndVerticalPanelsInOppositeDirection">Filling Horizontal and Vertical Panels in Opposite Direction</h3>
<p>
Both Vertical and Horizontal <a>Panel</a>s can have their elements be arranged in the opposite direction:
bottom-to-top for Vertical Panels and right-to-left for Horizontal Panels.
Just set <a>Panel.isOpposite</a> to true.
</p>
<pre class="lang-js" id="opposite">
diagram.add(
$(go.Part, "Horizontal",
{ background: "lightgray", isOpposite: true },
$(go.TextBlock, "0", { margin: 5, background: "lightgreen" }),
$(go.TextBlock, "1", { margin: 5, background: "lightgreen" }),
$(go.TextBlock, "2", { margin: 5, background: "lightgreen" }),
$(go.TextBlock, "3", { margin: 5, background: "lightgreen" }),
$(go.TextBlock, "4", { margin: 5, background: "lightgreen" })
));
diagram.add(
$(go.Part, "Vertical",
{ background: "lightgray", isOpposite: true },
$(go.TextBlock, "0", { margin: 5, background: "lightgreen" }),
$(go.TextBlock, "1", { margin: 5, background: "lightgreen" }),
$(go.TextBlock, "2", { margin: 5, background: "lightgreen" }),
$(go.TextBlock, "3", { margin: 5, background: "lightgreen" }),
$(go.TextBlock, "4", { margin: 5, background: "lightgreen" })
));
</pre>
<script>goCode("opposite", 600, 150)</script>
<h2 id="DefaultAlignmentAndStretch">Default Alignment and Stretch</h2>
<p>
Both Vertical and Horizontal <a>Panel</a>s support the <a>Panel.defaultAlignment</a> and <a>Panel.defaultStretch</a> properties.
This is a convenience so that you do not need to set the <a>GraphObject.alignment</a> or <a>GraphObject.stretch</a> property on each element.
</p>
<p>
Here is a Horizontal Panel with a default <a>GraphObject.alignment</a> of <a>Spot,Bottom</a>.
All of the <a>Shape</a>s are aligned at the bottom, even though the default alignment would normally be <a>Spot.Center</a>.
However, the last Shape has its height stretched to the full height of the panel, 90.
In this case the <a>GraphObject.margin</a> provides a little extra space around the object.
</p>
<pre class="lang-js" id="defaultAlignment">
diagram.add(
$(go.Part, "Horizontal",
{ background: "lightgray", height: 90, defaultAlignment: go.Spot.Bottom },
$(go.Shape, { width: 30, margin: 2, fill: "lightgreen", height: 60 }),
$(go.Shape, { width: 30, margin: 2, fill: "lightgreen", height: 30 }),
$(go.Shape, { width: 30, margin: 2, fill: "lightgreen", height: 40 }),
$(go.Shape, { width: 30, margin: 2, fill: "lightgreen", stretch: go.GraphObject.Fill })
));
</pre>
<script>goCode("defaultAlignment", 600, 150)</script>
<p>
Vertical and Horizontal Panels are relatively simple ways of arranging a column or a row of objects.
For more options, you may need to use a <a href="tablePanels.html">Table Panel</a>, even with the same set of objects.
This is especially true when you want more control over the stretching of one or more elements.
</p>
<h2 id="Spots">Spots</h2>
<p>
Before we discuss other kinds of <a>Panel</a>s, we should elaborate a bit about the concept of spots.
<a>Spot</a>s are a way of providing both relative and absolute positioning information.
</p>
<p>
You have already seen many of the most common uses of Spots, for specifying the alignment of objects within a panel,
as constant values of the <a>Spot</a> class:
</p>
<table>
<tr>
<td><a>Spot,TopLeft</a></td> <td><a>Spot,Top</a></td> <td><a>Spot,TopRight</a></td>
</tr>
<tr>
<td><a>Spot,Left</a></td> <td><a>Spot.Center</a></td> <td><a>Spot,Right</a></td>
</tr>
<tr>
<td><a>Spot,BottomLeft</a></td> <td><a>Spot,Bottom</a></td> <td><a>Spot,BottomRight</a></td>
</tr>
</table>
<p>
But Spots are more general than that.
The <a>Spot.x</a> and <a>Spot.y</a> properties can be any number between zero and one, inclusive.
Those values are the fractional distances along the X and Y axes from the top-left corner of an arbitrary rectangle.
So <a>Spot,TopLeft</a> is the same as new go.Spot(0, 0),
<a>Spot,BottomRight</a> is the same as new go.Spot(1, 1),
and <a>Spot,Right</a> is the same as new go.Spot(1, 0.5).
</p>
<p>
Here are the standard nine Spots shown on a rectangular shape.
</p>
<pre class="lang-js" id="standardSpots">
diagram.add(
$(go.Part, go.Panel.Spot, // or "Spot"
$(go.Shape, "Rectangle",
{ fill: "lightgreen", stroke: null, width: 100, height: 50 }),
$(go.TextBlock, "0,0", { alignment: new go.Spot(0, 0) }),
$(go.TextBlock, "0.5,0", { alignment: new go.Spot(0.5, 0) }),
$(go.TextBlock, "1,0", { alignment: new go.Spot(1, 0) }),
$(go.TextBlock, "0,0.5", { alignment: new go.Spot(0, 0.5) }),
$(go.TextBlock, "0.5,0.5", { alignment: new go.Spot(0.5, 0.5) }),
$(go.TextBlock, "1,0.5", { alignment: new go.Spot(1, 0.5) }),
$(go.TextBlock, "0,1", { alignment: new go.Spot(0, 1) }),
$(go.TextBlock, "0.5,1", { alignment: new go.Spot(0.5, 1) }),
$(go.TextBlock, "1,1", { alignment: new go.Spot(1, 1) })
));
</pre>
<script>goCode("standardSpots", 600, 100)</script>
<p>
Besides the fractional positioning of a spot relative to some rectangular area,
you can also specify an absolute offset.
The <a>Spot.offsetX</a> and <a>Spot.offsetY</a> properties determine a point that is
a distance from the fractional point given by <a>Spot.x</a> and <a>Spot.y</a>.
Here we show three TextBlocks near the bottom-left corner and three TextBlocks near the bottom-right corner.
The ones on the left are offset along the X-axis plus or minus 40 units;
the ones on the right are offset along the Y-axis plus or minus 20 units.
TextBlocks are also given a semi-transparent red background to help distinguish their bounds.
</p>
<pre class="lang-js" id="spotOffsets">
var pink = "rgba(255,0,0,.2)";
diagram.add(
$(go.Part, "Spot",
$(go.Shape, "Rectangle",
{ fill: "lightgreen", stroke: null, width: 200, height: 50 }),
// Near bottom-left corner:
$(go.TextBlock, "(-40,0)", { background: pink, alignment: new go.Spot(0, 1, -40, 0) }),
$(go.TextBlock, "(0,0)", { background: pink, alignment: new go.Spot(0, 1, 0, 0) }),
$(go.TextBlock, "(40,0)", { background: pink, alignment: new go.Spot(0, 1, 40, 0) }),
// Near bottom-right corner:
$(go.TextBlock, "(0,-20)", { background: pink, alignment: new go.Spot(1, 1, 0, -20) }),
$(go.TextBlock, "(0,0)", { background: pink, alignment: new go.Spot(1, 1, 0, 0) }),
$(go.TextBlock, "(0,20)", { background: pink, alignment: new go.Spot(1, 1, 0, 20) })
));
</pre>
<script>goCode("spotOffsets", 600, 100)</script>
<h2 id="AutoPanels">Auto Panels</h2>
<p>
Auto <a>Panel</a>s fit a "main" element just around the other elements of the panel.
The main element is usually the furthest back in the Z-order, i.e. the first element, so that the other elements are not obscured by it.
The main element is declared by setting <a>GraphObject.isPanelMain</a> to true;
but often no such element is present, so it uses the very first element of the panel.
</p>
<p>
Typically the Auto Panel will measure the non-"main" elements,
determine a width and a height that can enclose all of them,
and make the "main" element that size or slightly bigger.
You do <em>not</em> set the <a>GraphObject.desiredSize</a> (or <a>GraphObject.width</a> or <a>GraphObject.height</a>) of the "main" element.
</p>
<p>
An Auto Panel is the normal way to implement a border around an object.
Use a <a>Shape</a> as the first/"main" element -- it becomes the border.
The <a>Shape.figure</a> is normally "Rectangle" or "RoundedRectangle" or "Ellipse", as shown below.
The other elements become the "content" for the panel inside the border.
In the examples below there is only a single "content" element, a <a>TextBlock</a>.
We have set the <a>GraphObject.background</a> and <a>Shape.fill</a> properties to help show the sizes and positions of objects.
</p>
<p>
Auto Panels should have two or more elements in them.
</p>
<pre class="lang-js" id="autoPanels">
diagram.add(
$(go.Part, "Auto",
{ position: new go.Point(0, 0), background: "lightgray" },
$(go.Shape, "Rectangle", { fill: "lightgreen" }),
$(go.TextBlock, "some text", { background: "yellow" })
));
diagram.add(
$(go.Part, "Auto",
{ position: new go.Point(100, 0), background: "lightgray" },
$(go.Shape, "RoundedRectangle", { fill: "lightgreen" }),
$(go.TextBlock, "some text", { background: "yellow" })
));
diagram.add(
$(go.Part, "Auto",
{ position: new go.Point(200, 0), background: "lightgray" },
$(go.Shape, "Ellipse", { fill: "lightgreen" }),
$(go.TextBlock, "some text", { background: "yellow" })
));
</pre>
<script>goCode("autoPanels", 600, 100)</script>
<p>
If you add a <a>GraphObject.margin</a> to the <a>TextBlock</a> in each of the same three panels,
you will add a little space all around the "content" element inside the "main" element.
</p>
<pre class="lang-js" id="marginAutoPanels">
diagram.add(
$(go.Part, "Auto",
{ position: new go.Point(0, 0), background: "lightgray" },
$(go.Shape, "Rectangle", { fill: "lightgreen" }),
$(go.TextBlock, "some text", { margin: 2, background: "yellow" })
));
diagram.add(
$(go.Part, "Auto",
{ position: new go.Point(100, 0), background: "lightgray" },
$(go.Shape, "RoundedRectangle", { fill: "lightgreen" }),
$(go.TextBlock, "some text", { margin: 2, background: "yellow" })
));
diagram.add(
$(go.Part, "Auto",
{ position: new go.Point(200, 0), background: "lightgray" },
$(go.Shape, "Ellipse", { fill: "lightgreen" }),
$(go.TextBlock, "some text", { margin: 2, background: "yellow" })
));
</pre>
<script>goCode("marginAutoPanels", 600, 100)</script>
<p>
For most <a>Shape</a>s other than "Rectangle" figure we do not want to have the "main" shape be the same size as the "content" element.
Ellipses, for example, need to be significantly larger than the content to avoid having the content spill over the edge of the shape.
This can be controlled by setting the <a>Shape.spot1</a> and <a>Shape.spot2</a> properties, which determine the area where the content should go.
Many of the predefined figures have their own default values for spot1 and spot2.
</p>
<pre class="lang-js" id="spotAreaAutoPanels">
diagram.add(
$(go.Part, "Auto",
{ position: new go.Point(0, 0), background: "lightgray" },
$(go.Shape, "RoundedRectangle",
{ fill: "lightgreen", spot1: new go.Spot(0, 0), spot2: new go.Spot(1, 1) }),
$(go.TextBlock, "some text", { background: "yellow" })
));
diagram.add(
$(go.Part, "Auto",
{ position: new go.Point(100, 0), background: "lightgray" },
$(go.Shape, "RoundedRectangle",
{ fill: "lightgreen",
spot1: new go.Spot(0, 0, 10, 0), spot2: new go.Spot(1, 1, -10, -10) }),
$(go.TextBlock, "some text", { background: "yellow" })
));
diagram.add(
$(go.Part, "Auto",
{ position: new go.Point(200, 0), background: "lightgray" },
$(go.Shape, "RoundedRectangle",
{ fill: "lightgreen",
spot1: new go.Spot(0, 0, 0, 20), spot2: new go.Spot(1, 1, 0, -20) }),
$(go.TextBlock, "some text", { background: "yellow" })
));
</pre>
<script>goCode("spotAreaAutoPanels", 600, 100)</script>
<p>
The spot1 and spot2 properties on the main <a>Shape</a> are more general and more flexible than specifying the <a>GraphObject.margin</a> on the content element(s).
</p>
<h2 id="ConstrainedSizeAutoPanels">Constrained Size Auto Panels</h2>
<p>
If you constrain the size of the whole panel, there may be less or more space available to fit all of the "content" elements inside the "main" element.
In the following example each Part has a total size of 60x60, causing the "content" <a>TextBlock</a>s to be limited in width and height,
less than the natural width, which results in wrapping of the text.
However there may not be enough height available to show the whole content element(s), causing them to be clipped.
You can see that in the third Part the text is clipped, because there is less available area within an ellipse than within a rectangle.
</p>
<pre class="lang-js" id="autoPanelsConstrained">
diagram.add(
$(go.Part, "Auto",
{ width: 60, height: 60 }, // set the size of the whole panel
{ position: new go.Point(0, 0), background: "lightgray" },
$(go.Shape, "Rectangle", { fill: "lightgreen" }),
$(go.TextBlock, "Some Wrapping Text", { background: "yellow" })
));
diagram.add(
$(go.Part, "Auto",
{ width: 60, height: 60 }, // set the size of the whole panel
{ position: new go.Point(100, 0), background: "lightgray" },
$(go.Shape, "RoundedRectangle", { fill: "lightgreen" }),
$(go.TextBlock, "Some Wrapping Text", { background: "yellow" })
));
diagram.add(
$(go.Part, "Auto",
{ width: 60, height: 60 }, // set the size of the whole panel
{ position: new go.Point(200, 0), background: "lightgray" },
$(go.Shape, "Ellipse", { fill: "lightgreen" }),
$(go.TextBlock, "Some Wrapping Text", { background: "yellow" })
));
</pre>
<script>goCode("autoPanelsConstrained", 600, 100)</script>
<p>
You should not set the size (<a>GraphObject.desiredSize</a> or <a>GraphObject.width</a> or <a>GraphObject.height</a>)
of the "main" element of an Auto Panel.
</p>
<p>
Auto Panels should have two or more elements in them.
</p>
<h2 id="SpotPanels">Spot Panels</h2>
<p>
Spot <a>Panel</a>s are like Auto Panels in that there is a "main" element and there are "other" elements that are not resized.
The "other" elements are positioned about the "main" element based on the <a>GraphObject.alignment</a> property that has a <a>Spot</a> value.
The main feature of Spot Panels, unlike Auto Panels, is that those elements may extend beyond the bounds of the "main" element.
</p>
<p>
This is useful for having the main shape be a specific size and positioning smaller elements at particular places relative to the main shape.
Note in this example that the TextBlocks are centered at the four corners,
causing the panel to be larger than the main shape, as can be seen with the light gray background.
</p>
<pre class="lang-js" id="spotPanels">
diagram.add(
$(go.Part, "Spot",
{ background: "lightgray" },
$(go.Shape, "Rectangle",
{ fill: "lightgreen", width: 100, height: 50 }),
$(go.TextBlock, "TL", { background: "yellow", alignment: go.Spot.TopLeft }),
$(go.TextBlock, "TR", { background: "yellow", alignment: go.Spot.TopRight }),
$(go.TextBlock, "BL", { background: "yellow", alignment: go.Spot.BottomLeft }),
$(go.TextBlock, "BR", { background: "yellow", alignment: go.Spot.BottomRight })
));
</pre>
<script>goCode("spotPanels", 600, 100)</script>
<p>
A Spot Panel aligns its content elements in the general location given by its <a>GraphObject.alignment</a>.
The precise point in the content element that is positioned defaults to <a>Spot.Center</a>, as seen above.
But you can set the element's <a>GraphObject.alignmentFocus</a> to use a different spot.
For example, if you use the same alignmentFocus as the alignment, the elements will be just inside the main element's bounds:
</p>
<pre class="lang-js" id="focusInsideSpotPanels">
diagram.add(
$(go.Part, "Spot",
{ background: "lightgray" },
$(go.Shape, "Rectangle",
{ fill: "lightgreen", width: 100, height: 50 }),
$(go.TextBlock, "TL", { background: "yellow",
alignment: go.Spot.TopLeft, alignmentFocus: go.Spot.TopLeft }),
$(go.TextBlock, "TR", { background: "yellow",
alignment: go.Spot.TopRight, alignmentFocus: go.Spot.TopRight }),
$(go.TextBlock, "BL", { background: "yellow",
alignment: go.Spot.BottomLeft, alignmentFocus: go.Spot.BottomLeft }),
$(go.TextBlock, "BR", { background: "yellow",
alignment: go.Spot.BottomRight, alignmentFocus: go.Spot.BottomRight })
));
</pre>
<script>goCode("focusInsideSpotPanels", 600, 100)</script>
<p>
If you use the opposite alignmentFocus as the alignment, the elements will be just outside the main element's bounds:
</p>
<pre class="lang-js" id="focusOutsideSpotPanels">
diagram.add(
$(go.Part, "Spot",
{ background: "lightgray" },
$(go.Shape, "Rectangle",
{ fill: "lightgreen", width: 100, height: 50 }),
$(go.TextBlock, "TL", { background: "yellow",
alignment: go.Spot.TopLeft, alignmentFocus: go.Spot.BottomRight }),
$(go.TextBlock, "TR", { background: "yellow",
alignment: go.Spot.TopRight, alignmentFocus: go.Spot.BottomLeft }),
$(go.TextBlock, "BL", { background: "yellow",
alignment: go.Spot.BottomLeft, alignmentFocus: go.Spot.TopRight }),
$(go.TextBlock, "BR", { background: "yellow",
alignment: go.Spot.BottomRight, alignmentFocus: go.Spot.TopLeft })
));
</pre>
<script>goCode("focusOutsideSpotPanels", 600, 100)</script>
<p>
alignmentFocus offsetX/Y will also work to offset the alignmentFocus point, the same way it works for Link labels:
</p>
<pre class="lang-js" id="focusSpotPanels2">
diagram.layout = $(go.GridLayout,
{ wrappingColumn: 10, wrappingWidth: 900, isViewportSized: false });
var blue = "rgba(0,0,255,.2)";
diagram.add(
$(go.Part, "Vertical",
{ locationObjectName: 'main' },
$(go.Panel, "Spot",
$(go.Shape, "Rectangle",
{ name: 'main', fill: "lightgreen", stroke: null, width: 100, height: 100 }),
$(go.Shape, "Rectangle",
{ fill: "lightcoral", stroke: null, width: 30, height: 30,
alignment: go.Spot.TopRight, alignmentFocus: go.Spot.TopRight
})
),
$(go.TextBlock, "alignment: TopRight,\n alignmentFocus: TopRight",
{ font: '11px sans-serif' })
));
diagram.add(
$(go.Part, "Vertical",
{ locationObjectName: 'main' },
$(go.Panel, "Spot",
$(go.Shape, "Rectangle",
{ name: 'main', fill: "lightgreen", stroke: null, width: 100, height: 100 }),
$(go.Shape, "Rectangle",
{ fill: "lightcoral", stroke: null, width: 30, height: 30,
alignment: go.Spot.TopRight, alignmentFocus: go.Spot.BottomRight
})
),
$(go.TextBlock, "alignment: TopRight,\n alignmentFocus: BottomRight",
{ font: '11px sans-serif' })
));
diagram.add(
$(go.Part, "Vertical",
{ locationObjectName: 'main' },
$(go.Panel, "Spot",
$(go.Shape, "Rectangle",
{ name: 'main', fill: "lightgreen", stroke: null, width: 100, height: 100 }),
$(go.Shape, "Rectangle",
{ fill: "lightcoral", stroke: null, width: 30, height: 30,
// BottomRight with offsetX = 15
alignment: go.Spot.TopRight, alignmentFocus: new go.Spot(1, 1, 15, 0)
})
),
$(go.TextBlock, "alignment: TopRight,\n alignmentFocus: BottomRight with offsetX = 15",
{ font: '11px sans-serif' })
));
</pre>
<script>goCode("focusSpotPanels2", 700, 250)</script>
<h3 id="AligningSubElementsSpotPanels">Aligning to sub-elements with Spot Panels</h3>
<p>
You may find it necessary to align an object nested inside a Spot panel with that panel's main element.
This is often the case when you want an element of a Spot panel to appear to have its own text label or other decorator.
</p>
<p>
To do this, you can use <a>Panel.alignmentFocusName</a>.
In the example below, a Spot panel contains a main element and another Panel. We want to align the corners of the main element
the shape within this panel, so we give it a name and set alignmentFocusName on the panel.
</p>
<pre class="lang-js" id="alignmentFocusName">
diagram.add(
$(go.Node, "Spot",
// Main shape
$(go.Shape, { strokeWidth: 4, fill: 'lime' }),
// Instead of aligning this Panel, we want to align the shape inside of it, to the corner of the main shape
$(go.Panel, "Horizontal",
{ background: 'rgba(255,0,0,0.1)', alignmentFocusName: 'shape', alignment: go.Spot.TopRight, alignmentFocus: go.Spot.BottomLeft },
$(go.TextBlock, "some\nlong label", { margin: 8 }),
$(go.Shape, "RoundedRectangle", { width: 20, height: 20, name: 'shape', strokeWidth: 0, fill: 'red' },
new go.Binding("fill", "color"))
)
)
);
</pre>
<script>goCode("alignmentFocusName", 600, 300)</script>
<!-- begin new -->
<h3 id="StretchingWithSpotPanels">Stretching with Spot Panels</h3>
<p>
When a non-main element in a Spot panel stretches, it takes on the width and/or height of the main element.
This can be useful for aligning elements within the Panel.
</p>
<p>
In the example below, the red main element has three elements around it which stretch to its side's length.
The main element is the <a>Part.resizeObject</a>, and as it changes size the stretched elements will change size accordingly.
</p>
<pre class="lang-js" id="StretchSpotPanels">
diagram.add(
$(go.Part, "Spot",
{
resizable: true,
resizeObjectName: 'MAIN'
},
$(go.Shape, "Rectangle", { name: 'MAIN', strokeWidth: 0, width: 80, height: 60, fill: 'rgba(255,0,0, .8)' }), // red
$(go.Shape, "Rectangle", { stretch: go.GraphObject.Vertical, strokeWidth: 0, width: 20, fill: 'rgba(0,255,0, .3)', // green
alignment: go.Spot.Left,
alignmentFocus: go.Spot.Right
}),
$(go.Shape, "Rectangle", { stretch: go.GraphObject.Vertical, strokeWidth: 0, width: 20, fill: 'rgba(0,0,255, .3)' , // blue
alignment: go.Spot.Right,
alignmentFocus: go.Spot.Left
}),
$(go.Shape, "Rectangle", { stretch: go.GraphObject.Horizontal, strokeWidth: 0, height: 20, fill: 'rgba(255,0,255, .3)' , // pink
alignment: go.Spot.Bottom,
alignmentFocus: go.Spot.Top
})
));
diagram.select(diagram.parts.first());
</pre>
<script>goCode("StretchSpotPanels", 600, 300)</script>
<!-- end new -->
<h3 id="ConstrainingSizeWithSpotPanels">Constraining size with Spot Panels</h3>
<p>
If you constrain the size of the whole panel, the panel may clip its elements.
For example, when the whole panel must be 100x50, there is room horizontally but not vertically
for the main element plus all of its other elements after arranging them.
</p>
<pre class="lang-js" id="clipping">
diagram.add(
$(go.Part, "Spot",
{ background: "lightgray",
width: 100, height: 50 }, // it is unusual to set the size!
$(go.Shape, "Rectangle", { fill: "lightgreen", width: 40, height: 40 }),
$(go.TextBlock, "TL", { background: "yellow",
alignment: go.Spot.TopLeft, alignmentFocus: go.Spot.BottomRight }),
$(go.TextBlock, "TR", { background: "yellow",
alignment: go.Spot.TopRight, alignmentFocus: go.Spot.BottomLeft }),
$(go.TextBlock, "BL", { background: "yellow",
alignment: go.Spot.BottomLeft, alignmentFocus: go.Spot.TopRight }),
$(go.TextBlock, "BR", { background: "yellow",
alignment: go.Spot.BottomRight, alignmentFocus: go.Spot.TopLeft })
));
</pre>
<script>goCode("clipping", 600, 100)</script>
<p>
Spot Panels should have two or more elements in them.
</p>
<p>
Remember that the elements of every panel are drawn in order.
Normally you want the main element to be behind all of the other elements, so the main element will come first.
However if you want the main element to be in front of some or all of the other elements,
you can move the main element not to be the first element of the panel,
if you also set its <a>GraphObject.isPanelMain</a> property to true.
</p>
<pre class="lang-js" id="spotZorder">
diagram.add(
$(go.Part, "Spot",
{ background: "lightgray" },
$(go.TextBlock, "TL", { background: "yellow", alignment: go.Spot.TopLeft }),
$(go.TextBlock, "TR", { background: "yellow", alignment: go.Spot.TopRight }),
$(go.TextBlock, "BL", { background: "yellow", alignment: go.Spot.BottomLeft }),
$(go.TextBlock, "BR", { background: "yellow", alignment: go.Spot.BottomRight }),
// NOTE: the main element isn't first, so it must be declared by setting isPanelMain to true
$(go.Shape, "Rectangle",
{ isPanelMain: true },
{ fill: "lightgreen", width: 100, height: 50 })
));
</pre>
<script>goCode("spotZorder", 600, 100)</script>
<p>
Note how the opaque Shape, explicitly declared to be the main element, is now visually in front of
the non-main elements of the Spot Panel because it has been moved to be the last element in the panel.
</p>
<p>
Without setting <a>GraphObject.isPanelMain</a> to true on the desired main element, in this example
<a>Panel.findMainElement</a> would return the first TextBlock.
This would cause all of the other elements to be arranged around that TextBlock.
Since the TextBlock is small and the rectangular Shape is big and opaque,
the Shape would cover all of the other TextBlocks, so the user might not see any text,
depending on the size and alignment of those other TextBlocks.
</p>
<h3 id="ClippingWithSpotPanels">Clipping with Spot Panels</h3>
<p>
Spot Panels can set <a>Panel.isClipping</a> to true to use the main Panel element as a clipping area instead of a drawn Shape.
If used, the main element must be a Shape and its stroke and fill will not be drawn.
When <a>Panel.isClipping</a> is true, the Spot panel will size itself to be the <strong>intersection</strong> of the main element bounds and
all other elements' bounds, rather than the union of these bounds.
</p>
<p>
Example:
<pre class="lang-js" id="clipPictures">
diagram.layout = $(go.GridLayout);
// Without Panel.isClipping
diagram.add(
$(go.Part, "Spot",
{ scale: 2 },
$(go.Shape, "Circle", { width: 55, height: 55, strokeWidth: 0 } ),
$(go.Picture, "../samples/images/55x55.png",
{ width: 55, height: 55 }
)
)
);
// Using Panel.isClipping
diagram.add(
$(go.Part, "Spot",
{ isClipping: true, scale: 2 },
$(go.Shape, "Circle", { width: 55, height: 55, strokeWidth: 0 } ),
$(go.Picture, "../samples/images/55x55.png",
{ width: 55, height: 55 }
)
)
);
// Using Panel.isClipping and also having a surrounding panel
diagram.add(
$(go.Part, "Spot",
{ scale: 2 },
$(go.Shape, "Circle", { width: 65, height: 65, strokeWidth: 0, fill: 'red' } ),
$(go.Panel, "Spot",
{ isClipping: true },
$(go.Shape, "Circle", { width: 55, height: 55, strokeWidth: 0 } ),
$(go.Picture, "../samples/images/55x55.png",
{ width: 55, height: 55 }
)
)
)
);
</pre>
<script>goCode("clipPictures", 500, 200)</script>
<h2 id="ViewboxPanels">Viewbox Panels</h2>
<p>
Viewbox <a>Panel</a>s contain only a single element that is rescaled to fit the size of the Panel.
</p>
<p>
This is useful for taking an arbitrary element, especially a <a>Panel</a>, and automatically squeezing it to fit in a small fixed-size area.
The same can be achieved by setting the <a>GraphObject.scale</a> on that element, but with a Viewbox Panel that computation is performed automatically.
</p>
<p>
In this diagram there are two copies of the same Auto <a>Panel</a>,
each consisting of a <a>Picture</a> and a caption <a>TextBlock</a> surrounded by an Ellipse <a>Shape</a>.
The one on the left is inside a Viewbox <a>Panel</a> forced to fit in an 80x80 area;
the one on the right is its natural size.
Note that you can still see all of the elements of the panel at a reduced scale so that it can fit inside the Viewbox panel.
But because the nested panel is taller than it is wider, there is empty space on the sides of the 80x80 Viewbox.
</p>
<pre class="lang-js" id="viewboxPanel">
diagram.add(
$(go.Part, go.Panel.Viewbox, // or "Viewbox"
{ position: new go.Point(0, 0), background: "lightgray",
width: 80, height: 80 },
$(go.Panel, "Auto",
$(go.Shape, "Ellipse", { fill: "lightgreen" }),
$(go.Panel, "Vertical",
$(go.Picture, { source: "images/120x160.png" }),
$(go.TextBlock, "a 120x160 kitten")
)
)
));
diagram.add(
$(go.Part, "Auto",
{ position: new go.Point(100, 0), background: "lightgray" },
$(go.Shape, "Ellipse", { fill: "lightgreen" }),
$(go.Panel, "Vertical",
$(go.Picture, { source: "images/120x160.png" }),
$(go.TextBlock, "a 120x160 kitten")
)
));
</pre>
<script>goCode("viewboxPanel", 600, 270)</script>
</div>
</div>
</body>
</html>
+139
View File
@@ -0,0 +1,139 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Performance Considerations -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Performance Considerations</h1>
<p>
Getting good performance for your diagrams does not require any effort on your part when
the diagrams are limited to a few hundreds of nodes and links, especially on the desktop.
However when your app might deal with thousands or tens of thousands of nodes and links,
you may need to adapt your implementation to avoid expensive features.
</p>
<p>
The perceived performance of your diagram depends on many different factors.
</p>
<ul>
<li>JavaScript code is normally several to many times slower than Java or .NET code
on the same hardware platform.</li>
<li>JavaScript code performance varies between different browsers and versions of browsers.</li>
<li>Memory limitations, particularly on mobile devices, affect performance.</li>
<li>There can be a wide variation of drawing performance on different platforms.</li>
<li>Drawing and animation effects take resources.</li>
<li>Complicated nodes or links are slower to build and update and draw than simple ones.</li>
<li>Some layouts are inherently slower than others.</li>
</ul>
<h2 id="EffectsAndAppearances">Effects and Appearances</h2>
<p>
Shadows are relatively expensive to draw, so consider not setting <a>Part.isShadowed</a> to true.
Gradient <a>Brush</a>es are slower to draw than solid colors.
Complex <a>Shape</a> <a>Geometry</a>s are slower to draw than simpler ones, and they require more
computation when computing intersections.
</p>
<p>
Animation takes up resources; consider setting <a>AnimationManager.isEnabled</a> to false.
</p>
<h2 id="ConstructingAndSizingNodes">Constructing and Sizing Nodes</h2>
<p>
Keep your Nodes and Links as simple as you can make it.
Limit how many GraphObjects that you use in your templates.
Use simpler Panel types when feasible -- the "Table" Panel is the most featureful,
but maybe you can just use a "Horizontal" or a "Vertical" or a "Spot" or an "Auto" Panel.
A Panel should have two or more elements in them (although there can be exceptions).
If you have no elements in a Panel, delete the panel.
If you have only one element in a Panel, consider removing the panel and merging the element
into the panel's containing panel.
</p>
<p>
Do not include objects that not visible.
Limit how much data binding that you use, and avoid <a>Binding</a>s with no source property name
or that are <a>Binding.ofObject</a>.
</p>
<p>
If you have a <a>Picture</a> and you know its intended size beforehand,
it's best to set its <a>GraphObject.desiredSize</a>
(or <a>GraphObject.width</a> and <a>GraphObject.height</a>)
so that it does not have to re-measured once the image loads.
When nodes change size a <a>Layout</a> might need to be performed again,
so having fixed size nodes helps reduce diagram layouts.
In general, setting <a>GraphObject.desiredSize</a> on the elements of your nodes,
especially <a>Picture</a>s, will speed up how quickly <b>GoJS</b> can measure and arrange
the <a>Panel</a>s that form your Nodes or Links.
</p>
<h2 id="Links">Links</h2>
<p>
The <a>Link.routing</a> property value <a>Link,AvoidsNodes</a> can be slow in very large graphs.
Consider not using it in performance-minded large graphs,
or setting it only after the intial layout is completed (use "InitialLayoutCompleted" <a href="events.html">Diagram event listener</a>),
or ideally setting it at that time only on select Links.
</p>
<p>
Using a <a>Link.curve</a> value of either <a>Link,JumpOver</a> or <a>Link,JumpGap</a> is a lot slower than not
having to compute all the points where such links cross and drawing the small arc or drawing a gap.
</p>
<h2 id="Layouts">Layouts</h2>
<p>
<a>GridLayout</a> and <a>TreeLayout</a> are fast. <a>LayeredDigraphLayout</a> is slow.
</p>
<h2 id="Virtualization">Virtualization</h2>
<p>
For diagrams with many nodes and links that only display a fraction of them at a time,
you could implement some form of virtualization to optimize your diagram.
The <a href="../samples/virtualizedTree.html">Virtualized Tree sample</a> contains 123,456
total nodes, yet is fairly quick to load and render, because it only constructs nodes
and links that intersect with the viewport.
</p>
<p>
But this does complicate the implementation of the diagram, because you need to use a
separate model from the <a>Diagram.model</a> and manage adding and removing Nodes and
Links when the viewport changes.
Furthermore layout is more complicated because it needs to work on <a>LayoutVertex</a>es
and <a>LayoutEdge</a>s, not on <a>Node</a>s and <a>Link</a>s.
</p>
<p>
Other virtualization samples are listed in the <a href="../samples/index.html#performance">samples index</a>.
</p>
<h2 id="OtherConsiderations">Other considerations</h2>
<p>
If you want to disassociate the Diagram from the HTML Div element, set <a>Diagram.div</a> to null.
If you remove a part of the HTML DOM containing a Div with a Diagram, you will need to
set <a>Diagram.div</a> to null in order for the page to garbage collect the memory.
</p>
<p>
Depending on your app, it may be worthwhile to selectively toggle off some features
(like shadows and animation) or to use simpler templates altogether,
when slower environments are present, such as on mobile devices.
</p>
<p>
You can use multiple templates depending on your zoom level.
If you are zoomed out far enough (and therefore have a lot of nodes on the screen)
you can switch to a simplified template so that rendering (when panning, dragging, etc) is faster.
The process of switching templates has a performance cost, though,
since Parts have to rebuild themselves.
</p>
<p>
If you think you have a unique or high node count Diagramming situation that may benefit from other drawing optimizations, <a href="https://www.nwoods.com/contact.html">contact support</a>.
</p>
</div>
</div>
</body>
</html>
+399
View File
@@ -0,0 +1,399 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS User Permissions -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>User Permissions</h1>
<p>
Programmatically there are no restrictions on what you can do.
However you may want to restrict the actions that your users may perform.
</p>
<h3 id="DiagramIsEnabled">Diagram.isEnabled</h3>
<p>
The simplest restriction is to set <a>Diagram.isEnabled</a> to false.
Users will not be able to do much of anything.
In this example, even though the grouping, undo, and redo commands are enabled,
the commands cannot execute because the diagram is disabled.
</p>
<pre class="lang-js" id="isEnabled">
diagram.commandHandler.archetypeGroupData =
{ key: "Group", isGroup: true, color: "blue" };
var nodeDataArray = [
{ key: "Alpha" },
{ key: "Beta" },
{ key: "Delta", group: "Epsilon" },
{ key: "Gamma", group: "Epsilon" },
{ key: "Epsilon", isGroup: true }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" },
{ from: "Beta", to: "Beta" },
{ from: "Gamma", to: "Delta" },
{ from: "Delta", to: "Alpha" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
diagram.undoManager.isEnabled = true;
diagram.add($(go.Part, // this is just a visual comment
{ location: new go.Point(200, 50) },
$(go.TextBlock, "Diagram.isEnabled == false",
{ font: "16pt bold", stroke: "red" })
));
diagram.isEnabled = false; // Disable the diagram!
</pre>
<script>goCode("isEnabled", 600, 150)</script>
<h3 id="DiagramIsReadOnly">Diagram.isReadOnly</h3>
<p>
More common is to set <a>Diagram.isReadOnly</a> to true.
This allows users to scroll and zoom and to select parts, but not to insert or delete or drag or modify parts.
(If you want to allow scroll and zoom but not selection, you can disable selection, as discussed below.)
</p>
<pre class="lang-js" id="isReadOnly">
diagram.commandHandler.archetypeGroupData =
{ key: "Group", isGroup: true, color: "blue" };
var nodeDataArray = [
{ key: "Alpha" },
{ key: "Beta" },
{ key: "Delta", group: "Epsilon" },
{ key: "Gamma", group: "Epsilon" },
{ key: "Epsilon", isGroup: true }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" },
{ from: "Beta", to: "Beta" },
{ from: "Gamma", to: "Delta" },
{ from: "Delta", to: "Alpha" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
diagram.undoManager.isEnabled = true;
diagram.add($(go.Part, // this is just a visual comment
{ location: new go.Point(200, 50) },
$(go.TextBlock, "Diagram.isReadOnly == true",
{ font: "16pt bold", stroke: "red" })
));
// Disable diagram modifications, but allow navigation and selection
diagram.isReadOnly = true;
</pre>
<script>goCode("isReadOnly", 600, 150)</script>
<h3 id="DiagramIsModelReadOnly">Diagram.isModelReadOnly</h3>
<p>
Another possibility is to set <a>Model.isReadOnly</a> to true.
This allows users to scroll, zoom, select, and move parts, but not to insert or delete parts,
including not adding or removing links nor adding or removing group members.
</p>
<p>
The <a>Diagram.isModelReadOnly</a> property just gets and sets the <a>Model.isReadOnly</a> property.
If you are loading new Models, you will need to set this Diagram property after setting <a>Diagram.model</a>.
</p>
<pre class="lang-js" id="isModelReadOnly">
diagram.commandHandler.archetypeGroupData =
{ key: "Group", isGroup: true, color: "blue" };
var nodeDataArray = [
{ key: "Alpha" },
{ key: "Beta" },
{ key: "Delta", group: "Epsilon" },
{ key: "Gamma", group: "Epsilon" },
{ key: "Epsilon", isGroup: true }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" },
{ from: "Beta", to: "Beta" },
{ from: "Gamma", to: "Delta" },
{ from: "Delta", to: "Alpha" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
diagram.undoManager.isEnabled = true;
diagram.add($(go.Part, // this is just a visual comment
{ location: new go.Point(200, 50) },
$(go.TextBlock, "Diagram.model.isReadOnly == true",
{ font: "16pt bold", stroke: "red" })
));
diagram.model.isReadOnly = true; // Disable adding or removing parts
</pre>
<script>goCode("isModelReadOnly", 600, 150)</script>
<h3 id="AllowingCollapseExpand">Allowing Collapse and Expand</h3>
<p>
One common situation with diagrams that are <a>Diagram.isReadOnly</a> is that the user can not
collapse or expand trees or subgraphs. Instead of setting <a>Diagram.isReadOnly</a>, you can set
these properties:
</p>
<pre class="lang-js" id="collapseExpand">
$(go.Palette, "myPaletteDiv",
{
isReadOnly: false,
isModelReadOnly: true,
allowDelete: false,
allowInsert: false,
allowLink: false,
allowMove: false,
allowTextEdit: false,
// maybe other Diagram.allow... properties too, depending on your templates
// also consider disabling Tools
"contextMenuTool.isEnabled": false,
nodeTemplateMap: . . .
})
</pre>
<h2 id="SpecificPermissions">Specific permissions</h2>
<p>
More precise restrictions on the user can be imposed by setting properties of the <a>Diagram</a> or of a particular <a>Layer</a>
or of a particular <a>Part</a> or <a>GraphObject</a>.
</p>
<p>
Some restrictions, such as <a>Diagram.allowZoom</a>, only make sense when applying to the whole diagram.
Others may also apply to individual parts, such as <a>Part.copyable</a> and <a>Layer.allowCopy</a> corresponding to <a>Diagram.allowCopy</a>.
Finally some may apply to any <a>GraphObject</a>, for example properties for ports such as <a>GraphObject.toLinkable</a>,
or to text objects such as <a>TextBlock.editable</a>.
</p>
<p>
Any <a>Tool</a> can be disabled by setting <a>Tool.isEnabled</a> to false.
By default all Tools are enabled, but many cannot run because the conditions are not right for <a>Tool.canStart</a> to return true.
</p>
<p>
Here is a listing of what users can do and the properties that limit that functionality.
Most of these properties have a default value of true.
</p>
<h3 id="CutCommand">Cut command (<a>CommandHandler</a>)</h3>
<ul>
<li><a>Diagram.allowCopy</a>, <a>Diagram.allowDelete</a>, <a>Diagram.allowClipboard</a></li>
<li><a>Diagram.isReadOnly</a> and <a>Model.isReadOnly</a> (default values are false)</li>
</ul>
<h3 id="CopyCommand">Copy command (<a>CommandHandler</a>)</h3>
<ul>
<li><a>Diagram.allowCopy</a>, <a>Diagram.allowClipboard</a></li>
<li><a>Layer.allowCopy</a></li>
<li><a>Part.copyable</a></li>
</ul>
<h3 id="PasteCommand">Paste command (<a>CommandHandler</a>)</h3>
<ul>
<li><a>Diagram.allowInsert</a>, <a>Diagram.allowClipboard</a></li>
<li><a>Diagram.isReadOnly</a> and <a>Model.isReadOnly</a> (default values are false)</li>
<li>The clipboard's data format must be the same as the <a>Model.dataFormat</a></li>
</ul>
<h3 id="DeleteCommand">Delete command (<a>CommandHandler</a>)</h3>
<ul>
<li><a>Diagram.allowDelete</a></li>
<li><a>Layer.allowDelete</a></li>
<li><a>Part.deletable</a></li>
<li><a>Diagram.isReadOnly</a> and <a>Model.isReadOnly</a> (default values are false)</li>
</ul>
<h3 id="DragAndDropWithinDiagram">Drag-and-drop within diagram (<a>DraggingTool</a>)</h3>
<ul>
<li><a>Diagram.allowMove</a>, <a>Diagram.allowCopy</a>, <a>Diagram.allowInsert</a></li>
<li><a>Layer.allowMove</a>, <a>Layer.allowCopy</a></li>
<li><a>Part.movable</a>, <a>Part.copyable</a></li>
<li><a>DraggingTool.isCopyEnabled</a></li>
<li><a>Diagram.isReadOnly</a> for moving (default value is false)</li>
<li><a>Diagram.isReadOnly</a> and <a>Model.isReadOnly</a> for copying (default values are false)</li>
<li>Many properties affect dragging, including: <a>Part.maxLocation</a>, <a>Part.minLocation</a>, <a>Part.dragComputation</a>, and <a>DraggingTool.isGridSnapEnabled</a>.
Read about limiting user drags to be only horizontal or only vertical or only within the containing <a>Group</a> in the documentation for <a>DraggingTool</a>.</li>
<li><a>DraggingTool.isEnabled</a></li>
</ul>
<h3 id="DragAndDropOutOfDiagram">Drag-and-drop out of diagram (<a>DraggingTool</a>)</h3>
<ul>
<li><a>Diagram.allowDragOut</a> (default value is false except for <a>Palette</a> where it is true)</li>
<li><a>DraggingTool.isEnabled</a></li>
</ul>
<h3 id="DragAndDropIntoDiagram">Drag-and-drop into diagram (<a>DraggingTool</a>)</h3>
<ul>
<li><a>Diagram.allowDrop</a> (default value is true)</li>
<li><a>Diagram.allowInsert</a></li>
<li><a>Diagram.isReadOnly</a> and <a>Model.isReadOnly</a> (default values are false)</li>
<li><a>DraggingTool.isEnabled</a></li>
</ul>
<h3 id="InPlaceTextEditing">In-place text editing (<a>TextEditingTool</a>)</h3>
<ul>
<li><a>Diagram.allowTextEdit</a></li>
<li><a>Layer.allowTextEdit</a></li>
<li><a>Part.textEditable</a></li>
<li>
<a>TextBlock.editable</a>, <a>TextBlock.textValidation</a>, and <a>TextEditingTool.textValidation</a>
affect text editing (these are discussed in the section about <a href="validation.html">Validation</a>)
</li>
<li><a>Diagram.isReadOnly</a> (default value is false)</li>
<li><a>TextEditingTool.isEnabled</a></li>
<li><a>TextEditingTool.starting</a> controls how the editing may be initiated.</li>
</ul>
<h3 id="GroupCommand">Group command (<a>CommandHandler</a>)</h3>
<ul>
<li><a>Diagram.allowGroup</a>, <a>Diagram.allowInsert</a></li>
<li><a>Layer.allowGroup</a></li>
<li><a>Part.groupable</a></li>
<li>The <a>CommandHandler.groupSelection</a> command requires that <a>CommandHandler.archetypeGroupData</a>
has been set to a data object to be copied into the model to be represented by a new group in the diagram;
this property is null by default, causing the command to be disabled.
You will need to set the property to an object so that newly created groups have the desired
property values for any data binding by the group template.</li>
<li><a>Group.memberValidation</a> and <a>CommandHandler.memberValidation</a> also control which Parts may become members of a Group</li>
<li><a>Diagram.isReadOnly</a> and <a>Model.isReadOnly</a> (default values are false)</li>
</ul>
<h3 id="UngroupCommand">Ungroup command (<a>CommandHandler</a>)</h3>
<ul>
<li><a>Diagram.allowUngroup</a>, <a>Diagram.allowDelete</a></li>
<li><a>Layer.allowUngroup</a></li>
<li><a>Group.ungroupable</a> (default value is false)</li>
<li><a>Diagram.isReadOnly</a> and <a>Model.isReadOnly</a> (default values are false)</li>
</ul>
<h3 id="ClickCreating">Click-creating (<a>ClickCreatingTool</a>)</h3>
<ul>
<li><a>Diagram.allowInsert</a></li>
<li>
The <a>ClickCreatingTool</a> requires that <a>ClickCreatingTool.archetypeNodeData</a>
has been set to a data object to be copied into the model to be represented by a new part in the diagram;
this property is null by default, causing the tool to be disabled.
You will need to set the property to an object so that newly created nodes have the desired
property values for any data binding by the node template.
</li>
<li><a>Diagram.isReadOnly</a> and <a>Model.isReadOnly</a> (default values are false)</li>
<li><a>ClickCreatingTool.isEnabled</a></li>
<li><a>ClickCreatingTool.isDoubleClick</a> whether to insert on single click or double click.</li>
</ul>
<h3 id="DrawingNewLink">Drawing a new link (<a>LinkingTool</a>)</h3>
<ul>
<li><a>Diagram.allowLink</a></li>
<li><a>Layer.allowLink</a></li>
<li>
<a>GraphObject.fromLinkable</a>, <a>GraphObject.fromLinkableDuplicates</a>,
<a>GraphObject.fromLinkableSelfNode</a>, <a>GraphObject.fromMaxLinks</a>,
<a>GraphObject.toLinkable</a>, <a>GraphObject.toLinkableDuplicates</a>,
<a>GraphObject.toLinkableSelfNode</a>, <a>GraphObject.toMaxLinks</a>
(these are discussed in the section about <a href="validation.html">Validation</a>)
</li>
<li>The <a>LinkingTool</a> requires that <a>LinkingTool.archetypeLinkData</a>
has been set to a data object to be copied into the model to be represented by a new link in the diagram;
this property is by default set to an empty JavaScript object.
You may need to want to set properties on this object so that newly created links have the desired
property values for any data binding by the link template.</li>
<li><a>Diagram.isReadOnly</a> and <a>Model.isReadOnly</a> (default values are false)</li>
<li><a>LinkingTool.isEnabled</a></li>
</ul>
<h3 id="RelinkingExistingLink">Relinking an existing link (<a>RelinkingTool</a>)</h3>
<ul>
<li><a>Diagram.allowRelink</a></li>
<li><a>Layer.allowRelink</a></li>
<li><a>Link.relinkableFrom</a>, <a>Link.relinkableTo</a> (default values are false)</li>
<li>
<a>GraphObject.fromLinkable</a>, <a>GraphObject.fromLinkableDuplicates</a>,
<a>GraphObject.fromLinkableSelfNode</a>, <a>GraphObject.fromMaxLinks</a>,
<a>GraphObject.toLinkable</a>, <a>GraphObject.toLinkableDuplicates</a>,
<a>GraphObject.toLinkableSelfNode</a>, <a>GraphObject.toMaxLinks</a>
(these are discussed in the section about <a href="validation.html">Validation</a>)
</li>
<li><a>Diagram.isReadOnly</a> and <a>Model.isReadOnly</a> (default values are false)</li>
<li><a>RelinkingTool.isEnabled</a></li>
</ul>
<h3 id="ReshapingLink">Reshaping a link (<a>LinkReshapingTool</a>)</h3>
<ul>
<li><a>Diagram.allowReshape</a></li>
<li><a>Layer.allowReshape</a></li>
<li><a>Part.reshapable</a> (default value is false)</li>
<li><a>Link.resegmentable</a> also affects whether segments can be added or removed (default value is false)</li>
<li><a>Diagram.isReadOnly</a> (default value is false)</li>
<li><a>LinkReshapingTool.isEnabled</a></li>
</ul>
<h3 id="ResizingObject">Resizing an object (<a>ResizingTool</a>)</h3>
<ul>
<li><a>Diagram.allowResize</a></li>
<li><a>Layer.allowResize</a></li>
<li><a>Part.resizable</a> (default value is false)</li>
<li><a>Part.resizeCellSize</a>, <a>GraphObject.maxSize</a>, and <a>GraphObject.minSize</a> limit the size to which the user may resize the <a>Part.resizeObject</a></li>
<li><a>ResizingTool.maxSize</a>, <a>RotatingTool.minSize</a>, and <a>RotatingTool.cellSize</a> limit the size to which the user may resize the <a>Part.resizeObject</a></li>
<li><a>Diagram.isReadOnly</a> (default value is false)</li>
<li><a>ResizingTool.isEnabled</a></li>
</ul>
<h3 id="RotatingObject">Rotating an object (<a>RotatingTool</a>)</h3>
<ul>
<li><a>Diagram.allowRotate</a></li>
<li><a>Layer.allowRotate</a></li>
<li><a>Part.rotatable</a> (default value is false)</li>
<li><a>RotatingTool.snapAngleMultiple</a> and <a>RotatingTool.snapAngleEpsilon</a> limit the angles to which the user may rotate the <a>Part.rotateObject</a></li>
<li><a>Diagram.isReadOnly</a> (default value is false)</li>
<li><a>RotatingTool.isEnabled</a></li>
</ul>
<h3 id="ArrowAndPageCommands">Arrow and Page commands (<a>CommandHandler</a>), panning/scrolling the diagram (<a>PanningTool</a> and scrollbars)</h3>
<ul>
<li><a>Diagram.allowHorizontalScroll</a>, <a>Diagram.allowVerticalScroll</a></li>
<li><a>Diagram.hasHorizontalScrollbar</a>, <a>Diagram.hasVerticalScrollbar</a></li>
<li><a>Diagram.scrollMargin</a> and <a>Diagram.padding</a></li>
<li><a>Diagram.scrollMode</a> and <a>Diagram.positionComputation</a> for controlling how far the user may scroll</li>
<li><a>ToolManager.mouseWheelBehavior</a> controls whether mouse wheel events scroll or zoom</li>
<li>
See the DrawCommandHandler in the <a href="../extensions">Extensions</a> directory for
a <a>CommandHandler</a> that customizes the behavior of the arrow keys
</li>
<li><a>PanningTool.isEnabled</a></li>
<li><a>PanningTool.bubbles</a> controls whether panning gestures scroll the page rather than the viewport.</li>
</ul>
<h3 id="SelectAllCommand">SelectAll command (<a>CommandHandler</a>), click selecting (<a>ClickSelectingTool</a>), drag selecting (<a>DragSelectingTool</a>)</h3>
<ul>
<li><a>Diagram.allowSelect</a></li>
<li><a>Layer.allowSelect</a></li>
<li><a>Part.selectable</a></li>
<li><a>Diagram.maxSelectionCount</a> limits how many selectable <a>Part</a>s the user may select</li>
<li><a>DragSelectingTool.isEnabled</a></li>
</ul>
<h3 id="UndoRedoCommands">Undo/Redo commands (<a>CommandHandler</a>)</h3>
<ul>
<li><a>Diagram.allowUndo</a></li>
<li><a>UndoManager.isEnabled</a> (default value is false)</li>
<li><a>Diagram.isReadOnly</a> and <a>Model.isReadOnly</a> (default values are false)</li>
</ul>
<h3 id="ZoomCommands">Zoom commands (<a>CommandHandler</a>), zooming/rescaling the diagram (<a>ToolManager</a>)</h3>
<ul>
<li><a>Diagram.allowZoom</a></li>
<li><a>ToolManager.mouseWheelBehavior</a> controls whether mouse wheel events scroll or zoom</li>
<li><a>Diagram.minScale</a>, <a>Diagram.maxScale</a>, and <a>Diagram.scaleComputation</a> for controlling how far the user may zoom.</li>
</ul>
<h3 id="ContextMenus">Context Menus (<a>ContextMenuTool</a>)</h3>
<ul>
<li><a>GraphObject.contextMenu</a></li>
<li><a>Diagram.contextMenu</a></li>
<li><a>ContextMenuTool.isEnabled</a></li>
</ul>
</div>
</div>
</body>
</html>
+336
View File
@@ -0,0 +1,336 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Pictures -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Pictures</h1>
<p>
Use the <a>Picture</a> class to display images.
The most common usage is to set the <a>Picture.source</a> property with a URL string,
along with the <a>GraphObject.desiredSize</a> or the <a>GraphObject.width</a> and <a>GraphObject.height</a>.
</p>
<p>
If the URL is just a simple constant string, you can pass the string directly as an argument to <a>GraphObject,make</a>,
rather than assign the "source:" property. Both techniques have the same effect.
</p>
<p>
In these simplistic demonstrations, the code programmatically creates a Part and adds it to the Diagram.
Once you learn about models and data binding you will generally not create parts (nodes or links) programmatically.
</p>
<pre class="lang-js" id="source">
diagram.add(
$(go.Part,
$(go.Picture, "images/100x65.png")
));
</pre>
<script>goCode("source", 600, 160)</script>
<p>
However for more sophisticated control you can set the <a>Picture.element</a>
to an <b>HTMLImageElement</b> or an <b>HTMLCanvasElement</b>.
</p>
<h3 id="SimpleIconsUsingFonts">Simple Icons Using Fonts</h3>
<p>
Note: for showing simple icons you may want to use an icon font.
See the example using a <a>TextBlock</a> rather than a <a>Picture</a> at:
<a href="textBlocks.html#IconFonts">Icon Fonts</a>
</p>
<h2 id="Sizing">Sizing</h2>
<p>
If you do not set the <a>GraphObject.desiredSize</a> of a <a>Picture</a>, it will get the picture's natural size.
But when you set the desiredSize to be something different than the natural size, the picture may be stretched or compressed to fit.
</p>
<p>
The following pictures all show a picture of kittens that is 100x65 pixels.
</p>
<ul>
<li>The first picture shows the image at its natural size.</li>
<li>The second picture also shows the image at its natural size, but has its desiredSize set explicitly.</li>
<li>The third picture increases the size of the Picture, causing the image to be expanded evenly.</li>
<li>The fourth picture squeezes the 100x65 image into a 50x32.5 space -- half size.
This also maintains the original aspect ratio of the image.</li>
<li>The last picture sets the picture size to be 50x70, which changes the aspect ratio to be taller and thinner than the original.</li>
</ul>
<pre class="lang-js" id="sizedPictures">
diagram.add(
$(go.Part, "Table",
$(go.Picture, { source: "images/100x65.png", column: 0,
margin: 2 }),
$(go.TextBlock, "natural", { row: 1, column: 0 }),
$(go.Picture, { source: "images/100x65.png", column: 1,
width: 100, height: 65, margin: 2 }),
$(go.TextBlock, "same size", { row: 1, column: 1 }),
$(go.Picture, { source: "images/100x65.png", column: 2,
width: 200, height: 130, margin: 2 }),
$(go.TextBlock, "bigger", { row: 1, column: 2 }),
$(go.Picture, { source: "images/100x65.png", column: 3,
width: 50, height: 32.5, margin: 2 }),
$(go.TextBlock, "smaller", { row: 1, column: 3 }),
$(go.Picture, { source: "images/100x65.png", column: 4,
width: 50, height: 70, margin: 2 }),
$(go.TextBlock, "stretched", { row: 1, column: 4 })
));
</pre>
<script>goCode("sizedPictures", 600, 160)</script>
<p>
Note that it may take a while for the media to load.
Until the time that the media has loaded sufficiently to know its natural size, the Picture may have the wrong size, such as 0x0.
We recommend that you specify the desiredSize (or width and height) so that
the Panel(s) holding the Picture will not have to rearrange themselves once the media has loaded.
</p>
<p>
However for the times when you cannot know the natural size ahead of time, there are alternative ways of stretching images to fit in a given space.
</p>
<h2 id="ImageStretch">Image Stretch</h2>
<p>
Instead of always stretching or compressing to fill the desiredSize,
you can set the <a>Picture.imageStretch</a> property to control the size and aspect ratio of the drawn image.
</p>
<p>
The following pictures demonstrate the four possible values for Picture.imageStretch.
All four Pictures here have the size 60x80 and show the same 100x65 PNG file.
The Pictures also have a light green background, to show the space available that may be left unused, but is still part of the Picture's bounds.
</p>
<ul>
<li>The first picture demonstrates the default behavior, to stretch in both directions.
Note how the image is distorted to be narrower than it should be.
However, all of the image is shown.
Because the image fills the whole area and the image is not translucent, the background color does not show anywhere.
</li>
<li>You can see in the second picture, using an imageStretch of <a>GraphObject,None</a>,
how it only shows a fraction of the whole kitten image.
Because the desiredSize is smaller than the natural size of the image,
parts of the image are clipped.
</li>
<li>The third picture shows how a <a>GraphObject,Uniform</a> imageStretch will make sure that all of the image is shown,
at the expense of reducing the scale and leaving some empty space at the sides or at the top and bottom.
In this case, because the natural image aspect ratio is wider than the available 60x80 aspect ratio,
the empty space will be at the top and bottom.
</li>
<li>The fourth picture shows how a <a>GraphObject,UniformToFill</a> imageStretch will ensure that the whole area is occupied
with image, but that not all of the image is shown, since some may be clipped at the sides or at the top and bottom.
Such images normally have a larger scale than when using Uniform imageStretch.
In this case what must be clipped is at the sides of the image.
</li>
<li>Finally there is a separate Part containing the original image, sized naturally, for comparison.</li>
</ul>
<pre class="lang-js" id="stretchedPictures">
diagram.add(
$(go.Part, "Table",
$(go.Picture, "images/100x65.png",
{ column: 0, width: 60, height: 80, margin: 2, background: "chartreuse",
imageStretch: go.GraphObject.Fill }),
$(go.TextBlock, "Fill", { row: 1, column: 0 }),
$(go.Picture, "images/100x65.png",
{ column: 1, width: 60, height: 80, margin: 2, background: "chartreuse",
imageStretch: go.GraphObject.None }),
$(go.TextBlock, "None", { row: 1, column: 1 }),
$(go.Picture, "images/100x65.png",
{ column: 2, width: 60, height: 80, margin: 2, background: "chartreuse",
imageStretch: go.GraphObject.Uniform }),
$(go.TextBlock, "Uniform", { row: 1, column: 2 }),
$(go.Picture, "images/100x65.png",
{ column: 3, width: 60, height: 80, margin: 2, background: "chartreuse",
imageStretch: go.GraphObject.UniformToFill }),
$(go.TextBlock, "UniformToFill", { row: 1, column: 3 })
));
// The original image sized naturally, for comparison
diagram.add(
$(go.Part, "Vertical",
$(go.Picture, "images/100x65.png"),
$(go.TextBlock, "Original image,\nsized naturally")
));
</pre>
<script>goCode("stretchedPictures", 600, 120)</script>
<p>
When images are clipped you can control what part of the image is drawn by using the <a>Picture.imageAlignment</a> property.
</p>
<h2 id="Clipping">Clipping</h2>
<p>
If you have a Picture that must be clipped to a geometry, such as to produce a circular image, there are two options.
The first is to use a "frame" geometry to hide part of the image.
Typically this frame is the same color as the Diagram background or the background of the Node.
This method does not change the area of the Picture, does not allow for true transparency, and clicking anywhere in the bounds will always pick the picture.
</p>
<p>
A second method uses <a>Panel.isClipping</a>.
This property on a "Spot" Panel allows the filled area of the main Shape to serve as a clipping region instead of a drawn shape.
This method does not change the area of the Picture, but does allow for transparency
It affects object picking so that only the resultant drawn area is pickable; areas of the image that are not drawn cannot be "hit".
</p>
<p>
Examples of both follow:
</p>
<pre class="lang-js" id="clipPictures">
diagram.layout = $(go.GridLayout);
// Using a black "frame" geometry to hide part of the image.
// Typically this frame is the same color as the Diagram background or the background of the Node.
diagram.add(
$(go.Part, "Spot",
{ scale: 2 },
$(go.Picture, "../samples/images/55x55.png",
{
name: 'Picture',
desiredSize: new go.Size(55, 55),
background: 'red'
}
),
$(go.Shape,
{
strokeWidth: 0,
stroke: null,
geometryString: "f M0 0 L100 0 L100 100 L0 100 z M5,50a45,45 0 1,0 90,0a45,45 0 1,0 -90,0 z",
width: 56,
height: 56,
fill: 'black'
})
)
);
// Using Panel.isClipping
diagram.add(
$(go.Part, "Spot",
{ isClipping: true, scale: 2 },
$(go.Shape, "Circle", { width: 55, strokeWidth: 0 } ),
$(go.Picture, "../samples/images/55x55.png",
{ width: 55, height: 55 }
)
)
);
// Using Panel.isClipping and also having a surrounding panel
diagram.add(
$(go.Part, "Spot",
{ scale: 2 },
$(go.Shape, "Circle", { width: 65, strokeWidth: 0, fill: 'red' } ),
$(go.Panel, "Spot",
{ isClipping: true },
$(go.Shape, "Circle", { width: 55, strokeWidth: 0 } ),
$(go.Picture, "../samples/images/55x55.png",
{ width: 55, height: 55 }
)
)
)
);
</pre>
<script>goCode("clipPictures", 500, 200)</script>
<h2 id="Flipping">Flipping</h2>
<p>
You can flip image sources horizontally and vertically with the <a>Picture.flip</a> property:
</p>
<pre class="lang-js" id="flipPictures">
diagram.add(
$(go.Part, "Table",
$(go.Picture, { source: "images/100x65.png", column: 0, margin: 2,
flip: go.GraphObject.None
}),
$(go.TextBlock, "None (default)", { row: 1, column: 0 }),
$(go.Picture, { source: "images/100x65.png", column: 1, margin: 2,
flip: go.GraphObject.FlipHorizontal
}),
$(go.TextBlock, "FlipHorizontal", { row: 1, column: 1 }),
$(go.Picture, { source: "images/100x65.png", column: 2, margin: 2,
flip: go.GraphObject.FlipVertical
}),
$(go.TextBlock, "FlipVertical", { row: 1, column: 2 }),
$(go.Picture, { source: "images/100x65.png", column: 3, margin: 2,
flip: go.GraphObject.FlipBoth
}),
$(go.TextBlock, "FlipBoth", { row: 1, column: 3 })
));
</pre>
<script>goCode("flipPictures", 600, 160)</script>
<h2 id="CrossOriginPictures">Cross Origin Pictures</h2>
<p>
Since Pictures are backed by HTMLImageElements, they must abide by the same Cross-origin (CORS) rules that apply to Images.
If you are using images that apply to CORS rules, you may need to set the <a>Picture.sourceCrossOrigin</a> property to a function that returns an appropriate value.
If <code>sourceCrossOrigin</code> is supplied, the value returned by the function is used as the value of any constructed <code>image.crossOrigin</code>.
Example:
</p>
<pre class="lang-js">
$(go.Picture,
{ width: 64, height: 64 },
{ sourceCrossOrigin: function(pict) { return "use-credentials"; } },
new go.Binding("source", "path"))
</pre>
<p>
Common values to return are "use-credentials" and "anonymous", but other situations may call for other values or conditional values.
We suggest researching <a href="https://enable-cors.org/">cross-origin resource sharing</a> to determine what is right for your situation.
</p>
<p>
If you are using <a>Diagram.makeImage</a>, <a>Diagram.makeImageData</a>, or <a>Diagram.makeSvg</a>,
and you are seeing blank or missing images, CORS-related problems are the first thing to investigate.
</p>
<h2 id="UsingSVGAsPictureSource">Using SVG as a Picture source</h2>
<p>
Almost all browsers accept SVG files as a Picture source, but in many browsers you <strong>must</strong>:
</p>
<ul>
<li>Assign width and height attributes to the SVG element. These values should be integers. (necessary for Firefox)</li>
<li>Assign the Picture element a desired size, which <strong>must</strong> be the same as its width and height attributes (necessary for Internet Explorer).</li>
</ul>
<p>
This first SVG element has a width and height specified in its SVG element, and also has its desired size set. It should display in most browsers:
</p>
<pre class="lang-html">
&lt;svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="580" height="580"&gt;
...
</pre>
<pre class="lang-js" id="svg1">
diagram.add(
$(go.Part,
$(go.Picture, { desiredSize: new go.Size(580, 580), source: "images/tiger.svg" })
));
diagram.scale = 0.5;
</pre>
<script>goCode("svg1", 300, 300)</script>
<p style="color: red;"><strong>
This SVG element does not specify width and height attributes in its SVG element, and as a result some browsers may not render it:
</strong></p>
<pre class="lang-html">
&lt;svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"&gt;
...
</pre>
<pre class="lang-js" id="svg2">
diagram.add(
$(go.Part,
$(go.Picture, { source: "images/tiger-noWidthHeightSpecified.svg" })
));
diagram.scale = 0.5;
</pre>
<script>goCode("svg2", 300, 300)</script>
</div>
</div>
</body>
</html>
+289
View File
@@ -0,0 +1,289 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Ports in Nodes-- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Ports in Nodes</h1>
<p>
Although you have some control over where links will connect at a node (at a particular spot, along one or
more sides, or at the intersection with the edge), there are times when you want to have different logical
and graphical places at which links should connect. The elements at which a link may connect are called
<i>ports</i>. There may be any number of ports in a node. By default there is just one port, the whole node,
which results in the effect of having the whole node act as the port, as you have seen in all of the previous examples.
</p>
<p>
To declare that a particular element in a <a>Node</a> is a port, set the <a>GraphObject.portId</a> property to a string.
Note that the port-or-link-related properties may apply to any element in the visual tree of the node,
which is why they are properties of <a>GraphObject</a> rather than <a>Node</a>.
</p>
<p>
Port-like GraphObjects can only be in <a>Node</a>s or <a>Group</a>s, not in <a>Link</a>s or <a>Adornment</a>s or simple <a>Part</a>s.
So there is no reason to try to set <a>GraphObject.portId</a> on any object in a Link.
</p>
<p>
<p>
See samples that make use of ports in the <a href="../samples/index.html#ports">samples index</a>.
</p>
</p>
<h2 id="SinglePorts">Single Ports</h2>
<p>
In many situations you want to consider links logically related to the node as a whole but you don't want links
connecting to the whole node.
In this case each node has only one port, but you do not want the whole node to act as the one port.
</p>
<p>
For example, consider how links connect to the nodes when the whole node is acting as a port in one common manner.
The <a>GraphObject.fromSpot</a> and <a>GraphObject.toSpot</a> are at the middles of the sides.
Because the height of the whole node includes the text label,
the middle of the side is not the middle of the "icon", which in this case is a circle.
</p>
<pre class="lang-js" id="defaultPort">
diagram.nodeTemplate =
$(go.Node, "Vertical",
{ fromSpot: go.Spot.Right, toSpot: go.Spot.Left }, // port properties on the node
$(go.Shape, "Ellipse",
{ width: 30, height: 30, fill: "green" }),
$(go.TextBlock,
{ font: "20px sans-serif" },
new go.Binding("text", "key"))
);
var nodeDataArray = [
{ key: "Alpha" },
{ key: "Beta" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("defaultPort", 600, 100)</script>
<p>
This appearance does not look or behave quite right.
Really we want links to connect to the circular <a>Shape</a>.
</p>
<p>
If you want a particular element to act as the port rather than the whole node,
just set its <a>GraphObject.portId</a> to the empty string.
The empty string is the name of the default port.
</p>
<p>
In this example, we set <a>GraphObject.portId</a> on the circular shape.
Note that we move the other port-related properties, such as the port spots, to that object too.
</p>
<pre class="lang-js" id="singlePort">
diagram.nodeTemplate =
$(go.Node, "Vertical",
$(go.Shape, "Ellipse",
{ width: 30, height: 30, fill: "green",
portId: "", // now the Shape is the port, not the whole Node
fromSpot: go.Spot.Right, // port properties go on the port!
toSpot: go.Spot.Left
}),
$(go.TextBlock,
{ font: "20px sans-serif" },
new go.Binding("text", "key"))
);
var nodeDataArray = [
{ key: "Alpha" },
{ key: "Beta" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("singlePort", 600, 100)</script>
<p>
Notice how the links nicely connect the circular shapes by ignoring the text labels.
</p>
<h2 id="GeneralPorts">General Ports</h2>
<p>
It is also common to have diagrams where you want more than one port in a node.
The number of ports might even vary dynamically.
</p>
<p>
In order for a link data object to distinguish which port the link should connect to,
the <a>GraphLinksModel</a> supports two additional data properties that identify the names of the ports in the nodes
at both ends of the link.
<a>GraphLinksModel.getToKeyForLinkData</a> identifies the node to connect to;
<a>GraphLinksModel.getToPortIdForLinkData</a> identifies the port within the node.
Similarly, <a>GraphLinksModel.getFromKeyForLinkData</a> and <a>GraphLinksModel.getFromPortIdForLinkData</a> identify the node and its port.
</p>
<p>
Normally a <a>GraphLinksModel</a> assumes that there is no need to recognize port information on link data.
If you want to support port identifiers on link data, you need to set <a>GraphLinksModel.linkToPortIdProperty</a>
and <a>GraphLinksModel.linkFromPortIdProperty</a> to be the names of the link data properties.
If you do not set these properties, all port identifiers are assumed to be the empty string,
which is the name of the one default port for a node.
</p>
<p class="box bg-danger">
If you have set or bound <a>GraphObject.portId</a> on any element to be a non-empty string,
you will need to use a <a>GraphLinksModel</a> and set <a>GraphLinksModel.linkToPortIdProperty</a>
and <a>GraphLinksModel.linkFromPortIdProperty</a> to be the names of two properties on your link data,
or you will need to hard code the portId names in the link template(s)
(i.e. <a>Link.fromPortId</a> and <a>Link.toPortId</a>),
in order for the user to be able to link with those ports.
</p>
<pre class="lang-js" id="ports">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "Rectangle", { fill: "lightgray" }),
$(go.Panel, "Table",
$(go.RowColumnDefinition,
{ column: 0, alignment: go.Spot.Left}),
$(go.RowColumnDefinition,
{ column: 2, alignment: go.Spot.Right }),
$(go.TextBlock, // the node title
{ column: 0, row: 0, columnSpan: 3, alignment: go.Spot.Center,
font: "bold 10pt sans-serif", margin: new go.Margin(4, 2) },
new go.Binding("text", "key")),
$(go.Panel, "Horizontal",
{ column: 0, row: 1 },
$(go.Shape, // the "A" port
{ width: 6, height: 6, portId: "A", toSpot: go.Spot.Left }),
$(go.TextBlock, "A") // "A" port label
),
$(go.Panel, "Horizontal",
{ column: 0, row: 2 },
$(go.Shape, // the "B" port
{ width: 6, height: 6, portId: "B", toSpot: go.Spot.Left }),
$(go.TextBlock, "B") // "B" port label
),
$(go.Panel, "Horizontal",
{ column: 2, row: 1, rowSpan: 2 },
$(go.TextBlock, "Out"), // "Out" port label
$(go.Shape, // the "Out" port
{ width: 6, height: 6, portId: "Out", fromSpot: go.Spot.Right })
)
)
);
diagram.linkTemplate =
$(go.Link,
{ routing: go.Link.Orthogonal, corner: 3 },
$(go.Shape),
$(go.Shape, { toArrow: "Standard" })
);
diagram.layout = $(go.LayeredDigraphLayout, { columnSpacing: 10 });
diagram.model =
$(go.GraphLinksModel,
{ linkFromPortIdProperty: "fromPort", // required information:
linkToPortIdProperty: "toPort", // identifies data property names
nodeDataArray: [
{ key: "Add1" },
{ key: "Add2" },
{ key: "Subtract1" }
],
linkDataArray: [
{ from: "Add1", fromPort: "Out", to: "Subtract1", toPort: "A" },
{ from: "Add2", fromPort: "Out", to: "Subtract1", toPort: "B" }
] });
</pre>
<script>goCode("ports", 600, 150)</script>
<h2 id="DrawingNewLinks">Drawing new Links</h2>
<p>
Setting either or both of the <a>GraphObject.fromLinkable</a> and <a>GraphObject.toLinkable</a>
properties to true allows users to interactively draw new links between ports.
</p>
<p>
To draw a new link, mouse down on an "Out" port, move (drag) to nearby an input port,
and then mouse-up to complete the link.
</p>
<pre class="lang-js" id="linkablePorts">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "Rectangle", { fill: "lightgray" }),
$(go.Panel, "Table",
$(go.RowColumnDefinition,
{ column: 0, alignment: go.Spot.Left}),
$(go.RowColumnDefinition,
{ column: 2, alignment: go.Spot.Right }),
$(go.TextBlock, // the node title
{ column: 0, row: 0, columnSpan: 3, alignment: go.Spot.Center,
font: "bold 10pt sans-serif", margin: new go.Margin(4, 2) },
new go.Binding("text", "key")),
$(go.Panel, "Horizontal",
{ column: 0, row: 1 },
$(go.Shape, // the "A" port
{ width: 6, height: 6, portId: "A", toSpot: go.Spot.Left,
toLinkable: true, toMaxLinks: 1 }), // allow user-drawn links from here
$(go.TextBlock, "A") // "A" port label
),
$(go.Panel, "Horizontal",
{ column: 0, row: 2 },
$(go.Shape, // the "B" port
{ width: 6, height: 6, portId: "B", toSpot: go.Spot.Left,
toLinkable: true, toMaxLinks: 1 }), // allow user-drawn links from here
$(go.TextBlock, "B") // "B" port label
),
$(go.Panel, "Horizontal",
{ column: 2, row: 1, rowSpan: 2 },
$(go.TextBlock, "Out"), // "Out" port label
$(go.Shape, // the "Out" port
{ width: 6, height: 6, portId: "Out", fromSpot: go.Spot.Right,
fromLinkable: true }) // allow user-drawn links to here
)
)
);
diagram.linkTemplate =
$(go.Link,
{ routing: go.Link.Orthogonal, corner: 3 },
$(go.Shape),
$(go.Shape, { toArrow: "Standard" })
);
diagram.layout = $(go.LayeredDigraphLayout, { columnSpacing: 10 });
diagram.toolManager.linkingTool.temporaryLink.routing = go.Link.Orthogonal;
diagram.model =
$(go.GraphLinksModel,
{ linkFromPortIdProperty: "fromPort", // required information:
linkToPortIdProperty: "toPort", // identifies data property names
nodeDataArray: [
{ key: "Add1" },
{ key: "Add2" },
{ key: "Subtract1" }
],
linkDataArray: [
// no predeclared links
] });
</pre>
<script>goCode("linkablePorts", 600, 250)</script>
<p>
By default the user may not draw more than one link in the same direction between any pair of ports,
nor may the user draw a link connecting a node with itself.
Please read a general discussion of <a href="validation.html">Linking Validation</a>.
</p>
<p>
By setting <a>GraphObject.toMaxLinks</a> to 1, as shown in this example, the user may draw at most one link going into that port.
And because <a>GraphObject.fromLinkable</a> is false for that port element, the user will not be able to connect any links coming out of that port.
</p>
<p>
If you want to prevent the user from connecting any more than one Link with a Node, regardless of direction,
you will need to implement a <a>LinkingBaseTool.linkValidation</a> or a <a>Node.linkValidation</a> predicate.
See the discussion about <a href="validation.html#GeneralLinkingValidation">General Linking Validation</a>
</p>
</div>
</div>
</body>
</html>
+320
View File
@@ -0,0 +1,320 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Printing -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
<style type="text/css">
@media screen {
img {
border: 1px solid rgba(255,0,0,.4);
}
}
/* @media print specifies CSS rules that only apply when printing */
@media print {
/* CSS reset to clear styles for printing */
html, body, div {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* Hide everything on the page */
body * {
display: none;
}
#container, #content, #myImages, #myImages * {
/* Only display the images we want printed */
/* all of the image's parent divs
leading up to the body must be un-hidden (displayed) too
*/
display: block;
/* CSS reset to clear the specific visible divs for printing */
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* We have line breaks in the DIV
to separate the images in the browser,
but do not want those line breaks when printing
*/
#myImages br {
display: none;
}
#manyImgCode {
display: none;
}
#mycss {
display: none;
}
img {
/* Only some browsers respect this rule: */
page-break-inside: avoid;
/* Almost all browsers respect this rule: */
page-break-after:always;
}
}
/* The @page rules specify additional printing directives that browsers may respect
Here we suggest printing in landscape (instead of portrait) with a small margin.
Browsers, however, are free to ignore or override these suggestions.
See also:
https://developer.mozilla.org/en-US/docs/CSS/@page
https://dev.w3.org/csswg/css3-page/#at-page-rule
*/
@page {
/* Some browsers respect rules such as size: landscape */
margin: 1cm;
}
</style>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Printing</h1>
<p>
Printing a Diagram is typically accomplished by making several images of the Diagram and either saving them, inserting them into a PDF or other document, or printing them directly from the browser. On this page we will create several images out of a large Diagram and prepare some CSS so that printing this page prints only those images.
</p>
<p>
This page uses <a>Diagram.makeImage</a>, which has its own introduction page: <a href="makingImages.html">Making Images with GoJS</a>.
</p>
<p class="box bg-info">
Depending on your situation, you may want to use SVG to print instead, and there is also a page on <a href="makingSVG.html">Making SVG with GoJS</a>, which is nearly identical to making images. In this page's examples you could substitute <a>Diagram.makeSvg</a> for the same result.
</p>
<p>
Below is the Diagram we are going to prepare for print.
</p>
<!-- Don't bother showing this source -->
<pre class="lang-js" id="diag" style="display: none;">
window.myDiagram = diagram;
myDiagram.autoScale = go.Diagram.Uniform;
// define the Node template
myDiagram.nodeTemplate =
$(go.Node, "Spot",
{ locationSpot: go.Spot.Center },
$(go.Shape, "Rectangle",
{ fill: "lightgray", // the initial value, but data-binding may provide different value
stroke: "black",
desiredSize: new go.Size(30, 30) },
new go.Binding("fill", "fill")),
$(go.TextBlock,
new go.Binding("text", "text"))
);
// define the Link template to be minimal
myDiagram.linkTemplate =
$(go.Link,
{ selectable: false },
$(go.Shape));
myDiagram.layout = new go.LayeredDigraphLayout();
var minNodes = 125, maxNodes = 125;
myDiagram.startTransaction("generateDigraph");
// replace the diagram's model's nodeDataArray
var nodeArray = [];
// get the values from the fields and create a random number of nodes within the range
var min = parseInt(minNodes, 10);
var max = parseInt(maxNodes, 10);
if (isNaN(min)) min = 0;
if (isNaN(max) || max &lt; min) max = min;
var numNodes = Math.floor(Math.random() * (max - min + 1)) + min;
var i;
for (i = 0; i &lt; numNodes; i++) {
nodeArray.push({
key: i,
text: i.toString(),
fill: go.Brush.randomColor()
});
}
// randomize the node data
for (i = 0; i &lt; nodeArray.length; i++) {
var swap = Math.floor(Math.random() * nodeArray.length);
var temp = nodeArray[swap];
nodeArray[swap] = nodeArray[i];
nodeArray[i] = temp;
}
// set the nodeDataArray to this array of objects
myDiagram.model.nodeDataArray = nodeArray;
// replace the diagram's model's linkDataArray
var linkArray = [];
var nit = myDiagram.nodes;
var nodes = new go.List();
nodes.addAll(nit);
for (var i = 0; i &lt; nodes.count - 1; i++) {
var from = nodes.elt(i);
var numto = Math.floor(1 + (Math.random() * 3) / 2);
for (var j = 0; j &lt; numto; j++) {
var idx = Math.floor(i + 5 + Math.random() * 10);
if (idx &gt;= nodes.count) idx = i + (Math.random() * (nodes.count - i)) | 0;
var to = nodes.elt(idx);
linkArray.push({ from: from.data.key, to: to.data.key });
}
}
myDiagram.model.linkDataArray = linkArray;
myDiagram.commitTransaction("generateDigraph");
window.goCode3 = function(pre) {
if (diagramclass === undefined) diagramclass = go.Diagram;
if (typeof pre === "string") pre = document.getElementById(pre);
var f = eval("(function () {" + pre.textContent + "})");
f();
}
</pre>
<script>goCode("diag", 500, 500)</script>
<p>
Our code for print preparation contains a <code>generateImages</code> function that cuts the Diagram into several images of a given width and height. On this page it is called by default with (700, 960), but the width and height can be modified dynamically with HTML inputs below.
</p>
<pre class="lang-js" id="manyImgCode">
// if width or height are below 50, they are set to 50
function generateImages(width, height) {
// sanitize input
width = parseInt(width);
height = parseInt(height);
if (isNaN(width)) width = 100;
if (isNaN(height)) height = 100;
// Give a minimum size of 50x50
width = Math.max(width, 50);
height = Math.max(height, 50);
var imgDiv = document.getElementById('myImages');
imgDiv.innerHTML = ''; // clear out the old images, if any
var db = myDiagram.documentBounds;
var boundswidth = db.width;
var boundsheight = db.height;
var imgWidth = width;
var imgHeight = height;
var p = db.position;
for (var i = 0; i &lt; boundsheight; i += imgHeight) {
for (var j = 0; j &lt; boundswidth; j += imgWidth) {
var img = myDiagram.makeImage({
scale: 1,
position: new go.Point(p.x + j, p.y + i),
size: new go.Size(imgWidth, imgHeight)
});
// Append the new HTMLImageElement to the #myImages div
img.className = 'images';
imgDiv.appendChild(img);
imgDiv.appendChild(document.createElement('br'));
}
}
}
var button = document.getElementById('makeImages');
button.addEventListener('click', function() {
var width = parseInt(document.getElementById('widthInput').value);
var height = parseInt(document.getElementById('heightInput').value);
generateImages(width, height);
}, false);
// Call it with some default values
generateImages(700, 960);
</pre>
<p>
We want to show nothing but the images when this HTML page is printed, so we must use CSS rules to hide all page elements except the images themselves and the DOM parents of the images leading up to the body (#content, #myImages).
</p>
<p>
With the CSS below, printing this page will give yield nothing but the generated images that have been added to the end of this page. Typically the image or images used for printing may be hidden from the user completely, except when printing, or else added to a separate page in a new window.
</p>
<pre class="lang-css" id="mycss">
/* @media print specifies CSS rules that only apply when printing */
@media print {
/* CSS reset to clear styles for printing */
html, body, div {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* Hide everything on the page */
body * {
display: none;
}
#content, #myImages, #myImages * {
/* Only display the images we want printed */
/* all of the image's parent divs
leading up to the body must be un-hidden (displayed) too
*/
display: block;
/* CSS reset to clear the specific visible divs for printing */
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* We have line breaks in the DIV
to separate the images in the browser,
but do not want those line breaks when printing
*/
#myImages br {
display: none;
}
img {
/* Only some browsers respect this rule: */
page-break-inside: avoid;
/* Almost all browsers respect this rule: */
page-break-after:always;
}
}
/* The @page rules specify additional printing directives that browsers may respect
Here we suggest printing in landscape (instead of portrait) with a small margin.
Browsers, however, are free to ignore or override these suggestions.
See also:
https://developer.mozilla.org/en-US/docs/CSS/@page
https://dev.w3.org/csswg/css3-page/#at-page-rule
*/
@page {
/* Some browsers respect rules such as size: landscape */
margin: 1cm;
}
</pre>
<p>
The images at the end of this page are generated by calling <code>generateImages(700, 960)</code>. Using the inputs below the images can be replaced with those of a different size. Different papers, page orientations, and margins will require different sized images if they are to fill the paper, and (700, 960) is a suggested size for 8.5 inch by 11 paper in portrait orientation, with minimal margins.
</p>
<div style="border: 1px solid gray; padding: 10px; margin-bottom: 20px;">
Width: <input id="widthInput" value="700" />
Height: <input id="heightInput" value="960" />
<button id="makeImages">Generate images for printing</button>
</div>
<div id="myImages">
</div>
<script>goCode3('manyImgCode');</script>
</div>
</div>
</body>
</html>
+451
View File
@@ -0,0 +1,451 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS and React -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Using GoJS with React</h1>
<p class="box" style="background-color: lightgoldenrodyellow;">
Examples of most of the topics discussed on this page can be found in the <a href="https://github.com/NorthwoodsSoftware/gojs-react-basic" target="_blank">gojs-react-basic</a> project,
which serves as a simple starter project.
</p>
<p>
If you are new to GoJS, it may be helpful to first visit the <a href="../learn/index.html" target="_blank">Getting Started Tutorial</a>.
</p>
<p>
The easiest way to get a component set up for a GoJS Diagram is to use the <a href="https://github.com/NorthwoodsSoftware/gojs-react" target="_blank">gojs-react</a> package,
which exports React Components for GoJS Diagrams, Palettes, and Overviews.
The <a href="https://github.com/NorthwoodsSoftware/gojs-react-basic" target="_blank">gojs-react-basic</a> project demonstrates how to use these components.
More information about the package, including the various props it takes, can be found on the <a href="https://github.com/NorthwoodsSoftware/gojs-react" target="_blank">Github</a> or
<a href="https://npmjs.com/gojs-react" target="_blank">NPM</a> pages. Our examples will be using a <a>GraphLinksModel</a>, but any model can be used.
</p>
<h2 id="quickstart">Quick start with an existing React application</h2>
<h4 id="Installation">Installation</h4>
<p>
Start by installing GoJS and gojs-react: <code>npm install gojs gojs-react</code>.
</p>
<h4 id="DiagramStyling">Diagram styling</h4>
<p>
Next, set up a CSS class for the GoJS diagram's div:
</p>
<pre class="lang-css">
/* App.css */
.diagram-component {
width: 400px;
height: 400px;
border: solid 1px black;
background-color: white;
}
</pre>
<h4 id="RenderingComponent">Rendering the component</h4>
<p>
Finally, add an initDiagram function and a model change handler function, and add the ReactDiagram component inside your render method.
Note that the UndoManager should always be enabled to allow for transactions to take place,
but the <a>UndoManager.maxHistoryLength</a> can be set to 0 to prevent undo and redo.
</p>
<pre class="lang-js">
// App.js
import React from 'react';
import * as go from 'gojs';
import { ReactDiagram } from 'gojs-react';
import './App.css'; // contains .diagram-component CSS
// ...
/**
* Diagram initialization method, which is passed to the ReactDiagram component.
* This method is responsible for making the diagram and initializing the model and any templates.
* The model's data should not be set here, as the ReactDiagram component handles that via the other props.
*/
function initDiagram() {
const $ = go.GraphObject.make;
// set your license key here before creating the diagram: go.Diagram.licenseKey = "...";
const diagram =
$(go.Diagram,
{
'undoManager.isEnabled': true, // must be set to allow for model change listening
// 'undoManager.maxHistoryLength': 0, // uncomment disable undo/redo functionality
'clickCreatingTool.archetypeNodeData': { text: 'new node', color: 'lightblue' },
model: $(go.GraphLinksModel,
{
linkKeyProperty: 'key' // IMPORTANT! must be defined for merges and data sync when using GraphLinksModel
})
});
// define a simple Node template
diagram.nodeTemplate =
$(go.Node, 'Auto', // the Shape will go around the TextBlock
new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
$(go.Shape, 'RoundedRectangle',
{ name: 'SHAPE', fill: 'white', strokeWidth: 0 },
// Shape.fill is bound to Node.data.color
new go.Binding('fill', 'color')),
$(go.TextBlock,
{ margin: 8, editable: true }, // some room around the text
new go.Binding('text').makeTwoWay()
)
);
return diagram;
}
/**
* This function handles any changes to the GoJS model.
* It is here that you would make any updates to your React state, which is dicussed below.
*/
function handleModelChange(changes) {
alert('GoJS model changed!');
}
// render function...
function App() {
return (
&lt;div&gt;
...
&lt;ReactDiagram
initDiagram={initDiagram}
divClassName='diagram-component'
nodeDataArray={[
{ key: 0, text: 'Alpha', color: 'lightblue', loc: '0 0' },
{ key: 1, text: 'Beta', color: 'orange', loc: '150 0' },
{ key: 2, text: 'Gamma', color: 'lightgreen', loc: '0 150' },
{ key: 3, text: 'Delta', color: 'pink', loc: '150 150' }
]}
linkDataArray={[
{ key: -1, from: 0, to: 1 },
{ key: -2, from: 0, to: 2 },
{ key: -3, from: 1, to: 1 },
{ key: -4, from: 2, to: 3 },
{ key: -5, from: 3, to: 0 }
]}
onModelChange={handleModelChange}
/&gt;
...
&lt;/div&gt;
);
}
</pre>
<p>
That's it! You should now have a GoJS diagram rendering within your React application.
Try editing the text of a node or deleting a node, and you'll see an alert on the page.
</p>
<h2 id="stateful">Usage in a stateful React app</h2>
<p>
Typically the data being passed to the ReactDiagram component will be used elsewhere in your app and will exist in React state.
For example, you may have some kind of inspector that can be used to modify node properties, and therefore the state should be lifted up and held by a parent component
of both the diagram and the inspector.
</p>
<p>
A basic setup can be seen in the <a href="https://github.com/NorthwoodsSoftware/gojs-react-basic" target="_blank">gojs-react-basic</a> project,
but we'll describe some of the methodology here.
</p>
<h4 id="CreatingWrapperComponent">Creating a wrapper component</h4>
<p>
When handling state, it is often useful to write a wrapper component around the gojs-react components to pass the necessary props along and keep GoJS initialization out of the main app.
There are a few things that should be set up in the wrapper component:
<ul>
<li>a set of props coming in from the parent component which holds state and handlers</li>
<li>a ref to the ReactDiagram component so getDiagram() can be used</li>
<li>componentDidMount and componentWillUnmount methods to add/remove app-specific diagram listeners</li>
<li>an initDiagram method to be passed to the ReactDiagram component</li>
</ul>
Also note that the node and link data are <i>merged</i> into the GoJS model, thus properties should not be removed from node or link data,
but rather set to undefined if they are no longer needed; GoJS avoids destructive merging.
</p>
<p>
Below, we'll pass linkDataArray and modelData as props to the ReactDiagram, but note that they are not always needed in gojs-react components,
so your app may not need to include htem.
</p>
<pre class="lang-js">
import * as go from 'gojs';
import { ReactDiagram } from 'gojs-react';
import * as React from 'react';
// props passed in from a parent component holding state, some of which will be passed to ReactDiagram
interface WrapperProps {
nodeDataArray: Array&lt;go.ObjectData&gt;;
linkDataArray: Array&lt;go.ObjectData&gt;;
modelData: go.ObjectData;
skipsDiagramUpdate: boolean;
onDiagramEvent: (e: go.DiagramEvent) => void;
onModelChange: (e: go.IncrementalData) => void;
}
export class DiagramWrapper extends React.Component&lt;WrapperProps, {}&gt; {
/**
* Ref to keep a reference to the component, which provides access to the GoJS diagram via getDiagram().
*/
private diagramRef: React.RefObject&lt;ReactDiagram&gt;;
constructor(props: WrapperProps) {
super(props);
this.diagramRef = React.createRef();
}
/**
* Get the diagram reference and add any desired diagram listeners.
* Typically the same function will be used for each listener,
* with the function using a switch statement to handle the events.
* This is only necessary when you want to define additional app-specific diagram listeners.
*/
public componentDidMount() {
if (!this.diagramRef.current) return;
const diagram = this.diagramRef.current.getDiagram();
if (diagram instanceof go.Diagram) {
diagram.addDiagramListener('ChangedSelection', this.props.onDiagramEvent);
}
}
/**
* Get the diagram reference and remove listeners that were added during mounting.
* This is only necessary when you have defined additional app-specific diagram listeners.
*/
public componentWillUnmount() {
if (!this.diagramRef.current) return;
const diagram = this.diagramRef.current.getDiagram();
if (diagram instanceof go.Diagram) {
diagram.removeDiagramListener('ChangedSelection', this.props.onDiagramEvent);
}
}
/**
* Diagram initialization method, which is passed to the ReactDiagram component.
* This method is responsible for making the diagram and initializing the model, any templates,
* and maybe doing other initialization tasks like customizing tools.
* The model's data should not be set here, as the ReactDiagram component handles that via the other props.
*/
private initDiagram(): go.Diagram {
const $ = go.GraphObject.make;
// set your license key here before creating the diagram: go.Diagram.licenseKey = "...";
const diagram =
$(go.Diagram,
{
'undoManager.isEnabled': true, // must be set to allow for model change listening
// 'undoManager.maxHistoryLength': 0, // uncomment disable undo/redo functionality
'clickCreatingTool.archetypeNodeData': { text: 'new node', color: 'lightblue' },
model: $(go.GraphLinksModel,
{
linkKeyProperty: 'key', // IMPORTANT! must be defined for merges and data sync when using GraphLinksModel
// positive keys for nodes
makeUniqueKeyFunction: (m: go.Model, data: any) => {
let k = data.key || 1;
while (m.findNodeDataForKey(k)) k++;
data.key = k;
return k;
},
// negative keys for links
makeUniqueLinkKeyFunction: (m: go.GraphLinksModel, data: any) => {
let k = data.key || -1;
while (m.findLinkDataForKey(k)) k--;
data.key = k;
return k;
}
})
});
// define a simple Node template
diagram.nodeTemplate =
$(go.Node, 'Auto', // the Shape will go around the TextBlock
new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
$(go.Shape, 'RoundedRectangle',
{
name: 'SHAPE', fill: 'white', strokeWidth: 0,
// set the port properties:
portId: '', fromLinkable: true, toLinkable: true, cursor: 'pointer'
},
// Shape.fill is bound to Node.data.color
new go.Binding('fill', 'color')),
$(go.TextBlock,
{ margin: 8, editable: true, font: '400 .875rem Roboto, sans-serif' }, // some room around the text
new go.Binding('text').makeTwoWay()
)
);
// relinking depends on modelData
diagram.linkTemplate =
$(go.Link,
new go.Binding('relinkableFrom', 'canRelink').ofModel(),
new go.Binding('relinkableTo', 'canRelink').ofModel(),
$(go.Shape),
$(go.Shape, { toArrow: 'Standard' })
);
return diagram;
}
public render() {
return (
&lt;ReactDiagram
ref={this.diagramRef}
divClassName='diagram-component'
initDiagram={this.initDiagram}
nodeDataArray={this.props.nodeDataArray}
linkDataArray={this.props.linkDataArray}
modelData={this.props.modelData}
onModelChange={this.props.onModelChange}
skipsDiagramUpdate={this.props.skipsDiagramUpdate}
/&gt;
);
}
}
</pre>
<h4 id="UsingWrapperComponentWithinApp">Using the wrapper component within the app</h4>
<p>
The application should set up a few things to be passed to the wrapper described above:
<ul>
<li>state containing a nodeDataArray, linkDataArray, modelData object, and skipsDiagramUpdate flag</li>
<li>a handleDiagramEvent method for any app-specific DiagramEvents, such as 'ChangedSelection'</li>
<li>a handleModelChange method for updating state based on updates from the GoJS model</li>
</ul>
</p>
<pre class="lang-js">
import * as go from 'gojs';
import * as React from 'react';
import { DiagramWrapper } from './components/Diagram';
interface AppState {
// ...
nodeDataArray: Array&lt;go.ObjectData&gt;;
linkDataArray: Array&lt;go.ObjectData&gt;;
modelData: go.ObjectData;
selectedKey: number | null;
skipsDiagramUpdate: boolean;
}
class App extends React.Component&lt;{}, AppState&gt; {
constructor(props: object) {
super(props);
this.state = {
// ...
nodeDataArray: [
{ key: 0, text: 'Alpha', color: 'lightblue', loc: '0 0' },
{ key: 1, text: 'Beta', color: 'orange', loc: '150 0' },
{ key: 2, text: 'Gamma', color: 'lightgreen', loc: '0 150' },
{ key: 3, text: 'Delta', color: 'pink', loc: '150 150' }
],
linkDataArray: [
{ key: -1, from: 0, to: 1 },
{ key: -2, from: 0, to: 2 },
{ key: -3, from: 1, to: 1 },
{ key: -4, from: 2, to: 3 },
{ key: -5, from: 3, to: 0 }
],
modelData: {
canRelink: true
},
selectedKey: null,
skipsDiagramUpdate: false
};
// bind handler methods
this.handleDiagramEvent = this.handleDiagramEvent.bind(this);
this.handleModelChange = this.handleModelChange.bind(this);
this.handleRelinkChange = this.handleRelinkChange.bind(this);
}
/**
* Handle any app-specific DiagramEvents, in this case just selection changes.
* On ChangedSelection, find the corresponding data and set the selectedKey state.
*
* This is not required, and is only needed when handling DiagramEvents from the GoJS diagram.
* @param e a GoJS DiagramEvent
*/
public handleDiagramEvent(e: go.DiagramEvent) {
const name = e.name;
switch (name) {
case 'ChangedSelection': {
const sel = e.subject.first();
if (sel) {
this.setState({ selectedKey: sel.key });
} else {
this.setState({ selectedKey: null });
}
break;
}
default: break;
}
}
/**
* Handle GoJS model changes, which output an object of data changes via Model.toIncrementalData.
* This method should iterates over those changes and update state to keep in sync with the GoJS model.
* This can be done via setState in React or another preferred state management method.
* @param obj a JSON-formatted string
*/
public handleModelChange(obj: go.IncrementalData) {
const insertedNodeKeys = obj.insertedNodeKeys;
const modifiedNodeData = obj.modifiedNodeData;
const removedNodeKeys = obj.removedNodeKeys;
const insertedLinkKeys = obj.insertedLinkKeys;
const modifiedLinkData = obj.modifiedLinkData;
const removedLinkKeys = obj.removedLinkKeys;
const modifiedModelData = obj.modelData;
console.log(obj);
// see gojs-react-basic for an example model change handler
// when setting state, be sure to set skipsDiagramUpdate: true since GoJS already has this update
}
/**
* Handle changes to the checkbox on whether to allow relinking.
* @param e a change event from the checkbox
*/
public handleRelinkChange(e: any) {
const target = e.target;
const value = target.checked;
this.setState({ modelData: { canRelink: value }, skipsDiagramUpdate: false });
}
public render() {
let selKey;
if (this.state.selectedKey !== null) {
selKey = &lt;p&gt;Selected key: {this.state.selectedKey}&lt;/p&gt;;
}
return (
&lt;div&gt;
&lt;DiagramWrapper
nodeDataArray={this.state.nodeDataArray}
linkDataArray={this.state.linkDataArray}
modelData={this.state.modelData}
skipsDiagramUpdate={this.state.skipsDiagramUpdate}
onDiagramEvent={this.handleDiagramEvent}
onModelChange={this.handleModelChange}
/&gt;
&lt;label&gt;
Allow Relinking?
&lt;input
type='checkbox'
id='relink'
checked={this.state.modelData.canRelink}
onChange={this.handleRelinkChange} /&gt;
&lt;/label&gt;
{selKey}
&lt;/div&gt;
);
}
}
</pre>
<p>
These are the basics for setting up GoJS within a React application. See <a href="https://github.com/NorthwoodsSoftware/gojs-react-basic" target="_blank">gojs-react-basic</a>
for a working example and the <a href="https://github.com/NorthwoodsSoftware/gojs-react" target="_blank">gojs-react</a> Github page for further explanation of various props
passed to the components.
</p>
</div>
</div>
</body>
</html>
+229
View File
@@ -0,0 +1,229 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Replacing and Deleting Diagrams -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Replacing Diagrams and Models</h1>
<p>
Many applications will require the programmer to show different Diagrams on the same content area of the page. This is especially common in single-page webapps.
Often, you do not need to remove the Diagram, and create another one, to do this. Since the Diagram is analagous to a <em>view</em> in <em>model-view</em> architecture,
you can instead replace the <a>Diagram.model</a>, and perhaps other settings, like the Diagram.nodeTemplateMap or Diagram.layout.
Or you could build larger template maps that accomodate all Models you wish to present.
</p>
<p>
Below is an example of keeping a single Diagram, to be used as the view surface. It has a Model loaded,
and a button will load a different Model which uses different templates, and sets a different Layout.
This demonstrates re-use of the Diagram, which is often easier and more efficient than handling multiple Diagrams.
This is the normal way to show at most one diagram at a time.
</p>
<pre class="lang-js" id="first">
// A minimal Diagram
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle",
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 3, font: '28px sans-serif' }, // some room around the text
new go.Binding("text", "key"))
);
// Node template that is only used by the second model
diagram.nodeTemplateMap.add("TypeTwo",
$(go.Node, "Horizontal",
$(go.Shape, "Circle", { width: 24, height: 24, strokeWidth: 0, portId: "" },
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 2, font: "Bold 15px sans-serif" },
new go.Binding("text", "key"))
)
);
// Another node template that is only used by the second model
diagram.nodeTemplateMap.add("AnotherType",
$(go.Node, "Auto",
$(go.Shape, "Rectangle", { strokeWidth: 1, fill: 'lightyellow' },
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 12, font: "12px sans-serif" },
new go.Binding("text", "text"))
)
);
var firstModel = true;
loadModel();
// Toggle the Diagram's Model
var button1 = document.getElementById('button1');
button1.addEventListener('click', function() {
loadModel();
});
function loadModel() {
if (firstModel) { // load the first model
diagram.layout = new go.Layout(); // Simple layout
diagram.model = new go.GraphLinksModel(
[
{ key: "Alpha", color: "lightblue" },
{ key: "Beta", color: "lightblue" },
{ key: "Gamma", color: "lightgreen" },
{ key: "Delta", color: "lightgreen" }
],
[
{ from: "Alpha", to: "Beta" },
{ from: "Gamma", to: "Delta" }
]);
} else { // load the second model
diagram.layout = $(go.TreeLayout, { angle: 90 });
diagram.model = new go.GraphLinksModel(
[
{ key: "One", category: "TypeTwo", color: go.Brush.randomColor() },
{ key: "Two", category: "TypeTwo", color: go.Brush.randomColor() },
{ key: "Three", category: "TypeTwo", color: go.Brush.randomColor() },
{ key: "Four", category: "TypeTwo", color: go.Brush.randomColor() },
{ key: "Five", category: "TypeTwo", color: go.Brush.randomColor() },
{ key: "Six", category: "TypeTwo", color: go.Brush.randomColor() },
{ text: "Some comment", category: "AnotherType" }
],
[
{ from: "One", to: "Two" },
{ from: "One", to: "Three" },
{ from: "Three", to: "Four" },
{ from: "Three", to: "Five" },
{ from: "Four", to: "Six" }
]);
}
firstModel = !firstModel;
}
</pre>
<div id="dia1"></div>
<p><button id="button1">Toggle the Diagram's Model</button></p>
<script>goCode("first", 500, 400, go.Diagram, "dia1")</script>
<p>
Note that changing the Model destroys all state not kept in the Model, such as the currently selected Parts,
and if there are no data bindings for them, the positions of all Nodes as well, and so on.
These can be saved in the Model before switching, when they are relevant.
</p>
<h2 id="TwoDiagramsReusingOneDiv">Two Diagrams re-using one DIV</h2>
<p>
Sometimes users want to work on two or more Diagrams at once and keep all Diagram state.
If this is the case, you may wish to put two Diagrams on the page (as all samples with a Palette do),
or you may wish to put Diagrams into multiple "tabs" or some other mechanism,
like the <a href="../samples/planogram.html">Planogram sample does with its four Palettes</a>.
</p>
<p>
Alternatively, you may wish to display the two Diagrams in the same DIV, one at a time, by swapping them out.
You can swap the div by setting <a>Diagram.div</a> to <code>null</code> on the first Diagram,
and setting the Div on the second.
</p>
<pre class="lang-js" id="second">
// A very minimal Diagram
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "Circle",
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 3 }, // some room around the text
new go.Binding("text", "key"))
);
diagram.model = new go.GraphLinksModel([
{ key: "Alpha", color: "lightblue" },
{ key: "Beta", color: "orange" }
], [
{ from: "Alpha", to: "Beta" },
]);
var diagram2 = $(go.Diagram);
diagram2.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "Rectangle", { fill: 'lime' }),
$(go.TextBlock,
{ margin: 5, font: '22px sans-serif' },
new go.Binding("text", "key"))
);
diagram2.model = new go.GraphLinksModel([
{ key: "BigNode1" },
{ key: "BigNode2" },
{ key: "BigNode3" },
], [ ]);
var currentDiagram = diagram;
// Toggle the Diagram within this DIV with this button
var button2 = document.getElementById('button2');
button2.addEventListener('click', function() {
// Set one Diagram.div to null, and the other Diagram.div to this div
if (currentDiagram === diagram) {
var div = diagram.div;
diagram.div = null;
diagram2.div = div;
currentDiagram = diagram2;
} else {
var div = diagram2.div;
diagram2.div = null;
diagram.div = div;
currentDiagram = diagram;
}
});
</pre>
<div id="dia2"></div>
<button id="button2">Toggle Diagram within DIV</button>
<script>goCode("second", 400, 400, go.Diagram, "dia2")</script>
<p>
If you select a Node and move it, and toggle Diagrams back and forth, you will see that the selection and Node positioning persists.
Both Diagrams remain in memory, only the Div is swapped to use one or the other.
</p>
<h2 id="PermanentlyDeletingDiagram">Permanently deleting a Diagram</h2>
<p>
You may wish to remove a Diagram and ensure it leaves no memory footprint.
To do this, if you have not created any other references to your Diagram or GraphObjects or Tools or Layouts within, you can write:
</p>
<pre>
myDiagram.div = null;
myDiagram = null; // Assumes this is the only reference to your Diagram
</pre>
<p>
If you have used Pictures, you should also clear the Picture cache, which GoJS creates to store a map of source URLs to Image elements:
</p>
<pre>
// Clear any Image references that GoJS is holding onto
go.Picture.clearCache();
</pre>
</div>
</div>
</body>
</html>
+122
View File
@@ -0,0 +1,122 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Resizing Diagrams -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Resizing Diagrams</h1>
<p>
Sometimes it may be necessary to resize the div that contains a GoJS Diagram.
In older browsers or in Internet Explorer, GoJS does not listen or attempt to detect changes in the div's size,
so you must manually tell each Diagram when you perform an action that resizes its containing div.
</p>
<h2 id="UsingRequestUpdateToResizeDiv">Using <a>Diagram.requestUpdate</a> to resize a Div</h2>
<p>
The following example has a button that enlarges the Diagram's div. When it is clicked,
the div is visibly resized but in older browsers the Diagram remains the same size.
</p>
<pre class="lang-js" id="first">
// A minimal Diagram
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle",
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 3 }, // some room around the text
new go.Binding("text", "key"))
);
diagram.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: "Gamma", to: "Delta" },
{ from: "Delta", to: "Alpha" }
]);
// Resize the diagram with this button
var button1 = document.getElementById('button1');
button1.addEventListener('click', function() {
var div = diagram.div;
div.style.width = '200px';
});
</pre>
<div id="dia1"></div>
<p>This button won't work in Internet Explorer: <button id="button1">Expand div</button></p>
<script>goCode("first", 100, 110, go.Diagram, "dia1")</script>
<p>
Typically we will want the Diagram to resize to its div at the same time that the div resizes.
To do this we add a call to <a>Diagram.requestUpdate</a> <em>after</em> we have resized the div.
This checks to see if the Diagram's div has changed size, and if so, redraws the diagram at the appropriate new dimensions.
</p>
<p>
Below is nearly identical code, except that a call to <a>Diagram.requestUpdate</a> has been added.
</p>
<pre class="lang-js" id="second">
// A minimal Diagram
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle",
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 3 }, // some room around the text
new go.Binding("text", "key"))
);
diagram.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: "Gamma", to: "Delta" },
{ from: "Delta", to: "Alpha" }
]);
// Resize the diagram with this button
var button2 = document.getElementById('button2');
button2.addEventListener('click', function() {
var div = diagram.div;
div.style.width = '200px';
diagram.requestUpdate(); // Needed!
});
</pre>
<div id="dia2"></div>
<button id="button2">Expand div</button>
<script>goCode("second", 100, 110, go.Diagram, "dia2")</script>
<p>See also the <a href="../samples/tabs.html">jQuery Tabs sample</a>.</p>
<p>
In recent browsers the calls to <a>Diagram.requestUpdate</a> will not be necessary,
but they will not cause any harm.
Still, if you know that all of your users will be using recent browsers
and if you are using GoJS version 2.1.26 or later, you do not need to make this call.
</p>
</div>
</div>
</body>
</html>
+373
View File
@@ -0,0 +1,373 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Selection -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="../extensions/Figures.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Selection</h1>
<p>
Users normally select <a>Part</a>s manually by clicking on them and they deselect them by clicking in the background or pressing the Esc key.
You can select parts programmatically by setting <a>Part.isSelected</a>.
</p>
<p>
Users can also drag in the background in order to select the Parts that are within a rectangular area, via the <a>DragSelectingTool</a>.
Read more about that in the Introduction to Tools at <a href="tools.html#DragSelectingTool">DragSelectingTool</a>.
</p>
<p>
The <a>Diagram</a> keeps a collection of selected parts, <a>Diagram.selection</a>.
That collection is read-only -- the only way to select or deselect a Part is by setting its <a>Part.isSelected</a> property.
You can limit how many parts are selected by setting <a>Diagram.maxSelectionCount</a>.
Prevent all selection by the user by setting <a>Diagram.allowSelect</a> to false.
Or prevent a particular Part from being selected by setting <a>Part.selectable</a> to false.
</p>
<p>
You can show that a part is selected by either or both of two general techniques: adding <a>Adornment</a>s or
changing the appearance of some of the elements in the visual tree of the selected Part.
</p>
<h2 id="SelectionAdornments">Selection Adornments</h2>
<p>
It is common to display that a Part is selected by having it show a selection <a>Adornment</a> when the Part is selected.
For nodes this is normally a blue rectangle surrounding the whole Node.
This is the default behavior; if you do not want such an adornment, you can set <a>Part.selectionAdorned</a> to false.
</p>
<pre class="lang-js" id="adornmentDefault">
diagram.nodeTemplate =
$(go.Node, "Vertical",
// the location is the center of the Shape, not the center of the whole Node
{ locationSpot: go.Spot.Center, locationObjectName: "ICON" },
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape,
{
name: "ICON",
width: 40, height: 40,
fill: "gray",
portId: "" // the port is this Shape, not the whole Node
},
new go.Binding("figure")),
$(go.TextBlock,
{ margin: new go.Margin(5, 0, 0, 0) },
new go.Binding("text", "key"))
);
var nodeDataArray = [
{ key: "Alpha", figure: "Club", loc: "0 0" },
{ key: "Beta", figure: "Spade", loc: "200 50" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
diagram.commandHandler.selectAll();
</pre>
<script>goCode("adornmentDefault", 600, 150)</script>
<p>
By default an <a>Adornment</a> will apply to the whole <a>Node</a>.
What if you want attention to be drawn only to the main piece of a node?
You can accomplish that by naming that object and setting <a>Part.selectionObjectName</a> to that name.
</p>
<pre class="lang-js" id="adornmentObject">
diagram.nodeTemplate =
$(go.Node, "Vertical",
{ selectionObjectName: "ICON" }, // added this property!
// the location is the center of the Shape, not the center of the whole Node
{ locationSpot: go.Spot.Center, locationObjectName: "ICON" },
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape,
{
name: "ICON",
width: 40, height: 40,
fill: "gray",
portId: "" // the port is this Shape, not the whole Node
},
new go.Binding("figure")),
$(go.TextBlock,
{ margin: new go.Margin(5, 0, 0, 0) },
new go.Binding("text", "key"))
);
var nodeDataArray = [
{ key: "Alpha", figure: "Club", loc: "0 0" },
{ key: "Beta", figure: "Spade", loc: "200 50" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
diagram.selectCollection(diagram.nodes);
</pre>
<script>goCode("adornmentObject", 600, 150)</script>
<p>
Note how the <a>Part.selectionObjectName</a> property is similar to the <a>Part.locationObjectName</a>
in helping to treat a node as if only one piece of it really mattered.
</p>
<h3 id="CustomSelectionAdornments">Custom Selection Adornments</h3>
<p>
If you do want a selection adornment but want something different than the standard one, you can customize it.
Such customization can be done by setting the <a>Part.selectionAdornmentTemplate</a>.
In this example, nodes get thick blue rounded rectangles surrounding the selected node,
and links get thick blue lines following the selected link's path.
</p>
<pre class="lang-js" id="custom">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray" }),
$(go.TextBlock,
{ margin: 5 },
new go.Binding("text", "key")),
{
selectionAdornmentTemplate:
$(go.Adornment, "Auto",
$(go.Shape, "RoundedRectangle",
{ fill: null, stroke: "dodgerblue", strokeWidth: 8 }),
$(go.Placeholder)
) // end Adornment
}
);
diagram.linkTemplate =
$(go.Link,
$(go.Shape, { strokeWidth: 2 }),
$(go.Shape, { toArrow: "Standard" }),
{
selectionAdornmentTemplate:
$(go.Adornment,
$(go.Shape,
{ isPanelMain: true, stroke: "dodgerblue", strokeWidth: 8 }),
$(go.Shape,
{ toArrow: "Standard", fill: "dodgerblue", stroke: null, scale: 2.5 })
) // end Adornment
}
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "200 50" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
diagram.commandHandler.selectAll();
</pre>
<script>goCode("custom", 600, 100)</script>
<p>
Note that an <a>Adornment</a> is just a <a>Part</a>.
Adornments for nodes must contain a <a>Placeholder</a> in their visual tree.
The Placeholder gets positioned where the selected object is.
</p>
<p>
Adornments for links are assumed to be panels of <a>Panel.type</a> that is <a>Panel,Link</a>.
Hence the main element may be a <a>Shape</a> that gets the geometry of the selected Link's path shape,
and the other elements of the adornment may be positioned on or near the segments of the link route just as for a regular <a>Link</a>.
</p>
<h3 id="MoreComplexAdornments">More Complex Adornments</h3>
<p>
The custom <a>Adornment</a> for a <a>Node</a> need not be only a simple <a>Shape</a> outlining the selected node.
Here is an adornment that adds a button to the adornment which inserts a node and a link to that new node.
</p>
<pre class="lang-js" id="complex">
function addNodeAndLink(e, b) {
// take a button panel in an Adornment, get its Adornment, and then get its adorned Node
var node = b.part.adornedPart;
// we are modifying the model, so conduct a transaction
var diagram = node.diagram;
diagram.startTransaction("add node and link");
// have the Model add the node data
var newnode = { key: "N" };
diagram.model.addNodeData(newnode);
// locate the node initially where the parent node is
diagram.findNodeForData(newnode).location = node.location;
// and then add a link data connecting the original node with the new one
var newlink = { from: node.data.key, to: newnode.key };
diagram.model.addLinkData(newlink);
// finish the transaction -- will automatically perform a layout
diagram.commitTransaction("add node and link");
}
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle", { fill: "lightgray" }),
$(go.TextBlock,
{ margin: 5 },
new go.Binding("text", "key")),
{
selectionAdornmentTemplate:
$(go.Adornment, "Spot",
$(go.Panel, "Auto",
// this Adornment has a rectangular blue Shape around the selected node
$(go.Shape, { fill: null, stroke: "dodgerblue", strokeWidth: 3 }),
$(go.Placeholder)
),
// and this Adornment has a Button to the right of the selected node
$("Button",
{ alignment: go.Spot.Right, alignmentFocus: go.Spot.Left,
click: addNodeAndLink }, // define click behavior for Button in Adornment
$(go.TextBlock, "ADD", // the Button content
{ font: "bold 6pt sans-serif" })
)
) // end Adornment
}
);
diagram.layout = $(go.TreeLayout);
var nodeDataArray = [
{ key: "Alpha" },
{ key: "Beta" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
diagram.select(diagram.findNodeForKey("Beta"));
</pre>
<script>goCode("complex", 600, 200)</script>
<p>
Select any node and click the "ADD" button.
Note how the diagram is automatically laid out as a tree.
</p>
<h3 id="DataBinding">Data Binding</h3>
<p>
Like all <a>Part</a>s, <a>Adornment</a>s support data binding.
If the adorned Part has a data binding (i.e. if <a>Part.data</a> is non-null),
all adornments for that part will also be bound to the same data object.
</p>
<pre class="lang-js" id="binding">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray", strokeWidth: 2 },
new go.Binding("stroke", "color")),
$(go.TextBlock,
{ margin: 5 },
new go.Binding("text", "key")),
{
selectionAdornmentTemplate:
$(go.Adornment, "Auto",
$(go.Shape,
{ fill: null, stroke: "dodgerblue", strokeWidth: 6 },
new go.Binding("stroke", "color")),
$(go.Placeholder)
) // end Adornment
}
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0", color: "blue" },
{ key: "Beta", loc: "200 50", color: "red" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
diagram.selectCollection(diagram.nodes);
</pre>
<script>goCode("binding", 600, 100)</script>
<p>
Notice how each Adornment has the same color as the selected node's data.color.
</p>
<h2 id="SelectionAppearanceChanges">Selection Appearance changes</h2>
<p>
Adding a selection adornment is not the only way to indicate visually that a <a>Part</a> is selected.
You can also modify the appearance of one or more objects in your Part.
</p>
<p>
One way to do this is with data binding.
Here we data bind the <a>Shape.fill</a> to the <a>Part.isSelected</a> property
with a converter function that converts the boolean value to a color string or brush.
We also turn off the regular rectangular blue selection adornment.
</p>
<pre class="lang-js" id="isSelected">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ selectionAdorned: false }, // don't bother with any selection adornment
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle", { fill: "lightgray", strokeWidth: 2 },
// when this Part.isSelected changes value, change this Shape.fill value:
new go.Binding("fill", "isSelected", function(sel) {
if (sel) return "cyan"; else return "lightgray";
}).ofObject("")), // The object named "" is the root visual element, the Node itself
$(go.TextBlock,
{ margin: 5 },
new go.Binding("text", "key"))
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "200 50" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
diagram.select(diagram.findNodeForKey("Beta"));
</pre>
<script>goCode("isSelected", 600, 100)</script>
<p>
Now when you select a node its background color changes to cyan.
</p>
<p>
More generally you can execute code to modify the Part when <a>Part.isSelected</a> has changed value.
In this example we will have the same side effects as the previous example.
</p>
<pre class="lang-js" id="selectionChanged">
function onSelectionChanged(node) {
var icon = node.findObject("Icon");
if (icon !== null) {
if (node.isSelected)
icon.fill = "cyan";
else
icon.fill = "lightgray";
}
}
diagram.nodeTemplate =
$(go.Node, "Auto",
{ selectionAdorned: false, // don't bother with any selection adornment
selectionChanged: onSelectionChanged }, // executed when Part.isSelected has changed
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle",
{ name: "Icon", fill: "lightgray", strokeWidth: 2 }),
$(go.TextBlock,
{ margin: 5 },
new go.Binding("text", "key"))
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "200 50" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
diagram.select(diagram.findNodeForKey("Beta"));
</pre>
<script>goCode("selectionChanged", 600, 100)</script>
<p>
There are some restrictions on what you can do in such an event handler:
you should not select or deselect any parts, and you should not add or remove any parts from the diagram.
</p>
</div>
</div>
</body>
</html>
+159
View File
@@ -0,0 +1,159 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Server-Side Images with GoJS -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Creating Images on the Server</h1>
<p>
It may be useful for many applications to create images of Diagrams with <b>GoJS</b>, and this page details some of the options for such a task.
</p>
<h2 id="Puppeteer">Puppeteer</h2>
<p>
<a href="https://github.com/GoogleChrome/puppeteer">Puppeteer</a> is a Node library which provides a high-level API to
control headless Chrome. We can use it to create images server-side. If you have Node and npm installed you can
install it with <code>npm install puppeteer</code>.
<p>
The following code is a small example using Puppeteer.
If you saved the JavaScript as <code>puppet.js</code> and run it with node (<code>node createImage.js</code>)
it demonstrate creating two images: One from the Diagram called <code>gojs-screenshot.png</code> and one of the HTML page
called <code>page-screenshot.png</code>.
The Diagram code in the sample is the same as that in the <a href="../samples/minimal.html">Minimal sample</a>.
</p>
<pre class="lang-js">
// This example loads the GoJS library then adds HTML from scratch and evaluates some JavaScript,
// then creates a screenshot of Diagram with makeImageData, plus a screenshot of the page.
const puppeteer = require('puppeteer');
const fs = require('fs');
const parseDataUrl = (dataUrl) => {
const matches = dataUrl.match(/^data:(.+);base64,(.+)$/);
if (matches.length !== 3) {
throw new Error('Could not parse data URL.');
}
return { mime: matches[1], buffer: Buffer.from(matches[2], 'base64') };
};
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
// Point to a version of go.js, either a local file or one on the web at a CDN
await page.addScriptTag({
url: 'https://unpkg.com/gojs'
// path: '../../release/go.js'
});
// Create HTML for the page:
page.setContent('&lt;div id="myDiagramDiv" style="border: solid 1px black; width:400px; height:400px"&gt;&lt;/div&gt;');
// Set up a Diagram, and return the result of makeImageData:
const imageData = await page.evaluate(() => {
var $ = go.GraphObject.make;
var myDiagram = $(go.Diagram, "myDiagramDiv",
{
"animationManager.isEnabled": false,
"undoManager.isEnabled": true // enable undo &amp; redo
});
// define a simple Node template
myDiagram.nodeTemplate =
$(go.Node, "Auto", // the Shape will go around the TextBlock
$(go.Shape, "RoundedRectangle", { strokeWidth: 0 },
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 8 },
new go.Binding("text", "key"))
);
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" }
]);
return myDiagram.makeImageData();
});
// Output the GoJS makeImageData as a .png:
const { buffer } = parseDataUrl(imageData);
fs.writeFileSync('diagram-screenshot.png', buffer, 'base64');
// Output a page screenshot
await page.screenshot({ path: 'page-screenshot.png' });
await browser.close();
})();
</pre>
<p>
You can also use Puppeteer to fetch live HTML pages and do the same operations:
</p>
<pre class="lang-js">
// This example loads a web page with a GoJS diagram,
// then creates a screenshot of the Diagram with makeImageData, plus a screenshot of the page.
const puppeteer = require('puppeteer');
const fs = require('fs');
const parseDataUrl = (dataUrl) => {
const matches = dataUrl.match(/^data:(.+);base64,(.+)$/);
if (matches.length !== 3) {
throw new Error('Could not parse data URL.');
}
return { mime: matches[1], buffer: Buffer.from(matches[2], 'base64') };
};
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
// This does not have to be a page on the web, it can be a localhost page, or file://
await page.goto('https://gojs.net/samples/orgChartEditor.html', {
waitUntil: 'networkidle2' // ensures images are loaded
});
const imageData = await page.evaluate(() => {
window.myDiagram.animationManager.stopAnimation();
return window.myDiagram.makeImageData({
background: window.myDiagram.div.style.backgroundColor
});
});
// Output the GoJS makeImageData as a .png:
const { buffer } = parseDataUrl(imageData);
fs.writeFileSync('diagram-screenshot.png', buffer, 'base64');
// Output a page screenshot
await page.screenshot({ path: 'page-screenshot.png' });
await browser.close();
})();
</pre>
</div>
</div>
</body>
</html>
+368
View File
@@ -0,0 +1,368 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Shapes -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="../extensions/Figures.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Shapes</h1>
<p>
Use the <a>Shape</a> class to paint a geometrical figure.
You can control what kind of shape is drawn and how it is stroked and filled.
</p>
<p>
Shapes, like <a>TextBlock</a>s and <a>Picture</a>s, are "atomic" objects -- they cannot contain any other objects.
So a Shape will never draw some text or an image.
</p>
<p>
In these simplistic demonstrations, the code programmatically creates a Part and adds it to the Diagram.
Once you learn about models and data binding you will generally not create parts (nodes or links) programmatically.
</p>
<h2 id="Figures">Figures</h2>
<p>
You can set the <a>Shape.figure</a> property to commonly named kinds of shapes.
When using <a>GraphObject,make</a>, you can pass the figure name as a string argument.
You may also need to set the <a>GraphObject.desiredSize</a> or <a>GraphObject.width</a> and <a>GraphObject.height</a> properties,
although it is also common to have the size determined by the Panel that the shape is in.
</p>
<p>
Here are several of the most often used Shape figures:
</p>
<pre class="lang-js" id="figureShapes">
diagram.add(
$(go.Part, "Horizontal",
$(go.Shape, "Rectangle", { width: 40, height: 60, margin: 4, fill: null }),
$(go.Shape, "RoundedRectangle", { width: 40, height: 60, margin: 4, fill: null }),
$(go.Shape, "Ellipse", { width: 40, height: 60, margin: 4, fill: null }),
$(go.Shape, "Diamond", { width: 40, height: 60, margin: 4, fill: null }),
$(go.Shape, "TriangleRight", { width: 40, height: 60, margin: 4, fill: null }),
$(go.Shape, "TriangleDown", { width: 40, height: 60, margin: 4, fill: null }),
$(go.Shape, "TriangleLeft", { width: 40, height: 60, margin: 4, fill: null }),
$(go.Shape, "TriangleUp", { width: 40, height: 60, margin: 4, fill: null }),
$(go.Shape, "MinusLine", { width: 40, height: 60, margin: 4, fill: null }),
$(go.Shape, "PlusLine", { width: 40, height: 60, margin: 4, fill: null }),
$(go.Shape, "XLine", { width: 40, height: 60, margin: 4, fill: null })
));
</pre>
<script>goCode("figureShapes", 600, 100)</script>
<p>
You can see all of the named geometrical figures in the
<a href="../samples/shapes.html" target="samples">shapes</a> sample.
Some of the most commonly used figures are predefined in the <b>GoJS</b> library.
But most figures are defined in the <a href="../extensions/Figures.js" target="_blank">Figures.js</a> file
in the extensions directory.
</p>
<h2 id="FillAndStrokes">Fill and Strokes</h2>
<p>
The <a>Shape.stroke</a> property specifies the brush used to draw the shape's outline.
The <a>Shape.fill</a> property specifies the brush used to fill the shape's outline.
Additional "stroke..." properties also control how the shape's outline is drawn.
The most common such property is <a>Shape.strokeWidth</a>.
</p>
<pre class="lang-js" id="strokedShapes">
diagram.add(
$(go.Part, "Horizontal",
$(go.Shape, { figure: "Club", width: 40, height: 40, margin: 4
}), // default fill and stroke are "black"
$(go.Shape, { figure: "Club", width: 40, height: 40, margin: 4,
fill: "green" }),
$(go.Shape, { figure: "Club", width: 40, height: 40, margin: 4,
fill: "green", stroke: null }),
$(go.Shape, { figure: "Club", width: 40, height: 40, margin: 4,
fill: null, stroke: "green" }),
$(go.Shape, { figure: "Club", width: 40, height: 40, margin: 4,
fill: null, stroke: "green", strokeWidth: 3 }),
$(go.Shape, { figure: "Club", width: 40, height: 40, margin: 4,
fill: null, stroke: "green", strokeWidth: 6 }),
$(go.Shape, { figure: "Club", width: 40, height: 40, margin: 4,
fill: "green", background: "orange" })
));
</pre>
<script>goCode("strokedShapes", 600, 100)</script>
<p>
The <a>Shape.stroke</a> and <a>Shape.fill</a> properties take <a>Brush</a>es
but most often are given a CSS color string to denote solid color brushes.
These two properties default to a solid black brush.
However it is common to assign one of them to be either null or "transparent".
A null brush means that nothing is drawn for that stroke or fill.
A transparent brush produces the same appearance but different hit-testing behavior.
A shape with a null <a>Shape.fill</a> produces a "hollow" shape -- clicking inside
the shape will not "hit" that shape and thus not select the <a>Node</a> that that shape is in.
But a shape with a transparent fill produces a "filled" shape -- a mouse event inside the
shape will "hit" that shape.
</p>
<pre class="lang-js" class="lang-js" id="fill">
diagram.div.style.background = "lightgray";
diagram.add(
$(go.Part, "Table",
$(go.Shape, { row: 0, column: 0, figure: "Club", width: 60, height: 60, margin: 4,
fill: "green" }),
$(go.TextBlock, "green", { row: 1, column: 0 }),
$(go.Shape, { row: 0, column: 1, figure: "Club", width: 60, height: 60, margin: 4,
fill: "white" }),
$(go.TextBlock, "white", { row: 1, column: 1 }),
$(go.Shape, { row: 0, column: 2, figure: "Club", width: 60, height: 60, margin: 4,
fill: "transparent" }),
$(go.TextBlock, "transparent", { row: 1, column: 2 }),
$(go.Shape, { row: 0, column: 3, figure: "Club", width: 60, height: 60, margin: 4,
fill: null }),
$(go.TextBlock, "null", { row: 1, column: 3 })
));
</pre>
<script>goCode("fill", 600, 100)</script>
<p>
Try clicking inside each of the shapes to see which ones will respond to the click and cause the whole panel to be selected.
Note that with the "transparent" fill you can see the diagram background, yet when you click in it you "hit" the Shape.
Only the last one, with a null fill, is truly "hollow".
Clicking in the last shape will only result in a click on the diagram background, unless you click on the stroke outline.
</p>
<h2 id="Geometry">Geometry</h2>
<p>
Every <a>Shape</a> gets its "shape" from the <a>Geometry</a> that it uses.
A Geometry is just a saved description of how to draw some lines given a set of points.
Setting <a>Shape.figure</a> uses a named predefined geometry that can be parameterized.
In general it is most efficient to give a Shape a Geometry rather than giving it a figure.
</p>
<p>
If you want something different from all of the predefined figures in <b>GoJS</b>,
you can construct your own Geometry and set <a>Shape.geometry</a>.
One way of building your own <a>Geometry</a> is by building <a>PathFigure</a>s
consisting of <a>PathSegment</a>s.
This is often necessary when building a geometry whose points are computed based on some data.
</p>
<p>
But an easier way to create constant geometries is by
calling <a>Geometry,parse</a> to read a string that has a geometry-defining path expression,
or to set <a>Shape.geometryString</a> to such a string.
These expressions have commands for moving an imaginary "pen".
The syntax for geometry paths is documented in the <a href="geometry.html">Geometry Path Strings</a> page.
</p>
<p>
This example creates a Geometry that looks like the letter "W"
and uses it in several Shape objects with different stroke characteristics.
Geometry objects may be shared by multiple Shapes.
Note that there may be no need to specify the <a>GraphObject.desiredSize</a> or <a>GraphObject.width</a> and <a>GraphObject.height</a>,
because the Geometry defines its own size.
If the size is set or if it is imposed by the containing Panel, the effective geometry is determined by the <a>Shape.geometryStretch</a> property.
Depending on the value of the geometryStretch property, this may result in extra empty space or the clipping of the shape.
</p>
<pre class="lang-js" id="geometries">
var W_geometry = go.Geometry.parse("M 0,0 L 10,50 20,10 30,50 40,0", false);
diagram.add(
$(go.Part, "Horizontal",
$(go.Shape, { geometry: W_geometry, strokeWidth: 2 }),
$(go.Shape, { geometry: W_geometry, stroke: "blue", strokeWidth: 10,
strokeJoin: "miter", strokeCap: "butt" }),
$(go.Shape, { geometry: W_geometry, stroke: "blue", strokeWidth: 10,
strokeJoin: "miter", strokeCap: "round" }),
$(go.Shape, { geometry: W_geometry, stroke: "blue", strokeWidth: 10,
strokeJoin: "miter", strokeCap: "square" }),
$(go.Shape, { geometry: W_geometry, stroke: "green", strokeWidth: 10,
strokeJoin: "bevel", strokeCap: "butt" }),
$(go.Shape, { geometry: W_geometry, stroke: "green", strokeWidth: 10,
strokeJoin: "bevel", strokeCap: "round" }),
$(go.Shape, { geometry: W_geometry, stroke: "green", strokeWidth: 10,
strokeJoin: "bevel", strokeCap: "square" }),
$(go.Shape, { geometry: W_geometry, stroke: "red", strokeWidth: 10,
strokeJoin: "round", strokeCap: "butt" }),
$(go.Shape, { geometry: W_geometry, stroke: "red", strokeWidth: 10,
strokeJoin: "round", strokeCap: "round" }),
$(go.Shape, { geometry: W_geometry, stroke: "red", strokeWidth: 10,
strokeJoin: "round", strokeCap: "square" }),
$(go.Shape, { geometry: W_geometry, stroke: "purple", strokeWidth: 2,
strokeDashArray: [4, 2] }),
$(go.Shape, { geometry: W_geometry, stroke: "purple", strokeWidth: 2,
strokeDashArray: [6, 6, 2, 2] })
));
</pre>
<script>goCode("geometries", 600, 100)</script>
<h2 id="AngleAndScale">Angle and Scale</h2>
<p>
Besides setting the <a>GraphObject.desiredSize</a> or <a>GraphObject.width</a> and <a>GraphObject.height</a> to declare the size of a <a>Shape</a>,
you can also set other properties to affect the appearance.
For example, you can set the <a>GraphObject.angle</a> and <a>GraphObject.scale</a> properties.
</p>
<pre class="lang-js" id="transformedShapes">
diagram.add(
$(go.Part, "Table",
$(go.Shape, { row: 0, column: 1,
figure: "Club", fill: "green", width: 40, height: 40,
}), // default angle is zero; default scale is one
$(go.Shape, { row: 0, column: 2,
figure: "Club", fill: "green", width: 40, height: 40,
angle: 30 }),
$(go.Shape, { row: 0, column: 3,
figure: "Club", fill: "green", width: 40, height: 40,
scale: 1.5 }),
$(go.Shape, { row: 0, column: 4,
figure: "Club", fill: "green", width: 40, height: 40,
angle: 30, scale: 1.5 })
));
</pre>
<script>goCode("transformedShapes", 600, 100)</script>
<p>
The <a>Shape.fill</a> and <a>GraphObject.background</a> brushes scale and rotate along with the shape.
The <a>GraphObject.areaBackground</a> is drawn in the containing panel's coordinates,
so it is not affected by the object's scale or angle.
</p>
<p>
The following two shapes each use three separate linear gradient brushes, one for each of the three properties.
Note the unrotated shape on the left. Because its <a>GraphObject.background</a> brush is opaque,
you cannot see the <a>GraphObject.areaBackground</a> brush that fills the same area behind it.
</p>
<pre class="lang-js" id="backgrounds">
var bluered = $(go.Brush, "Linear", { 0.0: "blue", 1.0: "red" });
var yellowgreen = $(go.Brush, "Linear", { 0.0: "yellow", 1.0: "green" });
var grays = $(go.Brush, "Linear", { 0.0: "black", 1.0: "lightgray" });
diagram.add(
$(go.Part, "Table",
$(go.Shape, { row: 0, column: 0,
figure: "Club", width: 40, height: 40, angle: 0, scale: 1.5,
fill: bluered,
background: yellowgreen,
areaBackground: grays
}),
$(go.Shape, { row: 0, column: 1, width: 10, fill: null, stroke: null }),
$(go.Shape, { row: 0, column: 2,
figure: "Club", width: 40, height: 40, angle: 45, scale: 1.5,
fill: bluered,
background: yellowgreen,
areaBackground: grays
})
));
</pre>
<script>goCode("backgrounds", 600, 120)</script>
<h2 id="CustomFigures">Custom Figures</h2>
<p>
As shown above, one can easily create custom shapes just by setting <a>Shape.geometry</a> or <a>Shape.geometryString</a>.
This is particularly convenient when importing SVG.
However it is also possible to define additional named figures, which is convenient when you want to be able to easily
specify or change the geometry of an existing Shape by setting or data binding the <a>Shape.figure</a> property.
</p>
<p>
The static function <a>Shape,defineFigureGenerator</a> can be used to define new figure names.
The second argument is a function that is called with the <a>Shape</a> and the expected width and height
in order to generate and return a <a>Geometry</a>.
This permits parameterization of the geometry based on properties of the Shape and the expected size.
In particular, the <a>Shape.parameter1</a> and <a>Shape.parameter2</a> properties can be considered,
in addition to the width and height, while producing the Geometry.
To be valid, the generated Geometry bounds must be equal to or less than the supplied width and height.
</p>
<pre class="lang-js" id="defineFigure">
go.Shape.defineFigureGenerator("RoundedTopRectangle", function(shape, w, h) {
// this figure takes one parameter, the size of the corner
var p1 = 5; // default corner size
if (shape !== null) {
var param1 = shape.parameter1;
if (!isNaN(param1) && param1 >= 0) p1 = param1; // can't be negative or NaN
}
p1 = Math.min(p1, w / 2);
p1 = Math.min(p1, h / 2); // limit by whole height or by half height?
var geo = new go.Geometry();
// a single figure consisting of straight lines and quarter-circle arcs
geo.add(new go.PathFigure(0, p1)
.add(new go.PathSegment(go.PathSegment.Arc, 180, 90, p1, p1, p1, p1))
.add(new go.PathSegment(go.PathSegment.Line, w - p1, 0))
.add(new go.PathSegment(go.PathSegment.Arc, 270, 90, w - p1, p1, p1, p1))
.add(new go.PathSegment(go.PathSegment.Line, w, h))
.add(new go.PathSegment(go.PathSegment.Line, 0, h).close()));
// don't intersect with two top corners when used in an "Auto" Panel
geo.spot1 = new go.Spot(0, 0, 0.3 * p1, 0.3 * p1);
geo.spot2 = new go.Spot(1, 1, -0.3 * p1, 0);
return geo;
});
go.Shape.defineFigureGenerator("RoundedBottomRectangle", function(shape, w, h) {
// this figure takes one parameter, the size of the corner
var p1 = 5; // default corner size
if (shape !== null) {
var param1 = shape.parameter1;
if (!isNaN(param1) && param1 >= 0) p1 = param1; // can't be negative or NaN
}
p1 = Math.min(p1, w / 2);
p1 = Math.min(p1, h / 2); // limit by whole height or by half height?
var geo = new go.Geometry();
// a single figure consisting of straight lines and quarter-circle arcs
geo.add(new go.PathFigure(0, 0)
.add(new go.PathSegment(go.PathSegment.Line, w, 0))
.add(new go.PathSegment(go.PathSegment.Line, w, h - p1))
.add(new go.PathSegment(go.PathSegment.Arc, 0, 90, w - p1, h - p1, p1, p1))
.add(new go.PathSegment(go.PathSegment.Line, p1, h))
.add(new go.PathSegment(go.PathSegment.Arc, 90, 90, p1, h - p1, p1, p1).close()));
// don't intersect with two bottom corners when used in an "Auto" Panel
geo.spot1 = new go.Spot(0, 0, 0.3 * p1, 0);
geo.spot2 = new go.Spot(1, 1, -0.3 * p1, -0.3 * p1);
return geo;
});
diagram.nodeTemplate =
$(go.Part, "Spot",
{
selectionAdorned: false, // don't show the standard selection handle
resizable: true, resizeObjectName: "SHAPE", // user can resize the Shape
rotatable: true, rotateObjectName: "SHAPE", // user can rotate the Shape
// without rotating the label
},
$(go.Shape,
{
name: "SHAPE",
fill: $(go.Brush, "Linear", { 0.0: "white", 1.0: "gray" }),
desiredSize: new go.Size(100, 50)
},
new go.Binding("figure", "fig"),
new go.Binding("parameter1", "p1")),
$(go.Panel, "Vertical",
$(go.TextBlock,
new go.Binding("text", "fig")),
$(go.TextBlock, { stroke: "blue" },
new go.Binding("text", "parameter1", function(p1) { return p1; }).ofObject("SHAPE"))
)
);
diagram.model = new go.Model([
{ fig: "RoundedTopRectangle" },
{ fig: "RoundedTopRectangle", p1: 0 },
{ fig: "RoundedTopRectangle", p1: 3 },
{ fig: "RoundedTopRectangle", p1: 10 },
{ fig: "RoundedTopRectangle", p1: 50 },
{ fig: "RoundedTopRectangle", p1: 250 },
{ fig: "RoundedBottomRectangle" },
{ fig: "RoundedBottomRectangle", p1: 0 },
{ fig: "RoundedBottomRectangle", p1: 3 },
{ fig: "RoundedBottomRectangle", p1: 10 },
{ fig: "RoundedBottomRectangle", p1: 50 },
{ fig: "RoundedBottomRectangle", p1: 250 }
]);
</pre>
<script>goCode("defineFigure", 700, 300)</script>
<p>
Note how the <a>Shape.parameter1</a> property, data bound to the "p1" property, controls how rounded the corners are.
The definition of each figure limits the roundedness based on the actual size of the geometry.
You can see the effects by resizing the last shape -- the curve on the shape with p1==250 can be huge if the shape becomes huge.
</p>
<p>
You can find the definitions for many figures at: <a href="../extensions/Figures.js" target="_blank">Figures.js</a>.
The "RoundedTopRectangle" and "RoundedBottomRectangle" figures shown above are available at:
<a href="../extensions/RoundedRectangles.js" target="_blank">RoundedRectangles.js</a>.
</p>
</div>
</div>
</body>
</html>
+155
View File
@@ -0,0 +1,155 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Sized Groups -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Groups Without Placeholders</h1>
<p>
Although it is very common to use a <a>Placeholder</a> inside a <a>Group</a>, it is not required.
Using a <a>Shape</a>, for example, instead of a <a>Placeholder</a> permits features such as
having a group maintain a fixed size, independent of the sizes and positions of its member nodes,
and even when there are no member nodes at all.
It also may allow the user to resize the "area" if that functionality is desired.
</p>
<h2 id="FixedSizeGroups">Fixed size Groups</h2>
<p>
Not using a <a>Placeholder</a> in a <a>Group</a> means that you have to maintain the size and position of the group,
because it cannot depend on the size and position of its member nodes.
In these examples we will explicitly set and/or bind the <a>Part.location</a> of the nodes, including the groups.
The <a>Shape</a> that replaces the Placeholder in the group's template should also get its <a>GraphObject.desiredSize</a> set or bound.
</p>
<pre class="lang-js" id="fixedSize">
diagram.nodeTemplate =
$(go.Node,
new go.Binding("location", "loc", go.Point.parse),
$(go.TextBlock,
new go.Binding("text", "key"))
);
diagram.groupTemplate =
$(go.Group, "Vertical",
{ selectionObjectName: "PH",
locationObjectName: "PH" },
new go.Binding("location", "loc", go.Point.parse),
$(go.TextBlock, // group title
{ font: "Bold 12pt Sans-Serif" },
new go.Binding("text", "key")),
$(go.Shape, // using a Shape instead of a Placeholder
{ name: "PH",
fill: "lightyellow" },
new go.Binding("desiredSize", "size", go.Size.parse))
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", group: "Omega", loc: "75 75" },
{ key: "Gamma", group: "Omega", loc: "125 75" },
{ key: "Omega", isGroup: true, loc: "50 50", size: "150 50" },
{ key: "Delta", loc: "200 0" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }, // from outside the Group to inside it
{ from: "Beta", to: "Gamma" }, // this link is a member of the Group
{ from: "Omega", to: "Delta" } // from the Group to a Node
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
diagram.select(diagram.findNodeForKey("Omega"));
</pre>
<script>goCode("fixedSize", 600, 150)</script>
<p>
Note that moving the "Beta" or "Gamma" nodes does <i>not</i> change the size or position of the "Omega" group.
However moving or copying or deleting the group includes those member nodes in the operation.
</p>
<p>
One can control where the user may drag member nodes.
For example, the <a href="../samples/swimlanes.html" target="_blank">Swim Lanes</a> sample
demonstrates a custom <a>Part.dragComputation</a> function that limits the
motion of a member node to stay within its containing group.
</p>
<h2 id="ResizableGroups">Resizable Groups</h2>
<p>
You can make the main shape resizable by the user.
(At the current time groups are not rotatable.)
</p>
<p>
This example also makes the <a>Part.location</a> and <a>GraphObject.desiredSize</a> data bindings TwoWay,
so that as the user moves groups or resizes their main shapes, the data in the model is updated automatically.
</p>
<pre class="lang-js" id="resizable">
diagram.nodeTemplate =
$(go.Node,
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
$(go.TextBlock,
new go.Binding("text", "key"))
);
diagram.groupTemplate =
$(go.Group, "Vertical",
{ selectionObjectName: "PH",
locationObjectName: "PH",
resizable: true,
resizeObjectName: "PH" },
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
$(go.TextBlock, // group title
{ font: "Bold 12pt Sans-Serif" },
new go.Binding("text", "key")),
$(go.Shape, // using a Shape instead of a Placeholder
{ name: "PH",
fill: "lightyellow" },
new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify))
);
var nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", group: "Omega", loc: "75 75" },
{ key: "Gamma", group: "Omega", loc: "125 75" },
{ key: "Omega", isGroup: true, loc: "50 50", size: "150 50" },
{ key: "Delta", loc: "200 0" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }, // from outside the Group to inside it
{ from: "Beta", to: "Gamma" }, // this link is a member of the Group
{ from: "Omega", to: "Delta" } // from the Group to a Node
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
diagram.select(diagram.findNodeForKey("Omega"));
</pre>
<script>goCode("resizable", 600, 150)</script>
<p>
It is also possible to control how the user resizes a group.
For example, the <a href="../samples/swimlanes.html" target="_blank">Swim Lanes</a> sample
demonstrates a custom <a>ResizingTool</a> that limits how small each lane can go.
It also demonstrates a custom <a>Adornment</a> that has only two resize handles.
</p>
<h2 id="ContainersThatAreNotGroups">Containers that are not Groups</h2>
<p>
You do not have to use <a>Group</a>s as the only mechanism by which to organize a collection of <a>Part</a>s.
For example, the <a href="../samples/swimBands.html" target="_blank">Layer Bands</a> sample demonstrates
how some <a>Layout</a>s can be customized to automatically maintain the positions and sizes of special parts
that are in the background, appearing to surround the nodes that belong to each layout layer.
</p>
<p>
Not using <a>Group</a>s also means that it becomes possible to avoid some of the restrictions inherent in Groups,
such as the limitation that each Part can have at most one <a>Part.containingGroup</a>.
The <a href="../samples/sharedStates.html" target="_blank">Shared States</a> sample demonstrates how one can make
it appear that more than one "group" can contain a node.
However, this requires some additional custom <a>Tool</a>s and custom <a>Layout</a>s,
or always explicitly setting/binding the location and size of every node and "group".
</p>
</div>
</div>
</body>
</html>
+315
View File
@@ -0,0 +1,315 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Sizing of GraphObjects -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="../extensions/Figures.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Sizing GraphObjects</h1>
<p>
The size of a <a>GraphObject</a> is determined by the values of the <a>GraphObject.desiredSize</a>,
<a>GraphObject.minSize</a>, <a>GraphObject.maxSize</a> and <a>GraphObject.stretch</a> properties.
The actual size of an object after its containing panel measures and arranges it is given by several
read-only properties: <a>GraphObject.naturalBounds</a>, <a>GraphObject.measuredBounds</a>, and <a>GraphObject.actualBounds</a>.
</p>
<p>
The <a>GraphObject.width</a> property is exactly the same as the <a>Size.width</a> component of the <a>GraphObject.desiredSize</a>.
Similarly, the <a>GraphObject.height</a> property corresponds to the desiredSize's height.
The default value of <a>GraphObject.desiredSize</a> is <code>(NaN, NaN)</code> -- meaning that the size must be computed.
One can set the width to a real number and leave the height to be <code>NaN</code>, or vice-versa.
</p>
<p>
Users can also change the size of an object within a Part via the <a>ResizingTool</a>: Introduction to the <a href="tools.html#ResizingTool">ResizingTool</a>.
</p>
<h3 id="DesiredSizeMinSizeAndMaxSize">DesiredSize, MinSize, and MaxSize</h3>
<p>
When the <a>GraphObject.desiredSize</a> property is set to real numbers it gets that as its natural size.
When the desiredSize property is not set but there is a <a>GraphObject.stretch</a> value,
it will get the size of the available space.
When desiredSize is not set and there is no stretch,
an object prefers being its natural size,
based on the type of object that it is and the other properties that it has.
</p>
<p>
But the effective width and effective height, whether given by desiredSize or computed,
are each constrained by the <a>GraphObject.maxSize</a> and by the <a>GraphObject.minSize</a>.
The minimum size takes precedence over the maximum size in case of conflict.
</p>
<p>
The size for a GraphObject in a Table <a>Panel</a> may also be constrained by the width
of the column and the height of the row that the object is in.
</p>
<pre class="lang-js" id="shapeSizes">
diagram.add(
$(go.Part,
$(go.Panel, "Table",
{ defaultAlignment: go.Spot.Left },
$(go.RowColumnDefinition, { column: 0, width: 200 }),
$(go.RowColumnDefinition, { column: 1, width: 15 }),
$(go.Shape, "Rectangle",
{ row: 0, column: 0, fill: "green",
width: 100, height: 20 }),
$(go.TextBlock, { row: 0, column: 2,
text: "desiredSize: 100x20, no minSize, no maxSize" }),
$(go.Shape, "Rectangle",
{ row: 1, column: 0, fill: "red",
width: 100, height: 20,
minSize: new go.Size(150, 10) }),
$(go.TextBlock, { row: 1, column: 2,
text: "desired: 100x20, min: 150x10" }),
$(go.Shape, "Rectangle",
{ row: 2, column: 0, fill: "yellow",
width: 100, height: 20,
maxSize: new go.Size(50, 300) }),
$(go.TextBlock, { row: 2, column: 2,
text: "desired: 100x20, max: 50x300" }),
$(go.Shape, "Rectangle",
{ row: 3, column: 0, fill: "red",
width: 100, height: 20,
minSize: new go.Size(150, 10), maxSize: new go.Size(50, 300) }),
$(go.TextBlock, { row: 3, column: 2,
text: "desired: 100x20, min: 150x10, max: 50x300" })
)
));
</pre>
<script>goCode("shapeSizes", 600, 120)</script>
<h3 id="MeasuredAndActualSizes">Measured and Actual Sizes</h3>
<p>
Every GraphObject also has a <a>GraphObject.measuredBounds</a>,
which describes how big the object seems to be, and a <a>GraphObject.actualBounds</a>,
which describes the position and size of an object.
These read-only properties take into account any non-zero <a>GraphObject.angle</a> or non-unitary <a>GraphObject.scale</a>.
These measurements are in the containing <a>Panel</a>'s coordinate system.
</p>
<pre class="lang-js" id="sizedShapes">
function getSizeString(s) {
return s.width.toFixed(2) + "x" + s.height.toFixed(2);
}
var table =
$(go.Part, "Table",
$(go.Shape, { name: "A", row: 0, column: 1,
figure: "Club", fill: "green", background: "lightgray",
width: 40, height: 40,
}), // default angle is zero; default scale is one
$(go.Shape, { name: "B", row: 0, column: 2,
figure: "Club", fill: "green", background: "lightgray",
width: 40, height: 40,
angle: 30 }),
$(go.Shape, { name: "C", row: 0, column: 3,
figure: "Club", fill: "green", background: "lightgray",
width: 40, height: 40,
scale: 1.5 }),
$(go.Shape, { name: "D", row: 0, column: 4,
figure: "Club", fill: "green", background: "lightgray",
width: 40, height: 40,
angle: 30, scale: 1.5 }),
$(go.TextBlock, { row: 1, column: 1, margin: 4 },
new go.Binding("text", "naturalBounds", getSizeString).ofObject("A")),
$(go.TextBlock, { row: 1, column: 2, margin: 4 },
new go.Binding("text", "naturalBounds", getSizeString).ofObject("B")),
$(go.TextBlock, { row: 1, column: 3, margin: 4 },
new go.Binding("text", "naturalBounds", getSizeString).ofObject("C")),
$(go.TextBlock, { row: 1, column: 4, margin: 4 },
new go.Binding("text", "naturalBounds", getSizeString).ofObject("D")),
$(go.TextBlock, { row: 2, column: 1, margin: 4 },
new go.Binding("text", "measuredBounds", getSizeString).ofObject("A")),
$(go.TextBlock, { row: 2, column: 2, margin: 4 },
new go.Binding("text", "measuredBounds", getSizeString).ofObject("B")),
$(go.TextBlock, { row: 2, column: 3, margin: 4 },
new go.Binding("text", "measuredBounds", getSizeString).ofObject("C")),
$(go.TextBlock, { row: 2, column: 4, margin: 4 },
new go.Binding("text", "measuredBounds", getSizeString).ofObject("D")),
$(go.TextBlock, { row: 3, column: 1, margin: 4 },
new go.Binding("text", "actualBounds", getSizeString).ofObject("A")),
$(go.TextBlock, { row: 3, column: 2, margin: 4 },
new go.Binding("text", "actualBounds", getSizeString).ofObject("B")),
$(go.TextBlock, { row: 3, column: 3, margin: 4 },
new go.Binding("text", "actualBounds", getSizeString).ofObject("C")),
$(go.TextBlock, { row: 3, column: 4, margin: 4 },
new go.Binding("text", "actualBounds", getSizeString).ofObject("D")),
$(go.TextBlock, "naturalBounds:", { row: 1, column: 0, alignment: go.Spot.Left }),
$(go.TextBlock, "measuredBounds:", { row: 2, column: 0, alignment: go.Spot.Left }),
$(go.TextBlock, "actualBounds:", { row: 3, column: 0, alignment: go.Spot.Left })
);
diagram.add(table);
setTimeout(function() {
table.data = {}; // cause bindings to be evaluated after Shapes are measured
}, 500);
</pre>
<script>goCode("sizedShapes", 600, 180)</script>
<p>
Note that the size of the regular 40x40 shape is 41x41.
The additional size is due to the thickness of the pen (<a>Shape.strokeWidth</a>) used to outline the shape.
Rotating or increasing the scale causes the 40x40 shape to actually take up significantly more space.
</p>
<p>
To summarize: the <a>GraphObject.desiredSize</a> (a.k.a. <a>GraphObject.width</a> and <a>GraphObject.height</a>)
and the <a>GraphObject.naturalBounds</a> are in the object's local coordinate system.
The <a>GraphObject.minSize</a>, <a>GraphObject.maxSize</a>, <a>GraphObject.margin</a>, <a>GraphObject.measuredBounds</a>, and
<a>GraphObject.actualBounds</a> are all in the containing <a>Panel</a>'s coordinate system, or in document
coordinates if there is no such panel because it is a <a>Part</a>.
</p>
<h3 id="StretchingOfGraphObjects">Stretching of GraphObjects</h3>
<p>
When you specify a <a>GraphObject.stretch</a> value other than <a>GraphObject,None</a>,
the object will stretch or contract to fill the available space.
However, the <a>GraphObject.maxSize</a> and <a>GraphObject.minSize</a> properties still limit the size.
</p>
<p>
But setting the <a>GraphObject.desiredSize</a> (or equivalently, the <a>GraphObject.width</a> and/or <a>GraphObject.height</a>)
will cause any stretch value to be ignored.
</p>
<p>
In the following examples the left column is constrained to have a width of 200.
</p>
<pre class="lang-js" id="stretchSizes">
diagram.add(
$(go.Part,
$(go.Panel, "Table",
{ defaultAlignment: go.Spot.Left },
$(go.RowColumnDefinition, { column: 0, width: 200 }),
$(go.RowColumnDefinition, { column: 1, width: 15 }),
$(go.Shape, "Rectangle",
{ row: 0, column: 0, fill: "green",
stretch: go.GraphObject.Fill }),
$(go.TextBlock, { row: 0, column: 2,
text: "stretch: Fill, no minSize, no maxSize" }),
$(go.Shape, "Rectangle",
{ row: 1, column: 0, fill: "red",
stretch: go.GraphObject.Fill,
minSize: new go.Size(150, 10) }),
$(go.TextBlock, { row: 1, column: 2,
text: "stretch: Fill, min: 150x10" }),
$(go.Shape, "Rectangle",
{ row: 2, column: 0, fill: "yellow",
stretch: go.GraphObject.Fill,
maxSize: new go.Size(50, 300) }),
$(go.TextBlock, { row: 2, column: 2,
text: "stretch: Fill, max: 50x300" }),
$(go.Shape, "Rectangle",
{ row: 3, column: 0, fill: "red",
stretch: go.GraphObject.Fill,
minSize: new go.Size(150, 10), maxSize: new go.Size(50, 300) }),
$(go.TextBlock, { row: 3, column: 2,
text: "stretch: Fill, min: 150x10, max: 50x300" }),
$(go.Shape, "Rectangle",
{ row: 4, column: 0, fill: "red",
width: 100, stretch: go.GraphObject.Fill }),
$(go.TextBlock, { row: 4, column: 2,
text: "desired width & stretch: ignore stretch" })
)
));
</pre>
<script>goCode("stretchSizes", 600, 120)</script>
<p>
To summarize, if <a>GraphObject.desiredSize</a> is set, any <a>GraphObject.stretch</a> is ignored.
If <a>GraphObject.maxSize</a> conflicts with that value, it takes precedence.
And if <a>GraphObject.minSize</a> conflicts with those values, it takes precedence.
The width values are constrained independently of the height values.
</p>
<h2 id="StretchAndAlignment">Stretch and Alignment</h2>
<p>
The size of a <a>GraphObject</a> in a <a>Panel</a> is determined by many factors.
The <a>GraphObject.stretch</a> property specifies whether the width and/or height should take up all
of the space given to it by the Panel.
When the width and/or height is not stretched to fill the given space,
the <a>GraphObject.alignment</a> property controls where the object is placed if it is smaller than available space.
One may also stretch the width while aligning vertically, just as one may also
stretch vertically while aligning along the X axis.
</p>
<p>
The alignment value for a GraphObject, if not given by the value of <a>GraphObject.alignment</a>, may be inherited.
If the object is in a Table Panel, the value may inherit from the RowColumnDefinitions of
the row and of the column that the object is in.
Finally the value may be inherited from the <a>Panel.defaultAlignment</a> property.
</p>
<p>
If you specify a fill stretch (horizontal or vertical or both) and an alignment, the alignment will be ignored.
Basically if an object is exactly the size that is available to it, there is only one position for it, so all alignments are the same.
</p>
<h3 id="AlignmentOfShapes">Alignment of Shapes</h3>
<pre class="lang-js" id="shapeAlignment">
diagram.add(
$(go.Part,
$(go.Panel, "Table",
{ defaultAlignment: go.Spot.Left },
$(go.RowColumnDefinition, { column: 0, width: 200 }),
$(go.RowColumnDefinition, { column: 1, width: 15 }),
$(go.Shape, "Rectangle",
{ row: 0, column: 0, fill: "lightblue",
width: 100, height: 20, alignment: go.Spot.Left }),
$(go.TextBlock, { row: 0, column: 2, text: "alignment: Left" }),
$(go.Shape, "Rectangle",
{ row: 1, column: 0, fill: "lightblue",
width: 100, height: 20, alignment: go.Spot.Center }),
$(go.TextBlock, { row: 1, column: 2, text: "alignment: Center" }),
$(go.Shape, "Rectangle",
{ row: 2, column: 0, fill: "lightblue",
width: 100, height: 20, alignment: go.Spot.Right }),
$(go.TextBlock, { row: 2, column: 2, text: "alignment: Right" }),
$(go.Shape, "Rectangle",
{ row: 3, column: 0, fill: "yellow",
height: 20, stretch: go.GraphObject.Horizontal }),
$(go.TextBlock, { row: 3, column: 2, text: "stretch: Horizontal" }),
$(go.Shape, "Rectangle",
{ row: 4, column: 0, fill: "yellow",
height: 20, stretch: go.GraphObject.Horizontal, alignment: go.Spot.Right }),
$(go.TextBlock, { row: 4, column: 2,
text: "stretch: Horizontal, ignore alignment" })
)
));
</pre>
<script>goCode("shapeAlignment", 600, 120)</script>
<p>
When the element is larger than the available space, the <a>GraphObject.alignment</a>
property still controls where the element is positioned.
However the element will be clipped to fit.
</p>
<p>
To make things clearer in the following examples we have made the shape stroke thicker
and added a margin to separate the shapes.
</p>
<pre class="lang-js" id="bigShapeAlignment">
diagram.add(
$(go.Part,
$(go.Panel, "Table",
{ defaultAlignment: go.Spot.Left },
$(go.RowColumnDefinition, { column: 0, width: 200 }),
$(go.RowColumnDefinition, { column: 1, width: 15 }),
$(go.Shape, "Rectangle",
{ row: 0, column: 0, fill: "lightblue", strokeWidth: 2,
width: 300, height: 20, margin: 2, alignment: go.Spot.Left }),
$(go.TextBlock, { row: 0, column: 2, text: "big obj alignment: Left" }),
$(go.Shape, "Rectangle",
{ row: 1, column: 0, fill: "lightblue", strokeWidth: 2,
width: 300, height: 20, margin: 2, alignment: go.Spot.Center }),
$(go.TextBlock, { row: 1, column: 2, text: "big obj alignment: Center" }),
$(go.Shape, "Rectangle",
{ row: 2, column: 0, fill: "lightblue", strokeWidth: 2,
width: 300, height: 20, margin: 2, alignment: go.Spot.Right }),
$(go.TextBlock, { row: 2, column: 2, text: "big obj alignment: Right" })
)
));
</pre>
<script>goCode("bigShapeAlignment", 600, 100)</script>
</div>
</div>
</body>
</html>
+155
View File
@@ -0,0 +1,155 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Building GoJS from Source -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Building GoJS from TypeScript Sources</h1>
<p>
All customers can use the <code>go.js</code> and <code>go-debug.js</code> files to run their app.
However, if you have purchased a license to the <b>GoJS</b> TypeScript source code, you can build your app from the TypeScript source.
Popular module bundlers such as Webpack are able to remove a number of modules from the source, provided you are not using them,
to shrink the size of the deliverable file.
</p>
<p>
Because the <code>go.js</code> and <code>go-debug.js</code> files are built in a custom process to reduce size,
using Webpack or another bundler with the GoJS source, even though it removes unused modules,
may result in a larger file size than just using <code>go.js</code>. However, TypeScript
authors may appreciate the feedback and debugging capabilities of building from source.
</p>
<h2 id="MinimalSourceAndMaximal">MinimalSource and MaximalSource projects</h2>
<p>
The GoJS kit has two subdirectories under the <code>/projects</code> directory,
<code>/minimalSource</code> and <code>/maximalSource</code>.
</p>
<p>
The <a href="https://github.com/NorthwoodsSoftware/GoJS/tree/master/projects/minimalSource">minimalSource</a>
project shows how to build GoJS while removing all possible optional modules.
The <a href="https://github.com/NorthwoodsSoftware/GoJS/tree/master/projects/maximalSource">maximalSource</a>
project shows how to build GoJS while matching the functionality of the released go.js and go-debug.js files.
</p>
<p>
Both projects require <code>webpack</code> and <code>npm</code> to run.
</p>
<p>
There is additionally <a href="https://github.com/NorthwoodsSoftware/GoJS/tree/master/projects/minimalSource">minimalSourceBrowserify</a>,
which shows how to build from source with Browserify instead of Webpack.
</p>
<h2 id="ClassesModules">Classes/modules that can be removed when building from source</h2>
<p>
Several classes, such as <code>CommandHandler</code> and the Tools,
may be essential to add to your project, while others such as all but one <code>Layout</code> and one type of <code>Model</code> can often be removed.
Below is a list of modules that webpack can remove from builds, provided they are not referenced in your code:
</p>
<ul>
<li><a>CommandHandler</a>, required for keyboard functionality.
<li><code>SVGSurface</code>, required to call <a>Diagram.makeSvg</a>
<li><a>TreeModel</a>
<li><a>GraphLinksModel</a>, without loading this module, the default Diagram.model is of type <code>Model</code>.
<li><a>Overview</a>
<li><a>Palette</a>
</ul>
Layouts:
<ul>
<li><a>GridLayout</a>
<li><a>TreeLayout</a>
<li><a>LayeredDigraphLayout</a>
<li><a>CircularLayout</a>
<li><a>ForceDirectedLayout,</a>
</ul>
Mouse-down tools:
<ul>
<li><a>ActionTool</a>
<li><a>RelinkingTool</a>
<li><a>LinkReshapingTool</a>
<li><a>ResizingTool</a>
<li><a>RotatingTool</a>
</ul>
Mouse-move tools:
<ul>
<li><a>LinkingTool</a>
<li><a>DraggingTool</a>
<li><a>DragSelectingTool</a>
<li><a>PanningTool</a>
</ul>
Mouse-up tools:
<ul>
<li><a>ContextMenuTool</a>
<li><a>TextEditingTool</a>
<li><a>ClickCreatingTool</a>
<li><a>ClickSelectingTool</a>
</ul>
<p>There are several "built in" Panel types, each of which is a <a>PanelLayout</a>. Some of these are required for building the source:</p>
<ul>
<li><code>PanelLayoutPosition</code>
<li><code>PanelLayoutVertical</code>
<li><code>PanelLayoutLink</code>
<li><code>PanelLayoutAuto</code>
<li><code>PanelLayoutGrid</code>
</ul>
However, it is possible to build without the following panel types:
<ul>
<li><code>PanelLayoutHorizontal</code>
<li><code>PanelLayoutSpot</code>
<li><code>PanelLayoutTable</code>
<li><code>PanelLayoutViewbox</code>
<li><code>PanelLayoutTableRow</code>
<li><code>PanelLayoutTableColumn</code>
<li><code>PanelLayoutGraduated</code>
</ul>
<p>The source index files demonstrate the necessary calls to <code>Panel.addPanelLayout</code> to include each panel type.</p>
<h2 id="OptionalClasses">Using the optional classes</h2>
<p>
Many of the classes simply need to be used to be included in source building.
For some functionality, like the Tools, CommandHandler, and SVGSurface, you need to make sure you explicitly initalize them.
Examples of this can be found in the <a href="..\projects\maximalSource\maximal-index.ts">maximal-index.ts</a> code
for the <a href="https://github.com/NorthwoodsSoftware/GoJS/tree/master/projects/maximalSource">maximalSource</a> project.
Doing so is not necessary with the full <code>go.js</code> library because the <code>go</code> object already has references to each.
</p>
<p>
A typical GoJS project is not expected to remove all or even most of these modules, and may use the majority of them.
Because Northwood's internal build process is optimized to use the Google Closure Compiler in Advanced Mode,
it may take considerable effort to produce a <code>go.js</code>
bundle from source that is smaller than the one we include in the project's release directory.
</p>
<h2 id="license">Important license information about GoJS source code</h2>
<p>
The GoJS source code is subject to the terms of our license,
contained in <a href="https://www.nwoods.com/sales/info/SoftwareLicenseAgreement.pdf">SoftwareLicenseAgreement.pdf</a>.
</p>
<p>
Do not use, release (deploy), or distribute the unminified source code.
To build GoJS for your own application you must use a popular obfuscation/minification tool,
such as the Google Closure Compiler.
</p>
</div>
</div>
</body>
</html>
+347
View File
@@ -0,0 +1,347 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Storage -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Storage</h1>
<p>
Storing GoJS model data in cloud storage is an excellent way to save, load, create, and delete diagram data files without worrying about local system concerns.
Interfacing with popular cloud storage services is made easy with the GoCloudStorage library.
</p>
<p>
The GoCloudStorage library is not pre-packaged with GoJS. You can find the GoCloudStorage library <a href="../projects/storage/lib/gcs.js">here</a>.
</p>
<p>
The GoCloudStorage class system lets developers easily store their GoJS diagram model data to popular cloud storage services. The
<a href="../api/symbols/GoCloudStorage.html">GoCloudStorage class</a> itself is an abstract class, never to be instantiated.
Instead, its subclasses are used, each interfacing with a different cloud storage service. Currently, the GoCloudStorage system supports Dropbox,
Google Drive, Microsoft OneDrive, and Local Storage. Class names are:
<ul>
<li><a href="../api/symbols/GoDropBox.html">GoDropBox</a></li>
<li><a href="../api/symbols/GoGoogleDrive.html">GoGoogleDrive</a></li>
<li><a href="../api/symbols/GoOneDrive.html">GoOneDrive</a></li>
<li><a href="../api/symbols/GoLocalStorage.html">GoLocalStorage</a></li>
</ul>
</p>
<h3 id="GoCloudStorageSubclassConstruction">GoCloudStorage Subclass Construction</h3>
<p>
This section provides a description of how to create an instance of a specific GoCloudStorage subclass, GoGoogleDrive. Due to the variable nature of
cloud storage service APIs, GoCloudStorage subclass constructor parameters and behavior vary. It is recommended you read the full
<a href="../api/index.html">documentation</a> for any GoCloudStorage subclass you wish to use.
</p>
<p>
First, ensure you have a <code>script</code> tag with the path to your <code>gcs.js</code> library. All GoCloudStorage
subclasses are defined in the namespace <code>gcs</code>.
</p>
<p>
<strong>Note</strong>: To use any GoCloudStorage subclass (except for <a href="../api/symbols/GoLocalStorage.html">GoLocalStorage</a>), you must
also include a <code>script</code> tag referencing the storage service provider JS library. For each subclass, these could be something like:
<ul>
<li><a href="../api/symbols/GoDropBox.html">GoDropBox</a>: https://unpkg.com/dropbox@2.5.13/dist/DropboxTeam-sdk.min.js </li>
<li><a href="../api/symbols/GoGoogleDrive.html">GoGoogleDrive</a>: https://apis.google.com/js/api.js</li>
<li><a href="../api/symbols/GoOneDrive.html">GoOneDrive</a>: https://js.live.net/v7.2/OneDrive.js </li>
</ul>
</p>
<p>
Here is a valid constructor call for GoGoogleDrive.
</p>
<pre class="lang-js" id="simple">
// Create a valid GoGoogleDrive instance
var ggd = new gcs.GoGoogleDrive(
diagrams, // managedDiagrams parameter
"16225356139-n64vtg7konuetna3of3mfcaj2iffhgmg.apps.googleusercontent.com", // clientId parameter
"AIzaSydBje3lBL67MMVKw467_pvuRg7_XMVGf18", // pickerApiKey parameter
defaultModel, // defaultModel parameter
"../projects/storage/goCloudStorageIcons/" // iconsRelativeDirectory parameter
);
</pre>
<p>
<strong>Note</strong>: All client ID's / API keys on this page are fabricated, and for example purposes only.
</p>
<p>
What are all these parameters? We'll step through them, one by one.
</p>
<h4 id="ManagedDiagrams">Managed Diagrams </h4>
<p>
The first parameter passed to the GoGoogleDrive constructor is something called <code>diagrams</code>. This is the parameter known to all GoCloudStorage subclasses
as <code><a href="../api/symbols/GoCloudStorage.html#managedDiagrams">managedDiagrams</a></code>. It is either an Array of
GoJS Diagrams or a single GoJS Diagram that this instance of GoCloudStorage (in this case, GoGoogleDrive) will manage data storage for.
This parameter is required.
</p>
<h4 id="ClientID">Client ID</h4>
<p>
The second parameter passed to the GoGoogleDrive constructor is a long string. This is the <code>
<a href="../api/symbols/GoCloudStorage.html#clientId">clientId</a></code> parameter, required by all GoCloudStorage subclasses (except
<a href="../api/symbols/GoLocalStorage.html">GoLocalStorage</a>). This ID tells the cloud storage provider (in this case, Google) and the user
what application is asking to manipulate stored file data (in this case, Google Drive file data).
</p>
<p>
This is usually given by the cloud storage provider's developer console or similar.
You will need to register an application with the storage provider (in this case, Google) to obtain this ID.
Read more below at <a href="#ObtainingClientIDs">Obtaining Client IDs</a>.
</p>
<h4 id="GooglePickerAPIKey">Google Picker API Key</h4>
<p>
This is a GoGoogleDrive-specific parameter. Some GoCloudStorage subclasses require parameters that others do not.
Again, it is recommended you read the full <a href="../api/index.html">documentation</a> for any GoCloudStorage subclass you wish to use.
</p>
<p>
GoGoogleDrive requires this key to allow for the familiar, Google Drive file picker interface during graphical file manipulation. Read more about this special parameter
in the full <code><a href="../api/symbols/GoGoogleDrive.html#pickerApiKey">GoGoogleDrive.pickerApiKey</a></code> documentation.
</p>
<h4 id="DefaultModel">Default Model </h4>
<p>
It is the default model data assigned to newly created diagrams with calls to
<code><a href="../api/symbols/GoCloudStorage.html#create">create</a></code>. Generally, this value is obtained with a call to
<a href="../api/symbols/Model.html#toJson">Diagram.model.toJson()</a>.
</p>
<p>
This is an optional parameter for all GoCloudStorage subclasses.
If no value is supplied during construction, this defaults to a new <a href="../api/symbols/GraphLinksModel.html">GraphLinksModel</a>.
</p>
<h4 id="IconsRelativeDirectory">Icons Relative Directory</h4>
<p>
To use commands that call a GoCloudStorage subclass' custom <code><a href="../api/symbols/GoCloudStorage.html#ui">ui</a></code>,
you must specify the directory in which the icons for storage services reside, relative to the directory your application page is. This is provided
by the optional <code><a href="../api/symbols/GoCloudStorage.html#iconsRelativeDirectory">iconsRelativeDirectory</a></code> parameter. The default value is
"../goCloudStorageIcons/".
</p>
<p>
Exactly what the UI looks like varies between GoCloudStorage subclasses, though it certainly contains references to storage service icons. Without providing this
parameter, it's likely the space where these images go will appear blank.
</p>
<p>
Please refer to the full <a href="../api/index.html">documentation</a> for details on class-specific UIs.
</p>
<h3 id="ObtainingClientIDs">Obtaining Client IDs</h3>
<p>
All GoCloudStorage subclasses (except <a href="../api/symbols/GoLocalStorage.html">GoLocalStorage</a>) require a client ID as a parameter during construction.
This lets the storage service provider (i.e. Google, Dropbox...) and the user know the identity of the application trying to manipulate their remote filesystems.
Therefore, obtaining a client ID for the storage service you wish to use is a requirement to using the corresponding GoCloudStorage subclass.
</p>
<p>
The process for this varies from service to service, though the general steps are the same.
</p>
<ol>
<li><strong>Register an account</strong></li>
<p>
If you do not already have an account with the storage service provider, make one.
</p>
<li><strong>Register a web application</strong></li>
<p>
This step varies most from service to service. Create and register an application with the storage service provider.
</p>
<li><strong>Locate your new application's Client ID</strong></li>
<p>
Your newly registered application has a Client ID -- a long string like the one we saw in <a href="#GoCloudStorageSubclassConstruction">GoCloudStorage
Subclass Construction</a>. Use this string as the <code><a href="../api/symbols/GoCloudStorage.html#clientId">clientId</a></code> parameter for your
instance of GoCloudStorage.
</p>
</ol>
<p>
These storage-specific pages can help walk you through the process of creating / registering an application with their service.
<ul>
<li><a href="https://developers.google.com/drive/v3/web/quickstart/js">GoGoogleDrive</a></li>
<li><a href="https://docs.microsoft.com/en-us/onedrive/developer/rest-api/getting-started/app-registration">Microsoft OneDrive (Graph)</a></li>
<li><a href="https://www.dropbox.com/developers">Dropbox</a></li>
</ul>
</p>
<h3 id="SavingLoadingData">Saving / Loading Data </h3>
<p>
Now that you have a working instance of a GoCloudStorage subclass, let's start saving and loading GoJS Diagram model data. We will continue with our
GoGoogleDrive example from <a href="#GoCloudStorageSubclassConstruction">GoCloudStorage Subclass Construction</a>, referring to our specific GoGoogleDrive instance as
<code>ggd</code>.
</p>
<p>
We can save the model data of <code>ggd.managedDiagrams</code> to Google Drive in a variety of ways.
</p>
<h4 id="SaveVsSaveWithUI">Save vs. Save With UI</h4>
<p>
All GoCloudStorage subclasses have the functions <code>save()</code> and <code>saveWithUI()</code>. What's the difference?
</p>
<p>
<code>saveWithUI()</code> shows the <a href="../api/symbols/GoCloudStorage.html#ui">ui</a> element of the invoking instance of
GoCloudStorage, letting the user graphically specify a file name and/or save location.
</p>
<p>
<code>save()</code> is more nuanced. There are three cases. Let's return to our GoGoogleDrive example and explore them.
</p>
<ol>
<li><strong>Saving With a Specified Path</strong></li>
<p>
A call to <code>ggd.save(&#60;valid path string&#62;)</code> will save to that specific path in Google Drive, without showing any UI.
</p>
<p>
<strong>Note</strong>: What constitutes a valid path string parameter varies from service to service. See
<a href="../api/symbols/GoCloudStorage.html#getFile"> documentation</a> for more details.
</p>
<li><strong>Saving With a Valid Current Diagram File</strong></li>
<p>
If no path is supplied, but <code>ggd</code> has a valid <code><a href="../api/symbols/GoCloudStorage.html#currentDiagramFile">currentDiagramFile</a></code>
(a representation of the file from Google Drive <code>ggd</code> has currently open, and whose contents are loaded in <code>ggd.managedDiagrams</code>' models),
then the diagram file content at the path in Google Drive corresponding to <code>ggd.currentDiagramFile.path</code> is updated with the model contents of
<code>ggd.managedDiagrams</code>.
</p>
<li><strong>Saving With Neither</strong></li>
<p>
If no path is supplied and <code>ggd.currentDiagramFile</code> is not valid, <code>ggd.saveWithUI()</code> is called, prompting the user for a save name / location.
</p>
</ol>
<h4 id="Loading">Loading</h4>
<p>
Loading file data is more straightforward.
</p>
<p>
<code><a href="../api/symbols/GoCloudStorage.html#load">load(&#60;valid path string&#62;)</a></code> loads file contents from the cloud storage service and
into each of <code>managedDiagrams</code>' models. No UI appears.
<br />
<strong>Example</strong>: <code>ggd.load('ahjdhe^3n4dlKd4r')</code>
</p>
<p>
<code><a href="../api/symbols/GoCloudStorage.html#loadWithUI">loadWithUI()</a></code> displays the
<a href="../api/symbols/GoCloudStorage.html#ui">ui</a> and lets the user graphically choose which file to load.
<br />
<strong>Example</strong>: <code>ggd.loadWithUI()</code>
</p>
<p>
<strong>Note 1</strong>: The file being loaded must have been saved to storage from a page with GoJS Diagrams whose DIV IDs correspond with the DIV IDs of
<code><a href="../api/symbols/GoCloudStorage.html#managedDiagrams">managedDiagrams</a></code>. Otherwise, it will not be clear to the GoCloudStorage subclass
where to load model data to.
<br />
<strong>Note 2</strong>: Model data loaded into <code>managedDiagrams</code> from storage must be processed appropriately within the
application containing the invoking instance of GoCloudStorage (via <a href="templateMaps.html">node / link templates</a> or some other method). The GoCloudStorage
class system does not store any information other than model data.
</p>
</p>
<h3 id="CreatingRemovingData">Creating / Removing Data </h3>
<h4 id="CreatingFiles">Creating Files</h4>
<p>
Continuing with our GoGoogleDrive example, how would you create a new file in storage to save <code>ggd.managedDiagrams</code> to? Call the <code>create</code> function.
</p>
<p>
<code>create()</code> sets each of <code>ggd.managedDiagrams</code> to <code>ggd.defaultModel</code> (assigned at construction, back in
<a href="#GoCloudStorageSubclassConstruction">GoCloudStorage Subclass Construction</a>). If
<code><a href="../api/symbols/GoCloudStorage.html#isAutoSaving">ggd.isAutoSaving</a></code> is true, you will be prompted to
save your newly refreshed <code>managedDiagrams</code> to Google Drive via an automatic call to <code>saveWithUI()</code>.
</p>
<p>
Optionally, the <code>create</code> function can accept a path parameter, just as the <code>save()</code> and <code>load()</code> functions described in
<a href="#savingLoadingData">Saving / Loading Data</a>. If supplied, once each of <code>ggd.managedDiagrams</code> is reset to <code>defaultModel</code>,
their model data is saved to the given path in Google Drive, and no UI appears.
</p>
<h4 id="RemovingFiles">Removing Files</h4>
<p>
To remove a file from Google Drive, simply call <code>ggd.remove(&#60;some valid path string&#62;)</code>. The file at the given path in Google Drive will be removed,
without showing any UI.
</p>
<p>
To remove a file from Google Drive with the <a href="../api/symbols/GoCloudStorage.html#ui">ui</a> element, call <code>ggd.removeWithUI()</code>.
</p>
<h3 id="GoCloudStorageManager">Go Cloud Storage Manager </h3>
<p>
What if you wanted to be able to save / load the diagrams on your page to / from many different cloud storage services? Say, Google Drive and Microsoft OneDrive?
Or Microsoft OneDrive, Dropbox, and Google Drive? Or any combination of the currently supported GoCloudStorage subclasses? That's what the
<a href="../api/symbols/GoCloudStorageManager.html">Go Cloud Storage Manager</a> is for.
</p>
<h4 id="ConstructingGoCloudStorageManager">Constructing the GoCloudStorageManager</h4>
<p>
Observe the standard GoCloudStorageManager construction process:
<pre class="lang-js">
// Construct the CloudStorage subclasses you wish to manage
gls = new gcs.GoLocalStorage(myDiagram, defaultModel);
god = new gcs.GoOneDrive(myDiagram, 'f9b171a6-a12e-48c1-b86c-814ed40fcdd1', defaultModel);
ggd = new gcs.GoGoogleDrive(myDiagram, '16225373139-n24vtg7konuetna3ofbmfcaj2infhgmg.apps.googleusercontent.com',
'AIzaSyDBj43lBLpYMMVKw4aN_pvuRg7_XMVGf18', defaultModel);
gdb = new gcs.GoDropBox(myDiagram, '3sm2ko6q7u1gbix', defaultModel);
storages = [gls, god, ggd, gdb];
// Create the GoCloudStorageManager instance
storageManager = new gcs.GoCloudStorageManager(storages, "../projects/storage/goCloudStorageIcons/");
</pre>
Despite all that code, there are only two parameters GoCloudStorageManager takes.
</p>
<ul>
<li><strong><a href="../api/symbols/GoCloudStorageManager.html#storages">Storages</a></strong></li>
<p>
The first parameter, <code>storages</code>, is a either an Array or <a href="../api/Set.html">Set</a> of GoCloudStorage subclasses.
This tells the GoCloudStorageManager instance, <code>storageManager</code>, what storage services are being managed and how those services are managing their diagrams.
</p>
<li><strong><a href="../api/symbols/GoCloudStorageManager.html#iconsRelativeDirectory">Icons Relative Directory</a></strong></li>
<p>
The second parameter is a string, and corresponds to the
<code><a href="../api/symbols/GoCloudStorageManager.html#iconsRelativeDirectory">iconsRelativeDirectory</a></code> property of GoCloudStorageManager. This
is analogous to the <code>iconsRelativeDirectory</code> discussed in <a href="#GoCloudStorageSubclassConstruction">GoCloudStorage Subclass Construction</a>. The only
difference is the GoCloudStorageManager applies this property to each of the GoCloudStorage subclasses' <code>iconsRelativeDirectory</code> properties. This parameter
is optional, but not supplying it may mean there are blank spaces in the <a href="../api/symbols/GoCloudStorageManager.html#ui">ui</a> where the storage service
icons are supposed to be.
</p>
</ul>
<h4 id="UsingGoCloudStorageManager">Using the GoCloudStorageManager</h4>
<div style="text-align: center; padding: 20px;">
<figure >
<img src="images/gcsmSelectStorageService.png" />
<figcaption>The UI the appears after calling selectStorageService()</figcaption>
</figure>
</div>
<p>
First, set the GoCloudStorage subclass you want to use at the moment. This is done through a UI, which is brought up with a
call to <code><a href="../api/symbols/GoCloudStorageManager.html#selectStorageService">storageManager.selectStorageService()</a></code>.
<code><a href="../api/symbols/GoCloudStorageManager.html#currentStorageService">storageManager.currentStorageService</a></code> is set
to the GoCloudStorage subclass managing the storage service selected in the resultant UI.
</p>
<p>
The GoCloudStorageManager assumes a desire for mainly graphical manipulation of data, so calls to <code>save()</code>, <code>load()</code>, <code>create()</code>, and
<code>remove()</code> do not take any parameters and all launch the proper <a href="../api/symbols/GoCloudStorage.html#ui">ui</a> for
<code>storageManager.currentStorageService</code> (set by the the previous step).
</p>
<p>
You may want to update your page display or perform some other actions based on the saving / loading / removal / creation of data using GoCloudStorageManager.
All GoCloudStorageManager core methods (<code>save()</code>, <code>load()</code>, <code>create()</code>, and <code>remove()</code>) return Promises that resolve with
a <a href="../api/symbols/DiagramFile.html">DiagramFile</a>, representing the recently saved / loaded / created / removed file. With this data, you may update your
page display or perform any other action upon Promise resolution. Such as:
<pre class="lang-js">
// resolving the Promise returned after the Load action
storageManager.load().then(function(fileData){
// the fileData is a DiagramFile object
alert(fileData.name + " (file ID " + fileData.id + ") loaded from path " + fileData.path);
});
</pre>
<strong>Note</strong>: There are three guaranteed fields in any DiagramFile object: the <a href="../api/symbols/DiagramFile.html#name">name</a>,
<a href="../api/symbols/DiagramFile.html#id">id</a>, and <a href="../api/symbols/DiagramFile.html#path">path</a> of the represented file.
</p>
</div>
</div>
</body>
</html>
+219
View File
@@ -0,0 +1,219 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS SubGraphs -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Groups as SubGraphs</h1>
<p>
There are some common ways of treating the nodes and links that are the members of a group as if it were its own graph.
One way to declutter a diagram is to "collapse" a <a>Group</a> to hide the subgraph that it holds.
</p>
<p>
A <a>Group</a> has its own <a>Group.layout</a> that is responsible for the positioning of member <a>Node</a>s
and the routing of member <a>Link</a>s.
This is exactly like a <a>Diagram</a> having its own <a>Diagram.layout</a> that positions top-level Nodes and routes top-level Links.
</p>
<p>
Keep in mind that subgraphs are not separate Diagrams and that Groups are just one way of organizing Parts.
Because subgraphs are collections of Nodes and Links in the same Diagram as the Group itself,
it is possible to have Links that connect Nodes that are inside a Group with Nodes that are outside of the group.
It is also possible to have links that connect nodes with the group of which they are a member.
</p>
<h2 id="LayoutsOfSubgraphs">Layouts of SubGraphs</h2>
<p>
You can specify a <a>Layout</a> that applies to a group's subgraph by setting the <a>Group.layout</a> property.
This operates on the group's member nodes and links as if it were its own diagram.
A diagram layout of nodes that include groups with their own layout will treat those groups
as if they were simple nodes, albeit probably larger than normal nodes.
</p>
<p>
In this example the group has a different layout than the layout for the whole diagram.
In this case the only difference is the direction in which the layout works,
but you could use a completely different layout algorithm.
</p>
<p>
For simplicity these examples use the default templates for nodes and links.
</p>
<pre class="lang-js" id="layouts">
diagram.groupTemplate =
$(go.Group, "Auto",
// declare the Group.layout:
{ layout: $(go.LayeredDigraphLayout,
{ direction: 0, columnSpacing: 10 }) },
$(go.Shape, "RoundedRectangle", // surrounds everything
{ parameter1: 10, fill: "rgba(128,128,128,0.33)" }),
$(go.Panel, "Vertical", // position header above the subgraph
$(go.TextBlock, // group title near top, next to button
{ font: "Bold 12pt Sans-Serif" },
new go.Binding("text", "key")),
$(go.Placeholder, // represents area for all member parts
{ padding: 5, background: "white" })
)
);
// declare the Diagram.layout:
diagram.layout = $(go.LayeredDigraphLayout,
{ direction: 90, layerSpacing: 10, isRealtime: false });
var nodeDataArray = [
{ key: "Alpha" },
{ key: "Omega", isGroup: true },
{ key: "Beta", group: "Omega" },
{ key: "Gamma", group: "Omega" },
{ key: "Epsilon", group: "Omega" },
{ key: "Zeta", group: "Omega" },
{ key: "Delta" }
];
var linkDataArray = [
{ from: "Alpha", to: "Omega" }, // from a Node to the Group
{ from: "Beta", to: "Gamma" }, // this link is a member of the Group
{ from: "Beta", to: "Epsilon" }, // this link is a member of the Group
{ from: "Gamma", to: "Zeta" }, // this link is a member of the Group
{ from: "Epsilon", to: "Zeta" }, // this link is a member of the Group
{ from: "Omega", to: "Delta" } // from the Group to a Node
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("layouts", 600, 250)</script>
<p>
The default layout for a Group is an instance of <a>Layout</a>, just as it is for <a>Diagram</a>.
So if you do not specify a value for <a>Group.layout</a>, the default layout for the group will position
all member nodes that do not have a real <a>Part.location</a>.
</p>
<p>
If you explicitly set <a>Group.layout</a> to null, the Diagram will be responsible for laying out all of
Nodes and Links as if the Group did not exist.
This is possible because a subgraph is not another <a>Diagram</a>.
</p>
<h2 id="CollapsingAndExpandingGroups">Collapsing and Expanding Groups</h2>
<p>
One common technique to visually simplify a diagram is to hide parts of them by "collapsing" them.
In the case of <a>Group</a>s, it may make sense to be able to hide the subgraph.
</p>
<p>
To collapse a group, set <a>Group.isSubGraphExpanded</a> to false; to make sure it is expanded,
set that property to true.
</p>
<p>
It is commonplace to provide a button on a group to allow users to collapse and expand groups as they wish.
<b>GoJS</b> makes this easy to implement by providing a predefined kind of <a>Panel</a>, named "SubGraphExpanderButton",
that acts as a button to collapse and expand <a>Group</a>s.
This button changes the visibility of the member nodes and links but does not change
the visibility of the group itself.
When the group's visual tree includes a <a>Placeholder</a>, the placeholder will automatically
shrink when the member parts become invisible and will inflate when the member parts become visible again.
</p>
<p>
Click on the expander button to collapse or expand the group.
Changing the size of the group also invalidates the layout that is responsible for positioning the group as a single node.
Often the size of the group changes greatly, so a layout usually needs to be performed again.
</p>
<pre class="lang-js" id="collapseExpand">
diagram.groupTemplate =
$(go.Group, "Auto",
{ layout: $(go.LayeredDigraphLayout,
{ direction: 0, columnSpacing: 10 }) },
$(go.Shape, "RoundedRectangle", // surrounds everything
{ parameter1: 10, fill: "rgba(128,128,128,0.33)" }),
$(go.Panel, "Vertical", // position header above the subgraph
{ defaultAlignment: go.Spot.Left },
$(go.Panel, "Horizontal", // the header
{ defaultAlignment: go.Spot.Top },
$("SubGraphExpanderButton"), // this Panel acts as a Button
$(go.TextBlock, // group title near top, next to button
{ font: "Bold 12pt Sans-Serif" },
new go.Binding("text", "key"))
),
$(go.Placeholder, // represents area for all member parts
{ padding: new go.Margin(0, 10), background: "white" })
)
);
diagram.layout = $(go.LayeredDigraphLayout,
{ direction: 90, layerSpacing: 10, isRealtime: false });
var nodeDataArray = [
{ key: "Alpha" },
{ key: "Omega", isGroup: true },
{ key: "Beta", group: "Omega" },
{ key: "Gamma", group: "Omega" },
{ key: "Epsilon", group: "Omega" },
{ key: "Zeta", group: "Omega" },
{ key: "Delta" }
];
var linkDataArray = [
{ from: "Alpha", to: "Omega" }, // from a Node to the Group
{ from: "Beta", to: "Gamma" }, // this link is a member of the Group
{ from: "Beta", to: "Epsilon" }, // this link is a member of the Group
{ from: "Gamma", to: "Zeta" }, // this link is a member of the Group
{ from: "Epsilon", to: "Zeta" }, // this link is a member of the Group
{ from: "Omega", to: "Delta" } // from the Group to a Node
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("collapseExpand", 600, 250)</script>
<p>
If you do not want a layout to be performed again when the group changes size,
you can set the <a>Part.layoutConditions</a> property to control the circumstances under which
the layout will be invalidated.
</p>
<p>
<a>Placeholder</a>s can be part of complex panels.
The following example demonstrates a different way to have each Group have a header holding a button and some text.
</p>
<pre class="lang-js" id="collapseExpand2">
diagram.groupTemplate =
$(go.Group, "Auto",
{ layout: $(go.TreeLayout) },
$(go.Shape, "Rectangle", { fill: "orange", stroke: "darkorange" }),
$(go.Panel, "Table",
{ margin: 0.5 }, // avoid overlapping border with table contents
$(go.RowColumnDefinition, { row: 0, background: "white" }), // header is white
$("SubGraphExpanderButton", { row: 0, column: 0, margin: 3 }),
$(go.TextBlock, // title is centered in header
{ row: 0, column: 1, font: "bold 14px Sans-Serif", stroke: "darkorange",
textAlign: "center", stretch: go.GraphObject.Horizontal },
new go.Binding("text")),
$(go.Placeholder, // becomes zero-sized when Group.isSubGraphExpanded is false
{ row: 1, columnSpan: 2, padding: 10, alignment: go.Spot.TopLeft },
new go.Binding("padding", "isSubGraphExpanded",
function(exp) { return exp ? 10 : 0; } ).ofObject())
)
);
diagram.layout = $(go.TreeLayout, { isRealtime: false });
diagram.model = new go.GraphLinksModel([
{ key: 1, text: "Alpha" },
{ key: 2, text: "GROUP", isGroup: true },
{ key: 3, text: "Beta", group: 2 },
{ key: 4, text: "Gamma", group: 2 },
{ key: 5, text: "Delta" }
], [
{ from: 1, to: 3 },
{ from: 3, to: 4 },
{ from: 1, to: 5 }
]);
</pre>
<script>goCode("collapseExpand2", 600, 200)</script>
</div>
</div>
</body>
</html>
+126
View File
@@ -0,0 +1,126 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS SubTrees -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>SubTrees</h1>
<p>
Tree diagrams can get very large.
One way to simplify the diagram is to hide branches of the tree.
"Collapsing" a tree node means making all of its children and the links to them, not visible,
and recursively collapsing all of the children that have children.
</p>
<p>
To collapse a node in a tree, set <a>Node.isTreeExpanded</a> to false;
to make sure it is expanded, set that property to true.
You should not set this property to true on a <a>Node</a> that is not <a>GraphObject.visible</a>.
</p>
<p>
It is commonplace to provide a button on a node to allow users to collapse and expand subtrees as they wish.
<b>GoJS</b> makes this easy to implement by providing a predefined kind of <a>Panel</a>, named "TreeExpanderButton",
that acts as a button to collapse and expand the subtree of a node.
This button changes the visibility of all parts of the subtree except for the node itself.
</p>
<p>
Clicking on an expander button also invalidates the layout that is reponsible for the node.
Collapsing a subtree often results in a large empty area,
and expanding a subtree often results in overlapping nodes,
so a new layout ought to be performed again to make the tree look better.
</p>
<pre class="lang-js" id="tree">
diagram.nodeTemplate =
$(go.Node, "Horizontal",
$(go.Panel, "Auto",
$(go.Shape, "Ellipse", { fill: null }),
$(go.TextBlock,
new go.Binding("text", "key"))
),
$("TreeExpanderButton")
);
diagram.layout = $(go.TreeLayout);
var nodeDataArray = [
{ key: "Alpha" }, { key: "Beta" }, { key: "Gamma" }, { key: "Delta" },
{ key: "Epsilon" }, { key: "Zeta" }, { key: "Eta" }, { key: "Theta" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" },
{ from: "Beta", to: "Gamma" },
{ from: "Beta", to: "Delta" },
{ from: "Alpha", to: "Epsilon" },
{ from: "Epsilon", to: "Zeta" },
{ from: "Epsilon", to: "Eta" },
{ from: "Epsilon", to: "Theta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("tree", 600, 200)</script>
<p>
Notice that if you first collapse the "Beta" node and then collapse the "Alpha" root node,
if you then expand the "Alpha" node, the "Beta" node remains collapsed, whereas the "Epsilon" node remains expanded.
This is because the collapsing operation remembers the state of nodes within the subtree,
as the property <a>Node.wasTreeExpanded</a>.
The expanding operation respects the value of that property when recursing through the subtree.
</p>
<p>
You may also want to start off the tree mostly or completely collapsed.
First, set <a>Node.isTreeExpanded</a> to false in the template.
That will cause only the roots of the tree to be shown.
Second, if you want to show three levels of the tree, call <a>Node.expandTree</a>.
</p>
<pre class="lang-js" id="tree2">
diagram.nodeTemplate =
$(go.Node, "Horizontal",
{ isTreeExpanded: false }, // by default collapsed
$(go.Panel, "Auto",
$(go.Shape, "Ellipse", { fill: null }),
$(go.TextBlock,
new go.Binding("text", "key"))
),
$("TreeExpanderButton")
);
diagram.layout = $(go.TreeLayout);
// After the nodes and links have been created,
// expand each of the tree roots to 3 levels deep.
diagram.addDiagramListener("InitialLayoutCompleted", function(e) {
e.diagram.findTreeRoots().each(function(r) { r.expandTree(3); });
});
var nodeDataArray = [
{ key: "Alpha" }, { key: "Beta" }, { key: "Gamma" }, { key: "Delta" },
{ key: "Epsilon" }, { key: "Zeta" }, { key: "Eta" }, { key: "Theta" },
{ key: "Iota" }, { key: "Kappa" }, { key: "Lambda" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" },
{ from: "Beta", to: "Gamma" },
{ from: "Beta", to: "Delta" },
{ from: "Alpha", to: "Epsilon" },
{ from: "Epsilon", to: "Zeta" },
{ from: "Epsilon", to: "Eta" },
{ from: "Eta", to: "Theta" },
{ from: "Gamma", to: "Iota" },
{ from: "Iota", to: "Kappa" },
{ from: "Iota", to: "Lambda" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("tree2", 600, 200)</script>
</div>
</div>
</body>
</html>
+498
View File
@@ -0,0 +1,498 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Table Panels -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Table Panels</h1>
<p>
The "Table" Panel, <a>Panel,Table</a>, arranges objects in rows and columns.
</p>
<p>
See samples that make use of tables in the <a href="../samples/index.html#tables">samples index</a>.
</p>
<h2 id="SimpleTablePanels">Simple Table Panels</h2>
<p>
Each object in a Table Panel is put into the cell indexed by the value of <a>GraphObject.row</a> and <a>GraphObject.column</a>.
The panel will look at the rows and columns for all of the objects in the panel to determine how many rows and columns the table should have.
</p>
<pre class="lang-js" id="simpleTable">
diagram.add(
// all Parts are Panels
$(go.Part, go.Panel.Table, // or "Table"
$(go.TextBlock, "row 0\ncol 0",
{ row: 0, column: 0, margin: 2, background: "lightgray" }),
$(go.TextBlock, "row 0\ncol 1",
{ row: 0, column: 1, margin: 2, background: "lightgray" }),
$(go.TextBlock, "row 1\ncol 0",
{ row: 1, column: 0, margin: 2, background: "lightgray" }),
$(go.TextBlock, "row 1\ncol 2",
{ row: 1, column: 2, margin: 2, background: "lightgray" })
));
</pre>
<script>goCode("simpleTable", 600, 100)</script>
<p>
Note that not every "cell" of the table needs to have a <a>GraphObject</a> in it.
</p>
<p>
If there are multiple objects in a cell, they will probably overlap each other in the cell.
By default objects are center-aligned in each cell.
</p>
<pre class="lang-js" id="multipleInCell">
diagram.add(
$(go.Part,
$(go.Panel, "Table",
$(go.TextBlock, "row 0\ncol 0",
{ row: 0, column: 0, margin: 2, background: "lightgray" }),
$(go.TextBlock, "row 0 col 1\nyellow background",
// first object in the cell (row: 0, col: 1)
{ row: 0, column: 1, margin: 2,
background: "yellow", stroke: "gray" }),
$(go.TextBlock, "row 0\ncol 1",
// second object in that cell overlaps the first one,
// the bigger yellow TextBlock
{ row: 0, column: 1, margin: 2,
background: "transparent", stroke: "blue" }),
$(go.TextBlock, "row 1\ncol 0",
{ row: 1, column: 0, margin: 2, background: "lightgray" }),
$(go.TextBlock, "row 1\ncol 2",
{ row: 1, column: 2, margin: 2, background: "lightgray" })
)
));
</pre>
<script>goCode("multipleInCell", 600, 100)</script>
<p>
If a column or a row has no objects in it, that column or row is ignored.
</p>
<pre class="lang-js" id="emptyColumns">
diagram.add(
$(go.Part,
$(go.Panel, "Table",
$(go.TextBlock, "row 0\ncol 0",
{ row: 0, column: 0, margin: 2, background: "lightgray" }),
$(go.TextBlock, "row 0\ncol 11", // column 11 -- nothing in columns 1-10
{ row: 0, column: 11, margin: 2, background: "lightgray" }),
$(go.TextBlock, "row 1\ncol 0",
{ row: 1, column: 0, margin: 2, background: "lightgray" }),
$(go.TextBlock, "row 1\ncol 12", // column 12
{ row: 1, column: 12, margin: 2, background: "lightgray" })
)
));
</pre>
<script>goCode("emptyColumns", 600, 100)</script>
<h2 id="SizingOfRowsOrColumns">Sizing of Rows or Columns</h2>
<p>
The height of each row is normally determined by the greatest height of all of the objects in that row.
Similarly, the width of each column is normally determined by the greatest width of all of the objects in that column.
However you can provide row height or column width information for any row or column independent of any individual object
by setting properties of the desired <a>RowColumnDefinition</a> of the Table panel.
</p>
<p>
To fix a column width or a row height in code you can call <a>Panel.getColumnDefinition</a>
or <a>Panel.getRowDefinition</a> and then set <a>RowColumnDefinition.width</a> or <a>RowColumnDefinition.height</a>.
If you want to limit the width or height to certain ranges, set the <a>RowColumnDefinition.minimum</a> or <a>RowColumnDefinition.maximum</a>.
If the maximum and the width or height conflict, the maximum takes precedence.
For example, if the maximum is 70 but the natural value is 80, the actual value is limited to 70.
If that value conflicts with the minimum, the minimum takes precedence.
For example, if the minimum is 50 but the natural value is 40, the actual value is 50.
</p>
<p>
This example demonstrates how the column width may be controlled.
</p>
<pre class="lang-js" id="columnSizes">
diagram.add(
$(go.Part,
$(go.Panel, "Table",
{ defaultAlignment: go.Spot.Left },
$(go.RowColumnDefinition, { column: 0, width: 100 }),
$(go.RowColumnDefinition, { column: 1, width: 100, minimum: 150 }),
$(go.RowColumnDefinition, { column: 2, width: 100, maximum: 50 }),
$(go.RowColumnDefinition, { column: 3, width: 100, minimum: 150, maximum: 50 }),
$(go.TextBlock, "Text Block",
{ row: 0, column: 0, background: "green" }),
$(go.TextBlock, "Text Block",
{ row: 0, column: 1, background: "red" }),
$(go.TextBlock, "Text Block",
{ row: 0, column: 2, background: "yellow" }),
$(go.TextBlock, "Text Block",
{ row: 0, column: 3, background: "red" }),
$(go.Panel, "Auto",
{ row: 1, column: 0 },
$(go.Shape, "RoundedRectangle", { fill: "green" }),
$(go.TextBlock, "Auto Panel")
),
$(go.Panel, "Auto",
{ row: 1, column: 1 },
$(go.Shape, "RoundedRectangle", { fill: "red" }),
$(go.TextBlock, "Auto Panel")
),
$(go.Panel, "Auto",
{ row: 1, column: 2 },
$(go.Shape, "RoundedRectangle", { fill: "yellow" }),
$(go.TextBlock, "Auto Panel")
),
$(go.Panel, "Auto",
{ row: 1, column: 3 },
$(go.Shape, "RoundedRectangle", { fill: "red" }),
$(go.TextBlock, "Auto Panel")
),
$(go.TextBlock, "width: 100", { row: 2, column: 0 }),
$(go.TextBlock, "min: 150", { row: 2, column: 1 }),
$(go.TextBlock, "max: 50", { row: 2, column: 2 }),
$(go.TextBlock, "min & max", { row: 2, column: 3 })
)
));
</pre>
<script>goCode("columnSizes", 600, 120)</script>
<p>
Note how the column with a minimum of 150 has a lot of extra space in it,
and how the column with a maximum of 50 results in its elements being clipped.
</p>
<h2 id="StretchAndAlignmente">Stretch and Alignment</h2>
<p>
The size of a GraphObject in a Panel is determined by many factors.
The <a>GraphObject.stretch</a> property specifies whether the width and/or height should take up all
of the space given to it by the Panel.
When the width and/or height is not stretched to fill the given space,
the <a>GraphObject.alignment</a> property controls where the object is placed if it is smaller than available space.
One may also stretch the width while aligning vertically, just as one may also
stretch vertically while aligning along the X axis.
</p>
<p>
The alignment value for a GraphObject, if not given by the value of GraphObject.alignment, may be inherited.
If the object is in a Table Panel, the value may inherit from the <a>RowColumnDefinition.alignment</a>s of
the row and of the column that the object is in.
Finally the value may be inherited from the <a>Panel.defaultAlignment</a> property.
</p>
<p>
The same inheritance is true for the stretch value for a GraphObject: <a>GraphObject.stretch</a>,
<a>RowColumnDefinition.stretch</a>, and finally <a>Panel.defaultStretch</a>.
</p>
<h3 id="AlignmentInColumns">Alignment in Columns</h3>
<pre class="lang-js" id="columns">
diagram.add(
$(go.Part,
$(go.Panel, "Table",
{ defaultAlignment: go.Spot.Left },
$(go.RowColumnDefinition, { column: 0, width: 200 }),
$(go.RowColumnDefinition, { column: 1, width: 15 }),
$(go.Panel, "Auto",
{ row: 0, column: 0, alignment: go.Spot.Left },
$(go.Shape, "RoundedRectangle", { fill: "lightblue" }),
$(go.TextBlock, "Auto Panel")
),
$(go.TextBlock, "alignment: Left", { row: 0, column: 2 }),
$(go.Panel, "Auto",
{ row: 1, column: 0, alignment: go.Spot.Center},
$(go.Shape, "RoundedRectangle", { fill: "lightblue" }),
$(go.TextBlock, "Auto Panel")
),
$(go.TextBlock, "alignment: Center", { row: 1, column: 2 }),
$(go.Panel, "Auto",
{ row: 2, column: 0, alignment: go.Spot.Right },
$(go.Shape, "RoundedRectangle", { fill: "lightblue" }),
$(go.TextBlock, "Auto Panel")
),
$(go.TextBlock, "alignment: Right", { row: 2, column: 2 }),
$(go.Panel, "Auto",
{ row: 3, column: 0, stretch: go.GraphObject.Horizontal },
$(go.Shape, "RoundedRectangle", { fill: "yellow" }),
$(go.TextBlock, "Auto Panel")
),
$(go.TextBlock, "stretch: Horizontal", { row: 3, column: 2 })
)
));
</pre>
<script>goCode("columns", 600, 120)</script>
<h3 id="AlignmentInRows">Alignment in Rows</h3>
<pre class="lang-js" id="rows">
diagram.add(
$(go.Part,
$(go.Panel, "Table",
{ defaultAlignment: go.Spot.Top },
$(go.RowColumnDefinition, { row: 0, height: 50 }),
$(go.RowColumnDefinition, { row: 1, height: 15 }),
$(go.Panel, "Auto",
{ row: 0, column: 0, alignment: go.Spot.Top },
$(go.Shape, "RoundedRectangle", { fill: "lightblue" }),
$(go.TextBlock, "Auto Panel")
),
$(go.TextBlock, "alignment:\nTop", { row: 2, column: 0 }),
$(go.Panel, "Auto",
{ row: 0, column: 1, alignment: go.Spot.Center},
$(go.Shape, "RoundedRectangle", { fill: "lightblue" }),
$(go.TextBlock, "Auto Panel")
),
$(go.TextBlock, "alignment:\nCenter", { row: 2, column: 1 }),
$(go.Panel, "Auto",
{ row: 0, column: 2, alignment: go.Spot.Bottom },
$(go.Shape, "RoundedRectangle", { fill: "lightblue" }),
$(go.TextBlock, "Auto Panel")
),
$(go.TextBlock, "alignment:\nBottom", { row: 2, column: 2 }),
$(go.Panel, "Auto",
{ row: 0, column: 3, stretch: go.GraphObject.Vertical },
$(go.Shape, "RoundedRectangle", { fill: "yellow" }),
$(go.TextBlock, "Auto Panel")
),
$(go.TextBlock, "stretch:\nVertical", { row: 2, column: 3 })
)
));
</pre>
<script>goCode("rows", 600, 120)</script>
<h2 id="SpanningRowsOrColumns">Spanning Rows or Columns</h2>
<p>
An element in a Table Panel cell can cover more than one cell if you set the <a>GraphObject.rowSpan</a>
or <a>GraphObject.columnSpan</a> properties.
For example, if the value of GraphObject.columnSpan is greater than one, it specifies how many columns
that object may cover, starting with the value of <a>GraphObject.column</a>, but excluding the column
indexed by column + columnSpan.
</p>
<pre class="lang-js" id="columnSpan">
diagram.add(
$(go.Part,
$(go.Panel, "Table",
$(go.TextBlock, "Three Col Header", // spans all three columns
{ row: 0, column: 0, columnSpan: 3, stretch: go.GraphObject.Horizontal,
margin: 2, background: "lightgray" }),
$(go.TextBlock, "row 1\ncol 0",
{ row: 1, column: 0, margin: 2, background: "lightgray" }),
$(go.TextBlock, "row 1\ncol 1",
{ row: 1, column: 1, margin: 2, background: "lightgray" }),
$(go.TextBlock, "row 2\ncol 0",
{ row: 2, column: 0, margin: 2, background: "lightgray" }),
$(go.TextBlock, "row 2\ncol 2",
{ row: 2, column: 2, margin: 2, background: "lightgray" })
)
));
</pre>
<script>goCode("columnSpan", 600, 120)</script>
<p>
Here is an example that includes both column spanning and row spanning.
</p>
<pre class="lang-js" id="columnSpan2">
diagram.add(
$(go.Part,
$(go.Panel, "Table",
$(go.TextBlock, "Greetings",
{ row: 0, column: 0, columnSpan: 3, stretch: go.GraphObject.Horizontal,
margin: 2, background: "lightgray" }),
$(go.TextBlock, "numbers",
{ row: 1, column: 0, rowSpan: 2, stretch: go.GraphObject.Vertical,
margin: 2, background: "lightgray", angle: 270 }),
$(go.TextBlock, "row 1\ncol 1",
{ row: 1, column: 1, margin: 2, background: "lightgray" }),
$(go.TextBlock, "row 1\ncol 2",
{ row: 1, column: 2, margin: 2, background: "lightgray" }),
$(go.TextBlock, "row 2\ncol 1",
{ row: 2, column: 1, margin: 2, background: "lightgray" }),
$(go.TextBlock, "row 2\ncol 3",
{ row: 2, column: 3, margin: 2, background: "lightgray" }),
$(go.TextBlock, "Signature",
{ row: 3, column: 2, columnSpan: 2, stretch: go.GraphObject.Horizontal,
margin: 2, background: "lightgray" })
)
));
</pre>
<script>goCode("columnSpan2", 600, 120)</script>
<h2 id="SeparatorsAndRowColumnPadding">Separators and Row/Column Padding</h2>
<p>
Table Panels also support the optional drawing of lines between rows or columns.
The <a>RowColumnDefinition.separatorStrokeWidth</a> property controls the extra space that comes before a particular row or column.
The <a>RowColumnDefinition.separatorStroke</a> and <a>RowColumnDefinition.separatorDashArray</a> control if and how a line is drawn.
</p>
<p>
For example, if you want to treat the first row and the first column as "headers",
you can separate them from the rest of the table by drawing a black line before row 1 and column 1.
</p>
<pre class="lang-js" id="spacing">
diagram.add(
$(go.Part, "Auto",
$(go.Shape, { fill: "white", stroke: "gray", strokeWidth: 3 }),
$(go.Panel, "Table",
$(go.TextBlock, "Header 1",
{ row: 0, column: 1, font: "bold 10pt sans-serif", margin: 2 }),
$(go.TextBlock, "Header 2",
{ row: 0, column: 2, font: "bold 10pt sans-serif", margin: 2 }),
// drawn before row 1:
$(go.RowColumnDefinition,
{ row: 1, separatorStrokeWidth: 1.5, separatorStroke: "black" }),
// drawn before column 1:
$(go.RowColumnDefinition,
{ column: 1, separatorStrokeWidth: 1.5, separatorStroke: "black" }),
$(go.TextBlock, "One", { row: 1, column: 0, stroke: "green", margin: 2 }),
$(go.TextBlock, "row 1 col 1", { row: 1, column: 1, margin: 2 }),
$(go.TextBlock, "row 1 col 2", { row: 1, column: 2, margin: 2 }),
$(go.TextBlock, "Two", { row: 2, column: 0, stroke: "green", margin: 2 }),
$(go.TextBlock, "row 2 col 1", { row: 2, column: 1, margin: 2 }),
$(go.TextBlock, "row 2 col 2", { row: 2, column: 2, margin: 2 }),
$(go.TextBlock, "Three", { row: 3, column: 0, stroke: "green", margin: 2 }),
$(go.TextBlock, "row 3 col 1", { row: 3, column: 1, margin: 2 }),
$(go.TextBlock, "row 3 col 2", { row: 3, column: 2, margin: 2 })
)
));
</pre>
<script>goCode("spacing", 600, 150)</script>
<p>
If you want to have a default separator between each row, set the default separator properties of the <a>Panel</a>. These properties are:
</p>
<ul>
<li><a>Panel.defaultSeparatorPadding</a>
<li><a>Panel.defaultRowSeparatorStrokeWidth</a>
<li><a>Panel.defaultRowSeparatorStroke</a>
<li><a>Panel.defaultRowSeparatorDashArray</a>
<li><a>Panel.defaultColumnSeparatorStrokeWidth</a>
<li><a>Panel.defaultColumnSeparatorStroke</a>
<li><a>Panel.defaultColumnSeparatorDashArray</a>
</ul>
<p>
Any separator properties set on a particular RowColumnDefinition will take precedence over the default values provided on the Panel.
This permits keeping the special black line separating the header row and header column from the rest.
</p>
<pre class="lang-js" id="spacing2">
diagram.add(
$(go.Part, "Auto",
$(go.Shape, { fill: "white", stroke: "gray", strokeWidth: 3 }),
$(go.Panel, "Table",
// Set defaults for all rows and columns:
{ defaultRowSeparatorStroke: "gray",
defaultColumnSeparatorStroke: "gray" },
$(go.TextBlock, "Header 1",
{ row: 0, column: 1, font: "bold 10pt sans-serif", margin: 2 }),
$(go.TextBlock, "Header 2",
{ row: 0, column: 2, font: "bold 10pt sans-serif", margin: 2 }),
$(go.RowColumnDefinition,
{ row: 1, separatorStrokeWidth: 1.5, separatorStroke: "black" }),
$(go.RowColumnDefinition,
{ column: 1, separatorStrokeWidth: 1.5, separatorStroke: "black" }),
$(go.TextBlock, "One", { row: 1, column: 0, stroke: "green", margin: 2 }),
$(go.TextBlock, "row 1 col 1", { row: 1, column: 1, margin: 2 }),
$(go.TextBlock, "row 1 col 2", { row: 1, column: 2, margin: 2 }),
$(go.TextBlock, "Two", { row: 2, column: 0, stroke: "green", margin: 2 }),
$(go.TextBlock, "row 2 col 1", { row: 2, column: 1, margin: 2 }),
$(go.TextBlock, "row 2 col 2", { row: 2, column: 2, margin: 2 }),
$(go.TextBlock, "Three", { row: 3, column: 0, stroke: "green", margin: 2 }),
$(go.TextBlock, "row 3 col 1", { row: 3, column: 1, margin: 2 }),
$(go.TextBlock, "row 3 col 2", { row: 3, column: 2, margin: 2 })
)
));
</pre>
<script>goCode("spacing2", 600, 150)</script>
<p>
RowColumnDefinitions also have a <a>RowColumnDefinition.separatorPadding</a> property,
which can be used to add extra space to rows or columns.
When a <a>RowColumnDefinition.background</a> is set, it includes the padding in its area.
</p>
<pre class="lang-js" id="padding">
diagram.add(
$(go.Part, "Auto",
$(go.Shape, { fill: "white", stroke: "gray", strokeWidth: 3 }),
$(go.Panel, "Table",
// Set defaults for all rows and columns:
{ padding: 1.5,
defaultRowSeparatorStroke: "gray",
defaultColumnSeparatorStroke: "gray",
defaultSeparatorPadding: new go.Margin(18, 0, 8, 0) },
$(go.TextBlock, "Header 1",
{ row: 0, column: 1, font: "bold 10pt sans-serif", margin: 2 }),
$(go.TextBlock, "Header 2",
{ row: 0, column: 2, font: "bold 10pt sans-serif", margin: 2 }),
// Override the panel's default padding on the first row
$(go.RowColumnDefinition, { row: 0, separatorPadding: 0 }),
$(go.RowColumnDefinition,
{ row: 1, separatorStrokeWidth: 1.5, separatorStroke: "black" }),
$(go.RowColumnDefinition, { row: 2, background: 'lightblue' }),
$(go.RowColumnDefinition,
{ column: 1, separatorStrokeWidth: 1.5, separatorStroke: "black" }),
$(go.TextBlock, "One", { row: 1, column: 0, stroke: "green", margin: 2 }),
$(go.TextBlock, "row 1 col 1", { row: 1, column: 1, margin: 2 }),
$(go.TextBlock, "row 1 col 2", { row: 1, column: 2, margin: 2 }),
$(go.TextBlock, "Two", { row: 2, column: 0, stroke: "green", margin: 2 }),
$(go.TextBlock, "row 2 col 1", { row: 2, column: 1, margin: 2 }),
$(go.TextBlock, "row 2 col 2", { row: 2, column: 2, margin: 2 }),
$(go.TextBlock, "Three", { row: 3, column: 0, stroke: "green", margin: 2 }),
$(go.TextBlock, "row 3 col 1", { row: 3, column: 1, margin: 2 }),
$(go.TextBlock, "row 3 col 2", { row: 3, column: 2, margin: 2 })
)
));
</pre>
<script>goCode("padding", 600, 200)</script>
<h2 id="TableRowsAndTableColumns">TableRows and TableColumns</h2>
<p>
To avoid having to specify the row for each object, you can make use of a special Panel that can only be used in Table Panels,
the <a>Panel,TableRow</a> panel type. Put all of the objects for each row into a TableRow Panel.
You will still need to specify the column for each object in each row.
</p>
<pre class="lang-js" id="spacing3">
diagram.add(
$(go.Part, "Auto",
$(go.Shape, { fill: "white", stroke: "gray", strokeWidth: 3 }),
$(go.Panel, "Table",
// Set defaults for all rows and columns:
{ defaultRowSeparatorStroke: "gray",
defaultColumnSeparatorStroke: "gray" },
$(go.Panel, "TableRow", { row: 0 },
$(go.TextBlock, "Header 1",
{ column: 1, font: "bold 10pt sans-serif", margin: 2 }),
$(go.TextBlock, "Header 2",
{ column: 2, font: "bold 10pt sans-serif", margin: 2 })),
$(go.RowColumnDefinition,
{ row: 1, separatorStrokeWidth: 1.5, separatorStroke: "black" }),
$(go.RowColumnDefinition,
{ column: 1, separatorStrokeWidth: 1.5, separatorStroke: "black" }),
$(go.Panel, "TableRow", { row: 1 },
$(go.TextBlock, "One", { column: 0, stroke: "green", margin: 2 }),
$(go.TextBlock, "row 1 col 1", { column: 1, margin: 2 }),
$(go.TextBlock, "row 1 col 2", { column: 2, margin: 2 })
),
$(go.Panel, "TableRow", { row: 2 },
$(go.TextBlock, "Two", { column: 0, stroke: "green", margin: 2 }),
$(go.TextBlock, "row 2 col 1", { column: 1, margin: 2 }),
$(go.TextBlock, "row 2 col 2", { column: 2, margin: 2 })
),
$(go.Panel, "TableRow", { row: 3 },
$(go.TextBlock, "Three", { column: 0, stroke: "green", margin: 2 }),
$(go.TextBlock, "row 3 col 1", { column: 1, margin: 2 }),
$(go.TextBlock, "row 3 col 2", { column: 2, margin: 2 })
)
)
));
</pre>
<script>goCode("spacing3", 600, 150)</script>
<p>
The same kind of organization is also possible with columns by using <a>Panel,TableColumn</a> Panels.
</p>
</div>
</div>
</body>
</html>
+450
View File
@@ -0,0 +1,450 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Template Maps -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="../extensions/Figures.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Template Maps</h1>
<p>
Many of the previous examples have provided custom templates for nodes, groups, or links.
Those examples have shown how to make simple adaptations of the templates for particular data instances via data binding.
But what if you want to have nodes with drastically different appearances or behaviors in a
single diagram at the same time?
</p>
<p>
It is possible to define a node template that includes all possible configurations for all of the kinds
of nodes that you want to display. There would need to be a lot of data binding and/or code to make
the needed changes. Often you will want to make not-<a>GraphObject.visible</a> large parts of the template in order to make
visible the one panel that you want to show. But this technique is difficult to use -- templates get
way too complicated too quickly.
</p>
<p>
Instead <b>GoJS</b> supports as many templates as you want -- you choose dynamically which one you want to
use to represent a particular node data. This does mean potentially a lot of templates, but each one
will be much simpler, easier to write, and easier to maintain.
</p>
<p>
Each <a>Diagram</a> actually holds a <a>Map</a> of templates for each
type of <a>Part</a>: <a>Node</a>, <a>Group</a>, and <a>Link</a>.
Each Map associates a "category" name with a template.
For example, when the diagram wants to create a <a>Node</a> for a particular node data object,
the diagram uses that node data's category to look up the node template in the <a>Diagram.nodeTemplateMap</a>.
Similar lookups are done using the <a>Diagram.groupTemplateMap</a> and the <a>Diagram.linkTemplateMap</a>.
</p>
<p>
Each <a>Diagram</a> initially has its own template maps stocked with predefined categories.
The default category for any data object is the empty string, "".
The <a>Diagram.nodeTemplateMap</a> initially contains for the empty string a very simple <a>Node</a> template
holding a <a>TextBlock</a> whose <a>TextBlock.text</a> property is data bound to the data converted to a string.
You can see the default templates for nodes, groups, and links in a number of the previous examples,
such as the <a href="groups.html#GroupsLinks">Groups and Links</a> example.
</p>
<p>
The value of <a>Diagram.nodeTemplate</a> is just the value of <code>thatDiagram.nodeTemplateMap.get("")</code>.
Setting <a>Diagram.nodeTemplate</a> just replaces the template in <a>Diagram.nodeTemplateMap</a>
named with the empty string.
</p>
<p>
The implementations of all predefined templates are provided in <a href="../extensions/Templates.js">Templates.js</a> in the Extensions directory.
You may wish to copy and adapt these definitions when creating your own templates.
</p>
<h2 id="ExampleOfNodeTemplates">Example of Node templates</h2>
<pre class="lang-js" id="templates">
// the "simple" template just shows the key string and the color in the background,
// but it also includes a tooltip that shows the description
var simpletemplate =
$(go.Node, "Auto",
$(go.Shape, "Ellipse",
new go.Binding("fill", "color")),
$(go.TextBlock,
new go.Binding("text", "key")),
{
toolTip:
$("ToolTip",
$(go.TextBlock, { margin: 4 },
new go.Binding("text", "desc"))
)
}
);
// the "detailed" template shows all of the information in a Table Panel
var detailtemplate =
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle",
new go.Binding("fill", "color")),
$(go.Panel, "Table",
{ defaultAlignment: go.Spot.Left },
$(go.TextBlock, { row: 0, column: 0, columnSpan: 2, font: "bold 12pt sans-serif" },
new go.Binding("text", "key")),
$(go.TextBlock, { row: 1, column: 0 }, "Description:"),
$(go.TextBlock, { row: 1, column: 1 }, new go.Binding("text", "desc")),
$(go.TextBlock, { row: 2, column: 0 }, "Color:"),
$(go.TextBlock, { row: 2, column: 1 }, new go.Binding("text", "color"))
)
);
// create the nodeTemplateMap, holding three node templates:
var templmap = new go.Map(); // In TypeScript you could write: new go.Map&lt;string, go.Node&gt;();
// for each of the node categories, specify which template to use
templmap.add("simple", simpletemplate);
templmap.add("detailed", detailtemplate);
// for the default category, "", use the same template that Diagrams use by default;
// this just shows the key value as a simple TextBlock
templmap.add("", diagram.nodeTemplate);
diagram.nodeTemplateMap = templmap;
diagram.model.nodeDataArray = [
{ key: "Alpha", desc: "first letter", color: "green" }, // uses default category: ""
{ key: "Beta", desc: "second letter", color: "lightblue", category: "simple" },
{ key: "Gamma", desc: "third letter", color: "pink", category: "detailed" },
{ key: "Delta", desc: "fourth letter", color: "cyan", category: "detailed" }
];
</pre>
<script>goCode("templates", 600, 150)</script>
<p>
If you hover the mouse over the "Beta" node, you will see the tooltip showing the description string.
The detailed template does not bother using tooltips to show extra information because everything is already shown.
</p>
<p>
By default the way that the model and diagram know about the category of a node data or a link data is
by looking at its category property.
If you want to use a different property on the data, for example because you want to use the category
property to mean something different, set <a>Model.nodeCategoryProperty</a> to be the name
of the property that results in the actual category string value.
Or set <a>Model.nodeCategoryProperty</a> to be the empty string to cause all nodes to use the default node template.
</p>
<h2 id="ExampleOfItemTemplates">Example of Item Templates</h2>
<p>
For Panels with a value for <a>Panel.itemArray</a>, there is also the <a>Panel.itemTemplateMap</a>.
As with Nodes and Groups and Links, the <a>Panel.itemTemplate</a> is just a reference to the template named
with the empty string in the <a>Panel.itemTemplateMap</a>.
Similarly, the <a>Panel.itemCategoryProperty</a> names the property on the item data that identifies the
template to use from the itemTemplateMap.
</p>
<pre class="lang-js" id="itemTemplates">
// create a template map for items
var itemtemplates = new go.Map(); // In TypeScript you could write: new go.Map&lt;string, go.Panel&gt;();
// the template when type == "text"
itemtemplates.add("text",
$(go.Panel,
$(go.TextBlock,
new go.Binding("text"))
));
// the template when type == "button"
itemtemplates.add("button",
$("Button",
$(go.TextBlock,
new go.Binding("text")),
// convert a function name into a function value,
// because functions cannot be represented in JSON format
new go.Binding("click", "handler",
function(name) {
if (name === "alert") return raiseAlert; // defined below
return null;
})
));
diagram.nodeTemplate =
$(go.Node, "Vertical",
$(go.TextBlock,
new go.Binding("text", "key")),
$(go.Panel, "Auto",
$(go.Shape, { fill: "white" }),
$(go.Panel, "Vertical",
{
margin: 3,
defaultAlignment: go.Spot.Left,
itemCategoryProperty: "type", // this property controls the template used
itemTemplateMap: itemtemplates // map was defined above
},
new go.Binding("itemArray", "info"))
)
);
function raiseAlert(e, obj) { // here OBJ will be the item Panel
var node = obj.part;
alert(node.data.key + ": " + obj.data.text);
}
// The model data includes item arrays in the node data.
diagram.model = new go.GraphLinksModel( [
{ key: "Alpha",
info: [
{ type: "text", text: "some text" },
{ type: "button", text: "Click me!", handler: "alert"}
]
},
{ key: "Beta",
info: [
{ type: "text", text: "first line" },
{ type: "button", text: "First Button", handler: "alert"},
{ type: "text", text: "second line" },
{ type: "button", text: "Second Button", handler: "alert" }
]
}
],[
{ from: "Alpha", to: "Beta" }
]);
</pre>
<script>goCode("itemTemplates", 600, 150)</script>
<h2 id="ExampleOfTableHeaderShowingItemData">Example of Table Header Showing Item Data</h2>
<p>
The natural way to have a distinct header for a Table Panel is to have the first row (i.e. the first item)
hold the data for the header, but have it be styled differently.
In this example we define a "Header" item template in the <a>Panel.itemTemplateMap</a>.
</p>
<pre class="lang-js" id="header">
var itemTemplateMap = new go.Map();
itemTemplateMap.add("",
$(go.Panel, "TableRow",
$(go.TextBlock, new go.Binding("text", "name"),
{ column: 0, margin: 2, font: "bold 10pt sans-serif" }),
$(go.TextBlock, new go.Binding("text", "phone"),
{ column: 1, margin: 2 }),
$(go.TextBlock, new go.Binding("text", "loc"),
{ column: 2, margin: 2 })
));
itemTemplateMap.add("Header",
$(go.Panel, "TableRow",
$(go.TextBlock, new go.Binding("text", "name"),
{ column: 0, margin: 2, font: "bold 10pt sans-serif" }),
$(go.TextBlock, new go.Binding("text", "phone"),
{ column: 1, margin: 2, font: "bold 10pt sans-serif" }),
$(go.TextBlock, new go.Binding("text", "loc"),
{ column: 2, margin: 2, font: "bold 10pt sans-serif" })
));
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, { fill: "white" }),
$(go.Panel, "Table",
new go.Binding("itemArray", "people"),
{
defaultAlignment: go.Spot.Left,
defaultColumnSeparatorStroke: "black",
itemTemplateMap: itemTemplateMap
},
$(go.RowColumnDefinition,
{ row: 0, background: "lightgray" }),
$(go.RowColumnDefinition,
{ row: 1, separatorStroke: "black" })
)
);
diagram.model =
$(go.GraphLinksModel,
{
nodeDataArray: [
{ key: "group1",
people: [
{ name: "Person", phone: "Phone", loc: "Location", category: "Header" },
{ name: "Alice", phone: "2345", loc: "C4-E18" },
{ name: "Bob", phone: "9876", loc: "E1-B34" },
{ name: "Carol", phone: "1111", loc: "C4-E23" },
{ name: "Ted", phone: "2222", loc: "C4-E197" },
{ name: "Robert", phone: "5656", loc: "B1-A27" },
{ name: "Natalie", phone: "5698", loc: "B1-B6" }
] }
],
linkDataArray: [
]
}
);
</pre>
<script>goCode("header", 600, 200)</script>
<p>
If you do not want to have the header data in the itemArray,
and you want to define the header in the node template rather than as an item template,
see the example in <a href="itemArrays.html">Item Arrays</a>.
</p>
<h2 id="ChangingCategoryOfPart">Changing category of a Part</h2>
<p>
To change the representation of a data object, call <a>Model.setCategoryForNodeData</a>
or <a>GraphLinksModel.setCategoryForLinkData</a>.
(If you set the <a>Part.category</a> of a data bound <a>Part</a>, it will call the Model method for you.)
This causes the diagram to discard any existing Part for the data and re-create it using the new template that
is associated with the new category value.
</p>
<pre class="lang-js" id="changingCategory">
// this function changes the category of the node data to cause the Node to be replaced
function changeCategory(e, obj) {
var node = obj.part;
if (node) {
var diagram = node.diagram;
diagram.startTransaction("changeCategory");
var cat = diagram.model.getCategoryForNodeData(node.data);
if (cat === "simple")
cat = "detailed";
else
cat = "simple";
diagram.model.setCategoryForNodeData(node.data, cat);
diagram.commitTransaction("changeCategory");
}
}
// The "simple" template just shows the key string and the color in the background.
// There is a Button to invoke the changeCategory function.
var simpletemplate =
$(go.Node, "Spot",
$(go.Panel, "Auto",
$(go.Shape, "Ellipse",
new go.Binding("fill", "color")),
$(go.TextBlock,
new go.Binding("text", "key"))
),
$("Button",
{ alignment: go.Spot.TopRight },
$(go.Shape, "AsteriskLine", { width: 8, height: 8 }),
{ click: changeCategory })
);
// The "detailed" template shows all of the information in a Table Panel.
// There is a Button to invoke the changeCategory function.
var detailtemplate =
$(go.Node, "Spot",
$(go.Panel, "Auto",
$(go.Shape, "RoundedRectangle",
new go.Binding("fill", "color")),
$(go.Panel, "Table",
{ defaultAlignment: go.Spot.Left },
$(go.TextBlock, { row: 0, column: 0, columnSpan: 2, font: "bold 12pt sans-serif" },
new go.Binding("text", "key")),
$(go.TextBlock, { row: 1, column: 0 }, "Description:"),
$(go.TextBlock, { row: 1, column: 1 }, new go.Binding("text", "desc")),
$(go.TextBlock, { row: 2, column: 0 }, "Color:"),
$(go.TextBlock, { row: 2, column: 1 }, new go.Binding("text", "color"))
)
),
$("Button",
{ alignment: go.Spot.TopRight },
$(go.Shape, "AsteriskLine", { width: 8, height: 8 }),
{ click: changeCategory })
);
var templmap = new go.Map(); // In TypeScript you could write: new go.Map&lt;string, go.Node&gt;();
templmap.add("simple", simpletemplate);
templmap.add("detailed", detailtemplate);
diagram.nodeTemplateMap = templmap;
diagram.layout = $(go.TreeLayout);
diagram.model.nodeDataArray = [
{ key: "Beta", desc: "second letter", color: "lightblue", category: "simple" },
{ key: "Gamma", desc: "third letter", color: "pink", category: "detailed" },
{ key: "Delta", desc: "fourth letter", color: "cyan", category: "detailed" }
];
diagram.model.linkDataArray = [
{ from: "Beta", to: "Gamma" },
{ from: "Gamma", to: "Delta" }
];
</pre>
<script>goCode("changingCategory", 600, 150)</script>
<p>
Click on the "asterisk" button on any node to toggle dynamically between the "simple" and the "detailed" category for each node.
</p>
<h2 id="ChangingTemplateMaps">Changing template maps</h2>
<p>
You can also replace one or all of the diagram's template maps (e.g. <a>Diagram.nodeTemplateMap</a>)
in order to discard and re-create all of the nodes in the diagram.
If you are only using the default template for nodes, you would only need to replace the <a>Diagram.nodeTemplate</a>.
</p>
<p>
One common circumstance for doing this is as the <a>Diagram.scale</a> changes.
When the user zooms out far enough, there is no point in having too much detail about each of the nodes.
</p>
<p>
If you zoom out in this example, the <a>DiagramEvent</a> listener will detect when the <a>Diagram.scale</a>
becomes small enough to use the simpler template for all of the nodes.
Zoom in again and suddenly it uses the more detailed template.
</p>
<pre class="lang-js" id="changingMap">
// The "simple" template just shows the key string and the color in the background.
var simpletemplate =
$(go.Node, "Spot",
$(go.Panel, "Auto",
$(go.Shape, "Ellipse",
new go.Binding("fill", "color")),
$(go.TextBlock,
new go.Binding("text", "key"))
)
);
// The "detailed" template shows all of the information in a Table Panel.
var detailtemplate =
$(go.Node, "Spot",
$(go.Panel, "Auto",
$(go.Shape, "RoundedRectangle",
new go.Binding("fill", "color")),
$(go.Panel, "Table",
{ defaultAlignment: go.Spot.Left },
$(go.TextBlock, { row: 0, column: 0, columnSpan: 2, font: "bold 12pt sans-serif" },
new go.Binding("text", "key")),
$(go.TextBlock, { row: 1, column: 0 }, "Description:"),
$(go.TextBlock, { row: 1, column: 1 }, new go.Binding("text", "desc")),
$(go.TextBlock, { row: 2, column: 0 }, "Color:"),
$(go.TextBlock, { row: 2, column: 1 }, new go.Binding("text", "color"))
)
)
);
diagram.layout = $(go.TreeLayout);
diagram.model.nodeDataArray = [
{ key: "Beta", desc: "second letter", color: "lightblue" },
{ key: "Gamma", desc: "third letter", color: "pink" },
{ key: "Delta", desc: "fourth letter", color: "cyan" }
];
diagram.model.linkDataArray = [
{ from: "Beta", to: "Gamma" },
{ from: "Gamma", to: "Delta" }
];
// initially use the detailed templates
diagram.nodeTemplate = detailtemplate;
diagram.addDiagramListener("ViewportBoundsChanged",
function (e) {
if (diagram.scale &lt; 0.9) {
diagram.nodeTemplate = simpletemplate;
} else {
diagram.nodeTemplate = detailtemplate;
}
});
myDiagram = diagram; // make accessible to the HTML buttons
</pre>
<script>goCode("changingMap", 600, 150)</script>
<input id="ZoomOut" type="button" onclick="myDiagram.commandHandler.decreaseZoom()" value="Zoom Out" />
<input id="ZoomIn" type="button" onclick="myDiagram.commandHandler.increaseZoom()" value="Zoom In" />
<p>
Caution: if you modify a template <a>Map</a>, there is no notification that the map has changed.
You will need to call <a>Diagram.rebuildParts</a> explicitly.
If you are replacing the <a>Diagram.nodeTemplate</a> or the <a>Diagram.nodeTemplateMap</a>
or the corresponding properties for Groups or Links, the Diagram property setters will automatically
call <a>Diagram.rebuildParts</a>.
</p>
<p>
When one or more templates are replaced in a diagram, layouts are automatically performed again.
</p>
</div>
</div>
</body>
</html>
+336
View File
@@ -0,0 +1,336 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS TextBlocks -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>TextBlocks</h1>
<p>
Use the <a>TextBlock</a> class to display text.
</p>
<p>
Setting the <a>TextBlock.text</a> property is the only way to show a text string.
Because TextBlock inherits from <a>GraphObject</a>, some GraphObject properties will affect text.
But there are additional text-only options regarding how that text is formatted and drawn.
</p>
<p>
In these simplistic demonstrations, the code programmatically creates a Part and adds it to the Diagram.
Once you learn about models and data binding you will generally not create parts (nodes or links) programmatically.
</p>
<h2 id="FontAndColors">Font and colors</h2>
<p>
The size and stylistic appearance of the text is specified by the <a>TextBlock.font</a>.
The value may be any CSS font specifier string.
</p>
<p>
The text is drawn using the <a>TextBlock.stroke</a> brush.
The value may be any CSS color string or a <a>Brush</a>.
By default the stroke is "black".
</p>
<p>
You can also specify the brush to use as the background: <a>GraphObject.background</a>.
This defaults to no brush at all, which results in a transparent background.
The background is always rectangular.
</p>
<p>
In these simplistic demonstrations, the code programmatically creates a Part and adds it to the Diagram.
Once you learn about models and data binding you will generally not create parts (nodes or links) programmatically.
</p>
<pre class="lang-js" id="basicTextBlocks">
diagram.add(
$(go.Part, "Vertical",
$(go.TextBlock, { text: "a Text Block" }),
$(go.TextBlock, { text: "a Text Block", stroke: "red" }),
$(go.TextBlock, { text: "a Text Block", background: "lightblue" }),
$(go.TextBlock, { text: "a Text Block", font: "bold 14pt serif" })
));
</pre>
<script>goCode("basicTextBlocks", 600, 100)</script>
<h3 id="IconFonts">Icon Fonts</h3>
<p>
In some cases, you can show an icon that is provided by a font, instead of using a <a>Picture</a> or a <a>Shape</a>.
First, make sure the font is loaded in the page before creating the diagram.
</p>
<pre>
&lt;link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"&gt;
</pre>
<pre class="lang-js" id="awesomeFont">
diagram.add(
$(go.Node, "Auto",
$(go.Shape, { fill: "lightgreen" }),
$(go.Panel, "Horizontal",
{ margin: 8 },
$(go.TextBlock,
{ text: '\uf030', font: '10pt FontAwesome' }),
$(go.TextBlock, "an example using FontAwesome",
{ margin: new go.Margin(0, 0, 0, 2) })
)
));
</pre>
<script>goCode("awesomeFont", 600, 100)</script>
<h2 id="NaturalSizingOfTextBlocksVariesByBrowser">Natural Sizing of TextBlocks Varies by Browser</h2>
<p>
Because different browsers measure canvas text differently,
TextBlocks are the only objects in <b>GoJS</b> that may have inconsistent natural sizes between browsers or different devices.
For this reason, if you need objects to measure precisely and consistently across all browsers,
TextBlocks without an explicit size (<a>GraphObject.desiredSize</a>) should not be used to dictate
the size of any objects (ie, a TextBlock with no explicit size should not be the main element
of a <a>Panel</a> of type <a>Panel.Auto</a>).
</p>
<h2 id="SizingAndClipping">Sizing and Clipping</h2>
<p>
The natural size of a <a>TextBlock</a> is just big enough to render the text string with the given font.
However the actual size of the TextBlock can be larger or smaller in either dimension.
Larger dimensions result in areas with no text; smaller dimensions result in clipping.
</p>
<p>
To demonstrate this, the examples below start with a naturally sized TextBlock,
followed by ones with decreasing explicit sizes.
To better show the actual size of the TextBlocks below, we have given them lightgreen backgrounds.
</p>
<pre class="lang-js" id="sizingTextBlocks">
diagram.add(
$(go.Part, "Vertical",
$(go.TextBlock, { text: "a Text Block", background: "lightgreen", margin: 2 }),
$(go.TextBlock, { text: "a Text Block", background: "lightgreen", margin: 2,
width: 100, height: 33 }),
$(go.TextBlock, { text: "a Text Block", background: "lightgreen", margin: 2,
width: 60, height: 33 }),
$(go.TextBlock, { text: "a Text Block", background: "lightgreen", margin: 2,
width: 50, height: 22 }),
$(go.TextBlock, { text: "a Text Block", background: "lightgreen", margin: 2,
width: 40, height: 9 })
));
</pre>
<script>goCode("sizingTextBlocks", 600, 160)</script>
<h2 id="MaxLinesAndOverflow">Max Lines and Overflow</h2>
<p>
You can constrain the TextBlock's available size using <a>GraphObject.desiredSize</a> (width and height),
but you can also limit the vertical height with <a>TextBlock.maxLines</a>, which will limit the number allowed.
When there isn't enough space to display all text, you can decide how to use the remaining space with different
values for <a>TextBlock.overflow</a>. There are additional options in the wrapping section below.
</p>
<p>
The example below starts with a naturally sized TextBlock,
followed by ones with a max of 2 lines using the default <a>TextBlock.overflow</a> value of <code>OverflowClip</code>,
followed by one using the <a>TextBlock.overflow</a> value of <code>OverflowEllipsis</code>.
</p>
<pre class="lang-js" id="sizingTextBlocks2">
diagram.contentAlignment = go.Spot.Center,
diagram.add(
$(go.Part, "Vertical",
// Allow any number of lines, no clipping needed:
$(go.TextBlock, { text: "a Text Block that takes 4 lines",
font: '14pt sans-serif',
background: "lightblue",
overflow: go.TextBlock.OverflowClip /* the default value */,
// No max lines
margin: 2,
width: 90 }),
// Allow only 2 lines, OverflowClip:
$(go.TextBlock, { text: "a Text Block that takes 4 lines",
font: '14pt sans-serif',
background: "lightblue",
overflow: go.TextBlock.OverflowClip /* the default value */,
maxLines: 2,
margin: 2,
width: 90 }),
// Allow only 2 lines, OverflowEllipsis:
$(go.TextBlock, { text: "a Text Block that takes 4 lines",
font: '14pt sans-serif',
background: "lightblue",
overflow: go.TextBlock.OverflowEllipsis,
maxLines: 2,
margin: 2,
width: 90 })
));
</pre>
<script>goCode("sizingTextBlocks2", 600, 200)</script>
<h2 id="Wrapping">Wrapping</h2>
<p>
Text can also be automatically wrapped onto additional lines.
In order for wrapping to happen, the <a>TextBlock.wrap</a> property must not be None,
and there must be some constraint on the width to be narrower than it would naturally be.
</p>
<p>
In the following examples, the first TextBlock gets its natural size,
the second is limited to 50 wide but is not allowed to wrap, and the
other examples are limited to the same width but are allowed to wrap.
</p>
<pre class="lang-js" id="wrappingTextBlocks">
diagram.add(
$(go.Part, "Vertical",
$(go.TextBlock, { text: "a Text Block", background: "lightgreen", margin: 2 }),
$(go.TextBlock, { text: "a Text Block", background: "lightgreen", margin: 2,
width: 50, wrap: go.TextBlock.None }),
$(go.TextBlock, { text: "a Text Block", background: "lightgreen", margin: 2,
width: 50, wrap: go.TextBlock.WrapDesiredSize }),
$(go.TextBlock, { text: "a Text Block", background: "lightgreen", margin: 2,
width: 50, wrap: go.TextBlock.WrapFit })
));
</pre>
<script>goCode("wrappingTextBlocks", 600, 120)</script>
<h2 id="TextAlignment">Text Alignment</h2>
<p>
The <a>TextBlock.textAlign</a> property specifies where to draw the characters horizontally within the size of the <a>TextBlock</a>.
The value must be a CSS string.
</p>
<p>
This is different than the <a>GraphObject.alignment</a> property,
which controls where to place the object within the area allocated by the parent <a>Panel</a>.
</p>
<pre class="lang-js" id="textAlignTextBlocks">
diagram.add(
$(go.Part, "Horizontal",
$(go.Panel, "Vertical",
{ width: 150, defaultStretch: go.GraphObject.Horizontal },
$(go.TextBlock, { text: "textAlign: 'left'", background: "lightgreen", margin: 2,
textAlign: "left" }),
$(go.TextBlock, { text: "textAlign: 'center'", background: "lightgreen", margin: 2,
textAlign: "center" }),
$(go.TextBlock, { text: "textAlign: 'right'", background: "lightgreen", margin: 2,
textAlign: "right" })
),
$(go.Panel, "Vertical",
{ width: 150, defaultStretch: go.GraphObject.None },
$(go.TextBlock, { text: "alignment: Left", background: "lightgreen", margin: 2,
alignment: go.Spot.Left }),
$(go.TextBlock, { text: "alignment: Center", background: "lightgreen", margin: 2,
alignment: go.Spot.Center }),
$(go.TextBlock, { text: "alignment: Right", background: "lightgreen", margin: 2,
alignment: go.Spot.Right })
)
));
</pre>
<script> goCode("textAlignTextBlocks", 600, 100)</script>
<p>
The <a>TextBlock.verticalAlignment</a> property controls the vertical alignment of the glyphs within the bounds.
Neither <a>TextBlock.textAlign</a> nor <a>TextBlock.verticalAlignment</a> affect the sizing of the TextBlock.
</p>
<pre class="lang-js" id="verticalAlignment">
diagram.add(
$(go.Part, "Horizontal",
$(go.TextBlock, { text: "verticalAlignment: Top", verticalAlignment: go.Spot.Top,
width: 170, height: 60, background: "lightgreen", margin: 10 }),
$(go.TextBlock, { text: "verticalAlignment: Center", verticalAlignment: go.Spot.Center,
width: 170, height: 60, background: "lightgreen", margin: 10 }),
$(go.TextBlock, { text: "verticalAlignment: Bottom", verticalAlignment: go.Spot.Bottom,
width: 170, height: 60, background: "lightgreen", margin: 10 })
));
</pre>
<script> goCode("verticalAlignment", 600, 100)</script>
<h2 id="TextAlignAndMultilineOrWrapping">TextAlign and Multiline or Wrapping</h2>
<p>
The <a>TextBlock.textAlign</a> property is useful even when the TextBlock has its natural size.
This occurs when the text occupies multiple lines, whether by embedded newlines causing line breaks or by wrapping.
You can control whether text starting with the first newline character is ignored by setting the <a>TextBlock.isMultiline</a>.
By default both multiline and wrapping are enabled.
</p>
<pre class="lang-js" id="multilineTextBlocks">
diagram.add(
$(go.Part, "Vertical",
$(go.TextBlock, { text: "a Text Block\nwith three logical lines\nof text",
background: "lightgreen", margin: 2,
isMultiline: false }),
$(go.TextBlock, { text: "a Text Block\nwith three logical lines\nof text",
background: "lightgreen", margin: 2,
isMultiline: true }),
$(go.TextBlock, { text: "a Text Block\nwith three logical lines\nof centered text",
background: "lightgreen", margin: 2,
isMultiline: true, textAlign: "center" }),
$(go.TextBlock, { text: "a single line of centered text that should" +
" wrap because we will limit the width",
background: "lightgreen", margin: 2, width: 80,
wrap: go.TextBlock.WrapFit, textAlign: "center" })
));
</pre>
<script>goCode("multilineTextBlocks", 600, 230)</script>
<h2 id="Flipping">Flipping</h2>
<p>
You can flip text horizontally and vertically with the <a>TextBlock.flip</a> property:
</p>
<pre class="lang-js" id="flipPictures">
diagram.add(
$(go.Part, "Table",
{ defaultColumnSeparatorStrokeWidth: 3, defaultColumnSeparatorStroke: "gray", defaultSeparatorPadding: 5 },
$(go.TextBlock, { text: "Hello", column: 0, margin: 2, font: '26px serif',
flip: go.GraphObject.None
}),
$(go.TextBlock, "None (default)", { row: 1, column: 0 }),
$(go.TextBlock, { text: "Hello", column: 1, margin: 2, font: '26px serif',
flip: go.GraphObject.FlipHorizontal
}),
$(go.TextBlock, "FlipHorizontal", { row: 1, column: 1 }),
$(go.TextBlock, { text: "Hello", column: 2, margin: 2, font: '26px serif',
flip: go.GraphObject.FlipVertical
}),
$(go.TextBlock, "FlipVertical", { row: 1, column: 2 }),
$(go.TextBlock, { text: "Hello", column: 3, margin: 2, font: '26px serif',
flip: go.GraphObject.FlipBoth
}),
$(go.TextBlock, "FlipBoth", { row: 1, column: 3 })
));
</pre>
<script>goCode("flipPictures", 600, 160)</script>
<h2 id="Editing">Editing</h2>
<p>
<b>GoJS</b> also supports the in-place editing of text by the user.
You just need to set the <a>TextBlock.editable</a> property to true.
</p>
<p>
If you want to provide text validation of the user's input, you can set the <a>TextBlock.textValidation</a> property to a function.
You can also provide a more customized or sophisticated text editor by setting the <a>TextBlock.textEditor</a> property.
There is an example of text validation on the <a href="validation.html">Validation intro page.</a>
</p>
<pre class="lang-js" id="editingTextBlocks">
diagram.add(
$(go.Part,
$(go.TextBlock,
{ text: "select and then click to edit",
background: "lightblue",
editable: true, isMultiline: false })
));
diagram.add(
$(go.Part,
$(go.TextBlock,
{ text: "this one allows embedded newlines",
background: "lightblue",
editable: true })
));
</pre>
<script>goCode("editingTextBlocks", 600, 100)</script>
</div>
</div>
</body>
</html>
+774
View File
@@ -0,0 +1,774 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Tools -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="../extensions/Figures.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Tools</h1>
<p>
<a>Tool</a>s handle all of the input events.
There are many kinds of predefined Tool classes that implement all of the common operations that users do.
</p>
<p>
For flexibility and simplicity, all input events are canonicalized as <a>InputEvent</a>s and
redirected by the diagram to go to the <a>Diagram.currentTool</a>.
By default the Diagram.currentTool is an instance of <a>ToolManager</a> held as the <a>Diagram.toolManager</a>.
The ToolManager implements support for all mode-less tools.
The ToolManager is responsible for finding another tool that is ready to run and then making it the new current tool.
This causes the new tool to process all of the input events (mouse, keyboard, and touch) until the tool decides that it is finished,
at which time the diagram's current tool reverts back to the <a>Diagram.defaultTool</a>, which is normally the ToolManager, again.
</p>
<p>
Although the terminology includes the word "mouse", often that refers to both mouse events and touch events.
</p>
<p>
See samples that make use of <a>Tool</a>s in the <a href="../samples/index.html#tools">samples index</a>.
</p>
<h2 id="PredefinedTools">Predefined Tools</h2>
<p>
Each <a>Diagram</a> has an instance of most of the tool classes, all managed by the diagram's <a>ToolManager</a>.
If you want to change the interactive behavior, in many common cases you may be able to do so by setting properties
on the <a>Diagram</a>, on your <a>Part</a>s, or on individual <a>GraphObject</a>s.
But more generally you may need to modify one or more of the tools, which are accessible as properties of the <a>Diagram.toolManager</a>.
</p>
<p>
Some tools want to run when a mouse-down occurs. These tools include:
</p>
<ul>
<li><a>ToolManager.actionTool</a>, an <a>ActionTool</a>, for allowing "buttons" and other <a>GraphObject</a>s to grab events from the regular tools</li>
<li><a>ToolManager.relinkingTool</a>, a <a>RelinkingTool</a>, for reconnecting an existing <a>Link</a></li>
<li><a>ToolManager.linkReshapingTool</a>, a <a>LinkReshapingTool</a>, for changing the route of a <a>Link</a></li>
<li><a>ToolManager.resizingTool</a>, a <a>ResizingTool</a>, for changing the <a>GraphObject.desiredSize</a> of a <a>Part</a> or an object within a <a>Part</a></li>
<li><a>ToolManager.rotatingTool</a>, a <a>RotatingTool</a>, for changing the <a>GraphObject.angle</a> of a <a>Part</a> or an object within a <a>Part</a></li>
</ul>
<p>
Some tools want to run when a mouse-move occurs after a mouse-down. These tools include:
</p>
<ul>
<li><a>ToolManager.linkingTool</a>, a <a>LinkingTool</a>, for drawing a new <a>Link</a></li>
<li><a>ToolManager.draggingTool</a>, a <a>DraggingTool</a>, for moving or copying selected <a>Part</a>s</li>
<li><a>ToolManager.dragSelectingTool</a>, a <a>DragSelectingTool</a>, for rubber-band selection of some <a>Part</a>s within a rectangular area</li>
<li><a>ToolManager.panningTool</a>, a <a>PanningTool</a>, for panning/scrolling the diagram</li>
</ul>
<p>
Some tools only want to run upon a mouse-up event after a mouse-down. These tools include:
</p>
<ul>
<li><a>ToolManager.contextMenuTool</a>, a <a>ContextMenuTool</a>, for showing a context menu for a <a>GraphObject</a></li>
<li><a>ToolManager.textEditingTool</a>, a <a>TextEditingTool</a>, for in-place editing of <a>TextBlock</a>s in selected <a>Part</a>s</li>
<li><a>ToolManager.clickCreatingTool</a>, a <a>ClickCreatingTool</a>, for inserting a new <a>Part</a> when the user clicked</li>
<li><a>ToolManager.clickSelectingTool</a>, a <a>ClickSelectingTool</a>, for selecting or de-selecting a <a>Part</a></li>
</ul>
<p>
To change the behavior of a tool, you may be able to set properties on the tool, on the <a>Diagram</a>, on a particular <a>Part</a>,
or on a particular <a>GraphObject</a>.
</p>
<ul>
<li>For example, to disable the rubber-band selection tool (<a>DragSelectingTool</a>), set
<code>diagram.toolManager.dragSelectingTool.isEnabled = false;</code>.</li>
<li>You can change the appearance of a selected Part (actually its selection Adornment) by setting <a>Part.selectionAdornmentTemplate</a>.
(See <a href="selection.html">Selection</a> for more discussion.)</li>
<li>You can enable users to draw new links interactively (<a>LinkingTool</a>) by
setting <a>GraphObject.fromLinkable</a> and <a>GraphObject.toLinkable</a> on the port objects of your nodes.</li>
<li>You can disable the movement of a Part (<a>DraggingTool</a>), including Nodes and Groups, by setting <a>Part.movable</a> to false.</li>
<li>You can limit the movement of a Part by setting <a>Part.minLocation</a> and/or <a>Part.maxLocation</a>.
For more general limitations, set <a>Part.dragComputation</a> to a function that computes the desired new location.</li>
<li>You can disable resizing any part (<a>ResizingTool</a>) by setting <a>Diagram.allowResize</a> to false.</li>
<li>Tooltips, implemented by the <a>ToolManager</a>, are discussed in <a href="toolTips.html">ToolTips</a>.</li>
<li>Context menus, implemented by the <a>ContextMenuTool</a>, are discussed in <a href="contextMenus.html">Context Menus</a>.</li>
</ul>
<p>
More detail is available in the section about <a href="permissions.html">Permissions</a>.
</p>
<p>
Some commonly set properties include:
</p>
<ul>
<li>Enable inserting parts via double-clicking by the <a>ClickCreatingTool</a> by setting <a>ClickCreatingTool.archetypeNodeData</a> to a node data object.</li>
<li>Control what parts become selected by <a>DragSelectingTool</a> by setting <a>DragSelectingTool.isPartialInclusion</a>.</li>
<li>Customize the link data that is copied when a new link is drawn by <a>LinkingTool</a> by setting <a>LinkingTool.archetypeLinkData</a>.</li>
<li>Limit how parts are resized by the <a>ResizingTool</a> by setting <a>ResizingTool.cellSize</a>,
<a>ResizingTool.maxSize</a>, or <a>ResizingTool.minSize</a>.</li>
<li>Limit how parts are rotated by the <a>RotatingTool</a> by setting <a>RotatingTool.snapAngleEpsilon</a> or
<a>RotatingTool.snapAngleMultiple</a>.</li>
</ul>
<p>
Remember that all of the individual tools are available via the <a>Diagram.toolManager</a>.
For example, to enable the <a>ClickCreatingTool</a>:
</p>
<pre class="lang-js">
myDiagram.toolManager.clickCreatingTool.archetypeNodeData =
{ key: "Node", text: "some description", color: "green" };
</pre>
<p>
You can also set tool properties when using <a>GraphObject,make</a> to define your <a>Diagram</a>:
</p>
<pre class="lang-js">
var diagram =
$(go.Diagram, "myDiagramDiv",
{
allowCopy: false,
"grid.visible": true,
"grid.gridCellSize": new go.Size(30, 20),
"clickCreatingTool.archetypeNodeData": // a node data JavaScript object
{ key: "Node", text: "some description", color: "green" },
"dragSelectingTool.box": // an unbound Part
$(go.Part, { layerName: "Tool" },
$(go.Shape, { name: "SHAPE", fill: null, stroke: "blue", strokeWidth: 3 }) ),
"draggingTool.isGridSnapEnabled": true,
"linkReshapingTool.handleArchetype": // a GraphObject that is copied for each handle
$(go.Shape, { width: 10, height: 10, fill: "yellow" }),
"resizingTool.isGridSnapEnabled": true,
"rotatingTool.snapAngleMultiple": 90,
"rotatingTool.snapAngleEpsilon": 45
}
);
</pre>
<p>
At this time the syntax for setting properties on predefined subobjects only works for the <a>Diagram</a> class.
</p>
<h2 id="ToolLifecycle">The Tool Lifecycle</h2>
<p>While each prebuilt tool in GoJS is used for a different purpose, all Tools are guaranteed to share some functions and properties.
All tools share a general "lifecycle" -- that is, the order in which these common functions are called. One can think of this
cycle as "starting" when the ToolManager is alerted of some input event and begins searching through the pertinent list of tools (i.e.,
if the mouse-down event is registered, ToolManager starts searching its <a>ToolManager.mouseDownTools</a> list). Below is a diagram
representing the general lifecycle of a tool.
<pre class="lang-js" id="toolLifecycle" style="display: none">
diagram.nodeTemplate =
$(go.Node, "Auto", { locationSpot: go.Spot.Center },
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
$(go.Shape, "RoundedRectangle", { fill: "lightgreen", stroke: "lightgray" },
new go.Binding("fill", "color")),
$(go.TextBlock,
{
margin: 8,
wrap: go.TextBlock.WrapFit,
width: 170,
font: "bold 10pt sans-serif",
textAlign: "center"
},
new go.Binding("text"))
);
diagram.linkTemplate =
$(go.Link,
new go.Binding("curve"),
$(go.Shape),
$(go.Shape, // the arrowhead
{ toArrow: "OpenTriangle", fill: null }),
$(go.TextBlock, { margin: 1, segmentOffset: new go.Point(0,0) },
new go.Binding("text"),
new go.Binding("segmentOffset"),
new go.Binding("segmentOrientation"))
);
diagram.model =
$(go.GraphLinksModel,
{
nodeDataArray:
[
{ key: 1, text: "ToolManager receives mouse event and searches a tool list such as mouseDownTools", loc: "50 0" },
{ key: 2, text: "tool.canStart()", loc: "50 100", color: "lightyellow" },
{ key: 3, text: "toolManager.currentTool = the chosen tool", loc: "50 200" },
{ key: 4, text: "tool.doStart()", loc: "50 275", color: "lightyellow" },
{ key: 5, text: "tool.doActivate()", loc: "50 350", color: "lightyellow" },
{ key: 6, text: "isActive === true", loc: "50 425" },
{ key: 7, text: "tool.doMouseDown() or\ntool.doMouseMove() or\ntool.doMouseUp() or\ntool.doMouseWheel() or\ntool.doKeyDown() or\ntool.doKeyUp()",
loc: "-200 500", color: "lightyellow" },
{ key: 8, text: "tool.doCancel()", loc: "250 500", color: "lightyellow" },
{ key: 9, text: "tool.stopTool()", loc: "50 575", color: "lightyellow" },
{ key: 10, text: "toolManager.currentTool = toolManager.defaultTool", loc: "50 650" },
{ key: 11, text: "tool.doDeactivate()", loc: "50 725", color: "lightyellow" },
{ key: 12, text: "tool.doStop()", loc: "50 800", color: "lightyellow" }
],
linkDataArray:
[
{ from: 1, to: 2, text: "on each tool call", segmentOffset: new go.Point(0,-50) },
{ from: 2, to: 3, text: "if it returns true", segmentOffset: new go.Point(0,-50) },
{ from: 3, to: 4 },
{ from: 4, to: 5 },
{ from: 5, to: 6 },
{ from: 6, to: 7, text: "Receives input", curve: go.Link.Bezier, segmentOrientation: go.Link.OrientOpposite, segmentOffset: new go.Point(0,10) },
{ from: 7, to: 6, text: "Input is not terminal", curve: go.Link.Bezier, segmentOrientation: go.Link.OrientAlong, segmentOffset: new go.Point(0,10) },
{ from: 7, to: 9, text: "Input is terminal", segmentOrientation: go.Link.OrientAlong, segmentOffset: new go.Point(0,10) },
{ from: 6, to: 8, text: "User cancels tool", segmentOrientation: go.Link.OrientAlong, segmentOffset: new go.Point(0,-10)},
{ from: 8, to: 9 },
{ from: 9, to: 10 },
{ from: 10, to: 11 },
{ from: 11, to: 12 },
]
}
);
</pre>
<script>goCode("toolLifecycle", 700, 900)</script>
<p>
For more information on how these specific functions work, see the <a>Tool</a> documentation.
</p>
<h2 id="ToolsAndAdornments">Tools and Adornments</h2>
<p>
<a>Adornment</a>s are used for more than indicating that a <a>Part</a> is selected.
Each <a>Tool</a> that is in the <a>ToolManager.mouseDownTools</a> list
(in other words, any mode-less tool that is started with a mouse-down or finger-down event)
gets the opportunity to add its own Adornments for its own purposes when a Part is selected.
</p>
<h3 id="ResizingTool">ResizingTool</h3>
<p>
When a <a>Part</a> is resizable, the <a>ResizingTool</a> adds an <a>Adornment</a> containing eight
resize handles, four at the corners and four at the middles of the sides.
</p>
<p>
If you want to let the user resize the whole node, just set <a>Part.resizable</a> to true.
In this case resizing will set the Node's <a>GraphObject.desiredSize</a>.
</p>
<pre class="lang-js" id="resizing">
diagram.add(
$(go.Node, "Auto",
{ resizable: true },
$(go.Shape, "RoundedRectangle", { fill: "orange" }),
$(go.TextBlock, "Hello!", { margin: 5 })
));
diagram.commandHandler.selectAll();
</pre>
<script>goCode("resizing", 600, 100)</script>
<p>
If you want the user to resize a particular object within the node,
you need to name that object and assign <a>Part.resizeObjectName</a>.
Resizing will set the <a>Part.resizeObject</a>'s <a>GraphObject.desiredSize</a>,
in this case the Shape's desiredSize.
</p>
<pre class="lang-js" id="resizingObject">
diagram.add(
$(go.Node, "Vertical",
{ resizable: true, resizeObjectName: "SHAPE", // resize the Shape, not the Node
selectionObjectName: "SHAPE" },
$(go.Shape, "RoundedRectangle",
{ name: "SHAPE", fill: "orange", width: 50, height: 30 }),
$(go.TextBlock, "Hello!", { margin: 3 })
));
diagram.commandHandler.selectAll();
</pre>
<script>goCode("resizingObject", 600, 100)</script>
<p>
You can limit the minimum and maximum size for the resized object by setting
<a>GraphObject.maxSize</a> and <a>GraphObject.minSize</a>.
Note that these GraphObject properties are set on the <a>Part.resizeObject</a>, not on the <a>Part</a> itself.
</p>
<pre class="lang-js" id="resizingMaxMin">
diagram.add(
$(go.Node, "Vertical",
{ resizable: true, resizeObjectName: "SHAPE",
selectionObjectName: "SHAPE" },
$(go.Shape, "RoundedRectangle",
{ name: "SHAPE", fill: "orange", width: 50, height: 30,
// limit size by setting or binding maxSize and/or minSize
maxSize: new go.Size(100, 40), minSize: new go.Size(20, 20) }),
$(go.TextBlock, "Hello!", { margin: 3 })
));
diagram.commandHandler.selectAll();
</pre>
<script>goCode("resizingMaxMin", 600, 100)</script>
<p>
You can also cause resizing to be multiples of a given size by setting <a>Part.resizeCellSize</a>.
</p>
<pre class="lang-js" id="resizingCellSize">
diagram.add(
$(go.Node, "Vertical",
{ resizable: true, resizeObjectName: "SHAPE",
resizeCellSize: new go.Size(10, 10), // new size will be multiples of resizeCellSize
selectionObjectName: "SHAPE" },
$(go.Shape, "RoundedRectangle",
{ name: "SHAPE", fill: "orange", width: 50, height: 30,
maxSize: new go.Size(100, 40), minSize: new go.Size(20, 20) }),
$(go.TextBlock, "Hello!", { margin: 3 })
));
diagram.commandHandler.selectAll();
</pre>
<script>goCode("resizingCellSize", 600, 100)</script>
<p>
When an object is resizable, it is commonplace to try to remember the new size by updating the model data, so that it can be saved and loaded later.
This can be accomplished with a TwoWay <a>Binding</a> on the <a>GraphObject.desiredSize</a> property.
But note that the binding needs to be on the actual GraphObject that is resized, not on the whole Node.
In this case, because the <a>Part.resizeObjectName</a> is referring to a Shape, that means the binding needs to be on the Shape.
</p>
<pre class="lang-js" id="resizingObjectBinding">
diagram.add(
$(go.Node, "Vertical",
{ resizable: true, resizeObjectName: "SHAPE",
selectionObjectName: "SHAPE" },
$(go.Shape, "RoundedRectangle",
{ name: "SHAPE", fill: "orange", width: 50, height: 30 },
// TwoWay Binding of the desiredSize
new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify)),
$(go.TextBlock, "Hello!", { margin: 3 })
));
diagram.commandHandler.selectAll();
</pre>
<script>goCode("resizingObjectBinding", 600, 100)</script>
<p>
You can customize the resize handles by setting <a>Part.resizeAdornmentTemplate</a>.
For example, to allow the user to only change the width of a Shape in a Node,
the <a>Adornment</a> should have only two resize handles: one at the left and one at the right.
The Adornment is implemented as a Spot Panel that surrounds a <a>Placeholder</a>,
representing the adorned Shape, with two rectangular blue Shapes, each representing a handle.
There is also a TextBlock placed above the adorned shape showing the shape's current width.
</p>
<pre class="lang-js" id="resizingTemplate">
diagram.add(
$(go.Node, "Vertical",
{ resizable: true, resizeObjectName: "SHAPE",
resizeAdornmentTemplate: // specify what resize handles there are and how they look
$(go.Adornment, "Spot",
$(go.Placeholder), // takes size and position of adorned object
$(go.Shape, "Circle", // left resize handle
{ alignment: go.Spot.Left, cursor: "col-resize",
desiredSize: new go.Size(9, 9), fill: "lightblue", stroke: "dodgerblue" }),
$(go.Shape, "Circle", // right resize handle
{ alignment: go.Spot.Right, cursor: "col-resize",
desiredSize: new go.Size(9, 9), fill: "lightblue", stroke: "dodgerblue" }),
$(go.TextBlock, // show the width as text
{ alignment: go.Spot.Top, alignmentFocus: new go.Spot(0.5, 1, 0, -2),
stroke: "dodgerblue" },
new go.Binding("text", "adornedObject",
function(shp) { return shp.naturalBounds.width.toFixed(0); })
.ofObject())
),
selectionAdorned: false }, // don't show selection Adornment, a rectangle
$(go.Shape, "RoundedRectangle",
{ name: "SHAPE", fill: "orange", width: 50, height: 30,
maxSize: new go.Size(100, 40), minSize: new go.Size(20, 20) }),
$(go.TextBlock, "Hello!", { margin: 3 })
));
diagram.commandHandler.selectAll();
</pre>
<script>goCode("resizingTemplate", 600, 100)</script>
<p>
Note also that because <a>Part.selectionAdorned</a> is false, there is no blue rectangle default selection adornment.
</p>
<p>
There are examples custom resizing tools defined in the samples and extensions directories:
<a href="../extensions/FloorPlanEditor.html">Resize Multiple Tool (in Floor Plan Editor)</a>,
<a href="../samples/swimLanes.html">Lane Resizing Tool (in Swim Lanes)</a>, and
<a href="../samples/swimLanesVertical.html">Lane Resizing Tool (in Swim Lanes Vertical)</a>.
</p>
<h3 id="RotatingTool">RotatingTool</h3>
<p>
When a <a>Part</a> is rotatable, the <a>RotatingTool</a> adds an <a>Adornment</a> containing one
rotate handle a short distance from the object at the object's angle.
Since the default <a>GraphObject.angle</a> is zero, the rotate handle typically starts to the right of the object.
</p>
<p>
If you want to let the user rotate the whole node, just set <a>Part.rotatable</a> to true.
Rotating will set the Node's <a>GraphObject.angle</a>.
</p>
<pre class="lang-js" id="rotating">
diagram.add(
$(go.Node, "Auto",
{ rotatable: true, locationSpot: go.Spot.Center },
$(go.Shape, "RoundedRectangle", { fill: "orange" }),
$(go.TextBlock, "Hello!", { margin: 5 })
));
diagram.commandHandler.selectAll();
</pre>
<script>goCode("rotating", 600, 150)</script>
<p>
If you want the user to rotate a particular object within the node,
you need to name that object and assign <a>Part.rotateObjectName</a>.
Rotating will set the <a>Part.rotateObject</a>'s <a>GraphObject.angle</a>,
in this case the Shape's angle.
</p>
<pre class="lang-js" id="rotatingObject">
diagram.add(
$(go.Node, "Vertical",
{ rotatable: true, rotateObjectName: "SHAPE", // rotate the Shape, not the Node
locationSpot: go.Spot.Center, locationObjectName: "SHAPE",
selectionObjectName: "SHAPE" },
$(go.Shape, "RoundedRectangle",
{ name: "SHAPE", fill: "orange", width: 50, height: 30 }),
$(go.TextBlock, "Hello!", { margin: 3 })
));
diagram.commandHandler.selectAll();
</pre>
<script>goCode("rotatingObject", 600, 150)</script>
<p>
When an object is rotatable, it is commonplace to try to remember the new angle by updating the model data, so that it can be saved and loaded later.
This can be accomplished with a TwoWay <a>Binding</a> on the <a>GraphObject.angle</a> property.
But note that the binding needs to be on the actual GraphObject that is rotated, not on the whole Node.
In this case, because the <a>Part.rotateObjectName</a> is referring to a Shape, that means the binding needs to be on the Shape.
</p>
<pre class="lang-js" id="rotatingObjectBinding">
diagram.add(
$(go.Node, "Vertical",
{ rotatable: true, rotateObjectName: "SHAPE",
locationSpot: go.Spot.Center, locationObjectName: "SHAPE",
selectionObjectName: "SHAPE" },
$(go.Shape, "RoundedRectangle",
{ name: "SHAPE", fill: "orange", width: 50, height: 30 },
new go.Binding("angle").makeTwoWay()), // TwoWay Binding of angle
$(go.TextBlock, "Hello!", { margin: 3 })
));
diagram.commandHandler.selectAll();
</pre>
<script>goCode("rotatingObjectBinding", 600, 150)</script>
<p>
Another common customization is to position the rotate handle above the object when it is not rotated,
i.e. when its <a>GraphObject.angle</a> is zero.
This is accomplished by setting <a>RotatingTool.handleAngle</a> to 270.
</p>
<pre class="lang-js" id="rotatingToolAngle">
diagram.add(
$(go.Node, "Auto",
{ rotatable: true, locationSpot: go.Spot.Center },
new go.Binding("angle").makeTwoWay(), // TwoWay Binding of Node.angle
$(go.Shape, "RoundedRectangle", { fill: "orange" }),
$(go.TextBlock, "Hello!", { margin: 5 })
));
diagram.toolManager.rotatingTool.handleAngle = 270;
diagram.commandHandler.selectAll();
</pre>
<script>goCode("rotatingToolAngle", 600, 150)</script>
<p>
You can customize the rotate handle by setting <a>Part.rotateAdornmentTemplate</a>.
</p>
<pre class="lang-js" id="rotatingTemplate">
diagram.add(
$(go.Node, "Vertical",
{ rotatable: true, rotateObjectName: "SHAPE",
locationSpot: go.Spot.Center, locationObjectName: "SHAPE",
rotateAdornmentTemplate: // specify appearance of rotation handle
$(go.Adornment,
{ locationSpot: go.Spot.Center },
$(go.Shape, "BpmnActivityLoop",
{ width: 12, height: 12, cursor: "pointer",
background: "transparent", stroke: "dodgerblue", strokeWidth: 2 })),
selectionObjectName: "SHAPE" },
$(go.Shape, "RoundedRectangle",
{ name: "SHAPE", fill: "orange", width: 50, height: 30 }),
$(go.TextBlock, "Hello!", { margin: 3 })
));
diagram.commandHandler.selectAll();
</pre>
<script>goCode("rotatingTemplate", 600, 150)</script>
<p>
There are example custom rotating tools defined in the samples and extensions directories:
<a href="../extensions/FloorPlanEditor.html">Rotate Multiple Tool (in Floor Plan Editor)</a> and
<a href="../samples/seatingChart.html">Horizontal Text Rotating Tool (in Seating Chart)</a>.
</p>
<h3 id="RelinkingTool">RelinkingTool</h3>
<p>
When a <a>Link</a> is <a>Link.relinkableFrom</a> and/or <a>Link.relinkableTo</a>,
the <a>RelinkingTool</a> adds one or two <a>Adornment</a>s,
a diamond at each relinkable end of a selected link.
The user can drag a relinking handle to reconnect that end of the link to another port.
</p>
<p>
The <a>RelinkingTool</a> will automatically update the relationships between the nodes/ports,
both in the diagram and in the model. No <a>Binding</a>s are needed for such model updates.
</p>
<pre class="lang-js" id="relinking">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "Rectangle",
{ fill: "lightgray", portId: "", fromLinkable: true, toLinkable: true }),
$(go.TextBlock, { margin: 5},
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
{ relinkableFrom: true, relinkableTo: true },
$(go.Shape),
$(go.Shape, { toArrow: "Standard" })
);
var nodeDataArray = [
{ key: "Alpha" }, { key: "Beta" }, { key: "Gamma" }, { key: "Delta" }
];
var linkDataArray = [
{ from: "Alpha", to: "Delta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
diagram.select(diagram.findLinkForData(linkDataArray[0]));
</pre>
<script>goCode("relinking", 600, 150)</script>
<p>
The relinking handles can be customized by setting <a>RelinkingTool.fromHandleArchetype</a>
and <a>RelinkingTool.toHandleArchetype</a>.
At the current time they cannot be customized by setting a property on the Link.
</p>
<p>
You can limit which pairs of ports between which the user may draw new links or reconnect existing links.
This topic is covered by <a href="validation.html">Link Validation</a>.
</p>
<h3 id="LinkReshapingTool">LinkReshapingTool</h3>
<p>
When a <a>Link</a> is <a>Part.reshapable</a>, the <a>LinkReshapingTool</a> adds an <a>Adornment</a>
with several reshape handles at the interior points of a selected link's route.
When the user drags a reshape handle, the route of the Link, held by <a>Link.points</a>, is modified.
</p>
<p>
When a link is reshapable, it is commonplace to try to remember the new route by updating the link data
in the <a>GraphLinksModel</a>, so that it can be saved and loaded later.
This can be accomplished with a TwoWay <a>Binding</a> on the <a>Link.points</a> property.
If one also uses the property name "points" on the link data, <a>Model.toJson</a> will
automatically convert the <a>List</a> of <a>Point</a>s into an Array of numbers and vice-versa.
</p>
<pre class="lang-js" id="linkReshaping">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "Rectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5},
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
{ reshapable: true, routing: go.Link.Orthogonal },
new go.Binding("points").makeTwoWay(), // TwoWay Binding of Link.points
$(go.Shape),
$(go.Shape, { toArrow: "Standard" })
);
diagram.model = new go.GraphLinksModel([
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "200 50" }
], [
{ from: "Alpha", to: "Beta" }
]);
diagram.select(diagram.findLinkForData(diagram.model.linkDataArray[0]));
</pre>
<script>goCode("linkReshaping", 600, 150)</script>
<p>
The reshape handles are small blue squares.
The reshape handles can be customized by setting <a>LinkReshapingTool.handleArchetype</a>.
At the current time they cannot be customized by setting a property on the Link.
</p>
<p>
By setting <a>Link.resegmentable</a> to true, users can add or remove segments from links.
The resegmenting handles are even smaller blue diamonds at the middle of each segment.
When the user drags a resegmenting handle, a new segment is inserted into the link's route.
For orthogonal links, two new segments are introduced in order to maintain orthogonality.
When the user reshapes the link so that adjacent segments are co-linear (or nearly so),
the segment(s) are removed from the route.
</p>
<pre class="lang-js" id="linkResegmenting">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "Rectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5},
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
{ reshapable: true, resegmentable: true, routing: go.Link.Orthogonal },
new go.Binding("points").makeTwoWay(), // TwoWay Binding of Link.points
$(go.Shape),
$(go.Shape, { toArrow: "Standard" })
);
diagram.model = new go.GraphLinksModel([
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "200 50" }
], [
{ from: "Alpha", to: "Beta" }
]);
diagram.select(diagram.findLinkForData(diagram.model.linkDataArray[0]));
</pre>
<script>goCode("linkResegmenting", 600, 150)</script>
<p>
The resegmenting handles can be customized by setting <a>LinkReshapingTool.midHandleArchetype</a>.
At the current time they cannot be customized by setting a property on the Link.
Also at the current time resegmenting is not supported on Bezier-curved links.
</p>
<p>
If you want your users to be able to reshape Shape geometries that are not Link paths,
there is the <a href="../extensions/GeometryReshapingTool.js">Geometry Reshaping Tool</a>
used by the <a href="../extensions/PolygonDrawing.html">Polygon Drawing</a> and
<a href="../extensions/FreehandDrawing.html">Freehand Drawing</a> samples in the extensions directory.
It is defined in a separate JS file that you can load into your app.
</p>
<h2 id="ToolsAndToolParts">Tools and Tool Parts</h2>
<p>
Some tools make use of special <a>Part</a>s that they add to the "Tool" <a>Layer</a> as feedback during the tool's operation.
</p>
<h3 id="DragSelectingTool">DragSelectingTool</h3>
<p>
The <a>DragSelectingTool</a> uses the <a>DragSelectingTool.box</a> to show the area in which it will select Parts.
Normally this is a simple magenta rectangular shape. You can change it. For example here is a drag-selecting box
that is in the shape of a blue-outlined cloud.
</p>
<pre class="lang-js" id="dragSelecting">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "Rectangle", { fill: "lightgray" }),
$(go.TextBlock, { margin: 5},
new go.Binding("text", "key"))
);
diagram.toolManager.dragSelectingTool.isPartialInclusion = true;
diagram.toolManager.dragSelectingTool.box =
$(go.Part,
{ layerName: "Tool" },
$(go.Shape, "Cloud",
{ name: "SHAPE", fill: null, stroke: "dodgerblue", strokeWidth: 2 })
);
diagram.model = new go.GraphLinksModel([
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "200 50" }
], [
{ from: "Alpha", to: "Beta" }
]);
</pre>
<script>goCode("dragSelecting", 600, 200)</script>
<p>
Note that the <a>DragSelectingTool</a> expects that the object in the "box" to be resized is named "SHAPE".
The object should be rectangular too, or else the user might be misled by the area in which parts will be selected.
Finally note also that the box is not an Adornment because it does not "adorn" any Part.
It is just an unbound Part that is used temporarily by the DragSelectingTool.
</p>
<p>
There are examples of in-the-background-dragging tools defined in the extensions directory:
<a href="../extensions/RealtimeDragSelecting.html">Realtime Drag Selecting Tool</a>,
<a href="../extensions/DragCreating.html">Drag Creating Tool</a>, and
<a href="../extensions/DragZooming.html">Drag Zooming Tool</a>.
Each is defined in a separate JS file that you can load into your app.
</p>
<h3 id="LinkingToolAndRelinkingTool">LinkingTool and RelinkingTool</h3>
<p>
The linking tools, <a>LinkingTool</a> and <a>RelinkingTool</a>, inherit from a base class, <a>LinkingBaseTool</a>,
that uses several Parts: a temporary Link and temporary "to" and "from" Nodes.
</p>
<p>
To customize the appearance and behavior of the temporary Link that is shown during a linking operation,
you need to modify or replace the <a>LinkingBaseTool.temporaryLink</a>.
The default temporary link is a blue line with a standard arrowhead.
The originating port and the potential target port are shown by the <a>LinkingBaseTool.temporaryFromNode</a>
and <a>LinkingBaseTool.temporaryToNode</a>.
The default temporary ports are magenta rectangles.
</p>
<pre class="lang-js" id="linkingTools">
diagram.nodeTemplate =
$(go.Node, "Spot",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "RoundedRectangle",
{ width: 100, height: 40, fill: "lightyellow",
portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer" }),
$(go.TextBlock,
new go.Binding("text", "key"))
);
diagram.toolManager.linkingTool.temporaryLink =
$(go.Link,
{ layerName: "Tool" },
$(go.Shape,
{ stroke: "red", strokeWidth: 2, strokeDashArray: [4, 2] })
);
var tempfromnode =
$(go.Node,
{ layerName: "Tool" },
$(go.Shape, "RoundedRectangle",
{ stroke: "chartreuse", strokeWidth: 3, fill: null,
portId: "", width: 1, height: 1 })
);
diagram.toolManager.linkingTool.temporaryFromNode = tempfromnode;
diagram.toolManager.linkingTool.temporaryFromPort = tempfromnode.port;
var temptonode =
$(go.Node,
{ layerName: "Tool" },
$(go.Shape, "RoundedRectangle",
{ stroke: "cyan", strokeWidth: 3, fill: null,
portId: "", width: 1, height: 1 })
);
diagram.toolManager.linkingTool.temporaryToNode = temptonode;
diagram.toolManager.linkingTool.temporaryToPort = temptonode.port;
diagram.model = new go.GraphLinksModel([
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "200 50" },
{ key: "Gamma", loc: "400 0" }
]); // start off with no links
</pre>
<script>goCode("linkingTools", 600, 150)</script>
<p>
Try drawing a link from one node to the other.
You will notice that the nodes (actually the ports) are highlighted by the temporary nodes in chartreuse and cyan.
The temporary link is a dashed red line without an arrowhead.
</p>
<p>
If your app also supports relinking you will probably want to do the same customizations on the <a>RelinkingTool</a>.
</p>
<p>
There are examples of linking tools defined in the samples and extensions directories:
<a href="../extensions/PolylineLinking.html">Polyline Linking Tool</a>,
<a href="../samples/sequenceDiagram.html">Messaging Tool (in Sequence Diagram)</a>, and
<a href="../samples/sequenceDiagram.html">Custom Linking Tool (in Grafcet Diagram)</a>
</p>
<h2 id="CustomTools">Custom Tools</h2>
<p>
The GoJS samples and extensions demonstrate a number of custom tools, including:
<ul style="margin-bottom: 70px;">
<li><a href="../extensions/FloorPlanEditor.html">Resize Multiple Tool (in Floor Plan Editor)</a>
<li><a href="../samples/swimLanes.html">Lane Resizing Tool (in Swim Lanes)</a>
<li><a href="../samples/swimLanesVertical.html">Lane Resizing Tool (in Swim Lanes Vertical)</a>
<li><a href="../extensions/FloorPlanEditor.html">Rotate Multiple Tool (in Floor Plan Editor)</a>
<li><a href="../samples/seatingChart.html">Horizontal Text Rotating Tool (in Seating Chart)</a>
<li><a href="../extensions/GeometryReshapingTool.js">Geometry Reshaping Tool</a>
used by the <a href="../extensions/PolygonDrawing.html">Polygon Drawing</a> <a href="../extensions/FreehandDrawing.html">Freehand Drawing</a>
<li><a href="../extensions/RealtimeDragSelecting.html">Realtime Drag Selecting Tool</a>
<li><a href="../extensions/DragCreating.html">Drag Creating Tool</a>
<li><a href="../extensions/DragZooming.html">Drag Zooming Tool</a>
<li><a href="../extensions/PolylineLinking.html">Polyline Linking Tool</a>
<li><a href="../samples/sequenceDiagram.html">Messaging Tool (in Sequence Diagram)</a>
<li><a href="../samples/sequenceDiagram.html">Custom Linking Tool (in Grafcet Diagram)</a>
</ul>
</div>
</div>
</body>
</html>
+156
View File
@@ -0,0 +1,156 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Tooltips -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>ToolTips</h1>
<p>
<b>GoJS</b> provides a way to create customized tooltips for any object or for the diagram background.
</p>
<p>
A tooltip is an <a>Adornment</a> that is shown when the mouse hovers over an object that has its <a>GraphObject.toolTip</a> set.
The tooltip part is bound to the same data as the part itself.
</p>
<p>
See samples that make use of tooltips in the <a href="../samples/index.html#tooltips">samples index</a>.
</p>
<p>
It is typical to implement a tooltip as a "ToolTip" Panel holding a <a>TextBlock</a> or a Panel of TextBlocks and other objects.
Each "ToolTip" is just an "Auto" Panel <a>Adornment</a> that is shadowed, and where the border is a rectangular <a>Shape</a> with a light gray fill.
However you can implement the tooltip as any arbitrarily complicated Adornment.
</p>
<p>
You can see how the "ToolTip" builder is defined at
<a href="../extensions/Buttons.js">Buttons.js</a>.
</p>
<p>
In this example each <a>Node</a> has its <a>GraphObject.toolTip</a> property set to a Part that shows the
data.color property via a normal data binding.
The diagram gets its own tooltip by setting <a>Diagram.toolTip</a>.
</p>
<pre class="lang-js" id="tooltips">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle",
{ fill: "white" },
new go.Binding("fill", "color")),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key")),
{
toolTip: // define a tooltip for each node that displays the color as text
$("ToolTip",
$(go.TextBlock, { margin: 4 },
new go.Binding("text", "color"))
) // end of Adornment
}
);
// a function that produces the content of the diagram tooltip
function diagramInfo(model) {
return "Model:\n" + model.nodeDataArray.length + " nodes, " +
model.linkDataArray.length + " links";
}
// provide a tooltip for the background of the Diagram, when not over any Part
diagram.toolTip =
$("ToolTip",
$(go.TextBlock, { margin: 4 },
// use a converter to display information about the diagram model
new go.Binding("text", "", diagramInfo))
);
var nodeDataArray = [
{ key: "Alpha", color: "lightblue" },
{ key: "Beta", color: "pink" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("tooltips", 250, 100)</script>
<p>
Try pausing the mouse over each of the nodes or in the background of the diagram.
If you copy some parts, you will see that the tooltip for the diagram displays newer information about the diagram.
</p>
<p>
You can change how long for the mouse has to wait motionless before a tooltip appears by setting <a>ToolManager.hoverDelay</a>.
For example, when initializing a <a>Diagram</a>, <code>"toolManager.hoverDelay": 600</code> changes the delay to be 6/10ths of one second.
</p>
<p>
You can change how long the tooltip remains visible by setting <a>ToolManager.toolTipDuration</a>.
For example, <code>"toolManager.toolTipDuration": 10000</code> changes the visible time to 10 seconds.
</p>
<h3 id="Positioning">Positioning</h3>
<p>
There are two ways to customize the positioning of the tooltip relative to the adorned GraphObject.
One way is to override <a>ToolManager.positionToolTip</a>.
Another way is to have the tooltip <a>Adornment</a> include a <a>Placeholder</a>.
The Placeholder is positioned to have the same size and position as the adorned object.
When creating tooltips with Placeholders, don't use the predefined "ToolTip" builder as it will introduce an extra shape typically used
as the border for the "Auto" Panel.
</p>
<pre class="lang-js" id="tooltipsplaceholder">
// this is a normal Node template that also has a toolTip defined for it
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle",
{ fill: "white" },
new go.Binding("fill", "color")),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key")),
{
toolTip: // define a tooltip for each node
$(go.Adornment, "Spot", // that has several labels around it
{ background: "transparent" }, // avoid hiding tooltip when mouse moves
$(go.Placeholder, { padding: 5 }),
$(go.TextBlock,
{ alignment: go.Spot.Top, alignmentFocus: go.Spot.Bottom, stroke: "red" },
new go.Binding("text", "key", function(s) { return "key: " + s; })),
$(go.TextBlock, "Bottom",
{ alignment: go.Spot.Bottom, alignmentFocus: go.Spot.Top, stroke: "red" },
new go.Binding("text", "color", function(s) { return "color: " + s; }))
) // end Adornment
}
);
var nodeDataArray = [
{ key: "Alpha", color: "lightyellow" },
{ key: "Beta", color: "orange" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("tooltipsplaceholder", 350, 200)</script>
<p>
Note how the <a>Adornment</a> implementing the tooltip uses a "transparent" background
so that the tooltip is not automatically removed when the mouse moves.
</p>
<h2 id="HTMLTooltips">HTML Tooltips</h2>
<p>
It is possible to define custom tooltips using HTML instead of <a>Adornment</a>s using the <a>HTMLInfo</a> class.
The <a href="../samples/dataVisualization.html">Data Visualization sample</a> shows such tooltips.
See <a href="HTMLInteraction.html">HTML Interaction</a> for more discussion.
</p>
<p>
HTML tooltips require more effort to implement than using the default <b>GoJS</b> "ToolTip" and GraphObjects.
However you would have the full power of HTML/CSS/JavaScript to show whatever you want.
</p>
</div>
</div>
</body>
</html>
+367
View File
@@ -0,0 +1,367 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Transactions -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Transactions and the UndoManager</h1>
<p>
<b>GoJS</b> models and diagrams make use of an <a>UndoManager</a> that can record all changes and support
undoing and redoing those changes.
Each state change is recorded in a <a>ChangedEvent</a>, which includes enough information about both before and after
to be able to reproduce the state change in either direction, backward (undo) or forward (redo).
Such changes are grouped together into <a>Transaction</a>s so that a user action, which may result in many changes,
can be undone and redone as a single operation.
</p>
<p>
Not all state changes result in <a>ChangedEvent</a>s that can be recorded by the UndoManager.
Some properties are considered transient, such as <a>Diagram.position</a>, <a>Diagram.scale</a>,
<a>Diagram.currentTool</a>, <a>Diagram.currentCursor</a>, or <a>Diagram.isModified</a>.
Some changes are structural or considered unchanging, such as <a>Diagram.model</a>, any property of <a>CommandHandler</a>,
or any of the tool or layout properties.
But most <a>GraphObject</a> and model properties do raise a ChangedEvent on the Diagram or Model, respectively,
when a property value has been changed.
</p>
<h2 id="Transactions">Transactions</h2>
<p>
Whenever you modify a model or its data programmatically in response to some event, you should wrap the code in a transaction.
Call <a>Diagram.startTransaction</a> or <a>Model.startTransaction</a>, make the changes,
and then call <a>Diagram.commitTransaction</a> or <a>Model.commitTransaction</a>.
Although the primary benefit from using transactions is to group together side-effects for undo/redo,
you should use transactions even if your application does not support undo/redo by the user.
</p>
<p>
As with database transactions, you will want to perform transactions that are short and infrequent.
Do not leave transactions ongoing between user actions.
Consider whether it would be better to have a single transaction surrounding a loop
instead of starting and finishing a transaction repeatedly within a loop.
Do not execute transactions within a property setter -- such granularity is too small.
Instead execute a transaction where the properties are set in response to some user action or external event.
</p>
<p>
However, unlike database transactions, you do not need to conduct a transaction in order to access any state.
All JavaScript objects are in memory, so you can look at their properties at any time that it would make sense to do so.
But when you want to make state changes to a <a>Diagram</a> or a <a>GraphObject</a> or a <a>Model</a> or a JavaScript object in a model,
do so within a transaction.
</p>
<p>
The only exception is that transactions are unnecessary when initializing a model or a diagram before assigning
the model to the <a>Diagram.model</a> property.
(A Diagram only gets access to an UndoManager via the Model, the <a>Model.undoManager</a> property.)
</p>
<p>
Furthermore many event handlers and listeners are already executed within transactions
that are conducted by <a>Tool</a>s or <a>CommandHandler</a> commands,
so you often will not need to start and commit a transaction within such functions.
Read the API documentation for details about whether a function is called within a transaction.
For example, setting <a>GraphObject.click</a> to an event handler to respond to a click on an object
needs to perform a transaction if it wants to modify the model or the diagram.
Most custom click event handlers do not change the diagram but instead update some HTML.
</p>
<p>
But implementing an "ExternalObjectsDropped" <a>DiagramEvent</a> listener, which usually does want to
modify the just-dropped Parts in the <a>Diagram.selection</a>, is called within the <a>DraggingTool</a>'s
transaction, so no additional start/commit transaction calls are needed.
</p>
<p>
Finally, some customizations, such as the <a>Node.linkValidation</a> predicate, should not modify the diagram or model at all.
</p>
<p>
Both model changes and diagram changes are recorded in the <a>UndoManager</a>
only if the model's <a>UndoManager.isEnabled</a> has been set to true.
If you do not want the user to be able to perform undo or redo and also prevent the recording of any <a>Transaction</a>s,
but you still want to get "Transaction"-type <a>ChangedEvent</a>s because you want to update a database,
you can set <a>UndoManager.maxHistoryLimit</a> to zero.
</p>
<p>
To better understand the relationships between objects and transactions in memory, look at this diagram:
<p>
<pre class="lang-js" id="transactionsDiagram" style="display:none">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ scale : 1.6, isShadowed: true },
new go.Binding("location", "pos", go.Point.parse),
{ locationSpot: go.Spot.Center, portId: "NODE" },
$(go.Shape, "RoundedRectangle",
{ fill: "white", portId: "SHAPE" },
new go.Binding("fill", "color"),
new go.Binding("strokeWidth", "strokeW")),
$(go.TextBlock,
{ margin: 4, portId: "TEXTBLOCK" },
new go.Binding("text", "txt"))
);
// Represents the nodeDataArray for the two nodes
diagram.nodeTemplateMap.add("dataNode",
$(go.Node, "Auto",
{
locationSpot: go.Spot.Center,
scale: 1.2,
selectionAdorned: true,
fromSpot: go.Spot.AllSides,
toSpot: go.Spot.AllSides,
isShadowed: true
},
new go.Binding("location", "pos", go.Point.parse),
new go.Binding("toSpot", "tSpot"),
new go.Binding("fromSpot", "fSpot"),
$(go.Shape, "Rectangle",
{ fill: "white" }),
$(go.Panel, "Vertical",
{ defaultStretch: go.GraphObject.Horizontal },
$(go.TextBlock, headerStyle(), // Header:
{portId: "HEADER" },
new go.Binding("text", "head")),
$(go.Shape, "LineH", { height: 1, stretch: go.GraphObject.Fill }),
$(go.TextBlock, textStyle(), // Location:
{ portId: "ROW1" },
new go.Binding("text", "txt1")),
$(go.Shape, "LineH", { height: 1, stretch: go.GraphObject.Fill }),
$(go.TextBlock, textStyle(), // Fill:
{ portId: "ROW2" },
new go.Binding("text", "txt2"))
)
)
);
diagram.nodeTemplateMap.add("dataNodeChanged",
$(go.Node, "Auto",
{
locationSpot: go.Spot.Center,
scale: 1.2,
selectionAdorned: true,
fromSpot: go.Spot.AllSides,
toSpot: go.Spot.AllSides,
isShadowed: true
},
new go.Binding("location", "pos", go.Point.parse),
new go.Binding("toSpot", "tSpot"),
new go.Binding("fromSpot", "fSpot"),
$(go.Shape, "Rectangle",
{ fill: "white" }),
$(go.Panel, "Vertical",
{ defaultStretch: go.GraphObject.Horizontal },
$(go.TextBlock, headerStyle(), // Header:
{portId: "HEADER" },
new go.Binding("text", "head")),
$(go.Shape, "LineH", { height: 1, stretch: go.GraphObject.Fill }),
$(go.TextBlock, textStyle(), // Location:
{ portId: "ROW1" },
new go.Binding("text", "txt1")),
$(go.Shape, "LineH", { height: 1, stretch: go.GraphObject.Fill }),
$(go.TextBlock, textStyle(), // Fill:
{ portId: "ROW2" },
new go.Binding("text", "txt2")),
$(go.Shape, "LineH", { height: 1, stretch: go.GraphObject.Fill }),
$(go.TextBlock, textStyle(), // Text:
{ portId: "ROW3" },
new go.Binding("text", "txt3")),
)
)
);
diagram.linkTemplateMap.add("dataNode", // Links from dataNode to Nodes
$(go.Link,
{ routing: go.Link.Orthogonal, corner: 5 },
$(go.Shape, { stroke: "gray", strokeWidth: 2 }),
$(go.Shape, { toArrow: "Standard", stroke: "gray", fill: "gray" })
));
diagram.nodeTemplateMap.add("title",
$(go.Node, "Auto",
new go.Binding("location", "pos", go.Point.parse),
$(go.TextBlock,
{ font: "bold 25pt sans-serif", textAlign: "center"},
new go.Binding("text", "txt"))
));
diagram.nodeTemplateMap.add("nodeDataArray",
$(go.Node, "Auto",
{
locationSpot: go.Spot.Center,
scale: 1.2,
selectionAdorned: true,
fromSpot: go.Spot.AllSides,
toSpot: go.Spot.AllSides,
shadowColor: "#C5C1AA"
},
new go.Binding("location", "pos", go.Point.parse),
$(go.Shape, "Rectangle", { fill: "lightgray" }),
$(go.Panel, "Vertical",
{ defaultStretch: go.GraphObject.Horizontal },
$(go.TextBlock, headerStyle(),
{ portId: "HEADER", text: "nodeDataArray" }),
$(go.Shape, "LineH", { height: 1, stretch: go.GraphObject.Fill }),
$(go.TextBlock, textStyle(),
{ portId: "dataNode1", desiredSize: new go.Size(NaN,16) }),
$(go.Shape, "LineH", { height: 1, stretch: go.GraphObject.Fill }),
$(go.TextBlock, textStyle(),
{ portId: "dataNode2", desiredSize: new go.Size(NaN,16) })
)
));
diagram.linkTemplateMap.add("Data",
$(go.Link,
{ corner: 10, routing: go.Link.Orthogonal },
new go.Binding("curviness"),
$(go.Shape, { stroke: "gray" , strokeWidth: 2 }),
$(go.Shape, { toArrow: "Standard", fill: "gray", stroke: "gray", strokeWidth: 2 }),
$(go.TextBlock, { font: "bold 12pt Courier", segmentOffset: new go.Point(0, -10) },
new go.Binding("text", "label"),
new go.Binding("segmentOffset", "offset"))
));
diagram.scale = 0.8;
var model = new go.GraphLinksModel();
model.linkFromPortIdProperty = "fPID";
model.linkToPortIdProperty = "tPID"
model.nodeDataArray = [
{ key: 1, txt: "Diagram", color: "white", pos: "15 305"},
{ key: 2, txt: "Model", color: "white", pos: "215 305"},
{ key: 3, category: "dataNode", pos: "215 440", head: "Node Data Array", txt1: "nodeDataArray[0]", txt2: "nodeDataArray[1]"},
{ key: 4, pos: "215 187", color: "white", txt: "UndoManager"},
{ key: 5, pos: "215, 50", category: "dataNode", head: "List of Transactions", txt1: "history[0]", txt2: "history[1]"},
{ key: 6, pos: "630, 230", category: "dataNode", head: "List of ChangedEvents", txt1: "changes[0]", txt2: "changes[1]", fSpot: go.Spot.RightSide},
{ key: 7, pos: "15, 561", txt: "Alpha", color: "palegreen"},
{ key: 8, category: "dataNode", pos: "510 590", head: "Node Data", txt1: "color: \"palegreen\"", txt2: "text: \"Alpha\""},
{ key: 9, category: "dataNodeChanged", pos: "630 422", head: "ChangedEvent", txt1: "propertyName: \"color\"", txt2: "newValue: \"palegreen\"", txt3: "oldValue: \"red\""},
{ key: 10, category: "dataNodeChanged", pos: "530, 60", head: "Transaction", txt1: "name: \"change color\"", txt2: "isComplete: true", txt3: "changes: . . ."},
];
model.linkDataArray = [
{ from: 1, to: 2, category: "Data", label: ".model", offset: new go.Point(12, 14)},
{ from: 2, to: 4, category: "Data", label: ".undoManager", offset: new go.Point(-10, 60)},
{ from: 1, to: 4, category: "Data", label: ".undoManager", offset: new go.Point(0, -63)},
{ from: 2, tPID: "HEADER", to: 3, category: "Data", label: ".nodeDataArray", offset: new go.Point(0, -72)},
{ from: 4, to: 5, category: "Data", label: ".history", offset: new go.Point(0, -45)},
{ from: 5, fPID: "ROW1", tPID: "HEADER", to: 10, category: "Data", label: "history[0]", offset: new go.Point(35, 10)},
{ from: 1, to: 7, category: "Data", curviness: -70},
{ from: 7, tPID: "HEADER", to: 8, category: "Data", curviness: -70, label: ".data", offset: new go.Point(-70, -10)},
{ from: 3, tPID: "HEADER", fPID: "ROW1", to: 8, category: "Data", label: "nodeDataArray[0]", offset: new go.Point(-15, -85)},
{ from: 6, to: 9, fPID: "ROW2", category: "Data", label: "changes[1]", offset: new go.Point(0, 54)},
{ from: 9, to: 8, category: "Data", label: ".object", offset: new go.Point(-10, -13)},
{ from: 10, to: 6, fPID: "ROW3", category: "Data", label: ".changes", offset: new go.Point(-10, 13)},
];
diagram.model = model;
// Formatting
function headerStyle() {
return {
margin: 3,
font: "bold 12pt sans-serif",
minSize: new go.Size(140, 16),
maxSize: new go.Size(120, NaN),
textAlign: "center"
};
}
function textStyle() {
return {
margin: 3,
font: "italic 10pt sans-serif",
minSize: new go.Size(16, 16),
maxSize: new go.Size(160, NaN),
textAlign: "left"
};
}
</pre>
<script>goCode("transactionsDiagram", 650, 550)</script>
<p>
A typical case for using transactions is when some command makes a change to the model.
</p>
<pre class="lang-js" id="transaction">
// define a function named "addChild" that is invoked by a button click
addChild = function() {
var selnode = diagram.selection.first();
if (!(selnode instanceof go.Node)) return;
diagram.commit(function(d) {
// have the Model add a new node data
var newnode = { key: "N" };
d.model.addNodeData(newnode); // this makes sure the key is unique
// and then add a link data connecting the original node with the new one
var newlink = { from: selnode.data.key, to: newnode.key };
// add the new link to the model
d.model.addLinkData(newlink);
}, "add node and link");
};
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle", { fill: "whitesmoke" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key"))
);
diagram.layout = $(go.TreeLayout);
var nodeDataArray = [
{ key: "Alpha" },
{ key: "Beta" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
diagram.model.undoManager.isEnabled = true;
</pre>
<p>
In the following example, select a node and then click the button.
The addChild function adds a link connecting the selected node to a new node.
When no Node is selected, nothing happens.
</p>
<input type="button" onclick="addChild()" value="addChild() to selected Node" />
<script>goCode("transaction", 600, 200)</script>
<h2 id="SupportUndoManager">Supporting the UndoManager</h2>
<p>
Changes to JavaScript data properties do not automatically result in any notifications that can be observed.
Thus when you want to change the value of a property in a manner that can be undone and redone,
you should call <a>Model.setDataProperty</a> (or <a>Model.set</a>, which is an abbreviation for that method).
This will get the previous value for the property, set the property to the new value, and
call <a>Model.raiseDataChanged</a>, which will also automatically update any target bindings in the Node
corresponding to the data.
</p>
<pre class="lang-js" id="changingData">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle", { fill: "whitesmoke" }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "someValue")) // bind to the "someValue" data property
);
var nodeDataArray = [
{ key: "Alpha", someValue: 1 }
];
diagram.model = new go.GraphLinksModel(nodeDataArray);
diagram.model.undoManager.isEnabled = true;
// define a function named "incrementData" callable by onclick
incrementData = function() {
diagram.model.commit(function(m) {
var data = m.nodeDataArray[0]; // get the first node data
m.set(data, "someValue", data.someValue + 1);
}, "increment");
};
</pre>
<p>
Move the node around.
Click on the button to increase the value of the "someValue" property on the first node data.
Click to focus in the Diagram and then Ctrl-Z and Ctrl-Y to undo and redo the moves and value changes.
</p>
<input type="button" onclick="incrementData()" value="incrementData()" />
<script>goCode("changingData", 250, 150)</script>
</div>
</div>
</body>
</html>
+251
View File
@@ -0,0 +1,251 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Trees -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Trees and TreeLayout</h1>
<p>
There is no limit to the kinds of graphs that you can build in <b>GoJS</b>.
But the most common kind of graph forms a "tree".
A tree is a graph where each node may have at most one "tree parent" and at most one link connecting to that parent node,
and where there are no cycles within the graph.
</p>
<p>
Because trees occur so frequently in diagrams,
there is also a tree layout that offers many customizations specifically for trees.
</p>
<h2 id="ManualLayoutOfTreeStructure">Manual layout of a tree structure</h2>
<p>
You can of course position the nodes manually, either by hand or programmatically.
In this first example, the node locations are stored in the node data,
and there is a Binding of <a>Part.location</a> to the node data property.
</p>
<pre class="lang-js" id="tree">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "Ellipse", { fill: "white" }),
$(go.TextBlock,
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
{ routing: go.Link.Orthogonal, corner: 5 },
$(go.Shape));
var nodeDataArray = [
{ key: "Alpha", loc: "0 60" },
{ key: "Beta", loc: "100 15" },
{ key: "Gamma", loc: "200 0" },
{ key: "Delta", loc: "200 30" },
{ key: "Epsilon", loc: "100 90" },
{ key: "Zeta", loc: "200 60" },
{ key: "Eta", loc: "200 90" },
{ key: "Theta", loc: "200 120" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" },
{ from: "Beta", to: "Gamma" },
{ from: "Beta", to: "Delta" },
{ from: "Alpha", to: "Epsilon" },
{ from: "Epsilon", to: "Zeta" },
{ from: "Epsilon", to: "Eta" },
{ from: "Epsilon", to: "Theta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("tree", 600, 200)</script>
<p>
You can also get the same results by using a <a>TreeModel</a>.
</p>
<pre class="lang-js" id="tree2">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "Ellipse", { fill: "white" }),
$(go.TextBlock,
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
{ routing: go.Link.Orthogonal, corner: 5 },
$(go.Shape));
var nodeDataArray = [
{ key: "Alpha", loc: "0 60" },
{ key: "Beta", loc: "100 15", parent: "Alpha" },
{ key: "Gamma", loc: "200 0", parent: "Beta" },
{ key: "Delta", loc: "200 30", parent: "Beta" },
{ key: "Epsilon", loc: "100 90", parent: "Alpha" },
{ key: "Zeta", loc: "200 60", parent: "Epsilon" },
{ key: "Eta", loc: "200 90", parent: "Epsilon" },
{ key: "Theta", loc: "200 120", parent: "Epsilon" }
];
diagram.model = new go.TreeModel(nodeDataArray);
</pre>
<script>goCode("tree2", 600, 200)</script>
<h2 id="AutomaticTreeLayout">Automatic TreeLayout</h2>
<p>
It is most common to use <a>TreeLayout</a> for laying out trees.
Just assign <a>Diagram.layout</a> to a new instance of <a>TreeLayout</a>.
This example also defines the <code>setupTree</code> function that is used in later examples on this page.
</p>
<pre class="lang-js" id="treeLayout">
function setupTree(diagram) {
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "Ellipse", { fill: "white" }),
$(go.TextBlock,
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
{ routing: go.Link.Orthogonal, corner: 5 },
$(go.Shape));
var nodeDataArray = [
{ key: "Alpha" },
{ key: "Beta", parent: "Alpha" },
{ key: "Gamma", parent: "Beta" },
{ key: "Delta", parent: "Beta" },
{ key: "Epsilon", parent: "Alpha" },
{ key: "Zeta", parent: "Epsilon" },
{ key: "Eta", parent: "Epsilon" },
{ key: "Theta", parent: "Epsilon" }
];
diagram.model = new go.TreeModel(nodeDataArray);
}
setupTree(diagram);
diagram.layout = $(go.TreeLayout); // automatic tree layout
</pre>
<script>goCode("treeLayout", 600, 200)</script>
<script>
function setupTree(diagram) {
var $ = go.GraphObject.make;
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "Ellipse", { fill: "white" }),
$(go.TextBlock,
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
{ routing: go.Link.Orthogonal, corner: 5 },
$(go.Shape));
var nodeDataArray = [
{ key: "Alpha" },
{ key: "Beta", parent: "Alpha" },
{ key: "Gamma", parent: "Beta" },
{ key: "Delta", parent: "Beta" },
{ key: "Epsilon", parent: "Alpha" },
{ key: "Zeta", parent: "Epsilon" },
{ key: "Eta", parent: "Epsilon" },
{ key: "Theta", parent: "Epsilon" }
];
diagram.model = new go.TreeModel(nodeDataArray);
}
</script>
<h2 id="CommonTreeLayoutProperties">Common TreeLayout properties</h2>
<p>
The <a>TreeLayout.angle</a> property controls the general direction of tree growth.
This must be zero (towards the right), 90 (downward), 180 (leftward), or 270 (upward).
</p>
<pre class="lang-js" id="angle">
setupTree(diagram);
diagram.layout = $(go.TreeLayout, { angle: 90 });
</pre>
<script>goCode("angle", 600, 200)</script>
<p>
The <code>setupTree</code> function was defined above.
</p>
<p>
The <a>TreeLayout.alignment</a> property controls how the parent node is positioned relative to its children.
This must be one of the Alignment... constants defined on <a>TreeLayout</a>.
</p>
<pre class="lang-js" id="alignment">
setupTree(diagram);
diagram.layout = $(go.TreeLayout, { angle: 90, alignment: go.TreeLayout.AlignmentStart });
</pre>
<script>goCode("alignment", 600, 200)</script>
<p>
For tree layouts, all of the nodes are placed into "layers" according to the length of the chain of links from the root node.
These layers are not to be confused with Diagram <a>Layer</a>s, which control the Z-ordering of the nodes.
The <a>TreeLayout.layerSpacing</a> property controls how close the layers are to each other.
The <a>TreeLayout.nodeSpacing</a> property controls how close nodes are to each other in the same layer.
</p>
<pre class="lang-js" id="spacing">
setupTree(diagram);
diagram.layout = $(go.TreeLayout, { layerSpacing: 20, nodeSpacing: 0 });
</pre>
<script>goCode("spacing", 600, 200)</script>
<p>
The children of each node can be sorted. By default the <a>TreeLayout.comparer</a> function compares the
<a>Part.text</a> property. So if that property is data bound by the node template, and if you set the
<a>TreeLayout.sorting</a> property to sort in either ascending or descending order,
each parent node will have all of its children sorted in that order by their text strings.
(In this example that means alphabetical ordering of the English names of the letters of the Greek alphabet.)
</p>
<pre class="lang-js" id="sort">
setupTree(diagram);
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("text", "key"), // bind Part.text to support sorting
$(go.Shape, "Ellipse", { fill: "lightblue" }),
$(go.TextBlock,
new go.Binding("text", "key"))
);
diagram.layout = $(go.TreeLayout, { sorting: go.TreeLayout.SortingAscending });
</pre>
<script>goCode("sort", 600, 200)</script>
<p>
But you can provide your own function for ordering the children, such as:
</p>
<pre class="lang-js">
$(go.Diagram, . . .,
{
layout:
$(go.TreeLayout,
{
sorting: go.TreeLayout.SortingAscending,
comparer: function(a, b) {
// A and B are TreeVertexes
var av = a.node.data.index;
var bv = b.node.data.index;
if (av < bv) return -1;
if (av > bv) return 1;
return 0;
},
. . .
})
. . .
})
</pre>
</div>
</div>
</body>
</html>
+506
View File
@@ -0,0 +1,506 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Using Models -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Using Models and Templates</h1>
<p>
You can build a diagram of nodes and links programmatically.
But <b>GoJS</b> offers a way to build diagrams in a more declarative manner.
You only provide the node and link data (i.e. the model) necessary for the diagram
and instances of parts (i.e. the templates) that are automatically copied into the diagram.
Those templates may be parameterized by properties of the node and link data.
</p>
<h2 id="BuildingDiagramsWithCode">Building diagrams with code</h2>
<p>
Let us try to build two nodes and connect them with a link.
Here is one way of doing that:
</p>
<pre class="lang-js" id="twoNodesOneLinkCode">
var node1 =
$(go.Node, "Auto",
$(go.Shape,
{ figure: "RoundedRectangle",
fill: "lightblue" }),
$(go.TextBlock,
{ text: "Alpha",
margin: 5 })
)
diagram.add(node1);
var node2 =
$(go.Node, "Auto",
$(go.Shape,
{ figure: "RoundedRectangle",
fill: "pink" }),
$(go.TextBlock,
{ text: "Beta",
margin: 5 })
);
diagram.add(node2);
diagram.add(
$(go.Link,
{ fromNode: node1, toNode: node2 },
$(go.Shape)
));
</pre>
<script>goCode("twoNodesOneLinkCode", 250, 150)</script>
<p>
This produces a nice, simple diagram.
If you drag one of the nodes, you will see that the link remains connected to it.
</p>
<p>
Although this way of building a diagram will work, it will not scale up well when creating large diagrams.
Normally you will want a varying number of nodes each of which is very similar to the others.
It would be better to share the construction of the node but parameterize a few things where the values should vary.
</p>
<p>
One possibility would be put the code to build a Node into a function that returned a fully constructed Node,
including all of the Panels and other GraphObjects in its visual tree.
You would probably want to parameterize the function in order to provide the desired strings and colors and figures and image URLs.
However such an approach is very ad-hoc: it would be difficult for the system to know how to automatically call such functions
in order to create new nodes or new links on demand.
Furthermore as your application data changes dynamically, how would you use such functions to update properties
of existing objects within existing nodes and links, without inefficiently re-creating everything?
And if you wanted anything/everything to update automatically as your application data changes,
how would the system know what to do?
</p>
<p>
This diagram-building code is also more cumbersome than it needs to be
to manage references to nodes so that you can link them up.
This is similar to the earlier problem when building a node's visual tree in code
of having to use temporary named variables and referring to them when needed.
</p>
<p>
What we are looking for is the separation of the appearance, definition, and construction
of all of the nodes from the application data needed to describe the unique aspects of each particular node.
</p>
<h2 id="UsingModelAndTemplates">Using a Model and Templates</h2>
<p>
One way of achieving the separation of node appearance from node data is to use a data model and node templates.
A model is basically just a collection of data that holds the essential information for each node and each link.
A template is basically just a <a>Part</a> that can be copied; you would have different templates for <a>Node</a>s and for <a>Link</a>s.
</p>
<p>
In fact, a <a>Diagram</a> already has very simple default templates for Nodes and Links.
If you want to customize the appearance of the nodes in your diagram,
you can replace the default node template by setting <a>Diagram.nodeTemplate</a>.
</p>
<p>
To automatically make use of templates, provide the diagram a model holding the data for each node and the data for each link.
A <a>GraphLinksModel</a> holds the collections (actually arrays) of node data and link data as the values of
<a>GraphLinksModel.nodeDataArray</a> and <a>GraphLinksModel.linkDataArray</a>.
You then set the <a>Diagram.model</a> property so that the diagram can create <a>Node</a>s for all of the node data
and <a>Link</a>s for all of the link data.
</p>
<p>
Models interpret and maintain references between the data.
Each node data is expected to have a unique key value so that references to node data can be resolved reliably.
Models also manage dynamically adding and removing data.
</p>
<p>
The node data and the link data in models can be any JavaScript object.
You get to decide what properties those objects have -- add as many as you need for your app.
Since this is JavaScript, you can even add properties dynamically.
There are several properties that <b>GoJS</b> models assume exist on the data,
such as "key" (on node data) and "category" and "from" and "to" (the latter two on link data).
However you can tell the model to use different property names by setting the model
properties whose names end in "...Property".
</p>
<p>
A node data object normally has its node's unique key value in the "key" property.
Currently node data keys must be strings or numbers.
You can get the key for a Node either via the <a>Node.key</a> property or via <code>someNode.data.key</code>.
</p>
<p>
Let us create a diagram providing the minimal amount of necessary information.
The particular node data has been put into an array of JavaScript objects.
We declare the link relationships in a separate array of link data objects.
Each link data holds references to the node data by using their keys.
Normally the references are the values of the "from" and "to" properties.
</p>
<pre class="lang-js" id="simpleModelNoTemplates">
var nodeDataArray = [
{ key: "Alpha"},
{ key: "Beta" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("simpleModelNoTemplates", 250, 150)</script>
<p>
This results in two nodes and a link, but the nodes do not appear the way we want.
So we define the node template to be a generalization of the particular node constructions that we did above.
</p>
<pre class="lang-js" id="simpleModelNoBind">
diagram.nodeTemplate = // provide custom Node appearance
$(go.Node, "Auto",
$(go.Shape,
{ figure: "RoundedRectangle",
fill: "white" }),
$(go.TextBlock,
{ text: "hello!",
margin: 5 })
);
var nodeDataArray = [
{ key: "Alpha" },
{ key: "Beta" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("simpleModelNoBind", 250, 150)</script>
<p>
Now the graph looks better, but the nodes have not been parameterized -- they are all identical!
We can achieve that parameterization by using data binding.
</p>
<h2 id="ParameterizingNodesUsingDataBindings">Parameterizing Nodes using data binding</h2>
<p>
A data binding is a declarative statement that the value of the property of one object
should be used to set the value of a property of another object.
</p>
<p>
In this case, we want to make sure that the <a>TextBlock.text</a> property gets the
"key" value of the corresponding node data.
And we want to make sure that the <a>Shape.fill</a> property gets set to the color/brush given
by the "color" property value of the corresponding node data.
</p>
<p>
We can declare such data-bindings by creating <a>Binding</a> objects and associating them with the target <a>GraphObject</a>.
Programmatically you do this by calling <a>GraphObject.bind</a>.
But when using <b>go.GraphObject.make</b>, this happens automatically when you pass in a <a>Binding</a>.
</p>
<pre class="lang-js" id="simpleModelWithBind">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape,
{ figure: "RoundedRectangle",
fill: "white" }, // default Shape.fill value
new go.Binding("fill", "color")), // binding to get fill from nodedata.color
$(go.TextBlock,
{ margin: 5 },
new go.Binding("text", "key")) // binding to get TextBlock.text from nodedata.key
);
var nodeDataArray = [
{ key: "Alpha", color: "lightblue" }, // note extra property for each node data: color
{ key: "Beta", color: "pink" }
];
var linkDataArray = [
{ from: "Alpha", to: "Beta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("simpleModelWithBind", 250, 150)</script>
<p>
Now we have the same diagram result as before, but it is implemented in much more general manner.
You can easily add more node and link data to build bigger diagrams.
And you can easily change the appearance of all of the nodes without modifying the data.
</p>
<p>
Actually, you may notice that the <a>Link</a> is different: it has an arrowhead.
No arrowhead was included when we first built this diagram using code.
But the default <a>Diagram.linkTemplate</a> includes an arrowhead
and we did not replace the link template with a custom one in this example.
</p>
<p>
Notice that the value of <a>Shape.fill</a> in the template above gets a value twice.
First it is set to "white". Then the binding sets it to whatever value the node data's "color" property has.
It may be useful to be able to specify an initial value that remains in case the node data does
not have a "color" property or if there is an error getting that value.
</p>
<p>
At this point we can also be a bit more precise about what a template is.
A template is a <a>Part</a> that may have some data <a>Binding</a>s and that is not itself in a diagram
but may be copied to create parts that are added to a diagram.
</p>
<h3 id="TemplateDefinitions">Template Definitions</h3>
<p>
The implementations of all predefined templates are provided in <a href="../extensions/Templates.js">Templates.js</a> in the Extensions directory.
You may wish to copy and adapt these definitions when creating your own templates.
</p>
<p>
Those definitions might not be an up-to-date description
of the actual standard template implementations that are in <b>GoJS</b>.
</p>
<h2 id="KindsOfModels">Kinds of Models</h2>
<p>
A model is a way of interpreting a collection of data objects as an abstract graph
with various kinds of relationships determined by data properties and the assumptions that the model makes.
The simplest kind of model, <a>Model</a>, can only hold "parts" without any relationships between them --
no links or groups. But that model class acts as the base class for other kinds of models.
</p>
<h3 id="GraphLinksModel">GraphLinksModel</h3>
<p>
The kind of model you have seen above, <a>GraphLinksModel</a>, is actually the most general kind.
It supports link relationships using a separate link data object for each <a>Link</a>.
There is no inherent limitation on which <a>Nodes</a> a Link may connect, so reflexive and duplicate links are allowed.
Links might also result in cycles in the graph.
However you may prevent the user from drawing such links by setting various properties, such as <a>Diagram.validCycle</a>.
And if you want to have a link appear to connect with a link rather than with a node,
this is possible by having special nodes, known as "label nodes", that belong to links and are arranged along the
path of a link in the same manner as text labels are arranged on a link.
</p>
<p>
Furthermore a <a>GraphLinksModel</a> also supports identifying logically and physically different connection objects,
known as "ports", within a <a>Node</a>.
Thus an individual link may connect with a particular port rather than with the node as a whole.
The <a href="connectionPoints.html">Link Points</a> and <a href="ports.html">Ports</a> pages discuss this topic in more depth.
</p>
<p>
A <a>GraphLinksModel</a> also supports the group-membership relationship.
Any <a>Part</a> can belong to at most one <a>Group</a>; no group can be contained in itself, directly or indirectly.
You can learn more about grouping in other pages, such as <a href="groups.html">Groups</a>.
</p>
<h3 id="TreeModel">TreeModel</h3>
<p>
A simpler kind of model, the <a>TreeModel</a>, only supports link relationships that form a tree-structured graph.
There is no separate link data, so there is no "linkDataArray".
The parent-child relationship inherent in trees is determined by an extra property on the child node data which refers to the parent node by its key.
If that property, whose name defaults to "parent", is undefined, then that data's corresponding node is a tree root.
Each <a>Link</a> is still data bound, but the link's data is the child node data.
</p>
<pre class="lang-js" id="simpleTree">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape,
{ figure: "Ellipse" },
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 5 },
new go.Binding("text", "key"))
);
var nodeDataArray = [
{ key: "Alpha", color: "lightblue" },
{ key: "Beta", parent: "Alpha", color: "yellow" }, // note the "parent" property
{ key: "Gamma", parent: "Alpha", color: "orange" },
{ key: "Delta", parent: "Alpha", color: "lightgreen" }
];
diagram.model = new go.TreeModel(nodeDataArray);
</pre>
<script>goCode("simpleTree", 250, 150)</script>
<p>
Many of the tree-oriented samples make use of a TreeModel instead of a GraphLinksModel.
But just because your graph is tree-structured does not mean you have to use a TreeModel.
You may find that your data is organized with a separate "table" defining the link relationships,
so that using a GraphLinksModel is most natural.
Or you may want to use other features that TreeModel does not support.
</p>
<p>
Other pages such as <a href="trees.html">Trees</a> discuss tree-oriented features of <b>GoJS</b> in more detail.
</p>
<h2 id="IdentityAndReferences">Identity and References</h2>
<p>
Each <a>Node</a> is the visual representation of a specific JavaScript Object that is in the <a>Model.nodeDataArray</a>.
If there are two objects in the model, they will result in two nodes, even if the properties of both objects are exactly the same.
For example:
</p>
<pre class="lang-js">
myDiagram.model.nodeDataArray = [
{ text: "something", count: 17 },
{ text: "something", count: 17 }
];
</pre>
<p>
This will cause there to be two separate nodes that happen to have the same property values.
In fact each of those JavaScript Objects will get a different "key" value, so that references to nodes
will always be able to be distinguished.
</p>
<p>
This illustrates how the identity of each node is determined by the Object in memory that is the node's data.
You cannot delete a node by removing an object that is similar to one that is in the model.
Consider this statement:
</p>
<pre class="lang-js">
myDiagram.model.removeNodeData({ text: "something", count: 17 });
</pre>
<p>
Such code will never remove any node data from the <a>Model.nodeDataArray</a> nor any <a>Node</a> from any <a>Diagram</a>
because the Object that is passed to <a>Model.removeNodeData</a> is a new Object, not the <em>same Object</em> that is present in the model.
</p>
<p>
Nor can you find a node by giving it a similar node data object.
There is no such method on <a>Model</a>, although there is a <a>Model.findNodeDataForKey</a> method.
But if you really want to search for nodes that have particular properties,
you can call <a>Diagram.findNodesByExample</a>.
</p>
<pre class="lang-js">
var nodes = myDiagram.findNodesByExample({ text: "something", count: 17 });
nodes.each(function(n) { console.log(n.key); });
</pre>
<p>
For the model shown above, this will return a collection of two <a>Node</a>s.
It then iterates over that collection and prints each <a>Node.key</a>,
which in the case of the above model will be some automatically assigned key values.
</p>
<h3 id="ReferencesToNodes">References to Nodes</h3>
<p>
Although the identity of a node is the node's data object in the model, references to nodes are not "pointers" to those objects.
Instead, references are always by the "key" of the node data.
(The property need not be named "key" -- see <a>Model.nodeKeyProperty</a>.)
Using keys instead of direct references to data objects makes it easier to read and write models,
especially by <a>Model.toJson</a> and <a>Model,fromJson</a>, and to debug them in memory.
Thus <a>Link</a>s are defined by data using keys, and <a>Group</a> membership is determined using keys:
</p>
<pre class="lang-js">
myDiagram.model.nodeDataArray = [ // for a GraphLinksModel
{ key: "Alpha" },
{ key: "Beta", group: "Gamma" },
{ key: "Gamma", isGroup: true }
];
myDiagram.model.linkDataArray = [ // for a GraphLinksModel
{ from: "Alpha", to: "Beta"}
];
</pre>
<pre class="lang-js">
myDiagram.model.nodeDataArray = [ // for a TreeModel
{ key: "Alpha" },
{ key: "Beta", parent: "Alpha" }
];
</pre>
<h2 id="ModifyingModels">Modifying Models</h2>
<p>
If you want to add or remove nodes programmatically, you will probably want to call the
<a>Model.addNodeData</a> and <a>Model.removeNodeData</a> methods.
Use the <a>Model.findNodeDataForKey</a> method to find a particular node data object if you only have its unique key value.
You may also call <a>Model.copyNodeData</a> to make a copy of a node data object that you can then modify and pass to <a>Model.addNodeData</a>.
</p>
<p>
It does not work to simply mutate the Array that is the value of <a>Model.nodeDataArray</a>,
because the <b>GoJS</b> software will not be notified about any change to any JavaScript Array and
thus will not have a chance to add or remove <a>Node</a>s or other <a>Part</a>s as needed.
(But setting the <a>Model.nodeDataArray</a> property to refer to a different Array does of course notify the model.)
</p>
<p>
Similarly, it does not work to simply set a property of a node data object.
Any <a>Binding</a> that depends on the property will not be notified about any changes,
so it will not be able to update its target <a>GraphObject</a> property.
For example, setting the color property will not cause the <a>Shape</a> to change color.
</p>
<pre class="lang-js">
var data = myDiagram.model.findNodeDataForKey("Delta");
// This will NOT change the color of the "Delta" Node
if (data !== null) data.color = "red";
</pre>
<p>
Instead you need to call <a>Model.setDataProperty</a> to modify an object in the model.
</p>
<pre class="lang-js">
var data = myDiagram.model.findNodeDataForKey("Delta");
// This will update the color of the "Delta" Node
if (data !== null) myDiagram.model.setDataProperty(data, "color", "red");
</pre>
<p>
Calling model methods such as <a>Model.addNodeData</a> or <a>Model.setDataProperty</a> is required
when the JavaScript Array or Object is already part of the Model.
When first building the Array of Objects for the <a>Model.nodeDataArray</a>
or when initializing a JavaScript Object as a new node data object, such calls are not necessary.
But once the data is part of the Model, calling the model's methods to effect changes is necessary.
</p>
<h3 id="ExternallyModifiedData">Externally Modified Data</h3>
<p>
In some software architectures it might not be possible to insist that all data changes go through <a>Model</a> methods.
In such cases it is possible to call <a>Diagram.updateAllRelationshipsFromData</a> and
<a>Diagram.updateAllTargetBindings</a>.
</p>
<p>
However, please note that doing so will prevent the <a>UndoManager</a> from properly recording state changes.
There would be no way for the <a>UndoManager</a> to know what had been the previous values of properties.
Furthermore it makes it hard to have more than one Diagram showing the Model.
</p>
<h3 id="ImmutableData">Immutable Data</h3>
<p>
In some software architectures it is customary to have "models" consist of immutable (unmodifiable) data.
However, as the GoJS diagram is modified, its model data will be modified, so you cannot use that immutable data in the model.
You could make a copy of all of the immutable data and then replace the <a>Diagram.model</a> whenever the data
has changed outside of the diagram/model. But that would cause old Nodes and Links to be re-created,
and that would be unworkably expensive in time and space when the model is large.
</p>
<p>
If you do have immutable model data, you can update the existing <a>Model</a> and thus its <a>Diagram</a>s by calling
the <a>Model.mergeNodeDataArray</a> and <a>GraphLinksModel.mergeLinkDataArray</a> methods.
This will be much more efficient than replacing the <a>Model.nodeDataArray</a> and <a>GraphLinksModel.linkDataArray</a>
Arrays each time, because it will preserve the existing Nodes and Links if possible.
</p>
<p>
Note that this scheme depends on maintaining the "key"s for all of the node data and for all of the link data.
That happens automatically for all nodes, but for GraphLinksModels, it means setting
<a>GraphLinksModel.linkKeyProperty</a> to the name of the property on the link data that
you want to use to remember the key value.
</p>
<p>
After each diagram transaction some of the model data may have changed.
But you cannot share references to that modified data with the rest of the software that is expecting immutable data.
Instead you can call <a>Model.toIncrementalData</a> which will provide copies of the modified data.
That data can then be used to update the rest of the app's state.
Read more about this at <a href="react.html">Using GoJS with React</a>
and the <a href="https://github.com/NorthwoodsSoftware/gojs-react">gojs-react package</a>,
which provides generic Diagram components that you can use in your app using React.
</p>
<h2 id="SavingAndLoadingModels">Saving and Loading Models</h2>
<p>
<b>GoJS</b> does not require you to save models in any particular medium or format.
But because this is JavaScript and JSON is the most popular data-interchange format,
we do make it easy to write and read models as text in JSON format.
</p>
<p>
Just call <a>Model.toJson</a> to generate a string representing your model.
Call the static method <a>Model,fromJson</a> to construct and initialize a model given a string produced by <a>Model.toJson</a>.
Many of the samples demonstrate this -- search for JavaScript functions named "save" and "load".
Most of those functions write and read a TextArea on the page itself, so that you can see and modify the JSON text and then load it to get a new diagram.
But please be cautious when editing because JSON syntax is very strict, and any syntax errors will cause those "load" functions to fail.
</p>
<p>
JSON formatted text has strict limits on the kinds of data that you can represent without additional assumptions.
To save and load any data properties that you set on your node data (or link data), they need to meet the following requirements:
</p>
<ul>
<li>the property is enumerable and its name does not start with an underscore (you can use property names that do start with an underscore, but they won't be saved)</li>
<li>the property value is not undefined and is not a function (JSON cannot faithfully hold functions)</li>
<li>the model knows how to convert the property value to JSON format (numbers, strings, JavaScript Arrays, or plain JavaScript Objects)</li>
<li>property values that are Objects or Arrays form a tree structure -- no shared or cyclical references</li>
</ul>
<p>
<a>Model.toJson</a> and <a>Model,fromJson</a> will also handle instances of
<a>Point</a>, <a>Size</a>, <a>Rect</a>, <a>Spot</a>, <a>Margin</a>, <a>Geometry</a>, and non-pattern <a>Brush</a>es.
However we recommend that you store those objects in their string representations, using those classes' <code>parse</code> and <code>stringify</code> static functions.
</p>
<p>
Because you are using JavaScript, it is trivial for you to add data properties to your node data.
This allows you to associate whatever information you need with each node.
But if you need to associate some information with the model, which will be present even if there is no node data at all,
you can add properties to the <a>Model.modelData</a> object.
This object's properties will be written by <a>Model.toJson</a> and read by <a>Model,fromJson</a>, just as node data objects are written and read.
</p>
</div>
</div>
</body>
</html>
+600
View File
@@ -0,0 +1,600 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Validation -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Validation</h1>
<p>
Some operations require more sophisticated controls than the binary permission flags discussed in the previous <a href="permissions.html">section</a>.
When the user tries to draw a new link or reconnect an existing link, your application may want to restrict which links may be made,
depending on the data.
When the user tries to add a node to a group, your application may want to control whether it is permitted for that particular
node in that particular group.
When the user edits some text, your application may want to limit the kinds of strings that they enter.
</p>
<p>
Although not exactly "validation", you can also limit how users drag (move or copy) parts by setting several properties on <a>Part</a> and customizing the <a>DraggingTool</a>.
</p>
<h2 id="LinkingValidation">Linking Validation</h2>
<p>
There are a number of <a>GraphObject</a> properties that let you control what links the user may draw or reconnect.
These properties apply to each port element and affect the links that may connect with that port.
</p>
<h3 id="LinkableProperties">Linkable properties</h3>
<p>
The primary properties are <a>GraphObject.fromLinkable</a> and <a>GraphObject.toLinkable</a>.
If you do not have a <a>Node</a> containing an element with fromLinkable: true and another node
with toLinkable: true, the user will not be able to draw a new link between the nodes.
</p>
<pre class="lang-js" id="linkable">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "Ellipse",
{ fill: "green", portId: "", cursor: "pointer" },
new go.Binding("fromLinkable", "from"),
new go.Binding("toLinkable", "to")),
$(go.TextBlock,
{ stroke: "white", margin: 3 },
new go.Binding("text", "key"))
);
var nodeDataArray = [
{ key: "From1", loc: "0 0", from: true },
{ key: "From2", loc: "0 100", from: true },
{ key: "To1", loc: "150 0", to: true },
{ key: "To2", loc: "150 100", to: true }
];
var linkDataArray = [
// initially no links
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("linkable", 600, 150)</script>
<p>
Mouse down on the green ellipse (the cursor changes to a "pointer") and drag to start drawing a new link.
Note how the only permitted links are those going from a "From" node to a "To" node.
This is true even if you start the linking gesture on a "To" node.
</p>
<h3 id="SpanOfLinkableProperties">Span of Linkable properties</h3>
<p>
Because the <a>TextBlock</a> in the above example is not declared to be a port (i.e. there is no value for <a>GraphObject.portId</a>),
mouse events on the TextBlock do not start the <a>LinkingTool</a>.
This allows users the ability to select and move the node as well as any number of other operations.
</p>
<p>
You can certainly declare a <a>Panel</a> to have <a>GraphObject.fromLinkable</a> or <a>GraphObject.toLinkable</a> be true.
This will cause all elements inside that panel to behave as part of the port, including starting a linking operation.
Sometimes you will want to make the whole <a>Node</a> linkable.
If you still want the user to be able to select and drag the node, you will need to make some easy-to-click elements not-"linkable" within the node.
You can do that by explicitly setting <a>GraphObject.fromLinkable</a> and/or <a>GraphObject.toLinkable</a> to false.
The default value for those two properties is null, which means the "linkable"-ness is inherited from the containing panel.
</p>
<h2 id="OtherLinkingPermissionProperties">Other linking permission properties</h2>
<p>
Just because you have set <a>GraphObject.fromLinkable</a> and <a>GraphObject.toLinkable</a>
to true on the desired port objects
does not mean that you want to allow users to create a link from every such port/node to every other port/node.
There are other <a>GraphObject</a> properties governing linkability for both the "from" and the "to" ends.
</p>
<h3 id="LinkableDuplicatesProperties">LinkableDuplicates properties</h3>
<p>
One restriction that you may have noticed before is that the user cannot draw a second link between the same pair
of nodes in the same direction.
This example sets <a>GraphObject.fromLinkableDuplicates</a> or <a>GraphObject.toLinkableDuplicates</a> to true,
in order to permit such duplicate links between nodes.
</p>
<pre class="lang-js" id="linkableDuplicates">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "Ellipse",
{ fill: "green", portId: "", cursor: "pointer",
fromLinkableDuplicates: true, toLinkableDuplicates: true },
new go.Binding("fromLinkable", "from"),
new go.Binding("toLinkable", "to")),
$(go.TextBlock,
{ stroke: "white", margin: 3 },
new go.Binding("text", "key"))
);
var nodeDataArray = [
{ key: "From1", loc: "0 0", from: true },
{ key: "From2", loc: "0 100", from: true },
{ key: "To1", loc: "150 0", to: true },
{ key: "To2", loc: "150 100", to: true }
];
var linkDataArray = [
// initially no links
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("linkableDuplicates", 600, 150)</script>
<p>
Now try drawing multiple links between "From1" and "To1".
You can see how the links are automatically spread apart.
Try dragging one of the nodes to see what happens with the link routing.
A similar effect occurs also when the link's <a>Link.curve</a> is <a>Link,Bezier</a>.
</p>
<h3 id="LinkableSelfNodeProperties">LinkableSelfNode properties</h3>
<p>
Another standard restriction is that the user cannot draw a link from a node to itself.
Again it is easy to remove that restriction: just set <a>GraphObject.fromLinkableSelfNode</a>
and <a>GraphObject.toLinkableSelfNode</a> to true.
Note though that each node has to be both <a>GraphObject.fromLinkable</a> and <a>GraphObject.toLinkable</a>.
</p>
<pre class="lang-js" id="linkableSelfNodes">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "Ellipse",
{ fill: "green", portId: "", cursor: "pointer",
fromLinkable: true, toLinkable: true,
fromLinkableDuplicates: true, toLinkableDuplicates: true,
fromLinkableSelfNode: true, toLinkableSelfNode: true }),
$(go.TextBlock,
{ stroke: "white", margin: 3 },
new go.Binding("text", "key"))
);
var nodeDataArray = [
{ key: "Node1", loc: "0 0" },
{ key: "Node2", loc: "150 50" }
];
var linkDataArray = [
// initially no links
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("linkableSelfNodes", 600, 150)</script>
<p>
To draw a reflexive link, start drawing a new link but stay near the node when you release the mouse button.
This example also sets the "Duplicates" properties to true, so that you can draw multiple reflexive links.
</p>
<p>
In these examples there is only one port per node.
When there are multiple ports in a node, the restrictions actually apply per port, not per node.
But the restrictions of the "LinkableSelfNode" properties do span the whole node,
so they must be applied to both ports within a node for a link to connect to its own node.
</p>
<h3 id="MaxLinksProperties">MaxLinks properties</h3>
<p>
The final linking restriction properties control how many links may connect to a node/port.
This example sets the <a>GraphObject.toMaxLinks</a> property to 2,
even though <a>GraphObject.toLinkableDuplicates</a> is true,
to limit how many links may go into "to" nodes.
</p>
<pre class="lang-js" id="linkableMax">
diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "Ellipse",
{ fill: "green", portId: "", cursor: "pointer",
fromLinkableDuplicates: true, toLinkableDuplicates: true,
toMaxLinks: 2 }, // at most TWO links can come into this node
new go.Binding("fromLinkable", "from"),
new go.Binding("toLinkable", "to")),
$(go.TextBlock,
{ stroke: "white", margin: 3 },
new go.Binding("text", "key"))
);
var nodeDataArray = [
{ key: "From1", loc: "0 0", from: true },
{ key: "From2", loc: "0 100", from: true },
{ key: "To1", loc: "150 0", to: true },
{ key: "To2", loc: "150 100", to: true }
];
var linkDataArray = [
// initially no links
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("linkableMax", 600, 150)</script>
<p>
This example has no limit on the number of links that may come out of "from" nodes.
</p>
<p>
If this property is set, it is most commonly set to one.
Of course it depends on the nature of the application.
</p>
<p>
Note that the <a>GraphObject.toMaxLinks</a> and <a>GraphObject.fromMaxLinks</a> properties are independent of each other.
If you want to control the total number of links connecting with a port, not only "to" or "from" but both directions,
then you cannot use those two properties and instead must implement your own link validation predicate, as discussed below.
</p>
<h2 id="CyclesInGraphs">Cycles in graphs</h2>
<p>
If you want to make sure that the graph structure that your users create never have any cycles of links,
or that the graph is always tree-structured, <b>GoJS</b> makes that easy to enforce.
Just set <a>Diagram.validCycle</a> to <a>Diagram,CycleNotDirected</a> or <a>Diagram,CycleDestinationTree</a>.
The default value is <a>Diagram,CycleAll</a>, which imposes no restrictions -- all kinds of link cycles are allowed.
</p>
<p>
This example has nodes that allow links both to and from each node.
However the assignment of <a>Diagram.validCycle</a> will prevent the user from drawing
a second incoming link to any node and also ensures that the user draw no cycles in the graph.
</p>
<pre class="lang-js" id="tree">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "Ellipse",
{ fill: "green", portId: "", cursor: "pointer",
fromLinkable: true, toLinkable: true }),
$(go.TextBlock,
{ stroke: "white", margin: 3 },
new go.Binding("text", "key"))
);
var nodeDataArray = [
{ key: "Node1" }, { key: "Node2" }, { key: "Node3" },
{ key: "Node4" }, { key: "Node5" }, { key: "Node6" },
{ key: "Node7" }, { key: "Node8" }, { key: "Node9" }
];
var linkDataArray = [
// initially no links
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
// only allow links that maintain tree-structure
diagram.validCycle = go.Diagram.CycleDestinationTree;
</pre>
<script>goCode("tree", 600, 250)</script>
<p>
As you draw more links you can see how the set of potential linking destinations keeps getting smaller.
</p>
<h2 id="GeneralLinkingValidation">General linking validation</h2>
<p>
It may be the case that the semantics of your application will cause the set of valid link destinations to depend
on the node data (i.e. at the node and port at which the link started from and at the possible destination node/port)
in a manner that can only be implemented using code: a predicate function.
</p>
<p>
You can implement such domain-specific validation by setting <a>LinkingBaseTool.linkValidation</a> or <a>Node.linkValidation</a>.
These predicates, if supplied, are called for each pair of ports that the linking tool considers.
If the predicate returns false, the link may not be made.
Setting the property on the <a>LinkingTool</a> or <a>RelinkingTool</a>causes the predicate to be applied to all linking operations,
whereas setting the property on the <a>Node</a> only applies to linking operations involving that node.
The predicates are called only if all of the standard link checks pass, based on the properties discussed above.
</p>
<p>
In this example there are nodes of three different colors.
The <a>LinkingTool</a> and <a>RelinkingTool</a> are customized to use a function, <code>sameColor</code>,
to make sure the links only connect nodes of the same color.
Mouse-down and drag on the ellipses (where the cursor changes to a "pointer") to start drawing a new link.
You will see that the only permitted link destinations are nodes of the same color that do not already have a link to it from the same node.
</p>
<pre class="lang-js" id="linking">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "Ellipse",
{ cursor: "pointer", portId: "",
fromLinkable: true, toLinkable: true },
new go.Binding("fill", "color")),
$(go.TextBlock,
{ stroke: "white", margin: 3 },
new go.Binding("text", "key"))
);
diagram.linkTemplate =
$(go.Link,
{ curve: go.Link.Bezier, relinkableFrom: true, relinkableTo: true },
$(go.Shape, { strokeWidth: 2 },
new go.Binding("stroke", "fromNode", function(n) { return n.data.color; })
.ofObject()),
$(go.Shape, { toArrow: "Standard", stroke: null},
new go.Binding("fill", "fromNode", function(n) { return n.data.color; })
.ofObject())
);
// this predicate is true if both nodes have the same color
function sameColor(fromnode, fromport, tonode, toport) {
return fromnode.data.color === tonode.data.color;
// this could look at the fromport.fill and toport.fill instead,
// assuming that the ports are Shapes, which they are because portID was set on them,
// and that there is a data Binding on the Shape.fill
}
// only allow new links between ports of the same color
diagram.toolManager.linkingTool.linkValidation = sameColor;
// only allow reconnecting an existing link to a port of the same color
diagram.toolManager.relinkingTool.linkValidation = sameColor;
var nodeDataArray = [
{ key: "Red1", color: "red" },
{ key: "Blue1", color: "blue" },
{ key: "Green1", color: "green" },
{ key: "Green2", color: "green" },
{ key: "Red2", color: "red" },
{ key: "Blue2", color: "blue" },
{ key: "Red3", color: "red" },
{ key: "Green3", color: "green" },
{ key: "Blue3", color: "blue" }
];
var linkDataArray = [
// initially no links
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</pre>
<script>goCode("linking", 600, 250)</script>
<p>
To emphasize the color restriction, links have their colors bound to the "from" node data.
</p>
<h3 id="Limitingtotalnumberoflinksconnectingwithanode">Limiting total number of links connecting with a node</h3>
<p>
One can limit the number of links coming into a port by setting <a>GraphObject.toMaxLinks</a>.
Similarly, one can limit the number of links coming out of a port by setting <a>GraphObject.fromMaxLinks</a>.
But what if you want to limit the total number of links connecting with a port regardless of whether they are coming into or going out of a port?
Such constraints can only be implemented by a link validation predicate.
</p>
<p>
When wanting to limit the total number of links in either direction, connecting with each port, one can use this <a>Node.linkValidation</a> predicate:
</p>
<pre class="lang-js">
$(go.Node, . . .,
{
linkValidation: function(fromnode, fromport, tonode, toport) {
// total number of links connecting with a port is limited to 1:
return fromnode.findLinksConnected(fromport.portId).count +
tonode.findLinksConnected(toport.portId).count < 1;
}
}, . . .
</pre>
<p>
When wanting to limit the total number of links in either direction, connecting with a node for all of its ports, one can use this <a>Node.linkValidation</a> predicate:
</p>
<pre class="lang-js">
$(go.Node, . . .,
{
linkValidation: function(fromnode, fromport, tonode, toport) {
// total number of links connecting with all ports of a node is limited to 1:
return fromnode.linksConnected.count + tonode.linksConnected.count < 1;
}
}, . . .
</pre>
<h2 id="GroupingValidation">Grouping validation</h2>
<p>
When you want to limit the kinds of nodes that the user may add to a particular group,
you can implement a predicate as the <a>CommandHandler.memberValidation</a> or <a>Group.memberValidation</a> property.
Setting the property on the <a>CommandHandler</a> causes the predicate to be applied to all Groups,
whereas setting the property on the <a>Group</a> only applies to that group.
</p>
<p>
In this example the <code>samePrefix</code> predicate is used to determine if a Node
may be dropped into a Group.
Try dragging the simple textual nodes on the left side into either of the groups on the right side.
Only when dropping the node onto a group that is highlit "green" will the node be added as a member of the group.
You can verify that by moving the group to see if the textual node moves too.
</p>
<pre class="lang-js" id="grouping">
// this predicate is true if both node data keys start with the same letter
function samePrefix(group, node) {
if (group === null) return true; // when maybe dropping a node in the background
if (node instanceof go.Group) return false; // don't add Groups to Groups
return group.data.key.charAt(0) === node.data.key.charAt(0);
};
diagram.nodeTemplate =
$(go.Node,
new go.Binding("location", "loc", go.Point.parse),
$(go.TextBlock,
new go.Binding("text", "key"))
);
diagram.groupTemplate =
$(go.Group, "Vertical",
{
// only allow those simple nodes that have the same data key prefix:
memberValidation: samePrefix,
// don't need to define handlers on member Nodes and Links
handlesDragDropForMembers: true,
// support highlighting of Groups when allowing a drop to add a member
mouseDragEnter: function(e, grp, prev) {
// this will call samePrefix; it is true if any node has the same key prefix
if (grp.canAddMembers(grp.diagram.selection)) {
var shape = grp.findObject("SHAPE");
if (shape) shape.fill = "green";
grp.diagram.currentCursor = "";
} else {
grp.diagram.currentCursor = "not-allowed";
}
},
mouseDragLeave: function(e, grp, next) {
var shape = grp.findObject("SHAPE");
if (shape) shape.fill = "rgba(128,128,128,0.33)";
grp.diagram.currentCursor = "";
},
// actually add permitted new members when a drop occurs
mouseDrop: function(e, grp) {
if (grp.canAddMembers(grp.diagram.selection)) {
// this will only add nodes with the same key prefix
grp.addMembers(grp.diagram.selection, true);
} else { // and otherwise cancel the drop
grp.diagram.currentTool.doCancel();
}
}
},
// make sure all Groups are behind all regular Nodes
{ layerName: "Background" },
new go.Binding("location", "loc", go.Point.parse),
$(go.TextBlock,
{ alignment: go.Spot.Left, font: "Bold 12pt Sans-Serif" },
new go.Binding("text", "key")),
$(go.Shape,
{ name: "SHAPE", width: 100, height: 100,
fill: "rgba(128,128,128,0.33)" })
);
diagram.mouseDrop = function(e) {
// dropping in diagram background removes nodes from any group
diagram.commandHandler.addTopLevelParts(diagram.selection, true);
};
var nodeDataArray = [
{ key: "A group", isGroup: true, loc: "100 10" },
{ key: "B group", isGroup: true, loc: "100 140" },
{ key: "A1", loc: "10 30" }, // can be added to "A" group
{ key: "A2", loc: "10 60" },
{ key: "B1", loc: "10 90" }, // can be added to "B" group
{ key: "B2", loc: "10 120" },
{ key: "C1", loc: "10 150" } // cannot be added to either group
];
diagram.model = new go.GraphLinksModel(nodeDataArray, []);
</pre>
<script>goCode("grouping", 600, 300)</script>
<p>
These groups are fixed size groups -- they do not use <a>Placeholder</a>s.
So when a node is dropped into them the group does not automatically resize itself to surround its member nodes.
But that is also a benefit when dragging a node out of a group.
</p>
<p>
The validation predicate is also called when dragging a node that is already a member of a group.
You can see how it is acceptable to drop the node into its existing containing group.
And when it is dragged outside of the group into the diagram's background, the predicate is called with null as the "group" argument.
</p>
<p>
In this example it is always OK to drop a node in the background of the diagram rather than into a group.
If you want to disallow dropping in the background, you can call <code>myDiagram.currentTool.doCancel()</code>
in the <a>Diagram.mouseDrop</a> event handler.
If you want to show feedback during the drag in the background, you can implement a <a>Diagram.mouseDragOver</a> event handler that sets
<code>myDiagram.currentCursor = "not-allowed"</code>.
This would be behavior similar to that implemented above when dragging inside a Group.
</p>
<h2 id="TextEditingValidation">Text editing validation</h2>
<p>
You can also limit what text the user enters when they do in-place text editing of a <a>TextBlock</a>.
First, to enable any editing at all, you will need to set <a>TextBlock.editable</a> to true.
There may be many TextBlocks within a Part, but you might want to limit text editing to particular TextBlocks.
</p>
<p>
Normally there is no limitation on what text the user may enter.
If you want to provide a predicate to approve the input when the user finishes editing,
set the <a>TextEditingTool.textValidation</a> or <a>TextBlock.textValidation</a> property.
Setting the property on the <a>TextEditingTool</a> causes the predicate to be applied to all TextBlocks,
whereas setting the property on the <a>TextBlock</a> only applies to that text object.
</p>
<pre class="lang-js" id="textEditing">
// this predicate is true if the new string has at least three characters
// and has a vowel in it
function okName(textblock, oldstr, newstr) {
return newstr.length >= 3 && /[aeiouy]/i.test(newstr);
};
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, { fill: "lightyellow" }),
$(go.Panel, "Vertical",
{ margin: 3 },
$(go.TextBlock,
{ editable: true }, // no validation predicate
new go.Binding("text", "text1")),
$(go.TextBlock,
{ editable: true,
isMultiline: false, // don't allow embedded newlines
textValidation: okName }, // new string must be an OK name
new go.Binding("text", "text2"))
)
);
var nodeDataArray = [
{ key: 1, text1: "Hello", text2: "Dolly!" },
{ key: 2, text1: "Goodbye", text2: "Mr. Chips" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, []);
</pre>
<script>goCode("textEditing", 600, 100)</script>
<p>
Note how editing the top TextBlock accepts text without any vowels,
but the bottom one does not accept it and instead leaves the text editor open.
</p>
<p>
If you want to execute code after a text edit completes, implement a "TextEdited"
<a>DiagramEvent</a> listener.
</p>
<h3 id="ShowingTextEditingErrorMessage">Showing a Text Editing Error Message</h3>
<p>
If you would like to show a custom error message when text validation fails,
one way is to show a tooltip <a>Adornment</a>.
Here is an example where a valid string must contain the letter "W".
</p>
<pre class="lang-js" id="textEditingMessage">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape,
{ fill: "white", portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer" },
new go.Binding("fill", "color")),
$(go.TextBlock,
{
margin: 8,
editable: true,
isMultiline: false,
textValidation: function(tb, olds, news) {
return news.indexOf("W") >= 0; // new string must contain a "W"
},
errorFunction: function(tool, olds, news) {
// create and show tooltip about why editing failed for this textblock
var mgr = tool.diagram.toolManager;
mgr.hideToolTip(); // hide any currently showing tooltip
var node = tool.textBlock.part;
// create a GoJS tooltip, which is an Adornment
var tt = $("ToolTip",
{
"Border.fill": "pink",
"Border.stroke": "red",
"Border.strokeWidth": 2
},
$(go.TextBlock,
"Unable to replace the string '" + olds + "' with '" + news +
"' on node '" + node.key +
"'\nbecause the new string does not contain the capital letter 'W'."));
mgr.showToolTip(tt, node);
},
textEdited: function(tb, olds, news) {
var mgr = tb.diagram.toolManager;
mgr.hideToolTip();
}
},
new go.Binding("text").makeTwoWay())
);
diagram.model = new go.GraphLinksModel([
{ key: 1, text: "Alpha" },
{ key: 2, text: "Beta" }
], [
{ from: 1, to: 2 }
]);
</pre>
<script>goCode("textEditingMessage", 600, 200)</script>
<p>
Try editing the text of a node by twice clicking on some text.
If the string does not have the letter "W" in it, it will show an error message describing the problem.
</p>
</div>
</div>
</body>
</html>
+873
View File
@@ -0,0 +1,873 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Coordinate Systems-- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Coordinate Systems</h1>
<p>
A <a>Diagram</a> uses two major coordinate systems when drawing <a>Part</a>s: document and view coordinates.
Furthermore each <a>Panel</a> within a <a>Part</a> has its own coordinate system that its elements use.
</p>
<p>
All coordinate systems in <b>GoJS</b> have <a>Point</a>s with increasing values of X going rightwards and
increasing values of Y going downwards.
</p>
<h2 id="DocumentAndViewCoordinates">Document and View coordinates</h2>
<p>
The <a>Part.location</a> and <a>GraphObject.actualBounds</a> and <a>GraphObject.position</a> of Parts are in document coordinates.
Thus the <a>Point</a>s that may be saved in the model's node data are normally in document coordinates:
</p>
<pre class="lang-js">
diagram.model.nodeDataArray = [
{ key: "Alpha", loc: "0 0" },
{ key: "Beta", loc: "100 50" }
];
</pre>
<p>
But a Part with a <a>Part.location</a> of (0, 0) in document coordinates is not always drawn at the top-left corner of
the HTML Div element that the user sees in the page.
When the user scrolls the diagram the part will need to be drawn elsewhere on the canvas.
And if the user zooms in to make the parts appear larger, the parts will be drawn at different points in the canvas.
Yet the <a>Part.location</a> does not change value as the user scrolls or zooms the diagram.
</p>
<p>
Points in the canvas are in view coordinates: distances from the top-left corner in device-independent pixels.
The differences between document coordinates and view coordinates are primarily controlled by two <a>Diagram</a> properties:
<a>Diagram.position</a> and <a>Diagram.scale</a>.
Scrolling and panning change the Diagram.position.
Zooming in or out changes the Diagram.scale.
You can also convert between coordinate systems by calling <a>Diagram.transformDocToView</a> and <a>Diagram.transformViewToDoc</a>.
However very few properties and method arguments or return values are in view coordinates -- almost everything is in document
coordinates or in panel coordinates.
</p>
<p>
The <i>viewport</i> is the area of the document that is visible in the canvas.
That area is available as the <a>Diagram.viewportBounds</a>.
Note that the viewport bounds is in document coordinates, not in view coordinates!
The top-left corner of the viewport is (0,0) in view coordinates but is at <a>Diagram.position</a> in document coordinates.
The bottom-right corner of the viewport is at the canvas's (width,height) in view coordinates.
The bottom-right corner of the viewport in document coordinates depends on the <a>Diagram.scale</a>.
</p>
<p>
As an example of showing the viewport in the context of the whole document, an <a>Overview</a> does exactly that.
Take a look at the overview that is in the <a href="../samples/orgChartStatic.html">Org Chart sample</a>.
The overview shows the whole document of the main diagram.
The magenta box shows the main diagram's viewport within the whole document.
As you scroll or pan the main diagram, the viewport moves.
As you zoom out, the viewport gets larger.
</p>
<p>
To better understand the difference between document and viewport coordinates, look at this diagram:
</p>
<pre class="lang-js" id="diffCoordSystems" style="display:none">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ scale : 1.3},
new go.Binding("location", "loc", gridPointParse),
new go.Binding("scale", "scale"),
{ locationSpot: go.Spot.Center, portId: "NODE" },
$(go.Shape, "RoundedRectangle",
{ fill: "white", portId: "SHAPE" },
new go.Binding("fill", "color"),
new go.Binding("strokeWidth", "strokeW")),
$(go.TextBlock,
{ margin: 4, portId: "TEXTBLOCK" },
new go.Binding("text", "text"),
new go.Binding("stroke", "textColor"))
);
diagram.linkTemplate =
$(go.Link,
$(go.Shape, { stroke: "darkgray", strokeWidth: 2 }),
$(go.Shape, { toArrow: "Standard", stroke: "darkgray", fill: "darkgray" })
);
// colors
var docGridStroke = "rgba(70, 130, 180, 0.5)";
var viewGridStroke = "rgba(255, 128, 128, 1)";
var commentStroke = "brown";
var cellSide = 20; // side length of one grid cell
var cellSize = new go.Size(cellSide * 5, cellSide * 5);
var pointSize = 7;
function gridSizeParse(size) {
if (!(size instanceof go.Size)) {
size = go.Size.parse(size);
}
size.setTo(size.width * cellSide, size.height * cellSide);
return size;
}
function gridPointParse(point) {
if (!(point instanceof go.Point)) {
point = go.Point.parse(point);
}
point.setTo(point.x * cellSide, point.y * cellSide);
return point;
}
function LabelledPoint(x, y, label) {
return {
x: x,
y: y,
label: label
}
}
diagram.nodeTemplateMap.add("Description", // Template for comment node
$(go.Node, "Auto",
new go.Binding("location", "loc", gridPointParse),
new go.Binding("scale", "scale"),
{ locationSpot: go.Spot.Center, portId: "NODE" },
$(go.Shape, "RoundedRectangle",
{ fill: "white", portId: "SHAPE" },
new go.Binding("fill", "color"),
new go.Binding("strokeWidth", "strokeW")),
$(go.Panel, "Vertical",
$(go.TextBlock,
{font: "bold 11pt sans-serif", margin: new go.Margin(3, 0, 0, 0)},
new go.Binding("text", "header"),
new go.Binding("stroke", "textColor")),
$(go.TextBlock,
{ margin: 3, portId: "TEXTBLOCK" },
new go.Binding("text", "text"),
new go.Binding("stroke", "textColor"))
)
));
diagram.nodeTemplateMap.add("DeltaDescription", // Template for comment node
$(go.Node, "Auto",
new go.Binding("location", "loc", gridPointParse),
new go.Binding("scale", "scale"),
{ locationSpot: go.Spot.Center, portId: "NODE" },
$(go.Shape, "RoundedRectangle",
{ fill: "white", portId: "SHAPE" },
new go.Binding("fill", "color"),
new go.Binding("strokeWidth", "strokeW")),
$(go.Panel, "Vertical",
$(go.TextBlock,
{font: "bold 11pt sans-serif", margin: new go.Margin(3, 0, 0, 0)},
new go.Binding("text", "header"),
new go.Binding("stroke", "textColor")),
$(go.TextBlock,
{ margin: 3, portId: "TEXTBLOCK", alignment: go.Spot.Left },
new go.Binding("text", "text"),
new go.Binding("stroke", "textColor")),
$(go.TextBlock,
{ margin: 3, portId: "TEXTBLOCK", font: "italic 10pt sans-serif" },
new go.Binding("text", "desc"),
new go.Binding("stroke", "textColor"))
)
));
diagram.nodeTemplateMap.add("Point", // template for denoting points on the grid
$(go.Node, "Vertical",
{ movable: false },
new go.Binding("location", "point", function gridPointLocation(point) {
label = point.label;
// measure the longest line's width
textLines = label.split("\n");
width = 0
textLines.forEach(function(text) {
var textBlock = $(go.TextBlock, { text: text, font: "bold 10pt sans-serif"});
if (textBlock.naturalBounds.right > width) {
width = textBlock.naturalBounds.right;
}
});
// text block with entire string to measure height
var textBlock = $(go.TextBlock, { text: label, font: "bold 10pt sans-serif" });
point = new go.Point(point.x, point.y);
// convert from grid coordinates to diagram coordinates
gridPointParse(point);
// offset
point.setTo(point.x - width / 2, point.y - pointSize / 2 - textBlock.naturalBounds.bottom); // align to center of circle to intersection instead of top left corner
return point;
}),
$(go.TextBlock,
{position: new go.Point(0, -pointSize - 7), textAlign: "center", font : "bold 10pt sans-serif"},
new go.Binding("text", "point", function getLabel(point) {
return point.label;
}), new go.Binding("margin", "margin")),
$(go.Shape,
"Circle",
{width: pointSize, height: pointSize, alignment: go.Spot.Center})
));
diagram.linkTemplateMap.add("Comment", // Template for links from comments
$(go.Link,
{ curve: go.Link.Bezier },
new go.Binding("curviness"),
new go.Binding("fromSpot", "fromSpot"),
new go.Binding("toSpot", "toSpot"),
$(go.Shape, { stroke: commentStroke },
new go.Binding("stroke", "stroke")),
$(go.Shape, { toArrow: "OpenTriangle", stroke: commentStroke },
new go.Binding("stroke", "stroke"))
));
diagram.groupTemplateMap.add("Grid",
$(go.Group, "Position",
{ movable: false },
$(go.Shape, "Rectangle", { fill: "transparent", strokeWidth: 2},
new go.Binding("fill", "fill"),
new go.Binding("stroke", "border"),
new go.Binding("desiredSize", "size", gridSizeParse).makeTwoWay(go.Size.stringify)),
$(go.Panel, "Grid",
{ name: "DOCGRID", desiredSize: cellSize, gridCellSize: new go.Size(cellSide, cellSide) },
new go.Binding("desiredSize", "size", gridSizeParse).makeTwoWay(go.Size.stringify),
new go.Binding("gridCellSize", "cell", go.Size.parse).makeTwoWay(go.Size.stringify),
$(go.Shape, "LineV",
new go.Binding("stroke")),
$(go.Shape, "LineH",
new go.Binding("stroke"))
),
new go.Binding("location", "loc", gridPointParse)
));
diagram.initialContentAlignment = go.Spot.Center;
var model = new go.GraphLinksModel();
model.linkFromPortIdProperty = "fPID";
model.linkToPortIdProperty = "tPID"
model.nodeDataArray = [
{ key: "docGrid", isGroup: true, category: "Grid", stroke: docGridStroke, fill: "transparent", size: "24 20", border: docGridStroke },
{ key: "viewGrid", isGroup: true, group: "docGrid", category: "Grid", fill: "rgb(248,248,248)",stroke: viewGridStroke, size: "13.7 9.95", loc: "5.8 5.2", cell: "25 25", border: viewGridStroke},
{ key: "alpha", group: "docGrid", text: "Alpha", loc: "1.95 6.95"},
{ key: "beta", group: "docGrid", text: "Beta", loc: "12.4 1.25"},
{ key: "gamma", group: "docGrid", text: "Gamma", loc: "10 7.95"},
{ key: "delta", group: "docGrid", text: "Delta", loc: "14.5 11.95"},
{ key: "epsilon", group: "docGrid", text: "Epsilon", loc: "7.9 17.95"},
{ key: "zeta", group: "docGrid", text: "Zeta", loc: "12.35 18.7"},
{ key: "eta", group: "docGrid", text: "Eta", loc: "22.5 11.95"},
{ key: "point1", group: "docGrid", category: "Point", point: LabelledPoint(5.8, 5.2, "(300, 250) Document Coordinates\n(0, 0) Viewport Coordinates")},
{ key: "point2", group: "docGrid", category: "Point", point: LabelledPoint(19.3, 14.9, "(850, 650) Document Coordinates\n(550, 400) Viewport Coordinates"), margin: new go.Margin(0, 0, 4, 0)},
{ key: "point1", group: "docGrid", category: "Point", point: LabelledPoint(0, 0, "(0, 0) Document Coordinates")},
{ key: "point1", group: "docGrid", category: "Point", point: LabelledPoint(24, 20, "(1200, 1000) Document Coordinates")},
{ key: "viewportDesc", category: "Description", header: "Viewport", text: "position: (300, 250)\nviewportBounds: (550, 400)\nscale: 1.25", textColor: "brown", loc: "0 17"},
{ key: "documentDesc", category: "Description", header: "Document", text: "documentBounds: (1200, 1000)\npadding: (5, 5, 5, 5)", textColor: "rgb(50, 120, 160)", loc: "22 -3"},
{ key: "deltaDesc", category: "DeltaDescription", header: "Delta", text: "location: (650, 550)", desc: "Location is in document\ncoordinates, and does not\nchange with viewport\nmovement or scaling.", textColor: "black", loc: "26 6"}
];
model.linkDataArray = [
{ to: "viewGrid", from: "viewportDesc", category: "Comment", stroke: "brown"},
{ to: "docGrid", from: "documentDesc", category: "Comment", stroke: "rgb(50, 120, 160)"},
{ to: "delta", from: "deltaDesc", category: "Comment", stroke: "black", curviness: -10},
{ to: "gamma", from: "alpha"},
{ to: "gamma", from: "beta"},
{ to: "delta", from: "gamma"},
{ to: "epsilon", from: "delta"},
{ to: "zeta", from: "delta"},
{ to: "eta", from: "delta"}
];
diagram.model = model;
// Formatting
function headerStyle() {
return {
margin: 3,
font: "bold 12pt sans-serif",
minSize: new go.Size(140, 16),
maxSize: new go.Size(120, NaN),
textAlign: "center"
};
}
function textStyle() {
return {
margin: 3,
font: "italic 10pt sans-serif",
minSize: new go.Size(16, 16),
maxSize: new go.Size(160, NaN),
textAlign: "left"
};
}
</pre>
<script>goCode("diffCoordSystems", 750, 650)</script>
<h2 id="CoordinateSystemsExample">Coordinate systems example</h2>
<p>
This example shows three Parts at three different locations in document coordinates.
Pass the mouse over each of the parts to see where those locations are in view coordinates.
Initially you will see that the only difference between document and view coordinates are a constant offset.
That offset is due to the <a>Diagram.padding</a> that puts a little space between the edge of the canvas and
the edge of where the diagram's objects are.
It is also due to <a>Part.locationSpot</a> having the location be at the center of the "+" Shape,
not at the top-left corner of the whole Part.
</p>
<pre class="lang-js" id="coordsystems">
// read-only to avoid accidentally moving any Part in document coordinates
diagram.isReadOnly = true;
diagram.nodeTemplate =
$(go.Part, // no links or grouping, so use the simpler Part class instead of Node
{
locationSpot: go.Spot.Center, locationObjectName: "SHAPE",
layerName: "Background",
mouseOver: function (e, obj) { showPoint(obj.part.location); },
click: function (e, obj) { showPoint(obj.part.location); }
},
new go.Binding("location", "loc", go.Point.parse),
$(go.Shape, "PlusLine",
{ name: "SHAPE", width: 8, height: 8 }),
$(go.TextBlock,
{ position: new go.Point(6, 6), font: "8pt sans-serif" },
new go.Binding("text", "loc"))
);
diagram.model.nodeDataArray = [
{ loc: "0 0" },
{ loc: "100 0" },
{ loc: "100 50" }
];
function showPoint(loc) {
var docloc = diagram.transformDocToView(loc);
var elt = document.getElementById("Message1");
elt.textContent = "Selected node location,\ndocument coordinates: " + loc.x.toFixed(2) + " " + loc.y.toFixed(2) +
"\nview coordinates: " + docloc.x.toFixed(2) + " " + docloc.y.toFixed(2);
}
myDiagram = diagram; // make accessible to the HTML buttons
</pre>
<script>goCode("coordsystems", 300, 150)</script>
<textarea id="Message1" style="width: 300px; height: 70px">(move mouse over node to see points in document and in view coordinates)</textarea>
<input id="ZoomOut" type="button" onclick="myDiagram.commandHandler.decreaseZoom()" value="Zoom Out" />
<input id="ZoomIn" type="button" onclick="myDiagram.commandHandler.increaseZoom()" value="Zoom In" />
<p>
Then try scrolling or zooming in and looking at the locations of those parts in view coordinates.
Zooming in increases the <a>Diagram.scale</a> by a small factor.
That changes the locations in view coordinates, even though the locations in document coordinates did not change.
</p>
<p class="box bg-info">
To "move" a node one must change its <a>GraphObject.position</a> or <a>Part.location</a> in document coordinates.
To "scroll" a diagram one must change the <a>Diagram.position</a>.
Either way will cause a node to appear at a different point in the viewport.
</p>
<h2 id="DocumentBounds">Document bounds</h2>
<p>
All of the <a>Part</a>s of a diagram have positions and sizes (i.e. their <a>GraphObject.actualBounds</a>) in document coordinates.
The union of all of those parts' actualBounds constitutes the <a>Diagram.documentBounds</a>.
If all of the parts are close together, the document bounds might be small.
If some or all of the parts are far apart from each other, the document bounds might be large, even if there are only two parts
or if there is just one really large part.
The <a>Diagram.documentBounds</a> value is independent of the <a>Diagram.viewportBounds</a>.
The former only depends on the bounds of the parts; the latter only depends on the size of the canvas and the diagram's
position and scale.
</p>
<p>
<a>Diagram.computeBounds</a>, which is responsible for the bounds computation,
also adds the <a>Diagram.padding</a> Margin so that no Parts appear directly up against the edge of the diagram when scrolled to that side.
You may want to keep some parts, particularly background decorations, from being included in the document bounds computation.
Just set <a>Part.isInDocumentBounds</a> to false for such parts.
</p>
<p>
The diagram does not compute a new value for <a>Diagram.documentBounds</a> immediately upon any change to any part
or the addition or removal of a part.
Thus the <a>Diagram.documentBounds</a> property value may not be up-to-date until after a transaction completes.
</p>
<p>
The relative sizes of the <a>Diagram.documentBounds</a> and <a>Diagram.viewportBounds</a> control whether or not
scrollbars are needed.
You can set <a>Diagram.hasHorizontalScrollbar</a> and/or <a>Diagram.hasVerticalScrollbar</a> to false to
make sure no scrollbar appears even when needed.
</p>
<p>
If you do not want the <a>Diagram.documentBounds</a> to always reflect the sizes and locations of all of the nodes and links,
you can set the <a>Diagram.fixedBounds</a> property.
However if there are any nodes that are located beyond the fixedBounds, the user may be unable to scroll the diagram to see them.
</p>
<p>
If you want to be notified whenever the document bounds changes, you can register a "DocumentBoundsChanged" <a>DiagramEvent</a> listener.
</p>
<h2 id="ViewportBounds">Viewport bounds</h2>
<p>
The <a>Diagram.viewportBounds</a> always has x and y values that are given by the <a>Diagram.position</a>.
It always has width and height values that are computed from the canvas size and the <a>Diagram.scale</a>.
</p>
<p>
Users can scroll the document contents using keyboard commands, scrollbars or panning.
Programmatically, you can scroll using several means:
</p>
<ul>
<li>setting <a>Diagram.position</a></li>
<li>calling <a>Diagram.scrollToRect</a> or <a>Diagram.centerRect</a> or <a>Diagram.scroll</a></li>
<li>calling <a>Diagram.alignDocument</a></li>
<li>setting <a>Diagram.contentAlignment</a></li>
<li>calling <a>CommandHandler.scrollToPart</a></li>
</ul>
<p>
Furthermore, scrolling may happen automatically as nodes or links are added to or removed from or change visibility in the diagram.
Also, zooming will typically result in scrolling as well.
</p>
<p>
When scrolling, the <a>Diagram.position</a> normally will be limited to the range specified by the <a>Diagram.documentBounds</a>.
The short or "line" scrolling distance is controlled by <a>Diagram.scrollHorizontalLineChange</a> and <a>Diagram.scrollVerticalLineChange</a>.
The long or "page" scrolling distance is controlled by the size of the viewport.
If you want to control the precise values that the <a>Diagram.position</a> may have,
you can specify a <a>Diagram.positionComputation</a> function. See the example below.
</p>
<p>
User can zoom in or out using keyboard commands, mouse wheel, or pinching.
Programmatically, you can zoom using several means:
</p>
<ul>
<li>setting <a>Diagram.scale</a></li>
<li>calling <a>Diagram.zoomToFit</a> or <a>Diagram.zoomToRect</a></li>
<li>setting <a>Diagram.autoScale</a></li>
<li>calling <a>CommandHandler.decreaseZoom</a>, <a>CommandHandler.increaseZoom</a>, <a>CommandHandler.resetZoom</a>, or
<a>CommandHandler.zoomToFit</a></li>
</ul>
<p>
When zooming in or out, the <a>Diagram.scale</a> normally will be limited to the range given by <a>Diagram.minScale</a> and <a>Diagram.maxScale</a>.
If you want to control the precise values that the <a>Diagram.scale</a> may have,
you can specify a <a>Diagram.scaleComputation</a> function. See the example below.
</p>
<p>
If you want to be notified whenever the viewport bounds changes, you can register a "ViewportBoundsChanged" <a>DiagramEvent</a> listener.
</p>
<h2 id="ScrollMargin">Scroll margin</h2>
<p>
<a>Diagram.scrollMargin</a> allows the user to scroll into empty space at the edges of the viewport,
when the document bounds (including its <a>Diagram.padding</a> margin) is greater than the viewport bounds.
This can be useful when users need extra space at the edges of a Diagram,
for instance to have an area to create new nodes with the <a>ClickCreatingTool</a>.
</p>
<p>
<a>Diagram.padding</a> is added as if part of the document bounds,
whereas <code>scrollMargin</code> makes sure you can scroll to empty space beyond the document bounds.
Because of this, <code>scrollMargin</code> does not create additional scrollable empty space if none
is needed to scroll the margin distance beyond, such as when the document bounds are very small in the viewport.
</p>
<p>
Below is a Diagram with <code>scrollMargin</code> set to <code>100</code>.
As you drag to the boundary, you will find the additional space created by the margin.
</p>
<pre class="lang-js" id="scrollmargin" style="display: none;">
diagram.grid = $(go.Panel, "Grid",
$(go.Shape, "LineH", { stroke: "gray", strokeWidth: 0.5 }),
$(go.Shape, "LineH", { stroke: "darkslategray", strokeWidth: 1.5, interval: 10 }),
$(go.Shape, "LineV", { stroke: "gray", strokeWidth: 0.5 }),
$(go.Shape, "LineV", { stroke: "darkslategray", strokeWidth: 1.5, interval: 10 })
);
diagram.scrollMargin = 100;
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle",
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 3 },
new go.Binding("text", "key"))
);
var nodes = [];
for (var i = 0; i < 99; i++) {
nodes.push({ key: "Alpha", color: "lightblue" });
}
diagram.model = new go.GraphLinksModel(nodes,[]);
</pre>
<script>goCode("scrollmargin", 400, 400)</script>
<h2 id="ScrollingModes">Scrolling modes</h2>
<p>
<a>Diagram.scrollMode</a> allows the user to either scroll to document bound borders with <a>Diagram,DocumentScroll</a> (the default),
or scroll endlessly with <a>Diagram,InfiniteScroll</a>.
</p>
<p>
<a>Diagram.positionComputation</a> and <a>Diagram.scaleComputation</a> allow you to determine what positions
and scales are acceptable to be scrolled to.
For instance, you could allow only integer position values, or only allow scaling to the values of 0.5, 1, or 2.
</p>
<p>
The <a href="../samples/scrollModes.html">Scroll Modes sample</a> displays all the code for the example below,
which lets you toggle these three properties.
</p>
<pre class="lang-js" id="scrollmodes" style="display: none;">
diagram.minScale = 0.25;
diagram.grid = $(go.Panel, "Grid",
$(go.Shape, "LineH", { stroke: "gray", strokeWidth: 0.5 }),
$(go.Shape, "LineH", { stroke: "darkslategray", strokeWidth: 1.5, interval: 10 }),
$(go.Shape, "LineV", { stroke: "gray", strokeWidth: 0.5 }),
$(go.Shape, "LineV", { stroke: "darkslategray", strokeWidth: 1.5, interval: 10 })
);
diagram.toolManager.draggingTool.isGridSnapEnabled = true;
diagram.undoManager.isEnabled = true;
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle",
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 3 },
new go.Binding("text", "key"))
);
// create the model data that will be represented by Nodes and Links
diagram.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: "Gamma", to: "Delta" },
{ from: "Delta", to: "Alpha" }
]);
myDiagram2 = diagram; // make accessible to the HTML buttons
</pre>
<script>goCode("scrollmodes", 400, 400)</script>
<p>
<label><input id="infscroll" type="checkbox" />Enable Infinite Scrolling, setting <a>Diagram.scrollMode</a></label>
</p>
<pre class="lang-js">
myDiagram.scrollMode = checked ? go.Diagram.InfiniteScroll : go.Diagram.DocumentScroll;
</pre>
<p>
<label><input id="poscomp" type="checkbox" />Enable <a>Diagram.positionComputation</a> function</label>
</p>
<pre class="lang-js">
function positionfunc(diagram, pos) {
var size = diagram.grid.gridCellSize;
return new go.Point(
Math.round(pos.x / size.width) * size.width,
Math.round(pos.y / size.height) * size.height);
}
</pre>
<p>
<label><input id="scalecomp" type="checkbox" />Enable <a>Diagram.scaleComputation</a> function</label>
</p>
<pre class="lang-js">
function scalefunc(diagram, scale) {
var oldscale = diagram.scale;
if (scale > oldscale) {
return oldscale + 0.25;
} else if (scale < oldscale) {
return oldscale - 0.25;
}
return oldscale;
}
</pre>
<script type="text/javascript">
function positionfunc(diagram, pos) {
var size = diagram.grid.gridCellSize;
return new go.Point(
Math.round(pos.x / size.width) * size.width,
Math.round(pos.y / size.height) * size.height);
}
function scalefunc(diagram, scale) {
var oldscale = diagram.scale;
if (scale > oldscale) {
return oldscale + 0.25;
} else if (scale < oldscale) {
return oldscale - 0.25;
}
return oldscale;
}
var infscroll = document.getElementById('infscroll');
infscroll.addEventListener('change', function(e) {
myDiagram2.commit(function(d) { d.scrollMode = infscroll.checked ? go.Diagram.InfiniteScroll : go.Diagram.DocumentScroll; });
});
var poscomp = document.getElementById('poscomp');
poscomp.addEventListener('change', function(e) {
myDiagram2.commit(function(d) { d.positionComputation = poscomp.checked ? positionfunc : null; });
});
var scalecomp = document.getElementById('scalecomp');
scalecomp.addEventListener('change', function(e) {
myDiagram2.commit(function(d) { d.scaleComputation = scalecomp.checked ? scalefunc : null; });
});
</script>
<h2 id="PanelCoordinates">Panel coordinates</h2>
<p>
A <a>GraphObject</a> that is not a <a>Part</a> but is an element of a <a>Panel</a> has measurements
that are in panel coordinates, not in document coordinates.
That means that <a>GraphObject.position</a>, <a>GraphObject.actualBounds</a>, <a>GraphObject.maxSize</a>,
<a>GraphObject.minSize</a>, <a>GraphObject.measuredBounds</a>, <a>GraphObject.margin</a>, and
<a>RowColumnDefinition</a> properties apply to all elements of a panel using the same coordinate system.
</p>
<p>
Some <a>GraphObject</a> properties use units that have values before they are transformed for use by
the containing <a>Panel</a>'s coordinate system.
In particular, <a>GraphObject.desiredSize</a> (which means <a>GraphObject.width</a> and <a>GraphObject.height</a>),
<a>GraphObject.naturalBounds</a>, <a>Shape.geometry</a>, and <a>Shape.strokeWidth</a> are in "local" coordinates,
before the object is scaled and rotated by the value of <a>GraphObject.scale</a> and <a>GraphObject.angle</a>.
</p>
<p>
<a>GraphObject.actualBounds</a> will tell you the position and size of an element within its panel.
If you want to get the document position of some object that is within a Node,
call <a>GraphObject.getDocumentPoint</a>.
</p>
<p>
For examples of the sizes of elements in a panel, see <a href="sizing.html">Sizing GraphObjects</a>.
</p>
<h3 id="NestedPanelCoordinates">Nested Panel coordinates</h3>
<pre class="lang-js" id="nestedpanelcoords" style="display: none;">
// read-only to avoid accidentally moving any Part in document coordinates
diagram.isReadOnly = true;
diagram.allowSelect = false;
diagram.initialPosition = new go.Point(-5, -5);
diagram.initialScale = 0.45;
// data objects for data tables.
function InfoBox(key,gro,loc) {
this.category = "info";
this.key = key;
this.location = go.Point.parse(loc);
this.gro = gro;
}
// alignment properties for TextBlocks in data tables.
function AlignmentObject(column,columnSpan) {
this.column = column;
this.columnSpan = columnSpan;
this.verticalAlignment = go.Spot.Center;
this.textAlign = "center";
this.alignment = go.Spot.Center;
this.height = 24;
}
// creates functions which have limited precision return values.
function prec(conv) { return function (g) { return conv(g).toPrecision(3) }}
// generates cells in data tables
function dataBlock(conv, alo1, alo2) {
return $(go.TextBlock, "", new go.Binding("text", "gro", prec(conv)), new AlignmentObject(alo1, alo2));
}
var nodeTemplates = new go.Map();
// Template for data tables
nodeTemplates.add("info",
$(go.Node, "Auto",
// Allows location to be set in data object
new go.Binding("location"), { padding: 0, scale: 2 },
$(go.Panel, "Table",
{name: "table",
defaultRowSeparatorStroke: "black", defaultColumnSeparatorStroke: "black",
defaultAlignment: go.Spot.Center, background: "white"
},
// sets a different look for the defining row.
$(go.RowColumnDefinition,
{row: 0,
background: "lightgray", separatorStrokeWidth: 0,
separatorPadding: 0, coversSeparators: true,
height: 24
}),
// sets a different look for the defining column.
$(go.RowColumnDefinition,
{column: 0,
coversSeparators: true, separatorStrokeWidth: 0,
separatorPadding: 0, background: "lightgray",
width: 45
}),
// necessary to keep weirdness involving the columnSpan of certain elements in the table
// from causing separators to go through elements.
$(go.RowColumnDefinition, {column: 1, width: 28}),
$(go.RowColumnDefinition, {column: 2, separatorStroke: "transparent", width: 28}),
$(go.RowColumnDefinition, {column: 3, width: 28}),
$(go.RowColumnDefinition, {column: 4, separatorStroke: "transparent", width: 28}),
// defining row
$(go.Panel, "TableRow", {row: 0},
$(go.TextBlock, "Container", new AlignmentObject(1,2)),
$(go.TextBlock, "Diagram", new AlignmentObject(3,2))),
// angle row
$(go.Panel, "TableRow", {row: 1},
$(go.TextBlock, "angle", {column: 0}),
// container angle
dataBlock(function (g) { return g.angle }, 1, 2),
// document angle
dataBlock(function (g) { return g.getDocumentAngle() }, 3, 2)),
// scale row
$(go.Panel, "TableRow", {row: 2},
$(go.TextBlock, "scale", {column: 0}),
// container scale
dataBlock(function (g) { return g.scale }, 1, 2),
// document scale
dataBlock(function (g) { return g.getDocumentScale() }, 3, 2)),
// position row
$(go.Panel, "TableRow", {row: 3},
$(go.TextBlock, "X Y", {column: 0}),
// container x and y values
dataBlock(function (g) { return g.actualBounds.x }, 1, 1),
dataBlock(function (g) { return g.actualBounds.y }, 2, 1),
// document x and y values
dataBlock(function (g) { return g.getDocumentBounds().x }, 3, 1),
dataBlock(function (g) { return g.getDocumentBounds().y }, 4, 1)),
// dimension row
$(go.Panel, "TableRow", {row: 4},
$(go.TextBlock, "size", {column: 0}),
// container width and height
dataBlock(function (g) { return g.actualBounds.width }, 1, 1),
dataBlock(function (g) { return g.actualBounds.height }, 2, 1),
// document width and height
dataBlock(function (g) { return g.getDocumentBounds().width }, 3, 1),
dataBlock(function (g) { return g.getDocumentBounds().width }, 4, 1)))));
// data object for labels on data tables
function WordBubble(key,width,loc,desc,color) {
this.key = key; this.category = "words"; this.width = width;
this.desc = desc; this.location = go.Point.parse(loc); this.color = color;
}
// template for wordbubble objects
nodeTemplates.add("words",
$(go.Node, "Auto",
new go.Binding("location"),
$(go.TextBlock, "",
new go.Binding("text", "desc"),
new go.Binding("stroke","color"),
new go.Binding("width"),
{ textAlign: "left", font: "24pt sans-serif" })));
// creating the main node's template, adding the nested Panels to it, and adding it to the node template map.
let vertPanel = posPanel = spotPanel = vertLabel = topLabel = {};
var BigNode =
$(go.Node, "Auto",
{
location: new go.Point(300,0),
},
vertPanel =
$(go.Panel, "Vertical",
{portId: "vertPanel",
angle: 165, scale: 1.5,
background: "lightblue",
padding: 20
},
vertLabel =
$(go.TextBlock, "Vertical Panel", {font: "bold 12pt sans-serif"}),
posPanel =
$(go.Panel, "Position",
{portId: "posPanel",
angle: 120, scale: 0.8, padding: 50,
background: go.Brush.mix("brown", "lightyellow", 0.4)},
$(go.Panel, "Auto", { position: new go.Point(25,0), desiredSize: new go.Size(60,90)},
$(go.Shape, "Triangle", { fill: "transparent" }),
$(go.TextBlock, "This Side Up")),
$(go.TextBlock, "Position Panel", { position: new go.Point(0,100), font: "bold 12pt sans-serif" }),
),
spotPanel =
$(go.Panel, "Spot",
{portId: "spotPanel",
angle: 30, scale: 1.5,
background: "lightgreen" },
$(go.Shape, "RoundedRectangle", {strokeWidth: 0, desiredSize: new go.Size(50,100), fill: "transparent"}),
$(go.TextBlock, "Spot Panel",
{
font: "bold 12pt sans-serif",
alignment: go.Spot.Center,
}
),
$(go.TextBlock, "Top",
{
margin: 5,
font: "bold 12pt sans-serif",
alignment: go.Spot.Top,
}
),
bottomLabel =
$(go.TextBlock, "Bottom",
{portId: "bottomLabel",
font: "bold 12pt sans-serif",
alignment: go.Spot.Bottom
}))));
nodeTemplates.add("", BigNode);
diagram.nodeTemplateMap = nodeTemplates;
diagram.linkTemplate =
$(go.Link,
new go.Binding("fromNode", "from", diagram.findNodeForKey),
new go.Binding("to"), new go.Binding("toPortId"),
$(go.Shape, {strokeWidth: 5}),
$(go.Shape, {scale: 3,toArrow: "Standard"}));
diagram.model = new go.GraphLinksModel(
[
{key: "bn"},
// creating infoboxes
new InfoBox(0,vertPanel,"-20 30"),
new InfoBox(1,posPanel,"20 470"),
new InfoBox(2,spotPanel,"900 500"),
new InfoBox(3,bottomLabel,"900 180"),
// creating wordbubbles
new WordBubble(4,250,"60 0","Vertical Panel","blue"),
new WordBubble(5,250,"60 440","Position Panel","red"),
new WordBubble(6,250,"980 470","Spot Panel","green"),
new WordBubble(7,275,"960 105","TextBlock aligned at Spot.Bottom","black")
],
[
// linking each infobox to an item on the main node.
{from: 0, to: "bn", toPortId: "vertPanel"},
{from: 1, to: "bn", toPortId: "posPanel"},
{from: 2, to: "bn", toPortId: "spotPanel"},
{from: 3, to: "bn", toPortId: "bottomLabel"}
]);
</pre>
<p>
The transformations of each element in a <a>Panel</a> are compounded by that panel's transformations.
</p>
<script>goCode("nestedpanelcoords", 600, 400)</script>
<p>
The <a>TextBlock</a> that is "Bottom" has the default <a>GraphObject.angle</a> of zero, so that the text is drawn upright.
But that TextBlock is an element in the green "Spot" <a>Panel</a> whose <a>GraphObject.angle</a> to 30,
so it and its text should appear somewhat tilted.
However the blue "Vertical" Panel itself has an <a>GraphObject.angle</a> of 165.
Because each Panel has its own coordinate system and because transformations on nested elements are compounded,
the effective angle for the green Panel is 195 degrees, the sum of those individual angles (30 + 165), which is nearly upside down.
</p>
<p>
The <a>GraphObject.scale</a> property also affects how an object is sized in its container Panel.
The brown "Position" <a>Panel</a> has a scale of 0.8 relative to its container.
But because the "Vertical" Panel has a scale of 1.5, its effective scale is 1.2 overall,
the product of those individual scales (0.8 x 1.5).
</p>
</div>
</div>
</body>
</html>