feat: restore remaining non-"Editor" examples to Storybook (#150)
Converted the following remaining "stashed" examples to Storybook: - GraphLayout - HtmlLabel - MenuStyle - ShowRegion - Scrollbars (is starting to display, but appears to have a bug where `<g>` elements are inserted outside of the `<svg>` element) - Wires The examples are still not fully working. As the mxGraph examples were previously migrated to nextjs/react, some original code may not have been ported correctly. Additional changes in @maxgraph/core: - Fixes the default parameters in `MaxPopupMenu` and `Morphing` to restore the original `mxGraph` behavior. - Graph: add new "create*" factory methods to ease the extension: - createEdgeHandlerInstance(state: CellState): EdgeHandler - createGraphDataModel(): GraphDataModel - createSelectionModel() --------- Co-authored-by: Thomas Bouffard <27200110+tbouffard@users.noreply.github.com>development
parent
d5c60b120f
commit
63f002c263
|
@ -57,7 +57,9 @@ class MaxPopupMenu extends EventSource implements Partial<PopupMenuItem> {
|
|||
) {
|
||||
super();
|
||||
|
||||
this.factoryMethod = factoryMethod;
|
||||
if (factoryMethod) {
|
||||
this.factoryMethod = factoryMethod;
|
||||
}
|
||||
|
||||
// Adds the inner table
|
||||
this.table = document.createElement('table');
|
||||
|
@ -189,9 +191,9 @@ class MaxPopupMenu extends EventSource implements Partial<PopupMenuItem> {
|
|||
funct: Function,
|
||||
parent: PopupMenuItem | null = null,
|
||||
iconCls: string | null = null,
|
||||
enabled: boolean | null = null,
|
||||
active: boolean | null = null,
|
||||
noHover: boolean | null = null
|
||||
enabled = true,
|
||||
active = true,
|
||||
noHover = false
|
||||
) {
|
||||
parent = (parent ?? this) as PopupMenuItem;
|
||||
this.itemCount++;
|
||||
|
|
|
@ -92,9 +92,9 @@ class Graph extends EventSource {
|
|||
foldingEnabled: null | boolean = null;
|
||||
isConstrainedMoving = false;
|
||||
|
||||
/*****************************************************************************
|
||||
* Group: Variables (that maybe should be in the mixins, but need to be created for each new class instance)
|
||||
*****************************************************************************/
|
||||
// ===================================================================================================================
|
||||
// Group: Variables (that maybe should be in the mixins, but need to be created for each new class instance)
|
||||
// ===================================================================================================================
|
||||
|
||||
cells: Cell[] = [];
|
||||
|
||||
|
@ -111,10 +111,6 @@ class Graph extends EventSource {
|
|||
*/
|
||||
multiplicities: Multiplicity[] = [];
|
||||
|
||||
/*****************************************************************************
|
||||
* Group: Variables
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Holds the {@link GraphDataModel} that contains the cells to be displayed.
|
||||
*/
|
||||
|
@ -407,6 +403,89 @@ class Graph extends EventSource {
|
|||
containsValidationErrorsResource: string =
|
||||
Client.language != 'none' ? 'containsValidationErrors' : '';
|
||||
|
||||
// ===================================================================================================================
|
||||
// Group: "Create Class Instance" factory functions.
|
||||
// These can be overridden in subclasses of Graph to allow the Graph to instantiate user-defined implementations with
|
||||
// custom behavior.
|
||||
// ===================================================================================================================
|
||||
|
||||
/**
|
||||
* Creates a new {@link CellRenderer} to be used in this graph.
|
||||
*/
|
||||
createCellRenderer(): CellRenderer {
|
||||
return new CellRenderer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks to create a new {@link EdgeHandler} for the given {@link CellState}.
|
||||
*
|
||||
* @param state {@link CellState} to create the handler for.
|
||||
*/
|
||||
createEdgeHandlerInstance(state: CellState): EdgeHandler {
|
||||
// Note this method not being called createEdgeHandler to keep compatibility
|
||||
// with older code which overrides/calls createEdgeHandler
|
||||
return new EdgeHandler(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks to create a new {@link EdgeSegmentHandler} for the given {@link CellState}.
|
||||
*
|
||||
* @param state {@link CellState} to create the handler for.
|
||||
*/
|
||||
createEdgeSegmentHandler(state: CellState) {
|
||||
return new EdgeSegmentHandler(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks to create a new {@link ElbowEdgeHandler} for the given {@link CellState}.
|
||||
*
|
||||
* @param state {@link CellState} to create the handler for.
|
||||
*/
|
||||
createElbowEdgeHandler(state: CellState) {
|
||||
return new ElbowEdgeHandler(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link GraphDataModel} to be used in this graph.
|
||||
*/
|
||||
createGraphDataModel(): GraphDataModel {
|
||||
return new GraphDataModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link GraphView} to be used in this graph.
|
||||
*/
|
||||
createGraphView(): GraphView {
|
||||
return new GraphView(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link GraphSelectionModel} to be used in this graph.
|
||||
*/
|
||||
createSelectionModel() {
|
||||
return new GraphSelectionModel(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Stylesheet} to be used in this graph.
|
||||
*/
|
||||
createStylesheet(): Stylesheet {
|
||||
return new Stylesheet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks to create a new {@link VertexHandler} for the given {@link CellState}.
|
||||
*
|
||||
* @param state {@link CellState} to create the handler for.
|
||||
*/
|
||||
createVertexHandler(state: CellState): VertexHandler {
|
||||
return new VertexHandler(state);
|
||||
}
|
||||
|
||||
// ===================================================================================================================
|
||||
// Group: Main graph constructor and functions
|
||||
// ===================================================================================================================
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
model?: GraphDataModel,
|
||||
|
@ -416,10 +495,10 @@ class Graph extends EventSource {
|
|||
super();
|
||||
|
||||
this.container = container ?? document.createElement('div');
|
||||
this.model = model ?? new GraphDataModel();
|
||||
this.model = model ?? this.createGraphDataModel();
|
||||
this.plugins = plugins;
|
||||
this.cellRenderer = this.createCellRenderer();
|
||||
this.setStylesheet(stylesheet != null ? stylesheet : this.createStylesheet());
|
||||
this.setStylesheet(stylesheet ?? this.createStylesheet());
|
||||
this.view = this.createGraphView();
|
||||
|
||||
// Adds a graph model listener to update the view
|
||||
|
@ -437,7 +516,7 @@ class Graph extends EventSource {
|
|||
// Set the selection model
|
||||
this.setSelectionModel(this.createSelectionModel());
|
||||
|
||||
// Initiailzes plugins
|
||||
// Initializes plugins
|
||||
this.plugins.forEach((p: GraphPluginConstructor) => {
|
||||
this.pluginsMap[p.pluginId] = new p(this);
|
||||
});
|
||||
|
@ -445,7 +524,6 @@ class Graph extends EventSource {
|
|||
this.view.revalidate();
|
||||
}
|
||||
|
||||
createSelectionModel = () => new GraphSelectionModel(this);
|
||||
getContainer = () => this.container;
|
||||
getPlugin = (id: string) => this.pluginsMap[id] as unknown;
|
||||
getCellRenderer = () => this.cellRenderer;
|
||||
|
@ -489,27 +567,6 @@ class Graph extends EventSource {
|
|||
this.getDataModel().batchUpdate(fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Stylesheet} to be used in this graph.
|
||||
*/
|
||||
createStylesheet(): Stylesheet {
|
||||
return new Stylesheet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link GraphView} to be used in this graph.
|
||||
*/
|
||||
createGraphView() {
|
||||
return new GraphView(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link CellRenderer} to be used in this graph.
|
||||
*/
|
||||
createCellRenderer(): CellRenderer {
|
||||
return new CellRenderer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link GraphDataModel} that contains the cells.
|
||||
*/
|
||||
|
@ -960,15 +1017,6 @@ class Graph extends EventSource {
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks to create a new {@link VertexHandler} for the given {@link CellState}.
|
||||
*
|
||||
* @param state {@link CellState} to create the handler for.
|
||||
*/
|
||||
createVertexHandler(state: CellState): VertexHandler {
|
||||
return new VertexHandler(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks to create a new {@link EdgeHandler} for the given {@link CellState}.
|
||||
*
|
||||
|
@ -989,30 +1037,12 @@ class Graph extends EventSource {
|
|||
) {
|
||||
result = this.createEdgeSegmentHandler(state);
|
||||
} else {
|
||||
result = new EdgeHandler(state);
|
||||
result = this.createEdgeHandlerInstance(state);
|
||||
}
|
||||
|
||||
return result as EdgeHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks to create a new {@link EdgeSegmentHandler} for the given {@link CellState}.
|
||||
*
|
||||
* @param state {@link CellState} to create the handler for.
|
||||
*/
|
||||
createEdgeSegmentHandler(state: CellState) {
|
||||
return new EdgeSegmentHandler(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks to create a new {@link ElbowEdgeHandler} for the given {@link CellState}.
|
||||
*
|
||||
* @param state {@link CellState} to create the handler for.
|
||||
*/
|
||||
createElbowEdgeHandler(state: CellState) {
|
||||
return new ElbowEdgeHandler(state);
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Group: Drilldown
|
||||
*****************************************************************************/
|
||||
|
|
|
@ -56,7 +56,7 @@ import { Graph } from '../Graph';
|
|||
* @param delay Optional delay between the animation steps. Passed to <Animation>.
|
||||
*/
|
||||
class Morphing extends Animation {
|
||||
constructor(graph: Graph, steps = 6, ease = 1.5, delay: number) {
|
||||
constructor(graph: Graph, steps = 6, ease = 1.5, delay?: number) {
|
||||
super(delay);
|
||||
this.graph = graph;
|
||||
this.steps = steps;
|
||||
|
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
Copyright 2021-present The maxGraph project Contributors
|
||||
Copyright (c) 2006-2013, JGraph Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
Graph Layout
|
||||
|
||||
This example demonstrates using automatic graph layouts and listening to changes of the graph size
|
||||
to keep the container size in sync.
|
||||
*/
|
||||
|
||||
import {
|
||||
constants,
|
||||
CircleLayout,
|
||||
DomHelpers,
|
||||
FastOrganicLayout,
|
||||
Graph,
|
||||
InternalEvent,
|
||||
Morphing,
|
||||
Perimeter,
|
||||
} from '@maxgraph/core';
|
||||
|
||||
import { globalTypes, globalValues } from './shared/args.js';
|
||||
|
||||
// TODO apply this settings to the container used by the Graph
|
||||
const HTML_TEMPLATE = `
|
||||
<!-- Page passes the container for the graph to the program -->
|
||||
<body onload="main(document.getElementById('graphContainer'))">
|
||||
|
||||
<!-- Creates a container for the graph with a grid wallpaper. Make sure to define the position
|
||||
and overflow attributes! See comments on the adding of the size-listener on line 54 ff! -->
|
||||
<div id="graphContainer"
|
||||
style="position:relative;overflow:auto;width:821px;height:641px;background:url('editors/images/grid.gif');">
|
||||
</div>
|
||||
<br>
|
||||
</body>
|
||||
`;
|
||||
|
||||
export default {
|
||||
title: 'Layouts/GraphLayout',
|
||||
argTypes: {
|
||||
...globalTypes,
|
||||
animate: {
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
},
|
||||
},
|
||||
args: {
|
||||
...globalValues,
|
||||
animate: true,
|
||||
},
|
||||
};
|
||||
|
||||
const Template = ({ label, ...args }) => {
|
||||
const mainContainer = document.createElement('div');
|
||||
const container = document.createElement('div');
|
||||
|
||||
container.style.position = 'relative';
|
||||
container.style.overflow = 'hidden';
|
||||
container.style.width = `${args.width}px`;
|
||||
container.style.height = `${args.height}px`;
|
||||
container.style.background = 'url(/images/grid.gif)';
|
||||
container.style.cursor = 'default';
|
||||
|
||||
// Creates the graph inside the given container
|
||||
let graph = new Graph(container);
|
||||
|
||||
// Disables basic selection and cell handling
|
||||
graph.setEnabled(false);
|
||||
|
||||
// Changes the default vertex style in-place
|
||||
let style = graph.getStylesheet().getDefaultVertexStyle();
|
||||
style.shape = constants.SHAPE.ELLIPSE;
|
||||
style.perimiter = Perimeter.EllipsePerimeter;
|
||||
style.gradientColor = 'white';
|
||||
style.fontSize = 10;
|
||||
|
||||
// Updates the size of the container to match
|
||||
// the size of the graph when it changes. If
|
||||
// this is commented-out, and the DIV style's
|
||||
// overflow is set to "auto", then scrollbars
|
||||
// will appear for the diagram. If overflow is
|
||||
// set to "visible", then the diagram will be
|
||||
// visible even when outside the parent DIV.
|
||||
// With the code below, the parent DIV will be
|
||||
// resized to contain the complete graph.
|
||||
//graph.setResizeContainer(true);
|
||||
|
||||
// Larger grid size yields cleaner layout result
|
||||
graph.gridSize = 40;
|
||||
|
||||
// Gets the default parent for inserting new cells. This
|
||||
// is normally the first child of the root (ie. layer 0).
|
||||
let parent = graph.getDefaultParent();
|
||||
|
||||
// Creates a layout algorithm to be used
|
||||
// with the graph
|
||||
let layout = new FastOrganicLayout(graph);
|
||||
|
||||
// Moves stuff wider apart than usual
|
||||
layout.forceConstant = 80;
|
||||
|
||||
// Adds a button to execute the layout
|
||||
mainContainer.appendChild(
|
||||
DomHelpers.button('Circle Layout', function (evt) {
|
||||
graph.getDataModel().beginUpdate();
|
||||
try {
|
||||
// Creates a layout algorithm to be used
|
||||
// with the graph
|
||||
let circleLayout = new CircleLayout(graph);
|
||||
circleLayout.execute(parent);
|
||||
} finally {
|
||||
if (args.animate) {
|
||||
let morph = new Morphing(graph, 6, 1.5, 20);
|
||||
morph.addListener(InternalEvent.DONE, function () {
|
||||
graph.getDataModel().endUpdate();
|
||||
});
|
||||
morph.startAnimation();
|
||||
} else {
|
||||
graph.getDataModel().endUpdate();
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Adds a button to execute the layout
|
||||
mainContainer.appendChild(
|
||||
DomHelpers.button('Organic Layout', function (evt) {
|
||||
graph.getDataModel().beginUpdate();
|
||||
try {
|
||||
layout.execute(parent);
|
||||
} finally {
|
||||
if (args.animate) {
|
||||
// Default values are 6, 1.5, 20
|
||||
let morph = new Morphing(graph, 10, 1.7, 20);
|
||||
morph.addListener(InternalEvent.DONE, function () {
|
||||
graph.getDataModel().endUpdate();
|
||||
});
|
||||
morph.startAnimation();
|
||||
} else {
|
||||
graph.getDataModel().endUpdate();
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
mainContainer.appendChild(container);
|
||||
|
||||
// Adds cells to the model in a single step
|
||||
let w = 30;
|
||||
let h = 30;
|
||||
|
||||
graph.batchUpdate(() => {
|
||||
let v1 = graph.insertVertex(parent, null, 'A', 0, 0, w, h);
|
||||
let v2 = graph.insertVertex(parent, null, 'B', 0, 0, w, h);
|
||||
let v3 = graph.insertVertex(parent, null, 'C', 0, 0, w, h);
|
||||
let v4 = graph.insertVertex(parent, null, 'D', 0, 0, w, h);
|
||||
let v5 = graph.insertVertex(parent, null, 'E', 0, 0, w, h);
|
||||
let v6 = graph.insertVertex(parent, null, 'F', 0, 0, w, h);
|
||||
let v7 = graph.insertVertex(parent, null, 'G', 0, 0, w, h);
|
||||
let v8 = graph.insertVertex(parent, null, 'H', 0, 0, w, h);
|
||||
let e1 = graph.insertEdge(parent, null, 'ab', v1, v2);
|
||||
let e2 = graph.insertEdge(parent, null, 'ac', v1, v3);
|
||||
let e3 = graph.insertEdge(parent, null, 'cd', v3, v4);
|
||||
let e4 = graph.insertEdge(parent, null, 'be', v2, v5);
|
||||
let e5 = graph.insertEdge(parent, null, 'cf', v3, v6);
|
||||
let e6 = graph.insertEdge(parent, null, 'ag', v1, v7);
|
||||
let e7 = graph.insertEdge(parent, null, 'gh', v7, v8);
|
||||
let e8 = graph.insertEdge(parent, null, 'gc', v7, v3);
|
||||
let e9 = graph.insertEdge(parent, null, 'gd', v7, v4);
|
||||
let e10 = graph.insertEdge(parent, null, 'eh', v5, v8);
|
||||
|
||||
// Executes the layout
|
||||
layout.execute(parent);
|
||||
});
|
||||
|
||||
return mainContainer;
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
|
@ -0,0 +1,210 @@
|
|||
/*
|
||||
Copyright 2021-present The maxGraph project Contributors
|
||||
Copyright (c) 2006-2013, JGraph Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
HTML label
|
||||
|
||||
This example demonstrates using HTML labels that are connected to the state of the user object.
|
||||
*/
|
||||
|
||||
import {
|
||||
xmlUtils,
|
||||
domUtils,
|
||||
InternalEvent,
|
||||
RubberBandHandler,
|
||||
UndoManager,
|
||||
CodecRegistry,
|
||||
Graph,
|
||||
Cell,
|
||||
DomHelpers,
|
||||
GraphDataModel,
|
||||
CellEditorHandler,
|
||||
TooltipHandler,
|
||||
SelectionCellsHandler,
|
||||
PopupMenuHandler,
|
||||
ConnectionHandler,
|
||||
SelectionHandler,
|
||||
PanningHandler,
|
||||
} from '@maxgraph/core';
|
||||
|
||||
import { globalTypes, globalValues } from './shared/args.js';
|
||||
// style required by RubberBand
|
||||
import '@maxgraph/core/css/common.css';
|
||||
|
||||
export default {
|
||||
title: 'Labels/HtmlLabel',
|
||||
argTypes: {
|
||||
...globalTypes,
|
||||
},
|
||||
args: {
|
||||
...globalValues,
|
||||
},
|
||||
};
|
||||
|
||||
const Template = ({ label, ...args }) => {
|
||||
const container = document.createElement('div');
|
||||
container.style.position = 'relative';
|
||||
container.style.overflow = 'hidden';
|
||||
container.style.width = `${args.width}px`;
|
||||
container.style.height = `${args.height}px`;
|
||||
container.style.background = 'url(/images/grid.gif)';
|
||||
container.style.cursor = 'default';
|
||||
|
||||
// Disables the built-in context menu
|
||||
InternalEvent.disableContextMenu(container);
|
||||
|
||||
// Creates a user object that stores the state
|
||||
let doc = xmlUtils.createXmlDocument();
|
||||
let obj = doc.createElement('UserObject');
|
||||
obj.setAttribute('label', 'Hello, World!');
|
||||
obj.setAttribute('checked', 'false');
|
||||
|
||||
// Adds optional caching for the HTML label
|
||||
let cached = true;
|
||||
let MyCustomGraphDataModel;
|
||||
|
||||
if (cached) {
|
||||
// Ignores cached label in codec
|
||||
CodecRegistry.getCodec(Cell).exclude.push('div');
|
||||
|
||||
// Invalidates cached labels
|
||||
MyCustomGraphDataModel = class extends GraphDataModel {
|
||||
setValue(cell, value) {
|
||||
cell.div = null;
|
||||
super.setValue.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
MyCustomGraphDataModel = GraphDataModel;
|
||||
}
|
||||
|
||||
class MyCustomGraph extends Graph {
|
||||
createGraphDataModel() {
|
||||
return new MyCustomGraphDataModel();
|
||||
}
|
||||
|
||||
// Overrides method to provide a cell label in the display
|
||||
convertValueToString(cell) {
|
||||
if (cached && cell.div != null) {
|
||||
// Uses cached label
|
||||
return cell.div;
|
||||
} else if (
|
||||
domUtils.isNode(cell.value) &&
|
||||
cell.value.nodeName.toLowerCase() === 'userobject'
|
||||
) {
|
||||
// Returns a DOM for the label
|
||||
let div = document.createElement('div');
|
||||
div.innerHTML = cell.getAttribute('label');
|
||||
domUtils.br(div);
|
||||
|
||||
let checkbox = document.createElement('input');
|
||||
checkbox.setAttribute('type', 'checkbox');
|
||||
|
||||
if (cell.getAttribute('checked') == 'true') {
|
||||
checkbox.setAttribute('checked', 'checked');
|
||||
checkbox.defaultChecked = true;
|
||||
}
|
||||
|
||||
// Writes back to cell if checkbox is clicked
|
||||
InternalEvent.addListener(checkbox, 'change', function (evt) {
|
||||
let elt = cell.value.cloneNode(true);
|
||||
elt.setAttribute('checked', checkbox.checked ? 'true' : 'false');
|
||||
|
||||
graph.model.setValue(cell, elt);
|
||||
});
|
||||
|
||||
div.appendChild(checkbox);
|
||||
|
||||
if (cached) {
|
||||
// Caches label
|
||||
cell.div = div;
|
||||
}
|
||||
return div;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// Overrides method to store a cell label in the model
|
||||
cellLabelChanged(cell, newValue, autoSize) {
|
||||
if (
|
||||
domUtils.isNode(cell.value) &&
|
||||
cell.value.nodeName.toLowerCase() === 'userobject'
|
||||
) {
|
||||
// Clones the value for correct undo/redo
|
||||
let elt = cell.value.cloneNode(true);
|
||||
elt.setAttribute('label', newValue);
|
||||
newValue = elt;
|
||||
}
|
||||
|
||||
super.cellLabelChanged.apply(this, arguments);
|
||||
}
|
||||
|
||||
// Overrides method to create the editing value
|
||||
getEditingValue(cell) {
|
||||
if (
|
||||
domUtils.isNode(cell.value) &&
|
||||
cell.value.nodeName.toLowerCase() === 'userobject'
|
||||
) {
|
||||
return cell.getAttribute('label');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Creates the graph inside the given container
|
||||
let graph = new MyCustomGraph(container, null, [
|
||||
CellEditorHandler,
|
||||
TooltipHandler,
|
||||
SelectionCellsHandler,
|
||||
PopupMenuHandler,
|
||||
ConnectionHandler,
|
||||
SelectionHandler,
|
||||
PanningHandler,
|
||||
]);
|
||||
|
||||
// Enables HTML labels
|
||||
graph.setHtmlLabels(true);
|
||||
|
||||
// Enables rubberband selection
|
||||
new RubberBandHandler(graph);
|
||||
|
||||
let parent = graph.getDefaultParent();
|
||||
graph.insertVertex(parent, null, obj, 20, 20, 80, 60);
|
||||
|
||||
// Undo/redo
|
||||
let undoManager = new UndoManager();
|
||||
let listener = function (sender, evt) {
|
||||
undoManager.undoableEditHappened(evt.getProperty('edit'));
|
||||
};
|
||||
graph.getDataModel().addListener(InternalEvent.UNDO, listener);
|
||||
graph.getView().addListener(InternalEvent.UNDO, listener);
|
||||
|
||||
document.body.appendChild(
|
||||
DomHelpers.button('Undo', function () {
|
||||
undoManager.undo();
|
||||
})
|
||||
);
|
||||
|
||||
document.body.appendChild(
|
||||
DomHelpers.button('Redo', function () {
|
||||
undoManager.redo();
|
||||
})
|
||||
);
|
||||
|
||||
return container;
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
|
@ -0,0 +1,240 @@
|
|||
/*
|
||||
Copyright 2021-present The maxGraph project Contributors
|
||||
Copyright (c) 2006-2013, JGraph Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
Menustyle
|
||||
|
||||
This example demonstrates using CSS to style the mxPopupMenu.
|
||||
*/
|
||||
|
||||
import {
|
||||
Client,
|
||||
CellOverlay,
|
||||
CellRenderer,
|
||||
EventObject,
|
||||
ImageBox,
|
||||
InternalEvent,
|
||||
InternalMouseEvent,
|
||||
RubberBandHandler,
|
||||
Graph,
|
||||
PopupMenuHandler,
|
||||
CellEditorHandler,
|
||||
TooltipHandler,
|
||||
SelectionCellsHandler,
|
||||
ConnectionHandler,
|
||||
SelectionHandler,
|
||||
PanningHandler,
|
||||
} from '@maxgraph/core';
|
||||
import { globalTypes, globalValues } from './shared/args.js';
|
||||
// style required by RubberBand
|
||||
import '@maxgraph/core/css/common.css';
|
||||
|
||||
const CSS_TEMPLATE = `
|
||||
body div.mxPopupMenu {
|
||||
-webkit-box-shadow: 3px 3px 6px #C0C0C0;
|
||||
-moz-box-shadow: 3px 3px 6px #C0C0C0;
|
||||
box-shadow: 3px 3px 6px #C0C0C0;
|
||||
background: white;
|
||||
position: absolute;
|
||||
border: 3px solid #e7e7e7;
|
||||
padding: 3px;
|
||||
}
|
||||
body table.mxPopupMenu {
|
||||
border-collapse: collapse;
|
||||
margin: 0px;
|
||||
}
|
||||
body tr.mxPopupMenuItem {
|
||||
color: black;
|
||||
cursor: default;
|
||||
}
|
||||
body td.mxPopupMenuItem {
|
||||
padding: 6px 60px 6px 30px;
|
||||
font-family: Arial;
|
||||
font-size: 10pt;
|
||||
}
|
||||
body td.mxPopupMenuIcon {
|
||||
background-color: white;
|
||||
padding: 0px;
|
||||
}
|
||||
body tr.mxPopupMenuItemHover {
|
||||
background-color: #eeeeee;
|
||||
color: black;
|
||||
}
|
||||
table.mxPopupMenu hr {
|
||||
border-top: solid 1px #cccccc;
|
||||
}
|
||||
table.mxPopupMenu tr {
|
||||
font-size: 4pt;
|
||||
}
|
||||
`;
|
||||
|
||||
// TODO apply this settings to the container used by the Graph
|
||||
const HTML_TEMPLATE = `
|
||||
<body onload="main(document.getElementById('graphContainer'))">
|
||||
<!-- Creates a container for the graph with a grid wallpaper -->
|
||||
<div id="graphContainer"
|
||||
style="overflow:hidden;width:321px;height:241px;background:url('editors/images/grid.gif');cursor:default;">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
export default {
|
||||
title: 'Misc/MenuStyle',
|
||||
argTypes: {
|
||||
...globalTypes,
|
||||
},
|
||||
args: {
|
||||
...globalValues,
|
||||
},
|
||||
};
|
||||
|
||||
const Template = ({ label, ...args }) => {
|
||||
const styleElm = document.createElement('style');
|
||||
styleElm.innerText = CSS_TEMPLATE;
|
||||
document.head.appendChild(styleElm);
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.style.position = 'relative';
|
||||
container.style.overflow = 'hidden';
|
||||
container.style.width = `${args.width}px`;
|
||||
container.style.height = `${args.height}px`;
|
||||
container.style.background = 'url(/images/grid.gif)';
|
||||
container.style.cursor = 'default';
|
||||
|
||||
// Disables built-in context menu
|
||||
InternalEvent.disableContextMenu(container);
|
||||
|
||||
// Changes some default colors
|
||||
// TODO Find a way of modifying globally or setting locally! See https://github.com/maxGraph/maxGraph/issues/192
|
||||
//constants.HANDLE_FILLCOLOR = '#99ccff';
|
||||
//constants.HANDLE_STROKECOLOR = '#0088cf';
|
||||
//constants.VERTEX_SELECTION_COLOR = '#00a8ff';
|
||||
|
||||
class MyCustomCellRenderer extends CellRenderer {
|
||||
installCellOverlayListeners(state, overlay, shape) {
|
||||
super.installCellOverlayListeners.apply(this, arguments);
|
||||
let graph = state.view.graph;
|
||||
|
||||
InternalEvent.addGestureListeners(
|
||||
shape.node,
|
||||
function (evt) {
|
||||
graph.fireMouseEvent(
|
||||
InternalEvent.MOUSE_DOWN,
|
||||
new InternalMouseEvent(evt, state)
|
||||
);
|
||||
},
|
||||
function (evt) {
|
||||
graph.fireMouseEvent(
|
||||
InternalEvent.MOUSE_MOVE,
|
||||
new InternalMouseEvent(evt, state)
|
||||
);
|
||||
},
|
||||
function (evt) {}
|
||||
);
|
||||
|
||||
if (!Client.IS_TOUCH) {
|
||||
InternalEvent.addListener(shape.node, 'mouseup', function (evt) {
|
||||
overlay.fireEvent(
|
||||
new EventObject(InternalEvent.CLICK, 'event', evt, 'cell', state.cell)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MyCustomPopupMenuHandler extends PopupMenuHandler {
|
||||
// Configures automatic expand on mouseover
|
||||
autoExpand = true;
|
||||
|
||||
constructor(graph) {
|
||||
super(graph);
|
||||
|
||||
// Installs context menu
|
||||
this.factoryMethod = function (menu, cell, evt) {
|
||||
menu.addItem('Item 1', null, function () {
|
||||
alert('Item 1');
|
||||
});
|
||||
menu.addItem('Item 2', null, function () {
|
||||
alert('Item 2');
|
||||
});
|
||||
menu.addSeparator();
|
||||
|
||||
let submenu1 = menu.addItem('Submenu 1', null, null);
|
||||
menu.addItem(
|
||||
'Subitem 1',
|
||||
null,
|
||||
function () {
|
||||
alert('Subitem 1');
|
||||
},
|
||||
submenu1
|
||||
);
|
||||
menu.addItem(
|
||||
'Subitem 1',
|
||||
null,
|
||||
function () {
|
||||
alert('Subitem 2');
|
||||
},
|
||||
submenu1
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class MyCustomGraph extends Graph {
|
||||
createCellRenderer() {
|
||||
return new MyCustomCellRenderer();
|
||||
}
|
||||
}
|
||||
|
||||
// Creates the graph inside the given container
|
||||
let graph = new MyCustomGraph(container, null, [
|
||||
CellEditorHandler,
|
||||
TooltipHandler,
|
||||
SelectionCellsHandler,
|
||||
MyCustomPopupMenuHandler,
|
||||
ConnectionHandler,
|
||||
SelectionHandler,
|
||||
PanningHandler,
|
||||
RubberBandHandler,
|
||||
]);
|
||||
graph.setTooltips(true);
|
||||
|
||||
// Gets the default parent for inserting new cells. This
|
||||
// is normally the first child of the root (ie. layer 0).
|
||||
let parent = graph.getDefaultParent();
|
||||
|
||||
// Adds cells to the model in a single step
|
||||
let v1;
|
||||
graph.batchUpdate(() => {
|
||||
v1 = graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30);
|
||||
let v2 = graph.insertVertex(parent, null, 'World!', 200, 150, 80, 30);
|
||||
let e1 = graph.insertEdge(parent, null, '', v1, v2);
|
||||
});
|
||||
|
||||
// Creates a new overlay with an image and a tooltip and makes it "transparent" to events
|
||||
// and sets the overlay for the cell in the graph
|
||||
let overlay = new CellOverlay(
|
||||
new ImageBox('editors/images/overlays/check.png', 16, 16),
|
||||
'Overlay tooltip'
|
||||
);
|
||||
graph.addCellOverlay(v1, overlay);
|
||||
|
||||
return container;
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
|
@ -0,0 +1,588 @@
|
|||
/*
|
||||
Copyright 2021-present The maxGraph project Contributors
|
||||
Copyright (c) 2006-2015, JGraph Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
Scrollbars
|
||||
This example demonstrates using a scrollable table with different sections in a cell label.
|
||||
*/
|
||||
|
||||
import {
|
||||
Cell,
|
||||
CellRenderer,
|
||||
CellEditorHandler,
|
||||
Client,
|
||||
ConnectionHandler,
|
||||
constants,
|
||||
DomHelpers,
|
||||
domUtils,
|
||||
EdgeStyle,
|
||||
ImageBox,
|
||||
InternalEvent,
|
||||
Graph,
|
||||
GraphView,
|
||||
KeyHandler,
|
||||
PanningHandler,
|
||||
Perimeter,
|
||||
Point,
|
||||
Rectangle,
|
||||
RubberBandHandler,
|
||||
SelectionCellsHandler,
|
||||
SelectionHandler,
|
||||
xmlUtils,
|
||||
} from '@maxgraph/core';
|
||||
import {
|
||||
contextMenuTypes,
|
||||
contextMenuValues,
|
||||
globalValues,
|
||||
globalTypes,
|
||||
} from './shared/args.js';
|
||||
// style required by RubberBand
|
||||
import '@maxgraph/core/css/common.css';
|
||||
|
||||
const CSS_TEMPLATE = `
|
||||
table.title {
|
||||
border-color: black;
|
||||
border-collapse: collapse;
|
||||
cursor: move;
|
||||
height: 26px;
|
||||
border-bottom-style: none;
|
||||
color: black;
|
||||
}
|
||||
table.title th {
|
||||
font-size: 10pt;
|
||||
font-family: Verdana;
|
||||
white-space: nowrap;
|
||||
background: lightgray;
|
||||
font-weight: bold;
|
||||
}
|
||||
table.erd {
|
||||
font-size: 10pt;
|
||||
font-family: Verdana;
|
||||
border-color: black;
|
||||
border-collapse: collapse;
|
||||
overflow: auto;
|
||||
cursor: move;
|
||||
white-space: nowrap;
|
||||
}
|
||||
table.erd td {
|
||||
border-color: black;
|
||||
text-align: left;
|
||||
color: black;
|
||||
}
|
||||
button {
|
||||
position:absolute;
|
||||
}
|
||||
`;
|
||||
|
||||
// TODO apply this settings to the container used by the Graph
|
||||
const HTML_TEMPLATE = `
|
||||
<!-- Page passes the container for the graph to the program -->
|
||||
<body onload="main(document.getElementById('graphContainer'))">
|
||||
|
||||
<!-- Creates a container for the graph with a grid wallpaper. Width, height and cursor in the style are for IE only -->
|
||||
<div id="graphContainer"
|
||||
style="cursor:default;position:absolute;top:30px;left:0px;bottom:0px;right:0px;background:url('editors/images/grid.gif')">
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
|
||||
export default {
|
||||
title: 'Misc/Scrollbars',
|
||||
argTypes: {
|
||||
...contextMenuTypes,
|
||||
...globalTypes,
|
||||
},
|
||||
args: {
|
||||
...contextMenuValues,
|
||||
...globalValues,
|
||||
},
|
||||
};
|
||||
|
||||
const Template = ({ label, ...args }) => {
|
||||
const styleElm = document.createElement('style');
|
||||
styleElm.innerText = CSS_TEMPLATE;
|
||||
document.head.appendChild(styleElm);
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.style.position = 'relative';
|
||||
container.style.overflow = 'hidden';
|
||||
container.style.width = `${args.width}px`;
|
||||
container.style.height = `${args.height}px`;
|
||||
container.style.background = 'url(/images/grid.gif)';
|
||||
container.style.cursor = 'default';
|
||||
|
||||
// Disables foreignObjects
|
||||
Client.NO_FO = true;
|
||||
|
||||
class MyCustomSelectionHandler extends SelectionHandler {
|
||||
// Enables move preview in HTML to appear on top
|
||||
htmlPreview = true;
|
||||
}
|
||||
|
||||
// Disables the context menu
|
||||
InternalEvent.disableContextMenu(container);
|
||||
|
||||
// Defines global helper function to get y-coordinate for a given cell state and row
|
||||
let getRowY = function (state, tr) {
|
||||
let s = state.view.scale;
|
||||
let div = tr.parentNode.parentNode.parentNode;
|
||||
let offsetTop = parseInt(div.style.top);
|
||||
let y =
|
||||
state.y + (tr.offsetTop + tr.offsetHeight / 2 - div.scrollTop + offsetTop) * s;
|
||||
y = Math.min(state.y + state.height, Math.max(state.y + offsetTop * s, y));
|
||||
return y;
|
||||
};
|
||||
|
||||
// If connect preview is not moved away then getCellAt is used to detect the cell under
|
||||
// the mouse if the mouse is over the preview shape in IE (no event transparency), ie.
|
||||
// the built-in hit-detection of the HTML document will not be used in this case. This is
|
||||
// not a problem here since the preview moves away from the mouse as soon as it connects
|
||||
// to any given table row. This is because the edge connects to the outside of the row and
|
||||
// is aligned to the grid during the preview.
|
||||
class MyCustomConnectionHandler extends ConnectionHandler {
|
||||
movePreviewAway = false;
|
||||
|
||||
// Enables connect icons to appear on top of HTML
|
||||
moveIconFront = true;
|
||||
|
||||
// Defines an icon for creating new connections in the connection handler.
|
||||
// This will automatically disable the highlighting of the source vertex.
|
||||
connectImage = new ImageBox('images/connector.gif', 16, 16);
|
||||
|
||||
// Overrides target perimeter point for connection previews
|
||||
getTargetPerimeterPoint(state, me) {
|
||||
// Determines the y-coordinate of the target perimeter point
|
||||
// by using the currentRowNode assigned in updateRow
|
||||
let y = me.getY();
|
||||
if (this.currentRowNode != null) {
|
||||
y = getRowY(state, this.currentRowNode);
|
||||
}
|
||||
|
||||
// Checks on which side of the terminal to leave
|
||||
let x = state.x;
|
||||
if (this.previous.getCenterX() > state.getCenterX()) {
|
||||
x += state.width;
|
||||
}
|
||||
return new Point(x, y);
|
||||
}
|
||||
|
||||
// Overrides source perimeter point for connection previews
|
||||
getSourcePerimeterPoint(state, next, me) {
|
||||
let y = me.getY();
|
||||
if (this.sourceRowNode != null) {
|
||||
y = getRowY(state, this.sourceRowNode);
|
||||
}
|
||||
|
||||
// Checks on which side of the terminal to leave
|
||||
let x = state.x;
|
||||
if (next.x > state.getCenterX()) {
|
||||
x += state.width;
|
||||
}
|
||||
return new Point(x, y);
|
||||
}
|
||||
|
||||
// Disables connections to invalid rows
|
||||
isValidTarget(cell) {
|
||||
return this.currentRowNode != null;
|
||||
}
|
||||
|
||||
// Adds a new function to update the currentRow based on the given event
|
||||
// and return the DOM node for that row
|
||||
updateRow(target) {
|
||||
while (target != null && target.nodeName != 'TR') {
|
||||
target = target.parentNode;
|
||||
}
|
||||
this.currentRow = null;
|
||||
|
||||
// Checks if we're dealing with a row in the correct table
|
||||
if (target != null && target.parentNode.parentNode.className == 'erd') {
|
||||
// Stores the current row number in a property so that it can
|
||||
// be retrieved to create the preview and final edge
|
||||
let rowNumber = 0;
|
||||
let current = target.parentNode.firstChild;
|
||||
|
||||
while (target != current && current != null) {
|
||||
current = current.nextSibling;
|
||||
rowNumber++;
|
||||
}
|
||||
|
||||
this.currentRow = rowNumber + 1;
|
||||
} else {
|
||||
target = null;
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
// Adds placement of the connect icon based on the mouse event target (row)
|
||||
updateIcons(state, icons, me) {
|
||||
let target = me.getSource();
|
||||
target = this.updateRow(target);
|
||||
|
||||
if (target != null && this.currentRow != null) {
|
||||
let div = target.parentNode.parentNode.parentNode;
|
||||
let s = state.view.scale;
|
||||
|
||||
icons[0].node.style.visibility = 'visible';
|
||||
icons[0].bounds.x =
|
||||
state.x +
|
||||
target.offsetLeft +
|
||||
Math.min(state.width, target.offsetWidth * s) -
|
||||
this.icons[0].bounds.width -
|
||||
2;
|
||||
icons[0].bounds.y =
|
||||
state.y -
|
||||
this.icons[0].bounds.height / 2 +
|
||||
(target.offsetTop + target.offsetHeight / 2 - div.scrollTop + div.offsetTop) *
|
||||
s;
|
||||
icons[0].redraw();
|
||||
|
||||
this.currentRowNode = target;
|
||||
} else {
|
||||
icons[0].node.style.visibility = 'hidden';
|
||||
}
|
||||
}
|
||||
|
||||
// Updates the targetRow in the preview edge State
|
||||
mouseMove(sender, me) {
|
||||
if (this.edgeState != null) {
|
||||
this.currentRowNode = this.updateRow(me.getSource());
|
||||
|
||||
if (this.currentRow != null) {
|
||||
this.edgeState.cell.value.setAttribute('targetRow', this.currentRow);
|
||||
} else {
|
||||
this.edgeState.cell.value.setAttribute('targetRow', '0');
|
||||
}
|
||||
|
||||
// Destroys icon to prevent event redirection via image in IE
|
||||
this.destroyIcons();
|
||||
}
|
||||
super.mouseMove.apply(this, arguments);
|
||||
}
|
||||
|
||||
// Creates the edge state that may be used for preview
|
||||
createEdgeState(me) {
|
||||
let relation = doc.createElement('Relation');
|
||||
relation.setAttribute('sourceRow', this.currentRow || '0');
|
||||
relation.setAttribute('targetRow', '0');
|
||||
|
||||
let edge = this.createEdge(relation);
|
||||
let style = this.graph.getCellStyle(edge);
|
||||
let state = new Cell(this.graph.view, edge, style);
|
||||
|
||||
// Stores the source row in the handler
|
||||
this.sourceRowNode = this.currentRowNode;
|
||||
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
class MyCustomCellRenderer extends CellRenderer {
|
||||
// Scroll events should not start moving the vertex
|
||||
isLabelEvent(state, evt) {
|
||||
let source = InternalEvent.getSource(evt);
|
||||
|
||||
return (
|
||||
state.text != null &&
|
||||
source != state.text.node &&
|
||||
source != state.text.node.getElementsByTagName('div')[0]
|
||||
);
|
||||
}
|
||||
|
||||
// Adds scrollbars to the outermost div and keeps the
|
||||
// DIV position and size the same as the vertex
|
||||
redrawLabel(state) {
|
||||
super.redrawLabel.apply(this, arguments); // "supercall"
|
||||
let graph = state.view.graph;
|
||||
let model = graph.model;
|
||||
|
||||
if (state.cell.isVertex() && state.text != null) {
|
||||
// Scrollbars are on the div
|
||||
let s = graph.view.scale;
|
||||
let div = state.text.node.getElementsByTagName('div')[2];
|
||||
|
||||
if (div != null) {
|
||||
// Installs the handler for updating connected edges
|
||||
if (div.scrollHandler == null) {
|
||||
div.scrollHandler = true;
|
||||
|
||||
let updateEdges = () => {
|
||||
let edgeCount = state.cell.getEdgeCount();
|
||||
|
||||
// Only updates edges to avoid update in DOM order
|
||||
// for text label which would reset the scrollbar
|
||||
for (let i = 0; i < edgeCount; i++) {
|
||||
let edge = state.cell.getEdgeAt(i);
|
||||
graph.view.invalidate(edge, true, false);
|
||||
graph.view.validate(edge);
|
||||
}
|
||||
};
|
||||
|
||||
InternalEvent.addListener(div, 'scroll', updateEdges);
|
||||
InternalEvent.addListener(div, 'mouseup', updateEdges);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MyCustomGraphView extends GraphView {
|
||||
// Implements a special perimeter for table rows inside the table markup
|
||||
updateFloatingTerminalPoint(edge, start, end, source) {
|
||||
let next = this.getNextPoint(edge, end, source);
|
||||
let div = start.text.node.getElementsByTagName('div')[2];
|
||||
|
||||
let x = start.x;
|
||||
let y = start.getCenterY();
|
||||
|
||||
// Checks on which side of the terminal to leave
|
||||
if (next.x > x + start.width / 2) {
|
||||
x += start.width;
|
||||
}
|
||||
|
||||
if (div != null) {
|
||||
y = start.getCenterY() - div.scrollTop;
|
||||
|
||||
if (domUtils.isNode(edge.cell.value) && !start.cell.isCollapsed()) {
|
||||
let attr = source ? 'sourceRow' : 'targetRow';
|
||||
let row = parseInt(edge.cell.value.getAttribute(attr));
|
||||
|
||||
// HTML labels contain an outer table which is built-in
|
||||
let table = div.getElementsByTagName('table')[0];
|
||||
let trs = table.getElementsByTagName('tr');
|
||||
let tr = trs[Math.min(trs.length - 1, row - 1)];
|
||||
|
||||
// Gets vertical center of source or target row
|
||||
if (tr != null) {
|
||||
y = getRowY(start, tr);
|
||||
}
|
||||
}
|
||||
|
||||
// Keeps vertical coordinate inside start
|
||||
let offsetTop = parseInt(div.style.top) * start.view.scale;
|
||||
y = Math.min(
|
||||
isNaN(start.y + start.height) ? 9999999999999 : start.y + start.height,
|
||||
isNaN(Math.max(start.y + offsetTop, y))
|
||||
? 9999999999999
|
||||
: Math.max(start.y + offsetTop, y)
|
||||
);
|
||||
|
||||
// Updates the vertical position of the nearest point if we're not
|
||||
// dealing with a connection preview, in which case either the
|
||||
// edgeState or the absolutePoints are null
|
||||
if (edge != null && edge.absolutePoints != null) {
|
||||
next.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
edge.setAbsoluteTerminalPoint(new Point(x, y), source);
|
||||
|
||||
// Routes multiple incoming edges along common waypoints if
|
||||
// the edges have a common target row
|
||||
if (source && domUtils.isNode(edge.cell.value) && start != null && end != null) {
|
||||
let edges = this.graph.getEdgesBetween(start.cell, end.cell, true);
|
||||
let tmp = [];
|
||||
|
||||
// Filters the edges with the same source row
|
||||
let row = edge.cell.value.getAttribute('targetRow');
|
||||
|
||||
for (let i = 0; i < edges.length; i++) {
|
||||
if (
|
||||
domUtils.isNode(edges[i].value) &&
|
||||
edges[i].value.getAttribute('targetRow') == row
|
||||
) {
|
||||
tmp.push(edges[i]);
|
||||
}
|
||||
}
|
||||
|
||||
edges = tmp;
|
||||
|
||||
if (edges.length > 1 && edge.cell === edges[edges.length - 1]) {
|
||||
// Finds the vertical center
|
||||
let states = [];
|
||||
let y = 0;
|
||||
|
||||
for (let i = 0; i < edges.length; i++) {
|
||||
states[i] = this.getState(edges[i]);
|
||||
y += states[i].absolutePoints[0].y;
|
||||
}
|
||||
|
||||
y /= edges.length;
|
||||
|
||||
for (let i = 0; i < states.length; i++) {
|
||||
let x = states[i].absolutePoints[1].x;
|
||||
|
||||
if (states[i].absolutePoints.length < 5) {
|
||||
states[i].absolutePoints.splice(2, 0, new Point(x, y));
|
||||
} else {
|
||||
states[i].absolutePoints[2] = new Point(x, y);
|
||||
}
|
||||
|
||||
// Must redraw the previous edges with the changed point
|
||||
if (i < states.length - 1) {
|
||||
this.graph.cellRenderer.redraw(states[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MyCustomGraph extends Graph {
|
||||
// Override folding to allow for tables
|
||||
isCellFoldable(cell, collapse) {
|
||||
return cell.isVertex();
|
||||
}
|
||||
|
||||
// Overrides connectable state
|
||||
isCellConnectable(cell) {
|
||||
return !cell.isCollapsed();
|
||||
}
|
||||
|
||||
// Overrides getLabel to return empty labels for edges and
|
||||
// short markup for collapsed cells.
|
||||
getLabel(cell) {
|
||||
if (cell.isVertex()) {
|
||||
if (cell.isCollapsed()) {
|
||||
return (
|
||||
'<table style="overflow:hidden;" width="100%" height="100%" border="1" cellpadding="4" class="title" style="height:100%;">' +
|
||||
'<tr><th>Customers</th></tr>' +
|
||||
'</table>'
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
'<table style="overflow:hidden;" width="100%" border="1" cellpadding="4" class="title">' +
|
||||
'<tr><th colspan="2">Customers</th></tr>' +
|
||||
'</table>' +
|
||||
'<div style="overflow:auto;cursor:default;top:26px;bottom:0px;position:absolute;width:100%;">' +
|
||||
'<table width="100%" height="100%" border="1" cellpadding="4" class="erd">' +
|
||||
'<tr><td>' +
|
||||
'<img align="center" src="images/key.png"/>' +
|
||||
'<img align="center" src="images/plus.png"/>' +
|
||||
'</td><td>' +
|
||||
'<u>customerId</u></td></tr><tr><td></td><td>number</td></tr>' +
|
||||
'<tr><td></td><td>firstName</td></tr><tr><td></td><td>lastName</td></tr>' +
|
||||
'<tr><td></td><td>streetAddress</td></tr><tr><td></td><td>city</td></tr>' +
|
||||
'<tr><td></td><td>state</td></tr><tr><td></td><td>zip</td></tr>' +
|
||||
'</table></div>'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
createCellRenderer() {
|
||||
return new MyCustomCellRenderer();
|
||||
}
|
||||
|
||||
createGraphView() {
|
||||
return new MyCustomGraphView(this);
|
||||
}
|
||||
}
|
||||
|
||||
// Creates the graph inside the given container
|
||||
let graph = new MyCustomGraph(
|
||||
container,
|
||||
null,
|
||||
// use a dedicated set of plugins
|
||||
[
|
||||
CellEditorHandler,
|
||||
ConnectionHandler,
|
||||
MyCustomConnectionHandler,
|
||||
MyCustomSelectionHandler,
|
||||
PanningHandler,
|
||||
RubberBandHandler,
|
||||
SelectionCellsHandler,
|
||||
]
|
||||
);
|
||||
|
||||
// Uses the entity perimeter (below) as default
|
||||
graph.stylesheet.getDefaultVertexStyle().verticalAlign = constants.ALIGN.TOP;
|
||||
graph.stylesheet.getDefaultVertexStyle().perimeter = Perimeter.RectanglePerimeter;
|
||||
graph.stylesheet.getDefaultVertexStyle().shadow = true;
|
||||
graph.stylesheet.getDefaultVertexStyle().fillColor = '#DDEAFF';
|
||||
graph.stylesheet.getDefaultVertexStyle().gradientColor = '#A9C4EB';
|
||||
delete graph.stylesheet.getDefaultVertexStyle().strokeColor;
|
||||
|
||||
// Used for HTML labels that use up the complete vertex space (see
|
||||
// graph.cellRenderer.redrawLabel below for syncing the size)
|
||||
graph.stylesheet.getDefaultVertexStyle().overflow = 'fill';
|
||||
|
||||
// Uses the entity edge style as default
|
||||
graph.stylesheet.getDefaultEdgeStyle().edge = EdgeStyle.EntityRelation;
|
||||
graph.stylesheet.getDefaultEdgeStyle().strokeColor = 'black';
|
||||
graph.stylesheet.getDefaultEdgeStyle().fontColor = 'black';
|
||||
|
||||
// Allows new connections to be made but do not allow existing
|
||||
// connections to be changed for the sake of simplicity of this
|
||||
// example
|
||||
graph.setCellsDisconnectable(false);
|
||||
graph.setAllowDanglingEdges(false);
|
||||
graph.setCellsEditable(false);
|
||||
graph.setConnectable(true);
|
||||
graph.setPanning(true);
|
||||
graph.centerZoom = false;
|
||||
|
||||
// Enables HTML markup in all labels
|
||||
graph.setHtmlLabels(true);
|
||||
|
||||
// User objects (data) for the individual cells
|
||||
let doc = xmlUtils.createXmlDocument();
|
||||
|
||||
// Same should be used to create the XML node for the table
|
||||
// description and the rows (most probably as child nodes)
|
||||
let relation = doc.createElement('Relation');
|
||||
relation.setAttribute('sourceRow', '4');
|
||||
relation.setAttribute('targetRow', '6');
|
||||
|
||||
// Enables key handling (eg. escape)
|
||||
new KeyHandler(graph);
|
||||
|
||||
// Gets the default parent for inserting new cells. This
|
||||
// is normally the first child of the root (ie. layer 0).
|
||||
let parent = graph.getDefaultParent();
|
||||
|
||||
// Adds cells to the model in a single step
|
||||
let width = 160;
|
||||
let height = 230;
|
||||
|
||||
graph.batchUpdate(() => {
|
||||
let v1 = graph.insertVertex(parent, null, '', 20, 20, width, height);
|
||||
v1.geometry.alternateBounds = new Rectangle(0, 0, width, 26);
|
||||
|
||||
let v2 = graph.insertVertex(parent, null, '', 400, 150, width, height);
|
||||
v2.geometry.alternateBounds = new Rectangle(0, 0, width, 26);
|
||||
|
||||
graph.insertEdge(parent, null, relation, v1, v2);
|
||||
});
|
||||
|
||||
let btn1 = DomHelpers.button('+', function () {
|
||||
graph.zoomIn();
|
||||
});
|
||||
btn1.style.marginLeft = '20px';
|
||||
|
||||
document.body.appendChild(btn1);
|
||||
document.body.appendChild(
|
||||
DomHelpers.button('-', function () {
|
||||
graph.zoomOut();
|
||||
})
|
||||
);
|
||||
|
||||
return container;
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
Copyright 2021-present The maxGraph project Contributors
|
||||
Copyright (c) 2006-2013, JGraph Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
Show region
|
||||
|
||||
This example demonstrates using a custom rubberband handler to show the selected region in a new window.
|
||||
*/
|
||||
|
||||
import {
|
||||
eventUtils,
|
||||
Graph,
|
||||
InternalEvent,
|
||||
MaxLog as domUtils,
|
||||
MaxPopupMenu,
|
||||
Rectangle,
|
||||
RubberBandHandler,
|
||||
styleUtils,
|
||||
} from '@maxgraph/core';
|
||||
import {
|
||||
globalTypes,
|
||||
globalValues,
|
||||
rubberBandTypes,
|
||||
rubberBandValues,
|
||||
} from './shared/args.js';
|
||||
|
||||
const CSS_TEMPLATE = `
|
||||
body div.mxPopupMenu {
|
||||
-webkit-box-shadow: 3px 3px 6px #C0C0C0;
|
||||
-moz-box-shadow: 3px 3px 6px #C0C0C0;
|
||||
box-shadow: 3px 3px 6px #C0C0C0;
|
||||
background: white;
|
||||
position: absolute;
|
||||
border: 3px solid #e7e7e7;
|
||||
padding: 3px;
|
||||
}
|
||||
body table.mxPopupMenu {
|
||||
border-collapse: collapse;
|
||||
margin: 0px;
|
||||
}
|
||||
body tr.mxPopupMenuItem {
|
||||
color: black;
|
||||
cursor: default;
|
||||
}
|
||||
body td.mxPopupMenuItem {
|
||||
padding: 6px 60px 6px 30px;
|
||||
font-family: Arial;
|
||||
font-size: 10pt;
|
||||
}
|
||||
body td.mxPopupMenuIcon {
|
||||
background-color: white;
|
||||
padding: 0px;
|
||||
}
|
||||
body tr.mxPopupMenuItemHover {
|
||||
background-color: #eeeeee;
|
||||
color: black;
|
||||
}
|
||||
table.mxPopupMenu hr {
|
||||
border-top: solid 1px #cccccc;
|
||||
}
|
||||
table.mxPopupMenu tr {
|
||||
font-size: 4pt;
|
||||
}
|
||||
`;
|
||||
|
||||
// TODO apply this settings to the container used by the Graph
|
||||
const HTML_TEMPLATE = `
|
||||
<!-- Page passes the container for the graph to the program -->
|
||||
<body onload="main(document.getElementById('graphContainer'))">
|
||||
|
||||
<!-- Creates a container for the graph with a grid wallpaper -->
|
||||
<div id="graphContainer"
|
||||
style="overflow:hidden;width:321px;height:241px;background:url('editors/images/grid.gif');cursor:default;">
|
||||
</div>
|
||||
Use the right mouse button to select a region of the diagram and select <i>Show this</i>.
|
||||
</body>
|
||||
`;
|
||||
|
||||
export default {
|
||||
title: 'Misc/ShowRegion',
|
||||
argTypes: {
|
||||
...globalTypes,
|
||||
...rubberBandTypes,
|
||||
},
|
||||
args: {
|
||||
...globalValues,
|
||||
...rubberBandValues,
|
||||
},
|
||||
};
|
||||
|
||||
const Template = ({ label, ...args }) => {
|
||||
const styleElm = document.createElement('style');
|
||||
styleElm.innerText = CSS_TEMPLATE;
|
||||
document.head.appendChild(styleElm);
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.style.position = 'relative';
|
||||
container.style.overflow = 'hidden';
|
||||
container.style.width = `${args.width}px`;
|
||||
container.style.height = `${args.height}px`;
|
||||
container.style.background = 'url(/images/grid.gif)';
|
||||
container.style.cursor = 'default';
|
||||
|
||||
// Disables built-in context menu
|
||||
InternalEvent.disableContextMenu(container);
|
||||
|
||||
// Changes some default colors
|
||||
// TODO Find a way of modifying globally or setting locally! See https://github.com/maxGraph/maxGraph/issues/192
|
||||
//constants.HANDLE_FILLCOLOR = '#99ccff';
|
||||
//constants.HANDLE_STROKECOLOR = '#0088cf';
|
||||
//constants.VERTEX_SELECTION_COLOR = '#00a8ff';
|
||||
|
||||
// Creates the graph inside the given container
|
||||
let graph = new Graph(container);
|
||||
|
||||
class MyCustomRubberBandHandler extends RubberBandHandler {
|
||||
isForceRubberbandEvent(me) {
|
||||
return super.isForceRubberbandEvent(me) || me.isPopupTrigger();
|
||||
}
|
||||
|
||||
// Defines a new popup menu for region selection in the rubberband handler
|
||||
popupMenu = new MaxPopupMenu(function (menu, cell, evt) {
|
||||
let rect = new Rectangle(
|
||||
rubberband.x,
|
||||
rubberband.y,
|
||||
rubberband.width,
|
||||
rubberband.height
|
||||
);
|
||||
|
||||
menu.addItem('Show this', null, function () {
|
||||
rubberband.popupMenu.hideMenu();
|
||||
let bounds = graph.getGraphBounds();
|
||||
domUtils.show(
|
||||
graph,
|
||||
null,
|
||||
bounds.x - rubberband.x,
|
||||
bounds.y - rubberband.y,
|
||||
rubberband.width,
|
||||
rubberband.height
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
mouseDown(sender, me) {
|
||||
this.popupMenu.hideMenu();
|
||||
super.mouseDown(sender, me);
|
||||
}
|
||||
|
||||
mouseUp(sender, me) {
|
||||
if (eventUtils.isPopupTrigger(me.getEvent())) {
|
||||
if (!graph.getPlugin('PopupMenuHandler').isMenuShowing()) {
|
||||
let origin = styleUtils.getScrollOrigin();
|
||||
this.popupMenu.popup(
|
||||
me.getX() + origin.x + 1,
|
||||
me.getY() + origin.y + 1,
|
||||
null,
|
||||
me.getEvent()
|
||||
);
|
||||
this.reset();
|
||||
}
|
||||
} else {
|
||||
super.mouseUp(sender, me);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enables rubberband selection
|
||||
let rubberband = new MyCustomRubberBandHandler(graph);
|
||||
|
||||
// Gets the default parent for inserting new cells. This
|
||||
// is normally the first child of the root (ie. layer 0).
|
||||
let parent = graph.getDefaultParent();
|
||||
|
||||
// Adds cells to the model in a single step
|
||||
graph.batchUpdate(() => {
|
||||
let v1 = graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30);
|
||||
let v2 = graph.insertVertex(parent, null, 'World!', 200, 150, 80, 30);
|
||||
let e1 = graph.insertEdge(parent, null, '', v1, v2);
|
||||
});
|
||||
return container;
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
|
@ -0,0 +1,999 @@
|
|||
/*
|
||||
Copyright 2021-present The maxGraph project Contributors
|
||||
Copyright (c) 2006-2020, JGraph Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
====================
|
||||
README
|
||||
====================
|
||||
|
||||
- Edge-to-edge connections: We store the point where the mouse
|
||||
was released in the terminal points of the edge geometry and
|
||||
use that point to find the nearest segment on the target edge
|
||||
and the connection point between the two edges in
|
||||
mxGraphView.updateFixedTerminalPoint.
|
||||
|
||||
- The orthogonal router, which is implemented as an edge style,
|
||||
computes its result based on the output of mxGraphView.
|
||||
updateFixedTerminalPoint, which computes all connection points
|
||||
for edge-to-edge connections and constrained ports and vertices
|
||||
and stores them in state.absolutePoints.
|
||||
|
||||
- Routing directions are stored in the 'portConstraint' style.
|
||||
Possible values for this style horizontal and vertical. Note
|
||||
that this may have other values depending on the edge style.
|
||||
|
||||
- For edge-to-edge connections, a 'source-/targetConstraint'
|
||||
style is added in updateFixedTerminalPoint that contains the
|
||||
orientation of the segment that the edge connects to. Possible
|
||||
values are horizontal, vertical.
|
||||
|
||||
- An alternative solution for connection points via connection
|
||||
constraints is demonstrated. In this setup, the edge is connected
|
||||
to the parent cell directly. There are no child cells that act
|
||||
as "ports". Instead, the connection information is stored as a
|
||||
relative point in the connecting edge. (See also: portrefs.html
|
||||
for storing references to ports.)
|
||||
|
||||
*/
|
||||
|
||||
import {
|
||||
domUtils,
|
||||
styleUtils,
|
||||
mathUtils,
|
||||
cloneUtils,
|
||||
eventUtils,
|
||||
Graph,
|
||||
InternalEvent,
|
||||
RubberBandHandler,
|
||||
ConnectionHandler,
|
||||
ConnectionConstraint,
|
||||
Point,
|
||||
CylinderShape,
|
||||
CellRenderer,
|
||||
DomHelpers,
|
||||
EdgeStyle,
|
||||
Rectangle,
|
||||
EdgeHandler,
|
||||
StyleRegistry,
|
||||
EdgeSegmentHandler,
|
||||
UndoManager,
|
||||
CellEditorHandler,
|
||||
ConstraintHandler,
|
||||
Guide,
|
||||
ImageBox,
|
||||
GraphView,
|
||||
SelectionHandler,
|
||||
PanningHandler,
|
||||
TooltipHandler,
|
||||
SelectionCellsHandler,
|
||||
PopupMenuHandler,
|
||||
} from '@maxgraph/core';
|
||||
|
||||
import {
|
||||
contextMenuTypes,
|
||||
contextMenuValues,
|
||||
globalTypes,
|
||||
globalValues,
|
||||
rubberBandTypes,
|
||||
rubberBandValues,
|
||||
} from './shared/args.js';
|
||||
// style required by RubberBand
|
||||
import '@maxgraph/core/css/common.css';
|
||||
|
||||
export default {
|
||||
title: 'Connections/Wires',
|
||||
argTypes: {
|
||||
...contextMenuTypes,
|
||||
...globalTypes,
|
||||
...rubberBandTypes,
|
||||
},
|
||||
args: {
|
||||
...contextMenuValues,
|
||||
...globalValues,
|
||||
...rubberBandValues,
|
||||
},
|
||||
};
|
||||
|
||||
// TODO apply this settings to the container used by the Graph
|
||||
const HTML_TEMPLATE = `
|
||||
<body onload="main(document.getElementById('graphContainer'))">
|
||||
<div id="graphContainer"
|
||||
style="overflow:auto;position:relative;width:800px;height:600px;border:1px solid gray;background:url('images/wires-grid.gif');background-position:-1px 0px;cursor:crosshair;">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
const Template = ({ label, ...args }) => {
|
||||
const parentContainer = document.createElement('div');
|
||||
const container = document.createElement('div');
|
||||
|
||||
container.style.position = 'relative';
|
||||
container.style.overflow = 'hidden';
|
||||
container.style.width = `${args.width}px`;
|
||||
container.style.height = `${args.height}px`;
|
||||
container.style.background = 'url(/images/grid.gif)';
|
||||
container.style.cursor = 'default';
|
||||
parentContainer.appendChild(container);
|
||||
|
||||
// Changes some default colors
|
||||
// TODO Find a way of modifying globally or setting locally! See https://github.com/maxGraph/maxGraph/issues/192
|
||||
//constants.SHADOWCOLOR = '#C0C0C0';
|
||||
|
||||
let joinNodeSize = 7;
|
||||
let strokeWidth = 2;
|
||||
|
||||
class MyCustomGraph extends Graph {
|
||||
resetEdgesOnConnect = false;
|
||||
|
||||
createEdgeSegmentHandler(state) {
|
||||
return new MyCustomEdgeSegmentHandler(state);
|
||||
}
|
||||
|
||||
createGraphView() {
|
||||
return new MyCustomGraphView(this);
|
||||
}
|
||||
|
||||
createEdgeHandler(state) {
|
||||
return new MyCustomEdgeHandler(state);
|
||||
}
|
||||
|
||||
createHandler(state) {
|
||||
let result = null;
|
||||
|
||||
if (state != null) {
|
||||
if (state.cell.isEdge()) {
|
||||
let style = this.view.getEdgeStyle(state);
|
||||
|
||||
if (style == EdgeStyle.WireConnector) {
|
||||
return new EdgeSegmentHandler(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.createHandler.apply(this, arguments);
|
||||
}
|
||||
|
||||
// Adds oval markers for edge-to-edge connections.
|
||||
getCellStyle(cell) {
|
||||
let style = super.getCellStyle.apply(this, arguments);
|
||||
|
||||
if (style != null && cell?.isEdge()) {
|
||||
style = cloneUtils.clone(style);
|
||||
|
||||
if (cell.getTerminal(true)?.isEdge()) {
|
||||
style.startArrow = 'oval';
|
||||
}
|
||||
|
||||
if (cell.getTerminal(false)?.isEdge()) {
|
||||
style.endArrow = 'oval';
|
||||
}
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
getTooltipForCell(cell) {
|
||||
let tip = '';
|
||||
|
||||
if (cell != null) {
|
||||
let src = cell.getTerminal(true);
|
||||
if (src != null) {
|
||||
tip += this.getTooltipForCell(src) + ' ';
|
||||
}
|
||||
|
||||
let parent = cell.getParent();
|
||||
if (parent.isVertex()) {
|
||||
tip += this.getTooltipForCell(parent) + '.';
|
||||
}
|
||||
|
||||
tip += super.getTooltipForCell.apply(this, arguments);
|
||||
|
||||
let trg = cell.getTerminal(false);
|
||||
if (trg != null) {
|
||||
tip += ' ' + this.getTooltipForCell(trg);
|
||||
}
|
||||
}
|
||||
return tip;
|
||||
}
|
||||
|
||||
// Alternative solution for implementing connection points without child cells.
|
||||
// This can be extended as shown in portrefs.html example to allow for per-port
|
||||
// incoming/outgoing direction.
|
||||
getAllConnectionConstraints(terminal) {
|
||||
let geo = terminal != null ? terminal.cell.getGeometry() : null;
|
||||
|
||||
if (
|
||||
(geo != null ? !geo.relative : false) &&
|
||||
terminal.cell.isVertex() &&
|
||||
terminal.cell.getChildCount() === 0
|
||||
) {
|
||||
return [
|
||||
new ConnectionConstraint(new Point(0, 0.5), false),
|
||||
new ConnectionConstraint(new Point(1, 0.5), false),
|
||||
];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Provide means to make EdgeHandler and ConnectionHandler instantiate this subclass!
|
||||
class MyCustomConstraintHandler extends ConstraintHandler {
|
||||
// Replaces the port image
|
||||
pointImage = new ImageBox('images/dot.gif', 10, 10);
|
||||
}
|
||||
|
||||
class MyCustomGuide extends Guide {
|
||||
// Alt disables guides
|
||||
isEnabledForEvent(evt) {
|
||||
return !eventUtils.isAltDown(evt);
|
||||
}
|
||||
}
|
||||
|
||||
class MyCustomEdgeHandler extends EdgeHandler {
|
||||
// Enables snapping waypoints to terminals
|
||||
snapToTerminals = true;
|
||||
|
||||
isConnectableCell(cell) {
|
||||
return graph.getPlugin('ConnectionHandler').isConnectableCell(cell);
|
||||
}
|
||||
|
||||
connect(edge, terminal, isSource, isClone, me) {
|
||||
let result = null;
|
||||
let model = this.graph.getDataModel();
|
||||
let parent = model.getParent(edge);
|
||||
|
||||
model.beginUpdate();
|
||||
try {
|
||||
result = super.connect.apply(this, arguments);
|
||||
let geo = model.getGeometry(result);
|
||||
|
||||
if (geo != null) {
|
||||
geo = geo.clone();
|
||||
let pt = null;
|
||||
|
||||
if (terminal.isEdge()) {
|
||||
pt = this.abspoints[this.isSource ? 0 : this.abspoints.length - 1];
|
||||
pt.x = pt.x / this.graph.view.scale - this.graph.view.translate.x;
|
||||
pt.y = pt.y / this.graph.view.scale - this.graph.view.translate.y;
|
||||
|
||||
let pstate = this.graph.getView().getState(edge.getParent());
|
||||
|
||||
if (pstate != null) {
|
||||
pt.x -= pstate.origin.x;
|
||||
pt.y -= pstate.origin.y;
|
||||
}
|
||||
|
||||
pt.x -= this.graph.panDx / this.graph.view.scale;
|
||||
pt.y -= this.graph.panDy / this.graph.view.scale;
|
||||
}
|
||||
|
||||
geo.setTerminalPoint(pt, isSource);
|
||||
model.setGeometry(edge, geo);
|
||||
}
|
||||
} finally {
|
||||
model.endUpdate();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
createMarker() {
|
||||
let marker = super.createMarker.apply(this, arguments);
|
||||
// Adds in-place highlighting when reconnecting existing edges
|
||||
marker.highlight.highlight =
|
||||
this.graph.getPlugin('ConnectionHandler').marker.highlight.highlight;
|
||||
return marker;
|
||||
}
|
||||
}
|
||||
|
||||
// Switch for black background and bright styles
|
||||
let invert = false;
|
||||
let MyCustomCellEditorHandler;
|
||||
|
||||
if (invert) {
|
||||
container.style.backgroundColor = 'black';
|
||||
|
||||
// White in-place editor text color
|
||||
MyCustomCellEditorHandler = class extends CellEditorHandler {
|
||||
startEditing(cell, trigger) {
|
||||
super.startEditing.apply(this, arguments);
|
||||
|
||||
if (this.textarea != null) {
|
||||
this.textarea.style.color = '#FFFFFF';
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
MyCustomCellEditorHandler = CellEditorHandler;
|
||||
}
|
||||
|
||||
class MyCustomSelectionHandler extends SelectionHandler {
|
||||
previewColor = invert ? 'white' : 'black';
|
||||
// Enables guides
|
||||
guidesEnabled = true;
|
||||
|
||||
createGuide() {
|
||||
return new MyCustomGuide(this.graph, this.getGuideStates());
|
||||
}
|
||||
}
|
||||
|
||||
class MyCustomPanningHandler extends PanningHandler {
|
||||
// Panning handler consumed right click so this must be
|
||||
// disabled if right click should stop connection handler.
|
||||
isPopupTrigger() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class MyCustomConnectionHandler extends ConnectionHandler {
|
||||
// If connect preview is not moved away then getCellAt is used to detect the cell under
|
||||
// the mouse if the mouse is over the preview shape in IE (no event transparency), ie.
|
||||
// the built-in hit-detection of the HTML document will not be used in this case.
|
||||
movePreviewAway = false;
|
||||
waypointsEnabled = true;
|
||||
|
||||
// Starts connections on the background in wire-mode
|
||||
isStartEvent(me) {
|
||||
return checkbox.checked || super.isStartEvent.apply(this, arguments);
|
||||
}
|
||||
|
||||
// Avoids any connections for gestures within tolerance except when in wire-mode
|
||||
// or when over a port
|
||||
mouseUp(sender, me) {
|
||||
if (this.first != null && this.previous != null) {
|
||||
let point = styleUtils.convertPoint(this.graph.container, me.getX(), me.getY());
|
||||
let dx = Math.abs(point.x - this.first.x);
|
||||
let dy = Math.abs(point.y - this.first.y);
|
||||
|
||||
if (dx < this.graph.tolerance && dy < this.graph.tolerance) {
|
||||
// Selects edges in non-wire mode for single clicks, but starts
|
||||
// connecting for non-edges regardless of wire-mode
|
||||
if (!checkbox.checked && this.previous.cell.isEdge()) {
|
||||
this.reset();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
super.mouseUp.apply(this, arguments);
|
||||
}
|
||||
|
||||
// Overrides methods to preview and create new edges.
|
||||
|
||||
// Sets source terminal point for edge-to-edge connections.
|
||||
createEdgeState(me) {
|
||||
let edge = this.graph.createEdge();
|
||||
|
||||
if (this.sourceConstraint != null && this.previous != null) {
|
||||
edge.style =
|
||||
'exitX' +
|
||||
'=' +
|
||||
this.sourceConstraint.point.x +
|
||||
';' +
|
||||
'exitY' +
|
||||
'=' +
|
||||
this.sourceConstraint.point.y +
|
||||
';';
|
||||
} else if (me.getCell().isEdge()) {
|
||||
let scale = this.graph.view.scale;
|
||||
let tr = this.graph.view.translate;
|
||||
let pt = new Point(
|
||||
this.graph.snap(me.getGraphX() / scale) - tr.x,
|
||||
this.graph.snap(me.getGraphY() / scale) - tr.y
|
||||
);
|
||||
edge.geometry.setTerminalPoint(pt, true);
|
||||
}
|
||||
|
||||
return this.graph.view.createState(edge);
|
||||
}
|
||||
|
||||
// Uses right mouse button to create edges on background (see also: lines 67 ff)
|
||||
isStopEvent(me) {
|
||||
return me.getState() != null || eventUtils.isRightMouseButton(me.getEvent());
|
||||
}
|
||||
|
||||
// Updates target terminal point for edge-to-edge connections.
|
||||
updateCurrentState(me, point) {
|
||||
super.updateCurrentState.apply(this, arguments);
|
||||
|
||||
if (this.edgeState != null) {
|
||||
this.edgeState.cell.geometry.setTerminalPoint(null, false);
|
||||
|
||||
if (
|
||||
this.shape != null &&
|
||||
this.currentState != null &&
|
||||
this.currentState.cell.isEdge()
|
||||
) {
|
||||
let scale = this.graph.view.scale;
|
||||
let tr = this.graph.view.translate;
|
||||
let pt = new Point(
|
||||
this.graph.snap(me.getGraphX() / scale) - tr.x,
|
||||
this.graph.snap(me.getGraphY() / scale) - tr.y
|
||||
);
|
||||
this.edgeState.cell.geometry.setTerminalPoint(pt, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Adds in-place highlighting for complete cell area (no hotspot).
|
||||
createMarker() {
|
||||
let marker = super.createMarker.apply(this, arguments);
|
||||
|
||||
// Uses complete area of cell for new connections (no hotspot)
|
||||
marker.intersects = function (state, evt) {
|
||||
return true;
|
||||
};
|
||||
|
||||
// Adds in-place highlighting
|
||||
//const mxCellHighlightHighlight = mxCellHighlight.prototype.highlight;
|
||||
marker.highlight.highlight = function (state) {
|
||||
// TODO: Should this be a subclass of marker rather than assigning directly?
|
||||
if (this.state != state) {
|
||||
if (this.state != null) {
|
||||
this.state.style = this.lastStyle;
|
||||
|
||||
// Workaround for shape using current stroke width if no strokewidth defined
|
||||
this.state.style.strokeWidth = this.state.style.strokeWidth || '1';
|
||||
this.state.style.strokeColor = this.state.style.strokeColor || 'none';
|
||||
|
||||
if (this.state.shape != null) {
|
||||
this.state.view.graph.cellRenderer.configureShape(this.state);
|
||||
this.state.shape.redraw();
|
||||
}
|
||||
}
|
||||
|
||||
if (state != null) {
|
||||
this.lastStyle = state.style;
|
||||
state.style = cloneUtils.clone(state.style);
|
||||
state.style.strokeColor = '#00ff00';
|
||||
state.style.strokeWidth = '3';
|
||||
|
||||
if (state.shape != null) {
|
||||
state.view.graph.cellRenderer.configureShape(state);
|
||||
state.shape.redraw();
|
||||
}
|
||||
}
|
||||
this.state = state;
|
||||
}
|
||||
};
|
||||
|
||||
return marker;
|
||||
}
|
||||
|
||||
// Makes sure non-relative cells can only be connected via constraints
|
||||
isConnectableCell(cell) {
|
||||
if (cell.isEdge()) {
|
||||
return true;
|
||||
} else {
|
||||
let geo = cell != null ? cell.getGeometry() : null;
|
||||
return geo != null ? geo.relative : false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Updates connection points before the routing is called.
|
||||
|
||||
class MyCustomGraphView extends GraphView {
|
||||
// Computes the position of edge to edge connection points.
|
||||
updateFixedTerminalPoint(edge, terminal, source, constraint) {
|
||||
let pt = null;
|
||||
|
||||
if (constraint != null) {
|
||||
pt = this.graph.getConnectionPoint(terminal, constraint);
|
||||
}
|
||||
|
||||
if (source) {
|
||||
edge.sourceSegment = null;
|
||||
} else {
|
||||
edge.targetSegment = null;
|
||||
}
|
||||
|
||||
if (pt == null) {
|
||||
let s = this.scale;
|
||||
let tr = this.translate;
|
||||
let orig = edge.origin;
|
||||
let geo = edge.cell.getGeometry();
|
||||
pt = geo.getTerminalPoint(source);
|
||||
|
||||
// Computes edge-to-edge connection point
|
||||
if (pt != null) {
|
||||
pt = new Point(s * (tr.x + pt.x + orig.x), s * (tr.y + pt.y + orig.y));
|
||||
|
||||
// Finds nearest segment on edge and computes intersection
|
||||
if (terminal != null && terminal.absolutePoints != null) {
|
||||
let seg = mathUtils.findNearestSegment(terminal, pt.x, pt.y);
|
||||
|
||||
// Finds orientation of the segment
|
||||
let p0 = terminal.absolutePoints[seg];
|
||||
let pe = terminal.absolutePoints[seg + 1];
|
||||
let horizontal = p0.x - pe.x === 0;
|
||||
|
||||
// Stores the segment in the edge state
|
||||
let key = source ? 'sourceConstraint' : 'targetConstraint';
|
||||
let value = horizontal ? 'horizontal' : 'vertical';
|
||||
edge.style[key] = value;
|
||||
|
||||
// Keeps the coordinate within the segment bounds
|
||||
if (horizontal) {
|
||||
pt.x = p0.x;
|
||||
pt.y = Math.min(pt.y, Math.max(p0.y, pe.y));
|
||||
pt.y = Math.max(pt.y, Math.min(p0.y, pe.y));
|
||||
} else {
|
||||
pt.y = p0.y;
|
||||
pt.x = Math.min(pt.x, Math.max(p0.x, pe.x));
|
||||
pt.x = Math.max(pt.x, Math.min(p0.x, pe.x));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Computes constraint connection points on vertices and ports
|
||||
else if (terminal != null && terminal.cell.geometry.relative) {
|
||||
pt = new Point(
|
||||
this.getRoutingCenterX(terminal),
|
||||
this.getRoutingCenterY(terminal)
|
||||
);
|
||||
}
|
||||
|
||||
// Snaps point to grid
|
||||
/*if (pt != null)
|
||||
{
|
||||
let tr = this.graph.view.translate;
|
||||
let s = this.graph.view.scale;
|
||||
|
||||
pt.x = (this.graph.snap(pt.x / s - tr.x) + tr.x) * s;
|
||||
pt.y = (this.graph.snap(pt.y / s - tr.y) + tr.y) * s;
|
||||
}*/
|
||||
}
|
||||
|
||||
edge.setAbsoluteTerminalPoint(pt, source);
|
||||
}
|
||||
}
|
||||
|
||||
// Updates the terminal and control points in the cloned preview.
|
||||
class MyCustomEdgeSegmentHandler extends EdgeSegmentHandler {
|
||||
clonePreviewState(point, terminal) {
|
||||
let clone = super.clonePreviewState.apply(this, arguments);
|
||||
clone.cell = clone.cell.clone();
|
||||
|
||||
if (this.isSource || this.isTarget) {
|
||||
clone.cell.geometry = clone.cell.geometry.clone();
|
||||
|
||||
// Sets the terminal point of an edge if we're moving one of the endpoints
|
||||
if (clone.cell.isEdge()) {
|
||||
// TODO: Only set this if the target or source terminal is an edge
|
||||
clone.cell.geometry.setTerminalPoint(point, this.isSource);
|
||||
} else {
|
||||
clone.cell.geometry.setTerminalPoint(null, this.isSource);
|
||||
}
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
|
||||
// Imlements a custom resistor shape. Direction currently ignored here.
|
||||
|
||||
class ResistorShape extends CylinderShape {
|
||||
constructor() {
|
||||
// TODO: The original didn't seem to call the super
|
||||
super(null, null, null, null);
|
||||
}
|
||||
|
||||
redrawPath(path, x, y, w, h, isForeground) {
|
||||
let dx = w / 16;
|
||||
|
||||
if (isForeground) {
|
||||
path.moveTo(0, h / 2);
|
||||
path.lineTo(2 * dx, h / 2);
|
||||
path.lineTo(3 * dx, 0);
|
||||
path.lineTo(5 * dx, h);
|
||||
path.lineTo(7 * dx, 0);
|
||||
path.lineTo(9 * dx, h);
|
||||
path.lineTo(11 * dx, 0);
|
||||
path.lineTo(13 * dx, h);
|
||||
path.lineTo(14 * dx, h / 2);
|
||||
path.lineTo(16 * dx, h / 2);
|
||||
path.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CellRenderer.registerShape('resistor', ResistorShape);
|
||||
|
||||
// Imlements a custom resistor shape. Direction currently ignored here.
|
||||
|
||||
EdgeStyle.WireConnector = function (state, source, target, hints, result) {
|
||||
// Creates array of all way- and terminalpoints
|
||||
let pts = state.absolutePoints;
|
||||
let horizontal = true;
|
||||
let hint = null;
|
||||
|
||||
// Gets the initial connection from the source terminal or edge
|
||||
if (source != null && source.cell.isEdge()) {
|
||||
horizontal = state.style.sourceConstraint == 'horizontal';
|
||||
} else if (source != null) {
|
||||
horizontal = source.style.portConstraint != 'vertical';
|
||||
|
||||
// Checks the direction of the shape and rotates
|
||||
let direction = source.style.direction;
|
||||
|
||||
if (direction == 'north' || direction == 'south') {
|
||||
horizontal = !horizontal;
|
||||
}
|
||||
}
|
||||
|
||||
// Adds the first point
|
||||
// TODO: Should move along connected segment
|
||||
let pt = pts[0];
|
||||
|
||||
if (pt == null && source != null) {
|
||||
pt = new Point(
|
||||
state.view.getRoutingCenterX(source),
|
||||
state.view.getRoutingCenterY(source)
|
||||
);
|
||||
} else if (pt != null) {
|
||||
pt = pt.clone();
|
||||
}
|
||||
|
||||
let first = pt;
|
||||
|
||||
// Adds the waypoints
|
||||
if (hints != null && hints.length > 0) {
|
||||
// FIXME: First segment not movable
|
||||
/*hint = state.view.transformControlPoint(state, hints[0]);
|
||||
MaxLog.show();
|
||||
MaxLog.debug(hints.length,'hints0.y='+hint.y, pt.y)
|
||||
|
||||
if (horizontal && Math.floor(hint.y) != Math.floor(pt.y))
|
||||
{
|
||||
MaxLog.show();
|
||||
MaxLog.debug('add waypoint');
|
||||
|
||||
pt = new Point(pt.x, hint.y);
|
||||
result.push(pt);
|
||||
pt = pt.clone();
|
||||
//horizontal = !horizontal;
|
||||
}*/
|
||||
|
||||
for (let i = 0; i < hints.length; i++) {
|
||||
horizontal = !horizontal;
|
||||
hint = state.view.transformControlPoint(state, hints[i]);
|
||||
|
||||
if (horizontal) {
|
||||
if (pt.y !== hint.y) {
|
||||
pt.y = hint.y;
|
||||
result.push(pt.clone());
|
||||
}
|
||||
} else if (pt.x !== hint.x) {
|
||||
pt.x = hint.x;
|
||||
result.push(pt.clone());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
hint = pt;
|
||||
}
|
||||
|
||||
// Adds the last point
|
||||
pt = pts[pts.length - 1];
|
||||
|
||||
// TODO: Should move along connected segment
|
||||
if (pt == null && target != null) {
|
||||
pt = new Point(
|
||||
state.view.getRoutingCenterX(target),
|
||||
state.view.getRoutingCenterY(target)
|
||||
);
|
||||
}
|
||||
|
||||
if (horizontal) {
|
||||
if (pt.y !== hint.y && first.x !== pt.x) {
|
||||
result.push(new Point(pt.x, hint.y));
|
||||
}
|
||||
} else if (pt.x !== hint.x && first.y !== pt.y) {
|
||||
result.push(new Point(hint.x, pt.y));
|
||||
}
|
||||
};
|
||||
|
||||
StyleRegistry.putValue('wireEdgeStyle', EdgeStyle.WireConnector);
|
||||
|
||||
let graph = new MyCustomGraph(container, null, [
|
||||
MyCustomCellEditorHandler,
|
||||
TooltipHandler,
|
||||
SelectionCellsHandler,
|
||||
PopupMenuHandler,
|
||||
MyCustomConnectionHandler,
|
||||
MyCustomSelectionHandler,
|
||||
MyCustomPanningHandler,
|
||||
]);
|
||||
|
||||
let labelBackground = invert ? '#000000' : '#FFFFFF';
|
||||
let fontColor = invert ? '#FFFFFF' : '#000000';
|
||||
let strokeColor = invert ? '#C0C0C0' : '#000000';
|
||||
let fillColor = invert ? 'none' : '#FFFFFF';
|
||||
|
||||
graph.view.scale = 1;
|
||||
graph.setPanning(true);
|
||||
graph.setConnectable(true);
|
||||
graph.setConnectableEdges(true);
|
||||
graph.setDisconnectOnMove(false);
|
||||
graph.foldingEnabled = false;
|
||||
|
||||
//Maximum size
|
||||
graph.maximumGraphBounds = new Rectangle(0, 0, 800, 600);
|
||||
graph.border = 50;
|
||||
|
||||
// Enables return key to stop editing (use shift-enter for newlines)
|
||||
graph.setEnterStopsCellEditing(true);
|
||||
|
||||
// Adds rubberband selection
|
||||
new RubberBandHandler(graph);
|
||||
|
||||
// Adds a special tooltip for edges
|
||||
graph.setTooltips(true);
|
||||
|
||||
let style = graph.getStylesheet().getDefaultEdgeStyle();
|
||||
delete style.endArrow;
|
||||
style.strokeColor = strokeColor;
|
||||
style.labelBackgroundColor = labelBackground;
|
||||
style.edgeStyle = 'wireEdgeStyle';
|
||||
style.fontColor = fontColor;
|
||||
style.fontSize = '9';
|
||||
style.movable = '0';
|
||||
style.strokeWidth = strokeWidth;
|
||||
//style.rounded = '1';
|
||||
|
||||
// Sets join node size
|
||||
style.startSize = joinNodeSize;
|
||||
style.endSize = joinNodeSize;
|
||||
|
||||
style = graph.getStylesheet().getDefaultVertexStyle();
|
||||
style.gradientDirection = 'south';
|
||||
//style.gradientColor = '#909090';
|
||||
style.strokeColor = strokeColor;
|
||||
//style.fillColor = '#e0e0e0';
|
||||
style.fillColor = 'none';
|
||||
style.fontColor = fontColor;
|
||||
style.fontStyle = '1';
|
||||
style.fontSize = '12';
|
||||
style.resizable = '0';
|
||||
style.rounded = '1';
|
||||
style.strokeWidth = strokeWidth;
|
||||
|
||||
let parent = graph.getDefaultParent();
|
||||
|
||||
graph.batchUpdate(() => {
|
||||
let v1 = graph.insertVertex(parent, null, 'J1', 80, 40, 40, 80, {
|
||||
verticalLabelPosition: 'top',
|
||||
verticalAlign: 'bottom',
|
||||
shadow: true,
|
||||
fillColor,
|
||||
});
|
||||
v1.setConnectable(false);
|
||||
|
||||
let v11 = graph.insertVertex(v1, null, '1', 0, 0, 10, 16, {
|
||||
shape: 'line',
|
||||
align: 'left',
|
||||
verticalAlign: 'middle',
|
||||
fontSize: 10,
|
||||
routingCenterX: -0.5,
|
||||
spacingLeft: 12,
|
||||
fontColor,
|
||||
strokeColor,
|
||||
});
|
||||
v11.geometry.relative = true;
|
||||
v11.geometry.offset = new Point(-v11.geometry.width, 2);
|
||||
let v12 = v11.clone();
|
||||
v12.value = '2';
|
||||
v12.geometry.offset = new Point(-v11.geometry.width, 22);
|
||||
v1.insert(v12);
|
||||
let v13 = v11.clone();
|
||||
v13.value = '3';
|
||||
v13.geometry.offset = new Point(-v11.geometry.width, 42);
|
||||
v1.insert(v13);
|
||||
let v14 = v11.clone();
|
||||
v14.value = '4';
|
||||
v14.geometry.offset = new Point(-v11.geometry.width, 62);
|
||||
v1.insert(v14);
|
||||
|
||||
let v15 = v11.clone();
|
||||
v15.value = '5';
|
||||
v15.geometry.x = 1;
|
||||
v15.style = {
|
||||
shape: 'line',
|
||||
align: 'right',
|
||||
verticalAlign: 'middle',
|
||||
fontSize: 10,
|
||||
routingCenterX: 0.5,
|
||||
spacingRight: 12,
|
||||
fontColor,
|
||||
strokeColor,
|
||||
};
|
||||
v15.geometry.offset = new Point(0, 2);
|
||||
v1.insert(v15);
|
||||
let v16 = v15.clone();
|
||||
v16.value = '6';
|
||||
v16.geometry.offset = new Point(0, 22);
|
||||
v1.insert(v16);
|
||||
let v17 = v15.clone();
|
||||
v17.value = '7';
|
||||
v17.geometry.offset = new Point(0, 42);
|
||||
v1.insert(v17);
|
||||
let v18 = v15.clone();
|
||||
v18.value = '8';
|
||||
v18.geometry.offset = new Point(0, 62);
|
||||
v1.insert(v18);
|
||||
|
||||
let v19 = v15.clone();
|
||||
v19.value = 'clk';
|
||||
v19.geometry.x = 0.5;
|
||||
v19.geometry.y = 1;
|
||||
v19.geometry.width = 10;
|
||||
v19.geometry.height = 4;
|
||||
// NOTE: portConstraint is defined for east direction, so must be inverted here
|
||||
v19.style = {
|
||||
shape: 'triangle',
|
||||
direction: 'north',
|
||||
spacingBottom: 12,
|
||||
align: 'center',
|
||||
portConstraint: 'horizontal',
|
||||
fontSize: 8,
|
||||
strokeColor,
|
||||
routingCenterY: 0.5,
|
||||
};
|
||||
v19.geometry.offset = new Point(-4, -4);
|
||||
v1.insert(v19);
|
||||
|
||||
let v2 = graph.insertVertex(parent, null, 'R1', 220, 220, 80, 20, {
|
||||
shape: 'resistor',
|
||||
verticalLabelPosition: 'top',
|
||||
verticalAlign: 'bottom',
|
||||
});
|
||||
|
||||
// Uses implementation of connection points via constraints (see above)
|
||||
//v2.setConnectable(false);
|
||||
|
||||
/*let v21 = graph.insertVertex(v2, null, 'A', 0, 0.5, 10, 1,
|
||||
'shape=none;spacingBottom=11;spacingLeft=1;align=left;fontSize=8;'+
|
||||
'fontColor=#4c4c4c;strokeColor=#909090;');
|
||||
v21.geometry.relative = true;
|
||||
v21.geometry.offset = new Point(0, -1);
|
||||
|
||||
let v22 = graph.insertVertex(v2, null, 'B', 1, 0.5, 10, 1,
|
||||
'spacingBottom=11;spacingLeft=1;align=left;fontSize=8;'+
|
||||
'fontColor=#4c4c4c;strokeColor=#909090;');
|
||||
v22.geometry.relative = true;
|
||||
v22.geometry.offset = new Point(-10, -1);*/
|
||||
|
||||
let v3 = graph.addCell(graph.getDataModel().cloneCell(v1));
|
||||
v3.value = 'J3';
|
||||
v3.geometry.x = 420;
|
||||
v3.geometry.y = 340;
|
||||
|
||||
// Connection constraints implemented in edges, alternatively this
|
||||
// can be implemented using references, see: portrefs.html
|
||||
let e1 = graph.insertEdge(parent, null, 'e1', v1.getChildAt(7), v2, {
|
||||
entryX: 0,
|
||||
entryY: 0.5,
|
||||
entryPerimeter: 0,
|
||||
});
|
||||
e1.geometry.points = [new Point(180, 110)];
|
||||
|
||||
let e2 = graph.insertEdge(parent, null, 'e2', v1.getChildAt(4), v2, {
|
||||
entryX: 1,
|
||||
entryY: 0.5,
|
||||
entryPerimeter: 0,
|
||||
});
|
||||
e2.geometry.points = [new Point(320, 50), new Point(320, 230)];
|
||||
|
||||
let e3 = graph.insertEdge(parent, null, 'crossover', e1, e2);
|
||||
e3.geometry.setTerminalPoint(new Point(180, 140), true);
|
||||
e3.geometry.setTerminalPoint(new Point(320, 140), false);
|
||||
|
||||
// let e1 = graph.insertEdge(parent, null, 'e1', v1.getChildAt(7), v2.getChildAt(0));
|
||||
// e1.geometry.points = [new Point(180, 140)];
|
||||
|
||||
// let e2 = graph.insertEdge(parent, null, '', v1.getChildAt(4), v2.getChildAt(1));
|
||||
// e2.geometry.points = [new Point(320, 80)];
|
||||
|
||||
// let e3 = graph.insertEdge(parent, null, 'crossover', e1, e2);
|
||||
// e3.geometry.setTerminalPoint(new Point(180, 160), true);
|
||||
// e3.geometry.setTerminalPoint(new Point(320, 160), false);
|
||||
|
||||
let e4 = graph.insertEdge(parent, null, 'e4', v2, v3.getChildAt(0), {
|
||||
exitX: 1,
|
||||
exitY: 0.5,
|
||||
entryPerimeter: 0,
|
||||
});
|
||||
e4.geometry.points = [new Point(380, 230)];
|
||||
|
||||
let e5 = graph.insertEdge(parent, null, 'e5', v3.getChildAt(5), v1.getChildAt(0));
|
||||
e5.geometry.points = [new Point(500, 310), new Point(500, 20), new Point(50, 20)];
|
||||
|
||||
let e6 = graph.insertEdge(parent, null, '');
|
||||
e6.geometry.setTerminalPoint(new Point(100, 500), true);
|
||||
e6.geometry.setTerminalPoint(new Point(600, 500), false);
|
||||
|
||||
let e7 = graph.insertEdge(parent, null, 'e7', v3.getChildAt(7), e6);
|
||||
e7.geometry.setTerminalPoint(new Point(500, 500), false);
|
||||
e7.geometry.points = [new Point(500, 350)];
|
||||
});
|
||||
|
||||
parentContainer.appendChild(
|
||||
DomHelpers.button('Zoom In', function () {
|
||||
graph.zoomIn();
|
||||
})
|
||||
);
|
||||
|
||||
parentContainer.appendChild(
|
||||
DomHelpers.button('Zoom Out', function () {
|
||||
graph.zoomOut();
|
||||
})
|
||||
);
|
||||
|
||||
// Undo/redo
|
||||
let undoManager = new UndoManager();
|
||||
let listener = function (sender, evt) {
|
||||
undoManager.undoableEditHappened(evt.getProperty('edit'));
|
||||
};
|
||||
graph.getDataModel().addListener(InternalEvent.UNDO, listener);
|
||||
graph.getView().addListener(InternalEvent.UNDO, listener);
|
||||
|
||||
parentContainer.appendChild(
|
||||
DomHelpers.button('Undo', function () {
|
||||
undoManager.undo();
|
||||
})
|
||||
);
|
||||
|
||||
parentContainer.appendChild(
|
||||
DomHelpers.button('Redo', function () {
|
||||
undoManager.redo();
|
||||
})
|
||||
);
|
||||
|
||||
// Shows XML for debugging the actual model
|
||||
parentContainer.appendChild(
|
||||
DomHelpers.button('Delete', function () {
|
||||
graph.removeCells();
|
||||
})
|
||||
);
|
||||
|
||||
// Wire-mode
|
||||
let checkbox = document.createElement('input');
|
||||
checkbox.setAttribute('type', 'checkbox');
|
||||
|
||||
parentContainer.appendChild(checkbox);
|
||||
domUtils.write(parentContainer, 'Wire Mode');
|
||||
|
||||
// Grid
|
||||
let checkbox2 = document.createElement('input');
|
||||
checkbox2.setAttribute('type', 'checkbox');
|
||||
checkbox2.setAttribute('checked', 'true');
|
||||
|
||||
parentContainer.appendChild(checkbox2);
|
||||
domUtils.write(parentContainer, 'Grid');
|
||||
|
||||
InternalEvent.addListener(checkbox2, 'click', function (evt) {
|
||||
if (checkbox2.checked) {
|
||||
container.style.background = 'url(/images/grid.gif)';
|
||||
} else {
|
||||
container.style.background = '';
|
||||
}
|
||||
container.style.backgroundColor = invert ? 'black' : 'white';
|
||||
});
|
||||
InternalEvent.disableContextMenu(container);
|
||||
|
||||
return parentContainer;
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
|
@ -1,154 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2006-2013, JGraph Ltd
|
||||
*
|
||||
* Graph Layout
|
||||
*
|
||||
* This example demonstrates using
|
||||
* automatic graph layouts and listening to changes of the graph size
|
||||
* to keep the container size in sync.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import mxEvent from '../mxgraph/util/mxEvent';
|
||||
import mxGraph from '../mxgraph/view/mxGraph';
|
||||
import mxRubberband from '../mxgraph/handler/mxRubberband';
|
||||
import mxUtils from "../mxgraph/util/mxUtils";
|
||||
import mxCircleLayout from "../mxgraph/layout/mxCircleLayout";
|
||||
|
||||
|
||||
const HTML_TEMPLATE = `
|
||||
<!-- Page passes the container for the graph to the program -->
|
||||
<body onload="main(document.getElementById('graphContainer'))">
|
||||
|
||||
<!-- Creates a container for the graph with a grid wallpaper. Make sure to define the position
|
||||
and overflow attributes! See comments on the adding of the size-listener on line 54 ff! -->
|
||||
<div id="graphContainer"
|
||||
style="position:relative;overflow:auto;width:821px;height:641px;background:url('editors/images/grid.gif');">
|
||||
</div>
|
||||
<br>
|
||||
<input type="checkbox" id="animate" checked="checked"/> Transitions
|
||||
</body>
|
||||
`
|
||||
|
||||
|
||||
// Creates the graph inside the given container
|
||||
let graph = new mxGraph(container);
|
||||
|
||||
// Disables basic selection and cell handling
|
||||
graph.setEnabled(false);
|
||||
|
||||
// Changes the default vertex style in-place
|
||||
let style = graph.getStylesheet().getDefaultVertexStyle();
|
||||
style.shape = mxConstants.SHAPE_ELLIPSE;
|
||||
style.perimiter = Perimeter.EllipsePerimeter;
|
||||
style.gradientColor = 'white';
|
||||
style.fontSize = '10';
|
||||
|
||||
// Updates the size of the container to match
|
||||
// the size of the graph when it changes. If
|
||||
// this is commented-out, and the DIV style's
|
||||
// overflow is set to "auto", then scrollbars
|
||||
// will appear for the diagram. If overflow is
|
||||
// set to "visible", then the diagram will be
|
||||
// visible even when outside the parent DIV.
|
||||
// With the code below, the parent DIV will be
|
||||
// resized to contain the complete graph.
|
||||
//graph.setResizeContainer(true);
|
||||
|
||||
// Larger grid size yields cleaner layout result
|
||||
graph.gridSize = 40;
|
||||
|
||||
// Gets the default parent for inserting new cells. This
|
||||
// is normally the first child of the root (ie. layer 0).
|
||||
let parent = graph.getDefaultParent();
|
||||
|
||||
// Creates a layout algorithm to be used
|
||||
// with the graph
|
||||
let layout = new MxFastOrganicLayout(graph);
|
||||
|
||||
// Moves stuff wider apart than usual
|
||||
layout.forceConstant = 80;
|
||||
|
||||
// Reference to the transition checkbox
|
||||
let animate = document.getElementById('animate');
|
||||
|
||||
// Adds a button to execute the layout
|
||||
this.el2.insertBefore(mxUtils.button('Circle Layout',
|
||||
function(evt) {
|
||||
graph.getDataModel().beginUpdate();
|
||||
try {
|
||||
// Creates a layout algorithm to be used
|
||||
// with the graph
|
||||
let circleLayout = new mxCircleLayout(graph);
|
||||
circleLayout.execute(parent);
|
||||
} catch (e) {
|
||||
throw e;
|
||||
} finally {
|
||||
if (animate.checked) {
|
||||
let morph = new Morphing(graph);
|
||||
morph.addListener(mxEvent.DONE, function() {
|
||||
graph.getDataModel().endUpdate();
|
||||
});
|
||||
morph.startAnimation();
|
||||
} else {
|
||||
graph.getDataModel().endUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
), document.body.firstChild);
|
||||
|
||||
// Adds a button to execute the layout
|
||||
document.body.insertBefore(mxUtils.button('Organic Layout',
|
||||
function(evt)
|
||||
{
|
||||
graph.getDataModel().beginUpdate();
|
||||
try {
|
||||
layout.execute(parent);
|
||||
} catch (e) {
|
||||
throw e;
|
||||
} finally {
|
||||
if (animate.checked) {
|
||||
// Default values are 6, 1.5, 20
|
||||
let morph = new Morphing(graph, 10, 1.7, 20);
|
||||
morph.addListener(mxEvent.DONE, function() {
|
||||
graph.getDataModel().endUpdate();
|
||||
});
|
||||
morph.startAnimation();
|
||||
} else {
|
||||
graph.getDataModel().endUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
), document.body.firstChild);
|
||||
|
||||
// Adds cells to the model in a single step
|
||||
graph.getDataModel().beginUpdate();
|
||||
let w = 30;
|
||||
let h = 30;
|
||||
try {
|
||||
var v1 = graph.insertVertex(parent, null, 'A', 0, 0, w, h);
|
||||
var v2 = graph.insertVertex(parent, null, 'B', 0, 0, w, h);
|
||||
var v3 = graph.insertVertex(parent, null, 'C', 0, 0, w, h);
|
||||
var v4 = graph.insertVertex(parent, null, 'D', 0, 0, w, h);
|
||||
var v5 = graph.insertVertex(parent, null, 'E', 0, 0, w, h);
|
||||
var v6 = graph.insertVertex(parent, null, 'F', 0, 0, w, h);
|
||||
var v7 = graph.insertVertex(parent, null, 'G', 0, 0, w, h);
|
||||
var v8 = graph.insertVertex(parent, null, 'H', 0, 0, w, h);
|
||||
var e1 = graph.insertEdge(parent, null, 'ab', v1, v2);
|
||||
var e2 = graph.insertEdge(parent, null, 'ac', v1, v3);
|
||||
var e3 = graph.insertEdge(parent, null, 'cd', v3, v4);
|
||||
var e4 = graph.insertEdge(parent, null, 'be', v2, v5);
|
||||
var e5 = graph.insertEdge(parent, null, 'cf', v3, v6);
|
||||
var e6 = graph.insertEdge(parent, null, 'ag', v1, v7);
|
||||
var e7 = graph.insertEdge(parent, null, 'gh', v7, v8);
|
||||
var e8 = graph.insertEdge(parent, null, 'gc', v7, v3);
|
||||
var e9 = graph.insertEdge(parent, null, 'gd', v7, v4);
|
||||
var e10 = graph.insertEdge(parent, null, 'eh', v5, v8);
|
||||
|
||||
// Executes the layout
|
||||
layout.execute(parent);
|
||||
} finally {
|
||||
// Updates the display
|
||||
graph.getDataModel().endUpdate();
|
||||
}
|
||||
|
|
@ -1,138 +0,0 @@
|
|||
// @ts-check
|
||||
/**
|
||||
* Copyright (c) 2006-2013, JGraph Ltd
|
||||
*
|
||||
* HTML label
|
||||
*
|
||||
* This example demonstrates using
|
||||
* HTML labels that are connected to the state of the user object.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import mxEvent from '../mxgraph/util/mxEvent';
|
||||
import mxGraph from '../mxgraph/view/mxGraph';
|
||||
import mxRubberband from '../mxgraph/handler/mxRubberband';
|
||||
|
||||
|
||||
const HTML_TEMPLATE = `
|
||||
<!-- Page passes the container for the graph to the program -->
|
||||
<body onload="main(document.getElementById('graphContainer'))">
|
||||
|
||||
<!-- Creates a container for the graph with a grid wallpaper -->
|
||||
<div id="graphContainer"
|
||||
style="position:relative;overflow:hidden;width:321px;height:241px;background:url('editors/images/grid.gif');cursor:default;">
|
||||
</div>
|
||||
</body>
|
||||
`
|
||||
|
||||
|
||||
// Disables the built-in context menu
|
||||
mxEvent.disableContextMenu(container);
|
||||
|
||||
// Creates the graph inside the given container
|
||||
let graph = new mxGraph(container);
|
||||
|
||||
// Enables HTML labels
|
||||
graph.setHtmlLabels(true);
|
||||
|
||||
// Enables rubberband selection
|
||||
new mxRubberband(graph);
|
||||
|
||||
// Creates a user object that stores the state
|
||||
let doc = mxUtils.createXmlDocument();
|
||||
let obj = doc.createElement('UserObject');
|
||||
obj.setAttribute('label', 'Hello, World!');
|
||||
obj.setAttribute('checked', 'false');
|
||||
|
||||
// Adds optional caching for the HTML label
|
||||
let cached = true;
|
||||
|
||||
if (cached) {
|
||||
// Ignores cached label in codec
|
||||
CodecRegistry.getCodec(mxCell).exclude.push('div');
|
||||
|
||||
// Invalidates cached labels
|
||||
graph.model.setValue = function(cell, value) {
|
||||
cell.div = null;
|
||||
mxGraphModel.prototype.setValue.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
// Overrides method to provide a cell label in the display
|
||||
graph.convertValueToString = function(cell) {
|
||||
if (cached && cell.div != null) {
|
||||
// Uses cached label
|
||||
return cell.div;
|
||||
} else if (mxUtils.isNode(cell.value) && cell.value.nodeName.toLowerCase() == 'userobject') {
|
||||
// Returns a DOM for the label
|
||||
let div = document.createElement('div');
|
||||
div.innerHTML = cell.getAttribute('label');
|
||||
mxUtils.br(div);
|
||||
|
||||
let checkbox = document.createElement('input');
|
||||
checkbox.setAttribute('type', 'checkbox');
|
||||
|
||||
if (cell.getAttribute('checked') == 'true') {
|
||||
checkbox.setAttribute('checked', 'checked');
|
||||
checkbox.defaultChecked = true;
|
||||
}
|
||||
|
||||
// Writes back to cell if checkbox is clicked
|
||||
mxEvent.addListener(checkbox, 'change', function(evt) {
|
||||
let elt = cell.value.cloneNode(true);
|
||||
elt.setAttribute('checked', (checkbox.checked) ? 'true' : 'false');
|
||||
|
||||
graph.model.setValue(cell, elt);
|
||||
});
|
||||
|
||||
div.appendChild(checkbox);
|
||||
|
||||
if (cached) {
|
||||
// Caches label
|
||||
cell.div = div;
|
||||
}
|
||||
return div;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
// Overrides method to store a cell label in the model
|
||||
let cellLabelChanged = graph.cellLabelChanged;
|
||||
graph.cellLabelChanged = function(cell, newValue, autoSize) {
|
||||
if (mxUtils.isNode(cell.value) && cell.value.nodeName.toLowerCase() == 'userobject') {
|
||||
// Clones the value for correct undo/redo
|
||||
let elt = cell.value.cloneNode(true);
|
||||
elt.setAttribute('label', newValue);
|
||||
newValue = elt;
|
||||
}
|
||||
|
||||
cellLabelChanged.apply(this, arguments);
|
||||
};
|
||||
|
||||
// Overrides method to create the editing value
|
||||
let getEditingValue = graph.getEditingValue;
|
||||
graph.getEditingValue = function(cell) {
|
||||
if (mxUtils.isNode(cell.value) && cell.value.nodeName.toLowerCase() == 'userobject') {
|
||||
return cell.getAttribute('label');
|
||||
}
|
||||
};
|
||||
|
||||
let parent = graph.getDefaultParent();
|
||||
graph.insertVertex(parent, null, obj, 20, 20, 80, 60);
|
||||
|
||||
// Undo/redo
|
||||
let undoManager = new UndoManager();
|
||||
let listener = function(sender, evt) {
|
||||
undoManager.undoableEditHappened(evt.getProperty('edit'));
|
||||
};
|
||||
graph.getDataModel().addListener(mxEvent.UNDO, listener);
|
||||
graph.getView().addListener(mxEvent.UNDO, listener);
|
||||
|
||||
document.body.appendChild(mxUtils.button('Undo', function() {
|
||||
undoManager.undo();
|
||||
}));
|
||||
|
||||
document.body.appendChild(mxUtils.button('Redo', function() {
|
||||
undoManager.redo();
|
||||
}));
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2006-2013, JGraph Ltd
|
||||
*
|
||||
* Menustyle
|
||||
* Menustyle. This example demonstrates using
|
||||
* CSS to style the mxPopupMenu.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import mxEvent from '../mxgraph/util/mxEvent';
|
||||
import mxGraph from '../mxgraph/view/mxGraph';
|
||||
import mxRubberband from '../mxgraph/handler/mxRubberband';
|
||||
|
||||
|
||||
const HTML_TEMPLATE = `
|
||||
<!-- Page passes the container for the graph to the program -->
|
||||
<style type="text/css">
|
||||
body div.mxPopupMenu {
|
||||
-webkit-box-shadow: 3px 3px 6px #C0C0C0;
|
||||
-moz-box-shadow: 3px 3px 6px #C0C0C0;
|
||||
box-shadow: 3px 3px 6px #C0C0C0;
|
||||
background: white;
|
||||
position: absolute;
|
||||
border: 3px solid #e7e7e7;
|
||||
padding: 3px;
|
||||
}
|
||||
body table.mxPopupMenu {
|
||||
border-collapse: collapse;
|
||||
margin: 0px;
|
||||
}
|
||||
body tr.mxPopupMenuItem {
|
||||
color: black;
|
||||
cursor: default;
|
||||
}
|
||||
body td.mxPopupMenuItem {
|
||||
padding: 6px 60px 6px 30px;
|
||||
font-family: Arial;
|
||||
font-size: 10pt;
|
||||
}
|
||||
body td.mxPopupMenuIcon {
|
||||
background-color: white;
|
||||
padding: 0px;
|
||||
}
|
||||
body tr.mxPopupMenuItemHover {
|
||||
background-color: #eeeeee;
|
||||
color: black;
|
||||
}
|
||||
table.mxPopupMenu hr {
|
||||
border-top: solid 1px #cccccc;
|
||||
}
|
||||
table.mxPopupMenu tr {
|
||||
font-size: 4pt;
|
||||
}
|
||||
</style>
|
||||
<body onload="main(document.getElementById('graphContainer'))">
|
||||
|
||||
<!-- Creates a container for the graph with a grid wallpaper -->
|
||||
<div id="graphContainer"
|
||||
style="overflow:hidden;width:321px;height:241px;background:url('editors/images/grid.gif');cursor:default;">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
// Disables built-in context menu
|
||||
InternalEvent.disableContextMenu(document.body);
|
||||
|
||||
// Changes some default colors
|
||||
mxConstants.HANDLE_FILLCOLOR = '#99ccff';
|
||||
mxConstants.HANDLE_STROKECOLOR = '#0088cf';
|
||||
mxConstants.VERTEX_SELECTION_COLOR = '#00a8ff';
|
||||
|
||||
// Creates the graph inside the given container
|
||||
let graph = new mxGraph(container);
|
||||
graph.setTooltips(true);
|
||||
|
||||
// Enables rubberband selection
|
||||
new mxRubberband(graph);
|
||||
|
||||
// Gets the default parent for inserting new cells. This
|
||||
// is normally the first child of the root (ie. layer 0).
|
||||
let parent = graph.getDefaultParent();
|
||||
|
||||
// Adds cells to the model in a single step
|
||||
graph.batchUpdate(() => {
|
||||
var v1 = graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30);
|
||||
var v2 = graph.insertVertex(parent, null, 'World!', 200, 150, 80, 30);
|
||||
var e1 = graph.insertEdge(parent, null, '', v1, v2);
|
||||
});
|
||||
|
||||
// Creates a new overlay with an image and a tooltip and makes it "transparent" to events
|
||||
let overlay = new CellOverlay(new mxImage('editors/images/overlays/check.png', 16, 16), 'Overlay tooltip');
|
||||
|
||||
let mxCellRendererInstallCellOverlayListeners = mxCellRenderer.prototype.installCellOverlayListeners;
|
||||
mxCellRenderer.prototype.installCellOverlayListeners = function(state, overlay, shape){
|
||||
mxCellRendererInstallCellOverlayListeners.apply(this, arguments);
|
||||
var graph = state.view.graph;
|
||||
|
||||
mxEvent.addGestureListeners(shape.node,
|
||||
function (evt) {
|
||||
graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state));
|
||||
},
|
||||
function (evt) {
|
||||
graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, state));
|
||||
},
|
||||
function (evt) {
|
||||
});
|
||||
|
||||
if (!Client.IS_TOUCH) {
|
||||
mxEvent.addListener(shape.node, 'mouseup', function(evt) {
|
||||
overlay.fireEvent(new mxEventObject(mxEvent.CLICK,
|
||||
'event', evt, 'cell', state.cell));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Sets the overlay for the cell in the graph
|
||||
graph.addCellOverlay(v1, overlay);
|
||||
|
||||
// Configures automatic expand on mouseover
|
||||
graph.getPlugin('PopupMenuHandler').autoExpand = true;
|
||||
|
||||
// Installs context menu
|
||||
graph.getPlugin('PopupMenuHandler').factoryMethod = function(menu, cell, evt) {
|
||||
menu.addItem('Item 1', null, function() {
|
||||
alert('Item 1');
|
||||
});
|
||||
menu.addItem('Item 2', null, function() {
|
||||
alert('Item 2');
|
||||
});
|
||||
menu.addSeparator();
|
||||
|
||||
var submenu1 = menu.addItem('Submenu 1', null, null);
|
||||
menu.addItem('Subitem 1', null, function() {
|
||||
alert('Subitem 1');
|
||||
}, submenu1);
|
||||
menu.addItem('Subitem 1', null, function() {
|
||||
alert('Subitem 2');
|
||||
}, submenu1);
|
||||
};
|
|
@ -1,482 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2006-2015, JGraph Ltd
|
||||
*
|
||||
* Scrollbars
|
||||
*
|
||||
* This example demonstrates using
|
||||
* a scrollable table with different sections in a cell label.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import mxEvent from '../mxgraph/util/mxEvent';
|
||||
import mxGraph from '../mxgraph/view/mxGraph';
|
||||
import mxRubberband from '../mxgraph/handler/mxRubberband';
|
||||
import { error } from '../../packages/core/src/util/gui/MaxWindow';
|
||||
import { createXmlDocument } from '../../packages/core/src/util/xmlUtils';
|
||||
import { button } from '../../packages/core/src/util/dom/DomHelpers';
|
||||
import { isNode } from '../../packages/core/src/util/domUtils';
|
||||
|
||||
|
||||
const HTML_TEMPLATE = `
|
||||
<style type="text/css" media="screen">
|
||||
table.title {
|
||||
border-color: black;
|
||||
border-collapse: collapse;
|
||||
cursor: move;
|
||||
height: 26px;
|
||||
border-bottom-style: none;
|
||||
color: black;
|
||||
}
|
||||
table.title th {
|
||||
font-size: 10pt;
|
||||
font-family: Verdana;
|
||||
white-space: nowrap;
|
||||
background: lightgray;
|
||||
font-weight: bold;
|
||||
}
|
||||
table.erd {
|
||||
font-size: 10pt;
|
||||
font-family: Verdana;
|
||||
border-color: black;
|
||||
border-collapse: collapse;
|
||||
overflow: auto;
|
||||
cursor: move;
|
||||
white-space: nowrap;
|
||||
}
|
||||
table.erd td {
|
||||
border-color: black;
|
||||
text-align: left;
|
||||
color: black;
|
||||
}
|
||||
button {
|
||||
position:absolute;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Page passes the container for the graph to the program -->
|
||||
<body onload="main(document.getElementById('graphContainer'))">
|
||||
|
||||
<!-- Creates a container for the graph with a grid wallpaper. Width, height and cursor in the style are for IE only -->
|
||||
<div id="graphContainer"
|
||||
style="cursor:default;position:absolute;top:30px;left:0px;bottom:0px;right:0px;background:url('editors/images/grid.gif')">
|
||||
</div>
|
||||
</body>
|
||||
`
|
||||
|
||||
|
||||
// Must be disabled to compute positions inside the DOM tree of the cell label.
|
||||
mxGraphView.prototype.optimizeVmlReflows = false;
|
||||
|
||||
// If connect preview is not moved away then getCellAt is used to detect the cell under
|
||||
// the mouse if the mouse is over the preview shape in IE (no event transparency), ie.
|
||||
// the built-in hit-detection of the HTML document will not be used in this case. This is
|
||||
// not a problem here since the preview moves away from the mouse as soon as it connects
|
||||
// to any given table row. This is because the edge connects to the outside of the row and
|
||||
// is aligned to the grid during the preview.
|
||||
ConnectionHandler.prototype.movePreviewAway = false;
|
||||
|
||||
// Disables foreignObjects
|
||||
Client.NO_FO = true;
|
||||
|
||||
// Enables move preview in HTML to appear on top
|
||||
SelectionHandler.prototype.htmlPreview = true;
|
||||
|
||||
// Enables connect icons to appear on top of HTML
|
||||
ConnectionHandler.prototype.moveIconFront = true;
|
||||
|
||||
// Defines an icon for creating new connections in the connection handler.
|
||||
// This will automatically disable the highlighting of the source vertex.
|
||||
ConnectionHandler.prototype.connectImage = new Image('images/connector.gif', 16, 16);
|
||||
|
||||
// Disables the context menu
|
||||
mxEvent.disableContextMenu(container);
|
||||
|
||||
// Overrides target perimeter point for connection previews
|
||||
ConnectionHandler.prototype.getTargetPerimeterPoint = function(state, me) {
|
||||
// Determines the y-coordinate of the target perimeter point
|
||||
// by using the currentRowNode assigned in updateRow
|
||||
let y = me.getY();
|
||||
if (this.currentRowNode != null) {
|
||||
y = getRowY(state, this.currentRowNode);
|
||||
}
|
||||
|
||||
// Checks on which side of the terminal to leave
|
||||
let x = state.x;
|
||||
if (this.previous.getCenterX() > state.getCenterX()) {
|
||||
x += state.width;
|
||||
}
|
||||
return new Point(x, y);
|
||||
};
|
||||
|
||||
// Overrides source perimeter point for connection previews
|
||||
ConnectionHandler.prototype.getSourcePerimeterPoint = function(state, next, me)
|
||||
{
|
||||
let y = me.getY();
|
||||
if (this.sourceRowNode != null) {
|
||||
y = getRowY(state, this.sourceRowNode);
|
||||
}
|
||||
|
||||
// Checks on which side of the terminal to leave
|
||||
let x = state.x;
|
||||
if (next.x > state.getCenterX()) {
|
||||
x += state.width;
|
||||
}
|
||||
return new Point(x, y);
|
||||
};
|
||||
|
||||
// Disables connections to invalid rows
|
||||
ConnectionHandler.prototype.isValidTarget = function(cell) {
|
||||
return this.currentRowNode != null;
|
||||
};
|
||||
|
||||
// Creates the graph inside the given container
|
||||
let graph = new mxGraph(container);
|
||||
|
||||
// Uses the entity perimeter (below) as default
|
||||
graph.stylesheet.getDefaultVertexStyle().verticalAlign = mxConstants.ALIGN_TOP;
|
||||
graph.stylesheet.getDefaultVertexStyle().perimiter =
|
||||
Perimeter.EntityPerimeter;
|
||||
graph.stylesheet.getDefaultVertexStyle().shadow = true;
|
||||
graph.stylesheet.getDefaultVertexStyle().fillColor = '#DDEAFF';
|
||||
graph.stylesheet.getDefaultVertexStyle().gradientColor = '#A9C4EB';
|
||||
delete graph.stylesheet.getDefaultVertexStyle().strokeColor;
|
||||
|
||||
// Used for HTML labels that use up the complete vertex space (see
|
||||
// graph.cellRenderer.redrawLabel below for syncing the size)
|
||||
graph.stylesheet.getDefaultVertexStyle().overflow = 'fill';
|
||||
|
||||
// Uses the entity edge style as default
|
||||
graph.stylesheet.getDefaultEdgeStyle().edge =
|
||||
mxEdgeStyle.EntityRelation;
|
||||
graph.stylesheet.getDefaultEdgeStyle().strokeColor = 'black';
|
||||
graph.stylesheet.getDefaultEdgeStyle().fontColor = 'black';
|
||||
|
||||
// Allows new connections to be made but do not allow existing
|
||||
// connections to be changed for the sake of simplicity of this
|
||||
// example
|
||||
graph.setCellsDisconnectable(false);
|
||||
graph.setAllowDanglingEdges(false);
|
||||
graph.setCellsEditable(false);
|
||||
graph.setConnectable(true);
|
||||
graph.setPanning(true);
|
||||
graph.centerZoom = false;
|
||||
|
||||
// Override folding to allow for tables
|
||||
graph.isCellFoldable = function(cell, collapse) {
|
||||
return cell.isVertex();
|
||||
};
|
||||
|
||||
// Overrides connectable state
|
||||
graph.isCellConnectable = function(cell) {
|
||||
return !cell.isCollapsed();
|
||||
};
|
||||
|
||||
// Enables HTML markup in all labels
|
||||
graph.setHtmlLabels(true);
|
||||
|
||||
// Scroll events should not start moving the vertex
|
||||
graph.cellRenderer.isLabelEvent = function(state, evt) {
|
||||
let source = mxEvent.getSource(evt);
|
||||
|
||||
return state.text != null && source != state.text.node &&
|
||||
source != state.text.node.getElementsByTagName('div')[0];
|
||||
};
|
||||
|
||||
// Adds scrollbars to the outermost div and keeps the
|
||||
// DIV position and size the same as the vertex
|
||||
let oldRedrawLabel = graph.cellRenderer.redrawLabel;
|
||||
graph.cellRenderer.redrawLabel = function(state){
|
||||
oldRedrawLabel.apply(this, arguments); // "supercall"
|
||||
let graph = state.view.graph;
|
||||
let model = graph.model;
|
||||
|
||||
if (state.cell.isVertex() && state.text != null) {
|
||||
// Scrollbars are on the div
|
||||
let s = graph.view.scale;
|
||||
let div = state.text.node.getElementsByTagName('div')[2];
|
||||
|
||||
if (div != null) {
|
||||
// Installs the handler for updating connected edges
|
||||
if (div.scrollHandler == null) {
|
||||
div.scrollHandler = true;
|
||||
|
||||
let updateEdges = () => {
|
||||
let edgeCount = state.cell.getEdgeCount();
|
||||
|
||||
// Only updates edges to avoid update in DOM order
|
||||
// for text label which would reset the scrollbar
|
||||
for (let i = 0; i < edgeCount; i++)
|
||||
{
|
||||
let edge = state.cell.getEdgeAt(i);
|
||||
graph.view.invalidate(edge, true, false);
|
||||
graph.view.validate(edge);
|
||||
}
|
||||
};
|
||||
|
||||
mxEvent.addListener(div, 'scroll', updateEdges);
|
||||
mxEvent.addListener(div, 'mouseup', updateEdges);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Adds a new function to update the currentRow based on the given event
|
||||
// and return the DOM node for that row
|
||||
graph.getPlugin('ConnectionHandler').updateRow = function(target) {
|
||||
while (target != null && target.nodeName != 'TR') {
|
||||
target = target.parentNode;
|
||||
}
|
||||
this.currentRow = null;
|
||||
|
||||
// Checks if we're dealing with a row in the correct table
|
||||
if (target != null && target.parentNode.parentNode.className == 'erd') {
|
||||
// Stores the current row number in a property so that it can
|
||||
// be retrieved to create the preview and final edge
|
||||
let rowNumber = 0;
|
||||
let current = target.parentNode.firstChild;
|
||||
|
||||
while (target != current && current != null) {
|
||||
current = current.nextSibling;
|
||||
rowNumber++;
|
||||
}
|
||||
|
||||
this.currentRow = rowNumber + 1;
|
||||
} else {
|
||||
target = null;
|
||||
}
|
||||
return target;
|
||||
};
|
||||
|
||||
// Adds placement of the connect icon based on the mouse event target (row)
|
||||
graph.getPlugin('ConnectionHandler').updateIcons = function(state, icons, me) {
|
||||
let target = me.getSource();
|
||||
target = this.updateRow(target);
|
||||
|
||||
if (target != null && this.currentRow != null) {
|
||||
let div = target.parentNode.parentNode.parentNode;
|
||||
let s = state.view.scale;
|
||||
|
||||
icons[0].node.style.visibility = 'visible';
|
||||
icons[0].bounds.x = state.x + target.offsetLeft + Math.min(state.width,
|
||||
target.offsetWidth * s) - this.icons[0].bounds.width - 2;
|
||||
icons[0].bounds.y = state.y - this.icons[0].bounds.height / 2 + (target.offsetTop +
|
||||
target.offsetHeight / 2 - div.scrollTop + div.offsetTop) * s;
|
||||
icons[0].redraw();
|
||||
|
||||
this.currentRowNode = target;
|
||||
} else {
|
||||
icons[0].node.style.visibility = 'hidden';
|
||||
}
|
||||
};
|
||||
|
||||
// Updates the targetRow in the preview edge State
|
||||
let oldMouseMove = graph.getPlugin('ConnectionHandler').mouseMove;
|
||||
graph.getPlugin('ConnectionHandler').mouseMove = function(sender, me) {
|
||||
if (this.edgeState != null) {
|
||||
this.currentRowNode = this.updateRow(me.getSource());
|
||||
|
||||
if (this.currentRow != null) {
|
||||
this.edgeState.cell.value.setAttribute('targetRow', this.currentRow);
|
||||
} else {
|
||||
this.edgeState.cell.value.setAttribute('targetRow', '0');
|
||||
}
|
||||
|
||||
// Destroys icon to prevent event redirection via image in IE
|
||||
this.destroyIcons();
|
||||
}
|
||||
oldMouseMove.apply(this, arguments);
|
||||
};
|
||||
|
||||
// Creates the edge state that may be used for preview
|
||||
graph.getPlugin('ConnectionHandler').createEdgeState = function(me) {
|
||||
let relation = doc.createElement('Relation');
|
||||
relation.setAttribute('sourceRow', this.currentRow || '0');
|
||||
relation.setAttribute('targetRow', '0');
|
||||
|
||||
let edge = this.createEdge(relation);
|
||||
let style = this.graph.getCellStyle(edge);
|
||||
let state = new Cell(this.graph.view, edge, style);
|
||||
|
||||
// Stores the source row in the handler
|
||||
this.sourceRowNode = this.currentRowNode;
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
// Overrides getLabel to return empty labels for edges and
|
||||
// short markup for collapsed cells.
|
||||
graph.getLabel = function(cell) {
|
||||
if (cell.isVertex()) {
|
||||
if (cell.isCollapsed()) {
|
||||
return '<table style="overflow:hidden;" width="100%" height="100%" border="1" cellpadding="4" class="title" style="height:100%;">' +
|
||||
'<tr><th>Customers</th></tr>' +
|
||||
'</table>';
|
||||
} else {
|
||||
return '<table style="overflow:hidden;" width="100%" border="1" cellpadding="4" class="title">' +
|
||||
'<tr><th colspan="2">Customers</th></tr>' +
|
||||
'</table>'+
|
||||
'<div style="overflow:auto;cursor:default;top:26px;bottom:0px;position:absolute;width:100%;">'+
|
||||
'<table width="100%" height="100%" border="1" cellpadding="4" class="erd">' +
|
||||
'<tr><td>' +
|
||||
'<img align="center" src="images/key.png"/>' +
|
||||
'<img align="center" src="images/plus.png"/>' +
|
||||
'</td><td>' +
|
||||
'<u>customerId</u></td></tr><tr><td></td><td>number</td></tr>' +
|
||||
'<tr><td></td><td>firstName</td></tr><tr><td></td><td>lastName</td></tr>' +
|
||||
'<tr><td></td><td>streetAddress</td></tr><tr><td></td><td>city</td></tr>' +
|
||||
'<tr><td></td><td>state</td></tr><tr><td></td><td>zip</td></tr>' +
|
||||
'</table></div>';
|
||||
}
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
// User objects (data) for the individual cells
|
||||
let doc = createXmlDocument();
|
||||
|
||||
// Same should be used to create the XML node for the table
|
||||
// description and the rows (most probably as child nodes)
|
||||
let relation = doc.createElement('Relation');
|
||||
relation.setAttribute('sourceRow', '4');
|
||||
relation.setAttribute('targetRow', '6');
|
||||
|
||||
// Enables rubberband selection
|
||||
new mxRubberband(graph);
|
||||
|
||||
// Enables key handling (eg. escape)
|
||||
new KeyHandler(graph);
|
||||
|
||||
// Gets the default parent for inserting new cells. This
|
||||
// is normally the first child of the root (ie. layer 0).
|
||||
let parent = graph.getDefaultParent();
|
||||
|
||||
// Adds cells to the model in a single step
|
||||
let width = 160;
|
||||
let height = 230;
|
||||
graph.getDataModel().beginUpdate();
|
||||
try {
|
||||
var v1 = graph.insertVertex(parent, null, '', 20, 20, width, height);
|
||||
v1.geometry.alternateBounds = new Rectangle(0, 0, width, 26);
|
||||
|
||||
var v2 = graph.insertVertex(parent, null, '', 400, 150, width, height);
|
||||
v2.geometry.alternateBounds = new Rectangle(0, 0, width, 26);
|
||||
|
||||
graph.insertEdge(parent, null, relation, v1, v2);
|
||||
} finally {
|
||||
// Updates the display
|
||||
graph.getDataModel().endUpdate();
|
||||
}
|
||||
|
||||
var btn1 = button('+', function() {
|
||||
graph.zoomIn();
|
||||
});
|
||||
btn1.style.marginLeft = '20px';
|
||||
|
||||
document.body.appendChild(btn1);
|
||||
document.body.appendChild(button('-', function() {
|
||||
graph.zoomOut();
|
||||
}));
|
||||
|
||||
// Implements a special perimeter for table rows inside the table markup
|
||||
mxGraphView.prototype.updateFloatingTerminalPoint = function(edge, start, end, source) {
|
||||
let next = this.getNextPoint(edge, end, source);
|
||||
let div = start.text.node.getElementsByTagName('div')[2];
|
||||
|
||||
let x = start.x;
|
||||
let y = start.getCenterY();
|
||||
|
||||
// Checks on which side of the terminal to leave
|
||||
if (next.x > x + start.width / 2) {
|
||||
x += start.width;
|
||||
}
|
||||
|
||||
if (div != null) {
|
||||
y = start.getCenterY() - div.scrollTop;
|
||||
|
||||
if (isNode(edge.cell.value) && !start.cell.isCollapsed()) {
|
||||
let attr = (source) ? 'sourceRow' : 'targetRow';
|
||||
let row = parseInt(edge.cell.value.getAttribute(attr));
|
||||
|
||||
// HTML labels contain an outer table which is built-in
|
||||
let table = div.getElementsByTagName('table')[0];
|
||||
let trs = table.getElementsByTagName('tr');
|
||||
let tr = trs[Math.min(trs.length - 1, row - 1)];
|
||||
|
||||
// Gets vertical center of source or target row
|
||||
if (tr != null) {
|
||||
y = getRowY(start, tr);
|
||||
}
|
||||
}
|
||||
|
||||
// Keeps vertical coordinate inside start
|
||||
let offsetTop = parseInt(div.style.top) * start.view.scale;
|
||||
y = Math.min(start.y + start.height, Math.max(start.y + offsetTop, y));
|
||||
|
||||
// Updates the vertical position of the nearest point if we're not
|
||||
// dealing with a connection preview, in which case either the
|
||||
// edgeState or the absolutePoints are null
|
||||
if (edge != null && edge.absolutePoints != null) {
|
||||
next.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
edge.setAbsoluteTerminalPoint(new Point(x, y), source);
|
||||
|
||||
// Routes multiple incoming edges along common waypoints if
|
||||
// the edges have a common target row
|
||||
if (source && isNode(edge.cell.value) && start != null && end != null) {
|
||||
let edges = this.graph.getEdgesBetween(start.cell, end.cell, true);
|
||||
let tmp = [];
|
||||
|
||||
// Filters the edges with the same source row
|
||||
let row = edge.cell.value.getAttribute('targetRow');
|
||||
|
||||
for (let i = 0; i < edges.length; i++) {
|
||||
if (isNode(edges[i].value) && edges[i].value.getAttribute('targetRow') == row) {
|
||||
tmp.push(edges[i]);
|
||||
}
|
||||
}
|
||||
|
||||
edges = tmp;
|
||||
|
||||
if (edges.length > 1 && edge.cell == edges[edges.length - 1]) {
|
||||
// Finds the vertical center
|
||||
let states = [];
|
||||
let y = 0;
|
||||
|
||||
for (let i = 0; i < edges.length; i++) {
|
||||
states[i] = this.getState(edges[i]);
|
||||
y += states[i].absolutePoints[0].y;
|
||||
}
|
||||
|
||||
y /= edges.length;
|
||||
|
||||
for (let i = 0; i < states.length; i++) {
|
||||
let x = states[i].absolutePoints[1].x;
|
||||
|
||||
if (states[i].absolutePoints.length < 5) {
|
||||
states[i].absolutePoints.splice(2, 0, new Point(x, y));
|
||||
} else {
|
||||
states[i].absolutePoints[2] = new Point(x, y);
|
||||
}
|
||||
|
||||
// Must redraw the previous edges with the changed point
|
||||
if (i < states.length - 1) {
|
||||
this.graph.cellRenderer.redraw(states[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Defines global helper function to get y-coordinate for a given cell state and row
|
||||
let getRowY = function(state, tr) {
|
||||
let s = state.view.scale;
|
||||
let div = tr.parentNode.parentNode.parentNode;
|
||||
let offsetTop = parseInt(div.style.top);
|
||||
let y = state.y + (tr.offsetTop + tr.offsetHeight / 2 - div.scrollTop + offsetTop) * s;
|
||||
y = Math.min(state.y + state.height, Math.max(state.y + offsetTop * s, y));
|
||||
return y;
|
||||
};
|
||||
|
|
@ -1,134 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2006-2013, JGraph Ltd
|
||||
*
|
||||
* Show region
|
||||
*
|
||||
* This example demonstrates using a custom
|
||||
* rubberband handler to show the selected region in a new window.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import mxEvent from '../mxgraph/util/mxEvent';
|
||||
import mxGraph from '../mxgraph/view/mxGraph';
|
||||
import mxRubberband from '../mxgraph/handler/mxRubberband';
|
||||
import mxConstants from "../mxgraph/util/mxConstants";
|
||||
import mxPopupMenu from "../mxgraph/util/mxPopupMenu";
|
||||
import mxRectangle from "../mxgraph/util/mxRectangle";
|
||||
import mxUtils from "../mxgraph/util/mxUtils";
|
||||
|
||||
|
||||
const HTML_TEMPLATE = `
|
||||
<style type="text/css">
|
||||
body div.mxPopupMenu {
|
||||
-webkit-box-shadow: 3px 3px 6px #C0C0C0;
|
||||
-moz-box-shadow: 3px 3px 6px #C0C0C0;
|
||||
box-shadow: 3px 3px 6px #C0C0C0;
|
||||
background: white;
|
||||
position: absolute;
|
||||
border: 3px solid #e7e7e7;
|
||||
padding: 3px;
|
||||
}
|
||||
body table.mxPopupMenu {
|
||||
border-collapse: collapse;
|
||||
margin: 0px;
|
||||
}
|
||||
body tr.mxPopupMenuItem {
|
||||
color: black;
|
||||
cursor: default;
|
||||
}
|
||||
body td.mxPopupMenuItem {
|
||||
padding: 6px 60px 6px 30px;
|
||||
font-family: Arial;
|
||||
font-size: 10pt;
|
||||
}
|
||||
body td.mxPopupMenuIcon {
|
||||
background-color: white;
|
||||
padding: 0px;
|
||||
}
|
||||
body tr.mxPopupMenuItemHover {
|
||||
background-color: #eeeeee;
|
||||
color: black;
|
||||
}
|
||||
table.mxPopupMenu hr {
|
||||
border-top: solid 1px #cccccc;
|
||||
}
|
||||
table.mxPopupMenu tr {
|
||||
font-size: 4pt;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Page passes the container for the graph to the program -->
|
||||
<body onload="main(document.getElementById('graphContainer'))">
|
||||
|
||||
<!-- Creates a container for the graph with a grid wallpaper -->
|
||||
<div id="graphContainer"
|
||||
style="overflow:hidden;width:321px;height:241px;background:url('editors/images/grid.gif');cursor:default;">
|
||||
</div>
|
||||
Use the right mouse button to select a region of the diagram and select <i>Show this</i>.
|
||||
</body>
|
||||
`
|
||||
|
||||
|
||||
// Disables built-in context menu
|
||||
mxEvent.disableContextMenu(this.el);
|
||||
|
||||
// Changes some default colors
|
||||
mxConstants.HANDLE_FILLCOLOR = '#99ccff';
|
||||
mxConstants.HANDLE_STROKECOLOR = '#0088cf';
|
||||
mxConstants.VERTEX_SELECTION_COLOR = '#00a8ff';
|
||||
|
||||
// Creates the graph inside the given container
|
||||
let graph = new mxGraph(container);
|
||||
|
||||
// Enables rubberband selection
|
||||
let rubberband = new mxRubberband(graph);
|
||||
|
||||
rubberband.isForceRubberbandEvent = function(me) {
|
||||
return mxRubberband.prototype.isForceRubberbandEvent.apply(this, arguments) || mxEvent.isPopupTrigger(me.getEvent());
|
||||
}
|
||||
|
||||
// Defines a new popup menu for region selection in the rubberband handler
|
||||
rubberband.popupMenu = new mxPopupMenu(function(menu, cell, evt) {
|
||||
let rect = new mxRectangle(rubberband.x, rubberband.y, rubberband.width, rubberband.height);
|
||||
|
||||
menu.addItem('Show this', null, function() {
|
||||
rubberband.popupMenu.hideMenu();
|
||||
let bounds = graph.getGraphBounds();
|
||||
mxUtils.show(graph, null, bounds.x - rubberband.x, bounds.y - rubberband.y, rubberband.width, rubberband.height);
|
||||
});
|
||||
});
|
||||
|
||||
let rubberbandMouseDown = rubberband.mouseDown;
|
||||
rubberband.mouseDown = function(sender, me) {
|
||||
this.popupMenu.hideMenu();
|
||||
rubberbandMouseDown.apply(this, arguments);
|
||||
};
|
||||
|
||||
let rubberbandMouseUp = rubberband.mouseUp;
|
||||
rubberband.mouseUp = function(sender, me) {
|
||||
if (this.div != null && mxEvent.isPopupTrigger(me.getEvent())) {
|
||||
if (!graph.getPlugin('PopupMenuHandler').isMenuShowing()) {
|
||||
let origin = mxUtils.getScrollOrigin();
|
||||
this.popupMenu.popup(me.getX() + origin.x + 1, me.getY() + origin.y + 1, null, me.getEvent());
|
||||
this.reset();
|
||||
}
|
||||
} else {
|
||||
rubberbandMouseUp.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
// Gets the default parent for inserting new cells. This
|
||||
// is normally the first child of the root (ie. layer 0).
|
||||
let parent = graph.getDefaultParent();
|
||||
|
||||
// Adds cells to the model in a single step
|
||||
graph.getDataModel().beginUpdate();
|
||||
try {
|
||||
var v1 = graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30);
|
||||
var v2 = graph.insertVertex(parent, null, 'World!', 200, 150, 80, 30);
|
||||
var e1 = graph.insertEdge(parent, null, '', v1, v2);
|
||||
} finally {
|
||||
// Updates the display
|
||||
graph.getDataModel().endUpdate();
|
||||
}
|
||||
|
|
@ -1,894 +0,0 @@
|
|||
/**
|
||||
README
|
||||
******
|
||||
- Edge-to-edge connections: We store the point where the mouse
|
||||
was released in the terminal points of the edge geometry and
|
||||
use that point to find the nearest segment on the target edge
|
||||
and the connection point between the two edges in
|
||||
mxGraphView.updateFixedTerminalPoint.
|
||||
|
||||
- The orthogonal router, which is implemented as an edge style,
|
||||
computes its result based on the output of mxGraphView.
|
||||
updateFixedTerminalPoint, which computes all connection points
|
||||
for edge-to-edge connections and constrained ports and vertices
|
||||
and stores them in state.absolutePoints.
|
||||
|
||||
- Routing directions are stored in the 'portConstraint' style.
|
||||
Possible values for this style horizontal and vertical. Note
|
||||
that this may have other values depending on the edge style.
|
||||
|
||||
- For edge-to-edge connections, a 'source-/targetConstraint'
|
||||
style is added in updateFixedTerminalPoint that contains the
|
||||
orientation of the segment that the edge connects to. Possible
|
||||
values are horizontal, vertical.
|
||||
|
||||
- An alternative solution for connection points via connection
|
||||
constraints is demonstrated. In this setup, the edge is connected
|
||||
to the parent cell directly. There are no child cells that act
|
||||
as "ports". Instead, the connection information is stored as a
|
||||
relative point in the connecting edge. (See also: portrefs.html
|
||||
for storing references to ports.)
|
||||
|
||||
*/
|
||||
|
||||
import {
|
||||
Graph,
|
||||
InternalEvent,
|
||||
RubberBandHandler,
|
||||
ConnectionHandler,
|
||||
ConnectionConstraint,
|
||||
Geometry,
|
||||
PolylineShape,
|
||||
Point,
|
||||
CellState,
|
||||
} from '@maxgraph/core';
|
||||
|
||||
import { globalTypes } from '../.storybook/preview';
|
||||
|
||||
export default {
|
||||
title: 'Connections/Anchors',
|
||||
argTypes: {
|
||||
...globalTypes,
|
||||
rubberBand: {
|
||||
type: 'boolean',
|
||||
defaultValue: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const HTML_TEMPLATE = `
|
||||
<body onload="main(document.getElementById('graphContainer'))">
|
||||
<div id="graphContainer"
|
||||
style="overflow:auto;position:relative;width:800px;height:600px;border:1px solid gray;background:url('images/wires-grid.gif');background-position:-1px 0px;cursor:crosshair;">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
// If connect preview is not moved away then getCellAt is used to detect the cell under
|
||||
// the mouse if the mouse is over the preview shape in IE (no event transparency), ie.
|
||||
// the built-in hit-detection of the HTML document will not be used in this case.
|
||||
mxConnectionHandler.prototype.movePreviewAway = false;
|
||||
mxConnectionHandler.prototype.waypointsEnabled = true;
|
||||
mxGraph.prototype.resetEdgesOnConnect = false;
|
||||
mxConstants.SHADOWCOLOR = '#C0C0C0';
|
||||
let joinNodeSize = 7;
|
||||
let strokeWidth = 2;
|
||||
|
||||
// Replaces the port image
|
||||
mxConstraintHandler.prototype.pointImage = new mxImage('images/dot.gif', 10, 10);
|
||||
|
||||
// Enables guides
|
||||
mxGraphHandler.prototype.guidesEnabled = true;
|
||||
|
||||
// Alt disables guides
|
||||
mxGuide.prototype.isEnabledForEvent = function (evt) {
|
||||
return !mxEvent.isAltDown(evt);
|
||||
};
|
||||
|
||||
// Enables snapping waypoints to terminals
|
||||
mxEdgeHandler.prototype.snapToTerminals = true;
|
||||
|
||||
let graph = new mxGraph(container);
|
||||
graph.view.scale = 1;
|
||||
graph.setPanning(true);
|
||||
graph.setConnectable(true);
|
||||
graph.setConnectableEdges(true);
|
||||
graph.setDisconnectOnMove(false);
|
||||
graph.foldingEnabled = false;
|
||||
|
||||
//Maximum size
|
||||
graph.maximumGraphBounds = new mxRectangle(0, 0, 800, 600);
|
||||
graph.border = 50;
|
||||
|
||||
// Panning handler consumed right click so this must be
|
||||
// disabled if right click should stop connection handler.
|
||||
graph.getPlugin('PanningHandler').isPopupTrigger = function () {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Enables return key to stop editing (use shift-enter for newlines)
|
||||
graph.setEnterStopsCellEditing(true);
|
||||
|
||||
// Adds rubberband selection
|
||||
new mxRubberband(graph);
|
||||
|
||||
// Alternative solution for implementing connection points without child cells.
|
||||
// This can be extended as shown in portrefs.html example to allow for per-port
|
||||
// incoming/outgoing direction.
|
||||
graph.getAllConnectionConstraints = function (terminal) {
|
||||
let geo = terminal != null ? terminal.cell.getGeometry() : null;
|
||||
|
||||
if (
|
||||
(geo != null ? !geo.relative : false) &&
|
||||
terminal.cell.isVertex() &&
|
||||
this.getDataModel().getChildCount(terminal.cell) == 0
|
||||
) {
|
||||
return [
|
||||
new mxConnectionConstraint(new mxPoint(0, 0.5), false),
|
||||
new mxConnectionConstraint(new mxPoint(1, 0.5), false),
|
||||
];
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// Makes sure non-relative cells can only be connected via constraints
|
||||
graph.getPlugin('ConnectionHandler').isConnectableCell = function (cell) {
|
||||
if (this.graph.getDataModel().isEdge(cell)) {
|
||||
return true;
|
||||
} else {
|
||||
let geo = cell != null ? cell.getGeometry() : null;
|
||||
return geo != null ? geo.relative : false;
|
||||
}
|
||||
};
|
||||
mxEdgeHandler.prototype.isConnectableCell = function (cell) {
|
||||
return graph.getPlugin('ConnectionHandler').isConnectableCell(cell);
|
||||
};
|
||||
|
||||
// Adds a special tooltip for edges
|
||||
graph.setTooltips(true);
|
||||
|
||||
let getTooltipForCell = graph.getTooltipForCell;
|
||||
graph.getTooltipForCell = function (cell) {
|
||||
let tip = '';
|
||||
|
||||
if (cell != null) {
|
||||
let src = this.getDataModel().getTerminal(cell, true);
|
||||
if (src != null) {
|
||||
tip += this.getTooltipForCell(src) + ' ';
|
||||
}
|
||||
|
||||
let parent = this.getDataModel().getParent(cell);
|
||||
if (parent.isVertex()) {
|
||||
tip += this.getTooltipForCell(parent) + '.';
|
||||
}
|
||||
|
||||
tip += getTooltipForCell.apply(this, arguments);
|
||||
|
||||
let trg = this.getDataModel().getTerminal(cell, false);
|
||||
if (trg != null) {
|
||||
tip += ' ' + this.getTooltipForCell(trg);
|
||||
}
|
||||
}
|
||||
|
||||
return tip;
|
||||
};
|
||||
|
||||
// Switch for black background and bright styles
|
||||
let invert = false;
|
||||
|
||||
if (invert) {
|
||||
container.style.backgroundColor = 'black';
|
||||
|
||||
// White in-place editor text color
|
||||
mxCellEditorStartEditing = mxCellEditor.prototype.startEditing;
|
||||
mxCellEditor.prototype.startEditing = (cell, trigger) => {
|
||||
mxCellEditorStartEditing.apply(this, arguments);
|
||||
|
||||
if (this.textarea != null) {
|
||||
this.textarea.style.color = '#FFFFFF';
|
||||
}
|
||||
};
|
||||
|
||||
mxGraphHandler.prototype.previewColor = 'white';
|
||||
}
|
||||
|
||||
let labelBackground = invert ? '#000000' : '#FFFFFF';
|
||||
let fontColor = invert ? '#FFFFFF' : '#000000';
|
||||
let strokeColor = invert ? '#C0C0C0' : '#000000';
|
||||
let fillColor = invert ? 'none' : '#FFFFFF';
|
||||
|
||||
let style = graph.getStylesheet().getDefaultEdgeStyle();
|
||||
delete style.endArrow;
|
||||
style.strokeColor = strokeColor;
|
||||
style.labelBackgroundColor = labelBackground;
|
||||
style.edgeStyle = 'wireEdgeStyle';
|
||||
style.fontColor = fontColor;
|
||||
style.fontSize = '9';
|
||||
style.movable = '0';
|
||||
style.strokeWidth = strokeWidth;
|
||||
//style.rounded = '1';
|
||||
|
||||
// Sets join node size
|
||||
style.startSize = joinNodeSize;
|
||||
style.endSize = joinNodeSize;
|
||||
|
||||
style = graph.getStylesheet().getDefaultVertexStyle();
|
||||
style.gradientDirection = 'south';
|
||||
//style.gradientColor = '#909090';
|
||||
style.strokeColor = strokeColor;
|
||||
//style.fillColor = '#e0e0e0';
|
||||
style.fillColor = 'none';
|
||||
style.fontColor = fontColor;
|
||||
style.fontStyle = '1';
|
||||
style.fontSize = '12';
|
||||
style.resizable = '0';
|
||||
style.rounded = '1';
|
||||
style.strokeWidth = strokeWidth;
|
||||
|
||||
let parent = graph.getDefaultParent();
|
||||
|
||||
graph.getDataModel().beginUpdate();
|
||||
try {
|
||||
var v1 = graph.insertVertex(parent, null, 'J1', 80, 40, 40, 80, {
|
||||
verticalLabelPosition: 'top',
|
||||
verticalAlign: 'bottom',
|
||||
shadow: true,
|
||||
fillColor,
|
||||
});
|
||||
v1.setConnectable(false);
|
||||
|
||||
var v11 = graph.insertVertex(v1, null, '1', 0, 0, 10, 16, {
|
||||
shape: 'line',
|
||||
align: 'left',
|
||||
verticalAlign: 'middle',
|
||||
fontSize: 10,
|
||||
routingCenterX: -0.5,
|
||||
spacingLeft: 12,
|
||||
fontColor,
|
||||
strokeColor,
|
||||
});
|
||||
v11.geometry.relative = true;
|
||||
v11.geometry.offset = new mxPoint(-v11.geometry.width, 2);
|
||||
var v12 = v11.clone();
|
||||
v12.value = '2';
|
||||
v12.geometry.offset = new mxPoint(-v11.geometry.width, 22);
|
||||
v1.insert(v12);
|
||||
var v13 = v11.clone();
|
||||
v13.value = '3';
|
||||
v13.geometry.offset = new mxPoint(-v11.geometry.width, 42);
|
||||
v1.insert(v13);
|
||||
var v14 = v11.clone();
|
||||
v14.value = '4';
|
||||
v14.geometry.offset = new mxPoint(-v11.geometry.width, 62);
|
||||
v1.insert(v14);
|
||||
|
||||
var v15 = v11.clone();
|
||||
v15.value = '5';
|
||||
v15.geometry.x = 1;
|
||||
v15.style = {
|
||||
shape: 'line',
|
||||
align: 'right',
|
||||
verticalAlign: 'middle',
|
||||
fontSize: 10,
|
||||
routingCenterX: 0.5,
|
||||
spacingRight: 12,
|
||||
fontColor,
|
||||
strokeColor,
|
||||
};
|
||||
v15.geometry.offset = new mxPoint(0, 2);
|
||||
v1.insert(v15);
|
||||
var v16 = v15.clone();
|
||||
v16.value = '6';
|
||||
v16.geometry.offset = new mxPoint(0, 22);
|
||||
v1.insert(v16);
|
||||
var v17 = v15.clone();
|
||||
v17.value = '7';
|
||||
v17.geometry.offset = new mxPoint(0, 42);
|
||||
v1.insert(v17);
|
||||
var v18 = v15.clone();
|
||||
v18.value = '8';
|
||||
v18.geometry.offset = new mxPoint(0, 62);
|
||||
v1.insert(v18);
|
||||
|
||||
var v19 = v15.clone();
|
||||
v19.value = 'clk';
|
||||
v19.geometry.x = 0.5;
|
||||
v19.geometry.y = 1;
|
||||
v19.geometry.width = 10;
|
||||
v19.geometry.height = 4;
|
||||
// NOTE: portConstraint is defined for east direction, so must be inverted here
|
||||
v19.style = {
|
||||
shape: 'triangle',
|
||||
direction: 'north',
|
||||
spacingBottom: 12,
|
||||
align: 'center',
|
||||
portConstraint: 'horizontal',
|
||||
fontSize: 8,
|
||||
strokeColor,
|
||||
routingCenterY: 0.5,
|
||||
};
|
||||
v19.geometry.offset = new mxPoint(-4, -4);
|
||||
v1.insert(v19);
|
||||
|
||||
var v2 = graph.insertVertex(parent, null, 'R1', 220, 220, 80, 20, {
|
||||
shape: 'resistor',
|
||||
verticalLabelPosition: 'top',
|
||||
verticalAlign: 'bottom',
|
||||
});
|
||||
|
||||
// Uses implementation of connection points via constraints (see above)
|
||||
//v2.setConnectable(false);
|
||||
|
||||
/*var v21 = graph.insertVertex(v2, null, 'A', 0, 0.5, 10, 1,
|
||||
'shape=none;spacingBottom=11;spacingLeft=1;align=left;fontSize=8;'+
|
||||
'fontColor=#4c4c4c;strokeColor=#909090;');
|
||||
v21.geometry.relative = true;
|
||||
v21.geometry.offset = new mxPoint(0, -1);
|
||||
|
||||
var v22 = graph.insertVertex(v2, null, 'B', 1, 0.5, 10, 1,
|
||||
'spacingBottom=11;spacingLeft=1;align=left;fontSize=8;'+
|
||||
'fontColor=#4c4c4c;strokeColor=#909090;');
|
||||
v22.geometry.relative = true;
|
||||
v22.geometry.offset = new mxPoint(-10, -1);*/
|
||||
|
||||
var v3 = graph.addCell(graph.getDataModel().cloneCell(v1));
|
||||
v3.value = 'J3';
|
||||
v3.geometry.x = 420;
|
||||
v3.geometry.y = 340;
|
||||
|
||||
// Connection constraints implemented in edges, alternatively this
|
||||
// can be implemented using references, see: portrefs.html
|
||||
var e1 = graph.insertEdge(parent, null, 'e1', v1.getChildAt(7), v2, {
|
||||
entryX: 0,
|
||||
entryY: 0.5,
|
||||
entryPerimeter: 0,
|
||||
});
|
||||
e1.geometry.points = [new mxPoint(180, 110)];
|
||||
|
||||
var e2 = graph.insertEdge(parent, null, 'e2', v1.getChildAt(4), v2, {
|
||||
entryX: 1,
|
||||
entryY: 0.5,
|
||||
entryPerimeter: 0,
|
||||
});
|
||||
e2.geometry.points = [new mxPoint(320, 50), new mxPoint(320, 230)];
|
||||
|
||||
var e3 = graph.insertEdge(parent, null, 'crossover', e1, e2);
|
||||
e3.geometry.setTerminalPoint(new mxPoint(180, 140), true);
|
||||
e3.geometry.setTerminalPoint(new mxPoint(320, 140), false);
|
||||
|
||||
// var e1 = graph.insertEdge(parent, null, 'e1', v1.getChildAt(7), v2.getChildAt(0));
|
||||
// e1.geometry.points = [new mxPoint(180, 140)];
|
||||
|
||||
// var e2 = graph.insertEdge(parent, null, '', v1.getChildAt(4), v2.getChildAt(1));
|
||||
// e2.geometry.points = [new mxPoint(320, 80)];
|
||||
|
||||
// var e3 = graph.insertEdge(parent, null, 'crossover', e1, e2);
|
||||
// e3.geometry.setTerminalPoint(new mxPoint(180, 160), true);
|
||||
// e3.geometry.setTerminalPoint(new mxPoint(320, 160), false);
|
||||
|
||||
var e4 = graph.insertEdge(parent, null, 'e4', v2, v3.getChildAt(0), {
|
||||
exitX: 1,
|
||||
exitY: 0.5,
|
||||
entryPerimeter: 0,
|
||||
});
|
||||
e4.geometry.points = [new mxPoint(380, 230)];
|
||||
|
||||
var e5 = graph.insertEdge(parent, null, 'e5', v3.getChildAt(5), v1.getChildAt(0));
|
||||
e5.geometry.points = [new mxPoint(500, 310), new mxPoint(500, 20), new mxPoint(50, 20)];
|
||||
|
||||
var e6 = graph.insertEdge(parent, null, '');
|
||||
e6.geometry.setTerminalPoint(new mxPoint(100, 500), true);
|
||||
e6.geometry.setTerminalPoint(new mxPoint(600, 500), false);
|
||||
|
||||
var e7 = graph.insertEdge(parent, null, 'e7', v3.getChildAt(7), e6);
|
||||
e7.geometry.setTerminalPoint(new mxPoint(500, 500), false);
|
||||
e7.geometry.points = [new mxPoint(500, 350)];
|
||||
} finally {
|
||||
graph.getDataModel().endUpdate();
|
||||
}
|
||||
|
||||
document.body.appendChild(
|
||||
mxUtils.button('Zoom In', function () {
|
||||
graph.zoomIn();
|
||||
})
|
||||
);
|
||||
|
||||
document.body.appendChild(
|
||||
mxUtils.button('Zoom Out', function () {
|
||||
graph.zoomOut();
|
||||
})
|
||||
);
|
||||
|
||||
// Undo/redo
|
||||
let undoManager = new UndoManager();
|
||||
let listener = function (sender, evt) {
|
||||
undoManager.undoableEditHappened(evt.getProperty('edit'));
|
||||
};
|
||||
graph.getDataModel().addListener(mxEvent.UNDO, listener);
|
||||
graph.getView().addListener(mxEvent.UNDO, listener);
|
||||
|
||||
document.body.appendChild(
|
||||
mxUtils.button('Undo', function () {
|
||||
undoManager.undo();
|
||||
})
|
||||
);
|
||||
|
||||
document.body.appendChild(
|
||||
mxUtils.button('Redo', function () {
|
||||
undoManager.redo();
|
||||
})
|
||||
);
|
||||
|
||||
// Shows XML for debugging the actual model
|
||||
document.body.appendChild(
|
||||
mxUtils.button('Delete', function () {
|
||||
graph.removeCells();
|
||||
})
|
||||
);
|
||||
|
||||
// Wire-mode
|
||||
let checkbox = document.createElement('input');
|
||||
checkbox.setAttribute('type', 'checkbox');
|
||||
|
||||
document.body.appendChild(checkbox);
|
||||
mxUtils.write(document.body, 'Wire Mode');
|
||||
|
||||
// Starts connections on the background in wire-mode
|
||||
let connectionHandlerIsStartEvent = graph.getPlugin('ConnectionHandler').isStartEvent;
|
||||
graph.getPlugin('ConnectionHandler').isStartEvent = function (me) {
|
||||
return checkbox.checked || connectionHandlerIsStartEvent.apply(this, arguments);
|
||||
};
|
||||
|
||||
// Avoids any connections for gestures within tolerance except when in wire-mode
|
||||
// or when over a port
|
||||
let connectionHandlerMouseUp = graph.getPlugin('ConnectionHandler').mouseUp;
|
||||
graph.getPlugin('ConnectionHandler').mouseUp = function (sender, me) {
|
||||
if (this.first != null && this.previous != null) {
|
||||
let point = mxUtils.convertPoint(this.graph.container, me.getX(), me.getY());
|
||||
let dx = Math.abs(point.x - this.first.x);
|
||||
let dy = Math.abs(point.y - this.first.y);
|
||||
|
||||
if (dx < this.graph.tolerance && dy < this.graph.tolerance) {
|
||||
// Selects edges in non-wire mode for single clicks, but starts
|
||||
// connecting for non-edges regardless of wire-mode
|
||||
if (!checkbox.checked && this.graph.getDataModel().isEdge(this.previous.cell)) {
|
||||
this.reset();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
connectionHandlerMouseUp.apply(this, arguments);
|
||||
};
|
||||
|
||||
// Grid
|
||||
var checkbox2 = document.createElement('input');
|
||||
checkbox2.setAttribute('type', 'checkbox');
|
||||
checkbox2.setAttribute('checked', 'true');
|
||||
|
||||
document.body.appendChild(checkbox2);
|
||||
mxUtils.write(document.body, 'Grid');
|
||||
|
||||
mxEvent.addListener(checkbox2, 'click', function (evt) {
|
||||
if (checkbox2.checked) {
|
||||
container.style.background = "url('images/wires-grid.gif')";
|
||||
} else {
|
||||
container.style.background = '';
|
||||
}
|
||||
container.style.backgroundColor = invert ? 'black' : 'white';
|
||||
});
|
||||
mxEvent.disableContextMenu(container);
|
||||
// Updates connection points before the routing is called.
|
||||
|
||||
// Computes the position of edge to edge connection points.
|
||||
mxGraphView.prototype.updateFixedTerminalPoint = function (
|
||||
edge,
|
||||
terminal,
|
||||
source,
|
||||
constraint
|
||||
) {
|
||||
let pt = null;
|
||||
|
||||
if (constraint != null) {
|
||||
pt = this.graph.getConnectionPoint(terminal, constraint);
|
||||
}
|
||||
|
||||
if (source) {
|
||||
edge.sourceSegment = null;
|
||||
} else {
|
||||
edge.targetSegment = null;
|
||||
}
|
||||
|
||||
if (pt == null) {
|
||||
let s = this.scale;
|
||||
let tr = this.translate;
|
||||
let orig = edge.origin;
|
||||
let geo = edge.cell.getGeometry();
|
||||
pt = geo.getTerminalPoint(source);
|
||||
|
||||
// Computes edge-to-edge connection point
|
||||
if (pt != null) {
|
||||
pt = new mxPoint(s * (tr.x + pt.x + orig.x), s * (tr.y + pt.y + orig.y));
|
||||
|
||||
// Finds nearest segment on edge and computes intersection
|
||||
if (terminal != null && terminal.absolutePoints != null) {
|
||||
let seg = mxUtils.findNearestSegment(terminal, pt.x, pt.y);
|
||||
|
||||
// Finds orientation of the segment
|
||||
var p0 = terminal.absolutePoints[seg];
|
||||
let pe = terminal.absolutePoints[seg + 1];
|
||||
let horizontal = p0.x - pe.x == 0;
|
||||
|
||||
// Stores the segment in the edge state
|
||||
let key = source ? 'sourceConstraint' : 'targetConstraint';
|
||||
let value = horizontal ? 'horizontal' : 'vertical';
|
||||
edge.style[key] = value;
|
||||
|
||||
// Keeps the coordinate within the segment bounds
|
||||
if (horizontal) {
|
||||
pt.x = p0.x;
|
||||
pt.y = Math.min(pt.y, Math.max(p0.y, pe.y));
|
||||
pt.y = Math.max(pt.y, Math.min(p0.y, pe.y));
|
||||
} else {
|
||||
pt.y = p0.y;
|
||||
pt.x = Math.min(pt.x, Math.max(p0.x, pe.x));
|
||||
pt.x = Math.max(pt.x, Math.min(p0.x, pe.x));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Computes constraint connection points on vertices and ports
|
||||
else if (terminal != null && terminal.cell.geometry.relative) {
|
||||
pt = new mxPoint(
|
||||
this.getRoutingCenterX(terminal),
|
||||
this.getRoutingCenterY(terminal)
|
||||
);
|
||||
}
|
||||
|
||||
// Snaps point to grid
|
||||
/*if (pt != null)
|
||||
{
|
||||
let tr = this.graph.view.translate;
|
||||
let s = this.graph.view.scale;
|
||||
|
||||
pt.x = (this.graph.snap(pt.x / s - tr.x) + tr.x) * s;
|
||||
pt.y = (this.graph.snap(pt.y / s - tr.y) + tr.y) * s;
|
||||
}*/
|
||||
}
|
||||
|
||||
edge.setAbsoluteTerminalPoint(pt, source);
|
||||
};
|
||||
|
||||
// Overrides methods to preview and create new edges.
|
||||
|
||||
// Sets source terminal point for edge-to-edge connections.
|
||||
mxConnectionHandler.prototype.createEdgeState = function (me) {
|
||||
let edge = this.graph.createEdge();
|
||||
|
||||
if (this.sourceConstraint != null && this.previous != null) {
|
||||
edge.style =
|
||||
'exitX' +
|
||||
'=' +
|
||||
this.sourceConstraint.point.x +
|
||||
';' +
|
||||
'exitY' +
|
||||
'=' +
|
||||
this.sourceConstraint.point.y +
|
||||
';';
|
||||
} else if (this.graph.model.isEdge(me.getCell())) {
|
||||
let scale = this.graph.view.scale;
|
||||
let tr = this.graph.view.translate;
|
||||
let pt = new mxPoint(
|
||||
this.graph.snap(me.getGraphX() / scale) - tr.x,
|
||||
this.graph.snap(me.getGraphY() / scale) - tr.y
|
||||
);
|
||||
edge.geometry.setTerminalPoint(pt, true);
|
||||
}
|
||||
|
||||
return this.graph.view.createState(edge);
|
||||
};
|
||||
|
||||
// Uses right mouse button to create edges on background (see also: lines 67 ff)
|
||||
mxConnectionHandler.prototype.isStopEvent = function (me) {
|
||||
return me.getState() != null || mxEvent.isRightMouseButton(me.getEvent());
|
||||
};
|
||||
|
||||
// Updates target terminal point for edge-to-edge connections.
|
||||
mxConnectionHandlerUpdateCurrentState = mxConnectionHandler.prototype.updateCurrentState;
|
||||
mxConnectionHandler.prototype.updateCurrentState = function (me) {
|
||||
mxConnectionHandlerUpdateCurrentState.apply(this, arguments);
|
||||
|
||||
if (this.edgeState != null) {
|
||||
this.edgeState.cell.geometry.setTerminalPoint(null, false);
|
||||
|
||||
if (
|
||||
this.shape != null &&
|
||||
this.currentState != null &&
|
||||
this.currentState.view.graph.model.isEdge(this.currentState.cell)
|
||||
) {
|
||||
let scale = this.graph.view.scale;
|
||||
let tr = this.graph.view.translate;
|
||||
let pt = new mxPoint(
|
||||
this.graph.snap(me.getGraphX() / scale) - tr.x,
|
||||
this.graph.snap(me.getGraphY() / scale) - tr.y
|
||||
);
|
||||
this.edgeState.cell.geometry.setTerminalPoint(pt, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Updates the terminal and control points in the cloned preview.
|
||||
mxEdgeSegmentHandler.prototype.clonePreviewState = function (point, terminal) {
|
||||
let clone = mxEdgeHandler.prototype.clonePreviewState.apply(this, arguments);
|
||||
clone.cell = clone.cell.clone();
|
||||
|
||||
if (this.isSource || this.isTarget) {
|
||||
clone.cell.geometry = clone.cell.geometry.clone();
|
||||
|
||||
// Sets the terminal point of an edge if we're moving one of the endpoints
|
||||
if (this.graph.getDataModel().isEdge(clone.cell)) {
|
||||
// TODO: Only set this if the target or source terminal is an edge
|
||||
clone.cell.geometry.setTerminalPoint(point, this.isSource);
|
||||
} else {
|
||||
clone.cell.geometry.setTerminalPoint(null, this.isSource);
|
||||
}
|
||||
}
|
||||
|
||||
return clone;
|
||||
};
|
||||
|
||||
let mxEdgeHandlerConnect = mxEdgeHandler.prototype.connect;
|
||||
mxEdgeHandler.prototype.connect = function (edge, terminal, isSource, isClone, me) {
|
||||
let result = null;
|
||||
let model = this.graph.getDataModel();
|
||||
let parent = model.getParent(edge);
|
||||
|
||||
model.beginUpdate();
|
||||
try {
|
||||
result = mxEdgeHandlerConnect.apply(this, arguments);
|
||||
let geo = model.getGeometry(result);
|
||||
|
||||
if (geo != null) {
|
||||
geo = geo.clone();
|
||||
let pt = null;
|
||||
|
||||
if (model.isEdge(terminal)) {
|
||||
pt = this.abspoints[this.isSource ? 0 : this.abspoints.length - 1];
|
||||
pt.x = pt.x / this.graph.view.scale - this.graph.view.translate.x;
|
||||
pt.y = pt.y / this.graph.view.scale - this.graph.view.translate.y;
|
||||
|
||||
let pstate = this.graph
|
||||
.getView()
|
||||
.getState(this.graph.getDataModel().getParent(edge));
|
||||
|
||||
if (pstate != null) {
|
||||
pt.x -= pstate.origin.x;
|
||||
pt.y -= pstate.origin.y;
|
||||
}
|
||||
|
||||
pt.x -= this.graph.panDx / this.graph.view.scale;
|
||||
pt.y -= this.graph.panDy / this.graph.view.scale;
|
||||
}
|
||||
|
||||
geo.setTerminalPoint(pt, isSource);
|
||||
model.setGeometry(edge, geo);
|
||||
}
|
||||
} finally {
|
||||
model.endUpdate();
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// Adds in-place highlighting for complete cell area (no hotspot).
|
||||
|
||||
mxConnectionHandlerCreateMarker = mxConnectionHandler.prototype.createMarker;
|
||||
mxConnectionHandler.prototype.createMarker = function () {
|
||||
let marker = mxConnectionHandlerCreateMarker.apply(this, arguments);
|
||||
|
||||
// Uses complete area of cell for new connections (no hotspot)
|
||||
marker.intersects = function (state, evt) {
|
||||
return true;
|
||||
};
|
||||
|
||||
// Adds in-place highlighting
|
||||
mxCellHighlightHighlight = mxCellHighlight.prototype.highlight;
|
||||
marker.highlight.highlight = function (state) {
|
||||
if (this.state != state) {
|
||||
if (this.state != null) {
|
||||
this.state.style = this.lastStyle;
|
||||
|
||||
// Workaround for shape using current stroke width if no strokewidth defined
|
||||
this.state.style.strokeWidth = this.state.style.strokeWidth || '1';
|
||||
this.state.style.strokeColor = this.state.style.strokeColor || 'none';
|
||||
|
||||
if (this.state.shape != null) {
|
||||
this.state.view.graph.cellRenderer.configureShape(this.state);
|
||||
this.state.shape.redraw();
|
||||
}
|
||||
}
|
||||
|
||||
if (state != null) {
|
||||
this.lastStyle = state.style;
|
||||
state.style = mxUtils.clone(state.style);
|
||||
state.style.strokeColor = '#00ff00';
|
||||
state.style.strokeWidth = '3';
|
||||
|
||||
if (state.shape != null) {
|
||||
state.view.graph.cellRenderer.configureShape(state);
|
||||
state.shape.redraw();
|
||||
}
|
||||
}
|
||||
this.state = state;
|
||||
}
|
||||
};
|
||||
|
||||
return marker;
|
||||
};
|
||||
|
||||
mxEdgeHandlerCreateMarker = mxEdgeHandler.prototype.createMarker;
|
||||
mxEdgeHandler.prototype.createMarker = function () {
|
||||
let marker = mxEdgeHandlerCreateMarker.apply(this, arguments);
|
||||
// Adds in-place highlighting when reconnecting existing edges
|
||||
marker.highlight.highlight =
|
||||
this.graph.getPlugin('ConnectionHandler').marker.highlight.highlight;
|
||||
return marker;
|
||||
};
|
||||
|
||||
// Adds oval markers for edge-to-edge connections.
|
||||
|
||||
mxGraphGetCellStyle = mxGraph.prototype.getCellStyle;
|
||||
mxGraph.prototype.getCellStyle = function (cell) {
|
||||
let style = mxGraphGetCellStyle.apply(this, arguments);
|
||||
|
||||
if (style != null && this.model.isEdge(cell)) {
|
||||
style = mxUtils.clone(style);
|
||||
|
||||
if (this.model.isEdge(this.model.getTerminal(cell, true))) {
|
||||
style.startArrow = 'oval';
|
||||
}
|
||||
|
||||
if (this.model.isEdge(this.model.getTerminal(cell, false))) {
|
||||
style.endArrow = 'oval';
|
||||
}
|
||||
}
|
||||
return style;
|
||||
};
|
||||
|
||||
// Imlements a custom resistor shape. Direction currently ignored here.
|
||||
|
||||
function ResistorShape() {}
|
||||
ResistorShape.prototype = new mxCylinder();
|
||||
ResistorShape.prototype.constructor = ResistorShape;
|
||||
|
||||
ResistorShape.prototype.redrawPath = function (path, x, y, w, h, isForeground) {
|
||||
let dx = w / 16;
|
||||
|
||||
if (isForeground) {
|
||||
path.moveTo(0, h / 2);
|
||||
path.lineTo(2 * dx, h / 2);
|
||||
path.lineTo(3 * dx, 0);
|
||||
path.lineTo(5 * dx, h);
|
||||
path.lineTo(7 * dx, 0);
|
||||
path.lineTo(9 * dx, h);
|
||||
path.lineTo(11 * dx, 0);
|
||||
path.lineTo(13 * dx, h);
|
||||
path.lineTo(14 * dx, h / 2);
|
||||
path.lineTo(16 * dx, h / 2);
|
||||
path.end();
|
||||
}
|
||||
};
|
||||
|
||||
mxCellRenderer.registerShape('resistor', ResistorShape);
|
||||
|
||||
// Imlements a custom resistor shape. Direction currently ignored here.
|
||||
|
||||
mxEdgeStyle.WireConnector = function (state, source, target, hints, result) {
|
||||
// Creates array of all way- and terminalpoints
|
||||
let pts = state.absolutePoints;
|
||||
let horizontal = true;
|
||||
let hint = null;
|
||||
|
||||
// Gets the initial connection from the source terminal or edge
|
||||
if (source != null && state.view.graph.model.isEdge(source.cell)) {
|
||||
horizontal = state.style.sourceConstraint == 'horizontal';
|
||||
} else if (source != null) {
|
||||
horizontal = source.style.portConstraint != 'vertical';
|
||||
|
||||
// Checks the direction of the shape and rotates
|
||||
let direction = source.style.direction;
|
||||
|
||||
if (direction == 'north' || direction == 'south') {
|
||||
horizontal = !horizontal;
|
||||
}
|
||||
}
|
||||
|
||||
// Adds the first point
|
||||
// TODO: Should move along connected segment
|
||||
let pt = pts[0];
|
||||
|
||||
if (pt == null && source != null) {
|
||||
pt = new mxPoint(
|
||||
state.view.getRoutingCenterX(source),
|
||||
state.view.getRoutingCenterY(source)
|
||||
);
|
||||
} else if (pt != null) {
|
||||
pt = pt.clone();
|
||||
}
|
||||
|
||||
let first = pt;
|
||||
|
||||
// Adds the waypoints
|
||||
if (hints != null && hints.length > 0) {
|
||||
// FIXME: First segment not movable
|
||||
/*hint = state.view.transformControlPoint(state, hints[0]);
|
||||
MaxLog.show();
|
||||
MaxLog.debug(hints.length,'hints0.y='+hint.y, pt.y)
|
||||
|
||||
if (horizontal && Math.floor(hint.y) != Math.floor(pt.y))
|
||||
{
|
||||
MaxLog.show();
|
||||
MaxLog.debug('add waypoint');
|
||||
|
||||
pt = new mxPoint(pt.x, hint.y);
|
||||
result.push(pt);
|
||||
pt = pt.clone();
|
||||
//horizontal = !horizontal;
|
||||
}*/
|
||||
|
||||
for (let i = 0; i < hints.length; i++) {
|
||||
horizontal = !horizontal;
|
||||
hint = state.view.transformControlPoint(state, hints[i]);
|
||||
|
||||
if (horizontal) {
|
||||
if (pt.y != hint.y) {
|
||||
pt.y = hint.y;
|
||||
result.push(pt.clone());
|
||||
}
|
||||
} else if (pt.x != hint.x) {
|
||||
pt.x = hint.x;
|
||||
result.push(pt.clone());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
hint = pt;
|
||||
}
|
||||
|
||||
// Adds the last point
|
||||
pt = pts[pts.length - 1];
|
||||
|
||||
// TODO: Should move along connected segment
|
||||
if (pt == null && target != null) {
|
||||
pt = new mxPoint(
|
||||
state.view.getRoutingCenterX(target),
|
||||
state.view.getRoutingCenterY(target)
|
||||
);
|
||||
}
|
||||
|
||||
if (horizontal) {
|
||||
if (pt.y != hint.y && first.x != pt.x) {
|
||||
result.push(new mxPoint(pt.x, hint.y));
|
||||
}
|
||||
} else if (pt.x != hint.x && first.y != pt.y) {
|
||||
result.push(new mxPoint(hint.x, pt.y));
|
||||
}
|
||||
};
|
||||
|
||||
mxStyleRegistry.putValue('wireEdgeStyle', mxEdgeStyle.WireConnector);
|
||||
|
||||
// This connector needs an mxEdgeSegmentHandler
|
||||
mxGraphCreateHandler = mxGraph.prototype.createHandler;
|
||||
mxGraph.prototype.createHandler = function (state) {
|
||||
let result = null;
|
||||
|
||||
if (state != null) {
|
||||
if (this.model.isEdge(state.cell)) {
|
||||
let style = this.view.getEdgeStyle(state);
|
||||
|
||||
if (style == mxEdgeStyle.WireConnector) {
|
||||
return new mxEdgeSegmentHandler(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mxGraphCreateHandler.apply(this, arguments);
|
||||
};
|
|
@ -1,5 +0,0 @@
|
|||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
|
||||
export default (req, res) => {
|
||||
res.status(200).json({ name: 'John Doe' });
|
||||
};
|
Loading…
Reference in New Issue