started refactoring+reorganising core to not have mx prefix, and breaking up mxGraph into smaller classes for easier maintenance
parent
8d16eafd80
commit
f76a172cae
File diff suppressed because it is too large
Load Diff
|
@ -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()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 Cells;
|
||||
export default GraphCells;
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
*****************************************************************************/
|
||||
|
|
|
@ -10,6 +10,12 @@ class Vertex {
|
|||
*/
|
||||
vertexLabelsMovable: boolean = false;
|
||||
|
||||
/**
|
||||
* Specifies if negative coordinates for vertices are allowed.
|
||||
* @default true
|
||||
*/
|
||||
allowNegativeCoordinates: boolean = true;
|
||||
|
||||
/**
|
||||
* Function: insertVertex
|
||||
*
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 Connections;
|
||||
export default GraphConnections;
|
|
@ -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;
|
||||
export default GraphDragDrop;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
|
@ -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 addCell = (cell: Cell) => {
|
||||
if (!dict.get(cell) && this.getModel().contains(cell)) {
|
||||
if (cell.isEdge() || cell.isVertex()) {
|
||||
dict.put(cell, true);
|
||||
cells.push(cell);
|
||||
} else {
|
||||
const childCount = cell.getChildCount();
|
||||
|
||||
const edgeStyle = this.graph.getView().getEdgeStyle(
|
||||
state,
|
||||
geo != null ? geo.points : null,
|
||||
<CellState>source,
|
||||
<CellState>target
|
||||
);
|
||||
result = this.createEdgeHandler(state, edgeStyle);
|
||||
} else {
|
||||
result = this.createVertexHandler(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];
|
||||
|
||||
if (
|
||||
change.constructor !== RootChange &&
|
||||
(ignoreFn == null || !ignoreFn(change))
|
||||
) {
|
||||
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 result;
|
||||
return cells;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks to create a new {@link mxEdgeHandler} for the given {@link CellState}.
|
||||
*
|
||||
* @param state {@link mxCellState} to create the handler for.
|
||||
* Removes selection cells that are not in the model from the selection.
|
||||
*/
|
||||
createEdgeHandler(state: CellState, edgeStyle: any): mxEdgeHandler {
|
||||
let result = null;
|
||||
if (
|
||||
edgeStyle == mxEdgeStyle.Loop ||
|
||||
edgeStyle == mxEdgeStyle.ElbowConnector ||
|
||||
edgeStyle == mxEdgeStyle.SideToSide ||
|
||||
edgeStyle == mxEdgeStyle.TopToBottom
|
||||
) {
|
||||
result = this.createElbowEdgeHandler(state);
|
||||
} else if (
|
||||
edgeStyle == mxEdgeStyle.SegmentConnector ||
|
||||
edgeStyle == mxEdgeStyle.OrthConnector
|
||||
) {
|
||||
result = this.createEdgeSegmentHandler(state);
|
||||
} else {
|
||||
result = new mxEdgeHandler(state);
|
||||
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 {
|
||||
let par = cell.getParent();
|
||||
|
||||
while (par != null && par !== this.view.currentRoot) {
|
||||
if (par.isCollapsed() || !par.isVisible()) {
|
||||
removed.push(cell);
|
||||
break;
|
||||
}
|
||||
|
||||
par = par.getParent();
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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({
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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 = '';
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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`);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
Loading…
Reference in New Issue