Change CellArray to cellArrayUtils.

development
Junsik Shim 2022-09-17 11:30:23 +09:00
parent 0591c37a11
commit 3cc2713704
11 changed files with 234 additions and 236 deletions

View File

@ -145,6 +145,7 @@ export * as stringUtils from './util/StringUtils';
export * as xmlUtils from './util/xmlUtils';
export * as styleUtils from './util/styleUtils';
export * as mathUtils from './util/mathUtils';
export * as cellArrayUtils from './util/cellArrayUtils';
export { default as Animation } from './view/animate/Animation';
export { default as Effects } from './view/animate/Effects';

View File

@ -7,6 +7,7 @@
import Cell from '../view/cell/Cell';
import { Graph } from '../view/Graph';
import { getTopmostCells } from './cellArrayUtils';
/**
* @class
@ -140,7 +141,6 @@ class Clipboard {
* @param cells - Array of {@link mxCell} to be cut.
*/
static removeCells(graph: Graph, cells: Cell[]) {
// @ts-ignore
graph.removeCells(cells);
}
@ -154,9 +154,9 @@ class Clipboard {
*/
static copy(graph: Graph, cells?: Cell[]) {
cells = cells || graph.getSelectionCells();
const result = graph.getExportableCells(cells).getTopmostCells();
const result = getTopmostCells(graph.getExportableCells(cells));
Clipboard.insertCount = 1;
// @ts-ignore
Clipboard.setCells(graph.cloneCells(result));
return result;
}
@ -179,7 +179,7 @@ class Clipboard {
cells = graph.getImportableCells(Clipboard.getCells());
const delta = Clipboard.insertCount * Clipboard.STEPSIZE;
const parent = graph.getDefaultParent();
// @ts-ignore
cells = graph.importCells(cells, delta, delta, parent);
// Increments the counter and selects the inserted cells

View File

@ -0,0 +1,198 @@
import Cell from '../view/cell/Cell';
import Dictionary from './Dictionary';
import ObjectIdentity from './ObjectIdentity';
/**
* Returns the cells from the given array where the given filter function
* returns true.
*/
export const filterCells = (filter: Function) => (cells: Cell[]) => {
let result = [] as Cell[];
for (let i = 0; i < cells.length; i += 1) {
if (filter(cells[i])) {
result.push(cells[i]);
}
}
return result;
};
/**
* Returns all opposite vertices wrt terminal for the given edges, only
* returning sources and/or targets as specified. The result is returned
* as an array of {@link Cell}.
*
* @param {Cell} terminal that specifies the known end of the edges.
* @param sources Boolean that specifies if source terminals should be contained
* in the result. Default is true.
* @param targets Boolean that specifies if target terminals should be contained
* in the result. Default is true.
*/
export const getOpposites =
(terminal: Cell, sources: boolean = true, targets: boolean = true) =>
(edges: Cell[]) => {
const terminals = [] as Cell[];
for (let i = 0; i < edges.length; i += 1) {
const source = edges[i].getTerminal(true);
const target = edges[i].getTerminal(false);
// Checks if the terminal is the source of
// the edge and if the target should be
// stored in the result
if (source === terminal && target != null && target !== terminal && targets) {
terminals.push(target);
}
// Checks if the terminal is the taget of
// the edge and if the source should be
// stored in the result
else if (target === terminal && source != null && source !== terminal && sources) {
terminals.push(source);
}
}
return terminals;
};
/**
* Returns the topmost cells of the hierarchy in an array that contains no
* descendants for each {@link Cell} that it contains. Duplicates should be
* removed in the cells array to improve performance.
*/
export const getTopmostCells = (cells: Cell[]) => {
const dict = new Dictionary();
const tmp = [] as Cell[];
for (let i = 0; i < cells.length; i += 1) {
dict.put(cells[i], true);
}
for (let i = 0; i < cells.length; i += 1) {
const cell = cells[i];
let topmost = true;
let parent = cell.getParent();
while (parent != null) {
if (dict.get(parent)) {
topmost = false;
break;
}
parent = parent.getParent();
}
if (topmost) {
tmp.push(cell);
}
}
return tmp;
};
/**
* Returns an array that represents the set (no duplicates) of all parents
* for the given array of cells.
*/
export const getParents = (cells: Cell[]) => {
const parents = [];
const dict = new Dictionary();
for (const cell of cells) {
const parent = cell.getParent();
if (parent != null && !dict.get(parent)) {
dict.put(parent, true);
parents.push(parent);
}
}
return parents;
};
/**
* Returns an array of clones for the given array of {@link Cell}`.
* Depending on the value of includeChildren, a deep clone is created for
* each cell. Connections are restored based if the corresponding
* cell is contained in the passed in array.
*
* @param includeChildren Boolean indicating if the cells should be cloned
* with all descendants.
* @param mapping Optional mapping for existing clones.
*/
export const cloneCells =
(includeChildren = true, mapping: any = {}) =>
(cells: Cell[]) => {
const clones = [] as Cell[];
for (const cell of cells) {
clones.push(cloneCellImpl(cell, mapping, includeChildren));
}
for (let i = 0; i < clones.length; i += 1) {
if (clones[i] != null) {
restoreClone(<Cell>clones[i], cells[i], mapping);
}
}
return clones;
};
/**
* Inner helper method for cloning cells recursively.
*
* @private
*/
const cloneCellImpl = (
cell: Cell,
mapping: any = {},
includeChildren: boolean = false
): Cell => {
const ident = <string>ObjectIdentity.get(cell);
let clone = mapping ? mapping[ident] : null;
if (clone == null) {
clone = cell.clone();
mapping[ident] = clone;
if (includeChildren) {
const childCount = cell.getChildCount();
for (let i = 0; i < childCount; i += 1) {
const cloneChild = cloneCellImpl(<Cell>cell.getChildAt(i), mapping, true);
clone.insert(cloneChild);
}
}
}
return clone;
};
/**
* Inner helper method for restoring the connections in
* a network of cloned cells.
*
* @private
*/
export const restoreClone =
(clone: Cell, cell: Cell, mapping: any) => (cells: Cell[]) => {
const source = cell.getTerminal(true);
if (source != null) {
const tmp = mapping[<string>ObjectIdentity.get(source)];
if (tmp != null) {
tmp.insertEdge(clone, true);
}
}
const target = cell.getTerminal(false);
if (target != null) {
const tmp = mapping[<string>ObjectIdentity.get(target)];
if (tmp != null) {
tmp.insertEdge(clone, false);
}
}
const childCount = clone.getChildCount();
for (let i = 0; i < childCount; i += 1) {
restoreClone(<Cell>clone.getChildAt(i), <Cell>cell.getChildAt(i), mapping);
}
};

View File

@ -49,8 +49,6 @@ import Multiplicity from './other/Multiplicity';
import ImageBundle from './image/ImageBundle';
import GraphSelectionModel from './GraphSelectionModel';
import './cell/CellArray';
export const defaultPlugins: GraphPluginConstructor[] = [
CellEditorHandler,
TooltipHandler,

View File

@ -22,6 +22,7 @@ import VisibleChange from './undoable_changes/VisibleChange';
import Geometry from './geometry/Geometry';
import ObjectCodec from '../serialization/ObjectCodec';
import CodecRegistry from '../serialization/CodecRegistry';
import { cloneCells, filterCells } from '../util/cellArrayUtils';
import type { CellStyle, FilterFunction } from '../types';
@ -326,7 +327,7 @@ export class GraphDataModel extends EventSource {
}
filterCells(cells: Cell[], filter: FilterFunction) {
return cells.filterCells(filter);
return filterCells(filter)(cells);
}
getRoot(cell: Cell | null = null) {
@ -1156,7 +1157,7 @@ export class GraphDataModel extends EventSource {
*/
cloneCell(cell: Cell | null = null, includeChildren: boolean = true): Cell | null {
if (cell != null) {
return [cell].cloneCells(includeChildren)[0];
return cloneCells(includeChildren)([cell])[0];
}
return null;
}

View File

@ -1,209 +0,0 @@
import Cell from './Cell';
import Dictionary from '../../util/Dictionary';
import ObjectIdentity from '../../util/ObjectIdentity';
declare global {
interface Array<T> {
filterCells: (filter: Function) => T[];
getOpposites: (terminal: Cell, sources: boolean, targets: boolean) => T[];
getTopmostCells: () => T[];
getParents: () => T[];
cloneCells: (includeChildren?: boolean, mapping?: any) => T[];
cloneCellImpl: (cell: Cell, mapping?: any, includeChildren?: boolean) => T;
restoreClone: (clone: Cell, cell: Cell, mapping: any) => void;
}
}
/**
* Returns the cells from the given array where the given filter function
* returns true.
*/
Array.prototype.filterCells = function (filter: Function) {
let result = [] as Cell[];
for (let i = 0; i < this.length; i += 1) {
if (filter(this[i])) {
result.push(this[i]);
}
}
return result;
};
/**
* Returns all opposite vertices wrt terminal for the given edges, only
* returning sources and/or targets as specified. The result is returned
* as an array of {@link Cell}.
*
* @param {Cell} terminal that specifies the known end of the edges.
* @param sources Boolean that specifies if source terminals should be contained
* in the result. Default is true.
* @param targets Boolean that specifies if target terminals should be contained
* in the result. Default is true.
*/
Array.prototype.getOpposites = function (
terminal: Cell,
sources: boolean = true,
targets: boolean = true
) {
const terminals = [] as Cell[];
for (let i = 0; i < this.length; i += 1) {
const source = this[i].getTerminal(true);
const target = this[i].getTerminal(false);
// Checks if the terminal is the source of
// the edge and if the target should be
// stored in the result
if (source === terminal && target != null && target !== terminal && targets) {
terminals.push(target);
}
// Checks if the terminal is the taget of
// the edge and if the source should be
// stored in the result
else if (target === terminal && source != null && source !== terminal && sources) {
terminals.push(source);
}
}
return terminals;
};
/**
* Returns the topmost cells of the hierarchy in an array that contains no
* descendants for each {@link Cell} that it contains. Duplicates should be
* removed in the cells array to improve performance.
*/
Array.prototype.getTopmostCells = function () {
const dict = new Dictionary();
const tmp = [] as Cell[];
for (let i = 0; i < this.length; i += 1) {
dict.put(this[i], true);
}
for (let i = 0; i < this.length; i += 1) {
const cell = this[i];
let topmost = true;
let parent = cell.getParent();
while (parent != null) {
if (dict.get(parent)) {
topmost = false;
break;
}
parent = parent.getParent();
}
if (topmost) {
tmp.push(cell);
}
}
return tmp;
};
/**
* Returns an array that represents the set (no duplicates) of all parents
* for the given array of cells.
*/
Array.prototype.getParents = function () {
const parents = [];
const dict = new Dictionary();
for (const cell of this) {
const parent = cell.getParent();
if (parent != null && !dict.get(parent)) {
dict.put(parent, true);
parents.push(parent);
}
}
return parents;
};
/**
* Returns an array of clones for the given array of {@link Cell}`.
* Depending on the value of includeChildren, a deep clone is created for
* each cell. Connections are restored based if the corresponding
* cell is contained in the passed in array.
*
* @param includeChildren Boolean indicating if the cells should be cloned
* with all descendants.
* @param mapping Optional mapping for existing clones.
*/
Array.prototype.cloneCells = function (includeChildren = true, mapping: any = {}) {
const clones = [] as Cell[];
for (const cell of this) {
clones.push(this.cloneCellImpl(cell, mapping, includeChildren));
}
for (let i = 0; i < clones.length; i += 1) {
if (clones[i] != null) {
this.restoreClone(<Cell>clones[i], this[i], mapping);
}
}
return clones;
};
/**
* Inner helper method for cloning cells recursively.
*
* @private
*/
Array.prototype.cloneCellImpl = function (
cell: Cell,
mapping: any = {},
includeChildren: boolean = false
): Cell {
const ident = <string>ObjectIdentity.get(cell);
let clone = mapping ? mapping[ident] : null;
if (clone == null) {
clone = cell.clone();
mapping[ident] = clone;
if (includeChildren) {
const childCount = cell.getChildCount();
for (let i = 0; i < childCount; i += 1) {
const cloneChild = this.cloneCellImpl(<Cell>cell.getChildAt(i), mapping, true);
clone.insert(cloneChild);
}
}
}
return clone;
};
/**
* Inner helper method for restoring the connections in
* a network of cloned cells.
*
* @private
*/
Array.prototype.restoreClone = function (clone: Cell, cell: Cell, mapping: any) {
const source = cell.getTerminal(true);
if (source != null) {
const tmp = mapping[<string>ObjectIdentity.get(source)];
if (tmp != null) {
tmp.insertEdge(clone, true);
}
}
const target = cell.getTerminal(false);
if (target != null) {
const tmp = mapping[<string>ObjectIdentity.get(target)];
if (tmp != null) {
tmp.insertEdge(clone, false);
}
}
const childCount = clone.getChildCount();
for (let i = 0; i < childCount; i += 1) {
this.restoreClone(<Cell>clone.getChildAt(i), <Cell>cell.getChildAt(i), mapping);
}
};

View File

@ -97,14 +97,16 @@ class GraphHierarchyNode extends GraphAbstractHierarchyCell {
getPreviousLayerConnectedCells(layer: number): GraphAbstractHierarchyCell[] {
if (this.previousLayerConnectedCells == null) {
this.previousLayerConnectedCells = [];
this.previousLayerConnectedCells[0] = []; // new CellArray()??
this.previousLayerConnectedCells[0] = [];
for (let i = 0; i < this.connectsAsSource.length; i += 1) {
const edge = this.connectsAsSource[i];
if (edge.minRank === -1 || edge.minRank === layer - 1) {
// No dummy nodes in edge, add node of other side of edge
this.previousLayerConnectedCells[0].push(<GraphAbstractHierarchyCell>edge.target);
this.previousLayerConnectedCells[0].push(
<GraphAbstractHierarchyCell>edge.target
);
} else {
// Edge spans at least two layers, add edge
this.previousLayerConnectedCells[0].push(edge);

View File

@ -380,7 +380,7 @@ class GraphHierarchyModel {
* to create dummy nodes for edges that cross layers.
*/
fixRanks(): void {
// TODO: Should this be a CellArray?
// TODO: Should this be a Cell[]?
const rankList: { [key: number]: GraphAbstractHierarchyCell[] } = {};
this.ranks = [];

View File

@ -30,6 +30,7 @@ import Point from '../geometry/Point';
import { htmlEntities } from '../../util/StringUtils';
import CellState from '../cell/CellState';
import { Graph } from '../Graph';
import { cloneCells, getTopmostCells } from '../../util/cellArrayUtils';
import type { CellStateStyle, CellStyle, NumericCellStateStyleKeys } from '../../types';
@ -90,8 +91,8 @@ declare module '../Graph' {
) => Cell;
cloneCells: (
cells: Cell[],
allowInvalidEdges: boolean,
mapping: any,
allowInvalidEdges?: boolean,
mapping?: any,
keepPosition?: boolean
) => Cell[];
addCell: (
@ -144,9 +145,9 @@ declare module '../Graph' {
cells: Cell[],
dx: number,
dy: number,
target: Cell | null,
evt: MouseEvent | null,
mapping: any
target?: Cell | null,
evt?: MouseEvent | null,
mapping?: any
) => Cell[];
moveCells: (
cells: Cell[],
@ -836,7 +837,6 @@ export const CellsMixin: PartialType = {
* @param keepPosition Optional boolean indicating if the position of the cells should
* be updated to reflect the lost parent cell. Default is `false`.
*/
// cloneCell(cell: mxCell, allowInvalidEdges?: boolean, mapping?: any, keepPosition?: boolean): mxCellArray;
cloneCell(cell, allowInvalidEdges = false, mapping = null, keepPosition = false) {
return this.cloneCells([cell], allowInvalidEdges, mapping, keepPosition)[0];
},
@ -854,7 +854,6 @@ export const CellsMixin: PartialType = {
* @param keepPosition Optional boolean indicating if the position of the cells should
* be updated to reflect the lost parent cell. Default is `false`.
*/
// cloneCells(cells: mxCellArray, allowInvalidEdges?: boolean, mapping?: any, keepPosition?: boolean): mxCellArray;
cloneCells(cells, allowInvalidEdges = true, mapping = {}, keepPosition = false) {
let clones: Cell[];
@ -871,7 +870,7 @@ export const CellsMixin: PartialType = {
const { scale } = this.getView();
const trans = this.getView().translate;
const out: Cell[] = [];
clones = cells.cloneCells(true, mapping);
clones = cloneCells(true, mapping)(cells);
for (let i = 0; i < cells.length; i += 1) {
const cell = cells[i];
@ -1852,7 +1851,7 @@ export const CellsMixin: PartialType = {
) {
if (dx !== 0 || dy !== 0 || clone || target) {
// Removes descendants with ancestors in cells to avoid multiple moving
cells = cells.getTopmostCells();
cells = getTopmostCells(cells);
const origCells = cells;
this.batchUpdate(() => {

View File

@ -10,6 +10,7 @@ import {
GraphDataModel,
styleUtils,
stringUtils,
cellArrayUtils,
} from '@maxgraph/core';
import { globalTypes } from '../.storybook/preview';
@ -154,7 +155,7 @@ const Template = ({ label, ...args }) => {
if (graph.isEnabled() && !graph.isSelectionEmpty()) {
copyCells(
graph,
utils.sortCells(graph.model.getTopmostCells(graph.getSelectionCells()))
styleUtils.sortCells(cellArrayUtils.getTopmostCells(graph.getSelectionCells()))
);
dx = 0;
dy = 0;
@ -177,7 +178,7 @@ const Template = ({ label, ...args }) => {
let cells = [];
try {
const doc = utils.parseXml(xml);
const doc = xmlUtils.parseXml(xml);
const node = doc.documentElement;
if (node != null) {

View File

@ -1,4 +1,9 @@
import { Graph, RubberBandHandler, SelectionHandler, PopupMenuHandler } from '@maxgraph/core';
import {
Graph,
RubberBandHandler,
SelectionHandler,
PopupMenuHandler,
} from '@maxgraph/core';
import { globalTypes } from '../.storybook/preview';
@ -41,8 +46,9 @@ const Template = ({ label, ...args }) => {
const graphHandlerGetInitialCellForEvent =
SelectionHandler.prototype.getInitialCellForEvent;
SelectionHandler.prototype.getInitialCellForEvent = function (me) {
const model = this.graph.getDataModel();
const psel = this.graph.getSelectionCell().getParent();
const psel = this.graph.getSelectionCell()
? this.graph.getSelectionCell().getParent()
: null;
let cell = graphHandlerGetInitialCellForEvent.apply(this, arguments);
let parent = cell.getParent();
@ -65,8 +71,9 @@ const Template = ({ label, ...args }) => {
const graphHandlerIsDelayedSelection = SelectionHandler.prototype.isDelayedSelection;
SelectionHandler.prototype.isDelayedSelection = function (cell) {
let result = graphHandlerIsDelayedSelection.apply(this, arguments);
const model = this.graph.getDataModel();
const psel = this.graph.getSelectionCell().getParent();
const psel = this.graph.getSelectionCell()
? this.graph.getSelectionCell().getParent()
: null;
const parent = cell.getParent();
if (psel == null || (psel != cell && psel != parent)) {