- Add images for the examples.

- Convert more examples to storybook.
development
Junsik Shim 2021-04-20 21:22:55 +09:00
parent 1d05e161c9
commit 8c3afdfc77
76 changed files with 1791 additions and 1624 deletions

View File

@ -5,7 +5,6 @@
* Type definitions from the typed-mxgraph project
*/
import mxUtils from '../util/mxUtils';
import mxCellPath from '../view/cell/mxCellPath';
import mxCodecRegistry from './mxCodecRegistry';
import mxConstants from '../util/mxConstants';
@ -13,6 +12,7 @@ import mxCell from '../view/cell/mxCell';
import mxLog from '../util/gui/mxLog';
import { getFunctionName } from '../util/mxStringUtils';
import { importNode, isNode } from '../util/mxDomUtils';
import { createXmlDocument } from '../util/mxXmlUtils';
/**
* XML codec for JavaScript object graphs. See {@link mxObjectCodec} for a
@ -120,7 +120,7 @@ import { importNode, isNode } from '../util/mxDomUtils';
*/
class mxCodec {
constructor(document) {
this.document = document || mxUtils.createXmlDocument();
this.document = document || createXmlDocument();
this.objects = [];
}

View File

@ -1,5 +1,6 @@
import mxDragSource from './drag_pan/mxDragSource';
import mxPoint from './datatypes/mxPoint';
import mxConstants from './mxConstants';
/**
* Function: makeDraggable

View File

@ -7,4 +7,15 @@ export const parameters = {
date: /Date$/,
},
},
}
}
export const defaultArgTypes = {
width: {
type: 'number',
defaultValue: 800
},
height: {
type: 'number',
defaultValue: 600
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 887 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 954 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 728 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 914 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 857 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 899 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 709 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 783 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 896 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 895 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 879 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 849 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 918 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 912 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 847 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -1,10 +1,15 @@
import mxgraph from '@mxgraph/core';
import HelloWorld from './HelloWorld.stories';
import { defaultArgTypes } from '../.storybook/preview';
export default {
title: 'Connections/Anchors',
argTypes: {
...HelloWorld.argTypes
...defaultArgTypes.argTypes,
rubberBand: {
type: 'boolean',
defaultValue: true
}
}
};

View File

@ -0,0 +1,359 @@
import mxgraph from '@mxgraph/core';
import { defaultArgTypes } from '../.storybook/preview';
export default {
title: 'DnD_CopyPaste/Clipboard',
argTypes: {
...defaultArgTypes,
contextMenu: {
type: 'boolean',
defaultValue: false
},
rubberBand: {
type: 'boolean',
defaultValue: true
}
}
};
const Template = ({ label, ...args }) => {
const {
mxGraph,
mxEvent,
mxRubberband,
mxClipboard,
mxUtils,
mxEventUtils,
mxClient,
mxCodec,
mxGraphModel,
mxStringUtils
} = mxgraph;
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
if (!args.contextMenu)
mxEvent.disableContextMenu(container);
// Creates the graph inside the given this.el
const graph = new mxGraph(container);
// Public helper method for shared clipboard.
mxClipboard.cellsToString = function(cells) {
const codec = new mxCodec();
const model = new mxGraphModel();
const parent = model.getChildAt(model.getRoot(), 0);
for (let i = 0; i < cells.length; i++) {
model.add(parent, cells[i]);
}
return mxUtils.getXml(codec.encode(model));
};
// Focused but invisible textarea during control or meta key events
const textInput = document.createElement('textarea');
mxUtils.setOpacity(textInput, 0);
textInput.style.width = '1px';
textInput.style.height = '1px';
let restoreFocus = false;
const gs = graph.gridSize;
let lastPaste = null;
let dx = 0;
let dy = 0;
// Workaround for no copy event in IE/FF if empty
textInput.value = ' ';
// Shows a textare when control/cmd is pressed to handle native clipboard actions
mxEvent.addListener(document, 'keydown', function(evt) {
// No dialog visible
const source = mxEventUtils.getSource(evt);
if (
graph.isEnabled() &&
!graph.isMouseDown &&
!graph.isEditing() &&
source.nodeName !== 'INPUT'
) {
if (
evt.keyCode === 224 /* FF */ ||
(!mxClient.IS_MAC && evt.keyCode === 17) /* Control */ ||
(mxClient.IS_MAC &&
(evt.keyCode === 91 || evt.keyCode === 93)) /* Left/Right Meta */
) {
// Cannot use parentNode for check in IE
if (!restoreFocus) {
// Avoid autoscroll but allow handling of events
textInput.style.position = 'absolute';
textInput.style.left = `${graph.container.scrollLeft + 10}px`;
textInput.style.top = `${graph.container.scrollTop + 10}px`;
graph.container.appendChild(textInput);
restoreFocus = true;
textInput.focus();
textInput.select();
}
}
}
});
// Restores focus on graph this.el and removes text input from DOM
mxEvent.addListener(document, 'keyup', function(evt) {
if (
restoreFocus &&
(evt.keyCode === 224 /* FF */ ||
evt.keyCode === 17 /* Control */ ||
evt.keyCode === 91 ||
evt.keyCode === 93) /* Meta */
) {
restoreFocus = false;
if (!graph.isEditing()) {
graph.container.focus();
}
textInput.parentNode.removeChild(textInput);
}
});
// Inserts the XML for the given cells into the text input for copy
const copyCells = function(graph, cells) {
if (cells.length > 0) {
const clones = graph.cloneCells(cells);
// Checks for orphaned relative children and makes absolute
for (let i = 0; i < clones.length; i++) {
const state = graph.view.getState(cells[i]);
if (state != null) {
const geo = graph.getCellGeometry(clones[i]);
if (geo != null && geo.relative) {
geo.relative = false;
geo.x = state.x / state.view.scale - state.view.translate.x;
geo.y = state.y / state.view.scale - state.view.translate.y;
}
}
}
textInput.value = mxClipboard.cellsToString(clones);
}
textInput.select();
lastPaste = textInput.value;
};
// Handles copy event by putting XML for current selection into text input
mxEvent.addListener(
textInput,
'copy',
mxUtils.bind(this, function(evt) {
if (graph.isEnabled() && !graph.isSelectionEmpty()) {
copyCells(
graph,
mxUtils.sortCells(
graph.model.getTopmostCells(graph.getSelectionCells())
)
);
dx = 0;
dy = 0;
}
})
);
// Handles cut event by removing cells putting XML into text input
mxEvent.addListener(
textInput,
'cut',
mxUtils.bind(this, function(evt) {
if (graph.isEnabled() && !graph.isSelectionEmpty()) {
copyCells(graph, graph.removeCells());
dx = -gs;
dy = -gs;
}
})
);
// Merges XML into existing graph and layers
const importXml = function(xml, dx, dy) {
dx = dx != null ? dx : 0;
dy = dy != null ? dy : 0;
let cells = [];
try {
const doc = mxUtils.parseXml(xml);
const node = doc.documentElement;
if (node != null) {
const model = new mxGraphModel();
const codec = new mxCodec(node.ownerDocument);
codec.decode(node, model);
const childCount = model.getChildCount(model.getRoot());
const targetChildCount = graph.model.getChildCount(
graph.model.getRoot()
);
// Merges existing layers and adds new layers
graph.model.beginUpdate();
try {
for (let i = 0; i < childCount; i++) {
let parent = model.getChildAt(model.getRoot(), i);
// Adds cells to existing layers if not locked
if (targetChildCount > i) {
// Inserts into active layer if only one layer is being pasted
const target =
childCount === 1
? graph.getDefaultParent()
: graph.model.getChildAt(graph.model.getRoot(), i);
if (!graph.isCellLocked(target)) {
const children = model.getChildren(parent);
cells = cells.concat(
graph.importCells(children, dx, dy, target)
);
}
} else {
// Delta is non cascading, needs separate move for layers
parent = graph.importCells(
[parent],
0,
0,
graph.model.getRoot()
)[0];
const children = graph.model.getChildren(parent);
graph.moveCells(children, dx, dy);
cells = cells.concat(children);
}
}
} finally {
graph.model.endUpdate();
}
}
} catch (e) {
alert(e);
throw e;
}
return cells;
};
// Parses and inserts XML into graph
const pasteText = function(text) {
const xml = mxStringUtils.trim(text);
const x =
graph.container.scrollLeft / graph.view.scale - graph.view.translate.x;
const y =
graph.container.scrollTop / graph.view.scale - graph.view.translate.y;
if (xml.length > 0) {
if (lastPaste !== xml) {
lastPaste = xml;
dx = 0;
dy = 0;
} else {
dx += gs;
dy += gs;
}
// Standard paste via control-v
if (xml.substring(0, 14) === '<mxGraphModel>') {
graph.setSelectionCells(importXml(xml, dx, dy));
graph.scrollCellToVisible(graph.getSelectionCell());
}
}
};
// Function to fetch text from paste events
const extractGraphModelFromEvent = function(evt) {
let data = null;
if (evt != null) {
const provider =
evt.dataTransfer != null ? evt.dataTransfer : evt.clipboardData;
if (provider != null) {
data =
mxUtils.indexOf(provider.types, 'text/html') >= 0
? provider.getData('text/html')
: null;
if (
mxUtils.indexOf(
provider.types,
'text/plain' && (data == null || data.length === 0)
)
) {
data = provider.getData('text/plain');
}
}
}
return data;
};
// Handles paste event by parsing and inserting XML
mxEvent.addListener(textInput, 'paste', function(evt) {
// Clears existing contents before paste - should not be needed
// because all text is selected, but doesn't hurt since the
// actual pasting of the new text is delayed in all cases.
textInput.value = '';
if (graph.isEnabled()) {
const xml = extractGraphModelFromEvent(evt);
if (xml != null && xml.length > 0) {
pasteText(xml);
} else {
// Timeout for new value to appear
window.setTimeout(
mxUtils.bind(this, function() {
pasteText(textInput.value);
}),
0
);
}
}
textInput.select();
});
// Enables rubberband selection
if (args.rubberBand)
new mxRubberband(graph);
// Gets the default parent for inserting new cells. This
// is normally the first child of the root (ie. layer 0).
const parent = graph.getDefaultParent();
// Adds cells to the model in a single step
graph.batchUpdate(() => {
const v1 = graph.insertVertex({
parent,
value: 'Hello,',
position: [20, 20],
size: [80, 30],
});
const v2 = graph.insertVertex({
parent,
value: 'World!',
position: [200, 150],
size: [80, 30],
});
const e1 = graph.insertEdge({ parent, source: v1, target: v2 });
});
return container;
}
export const Default = Template.bind({});

View File

@ -0,0 +1,189 @@
import mxgraph from '@mxgraph/core';
import { defaultArgTypes } from '../.storybook/preview';
export default {
title: 'DnD_CopyPaste/DragSource',
argTypes: {
...defaultArgTypes,
rubberBand: {
type: 'boolean',
defaultValue: true
}
}
};
const Template = ({ label, ...args }) => {
const {
mxGraph,
mxDomUtils,
mxRubberband,
mxDragSource,
mxUtils,
mxGestureUtils,
mxEdgeHandler,
mxGraphHandler,
mxGuide,
mxEventUtils,
mxCell,
mxGeometry
} = mxgraph;
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.cursor = 'default';
class MyCustomGuide extends mxGuide {
isEnabledForEvent(evt) {
// Alt disables guides
return !mxEventUtils.isAltDown(evt);
}
}
class MyCustomGraphHandler extends mxGraphHandler {
// Enables guides
guidesEnabled = true;
createGuide() {
return new MyCustomGuide(this.graph, this.getGuideStates());
}
}
class MyCustomEdgeHandler extends mxEdgeHandler {
// Enables snapping waypoints to terminals
snapToTerminals = true;
}
class MyCustomGraph extends mxGraph {
createGraphHandler() {
return new MyCustomGraphHandler(this);
}
createEdgeHandler(state, edgeStyle) {
return new MyCustomEdgeHandler(state, edgeStyle);
}
}
const graphs = [];
// Creates the graph inside the given container
for (let i = 0; i < 2; i++) {
const subContainer = document.createElement('div');
subContainer.style.overflow = 'hidden';
subContainer.style.position = 'relative';
subContainer.style.width = '321px';
subContainer.style.height = '241px';
subContainer.style.background = "url('/images/grid.gif')";
subContainer.style.cursor = 'default';
container.appendChild(subContainer);
const graph = new MyCustomGraph(subContainer);
graph.gridSize = 30;
// Uncomment the following if you want the container
// to fit the size of the graph
// graph.setResizeContainer(true);
// Enables rubberband selection
if (args.rubberBand)
new mxRubberband(graph);
// Gets the default parent for inserting new cells. This
// is normally the first child of the root (ie. layer 0).
const parent = graph.getDefaultParent();
// Adds cells to the model in a single step
graph.batchUpdate(() => {
const v1 = graph.insertVertex({
parent,
value: 'Hello,',
position: [20, 20],
size: [80, 30],
});
const v2 = graph.insertVertex({
parent,
value: 'World!',
position: [200, 150],
size: [80, 30],
});
const e1 = graph.insertEdge({
parent,
source: v1,
target: v2,
});
});
graphs.push(graph);
}
// Returns the graph under the mouse
const graphF = evt => {
const x = mxEventUtils.getClientX(evt);
const y = mxEventUtils.getClientY(evt);
const elt = document.elementFromPoint(x, y);
for (const graph of graphs) {
if (mxDomUtils.isAncestorNode(graph.container, elt)) {
return graph;
}
}
return null;
};
// Inserts a cell at the given location
const funct = (graph, evt, target, x, y) => {
const cell = new mxCell('Test', new mxGeometry(0, 0, 120, 40));
cell.vertex = true;
const cells = graph.importCells([cell], x, y, target);
if (cells != null && cells.length > 0) {
graph.scrollCellToVisible(cells[0]);
graph.setSelectionCells(cells);
}
};
// Creates a DOM node that acts as the drag source
const img = mxUtils.createImage('images/icons48/gear.png');
img.style.width = '48px';
img.style.height = '48px';
container.appendChild(img);
// Creates the element that is being for the actual preview.
const dragElt = document.createElement('div');
dragElt.style.border = 'dashed black 1px';
dragElt.style.width = '120px';
dragElt.style.height = '40px';
// Drag source is configured to use dragElt for preview and as drag icon
// if scalePreview (last) argument is true. Dx and dy are null to force
// the use of the defaults. Note that dx and dy are only used for the
// drag icon but not for the preview.
const ds = mxGestureUtils.makeDraggable(
img,
graphF,
funct,
dragElt,
null,
null,
graphs[0].autoscroll,
true
);
// Redirects feature to global switch. Note that this feature should only be used
// if the the x and y arguments are used in funct to insert the cell.
ds.isGuidesEnabled = () => {
return graphs[0].graphHandler.guidesEnabled;
};
// Restores original drag icon while outside of graph
ds.createDragElement = mxDragSource.prototype.createDragElement;
return container;
}
export const Default = Template.bind({});

View File

@ -0,0 +1,193 @@
import mxgraph from '@mxgraph/core';
import { defaultArgTypes } from '../.storybook/preview';
export default {
title: 'DnD_CopyPaste/Drop',
argTypes: {
...defaultArgTypes,
contextMenu: {
type: 'boolean',
defaultValue: false
},
rubberBand: {
type: 'boolean',
defaultValue: true
}
}
};
const Template = ({ label, ...args }) => {
const {
mxGraph,
mxDomUtils,
mxRubberband,
mxDragSource,
mxUtils,
mxGestureUtils,
mxEdgeHandler,
mxGraphHandler,
mxGuide,
mxEventUtils,
mxEvent,
mxClient
} = mxgraph;
const div = document.createElement('div');
div.innerHTML = 'Drag & drop your images below:<br>';
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';
div.appendChild(container);
// Checks if the browser is supported
const fileSupport =
window.File != null &&
window.FileReader != null &&
window.FileList != null;
if (!fileSupport || !mxClient.isBrowserSupported()) {
// Displays an error message if the browser is not supported.
mxUtils.error('Browser is not supported!', 200, false);
} else {
// Disables the built-in context menu
if (!args.contextMenu)
mxEvent.disableContextMenu(container);
// Creates the graph inside the given this.el
const graph = new mxGraph(container);
// Enables rubberband selection
if (args.rubberBand)
new mxRubberband(graph);
mxEvent.addListener(container, 'dragover', function(evt) {
if (graph.isEnabled()) {
evt.stopPropagation();
evt.preventDefault();
}
});
mxEvent.addListener(container, 'drop', evt => {
if (graph.isEnabled()) {
evt.stopPropagation();
evt.preventDefault();
// Gets drop location point for vertex
const pt = mxUtils.convertPoint(
graph.container,
mxEventUtils.getClientX(evt),
mxEventUtils.getClientY(evt)
);
const tr = graph.view.translate;
const { scale } = graph.view;
const x = pt.x / scale - tr.x;
const y = pt.y / scale - tr.y;
// Converts local images to data urls
const filesArray = evt.dataTransfer.files;
for (let i = 0; i < filesArray.length; i++) {
handleDrop(graph, filesArray[i], x + i * 10, y + i * 10);
}
}
});
}
return div;
}
function handleDrop(graph, file, x, y) {
// Handles each file as a separate insert for simplicity.
// Use barrier to handle multiple files as a single insert.
if (file.type.substring(0, 5) === 'image') {
const reader = new FileReader();
reader.onload = function(e) {
// Gets size of image for vertex
let data = e.target.result;
// SVG needs special handling to add viewbox if missing and
// find initial size from SVG attributes (only for IE11)
if (file.type.substring(0, 9) === 'image/svg') {
const comma = data.indexOf(',');
const svgText = atob(data.substring(comma + 1));
const root = mxUtils.parseXml(svgText);
// Parses SVG to find width and height
if (root != null) {
const svgs = root.getElementsByTagName('svg');
if (svgs.length > 0) {
const svgRoot = svgs[0];
let w = parseFloat(svgRoot.getAttribute('width'));
let h = parseFloat(svgRoot.getAttribute('height'));
// Check if viewBox attribute already exists
const vb = svgRoot.getAttribute('viewBox');
if (vb == null || vb.length === 0) {
svgRoot.setAttribute('viewBox', `0 0 ${w} ${h}`);
}
// Uses width and height from viewbox for
// missing width and height attributes
else if (Number.isNaN(w) || Number.isNaN(h)) {
const tokens = vb.split(' ');
if (tokens.length > 3) {
w = parseFloat(tokens[2]);
h = parseFloat(tokens[3]);
}
}
w = Math.max(1, Math.round(w));
h = Math.max(1, Math.round(h));
data = `data:image/svg+xml,${btoa(
mxUtils.getXml(svgs[0], '\n')
)}`;
graph.insertVertex({
position: [x, y],
size: [w, h],
style: `shape=image;image=${data};`,
});
}
}
} else {
const img = new Image();
img.onload = () => {
const w = Math.max(1, img.width);
const h = Math.max(1, img.height);
// Converts format of data url to cell style value for use in vertex
const semi = data.indexOf(';');
if (semi > 0) {
data =
data.substring(0, semi) +
data.substring(data.indexOf(',', semi + 1));
}
graph.insertVertex({
position: [x, y],
size: [w, h],
style: `shape=image;image=${data};`,
});
};
img.src = data;
}
};
reader.readAsDataURL(file);
}
}
export const Default = Template.bind({});

View File

@ -1,16 +1,11 @@
import mxgraph from '@mxgraph/core';
import { defaultArgTypes } from '../.storybook/preview';
export default {
title: 'Connections/EdgeTolerance',
argTypes: {
width: {
type: 'number',
defaultValue: 800
},
height: {
type: 'number',
defaultValue: 600
}
...defaultArgTypes.argTypes
}
};

View File

@ -0,0 +1,164 @@
import mxgraph from '@mxgraph/core';
import { defaultArgTypes } from '../.storybook/preview';
export default {
title: 'Editing/Editing',
argTypes: {
...defaultArgTypes,
contextMenu: {
type: 'boolean',
defaultValue: false
},
rubberBand: {
type: 'boolean',
defaultValue: true
}
}
};
const Template = ({ label, ...args }) => {
const {
mxGraph,
mxEvent,
mxKeyHandler,
mxUtils,
mxDomUtils,
mxCloneUtils,
mxEventUtils
} = mxgraph;
const div = document.createElement('div');
div.innerHTML = `
<h1>Editing</h1>
This example demonstrates using the in-place editor trigger to specify
the editing value and write the new value into a specific field of the
user object. Wrapping and DOM nodes as labels are also demonstrated
here.
<br />
<br />
Double-click the upper/lower half of the cell to edit different fields
of the user object.
`;
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';
div.appendChild(container);
class MyCustomGraph extends mxGraph {
getLabel(cell) {
// Returns a HTML representation of the cell where the
// upper half is the first value, lower half is second
// value
const table = document.createElement('table');
table.style.height = '100%';
table.style.width = '100%';
const body = document.createElement('tbody');
const tr1 = document.createElement('tr');
const td1 = document.createElement('td');
td1.style.textAlign = 'center';
td1.style.fontSize = '12px';
td1.style.color = '#774400';
mxDomUtils.write(td1, cell.value.first);
const tr2 = document.createElement('tr');
const td2 = document.createElement('td');
td2.style.textAlign = 'center';
td2.style.fontSize = '12px';
td2.style.color = '#774400';
mxDomUtils.write(td2, cell.value.second);
tr1.appendChild(td1);
tr2.appendChild(td2);
body.appendChild(tr1);
body.appendChild(tr2);
table.appendChild(body);
return table;
}
getEditingValue(cell, evt) {
// Returns the editing value for the given cell and event
evt.fieldname = this.__getFieldnameForEvent(cell, evt);
return cell.value[evt.fieldname] || '';
}
__getFieldnameForEvent(cell, evt) {
// Helper method that returns the fieldname to be used for
// a mouse event
if (evt != null) {
// Finds the relative coordinates inside the cell
const point = mxUtils.convertPoint(
this.container,
mxEventUtils.getClientX(evt),
mxEventUtils.getClientY(evt)
);
const state = this.getView().getState(cell);
if (state != null) {
point.x -= state.x;
point.y -= state.y;
// Returns second if mouse in second half of cell
if (point.y > state.height / 2) {
return 'second';
}
}
}
return 'first';
}
labelChanged(cell, newValue, trigger) {
// Sets the new value for the given cell and trigger
const name = trigger != null ? trigger.fieldname : null;
if (name != null) {
// Clones the user object for correct undo and puts
// the new value in the correct field.
const value = mxCloneUtils.clone(cell.value);
value[name] = newValue;
newValue = value;
super.labelChanged(cell, newValue, trigger);
}
}
}
// Creates the graph inside the given container
const graph = new MyCustomGraph(container);
graph.setHtmlLabels(true);
// Adds handling of return and escape keystrokes for editing
const keyHandler = new mxKeyHandler(graph);
// Sample user objects with 2 fields
const value = {};
value.first = 'First value';
value.second = 'Second value';
// Gets the default parent for inserting new cells. This
// is normally the first child of the root (ie. layer 0).
const parent = graph.getDefaultParent();
// Adds cells to the model in a single step
graph.batchUpdate(() => {
const v1 = graph.insertVertex({
parent,
value,
position: [100, 60],
size: [120, 80],
style: 'overflow=fill;',
});
});
return div;
}
export const Default = Template.bind({});

View File

@ -1,11 +1,19 @@
import mxgraph from '@mxgraph/core';
import HelloWorld from './HelloWorld.stories';
import { defaultArgTypes } from '../.storybook/preview';
export default {
title: 'Backgrounds/ExtendCanvas',
argTypes: {
...HelloWorld.argTypes
...defaultArgTypes.argTypes,
contextMenu: {
type: 'boolean',
defaultValue: false
},
rubberBand: {
type: 'boolean',
defaultValue: true
}
}
};
@ -15,9 +23,7 @@ const Template = ({ label, ...args }) => {
mxEvent,
mxRubberband,
mxRectangle,
mxGraphView,
mxPoint,
mxDomHelpers,
mxUtils
} = mxgraph;

View File

@ -0,0 +1,266 @@
import mxgraph from '@mxgraph/core';
import { defaultArgTypes } from '../.storybook/preview';
export default {
title: 'Connections/FixedPoints',
argTypes: {
...defaultArgTypes.argTypes,
rubberBand: {
type: 'boolean',
defaultValue: true
}
}
};
const Template = ({ label, ...args }) => {
const {
mxGraph,
mxRubberband,
mxConnectionHandler,
mxConnectionConstraint,
mxConstraintHandler,
mxPoint,
mxCellState,
mxEdgeHandler
} = mxgraph;
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';
class MyCustomConstraintHandler extends mxConstraintHandler {
// Snaps to fixed points
intersects(icon, point, source, existingEdge) {
return (
!source || existingEdge || mxUtils.intersects(icon.bounds, point)
);
}
}
class MyCustomConnectionHandler extends mxConnectionHandler {
// connectImage = new mxImage('images/connector.gif', 16, 16);
isConnectableCell(cell) {
return false;
}
/*
* Special case: Snaps source of new connections to fixed points
* Without a connect preview in connectionHandler.createEdgeState mouseMove
* and getSourcePerimeterPoint should be overriden by setting sourceConstraint
* sourceConstraint to null in mouseMove and updating it and returning the
* nearest point (cp) in getSourcePerimeterPoint (see below)
*/
updateEdgeState(pt, constraint) {
if (pt != null && this.previous != null) {
const constraints = this.graph.getAllConnectionConstraints(
this.previous
);
let nearestConstraint = null;
let dist = null;
for (let i = 0; i < constraints.length; i++) {
const cp = this.graph.getConnectionPoint(
this.previous,
constraints[i]
);
if (cp != null) {
const tmp =
(cp.x - pt.x) * (cp.x - pt.x) + (cp.y - pt.y) * (cp.y - pt.y);
if (dist == null || tmp < dist) {
nearestConstraint = constraints[i];
dist = tmp;
}
}
}
if (nearestConstraint != null) {
this.sourceConstraint = nearestConstraint;
}
// In case the edge style must be changed during the preview:
// this.edgeState.style['edgeStyle'] = 'orthogonalEdgeStyle';
// And to use the new edge style in the new edge inserted into the graph,
// update the cell style as follows:
// this.edgeState.cell.style = mxUtils.setStyle(this.edgeState.cell.style, 'edgeStyle', this.edgeState.style['edgeStyle']);
}
return super.updateEdgeState(pt, constraint);
}
createEdgeState(me) {
// Connect preview
const edge = this.graph.createEdge(
null,
null,
null,
null,
null,
'edgeStyle=orthogonalEdgeStyle'
);
return new mxCellState(
this.graph.view,
edge,
this.graph.getCellStyle(edge)
);
}
}
class MyCustomEdgeHandler extends mxEdgeHandler {
// Disables floating connections (only use with no connect image)
isConnectableCell(cell) {
return graph.connectionHandler.isConnectableCell(cell);
}
}
class MyCustomGraph extends mxGraph {
createConnectionHandler() {
const r = new MyCustomConnectionHandler();
r.constraintHandler = new MyCustomConstraintHandler(this);
return r;
}
createEdgeHandler(state, edgeStyle) {
const r = new MyCustomEdgeHandler(state, edgeStyle);
r.constraintHandler = new MyCustomConstraintHandler(this);
return r;
}
getAllConnectionConstraints(terminal) {
if (terminal != null && this.model.isVertex(terminal.cell)) {
return [
new mxConnectionConstraint(new mxPoint(0, 0), true),
new mxConnectionConstraint(new mxPoint(0.5, 0), true),
new mxConnectionConstraint(new mxPoint(1, 0), true),
new mxConnectionConstraint(new mxPoint(0, 0.5), true),
new mxConnectionConstraint(new mxPoint(1, 0.5), true),
new mxConnectionConstraint(new mxPoint(0, 1), true),
new mxConnectionConstraint(new mxPoint(0.5, 1), true),
new mxConnectionConstraint(new mxPoint(1, 1), true),
];
}
return null;
}
}
// Creates the graph inside the given container
const graph = new MyCustomGraph(container);
graph.setConnectable(true);
// Enables rubberband selection
if (args.rubberBand)
new mxRubberband(graph);
// Gets the default parent for inserting new cells. This
// is normally the first child of the root (ie. layer 0).
const parent = graph.getDefaultParent();
// Adds cells to the model in a single step
graph.batchUpdate(() => {
const v1 = graph.insertVertex({
parent,
value: 'Hello,',
position: [20, 20],
size: [80, 60],
style: 'shape=triangle;perimeter=trianglePerimeter',
});
const v2 = graph.insertVertex({
parent,
value: 'World!',
position: [200, 150],
size: [80, 60],
style: 'shape=ellipse;perimeter=ellipsePerimeter',
});
const v3 = graph.insertVertex({
parent,
value: 'Hello,',
position: [200, 20],
size: [80, 30],
});
const e1 = graph.insertEdge({
parent,
value: '',
source: v1,
target: v2,
style:
'edgeStyle=elbowEdgeStyle;elbow=horizontal;' +
'exitX=0.5;exitY=1;exitPerimeter=1;entryX=0;entryY=0;entryPerimeter=1;',
});
const e2 = graph.insertEdge({
parent,
value: '',
source: v3,
target: v2,
style:
'edgeStyle=elbowEdgeStyle;elbow=horizontal;orthogonal=0;' +
'entryX=0;entryY=0;entryPerimeter=1;',
});
});
// Use this code to snap the source point for new connections without a connect preview,
// ie. without an overridden graph.connectionHandler.createEdgeState
/*
let mxConnectionHandlerMouseMove = mxConnectionHandler.prototype.mouseMove;
mxConnectionHandler.prototype.mouseMove = function(sender, me)
{
this.sourceConstraint = null;
mxConnectionHandlerMouseMove.apply(this, arguments);
};
let mxConnectionHandlerGetSourcePerimeterPoint = mxConnectionHandler.prototype.getSourcePerimeterPoint;
mxConnectionHandler.prototype.getSourcePerimeterPoint = function(state, pt, me)
{
let result = null;
if (this.previous != null && pt != null)
{
let constraints = this.graph.getAllConnectionConstraints(this.previous);
let nearestConstraint = null;
let nearest = null;
let dist = null;
for (let i = 0; i < constraints.length; i++)
{
let cp = this.graph.getConnectionPoint(this.previous, constraints[i]);
if (cp != null)
{
let tmp = (cp.x - pt.x) * (cp.x - pt.x) + (cp.y - pt.y) * (cp.y - pt.y);
if (dist == null || tmp < dist)
{
nearestConstraint = constraints[i];
nearest = cp;
dist = tmp;
}
}
}
if (nearestConstraint != null)
{
this.sourceConstraint = nearestConstraint;
result = nearest;
}
}
if (result == null)
{
result = mxConnectionHandlerGetSourcePerimeterPoint.apply(this, arguments);
}
return result;
};
*/
return container;
}
export const Default = Template.bind({});

View File

@ -1,11 +1,19 @@
import mxgraph from '@mxgraph/core';
import HelloWorld from './HelloWorld.stories';
import { defaultArgTypes } from '../.storybook/preview';
export default {
title: 'Backgrounds/Grid',
argTypes: {
...HelloWorld.argTypes
...defaultArgTypes.argTypes,
contextMenu: {
type: 'boolean',
defaultValue: false
},
rubberBand: {
type: 'boolean',
defaultValue: true
}
}
};

View File

@ -0,0 +1,115 @@
import mxgraph from '@mxgraph/core';
import { defaultArgTypes } from '../.storybook/preview';
export default {
title: 'Connections/HelloPort',
argTypes: {
...defaultArgTypes.argTypes,
rubberBand: {
type: 'boolean',
defaultValue: true
}
}
};
const Template = ({ label, ...args }) => {
const {
mxGraph,
mxRubberband,
mxEdgeStyle,
mxPoint,
mxConstants,
mxDomHelpers
} = mxgraph;
const div = 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';
div.appendChild(container);
// Creates the graph inside the given container
const graph = new mxGraph(container);
graph.setConnectable(true);
graph.setTooltips(true);
// Sets the default edge style
const style = graph.getStylesheet().getDefaultEdgeStyle();
style[mxConstants.STYLE_EDGE] = mxEdgeStyle.ElbowConnector;
// Ports are not used as terminals for edges, they are
// only used to compute the graphical connection point
graph.isPort = function(cell) {
const geo = this.getCellGeometry(cell);
return geo != null ? geo.relative : false;
};
// Implements a tooltip that shows the actual
// source and target of an edge
graph.getTooltipForCell = function(cell) {
if (this.model.isEdge(cell)) {
return `${this.convertValueToString(
this.model.getTerminal(cell, true)
)} => ${this.convertValueToString(
this.model.getTerminal(cell, false)
)}`;
}
return mxGraph.prototype.getTooltipForCell.apply(this, arguments);
};
// Removes the folding icon and disables any folding
graph.isCellFoldable = function(cell) {
return false;
};
// Enables rubberband selection
if (args.rubberBand)
new mxRubberband(graph);
// Gets the default parent for inserting new cells. This
// is normally the first child of the root (ie. layer 0).
const parent = graph.getDefaultParent();
// Adds cells to the model in a single step
graph.getModel().beginUpdate();
try {
const v1 = graph.insertVertex(parent, null, 'Hello', 20, 80, 80, 30);
v1.setConnectable(false);
const v11 = graph.insertVertex(v1, null, '', 1, 1, 10, 10);
v11.geometry.offset = new mxPoint(-5, -5);
v11.geometry.relative = true;
const v12 = graph.insertVertex(v1, null, '', 1, 0, 10, 10);
v12.geometry.offset = new mxPoint(-5, -5);
v12.geometry.relative = true;
const v2 = graph.insertVertex(parent, null, 'World!', 200, 150, 80, 30);
const v3 = graph.insertVertex(parent, null, 'World2', 200, 20, 80, 30);
var e1 = graph.insertEdge(parent, null, '', v11, v2);
var e1 = graph.insertEdge(parent, null, '', v12, v3);
} finally {
// Updates the display
graph.getModel().endUpdate();
}
const controller = document.createElement('div');
div.appendChild(controller);
const button = mxDomHelpers.button('View XML', function() {
const encoder = new mxCodec();
const node = encoder.encode(graph.getModel());
mxUtils.popup(mxUtils.getPrettyXml(node), true);
});
controller.appendChild(button);
return div;
}
export const Default = Template.bind({});

View File

@ -1,16 +1,11 @@
import mxgraph from '@mxgraph/core';
import { defaultArgTypes } from '../.storybook/preview';
export default {
title: 'Basic/HelloWorld',
argTypes: {
width: {
type: 'number',
defaultValue: 800
},
height: {
type: 'number',
defaultValue: 600
},
...defaultArgTypes,
contextMenu: {
type: 'boolean',
defaultValue: false
@ -30,6 +25,7 @@ const Template = ({ label, ...args }) => {
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';
if (!args.contextMenu)

View File

@ -0,0 +1,177 @@
import mxgraph from '@mxgraph/core';
import { defaultArgTypes } from '../.storybook/preview';
export default {
title: 'Connections/Orthogonal',
argTypes: {
...defaultArgTypes.argTypes
}
};
const Template = ({ label, ...args }) => {
const {
mxGraph,
mxRubberband,
mxConnectionHandler,
mxGraphHandler,
mxGuide,
mxPoint,
mxCellState,
mxEdgeHandler,
mxGraphView
} = mxgraph;
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';
// 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;
// Enables orthogonal connect preview in IE
mxConnectionHandler.prototype.movePreviewAway = false;
// Creates the graph inside the given container
const graph = new mxGraph(container);
graph.disconnectOnMove = false;
graph.foldingEnabled = false;
graph.cellsResizable = false;
graph.extendParents = false;
graph.setConnectable(true);
// Implements perimeter-less connection points as fixed points (computed before the edge style).
graph.view.updateFixedTerminalPoint = function(
edge,
terminal,
source,
constraint
) {
mxGraphView.prototype.updateFixedTerminalPoint.apply(this, arguments);
const pts = edge.absolutePoints;
const pt = pts[source ? 0 : pts.length - 1];
if (
terminal != null &&
pt == null &&
this.getPerimeterFunction(terminal) == null
) {
edge.setAbsoluteTerminalPoint(
new mxPoint(
this.getRoutingCenterX(terminal),
this.getRoutingCenterY(terminal)
),
source
);
}
};
// Changes the default edge style
graph.getStylesheet().getDefaultEdgeStyle().edgeStyle =
'orthogonalEdgeStyle';
delete graph.getStylesheet().getDefaultEdgeStyle().endArrow;
// Implements the connect preview
graph.connectionHandler.createEdgeState = function(me) {
const edge = graph.createEdge(null, null, null, null, null);
return new mxCellState(
this.graph.view,
edge,
this.graph.getCellStyle(edge)
);
};
// Uncomment the following if you want the container
// to fit the size of the graph
// graph.setResizeContainer(true);
// Enables rubberband selection
if (args.rubberBand)
new mxRubberband(graph);
// Gets the default parent for inserting new cells. This
// is normally the first child of the root (ie. layer 0).
const parent = graph.getDefaultParent();
// Adds cells to the model in a single step
graph.getModel().beginUpdate();
try {
const v1 = graph.insertVertex(parent, null, '', 40, 40, 40, 30);
v1.setConnectable(false);
const v11 = graph.insertVertex(
v1,
null,
'',
0.5,
0,
10,
40,
'portConstraint=northsouth;',
true
);
v11.geometry.offset = new mxPoint(-5, -5);
const v12 = graph.insertVertex(
v1,
null,
'',
0,
0.5,
10,
10,
'portConstraint=west;shape=triangle;direction=west;perimeter=none;' +
'routingCenterX=-0.5;routingCenterY=0;',
true
);
v12.geometry.offset = new mxPoint(-10, -5);
const v13 = graph.insertVertex(
v1,
null,
'',
1,
0.5,
10,
10,
'portConstraint=east;shape=triangle;direction=east;perimeter=none;' +
'routingCenterX=0.5;routingCenterY=0;',
true
);
v13.geometry.offset = new mxPoint(0, -5);
const v2 = graph.addCell(graph.getModel().cloneCell(v1));
v2.geometry.x = 200;
v2.geometry.y = 60;
const v3 = graph.addCell(graph.getModel().cloneCell(v1));
v3.geometry.x = 40;
v3.geometry.y = 150;
const v4 = graph.addCell(graph.getModel().cloneCell(v1));
v4.geometry.x = 200;
v4.geometry.y = 170;
graph.insertEdge(parent, null, '', v1.getChildAt(2), v2.getChildAt(1));
graph.insertEdge(parent, null, '', v2.getChildAt(2), v3.getChildAt(1));
graph.insertEdge(parent, null, '', v3.getChildAt(2), v4.getChildAt(1));
} finally {
// Updates the display
graph.getModel().endUpdate();
}
return container;
}
export const Default = Template.bind({});

View File

@ -0,0 +1,279 @@
import mxgraph from '@mxgraph/core';
import { defaultArgTypes } from '../.storybook/preview';
export default {
title: 'Connections/PortRefs',
argTypes: {
...defaultArgTypes.argTypes
}
};
const Template = ({ label, ...args }) => {
const {
mxGraph,
mxRubberband,
mxPoint,
mxEdgeHandler,
mxConstraintHandler,
mxImage,
mxShape,
mxTriangle,
mxConstants,
mxConnectionConstraint
} = mxgraph;
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';
// Replaces the port image
mxConstraintHandler.prototype.pointImage = new mxImage(
'/images/dot.gif',
10,
10
);
const graph = new mxGraph(container);
graph.setConnectable(true);
// Disables automatic handling of ports. This disables the reset of the
// respective style in mxGraph.cellConnected. Note that this feature may
// be useful if floating and fixed connections are combined.
graph.setPortsEnabled(false);
// 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).
const parent = graph.getDefaultParent();
// Ports are equal for all shapes...
const ports = new Array();
// NOTE: Constraint is used later for orthogonal edge routing (currently ignored)
ports.w = { x: 0, y: 0.5, perimeter: true, constraint: 'west' };
ports.e = { x: 1, y: 0.5, perimeter: true, constraint: 'east' };
ports.n = { x: 0.5, y: 0, perimeter: true, constraint: 'north' };
ports.s = { x: 0.5, y: 1, perimeter: true, constraint: 'south' };
ports.nw = { x: 0, y: 0, perimeter: true, constraint: 'north west' };
ports.ne = { x: 1, y: 0, perimeter: true, constraint: 'north east' };
ports.sw = { x: 0, y: 1, perimeter: true, constraint: 'south west' };
ports.se = { x: 1, y: 1, perimeter: true, constraint: 'south east' };
// ... except for triangles
const ports2 = new Array();
// NOTE: Constraint is used later for orthogonal edge routing (currently ignored)
ports2.in1 = { x: 0, y: 0, perimeter: true, constraint: 'west' };
ports2.in2 = { x: 0, y: 0.25, perimeter: true, constraint: 'west' };
ports2.in3 = { x: 0, y: 0.5, perimeter: true, constraint: 'west' };
ports2.in4 = { x: 0, y: 0.75, perimeter: true, constraint: 'west' };
ports2.in5 = { x: 0, y: 1, perimeter: true, constraint: 'west' };
ports2.out1 = {
x: 0.5,
y: 0,
perimeter: true,
constraint: 'north east',
};
ports2.out2 = { x: 1, y: 0.5, perimeter: true, constraint: 'east' };
ports2.out3 = {
x: 0.5,
y: 1,
perimeter: true,
constraint: 'south east',
};
// Extends shapes classes to return their ports
mxShape.prototype.getPorts = function() {
return ports;
};
mxTriangle.prototype.getPorts = function() {
return ports2;
};
// Disables floating connections (only connections via ports allowed)
graph.connectionHandler.isConnectableCell = function(cell) {
return false;
};
mxEdgeHandler.prototype.isConnectableCell = function(cell) {
return graph.connectionHandler.isConnectableCell(cell);
};
// Disables existing port functionality
graph.view.getTerminalPort = function(state, terminal, source) {
return terminal;
};
// Returns all possible ports for a given terminal
graph.getAllConnectionConstraints = function(terminal, source) {
if (
terminal != null &&
terminal.shape != null &&
terminal.shape.stencil != null
) {
// for stencils with existing constraints...
if (terminal.shape.stencil != null) {
return terminal.shape.stencil.constraints;
}
} else if (terminal != null && this.model.isVertex(terminal.cell)) {
if (terminal.shape != null) {
const ports = terminal.shape.getPorts();
const cstrs = new Array();
for (const id in ports) {
const port = ports[id];
const cstr = new mxConnectionConstraint(
new mxPoint(port.x, port.y),
port.perimeter
);
cstr.id = id;
cstrs.push(cstr);
}
return cstrs;
}
}
return null;
};
// Sets the port for the given connection
graph.setConnectionConstraint = function(
edge,
terminal,
source,
constraint
) {
if (constraint != null) {
const key = source
? mxConstants.STYLE_SOURCE_PORT
: mxConstants.STYLE_TARGET_PORT;
if (constraint == null || constraint.id == null) {
this.setCellStyles(key, null, [edge]);
} else if (constraint.id != null) {
this.setCellStyles(key, constraint.id, [edge]);
}
}
};
// Returns the port for the given connection
graph.getConnectionConstraint = function(edge, terminal, source) {
const key = source
? mxConstants.STYLE_SOURCE_PORT
: mxConstants.STYLE_TARGET_PORT;
const id = edge.style[key];
if (id != null) {
const c = new mxConnectionConstraint(null, null);
c.id = id;
return c;
}
return null;
};
// Returns the actual point for a port by redirecting the constraint to the port
const graphGetConnectionPoint = graph.getConnectionPoint;
graph.getConnectionPoint = function(vertex, constraint) {
if (constraint.id != null && vertex != null && vertex.shape != null) {
const port = vertex.shape.getPorts()[constraint.id];
if (port != null) {
constraint = new mxConnectionConstraint(
new mxPoint(port.x, port.y),
port.perimeter
);
}
}
return graphGetConnectionPoint.apply(this, arguments);
};
// Adds cells to the model in a single step
graph.getModel().beginUpdate();
try {
const v1 = graph.insertVertex(parent, null, 'A', 20, 20, 100, 40);
const v2 = graph.insertVertex(
parent,
null,
'B',
80,
100,
100,
100,
'shape=ellipse;perimeter=ellipsePerimeter'
);
const v3 = graph.insertVertex(
parent,
null,
'C',
190,
30,
100,
60,
'shape=triangle;perimeter=trianglePerimeter;direction=south'
);
const e1 = graph.insertEdge(
parent,
null,
'',
v1,
v2,
'sourcePort=s;targetPort=nw'
);
const e2 = graph.insertEdge(
parent,
null,
'',
v1,
v3,
'sourcePort=e;targetPort=out3'
);
} finally {
// Updates the display
graph.getModel().endUpdate();
}
// Comming soon... Integration with orthogonal edge style
// Sets default edge style to use port constraints (needs to be moved up when uncommented)
// graph.getStylesheet().getDefaultEdgeStyle()['edgeStyle'] = 'orthogonalEdgeStyle';
/* let mxUtilsGetPortConstraints = mxUtils.getPortConstraints;
mxUtils.getPortConstraints = function(terminal, edge, source, defaultValue)
{
let key = (source) ? mxConstants.STYLE_SOURCE_PORT : mxConstants.STYLE_TARGET_PORT;
let id = edge.style[key];
let port = terminal.shape.getPorts()[id];
// TODO: Add support for rotation, direction
if (port != null)
{
return port.constraint;
}
return mxUtilsGetPortConstraints.apply(this, arguments);
};
// Connect preview
graph.connectionHandler.createEdgeState = function(me)
{
let edge = graph.createEdge(null, null, null, null, null);
return new mxCellState(this.graph.view, edge, this.graph.getCellStyle(edge));
};
*/
return container;
}
export const Default = Template.bind({});

View File

@ -1,270 +0,0 @@
/**
* Copyright (c) 2006-2013, JGraph Ltd
* Converted to ES9 syntax/React by David Morrissey 2021
*/
import React from 'react';
import mxEvent from '../../mxgraph/util/event/mxEvent';
import mxGraph from '../../mxgraph/view/graph/mxGraph';
import mxRubberband from '../../mxgraph/handler/mxRubberband';
import mxRectangle from '../../mxgraph/util/datatypes/mxRectangle';
import mxUtils from '../../mxgraph/util/mxUtils';
import mxPoint from '../../mxgraph/util/datatypes/mxPoint';
class ExtendCanvas extends React.Component {
constructor(props) {
super(props);
}
render() {
// A container for the graph
return (
<>
<h1>Extend canvas</h1>
This example demonstrates implementing an infinite canvas with
scrollbars.
<div
ref={el => {
this.el = el;
}}
style={{
position: 'relative',
overflow: 'auto',
height: '241px',
background: "url('editors/images/grid.gif')",
cursor: 'default',
}}
/>
</>
);
}
componentDidMount() {
// Disables the built-in context menu
mxEvent.disableContextMenu(this.el);
/**
* Specifies the size of the size for "tiles" to be used for a graph with
* scrollbars but no visible background page. A good value is large
* enough to reduce the number of repaints that is caused for auto-
* translation, which depends on this value, and small enough to give
* a small empty buffer around the graph. Default is 400x400.
*/
const scrollTileSize = new mxRectangle(0, 0, 400, 400);
class MyCustomGraph extends mxGraph {
/**
* Returns the padding for pages in page view with scrollbars.
*/
getPagePadding() {
return new mxPoint(
Math.max(0, Math.round(this.container.offsetWidth - 34)),
Math.max(0, Math.round(this.container.offsetHeight - 34))
);
}
/**
* Returns the size of the page format scaled with the page size.
*/
getPageSize() {
return this.pageVisible
? new mxRectangle(
0,
0,
this.pageFormat.width * this.pageScale,
this.pageFormat.height * this.pageScale
)
: scrollTileSize;
}
/**
* Returns a rectangle describing the position and count of the
* background pages, where x and y are the position of the top,
* left page and width and height are the vertical and horizontal
* page count.
*/
getPageLayout() {
const size = this.pageVisible ? this.getPageSize() : scrollTileSize;
const bounds = this.getGraphBounds();
if (bounds.width === 0 || bounds.height === 0) {
return new mxRectangle(0, 0, 1, 1);
}
// Computes untransformed graph bounds
const x = Math.ceil(bounds.x / this.view.scale - this.view.translate.x);
const y = Math.ceil(bounds.y / this.view.scale - this.view.translate.y);
const w = Math.floor(bounds.width / this.view.scale);
const h = Math.floor(bounds.height / this.view.scale);
const x0 = Math.floor(x / size.width);
const y0 = Math.floor(y / size.height);
const w0 = Math.ceil((x + w) / size.width) - x0;
const h0 = Math.ceil((y + h) / size.height) - y0;
return new mxRectangle(x0, y0, w0, h0);
}
getPreferredPageSize(bounds, width, height) {
const pages = this.getPageLayout();
const size = this.getPageSize();
return new mxRectangle(
0,
0,
pages.width * size.width,
pages.height * size.height
);
}
sizeDidChange() {
if (this.container != null && mxUtils.hasScrollbars(this.container)) {
const pages = this.getPageLayout();
const pad = this.getPagePadding();
const size = this.getPageSize();
// Updates the minimum graph size
const minw = Math.ceil(
(2 * pad.x) / this.view.scale + pages.width * size.width
);
const minh = Math.ceil(
(2 * pad.y) / this.view.scale + pages.height * size.height
);
const min = this.minimumGraphSize;
// LATER: Fix flicker of scrollbar size in IE quirks mode
// after delayed call in window.resize event handler
if (min == null || min.width !== minw || min.height !== minh) {
this.minimumGraphSize = new mxRectangle(0, 0, minw, minh);
}
// Updates auto-translate to include padding and graph size
const dx = pad.x / this.view.scale - pages.x * size.width;
const dy = pad.y / this.view.scale - pages.y * size.height;
if (
!this.autoTranslate &&
(this.view.translate.x !== dx || this.view.translate.y !== dy)
) {
this.autoTranslate = true;
this.view.x0 = pages.x;
this.view.y0 = pages.y;
// NOTE: THIS INVOKES THIS METHOD AGAIN. UNFORTUNATELY THERE IS NO WAY AROUND THIS SINCE THE
// BOUNDS ARE KNOWN AFTER THE VALIDATION AND SETTING THE TRANSLATE TRIGGERS A REVALIDATION.
// SHOULD MOVE TRANSLATE/SCALE TO VIEW.
const tx = this.view.translate.x;
const ty = this.view.translate.y;
this.view.setTranslate(dx, dy);
this.container.scrollLeft += (dx - tx) * this.view.scale;
this.container.scrollTop += (dy - ty) * this.view.scale;
this.autoTranslate = false;
return;
}
super.sizeDidChange();
}
}
}
// Creates the graph inside the given container
const graph = (this.graph = new MyCustomGraph(this.el));
graph.panningHandler.ignoreCell = true;
graph.setPanning(true);
// Fits the number of background pages to the graph
graph.view.getBackgroundPageBounds = function() {
const layout = this.graph.getPageLayout();
const page = this.graph.getPageSize();
return new mxRectangle(
this.scale * (this.translate.x + layout.x * page.width),
this.scale * (this.translate.y + layout.y * page.height),
this.scale * layout.width * page.width,
this.scale * layout.height * page.height
);
};
/**
* Guesses autoTranslate to avoid another repaint (see below).
* Works if only the scale of the graph changes or if pages
* are visible and the visible pages do not change.
*/
const graphViewValidate = graph.view.validate;
graph.view.validate = function() {
if (
this.graph.container != null &&
mxUtils.hasScrollbars(this.graph.container)
) {
const pad = this.graph.getPagePadding();
const size = this.graph.getPageSize();
// Updating scrollbars here causes flickering in quirks and is not needed
// if zoom method is always used to set the current scale on the graph.
const tx = this.translate.x;
const ty = this.translate.y;
this.translate.x = pad.x / this.scale - (this.x0 || 0) * size.width;
this.translate.y = pad.y / this.scale - (this.y0 || 0) * size.height;
}
graphViewValidate.apply(this, arguments);
};
// 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).
const parent = graph.getDefaultParent();
// Adds cells to the model in a single step
graph.batchUpdate(() => {
const v1 = graph.insertVertex({
parent,
value: 'Hello,',
position: [20, 20],
size: [80, 30],
});
const v2 = graph.insertVertex({
parent,
value: 'World!',
position: [200, 150],
size: [80, 30],
});
const e1 = graph.insertEdge({
parent,
source: v1,
target: v2,
});
});
// Sets initial scrollbar positions
window.setTimeout(() => {
const bounds = graph.getGraphBounds();
const width = Math.max(
bounds.width,
scrollTileSize.width * graph.view.scale
);
const height = Math.max(
bounds.height,
scrollTileSize.height * graph.view.scale
);
graph.container.scrollTop = Math.floor(
Math.max(
0,
bounds.y - Math.max(20, (graph.container.clientHeight - height) / 4)
)
);
graph.container.scrollLeft = Math.floor(
Math.max(
0,
bounds.x - Math.max(0, (graph.container.clientWidth - width) / 2)
)
);
}, 0);
}
}
export default ExtendCanvas;

View File

@ -1,218 +0,0 @@
/**
* Copyright (c) 2006-2013, JGraph Ltd
* Converted to ES9 syntax/React by David Morrissey 2021
*/
import React from 'react';
import mxEvent from '../../mxgraph/util/event/mxEvent';
import mxGraph from '../../mxgraph/view/graph/mxGraph';
import mxRubberband from '../../mxgraph/handler/mxRubberband';
import mxPoint from '../../mxgraph/util/datatypes/mxPoint';
import mxLog from '../../mxgraph/util/gui/mxLog';
import mxUtils from '../../mxgraph/util/mxUtils';
import mxGraphView from '../../mxgraph/view/graph/mxGraphView';
class Grid extends React.Component {
constructor(props) {
super(props);
}
render() {
// A container for the graph
return (
<>
<h1>Grid</h1>
This example demonstrates drawing a grid dynamically using HTML 5
canvas.
<div
ref={el => {
this.el = el;
}}
style={{
overflow: 'hidden',
height: '481px',
cursor: 'default',
}}
/>
<div
ref={el => {
this.el2 = el;
}}
/>
</>
);
}
componentDidMount() {
mxEvent.disableContextMenu(this.el);
// Creates the graph inside the given container
const graph = new mxGraph(this.el);
graph.graphHandler.scaleGrid = true;
graph.setPanning(true);
// Enables rubberband selection
new mxRubberband(graph);
let repaintGrid;
// Create grid dynamically (requires canvas)
(function() {
try {
const canvas = document.createElement('canvas');
canvas.style.position = 'absolute';
canvas.style.top = '0px';
canvas.style.left = '0px';
canvas.style.zIndex = -1;
graph.container.appendChild(canvas);
const ctx = canvas.getContext('2d');
// Modify event filtering to accept canvas as container
const mxGraphViewIsContainerEvent =
mxGraphView.prototype.isContainerEvent;
mxGraphView.prototype.isContainerEvent = function(evt) {
return (
mxGraphViewIsContainerEvent.apply(this, arguments) ||
mxEvent.getSource(evt) === canvas
);
};
let s = 0;
let gs = 0;
let tr = new mxPoint();
let w = 0;
let h = 0;
repaintGrid = function() {
if (ctx != null) {
const bounds = graph.getGraphBounds();
const width = Math.max(
bounds.x + bounds.width,
graph.container.clientWidth
);
const height = Math.max(
bounds.y + bounds.height,
graph.container.clientHeight
);
const sizeChanged = width !== w || height !== h;
if (
graph.view.scale !== s ||
graph.view.translate.x !== tr.x ||
graph.view.translate.y !== tr.y ||
gs !== graph.gridSize ||
sizeChanged
) {
tr = graph.view.translate.clone();
s = graph.view.scale;
gs = graph.gridSize;
w = width;
h = height;
// Clears the background if required
if (!sizeChanged) {
ctx.clearRect(0, 0, w, h);
} else {
canvas.setAttribute('width', w);
canvas.setAttribute('height', h);
}
const tx = tr.x * s;
const ty = tr.y * s;
// Sets the distance of the grid lines in pixels
const minStepping = graph.gridSize;
let stepping = minStepping * s;
if (stepping < minStepping) {
const count =
Math.round(Math.ceil(minStepping / stepping) / 2) * 2;
stepping = count * stepping;
}
const xs = Math.floor((0 - tx) / stepping) * stepping + tx;
let xe = Math.ceil(w / stepping) * stepping;
const ys = Math.floor((0 - ty) / stepping) * stepping + ty;
let ye = Math.ceil(h / stepping) * stepping;
xe += Math.ceil(stepping);
ye += Math.ceil(stepping);
const ixs = Math.round(xs);
const ixe = Math.round(xe);
const iys = Math.round(ys);
const iye = Math.round(ye);
// Draws the actual grid
ctx.strokeStyle = '#f6f6f6';
ctx.beginPath();
for (let x = xs; x <= xe; x += stepping) {
x = Math.round((x - tx) / stepping) * stepping + tx;
const ix = Math.round(x);
ctx.moveTo(ix + 0.5, iys + 0.5);
ctx.lineTo(ix + 0.5, iye + 0.5);
}
for (let y = ys; y <= ye; y += stepping) {
y = Math.round((y - ty) / stepping) * stepping + ty;
const iy = Math.round(y);
ctx.moveTo(ixs + 0.5, iy + 0.5);
ctx.lineTo(ixe + 0.5, iy + 0.5);
}
ctx.closePath();
ctx.stroke();
}
}
};
} catch (e) {
mxLog.show();
mxLog.debug('Using background image');
this.el.style.backgroundImage = "url('editors/images/grid.gif')";
}
const mxGraphViewValidateBackground =
mxGraphView.prototype.validateBackground;
mxGraphView.prototype.validateBackground = function() {
mxGraphViewValidateBackground.apply(this, arguments);
repaintGrid();
};
})();
// Gets the default parent for inserting new cells. This
// is normally the first child of the root (ie. layer 0).
const parent = graph.getDefaultParent();
// Adds cells to the model in a single step
graph.getModel().beginUpdate();
try {
const v1 = graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30);
const v2 = graph.insertVertex(parent, null, 'World!', 200, 150, 80, 30);
const e1 = graph.insertEdge(parent, null, '', v1, v2);
} finally {
// Updates the display
graph.getModel().endUpdate();
}
graph.centerZoom = false;
this.el2.appendChild(
mxUtils.button('+', function() {
graph.zoomIn();
})
);
this.el2.appendChild(
mxUtils.button('-', function() {
graph.zoomOut();
})
);
}
}
export default Grid;

View File

@ -1,14 +0,0 @@
import React from 'react';
import Grid from './Grid';
import Preview from '../Previews';
import ExtendCanvas from './ExtendCanvas';
import PageTabs from '../PageTabs';
export default function _Backgrounds() {
return (
<PageTabs curPageURL="/backgrounds">
<Preview sourceKey="ExtendCanvas" content={<ExtendCanvas />} />
<Preview sourceKey="Grid" content={<Grid />} />
</PageTabs>
);
}

View File

@ -1,75 +0,0 @@
/*
* Copyright (c) 2006-2018, JGraph Ltd
* Converted to ES9 syntax/React by David Morrissey 2021
*/
import React from 'react';
import mxEvent from '../../mxgraph/util/event/mxEvent';
import mxGraph from '../../mxgraph/view/graph/mxGraph';
import mxRubberband from '../../mxgraph/handler/mxRubberband';
class HelloWorld extends React.Component {
constructor(props) {
super(props);
}
render() {
// A container for the graph with a grid wallpaper
return (
<>
<h1>Hello, World!</h1>
This example demonstrates using a DOM node to create a graph and adding
vertices and edges.
<div
ref={el => {
this.el = el;
}}
style={{
position: 'relative',
overflow: 'hidden',
height: '241px',
background: "url('editors/images/grid.gif')",
cursor: 'default',
}}
/>
</>
);
}
componentDidMount() {
// Create a sample graph in the DOM node with the specified ID.
mxEvent.disableContextMenu(this.el); // Disable the built-in context menu
const graph = new mxGraph(this.el); // Create the graph inside the given container
new mxRubberband(graph); // Enable rubberband selection
// Get the default parent for inserting new cells. This
// is normally the first child of the root (ie. layer 0).
const parent = graph.getDefaultParent();
graph.batchUpdate(() => {
// Add cells to the model in a single step
const vertex1 = graph.insertVertex({
parent,
value: 'Hello',
position: [20, 20],
size: [80, 30],
relative: false,
});
const vertex2 = graph.insertVertex({
parent,
value: 'World!',
position: [200, 150],
size: [80, 30],
relative: false,
});
const edge = graph.insertEdge({
parent,
// value: 'to the',
source: vertex1,
target: vertex2,
});
});
}
}
export default HelloWorld;

View File

@ -1,57 +0,0 @@
/**
* Copyright (c) 2006-2013, JGraph Ltd
Template. This is used as a template HTML file by the
backends to demonstrate the deployment of the client with a graph embedded
in the page as XML data (see graph variable in the onload-handler).
*** THIS FILE MUST BE DEPLOYED BY ONE OF THE BACKENDS! ***
*/
import React from 'react';
import mxGraph from '../../mxgraph/view/graph/mxGraph';
import mxRubberband from '../../mxgraph/handler/mxRubberband';
import mxUtils from '../../mxgraph/util/mxUtils';
import mxCodec from '../../mxgraph/serialization/mxCodec';
class Template extends React.Component {
constructor(props) {
super(props);
}
render() {
// A container for the graph
return (
<>
<h1>Hello, World!</h1>
<div
ref={el => {
this.el = el;
}}
style={{
overflow: 'hidden',
position: 'relative',
height: '241px',
background:
"url('/mxgraph/javascript/examples/editors/images/grid.gif')",
cursor: 'default',
}}
/>
</>
);
}
componentDidMount() {
// Creates the graph inside the given container
const graph = new mxGraph(this.el);
// Adds rubberband selection to the graph
new mxRubberband(graph);
const doc = mxUtils.parseXml(xml);
const codec = new mxCodec(doc);
codec.decode(doc.documentElement, graph.getModel());
}
}
export default Template;

View File

@ -1,15 +0,0 @@
import React from 'react';
import HelloWorld from './HelloWorld';
import Preview from '../Previews';
import PageTabs from '../PageTabs';
export default function _Basic() {
{
/* <Preview sourceKey="Template" content={<Template />} /> */
}
return (
<PageTabs curPageURL="/basic">
<Preview sourceKey="HelloWorld" content={<HelloWorld />} />
</PageTabs>
);
}

View File

@ -1,144 +0,0 @@
/**
* Copyright (c) 2006-2013, JGraph Ltd
* Converted to ES9 syntax/React by David Morrissey 2021
*/
import React from 'react';
import mxEvent from '../../mxgraph/util/event/mxEvent';
import mxGraph from '../../mxgraph/view/graph/mxGraph';
import mxRubberband from '../../mxgraph/handler/mxRubberband';
import mxShape from '../../mxgraph/shape/mxShape';
import mxConnectionConstraint from '../../mxgraph/view/connection/mxConnectionConstraint';
import mxPoint from '../../mxgraph/util/datatypes/mxPoint';
import mxPolyline from '../../mxgraph/shape/edge/mxPolyline';
import mxCellState from '../../mxgraph/view/cell/mxCellState';
import mxGeometry from '../../mxgraph/util/datatypes/mxGeometry';
import mxConnectionHandler from '../../mxgraph/handler/mxConnectionHandler';
class Anchors extends React.Component {
constructor(props) {
super(props);
}
render() {
// A container for the graph
return (
<>
<h1>Anchors</h1>
This example demonstrates defining fixed connection points for all
shapes.
<div
ref={el => {
this.el = el;
}}
style={{
position: 'relative',
overflow: 'hidden',
height: '241px',
background: 'url("editors/images/grid.gif")',
cursor: 'default',
}}
/>
</>
);
}
componentDidMount() {
// Disables the built-in context menu
mxEvent.disableContextMenu(this.el);
class MyCustomConnectionHandler extends mxConnectionHandler {
// Enables connect preview for the default edge style
createEdgeState(me) {
const edge = graph.createEdge(null, null, null, null, null);
return new mxCellState(
this.graph.view,
edge,
this.graph.getCellStyle(edge)
);
}
}
class MyCustomGraph extends mxGraph {
getAllConnectionConstraints(terminal, source) {
// Overridden to define per-shape connection points
if (terminal != null && terminal.shape != null) {
if (terminal.shape.stencil != null) {
if (terminal.shape.stencil.constraints != null) {
return terminal.shape.stencil.constraints;
}
} else if (terminal.shape.constraints != null) {
return terminal.shape.constraints;
}
}
return null;
}
createConnectionHandler() {
return new MyCustomConnectionHandler(this);
}
}
class MyCustomGeometryClass extends mxGeometry {
// Defines the default constraints for the vertices
constraints = [
new mxConnectionConstraint(new mxPoint(0.25, 0), true),
new mxConnectionConstraint(new mxPoint(0.5, 0), true),
new mxConnectionConstraint(new mxPoint(0.75, 0), true),
new mxConnectionConstraint(new mxPoint(0, 0.25), true),
new mxConnectionConstraint(new mxPoint(0, 0.5), true),
new mxConnectionConstraint(new mxPoint(0, 0.75), true),
new mxConnectionConstraint(new mxPoint(1, 0.25), true),
new mxConnectionConstraint(new mxPoint(1, 0.5), true),
new mxConnectionConstraint(new mxPoint(1, 0.75), true),
new mxConnectionConstraint(new mxPoint(0.25, 1), true),
new mxConnectionConstraint(new mxPoint(0.5, 1), true),
new mxConnectionConstraint(new mxPoint(0.75, 1), true),
];
}
// Edges have no connection points
mxPolyline.prototype.constraints = null;
// Creates the graph inside the given container
const graph = new MyCustomGraph(this.el);
graph.setConnectable(true);
// Specifies the default edge style
graph.getStylesheet().getDefaultEdgeStyle().edgeStyle =
'orthogonalEdgeStyle';
// 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).
const parent = graph.getDefaultParent();
// Adds cells to the model in a single step
graph.batchUpdate(() => {
const v1 = graph.insertVertex({
parent,
value: 'Hello,',
position: [20, 20],
size: [80, 30],
geometryClass: MyCustomGeometryClass,
});
const v2 = graph.insertVertex({
parent,
value: 'World!',
position: [200, 150],
size: [80, 30],
geometryClass: MyCustomGeometryClass,
});
const e1 = graph.insertEdge({
parent,
value: '',
position: v1,
size: v2,
});
});
}
}
export default Anchors;

View File

@ -1,189 +0,0 @@
/**
* Copyright (c) 2006-2013, JGraph Ltd
* Converted to ES9 syntax/React by David Morrissey 2021
*/
import React from 'react';
import mxEvent from '../../mxgraph/util/event/mxEvent';
import mxGraph from '../../mxgraph/view/graph/mxGraph';
import mxRubberband from '../../mxgraph/handler/mxRubberband';
import mxGraphHandler from '../../mxgraph/handler/mxGraphHandler';
import mxGuide from '../../mxgraph/util/mxGuide';
import mxEdgeHandler from '../../mxgraph/handler/mxEdgeHandler';
import mxConnectionHandler from '../../mxgraph/handler/mxConnectionHandler';
import mxGraphView from '../../mxgraph/view/graph/mxGraphView';
import mxPoint from '../../mxgraph/util/datatypes/mxPoint';
import mxCellState from '../../mxgraph/view/cell/mxCellState';
class Orthogonal extends React.Component {
constructor(props) {
super(props);
}
render() {
// A container for the graph
return (
<>
<h1>Orthogonal</h1>
This example demonstrates the use of port constraints and orthogonal
edge styles and handlers.
<div
ref={el => {
this.el = el;
}}
style={{
overflow: 'hidden',
position: 'relative',
height: '241px',
background: "url('editors/images/grid.gif')",
cursor: 'default',
}}
/>
</>
);
}
componentDidMount() {
// 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;
// Enables orthogonal connect preview in IE
mxConnectionHandler.prototype.movePreviewAway = false;
// Creates the graph inside the given container
const graph = new mxGraph(this.el);
graph.disconnectOnMove = false;
graph.foldingEnabled = false;
graph.cellsResizable = false;
graph.extendParents = false;
graph.setConnectable(true);
// Implements perimeter-less connection points as fixed points (computed before the edge style).
graph.view.updateFixedTerminalPoint = function(
edge,
terminal,
source,
constraint
) {
mxGraphView.prototype.updateFixedTerminalPoint.apply(this, arguments);
const pts = edge.absolutePoints;
const pt = pts[source ? 0 : pts.length - 1];
if (
terminal != null &&
pt == null &&
this.getPerimeterFunction(terminal) == null
) {
edge.setAbsoluteTerminalPoint(
new mxPoint(
this.getRoutingCenterX(terminal),
this.getRoutingCenterY(terminal)
),
source
);
}
};
// Changes the default edge style
graph.getStylesheet().getDefaultEdgeStyle().edgeStyle =
'orthogonalEdgeStyle';
delete graph.getStylesheet().getDefaultEdgeStyle().endArrow;
// Implements the connect preview
graph.connectionHandler.createEdgeState = function(me) {
const edge = graph.createEdge(null, null, null, null, null);
return new mxCellState(
this.graph.view,
edge,
this.graph.getCellStyle(edge)
);
};
// Uncomment the following if you want the container
// to fit the size of the graph
// graph.setResizeContainer(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).
const parent = graph.getDefaultParent();
// Adds cells to the model in a single step
graph.getModel().beginUpdate();
try {
const v1 = graph.insertVertex(parent, null, '', 40, 40, 40, 30);
v1.setConnectable(false);
const v11 = graph.insertVertex(
v1,
null,
'',
0.5,
0,
10,
40,
'portConstraint=northsouth;',
true
);
v11.geometry.offset = new mxPoint(-5, -5);
const v12 = graph.insertVertex(
v1,
null,
'',
0,
0.5,
10,
10,
'portConstraint=west;shape=triangle;direction=west;perimeter=none;' +
'routingCenterX=-0.5;routingCenterY=0;',
true
);
v12.geometry.offset = new mxPoint(-10, -5);
const v13 = graph.insertVertex(
v1,
null,
'',
1,
0.5,
10,
10,
'portConstraint=east;shape=triangle;direction=east;perimeter=none;' +
'routingCenterX=0.5;routingCenterY=0;',
true
);
v13.geometry.offset = new mxPoint(0, -5);
const v2 = graph.addCell(graph.getModel().cloneCell(v1));
v2.geometry.x = 200;
v2.geometry.y = 60;
const v3 = graph.addCell(graph.getModel().cloneCell(v1));
v3.geometry.x = 40;
v3.geometry.y = 150;
const v4 = graph.addCell(graph.getModel().cloneCell(v1));
v4.geometry.x = 200;
v4.geometry.y = 170;
graph.insertEdge(parent, null, '', v1.getChildAt(2), v2.getChildAt(1));
graph.insertEdge(parent, null, '', v2.getChildAt(2), v3.getChildAt(1));
graph.insertEdge(parent, null, '', v3.getChildAt(2), v4.getChildAt(1));
} finally {
// Updates the display
graph.getModel().endUpdate();
}
}
}
export default Orthogonal;

View File

@ -1,22 +0,0 @@
import React from 'react';
import Preview from '../Previews';
import Anchors from './Anchors';
import EdgeTolerance from './EdgeTolerance';
import FixedPoints from './FixedPoints';
import HelloPort from './HelloPort';
import Orthogonal from './Orthogonal';
import PortRefs from './PortRefs';
import PageTabs from '../PageTabs';
export default function _Connections() {
return (
<PageTabs curPageURL="/connections">
<Preview sourceKey="Anchors" content={<Anchors />} />
<Preview sourceKey="EdgeTolerance" content={<EdgeTolerance />} />
<Preview sourceKey="FixedPoints" content={<FixedPoints />} />
<Preview sourceKey="HelloPort" content={<HelloPort />} />
<Preview sourceKey="Orthogonal" content={<Orthogonal />} />
<Preview sourceKey="PortRefs" content={<PortRefs />} />
</PageTabs>
);
}

View File

@ -1,225 +0,0 @@
/**
* Copyright (c) 2006-2013, JGraph Ltd
* Converted to ES9 syntax/React by David Morrissey 2021
*/
import React from 'react';
import mxEvent from '../../mxgraph/util/event/mxEvent';
import mxGraph from '../../mxgraph/view/graph/mxGraph';
import mxRubberband from '../../mxgraph/handler/mxRubberband';
import mxCell from '../../mxgraph/view/cell/mxCell';
import mxGeometry from '../../mxgraph/util/datatypes/mxGeometry';
import mxUtils from '../../mxgraph/util/mxUtils';
import mxDragSource from '../../mxgraph/util/drag_pan/mxDragSource';
import mxGraphHandler from '../../mxgraph/handler/mxGraphHandler';
import mxGuide from '../../mxgraph/util/mxGuide';
import mxEdgeHandler from '../../mxgraph/handler/mxEdgeHandler';
class DragSource extends React.Component {
constructor(props) {
super(props);
}
render() {
// A container for the graph
return (
<>
<h1>Dragsource</h1>
This example demonstrates using one drag source for multiple graphs and
changing the drag icon.
<div
ref={el => {
this.el = el;
}}
style={{}}
/>
</>
);
}
componentDidMount() {
class MyCustomGuide extends mxGuide {
isEnabledForEvent(evt) {
// Alt disables guides
return !mxEvent.isAltDown(evt);
}
}
class MyCustomGraphHandler extends mxGraphHandler {
// Enables guides
guidesEnabled = true;
createGuide() {
return new MyCustomGuide(this.graph, this.getGuideStates());
}
}
class MyCustomEdgeHandler extends mxEdgeHandler {
// Enables snapping waypoints to terminals
snapToTerminals = true;
}
class MyCustomGraph extends mxGraph {
createGraphHandler() {
return new MyCustomGraphHandler(this);
}
createEdgeHandler(state, edgeStyle) {
return new MyCustomEdgeHandler(state, edgeStyle);
}
}
const graphs = [];
// Creates the graph inside the given container
for (let i = 0; i < 2; i++) {
const container = document.createElement('div');
container.style.overflow = 'hidden';
container.style.position = 'relative';
container.style.width = '321px';
container.style.height = '241px';
container.style.background = "url('editors/images/grid.gif')";
container.style.cursor = 'default';
this.el.appendChild(container);
const graph = new MyCustomGraph(container);
graph.gridSize = 30;
// Uncomment the following if you want the container
// to fit the size of the graph
// graph.setResizeContainer(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).
const parent = graph.getDefaultParent();
// Adds cells to the model in a single step
graph.batchUpdate(() => {
const v1 = graph.insertVertex({
parent,
value: 'Hello,',
position: [20, 20],
size: [80, 30],
});
const v2 = graph.insertVertex({
parent,
value: 'World!',
position: [200, 150],
size: [80, 30],
});
const e1 = graph.insertEdge({
parent,
source: v1,
target: v2,
});
});
graphs.push(graph);
}
// Returns the graph under the mouse
const graphF = evt => {
const x = mxEvent.getClientX(evt);
const y = mxEvent.getClientY(evt);
const elt = document.elementFromPoint(x, y);
for (const graph of graphs) {
if (mxUtils.isAncestorNode(graph.container, elt)) {
return graph;
}
}
return null;
};
// Inserts a cell at the given location
const funct = (graph, evt, target, x, y) => {
const cell = new mxCell('Test', new mxGeometry(0, 0, 120, 40));
cell.vertex = true;
const cells = graph.importCells([cell], x, y, target);
if (cells != null && cells.length > 0) {
graph.scrollCellToVisible(cells[0]);
graph.setSelectionCells(cells);
}
};
// Creates a DOM node that acts as the drag source
const img = mxUtils.createImage('images/icons48/gear.png');
img.style.width = '48px';
img.style.height = '48px';
this.el.appendChild(img);
// Creates the element that is being for the actual preview.
const dragElt = document.createElement('div');
dragElt.style.border = 'dashed black 1px';
dragElt.style.width = '120px';
dragElt.style.height = '40px';
// Drag source is configured to use dragElt for preview and as drag icon
// if scalePreview (last) argument is true. Dx and dy are null to force
// the use of the defaults. Note that dx and dy are only used for the
// drag icon but not for the preview.
const ds = mxUtils.makeDraggable(
img,
graphF,
funct,
dragElt,
null,
null,
graphs[0].autoscroll,
true
);
// Redirects feature to global switch. Note that this feature should only be used
// if the the x and y arguments are used in funct to insert the cell.
ds.isGuidesEnabled = () => {
return graphs[0].graphHandler.guidesEnabled;
};
// Restores original drag icon while outside of graph
ds.createDragElement = mxDragSource.prototype.createDragElement;
}
// NOTE: To enable cross-document DnD (eg. between frames),
// the following methods need to be overridden:
/* mxDragSourceMouseUp = mxDragSource.prototype.mouseUp;
mxDragSource.prototype.mouseUp = function(evt)
{
let doc = this.element.ownerDocument;
if (doc != document)
{
let mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
if (this.mouseUpHandler != null)
{
mxEvent.removeListener(doc, mu, this.mouseUpHandler);
}
}
mxDragSourceMouseUp.apply(this, arguments);
}; */
/* mxDragSourceMouseDown = mxDragSource.prototype.mouseDown;
mxDragSource.prototype.mouseDown = function(evt)
{
if (this.enabled && !mxEvent.isConsumed(evt))
{
mxDragSourceMouseDown.apply(this, arguments);
let doc = this.element.ownerDocument;
if (doc != document)
{
let mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
mxEvent.addListener(doc, mu, this.mouseUpHandler);
}
}
}; */
}
export default DragSource;

View File

@ -1,184 +0,0 @@
/**
* Copyright (c) 2006-2013, JGraph Ltd
* Converted to ES9 syntax/React by David Morrissey 2021
*/
import React from 'react';
import mxEvent from '../../mxgraph/util/event/mxEvent';
import mxGraph from '../../mxgraph/view/graph/mxGraph';
import mxRubberband from '../../mxgraph/handler/mxRubberband';
import mxUtils from '../../mxgraph/util/mxUtils';
import mxClient from '../../mxgraph/mxClient';
class Drop extends React.Component {
constructor(props) {
super(props);
}
render() {
// A container for the graph
return (
<>
<h1>Drop</h1>
This example demonstrates handling native drag and drop of images
(requires modern browser).
<div
ref={el => {
this.el = el;
}}
style={{
position: 'relative',
overflow: 'hidden',
height: '441px',
background: `url('editors/images/grid.gif')`,
cursor: 'default',
}}
/>
</>
);
}
componentDidMount() {
// Checks if the browser is supported
const fileSupport =
window.File != null &&
window.FileReader != null &&
window.FileList != null;
if (!fileSupport || !mxClient.isBrowserSupported()) {
// Displays an error message if the browser is not supported.
mxUtils.error('Browser is not supported!', 200, false);
} else {
// Disables the built-in context menu
mxEvent.disableContextMenu(this.el);
// Creates the graph inside the given this.el
const graph = new mxGraph(this.el);
// Enables rubberband selection
new mxRubberband(graph);
mxEvent.addListener(this.el, 'dragover', function(evt) {
if (graph.isEnabled()) {
evt.stopPropagation();
evt.preventDefault();
}
});
mxEvent.addListener(this.el, 'drop', evt => {
if (graph.isEnabled()) {
evt.stopPropagation();
evt.preventDefault();
// Gets drop location point for vertex
const pt = mxUtils.convertPoint(
graph.container,
mxEvent.getClientX(evt),
mxEvent.getClientY(evt)
);
const tr = graph.view.translate;
const { scale } = graph.view;
const x = pt.x / scale - tr.x;
const y = pt.y / scale - tr.y;
// Converts local images to data urls
const filesArray = evt.dataTransfer.files;
for (let i = 0; i < filesArray.length; i++) {
this.handleDrop(graph, filesArray[i], x + i * 10, y + i * 10);
}
}
});
}
}
handleDrop(graph, file, x, y) {
// Handles each file as a separate insert for simplicity.
// Use barrier to handle multiple files as a single insert.
if (file.type.substring(0, 5) === 'image') {
const reader = new FileReader();
reader.onload = function(e) {
// Gets size of image for vertex
let data = e.target.result;
// SVG needs special handling to add viewbox if missing and
// find initial size from SVG attributes (only for IE11)
if (file.type.substring(0, 9) === 'image/svg') {
const comma = data.indexOf(',');
const svgText = atob(data.substring(comma + 1));
const root = mxUtils.parseXml(svgText);
// Parses SVG to find width and height
if (root != null) {
const svgs = root.getElementsByTagName('svg');
if (svgs.length > 0) {
const svgRoot = svgs[0];
let w = parseFloat(svgRoot.getAttribute('width'));
let h = parseFloat(svgRoot.getAttribute('height'));
// Check if viewBox attribute already exists
const vb = svgRoot.getAttribute('viewBox');
if (vb == null || vb.length === 0) {
svgRoot.setAttribute('viewBox', `0 0 ${w} ${h}`);
}
// Uses width and height from viewbox for
// missing width and height attributes
else if (Number.isNaN(w) || Number.isNaN(h)) {
const tokens = vb.split(' ');
if (tokens.length > 3) {
w = parseFloat(tokens[2]);
h = parseFloat(tokens[3]);
}
}
w = Math.max(1, Math.round(w));
h = Math.max(1, Math.round(h));
data = `data:image/svg+xml,${btoa(
mxUtils.getXml(svgs[0], '\n')
)}`;
graph.insertVertex({
position: [x, y],
size: [w, h],
style: `shape=image;image=${data};`,
});
}
}
} else {
const img = new Image();
img.onload = () => {
const w = Math.max(1, img.width);
const h = Math.max(1, img.height);
// Converts format of data url to cell style value for use in vertex
const semi = data.indexOf(';');
if (semi > 0) {
data =
data.substring(0, semi) +
data.substring(data.indexOf(',', semi + 1));
}
graph.insertVertex({
position: [x, y],
size: [w, h],
style: `shape=image;image=${data};`,
});
};
img.src = data;
}
};
reader.readAsDataURL(file);
}
}
}
export default Drop;

View File

@ -1,16 +0,0 @@
import React from 'react';
import Preview from '../Previews';
import Clipboard from './Clipboard';
import DragSource from './DragSource';
import Drop from './Drop';
import PageTabs from '../PageTabs';
export default function _DnDCopyPaste() {
return (
<PageTabs curPageURL="/dnd_copypaste">
<Preview sourceKey="Clipboard" content={<Clipboard />} />
<Preview sourceKey="DragSource" content={<DragSource />} />
<Preview sourceKey="Drop" content={<Drop />} />
</PageTabs>
);
}

View File

@ -1,156 +0,0 @@
/**
* Copyright (c) 2006-2013, JGraph Ltd
* Converted to ES9 syntax/React by David Morrissey 2021
*/
import React from 'react';
import mxEvent from '../../mxgraph/util/event/mxEvent';
import mxGraph from '../../mxgraph/view/graph/mxGraph';
import mxUtils from '../../mxgraph/util/mxUtils';
import mxKeyHandler from '../../mxgraph/handler/mxKeyHandler';
class Editing extends React.Component {
constructor(props) {
super(props);
}
render() {
// A container for the graph
return (
<>
<h1>Editing</h1>
This example demonstrates using the in-place editor trigger to specify
the editing value and write the new value into a specific field of the
user object. Wrapping and DOM nodes as labels are also demonstrated
here.
<br />
<br />
Double-click the upper/lower half of the cell to edit different fields
of the user object.
<div
ref={el => {
this.el = el;
}}
style={{
overflow: 'hidden',
position: 'relative',
height: '241px',
background: "url('editors/images/grid.gif')",
}}
/>
</>
);
}
componentDidMount() {
class MyCustomGraph extends mxGraph {
getLabel(cell) {
// Returns a HTML representation of the cell where the
// upper half is the first value, lower half is second
// value
const table = document.createElement('table');
table.style.height = '100%';
table.style.width = '100%';
const body = document.createElement('tbody');
const tr1 = document.createElement('tr');
const td1 = document.createElement('td');
td1.style.textAlign = 'center';
td1.style.fontSize = '12px';
td1.style.color = '#774400';
mxUtils.write(td1, cell.value.first);
const tr2 = document.createElement('tr');
const td2 = document.createElement('td');
td2.style.textAlign = 'center';
td2.style.fontSize = '12px';
td2.style.color = '#774400';
mxUtils.write(td2, cell.value.second);
tr1.appendChild(td1);
tr2.appendChild(td2);
body.appendChild(tr1);
body.appendChild(tr2);
table.appendChild(body);
return table;
}
getEditingValue(cell, evt) {
// Returns the editing value for the given cell and event
evt.fieldname = this.__getFieldnameForEvent(cell, evt);
return cell.value[evt.fieldname] || '';
}
__getFieldnameForEvent(cell, evt) {
// Helper method that returns the fieldname to be used for
// a mouse event
if (evt != null) {
// Finds the relative coordinates inside the cell
const point = mxUtils.convertPoint(
this.container,
mxEvent.getClientX(evt),
mxEvent.getClientY(evt)
);
const state = this.getView().getState(cell);
if (state != null) {
point.x -= state.x;
point.y -= state.y;
// Returns second if mouse in second half of cell
if (point.y > state.height / 2) {
return 'second';
}
}
}
return 'first';
}
labelChanged(cell, newValue, trigger) {
// Sets the new value for the given cell and trigger
const name = trigger != null ? trigger.fieldname : null;
if (name != null) {
// Clones the user object for correct undo and puts
// the new value in the correct field.
const value = mxUtils.clone(cell.value);
value[name] = newValue;
newValue = value;
super.labelChanged(cell, newValue, trigger);
}
}
}
// Creates the graph inside the given container
const graph = new MyCustomGraph(this.el);
graph.setHtmlLabels(true);
// Adds handling of return and escape keystrokes for editing
const keyHandler = new mxKeyHandler(graph);
// Sample user objects with 2 fields
const value = {};
value.first = 'First value';
value.second = 'Second value';
// Gets the default parent for inserting new cells. This
// is normally the first child of the root (ie. layer 0).
const parent = graph.getDefaultParent();
// Adds cells to the model in a single step
graph.batchUpdate(() => {
const v1 = graph.insertVertex({
parent,
value,
position: [100, 60],
size: [120, 80],
style: 'overflow=fill;',
});
});
}
}
export default Editing;

View File

@ -1,12 +0,0 @@
import React from 'react';
import Preview from '../Previews';
import Editing from './Editing';
import PageTabs from '../PageTabs';
export default function _Editing() {
return (
<PageTabs curPageURL="/editing">
<Preview sourceKey="Editing" content={<Editing />} />
</PageTabs>
);
}