2022-08-30 15:36:33 +00:00
|
|
|
/*
|
|
|
|
Copyright 2021-present The maxGraph project Contributors
|
|
|
|
Copyright (c) 2006-2020, JGraph Ltd
|
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2021-09-07 09:07:27 +00:00
|
|
|
import {
|
|
|
|
Graph,
|
|
|
|
InternalEvent,
|
2022-01-08 01:49:35 +00:00
|
|
|
RubberBandHandler,
|
2021-09-07 09:07:27 +00:00
|
|
|
Rectangle,
|
|
|
|
Point,
|
2022-01-08 01:49:35 +00:00
|
|
|
styleUtils,
|
2021-09-07 09:07:27 +00:00
|
|
|
} from '@maxgraph/core';
|
2021-04-19 12:56:58 +00:00
|
|
|
|
2021-04-21 04:54:09 +00:00
|
|
|
import { globalTypes } from '../.storybook/preview';
|
2021-04-19 12:56:58 +00:00
|
|
|
|
|
|
|
export default {
|
|
|
|
title: 'Backgrounds/ExtendCanvas',
|
|
|
|
argTypes: {
|
2021-04-21 04:54:09 +00:00
|
|
|
...globalTypes,
|
2021-04-20 12:22:55 +00:00
|
|
|
contextMenu: {
|
|
|
|
type: 'boolean',
|
2021-08-30 14:20:26 +00:00
|
|
|
defaultValue: false,
|
2021-04-20 12:22:55 +00:00
|
|
|
},
|
|
|
|
rubberBand: {
|
|
|
|
type: 'boolean',
|
2021-08-30 14:20:26 +00:00
|
|
|
defaultValue: true,
|
|
|
|
},
|
|
|
|
},
|
2021-04-19 12:56:58 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const Template = ({ label, ...args }) => {
|
|
|
|
const container = document.createElement('div');
|
|
|
|
container.style.position = 'relative';
|
|
|
|
container.style.overflow = 'auto';
|
|
|
|
container.style.width = `${args.width}px`;
|
|
|
|
container.style.height = `${args.height}px`;
|
|
|
|
container.style.cursor = 'default';
|
|
|
|
container.style.background = 'url(/images/grid.gif)';
|
|
|
|
|
2021-08-30 14:20:26 +00:00
|
|
|
if (!args.contextMenu) InternalEvent.disableContextMenu(container);
|
2021-04-19 12:56:58 +00:00
|
|
|
|
|
|
|
/**
|
2021-08-30 14:20:26 +00:00
|
|
|
* Specifies the size of the size for "tiles" to be used for a graph with
|
|
|
|
* scrollbars but no visible background page. A good value is large
|
|
|
|
* enough to reduce the number of repaints that is caused for auto-
|
|
|
|
* translation, which depends on this value, and small enough to give
|
|
|
|
* a small empty buffer around the graph. Default is 400x400.
|
|
|
|
*/
|
|
|
|
const scrollTileSize = new Rectangle(0, 0, 400, 400);
|
|
|
|
|
|
|
|
class MyCustomGraph extends Graph {
|
|
|
|
/**
|
|
|
|
* Returns the padding for pages in page view with scrollbars.
|
2021-04-19 12:56:58 +00:00
|
|
|
*/
|
2021-08-30 14:20:26 +00:00
|
|
|
getPagePadding() {
|
|
|
|
return new Point(
|
|
|
|
Math.max(0, Math.round(this.container.offsetWidth - 34)),
|
|
|
|
Math.max(0, Math.round(this.container.offsetHeight - 34))
|
|
|
|
);
|
|
|
|
}
|
2021-04-19 12:56:58 +00:00
|
|
|
|
2021-08-30 14:20:26 +00:00
|
|
|
/**
|
|
|
|
* Returns the size of the page format scaled with the page size.
|
|
|
|
*/
|
|
|
|
getPageSize() {
|
|
|
|
return this.pageVisible
|
|
|
|
? new Rectangle(
|
2021-04-19 12:56:58 +00:00
|
|
|
0,
|
|
|
|
0,
|
|
|
|
this.pageFormat.width * this.pageScale,
|
2021-08-30 14:20:26 +00:00
|
|
|
this.pageFormat.height * this.pageScale
|
|
|
|
)
|
|
|
|
: scrollTileSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a rectangle describing the position and count of the
|
|
|
|
* background pages, where x and y are the position of the top,
|
|
|
|
* left page and width and height are the vertical and horizontal
|
|
|
|
* page count.
|
|
|
|
*/
|
|
|
|
getPageLayout() {
|
|
|
|
const size = this.pageVisible ? this.getPageSize() : scrollTileSize;
|
|
|
|
const bounds = this.getGraphBounds();
|
|
|
|
|
|
|
|
if (bounds.width === 0 || bounds.height === 0) {
|
|
|
|
return new Rectangle(0, 0, 1, 1);
|
2021-04-19 12:56:58 +00:00
|
|
|
}
|
|
|
|
|
2021-08-30 14:20:26 +00:00
|
|
|
// Computes untransformed graph bounds
|
|
|
|
const x = Math.ceil(bounds.x / this.view.scale - this.view.translate.x);
|
|
|
|
const y = Math.ceil(bounds.y / this.view.scale - this.view.translate.y);
|
|
|
|
const w = Math.floor(bounds.width / this.view.scale);
|
|
|
|
const h = Math.floor(bounds.height / this.view.scale);
|
2021-04-19 12:56:58 +00:00
|
|
|
|
2021-08-30 14:20:26 +00:00
|
|
|
const x0 = Math.floor(x / size.width);
|
|
|
|
const y0 = Math.floor(y / size.height);
|
|
|
|
const w0 = Math.ceil((x + w) / size.width) - x0;
|
|
|
|
const h0 = Math.ceil((y + h) / size.height) - y0;
|
2021-04-19 12:56:58 +00:00
|
|
|
|
2021-08-30 14:20:26 +00:00
|
|
|
return new Rectangle(x0, y0, w0, h0);
|
|
|
|
}
|
2021-04-19 12:56:58 +00:00
|
|
|
|
2021-08-30 14:20:26 +00:00
|
|
|
getPreferredPageSize(bounds, width, height) {
|
|
|
|
const pages = this.getPageLayout();
|
|
|
|
const size = this.getPageSize();
|
|
|
|
|
|
|
|
return new Rectangle(0, 0, pages.width * size.width, pages.height * size.height);
|
|
|
|
}
|
2021-04-19 12:56:58 +00:00
|
|
|
|
2021-08-30 14:20:26 +00:00
|
|
|
sizeDidChange() {
|
2022-01-08 01:49:35 +00:00
|
|
|
if (this.container != null && styleUtils.hasScrollbars(this.container)) {
|
2021-04-19 12:56:58 +00:00
|
|
|
const pages = this.getPageLayout();
|
2021-08-30 14:20:26 +00:00
|
|
|
const pad = this.getPagePadding();
|
2021-04-19 12:56:58 +00:00
|
|
|
const size = this.getPageSize();
|
|
|
|
|
2021-08-30 14:20:26 +00:00
|
|
|
// Updates the minimum graph size
|
|
|
|
const minw = Math.ceil((2 * pad.x) / this.view.scale + pages.width * size.width);
|
|
|
|
const minh = Math.ceil(
|
|
|
|
(2 * pad.y) / this.view.scale + pages.height * size.height
|
2021-04-19 12:56:58 +00:00
|
|
|
);
|
|
|
|
|
2021-08-30 14:20:26 +00:00
|
|
|
const min = this.minimumGraphSize;
|
|
|
|
|
|
|
|
// LATER: Fix flicker of scrollbar size in IE quirks mode
|
|
|
|
// after delayed call in window.resize event handler
|
|
|
|
if (min == null || min.width !== minw || min.height !== minh) {
|
|
|
|
this.minimumGraphSize = new Rectangle(0, 0, minw, minh);
|
2021-04-19 12:56:58 +00:00
|
|
|
}
|
2021-08-30 14:20:26 +00:00
|
|
|
|
|
|
|
// Updates auto-translate to include padding and graph size
|
|
|
|
const dx = pad.x / this.view.scale - pages.x * size.width;
|
|
|
|
const dy = pad.y / this.view.scale - pages.y * size.height;
|
|
|
|
|
|
|
|
if (
|
|
|
|
!this.autoTranslate &&
|
|
|
|
(this.view.translate.x !== dx || this.view.translate.y !== dy)
|
|
|
|
) {
|
|
|
|
this.autoTranslate = true;
|
|
|
|
this.view.x0 = pages.x;
|
|
|
|
this.view.y0 = pages.y;
|
|
|
|
|
|
|
|
// NOTE: THIS INVOKES THIS METHOD AGAIN. UNFORTUNATELY THERE IS NO WAY AROUND THIS SINCE THE
|
|
|
|
// BOUNDS ARE KNOWN AFTER THE VALIDATION AND SETTING THE TRANSLATE TRIGGERS A REVALIDATION.
|
|
|
|
// SHOULD MOVE TRANSLATE/SCALE TO VIEW.
|
|
|
|
const tx = this.view.translate.x;
|
|
|
|
const ty = this.view.translate.y;
|
|
|
|
|
|
|
|
this.view.setTranslate(dx, dy);
|
|
|
|
this.container.scrollLeft += (dx - tx) * this.view.scale;
|
|
|
|
this.container.scrollTop += (dy - ty) * this.view.scale;
|
|
|
|
|
|
|
|
this.autoTranslate = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
super.sizeDidChange();
|
2021-04-19 12:56:58 +00:00
|
|
|
}
|
|
|
|
}
|
2021-08-30 14:20:26 +00:00
|
|
|
}
|
2021-04-19 12:56:58 +00:00
|
|
|
|
2021-08-30 14:20:26 +00:00
|
|
|
// Creates the graph inside the given container
|
|
|
|
const graph = new MyCustomGraph(container);
|
2021-09-10 05:17:59 +00:00
|
|
|
const panningHandler = graph.getPlugin('PanningHandler');
|
|
|
|
panningHandler.ignoreCell = true;
|
2021-08-30 14:20:26 +00:00
|
|
|
graph.setPanning(true);
|
2021-04-19 12:56:58 +00:00
|
|
|
|
2021-08-30 14:20:26 +00:00
|
|
|
// Fits the number of background pages to the graph
|
|
|
|
graph.view.getBackgroundPageBounds = function () {
|
|
|
|
const layout = this.graph.getPageLayout();
|
|
|
|
const page = this.graph.getPageSize();
|
2021-04-19 12:56:58 +00:00
|
|
|
|
2021-08-30 14:20:26 +00:00
|
|
|
return new Rectangle(
|
|
|
|
this.scale * (this.translate.x + layout.x * page.width),
|
|
|
|
this.scale * (this.translate.y + layout.y * page.height),
|
|
|
|
this.scale * layout.width * page.width,
|
|
|
|
this.scale * layout.height * page.height
|
|
|
|
);
|
|
|
|
};
|
2021-04-19 12:56:58 +00:00
|
|
|
|
2021-08-30 14:20:26 +00:00
|
|
|
/**
|
|
|
|
* Guesses autoTranslate to avoid another repaint (see below).
|
|
|
|
* Works if only the scale of the graph changes or if pages
|
|
|
|
* are visible and the visible pages do not change.
|
|
|
|
*/
|
|
|
|
const graphViewValidate = graph.view.validate;
|
|
|
|
graph.view.validate = function () {
|
2022-01-08 01:49:35 +00:00
|
|
|
if (this.graph.container != null && styleUtils.hasScrollbars(this.graph.container)) {
|
2021-08-30 14:20:26 +00:00
|
|
|
const pad = this.graph.getPagePadding();
|
|
|
|
const size = this.graph.getPageSize();
|
|
|
|
|
|
|
|
// Updating scrollbars here causes flickering in quirks and is not needed
|
|
|
|
// if zoom method is always used to set the current scale on the graph.
|
|
|
|
const tx = this.translate.x;
|
|
|
|
const ty = this.translate.y;
|
|
|
|
this.translate.x = pad.x / this.scale - (this.x0 || 0) * size.width;
|
|
|
|
this.translate.y = pad.y / this.scale - (this.y0 || 0) * size.height;
|
|
|
|
}
|
|
|
|
|
|
|
|
graphViewValidate.apply(this, arguments);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Enables rubberband selection
|
2022-01-08 01:49:35 +00:00
|
|
|
if (args.rubberBand) new RubberBandHandler(graph);
|
2021-08-30 14:20:26 +00:00
|
|
|
|
|
|
|
// Gets the default parent for inserting new cells. This
|
|
|
|
// is normally the first child of the root (ie. layer 0).
|
|
|
|
const parent = graph.getDefaultParent();
|
|
|
|
|
|
|
|
// Adds cells to the model in a single step
|
|
|
|
graph.batchUpdate(() => {
|
|
|
|
const v1 = graph.insertVertex({
|
|
|
|
parent,
|
|
|
|
value: 'Hello,',
|
|
|
|
position: [20, 20],
|
|
|
|
size: [80, 30],
|
|
|
|
});
|
|
|
|
const v2 = graph.insertVertex({
|
|
|
|
parent,
|
|
|
|
value: 'World!',
|
|
|
|
position: [200, 150],
|
|
|
|
size: [80, 30],
|
|
|
|
});
|
|
|
|
const e1 = graph.insertEdge({
|
|
|
|
parent,
|
|
|
|
source: v1,
|
|
|
|
target: v2,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
// Sets initial scrollbar positions
|
|
|
|
window.setTimeout(() => {
|
|
|
|
const bounds = graph.getGraphBounds();
|
|
|
|
const width = Math.max(bounds.width, scrollTileSize.width * graph.view.scale);
|
|
|
|
const height = Math.max(bounds.height, scrollTileSize.height * graph.view.scale);
|
|
|
|
graph.container.scrollTop = Math.floor(
|
|
|
|
Math.max(0, bounds.y - Math.max(20, (graph.container.clientHeight - height) / 4))
|
|
|
|
);
|
|
|
|
graph.container.scrollLeft = Math.floor(
|
|
|
|
Math.max(0, bounds.x - Math.max(0, (graph.container.clientWidth - width) / 2))
|
|
|
|
);
|
|
|
|
}, 0);
|
2021-04-19 12:56:58 +00:00
|
|
|
|
|
|
|
return container;
|
2021-08-30 14:20:26 +00:00
|
|
|
};
|
2021-04-19 12:56:58 +00:00
|
|
|
|
2021-08-30 14:20:26 +00:00
|
|
|
export const Default = Template.bind({});
|