Change CellArray to cellArrayUtils.

development
Junsik Shim 2022-09-17 11:30:23 +09:00
parent 722021006d
commit ec7a525ea0
11 changed files with 234 additions and 252 deletions

View File

@ -161,6 +161,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

@ -18,6 +18,7 @@ limitations under the License.
import Cell from '../view/cell/Cell';
import { Graph } from '../view/Graph';
import { getTopmostCells } from './cellArrayUtils';
/**
* @class
@ -151,7 +152,6 @@ class Clipboard {
* @param cells - Array of {@link mxCell} to be cut.
*/
static removeCells(graph: Graph, cells: Cell[]) {
// @ts-ignore
graph.removeCells(cells);
}
@ -165,9 +165,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;
}
@ -190,7 +190,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

@ -60,8 +60,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

@ -34,6 +34,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';
@ -338,7 +339,7 @@ export class GraphDataModel extends EventSource {
}
filterCells(cells: Cell[], filter: FilterFunction) {
return cells.filterCells(filter);
return filterCells(filter)(cells);
}
getRoot(cell: Cell | null = null) {
@ -1168,7 +1169,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,225 +0,0 @@
/*
Copyright 2021-present The maxGraph project Contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
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

@ -109,14 +109,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

@ -391,7 +391,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

@ -46,6 +46,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';
@ -106,8 +107,8 @@ declare module '../Graph' {
) => Cell;
cloneCells: (
cells: Cell[],
allowInvalidEdges: boolean,
mapping: any,
allowInvalidEdges?: boolean,
mapping?: any,
keepPosition?: boolean
) => Cell[];
addCell: (
@ -160,9 +161,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[],
@ -852,7 +853,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];
},
@ -870,7 +870,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[];
@ -887,7 +886,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];
@ -1868,7 +1867,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

@ -27,6 +27,7 @@ import {
GraphDataModel,
styleUtils,
stringUtils,
cellArrayUtils,
} from '@maxgraph/core';
import { globalTypes } from '../.storybook/preview';
@ -171,7 +172,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;
@ -194,7 +195,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

@ -15,7 +15,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { Graph, RubberBandHandler, SelectionHandler, PopupMenuHandler } from '@maxgraph/core';
import {
Graph,
RubberBandHandler,
SelectionHandler,
PopupMenuHandler,
} from '@maxgraph/core';
import { globalTypes } from '../.storybook/preview';
@ -58,8 +63,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();
@ -82,8 +88,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)) {