Merge branch 'development' into development

development
Thomas Bouffard 2022-10-22 10:41:32 +02:00 committed by GitHub
commit ef76a1db87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 175 additions and 155 deletions

View File

@ -70,9 +70,6 @@ For more details, have a look at the [storybook stories](packages/html/stories).
maxGraph is written in TypeScript and provides type definitions so maxGraph can be easily integrated into TypeScript projects.
**WARN**: some definitions are currently buggy, so please set `skipLibCheck` to `true` in the `tsconfig.json` file of your project.
For more details, see issues [#96](https://github.com/maxGraph/maxGraph/issues/96) and [#105](https://github.com/maxGraph/maxGraph/issues/105#issuecomment-1240640369).
## Support

View File

@ -508,7 +508,6 @@ class SvgCanvas2D extends AbstractCanvas2D {
/**
* Private helper function to create SVG elements
*/
// createGradientId(start: string, end: string, alpha1: string, alpha2: string, direction: string): string;
createGradientId(
start: string,
end: string,
@ -562,13 +561,11 @@ class SvgCanvas2D extends AbstractCanvas2D {
alpha2: number,
direction: DirectionValue
) {
if (!this.root) return;
const id = this.createGradientId(start, end, alpha1, alpha2, direction);
let gradient: Gradient | null = this.gradients[id];
if (!gradient) {
const svg = this.root.ownerSVGElement;
const svg = this.root!.ownerSVGElement;
let counter = 0;
let tmpId = `${id}-${counter}`;
@ -649,8 +646,6 @@ class SvgCanvas2D extends AbstractCanvas2D {
* Private helper function to create SVG elements
*/
addNode(filled: boolean, stroked: boolean) {
if (!this.root) return;
const { node } = this;
const s = this.state;
@ -689,12 +684,12 @@ class SvgCanvas2D extends AbstractCanvas2D {
}
if (s.shadow) {
this.root.appendChild(this.createShadow(node));
this.root!.appendChild(this.createShadow(node));
}
// Adds stroke tolerance
if (this.strokeTolerance > 0 && !filled) {
this.root.appendChild(this.createTolerance(node));
this.root!.appendChild(this.createTolerance(node));
}
// Adds pointer events
@ -717,7 +712,7 @@ class SvgCanvas2D extends AbstractCanvas2D {
node.getAttribute('pointer-events') !== NONE
) {
// LATER: Update existing DOM for performance
this.root.appendChild(node);
this.root!.appendChild(node);
}
this.node = null;
@ -728,12 +723,10 @@ class SvgCanvas2D extends AbstractCanvas2D {
* Transfers the stroke attributes from <state> to <node>.
*/
updateFill() {
if (!this.node) return;
const s = this.state;
if (s.alpha < 1 || s.fillAlpha < 1) {
this.node.setAttribute('fill-opacity', String(s.alpha * s.fillAlpha));
this.node!.setAttribute('fill-opacity', String(s.alpha * s.fillAlpha));
}
if (s.fillColor !== NONE) {
@ -749,12 +742,12 @@ class SvgCanvas2D extends AbstractCanvas2D {
if (this.root?.ownerDocument === document && useAbsoluteIds) {
// Workaround for no fill with base tag in page (escape brackets)
const base = this.getBaseUrl().replace(/([\(\)])/g, '\\$1');
this.node.setAttribute('fill', `url(${base}#${id})`);
this.node!.setAttribute('fill', `url(${base}#${id})`);
} else {
this.node.setAttribute('fill', `url(#${id})`);
this.node!.setAttribute('fill', `url(#${id})`);
}
} else {
this.node.setAttribute('fill', s.fillColor.toLowerCase());
this.node!.setAttribute('fill', s.fillColor.toLowerCase());
}
}
}
@ -762,7 +755,6 @@ class SvgCanvas2D extends AbstractCanvas2D {
/**
* Returns the current stroke width (>= 1), ie. max(1, this.format(this.state.strokeWidth * this.state.scale)).
*/
// getCurrentStrokeWidth(): number;
getCurrentStrokeWidth() {
return Math.max(
this.minStrokeWidth,
@ -774,29 +766,27 @@ class SvgCanvas2D extends AbstractCanvas2D {
* Transfers the stroke attributes from {@link mxAbstractCanvas2D.state} to {@link node}.
*/
updateStroke() {
if (!this.node) return;
const s = this.state;
if (s.strokeColor && s.strokeColor !== NONE) {
this.node.setAttribute('stroke', s.strokeColor.toLowerCase());
this.node!.setAttribute('stroke', s.strokeColor.toLowerCase());
}
if (s.alpha < 1 || s.strokeAlpha < 1) {
this.node.setAttribute('stroke-opacity', String(s.alpha * s.strokeAlpha));
this.node!.setAttribute('stroke-opacity', String(s.alpha * s.strokeAlpha));
}
const sw = this.getCurrentStrokeWidth();
if (sw !== 1) {
this.node.setAttribute('stroke-width', String(sw));
this.node!.setAttribute('stroke-width', String(sw));
}
if (this.node.nodeName === 'path') {
if (this.node!.nodeName === 'path') {
this.updateStrokeAttributes();
}
if (s.dashed) {
this.node.setAttribute(
this.node!.setAttribute(
'stroke-dasharray',
this.createDashPattern((s.fixDash ? 1 : s.strokeWidth) * s.scale)
);
@ -807,13 +797,11 @@ class SvgCanvas2D extends AbstractCanvas2D {
* Transfers the stroke attributes from {@link mxAbstractCanvas2D.state} to {@link node}.
*/
updateStrokeAttributes() {
if (!this.node) return;
const s = this.state;
// Linejoin miter is default in SVG
if (s.lineJoin && s.lineJoin !== 'miter') {
this.node.setAttribute('stroke-linejoin', s.lineJoin);
this.node!.setAttribute('stroke-linejoin', s.lineJoin);
}
if (s.lineCap) {
@ -826,13 +814,13 @@ class SvgCanvas2D extends AbstractCanvas2D {
// Linecap butt is default in SVG
if (value !== 'butt') {
this.node.setAttribute('stroke-linecap', value);
this.node!.setAttribute('stroke-linecap', value);
}
}
// Miterlimit 10 is default in our document
if (s.miterLimit != null && (!this.styleEnabled || s.miterLimit !== 10)) {
this.node.setAttribute('stroke-miterlimit', String(s.miterLimit));
this.node!.setAttribute('stroke-miterlimit', String(s.miterLimit));
}
}
@ -858,7 +846,6 @@ class SvgCanvas2D extends AbstractCanvas2D {
/**
* Creates a hit detection tolerance shape for the given node.
*/
// createTolerance(node: Element): Element;
createTolerance(node: SVGElement) {
const tol = node.cloneNode(true) as SVGElement;
const sw = parseFloat(tol.getAttribute('stroke-width') || '1') + this.strokeTolerance;
@ -909,8 +896,6 @@ class SvgCanvas2D extends AbstractCanvas2D {
* Experimental implementation for hyperlinks.
*/
setLink(link: string) {
if (!this.root) return;
if (!link) {
this.root = this.originalRoot;
} else {
@ -920,13 +905,13 @@ class SvgCanvas2D extends AbstractCanvas2D {
// Workaround for implicit namespace handling in HTML5 export, IE adds NS1 namespace so use code below
// in all IE versions except quirks mode. KNOWN: Adds xlink namespace to each image tag in output.
if (node.setAttributeNS == null || this.root.ownerDocument !== document) {
if (node.setAttributeNS == null || this.root!.ownerDocument !== document) {
node.setAttribute('xlink:href', link);
} else {
node.setAttributeNS(NS_XLINK, 'xlink:href', link);
}
this.root.appendChild(node);
this.root!.appendChild(node);
this.root = node;
}
}
@ -981,7 +966,6 @@ class SvgCanvas2D extends AbstractCanvas2D {
/**
* Extends superclass to create path.
*/
// begin(): void;
begin() {
super.begin();
this.node = this.createElement('path');
@ -1005,16 +989,14 @@ class SvgCanvas2D extends AbstractCanvas2D {
* Private helper function to create SVG elements
*/
roundrect(x: number, y: number, w: number, h: number, dx: number, dy: number) {
if (!this.node) return;
this.rect(x, y, w, h);
if (dx > 0) {
this.node.setAttribute('rx', String(this.format(dx * this.state.scale)));
this.node!.setAttribute('rx', String(this.format(dx * this.state.scale)));
}
if (dy > 0) {
this.node.setAttribute('ry', String(this.format(dy * this.state.scale)));
this.node!.setAttribute('ry', String(this.format(dy * this.state.scale)));
}
}
@ -1045,8 +1027,6 @@ class SvgCanvas2D extends AbstractCanvas2D {
flipH = false,
flipV = false
) {
if (!this.root) return;
src = this.converter.convert(src);
const s = this.state;
@ -1092,7 +1072,7 @@ class SvgCanvas2D extends AbstractCanvas2D {
dy = -h - 2 * y;
}
// Adds image tansformation to existing transform
// Adds image transformation to existing transform
tr += `scale(${sx},${sy})translate(${dx * s.scale},${dy * s.scale})`;
}
@ -1104,7 +1084,7 @@ class SvgCanvas2D extends AbstractCanvas2D {
node.setAttribute('pointer-events', 'none');
}
this.root.appendChild(node);
this.root!.appendChild(node);
}
/**
@ -1134,8 +1114,6 @@ class SvgCanvas2D extends AbstractCanvas2D {
* Note: signature changed in mxgraph 4.1.0
*/
createDiv(str: string | HTMLElement) {
if (!this.root) return;
let val = str;
if (!isNode(val)) {
@ -1152,7 +1130,7 @@ class SvgCanvas2D extends AbstractCanvas2D {
const div3 = div2.cloneNode(false);
// Creates a copy for export
if (this.root.ownerDocument !== document) {
if (this.root!.ownerDocument !== document) {
div2.appendChild(n.cloneNode(true));
} else {
div2.appendChild(n);
@ -1390,7 +1368,6 @@ class SvgCanvas2D extends AbstractCanvas2D {
/**
* Private helper function to create SVG elements
*/
// getTextCss(): string;
getTextCss() {
const s = this.state;
const lh = ABSOLUTE_LINE_HEIGHT
@ -1450,8 +1427,6 @@ class SvgCanvas2D extends AbstractCanvas2D {
rotation = 0,
dir: TextDirectionValue
) {
if (!this.root) return;
if (this.textEnabled && str != null) {
rotation = rotation != null ? rotation : 0;
@ -1479,7 +1454,7 @@ class SvgCanvas2D extends AbstractCanvas2D {
rotation,
dir,
div,
this.root
this.root!
);
}
} else {
@ -1552,8 +1527,6 @@ class SvgCanvas2D extends AbstractCanvas2D {
rotation = 0,
dir: TextDirectionValue
) {
if (!this.root) return;
const s = this.state;
const size = s.fontSize;
const node = this.createElement('g');
@ -1604,13 +1577,13 @@ class SvgCanvas2D extends AbstractCanvas2D {
this.defs.appendChild(c);
} else {
// Makes sure clip is removed with referencing node
this.root.appendChild(c);
this.root!.appendChild(c);
}
if (
!Client.IS_CHROMEAPP &&
!Client.IS_EDGE &&
this.root.ownerDocument === document
this.root!.ownerDocument === document
) {
// Workaround for potential base tag
const base = this.getBaseUrl().replace(/([\(\)])/g, '\\$1');
@ -1684,7 +1657,7 @@ class SvgCanvas2D extends AbstractCanvas2D {
cy += lh;
}
this.root.appendChild(node);
this.root!.appendChild(node);
this.addTextBackground(
node,
str,

View File

@ -22,6 +22,7 @@ import Geometry from '../geometry/Geometry';
import Point from '../geometry/Point';
import { Graph } from '../Graph';
import Cell from '../cell/Cell';
import { GraphLayoutTraverseArgs } from './types';
/**
* @class GraphLayout
@ -141,13 +142,7 @@ class GraphLayout {
* null for the first step of the traversal.
* @param visited Optional {@link Dictionary} of cell paths for the visited cells.
*/
traverse(
vertex: Cell,
directed?: boolean,
func?: Function,
edge?: Cell,
visited?: Dictionary<Cell, boolean>
): void {
traverse({vertex, directed, func, edge, visited}: GraphLayoutTraverseArgs): void {
if (func != null && vertex != null) {
directed = directed != null ? directed : true;
visited = visited || new Dictionary();
@ -161,12 +156,18 @@ class GraphLayout {
if (edgeCount > 0) {
for (let i = 0; i < edgeCount; i += 1) {
const e = vertex.getEdgeAt(i);
const e: Cell = vertex.getEdgeAt(i);
const isSource = e.getTerminal(true) === vertex;
if (!directed || isSource) {
const next = this.graph.view.getVisibleTerminal(e, !isSource);
this.traverse(<Cell>next, directed, func, e, visited);
this.traverse({
vertex: next,
directed,
func,
edge: e,
visited
});
}
}
}

View File

@ -27,7 +27,7 @@ import MedianHybridCrossingReduction from './hierarchical/MedianHybridCrossingRe
import CoordinateAssignment from './hierarchical/CoordinateAssignment';
import { Graph } from '../../view/Graph';
import Cell from '../../view/cell/Cell';
import GraphHierarchyNode from './datatypes/GraphHierarchyNode';
import { HierarchicalGraphLayoutTraverseArgs } from './types';
/**
* A hierarchical layout algorithm.
@ -444,15 +444,17 @@ class HierarchicalLayout extends GraphLayout {
const vertexSet = Object();
hierarchyVertices.push(vertexSet);
this.traverse(
candidateRoots[i],
true,
null,
allVertexSet,
vertexSet,
hierarchyVertices,
filledVertexSet
);
this.traverse({
vertex: candidateRoots[i],
directed: true,
edge: null,
allVertices: allVertexSet,
currentComp: vertexSet,
hierarchyVertices: hierarchyVertices,
filledVertexSet: filledVertexSet,
func: null,
visited: null
});
}
for (let i = 0; i < candidateRoots.length; i += 1) {
@ -477,15 +479,17 @@ class HierarchicalLayout extends GraphLayout {
const vertexSet = Object();
hierarchyVertices.push(vertexSet);
this.traverse(
roots[i],
true,
null,
allVertexSet,
vertexSet,
hierarchyVertices,
null
);
this.traverse({
vertex: roots[i],
directed: true,
edge: null,
allVertices: allVertexSet,
currentComp: vertexSet,
hierarchyVertices: hierarchyVertices,
filledVertexSet: null,
func: null,
visited: null
});
}
}
@ -600,16 +604,14 @@ class HierarchicalLayout extends GraphLayout {
* null for the first step of the traversal.
* @param allVertices Array of cell paths for the visited cells.
*/
// @ts-ignore
traverse(
vertex: Cell,
directed: boolean = false,
edge: Cell | null = null,
allVertices: { [key: string]: Cell } | null = null,
currentComp: { [key: string]: Cell | null },
hierarchyVertices: GraphHierarchyNode[],
filledVertexSet: { [key: string]: Cell } | null = null
) {
traverse({
vertex,
directed,
allVertices,
currentComp,
hierarchyVertices,
filledVertexSet
}: HierarchicalGraphLayoutTraverseArgs) {
if (vertex != null && allVertices != null) {
// Has this vertex been seen before in any traversal
// And if the filled vertex set is populated, only
@ -666,15 +668,17 @@ class HierarchicalLayout extends GraphLayout {
}
if (netCount >= 0) {
currentComp = this.traverse(
<Cell>next,
currentComp = this.traverse({
vertex: next,
directed,
edges[i],
edge: edges[i],
allVertices,
currentComp,
hierarchyVertices,
filledVertexSet
);
filledVertexSet,
func: null,
visited: null
});
}
}
}

View File

@ -29,7 +29,7 @@ import CoordinateAssignment from './hierarchical/CoordinateAssignment';
import { Graph } from '../Graph';
import Cell from '../cell/Cell';
import Geometry from '../../view/geometry/Geometry';
import GraphHierarchyNode from './datatypes/GraphHierarchyNode';
import { SwimlaneGraphLayoutTraverseArgs } from './types';
/**
* A hierarchical layout algorithm.
@ -579,16 +579,18 @@ class SwimlaneLayout extends GraphLayout {
const vertexSet = Object();
hierarchyVertices.push(vertexSet);
this.traverse(
candidateRoots[i],
true,
null,
allVertexSet,
vertexSet,
this.traverse({
vertex: candidateRoots[i],
directed: true,
edge: null,
allVertices: allVertexSet,
currentComp: vertexSet,
hierarchyVertices,
filledVertexSet,
laneCounter
);
swimlaneIndex: laneCounter,
func: null,
visited: null
});
}
for (let i = 0; i < candidateRoots.length; i += 1) {
@ -612,16 +614,18 @@ class SwimlaneLayout extends GraphLayout {
for (let i = 0; i < roots.length; i += 1) {
const vertexSet = Object();
hierarchyVertices.push(vertexSet);
this.traverse(
roots[i],
true,
null,
allVertexSet,
vertexSet,
this.traverse({
vertex: roots[i],
directed: true,
edge: null,
allVertices: allVertexSet,
currentComp: vertexSet,
hierarchyVertices,
null,
i
); // CHECK THIS PARAM!! ====================
filledVertexSet: null,
swimlaneIndex: i,
func: null,
visited: null
}); // CHECK THIS PARAM!! ====================
}
}
@ -731,17 +735,15 @@ class SwimlaneLayout extends GraphLayout {
* @param allVertices Array of cell paths for the visited cells.
* @param swimlaneIndex the laid out order index of the swimlane vertex is contained in
*/
// @ts-ignore
traverse(
vertex: Cell | null = null,
directed: boolean,
edge: Cell | null,
allVertices: { [key: string]: Cell } | null = null,
currentComp: { [key: string]: Cell },
hierarchyVertices: GraphHierarchyNode[],
filledVertexSet: { [key: string]: Cell } | null = null,
swimlaneIndex: number
) {
traverse({
vertex,
directed,
allVertices,
currentComp,
hierarchyVertices,
filledVertexSet,
swimlaneIndex
}: SwimlaneGraphLayoutTraverseArgs) {
if (vertex != null && allVertices != null) {
// Has this vertex been seen before in any traversal
// And if the filled vertex set is populated, only
@ -764,7 +766,6 @@ class SwimlaneLayout extends GraphLayout {
}
const edges = this.getEdges(vertex);
const { model } = this.graph;
for (let i = 0; i < edges.length; i += 1) {
let otherVertex = this.getVisibleTerminal(edges[i], true);
@ -775,16 +776,15 @@ class SwimlaneLayout extends GraphLayout {
}
let otherIndex = 0;
const swimlanes = this.swimlanes as Cell[];
// Get the swimlane index of the other terminal
for (otherIndex = 0; otherIndex < swimlanes.length; otherIndex++) {
if (swimlanes[otherIndex].isAncestor(otherVertex)) {
for (otherIndex = 0; otherIndex < this.swimlanes!.length; otherIndex++) {
if (this.swimlanes![otherIndex].isAncestor(otherVertex)) {
break;
}
}
if (otherIndex >= swimlanes.length) {
if (otherIndex >= this.swimlanes!.length) {
continue;
}
@ -795,16 +795,18 @@ class SwimlaneLayout extends GraphLayout {
otherIndex > swimlaneIndex ||
((!directed || isSource) && otherIndex === swimlaneIndex)
) {
currentComp = this.traverse(
<Cell>otherVertex,
currentComp = this.traverse({
vertex: otherVertex,
directed,
edges[i],
edge: edges[i],
allVertices,
currentComp,
hierarchyVertices,
filledVertexSet,
otherIndex
);
swimlaneIndex: otherIndex,
func: null,
visited: null
});
}
}
} else if (currentComp[vertexID] == null) {

View File

@ -0,0 +1,38 @@
/*
Copyright 2022-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 type Cell from '../cell/Cell';
import type Dictionary from '../../util/Dictionary';
import type GraphHierarchyNode from './datatypes/GraphHierarchyNode';
export interface GraphLayoutTraverseArgs {
vertex: Cell | null;
directed: boolean | null;
func: Function | null;
edge: Cell | null;
visited: Dictionary<Cell, boolean> | null;
}
export interface HierarchicalGraphLayoutTraverseArgs extends GraphLayoutTraverseArgs {
allVertices: { [key: string]: Cell } | null;
currentComp: { [key: string]: Cell | null };
hierarchyVertices: GraphHierarchyNode[];
filledVertexSet: { [key: string]: Cell } | null;
}
export interface SwimlaneGraphLayoutTraverseArgs extends HierarchicalGraphLayoutTraverseArgs {
swimlaneIndex: number;
}

View File

@ -29,7 +29,7 @@ import type { CellStyle } from '../../types';
declare module '../Graph' {
interface Graph {
resetEdgesOnResize: boolean;
resetEdgesOnMove: false;
resetEdgesOnMove: boolean;
resetEdgesOnConnect: boolean;
connectableEdges: boolean;
allowDanglingEdges: boolean;

View File

@ -18,8 +18,7 @@ limitations under the License.
import {
Graph,
SelectionHandler,
InternalEvent,
constants,
eventUtils,
EdgeHandler,
EdgeStyle,
RubberBandHandler,
@ -47,12 +46,9 @@ const Template = ({ label, ...args }) => {
container.style.background = 'url(/images/grid.gif)';
container.style.cursor = 'default';
// Enables guides
SelectionHandler.prototype.guidesEnabled = true;
// Alt disables guides
SelectionHandler.prototype.useGuidesForEvent = function (me) {
return !InternalEvent.isAltDown(me.getEvent());
return !eventUtils.isAltDown(me.getEvent());
};
// Defines the guides to be red (default)
@ -69,6 +65,11 @@ const Template = ({ label, ...args }) => {
graph.setConnectable(true);
graph.gridSize = 30;
// Enables guides
const selectionHandler = graph.getPlugin('SelectionHandler');
if (selectionHandler)
selectionHandler.guidesEnabled = true;
// Changes the default style for edges "in-place" and assigns
// an alternate edge style which is applied in Graph.flip
// when the user double clicks on the adjustment control point

View File

@ -62,7 +62,7 @@ const Template = ({ label, ...args }) => {
if (cell != null) {
const overlays = graph.getCellOverlays(cell);
if (overlays == null) {
if (overlays.length == 0) {
// Creates a new overlay with an image and a tooltip
const overlay = new CellOverlay(
new ImageBox('/images/check.png', 16, 16),

View File

@ -61,7 +61,7 @@ const Template = ({ label, ...args }) => {
const f = () => {
const overlays = graph.getCellOverlays(v1);
if (overlays == null) {
if (overlays.length == 0) {
graph.removeCellOverlays(v2);
graph.setCellWarning(v1, 'Tooltip');
} else {

View File

@ -60,7 +60,7 @@ const Template = ({ label, ...args }) => {
const isVisible = function () {
// TODO super cannot be used here
// let result = super.isVisible();
let result;
let result = true;
if (result && this.value != null) {
result =
(showOne && this.value == '1') ||

View File

@ -2,8 +2,15 @@
Initialized from https://github.com/vitejs/vite/tree/v2.9.8/packages/create-vite/template-vanilla-ts
Do not forget to initialize all packages (you may also need to build the maxgraph@core package)
## Setup
Initialize all packages
> From the repository root, run `npm install`.
Build maxgraph@core
> From the `packages/core` directory, run `npm run generate-esm`.
## Run
Run `npm run dev` and go to http://localhost:5173/

View File

@ -29,8 +29,7 @@ class CustomRectangleShape extends RectangleShape {
constructor(bounds: Rectangle, fill: ColorValue, stroke: ColorValue) {
super(bounds, fill, stroke, 3);
// TODO if set, the shape is not painted
// this.isRounded = true; // force rounded shape
this.isRounded = true; // force rounded shape
}
paintBackground(c: AbstractCanvas2D, x: number, y: number, w: number, h: number): void {

View File

@ -13,9 +13,7 @@
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
// TODO required because some type definitions in the @maxgraph/core package generate errors: https://github.com/maxGraph/maxGraph/issues/96
"skipLibCheck": true
"noImplicitReturns": true
},
"include": ["src"]
}