/* * Copyright [2021] [wisemapping] * * Licensed under WiseMapping Public License, Version 1.0 (the "License"). * It is basically the Apache License, Version 2.0 (the "License") plus the * "powered by wisemapping" text requirement on every single page; * you may not use this file except in compliance with the License. * You may obtain a copy of the license at * * http://www.wisemapping.org/license * * 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 $ from 'jquery'; import { $assert, $defined } from '@wisemapping/core-js'; import Events from '../Events'; import RootedTreeSet from './RootedTreeSet'; import OriginalLayout from './OriginalLayout'; import ChangeEvent from './ChangeEvent'; import SizeType from '../SizeType'; import Node from './Node'; import PositionType from '../PositionType'; class LayoutManager extends Events { private _treeSet: RootedTreeSet; private _layout: OriginalLayout; private _events: ChangeEvent[]; constructor(rootNodeId: number, rootSize: SizeType) { super(); $assert($defined(rootNodeId), 'rootNodeId can not be null'); $assert(rootSize, 'rootSize can not be null'); this._treeSet = new RootedTreeSet(); this._layout = new OriginalLayout(this._treeSet); const rootNode = this._layout.createNode(rootNodeId, rootSize, { x: 0, y: 0 }, 'root'); this._treeSet.setRoot(rootNode); this._events = []; } updateNodeSize(id: number, size: SizeType): void { $assert($defined(id), 'id can not be null'); const node = this._treeSet.find(id); node.setSize(size); } updateShrinkState(id: number, value: boolean): void { $assert($defined(id), 'id can not be null'); $assert($defined(value), 'value can not be null'); const node = this._treeSet.find(id); node.setShrunken(value); } find(id: number): Node { return this._treeSet.find(id); } /** * @param id * @param position * @throws will throw an error if id is null or undefined * @throws will throw an error if position is null or undefined * @throws will throw an error if the position's x property is null or undefined * @throws will throw an error if the position's y property is null or undefined */ moveNode(id: number, position: PositionType) { $assert($defined(id), 'id cannot be null'); $assert($defined(position), 'position cannot be null'); $assert($defined(position.x), 'x can not be null'); $assert($defined(position.y), 'y can not be null'); const node = this._treeSet.find(id); node.setPosition(position); } connectNode(parentId: number, childId: number, order: number) { $assert($defined(parentId), 'parentId cannot be null'); $assert($defined(childId), 'childId cannot be null'); $assert($defined(order), 'order cannot be null'); this._layout.connectNode(parentId, childId, order); return this; } disconnectNode(id: number): void { $assert($defined(id), 'id can not be null'); this._layout.disconnectNode(id); } /** * @param id * @param size * @param position * @throws will throw an error if id is null or undefined * @return this */ addNode(id: number, size: SizeType, position: PositionType) { $assert($defined(id), 'id can not be null'); const result = this._layout.createNode(id, size, position, 'topic'); this._treeSet.add(result); return this; } removeNode(id: number) { $assert($defined(id), 'id can not be null'); const node = this._treeSet.find(id); // Is It connected ? if (this._treeSet.getParent(node)) { this.disconnectNode(id); } // Remove the all the branch ... this._treeSet.remove(id); return this; } predict(parentId: number, nodeId: number, position: PositionType): { order: number, position: PositionType } { $assert($defined(parentId), 'parentId can not be null'); const parent = this._treeSet.find(parentId); const node = nodeId ? this._treeSet.find(nodeId) : null; const sorter = parent.getSorter(); const result = sorter.predict(this._treeSet, parent, node, position); return { order: result[0], position: result[1] }; } dump() { console.log(this._treeSet.dump()); } plot(containerId: string, size = { width: 200, height: 200 }) { // this method is only used from tests that include Raphael if (!globalThis.Raphael) { console.warn('Raphael.js not found, exiting plot()'); return null; } $assert(containerId, 'containerId cannot be null'); const squaresize = 10; const canvas = globalThis.Raphael(containerId, size.width, size.height); canvas.drawGrid( 0, 0, size.width, size.height, size.width / squaresize, size.height / squaresize, ); this._treeSet.plot(canvas); return canvas; } layout(flush: boolean): LayoutManager { // File repositioning ... this._layout.layout(); // Collect changes ... this._collectChanges(this._treeSet.getTreeRoots()); if (flush) { this._flushEvents(); } return this; } private _flushEvents() { this._events.forEach(((event) => { this.fireEvent('change', event); })); this._events = []; } private _collectChanges(nodes: Node[]) { nodes.forEach(((node) => { if (node.hasOrderChanged() || node.hasPositionChanged()) { // Find or create a event ... const id = node.getId(); let event: ChangeEvent = this._events.find((e) => e.getId() === id); if (!event) { event = new ChangeEvent(id); } // Update nodes ... event.setOrder(node.getOrder()); event.setPosition(node.getPosition()); node.resetPositionState(); node.resetOrderState(); node.resetFreeState(); this._events.push(event); } this._collectChanges(this._treeSet.getChildren(node)); })); } } export default LayoutManager;