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();
|
super();
|
||||||
|
|
||||||
|
if (factoryMethod) {
|
||||||
this.factoryMethod = factoryMethod;
|
this.factoryMethod = factoryMethod;
|
||||||
|
}
|
||||||
|
|
||||||
// Adds the inner table
|
// Adds the inner table
|
||||||
this.table = document.createElement('table');
|
this.table = document.createElement('table');
|
||||||
|
@ -189,9 +191,9 @@ class MaxPopupMenu extends EventSource implements Partial<PopupMenuItem> {
|
||||||
funct: Function,
|
funct: Function,
|
||||||
parent: PopupMenuItem | null = null,
|
parent: PopupMenuItem | null = null,
|
||||||
iconCls: string | null = null,
|
iconCls: string | null = null,
|
||||||
enabled: boolean | null = null,
|
enabled = true,
|
||||||
active: boolean | null = null,
|
active = true,
|
||||||
noHover: boolean | null = null
|
noHover = false
|
||||||
) {
|
) {
|
||||||
parent = (parent ?? this) as PopupMenuItem;
|
parent = (parent ?? this) as PopupMenuItem;
|
||||||
this.itemCount++;
|
this.itemCount++;
|
||||||
|
|
|
@ -92,9 +92,9 @@ class Graph extends EventSource {
|
||||||
foldingEnabled: null | boolean = null;
|
foldingEnabled: null | boolean = null;
|
||||||
isConstrainedMoving = false;
|
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[] = [];
|
cells: Cell[] = [];
|
||||||
|
|
||||||
|
@ -111,10 +111,6 @@ class Graph extends EventSource {
|
||||||
*/
|
*/
|
||||||
multiplicities: Multiplicity[] = [];
|
multiplicities: Multiplicity[] = [];
|
||||||
|
|
||||||
/*****************************************************************************
|
|
||||||
* Group: Variables
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds the {@link GraphDataModel} that contains the cells to be displayed.
|
* Holds the {@link GraphDataModel} that contains the cells to be displayed.
|
||||||
*/
|
*/
|
||||||
|
@ -407,6 +403,89 @@ class Graph extends EventSource {
|
||||||
containsValidationErrorsResource: string =
|
containsValidationErrorsResource: string =
|
||||||
Client.language != 'none' ? 'containsValidationErrors' : '';
|
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(
|
constructor(
|
||||||
container: HTMLElement,
|
container: HTMLElement,
|
||||||
model?: GraphDataModel,
|
model?: GraphDataModel,
|
||||||
|
@ -416,10 +495,10 @@ class Graph extends EventSource {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.container = container ?? document.createElement('div');
|
this.container = container ?? document.createElement('div');
|
||||||
this.model = model ?? new GraphDataModel();
|
this.model = model ?? this.createGraphDataModel();
|
||||||
this.plugins = plugins;
|
this.plugins = plugins;
|
||||||
this.cellRenderer = this.createCellRenderer();
|
this.cellRenderer = this.createCellRenderer();
|
||||||
this.setStylesheet(stylesheet != null ? stylesheet : this.createStylesheet());
|
this.setStylesheet(stylesheet ?? this.createStylesheet());
|
||||||
this.view = this.createGraphView();
|
this.view = this.createGraphView();
|
||||||
|
|
||||||
// Adds a graph model listener to update the view
|
// Adds a graph model listener to update the view
|
||||||
|
@ -437,7 +516,7 @@ class Graph extends EventSource {
|
||||||
// Set the selection model
|
// Set the selection model
|
||||||
this.setSelectionModel(this.createSelectionModel());
|
this.setSelectionModel(this.createSelectionModel());
|
||||||
|
|
||||||
// Initiailzes plugins
|
// Initializes plugins
|
||||||
this.plugins.forEach((p: GraphPluginConstructor) => {
|
this.plugins.forEach((p: GraphPluginConstructor) => {
|
||||||
this.pluginsMap[p.pluginId] = new p(this);
|
this.pluginsMap[p.pluginId] = new p(this);
|
||||||
});
|
});
|
||||||
|
@ -445,7 +524,6 @@ class Graph extends EventSource {
|
||||||
this.view.revalidate();
|
this.view.revalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
createSelectionModel = () => new GraphSelectionModel(this);
|
|
||||||
getContainer = () => this.container;
|
getContainer = () => this.container;
|
||||||
getPlugin = (id: string) => this.pluginsMap[id] as unknown;
|
getPlugin = (id: string) => this.pluginsMap[id] as unknown;
|
||||||
getCellRenderer = () => this.cellRenderer;
|
getCellRenderer = () => this.cellRenderer;
|
||||||
|
@ -489,27 +567,6 @@ class Graph extends EventSource {
|
||||||
this.getDataModel().batchUpdate(fn);
|
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.
|
* Returns the {@link GraphDataModel} that contains the cells.
|
||||||
*/
|
*/
|
||||||
|
@ -960,15 +1017,6 @@ class Graph extends EventSource {
|
||||||
return result;
|
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}.
|
* Hooks to create a new {@link EdgeHandler} for the given {@link CellState}.
|
||||||
*
|
*
|
||||||
|
@ -989,30 +1037,12 @@ class Graph extends EventSource {
|
||||||
) {
|
) {
|
||||||
result = this.createEdgeSegmentHandler(state);
|
result = this.createEdgeSegmentHandler(state);
|
||||||
} else {
|
} else {
|
||||||
result = new EdgeHandler(state);
|
result = this.createEdgeHandlerInstance(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result as EdgeHandler;
|
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
|
* Group: Drilldown
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
|
@ -56,7 +56,7 @@ import { Graph } from '../Graph';
|
||||||
* @param delay Optional delay between the animation steps. Passed to <Animation>.
|
* @param delay Optional delay between the animation steps. Passed to <Animation>.
|
||||||
*/
|
*/
|
||||||
class Morphing extends 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);
|
super(delay);
|
||||||
this.graph = graph;
|
this.graph = graph;
|
||||||
this.steps = steps;
|
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