1000 lines
29 KiB
JavaScript
1000 lines
29 KiB
JavaScript
/*
|
|
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({});
|