devexpress-diagram
Version:
DevExpress Diagram Control
280 lines (259 loc) • 10.8 kB
text/typescript
import { Shape } from "./Shapes/Shape";
import { Connector } from "./Connectors/Connector";
import { DiagramItem, ItemKey, ItemDataKey } from "./DiagramItem";
import { GeometryUtils } from "../Utils";
import { Rectangle } from "@devexpress/utils/lib/geometry/rectangle";
import { Size } from "@devexpress/utils/lib/geometry/size";
import { Point } from "@devexpress/utils/lib/geometry/point";
import { ImageCache } from "../Images/ImageCache";
import { ImageLoader } from "../Images/ImageLoader";
import { ColorUtils } from "@devexpress/utils/lib/utils/color";
import { ShapeDescription } from "./Shapes/Descriptions/ShapeDescription";
import { DiagramUnit } from "../Enums";
import { ModelUtils } from "./ModelUtils";
export class DiagramModel {
items: DiagramItem[] = [];
private rectangle: Rectangle;
private itemIndexByKey: {[key: string]: number} = {};
private keyCounter: number = 0;
static defaultPageColor: number = ColorUtils.LIGHT_COLOR;
pageSize: Size = new Size(8391, 11906);
pageLandscape: boolean = false;
pageColor: number = DiagramModel.defaultPageColor;
size: Size;
units: DiagramUnit = DiagramUnit.In;
snapStartPoint = new Point(0, 0);
constructor(pageSize: Size = new Size(8391, 11906)) {
this.pageSize = pageSize;
this.size = this.pageSize.clone();
this.rectangle = Rectangle.fromGeometry(new Point(0, 0), new Size(0, 0));
this.initializeKeyCounter();
}
get pageWidth() {
return this.pageLandscape ? this.pageSize.height : this.pageSize.width;
}
get pageHeight() {
return this.pageLandscape ? this.pageSize.width : this.pageSize.height;
}
getRectangle(forceUpdate: boolean) {
if(forceUpdate)
this.rectangle = ModelUtils.createRectangle(this.items);
return this.rectangle;
}
pushItem(item: DiagramItem) {
const index = this.items.push(item);
this.itemIndexByKey[item.key] = index - 1;
if(item instanceof Shape && !item.image.isEmpty)
this.cacheShapeImage(item);
}
removeItem(item: DiagramItem) {
const index = this.getItemIndex(item);
delete this.itemIndexByKey[item.key];
this.items.splice(index, 1);
this.updateIndicesHash(index);
}
private updateIndicesHash(startIndex: number) {
for(let i = startIndex; i < this.items.length; i++)
this.itemIndexByKey[this.items[i].key] = i;
}
getItemIndex(item: DiagramItem): number {
return this.itemIndexByKey[item.key];
}
findShape(key: ItemKey): Shape {
const shape = this.findItem(key);
return shape instanceof Shape ? shape : undefined;
}
private findShapesCore(callback: (shape: Shape) => boolean): Shape[] {
const shapes: Shape[] = [];
this.items.forEach(item => {
if(item instanceof Shape)
if(callback(item)) {
shapes.push(item);
return;
}
});
return shapes;
}
private findShapeCore(callback: (shape: Shape) => boolean): Shape {
for(let i = 0; i < this.items.length; i++) {
const item = this.items[i];
if(item instanceof Shape)
if(callback(item))
return item;
}
}
findShapeAtPosition(position: Point): Shape {
return this.findShapeCore(shape => shape.position.equals(position));
}
findShapeByDataKey(key: ItemDataKey): Shape {
return this.findShapeCore(shape => shape.dataKey === key);
}
findShapesByImageUrl(imageUrl: string): Shape[] {
return this.findShapesCore(shape => shape.image.url === imageUrl);
}
findShapesByDescription(description: ShapeDescription): Shape[] {
return this.findShapesCore(shape => shape.description.key === description.key);
}
cacheShapeImage(shape: Shape) {
const cacheImageInfo = ImageCache.instance.createUnloadedInfoByShapeImageInfo(shape.image);
if(cacheImageInfo.isLoaded)
shape.image.loadBase64Content(cacheImageInfo.base64);
}
loadAllImages(imageLoader: ImageLoader) {
ImageCache.instance.loadAllImages(imageLoader);
}
findContainer(key: ItemKey): Shape {
const shape = this.findShape(key);
return shape && shape.enableChildren ? shape : undefined;
}
findNearestContainer(key: ItemKey): Shape {
const shape = this.findShape(key);
if(shape)
return shape.enableChildren ? shape : shape.container;
else
return undefined;
}
getChildren(container: Shape): DiagramItem[] {
return container.children.map(child => this.findItem(child.key)).filter(item => item);
}
findChild(container: Shape, key: ItemKey, recursive: boolean = true): DiagramItem {
let result: DiagramItem;
container.children.forEach(child => {
if(result) return;
if(child.key === key) {
result = child;
return;
}
if(recursive && child instanceof Shape) {
result = this.findChild(child, key, recursive);
if(result)
return;
}
});
return result;
}
findItemContainerCore(item: DiagramItem, callback?: (shape: Shape) => boolean): Shape {
let container = item.container;
while(container) {
if(!callback || callback(container))
break;
container = container.container;
}
return container;
}
findItemContainer(item: DiagramItem): Shape {
return this.findItemContainerCore(item);
}
findItemCollapsedContainer(item: DiagramItem): Shape {
return this.findItemContainerCore(item, c => !c.expanded);
}
findItemTopCollapsedContainer(item: DiagramItem): Shape {
let container = item.container;
let collapsedContainer: Shape;
while(container) {
if(!container.expanded)
collapsedContainer = container;
container = container.container;
}
return collapsedContainer;
}
isContainerItem(container: Shape, item: DiagramItem): boolean {
return this.findItemContainerCore(item, c => c.key === container.key) !== undefined;
}
findConnector(key: ItemKey): Connector {
const connector = this.findItem(key);
return connector instanceof Connector ? connector : undefined;
}
private findConnectorCore(callback: (connector: Connector) => boolean): Connector {
for(let i = 0; i < this.items.length; i++) {
const item = this.items[i];
if(item instanceof Connector)
if(callback(item))
return item;
}
}
findConnectorAtPoints(points: Point[]): Connector {
return this.findConnectorCore(connector => GeometryUtils.arePointsEqual(connector.points, points));
}
findConnectorByDataKey(key: ItemDataKey): Connector {
return this.findConnectorCore(connector => connector.dataKey === key);
}
findConnectorByBeginEndDataKeys(beginDataKey: ItemDataKey, endDataKey: ItemDataKey): Connector {
return this.findConnectorCore(connector => (connector.beginItem && connector.beginItem.dataKey === beginDataKey) &&
(connector.endItem && connector.endItem.dataKey === endDataKey));
}
findConnectorsCore(callback: (connector: Connector) => boolean): Connector[] {
const result: Connector[] = [];
this.items.forEach(item => {
if(item instanceof Connector)
if(callback(item)) {
result.push(item);
return;
}
});
return result;
}
findConnectorsWithoutBeginItem(): Connector[] {
return this.findConnectorsCore(connector => !connector.beginItem);
}
findConnectorsWithoutEndItem(): Connector[] {
return this.findConnectorsCore(connector => !connector.endItem);
}
findItem(key: ItemKey): DiagramItem {
return this.items[this.itemIndexByKey[key]];
}
findItemByDataKey(key: ItemDataKey): DiagramItem {
return this.findItemCore(item => item.dataKey === key);
}
private findItemCore(callback: (item: DiagramItem) => boolean): DiagramItem {
for(let i = 0; i < this.items.length; i++)
if(callback(this.items[i]))
return this.items[i];
}
static isIntersectedItems(item1: DiagramItem, item2: DiagramItem): boolean {
let result = false;
if(item1 instanceof Shape)
result = item2.intersectedByRect(item1.rectangle);
else if(item1 instanceof Connector)
item1.getSegments().forEach(s1 => {
if(item2 instanceof Shape)
result = result || s1.isIntersectedByRect(item2.rectangle);
else if(item2 instanceof Connector)
item2.getSegments().forEach(s2 => {
result = result || s1.isIntersected(s2);
});
});
return result;
}
getIntersectItems(item: DiagramItem): DiagramItem[] {
const result = [];
this.items.forEach(i => {
if(i.container !== item.container)
return;
if(item !== i && (!(i instanceof Connector) || item.attachedConnectors.indexOf(i) === -1) &&
DiagramModel.isIntersectedItems(i, item))
result.push(i);
});
return result;
}
getIntersectItemsMinZIndex(item: DiagramItem): number {
const items = this.getIntersectItems(item);
return items.map(i => i.zIndex).reduce((prev, cur) => Math.min(prev, cur), Number.MAX_VALUE);
}
getIntersectItemsMaxZIndex(item: DiagramItem): number {
const items = this.getIntersectItems(item);
return items.map(i => i.zIndex).reduce((prev, cur) => Math.max(prev, cur), -Number.MAX_VALUE);
}
iterateItems(callback: (item: DiagramItem) => void): void {
this.items.forEach(callback);
}
getNextKey(): ItemKey {
return (this.keyCounter++).toString();
}
initializeKeyCounter(): void {
this.keyCounter = this.items.reduce((prev, cur) => {
const num = parseInt(cur.key);
return Math.max(prev, isNaN(num) ? 0 : num + 1);
}, this.items.length);
}
}