feat: restore remaining non-"Editor" examples to Storybook (#150)

Converted the following remaining "stashed" examples to Storybook:
  - GraphLayout
  - HtmlLabel
  - MenuStyle
  - ShowRegion
  - Scrollbars (is starting to display, but appears to have a bug where `<g>` elements are inserted outside of the `<svg>` element)
  - Wires

The examples are still not fully working. As the mxGraph examples were previously migrated to nextjs/react, some original code may not have been ported correctly.


Additional changes in @maxgraph/core:
  - Fixes the default parameters in `MaxPopupMenu` and `Morphing` to restore the original `mxGraph` behavior.
  - Graph: add new "create*" factory methods to ease the extension:
    - createEdgeHandlerInstance(state: CellState): EdgeHandler
    - createGraphDataModel(): GraphDataModel
    - createSelectionModel()

---------

Co-authored-by: Thomas Bouffard <27200110+tbouffard@users.noreply.github.com>
development
Dave Morrissey 2023-12-22 16:54:34 +11:00 committed by GitHub
parent d5c60b120f
commit 63f002c263
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 2524 additions and 2012 deletions

View File

@ -57,7 +57,9 @@ class MaxPopupMenu extends EventSource implements Partial<PopupMenuItem> {
) {
super();
if (factoryMethod) {
this.factoryMethod = factoryMethod;
}
// Adds the inner table
this.table = document.createElement('table');
@ -189,9 +191,9 @@ class MaxPopupMenu extends EventSource implements Partial<PopupMenuItem> {
funct: Function,
parent: PopupMenuItem | null = null,
iconCls: string | null = null,
enabled: boolean | null = null,
active: boolean | null = null,
noHover: boolean | null = null
enabled = true,
active = true,
noHover = false
) {
parent = (parent ?? this) as PopupMenuItem;
this.itemCount++;

View File

@ -92,9 +92,9 @@ class Graph extends EventSource {
foldingEnabled: null | boolean = null;
isConstrainedMoving = false;
/*****************************************************************************
* Group: Variables (that maybe should be in the mixins, but need to be created for each new class instance)
*****************************************************************************/
// ===================================================================================================================
// Group: Variables (that maybe should be in the mixins, but need to be created for each new class instance)
// ===================================================================================================================
cells: Cell[] = [];
@ -111,10 +111,6 @@ class Graph extends EventSource {
*/
multiplicities: Multiplicity[] = [];
/*****************************************************************************
* Group: Variables
*****************************************************************************/
/**
* Holds the {@link GraphDataModel} that contains the cells to be displayed.
*/
@ -407,6 +403,89 @@ class Graph extends EventSource {
containsValidationErrorsResource: string =
Client.language != 'none' ? 'containsValidationErrors' : '';
// ===================================================================================================================
// Group: "Create Class Instance" factory functions.
// These can be overridden in subclasses of Graph to allow the Graph to instantiate user-defined implementations with
// custom behavior.
// ===================================================================================================================
/**
* Creates a new {@link CellRenderer} to be used in this graph.
*/
createCellRenderer(): CellRenderer {
return new CellRenderer();
}
/**
* Hooks to create a new {@link EdgeHandler} for the given {@link CellState}.
*
* @param state {@link CellState} to create the handler for.
*/
createEdgeHandlerInstance(state: CellState): EdgeHandler {
// Note this method not being called createEdgeHandler to keep compatibility
// with older code which overrides/calls createEdgeHandler
return new EdgeHandler(state);
}
/**
* Hooks to create a new {@link EdgeSegmentHandler} for the given {@link CellState}.
*
* @param state {@link CellState} to create the handler for.
*/
createEdgeSegmentHandler(state: CellState) {
return new EdgeSegmentHandler(state);
}
/**
* Hooks to create a new {@link ElbowEdgeHandler} for the given {@link CellState}.
*
* @param state {@link CellState} to create the handler for.
*/
createElbowEdgeHandler(state: CellState) {
return new ElbowEdgeHandler(state);
}
/**
* Creates a new {@link GraphDataModel} to be used in this graph.
*/
createGraphDataModel(): GraphDataModel {
return new GraphDataModel();
}
/**
* Creates a new {@link GraphView} to be used in this graph.
*/
createGraphView(): GraphView {
return new GraphView(this);
}
/**
* Creates a new {@link GraphSelectionModel} to be used in this graph.
*/
createSelectionModel() {
return new GraphSelectionModel(this);
}
/**
* Creates a new {@link Stylesheet} to be used in this graph.
*/
createStylesheet(): Stylesheet {
return new Stylesheet();
}
/**
* Hooks to create a new {@link VertexHandler} for the given {@link CellState}.
*
* @param state {@link CellState} to create the handler for.
*/
createVertexHandler(state: CellState): VertexHandler {
return new VertexHandler(state);
}
// ===================================================================================================================
// Group: Main graph constructor and functions
// ===================================================================================================================
constructor(
container: HTMLElement,
model?: GraphDataModel,
@ -416,10 +495,10 @@ class Graph extends EventSource {
super();
this.container = container ?? document.createElement('div');
this.model = model ?? new GraphDataModel();
this.model = model ?? this.createGraphDataModel();
this.plugins = plugins;
this.cellRenderer = this.createCellRenderer();
this.setStylesheet(stylesheet != null ? stylesheet : this.createStylesheet());
this.setStylesheet(stylesheet ?? this.createStylesheet());
this.view = this.createGraphView();
// Adds a graph model listener to update the view
@ -437,7 +516,7 @@ class Graph extends EventSource {
// Set the selection model
this.setSelectionModel(this.createSelectionModel());
// Initiailzes plugins
// Initializes plugins
this.plugins.forEach((p: GraphPluginConstructor) => {
this.pluginsMap[p.pluginId] = new p(this);
});
@ -445,7 +524,6 @@ class Graph extends EventSource {
this.view.revalidate();
}
createSelectionModel = () => new GraphSelectionModel(this);
getContainer = () => this.container;
getPlugin = (id: string) => this.pluginsMap[id] as unknown;
getCellRenderer = () => this.cellRenderer;
@ -489,27 +567,6 @@ class Graph extends EventSource {
this.getDataModel().batchUpdate(fn);
}
/**
* Creates a new {@link Stylesheet} to be used in this graph.
*/
createStylesheet(): Stylesheet {
return new Stylesheet();
}
/**
* Creates a new {@link GraphView} to be used in this graph.
*/
createGraphView() {
return new GraphView(this);
}
/**
* Creates a new {@link CellRenderer} to be used in this graph.
*/
createCellRenderer(): CellRenderer {
return new CellRenderer();
}
/**
* Returns the {@link GraphDataModel} that contains the cells.
*/
@ -960,15 +1017,6 @@ class Graph extends EventSource {
return result;
}
/**
* Hooks to create a new {@link VertexHandler} for the given {@link CellState}.
*
* @param state {@link CellState} to create the handler for.
*/
createVertexHandler(state: CellState): VertexHandler {
return new VertexHandler(state);
}
/**
* Hooks to create a new {@link EdgeHandler} for the given {@link CellState}.
*
@ -989,30 +1037,12 @@ class Graph extends EventSource {
) {
result = this.createEdgeSegmentHandler(state);
} else {
result = new EdgeHandler(state);
result = this.createEdgeHandlerInstance(state);
}
return result as EdgeHandler;
}
/**
* Hooks to create a new {@link EdgeSegmentHandler} for the given {@link CellState}.
*
* @param state {@link CellState} to create the handler for.
*/
createEdgeSegmentHandler(state: CellState) {
return new EdgeSegmentHandler(state);
}
/**
* Hooks to create a new {@link ElbowEdgeHandler} for the given {@link CellState}.
*
* @param state {@link CellState} to create the handler for.
*/
createElbowEdgeHandler(state: CellState) {
return new ElbowEdgeHandler(state);
}
/*****************************************************************************
* Group: Drilldown
*****************************************************************************/

View File

@ -56,7 +56,7 @@ import { Graph } from '../Graph';
* @param delay Optional delay between the animation steps. Passed to <Animation>.
*/
class Morphing extends Animation {
constructor(graph: Graph, steps = 6, ease = 1.5, delay: number) {
constructor(graph: Graph, steps = 6, ease = 1.5, delay?: number) {
super(delay);
this.graph = graph;
this.steps = steps;

View File

@ -0,0 +1,193 @@
/*
Copyright 2021-present The maxGraph project Contributors
Copyright (c) 2006-2013, JGraph Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
Graph Layout
This example demonstrates using automatic graph layouts and listening to changes of the graph size
to keep the container size in sync.
*/
import {
constants,
CircleLayout,
DomHelpers,
FastOrganicLayout,
Graph,
InternalEvent,
Morphing,
Perimeter,
} from '@maxgraph/core';
import { globalTypes, globalValues } from './shared/args.js';
// TODO apply this settings to the container used by the Graph
const HTML_TEMPLATE = `
<!-- Page passes the container for the graph to the program -->
<body onload="main(document.getElementById('graphContainer'))">
<!-- Creates a container for the graph with a grid wallpaper. Make sure to define the position
and overflow attributes! See comments on the adding of the size-listener on line 54 ff! -->
<div id="graphContainer"
style="position:relative;overflow:auto;width:821px;height:641px;background:url('editors/images/grid.gif');">
</div>
<br>
</body>
`;
export default {
title: 'Layouts/GraphLayout',
argTypes: {
...globalTypes,
animate: {
type: 'boolean',
defaultValue: false,
},
},
args: {
...globalValues,
animate: true,
},
};
const Template = ({ label, ...args }) => {
const mainContainer = document.createElement('div');
const container = document.createElement('div');
container.style.position = 'relative';
container.style.overflow = 'hidden';
container.style.width = `${args.width}px`;
container.style.height = `${args.height}px`;
container.style.background = 'url(/images/grid.gif)';
container.style.cursor = 'default';
// Creates the graph inside the given container
let graph = new Graph(container);
// Disables basic selection and cell handling
graph.setEnabled(false);
// Changes the default vertex style in-place
let style = graph.getStylesheet().getDefaultVertexStyle();
style.shape = constants.SHAPE.ELLIPSE;
style.perimiter = Perimeter.EllipsePerimeter;
style.gradientColor = 'white';
style.fontSize = 10;
// Updates the size of the container to match
// the size of the graph when it changes. If
// this is commented-out, and the DIV style's
// overflow is set to "auto", then scrollbars
// will appear for the diagram. If overflow is
// set to "visible", then the diagram will be
// visible even when outside the parent DIV.
// With the code below, the parent DIV will be
// resized to contain the complete graph.
//graph.setResizeContainer(true);
// Larger grid size yields cleaner layout result
graph.gridSize = 40;
// Gets the default parent for inserting new cells. This
// is normally the first child of the root (ie. layer 0).
let parent = graph.getDefaultParent();
// Creates a layout algorithm to be used
// with the graph
let layout = new FastOrganicLayout(graph);
// Moves stuff wider apart than usual
layout.forceConstant = 80;
// Adds a button to execute the layout
mainContainer.appendChild(
DomHelpers.button('Circle Layout', function (evt) {
graph.getDataModel().beginUpdate();
try {
// Creates a layout algorithm to be used
// with the graph
let circleLayout = new CircleLayout(graph);
circleLayout.execute(parent);
} finally {
if (args.animate) {
let morph = new Morphing(graph, 6, 1.5, 20);
morph.addListener(InternalEvent.DONE, function () {
graph.getDataModel().endUpdate();
});
morph.startAnimation();
} else {
graph.getDataModel().endUpdate();
}
}
})
);
// Adds a button to execute the layout
mainContainer.appendChild(
DomHelpers.button('Organic Layout', function (evt) {
graph.getDataModel().beginUpdate();
try {
layout.execute(parent);
} finally {
if (args.animate) {
// Default values are 6, 1.5, 20
let morph = new Morphing(graph, 10, 1.7, 20);
morph.addListener(InternalEvent.DONE, function () {
graph.getDataModel().endUpdate();
});
morph.startAnimation();
} else {
graph.getDataModel().endUpdate();
}
}
})
);
mainContainer.appendChild(container);
// Adds cells to the model in a single step
let w = 30;
let h = 30;
graph.batchUpdate(() => {
let v1 = graph.insertVertex(parent, null, 'A', 0, 0, w, h);
let v2 = graph.insertVertex(parent, null, 'B', 0, 0, w, h);
let v3 = graph.insertVertex(parent, null, 'C', 0, 0, w, h);
let v4 = graph.insertVertex(parent, null, 'D', 0, 0, w, h);
let v5 = graph.insertVertex(parent, null, 'E', 0, 0, w, h);
let v6 = graph.insertVertex(parent, null, 'F', 0, 0, w, h);
let v7 = graph.insertVertex(parent, null, 'G', 0, 0, w, h);
let v8 = graph.insertVertex(parent, null, 'H', 0, 0, w, h);
let e1 = graph.insertEdge(parent, null, 'ab', v1, v2);
let e2 = graph.insertEdge(parent, null, 'ac', v1, v3);
let e3 = graph.insertEdge(parent, null, 'cd', v3, v4);
let e4 = graph.insertEdge(parent, null, 'be', v2, v5);
let e5 = graph.insertEdge(parent, null, 'cf', v3, v6);
let e6 = graph.insertEdge(parent, null, 'ag', v1, v7);
let e7 = graph.insertEdge(parent, null, 'gh', v7, v8);
let e8 = graph.insertEdge(parent, null, 'gc', v7, v3);
let e9 = graph.insertEdge(parent, null, 'gd', v7, v4);
let e10 = graph.insertEdge(parent, null, 'eh', v5, v8);
// Executes the layout
layout.execute(parent);
});
return mainContainer;
};
export const Default = Template.bind({});

View File

@ -0,0 +1,210 @@
/*
Copyright 2021-present The maxGraph project Contributors
Copyright (c) 2006-2013, JGraph Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
HTML label
This example demonstrates using HTML labels that are connected to the state of the user object.
*/
import {
xmlUtils,
domUtils,
InternalEvent,
RubberBandHandler,
UndoManager,
CodecRegistry,
Graph,
Cell,
DomHelpers,
GraphDataModel,
CellEditorHandler,
TooltipHandler,
SelectionCellsHandler,
PopupMenuHandler,
ConnectionHandler,
SelectionHandler,
PanningHandler,
} from '@maxgraph/core';
import { globalTypes, globalValues } from './shared/args.js';
// style required by RubberBand
import '@maxgraph/core/css/common.css';
export default {
title: 'Labels/HtmlLabel',
argTypes: {
...globalTypes,
},
args: {
...globalValues,
},
};
const Template = ({ label, ...args }) => {
const container = document.createElement('div');
container.style.position = 'relative';
container.style.overflow = 'hidden';
container.style.width = `${args.width}px`;
container.style.height = `${args.height}px`;
container.style.background = 'url(/images/grid.gif)';
container.style.cursor = 'default';
// Disables the built-in context menu
InternalEvent.disableContextMenu(container);
// Creates a user object that stores the state
let doc = xmlUtils.createXmlDocument();
let obj = doc.createElement('UserObject');
obj.setAttribute('label', 'Hello, World!');
obj.setAttribute('checked', 'false');
// Adds optional caching for the HTML label
let cached = true;
let MyCustomGraphDataModel;
if (cached) {
// Ignores cached label in codec
CodecRegistry.getCodec(Cell).exclude.push('div');
// Invalidates cached labels
MyCustomGraphDataModel = class extends GraphDataModel {
setValue(cell, value) {
cell.div = null;
super.setValue.apply(this, arguments);
}
};
} else {
MyCustomGraphDataModel = GraphDataModel;
}
class MyCustomGraph extends Graph {
createGraphDataModel() {
return new MyCustomGraphDataModel();
}
// Overrides method to provide a cell label in the display
convertValueToString(cell) {
if (cached && cell.div != null) {
// Uses cached label
return cell.div;
} else if (
domUtils.isNode(cell.value) &&
cell.value.nodeName.toLowerCase() === 'userobject'
) {
// Returns a DOM for the label
let div = document.createElement('div');
div.innerHTML = cell.getAttribute('label');
domUtils.br(div);
let checkbox = document.createElement('input');
checkbox.setAttribute('type', 'checkbox');
if (cell.getAttribute('checked') == 'true') {
checkbox.setAttribute('checked', 'checked');
checkbox.defaultChecked = true;
}
// Writes back to cell if checkbox is clicked
InternalEvent.addListener(checkbox, 'change', function (evt) {
let elt = cell.value.cloneNode(true);
elt.setAttribute('checked', checkbox.checked ? 'true' : 'false');
graph.model.setValue(cell, elt);
});
div.appendChild(checkbox);
if (cached) {
// Caches label
cell.div = div;
}
return div;
}
return '';
}
// Overrides method to store a cell label in the model
cellLabelChanged(cell, newValue, autoSize) {
if (
domUtils.isNode(cell.value) &&
cell.value.nodeName.toLowerCase() === 'userobject'
) {
// Clones the value for correct undo/redo
let elt = cell.value.cloneNode(true);
elt.setAttribute('label', newValue);
newValue = elt;
}
super.cellLabelChanged.apply(this, arguments);
}
// Overrides method to create the editing value
getEditingValue(cell) {
if (
domUtils.isNode(cell.value) &&
cell.value.nodeName.toLowerCase() === 'userobject'
) {
return cell.getAttribute('label');
}
}
}
// Creates the graph inside the given container
let graph = new MyCustomGraph(container, null, [
CellEditorHandler,
TooltipHandler,
SelectionCellsHandler,
PopupMenuHandler,
ConnectionHandler,
SelectionHandler,
PanningHandler,
]);
// Enables HTML labels
graph.setHtmlLabels(true);
// Enables rubberband selection
new RubberBandHandler(graph);
let parent = graph.getDefaultParent();
graph.insertVertex(parent, null, obj, 20, 20, 80, 60);
// Undo/redo
let undoManager = new UndoManager();
let listener = function (sender, evt) {
undoManager.undoableEditHappened(evt.getProperty('edit'));
};
graph.getDataModel().addListener(InternalEvent.UNDO, listener);
graph.getView().addListener(InternalEvent.UNDO, listener);
document.body.appendChild(
DomHelpers.button('Undo', function () {
undoManager.undo();
})
);
document.body.appendChild(
DomHelpers.button('Redo', function () {
undoManager.redo();
})
);
return container;
};
export const Default = Template.bind({});

View File

@ -0,0 +1,240 @@
/*
Copyright 2021-present The maxGraph project Contributors
Copyright (c) 2006-2013, JGraph Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
Menustyle
This example demonstrates using CSS to style the mxPopupMenu.
*/
import {
Client,
CellOverlay,
CellRenderer,
EventObject,
ImageBox,
InternalEvent,
InternalMouseEvent,
RubberBandHandler,
Graph,
PopupMenuHandler,
CellEditorHandler,
TooltipHandler,
SelectionCellsHandler,
ConnectionHandler,
SelectionHandler,
PanningHandler,
} from '@maxgraph/core';
import { globalTypes, globalValues } from './shared/args.js';
// style required by RubberBand
import '@maxgraph/core/css/common.css';
const CSS_TEMPLATE = `
body div.mxPopupMenu {
-webkit-box-shadow: 3px 3px 6px #C0C0C0;
-moz-box-shadow: 3px 3px 6px #C0C0C0;
box-shadow: 3px 3px 6px #C0C0C0;
background: white;
position: absolute;
border: 3px solid #e7e7e7;
padding: 3px;
}
body table.mxPopupMenu {
border-collapse: collapse;
margin: 0px;
}
body tr.mxPopupMenuItem {
color: black;
cursor: default;
}
body td.mxPopupMenuItem {
padding: 6px 60px 6px 30px;
font-family: Arial;
font-size: 10pt;
}
body td.mxPopupMenuIcon {
background-color: white;
padding: 0px;
}
body tr.mxPopupMenuItemHover {
background-color: #eeeeee;
color: black;
}
table.mxPopupMenu hr {
border-top: solid 1px #cccccc;
}
table.mxPopupMenu tr {
font-size: 4pt;
}
`;
// TODO apply this settings to the container used by the Graph
const HTML_TEMPLATE = `
<body onload="main(document.getElementById('graphContainer'))">
<!-- Creates a container for the graph with a grid wallpaper -->
<div id="graphContainer"
style="overflow:hidden;width:321px;height:241px;background:url('editors/images/grid.gif');cursor:default;">
</div>
</body>
</html>
`;
export default {
title: 'Misc/MenuStyle',
argTypes: {
...globalTypes,
},
args: {
...globalValues,
},
};
const Template = ({ label, ...args }) => {
const styleElm = document.createElement('style');
styleElm.innerText = CSS_TEMPLATE;
document.head.appendChild(styleElm);
const container = document.createElement('div');
container.style.position = 'relative';
container.style.overflow = 'hidden';
container.style.width = `${args.width}px`;
container.style.height = `${args.height}px`;
container.style.background = 'url(/images/grid.gif)';
container.style.cursor = 'default';
// Disables built-in context menu
InternalEvent.disableContextMenu(container);
// Changes some default colors
// TODO Find a way of modifying globally or setting locally! See https://github.com/maxGraph/maxGraph/issues/192
//constants.HANDLE_FILLCOLOR = '#99ccff';
//constants.HANDLE_STROKECOLOR = '#0088cf';
//constants.VERTEX_SELECTION_COLOR = '#00a8ff';
class MyCustomCellRenderer extends CellRenderer {
installCellOverlayListeners(state, overlay, shape) {
super.installCellOverlayListeners.apply(this, arguments);
let graph = state.view.graph;
InternalEvent.addGestureListeners(
shape.node,
function (evt) {
graph.fireMouseEvent(
InternalEvent.MOUSE_DOWN,
new InternalMouseEvent(evt, state)
);
},
function (evt) {
graph.fireMouseEvent(
InternalEvent.MOUSE_MOVE,
new InternalMouseEvent(evt, state)
);
},
function (evt) {}
);
if (!Client.IS_TOUCH) {
InternalEvent.addListener(shape.node, 'mouseup', function (evt) {
overlay.fireEvent(
new EventObject(InternalEvent.CLICK, 'event', evt, 'cell', state.cell)
);
});
}
}
}
class MyCustomPopupMenuHandler extends PopupMenuHandler {
// Configures automatic expand on mouseover
autoExpand = true;
constructor(graph) {
super(graph);
// Installs context menu
this.factoryMethod = function (menu, cell, evt) {
menu.addItem('Item 1', null, function () {
alert('Item 1');
});
menu.addItem('Item 2', null, function () {
alert('Item 2');
});
menu.addSeparator();
let submenu1 = menu.addItem('Submenu 1', null, null);
menu.addItem(
'Subitem 1',
null,
function () {
alert('Subitem 1');
},
submenu1
);
menu.addItem(
'Subitem 1',
null,
function () {
alert('Subitem 2');
},
submenu1
);
};
}
}
class MyCustomGraph extends Graph {
createCellRenderer() {
return new MyCustomCellRenderer();
}
}
// Creates the graph inside the given container
let graph = new MyCustomGraph(container, null, [
CellEditorHandler,
TooltipHandler,
SelectionCellsHandler,
MyCustomPopupMenuHandler,
ConnectionHandler,
SelectionHandler,
PanningHandler,
RubberBandHandler,
]);
graph.setTooltips(true);
// Gets the default parent for inserting new cells. This
// is normally the first child of the root (ie. layer 0).
let parent = graph.getDefaultParent();
// Adds cells to the model in a single step
let v1;
graph.batchUpdate(() => {
v1 = graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30);
let v2 = graph.insertVertex(parent, null, 'World!', 200, 150, 80, 30);
let e1 = graph.insertEdge(parent, null, '', v1, v2);
});
// Creates a new overlay with an image and a tooltip and makes it "transparent" to events
// and sets the overlay for the cell in the graph
let overlay = new CellOverlay(
new ImageBox('editors/images/overlays/check.png', 16, 16),
'Overlay tooltip'
);
graph.addCellOverlay(v1, overlay);
return container;
};
export const Default = Template.bind({});

View File

@ -0,0 +1,588 @@
/*
Copyright 2021-present The maxGraph project Contributors
Copyright (c) 2006-2015, JGraph Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
Scrollbars
This example demonstrates using a scrollable table with different sections in a cell label.
*/
import {
Cell,
CellRenderer,
CellEditorHandler,
Client,
ConnectionHandler,
constants,
DomHelpers,
domUtils,
EdgeStyle,
ImageBox,
InternalEvent,
Graph,
GraphView,
KeyHandler,
PanningHandler,
Perimeter,
Point,
Rectangle,
RubberBandHandler,
SelectionCellsHandler,
SelectionHandler,
xmlUtils,
} from '@maxgraph/core';
import {
contextMenuTypes,
contextMenuValues,
globalValues,
globalTypes,
} from './shared/args.js';
// style required by RubberBand
import '@maxgraph/core/css/common.css';
const CSS_TEMPLATE = `
table.title {
border-color: black;
border-collapse: collapse;
cursor: move;
height: 26px;
border-bottom-style: none;
color: black;
}
table.title th {
font-size: 10pt;
font-family: Verdana;
white-space: nowrap;
background: lightgray;
font-weight: bold;
}
table.erd {
font-size: 10pt;
font-family: Verdana;
border-color: black;
border-collapse: collapse;
overflow: auto;
cursor: move;
white-space: nowrap;
}
table.erd td {
border-color: black;
text-align: left;
color: black;
}
button {
position:absolute;
}
`;
// TODO apply this settings to the container used by the Graph
const HTML_TEMPLATE = `
<!-- Page passes the container for the graph to the program -->
<body onload="main(document.getElementById('graphContainer'))">
<!-- Creates a container for the graph with a grid wallpaper. Width, height and cursor in the style are for IE only -->
<div id="graphContainer"
style="cursor:default;position:absolute;top:30px;left:0px;bottom:0px;right:0px;background:url('editors/images/grid.gif')">
</div>
</body>
`;
export default {
title: 'Misc/Scrollbars',
argTypes: {
...contextMenuTypes,
...globalTypes,
},
args: {
...contextMenuValues,
...globalValues,
},
};
const Template = ({ label, ...args }) => {
const styleElm = document.createElement('style');
styleElm.innerText = CSS_TEMPLATE;
document.head.appendChild(styleElm);
const container = document.createElement('div');
container.style.position = 'relative';
container.style.overflow = 'hidden';
container.style.width = `${args.width}px`;
container.style.height = `${args.height}px`;
container.style.background = 'url(/images/grid.gif)';
container.style.cursor = 'default';
// Disables foreignObjects
Client.NO_FO = true;
class MyCustomSelectionHandler extends SelectionHandler {
// Enables move preview in HTML to appear on top
htmlPreview = true;
}
// Disables the context menu
InternalEvent.disableContextMenu(container);
// Defines global helper function to get y-coordinate for a given cell state and row
let getRowY = function (state, tr) {
let s = state.view.scale;
let div = tr.parentNode.parentNode.parentNode;
let offsetTop = parseInt(div.style.top);
let y =
state.y + (tr.offsetTop + tr.offsetHeight / 2 - div.scrollTop + offsetTop) * s;
y = Math.min(state.y + state.height, Math.max(state.y + offsetTop * s, y));
return y;
};
// If connect preview is not moved away then getCellAt is used to detect the cell under
// the mouse if the mouse is over the preview shape in IE (no event transparency), ie.
// the built-in hit-detection of the HTML document will not be used in this case. This is
// not a problem here since the preview moves away from the mouse as soon as it connects
// to any given table row. This is because the edge connects to the outside of the row and
// is aligned to the grid during the preview.
class MyCustomConnectionHandler extends ConnectionHandler {
movePreviewAway = false;
// Enables connect icons to appear on top of HTML
moveIconFront = true;
// Defines an icon for creating new connections in the connection handler.
// This will automatically disable the highlighting of the source vertex.
connectImage = new ImageBox('images/connector.gif', 16, 16);
// Overrides target perimeter point for connection previews
getTargetPerimeterPoint(state, me) {
// Determines the y-coordinate of the target perimeter point
// by using the currentRowNode assigned in updateRow
let y = me.getY();
if (this.currentRowNode != null) {
y = getRowY(state, this.currentRowNode);
}
// Checks on which side of the terminal to leave
let x = state.x;
if (this.previous.getCenterX() > state.getCenterX()) {
x += state.width;
}
return new Point(x, y);
}
// Overrides source perimeter point for connection previews
getSourcePerimeterPoint(state, next, me) {
let y = me.getY();
if (this.sourceRowNode != null) {
y = getRowY(state, this.sourceRowNode);
}
// Checks on which side of the terminal to leave
let x = state.x;
if (next.x > state.getCenterX()) {
x += state.width;
}
return new Point(x, y);
}
// Disables connections to invalid rows
isValidTarget(cell) {
return this.currentRowNode != null;
}
// Adds a new function to update the currentRow based on the given event
// and return the DOM node for that row
updateRow(target) {
while (target != null && target.nodeName != 'TR') {
target = target.parentNode;
}
this.currentRow = null;
// Checks if we're dealing with a row in the correct table
if (target != null && target.parentNode.parentNode.className == 'erd') {
// Stores the current row number in a property so that it can
// be retrieved to create the preview and final edge
let rowNumber = 0;
let current = target.parentNode.firstChild;
while (target != current && current != null) {
current = current.nextSibling;
rowNumber++;
}
this.currentRow = rowNumber + 1;
} else {
target = null;
}
return target;
}
// Adds placement of the connect icon based on the mouse event target (row)
updateIcons(state, icons, me) {
let target = me.getSource();
target = this.updateRow(target);
if (target != null && this.currentRow != null) {
let div = target.parentNode.parentNode.parentNode;
let s = state.view.scale;
icons[0].node.style.visibility = 'visible';
icons[0].bounds.x =
state.x +
target.offsetLeft +
Math.min(state.width, target.offsetWidth * s) -
this.icons[0].bounds.width -
2;
icons[0].bounds.y =
state.y -
this.icons[0].bounds.height / 2 +
(target.offsetTop + target.offsetHeight / 2 - div.scrollTop + div.offsetTop) *
s;
icons[0].redraw();
this.currentRowNode = target;
} else {
icons[0].node.style.visibility = 'hidden';
}
}
// Updates the targetRow in the preview edge State
mouseMove(sender, me) {
if (this.edgeState != null) {
this.currentRowNode = this.updateRow(me.getSource());
if (this.currentRow != null) {
this.edgeState.cell.value.setAttribute('targetRow', this.currentRow);
} else {
this.edgeState.cell.value.setAttribute('targetRow', '0');
}
// Destroys icon to prevent event redirection via image in IE
this.destroyIcons();
}
super.mouseMove.apply(this, arguments);
}
// Creates the edge state that may be used for preview
createEdgeState(me) {
let relation = doc.createElement('Relation');
relation.setAttribute('sourceRow', this.currentRow || '0');
relation.setAttribute('targetRow', '0');
let edge = this.createEdge(relation);
let style = this.graph.getCellStyle(edge);
let state = new Cell(this.graph.view, edge, style);
// Stores the source row in the handler
this.sourceRowNode = this.currentRowNode;
return state;
}
}
class MyCustomCellRenderer extends CellRenderer {
// Scroll events should not start moving the vertex
isLabelEvent(state, evt) {
let source = InternalEvent.getSource(evt);
return (
state.text != null &&
source != state.text.node &&
source != state.text.node.getElementsByTagName('div')[0]
);
}
// Adds scrollbars to the outermost div and keeps the
// DIV position and size the same as the vertex
redrawLabel(state) {
super.redrawLabel.apply(this, arguments); // "supercall"
let graph = state.view.graph;
let model = graph.model;
if (state.cell.isVertex() && state.text != null) {
// Scrollbars are on the div
let s = graph.view.scale;
let div = state.text.node.getElementsByTagName('div')[2];
if (div != null) {
// Installs the handler for updating connected edges
if (div.scrollHandler == null) {
div.scrollHandler = true;
let updateEdges = () => {
let edgeCount = state.cell.getEdgeCount();
// Only updates edges to avoid update in DOM order
// for text label which would reset the scrollbar
for (let i = 0; i < edgeCount; i++) {
let edge = state.cell.getEdgeAt(i);
graph.view.invalidate(edge, true, false);
graph.view.validate(edge);
}
};
InternalEvent.addListener(div, 'scroll', updateEdges);
InternalEvent.addListener(div, 'mouseup', updateEdges);
}
}
}
}
}
class MyCustomGraphView extends GraphView {
// Implements a special perimeter for table rows inside the table markup
updateFloatingTerminalPoint(edge, start, end, source) {
let next = this.getNextPoint(edge, end, source);
let div = start.text.node.getElementsByTagName('div')[2];
let x = start.x;
let y = start.getCenterY();
// Checks on which side of the terminal to leave
if (next.x > x + start.width / 2) {
x += start.width;
}
if (div != null) {
y = start.getCenterY() - div.scrollTop;
if (domUtils.isNode(edge.cell.value) && !start.cell.isCollapsed()) {
let attr = source ? 'sourceRow' : 'targetRow';
let row = parseInt(edge.cell.value.getAttribute(attr));
// HTML labels contain an outer table which is built-in
let table = div.getElementsByTagName('table')[0];
let trs = table.getElementsByTagName('tr');
let tr = trs[Math.min(trs.length - 1, row - 1)];
// Gets vertical center of source or target row
if (tr != null) {
y = getRowY(start, tr);
}
}
// Keeps vertical coordinate inside start
let offsetTop = parseInt(div.style.top) * start.view.scale;
y = Math.min(
isNaN(start.y + start.height) ? 9999999999999 : start.y + start.height,
isNaN(Math.max(start.y + offsetTop, y))
? 9999999999999
: Math.max(start.y + offsetTop, y)
);
// Updates the vertical position of the nearest point if we're not
// dealing with a connection preview, in which case either the
// edgeState or the absolutePoints are null
if (edge != null && edge.absolutePoints != null) {
next.y = y;
}
}
edge.setAbsoluteTerminalPoint(new Point(x, y), source);
// Routes multiple incoming edges along common waypoints if
// the edges have a common target row
if (source && domUtils.isNode(edge.cell.value) && start != null && end != null) {
let edges = this.graph.getEdgesBetween(start.cell, end.cell, true);
let tmp = [];
// Filters the edges with the same source row
let row = edge.cell.value.getAttribute('targetRow');
for (let i = 0; i < edges.length; i++) {
if (
domUtils.isNode(edges[i].value) &&
edges[i].value.getAttribute('targetRow') == row
) {
tmp.push(edges[i]);
}
}
edges = tmp;
if (edges.length > 1 && edge.cell === edges[edges.length - 1]) {
// Finds the vertical center
let states = [];
let y = 0;
for (let i = 0; i < edges.length; i++) {
states[i] = this.getState(edges[i]);
y += states[i].absolutePoints[0].y;
}
y /= edges.length;
for (let i = 0; i < states.length; i++) {
let x = states[i].absolutePoints[1].x;
if (states[i].absolutePoints.length < 5) {
states[i].absolutePoints.splice(2, 0, new Point(x, y));
} else {
states[i].absolutePoints[2] = new Point(x, y);
}
// Must redraw the previous edges with the changed point
if (i < states.length - 1) {
this.graph.cellRenderer.redraw(states[i]);
}
}
}
}
}
}
class MyCustomGraph extends Graph {
// Override folding to allow for tables
isCellFoldable(cell, collapse) {
return cell.isVertex();
}
// Overrides connectable state
isCellConnectable(cell) {
return !cell.isCollapsed();
}
// Overrides getLabel to return empty labels for edges and
// short markup for collapsed cells.
getLabel(cell) {
if (cell.isVertex()) {
if (cell.isCollapsed()) {
return (
'<table style="overflow:hidden;" width="100%" height="100%" border="1" cellpadding="4" class="title" style="height:100%;">' +
'<tr><th>Customers</th></tr>' +
'</table>'
);
} else {
return (
'<table style="overflow:hidden;" width="100%" border="1" cellpadding="4" class="title">' +
'<tr><th colspan="2">Customers</th></tr>' +
'</table>' +
'<div style="overflow:auto;cursor:default;top:26px;bottom:0px;position:absolute;width:100%;">' +
'<table width="100%" height="100%" border="1" cellpadding="4" class="erd">' +
'<tr><td>' +
'<img align="center" src="images/key.png"/>' +
'<img align="center" src="images/plus.png"/>' +
'</td><td>' +
'<u>customerId</u></td></tr><tr><td></td><td>number</td></tr>' +
'<tr><td></td><td>firstName</td></tr><tr><td></td><td>lastName</td></tr>' +
'<tr><td></td><td>streetAddress</td></tr><tr><td></td><td>city</td></tr>' +
'<tr><td></td><td>state</td></tr><tr><td></td><td>zip</td></tr>' +
'</table></div>'
);
}
} else {
return '';
}
}
createCellRenderer() {
return new MyCustomCellRenderer();
}
createGraphView() {
return new MyCustomGraphView(this);
}
}
// Creates the graph inside the given container
let graph = new MyCustomGraph(
container,
null,
// use a dedicated set of plugins
[
CellEditorHandler,
ConnectionHandler,
MyCustomConnectionHandler,
MyCustomSelectionHandler,
PanningHandler,
RubberBandHandler,
SelectionCellsHandler,
]
);
// Uses the entity perimeter (below) as default
graph.stylesheet.getDefaultVertexStyle().verticalAlign = constants.ALIGN.TOP;
graph.stylesheet.getDefaultVertexStyle().perimeter = Perimeter.RectanglePerimeter;
graph.stylesheet.getDefaultVertexStyle().shadow = true;
graph.stylesheet.getDefaultVertexStyle().fillColor = '#DDEAFF';
graph.stylesheet.getDefaultVertexStyle().gradientColor = '#A9C4EB';
delete graph.stylesheet.getDefaultVertexStyle().strokeColor;
// Used for HTML labels that use up the complete vertex space (see
// graph.cellRenderer.redrawLabel below for syncing the size)
graph.stylesheet.getDefaultVertexStyle().overflow = 'fill';
// Uses the entity edge style as default
graph.stylesheet.getDefaultEdgeStyle().edge = EdgeStyle.EntityRelation;
graph.stylesheet.getDefaultEdgeStyle().strokeColor = 'black';
graph.stylesheet.getDefaultEdgeStyle().fontColor = 'black';
// Allows new connections to be made but do not allow existing
// connections to be changed for the sake of simplicity of this
// example
graph.setCellsDisconnectable(false);
graph.setAllowDanglingEdges(false);
graph.setCellsEditable(false);
graph.setConnectable(true);
graph.setPanning(true);
graph.centerZoom = false;
// Enables HTML markup in all labels
graph.setHtmlLabels(true);
// User objects (data) for the individual cells
let doc = xmlUtils.createXmlDocument();
// Same should be used to create the XML node for the table
// description and the rows (most probably as child nodes)
let relation = doc.createElement('Relation');
relation.setAttribute('sourceRow', '4');
relation.setAttribute('targetRow', '6');
// Enables key handling (eg. escape)
new KeyHandler(graph);
// Gets the default parent for inserting new cells. This
// is normally the first child of the root (ie. layer 0).
let parent = graph.getDefaultParent();
// Adds cells to the model in a single step
let width = 160;
let height = 230;
graph.batchUpdate(() => {
let v1 = graph.insertVertex(parent, null, '', 20, 20, width, height);
v1.geometry.alternateBounds = new Rectangle(0, 0, width, 26);
let v2 = graph.insertVertex(parent, null, '', 400, 150, width, height);
v2.geometry.alternateBounds = new Rectangle(0, 0, width, 26);
graph.insertEdge(parent, null, relation, v1, v2);
});
let btn1 = DomHelpers.button('+', function () {
graph.zoomIn();
});
btn1.style.marginLeft = '20px';
document.body.appendChild(btn1);
document.body.appendChild(
DomHelpers.button('-', function () {
graph.zoomOut();
})
);
return container;
};
export const Default = Template.bind({});

View File

@ -0,0 +1,197 @@
/*
Copyright 2021-present The maxGraph project Contributors
Copyright (c) 2006-2013, JGraph Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
Show region
This example demonstrates using a custom rubberband handler to show the selected region in a new window.
*/
import {
eventUtils,
Graph,
InternalEvent,
MaxLog as domUtils,
MaxPopupMenu,
Rectangle,
RubberBandHandler,
styleUtils,
} from '@maxgraph/core';
import {
globalTypes,
globalValues,
rubberBandTypes,
rubberBandValues,
} from './shared/args.js';
const CSS_TEMPLATE = `
body div.mxPopupMenu {
-webkit-box-shadow: 3px 3px 6px #C0C0C0;
-moz-box-shadow: 3px 3px 6px #C0C0C0;
box-shadow: 3px 3px 6px #C0C0C0;
background: white;
position: absolute;
border: 3px solid #e7e7e7;
padding: 3px;
}
body table.mxPopupMenu {
border-collapse: collapse;
margin: 0px;
}
body tr.mxPopupMenuItem {
color: black;
cursor: default;
}
body td.mxPopupMenuItem {
padding: 6px 60px 6px 30px;
font-family: Arial;
font-size: 10pt;
}
body td.mxPopupMenuIcon {
background-color: white;
padding: 0px;
}
body tr.mxPopupMenuItemHover {
background-color: #eeeeee;
color: black;
}
table.mxPopupMenu hr {
border-top: solid 1px #cccccc;
}
table.mxPopupMenu tr {
font-size: 4pt;
}
`;
// TODO apply this settings to the container used by the Graph
const HTML_TEMPLATE = `
<!-- Page passes the container for the graph to the program -->
<body onload="main(document.getElementById('graphContainer'))">
<!-- Creates a container for the graph with a grid wallpaper -->
<div id="graphContainer"
style="overflow:hidden;width:321px;height:241px;background:url('editors/images/grid.gif');cursor:default;">
</div>
Use the right mouse button to select a region of the diagram and select <i>Show this</i>.
</body>
`;
export default {
title: 'Misc/ShowRegion',
argTypes: {
...globalTypes,
...rubberBandTypes,
},
args: {
...globalValues,
...rubberBandValues,
},
};
const Template = ({ label, ...args }) => {
const styleElm = document.createElement('style');
styleElm.innerText = CSS_TEMPLATE;
document.head.appendChild(styleElm);
const container = document.createElement('div');
container.style.position = 'relative';
container.style.overflow = 'hidden';
container.style.width = `${args.width}px`;
container.style.height = `${args.height}px`;
container.style.background = 'url(/images/grid.gif)';
container.style.cursor = 'default';
// Disables built-in context menu
InternalEvent.disableContextMenu(container);
// Changes some default colors
// TODO Find a way of modifying globally or setting locally! See https://github.com/maxGraph/maxGraph/issues/192
//constants.HANDLE_FILLCOLOR = '#99ccff';
//constants.HANDLE_STROKECOLOR = '#0088cf';
//constants.VERTEX_SELECTION_COLOR = '#00a8ff';
// Creates the graph inside the given container
let graph = new Graph(container);
class MyCustomRubberBandHandler extends RubberBandHandler {
isForceRubberbandEvent(me) {
return super.isForceRubberbandEvent(me) || me.isPopupTrigger();
}
// Defines a new popup menu for region selection in the rubberband handler
popupMenu = new MaxPopupMenu(function (menu, cell, evt) {
let rect = new Rectangle(
rubberband.x,
rubberband.y,
rubberband.width,
rubberband.height
);
menu.addItem('Show this', null, function () {
rubberband.popupMenu.hideMenu();
let bounds = graph.getGraphBounds();
domUtils.show(
graph,
null,
bounds.x - rubberband.x,
bounds.y - rubberband.y,
rubberband.width,
rubberband.height
);
});
});
mouseDown(sender, me) {
this.popupMenu.hideMenu();
super.mouseDown(sender, me);
}
mouseUp(sender, me) {
if (eventUtils.isPopupTrigger(me.getEvent())) {
if (!graph.getPlugin('PopupMenuHandler').isMenuShowing()) {
let origin = styleUtils.getScrollOrigin();
this.popupMenu.popup(
me.getX() + origin.x + 1,
me.getY() + origin.y + 1,
null,
me.getEvent()
);
this.reset();
}
} else {
super.mouseUp(sender, me);
}
}
}
// Enables rubberband selection
let rubberband = new MyCustomRubberBandHandler(graph);
// Gets the default parent for inserting new cells. This
// is normally the first child of the root (ie. layer 0).
let parent = graph.getDefaultParent();
// Adds cells to the model in a single step
graph.batchUpdate(() => {
let v1 = graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30);
let v2 = graph.insertVertex(parent, null, 'World!', 200, 150, 80, 30);
let e1 = graph.insertEdge(parent, null, '', v1, v2);
});
return container;
};
export const Default = Template.bind({});

View File

@ -0,0 +1,999 @@
/*
Copyright 2021-present The maxGraph project Contributors
Copyright (c) 2006-2020, JGraph Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
====================
README
====================
- Edge-to-edge connections: We store the point where the mouse
was released in the terminal points of the edge geometry and
use that point to find the nearest segment on the target edge
and the connection point between the two edges in
mxGraphView.updateFixedTerminalPoint.
- The orthogonal router, which is implemented as an edge style,
computes its result based on the output of mxGraphView.
updateFixedTerminalPoint, which computes all connection points
for edge-to-edge connections and constrained ports and vertices
and stores them in state.absolutePoints.
- Routing directions are stored in the 'portConstraint' style.
Possible values for this style horizontal and vertical. Note
that this may have other values depending on the edge style.
- For edge-to-edge connections, a 'source-/targetConstraint'
style is added in updateFixedTerminalPoint that contains the
orientation of the segment that the edge connects to. Possible
values are horizontal, vertical.
- An alternative solution for connection points via connection
constraints is demonstrated. In this setup, the edge is connected
to the parent cell directly. There are no child cells that act
as "ports". Instead, the connection information is stored as a
relative point in the connecting edge. (See also: portrefs.html
for storing references to ports.)
*/
import {
domUtils,
styleUtils,
mathUtils,
cloneUtils,
eventUtils,
Graph,
InternalEvent,
RubberBandHandler,
ConnectionHandler,
ConnectionConstraint,
Point,
CylinderShape,
CellRenderer,
DomHelpers,
EdgeStyle,
Rectangle,
EdgeHandler,
StyleRegistry,
EdgeSegmentHandler,
UndoManager,
CellEditorHandler,
ConstraintHandler,
Guide,
ImageBox,
GraphView,
SelectionHandler,
PanningHandler,
TooltipHandler,
SelectionCellsHandler,
PopupMenuHandler,
} from '@maxgraph/core';
import {
contextMenuTypes,
contextMenuValues,
globalTypes,
globalValues,
rubberBandTypes,
rubberBandValues,
} from './shared/args.js';
// style required by RubberBand
import '@maxgraph/core/css/common.css';
export default {
title: 'Connections/Wires',
argTypes: {
...contextMenuTypes,
...globalTypes,
...rubberBandTypes,
},
args: {
...contextMenuValues,
...globalValues,
...rubberBandValues,
},
};
// TODO apply this settings to the container used by the Graph
const HTML_TEMPLATE = `
<body onload="main(document.getElementById('graphContainer'))">
<div id="graphContainer"
style="overflow:auto;position:relative;width:800px;height:600px;border:1px solid gray;background:url('images/wires-grid.gif');background-position:-1px 0px;cursor:crosshair;">
</div>
</body>
</html>
`;
const Template = ({ label, ...args }) => {
const parentContainer = document.createElement('div');
const container = document.createElement('div');
container.style.position = 'relative';
container.style.overflow = 'hidden';
container.style.width = `${args.width}px`;
container.style.height = `${args.height}px`;
container.style.background = 'url(/images/grid.gif)';
container.style.cursor = 'default';
parentContainer.appendChild(container);
// Changes some default colors
// TODO Find a way of modifying globally or setting locally! See https://github.com/maxGraph/maxGraph/issues/192
//constants.SHADOWCOLOR = '#C0C0C0';
let joinNodeSize = 7;
let strokeWidth = 2;
class MyCustomGraph extends Graph {
resetEdgesOnConnect = false;
createEdgeSegmentHandler(state) {
return new MyCustomEdgeSegmentHandler(state);
}
createGraphView() {
return new MyCustomGraphView(this);
}
createEdgeHandler(state) {
return new MyCustomEdgeHandler(state);
}
createHandler(state) {
let result = null;
if (state != null) {
if (state.cell.isEdge()) {
let style = this.view.getEdgeStyle(state);
if (style == EdgeStyle.WireConnector) {
return new EdgeSegmentHandler(state);
}
}
}
return super.createHandler.apply(this, arguments);
}
// Adds oval markers for edge-to-edge connections.
getCellStyle(cell) {
let style = super.getCellStyle.apply(this, arguments);
if (style != null && cell?.isEdge()) {
style = cloneUtils.clone(style);
if (cell.getTerminal(true)?.isEdge()) {
style.startArrow = 'oval';
}
if (cell.getTerminal(false)?.isEdge()) {
style.endArrow = 'oval';
}
}
return style;
}
getTooltipForCell(cell) {
let tip = '';
if (cell != null) {
let src = cell.getTerminal(true);
if (src != null) {
tip += this.getTooltipForCell(src) + ' ';
}
let parent = cell.getParent();
if (parent.isVertex()) {
tip += this.getTooltipForCell(parent) + '.';
}
tip += super.getTooltipForCell.apply(this, arguments);
let trg = cell.getTerminal(false);
if (trg != null) {
tip += ' ' + this.getTooltipForCell(trg);
}
}
return tip;
}
// Alternative solution for implementing connection points without child cells.
// This can be extended as shown in portrefs.html example to allow for per-port
// incoming/outgoing direction.
getAllConnectionConstraints(terminal) {
let geo = terminal != null ? terminal.cell.getGeometry() : null;
if (
(geo != null ? !geo.relative : false) &&
terminal.cell.isVertex() &&
terminal.cell.getChildCount() === 0
) {
return [
new ConnectionConstraint(new Point(0, 0.5), false),
new ConnectionConstraint(new Point(1, 0.5), false),
];
}
return null;
}
}
// FIXME: Provide means to make EdgeHandler and ConnectionHandler instantiate this subclass!
class MyCustomConstraintHandler extends ConstraintHandler {
// Replaces the port image
pointImage = new ImageBox('images/dot.gif', 10, 10);
}
class MyCustomGuide extends Guide {
// Alt disables guides
isEnabledForEvent(evt) {
return !eventUtils.isAltDown(evt);
}
}
class MyCustomEdgeHandler extends EdgeHandler {
// Enables snapping waypoints to terminals
snapToTerminals = true;
isConnectableCell(cell) {
return graph.getPlugin('ConnectionHandler').isConnectableCell(cell);
}
connect(edge, terminal, isSource, isClone, me) {
let result = null;
let model = this.graph.getDataModel();
let parent = model.getParent(edge);
model.beginUpdate();
try {
result = super.connect.apply(this, arguments);
let geo = model.getGeometry(result);
if (geo != null) {
geo = geo.clone();
let pt = null;
if (terminal.isEdge()) {
pt = this.abspoints[this.isSource ? 0 : this.abspoints.length - 1];
pt.x = pt.x / this.graph.view.scale - this.graph.view.translate.x;
pt.y = pt.y / this.graph.view.scale - this.graph.view.translate.y;
let pstate = this.graph.getView().getState(edge.getParent());
if (pstate != null) {
pt.x -= pstate.origin.x;
pt.y -= pstate.origin.y;
}
pt.x -= this.graph.panDx / this.graph.view.scale;
pt.y -= this.graph.panDy / this.graph.view.scale;
}
geo.setTerminalPoint(pt, isSource);
model.setGeometry(edge, geo);
}
} finally {
model.endUpdate();
}
return result;
}
createMarker() {
let marker = super.createMarker.apply(this, arguments);
// Adds in-place highlighting when reconnecting existing edges
marker.highlight.highlight =
this.graph.getPlugin('ConnectionHandler').marker.highlight.highlight;
return marker;
}
}
// Switch for black background and bright styles
let invert = false;
let MyCustomCellEditorHandler;
if (invert) {
container.style.backgroundColor = 'black';
// White in-place editor text color
MyCustomCellEditorHandler = class extends CellEditorHandler {
startEditing(cell, trigger) {
super.startEditing.apply(this, arguments);
if (this.textarea != null) {
this.textarea.style.color = '#FFFFFF';
}
}
};
} else {
MyCustomCellEditorHandler = CellEditorHandler;
}
class MyCustomSelectionHandler extends SelectionHandler {
previewColor = invert ? 'white' : 'black';
// Enables guides
guidesEnabled = true;
createGuide() {
return new MyCustomGuide(this.graph, this.getGuideStates());
}
}
class MyCustomPanningHandler extends PanningHandler {
// Panning handler consumed right click so this must be
// disabled if right click should stop connection handler.
isPopupTrigger() {
return false;
}
}
class MyCustomConnectionHandler extends ConnectionHandler {
// If connect preview is not moved away then getCellAt is used to detect the cell under
// the mouse if the mouse is over the preview shape in IE (no event transparency), ie.
// the built-in hit-detection of the HTML document will not be used in this case.
movePreviewAway = false;
waypointsEnabled = true;
// Starts connections on the background in wire-mode
isStartEvent(me) {
return checkbox.checked || super.isStartEvent.apply(this, arguments);
}
// Avoids any connections for gestures within tolerance except when in wire-mode
// or when over a port
mouseUp(sender, me) {
if (this.first != null && this.previous != null) {
let point = styleUtils.convertPoint(this.graph.container, me.getX(), me.getY());
let dx = Math.abs(point.x - this.first.x);
let dy = Math.abs(point.y - this.first.y);
if (dx < this.graph.tolerance && dy < this.graph.tolerance) {
// Selects edges in non-wire mode for single clicks, but starts
// connecting for non-edges regardless of wire-mode
if (!checkbox.checked && this.previous.cell.isEdge()) {
this.reset();
}
return;
}
}
super.mouseUp.apply(this, arguments);
}
// Overrides methods to preview and create new edges.
// Sets source terminal point for edge-to-edge connections.
createEdgeState(me) {
let edge = this.graph.createEdge();
if (this.sourceConstraint != null && this.previous != null) {
edge.style =
'exitX' +
'=' +
this.sourceConstraint.point.x +
';' +
'exitY' +
'=' +
this.sourceConstraint.point.y +
';';
} else if (me.getCell().isEdge()) {
let scale = this.graph.view.scale;
let tr = this.graph.view.translate;
let pt = new Point(
this.graph.snap(me.getGraphX() / scale) - tr.x,
this.graph.snap(me.getGraphY() / scale) - tr.y
);
edge.geometry.setTerminalPoint(pt, true);
}
return this.graph.view.createState(edge);
}
// Uses right mouse button to create edges on background (see also: lines 67 ff)
isStopEvent(me) {
return me.getState() != null || eventUtils.isRightMouseButton(me.getEvent());
}
// Updates target terminal point for edge-to-edge connections.
updateCurrentState(me, point) {
super.updateCurrentState.apply(this, arguments);
if (this.edgeState != null) {
this.edgeState.cell.geometry.setTerminalPoint(null, false);
if (
this.shape != null &&
this.currentState != null &&
this.currentState.cell.isEdge()
) {
let scale = this.graph.view.scale;
let tr = this.graph.view.translate;
let pt = new Point(
this.graph.snap(me.getGraphX() / scale) - tr.x,
this.graph.snap(me.getGraphY() / scale) - tr.y
);
this.edgeState.cell.geometry.setTerminalPoint(pt, false);
}
}
}
// Adds in-place highlighting for complete cell area (no hotspot).
createMarker() {
let marker = super.createMarker.apply(this, arguments);
// Uses complete area of cell for new connections (no hotspot)
marker.intersects = function (state, evt) {
return true;
};
// Adds in-place highlighting
//const mxCellHighlightHighlight = mxCellHighlight.prototype.highlight;
marker.highlight.highlight = function (state) {
// TODO: Should this be a subclass of marker rather than assigning directly?
if (this.state != state) {
if (this.state != null) {
this.state.style = this.lastStyle;
// Workaround for shape using current stroke width if no strokewidth defined
this.state.style.strokeWidth = this.state.style.strokeWidth || '1';
this.state.style.strokeColor = this.state.style.strokeColor || 'none';
if (this.state.shape != null) {
this.state.view.graph.cellRenderer.configureShape(this.state);
this.state.shape.redraw();
}
}
if (state != null) {
this.lastStyle = state.style;
state.style = cloneUtils.clone(state.style);
state.style.strokeColor = '#00ff00';
state.style.strokeWidth = '3';
if (state.shape != null) {
state.view.graph.cellRenderer.configureShape(state);
state.shape.redraw();
}
}
this.state = state;
}
};
return marker;
}
// Makes sure non-relative cells can only be connected via constraints
isConnectableCell(cell) {
if (cell.isEdge()) {
return true;
} else {
let geo = cell != null ? cell.getGeometry() : null;
return geo != null ? geo.relative : false;
}
}
}
// Updates connection points before the routing is called.
class MyCustomGraphView extends GraphView {
// Computes the position of edge to edge connection points.
updateFixedTerminalPoint(edge, terminal, source, constraint) {
let pt = null;
if (constraint != null) {
pt = this.graph.getConnectionPoint(terminal, constraint);
}
if (source) {
edge.sourceSegment = null;
} else {
edge.targetSegment = null;
}
if (pt == null) {
let s = this.scale;
let tr = this.translate;
let orig = edge.origin;
let geo = edge.cell.getGeometry();
pt = geo.getTerminalPoint(source);
// Computes edge-to-edge connection point
if (pt != null) {
pt = new Point(s * (tr.x + pt.x + orig.x), s * (tr.y + pt.y + orig.y));
// Finds nearest segment on edge and computes intersection
if (terminal != null && terminal.absolutePoints != null) {
let seg = mathUtils.findNearestSegment(terminal, pt.x, pt.y);
// Finds orientation of the segment
let p0 = terminal.absolutePoints[seg];
let pe = terminal.absolutePoints[seg + 1];
let horizontal = p0.x - pe.x === 0;
// Stores the segment in the edge state
let key = source ? 'sourceConstraint' : 'targetConstraint';
let value = horizontal ? 'horizontal' : 'vertical';
edge.style[key] = value;
// Keeps the coordinate within the segment bounds
if (horizontal) {
pt.x = p0.x;
pt.y = Math.min(pt.y, Math.max(p0.y, pe.y));
pt.y = Math.max(pt.y, Math.min(p0.y, pe.y));
} else {
pt.y = p0.y;
pt.x = Math.min(pt.x, Math.max(p0.x, pe.x));
pt.x = Math.max(pt.x, Math.min(p0.x, pe.x));
}
}
}
// Computes constraint connection points on vertices and ports
else if (terminal != null && terminal.cell.geometry.relative) {
pt = new Point(
this.getRoutingCenterX(terminal),
this.getRoutingCenterY(terminal)
);
}
// Snaps point to grid
/*if (pt != null)
{
let tr = this.graph.view.translate;
let s = this.graph.view.scale;
pt.x = (this.graph.snap(pt.x / s - tr.x) + tr.x) * s;
pt.y = (this.graph.snap(pt.y / s - tr.y) + tr.y) * s;
}*/
}
edge.setAbsoluteTerminalPoint(pt, source);
}
}
// Updates the terminal and control points in the cloned preview.
class MyCustomEdgeSegmentHandler extends EdgeSegmentHandler {
clonePreviewState(point, terminal) {
let clone = super.clonePreviewState.apply(this, arguments);
clone.cell = clone.cell.clone();
if (this.isSource || this.isTarget) {
clone.cell.geometry = clone.cell.geometry.clone();
// Sets the terminal point of an edge if we're moving one of the endpoints
if (clone.cell.isEdge()) {
// TODO: Only set this if the target or source terminal is an edge
clone.cell.geometry.setTerminalPoint(point, this.isSource);
} else {
clone.cell.geometry.setTerminalPoint(null, this.isSource);
}
}
return clone;
}
}
// Imlements a custom resistor shape. Direction currently ignored here.
class ResistorShape extends CylinderShape {
constructor() {
// TODO: The original didn't seem to call the super
super(null, null, null, null);
}
redrawPath(path, x, y, w, h, isForeground) {
let dx = w / 16;
if (isForeground) {
path.moveTo(0, h / 2);
path.lineTo(2 * dx, h / 2);
path.lineTo(3 * dx, 0);
path.lineTo(5 * dx, h);
path.lineTo(7 * dx, 0);
path.lineTo(9 * dx, h);
path.lineTo(11 * dx, 0);
path.lineTo(13 * dx, h);
path.lineTo(14 * dx, h / 2);
path.lineTo(16 * dx, h / 2);
path.end();
}
}
}
CellRenderer.registerShape('resistor', ResistorShape);
// Imlements a custom resistor shape. Direction currently ignored here.
EdgeStyle.WireConnector = function (state, source, target, hints, result) {
// Creates array of all way- and terminalpoints
let pts = state.absolutePoints;
let horizontal = true;
let hint = null;
// Gets the initial connection from the source terminal or edge
if (source != null && source.cell.isEdge()) {
horizontal = state.style.sourceConstraint == 'horizontal';
} else if (source != null) {
horizontal = source.style.portConstraint != 'vertical';
// Checks the direction of the shape and rotates
let direction = source.style.direction;
if (direction == 'north' || direction == 'south') {
horizontal = !horizontal;
}
}
// Adds the first point
// TODO: Should move along connected segment
let pt = pts[0];
if (pt == null && source != null) {
pt = new Point(
state.view.getRoutingCenterX(source),
state.view.getRoutingCenterY(source)
);
} else if (pt != null) {
pt = pt.clone();
}
let first = pt;
// Adds the waypoints
if (hints != null && hints.length > 0) {
// FIXME: First segment not movable
/*hint = state.view.transformControlPoint(state, hints[0]);
MaxLog.show();
MaxLog.debug(hints.length,'hints0.y='+hint.y, pt.y)
if (horizontal && Math.floor(hint.y) != Math.floor(pt.y))
{
MaxLog.show();
MaxLog.debug('add waypoint');
pt = new Point(pt.x, hint.y);
result.push(pt);
pt = pt.clone();
//horizontal = !horizontal;
}*/
for (let i = 0; i < hints.length; i++) {
horizontal = !horizontal;
hint = state.view.transformControlPoint(state, hints[i]);
if (horizontal) {
if (pt.y !== hint.y) {
pt.y = hint.y;
result.push(pt.clone());
}
} else if (pt.x !== hint.x) {
pt.x = hint.x;
result.push(pt.clone());
}
}
} else {
hint = pt;
}
// Adds the last point
pt = pts[pts.length - 1];
// TODO: Should move along connected segment
if (pt == null && target != null) {
pt = new Point(
state.view.getRoutingCenterX(target),
state.view.getRoutingCenterY(target)
);
}
if (horizontal) {
if (pt.y !== hint.y && first.x !== pt.x) {
result.push(new Point(pt.x, hint.y));
}
} else if (pt.x !== hint.x && first.y !== pt.y) {
result.push(new Point(hint.x, pt.y));
}
};
StyleRegistry.putValue('wireEdgeStyle', EdgeStyle.WireConnector);
let graph = new MyCustomGraph(container, null, [
MyCustomCellEditorHandler,
TooltipHandler,
SelectionCellsHandler,
PopupMenuHandler,
MyCustomConnectionHandler,
MyCustomSelectionHandler,
MyCustomPanningHandler,
]);
let labelBackground = invert ? '#000000' : '#FFFFFF';
let fontColor = invert ? '#FFFFFF' : '#000000';
let strokeColor = invert ? '#C0C0C0' : '#000000';
let fillColor = invert ? 'none' : '#FFFFFF';
graph.view.scale = 1;
graph.setPanning(true);
graph.setConnectable(true);
graph.setConnectableEdges(true);
graph.setDisconnectOnMove(false);
graph.foldingEnabled = false;
//Maximum size
graph.maximumGraphBounds = new Rectangle(0, 0, 800, 600);
graph.border = 50;
// Enables return key to stop editing (use shift-enter for newlines)
graph.setEnterStopsCellEditing(true);
// Adds rubberband selection
new RubberBandHandler(graph);
// Adds a special tooltip for edges
graph.setTooltips(true);
let style = graph.getStylesheet().getDefaultEdgeStyle();
delete style.endArrow;
style.strokeColor = strokeColor;
style.labelBackgroundColor = labelBackground;
style.edgeStyle = 'wireEdgeStyle';
style.fontColor = fontColor;
style.fontSize = '9';
style.movable = '0';
style.strokeWidth = strokeWidth;
//style.rounded = '1';
// Sets join node size
style.startSize = joinNodeSize;
style.endSize = joinNodeSize;
style = graph.getStylesheet().getDefaultVertexStyle();
style.gradientDirection = 'south';
//style.gradientColor = '#909090';
style.strokeColor = strokeColor;
//style.fillColor = '#e0e0e0';
style.fillColor = 'none';
style.fontColor = fontColor;
style.fontStyle = '1';
style.fontSize = '12';
style.resizable = '0';
style.rounded = '1';
style.strokeWidth = strokeWidth;
let parent = graph.getDefaultParent();
graph.batchUpdate(() => {
let v1 = graph.insertVertex(parent, null, 'J1', 80, 40, 40, 80, {
verticalLabelPosition: 'top',
verticalAlign: 'bottom',
shadow: true,
fillColor,
});
v1.setConnectable(false);
let v11 = graph.insertVertex(v1, null, '1', 0, 0, 10, 16, {
shape: 'line',
align: 'left',
verticalAlign: 'middle',
fontSize: 10,
routingCenterX: -0.5,
spacingLeft: 12,
fontColor,
strokeColor,
});
v11.geometry.relative = true;
v11.geometry.offset = new Point(-v11.geometry.width, 2);
let v12 = v11.clone();
v12.value = '2';
v12.geometry.offset = new Point(-v11.geometry.width, 22);
v1.insert(v12);
let v13 = v11.clone();
v13.value = '3';
v13.geometry.offset = new Point(-v11.geometry.width, 42);
v1.insert(v13);
let v14 = v11.clone();
v14.value = '4';
v14.geometry.offset = new Point(-v11.geometry.width, 62);
v1.insert(v14);
let v15 = v11.clone();
v15.value = '5';
v15.geometry.x = 1;
v15.style = {
shape: 'line',
align: 'right',
verticalAlign: 'middle',
fontSize: 10,
routingCenterX: 0.5,
spacingRight: 12,
fontColor,
strokeColor,
};
v15.geometry.offset = new Point(0, 2);
v1.insert(v15);
let v16 = v15.clone();
v16.value = '6';
v16.geometry.offset = new Point(0, 22);
v1.insert(v16);
let v17 = v15.clone();
v17.value = '7';
v17.geometry.offset = new Point(0, 42);
v1.insert(v17);
let v18 = v15.clone();
v18.value = '8';
v18.geometry.offset = new Point(0, 62);
v1.insert(v18);
let v19 = v15.clone();
v19.value = 'clk';
v19.geometry.x = 0.5;
v19.geometry.y = 1;
v19.geometry.width = 10;
v19.geometry.height = 4;
// NOTE: portConstraint is defined for east direction, so must be inverted here
v19.style = {
shape: 'triangle',
direction: 'north',
spacingBottom: 12,
align: 'center',
portConstraint: 'horizontal',
fontSize: 8,
strokeColor,
routingCenterY: 0.5,
};
v19.geometry.offset = new Point(-4, -4);
v1.insert(v19);
let v2 = graph.insertVertex(parent, null, 'R1', 220, 220, 80, 20, {
shape: 'resistor',
verticalLabelPosition: 'top',
verticalAlign: 'bottom',
});
// Uses implementation of connection points via constraints (see above)
//v2.setConnectable(false);
/*let v21 = graph.insertVertex(v2, null, 'A', 0, 0.5, 10, 1,
'shape=none;spacingBottom=11;spacingLeft=1;align=left;fontSize=8;'+
'fontColor=#4c4c4c;strokeColor=#909090;');
v21.geometry.relative = true;
v21.geometry.offset = new Point(0, -1);
let v22 = graph.insertVertex(v2, null, 'B', 1, 0.5, 10, 1,
'spacingBottom=11;spacingLeft=1;align=left;fontSize=8;'+
'fontColor=#4c4c4c;strokeColor=#909090;');
v22.geometry.relative = true;
v22.geometry.offset = new Point(-10, -1);*/
let v3 = graph.addCell(graph.getDataModel().cloneCell(v1));
v3.value = 'J3';
v3.geometry.x = 420;
v3.geometry.y = 340;
// Connection constraints implemented in edges, alternatively this
// can be implemented using references, see: portrefs.html
let e1 = graph.insertEdge(parent, null, 'e1', v1.getChildAt(7), v2, {
entryX: 0,
entryY: 0.5,
entryPerimeter: 0,
});
e1.geometry.points = [new Point(180, 110)];
let e2 = graph.insertEdge(parent, null, 'e2', v1.getChildAt(4), v2, {
entryX: 1,
entryY: 0.5,
entryPerimeter: 0,
});
e2.geometry.points = [new Point(320, 50), new Point(320, 230)];
let e3 = graph.insertEdge(parent, null, 'crossover', e1, e2);
e3.geometry.setTerminalPoint(new Point(180, 140), true);
e3.geometry.setTerminalPoint(new Point(320, 140), false);
// let e1 = graph.insertEdge(parent, null, 'e1', v1.getChildAt(7), v2.getChildAt(0));
// e1.geometry.points = [new Point(180, 140)];
// let e2 = graph.insertEdge(parent, null, '', v1.getChildAt(4), v2.getChildAt(1));
// e2.geometry.points = [new Point(320, 80)];
// let e3 = graph.insertEdge(parent, null, 'crossover', e1, e2);
// e3.geometry.setTerminalPoint(new Point(180, 160), true);
// e3.geometry.setTerminalPoint(new Point(320, 160), false);
let e4 = graph.insertEdge(parent, null, 'e4', v2, v3.getChildAt(0), {
exitX: 1,
exitY: 0.5,
entryPerimeter: 0,
});
e4.geometry.points = [new Point(380, 230)];
let e5 = graph.insertEdge(parent, null, 'e5', v3.getChildAt(5), v1.getChildAt(0));
e5.geometry.points = [new Point(500, 310), new Point(500, 20), new Point(50, 20)];
let e6 = graph.insertEdge(parent, null, '');
e6.geometry.setTerminalPoint(new Point(100, 500), true);
e6.geometry.setTerminalPoint(new Point(600, 500), false);
let e7 = graph.insertEdge(parent, null, 'e7', v3.getChildAt(7), e6);
e7.geometry.setTerminalPoint(new Point(500, 500), false);
e7.geometry.points = [new Point(500, 350)];
});
parentContainer.appendChild(
DomHelpers.button('Zoom In', function () {
graph.zoomIn();
})
);
parentContainer.appendChild(
DomHelpers.button('Zoom Out', function () {
graph.zoomOut();
})
);
// Undo/redo
let undoManager = new UndoManager();
let listener = function (sender, evt) {
undoManager.undoableEditHappened(evt.getProperty('edit'));
};
graph.getDataModel().addListener(InternalEvent.UNDO, listener);
graph.getView().addListener(InternalEvent.UNDO, listener);
parentContainer.appendChild(
DomHelpers.button('Undo', function () {
undoManager.undo();
})
);
parentContainer.appendChild(
DomHelpers.button('Redo', function () {
undoManager.redo();
})
);
// Shows XML for debugging the actual model
parentContainer.appendChild(
DomHelpers.button('Delete', function () {
graph.removeCells();
})
);
// Wire-mode
let checkbox = document.createElement('input');
checkbox.setAttribute('type', 'checkbox');
parentContainer.appendChild(checkbox);
domUtils.write(parentContainer, 'Wire Mode');
// Grid
let checkbox2 = document.createElement('input');
checkbox2.setAttribute('type', 'checkbox');
checkbox2.setAttribute('checked', 'true');
parentContainer.appendChild(checkbox2);
domUtils.write(parentContainer, 'Grid');
InternalEvent.addListener(checkbox2, 'click', function (evt) {
if (checkbox2.checked) {
container.style.background = 'url(/images/grid.gif)';
} else {
container.style.background = '';
}
container.style.backgroundColor = invert ? 'black' : 'white';
});
InternalEvent.disableContextMenu(container);
return parentContainer;
};
export const Default = Template.bind({});

View File

@ -1,154 +0,0 @@
/**
* Copyright (c) 2006-2013, JGraph Ltd
*
* Graph Layout
*
* This example demonstrates using
* automatic graph layouts and listening to changes of the graph size
* to keep the container size in sync.
*/
import React from 'react';
import mxEvent from '../mxgraph/util/mxEvent';
import mxGraph from '../mxgraph/view/mxGraph';
import mxRubberband from '../mxgraph/handler/mxRubberband';
import mxUtils from "../mxgraph/util/mxUtils";
import mxCircleLayout from "../mxgraph/layout/mxCircleLayout";
const HTML_TEMPLATE = `
<!-- Page passes the container for the graph to the program -->
<body onload="main(document.getElementById('graphContainer'))">
<!-- Creates a container for the graph with a grid wallpaper. Make sure to define the position
and overflow attributes! See comments on the adding of the size-listener on line 54 ff! -->
<div id="graphContainer"
style="position:relative;overflow:auto;width:821px;height:641px;background:url('editors/images/grid.gif');">
</div>
<br>
<input type="checkbox" id="animate" checked="checked"/> Transitions
</body>
`
// Creates the graph inside the given container
let graph = new mxGraph(container);
// Disables basic selection and cell handling
graph.setEnabled(false);
// Changes the default vertex style in-place
let style = graph.getStylesheet().getDefaultVertexStyle();
style.shape = mxConstants.SHAPE_ELLIPSE;
style.perimiter = Perimeter.EllipsePerimeter;
style.gradientColor = 'white';
style.fontSize = '10';
// Updates the size of the container to match
// the size of the graph when it changes. If
// this is commented-out, and the DIV style's
// overflow is set to "auto", then scrollbars
// will appear for the diagram. If overflow is
// set to "visible", then the diagram will be
// visible even when outside the parent DIV.
// With the code below, the parent DIV will be
// resized to contain the complete graph.
//graph.setResizeContainer(true);
// Larger grid size yields cleaner layout result
graph.gridSize = 40;
// Gets the default parent for inserting new cells. This
// is normally the first child of the root (ie. layer 0).
let parent = graph.getDefaultParent();
// Creates a layout algorithm to be used
// with the graph
let layout = new MxFastOrganicLayout(graph);
// Moves stuff wider apart than usual
layout.forceConstant = 80;
// Reference to the transition checkbox
let animate = document.getElementById('animate');
// Adds a button to execute the layout
this.el2.insertBefore(mxUtils.button('Circle Layout',
function(evt) {
graph.getDataModel().beginUpdate();
try {
// Creates a layout algorithm to be used
// with the graph
let circleLayout = new mxCircleLayout(graph);
circleLayout.execute(parent);
} catch (e) {
throw e;
} finally {
if (animate.checked) {
let morph = new Morphing(graph);
morph.addListener(mxEvent.DONE, function() {
graph.getDataModel().endUpdate();
});
morph.startAnimation();
} else {
graph.getDataModel().endUpdate();
}
}
}
), document.body.firstChild);
// Adds a button to execute the layout
document.body.insertBefore(mxUtils.button('Organic Layout',
function(evt)
{
graph.getDataModel().beginUpdate();
try {
layout.execute(parent);
} catch (e) {
throw e;
} finally {
if (animate.checked) {
// Default values are 6, 1.5, 20
let morph = new Morphing(graph, 10, 1.7, 20);
morph.addListener(mxEvent.DONE, function() {
graph.getDataModel().endUpdate();
});
morph.startAnimation();
} else {
graph.getDataModel().endUpdate();
}
}
}
), document.body.firstChild);
// Adds cells to the model in a single step
graph.getDataModel().beginUpdate();
let w = 30;
let h = 30;
try {
var v1 = graph.insertVertex(parent, null, 'A', 0, 0, w, h);
var v2 = graph.insertVertex(parent, null, 'B', 0, 0, w, h);
var v3 = graph.insertVertex(parent, null, 'C', 0, 0, w, h);
var v4 = graph.insertVertex(parent, null, 'D', 0, 0, w, h);
var v5 = graph.insertVertex(parent, null, 'E', 0, 0, w, h);
var v6 = graph.insertVertex(parent, null, 'F', 0, 0, w, h);
var v7 = graph.insertVertex(parent, null, 'G', 0, 0, w, h);
var v8 = graph.insertVertex(parent, null, 'H', 0, 0, w, h);
var e1 = graph.insertEdge(parent, null, 'ab', v1, v2);
var e2 = graph.insertEdge(parent, null, 'ac', v1, v3);
var e3 = graph.insertEdge(parent, null, 'cd', v3, v4);
var e4 = graph.insertEdge(parent, null, 'be', v2, v5);
var e5 = graph.insertEdge(parent, null, 'cf', v3, v6);
var e6 = graph.insertEdge(parent, null, 'ag', v1, v7);
var e7 = graph.insertEdge(parent, null, 'gh', v7, v8);
var e8 = graph.insertEdge(parent, null, 'gc', v7, v3);
var e9 = graph.insertEdge(parent, null, 'gd', v7, v4);
var e10 = graph.insertEdge(parent, null, 'eh', v5, v8);
// Executes the layout
layout.execute(parent);
} finally {
// Updates the display
graph.getDataModel().endUpdate();
}

View File

@ -1,138 +0,0 @@
// @ts-check
/**
* Copyright (c) 2006-2013, JGraph Ltd
*
* HTML label
*
* This example demonstrates using
* HTML labels that are connected to the state of the user object.
*/
import React from 'react';
import mxEvent from '../mxgraph/util/mxEvent';
import mxGraph from '../mxgraph/view/mxGraph';
import mxRubberband from '../mxgraph/handler/mxRubberband';
const HTML_TEMPLATE = `
<!-- Page passes the container for the graph to the program -->
<body onload="main(document.getElementById('graphContainer'))">
<!-- Creates a container for the graph with a grid wallpaper -->
<div id="graphContainer"
style="position:relative;overflow:hidden;width:321px;height:241px;background:url('editors/images/grid.gif');cursor:default;">
</div>
</body>
`
// Disables the built-in context menu
mxEvent.disableContextMenu(container);
// Creates the graph inside the given container
let graph = new mxGraph(container);
// Enables HTML labels
graph.setHtmlLabels(true);
// Enables rubberband selection
new mxRubberband(graph);
// Creates a user object that stores the state
let doc = mxUtils.createXmlDocument();
let obj = doc.createElement('UserObject');
obj.setAttribute('label', 'Hello, World!');
obj.setAttribute('checked', 'false');
// Adds optional caching for the HTML label
let cached = true;
if (cached) {
// Ignores cached label in codec
CodecRegistry.getCodec(mxCell).exclude.push('div');
// Invalidates cached labels
graph.model.setValue = function(cell, value) {
cell.div = null;
mxGraphModel.prototype.setValue.apply(this, arguments);
};
}
// Overrides method to provide a cell label in the display
graph.convertValueToString = function(cell) {
if (cached && cell.div != null) {
// Uses cached label
return cell.div;
} else if (mxUtils.isNode(cell.value) && cell.value.nodeName.toLowerCase() == 'userobject') {
// Returns a DOM for the label
let div = document.createElement('div');
div.innerHTML = cell.getAttribute('label');
mxUtils.br(div);
let checkbox = document.createElement('input');
checkbox.setAttribute('type', 'checkbox');
if (cell.getAttribute('checked') == 'true') {
checkbox.setAttribute('checked', 'checked');
checkbox.defaultChecked = true;
}
// Writes back to cell if checkbox is clicked
mxEvent.addListener(checkbox, 'change', function(evt) {
let elt = cell.value.cloneNode(true);
elt.setAttribute('checked', (checkbox.checked) ? 'true' : 'false');
graph.model.setValue(cell, elt);
});
div.appendChild(checkbox);
if (cached) {
// Caches label
cell.div = div;
}
return div;
}
return '';
};
// Overrides method to store a cell label in the model
let cellLabelChanged = graph.cellLabelChanged;
graph.cellLabelChanged = function(cell, newValue, autoSize) {
if (mxUtils.isNode(cell.value) && cell.value.nodeName.toLowerCase() == 'userobject') {
// Clones the value for correct undo/redo
let elt = cell.value.cloneNode(true);
elt.setAttribute('label', newValue);
newValue = elt;
}
cellLabelChanged.apply(this, arguments);
};
// Overrides method to create the editing value
let getEditingValue = graph.getEditingValue;
graph.getEditingValue = function(cell) {
if (mxUtils.isNode(cell.value) && cell.value.nodeName.toLowerCase() == 'userobject') {
return cell.getAttribute('label');
}
};
let parent = graph.getDefaultParent();
graph.insertVertex(parent, null, obj, 20, 20, 80, 60);
// Undo/redo
let undoManager = new UndoManager();
let listener = function(sender, evt) {
undoManager.undoableEditHappened(evt.getProperty('edit'));
};
graph.getDataModel().addListener(mxEvent.UNDO, listener);
graph.getView().addListener(mxEvent.UNDO, listener);
document.body.appendChild(mxUtils.button('Undo', function() {
undoManager.undo();
}));
document.body.appendChild(mxUtils.button('Redo', function() {
undoManager.redo();
}));

View File

@ -1,140 +0,0 @@
/**
* Copyright (c) 2006-2013, JGraph Ltd
*
* Menustyle
* Menustyle. This example demonstrates using
* CSS to style the mxPopupMenu.
*/
import React from 'react';
import mxEvent from '../mxgraph/util/mxEvent';
import mxGraph from '../mxgraph/view/mxGraph';
import mxRubberband from '../mxgraph/handler/mxRubberband';
const HTML_TEMPLATE = `
<!-- Page passes the container for the graph to the program -->
<style type="text/css">
body div.mxPopupMenu {
-webkit-box-shadow: 3px 3px 6px #C0C0C0;
-moz-box-shadow: 3px 3px 6px #C0C0C0;
box-shadow: 3px 3px 6px #C0C0C0;
background: white;
position: absolute;
border: 3px solid #e7e7e7;
padding: 3px;
}
body table.mxPopupMenu {
border-collapse: collapse;
margin: 0px;
}
body tr.mxPopupMenuItem {
color: black;
cursor: default;
}
body td.mxPopupMenuItem {
padding: 6px 60px 6px 30px;
font-family: Arial;
font-size: 10pt;
}
body td.mxPopupMenuIcon {
background-color: white;
padding: 0px;
}
body tr.mxPopupMenuItemHover {
background-color: #eeeeee;
color: black;
}
table.mxPopupMenu hr {
border-top: solid 1px #cccccc;
}
table.mxPopupMenu tr {
font-size: 4pt;
}
</style>
<body onload="main(document.getElementById('graphContainer'))">
<!-- Creates a container for the graph with a grid wallpaper -->
<div id="graphContainer"
style="overflow:hidden;width:321px;height:241px;background:url('editors/images/grid.gif');cursor:default;">
</div>
</body>
</html>
`
// Disables built-in context menu
InternalEvent.disableContextMenu(document.body);
// Changes some default colors
mxConstants.HANDLE_FILLCOLOR = '#99ccff';
mxConstants.HANDLE_STROKECOLOR = '#0088cf';
mxConstants.VERTEX_SELECTION_COLOR = '#00a8ff';
// Creates the graph inside the given container
let graph = new mxGraph(container);
graph.setTooltips(true);
// Enables rubberband selection
new mxRubberband(graph);
// Gets the default parent for inserting new cells. This
// is normally the first child of the root (ie. layer 0).
let parent = graph.getDefaultParent();
// Adds cells to the model in a single step
graph.batchUpdate(() => {
var v1 = graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30);
var v2 = graph.insertVertex(parent, null, 'World!', 200, 150, 80, 30);
var e1 = graph.insertEdge(parent, null, '', v1, v2);
});
// Creates a new overlay with an image and a tooltip and makes it "transparent" to events
let overlay = new CellOverlay(new mxImage('editors/images/overlays/check.png', 16, 16), 'Overlay tooltip');
let mxCellRendererInstallCellOverlayListeners = mxCellRenderer.prototype.installCellOverlayListeners;
mxCellRenderer.prototype.installCellOverlayListeners = function(state, overlay, shape){
mxCellRendererInstallCellOverlayListeners.apply(this, arguments);
var graph = state.view.graph;
mxEvent.addGestureListeners(shape.node,
function (evt) {
graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state));
},
function (evt) {
graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, state));
},
function (evt) {
});
if (!Client.IS_TOUCH) {
mxEvent.addListener(shape.node, 'mouseup', function(evt) {
overlay.fireEvent(new mxEventObject(mxEvent.CLICK,
'event', evt, 'cell', state.cell));
});
}
};
// Sets the overlay for the cell in the graph
graph.addCellOverlay(v1, overlay);
// Configures automatic expand on mouseover
graph.getPlugin('PopupMenuHandler').autoExpand = true;
// Installs context menu
graph.getPlugin('PopupMenuHandler').factoryMethod = function(menu, cell, evt) {
menu.addItem('Item 1', null, function() {
alert('Item 1');
});
menu.addItem('Item 2', null, function() {
alert('Item 2');
});
menu.addSeparator();
var submenu1 = menu.addItem('Submenu 1', null, null);
menu.addItem('Subitem 1', null, function() {
alert('Subitem 1');
}, submenu1);
menu.addItem('Subitem 1', null, function() {
alert('Subitem 2');
}, submenu1);
};

View File

@ -1,482 +0,0 @@
/**
* Copyright (c) 2006-2015, JGraph Ltd
*
* Scrollbars
*
* This example demonstrates using
* a scrollable table with different sections in a cell label.
*/
import React from 'react';
import mxEvent from '../mxgraph/util/mxEvent';
import mxGraph from '../mxgraph/view/mxGraph';
import mxRubberband from '../mxgraph/handler/mxRubberband';
import { error } from '../../packages/core/src/util/gui/MaxWindow';
import { createXmlDocument } from '../../packages/core/src/util/xmlUtils';
import { button } from '../../packages/core/src/util/dom/DomHelpers';
import { isNode } from '../../packages/core/src/util/domUtils';
const HTML_TEMPLATE = `
<style type="text/css" media="screen">
table.title {
border-color: black;
border-collapse: collapse;
cursor: move;
height: 26px;
border-bottom-style: none;
color: black;
}
table.title th {
font-size: 10pt;
font-family: Verdana;
white-space: nowrap;
background: lightgray;
font-weight: bold;
}
table.erd {
font-size: 10pt;
font-family: Verdana;
border-color: black;
border-collapse: collapse;
overflow: auto;
cursor: move;
white-space: nowrap;
}
table.erd td {
border-color: black;
text-align: left;
color: black;
}
button {
position:absolute;
}
</style>
<!-- Page passes the container for the graph to the program -->
<body onload="main(document.getElementById('graphContainer'))">
<!-- Creates a container for the graph with a grid wallpaper. Width, height and cursor in the style are for IE only -->
<div id="graphContainer"
style="cursor:default;position:absolute;top:30px;left:0px;bottom:0px;right:0px;background:url('editors/images/grid.gif')">
</div>
</body>
`
// Must be disabled to compute positions inside the DOM tree of the cell label.
mxGraphView.prototype.optimizeVmlReflows = false;
// If connect preview is not moved away then getCellAt is used to detect the cell under
// the mouse if the mouse is over the preview shape in IE (no event transparency), ie.
// the built-in hit-detection of the HTML document will not be used in this case. This is
// not a problem here since the preview moves away from the mouse as soon as it connects
// to any given table row. This is because the edge connects to the outside of the row and
// is aligned to the grid during the preview.
ConnectionHandler.prototype.movePreviewAway = false;
// Disables foreignObjects
Client.NO_FO = true;
// Enables move preview in HTML to appear on top
SelectionHandler.prototype.htmlPreview = true;
// Enables connect icons to appear on top of HTML
ConnectionHandler.prototype.moveIconFront = true;
// Defines an icon for creating new connections in the connection handler.
// This will automatically disable the highlighting of the source vertex.
ConnectionHandler.prototype.connectImage = new Image('images/connector.gif', 16, 16);
// Disables the context menu
mxEvent.disableContextMenu(container);
// Overrides target perimeter point for connection previews
ConnectionHandler.prototype.getTargetPerimeterPoint = function(state, me) {
// Determines the y-coordinate of the target perimeter point
// by using the currentRowNode assigned in updateRow
let y = me.getY();
if (this.currentRowNode != null) {
y = getRowY(state, this.currentRowNode);
}
// Checks on which side of the terminal to leave
let x = state.x;
if (this.previous.getCenterX() > state.getCenterX()) {
x += state.width;
}
return new Point(x, y);
};
// Overrides source perimeter point for connection previews
ConnectionHandler.prototype.getSourcePerimeterPoint = function(state, next, me)
{
let y = me.getY();
if (this.sourceRowNode != null) {
y = getRowY(state, this.sourceRowNode);
}
// Checks on which side of the terminal to leave
let x = state.x;
if (next.x > state.getCenterX()) {
x += state.width;
}
return new Point(x, y);
};
// Disables connections to invalid rows
ConnectionHandler.prototype.isValidTarget = function(cell) {
return this.currentRowNode != null;
};
// Creates the graph inside the given container
let graph = new mxGraph(container);
// Uses the entity perimeter (below) as default
graph.stylesheet.getDefaultVertexStyle().verticalAlign = mxConstants.ALIGN_TOP;
graph.stylesheet.getDefaultVertexStyle().perimiter =
Perimeter.EntityPerimeter;
graph.stylesheet.getDefaultVertexStyle().shadow = true;
graph.stylesheet.getDefaultVertexStyle().fillColor = '#DDEAFF';
graph.stylesheet.getDefaultVertexStyle().gradientColor = '#A9C4EB';
delete graph.stylesheet.getDefaultVertexStyle().strokeColor;
// Used for HTML labels that use up the complete vertex space (see
// graph.cellRenderer.redrawLabel below for syncing the size)
graph.stylesheet.getDefaultVertexStyle().overflow = 'fill';
// Uses the entity edge style as default
graph.stylesheet.getDefaultEdgeStyle().edge =
mxEdgeStyle.EntityRelation;
graph.stylesheet.getDefaultEdgeStyle().strokeColor = 'black';
graph.stylesheet.getDefaultEdgeStyle().fontColor = 'black';
// Allows new connections to be made but do not allow existing
// connections to be changed for the sake of simplicity of this
// example
graph.setCellsDisconnectable(false);
graph.setAllowDanglingEdges(false);
graph.setCellsEditable(false);
graph.setConnectable(true);
graph.setPanning(true);
graph.centerZoom = false;
// Override folding to allow for tables
graph.isCellFoldable = function(cell, collapse) {
return cell.isVertex();
};
// Overrides connectable state
graph.isCellConnectable = function(cell) {
return !cell.isCollapsed();
};
// Enables HTML markup in all labels
graph.setHtmlLabels(true);
// Scroll events should not start moving the vertex
graph.cellRenderer.isLabelEvent = function(state, evt) {
let source = mxEvent.getSource(evt);
return state.text != null && source != state.text.node &&
source != state.text.node.getElementsByTagName('div')[0];
};
// Adds scrollbars to the outermost div and keeps the
// DIV position and size the same as the vertex
let oldRedrawLabel = graph.cellRenderer.redrawLabel;
graph.cellRenderer.redrawLabel = function(state){
oldRedrawLabel.apply(this, arguments); // "supercall"
let graph = state.view.graph;
let model = graph.model;
if (state.cell.isVertex() && state.text != null) {
// Scrollbars are on the div
let s = graph.view.scale;
let div = state.text.node.getElementsByTagName('div')[2];
if (div != null) {
// Installs the handler for updating connected edges
if (div.scrollHandler == null) {
div.scrollHandler = true;
let updateEdges = () => {
let edgeCount = state.cell.getEdgeCount();
// Only updates edges to avoid update in DOM order
// for text label which would reset the scrollbar
for (let i = 0; i < edgeCount; i++)
{
let edge = state.cell.getEdgeAt(i);
graph.view.invalidate(edge, true, false);
graph.view.validate(edge);
}
};
mxEvent.addListener(div, 'scroll', updateEdges);
mxEvent.addListener(div, 'mouseup', updateEdges);
}
}
}
};
// Adds a new function to update the currentRow based on the given event
// and return the DOM node for that row
graph.getPlugin('ConnectionHandler').updateRow = function(target) {
while (target != null && target.nodeName != 'TR') {
target = target.parentNode;
}
this.currentRow = null;
// Checks if we're dealing with a row in the correct table
if (target != null && target.parentNode.parentNode.className == 'erd') {
// Stores the current row number in a property so that it can
// be retrieved to create the preview and final edge
let rowNumber = 0;
let current = target.parentNode.firstChild;
while (target != current && current != null) {
current = current.nextSibling;
rowNumber++;
}
this.currentRow = rowNumber + 1;
} else {
target = null;
}
return target;
};
// Adds placement of the connect icon based on the mouse event target (row)
graph.getPlugin('ConnectionHandler').updateIcons = function(state, icons, me) {
let target = me.getSource();
target = this.updateRow(target);
if (target != null && this.currentRow != null) {
let div = target.parentNode.parentNode.parentNode;
let s = state.view.scale;
icons[0].node.style.visibility = 'visible';
icons[0].bounds.x = state.x + target.offsetLeft + Math.min(state.width,
target.offsetWidth * s) - this.icons[0].bounds.width - 2;
icons[0].bounds.y = state.y - this.icons[0].bounds.height / 2 + (target.offsetTop +
target.offsetHeight / 2 - div.scrollTop + div.offsetTop) * s;
icons[0].redraw();
this.currentRowNode = target;
} else {
icons[0].node.style.visibility = 'hidden';
}
};
// Updates the targetRow in the preview edge State
let oldMouseMove = graph.getPlugin('ConnectionHandler').mouseMove;
graph.getPlugin('ConnectionHandler').mouseMove = function(sender, me) {
if (this.edgeState != null) {
this.currentRowNode = this.updateRow(me.getSource());
if (this.currentRow != null) {
this.edgeState.cell.value.setAttribute('targetRow', this.currentRow);
} else {
this.edgeState.cell.value.setAttribute('targetRow', '0');
}
// Destroys icon to prevent event redirection via image in IE
this.destroyIcons();
}
oldMouseMove.apply(this, arguments);
};
// Creates the edge state that may be used for preview
graph.getPlugin('ConnectionHandler').createEdgeState = function(me) {
let relation = doc.createElement('Relation');
relation.setAttribute('sourceRow', this.currentRow || '0');
relation.setAttribute('targetRow', '0');
let edge = this.createEdge(relation);
let style = this.graph.getCellStyle(edge);
let state = new Cell(this.graph.view, edge, style);
// Stores the source row in the handler
this.sourceRowNode = this.currentRowNode;
return state;
};
// Overrides getLabel to return empty labels for edges and
// short markup for collapsed cells.
graph.getLabel = function(cell) {
if (cell.isVertex()) {
if (cell.isCollapsed()) {
return '<table style="overflow:hidden;" width="100%" height="100%" border="1" cellpadding="4" class="title" style="height:100%;">' +
'<tr><th>Customers</th></tr>' +
'</table>';
} else {
return '<table style="overflow:hidden;" width="100%" border="1" cellpadding="4" class="title">' +
'<tr><th colspan="2">Customers</th></tr>' +
'</table>'+
'<div style="overflow:auto;cursor:default;top:26px;bottom:0px;position:absolute;width:100%;">'+
'<table width="100%" height="100%" border="1" cellpadding="4" class="erd">' +
'<tr><td>' +
'<img align="center" src="images/key.png"/>' +
'<img align="center" src="images/plus.png"/>' +
'</td><td>' +
'<u>customerId</u></td></tr><tr><td></td><td>number</td></tr>' +
'<tr><td></td><td>firstName</td></tr><tr><td></td><td>lastName</td></tr>' +
'<tr><td></td><td>streetAddress</td></tr><tr><td></td><td>city</td></tr>' +
'<tr><td></td><td>state</td></tr><tr><td></td><td>zip</td></tr>' +
'</table></div>';
}
} else {
return '';
}
};
// User objects (data) for the individual cells
let doc = createXmlDocument();
// Same should be used to create the XML node for the table
// description and the rows (most probably as child nodes)
let relation = doc.createElement('Relation');
relation.setAttribute('sourceRow', '4');
relation.setAttribute('targetRow', '6');
// Enables rubberband selection
new mxRubberband(graph);
// Enables key handling (eg. escape)
new KeyHandler(graph);
// Gets the default parent for inserting new cells. This
// is normally the first child of the root (ie. layer 0).
let parent = graph.getDefaultParent();
// Adds cells to the model in a single step
let width = 160;
let height = 230;
graph.getDataModel().beginUpdate();
try {
var v1 = graph.insertVertex(parent, null, '', 20, 20, width, height);
v1.geometry.alternateBounds = new Rectangle(0, 0, width, 26);
var v2 = graph.insertVertex(parent, null, '', 400, 150, width, height);
v2.geometry.alternateBounds = new Rectangle(0, 0, width, 26);
graph.insertEdge(parent, null, relation, v1, v2);
} finally {
// Updates the display
graph.getDataModel().endUpdate();
}
var btn1 = button('+', function() {
graph.zoomIn();
});
btn1.style.marginLeft = '20px';
document.body.appendChild(btn1);
document.body.appendChild(button('-', function() {
graph.zoomOut();
}));
// Implements a special perimeter for table rows inside the table markup
mxGraphView.prototype.updateFloatingTerminalPoint = function(edge, start, end, source) {
let next = this.getNextPoint(edge, end, source);
let div = start.text.node.getElementsByTagName('div')[2];
let x = start.x;
let y = start.getCenterY();
// Checks on which side of the terminal to leave
if (next.x > x + start.width / 2) {
x += start.width;
}
if (div != null) {
y = start.getCenterY() - div.scrollTop;
if (isNode(edge.cell.value) && !start.cell.isCollapsed()) {
let attr = (source) ? 'sourceRow' : 'targetRow';
let row = parseInt(edge.cell.value.getAttribute(attr));
// HTML labels contain an outer table which is built-in
let table = div.getElementsByTagName('table')[0];
let trs = table.getElementsByTagName('tr');
let tr = trs[Math.min(trs.length - 1, row - 1)];
// Gets vertical center of source or target row
if (tr != null) {
y = getRowY(start, tr);
}
}
// Keeps vertical coordinate inside start
let offsetTop = parseInt(div.style.top) * start.view.scale;
y = Math.min(start.y + start.height, Math.max(start.y + offsetTop, y));
// Updates the vertical position of the nearest point if we're not
// dealing with a connection preview, in which case either the
// edgeState or the absolutePoints are null
if (edge != null && edge.absolutePoints != null) {
next.y = y;
}
}
edge.setAbsoluteTerminalPoint(new Point(x, y), source);
// Routes multiple incoming edges along common waypoints if
// the edges have a common target row
if (source && isNode(edge.cell.value) && start != null && end != null) {
let edges = this.graph.getEdgesBetween(start.cell, end.cell, true);
let tmp = [];
// Filters the edges with the same source row
let row = edge.cell.value.getAttribute('targetRow');
for (let i = 0; i < edges.length; i++) {
if (isNode(edges[i].value) && edges[i].value.getAttribute('targetRow') == row) {
tmp.push(edges[i]);
}
}
edges = tmp;
if (edges.length > 1 && edge.cell == edges[edges.length - 1]) {
// Finds the vertical center
let states = [];
let y = 0;
for (let i = 0; i < edges.length; i++) {
states[i] = this.getState(edges[i]);
y += states[i].absolutePoints[0].y;
}
y /= edges.length;
for (let i = 0; i < states.length; i++) {
let x = states[i].absolutePoints[1].x;
if (states[i].absolutePoints.length < 5) {
states[i].absolutePoints.splice(2, 0, new Point(x, y));
} else {
states[i].absolutePoints[2] = new Point(x, y);
}
// Must redraw the previous edges with the changed point
if (i < states.length - 1) {
this.graph.cellRenderer.redraw(states[i]);
}
}
}
}
};
// Defines global helper function to get y-coordinate for a given cell state and row
let getRowY = function(state, tr) {
let s = state.view.scale;
let div = tr.parentNode.parentNode.parentNode;
let offsetTop = parseInt(div.style.top);
let y = state.y + (tr.offsetTop + tr.offsetHeight / 2 - div.scrollTop + offsetTop) * s;
y = Math.min(state.y + state.height, Math.max(state.y + offsetTop * s, y));
return y;
};

View File

@ -1,134 +0,0 @@
/**
* Copyright (c) 2006-2013, JGraph Ltd
*
* Show region
*
* This example demonstrates using a custom
* rubberband handler to show the selected region in a new window.
*/
import React from 'react';
import mxEvent from '../mxgraph/util/mxEvent';
import mxGraph from '../mxgraph/view/mxGraph';
import mxRubberband from '../mxgraph/handler/mxRubberband';
import mxConstants from "../mxgraph/util/mxConstants";
import mxPopupMenu from "../mxgraph/util/mxPopupMenu";
import mxRectangle from "../mxgraph/util/mxRectangle";
import mxUtils from "../mxgraph/util/mxUtils";
const HTML_TEMPLATE = `
<style type="text/css">
body div.mxPopupMenu {
-webkit-box-shadow: 3px 3px 6px #C0C0C0;
-moz-box-shadow: 3px 3px 6px #C0C0C0;
box-shadow: 3px 3px 6px #C0C0C0;
background: white;
position: absolute;
border: 3px solid #e7e7e7;
padding: 3px;
}
body table.mxPopupMenu {
border-collapse: collapse;
margin: 0px;
}
body tr.mxPopupMenuItem {
color: black;
cursor: default;
}
body td.mxPopupMenuItem {
padding: 6px 60px 6px 30px;
font-family: Arial;
font-size: 10pt;
}
body td.mxPopupMenuIcon {
background-color: white;
padding: 0px;
}
body tr.mxPopupMenuItemHover {
background-color: #eeeeee;
color: black;
}
table.mxPopupMenu hr {
border-top: solid 1px #cccccc;
}
table.mxPopupMenu tr {
font-size: 4pt;
}
</style>
<!-- Page passes the container for the graph to the program -->
<body onload="main(document.getElementById('graphContainer'))">
<!-- Creates a container for the graph with a grid wallpaper -->
<div id="graphContainer"
style="overflow:hidden;width:321px;height:241px;background:url('editors/images/grid.gif');cursor:default;">
</div>
Use the right mouse button to select a region of the diagram and select <i>Show this</i>.
</body>
`
// Disables built-in context menu
mxEvent.disableContextMenu(this.el);
// Changes some default colors
mxConstants.HANDLE_FILLCOLOR = '#99ccff';
mxConstants.HANDLE_STROKECOLOR = '#0088cf';
mxConstants.VERTEX_SELECTION_COLOR = '#00a8ff';
// Creates the graph inside the given container
let graph = new mxGraph(container);
// Enables rubberband selection
let rubberband = new mxRubberband(graph);
rubberband.isForceRubberbandEvent = function(me) {
return mxRubberband.prototype.isForceRubberbandEvent.apply(this, arguments) || mxEvent.isPopupTrigger(me.getEvent());
}
// Defines a new popup menu for region selection in the rubberband handler
rubberband.popupMenu = new mxPopupMenu(function(menu, cell, evt) {
let rect = new mxRectangle(rubberband.x, rubberband.y, rubberband.width, rubberband.height);
menu.addItem('Show this', null, function() {
rubberband.popupMenu.hideMenu();
let bounds = graph.getGraphBounds();
mxUtils.show(graph, null, bounds.x - rubberband.x, bounds.y - rubberband.y, rubberband.width, rubberband.height);
});
});
let rubberbandMouseDown = rubberband.mouseDown;
rubberband.mouseDown = function(sender, me) {
this.popupMenu.hideMenu();
rubberbandMouseDown.apply(this, arguments);
};
let rubberbandMouseUp = rubberband.mouseUp;
rubberband.mouseUp = function(sender, me) {
if (this.div != null && mxEvent.isPopupTrigger(me.getEvent())) {
if (!graph.getPlugin('PopupMenuHandler').isMenuShowing()) {
let origin = mxUtils.getScrollOrigin();
this.popupMenu.popup(me.getX() + origin.x + 1, me.getY() + origin.y + 1, null, me.getEvent());
this.reset();
}
} else {
rubberbandMouseUp.apply(this, arguments);
}
};
// Gets the default parent for inserting new cells. This
// is normally the first child of the root (ie. layer 0).
let parent = graph.getDefaultParent();
// Adds cells to the model in a single step
graph.getDataModel().beginUpdate();
try {
var v1 = graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30);
var v2 = graph.insertVertex(parent, null, 'World!', 200, 150, 80, 30);
var e1 = graph.insertEdge(parent, null, '', v1, v2);
} finally {
// Updates the display
graph.getDataModel().endUpdate();
}

View File

@ -1,894 +0,0 @@
/**
README
******
- Edge-to-edge connections: We store the point where the mouse
was released in the terminal points of the edge geometry and
use that point to find the nearest segment on the target edge
and the connection point between the two edges in
mxGraphView.updateFixedTerminalPoint.
- The orthogonal router, which is implemented as an edge style,
computes its result based on the output of mxGraphView.
updateFixedTerminalPoint, which computes all connection points
for edge-to-edge connections and constrained ports and vertices
and stores them in state.absolutePoints.
- Routing directions are stored in the 'portConstraint' style.
Possible values for this style horizontal and vertical. Note
that this may have other values depending on the edge style.
- For edge-to-edge connections, a 'source-/targetConstraint'
style is added in updateFixedTerminalPoint that contains the
orientation of the segment that the edge connects to. Possible
values are horizontal, vertical.
- An alternative solution for connection points via connection
constraints is demonstrated. In this setup, the edge is connected
to the parent cell directly. There are no child cells that act
as "ports". Instead, the connection information is stored as a
relative point in the connecting edge. (See also: portrefs.html
for storing references to ports.)
*/
import {
Graph,
InternalEvent,
RubberBandHandler,
ConnectionHandler,
ConnectionConstraint,
Geometry,
PolylineShape,
Point,
CellState,
} from '@maxgraph/core';
import { globalTypes } from '../.storybook/preview';
export default {
title: 'Connections/Anchors',
argTypes: {
...globalTypes,
rubberBand: {
type: 'boolean',
defaultValue: true,
},
},
};
const HTML_TEMPLATE = `
<body onload="main(document.getElementById('graphContainer'))">
<div id="graphContainer"
style="overflow:auto;position:relative;width:800px;height:600px;border:1px solid gray;background:url('images/wires-grid.gif');background-position:-1px 0px;cursor:crosshair;">
</div>
</body>
</html>
`;
// If connect preview is not moved away then getCellAt is used to detect the cell under
// the mouse if the mouse is over the preview shape in IE (no event transparency), ie.
// the built-in hit-detection of the HTML document will not be used in this case.
mxConnectionHandler.prototype.movePreviewAway = false;
mxConnectionHandler.prototype.waypointsEnabled = true;
mxGraph.prototype.resetEdgesOnConnect = false;
mxConstants.SHADOWCOLOR = '#C0C0C0';
let joinNodeSize = 7;
let strokeWidth = 2;
// Replaces the port image
mxConstraintHandler.prototype.pointImage = new mxImage('images/dot.gif', 10, 10);
// Enables guides
mxGraphHandler.prototype.guidesEnabled = true;
// Alt disables guides
mxGuide.prototype.isEnabledForEvent = function (evt) {
return !mxEvent.isAltDown(evt);
};
// Enables snapping waypoints to terminals
mxEdgeHandler.prototype.snapToTerminals = true;
let graph = new mxGraph(container);
graph.view.scale = 1;
graph.setPanning(true);
graph.setConnectable(true);
graph.setConnectableEdges(true);
graph.setDisconnectOnMove(false);
graph.foldingEnabled = false;
//Maximum size
graph.maximumGraphBounds = new mxRectangle(0, 0, 800, 600);
graph.border = 50;
// Panning handler consumed right click so this must be
// disabled if right click should stop connection handler.
graph.getPlugin('PanningHandler').isPopupTrigger = function () {
return false;
};
// Enables return key to stop editing (use shift-enter for newlines)
graph.setEnterStopsCellEditing(true);
// Adds rubberband selection
new mxRubberband(graph);
// Alternative solution for implementing connection points without child cells.
// This can be extended as shown in portrefs.html example to allow for per-port
// incoming/outgoing direction.
graph.getAllConnectionConstraints = function (terminal) {
let geo = terminal != null ? terminal.cell.getGeometry() : null;
if (
(geo != null ? !geo.relative : false) &&
terminal.cell.isVertex() &&
this.getDataModel().getChildCount(terminal.cell) == 0
) {
return [
new mxConnectionConstraint(new mxPoint(0, 0.5), false),
new mxConnectionConstraint(new mxPoint(1, 0.5), false),
];
}
return null;
};
// Makes sure non-relative cells can only be connected via constraints
graph.getPlugin('ConnectionHandler').isConnectableCell = function (cell) {
if (this.graph.getDataModel().isEdge(cell)) {
return true;
} else {
let geo = cell != null ? cell.getGeometry() : null;
return geo != null ? geo.relative : false;
}
};
mxEdgeHandler.prototype.isConnectableCell = function (cell) {
return graph.getPlugin('ConnectionHandler').isConnectableCell(cell);
};
// Adds a special tooltip for edges
graph.setTooltips(true);
let getTooltipForCell = graph.getTooltipForCell;
graph.getTooltipForCell = function (cell) {
let tip = '';
if (cell != null) {
let src = this.getDataModel().getTerminal(cell, true);
if (src != null) {
tip += this.getTooltipForCell(src) + ' ';
}
let parent = this.getDataModel().getParent(cell);
if (parent.isVertex()) {
tip += this.getTooltipForCell(parent) + '.';
}
tip += getTooltipForCell.apply(this, arguments);
let trg = this.getDataModel().getTerminal(cell, false);
if (trg != null) {
tip += ' ' + this.getTooltipForCell(trg);
}
}
return tip;
};
// Switch for black background and bright styles
let invert = false;
if (invert) {
container.style.backgroundColor = 'black';
// White in-place editor text color
mxCellEditorStartEditing = mxCellEditor.prototype.startEditing;
mxCellEditor.prototype.startEditing = (cell, trigger) => {
mxCellEditorStartEditing.apply(this, arguments);
if (this.textarea != null) {
this.textarea.style.color = '#FFFFFF';
}
};
mxGraphHandler.prototype.previewColor = 'white';
}
let labelBackground = invert ? '#000000' : '#FFFFFF';
let fontColor = invert ? '#FFFFFF' : '#000000';
let strokeColor = invert ? '#C0C0C0' : '#000000';
let fillColor = invert ? 'none' : '#FFFFFF';
let style = graph.getStylesheet().getDefaultEdgeStyle();
delete style.endArrow;
style.strokeColor = strokeColor;
style.labelBackgroundColor = labelBackground;
style.edgeStyle = 'wireEdgeStyle';
style.fontColor = fontColor;
style.fontSize = '9';
style.movable = '0';
style.strokeWidth = strokeWidth;
//style.rounded = '1';
// Sets join node size
style.startSize = joinNodeSize;
style.endSize = joinNodeSize;
style = graph.getStylesheet().getDefaultVertexStyle();
style.gradientDirection = 'south';
//style.gradientColor = '#909090';
style.strokeColor = strokeColor;
//style.fillColor = '#e0e0e0';
style.fillColor = 'none';
style.fontColor = fontColor;
style.fontStyle = '1';
style.fontSize = '12';
style.resizable = '0';
style.rounded = '1';
style.strokeWidth = strokeWidth;
let parent = graph.getDefaultParent();
graph.getDataModel().beginUpdate();
try {
var v1 = graph.insertVertex(parent, null, 'J1', 80, 40, 40, 80, {
verticalLabelPosition: 'top',
verticalAlign: 'bottom',
shadow: true,
fillColor,
});
v1.setConnectable(false);
var v11 = graph.insertVertex(v1, null, '1', 0, 0, 10, 16, {
shape: 'line',
align: 'left',
verticalAlign: 'middle',
fontSize: 10,
routingCenterX: -0.5,
spacingLeft: 12,
fontColor,
strokeColor,
});
v11.geometry.relative = true;
v11.geometry.offset = new mxPoint(-v11.geometry.width, 2);
var v12 = v11.clone();
v12.value = '2';
v12.geometry.offset = new mxPoint(-v11.geometry.width, 22);
v1.insert(v12);
var v13 = v11.clone();
v13.value = '3';
v13.geometry.offset = new mxPoint(-v11.geometry.width, 42);
v1.insert(v13);
var v14 = v11.clone();
v14.value = '4';
v14.geometry.offset = new mxPoint(-v11.geometry.width, 62);
v1.insert(v14);
var v15 = v11.clone();
v15.value = '5';
v15.geometry.x = 1;
v15.style = {
shape: 'line',
align: 'right',
verticalAlign: 'middle',
fontSize: 10,
routingCenterX: 0.5,
spacingRight: 12,
fontColor,
strokeColor,
};
v15.geometry.offset = new mxPoint(0, 2);
v1.insert(v15);
var v16 = v15.clone();
v16.value = '6';
v16.geometry.offset = new mxPoint(0, 22);
v1.insert(v16);
var v17 = v15.clone();
v17.value = '7';
v17.geometry.offset = new mxPoint(0, 42);
v1.insert(v17);
var v18 = v15.clone();
v18.value = '8';
v18.geometry.offset = new mxPoint(0, 62);
v1.insert(v18);
var v19 = v15.clone();
v19.value = 'clk';
v19.geometry.x = 0.5;
v19.geometry.y = 1;
v19.geometry.width = 10;
v19.geometry.height = 4;
// NOTE: portConstraint is defined for east direction, so must be inverted here
v19.style = {
shape: 'triangle',
direction: 'north',
spacingBottom: 12,
align: 'center',
portConstraint: 'horizontal',
fontSize: 8,
strokeColor,
routingCenterY: 0.5,
};
v19.geometry.offset = new mxPoint(-4, -4);
v1.insert(v19);
var v2 = graph.insertVertex(parent, null, 'R1', 220, 220, 80, 20, {
shape: 'resistor',
verticalLabelPosition: 'top',
verticalAlign: 'bottom',
});
// Uses implementation of connection points via constraints (see above)
//v2.setConnectable(false);
/*var v21 = graph.insertVertex(v2, null, 'A', 0, 0.5, 10, 1,
'shape=none;spacingBottom=11;spacingLeft=1;align=left;fontSize=8;'+
'fontColor=#4c4c4c;strokeColor=#909090;');
v21.geometry.relative = true;
v21.geometry.offset = new mxPoint(0, -1);
var v22 = graph.insertVertex(v2, null, 'B', 1, 0.5, 10, 1,
'spacingBottom=11;spacingLeft=1;align=left;fontSize=8;'+
'fontColor=#4c4c4c;strokeColor=#909090;');
v22.geometry.relative = true;
v22.geometry.offset = new mxPoint(-10, -1);*/
var v3 = graph.addCell(graph.getDataModel().cloneCell(v1));
v3.value = 'J3';
v3.geometry.x = 420;
v3.geometry.y = 340;
// Connection constraints implemented in edges, alternatively this
// can be implemented using references, see: portrefs.html
var e1 = graph.insertEdge(parent, null, 'e1', v1.getChildAt(7), v2, {
entryX: 0,
entryY: 0.5,
entryPerimeter: 0,
});
e1.geometry.points = [new mxPoint(180, 110)];
var e2 = graph.insertEdge(parent, null, 'e2', v1.getChildAt(4), v2, {
entryX: 1,
entryY: 0.5,
entryPerimeter: 0,
});
e2.geometry.points = [new mxPoint(320, 50), new mxPoint(320, 230)];
var e3 = graph.insertEdge(parent, null, 'crossover', e1, e2);
e3.geometry.setTerminalPoint(new mxPoint(180, 140), true);
e3.geometry.setTerminalPoint(new mxPoint(320, 140), false);
// var e1 = graph.insertEdge(parent, null, 'e1', v1.getChildAt(7), v2.getChildAt(0));
// e1.geometry.points = [new mxPoint(180, 140)];
// var e2 = graph.insertEdge(parent, null, '', v1.getChildAt(4), v2.getChildAt(1));
// e2.geometry.points = [new mxPoint(320, 80)];
// var e3 = graph.insertEdge(parent, null, 'crossover', e1, e2);
// e3.geometry.setTerminalPoint(new mxPoint(180, 160), true);
// e3.geometry.setTerminalPoint(new mxPoint(320, 160), false);
var e4 = graph.insertEdge(parent, null, 'e4', v2, v3.getChildAt(0), {
exitX: 1,
exitY: 0.5,
entryPerimeter: 0,
});
e4.geometry.points = [new mxPoint(380, 230)];
var e5 = graph.insertEdge(parent, null, 'e5', v3.getChildAt(5), v1.getChildAt(0));
e5.geometry.points = [new mxPoint(500, 310), new mxPoint(500, 20), new mxPoint(50, 20)];
var e6 = graph.insertEdge(parent, null, '');
e6.geometry.setTerminalPoint(new mxPoint(100, 500), true);
e6.geometry.setTerminalPoint(new mxPoint(600, 500), false);
var e7 = graph.insertEdge(parent, null, 'e7', v3.getChildAt(7), e6);
e7.geometry.setTerminalPoint(new mxPoint(500, 500), false);
e7.geometry.points = [new mxPoint(500, 350)];
} finally {
graph.getDataModel().endUpdate();
}
document.body.appendChild(
mxUtils.button('Zoom In', function () {
graph.zoomIn();
})
);
document.body.appendChild(
mxUtils.button('Zoom Out', function () {
graph.zoomOut();
})
);
// Undo/redo
let undoManager = new UndoManager();
let listener = function (sender, evt) {
undoManager.undoableEditHappened(evt.getProperty('edit'));
};
graph.getDataModel().addListener(mxEvent.UNDO, listener);
graph.getView().addListener(mxEvent.UNDO, listener);
document.body.appendChild(
mxUtils.button('Undo', function () {
undoManager.undo();
})
);
document.body.appendChild(
mxUtils.button('Redo', function () {
undoManager.redo();
})
);
// Shows XML for debugging the actual model
document.body.appendChild(
mxUtils.button('Delete', function () {
graph.removeCells();
})
);
// Wire-mode
let checkbox = document.createElement('input');
checkbox.setAttribute('type', 'checkbox');
document.body.appendChild(checkbox);
mxUtils.write(document.body, 'Wire Mode');
// Starts connections on the background in wire-mode
let connectionHandlerIsStartEvent = graph.getPlugin('ConnectionHandler').isStartEvent;
graph.getPlugin('ConnectionHandler').isStartEvent = function (me) {
return checkbox.checked || connectionHandlerIsStartEvent.apply(this, arguments);
};
// Avoids any connections for gestures within tolerance except when in wire-mode
// or when over a port
let connectionHandlerMouseUp = graph.getPlugin('ConnectionHandler').mouseUp;
graph.getPlugin('ConnectionHandler').mouseUp = function (sender, me) {
if (this.first != null && this.previous != null) {
let point = mxUtils.convertPoint(this.graph.container, me.getX(), me.getY());
let dx = Math.abs(point.x - this.first.x);
let dy = Math.abs(point.y - this.first.y);
if (dx < this.graph.tolerance && dy < this.graph.tolerance) {
// Selects edges in non-wire mode for single clicks, but starts
// connecting for non-edges regardless of wire-mode
if (!checkbox.checked && this.graph.getDataModel().isEdge(this.previous.cell)) {
this.reset();
}
return;
}
}
connectionHandlerMouseUp.apply(this, arguments);
};
// Grid
var checkbox2 = document.createElement('input');
checkbox2.setAttribute('type', 'checkbox');
checkbox2.setAttribute('checked', 'true');
document.body.appendChild(checkbox2);
mxUtils.write(document.body, 'Grid');
mxEvent.addListener(checkbox2, 'click', function (evt) {
if (checkbox2.checked) {
container.style.background = "url('images/wires-grid.gif')";
} else {
container.style.background = '';
}
container.style.backgroundColor = invert ? 'black' : 'white';
});
mxEvent.disableContextMenu(container);
// Updates connection points before the routing is called.
// Computes the position of edge to edge connection points.
mxGraphView.prototype.updateFixedTerminalPoint = function (
edge,
terminal,
source,
constraint
) {
let pt = null;
if (constraint != null) {
pt = this.graph.getConnectionPoint(terminal, constraint);
}
if (source) {
edge.sourceSegment = null;
} else {
edge.targetSegment = null;
}
if (pt == null) {
let s = this.scale;
let tr = this.translate;
let orig = edge.origin;
let geo = edge.cell.getGeometry();
pt = geo.getTerminalPoint(source);
// Computes edge-to-edge connection point
if (pt != null) {
pt = new mxPoint(s * (tr.x + pt.x + orig.x), s * (tr.y + pt.y + orig.y));
// Finds nearest segment on edge and computes intersection
if (terminal != null && terminal.absolutePoints != null) {
let seg = mxUtils.findNearestSegment(terminal, pt.x, pt.y);
// Finds orientation of the segment
var p0 = terminal.absolutePoints[seg];
let pe = terminal.absolutePoints[seg + 1];
let horizontal = p0.x - pe.x == 0;
// Stores the segment in the edge state
let key = source ? 'sourceConstraint' : 'targetConstraint';
let value = horizontal ? 'horizontal' : 'vertical';
edge.style[key] = value;
// Keeps the coordinate within the segment bounds
if (horizontal) {
pt.x = p0.x;
pt.y = Math.min(pt.y, Math.max(p0.y, pe.y));
pt.y = Math.max(pt.y, Math.min(p0.y, pe.y));
} else {
pt.y = p0.y;
pt.x = Math.min(pt.x, Math.max(p0.x, pe.x));
pt.x = Math.max(pt.x, Math.min(p0.x, pe.x));
}
}
}
// Computes constraint connection points on vertices and ports
else if (terminal != null && terminal.cell.geometry.relative) {
pt = new mxPoint(
this.getRoutingCenterX(terminal),
this.getRoutingCenterY(terminal)
);
}
// Snaps point to grid
/*if (pt != null)
{
let tr = this.graph.view.translate;
let s = this.graph.view.scale;
pt.x = (this.graph.snap(pt.x / s - tr.x) + tr.x) * s;
pt.y = (this.graph.snap(pt.y / s - tr.y) + tr.y) * s;
}*/
}
edge.setAbsoluteTerminalPoint(pt, source);
};
// Overrides methods to preview and create new edges.
// Sets source terminal point for edge-to-edge connections.
mxConnectionHandler.prototype.createEdgeState = function (me) {
let edge = this.graph.createEdge();
if (this.sourceConstraint != null && this.previous != null) {
edge.style =
'exitX' +
'=' +
this.sourceConstraint.point.x +
';' +
'exitY' +
'=' +
this.sourceConstraint.point.y +
';';
} else if (this.graph.model.isEdge(me.getCell())) {
let scale = this.graph.view.scale;
let tr = this.graph.view.translate;
let pt = new mxPoint(
this.graph.snap(me.getGraphX() / scale) - tr.x,
this.graph.snap(me.getGraphY() / scale) - tr.y
);
edge.geometry.setTerminalPoint(pt, true);
}
return this.graph.view.createState(edge);
};
// Uses right mouse button to create edges on background (see also: lines 67 ff)
mxConnectionHandler.prototype.isStopEvent = function (me) {
return me.getState() != null || mxEvent.isRightMouseButton(me.getEvent());
};
// Updates target terminal point for edge-to-edge connections.
mxConnectionHandlerUpdateCurrentState = mxConnectionHandler.prototype.updateCurrentState;
mxConnectionHandler.prototype.updateCurrentState = function (me) {
mxConnectionHandlerUpdateCurrentState.apply(this, arguments);
if (this.edgeState != null) {
this.edgeState.cell.geometry.setTerminalPoint(null, false);
if (
this.shape != null &&
this.currentState != null &&
this.currentState.view.graph.model.isEdge(this.currentState.cell)
) {
let scale = this.graph.view.scale;
let tr = this.graph.view.translate;
let pt = new mxPoint(
this.graph.snap(me.getGraphX() / scale) - tr.x,
this.graph.snap(me.getGraphY() / scale) - tr.y
);
this.edgeState.cell.geometry.setTerminalPoint(pt, false);
}
}
};
// Updates the terminal and control points in the cloned preview.
mxEdgeSegmentHandler.prototype.clonePreviewState = function (point, terminal) {
let clone = mxEdgeHandler.prototype.clonePreviewState.apply(this, arguments);
clone.cell = clone.cell.clone();
if (this.isSource || this.isTarget) {
clone.cell.geometry = clone.cell.geometry.clone();
// Sets the terminal point of an edge if we're moving one of the endpoints
if (this.graph.getDataModel().isEdge(clone.cell)) {
// TODO: Only set this if the target or source terminal is an edge
clone.cell.geometry.setTerminalPoint(point, this.isSource);
} else {
clone.cell.geometry.setTerminalPoint(null, this.isSource);
}
}
return clone;
};
let mxEdgeHandlerConnect = mxEdgeHandler.prototype.connect;
mxEdgeHandler.prototype.connect = function (edge, terminal, isSource, isClone, me) {
let result = null;
let model = this.graph.getDataModel();
let parent = model.getParent(edge);
model.beginUpdate();
try {
result = mxEdgeHandlerConnect.apply(this, arguments);
let geo = model.getGeometry(result);
if (geo != null) {
geo = geo.clone();
let pt = null;
if (model.isEdge(terminal)) {
pt = this.abspoints[this.isSource ? 0 : this.abspoints.length - 1];
pt.x = pt.x / this.graph.view.scale - this.graph.view.translate.x;
pt.y = pt.y / this.graph.view.scale - this.graph.view.translate.y;
let pstate = this.graph
.getView()
.getState(this.graph.getDataModel().getParent(edge));
if (pstate != null) {
pt.x -= pstate.origin.x;
pt.y -= pstate.origin.y;
}
pt.x -= this.graph.panDx / this.graph.view.scale;
pt.y -= this.graph.panDy / this.graph.view.scale;
}
geo.setTerminalPoint(pt, isSource);
model.setGeometry(edge, geo);
}
} finally {
model.endUpdate();
}
return result;
};
// Adds in-place highlighting for complete cell area (no hotspot).
mxConnectionHandlerCreateMarker = mxConnectionHandler.prototype.createMarker;
mxConnectionHandler.prototype.createMarker = function () {
let marker = mxConnectionHandlerCreateMarker.apply(this, arguments);
// Uses complete area of cell for new connections (no hotspot)
marker.intersects = function (state, evt) {
return true;
};
// Adds in-place highlighting
mxCellHighlightHighlight = mxCellHighlight.prototype.highlight;
marker.highlight.highlight = function (state) {
if (this.state != state) {
if (this.state != null) {
this.state.style = this.lastStyle;
// Workaround for shape using current stroke width if no strokewidth defined
this.state.style.strokeWidth = this.state.style.strokeWidth || '1';
this.state.style.strokeColor = this.state.style.strokeColor || 'none';
if (this.state.shape != null) {
this.state.view.graph.cellRenderer.configureShape(this.state);
this.state.shape.redraw();
}
}
if (state != null) {
this.lastStyle = state.style;
state.style = mxUtils.clone(state.style);
state.style.strokeColor = '#00ff00';
state.style.strokeWidth = '3';
if (state.shape != null) {
state.view.graph.cellRenderer.configureShape(state);
state.shape.redraw();
}
}
this.state = state;
}
};
return marker;
};
mxEdgeHandlerCreateMarker = mxEdgeHandler.prototype.createMarker;
mxEdgeHandler.prototype.createMarker = function () {
let marker = mxEdgeHandlerCreateMarker.apply(this, arguments);
// Adds in-place highlighting when reconnecting existing edges
marker.highlight.highlight =
this.graph.getPlugin('ConnectionHandler').marker.highlight.highlight;
return marker;
};
// Adds oval markers for edge-to-edge connections.
mxGraphGetCellStyle = mxGraph.prototype.getCellStyle;
mxGraph.prototype.getCellStyle = function (cell) {
let style = mxGraphGetCellStyle.apply(this, arguments);
if (style != null && this.model.isEdge(cell)) {
style = mxUtils.clone(style);
if (this.model.isEdge(this.model.getTerminal(cell, true))) {
style.startArrow = 'oval';
}
if (this.model.isEdge(this.model.getTerminal(cell, false))) {
style.endArrow = 'oval';
}
}
return style;
};
// Imlements a custom resistor shape. Direction currently ignored here.
function ResistorShape() {}
ResistorShape.prototype = new mxCylinder();
ResistorShape.prototype.constructor = ResistorShape;
ResistorShape.prototype.redrawPath = function (path, x, y, w, h, isForeground) {
let dx = w / 16;
if (isForeground) {
path.moveTo(0, h / 2);
path.lineTo(2 * dx, h / 2);
path.lineTo(3 * dx, 0);
path.lineTo(5 * dx, h);
path.lineTo(7 * dx, 0);
path.lineTo(9 * dx, h);
path.lineTo(11 * dx, 0);
path.lineTo(13 * dx, h);
path.lineTo(14 * dx, h / 2);
path.lineTo(16 * dx, h / 2);
path.end();
}
};
mxCellRenderer.registerShape('resistor', ResistorShape);
// Imlements a custom resistor shape. Direction currently ignored here.
mxEdgeStyle.WireConnector = function (state, source, target, hints, result) {
// Creates array of all way- and terminalpoints
let pts = state.absolutePoints;
let horizontal = true;
let hint = null;
// Gets the initial connection from the source terminal or edge
if (source != null && state.view.graph.model.isEdge(source.cell)) {
horizontal = state.style.sourceConstraint == 'horizontal';
} else if (source != null) {
horizontal = source.style.portConstraint != 'vertical';
// Checks the direction of the shape and rotates
let direction = source.style.direction;
if (direction == 'north' || direction == 'south') {
horizontal = !horizontal;
}
}
// Adds the first point
// TODO: Should move along connected segment
let pt = pts[0];
if (pt == null && source != null) {
pt = new mxPoint(
state.view.getRoutingCenterX(source),
state.view.getRoutingCenterY(source)
);
} else if (pt != null) {
pt = pt.clone();
}
let first = pt;
// Adds the waypoints
if (hints != null && hints.length > 0) {
// FIXME: First segment not movable
/*hint = state.view.transformControlPoint(state, hints[0]);
MaxLog.show();
MaxLog.debug(hints.length,'hints0.y='+hint.y, pt.y)
if (horizontal && Math.floor(hint.y) != Math.floor(pt.y))
{
MaxLog.show();
MaxLog.debug('add waypoint');
pt = new mxPoint(pt.x, hint.y);
result.push(pt);
pt = pt.clone();
//horizontal = !horizontal;
}*/
for (let i = 0; i < hints.length; i++) {
horizontal = !horizontal;
hint = state.view.transformControlPoint(state, hints[i]);
if (horizontal) {
if (pt.y != hint.y) {
pt.y = hint.y;
result.push(pt.clone());
}
} else if (pt.x != hint.x) {
pt.x = hint.x;
result.push(pt.clone());
}
}
} else {
hint = pt;
}
// Adds the last point
pt = pts[pts.length - 1];
// TODO: Should move along connected segment
if (pt == null && target != null) {
pt = new mxPoint(
state.view.getRoutingCenterX(target),
state.view.getRoutingCenterY(target)
);
}
if (horizontal) {
if (pt.y != hint.y && first.x != pt.x) {
result.push(new mxPoint(pt.x, hint.y));
}
} else if (pt.x != hint.x && first.y != pt.y) {
result.push(new mxPoint(hint.x, pt.y));
}
};
mxStyleRegistry.putValue('wireEdgeStyle', mxEdgeStyle.WireConnector);
// This connector needs an mxEdgeSegmentHandler
mxGraphCreateHandler = mxGraph.prototype.createHandler;
mxGraph.prototype.createHandler = function (state) {
let result = null;
if (state != null) {
if (this.model.isEdge(state.cell)) {
let style = this.view.getEdgeStyle(state);
if (style == mxEdgeStyle.WireConnector) {
return new mxEdgeSegmentHandler(state);
}
}
}
return mxGraphCreateHandler.apply(this, arguments);
};

View File

@ -1,5 +0,0 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
export default (req, res) => {
res.status(200).json({ name: 'John Doe' });
};