started refactoring+reorganising core to not have mx prefix, and breaking up mxGraph into smaller classes for easier maintenance

development
mcyph 2021-06-06 23:04:44 +10:00
parent 8d16eafd80
commit f76a172cae
36 changed files with 2356 additions and 2237 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1817,7 +1817,7 @@ class GraphHandler {
(state.cell.isEdge() || state.cell.isVertex()) &&
this.graph.isCellDeletable(state.cell) &&
state.cell.getChildCount() === 0 &&
this.graph.isTransparentState(state)
state.isTransparentState()
);
}

View File

@ -15,7 +15,7 @@ import {
import mxCellHighlight from '../selection/mxCellHighlight';
import EventObject from '../event/EventObject';
import InternalEvent from '../event/InternalEvent';
import utils from '../../util/Utils';
import utils, { intersectsHotspot } from '../../util/Utils';
import graph from '../Graph';
import { ColorValue } from '../../types';
import CellState from './datatypes/CellState';
@ -343,8 +343,7 @@ class CellMarker extends EventSource {
* returns true, then the state is stored in <validState>. The return value
* of this method is used as the argument for <getMarkerColor>.
*/
// isValidState(state: mxCellState): boolean;
isValidState(state) {
isValidState(state: CellState): boolean {
return true;
}
@ -354,8 +353,7 @@ class CellMarker extends EventSource {
* Returns the valid- or invalidColor depending on the value of isValid.
* The given <mxCellState> is ignored by this implementation.
*/
// getMarkerColor(evt: Event, state: mxCellState, isValid: boolean): string;
getMarkerColor(evt, state, isValid) {
getMarkerColor(evt: Event, state: CellState, isValid: boolean): string {
return isValid ? this.validColor : this.invalidColor;
}
@ -365,8 +363,7 @@ class CellMarker extends EventSource {
* Uses <getCell>, <getStateToMark> and <intersects> to return the
* <mxCellState> for the given <mxMouseEvent>.
*/
// getState(me: mxMouseEvent): mxCellState;
getState(me) {
getState(me: InternalMouseEvent): CellState {
const view = this.graph.getView();
const cell = this.getCell(me);
const state = this.getStateToMark(view.getState(cell));
@ -380,8 +377,7 @@ class CellMarker extends EventSource {
* Returns the <mxCell> for the given event and cell. This returns the
* given cell.
*/
// getCell(me: mxMouseEvent): mxCell;
getCell(me) {
getCell(me: InternalMouseEvent): Cell {
return me.getCell();
}
@ -403,10 +399,9 @@ class CellMarker extends EventSource {
* This returns true if the <hotspot> is 0 or the coordinates are inside
* the hotspot for the given cell state.
*/
// intersects(state: mxCellState, me: mxMouseEvent): boolean;
intersects(state, me) {
intersects(state: CellState, me: InternalMouseEvent): boolean {
if (this.hotspotEnabled) {
return utils.intersectsHotspot(
return intersectsHotspot(
state,
me.getGraphX(),
me.getGraphY(),
@ -415,7 +410,6 @@ class CellMarker extends EventSource {
MAX_HOTSPOT_SIZE
);
}
return true;
}

View File

@ -47,7 +47,7 @@ import {
SHAPE_SWIMLANE,
SHAPE_TRIANGLE,
} from '../../util/Constants';
import utils, { convertPoint, getValue } from '../../util/Utils';
import utils, {convertPoint, equalPoints, getRotatedPoint, getValue, mod, toRadians} from '../../util/Utils';
import Rectangle from '../geometry/Rectangle';
import StencilRegistry from '../geometry/shape/node/StencilRegistry';
import InternalEvent from '../event/InternalEvent';
@ -234,9 +234,7 @@ class CellRenderer {
*/
createIndicatorShape(state: CellState) {
if (state.shape) {
state.shape.indicatorShape = this.getShape(
state.view.graph.getIndicatorShape(state)
);
state.shape.indicatorShape = this.getShape(state.getIndicatorShape());
}
}
@ -280,14 +278,12 @@ class CellRenderer {
if (shape) {
shape.apply(state);
shape.image = state.view.graph.getImage(state);
shape.indicatorColor = state.view.graph.getIndicatorColor(state);
shape.image = state.getImage();
shape.indicatorColor = state.getIndicatorColor();
shape.indicatorStrokeColor = state.style.indicatorStrokeColor;
shape.indicatorGradientColor = state.view.graph.getIndicatorGradientColor(
state
);
shape.indicatorGradientColor = state.getIndicatorGradientColor();
shape.indicatorDirection = state.style.indicatorDirection;
shape.indicatorImage = state.view.graph.getIndicatorImage(state);
shape.indicatorImage = state.getIndicatorImage();
this.postConfigureShape(state);
}
}
@ -300,8 +296,7 @@ class CellRenderer {
* This implementation resolves these keywords on the fill, stroke
* and gradient color keys.
*/
// postConfigureShape(state: mxCellState): void;
postConfigureShape(state: CellState) {
postConfigureShape(state: CellState): void {
if (state.shape != null) {
this.resolveColor(state, 'indicatorGradientColor', 'gradientColor');
this.resolveColor(state, 'indicatorColor', 'fillColor');
@ -317,8 +312,7 @@ class CellRenderer {
* Resolves special keywords 'inherit', 'indicated' and 'swimlane' and sets
* the respective color on the shape.
*/
// checkPlaceholderStyles(state: mxCellState): boolean;
checkPlaceholderStyles(state: CellState) {
checkPlaceholderStyles(state: CellState): boolean {
// LATER: Check if the color has actually changed
if (state.style != null) {
const values = ['inherit', 'swimlane', 'indicated'];
@ -339,8 +333,7 @@ class CellRenderer {
* Resolves special keywords 'inherit', 'indicated' and 'swimlane' and sets
* the respective color on the shape.
*/
// resolveColor(state: mxCellState, field: string, key: string): void;
resolveColor(state: CellState, field: string, key: string) {
resolveColor(state: CellState, field: string, key: string): void {
const shape = key === 'fontColor' ? state.text : state.shape;
if (shape != null) {
@ -365,7 +358,7 @@ class CellRenderer {
referenced = state.cell;
}
referenced = graph.getSwimlane(<Cell>referenced);
referenced = graph.swimlane.getSwimlane(<Cell>referenced);
key = graph.swimlaneIndicatorColorAttribute;
} else if (value === 'indicated' && state.shape != null) {
// @ts-ignore
@ -443,7 +436,7 @@ class CellRenderer {
value,
new Rectangle(),
state.style.align || ALIGN_CENTER,
graph.getVerticalAlign(state),
state.getVerticalAlign(),
state.style.fontColor,
state.style.fontFamily,
state.style.fontSize,
@ -500,7 +493,7 @@ class CellRenderer {
state.text.node,
(evt: InternalMouseEvent) => {
if (this.isLabelEvent(state, evt)) {
graph.fireMouseEvent(
graph.event.fireMouseEvent(
InternalEvent.MOUSE_DOWN,
new InternalMouseEvent(evt, state)
);
@ -511,7 +504,7 @@ class CellRenderer {
},
(evt: InternalMouseEvent) => {
if (this.isLabelEvent(state, evt)) {
graph.fireMouseEvent(
graph.event.fireMouseEvent(
InternalEvent.MOUSE_MOVE,
new InternalMouseEvent(evt, getState(evt))
);
@ -519,7 +512,7 @@ class CellRenderer {
},
(evt: InternalMouseEvent) => {
if (this.isLabelEvent(state, evt)) {
graph.fireMouseEvent(
graph.event.fireMouseEvent(
InternalEvent.MOUSE_UP,
new InternalMouseEvent(evt, getState(evt))
);
@ -529,10 +522,10 @@ class CellRenderer {
);
// Uses double click timeout in mxGraph for quirks mode
if (graph.nativeDblClickEnabled) {
if (graph.event.nativeDblClickEnabled) {
InternalEvent.addListener(state.text.node, 'dblclick', (evt: MouseEvent) => {
if (this.isLabelEvent(state, evt)) {
graph.dblClick(evt, state.cell);
graph.event.dblClick(evt, state.cell);
InternalEvent.consume(evt);
}
});
@ -549,8 +542,7 @@ class CellRenderer {
*
* state - <mxCellState> whose label should be initialized.
*/
// initializeLabel(state: mxCellState, shape: mxShape): void;
initializeLabel(state: CellState, shape: Shape) {
initializeLabel(state: CellState, shape: Shape): void {
if (mxClient.IS_SVG && mxClient.NO_FO && shape.dialect !== DIALECT_SVG) {
shape.init(state.view.graph.container);
} else {
@ -567,8 +559,7 @@ class CellRenderer {
*
* state - <mxCellState> for which the overlay should be created.
*/
// createCellOverlays(state: mxCellState): void;
createCellOverlays(state: CellState) {
createCellOverlays(state: CellState): void {
const { graph } = state.view;
const overlays = graph.getCellOverlays(state.cell);
let dict = null;
@ -623,7 +614,6 @@ class CellRenderer {
* state - <mxCellState> for which the overlay should be created.
* overlay - <mxImageShape> that represents the overlay.
*/
// initializeOverlay(state: mxCellState, overlay: mxImageShape): void;
initializeOverlay(state: CellState, overlay: ImageShape): void {
overlay.init(state.view.getOverlayPane());
}
@ -634,12 +624,11 @@ class CellRenderer {
* Installs the listeners for the given <mxCellState>, <mxCellOverlay> and
* <mxShape> that represents the overlay.
*/
// installCellOverlayListeners(state: mxCellState, overlay: mxCellOverlay, shape: mxShape): void;
installCellOverlayListeners(
state: CellState,
overlay: CellOverlay,
shape: Shape
) {
): void {
const { graph } = state.view;
InternalEvent.addListener(shape.node, 'click', (evt: Event) => {
@ -680,8 +669,7 @@ class CellRenderer {
*
* state - <mxCellState> for which the control should be created.
*/
// createControl(state: mxCellState): void;
createControl(state: CellState) {
createControl(state: CellState): void {
const { graph } = state.view;
const image = graph.getFoldingImage(state);
@ -714,7 +702,6 @@ class CellRenderer {
*
* state - <mxCellState> whose control click handler should be returned.
*/
// createControlClickHandler(state: mxCellState): void;
createControlClickHandler(state: CellState): Function {
const { graph } = state.view;
@ -784,20 +771,20 @@ class CellRenderer {
node,
(evt: Event) => {
first = new Point(getClientX(evt), getClientY(evt));
graph.fireMouseEvent(
graph.event.fireMouseEvent(
InternalEvent.MOUSE_DOWN,
new InternalMouseEvent(evt, state)
);
InternalEvent.consume(evt);
},
(evt: Event) => {
graph.fireMouseEvent(
graph.event.fireMouseEvent(
InternalEvent.MOUSE_MOVE,
new InternalMouseEvent(evt, state)
);
},
(evt: Event) => {
graph.fireMouseEvent(InternalEvent.MOUSE_UP, new InternalMouseEvent(evt, state));
graph.event.fireMouseEvent(InternalEvent.MOUSE_UP, new InternalMouseEvent(evt, state));
InternalEvent.consume(evt);
}
);
@ -839,8 +826,8 @@ class CellRenderer {
* state - <mxCellState> whose shape fired the event.
* evt - Mouse event which was fired.
*/
// isShapeEvent(state: mxCellState, evt: MouseEvent): boolean;
isShapeEvent(state: CellState, evt: InternalMouseEvent | MouseEvent) {
// isShapeEvent(state: mxCellState, evt: MouseEvent);
isShapeEvent(state: CellState, evt: InternalMouseEvent | MouseEvent): boolean {
return true;
}
@ -856,7 +843,7 @@ class CellRenderer {
* evt - Mouse event which was fired.
*/
// isLabelEvent(state: mxCellState, evt: MouseEvent): boolean;
isLabelEvent(state: CellState, evt: InternalMouseEvent | MouseEvent) {
isLabelEvent(state: CellState, evt: InternalMouseEvent | MouseEvent): boolean {
return true;
}
@ -996,7 +983,7 @@ class CellRenderer {
state.text.apply(state);
// Special case where value is obtained via hook in graph
state.text.valign = <string>graph.getVerticalAlign(state);
state.text.valign = <string>state.getVerticalAlign();
}
const bounds = this.getLabelBounds(state);
@ -1284,8 +1271,7 @@ class CellRenderer {
*
* state - <mxCellState> whose overlays should be redrawn.
*/
// redrawCellOverlays(state: mxCellState, forced?: boolean): void;
redrawCellOverlays(state: CellState, forced: boolean = false) {
redrawCellOverlays(state: CellState, forced: boolean = false): void {
this.createCellOverlays(state);
if (state.overlays != null) {
@ -1540,7 +1526,6 @@ class CellRenderer {
*
* state - <mxCellState> whose shapes should be returned.
*/
// getShapesForState(state: mxCellState): mxShape[];
getShapesForState(
state: CellState
): [Shape | null, mxText | null, Shape | null] {
@ -1563,7 +1548,6 @@ class CellRenderer {
* be drawn into the DOM. If this is false then redraw and/or reconfigure
* will not be called on the shape.
*/
// redraw(state: mxCellState, force?: boolean, rendering?: boolean): void;
redraw(
state: CellState,
force: boolean = false,
@ -1587,7 +1571,6 @@ class CellRenderer {
*
* state - <mxCellState> whose label should be redrawn.
*/
// redrawShape(state: mxCellState, force?: boolean, rendering?: boolean): void;
redrawShape(
state: CellState,
force: boolean = false,
@ -1645,7 +1628,7 @@ class CellRenderer {
if (
state.shape != null &&
state.shape.indicatorShape !=
this.getShape(<string>state.view.graph.getIndicatorShape(state))
this.getShape(state.getIndicatorShape())
) {
if (state.shape.indicator != null) {
state.shape.indicator.destroy();
@ -1702,8 +1685,7 @@ class CellRenderer {
*
* Invokes redraw on the shape of the given state.
*/
// doRedrawShape(state: mxCellState): void;
doRedrawShape(state: CellState) {
doRedrawShape(state: CellState): void {
state.shape?.redraw();
}
@ -1732,8 +1714,7 @@ class CellRenderer {
*
* state - <mxCellState> for which the shapes should be destroyed.
*/
// destroy(state: mxCellState): void;
destroy(state: CellState) {
destroy(state: CellState): void {
if (state.shape != null) {
if (state.text != null) {
state.text.destroy();

View File

@ -33,7 +33,7 @@ import InternalMouseEvent from "../event/InternalMouseEvent";
import Graph from "../Graph";
import CellState from "./datatypes/CellState";
class Cells {
class GraphCells {
constructor(graph: Graph) {
this.graph = graph;
}
@ -41,6 +41,68 @@ class Cells {
graph: Graph;
/**
* Specifies the return value for {@link isCellsResizable}.
* @default true
*/
cellsResizable: boolean = true;
/**
* Specifies the return value for {@link isCellsBendable}.
* @default true
*/
cellsBendable: boolean = true;
/**
* Specifies the return value for {@link isCellsSelectable}.
* @default true
*/
cellsSelectable: boolean = true;
/**
* Specifies the return value for {@link isCellsDisconnectable}.
* @default true
*/
cellsDisconnectable: boolean = true;
/**
* Specifies if the graph should automatically update the cell size after an
* edit. This is used in {@link isAutoSizeCell}.
* @default false
*/
autoSizeCells: boolean = false;
/**
* Specifies if autoSize style should be applied when cells are added.
* @default false
*/
autoSizeCellsOnAdd: boolean = false;
/**
* Specifies the return value for {@link isCellLocked}.
* @default false
*/
cellsLocked: boolean = false;
/**
* Specifies the return value for {@link isCellCloneable}.
* @default true
*/
cellsCloneable: boolean = true;
/**
* Specifies the return value for {@link isCellDeletable}.
* @default true
*/
cellsDeletable: boolean = true;
/**
* Specifies the return value for {@link isCellMovable}.
* @default true
*/
cellsMovable: boolean = true;
/**
* Returns the bounding box for the given array of {@link Cell}. The bounding box for
* each cell and its descendants is computed using {@link view.getBoundingBox}.
@ -1235,7 +1297,7 @@ class Cells {
geo.x += Math.round((geo.width - size.width) / 2);
}
const valign = this.getVerticalAlign(state);
const valign = state.getVerticalAlign();
if (valign === ALIGN_BOTTOM) {
geo.y += geo.height - size.height;
@ -1311,7 +1373,7 @@ class Cells {
let dy = 0;
// Adds dimension of image if shape is a label
if (this.getImage(state) != null || style.image != null) {
if (state.getImage() != null || style.image != null) {
if (style.shape === SHAPE_LABEL) {
if (style.verticalAlign === ALIGN_MIDDLE) {
dx +=
@ -2061,14 +2123,6 @@ class Cells {
return null;
}
/**
* Returns the bounds inside which the diagram should be kept as an
* {@link Rectangle}.
*/
getMaximumGraphBounds(): Rectangle | null {
return this.maximumGraphBounds;
}
/**
* Keeps the given cell inside the bounds returned by
* {@link getCellContainmentArea} for its parent, according to the rules defined by
@ -2199,9 +2253,6 @@ class Cells {
}
}
/*****************************************************************************
* Group: Cell retrieval
*****************************************************************************/
@ -2493,7 +2544,6 @@ class Cells {
return false;
}
/**
* Returns whether or not the specified parent is a valid
* ancestor of the specified cell, either direct or indirectly
@ -2800,6 +2850,191 @@ class Cells {
setCellsResizable(value: boolean): void {
this.cellsResizable = value;
}
/**
* Returns true if the given cell is bendable. This returns {@link cellsBendable}
* for all given cells if {@link isLocked} does not return true for the given
* cell and its style does not specify {@link mxConstants.STYLE_BENDABLE} to be 0.
*
* @param cell {@link mxCell} whose bendable state should be returned.
*/
isCellBendable(cell: Cell): boolean {
const style = this.getCurrentCellStyle(cell);
return (
this.isCellsBendable() &&
!this.isCellLocked(cell) &&
style.bendable !== 0
);
}
export default Cells;
/**
* Returns {@link cellsBenadable}.
*/
isCellsBendable(): boolean {
return this.cellsBendable;
}
/**
* Specifies if the graph should allow bending of edges. This
* implementation updates {@link bendable}.
*
* @param value Boolean indicating if the graph should allow bending of
* edges.
*/
setCellsBendable(value: boolean): void {
this.cellsBendable = value;
}
/**
* Returns true if the size of the given cell should automatically be
* updated after a change of the label. This implementation returns
* {@link autoSizeCells} or checks if the cell style does specify
* {@link 'autoSize'} to be 1.
*
* @param cell {@link mxCell} that should be resized.
*/
isAutoSizeCell(cell: Cell): boolean {
const style = this.getCurrentCellStyle(cell);
return this.isAutoSizeCells() || style.autosize == 1;
}
/**
* Returns {@link autoSizeCells}.
*/
isAutoSizeCells(): boolean {
return this.autoSizeCells;
}
/**
* Specifies if cell sizes should be automatically updated after a label
* change. This implementation sets {@link autoSizeCells} to the given parameter.
* To update the size of cells when the cells are added, set
* {@link autoSizeCellsOnAdd} to true.
*
* @param value Boolean indicating if cells should be resized
* automatically.
*/
setAutoSizeCells(value: boolean): void {
this.autoSizeCells = value;
}
/**
* Returns true if the parent of the given cell should be extended if the
* child has been resized so that it overlaps the parent. This
* implementation returns {@link isExtendParents} if the cell is not an edge.
*
* @param cell {@link mxCell} that has been resized.
*/
isExtendParent(cell: Cell): boolean {
return !cell.isEdge() && this.isExtendParents();
}
/**
* Returns {@link extendParents}.
*/
isExtendParents(): boolean {
return this.extendParents;
}
/**
* Sets {@link extendParents}.
*
* @param value New boolean value for {@link extendParents}.
*/
setExtendParents(value: boolean): void {
this.extendParents = value;
}
/**
* Returns {@link extendParentsOnAdd}.
*/
isExtendParentsOnAdd(cell: Cell): boolean {
return this.extendParentsOnAdd;
}
/**
* Sets {@link extendParentsOnAdd}.
*
* @param value New boolean value for {@link extendParentsOnAdd}.
*/
setExtendParentsOnAdd(value: boolean): void {
this.extendParentsOnAdd = value;
}
/**
* Returns {@link extendParentsOnMove}.
*/
isExtendParentsOnMove(): boolean {
return this.extendParentsOnMove;
}
/**
* Sets {@link extendParentsOnMove}.
*
* @param value New boolean value for {@link extendParentsOnAdd}.
*/
setExtendParentsOnMove(value: boolean): void {
this.extendParentsOnMove = value;
}
/*****************************************************************************
* Group: Graph appearance
*****************************************************************************/
/**
* Returns the cursor value to be used for the CSS of the shape for the
* given cell. This implementation returns null.
*
* @param cell {@link mxCell} whose cursor should be returned.
*/
getCursorForCell(cell: Cell): string | null {
return null;
}
/*****************************************************************************
* Group: Graph display
*****************************************************************************/
/**
* Returns the scaled, translated bounds for the given cell. See
* {@link GraphView.getBounds} for arrays.
*
* @param cell {@link mxCell} whose bounds should be returned.
* @param includeEdges Optional boolean that specifies if the bounds of
* the connected edges should be included. Default is `false`.
* @param includeDescendants Optional boolean that specifies if the bounds
* of all descendants should be included. Default is `false`.
*/
getCellBounds(
cell: Cell,
includeEdges: boolean = false,
includeDescendants: boolean = false
): Rectangle | null {
let cells = new CellArray(cell);
// Includes all connected edges
if (includeEdges) {
cells = cells.concat(<CellArray>cell.getEdges());
}
let result = this.view.getBounds(cells);
// Recursively includes the bounds of the children
if (includeDescendants) {
for (const child of cell.getChildren()) {
const tmp = this.getCellBounds(child, includeEdges, true);
if (result != null) {
result.add(tmp);
} else {
result = tmp;
}
}
}
return result;
}
}
export default GraphCells;

View File

@ -600,7 +600,7 @@ class Cell {
* @param defaultValueOptional default value to use if the attribute has no
* value.
*/
getAttribute(name: string, defaultValue: any): any {
getAttribute(name: string, defaultValue?: any): any {
const userObject = this.getValue();
const val =
isNotNullish(userObject) && userObject.nodeType === NODETYPE_ELEMENT

View File

@ -14,6 +14,9 @@ import mxText from '../../geometry/shape/mxText';
import mxDictionary from '../../../util/mxDictionary';
import type { CellStateStyles } from '../../../types';
import Image from "../../image/Image";
import {ALIGN_MIDDLE, NONE} from "../../../util/Constants";
import {getValue} from "../../../util/Utils";
/**
* Class: mxCellState
@ -454,6 +457,104 @@ class CellState extends Rectangle {
const trg = this.getVisibleTerminalState(false);
return src && src === trg;
}
/*****************************************************************************
* Group: Graph appearance
*****************************************************************************/
/**
* Returns the vertical alignment for the given cell state. This
* implementation returns the value stored under
* {@link 'verticalAlign'} in the cell style.
*
* @param state {@link mxCellState} whose vertical alignment should be
* returned.
*/
getVerticalAlign(): string | null {
return this.style != null
? this.style.verticalAlign || ALIGN_MIDDLE
: null;
}
/**
* Returns true if the given state has no stroke- or fillcolor and no image.
*
* @param state {@link mxCellState} to check.
*/
isTransparentState(): boolean {
let result = false;
const stroke = getValue(this.style, 'strokeColor', NONE);
const fill = getValue(this.style, 'fillColor', NONE);
result = stroke === NONE && fill === NONE && this.getImage(state) == null;
return result;
}
/**
* Returns the image URL for the given cell state. This implementation
* returns the value stored under {@link 'image'} in the cell
* style.
*
* @param state {@link mxCellState} whose image URL should be returned.
*/
getImage(): Image | null {
return this.style != null
? this.style.image
: null;
}
/**
* Returns the indicator color for the given cell state. This
* implementation returns the value stored under
* {@link mxConstants.STYLE_INDICATOR_COLOR} in the cell style.
*
* @param state {@link mxCellState} whose indicator color should be
* returned.
*/
getIndicatorColor(): string | null {
return this.style != null
? this.style.indicatorColor
: null;
}
/**
* Returns the indicator gradient color for the given cell state. This
* implementation returns the value stored under
* {@link mxConstants.STYLE_INDICATOR_GRADIENTCOLOR} in the cell style.
*
* @param state {@link mxCellState} whose indicator gradient color should be
* returned.
*/
getIndicatorGradientColor(): string | null {
return this.style != null
? this.style.gradientColor
: null;
}
/**
* Returns the indicator shape for the given cell state. This
* implementation returns the value stored under
* {@link mxConstants.STYLE_INDICATOR_SHAPE} in the cell style.
*
* @param state {@link mxCellState} whose indicator shape should be returned.
*/
getIndicatorShape(): string | null {
return this.style != null
? this.style.indicatorShape
: null;
}
/**
* Returns the indicator image for the given cell state. This
* implementation returns the value stored under
* {@link mxConstants.STYLE_INDICATOR_IMAGE} in the cell style.
*
* @param state {@link mxCellState} whose indicator image should be returned.
*/
getIndicatorImage(): Image | null {
return this.style != null
? this.style.indicatorImage
: null;
}
}
export default CellState;

View File

@ -17,6 +17,66 @@ class Edge {
graph: Graph;
/**
* Specifies if edge control points should be reset after the resize of a
* connected cell.
* @default false
*/
resetEdgesOnResize: boolean = false;
/**
* Specifies if edge control points should be reset after the move of a
* connected cell.
* @default false
*/
resetEdgesOnMove: boolean = false;
/**
* Specifies if edge control points should be reset after the the edge has been
* reconnected.
* @default true
*/
resetEdgesOnConnect: boolean = true;
/**
* Specifies if edges are connectable. This overrides the connectable field in edges.
* @default false
*/
connectableEdges: boolean = false;
/**
* Specifies if edges with disconnected terminals are allowed in the graph.
* @default true
*/
allowDanglingEdges: boolean = true;
/**
* Specifies if edges that are cloned should be validated and only inserted
* if they are valid.
* @default true
*/
cloneInvalidEdges: boolean = false;
/**
* Specifies if edges should be disconnected from their terminals when they
* are moved.
* @default true
*/
disconnectOnMove: boolean = true;
/**
* Specifies the alternate edge style to be used if the main control point
* on an edge is being double clicked.
* @default null
*/
alternateEdgeStyle: string | null = null;
/**
* Specifies the return value for edges in {@link isLabelMovable}.
* @default true
*/
edgeLabelsMovable: boolean = true;
/*****************************************************************************
* Group: Cell alignment and orientation
*****************************************************************************/

View File

@ -10,6 +10,12 @@ class Vertex {
*/
vertexLabelsMovable: boolean = false;
/**
* Specifies if negative coordinates for vertices are allowed.
* @default true
*/
allowNegativeCoordinates: boolean = true;
/**
* Function: insertVertex
*

View File

@ -12,8 +12,9 @@ import InternalEvent from "../event/InternalEvent";
import mxDictionary from "../../util/mxDictionary";
import Geometry from "../geometry/Geometry";
import Graph from "../Graph";
import mxConnectionHandler from "./mxConnectionHandler";
class Connections {
class GraphConnections {
constructor(graph: Graph) {
this.graph = graph;
}
@ -580,9 +581,6 @@ class Connections {
}
}
/**
* Returns all visible edges connected to the given cell without loops.
*
@ -595,11 +593,6 @@ class Connections {
return this.getEdges(cell, parent, true, true, false);
}
/**
* Returns true if the given cell should be kept inside the bounds of its
* parent according to the rules defined by {@link getOverlap} and
@ -643,6 +636,117 @@ class Connections {
setConstrainRelativeChildren(value: boolean): void {
this.constrainRelativeChildren = value;
}
/*****************************************************************************
* Group: Graph behaviour
*****************************************************************************/
/**
* Returns {@link disconnectOnMove} as a boolean.
*/
isDisconnectOnMove(): boolean {
return this.disconnectOnMove;
}
export default Connections;
/**
* Specifies if edges should be disconnected when moved. (Note: Cloned
* edges are always disconnected.)
*
* @param value Boolean indicating if edges should be disconnected
* when moved.
*/
setDisconnectOnMove(value: boolean): void {
this.disconnectOnMove = value;
}
/**
* Returns true if the given cell is disconnectable from the source or
* target terminal. This returns {@link isCellsDisconnectable} for all given
* cells if {@link isCellLocked} does not return true for the given cell.
*
* @param cell {@link mxCell} whose disconnectable state should be returned.
* @param terminal {@link mxCell} that represents the source or target terminal.
* @param source Boolean indicating if the source or target terminal is to be
* disconnected.
*/
isCellDisconnectable(
cell: Cell,
terminal: Cell | null = null,
source: boolean = false
): boolean {
return this.isCellsDisconnectable() && !this.isCellLocked(cell);
}
/**
* Returns {@link cellsDisconnectable}.
*/
isCellsDisconnectable(): boolean {
return this.cellsDisconnectable;
}
/**
* Sets {@link cellsDisconnectable}.
*/
setCellsDisconnectable(value: boolean): void {
this.cellsDisconnectable = value;
}
/**
* Returns true if the given cell is a valid source for new connections.
* This implementation returns true for all non-null values and is
* called by is called by {@link isValidConnection}.
*
* @param cell {@link mxCell} that represents a possible source or null.
*/
isValidSource(cell: Cell): boolean {
return (
(cell == null && this.allowDanglingEdges) ||
(cell != null &&
(!cell.isEdge() || this.connectableEdges) &&
cell.isConnectable())
);
}
/**
* Returns {@link isValidSource} for the given cell. This is called by
* {@link isValidConnection}.
*
* @param cell {@link mxCell} that represents a possible target or null.
*/
isValidTarget(cell: Cell): boolean {
return this.isValidSource(cell);
}
/**
* Returns true if the given target cell is a valid target for source.
* This is a boolean implementation for not allowing connections between
* certain pairs of vertices and is called by {@link getEdgeValidationError}.
* This implementation returns true if {@link isValidSource} returns true for
* the source and {@link isValidTarget} returns true for the target.
*
* @param source {@link mxCell} that represents the source cell.
* @param target {@link mxCell} that represents the target cell.
*/
isValidConnection(source: Cell, target: Cell): boolean {
return this.isValidSource(source) && this.isValidTarget(target);
}
/**
* Specifies if the graph should allow new connections. This implementation
* updates {@link mxConnectionHandler.enabled} in {@link connectionHandler}.
*
* @param connectable Boolean indicating if new connections should be allowed.
*/
setConnectable(connectable: boolean): void {
(<mxConnectionHandler>this.connectionHandler).setEnabled(connectable);
}
/**
* Returns true if the {@link connectionHandler} is enabled.
*/
isConnectable(): boolean {
return (<mxConnectionHandler>this.connectionHandler).isEnabled();
}
}
export default GraphConnections;

View File

@ -1,4 +1,18 @@
class GraphDragDrop {
/**
* Specifies the return value for {@link isDropEnabled}.
* @default false
*/
dropEnabled: boolean = false;
/**
* Specifies if dropping onto edges should be enabled. This is ignored if
* {@link dropEnabled} is `false`. If enabled, it will call {@link splitEdge} to carry
* out the drop operation.
* @default true
*/
splitEnabled: boolean = true;
/**
* Specifies if the graph should automatically scroll if the mouse goes near
* the container edge while dragging. This is only taken into account if the
@ -18,6 +32,29 @@ class GraphDragDrop {
* @default true
*/
autoExtend: boolean = true;
/*****************************************************************************
* Group: Graph behaviour
*****************************************************************************/
/**
* Returns {@link dropEnabled} as a boolean.
*/
isDropEnabled(): boolean {
return this.dropEnabled;
}
/**
* Specifies if the graph should allow dropping of cells onto or into other
* cells.
*
* @param dropEnabled Boolean indicating if the graph should allow dropping
* of cells into other cells.
*/
setDropEnabled(value: boolean): void {
this.dropEnabled = value;
}
}
export default GraphDragDrop;

View File

@ -13,6 +13,12 @@ class InPlaceEditing {
graph: Graph;
/**
* Specifies the return value for {@link isCellEditable}.
* @default true
*/
cellsEditable: boolean = true;
/*****************************************************************************
* Group: Cell in-place editing
*****************************************************************************/
@ -158,6 +164,88 @@ class InPlaceEditing {
}
});
}
/*****************************************************************************
* Group: Graph behaviour
*****************************************************************************/
/**
* Returns true if the given cell is currently being edited.
* If no cell is specified then this returns true if any
* cell is currently being edited.
*
* @param cell {@link mxCell} that should be checked.
*/
isEditing(cell: Cell | null = null): boolean {
if (this.cellEditor != null) {
const editingCell = this.cellEditor.getEditingCell();
return cell == null ? editingCell != null : cell === editingCell;
}
return false;
}
/**
* Returns {@link invokesStopCellEditing}.
*/
isInvokesStopCellEditing(): boolean {
return this.invokesStopCellEditing;
}
/**
* Sets {@link invokesStopCellEditing}.
*/
setInvokesStopCellEditing(value: boolean): void {
this.invokesStopCellEditing = value;
}
/**
* Returns {@link enterStopsCellEditing}.
*/
isEnterStopsCellEditing(): boolean {
return this.enterStopsCellEditing;
}
/**
* Sets {@link enterStopsCellEditing}.
*/
setEnterStopsCellEditing(value: boolean): void {
this.enterStopsCellEditing = value;
}
/**
* Returns true if the given cell is editable. This returns {@link cellsEditable} for
* all given cells if {@link isCellLocked} does not return true for the given cell
* and its style does not specify {@link 'editable'} to be 0.
*
* @param cell {@link mxCell} whose editable state should be returned.
*/
isCellEditable(cell: Cell): boolean {
const style = this.getCurrentCellStyle(cell);
return (
this.isCellsEditable() &&
!this.isCellLocked(cell) &&
style.editable != 0
);
}
/**
* Returns {@link cellsEditable}.
*/
isCellsEditable(): boolean {
return this.cellsEditable;
}
/**
* Specifies if the graph should allow in-place editing for cell labels.
* This implementation updates {@link cellsEditable}.
*
* @param value Boolean indicating if the graph should allow in-place
* editing.
*/
setCellsEditable(value: boolean): void {
this.cellsEditable = value;
}
}
export default InPlaceEditing;

View File

@ -16,7 +16,7 @@ import PanningHandler from "../panning/PanningHandler";
import mxConnectionHandler from "../connection/mxConnectionHandler";
import Point from "../geometry/Point";
import {convertPoint, getValue} from "../../util/Utils";
import {NONE} from "../../util/Constants";
import {NONE, SHAPE_SWIMLANE} from "../../util/Constants";
import mxClient from "../../mxClient";
import EventSource from "./EventSource";
import CellEditor from "../editing/CellEditor";
@ -1091,6 +1091,40 @@ class GraphEvents {
p.y = this.snap(p.y / s - tr.y - off);
return p;
}
/*****************************************************************************
* Group: Graph behaviour
*****************************************************************************/
/**
* Returns {@link escapeEnabled}.
*/
isEscapeEnabled(): boolean {
return this.escapeEnabled;
}
/**
* Sets {@link escapeEnabled}.
*
* @param enabled Boolean indicating if escape should be enabled.
*/
setEscapeEnabled(value: boolean): void {
this.escapeEnabled = value;
}
/*****************************************************************************
* Group: Graph appearance
*****************************************************************************/
/**
* Returns the cursor value to be used for the CSS of the shape for the
* given event. This implementation calls {@link getCursorForCell}.
*
* @param me {@link mxMouseEvent} whose cursor should be returned.
*/
getCursorForMouseEvent(me: InternalMouseEvent): string | null {
return this.getCursorForCell(me.getCell());
}
}
export default GraphEvents;

View File

@ -47,6 +47,15 @@ class GraphFolding {
graph: Graph;
options: GraphFoldingOptions;
/**
* Specifies the resource key for the tooltip on the collapse/expand icon.
* If the resource for this key does not exist then the value is used as
* the tooltip.
* @default 'collapse-expand'
*/
collapseExpandResource: string =
mxClient.language != 'none' ? 'collapse-expand' : '';
/**
*
* @default true

View File

@ -385,6 +385,64 @@ class GraphGrouping {
});
return cells;
}
/*****************************************************************************
* Group: Drilldown
*****************************************************************************/
/**
* Uses the given cell as the root of the displayed cell hierarchy. If no
* cell is specified then the selection cell is used. The cell is only used
* if {@link isValidRoot} returns true.
*
* @param cell Optional {@link Cell} to be used as the new root. Default is the
* selection cell.
*/
enterGroup(cell: Cell): void {
cell = cell || this.getSelectionCell();
if (cell != null && this.isValidRoot(cell)) {
this.view.setCurrentRoot(cell);
this.clearSelection();
}
}
/**
* Changes the current root to the next valid root in the displayed cell
* hierarchy.
*/
exitGroup(): void {
const root = this.getModel().getRoot();
const current = this.getCurrentRoot();
if (current != null) {
let next = <Cell>current.getParent();
// Finds the next valid root in the hierarchy
while (
next !== root &&
!this.isValidRoot(next) &&
next.getParent() !== root
) {
next = <Cell>next.getParent();
}
// Clears the current root if the new root is
// the model's root or one of the layers.
if (next === root || next.getParent() === root) {
this.view.setCurrentRoot(null);
} else {
this.view.setCurrentRoot(next);
}
const state = this.view.getState(current);
// Selects the previous root in the graph
if (state != null) {
this.selection.setSelectionCell(current);
}
}
}
}
export default GraphGrouping;

View File

@ -0,0 +1,163 @@
import Cell from "../cell/datatypes/Cell";
import {getValue} from "../../util/Utils";
class GraphLabel {
/**
* Returns a string or DOM node that represents the label for the given
* cell. This implementation uses {@link convertValueToString} if {@link labelsVisible}
* is true. Otherwise it returns an empty string.
*
* To truncate a label to match the size of the cell, the following code
* can be used.
*
* ```javascript
* graph.getLabel = function(cell)
* {
* var label = getLabel.apply(this, arguments);
*
* if (label != null && this.model.isVertex(cell))
* {
* var geo = cell.getCellGeometry();
*
* if (geo != null)
* {
* var max = parseInt(geo.width / 8);
*
* if (label.length > max)
* {
* label = label.substring(0, max)+'...';
* }
* }
* }
* return mxUtils.htmlEntities(label);
* }
* ```
*
* A resize listener is needed in the graph to force a repaint of the label
* after a resize.
*
* ```javascript
* graph.addListener(mxEvent.RESIZE_CELLS, function(sender, evt)
* {
* var cells = evt.getProperty('cells');
*
* for (var i = 0; i < cells.length; i++)
* {
* this.view.removeState(cells[i]);
* }
* });
* ```
*
* @param cell {@link mxCell} whose label should be returned.
*/
getLabel(cell: Cell): string | Node | null {
let result: string | null = '';
if (this.labelsVisible && cell != null) {
const style = this.getCurrentCellStyle(cell);
if (!getValue(style, 'noLabel', false)) {
result = this.convertValueToString(cell);
}
}
return result;
}
/**
* Returns true if the label must be rendered as HTML markup. The default
* implementation returns {@link htmlLabels}.
*
* @param cell {@link mxCell} whose label should be displayed as HTML markup.
*/
isHtmlLabel(cell: Cell): boolean {
return this.isHtmlLabels();
}
/**
* Returns {@link htmlLabels}.
*/
isHtmlLabels(): boolean {
return this.htmlLabels;
}
/**
* Sets {@link htmlLabels}.
*/
setHtmlLabels(value: boolean): void {
this.htmlLabels = value;
}
/**
* This enables wrapping for HTML labels.
*
* Returns true if no white-space CSS style directive should be used for
* displaying the given cells label. This implementation returns true if
* {@link 'whiteSpace'} in the style of the given cell is 'wrap'.
*
* This is used as a workaround for IE ignoring the white-space directive
* of child elements if the directive appears in a parent element. It
* should be overridden to return true if a white-space directive is used
* in the HTML markup that represents the given cells label. In order for
* HTML markup to work in labels, {@link isHtmlLabel} must also return true
* for the given cell.
*
* @example
*
* ```javascript
* graph.getLabel = function(cell)
* {
* var tmp = getLabel.apply(this, arguments); // "supercall"
*
* if (this.model.isEdge(cell))
* {
* tmp = '<div style="width: 150px; white-space:normal;">'+tmp+'</div>';
* }
*
* return tmp;
* }
*
* graph.isWrapping = function(state)
* {
* return this.model.isEdge(state.cell);
* }
* ```
*
* Makes sure no edge label is wider than 150 pixels, otherwise the content
* is wrapped. Note: No width must be specified for wrapped vertex labels as
* the vertex defines the width in its geometry.
*
* @param state {@link mxCell} whose label should be wrapped.
*/
isWrapping(cell: Cell): boolean {
return this.getCurrentCellStyle(cell).whiteSpace === 'wrap';
}
/**
* Returns true if the overflow portion of labels should be hidden. If this
* returns true then vertex labels will be clipped to the size of the vertices.
* This implementation returns true if `overflow` in the
* style of the given cell is 'hidden'.
*
* @param state {@link mxCell} whose label should be clipped.
*/
isLabelClipped(cell: Cell): boolean {
return this.getCurrentCellStyle(cell).overflow === 'hidden';
}
/**
* Returns true if the given edges's label is moveable. This returns
* {@link movable} for all given cells if {@link isLocked} does not return true
* for the given cell.
*
* @param cell {@link mxCell} whose label should be moved.
*/
isLabelMovable(cell: Cell): boolean {
return (
!this.isCellLocked(cell) &&
((cell.isEdge() && this.edgeLabelsMovable) ||
(cell.isVertex() && this.vertexLabelsMovable))
);
}
}
export default GraphLabel;

View File

@ -0,0 +1,119 @@
import Rectangle from "../geometry/Rectangle";
import Point from "../geometry/Point";
import mxPolyline from "../geometry/shape/edge/mxPolyline";
class GraphPageBreaks {
horizontalPageBreaks: any[] | null = null;
verticalPageBreaks: any[] | null = null;
/**
* Invokes from {@link sizeDidChange} to redraw the page breaks.
*
* @param visible Boolean that specifies if page breaks should be shown.
* @param width Specifies the width of the container in pixels.
* @param height Specifies the height of the container in pixels.
*/
updatePageBreaks(visible: boolean, width: number, height: number): void {
const { scale } = this.view;
const tr = this.getView().translate;
const fmt = this.pageFormat;
const ps = scale * this.pageScale;
const bounds = new Rectangle(0, 0, fmt.width * ps, fmt.height * ps);
const gb = Rectangle.fromRectangle(this.getGraphBounds());
gb.width = Math.max(1, gb.width);
gb.height = Math.max(1, gb.height);
bounds.x =
Math.floor((gb.x - tr.x * scale) / bounds.width) * bounds.width +
tr.x * scale;
bounds.y =
Math.floor((gb.y - tr.y * scale) / bounds.height) * bounds.height +
tr.y * scale;
gb.width =
Math.ceil((gb.width + (gb.x - bounds.x)) / bounds.width) * bounds.width;
gb.height =
Math.ceil((gb.height + (gb.y - bounds.y)) / bounds.height) *
bounds.height;
// Does not show page breaks if the scale is too small
visible =
visible && Math.min(bounds.width, bounds.height) > this.minPageBreakDist;
const horizontalCount = visible
? Math.ceil(gb.height / bounds.height) + 1
: 0;
const verticalCount = visible ? Math.ceil(gb.width / bounds.width) + 1 : 0;
const right = (verticalCount - 1) * bounds.width;
const bottom = (horizontalCount - 1) * bounds.height;
if (this.horizontalPageBreaks == null && horizontalCount > 0) {
this.horizontalPageBreaks = [];
}
if (this.verticalPageBreaks == null && verticalCount > 0) {
this.verticalPageBreaks = [];
}
const drawPageBreaks = (breaks: any) => {
if (breaks != null) {
const count =
breaks === this.horizontalPageBreaks
? horizontalCount
: verticalCount;
for (let i = 0; i <= count; i += 1) {
const pts =
breaks === this.horizontalPageBreaks
? [
new Point(
Math.round(bounds.x),
Math.round(bounds.y + i * bounds.height)
),
new Point(
Math.round(bounds.x + right),
Math.round(bounds.y + i * bounds.height)
),
]
: [
new Point(
Math.round(bounds.x + i * bounds.width),
Math.round(bounds.y)
),
new Point(
Math.round(bounds.x + i * bounds.width),
Math.round(bounds.y + bottom)
),
];
if (breaks[i] != null) {
breaks[i].points = pts;
breaks[i].redraw();
} else {
const pageBreak = new mxPolyline(pts, this.pageBreakColor);
pageBreak.dialect = this.dialect;
pageBreak.pointerEvents = false;
pageBreak.isDashed = this.pageBreakDashed;
pageBreak.init(this.getView().backgroundPane);
pageBreak.redraw();
breaks[i] = pageBreak;
}
}
for (let i = count; i < breaks.length; i += 1) {
breaks[i].destroy();
}
breaks.splice(count, breaks.length - count);
}
};
drawPageBreaks(this.horizontalPageBreaks);
drawPageBreaks(this.verticalPageBreaks);
}
}
export default GraphPageBreaks;

View File

@ -334,6 +334,21 @@ class GraphPanning {
return isChanged;
}
/*****************************************************************************
* Group: Graph behaviour
*****************************************************************************/
/**
* Specifies if panning should be enabled. This implementation updates
* {@link PanningHandler.panningEnabled} in {@link panningHandler}.
*
* @param enabled Boolean indicating if panning should be enabled.
*/
setPanning(enabled: boolean): void {
(<PanningHandler>this.panningHandler).panningEnabled = enabled;
}
}
export default GraphPanning;

View File

@ -0,0 +1,83 @@
import Cell from "../cell/datatypes/Cell";
class GraphPorts {
/*****************************************************************************
* Group: Drilldown
*****************************************************************************/
/**
* Returns true if the given cell is a "port", that is, when connecting to
* it, the cell returned by getTerminalForPort should be used as the
* terminal and the port should be referenced by the ID in either the
* mxConstants.STYLE_SOURCE_PORT or the or the
* mxConstants.STYLE_TARGET_PORT. Note that a port should not be movable.
* This implementation always returns false.
*
* A typical implementation is the following:
*
* ```javascript
* graph.isPort = function(cell)
* {
* var geo = cell.getGeometry();
*
* return (geo != null) ? geo.relative : false;
* };
* ```
*
* @param cell {@link mxCell} that represents the port.
*/
isPort(cell: Cell): boolean {
return false;
}
/**
* Returns the terminal to be used for a given port. This implementation
* always returns the parent cell.
*
* @param cell {@link mxCell} that represents the port.
* @param source If the cell is the source or target port.
*/
getTerminalForPort(cell: Cell, source: boolean = false): Cell | null {
return cell.getParent();
}
/*****************************************************************************
* Group: Graph behaviour
*****************************************************************************/
/**
* Returns {@link portsEnabled} as a boolean.
*/
isPortsEnabled(): boolean {
return this.portsEnabled;
}
/**
* Specifies if the ports should be enabled.
*
* @param value Boolean indicating if the ports should be enabled.
*/
setPortsEnabled(value: boolean): void {
this.portsEnabled = value;
}
/**
* Returns {@link splitEnabled} as a boolean.
*/
isSplitEnabled(): boolean {
return this.splitEnabled;
}
/**
* Specifies if the graph should allow dropping of cells onto or into other
* cells.
*
* @param dropEnabled Boolean indicating if the graph should allow dropping
* of cells into other cells.
*/
setSplitEnabled(value: boolean): void {
this.splitEnabled = value;
}
}
export default GraphPorts;

View File

@ -15,6 +15,9 @@ import mxUndoableEdit from "../model/mxUndoableEdit";
import EventObject from "../event/EventObject";
import InternalEvent from "../event/InternalEvent";
import EventSource from "../event/EventSource";
import mxDictionary from "../../util/mxDictionary";
import RootChange from "../model/RootChange";
import ChildChange from "../model/ChildChange";
class Selection extends EventSource {
constructor(graph: graph) {
@ -614,88 +617,89 @@ class Selection extends EventSource {
*****************************************************************************/
/**
* Creates a new handler for the given cell state. This implementation
* returns a new {@link mxEdgeHandler} of the corresponding cell is an edge,
* otherwise it returns an {@link mxVertexHandler}.
* Function: getSelectionCellsForChanges
*
* Returns the cells to be selected for the given array of changes.
*
* Parameters:
*
* ignoreFn - Optional function that takes a change and returns true if the
* change should be ignored.
*
* @param state {@link mxCellState} whose handler should be created.
*/
createHandler(
state: CellState | null = null
): mxEdgeHandler | mxVertexHandler | null {
let result: mxEdgeHandler | mxVertexHandler | null = null;
getSelectionCellsForChanges(
changes: any[],
ignoreFn: Function | null = null
): CellArray {
const dict = new mxDictionary();
const cells: CellArray = new CellArray();
if (state != null) {
if (state.cell.isEdge()) {
const source = state.getVisibleTerminalState(true);
const target = state.getVisibleTerminalState(false);
const geo = (<Cell>state.cell).getGeometry();
const edgeStyle = this.graph.getView().getEdgeStyle(
state,
geo != null ? geo.points : null,
<CellState>source,
<CellState>target
);
result = this.createEdgeHandler(state, edgeStyle);
const addCell = (cell: Cell) => {
if (!dict.get(cell) && this.getModel().contains(cell)) {
if (cell.isEdge() || cell.isVertex()) {
dict.put(cell, true);
cells.push(cell);
} else {
result = this.createVertexHandler(state);
}
}
return result;
}
const childCount = cell.getChildCount();
/**
* Hooks to create a new {@link mxVertexHandler} for the given {@link CellState}.
*
* @param state {@link mxCellState} to create the handler for.
*/
createVertexHandler(state: CellState): mxVertexHandler {
return new mxVertexHandler(state);
for (let i = 0; i < childCount; i += 1) {
addCell(<Cell>cell.getChildAt(i));
}
}
}
};
for (let i = 0; i < changes.length; i += 1) {
const change = changes[i];
/**
* Hooks to create a new {@link mxEdgeHandler} for the given {@link CellState}.
*
* @param state {@link mxCellState} to create the handler for.
*/
createEdgeHandler(state: CellState, edgeStyle: any): mxEdgeHandler {
let result = null;
if (
edgeStyle == mxEdgeStyle.Loop ||
edgeStyle == mxEdgeStyle.ElbowConnector ||
edgeStyle == mxEdgeStyle.SideToSide ||
edgeStyle == mxEdgeStyle.TopToBottom
change.constructor !== RootChange &&
(ignoreFn == null || !ignoreFn(change))
) {
result = this.createElbowEdgeHandler(state);
} else if (
edgeStyle == mxEdgeStyle.SegmentConnector ||
edgeStyle == mxEdgeStyle.OrthConnector
) {
result = this.createEdgeSegmentHandler(state);
let cell = null;
if (change instanceof ChildChange) {
cell = change.child;
} else if (change.cell != null && change.cell instanceof Cell) {
cell = change.cell;
}
if (cell != null) {
addCell(cell);
}
}
}
return cells;
}
/**
* Removes selection cells that are not in the model from the selection.
*/
updateSelection(): void {
const cells = this.getSelectionCells();
const removed = new CellArray();
for (const cell of cells) {
if (!this.getModel().contains(cell) || !cell.isVisible()) {
removed.push(cell);
} else {
result = new mxEdgeHandler(state);
}
return result;
let par = cell.getParent();
while (par != null && par !== this.view.currentRoot) {
if (par.isCollapsed() || !par.isVisible()) {
removed.push(cell);
break;
}
/**
* Hooks to create a new {@link mxEdgeSegmentHandler} for the given {@link CellState}.
*
* @param state {@link mxCellState} to create the handler for.
*/
createEdgeSegmentHandler(state: CellState): mxEdgeSegmentHandler {
return new mxEdgeSegmentHandler(state);
par = par.getParent();
}
/**
* Hooks to create a new {@link mxElbowEdgeHandler} for the given {@link CellState}.
*
* @param state {@link mxCellState} to create the handler for.
*/
createElbowEdgeHandler(state: CellState): mxElbowEdgeHandler {
return new mxElbowEdgeHandler(state);
}
}
this.selection.removeSelectionCells(removed);
}
}
export default Selection;

View File

@ -0,0 +1,146 @@
import Point from "../geometry/Point";
import Rectangle from "../geometry/Rectangle";
class GraphSnap {
/**
* Specifies the grid size.
* @default 10
*/
gridSize: number = 10;
/**
* Specifies if the grid is enabled. This is used in {@link snap}.
* @default true
*/
gridEnabled: boolean = true;
/*****************************************************************************
* Group: Graph display
*****************************************************************************/
/**
* Snaps the given numeric value to the grid if {@link gridEnabled} is true.
*
* @param value Numeric value to be snapped to the grid.
*/
snap(value: number): number {
if (this.gridEnabled) {
value = Math.round(value / this.gridSize) * this.gridSize;
}
return value;
}
/**
* Function: snapDelta
*
* Snaps the given delta with the given scaled bounds.
*/
snapDelta(
delta: Point,
bounds: Rectangle,
ignoreGrid: boolean = false,
ignoreHorizontal: boolean = false,
ignoreVertical: boolean = false
): Point {
const t = this.view.translate;
const s = this.view.scale;
if (!ignoreGrid && this.gridEnabled) {
const tol = this.gridSize * s * 0.5;
if (!ignoreHorizontal) {
const tx = bounds.x - (this.snap(bounds.x / s - t.x) + t.x) * s;
if (Math.abs(delta.x - tx) < tol) {
delta.x = 0;
} else {
delta.x = this.snap(delta.x / s) * s - tx;
}
}
if (!ignoreVertical) {
const ty = bounds.y - (this.snap(bounds.y / s - t.y) + t.y) * s;
if (Math.abs(delta.y - ty) < tol) {
delta.y = 0;
} else {
delta.y = this.snap(delta.y / s) * s - ty;
}
}
} else {
const tol = 0.5 * s;
if (!ignoreHorizontal) {
const tx = bounds.x - (Math.round(bounds.x / s - t.x) + t.x) * s;
if (Math.abs(delta.x - tx) < tol) {
delta.x = 0;
} else {
delta.x = Math.round(delta.x / s) * s - tx;
}
}
if (!ignoreVertical) {
const ty = bounds.y - (Math.round(bounds.y / s - t.y) + t.y) * s;
if (Math.abs(delta.y - ty) < tol) {
delta.y = 0;
} else {
delta.y = Math.round(delta.y / s) * s - ty;
}
}
}
return delta;
}
/*****************************************************************************
* Group: Graph behaviour
*****************************************************************************/
/**
* Returns {@link gridEnabled} as a boolean.
*/
isGridEnabled(): boolean {
return this.gridEnabled;
}
/**
* Specifies if the grid should be enabled.
*
* @param value Boolean indicating if the grid should be enabled.
*/
setGridEnabled(value: boolean): void {
this.gridEnabled = value;
}
/**
* Returns {@link gridSize}.
*/
getGridSize(): number {
return this.gridSize;
}
/**
* Sets {@link gridSize}.
*/
setGridSize(value: number): void {
this.gridSize = value;
}
/**
* Returns {@link tolerance}.
*/
getTolerance(): number {
return this.tolerance;
}
/**
* Sets {@link tolerance}.
*/
setTolerance(value: number): void {
this.tolerance = value;
}
}
export default GraphSnap;

View File

@ -0,0 +1,36 @@
import Cell from "../cell/datatypes/Cell";
import CellArray from "../cell/datatypes/CellArray";
import InternalMouseEvent from "../event/InternalMouseEvent";
class GraphSplit {
/**
* Returns true if the given edge may be splitted into two edges with the
* given cell as a new terminal between the two.
*
* @param target {@link mxCell} that represents the edge to be splitted.
* @param cells {@link mxCell} that should split the edge.
* @param evt Mouseevent that triggered the invocation.
*/
// isSplitTarget(target: mxCell, cells: mxCellArray, evt: Event): boolean;
isSplitTarget(target: Cell, cells: CellArray, evt: InternalMouseEvent): boolean {
if (
target.isEdge() &&
cells != null &&
cells.length == 1 &&
cells[0].isConnectable() &&
this.getEdgeValidationError(target, target.getTerminal(true), cells[0]) ==
null
) {
const src = <Cell>target.getTerminal(true);
const trg = <Cell>target.getTerminal(false);
return (
!cells[0].isAncestor(src) &&
!cells[0].isAncestor(trg)
);
}
return false;
}
}
export default GraphSplit;

View File

@ -1,9 +1,41 @@
import Cell from "../cell/datatypes/Cell";
import Rectangle from "../geometry/Rectangle";
import utils, {convertPoint, getValue} from "../../util/Utils";
import {
DEFAULT_STARTSIZE,
DIRECTION_EAST,
DIRECTION_NORTH,
DIRECTION_SOUTH,
DIRECTION_WEST, SHAPE_SWIMLANE
} from "../../util/Constants";
import CellArray from "../cell/datatypes/CellArray";
import InternalMouseEvent from "../event/InternalMouseEvent";
import {getClientX, getClientY} from "../../util/EventUtils";
class Swimlane {
constructor() {
}
/**
* Specifies if swimlanes should be selectable via the content if the
* mouse is released.
* @default true
*/
swimlaneSelectionEnabled: boolean = true;
/**
* Specifies if nesting of swimlanes is allowed.
* @default true
*/
swimlaneNesting: boolean = true;
/**
* The attribute used to find the color for the indicator if the indicator
* color is set to 'swimlane'.
* @default {@link 'fillColor'}
*/
swimlaneIndicatorColorAttribute: string = 'fillColor';
/**
* Returns the nearest ancestor of the given cell which is a swimlane, or
* the given cell, if it is itself a swimlane.
@ -93,6 +125,257 @@ class Swimlane {
}
return false;
}
/*****************************************************************************
* Group: Graph appearance
*****************************************************************************/
/**
* Returns the start size of the given swimlane, that is, the width or
* height of the part that contains the title, depending on the
* horizontal style. The return value is an {@link Rectangle} with either
* width or height set as appropriate.
*
* @param swimlane {@link mxCell} whose start size should be returned.
* @param ignoreState Optional boolean that specifies if cell state should be ignored.
*/
getStartSize(swimlane: Cell, ignoreState: boolean = false): Rectangle {
const result = new Rectangle();
const style = this.getCurrentCellStyle(swimlane, ignoreState);
const size = parseInt(
getValue(style, 'startSize', DEFAULT_STARTSIZE)
);
if (getValue(style, 'horizontal', true)) {
result.height = size;
} else {
result.width = size;
}
return result;
}
/**
* Returns the direction for the given swimlane style.
*/
getSwimlaneDirection(style: any): string {
const dir = getValue(style, 'direction', DIRECTION_EAST);
const flipH = getValue(style, 'flipH', 0) == 1;
const flipV = getValue(style, 'flipV', 0) == 1;
const h = getValue(style, 'horizontal', true);
let n = h ? 0 : 3;
if (dir === DIRECTION_NORTH) {
n--;
} else if (dir === DIRECTION_WEST) {
n += 2;
} else if (dir === DIRECTION_SOUTH) {
n += 1;
}
const mod = utils.mod(n, 2);
if (flipH && mod === 1) {
n += 2;
}
if (flipV && mod === 0) {
n += 2;
}
return [DIRECTION_NORTH, DIRECTION_EAST, DIRECTION_SOUTH, DIRECTION_WEST][
utils.mod(n, 4)
];
}
/**
* Returns the actual start size of the given swimlane taking into account
* direction and horizontal and vertial flip styles. The start size is
* returned as an {@link Rectangle} where top, left, bottom, right start sizes
* are returned as x, y, height and width, respectively.
*
* @param swimlane {@link mxCell} whose start size should be returned.
* @param ignoreState Optional boolean that specifies if cell state should be ignored.
*/
getActualStartSize(
swimlane: Cell,
ignoreState: boolean = false
): Rectangle {
const result = new Rectangle();
if (this.isSwimlane(swimlane, ignoreState)) {
const style = this.getCurrentCellStyle(swimlane, ignoreState);
const size = parseInt(getValue(style, 'startSize', DEFAULT_STARTSIZE));
const dir = this.getSwimlaneDirection(style);
if (dir === DIRECTION_NORTH) {
result.y = size;
} else if (dir === DIRECTION_WEST) {
result.x = size;
} else if (dir === DIRECTION_SOUTH) {
result.height = size;
} else {
result.width = size;
}
}
return result;
}
/**
* Returns true if the given cell is a swimlane in the graph. A swimlane is
* a container cell with some specific behaviour. This implementation
* checks if the shape associated with the given cell is a {@link mxSwimlane}.
*
* @param cell {@link mxCell} to be checked.
* @param ignoreState Optional boolean that specifies if the cell state should be ignored.
*/
isSwimlane(cell: Cell, ignoreState: boolean = false): boolean {
if (
cell != null &&
cell.getParent() !== this.getModel().getRoot() &&
!cell.isEdge()
) {
return (
this.getCurrentCellStyle(cell, ignoreState).shape ===
SHAPE_SWIMLANE
);
}
return false;
}
/*****************************************************************************
* Group: Graph behaviour
*****************************************************************************/
/**
* Returns true if the given cell is a valid drop target for the specified
* cells. If {@link splitEnabled} is true then this returns {@link isSplitTarget} for
* the given arguments else it returns true if the cell is not collapsed
* and its child count is greater than 0.
*
* @param cell {@link mxCell} that represents the possible drop target.
* @param cells {@link mxCell} that should be dropped into the target.
* @param evt Mouseevent that triggered the invocation.
*/
// isValidDropTarget(cell: mxCell, cells: mxCellArray, evt: Event): boolean;
isValidDropTarget(cell: Cell, cells: CellArray, evt: InternalMouseEvent): boolean {
return (
cell != null &&
((this.isSplitEnabled() && this.isSplitTarget(cell, cells, evt)) ||
(!cell.isEdge() &&
(this.isSwimlane(cell) ||
(cell.getChildCount() > 0 && !cell.isCollapsed()))))
);
}
/**
* Returns the given cell if it is a drop target for the given cells or the
* nearest ancestor that may be used as a drop target for the given cells.
* If the given array contains a swimlane and {@link swimlaneNesting} is false
* then this always returns null. If no cell is given, then the bottommost
* swimlane at the location of the given event is returned.
*
* This function should only be used if {@link isDropEnabled} returns true.
*
* @param cells Array of {@link Cell} which are to be dropped onto the target.
* @param evt Mouseevent for the drag and drop.
* @param cell {@link mxCell} that is under the mousepointer.
* @param clone Optional boolean to indicate of cells will be cloned.
*/
// getDropTarget(cells: mxCellArray, evt: Event, cell: mxCell, clone?: boolean): mxCell;
getDropTarget(
cells: CellArray,
evt: InternalMouseEvent,
cell: Cell | null = null,
clone: boolean = false
): Cell | null {
if (!this.isSwimlaneNesting()) {
for (let i = 0; i < cells.length; i += 1) {
if (this.isSwimlane(cells[i])) {
return null;
}
}
}
const pt = convertPoint(
this.container,
getClientX(evt),
getClientY(evt)
);
pt.x -= this.panDx;
pt.y -= this.panDy;
const swimlane = this.getSwimlaneAt(pt.x, pt.y);
if (cell == null) {
cell = swimlane;
} else if (swimlane != null) {
// Checks if the cell is an ancestor of the swimlane
// under the mouse and uses the swimlane in that case
let tmp = swimlane.getParent();
while (tmp != null && this.isSwimlane(tmp) && tmp != cell) {
tmp = tmp.getParent();
}
if (tmp == cell) {
cell = swimlane;
}
}
while (
cell != null &&
!this.isValidDropTarget(cell, cells, evt) &&
!this.getModel().isLayer(cell)
) {
cell = cell.getParent();
}
// Checks if parent is dropped into child if not cloning
if (!clone) {
let parent = cell;
while (parent != null && cells.indexOf(parent) < 0) {
parent = parent.getParent();
}
}
return !this.getModel().isLayer(<Cell>cell) && parent == null
? cell
: null;
}
/**
* Returns {@link swimlaneNesting} as a boolean.
*/
isSwimlaneNesting(): boolean {
return this.swimlaneNesting;
}
/**
* Specifies if swimlanes can be nested by drag and drop. This is only
* taken into account if dropEnabled is true.
*
* @param value Boolean indicating if swimlanes can be nested.
*/
setSwimlaneNesting(value: boolean): void {
this.swimlaneNesting = value;
}
/**
* Returns {@link swimlaneSelectionEnabled} as a boolean.
*/
isSwimlaneSelectionEnabled(): boolean {
return this.swimlaneSelectionEnabled;
}
/**
* Specifies if swimlanes should be selected if the mouse is released
* over their content area.
*
* @param value Boolean indicating if swimlanes content areas
* should be selected when the mouse is released over them.
*/
setSwimlaneSelectionEnabled(value: boolean): void {
this.swimlaneSelectionEnabled = value;
}
}
export default Swimlane;

View File

@ -0,0 +1,127 @@
import CellState from "../cell/datatypes/CellState";
import {htmlEntities} from "../../util/StringUtils";
import Resources from "../../util/Resources";
import Shape from "../geometry/shape/Shape";
import mxSelectionCellsHandler from "../selection/mxSelectionCellsHandler";
import Cell from "../cell/datatypes/Cell";
import TooltipHandler from "../popups_menus/TooltipHandler";
class GraphTooltip {
/**
* Returns the string or DOM node that represents the tooltip for the given
* state, node and coordinate pair. This implementation checks if the given
* node is a folding icon or overlay and returns the respective tooltip. If
* this does not result in a tooltip, the handler for the cell is retrieved
* from {@link selectionCellsHandler} and the optional getTooltipForNode method is
* called. If no special tooltip exists here then {@link getTooltipForCell} is used
* with the cell in the given state as the argument to return a tooltip for the
* given state.
*
* @param state {@link mxCellState} whose tooltip should be returned.
* @param node DOM node that is currently under the mouse.
* @param x X-coordinate of the mouse.
* @param y Y-coordinate of the mouse.
*/
// getTooltip(state: mxCellState, node: Node, x: number, y: number): string;
getTooltip(
state: CellState,
node: HTMLElement,
x: number,
y: number
): string | null {
let tip: string | null = null;
if (state != null) {
// Checks if the mouse is over the folding icon
if (
state.control != null &&
// @ts-ignore
(node === state.control.node || node.parentNode === state.control.node)
) {
tip = this.collapseExpandResource;
tip = htmlEntities(Resources.get(tip) || tip, true).replace(
/\\n/g,
'<br>'
);
}
if (tip == null && state.overlays != null) {
state.overlays.visit((id: string, shape: Shape) => {
// LATER: Exit loop if tip is not null
if (
tip == null &&
// @ts-ignore
(node === shape.node || node.parentNode === shape.node)
) {
// @ts-ignore
tip = shape.overlay.toString();
}
});
}
if (tip == null) {
const handler = (<mxSelectionCellsHandler>(
this.selectionCellsHandler
)).getHandler(<Cell>state.cell);
if (
handler != null &&
typeof handler.getTooltipForNode === 'function'
) {
tip = handler.getTooltipForNode(node);
}
}
if (tip == null) {
tip = this.getTooltipForCell(<Cell>state.cell);
}
}
return tip;
}
/**
* Returns the string or DOM node to be used as the tooltip for the given
* cell. This implementation uses the cells getTooltip function if it
* exists, or else it returns {@link convertValueToString} for the cell.
*
* @example
*
* ```javascript
* graph.getTooltipForCell = function(cell)
* {
* return 'Hello, World!';
* }
* ```
*
* Replaces all tooltips with the string Hello, World!
*
* @param cell {@link mxCell} whose tooltip should be returned.
*/
getTooltipForCell(cell: Cell): string | null {
let tip = null;
if (cell != null && 'getTooltip' in cell) {
// @ts-ignore
tip = cell.getTooltip();
} else {
tip = this.convertValueToString(cell);
}
return tip;
}
/*****************************************************************************
* Group: Graph behaviour
*****************************************************************************/
/**
* Specifies if tooltips should be enabled. This implementation updates
* {@link TooltipHandler.enabled} in {@link tooltipHandler}.
*
* @param enabled Boolean indicating if tooltips should be enabled.
*/
setTooltips(enabled: boolean): void {
(<TooltipHandler>this.tooltipHandler).setEnabled(enabled);
}
}
export default GraphTooltip;

View File

@ -0,0 +1,217 @@
import Rectangle from "../geometry/Rectangle";
import {hasScrollbars} from "../../util/Utils";
class GraphZoom {
/**
* Specifies the factor used for {@link zoomIn} and {@link zoomOut}.
* @default 1.2 (120%)
*/
zoomFactor: number = 1.2;
/**
* Specifies if the viewport should automatically contain the selection cells after a zoom operation.
* @default false
*/
keepSelectionVisibleOnZoom: boolean = false;
/**
* Specifies if the zoom operations should go into the center of the actual
* diagram rather than going from top, left.
* @default true
*/
centerZoom: boolean = true;
/*****************************************************************************
* Group: Graph display
*****************************************************************************/
/**
* Zooms into the graph by {@link zoomFactor}.
*/
zoomIn(): void {
this.zoom(this.zoomFactor);
}
/**
* Zooms out of the graph by {@link zoomFactor}.
*/
zoomOut(): void {
this.zoom(1 / this.zoomFactor);
}
/**
* Resets the zoom and panning in the view.
*/
zoomActual(): void {
if (this.view.scale === 1) {
this.view.setTranslate(0, 0);
} else {
this.view.translate.x = 0;
this.view.translate.y = 0;
this.view.setScale(1);
}
}
/**
* Zooms the graph to the given scale with an optional boolean center
* argument, which is passd to {@link zoom}.
*/
zoomTo(scale: number, center: boolean = false): void {
this.zoom(scale / this.view.scale, center);
}
/**
* Zooms the graph using the given factor. Center is an optional boolean
* argument that keeps the graph scrolled to the center. If the center argument
* is omitted, then {@link centerZoom} will be used as its value.
*/
zoom(factor: number, center: boolean = this.centerZoom): void {
const scale = Math.round(this.view.scale * factor * 100) / 100;
const state = this.view.getState(this.getSelectionCell());
const container = <HTMLElement>this.container;
factor = scale / this.view.scale;
if (this.keepSelectionVisibleOnZoom && state != null) {
const rect = new Rectangle(
state.x * factor,
state.y * factor,
state.width * factor,
state.height * factor
);
// Refreshes the display only once if a scroll is carried out
this.view.scale = scale;
if (!this.scrollRectToVisible(rect)) {
this.view.revalidate();
// Forces an event to be fired but does not revalidate again
this.view.setScale(scale);
}
} else {
const _hasScrollbars = hasScrollbars(this.container);
if (center && !_hasScrollbars) {
let dx = container.offsetWidth;
let dy = container.offsetHeight;
if (factor > 1) {
const f = (factor - 1) / (scale * 2);
dx *= -f;
dy *= -f;
} else {
const f = (1 / factor - 1) / (this.view.scale * 2);
dx *= f;
dy *= f;
}
this.view.scaleAndTranslate(
scale,
this.view.translate.x + dx,
this.view.translate.y + dy
);
} else {
// Allows for changes of translate and scrollbars during setscale
const tx = this.view.translate.x;
const ty = this.view.translate.y;
const sl = container.scrollLeft;
const st = container.scrollTop;
this.view.setScale(scale);
if (_hasScrollbars) {
let dx = 0;
let dy = 0;
if (center) {
dx = (container.offsetWidth * (factor - 1)) / 2;
dy = (container.offsetHeight * (factor - 1)) / 2;
}
container.scrollLeft =
(this.view.translate.x - tx) * this.view.scale +
Math.round(sl * factor + dx);
container.scrollTop =
(this.view.translate.y - ty) * this.view.scale +
Math.round(st * factor + dy);
}
}
}
}
/**
* Zooms the graph to the specified rectangle. If the rectangle does not have same aspect
* ratio as the display container, it is increased in the smaller relative dimension only
* until the aspect match. The original rectangle is centralised within this expanded one.
*
* Note that the input rectangular must be un-scaled and un-translated.
*
* @param rect The un-scaled and un-translated rectangluar region that should be just visible
* after the operation
*/
zoomToRect(rect: Rectangle): void {
const container = <HTMLElement>this.container;
const scaleX = container.clientWidth / rect.width;
const scaleY = container.clientHeight / rect.height;
const aspectFactor = scaleX / scaleY;
// Remove any overlap of the rect outside the client area
rect.x = Math.max(0, rect.x);
rect.y = Math.max(0, rect.y);
let rectRight = Math.min(container.scrollWidth, rect.x + rect.width);
let rectBottom = Math.min(container.scrollHeight, rect.y + rect.height);
rect.width = rectRight - rect.x;
rect.height = rectBottom - rect.y;
// The selection area has to be increased to the same aspect
// ratio as the container, centred around the centre point of the
// original rect passed in.
if (aspectFactor < 1.0) {
// Height needs increasing
const newHeight = rect.height / aspectFactor;
const deltaHeightBuffer = (newHeight - rect.height) / 2.0;
rect.height = newHeight;
// Assign up to half the buffer to the upper part of the rect, not crossing 0
// put the rest on the bottom
const upperBuffer = Math.min(rect.y, deltaHeightBuffer);
rect.y -= upperBuffer;
// Check if the bottom has extended too far
rectBottom = Math.min(container.scrollHeight, rect.y + rect.height);
rect.height = rectBottom - rect.y;
} else {
// Width needs increasing
const newWidth = rect.width * aspectFactor;
const deltaWidthBuffer = (newWidth - rect.width) / 2.0;
rect.width = newWidth;
// Assign up to half the buffer to the upper part of the rect, not crossing 0
// put the rest on the bottom
const leftBuffer = Math.min(rect.x, deltaWidthBuffer);
rect.x -= leftBuffer;
// Check if the right hand side has extended too far
rectRight = Math.min(container.scrollWidth, rect.x + rect.width);
rect.width = rectRight - rect.x;
}
const scale = container.clientWidth / rect.width;
const newScale = this.view.scale * scale;
if (!hasScrollbars(this.container)) {
this.view.scaleAndTranslate(
newScale,
this.view.translate.x - rect.x / this.view.scale,
this.view.translate.y - rect.y / this.view.scale
);
} else {
this.view.setScale(newScale);
container.scrollLeft = Math.round(rect.x * scale);
container.scrollTop = Math.round(rect.y * scale);
}
}
}
export default GraphZoom;

View File

@ -33,6 +33,17 @@ const Template = ({ label, ...args }) => {
'shape=cylinder;strokeWidth=2;fillColor=#ffffff;strokeColor=black;' +
'gradientColor=#a0a0a0;fontColor=black;fontStyle=1;spacingTop=14;';
/*const vertexStyle = {
shape: 'cylinder',
strokeWidth: 2,
fillColor: '#ffffff',
strokeColor: 'black',
gradientColor: '#a0a0a0',
fontColor: 'black',
fontStyle: 1,
spacingTop: 14,
};*/
let e1;
graph.batchUpdate(() => {
const v1 = graph.insertVertex({

View File

@ -265,7 +265,7 @@ const Template = ({ label, ...args }) => {
}
// Standard paste via control-v
if (xml.substring(0, 14) === '<mxGraphModel>') {
if (xml.substring(0, 14) === '<Transactions>') {
graph.setSelectionCells(importXml(xml, dx, dy));
graph.scrollCellToVisible(graph.getSelectionCell());
}

View File

@ -27,7 +27,7 @@ const Template = ({ label, ...args }) => {
const parent = graph.getDefaultParent();
const getStyle = function() {
// Extends mxGraphModel.getStyle to show an image when collapsed
// Extends Transactions.getStyle to show an image when collapsed
// TODO cannot use super without a parent class
// let style = super.getStyle();
let style = '';

View File

@ -112,7 +112,7 @@ function handleDrop(graph, file, x, y) {
if (file.type.substring(0, 9) === 'image/svg') {
const comma = data.indexOf(',');
const svgText = atob(data.substring(comma + 1));
const root = mxUtils.parseXml(svgText);
const root = utils.parseXml(svgText);
// Parses SVG to find width and height
if (root != null) {
@ -144,7 +144,7 @@ function handleDrop(graph, file, x, y) {
h = Math.max(1, Math.round(h));
data = `data:image/svg+xml,${btoa(
mxUtils.getXml(svgs[0], '\n')
utils.getXml(svgs[0], '\n')
)}`;
graph.insertVertex({
position: [x, y],

View File

@ -158,16 +158,16 @@ function createPopupMenu(graph, menu, cell, evt) {
// Function to create the entries in the popupmenu
if (cell != null) {
menu.addItem('Cell Item', '/images/image.gif', () => {
mxUtils.alert('MenuItem1');
utils.alert('MenuItem1');
});
} else {
menu.addItem('No-Cell Item', '/images/image.gif', () => {
mxUtils.alert('MenuItem2');
utils.alert('MenuItem2');
});
}
menu.addSeparator();
menu.addItem('MenuItem3', '/images/warning.gif', () => {
mxUtils.alert(`MenuItem3: ${graph.getSelectionCount()} selected`);
utils.alert(`MenuItem3: ${graph.getSelectionCount()} selected`);
});
}

View File

@ -34,7 +34,7 @@ const Template = ({ label, ...args }) => {
if (!mxClient.isBrowserSupported()) {
// Displays an error message if the browser is
// not supported.
mxUtils.error('Browser is not supported!', 200, false);
utils.error('Browser is not supported!', 200, false);
} else {
// Creates the graph inside the given container
const graph = new mxGraph(container);
@ -50,7 +50,7 @@ const Template = ({ label, ...args }) => {
// Changes the default vertex style in-place
let style = graph.getStylesheet().getDefaultVertexStyle();
style.shape = mxConstants.SHAPE_ROUNDED;
style.perimiter = mxPerimeter.RectanglePerimeter;
style.perimiter = Perimeter.RectanglePerimeter;
style.gradientColor = 'white';
style.perimeterSpacing = 4;
style.shadow = true;
@ -58,7 +58,7 @@ const Template = ({ label, ...args }) => {
style = graph.getStylesheet().getDefaultEdgeStyle();
style.labelBackgroundColor = 'white';
style = mxUtils.clone(style);
style = utils.clone(style);
style.startArrow = mxConstants.ARROW_CLASSIC;
graph.getStylesheet().putCellStyle('2way', style);
@ -78,7 +78,7 @@ const Template = ({ label, ...args }) => {
// Adds a button to execute the layout
this.el2.appendChild(
mxUtils.button('Arrange', function(evt) {
utils.button('Arrange', function(evt) {
const parent = graph.getDefaultParent();
layout.execute(parent);
})
@ -105,7 +105,7 @@ const Template = ({ label, ...args }) => {
}
graph.dblClick = function(evt, cell) {
const mxe = new mxEventObject(
const mxe = new EventObject(
mxEvent.DOUBLE_CLICK,
'event',
evt,
@ -120,7 +120,7 @@ const Template = ({ label, ...args }) => {
!mxe.isConsumed() &&
cell != null
) {
mxUtils.alert(
utils.alert(
`Show properties for cell ${cell.customId || cell.getId()}`
);
}
@ -136,7 +136,7 @@ const Template = ({ label, ...args }) => {
// is normally the first child of the root (ie. layer 0).
const parent = graph.getDefaultParent();
const req = mxUtils.load(filename);
const req = utils.load(filename);
const text = req.getText();
const lines = text.split('\n');
@ -192,7 +192,7 @@ const Template = ({ label, ...args }) => {
// Parses the mxGraph XML file format
function read(graph, filename) {
const req = mxUtils.load(filename);
const req = utils.load(filename);
const root = req.getDocumentElement();
const dec = new mxCodec(root.ownerDocument);

View File

@ -37,7 +37,7 @@ const Template = ({ label, ...args }) => {
// Snaps to fixed points
intersects(icon, point, source, existingEdge) {
return (
!source || existingEdge || mxUtils.intersects(icon.bounds, point)
!source || existingEdge || utils.intersects(icon.bounds, point)
);
}
}

View File

@ -107,7 +107,7 @@ const Template = ({ label, ...args }) => {
const button = mxDomHelpers.button('View XML', function() {
const encoder = new mxCodec();
const node = encoder.encode(graph.getModel());
mxUtils.popup(mxUtils.getPrettyXml(node), true);
utils.popup(utils.getPrettyXml(node), true);
});
controller.appendChild(button);

View File

@ -40,7 +40,7 @@ const Template = ({ label, ...args }) => {
// Creates a process display using the activity names as IDs to refer to the elements
const xml =
'<mxGraphModel><root><mxCell id="0"/><mxCell id="1" parent="0"/>' +
'<Transactions><root><mxCell id="0"/><mxCell id="1" parent="0"/>' +
'<mxCell id="2" value="Claim Handling Process" style="swimlane" vertex="1" parent="1"><mxGeometry x="1" width="850" height="400" as="geometry"/></mxCell>' +
'<mxCell id="3" value="Claim Manager" style="swimlane" vertex="1" parent="2"><mxGeometry x="30" width="820" height="200" as="geometry"/></mxCell>' +
'<mxCell id="5" value="" style="start" vertex="1" parent="3"><mxGeometry x="40" y="85" width="30" height="30" as="geometry"/></mxCell>' +
@ -77,7 +77,7 @@ const Template = ({ label, ...args }) => {
'<mxCell id="29" value="" edge="1" parent="2" source="22" target="EnterAccountingData"><mxGeometry relative="1" as="geometry"><Array as="points"><mxPoint x="469" y="40"/></Array></mxGeometry></mxCell>' +
'<mxCell id="30" value="" edge="1" parent="2" source="27" target="EnterAccountingData"><mxGeometry relative="1" as="geometry"><Array as="points"><mxPoint x="469" y="40"/></Array></mxGeometry></mxCell>' +
'<mxCell id="33" value="" edge="1" parent="2" source="6" target="EnterAccountingData"><mxGeometry relative="1" as="geometry"><Array as="points"><mxPoint x="255" y="200"/></Array></mxGeometry></mxCell>' +
'</root></mxGraphModel>';
'</root></Transactions>';
const doc = mxXmlUtils.parseXml(xml);
const codec = new mxCodec(doc);
codec.decode(doc.documentElement, graph.getModel());
@ -173,7 +173,7 @@ const Template = ({ label, ...args }) => {
* which is being displayed on click.
*/
function createOverlay(image, tooltip) {
const overlay = new mxCellOverlay(image, tooltip);
const overlay = new CellOverlay(image, tooltip);
// Installs a handler for clicks on the overlay
overlay.addListener(mxEvent.CLICK, function(sender, evt) {

View File

@ -232,7 +232,7 @@ const Template = ({ label, ...args }) => {
});
tb.addItem('Print', 'images/print32.png', function(evt) {
const preview = new mxPrintPreview(graph, 1);
const preview = new PrintPreview(graph, 1);
preview.open();
});
@ -241,7 +241,7 @@ const Template = ({ label, ...args }) => {
if (pageCount != null) {
const scale = mxUtils.getScaleForPageCount(pageCount, graph);
const preview = new mxPrintPreview(graph, scale);
const preview = new PrintPreview(graph, scale);
preview.open();
}
});
@ -285,7 +285,7 @@ const Template = ({ label, ...args }) => {
menu.addSeparator();
menu.addItem('Print', '/images/print.gif', function() {
const preview = new mxPrintPreview(graph, 1);
const preview = new PrintPreview(graph, 1);
preview.open();
});
@ -294,7 +294,7 @@ const Template = ({ label, ...args }) => {
if (pageCount != null) {
const scale = mxUtils.getScaleForPageCount(pageCount, graph);
const preview = new mxPrintPreview(graph, scale);
const preview = new PrintPreview(graph, scale);
preview.open();
}
});

View File

@ -128,7 +128,7 @@ const Template = ({ label, ...args }) => {
const x0 = Math.floor(bounds.x / pf.width) * pf.width;
const y0 = Math.floor(bounds.y / pf.height) * pf.height;
const preview = new mxPrintPreview(graph, scale, pf, 0, -x0, -y0);
const preview = new PrintPreview(graph, scale, pf, 0, -x0, -y0);
preview.marginTop = headerSize * scale * graph.pageScale;
preview.marginBottom = footerSize * scale * graph.pageScale;
preview.autoOrigin = false;
@ -153,11 +153,11 @@ const Template = ({ label, ...args }) => {
const footer = header.cloneNode(true);
mxUtils.write(header, `Page ${pageNumber} - Header`);
utils.write(header, `Page ${pageNumber} - Header`);
header.style.borderBottom = '1px solid gray';
header.style.top = '0px';
mxUtils.write(footer, `Page ${pageNumber} - Footer`);
utils.write(footer, `Page ${pageNumber} - Footer`);
footer.style.borderTop = '1px solid gray';
footer.style.bottom = '0px';