|
@ -5,7 +5,6 @@
|
||||||
* Type definitions from the typed-mxgraph project
|
* Type definitions from the typed-mxgraph project
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import mxUtils from '../util/mxUtils';
|
|
||||||
import mxCellPath from '../view/cell/mxCellPath';
|
import mxCellPath from '../view/cell/mxCellPath';
|
||||||
import mxCodecRegistry from './mxCodecRegistry';
|
import mxCodecRegistry from './mxCodecRegistry';
|
||||||
import mxConstants from '../util/mxConstants';
|
import mxConstants from '../util/mxConstants';
|
||||||
|
@ -13,6 +12,7 @@ import mxCell from '../view/cell/mxCell';
|
||||||
import mxLog from '../util/gui/mxLog';
|
import mxLog from '../util/gui/mxLog';
|
||||||
import { getFunctionName } from '../util/mxStringUtils';
|
import { getFunctionName } from '../util/mxStringUtils';
|
||||||
import { importNode, isNode } from '../util/mxDomUtils';
|
import { importNode, isNode } from '../util/mxDomUtils';
|
||||||
|
import { createXmlDocument } from '../util/mxXmlUtils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* XML codec for JavaScript object graphs. See {@link mxObjectCodec} for a
|
* XML codec for JavaScript object graphs. See {@link mxObjectCodec} for a
|
||||||
|
@ -120,7 +120,7 @@ import { importNode, isNode } from '../util/mxDomUtils';
|
||||||
*/
|
*/
|
||||||
class mxCodec {
|
class mxCodec {
|
||||||
constructor(document) {
|
constructor(document) {
|
||||||
this.document = document || mxUtils.createXmlDocument();
|
this.document = document || createXmlDocument();
|
||||||
this.objects = [];
|
this.objects = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import mxDragSource from './drag_pan/mxDragSource';
|
import mxDragSource from './drag_pan/mxDragSource';
|
||||||
import mxPoint from './datatypes/mxPoint';
|
import mxPoint from './datatypes/mxPoint';
|
||||||
|
import mxConstants from './mxConstants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function: makeDraggable
|
* Function: makeDraggable
|
||||||
|
|
|
@ -7,4 +7,15 @@ export const parameters = {
|
||||||
date: /Date$/,
|
date: /Date$/,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const defaultArgTypes = {
|
||||||
|
width: {
|
||||||
|
type: 'number',
|
||||||
|
defaultValue: 800
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: 'number',
|
||||||
|
defaultValue: 600
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 887 B |
After Width: | Height: | Size: 253 B |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 954 B |
After Width: | Height: | Size: 728 B |
After Width: | Height: | Size: 781 B |
After Width: | Height: | Size: 914 B |
After Width: | Height: | Size: 517 B |
After Width: | Height: | Size: 857 B |
After Width: | Height: | Size: 529 B |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 326 B |
After Width: | Height: | Size: 899 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 379 B |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 300 B |
After Width: | Height: | Size: 7.3 KiB |
After Width: | Height: | Size: 485 B |
After Width: | Height: | Size: 709 B |
After Width: | Height: | Size: 783 B |
After Width: | Height: | Size: 236 B |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 896 B |
After Width: | Height: | Size: 895 B |
After Width: | Height: | Size: 80 B |
After Width: | Height: | Size: 43 B |
After Width: | Height: | Size: 155 B |
After Width: | Height: | Size: 879 B |
After Width: | Height: | Size: 849 B |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 918 B |
After Width: | Height: | Size: 912 B |
After Width: | Height: | Size: 50 B |
After Width: | Height: | Size: 858 B |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 847 B |
After Width: | Height: | Size: 2.1 KiB |
|
@ -1,10 +1,15 @@
|
||||||
import mxgraph from '@mxgraph/core';
|
import mxgraph from '@mxgraph/core';
|
||||||
import HelloWorld from './HelloWorld.stories';
|
|
||||||
|
import { defaultArgTypes } from '../.storybook/preview';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Connections/Anchors',
|
title: 'Connections/Anchors',
|
||||||
argTypes: {
|
argTypes: {
|
||||||
...HelloWorld.argTypes
|
...defaultArgTypes.argTypes,
|
||||||
|
rubberBand: {
|
||||||
|
type: 'boolean',
|
||||||
|
defaultValue: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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({});
|
|
@ -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({});
|
|
@ -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({});
|
|
@ -1,16 +1,11 @@
|
||||||
import mxgraph from '@mxgraph/core';
|
import mxgraph from '@mxgraph/core';
|
||||||
|
|
||||||
|
import { defaultArgTypes } from '../.storybook/preview';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Connections/EdgeTolerance',
|
title: 'Connections/EdgeTolerance',
|
||||||
argTypes: {
|
argTypes: {
|
||||||
width: {
|
...defaultArgTypes.argTypes
|
||||||
type: 'number',
|
|
||||||
defaultValue: 800
|
|
||||||
},
|
|
||||||
height: {
|
|
||||||
type: 'number',
|
|
||||||
defaultValue: 600
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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({});
|
|
@ -1,11 +1,19 @@
|
||||||
import mxgraph from '@mxgraph/core';
|
import mxgraph from '@mxgraph/core';
|
||||||
|
|
||||||
import HelloWorld from './HelloWorld.stories';
|
import { defaultArgTypes } from '../.storybook/preview';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Backgrounds/ExtendCanvas',
|
title: 'Backgrounds/ExtendCanvas',
|
||||||
argTypes: {
|
argTypes: {
|
||||||
...HelloWorld.argTypes
|
...defaultArgTypes.argTypes,
|
||||||
|
contextMenu: {
|
||||||
|
type: 'boolean',
|
||||||
|
defaultValue: false
|
||||||
|
},
|
||||||
|
rubberBand: {
|
||||||
|
type: 'boolean',
|
||||||
|
defaultValue: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -15,9 +23,7 @@ const Template = ({ label, ...args }) => {
|
||||||
mxEvent,
|
mxEvent,
|
||||||
mxRubberband,
|
mxRubberband,
|
||||||
mxRectangle,
|
mxRectangle,
|
||||||
mxGraphView,
|
|
||||||
mxPoint,
|
mxPoint,
|
||||||
mxDomHelpers,
|
|
||||||
mxUtils
|
mxUtils
|
||||||
} = mxgraph;
|
} = mxgraph;
|
||||||
|
|
||||||
|
|
|
@ -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({});
|
|
@ -1,11 +1,19 @@
|
||||||
import mxgraph from '@mxgraph/core';
|
import mxgraph from '@mxgraph/core';
|
||||||
|
|
||||||
import HelloWorld from './HelloWorld.stories';
|
import { defaultArgTypes } from '../.storybook/preview';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Backgrounds/Grid',
|
title: 'Backgrounds/Grid',
|
||||||
argTypes: {
|
argTypes: {
|
||||||
...HelloWorld.argTypes
|
...defaultArgTypes.argTypes,
|
||||||
|
contextMenu: {
|
||||||
|
type: 'boolean',
|
||||||
|
defaultValue: false
|
||||||
|
},
|
||||||
|
rubberBand: {
|
||||||
|
type: 'boolean',
|
||||||
|
defaultValue: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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({});
|
|
@ -1,16 +1,11 @@
|
||||||
import mxgraph from '@mxgraph/core';
|
import mxgraph from '@mxgraph/core';
|
||||||
|
|
||||||
|
import { defaultArgTypes } from '../.storybook/preview';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Basic/HelloWorld',
|
title: 'Basic/HelloWorld',
|
||||||
argTypes: {
|
argTypes: {
|
||||||
width: {
|
...defaultArgTypes,
|
||||||
type: 'number',
|
|
||||||
defaultValue: 800
|
|
||||||
},
|
|
||||||
height: {
|
|
||||||
type: 'number',
|
|
||||||
defaultValue: 600
|
|
||||||
},
|
|
||||||
contextMenu: {
|
contextMenu: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
defaultValue: false
|
defaultValue: false
|
||||||
|
@ -30,6 +25,7 @@ const Template = ({ label, ...args }) => {
|
||||||
container.style.overflow = 'hidden';
|
container.style.overflow = 'hidden';
|
||||||
container.style.width = `${args.width}px`;
|
container.style.width = `${args.width}px`;
|
||||||
container.style.height = `${args.height}px`;
|
container.style.height = `${args.height}px`;
|
||||||
|
container.style.background = 'url(/images/grid.gif)';
|
||||||
container.style.cursor = 'default';
|
container.style.cursor = 'default';
|
||||||
|
|
||||||
if (!args.contextMenu)
|
if (!args.contextMenu)
|
||||||
|
|
|
@ -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({});
|
|
@ -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({});
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -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;
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|