diff --git a/src/mxgraph/shape/mxText.ts b/src/mxgraph/shape/mxText.ts index af6f1c128..1d33cd863 100644 --- a/src/mxgraph/shape/mxText.ts +++ b/src/mxgraph/shape/mxText.ts @@ -14,6 +14,136 @@ import mxRectangle from '../util/datatypes/mxRectangle'; import mxCellState from '../util/datatypes/mxCellState'; class mxText extends mxShape { + /** + * Class: mxText + * + * Extends to implement a text shape. To change vertical text from + * bottom to top to top to bottom, the following code can be used: + * + * (code) + * verticalTextRotation = 90; + * (end) + * + * Constructor: mxText + * + * Constructs a new text shape. + * + * Parameters: + * + * value - String that represents the text to be displayed. This is stored in + * . + * bounds - that defines the bounds. This is stored in + * . + * align - Specifies the horizontal alignment. Default is ''. This is stored in + * . + * valign - Specifies the vertical alignment. Default is ''. This is stored in + * . + * color - String that specifies the text color. Default is 'black'. This is + * stored in . + * family - String that specifies the font family. Default is + * . This is stored in . + * size - Integer that specifies the font size. Default is + * . This is stored in . + * fontStyle - Specifies the font style. Default is 0. This is stored in + * . + * spacing - Integer that specifies the global spacing. Default is 2. This is + * stored in . + * spacingTop - Integer that specifies the top spacing. Default is 0. The + * sum of the spacing and this is stored in . + * spacingRight - Integer that specifies the right spacing. Default is 0. The + * sum of the spacing and this is stored in . + * spacingBottom - Integer that specifies the bottom spacing. Default is 0.The + * sum of the spacing and this is stored in . + * spacingLeft - Integer that specifies the left spacing. Default is 0. The + * sum of the spacing and this is stored in . + * horizontal - Boolean that specifies if the label is horizontal. Default is + * true. This is stored in . + * background - String that specifies the background color. Default is null. + * This is stored in . + * border - String that specifies the label border color. Default is null. + * This is stored in . + * wrap - Specifies if word-wrapping should be enabled. Default is false. + * This is stored in . + * clipped - Specifies if the label should be clipped. Default is false. + * This is stored in . + * overflow - Value of the overflow style. Default is 'visible'. + */ + constructor( + value: string, + bounds: mxRectangle, + align: string=mxConstants.ALIGN_CENTER, + valign: string=mxConstants.ALIGN_MIDDLE, + color: string='black', + family: string=mxConstants.DEFAULT_FONTFAMILY, + size: number=mxConstants.DEFAULT_FONTSIZE, + fontStyle: number=mxConstants.DEFAULT_FONTSTYLE, + spacing: number=2, + spacingTop: number=0, + spacingRight: number=0, + spacingBottom: number=0, + spacingLeft: number=0, + horizontal: boolean=true, + background: string | null=null, + border: string | null=null, + wrap: boolean=false, + clipped: boolean=false, + overflow: string='visible', + labelPadding: number=0, + textDirection + ) { + super(); + this.value = value; + this.bounds = bounds; + this.color = color; + this.align = align; + this.valign = valign; + this.family = family; + this.size = size; + this.fontStyle = fontStyle; + this.spacing = parseInt(String(spacing || 2)); + this.spacingTop = parseInt(String(spacing || 2)) + parseInt(String(spacingTop || 0)); + this.spacingRight = parseInt(String(spacing || 2)) + parseInt(String(spacingRight || 0)); + this.spacingBottom = parseInt(String(spacing || 2)) + parseInt(String(spacingBottom || 0)); + this.spacingLeft = parseInt(String(spacing || 2)) + parseInt(String(spacingLeft || 0)); + this.horizontal = horizontal; + this.background = background; + this.border = border; + this.wrap = wrap; + this.clipped = clipped; + this.overflow = overflow; + this.labelPadding = labelPadding; + this.textDirection = textDirection; + this.rotation = 0; + this.updateMargin(); + } + + // TODO: Document me! + value: string; + bounds: mxRectangle; + align: string=mxConstants.ALIGN_CENTER; + valign: string=mxConstants.ALIGN_MIDDLE; + color: string='black'; + family: string=mxConstants.DEFAULT_FONTFAMILY; + size: number=mxConstants.DEFAULT_FONTSIZE; + fontStyle: number=mxConstants.DEFAULT_FONTSTYLE; + spacing: number=2; + spacingTop: number=0; + spacingRight: number=0; + spacingBottom: number=0; + spacingLeft: number=0; + horizontal: boolean=true; + background: string | null=null; + border: string | null=null; + wrap: boolean=false; + clipped: boolean=false; + overflow: string='visible'; + labelPadding: number=0; + textDirection; + margin: mxRectangle | null=null; + unrotatedBoundingBox: mxRectangle | null=null; + flipH: boolean=false; + flipV: boolean=false; + /** * Variable: baseSpacingTop * @@ -78,15 +208,6 @@ class mxText extends mxShape { */ ignoreStringSize: boolean = false; - /** - * Variable: textWidthPadding - * - * Specifies the padding to be added to the text width for the bounding box. - * This is needed to make sure no clipping is applied to borders. Default is 4 - * for IE 8 standards mode and 3 for all others. - */ - textWidthPadding: number = 3; - /** * Variable: lastValue * @@ -101,124 +222,8 @@ class mxText extends mxShape { */ cacheEnabled: boolean = true; - /** - * Class: mxText - * - * Extends to implement a text shape. To change vertical text from - * bottom to top to top to bottom, the following code can be used: - * - * (code) - * verticalTextRotation = 90; - * (end) - * - * Constructor: mxText - * - * Constructs a new text shape. - * - * Parameters: - * - * value - String that represents the text to be displayed. This is stored in - * . - * bounds - that defines the bounds. This is stored in - * . - * align - Specifies the horizontal alignment. Default is ''. This is stored in - * . - * valign - Specifies the vertical alignment. Default is ''. This is stored in - * . - * color - String that specifies the text color. Default is 'black'. This is - * stored in . - * family - String that specifies the font family. Default is - * . This is stored in . - * size - Integer that specifies the font size. Default is - * . This is stored in . - * fontStyle - Specifies the font style. Default is 0. This is stored in - * . - * spacing - Integer that specifies the global spacing. Default is 2. This is - * stored in . - * spacingTop - Integer that specifies the top spacing. Default is 0. The - * sum of the spacing and this is stored in . - * spacingRight - Integer that specifies the right spacing. Default is 0. The - * sum of the spacing and this is stored in . - * spacingBottom - Integer that specifies the bottom spacing. Default is 0.The - * sum of the spacing and this is stored in . - * spacingLeft - Integer that specifies the left spacing. Default is 0. The - * sum of the spacing and this is stored in . - * horizontal - Boolean that specifies if the label is horizontal. Default is - * true. This is stored in . - * background - String that specifies the background color. Default is null. - * This is stored in . - * border - String that specifies the label border color. Default is null. - * This is stored in . - * wrap - Specifies if word-wrapping should be enabled. Default is false. - * This is stored in . - * clipped - Specifies if the label should be clipped. Default is false. - * This is stored in . - * overflow - Value of the overflow style. Default is 'visible'. - */ - constructor( - value, - bounds, - align, - valign, - color, - family, - size, - fontStyle, - spacing, - spacingTop, - spacingRight, - spacingBottom, - spacingLeft, - horizontal, - background, - border, - wrap, - clipped, - overflow, - labelPadding, - textDirection - ) { - super(); - this.value = value; - this.bounds = bounds; - this.color = color != null ? color : 'black'; - this.align = align != null ? align : mxConstants.ALIGN_CENTER; - this.valign = valign != null ? valign : mxConstants.ALIGN_MIDDLE; - this.family = family != null ? family : mxConstants.DEFAULT_FONTFAMILY; - this.size = size != null ? size : mxConstants.DEFAULT_FONTSIZE; - this.fontStyle = - fontStyle != null ? fontStyle : mxConstants.DEFAULT_FONTSTYLE; - this.spacing = parseInt(spacing || 2); - this.spacingTop = this.spacing + parseInt(spacingTop || 0); - this.spacingRight = this.spacing + parseInt(spacingRight || 0); - this.spacingBottom = this.spacing + parseInt(spacingBottom || 0); - this.spacingLeft = this.spacing + parseInt(spacingLeft || 0); - this.horizontal = horizontal != null ? horizontal : true; - this.background = background; - this.border = border; - this.wrap = wrap != null ? wrap : false; - this.clipped = clipped != null ? clipped : false; - this.overflow = overflow != null ? overflow : 'visible'; - this.labelPadding = labelPadding != null ? labelPadding : 0; - this.textDirection = textDirection; - this.rotation = 0; - this.updateMargin(); - } - - /** - * Function: isHtmlAllowed - * - * Returns true if HTML is allowed for this shape. This implementation returns - * true if the browser is not in IE8 standards mode. - */ - isHtmlAllowed(): boolean { - return document.documentMode !== 8 || mxClient.IS_EM; - } - /** * Function: getSvgScreenOffset - * - * Disables offset in IE9 for crisper image output. */ getSvgScreenOffset(): number { return 0; @@ -342,32 +347,17 @@ class mxText extends mxShape { this.dialect === mxConstants.DIALECT_STRICTHTML) ) { if (this.node.nodeName === 'DIV') { - if (mxClient.IS_SVG) { - this.redrawHtmlShapeWithCss3(); - } else { - this.updateSize( - this.node, - this.state == null || this.state.view.textDiv == null - ); - - this.updateHtmlTransform(); - } - + this.redrawHtmlShape(); this.updateBoundingBox(); } else { const canvas = this.createCanvas(); - if (canvas != null && canvas.updateText != null) { - // Specifies if events should be handled - canvas.pointerEvents = this.pointerEvents; + // Specifies if events should be handled + canvas.pointerEvents = this.pointerEvents; - this.paint(canvas, true); - this.destroyCanvas(canvas); - this.updateBoundingBox(); - } else { - // Fallback if canvas does not support updateText (VML) - super.redraw(); - } + this.paint(canvas, true); + this.destroyCanvas(canvas); + this.updateBoundingBox(); } } else { super.redraw(); @@ -406,7 +396,7 @@ class mxText extends mxShape { delete this.background; delete this.border; this.textDirection = mxConstants.DEFAULT_TEXT_DIRECTION; - delete this.margin; + this.margin = null; } /** @@ -553,7 +543,6 @@ class mxText extends mxShape { result = result.firstChild.firstChild.firstChild.firstChild.firstChild; } } - return result; } @@ -596,74 +585,39 @@ class mxText extends mxShape { let ow = null; let oh = null; - if (node.ownerSVGElement != null) { - if ( - node.firstChild != null && - node.firstChild.firstChild != null && - node.firstChild.firstChild.nodeName === 'foreignObject' - ) { - // Uses second inner DIV for font metrics - node = node.firstChild.firstChild.firstChild.firstChild; - oh = node.offsetHeight * this.scale; + if ( + node.firstChild != null && + node.firstChild.firstChild != null && + node.firstChild.firstChild.nodeName === 'foreignObject' + ) { + // Uses second inner DIV for font metrics + node = node.firstChild.firstChild.firstChild.firstChild; + oh = node.offsetHeight * this.scale; - if (this.overflow === 'width') { - ow = this.boundingBox.width; - } else { - ow = node.offsetWidth * this.scale; - } + if (this.overflow === 'width') { + ow = this.boundingBox.width; } else { - try { - const b = node.getBBox(); - - // Workaround for bounding box of empty string - if ( - typeof this.value === 'string' && - mxUtils.trim(this.value) == 0 - ) { - this.boundingBox = null; - } else if (b.width === 0 && b.height === 0) { - this.boundingBox = null; - } else { - this.boundingBox = new mxRectangle(b.x, b.y, b.width, b.height); - } - - return; - } catch (e) { - // Ignores NS_ERROR_FAILURE in FF if container display is none. - } + ow = node.offsetWidth * this.scale; } } else { - const td = this.state != null ? this.state.view.textDiv : null; - - // Use cached offset size - if (this.offsetWidth != null && this.offsetHeight != null) { - ow = this.offsetWidth * this.scale; - oh = this.offsetHeight * this.scale; - } else { - // Cannot get node size while container hidden so a - // shared temporary DIV is used for text measuring - if (td != null) { - this.updateFont(td); - this.updateSize(td, false); - this.updateInnerHtml(td); - - node = td; - } - - let sizeDiv = node; + try { + const b = node.getBBox(); + // Workaround for bounding box of empty string if ( - sizeDiv.firstChild != null && - sizeDiv.firstChild.nodeName === 'DIV' + typeof this.value === 'string' && + mxUtils.trim(this.value) == 0 ) { - sizeDiv = sizeDiv.firstChild; + this.boundingBox = null; + } else if (b.width === 0 && b.height === 0) { + this.boundingBox = null; + } else { + this.boundingBox = new mxRectangle(b.x, b.y, b.width, b.height); } - this.offsetWidth = sizeDiv.offsetWidth + this.textWidthPadding; - this.offsetHeight = sizeDiv.offsetHeight; - - ow = this.offsetWidth * this.scale; - oh = this.offsetHeight * this.scale; + return; + } catch (e) { + // Ignores NS_ERROR_FAILURE in FF if container display is none. } } @@ -680,7 +634,7 @@ class mxText extends mxShape { if (this.boundingBox != null) { if (rot !== 0) { // Accounts for pre-rotated x and y - const bbox = mxUtils.getBoundingBox( + const bbox = mxUtils.getBoundingBox( new mxRectangle( this.margin.x * this.boundingBox.width, this.margin.y * this.boundingBox.height, @@ -836,37 +790,6 @@ class mxText extends mxShape { * Updates the HTML node(s) to reflect the latest bounds and scale. */ redrawHtmlShape() { - if (mxClient.IS_SVG) { - this.redrawHtmlShapeWithCss3(); - } else { - const { style } = this.node; - - // Resets CSS styles - style.whiteSpace = 'normal'; - style.overflow = ''; - style.width = ''; - style.height = ''; - - this.updateValue(); - this.updateFont(this.node); - this.updateSize( - this.node, - this.state == null || this.state.view.textDiv == null - ); - - this.offsetWidth = null; - this.offsetHeight = null; - - this.updateHtmlTransform(); - } - } - - /** - * Function: redrawHtmlShapeWithCss3 - * - * Updates the HTML node(s) to reflect the latest bounds and scale. - */ - redrawHtmlShapeWithCss3() { const w = Math.max(0, Math.round(this.bounds.width / this.scale)); const h = Math.max(0, Math.round(this.bounds.height / this.scale)); const flex = @@ -931,56 +854,6 @@ class mxText extends mxShape { ); } - /** - * Function: updateHtmlTransform - * - * Returns the spacing as an . - */ - updateHtmlTransform() { - const theta = this.getTextRotation(); - const { style } = this.node; - const dx = this.margin.x; - const dy = this.margin.y; - - if (theta !== 0) { - mxUtils.setPrefixedStyle( - style, - 'transformOrigin', - `${-dx * 100}%` + ` ${-dy * 100}%` - ); - mxUtils.setPrefixedStyle( - style, - 'transform', - `translate(${dx * 100}%` + - `,${dy * 100}%) ` + - `scale(${this.scale}) rotate(${theta}deg)` - ); - } else { - mxUtils.setPrefixedStyle(style, 'transformOrigin', '0% 0%'); - mxUtils.setPrefixedStyle( - style, - 'transform', - `scale(${this.scale}) ` + `translate(${dx * 100}%` + `,${dy * 100}%)` - ); - } - - style.left = `${Math.round( - this.bounds.x - - Math.ceil( - dx * (this.overflow !== 'fill' && this.overflow !== 'width' ? 3 : 1) - ) - )}px`; - style.top = `${Math.round( - this.bounds.y - dy * (this.overflow !== 'fill' ? 3 : 1) - )}px`; - - if (this.opacity < 100) { - style.opacity = this.opacity / 100; - } else { - style.opacity = ''; - } - } - /** * Function: updateInnerHtml * @@ -1006,159 +879,6 @@ class mxText extends mxShape { } } - /** - * Function: updateHtmlFilter - * - * Rotated text rendering quality is bad for IE9 quirks/IE8 standards - */ - updateHtmlFilter() { - const { style } = this.node; - const dx = this.margin.x; - let dy = this.margin.y; - const s = this.scale; - - // Resets filter before getting offsetWidth - mxUtils.setOpacity(this.node, this.opacity); - - // Adds 1 to match table height in 1.x - let ow = 0; - let oh = 0; - const td = this.state != null ? this.state.view.textDiv : null; - let sizeDiv = this.node; - - // Fallback for hidden text rendering in IE quirks mode - if (td != null) { - td.style.overflow = ''; - td.style.height = ''; - td.style.width = ''; - - this.updateFont(td); - this.updateSize(td, false); - this.updateInnerHtml(td); - - const w = Math.round(this.bounds.width / this.scale); - - if (this.wrap && w > 0) { - td.style.whiteSpace = 'normal'; - td.style.wordWrap = mxConstants.WORD_WRAP; - ow = w; - - if (this.clipped) { - ow = Math.min(ow, this.bounds.width); - } - - td.style.width = `${ow}px`; - } else { - td.style.whiteSpace = 'nowrap'; - } - - sizeDiv = td; - - if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName === 'DIV') { - sizeDiv = sizeDiv.firstChild; - - if (this.wrap && td.style.wordWrap === 'break-word') { - sizeDiv.style.width = '100%'; - } - } - - // Required to update the height of the text box after wrapping width is known - if (!this.clipped && this.wrap && w > 0) { - ow = sizeDiv.offsetWidth + this.textWidthPadding; - td.style.width = `${ow}px`; - } - - oh = sizeDiv.offsetHeight + 2; - } else if ( - sizeDiv.firstChild != null && - sizeDiv.firstChild.nodeName === 'DIV' - ) { - sizeDiv = sizeDiv.firstChild; - oh = sizeDiv.offsetHeight; - } - - ow = sizeDiv.offsetWidth + this.textWidthPadding; - - if (this.clipped) { - oh = Math.min(oh, this.bounds.height); - } - - let w = this.bounds.width / s; - let h = this.bounds.height / s; - - // Handles special case for live preview with no wrapper DIV and no textDiv - if (this.overflow === 'fill') { - oh = h; - ow = w; - } else if (this.overflow === 'width') { - oh = sizeDiv.scrollHeight; - ow = w; - } - - // Stores for later use - this.offsetWidth = ow; - this.offsetHeight = oh; - - h = oh; - - if (this.overflow !== 'fill' && this.overflow !== 'width') { - if (this.clipped) { - ow = Math.min(w, ow); - } - - w = ow; - } - - h *= s; - w *= s; - - // Rotation case is handled via VML canvas - let rad = this.getTextRotation() * (Math.PI / 180); - - // Precalculate cos and sin for the rotation - const real_cos = parseFloat(parseFloat(Math.cos(rad)).toFixed(8)); - const real_sin = parseFloat(parseFloat(Math.sin(-rad)).toFixed(8)); - - rad %= 2 * Math.PI; - - if (rad < 0) { - rad += 2 * Math.PI; - } - - rad %= Math.PI; - - if (rad > Math.PI / 2) { - rad = Math.PI - rad; - } - - const cos = Math.cos(rad); - const sin = Math.sin(-rad); - - const tx = w * -(dx + 0.5); - const ty = h * -(dy + 0.5); - - const top_fix = (h - h * cos + w * sin) / 2 + real_sin * tx - real_cos * ty; - const left_fix = - (w - w * cos + h * sin) / 2 - real_cos * tx - real_sin * ty; - - if (rad !== 0) { - const f = `progid:DXImageTransform.Microsoft.Matrix(M11=${real_cos}, M12=${real_sin}, M21=${-real_sin}, M22=${real_cos}, sizingMethod='auto expand')`; - - if (style.filter != null && style.filter.length > 0) { - style.filter += ` ${f}`; - } else { - style.filter = f; - } - } - - // Workaround for rendering offsets - dy = 0; - - style.zoom = s; - style.left = `${Math.round(this.bounds.x + left_fix - w / 2)}px`; - style.top = `${Math.round(this.bounds.y + top_fix - h / 2 + dy)}px`; - } - /** * Function: updateValue * diff --git a/src/mxgraph/shape/node/mxRectangleShape.ts b/src/mxgraph/shape/node/mxRectangleShape.ts index cc79cdbcc..b527ee146 100644 --- a/src/mxgraph/shape/node/mxRectangleShape.ts +++ b/src/mxgraph/shape/node/mxRectangleShape.ts @@ -32,12 +32,10 @@ import mxSvgCanvas2D from "../../util/canvas/mxSvgCanvas2D"; * 1. This is stored in . */ class mxRectangleShape extends mxShape { - strokewidth: number; - constructor( bounds: mxRectangle | null=null, - fill: string = '#FFFFFF', - stroke: string = '#000000', + fill: string | null = '#FFFFFF', + stroke: string | null = '#000000', strokewidth: number = 1 ) { super(); @@ -47,6 +45,9 @@ class mxRectangleShape extends mxShape { this.strokewidth = strokewidth; } + // TODO: Document me! + strokewidth: number; + /** * Function: isHtmlAllowed * diff --git a/src/mxgraph/util/datatypes/mxCellState.ts b/src/mxgraph/util/datatypes/mxCellState.ts index f3c2d4df2..f2e6051c9 100644 --- a/src/mxgraph/util/datatypes/mxCellState.ts +++ b/src/mxgraph/util/datatypes/mxCellState.ts @@ -15,31 +15,31 @@ import mxGraph from "../../view/graph/mxGraph"; class mxCellState extends mxRectangle { // TODO: Document me!! - cellBounds: mxRectangle | undefined; + cellBounds: mxRectangle | null = null; - paintBounds: mxRectangle | undefined; + paintBounds: mxRectangle | null = null; - boundingBox: mxRectangle | undefined; + boundingBox: mxRectangle | null = null; // Used by mxCellRenderer's createControl() - control: mxShape | undefined; + control: mxShape | null = null; // Used by mxCellRenderer's createCellOverlays() - overlays: any[] | null | undefined; + overlays: any[] | null = null; /** * Variable: view * * Reference to the enclosing . */ - view: mxGraphView | null = null; + view: mxGraphView; /** * Variable: cell * * Reference to the that is represented by this state. */ - cell: mxCell | null = null; + cell: mxCell; /** * Variable: style @@ -47,7 +47,7 @@ class mxCellState extends mxRectangle { * Contains an array of key, value pairs that represent the style of the * cell. */ - style: any | null = null; // TODO: Important - make the style type more strictly typed to allow for typescript checking of individual properties!!! + style: any; // TODO: Important - make the style type more strictly typed to allow for typescript checking of individual properties!!! /** * Variable: invalidStyle @@ -69,7 +69,7 @@ class mxCellState extends mxRectangle { * that holds the origin for all child cells. Default is a new * empty . */ - origin: mxPoint | null = null; + origin: mxPoint; /** * Variable: absolutePoints @@ -86,7 +86,7 @@ class mxCellState extends mxRectangle { * absolute coordinates of the label position. For vertices, this is the * offset of the label relative to the top, left corner of the vertex. */ - absoluteOffset: mxPoint | null = null; + absoluteOffset: mxPoint; /** * Variable: visibleSourceState diff --git a/src/mxgraph/view/cell/mxCell.ts b/src/mxgraph/view/cell/mxCell.ts index 05a5704e9..9dafadf0b 100644 --- a/src/mxgraph/view/cell/mxCell.ts +++ b/src/mxgraph/view/cell/mxCell.ts @@ -488,7 +488,7 @@ class mxCell { * * child - Child whose index should be returned. */ - getIndex(child: mxCell): number { + getIndex(child: mxCell | null): number { return mxUtils.indexOf(this.children, child); } diff --git a/src/mxgraph/view/cell/mxCellEditor.ts b/src/mxgraph/view/cell/mxCellEditor.ts index 6380f8ca8..9a02d456d 100644 --- a/src/mxgraph/view/cell/mxCellEditor.ts +++ b/src/mxgraph/view/cell/mxCellEditor.ts @@ -14,8 +14,141 @@ import mxGraph from '../graph/mxGraph'; import mxCell from './mxCell'; import mxMouseEvent from '../../util/event/mxMouseEvent'; import mxCellState from '../../util/datatypes/mxCellState'; +import mxShape from "../../shape/mxShape"; +import mxEventObject from "../../util/event/mxEventObject"; +/** + * Class: mxCellEditor + * + * In-place editor for the graph. To control this editor, use + * , and + * . If is true then + * ctrl-enter or shift-enter can be used to create a linefeed. The F2 and + * escape keys can always be used to stop editing. + * + * To customize the location of the textbox in the graph, override + * as follows: + * + * (code) + * graph.cellEditor.getEditorBounds = (state)=> + * { + * let result = getEditorBounds.apply(this, arguments); + * + * if (this.graph.getModel().isEdge(state.cell)) + * { + * result.x = state.getCenterX() - result.width / 2; + * result.y = state.getCenterY() - result.height / 2; + * } + * + * return result; + * }; + * (end) + * + * Note that this hook is only called if is false. If is true, + * then is used to compute the current bounds of the textbox. + * + * The textarea uses the mxCellEditor CSS class. You can modify this class in + * your custom CSS. Note: You should modify the CSS after loading the client + * in the page. + * + * Example: + * + * To only allow numeric input in the in-place editor, use the following code. + * + * (code) + * let text = graph.cellEditor.textarea; + * + * mxEvent.addListener(text, 'keydown', function (evt) + * { + * if (!(evt.keyCode >= 48 && evt.keyCode <= 57) && + * !(evt.keyCode >= 96 && evt.keyCode <= 105)) + * { + * mxEvent.consume(evt); + * } + * }); + * (end) + * + * Placeholder: + * + * To implement a placeholder for cells without a label, use the + * variable. + * + * Resize in Chrome: + * + * Resize of the textarea is disabled by default. If you want to enable + * this feature extend and set this.textarea.style.resize = ''. + * + * To start editing on a key press event, the container of the graph + * should have focus or a focusable parent should be used to add the + * key press handler as follows. + * + * (code) + * mxEvent.addListener(graph.container, 'keypress', mxUtils.bind(this, (evt)=> + * { + * if (!graph.isEditing() && !graph.isSelectionEmpty() && evt.which !== 0 && + * !mxEvent.isAltDown(evt) && !mxEvent.isControlDown(evt) && !mxEvent.isMetaDown(evt)) + * { + * graph.startEditing(); + * + * if (mxClient.IS_FF) + * { + * graph.cellEditor.textarea.value = String.fromCharCode(evt.which); + * } + * } + * })); + * (end) + * + * To allow focus for a DIV, and hence to receive key press events, some browsers + * require it to have a valid tabindex attribute. In this case the following + * code may be used to keep the container focused. + * + * (code) + * let graphFireMouseEvent = graph.fireMouseEvent; + * graph.fireMouseEvent = (evtName, me, sender)=> + * { + * if (evtName == mxEvent.MOUSE_DOWN) + * { + * this.container.focus(); + * } + * + * graphFireMouseEvent.apply(this, arguments); + * }; + * (end) + * + * Constructor: mxCellEditor + * + * Constructs a new in-place editor for the specified graph. + * + * Parameters: + * + * graph - Reference to the enclosing . + */ class mxCellEditor { + constructor(graph: mxGraph) { + this.graph = graph; + + // Stops editing after zoom changes + this.zoomHandler = () => { + if (this.graph.isEditing()) { + this.resize(); + } + }; + + // Handling of deleted cells while editing + this.changeHandler = (sender: any) => { + if ( + this.editingCell != null && + this.graph.getView().getState(this.editingCell, false) == null + ) { + this.stopEditing(true); + } + }; + + this.graph.view.addListener(mxEvent.SCALE, this.zoomHandler); + this.graph.view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.zoomHandler); + this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler); + } + // TODO: Document me! changeHandler: Function | null; @@ -25,16 +158,16 @@ class mxCellEditor { bounds: mxRectangle | null = null; - resizeThread: number | null; + resizeThread: number | null = null; - textDirection: '' | 'auto' | 'ltr' | 'rtl' | null; + textDirection: '' | 'auto' | 'ltr' | 'rtl' | null = null; /** * Variable: graph * * Reference to the enclosing . */ - graph: mxGraph = null; + graph: mxGraph; /** * Variable: textarea @@ -42,21 +175,21 @@ class mxCellEditor { * Holds the DIV that is used for text editing. Note that this may be null before the first * edit. Instantiated in . */ - textarea: HTMLElement = null; + textarea: HTMLElement | null = null; /** * Variable: editingCell * * Reference to the that is currently being edited. */ - editingCell: mxCell = null; + editingCell: mxCell | null = null; /** * Variable: trigger * * Reference to the event that was used to start editing. */ - trigger: mxMouseEvent = null; + trigger: mxMouseEvent | null = null; /** * Variable: modified @@ -106,7 +239,7 @@ class mxCellEditor { * * Reference to the label DOM node that has been hidden. */ - textNode: HTMLElement | null = null; + textNode: SVGGElement | null = null; /** * Variable: zIndex @@ -153,137 +286,6 @@ class mxCellEditor { */ align: string | null = null; - /** - * Class: mxCellEditor - * - * In-place editor for the graph. To control this editor, use - * , and - * . If is true then - * ctrl-enter or shift-enter can be used to create a linefeed. The F2 and - * escape keys can always be used to stop editing. - * - * To customize the location of the textbox in the graph, override - * as follows: - * - * (code) - * graph.cellEditor.getEditorBounds = (state)=> - * { - * let result = getEditorBounds.apply(this, arguments); - * - * if (this.graph.getModel().isEdge(state.cell)) - * { - * result.x = state.getCenterX() - result.width / 2; - * result.y = state.getCenterY() - result.height / 2; - * } - * - * return result; - * }; - * (end) - * - * Note that this hook is only called if is false. If is true, - * then is used to compute the current bounds of the textbox. - * - * The textarea uses the mxCellEditor CSS class. You can modify this class in - * your custom CSS. Note: You should modify the CSS after loading the client - * in the page. - * - * Example: - * - * To only allow numeric input in the in-place editor, use the following code. - * - * (code) - * let text = graph.cellEditor.textarea; - * - * mxEvent.addListener(text, 'keydown', function (evt) - * { - * if (!(evt.keyCode >= 48 && evt.keyCode <= 57) && - * !(evt.keyCode >= 96 && evt.keyCode <= 105)) - * { - * mxEvent.consume(evt); - * } - * }); - * (end) - * - * Placeholder: - * - * To implement a placeholder for cells without a label, use the - * variable. - * - * Resize in Chrome: - * - * Resize of the textarea is disabled by default. If you want to enable - * this feature extend and set this.textarea.style.resize = ''. - * - * To start editing on a key press event, the container of the graph - * should have focus or a focusable parent should be used to add the - * key press handler as follows. - * - * (code) - * mxEvent.addListener(graph.container, 'keypress', mxUtils.bind(this, (evt)=> - * { - * if (!graph.isEditing() && !graph.isSelectionEmpty() && evt.which !== 0 && - * !mxEvent.isAltDown(evt) && !mxEvent.isControlDown(evt) && !mxEvent.isMetaDown(evt)) - * { - * graph.startEditing(); - * - * if (mxClient.IS_FF) - * { - * graph.cellEditor.textarea.value = String.fromCharCode(evt.which); - * } - * } - * })); - * (end) - * - * To allow focus for a DIV, and hence to receive key press events, some browsers - * require it to have a valid tabindex attribute. In this case the following - * code may be used to keep the container focused. - * - * (code) - * let graphFireMouseEvent = graph.fireMouseEvent; - * graph.fireMouseEvent = (evtName, me, sender)=> - * { - * if (evtName == mxEvent.MOUSE_DOWN) - * { - * this.container.focus(); - * } - * - * graphFireMouseEvent.apply(this, arguments); - * }; - * (end) - * - * Constructor: mxCellEditor - * - * Constructs a new in-place editor for the specified graph. - * - * Parameters: - * - * graph - Reference to the enclosing . - */ - constructor(graph: mxGraph) { - this.graph = graph; - - // Stops editing after zoom changes - this.zoomHandler = () => { - if (this.graph.isEditing()) { - this.resize(); - } - }; - - // Handling of deleted cells while editing - this.changeHandler = sender => { - if ( - this.editingCell != null && - this.graph.getView().getState(this.editingCell, false) == null - ) { - this.stopEditing(true); - } - }; - - this.graph.view.addListener(mxEvent.SCALE, this.zoomHandler); - this.graph.view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.zoomHandler); - this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler); - } - /** * Function: init * @@ -309,8 +311,8 @@ class mxCellEditor { * * Called in if cancel is false to invoke . */ - applyValue(state: mxCellState, value): void { - this.graph.labelChanged(state.cell, value, this.trigger); + applyValue(state: mxCellState, value: any): void { + this.graph.labelChanged(state.cell, value, this.trigger); } /** @@ -318,7 +320,7 @@ class mxCellEditor { * * Sets the temporary horizontal alignment for the current editing session. */ - setAlign(align) { + setAlign(align: string): void { if (this.textarea != null) { this.textarea.style.textAlign = align; } @@ -332,7 +334,7 @@ class mxCellEditor { * * Gets the initial editing value for the given cell. */ - getInitialValue(state, trigger) { + getInitialValue(state: mxCellState, trigger: mxEventObject | mxMouseEvent) { let result = mxUtils.htmlEntities( this.graph.getEditingValue(state.cell, trigger), false @@ -347,6 +349,7 @@ class mxCellEditor { * Returns the current editing value. */ getCurrentValue(state: mxCellState) { + // @ts-ignore return mxUtils.extractTextWithWhitespace(this.textarea.childNodes); } @@ -356,7 +359,7 @@ class mxCellEditor { * Returns true if is true and shift, control and meta * are not pressed. */ - isCancelEditingKeyEvent(evt) { + isCancelEditingKeyEvent(evt: KeyboardEvent) { return ( this.escapeCancelsEditing || mxEvent.isShiftDown(evt) || @@ -370,31 +373,31 @@ class mxCellEditor { * * Installs listeners for focus, change and standard key event handling. */ - installListeners(elt) { + installListeners(elt: HTMLElement) { // Applies value if text is dragged // LATER: Gesture mouse events ignored for starting move mxEvent.addListener( elt, 'dragstart', - mxUtils.bind(this, evt => { + (evt: Event) => { this.graph.stopEditing(false); mxEvent.consume(evt); - }) + } ); // Applies value if focus is lost mxEvent.addListener( elt, 'blur', - mxUtils.bind(this, evt => { + (evt: Event) => { if (this.blurEnabled) { this.focusLost(); } - }) + } ); // Updates modified state and handles placeholder text - mxEvent.addListener(elt, 'keydown', evt => { + mxEvent.addListener(elt, 'keydown', (evt: KeyboardEvent) => { if (!mxEvent.isConsumed(evt)) { if (this.isStopEditingEvent(evt)) { this.graph.stopEditing(false); @@ -407,7 +410,7 @@ class mxCellEditor { }); // Keypress only fires if printable key was pressed and handles removing the empty placeholder - const keypressHandler = evt => { + const keypressHandler = (evt: KeyboardEvent) => { if (this.editingCell != null) { // Clears the initial empty label on the first keystroke // and workaround for FF which fires keypress for delete and backspace @@ -428,18 +431,20 @@ class mxCellEditor { mxEvent.addListener(elt, 'paste', keypressHandler); // Handler for updating the empty label text value after a change - const keyupHandler = evt => { + const keyupHandler = (evt: KeyboardEvent) => { if (this.editingCell != null) { // Uses an optional text value for sempty labels which is cleared // when the first keystroke appears. This makes it easier to see // that a label is being edited even if the label is empty. // In Safari and FF, an empty text is represented by
which isn't enough to force a valid size + const textarea = this.textarea; + if ( - this.textarea.innerHTML.length === 0 || - this.textarea.innerHTML === '
' + textarea.innerHTML.length === 0 || + textarea.innerHTML === '
' ) { - this.textarea.innerHTML = this.getEmptyLabelText(); - this.clearOnChange = this.textarea.innerHTML.length > 0; + textarea.innerHTML = this.getEmptyLabelText(); + this.clearOnChange = textarea.innerHTML.length > 0; } else { this.clearOnChange = false; } @@ -453,7 +458,7 @@ class mxCellEditor { // Adds automatic resizing of the textbox while typing using input, keyup and/or DOM change events const evtName = 'input'; - const resizeHandler = mxUtils.bind(this, evt => { + const resizeHandler = (evt: Event) => { if ( this.editingCell != null && this.autoSize && @@ -470,7 +475,7 @@ class mxCellEditor { this.resize(); }, 0); } - }); + }; mxEvent.addListener(elt, evtName, resizeHandler); mxEvent.addListener(window, 'resize', resizeHandler); @@ -485,7 +490,7 @@ class mxCellEditor { * returns true if F2 is pressed of if is true * and enter is pressed without control or shift. */ - isStopEditingEvent(evt) { + isStopEditingEvent(evt: KeyboardEvent) { return ( evt.keyCode === 113 /* F2 */ || (this.graph.isEnterStopsCellEditing() && @@ -500,7 +505,7 @@ class mxCellEditor { * * Returns true if this editor is the source for the given native event. */ - isEventSource(evt): boolean { + isEventSource(evt: Event): boolean { return mxEvent.getSource(evt) === this.textarea; } @@ -519,7 +524,7 @@ class mxCellEditor { if (!this.autoSize || state.style.overflow === 'fill') { // Specifies the bounds of the editor box - this.bounds = this.getEditorBounds(state); + this.bounds = this.getEditorBounds(state); this.textarea.style.width = `${Math.round( this.bounds.width / scale )}px`; @@ -621,6 +626,7 @@ class mxCellEditor { !state.view.graph.cellRenderer.legacySpacing || state.style[mxConstants.STYLE_OVERFLOW] !== 'width' ) { + // @ts-ignore const dummy = new mxText(); // FIXME!!!! =================================================================================================== const spacing = parseInt(state.style.spacing || 2) * scale; const spacingTop = @@ -758,7 +764,7 @@ class mxCellEditor { * Returns the background color for the in-place editor. This implementation * always returns null. */ - getBackgroundColor(state): string | null { + getBackgroundColor(state: mxCellState): string | null { return null; } @@ -772,7 +778,7 @@ class mxCellEditor { * cell - to start editing. * trigger - Optional mouse event that triggered the editor. */ - startEditing(cell: mxCell, trigger: MouseEvent | null = null): void { + startEditing(cell: mxCell, trigger: mxMouseEvent | null = null): void { this.stopEditing(true); this.align = null; @@ -819,19 +825,20 @@ class mxCellEditor { txtDecor.push('line-through'); } - this.textarea.style.lineHeight = mxConstants.ABSOLUTE_LINE_HEIGHT + const textarea = this.textarea; + textarea.style.lineHeight = mxConstants.ABSOLUTE_LINE_HEIGHT ? `${Math.round(size * mxConstants.LINE_HEIGHT)}px` : String(mxConstants.LINE_HEIGHT); - this.textarea.style.backgroundColor = this.getBackgroundColor(state); - this.textarea.style.textDecoration = txtDecor.join(' '); - this.textarea.style.fontWeight = bold ? 'bold' : 'normal'; - this.textarea.style.fontStyle = italic ? 'italic' : ''; - this.textarea.style.fontSize = `${Math.round(size)}px`; - this.textarea.style.zIndex = String(this.zIndex); - this.textarea.style.fontFamily = family; - this.textarea.style.textAlign = align; - this.textarea.style.outline = 'none'; - this.textarea.style.color = color; + textarea.style.backgroundColor = this.getBackgroundColor(state) || 'transparent'; + textarea.style.textDecoration = txtDecor.join(' '); + textarea.style.fontWeight = bold ? 'bold' : 'normal'; + textarea.style.fontStyle = italic ? 'italic' : ''; + textarea.style.fontSize = `${Math.round(size)}px`; + textarea.style.zIndex = String(this.zIndex); + textarea.style.fontFamily = family; + textarea.style.textAlign = align; + textarea.style.outline = 'none'; + textarea.style.color = color; let dir = (this.textDirection = state.style.textDirection != null @@ -849,30 +856,31 @@ class mxCellEditor { } if (dir === 'ltr' || dir === 'rtl') { - this.textarea.setAttribute('dir', dir); + textarea.setAttribute('dir', dir); } else { - this.textarea.removeAttribute('dir'); + textarea.removeAttribute('dir'); } // Sets the initial editing value - this.textarea.innerHTML = this.getInitialValue(state, trigger) || ''; - this.initialValue = this.textarea.innerHTML; + textarea.innerHTML = this.getInitialValue(state, trigger) || ''; + this.initialValue = textarea.innerHTML; // Uses an optional text value for empty labels which is cleared // when the first keystroke appears. This makes it easier to see // that a label is being edited even if the label is empty. if ( - this.textarea.innerHTML.length === 0 || - this.textarea.innerHTML === '
' + textarea.innerHTML.length === 0 || + textarea.innerHTML === '
' ) { - this.textarea.innerHTML = this.getEmptyLabelText(); + textarea.innerHTML = this.getEmptyLabelText(); this.clearOnChange = true; } else { this.clearOnChange = - this.textarea.innerHTML === this.getEmptyLabelText(); + textarea.innerHTML === this.getEmptyLabelText(); } - this.graph.container.appendChild(this.textarea); + // @ts-ignore + this.graph.container.appendChild(textarea); // Update this after firing all potential events that could update the cleanOnChange flag this.editingCell = cell; @@ -880,13 +888,14 @@ class mxCellEditor { this.textNode = null; if (state.text != null && this.isHideLabel(state)) { - this.textNode = state.text.node; + this.textNode = state.text.node; this.textNode.style.visibility = 'hidden'; } // Workaround for initial offsetHeight not ready for heading in markup if ( this.autoSize && + // @ts-ignore (this.graph.model.isEdge(state.cell) || state.style[mxConstants.STYLE_OVERFLOW] !== 'fill') ) { @@ -903,15 +912,15 @@ class mxCellEditor { // Workaround for NS_ERROR_FAILURE in FF try { // Prefers blinking cursor over no selected text if empty - this.textarea.focus(); + textarea.focus(); if ( this.isSelectText() && - this.textarea.innerHTML.length > 0 && - (this.textarea.innerHTML !== this.getEmptyLabelText() || + textarea.innerHTML.length > 0 && + (textarea.innerHTML !== this.getEmptyLabelText() || !this.clearOnChange) ) { - document.execCommand('selectAll', false, null); + document.execCommand('selectAll', false); } } catch (e) { // ignore @@ -958,30 +967,31 @@ class mxCellEditor { } const state = !cancel ? this.graph.view.getState(this.editingCell) : null; + const textarea = this.textarea; const initial = this.initialValue; this.initialValue = null; this.editingCell = null; this.trigger = null; this.bounds = null; - this.textarea.blur(); + textarea.blur(); this.clearSelection(); - if (this.textarea.parentNode != null) { - this.textarea.parentNode.removeChild(this.textarea); + if (textarea.parentNode != null) { + textarea.parentNode.removeChild(textarea); } if ( this.clearOnChange && - this.textarea.innerHTML === this.getEmptyLabelText() + textarea.innerHTML === this.getEmptyLabelText() ) { - this.textarea.innerHTML = ''; + textarea.innerHTML = ''; this.clearOnChange = false; } if ( state != null && - (this.textarea.innerHTML !== initial || this.align != null) + (textarea.innerHTML !== initial || this.align != null) ) { this.prepareTextarea(); const value = this.getCurrentValue(state); @@ -1016,11 +1026,12 @@ class mxCellEditor { * This implementation removes the extra trailing linefeed in Firefox. */ prepareTextarea(): void { + const textarea = this.textarea; if ( - this.textarea.lastChild != null && - this.textarea.lastChild.nodeName === 'BR' + textarea.lastChild != null && + textarea.lastChild.nodeName === 'BR' ) { - this.textarea.removeChild(this.textarea.lastChild); + textarea.removeChild(textarea.lastChild); } } @@ -1041,12 +1052,13 @@ class mxCellEditor { */ getMinimumSize(state: mxCellState): mxRectangle { const { scale } = this.graph.getView(); + const textarea = this.textarea; return new mxRectangle( 0, 0, state.text == null ? 30 : state.text.size * scale + 20, - this.textarea.style.textAlign === 'left' ? 120 : 40 + textarea.style.textAlign === 'left' ? 120 : 40 ); } @@ -1068,8 +1080,9 @@ class mxCellEditor { state.view.graph.cellRenderer.legacySpacing && state.style[mxConstants.STYLE_OVERFLOW] === 'fill' ) { - result = state.shape.getLabelBounds(mxRectangle.fromRectangle(state)); + result = (state.shape).getLabelBounds(mxRectangle.fromRectangle(state)); } else { + // @ts-ignore const dummy = new mxText(); // FIXME!!!! =================================================================================================== const spacing: number = parseInt(state.style.spacing || 0) * scale; const spacingTop: number = @@ -1146,7 +1159,7 @@ class mxCellEditor { // Applies the horizontal and vertical label positions if (this.graph.getModel().isVertex(state.cell)) { - const horizontal: string = mxUtils.getStringValue( + const horizontal: string = mxUtils.getStringValue( state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER @@ -1191,8 +1204,8 @@ class mxCellEditor { * cell - for which a text for an empty editing box should be * returned. */ - getEmptyLabelText(cell: mxCell | null = null): string | null { - return this.emptyLabelText; + getEmptyLabelText(cell: mxCell | null = null): string { + return this.emptyLabelText || ''; } /** diff --git a/src/mxgraph/view/cell/mxCellOverlay.ts b/src/mxgraph/view/cell/mxCellOverlay.ts index 78fa1a570..77aa59af0 100644 --- a/src/mxgraph/view/cell/mxCellOverlay.ts +++ b/src/mxgraph/view/cell/mxCellOverlay.ts @@ -49,7 +49,7 @@ class mxCellOverlay extends mxEventSource { * Holds the offset as an . The offset will be scaled according to the * current scale. */ - offset: mxPoint | null = null; + offset: mxPoint = new mxPoint(); /** * Variable: cursor @@ -172,11 +172,12 @@ class mxCellOverlay extends mxEventSource { const s = state.view.scale; let pt = null; - const w = this.image.width; - const h = this.image.height; + const image = this.image; + const w = image.width; + const h = image.height; if (isEdge) { - const pts = state.absolutePoints; + const pts = state.absolutePoints; if (pts.length % 2 === 1) { pt = pts[Math.floor(pts.length / 2)]; diff --git a/src/mxgraph/view/cell/mxCellRenderer.ts b/src/mxgraph/view/cell/mxCellRenderer.ts index fcfc44970..f2abcb306 100644 --- a/src/mxgraph/view/cell/mxCellRenderer.ts +++ b/src/mxgraph/view/cell/mxCellRenderer.ts @@ -33,6 +33,7 @@ import mxPoint from '../../util/datatypes/mxPoint'; import mxShape from '../../shape/mxShape'; import mxCellState from '../../util/datatypes/mxCellState'; import mxCell from './mxCell'; +import mxGraphModel from "../graph/mxGraphModel"; class mxCellRenderer { /** @@ -56,7 +57,7 @@ class mxCellRenderer { * * Defines the default shape for vertices. Default is . */ - defaultVertexShape: typeof mxShape = mxRectangleShape; + defaultVertexShape: typeof mxRectangleShape = mxRectangleShape; /** * Variable: defaultTextShape @@ -1532,7 +1533,8 @@ class mxCellRenderer { force: boolean = false, rendering: boolean = true ): boolean { - const { model } = state.view.graph; + const model = state.view.graph.model; + let shapeChanged = false; // Forces creation of new shape if shape style has changed diff --git a/src/mxgraph/view/cell/mxCellStatePreview.ts b/src/mxgraph/view/cell/mxCellStatePreview.ts index 1920a105f..bec7a98ad 100644 --- a/src/mxgraph/view/cell/mxCellStatePreview.ts +++ b/src/mxgraph/view/cell/mxCellStatePreview.ts @@ -10,6 +10,7 @@ import mxDictionary from '../../util/datatypes/mxDictionary'; import mxCellState from '../../util/datatypes/mxCellState'; import mxCell from './mxCell'; import mxGraph from '../graph/mxGraph'; +import mxGraphView from "../graph/mxGraphView"; class mxCellStatePreview { /** @@ -17,14 +18,14 @@ class mxCellStatePreview { * * Reference to the enclosing . */ - graph: mxGraph | null = null; + graph: mxGraph; /** * Variable: deltas * * Reference to the enclosing . */ - deltas: mxDictionary | null = null; + deltas: mxDictionary; /** * Variable: count @@ -96,22 +97,18 @@ class mxCellStatePreview { * Function: show */ show(visitor: Function | null = null) { - this.deltas.visit( - mxUtils.bind(this, (key, delta) => { - this.translateState(delta.state, delta.point.x, delta.point.y); - }) - ); + this.deltas.visit((key: string, delta: any) => { + this.translateState(delta.state, delta.point.x, delta.point.y); + }); - this.deltas.visit( - mxUtils.bind(this, (key, delta) => { - this.revalidateState( - delta.state, - delta.point.x, - delta.point.y, - visitor - ); - }) - ); + this.deltas.visit((key: string, delta: any) => { + this.revalidateState( + delta.state, + delta.point.x, + delta.point.y, + visitor + ); + }); } /** @@ -122,7 +119,7 @@ class mxCellStatePreview { const model = this.graph.getModel(); if (model.isVertex(state.cell)) { - state.view.updateCellState(state); + (state.view).updateCellState(state); const geo = model.getGeometry(state.cell); // Moves selection cells and non-relative vertices in @@ -142,7 +139,7 @@ class mxCellStatePreview { for (let i = 0; i < childCount; i += 1) { this.translateState( - state.view.getState(model.getChildAt(state.cell, i)), + (state.view).getState(model.getChildAt(state.cell, i)), dx, dy ); @@ -168,8 +165,8 @@ class mxCellStatePreview { state.view.updateCellState(state); } - const geo = this.graph.getCellGeometry(state.cell); - const pState = state.view.getState(model.getParent(state.cell)); + const geo = this.graph.getCellGeometry(state.cell); + const pState = state.view.getState(model.getParent(state.cell)); // Moves selection vertices which are relative if ( @@ -210,10 +207,10 @@ class mxCellStatePreview { */ addEdges(state: mxCellState): void { const model = this.graph.getModel(); - const edgeCount = model.getEdgeCount(state.cell); + const edgeCount = model.getEdgeCount(state.cell); for (let i = 0; i < edgeCount; i += 1) { - const s = state.view.getState(model.getEdgeAt(state.cell, i)); + const s = state.view.getState(model.getEdgeAt(state.cell, i)); if (s != null) { this.moveState(s, 0, 0); diff --git a/src/mxgraph/view/connection/mxConnectionConstraint.ts b/src/mxgraph/view/connection/mxConnectionConstraint.ts index bf5b82e75..65e75eba0 100644 --- a/src/mxgraph/view/connection/mxConnectionConstraint.ts +++ b/src/mxgraph/view/connection/mxConnectionConstraint.ts @@ -63,7 +63,7 @@ class mxConnectionConstraint { */ constructor(point: mxPoint | null=null, perimeter: boolean=true, - name: string='', + name: string | null=null, dx: number | null=null, dy: number | null=null) { this.point = point; diff --git a/src/mxgraph/view/graph/mxGraph.ts b/src/mxgraph/view/graph/mxGraph.ts index a17c0a37f..b858f0398 100644 --- a/src/mxgraph/view/graph/mxGraph.ts +++ b/src/mxgraph/view/graph/mxGraph.ts @@ -53,7 +53,686 @@ import mxCodecRegistry from "../../serialization/mxCodecRegistry"; import mxShape from "../../shape/mxShape"; import mxLabel from "../../shape/mxLabel"; +/** + * Class: mxGraph + * + * Extends to implement a graph component for + * the browser. This is the main class of the package. To activate + * panning and connections use and . + * For rubberband selection you must create a new instance of + * . The following listeners are added to + * by default: + * + * - : that displays tooltips + * - : for panning and popup menus + * - : for creating connections + * - : for moving and cloning cells + * + * These listeners will be called in the above order if they are enabled. + * + * Background Images: + * + * To display a background image, set the image, image width and + * image height using . If one of the + * above values has changed then the 's + * should be invoked. + * + * Cell Images: + * + * To use images in cells, a shape must be specified in the default + * vertex style (or any named style). Possible shapes are + * and . + * The code to change the shape used in the default vertex style, + * the following code is used: + * + * (code) + * let style = graph.getStylesheet().getDefaultVertexStyle(); + * style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_IMAGE; + * (end) + * + * For the default vertex style, the image to be displayed can be + * specified in a cell's style using the + * key and the image URL as a value, for example: + * + * (code) + * image=http://www.example.com/image.gif + * (end) + * + * For a named style, the the stylename must be the first element + * of the cell style: + * + * (code) + * stylename;image=http://www.example.com/image.gif + * (end) + * + * A cell style can have any number of key=value pairs added, divided + * by a semicolon as follows: + * + * (code) + * [stylename;|key=value;] + * (end) + * + * Labels: + * + * The cell labels are defined by which uses + * if is true. If a label must be rendered as HTML markup, then + * should return true for the respective cell. If all labels + * contain HTML markup, can be set to true. NOTE: Enabling HTML + * labels carries a possible security risk (see the section on security in + * the manual). + * + * If wrapping is needed for a label, then and must + * return true for the cell whose label should be wrapped. See for + * an example. + * + * If clipping is needed to keep the rendering of a HTML label inside the + * bounds of its vertex, then should return true for the + * respective cell. + * + * By default, edge labels are movable and vertex labels are fixed. This can be + * changed by setting and , or by + * overriding . + * + * In-place Editing: + * + * In-place editing is started with a doubleclick or by typing F2. + * Programmatically, is used to check if the cell is editable + * () and call , which invokes + * . The editor uses the value returned + * by as the editing value. + * + * After in-place editing, is called, which invokes + * , which in turn calls + * via . + * + * The event that triggers in-place editing is passed through to the + * , which may take special actions depending on the type of the + * event or mouse location, and is also passed to . The event + * is then passed back to the event processing functions which can perform + * specific actions based on the trigger event. + * + * Tooltips: + * + * Tooltips are implemented by , which calls + * if a cell is under the mousepointer. The default implementation checks if + * the cell has a getTooltip function and calls it if it exists. Hence, in order + * to provide custom tooltips, the cell must provide a getTooltip function, or + * one of the two above functions must be overridden. + * + * Typically, for custom cell tooltips, the latter function is overridden as + * follows: + * + * (code) + * graph.getTooltipForCell = (cell)=> + * { + * let label = this.convertValueToString(cell); + * return 'Tooltip for '+label; + * } + * (end) + * + * When using a config file, the function is overridden in the mxGraph section + * using the following entry: + * + * (code) + * + * { + * let label = this.convertValueToString(cell); + * return 'Tooltip for '+label; + * } + * ]]> + * (end) + * + * "this" refers to the graph in the implementation, so for example to check if + * a cell is an edge, you use this.getModel().isEdge(cell) + * + * For replacing the default implementation of (rather than + * replacing the function on a specific instance), the following code should be + * used after loading the JavaScript files, but before creating a new mxGraph + * instance using : + * + * (code) + * getTooltipForCell = (cell)=> + * { + * let label = this.convertValueToString(cell); + * return 'Tooltip for '+label; + * } + * (end) + * + * Shapes & Styles: + * + * The implementation of new shapes is demonstrated in the examples. We'll assume + * that we have implemented a custom shape with the name BoxShape which we want + * to use for drawing vertices. To use this shape, it must first be registered in + * the cell renderer as follows: + * + * (code) + * mxCellRenderer.registerShape('box', BoxShape); + * (end) + * + * The code registers the BoxShape constructor under the name box in the cell + * renderer of the graph. The shape can now be referenced using the shape-key in + * a style definition. (The cell renderer contains a set of additional shapes, + * namely one for each constant with a SHAPE-prefix in .) + * + * Styles are a collection of key, value pairs and a stylesheet is a collection + * of named styles. The names are referenced by the cellstyle, which is stored + * in with the following format: [stylename;|key=value;]. The + * string is resolved to a collection of key, value pairs, where the keys are + * overridden with the values in the string. + * + * When introducing a new shape, the name under which the shape is registered + * must be used in the stylesheet. There are three ways of doing this: + * + * - By changing the default style, so that all vertices will use the new + * shape + * - By defining a new style, so that only vertices with the respective + * cellstyle will use the new shape + * - By using shape=box in the cellstyle's optional list of key, value pairs + * to be overridden + * + * In the first case, the code to fetch and modify the default style for + * vertices is as follows: + * + * (code) + * let style = graph.getStylesheet().getDefaultVertexStyle(); + * style[mxConstants.STYLE_SHAPE] = 'box'; + * (end) + * + * The code takes the default vertex style, which is used for all vertices that + * do not have a specific cellstyle, and modifies the value for the shape-key + * in-place to use the new BoxShape for drawing vertices. This is done by + * assigning the box value in the second line, which refers to the name of the + * BoxShape in the cell renderer. + * + * In the second case, a collection of key, value pairs is created and then + * added to the stylesheet under a new name. In order to distinguish the + * shapename and the stylename we'll use boxstyle for the stylename: + * + * (code) + * let style = {}; + * style[mxConstants.STYLE_SHAPE] = 'box'; + * style[mxConstants.STYLE_STROKECOLOR] = '#000000'; + * style[mxConstants.STYLE_FONTCOLOR] = '#000000'; + * graph.getStylesheet().putCellStyle('boxstyle', style); + * (end) + * + * The code adds a new style with the name boxstyle to the stylesheet. To use + * this style with a cell, it must be referenced from the cellstyle as follows: + * + * (code) + * let vertex = graph.insertVertex(parent, null, 'Hello, World!', 20, 20, 80, 20, + * 'boxstyle'); + * (end) + * + * To summarize, each new shape must be registered in the with + * a unique name. That name is then used as the value of the shape-key in a + * default or custom style. If there are multiple custom shapes, then there + * should be a separate style for each shape. + * + * Inheriting Styles: + * + * For fill-, stroke-, gradient-, font- and indicatorColors special keywords + * can be used. The inherit keyword for one of these colors will inherit the + * color for the same key from the parent cell. The swimlane keyword does the + * same, but inherits from the nearest swimlane in the ancestor hierarchy. + * Finally, the indicated keyword will use the color of the indicator as the + * color for the given key. + * + * Scrollbars: + * + * The overflow CSS property defines if scrollbars are used to + * display the graph. For values of 'auto' or 'scroll', the scrollbars will + * be shown. Note that the flag is normally not used + * together with scrollbars, as it will resize the container to match the + * size of the graph after each change. + * + * Multiplicities and Validation: + * + * To control the possible connections in mxGraph, is + * used. The default implementation of the function uses , + * which is an array of . Using this class allows to establish + * simple multiplicities, which are enforced by the graph. + * + * The uses to determine for which terminals it + * applies. The default implementation of works with DOM nodes (XML + * nodes) and checks if the given type parameter matches the nodeName of the + * node (case insensitive). Optionally, an attributename and value can be + * specified which are also checked. + * + * is called whenever the connectivity of an edge + * changes. It returns an empty string or an error message if the edge is + * invalid or null if the edge is valid. If the returned string is not empty + * then it is displayed as an error message. + * + * allows to specify the multiplicity between a terminal and + * its possible neighbors. For example, if any rectangle may only be connected + * to, say, a maximum of two circles you can add the following rule to + * : + * + * (code) + * graph.multiplicities.push(new mxMultiplicity( + * true, 'rectangle', null, null, 0, 2, ['circle'], + * 'Only 2 targets allowed', + * 'Only shape targets allowed')); + * (end) + * + * This will display the first error message whenever a rectangle is connected + * to more than two circles and the second error message if a rectangle is + * connected to anything but a circle. + * + * For certain multiplicities, such as a minimum of 1 connection, which cannot + * be enforced at cell creation time (unless the cell is created together with + * the connection), mxGraph offers which checks all multiplicities + * for all cells and displays the respective error messages in an overlay icon + * on the cells. + * + * If a cell is collapsed and contains validation errors, a respective warning + * icon is attached to the collapsed cell. + * + * Auto-Layout: + * + * For automatic layout, the hook is provided in . + * It can be overridden to return a layout algorithm for the children of a + * given cell. + * + * Unconnected edges: + * + * The default values for all switches are designed to meet the requirements of + * general diagram drawing applications. A very typical set of settings to + * avoid edges that are not connected is the following: + * + * (code) + * graph.setAllowDanglingEdges(false); + * graph.setDisconnectOnMove(false); + * (end) + * + * Setting the switch to true is optional. This switch + * controls if edges are inserted after a copy, paste or clone-drag if they are + * invalid. For example, edges are invalid if copied or control-dragged without + * having selected the corresponding terminals and allowDanglingEdges is + * false, in which case the edges will not be cloned if the switch is false. + * + * Output: + * + * To produce an XML representation for a diagram, the following code can be + * used. + * + * (code) + * let enc = new mxCodec(mxUtils.createXmlDocument()); + * let node = enc.encode(graph.getModel()); + * (end) + * + * This will produce an XML node than can be handled using the DOM API or + * turned into a string representation using the following code: + * + * (code) + * let xml = mxUtils.getXml(node); + * (end) + * + * To obtain a formatted string, mxUtils.getPrettyXml can be used instead. + * + * This string can now be stored in a local persistent storage (for example + * using Google Gears) or it can be passed to a backend using mxUtils.post as + * follows. The url variable is the URL of the Java servlet, PHP page or HTTP + * handler, depending on the server. + * + * (code) + * let xmlString = encodeURIComponent(mxUtils.getXml(node)); + * mxUtils.post(url, 'xml='+xmlString, (req)=> + * { + * // Process server response using req of type mxXmlRequest + * }); + * (end) + * + * Input: + * + * To load an XML representation of a diagram into an existing graph object + * mxUtils.load can be used as follows. The url variable is the URL of the Java + * servlet, PHP page or HTTP handler that produces the XML string. + * + * (code) + * let xmlDoc = mxUtils.load(url).getXml(); + * let node = xmlDoc.documentElement; + * let dec = new mxCodec(node.ownerDocument); + * dec.decode(node, graph.getModel()); + * (end) + * + * For creating a page that loads the client and a diagram using a single + * request please refer to the deployment examples in the backends. + * + * Functional dependencies: + * + * (see images/callgraph.png) + * + * Resources: + * + * resources/graph - Language resources for mxGraph + * + * Group: Events + * + * Event: mxEvent.ROOT + * + * Fires if the root in the model has changed. This event has no properties. + * + * Event: mxEvent.ALIGN_CELLS + * + * Fires between begin- and endUpdate in . The cells + * and align properties contain the respective arguments that were + * passed to . + * + * Event: mxEvent.FLIP_EDGE + * + * Fires between begin- and endUpdate in . The edge + * property contains the edge passed to . + * + * Event: mxEvent.ORDER_CELLS + * + * Fires between begin- and endUpdate in . The cells + * and back properties contain the respective arguments that were + * passed to . + * + * Event: mxEvent.CELLS_ORDERED + * + * Fires between begin- and endUpdate in . The cells + * and back arguments contain the respective arguments that were + * passed to . + * + * Event: mxEvent.GROUP_CELLS + * + * Fires between begin- and endUpdate in . The group, + * cells and border arguments contain the respective + * arguments that were passed to . + * + * Event: mxEvent.UNGROUP_CELLS + * + * Fires between begin- and endUpdate in . The cells + * property contains the array of cells that was passed to . + * + * Event: mxEvent.REMOVE_CELLS_FROM_PARENT + * + * Fires between begin- and endUpdate in . The + * cells property contains the array of cells that was passed to + * . + * + * Event: mxEvent.ADD_CELLS + * + * Fires between begin- and endUpdate in . The cells, + * parent, index, source and + * target properties contain the respective arguments that were + * passed to . + * + * Event: mxEvent.CELLS_ADDED + * + * Fires between begin- and endUpdate in . The cells, + * parent, index, source, + * target and absolute properties contain the + * respective arguments that were passed to . + * + * Event: mxEvent.REMOVE_CELLS + * + * Fires between begin- and endUpdate in . The cells + * and includeEdges arguments contain the respective arguments + * that were passed to . + * + * Event: mxEvent.CELLS_REMOVED + * + * Fires between begin- and endUpdate in . The cells + * argument contains the array of cells that was removed. + * + * Event: mxEvent.SPLIT_EDGE + * + * Fires between begin- and endUpdate in . The edge + * property contains the edge to be splitted, the cells, + * newEdge, dx and dy properties contain + * the respective arguments that were passed to . + * + * Event: mxEvent.TOGGLE_CELLS + * + * Fires between begin- and endUpdate in . The show, + * cells and includeEdges properties contain the + * respective arguments that were passed to . + * + * Event: mxEvent.FOLD_CELLS + * + * Fires between begin- and endUpdate in . The + * collapse, cells and recurse + * properties contain the respective arguments that were passed to . + * + * Event: mxEvent.CELLS_FOLDED + * + * Fires between begin- and endUpdate in cellsFolded. The + * collapse, cells and recurse + * properties contain the respective arguments that were passed to + * . + * + * Event: mxEvent.UPDATE_CELL_SIZE + * + * Fires between begin- and endUpdate in . The + * cell and ignoreChildren properties contain the + * respective arguments that were passed to . + * + * Event: mxEvent.RESIZE_CELLS + * + * Fires between begin- and endUpdate in . The cells + * and bounds properties contain the respective arguments that + * were passed to . + * + * Event: mxEvent.CELLS_RESIZED + * + * Fires between begin- and endUpdate in . The cells + * and bounds properties contain the respective arguments that + * were passed to . + * + * Event: mxEvent.MOVE_CELLS + * + * Fires between begin- and endUpdate in . The cells, + * dx, dy, clone, target + * and event properties contain the respective arguments that + * were passed to . + * + * Event: mxEvent.CELLS_MOVED + * + * Fires between begin- and endUpdate in . The cells, + * dx, dy and disconnect properties + * contain the respective arguments that were passed to . + * + * Event: mxEvent.CONNECT_CELL + * + * Fires between begin- and endUpdate in . The edge, + * terminal and source properties contain the + * respective arguments that were passed to . + * + * Event: mxEvent.CELL_CONNECTED + * + * Fires between begin- and endUpdate in . The + * edge, terminal and source properties + * contain the respective arguments that were passed to . + * + * Event: mxEvent.REFRESH + * + * Fires after was executed. This event has no properties. + * + * Event: mxEvent.CLICK + * + * Fires in after a click event. The event property + * contains the original mouse event and cell property contains + * the cell under the mouse or null if the background was clicked. + * + * Event: mxEvent.DOUBLE_CLICK + * + * Fires in after a double click. The event property + * contains the original mouse event and the cell property + * contains the cell under the mouse or null if the background was clicked. + * + * Event: mxEvent.GESTURE + * + * Fires in after a touch gesture. The event + * property contains the original gesture end event and the cell + * property contains the optional cell associated with the gesture. + * + * Event: mxEvent.TAP_AND_HOLD + * + * Fires in if a tap and hold event was detected. The event + * property contains the initial touch event and the cell property + * contains the cell under the mouse or null if the background was clicked. + * + * Event: mxEvent.FIRE_MOUSE_EVENT + * + * Fires in before the mouse listeners are invoked. The + * eventName property contains the event name and the + * event property contains the . + * + * Event: mxEvent.SIZE + * + * Fires after was executed. The bounds property + * contains the new graph bounds. + * + * Event: mxEvent.START_EDITING + * + * Fires before the in-place editor starts in . The + * cell property contains the cell that is being edited and the + * event property contains the optional event argument that was + * passed to . + * + * Event: mxEvent.EDITING_STARTED + * + * Fires after the in-place editor starts in . The + * cell property contains the cell that is being edited and the + * event property contains the optional event argument that was + * passed to . + * + * Event: mxEvent.EDITING_STOPPED + * + * Fires after the in-place editor stops in . + * + * Event: mxEvent.LABEL_CHANGED + * + * Fires between begin- and endUpdate in . The + * cell property contains the cell, the value + * property contains the new value for the cell, the old property + * contains the old value and the optional event property contains + * the mouse event that started the edit. + * + * Event: mxEvent.ADD_OVERLAY + * + * Fires after an overlay is added in . The cell + * property contains the cell and the overlay property contains + * the that was added. + * + * Event: mxEvent.REMOVE_OVERLAY + * + * Fires after an overlay is removed in and + * . The cell property contains the cell and + * the overlay property contains the that was + * removed. + * + * Constructor: mxGraph + * + * Constructs a new mxGraph in the specified container. Model is an optional + * mxGraphModel. If no model is provided, a new mxGraphModel instance is + * used as the model. The container must have a valid owner document prior + * to calling this function in Internet Explorer. + * + * Example: + * + * To create a graph inside a DOM node with an id of graph: + * (code) + * let container = document.getElementById('graph'); + * let graph = new mxGraph(container); + * (end) + * + * Parameters: + * + * container - Optional DOM node that acts as a container for the graph. + * If this is null then the container can be initialized later using + * . + * model - Optional that constitutes the graph data. + * stylesheet - Optional to be used in the graph. + */ class mxGraph extends mxEventSource { + constructor(container: HTMLElement, + model: mxGraphModel, + renderHint: string=mxConstants.DIALECT_SVG, + stylesheet: mxStylesheet | null=null) { + super(); + + // Initializes the variable in case the prototype has been + // modified to hold some listeners (which is possible because + // the createHandlers call is executed regardless of the + // arguments passed into the ctor). + this.mouseListeners = null; + + // Converts the renderHint into a dialect + this.renderHint = renderHint; + this.dialect = 'svg'; + + // Initializes the main members that do not require a container + this.model = model != null ? model : new mxGraphModel(); + this.multiplicities = []; + this.imageBundles = []; + this.cellRenderer = this.createCellRenderer(); + this.setSelectionModel(this.createSelectionModel()); + this.setStylesheet( + stylesheet != null ? stylesheet : this.createStylesheet() + ); + this.view = this.createGraphView(); + + // Adds a graph model listener to update the view + this.graphModelChangeListener = (sender: any, evt: mxEventObject) => { + this.graphModelChanged(evt.getProperty('edit').changes); + }; + + this.getModel().addListener(mxEvent.CHANGE, this.graphModelChangeListener); + + // Installs basic event handlers with disabled default settings. + this.createHandlers(); + + // Initializes the display if a container was specified + if (container != null) { + this.init(container); + } + + this.getView().revalidate(); + } + + /** + * Function: init + * + * Initializes the and creates the respective datastructures. + * + * Parameters: + * + * container - DOM node that will contain the graph display. + */ + init(container: HTMLElement): void { + this.container = container; + + // Initializes the in-place editor + this.cellEditor = this.createCellEditor(); + + // Initializes the container using the view + this.getView().init(); + + // Updates the size of the container for the current graph + this.sizeDidChange(); + + // Hides tooltips and resets tooltip timer if mouse leaves container + mxEvent.addListener( + container, + 'mouseleave', + (evt: MouseEvent) => { + if ( + this.tooltipHandler != null && + this.tooltipHandler.div != null && + this.tooltipHandler.div != evt.relatedTarget + ) { + this.tooltipHandler.hide(); + } + } + ); + } + // TODO: Document me! container: HTMLElement | null = null; destroyed: boolean = false; @@ -112,7 +791,7 @@ class mxGraph extends mxEventSource { * * Holds the that caches the for the cells. */ - view: mxGraphView | null = null; + view: mxGraphView; /** * Variable: stylesheet @@ -152,7 +831,7 @@ class mxGraph extends mxEventSource { * * Holds the for rendering the cells in the graph. */ - cellRenderer: mxCellRenderer = null; + cellRenderer: mxCellRenderer; /** * Variable: multiplicities @@ -328,7 +1007,7 @@ class mxGraph extends mxEventSource { * Specifies the alternate edge style to be used if the main control point * on an edge is being doubleclicked. Default is null. */ - alternateEdgeStyle: mxEdgeStyle | null = null; + alternateEdgeStyle: string | null = null; /** * Variable: backgroundImage @@ -1029,729 +1708,6 @@ class mxGraph extends mxEventSource { collapseExpandResource: string = mxClient.language != 'none' ? 'collapse-expand' : ''; - /** - * Class: mxGraph - * - * Extends to implement a graph component for - * the browser. This is the main class of the package. To activate - * panning and connections use and . - * For rubberband selection you must create a new instance of - * . The following listeners are added to - * by default: - * - * - : that displays tooltips - * - : for panning and popup menus - * - : for creating connections - * - : for moving and cloning cells - * - * These listeners will be called in the above order if they are enabled. - * - * Background Images: - * - * To display a background image, set the image, image width and - * image height using . If one of the - * above values has changed then the 's - * should be invoked. - * - * Cell Images: - * - * To use images in cells, a shape must be specified in the default - * vertex style (or any named style). Possible shapes are - * and . - * The code to change the shape used in the default vertex style, - * the following code is used: - * - * (code) - * let style = graph.getStylesheet().getDefaultVertexStyle(); - * style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_IMAGE; - * (end) - * - * For the default vertex style, the image to be displayed can be - * specified in a cell's style using the - * key and the image URL as a value, for example: - * - * (code) - * image=http://www.example.com/image.gif - * (end) - * - * For a named style, the the stylename must be the first element - * of the cell style: - * - * (code) - * stylename;image=http://www.example.com/image.gif - * (end) - * - * A cell style can have any number of key=value pairs added, divided - * by a semicolon as follows: - * - * (code) - * [stylename;|key=value;] - * (end) - * - * Labels: - * - * The cell labels are defined by which uses - * if is true. If a label must be rendered as HTML markup, then - * should return true for the respective cell. If all labels - * contain HTML markup, can be set to true. NOTE: Enabling HTML - * labels carries a possible security risk (see the section on security in - * the manual). - * - * If wrapping is needed for a label, then and must - * return true for the cell whose label should be wrapped. See for - * an example. - * - * If clipping is needed to keep the rendering of a HTML label inside the - * bounds of its vertex, then should return true for the - * respective cell. - * - * By default, edge labels are movable and vertex labels are fixed. This can be - * changed by setting and , or by - * overriding . - * - * In-place Editing: - * - * In-place editing is started with a doubleclick or by typing F2. - * Programmatically, is used to check if the cell is editable - * () and call , which invokes - * . The editor uses the value returned - * by as the editing value. - * - * After in-place editing, is called, which invokes - * , which in turn calls - * via . - * - * The event that triggers in-place editing is passed through to the - * , which may take special actions depending on the type of the - * event or mouse location, and is also passed to . The event - * is then passed back to the event processing functions which can perform - * specific actions based on the trigger event. - * - * Tooltips: - * - * Tooltips are implemented by , which calls - * if a cell is under the mousepointer. The default implementation checks if - * the cell has a getTooltip function and calls it if it exists. Hence, in order - * to provide custom tooltips, the cell must provide a getTooltip function, or - * one of the two above functions must be overridden. - * - * Typically, for custom cell tooltips, the latter function is overridden as - * follows: - * - * (code) - * graph.getTooltipForCell = (cell)=> - * { - * let label = this.convertValueToString(cell); - * return 'Tooltip for '+label; - * } - * (end) - * - * When using a config file, the function is overridden in the mxGraph section - * using the following entry: - * - * (code) - * - * { - * let label = this.convertValueToString(cell); - * return 'Tooltip for '+label; - * } - * ]]> - * (end) - * - * "this" refers to the graph in the implementation, so for example to check if - * a cell is an edge, you use this.getModel().isEdge(cell) - * - * For replacing the default implementation of (rather than - * replacing the function on a specific instance), the following code should be - * used after loading the JavaScript files, but before creating a new mxGraph - * instance using : - * - * (code) - * getTooltipForCell = (cell)=> - * { - * let label = this.convertValueToString(cell); - * return 'Tooltip for '+label; - * } - * (end) - * - * Shapes & Styles: - * - * The implementation of new shapes is demonstrated in the examples. We'll assume - * that we have implemented a custom shape with the name BoxShape which we want - * to use for drawing vertices. To use this shape, it must first be registered in - * the cell renderer as follows: - * - * (code) - * mxCellRenderer.registerShape('box', BoxShape); - * (end) - * - * The code registers the BoxShape constructor under the name box in the cell - * renderer of the graph. The shape can now be referenced using the shape-key in - * a style definition. (The cell renderer contains a set of additional shapes, - * namely one for each constant with a SHAPE-prefix in .) - * - * Styles are a collection of key, value pairs and a stylesheet is a collection - * of named styles. The names are referenced by the cellstyle, which is stored - * in with the following format: [stylename;|key=value;]. The - * string is resolved to a collection of key, value pairs, where the keys are - * overridden with the values in the string. - * - * When introducing a new shape, the name under which the shape is registered - * must be used in the stylesheet. There are three ways of doing this: - * - * - By changing the default style, so that all vertices will use the new - * shape - * - By defining a new style, so that only vertices with the respective - * cellstyle will use the new shape - * - By using shape=box in the cellstyle's optional list of key, value pairs - * to be overridden - * - * In the first case, the code to fetch and modify the default style for - * vertices is as follows: - * - * (code) - * let style = graph.getStylesheet().getDefaultVertexStyle(); - * style[mxConstants.STYLE_SHAPE] = 'box'; - * (end) - * - * The code takes the default vertex style, which is used for all vertices that - * do not have a specific cellstyle, and modifies the value for the shape-key - * in-place to use the new BoxShape for drawing vertices. This is done by - * assigning the box value in the second line, which refers to the name of the - * BoxShape in the cell renderer. - * - * In the second case, a collection of key, value pairs is created and then - * added to the stylesheet under a new name. In order to distinguish the - * shapename and the stylename we'll use boxstyle for the stylename: - * - * (code) - * let style = {}; - * style[mxConstants.STYLE_SHAPE] = 'box'; - * style[mxConstants.STYLE_STROKECOLOR] = '#000000'; - * style[mxConstants.STYLE_FONTCOLOR] = '#000000'; - * graph.getStylesheet().putCellStyle('boxstyle', style); - * (end) - * - * The code adds a new style with the name boxstyle to the stylesheet. To use - * this style with a cell, it must be referenced from the cellstyle as follows: - * - * (code) - * let vertex = graph.insertVertex(parent, null, 'Hello, World!', 20, 20, 80, 20, - * 'boxstyle'); - * (end) - * - * To summarize, each new shape must be registered in the with - * a unique name. That name is then used as the value of the shape-key in a - * default or custom style. If there are multiple custom shapes, then there - * should be a separate style for each shape. - * - * Inheriting Styles: - * - * For fill-, stroke-, gradient-, font- and indicatorColors special keywords - * can be used. The inherit keyword for one of these colors will inherit the - * color for the same key from the parent cell. The swimlane keyword does the - * same, but inherits from the nearest swimlane in the ancestor hierarchy. - * Finally, the indicated keyword will use the color of the indicator as the - * color for the given key. - * - * Scrollbars: - * - * The overflow CSS property defines if scrollbars are used to - * display the graph. For values of 'auto' or 'scroll', the scrollbars will - * be shown. Note that the flag is normally not used - * together with scrollbars, as it will resize the container to match the - * size of the graph after each change. - * - * Multiplicities and Validation: - * - * To control the possible connections in mxGraph, is - * used. The default implementation of the function uses , - * which is an array of . Using this class allows to establish - * simple multiplicities, which are enforced by the graph. - * - * The uses to determine for which terminals it - * applies. The default implementation of works with DOM nodes (XML - * nodes) and checks if the given type parameter matches the nodeName of the - * node (case insensitive). Optionally, an attributename and value can be - * specified which are also checked. - * - * is called whenever the connectivity of an edge - * changes. It returns an empty string or an error message if the edge is - * invalid or null if the edge is valid. If the returned string is not empty - * then it is displayed as an error message. - * - * allows to specify the multiplicity between a terminal and - * its possible neighbors. For example, if any rectangle may only be connected - * to, say, a maximum of two circles you can add the following rule to - * : - * - * (code) - * graph.multiplicities.push(new mxMultiplicity( - * true, 'rectangle', null, null, 0, 2, ['circle'], - * 'Only 2 targets allowed', - * 'Only shape targets allowed')); - * (end) - * - * This will display the first error message whenever a rectangle is connected - * to more than two circles and the second error message if a rectangle is - * connected to anything but a circle. - * - * For certain multiplicities, such as a minimum of 1 connection, which cannot - * be enforced at cell creation time (unless the cell is created together with - * the connection), mxGraph offers which checks all multiplicities - * for all cells and displays the respective error messages in an overlay icon - * on the cells. - * - * If a cell is collapsed and contains validation errors, a respective warning - * icon is attached to the collapsed cell. - * - * Auto-Layout: - * - * For automatic layout, the hook is provided in . - * It can be overridden to return a layout algorithm for the children of a - * given cell. - * - * Unconnected edges: - * - * The default values for all switches are designed to meet the requirements of - * general diagram drawing applications. A very typical set of settings to - * avoid edges that are not connected is the following: - * - * (code) - * graph.setAllowDanglingEdges(false); - * graph.setDisconnectOnMove(false); - * (end) - * - * Setting the switch to true is optional. This switch - * controls if edges are inserted after a copy, paste or clone-drag if they are - * invalid. For example, edges are invalid if copied or control-dragged without - * having selected the corresponding terminals and allowDanglingEdges is - * false, in which case the edges will not be cloned if the switch is false. - * - * Output: - * - * To produce an XML representation for a diagram, the following code can be - * used. - * - * (code) - * let enc = new mxCodec(mxUtils.createXmlDocument()); - * let node = enc.encode(graph.getModel()); - * (end) - * - * This will produce an XML node than can be handled using the DOM API or - * turned into a string representation using the following code: - * - * (code) - * let xml = mxUtils.getXml(node); - * (end) - * - * To obtain a formatted string, mxUtils.getPrettyXml can be used instead. - * - * This string can now be stored in a local persistent storage (for example - * using Google Gears) or it can be passed to a backend using mxUtils.post as - * follows. The url variable is the URL of the Java servlet, PHP page or HTTP - * handler, depending on the server. - * - * (code) - * let xmlString = encodeURIComponent(mxUtils.getXml(node)); - * mxUtils.post(url, 'xml='+xmlString, (req)=> - * { - * // Process server response using req of type mxXmlRequest - * }); - * (end) - * - * Input: - * - * To load an XML representation of a diagram into an existing graph object - * mxUtils.load can be used as follows. The url variable is the URL of the Java - * servlet, PHP page or HTTP handler that produces the XML string. - * - * (code) - * let xmlDoc = mxUtils.load(url).getXml(); - * let node = xmlDoc.documentElement; - * let dec = new mxCodec(node.ownerDocument); - * dec.decode(node, graph.getModel()); - * (end) - * - * For creating a page that loads the client and a diagram using a single - * request please refer to the deployment examples in the backends. - * - * Functional dependencies: - * - * (see images/callgraph.png) - * - * Resources: - * - * resources/graph - Language resources for mxGraph - * - * Group: Events - * - * Event: mxEvent.ROOT - * - * Fires if the root in the model has changed. This event has no properties. - * - * Event: mxEvent.ALIGN_CELLS - * - * Fires between begin- and endUpdate in . The cells - * and align properties contain the respective arguments that were - * passed to . - * - * Event: mxEvent.FLIP_EDGE - * - * Fires between begin- and endUpdate in . The edge - * property contains the edge passed to . - * - * Event: mxEvent.ORDER_CELLS - * - * Fires between begin- and endUpdate in . The cells - * and back properties contain the respective arguments that were - * passed to . - * - * Event: mxEvent.CELLS_ORDERED - * - * Fires between begin- and endUpdate in . The cells - * and back arguments contain the respective arguments that were - * passed to . - * - * Event: mxEvent.GROUP_CELLS - * - * Fires between begin- and endUpdate in . The group, - * cells and border arguments contain the respective - * arguments that were passed to . - * - * Event: mxEvent.UNGROUP_CELLS - * - * Fires between begin- and endUpdate in . The cells - * property contains the array of cells that was passed to . - * - * Event: mxEvent.REMOVE_CELLS_FROM_PARENT - * - * Fires between begin- and endUpdate in . The - * cells property contains the array of cells that was passed to - * . - * - * Event: mxEvent.ADD_CELLS - * - * Fires between begin- and endUpdate in . The cells, - * parent, index, source and - * target properties contain the respective arguments that were - * passed to . - * - * Event: mxEvent.CELLS_ADDED - * - * Fires between begin- and endUpdate in . The cells, - * parent, index, source, - * target and absolute properties contain the - * respective arguments that were passed to . - * - * Event: mxEvent.REMOVE_CELLS - * - * Fires between begin- and endUpdate in . The cells - * and includeEdges arguments contain the respective arguments - * that were passed to . - * - * Event: mxEvent.CELLS_REMOVED - * - * Fires between begin- and endUpdate in . The cells - * argument contains the array of cells that was removed. - * - * Event: mxEvent.SPLIT_EDGE - * - * Fires between begin- and endUpdate in . The edge - * property contains the edge to be splitted, the cells, - * newEdge, dx and dy properties contain - * the respective arguments that were passed to . - * - * Event: mxEvent.TOGGLE_CELLS - * - * Fires between begin- and endUpdate in . The show, - * cells and includeEdges properties contain the - * respective arguments that were passed to . - * - * Event: mxEvent.FOLD_CELLS - * - * Fires between begin- and endUpdate in . The - * collapse, cells and recurse - * properties contain the respective arguments that were passed to . - * - * Event: mxEvent.CELLS_FOLDED - * - * Fires between begin- and endUpdate in cellsFolded. The - * collapse, cells and recurse - * properties contain the respective arguments that were passed to - * . - * - * Event: mxEvent.UPDATE_CELL_SIZE - * - * Fires between begin- and endUpdate in . The - * cell and ignoreChildren properties contain the - * respective arguments that were passed to . - * - * Event: mxEvent.RESIZE_CELLS - * - * Fires between begin- and endUpdate in . The cells - * and bounds properties contain the respective arguments that - * were passed to . - * - * Event: mxEvent.CELLS_RESIZED - * - * Fires between begin- and endUpdate in . The cells - * and bounds properties contain the respective arguments that - * were passed to . - * - * Event: mxEvent.MOVE_CELLS - * - * Fires between begin- and endUpdate in . The cells, - * dx, dy, clone, target - * and event properties contain the respective arguments that - * were passed to . - * - * Event: mxEvent.CELLS_MOVED - * - * Fires between begin- and endUpdate in . The cells, - * dx, dy and disconnect properties - * contain the respective arguments that were passed to . - * - * Event: mxEvent.CONNECT_CELL - * - * Fires between begin- and endUpdate in . The edge, - * terminal and source properties contain the - * respective arguments that were passed to . - * - * Event: mxEvent.CELL_CONNECTED - * - * Fires between begin- and endUpdate in . The - * edge, terminal and source properties - * contain the respective arguments that were passed to . - * - * Event: mxEvent.REFRESH - * - * Fires after was executed. This event has no properties. - * - * Event: mxEvent.CLICK - * - * Fires in after a click event. The event property - * contains the original mouse event and cell property contains - * the cell under the mouse or null if the background was clicked. - * - * Event: mxEvent.DOUBLE_CLICK - * - * Fires in after a double click. The event property - * contains the original mouse event and the cell property - * contains the cell under the mouse or null if the background was clicked. - * - * Event: mxEvent.GESTURE - * - * Fires in after a touch gesture. The event - * property contains the original gesture end event and the cell - * property contains the optional cell associated with the gesture. - * - * Event: mxEvent.TAP_AND_HOLD - * - * Fires in if a tap and hold event was detected. The event - * property contains the initial touch event and the cell property - * contains the cell under the mouse or null if the background was clicked. - * - * Event: mxEvent.FIRE_MOUSE_EVENT - * - * Fires in before the mouse listeners are invoked. The - * eventName property contains the event name and the - * event property contains the . - * - * Event: mxEvent.SIZE - * - * Fires after was executed. The bounds property - * contains the new graph bounds. - * - * Event: mxEvent.START_EDITING - * - * Fires before the in-place editor starts in . The - * cell property contains the cell that is being edited and the - * event property contains the optional event argument that was - * passed to . - * - * Event: mxEvent.EDITING_STARTED - * - * Fires after the in-place editor starts in . The - * cell property contains the cell that is being edited and the - * event property contains the optional event argument that was - * passed to . - * - * Event: mxEvent.EDITING_STOPPED - * - * Fires after the in-place editor stops in . - * - * Event: mxEvent.LABEL_CHANGED - * - * Fires between begin- and endUpdate in . The - * cell property contains the cell, the value - * property contains the new value for the cell, the old property - * contains the old value and the optional event property contains - * the mouse event that started the edit. - * - * Event: mxEvent.ADD_OVERLAY - * - * Fires after an overlay is added in . The cell - * property contains the cell and the overlay property contains - * the that was added. - * - * Event: mxEvent.REMOVE_OVERLAY - * - * Fires after an overlay is removed in and - * . The cell property contains the cell and - * the overlay property contains the that was - * removed. - * - * Constructor: mxGraph - * - * Constructs a new mxGraph in the specified container. Model is an optional - * mxGraphModel. If no model is provided, a new mxGraphModel instance is - * used as the model. The container must have a valid owner document prior - * to calling this function in Internet Explorer. RenderHint is a string to - * affect the display performance and rendering in IE, but not in SVG-based - * browsers. The parameter is mapped to , which may - * be one of for SVG-based browsers, - * for fastest display mode, - * for faster display mode, - * for fast and - * for exact display mode (slowest). The dialects are defined in mxConstants. - * The default values are DIALECT_SVG for SVG-based browsers and - * DIALECT_MIXED for IE. - * - * The possible values for the renderingHint parameter are explained below: - * - * fast - The parameter is based on the fact that the display performance is - * highly improved in IE if the VML is not contained within a VML group - * element. The lack of a group element only slightly affects the display while - * panning, but improves the performance by almost a factor of 2, while keeping - * the display sufficiently accurate. This also allows to render certain shapes as HTML - * if the display accuracy is not affected, which is implemented by - * . This is the default setting and is mapped to - * DIALECT_MIXEDHTML. - * faster - Same as fast, but more expensive shapes are avoided. This is - * controlled by . The default implementation will - * avoid gradients and rounded rectangles, but more significant shapes, such - * as rhombus, ellipse, actor and cylinder will be rendered accurately. This - * setting is mapped to DIALECT_PREFERHTML. - * fastest - Almost anything will be rendered in Html. This allows for - * rectangles, labels and images. This setting is mapped to - * DIALECT_STRICTHTML. - * exact - If accurate panning is required and if the diagram is small (up - * to 100 cells), then this value should be used. In this mode, a group is - * created that contains the VML. This allows for accurate panning and is - * mapped to DIALECT_VML. - * - * Example: - * - * To create a graph inside a DOM node with an id of graph: - * (code) - * let container = document.getElementById('graph'); - * let graph = new mxGraph(container); - * (end) - * - * Parameters: - * - * container - Optional DOM node that acts as a container for the graph. - * If this is null then the container can be initialized later using - * . - * model - Optional that constitutes the graph data. - * renderHint - Optional string that specifies the display accuracy and - * performance. Default is mxConstants.DIALECT_MIXEDHTML (for IE). - * stylesheet - Optional to be used in the graph. - */ - constructor(container: HTMLElement, - model: mxGraphModel, - renderHint: string=mxConstants.DIALECT_MIXEDHTML, - stylesheet: mxStylesheet | null=null) { - super(); - - // Initializes the variable in case the prototype has been - // modified to hold some listeners (which is possible because - // the createHandlers call is executed regardless of the - // arguments passed into the ctor). - this.mouseListeners = null; - - // Converts the renderHint into a dialect - this.renderHint = renderHint; - - if (mxClient.IS_SVG) { - this.dialect = mxConstants.DIALECT_SVG; - } else if (renderHint === mxConstants.RENDERING_HINT_FASTEST) { - this.dialect = mxConstants.DIALECT_STRICTHTML; - } else if (renderHint === mxConstants.RENDERING_HINT_FASTER) { - this.dialect = mxConstants.DIALECT_PREFERHTML; - } // default for VML - else { - this.dialect = mxConstants.DIALECT_MIXEDHTML; - } - - // Initializes the main members that do not require a container - this.model = model != null ? model : new mxGraphModel(); - this.multiplicities = []; - this.imageBundles = []; - this.cellRenderer = this.createCellRenderer(); - this.setSelectionModel(this.createSelectionModel()); - this.setStylesheet( - stylesheet != null ? stylesheet : this.createStylesheet() - ); - this.view = this.createGraphView(); - - // Adds a graph model listener to update the view - this.graphModelChangeListener = (sender: any, evt: mxEventObject) => { - this.graphModelChanged(evt.getProperty('edit').changes); - }; - - this.getModel().addListener(mxEvent.CHANGE, this.graphModelChangeListener); - - // Installs basic event handlers with disabled default settings. - this.createHandlers(); - - // Initializes the display if a container was specified - if (container != null) { - this.init(container); - } - - this.getView().revalidate(); - } - - /** - * Function: init - * - * Initializes the and creates the respective datastructures. - * - * Parameters: - * - * container - DOM node that will contain the graph display. - */ - init(container: HTMLElement): void { - this.container = container; - - // Initializes the in-place editor - this.cellEditor = this.createCellEditor(); - - // Initializes the container using the view - this.getView().init(); - - // Updates the size of the container for the current graph - this.sizeDidChange(); - - // Hides tooltips and resets tooltip timer if mouse leaves container - mxEvent.addListener( - container, - 'mouseleave', - (evt: MouseEvent) => { - if ( - this.tooltipHandler != null && - this.tooltipHandler.div != null && - this.tooltipHandler.div != evt.relatedTarget - ) { - this.tooltipHandler.hide(); - } - } - ); - } - // TODO: Document me!! batchUpdate(fn: Function): void { (this.getModel()).beginUpdate(); @@ -2386,7 +2342,7 @@ class mxGraph extends mxEventSource { * * evt - Optional mouse event that triggered the editing. */ - startEditing(evt: mxMouseEvent) { + startEditing(evt: MouseEvent) { this.startEditingAtCell(null, evt); } @@ -2438,7 +2394,7 @@ class mxGraph extends mxEventSource { * evt - Optional mouse event that triggered the editor. */ getEditingValue(cell: mxCell, - evt: mxEventObject): string | null { + evt: mxEventObject | mxMouseEvent): string | null { return this.convertValueToString(cell); } @@ -2474,7 +2430,7 @@ class mxGraph extends mxEventSource { */ labelChanged(cell: mxCell, value: any, - evt: mxEventObject): mxCell { + evt: mxMouseEvent | mxEventObject): mxCell { this.getModel().beginUpdate(); try { @@ -2858,6 +2814,7 @@ class mxGraph extends mxEventSource { // Updates the clipping region. This is an expensive // operation that should not be executed too often. + // @ts-ignore root.style.width = `${width}px`; c.scrollLeft += border - dx; @@ -2883,6 +2840,7 @@ class mxGraph extends mxEventSource { // Updates the clipping region. This is an expensive // operation that should not be executed too often. + // @ts-ignore root.style.height = `${height}px`; c.scrollTop += border - dy; @@ -3332,7 +3290,7 @@ class mxGraph extends mxEventSource { * cell - whose style should be returned as an array. * ignoreState - Optional boolean that specifies if the cell state should be ignored. */ - getCurrentCellStyle(cell: mxCell, + getCurrentCellStyle(cell: mxCell | null, ignoreState: boolean=false): any { const state = ignoreState ? null : this.getView().getState(cell); @@ -3353,7 +3311,7 @@ class mxGraph extends mxEventSource { * * cell - whose style should be returned as an array. */ - getCellStyle(cell: mxCell): any { + getCellStyle(cell: mxCell | null): any { const stylename = this.getModel().getStyle(cell); let style = null; const stylesheet = this.stylesheet; @@ -3490,7 +3448,7 @@ class mxGraph extends mxEventSource { */ toggleCellStyles(key: string, defaultValue: boolean=false, - cells: mxCell[]=this.getSelectionCells()) { + cells: (mxCell | null)[]=this.getSelectionCells()) { let value = null; @@ -3520,7 +3478,7 @@ class mxGraph extends mxEventSource { */ setCellStyles(key: string, value: string | number | null=null, - cells: mxCell[]=this.getSelectionCells()) { + cells: (mxCell | null)[]=this.getSelectionCells()) { mxUtils.setCellStyles(this.model, cells, key, value); } @@ -3972,9 +3930,9 @@ class mxGraph extends mxEventSource { * * Returns the bounds to be used for the given group and children. */ - getBoundsForGroup(group, - children, - border) { + getBoundsForGroup(group: mxCell, + children: mxCell[], + border: number | null) { const result = this.getBoundingBoxFromGeometry(children, true); @@ -4066,8 +4024,8 @@ class mxGraph extends mxEventSource { if (state != null && geo != null && geo.relative) { geo = geo.clone(); - geo.x = (state).origin.x; - geo.y = (state).origin.y; + geo.x = (state.origin).x; + geo.y = (state.origin).y; geo.relative = false; this.getModel().setGeometry(children[j], geo); @@ -4975,7 +4933,7 @@ class mxGraph extends mxEventSource { // Disconnects edges which are not being removed const edges = this.getAllEdges([cells[i]]); - const disconnectTerminal = mxUtils.bind(this, (edge: mxCell, source: mxCell) => { + const disconnectTerminal = mxUtils.bind(this, (edge: mxCell, source: boolean) => { let geo = this.getModel().getGeometry(edge); if (geo != null) { @@ -6171,7 +6129,7 @@ class mxGraph extends mxEventSource { dict.put(cells[i], true); } - const isSelected = (cell: mxCell) => { + const isSelected = (cell: mxCell | null) => { while (cell != null) { if (dict.get(cell)) { return true; @@ -6813,7 +6771,7 @@ class mxGraph extends mxEventSource { * source - Boolean indicating if the terminal is the source or target. */ getConnectionConstraint(edge: mxCellState, - terminal: mxCellState, + terminal: mxCellState | null=null, source: boolean=false): mxConnectionConstraint { let point = null; // @ts-ignore @@ -7005,8 +6963,8 @@ class mxGraph extends mxEventSource { const { scale } = this.view; point = new mxPoint( - bounds.x + constraint.point.x * bounds.width + constraint.dx * scale, - bounds.y + constraint.point.y * bounds.height + constraint.dy * scale + bounds.x + constraint.point.x * bounds.width + constraint.dx * scale, + bounds.y + constraint.point.y * bounds.height + constraint.dy * scale ); // Rotation for direction before projection on perimeter @@ -7529,7 +7487,7 @@ class mxGraph extends mxEventSource { */ getCellBounds(cell: mxCell, includeEdges: boolean=false, - includeDescendants: boolean=false): mxRectangle { + includeDescendants: boolean=false): mxRectangle | null { let cells = [cell]; @@ -7644,7 +7602,7 @@ class mxGraph extends mxEventSource { bbox = tmp; } else { - const parent = this.getModel().getParent(cells[i]); + const parent = this.getModel().getParent(cell); if (geo.relative) { if ( @@ -7728,7 +7686,7 @@ class mxGraph extends mxEventSource { * * cell - Optional for which the cell states should be cleared. */ - refresh(cell: mxCell): void { + refresh(cell: mxCell | null=null): void { this.getView().clear(cell, cell == null); this.getView().validate(); this.sizeDidChange(); @@ -7834,7 +7792,7 @@ class mxGraph extends mxEventSource { container.scrollLeft = -dx; container.scrollTop = -dy; } else { - const canvas = this.getView().getCanvas(); + const canvas = this.getView().getCanvas(); // Puts everything inside the container in a DIV so that it // can be moved without changing the state of the container @@ -9185,6 +9143,7 @@ class mxGraph extends mxEventSource { let tip = null; if (cell != null && 'getTooltip' in cell) { + // @ts-ignore tip = cell.getTooltip(); } else { tip = this.convertValueToString(cell); @@ -12062,7 +12021,7 @@ class mxGraph extends mxEventSource { * * cell - to be selected. */ - setSelectionCell(cell: mxCell): void { + setSelectionCell(cell: mxCell | null): void { this.getSelectionModel().setCell(cell); } @@ -12201,7 +12160,7 @@ class mxGraph extends mxEventSource { isParent: boolean=false, isChild: boolean=false): void { - const sel = this.selectionModel; + const sel = this.selectionModel; const cell = sel.cells.length > 0 ? sel.cells[0] : null; if (sel.cells.length > 1) { @@ -12232,7 +12191,7 @@ class mxGraph extends mxEventSource { this.setSelectionCell(child); } } else if (childCount > 0) { - let i = parent.getIndex(cell); + let i = (parent).getIndex(cell); if (isNext) { i++; @@ -12411,8 +12370,8 @@ class mxGraph extends mxEventSource { const edgeStyle = this.getView().getEdgeStyle( state, geo != null ? geo.points : null, - source, - target + source, + target ); result = this.createEdgeHandler(state, edgeStyle); } else { @@ -12561,7 +12520,7 @@ class mxGraph extends mxEventSource { evtName === mxEvent.MOUSE_MOVE ) { me.state = this.getView().getState( - this.getCellAt(pt.x, pt.y, null, null, null, state => { + this.getCellAt(pt.x, pt.y, null, true, true, (state: mxCellState) => { return ( state.shape == null || state.shape.paintBackground !== this.paintBackground || diff --git a/src/mxgraph/view/graph/mxGraphModel.ts b/src/mxgraph/view/graph/mxGraphModel.ts index ecbe782c1..9ad8973e6 100644 --- a/src/mxgraph/view/graph/mxGraphModel.ts +++ b/src/mxgraph/view/graph/mxGraphModel.ts @@ -1094,7 +1094,7 @@ class mxGraphModel extends mxEventSource { * isSource - Boolean indicating which end of the edge should be returned. */ getTerminal(edge: mxCell | null, - isSource: boolean=false) { + isSource: boolean=false): mxCell | null { return edge != null ? edge.getTerminal(isSource) : null; } @@ -1623,7 +1623,7 @@ class mxGraphModel extends mxEventSource { * * cell - whose style should be returned. */ - getStyle(cell: mxCell): any { + getStyle(cell: mxCell | null): any { return cell != null ? cell.getStyle() : null; } @@ -1730,7 +1730,7 @@ class mxGraphModel extends mxEventSource { * * cell - whose visible state should be returned. */ - isVisible(cell: mxCell): boolean { + isVisible(cell: mxCell | null): boolean { return cell != null ? cell.isVisible() : false; } diff --git a/src/mxgraph/view/graph/mxGraphSelectionModel.ts b/src/mxgraph/view/graph/mxGraphSelectionModel.ts index 50eab7bcc..4d3f6d16b 100644 --- a/src/mxgraph/view/graph/mxGraphSelectionModel.ts +++ b/src/mxgraph/view/graph/mxGraphSelectionModel.ts @@ -161,7 +161,7 @@ class mxGraphSelectionModel extends mxEventSource { * * cell - to be selected. */ - setCell(cell: mxCell): void { + setCell(cell: mxCell | null): void { if (cell != null) { this.setCells([cell]); } diff --git a/src/mxgraph/view/graph/mxGraphView.ts b/src/mxgraph/view/graph/mxGraphView.ts index 69d852a13..fc8293b78 100644 --- a/src/mxgraph/view/graph/mxGraphView.ts +++ b/src/mxgraph/view/graph/mxGraphView.ts @@ -101,7 +101,7 @@ class mxGraphView extends mxEventSource { * * Returns reference to the enclosing . */ - graph: mxGraph | null = null; + graph: mxGraph; /** * Variable: currentRoot @@ -1548,9 +1548,9 @@ class mxGraphView extends mxEventSource { * associated. */ isLoopStyleEnabled(edge: mxCellState, - points: mxPoint[], - source: mxCellState, - target: mxCellState): boolean { + points: mxPoint[]=[], + source: mxCellState | null=null, + target: mxCellState | null=null): boolean { const sc = (this.graph).getConnectionConstraint(edge, source, true); const tc = (this.graph).getConnectionConstraint(edge, target, false); @@ -1575,9 +1575,9 @@ class mxGraphView extends mxEventSource { * Returns the edge style function to be used to render the given edge state. */ getEdgeStyle(edge: mxCellState, - points: mxPoint[], - source: mxCellState, - target: mxCellState): any { + points: mxPoint[]=[], + source: mxCellState | null=null, + target: mxCellState | null=null): any { let edgeStyle: any = this.isLoopStyleEnabled(edge, points, source, target) ? mxUtils.getValue( @@ -1970,7 +1970,7 @@ class mxGraphView extends mxEventSource { * source - Boolean that specifies if the source or target terminal * should be returned. */ - getVisibleTerminal(edge: mxCell, + getVisibleTerminal(edge: mxCell | null, source: boolean) { const model = (this.graph).getModel(); diff --git a/src/mxgraph/view/graph/mxOutline.ts b/src/mxgraph/view/graph/mxOutline.ts index 41e9bcf3e..ae7bc3a43 100644 --- a/src/mxgraph/view/graph/mxOutline.ts +++ b/src/mxgraph/view/graph/mxOutline.ts @@ -13,10 +13,175 @@ import mxGraph from './mxGraph'; import mxImageShape from '../../shape/node/mxImageShape'; import mxEvent from '../../util/event/mxEvent'; import mxUtils from '../../util/mxUtils'; -import mxClient from '../../mxClient'; import mxImage from '../../util/image/mxImage'; +import mxEventObject from "../../util/event/mxEventObject"; +/** + * Class: mxOutline + * + * Implements an outline (aka overview) for a graph. Set to true + * to enable updates while the source graph is panning. + * + * Example: + * + * (code) + * let outline = new mxOutline(graph, div); + * (end) + * + * To move the graph to the top, left corner the following code can be used. + * + * (code) + * let scale = graph.view.scale; + * let bounds = graph.getGraphBounds(); + * graph.view.setTranslate(-bounds.x / scale, -bounds.y / scale); + * (end) + * + * To toggle the suspended mode, the following can be used. + * + * (code) + * outline.suspended = !outln.suspended; + * if (!outline.suspended) + * { + * outline.update(true); + * } + * (end) + * + * Constructor: mxOutline + * + * Constructs a new outline for the specified graph inside the given + * container. + * + * Parameters: + * + * source - to create the outline for. + * container - DOM node that will contain the outline. + */ class mxOutline { + constructor(source: mxGraph, container: HTMLElement | null = null) { + this.source = source; + + if (container != null) { + this.init(container); + } + } + + /** + * Function: init + * + * Initializes the outline inside the given container. + */ + init(container: HTMLElement) { + this.outline = this.createGraph(container); + + // Do not repaint when suspended + const outlineGraphModelChanged = this.outline.graphModelChanged; + this.outline.graphModelChanged = mxUtils.bind(this, (changes: any) => { + if (!this.suspended && this.outline != null) { + outlineGraphModelChanged.apply(this.outline, [changes]); + } + }); + + // Enable faster painting in SVG + //const node = this.outline.getView().getCanvas().parentNode; + //node.setAttribute('shape-rendering', 'optimizeSpeed'); + //node.setAttribute('image-rendering', 'optimizeSpeed'); + + // Hides cursors and labels + this.outline.labelsVisible = this.labelsVisible; + this.outline.setEnabled(false); + + this.updateHandler = (sender: any, evt: mxEventObject) => { + if (!this.suspended && !this.active) { + this.update(); + } + }; + + // Updates the scale of the outline after a change of the main graph + this.source.getModel().addListener(mxEvent.CHANGE, this.updateHandler); + this.outline.addMouseListener(this); + + // Adds listeners to keep the outline in sync with the source graph + const view = this.source.getView(); + view.addListener(mxEvent.SCALE, this.updateHandler); + view.addListener(mxEvent.TRANSLATE, this.updateHandler); + view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.updateHandler); + view.addListener(mxEvent.DOWN, this.updateHandler); + view.addListener(mxEvent.UP, this.updateHandler); + + // Updates blue rectangle on scroll + mxEvent.addListener(this.source.container, 'scroll', this.updateHandler); + + this.panHandler = (sender: any, evt: mxEventObject) => { + if (this.updateOnPan) { + (this.updateHandler)(sender, evt); + } + }; + this.source.addListener(mxEvent.PAN, this.panHandler); + + // Refreshes the graph in the outline after a refresh of the main graph + this.refreshHandler = (sender: any) => { + const outline = this.outline; + outline.setStylesheet(this.source.getStylesheet()); + outline.refresh(); + }; + this.source.addListener(mxEvent.REFRESH, this.refreshHandler); + + // Creates the blue rectangle for the viewport + this.bounds = new mxRectangle(0, 0, 0, 0); + this.selectionBorder = new mxRectangleShape( + this.bounds, + null, + mxConstants.OUTLINE_COLOR, + mxConstants.OUTLINE_STROKEWIDTH + ); + this.selectionBorder.dialect = this.outline.dialect; + this.selectionBorder.init(this.outline.getView().getOverlayPane()); + const selectionBorderNode = this.selectionBorder.node; + + // Handles event by catching the initial pointer start and then listening to the + // complete gesture on the event target. This is needed because all the events + // are routed via the initial element even if that element is removed from the + // DOM, which happens when we repaint the selection border and zoom handles. + const handler = (evt: Event) => { + const t = mxEvent.getSource(evt); + + const redirect = (evt: Event) => { + const outline = this.outline; + outline.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt)); + }; + + var redirect2 = (evt: Event) => { + const outline = this.outline; + mxEvent.removeGestureListeners(t, null, redirect, redirect2); + outline.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt)); + }; + + const outline = this.outline; + mxEvent.addGestureListeners(t, null, redirect, redirect2); + outline.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt)); + }; + + mxEvent.addGestureListeners(this.selectionBorder.node, handler); + + // Creates a small blue rectangle for sizing (sizer handle) + const sizer = this.sizer = this.createSizer(); + const sizerNode = sizer.node; + + sizer.init(this.outline.getView().getOverlayPane()); + + if (this.enabled) { + sizerNode.style.cursor = 'nwse-resize'; + } + + mxEvent.addGestureListeners(this.sizer.node, handler); + + selectionBorderNode.style.display = this.showViewport ? '' : 'none'; + sizerNode.style.display = selectionBorderNode.style.display; + selectionBorderNode.style.cursor = 'move'; + + this.update(false); + } + // TODO: Document me!! sizer: mxRectangleShape | null=null; @@ -32,7 +197,7 @@ class mxOutline { bounds: mxRectangle | null=null; - zoom: number | null=null; + zoom: boolean=false; startX: number | null=null; @@ -56,17 +221,14 @@ class mxOutline { * * Reference to the that renders the outline. */ - outline: mxGraph; + outline: mxGraph | null=null; /** * Function: graphRenderHint * - * Renderhint to be used for the outline graph. Default is faster. + * Renderhint to be used for the outline graph. Default is exact. */ - graphRenderHint: - | mxConstants.RENDERING_HINT_EXACT - | mxConstants.RENDERING_HINT_FASTER - | mxConstants.RENDERING_HINT_FASTEST = mxConstants.RENDERING_HINT_FASTER; + graphRenderHint: string = 'exact'; /** * Variable: enabled @@ -143,54 +305,6 @@ class mxOutline { */ suspended: boolean = false; - /** - * Class: mxOutline - * - * Implements an outline (aka overview) for a graph. Set to true - * to enable updates while the source graph is panning. - * - * Example: - * - * (code) - * let outline = new mxOutline(graph, div); - * (end) - * - * To move the graph to the top, left corner the following code can be used. - * - * (code) - * let scale = graph.view.scale; - * let bounds = graph.getGraphBounds(); - * graph.view.setTranslate(-bounds.x / scale, -bounds.y / scale); - * (end) - * - * To toggle the suspended mode, the following can be used. - * - * (code) - * outline.suspended = !outln.suspended; - * if (!outline.suspended) - * { - * outline.update(true); - * } - * (end) - * - * Constructor: mxOutline - * - * Constructs a new outline for the specified graph inside the given - * container. - * - * Parameters: - * - * source - to create the outline for. - * container - DOM node that will contain the outline. - */ - constructor(source: mxGraph, container: HTMLElement | null = null) { - this.source = source; - - if (container != null) { - this.init(container); - } - } - /** * Function: createGraph * @@ -208,118 +322,6 @@ class mxOutline { return graph; } - /** - * Function: init - * - * Initializes the outline inside the given container. - */ - init(container: HTMLElement) { - this.outline = this.createGraph(container); - - // Do not repaint when suspended - const outlineGraphModelChanged = this.outline.graphModelChanged; - this.outline.graphModelChanged = mxUtils.bind(this, changes => { - if (!this.suspended && this.outline != null) { - outlineGraphModelChanged.apply(this.outline, [changes]); - } - }); - - // Enable faster painting in SVG - const node = this.outline.getView().getCanvas().parentNode; - node.setAttribute('shape-rendering', 'optimizeSpeed'); - node.setAttribute('image-rendering', 'optimizeSpeed'); - - // Hides cursors and labels - this.outline.labelsVisible = this.labelsVisible; - this.outline.setEnabled(false); - - this.updateHandler = (sender, evt) => { - if (!this.suspended && !this.active) { - this.update(); - } - }; - - // Updates the scale of the outline after a change of the main graph - this.source.getModel().addListener(mxEvent.CHANGE, this.updateHandler); - this.outline.addMouseListener(this); - - // Adds listeners to keep the outline in sync with the source graph - const view = this.source.getView(); - view.addListener(mxEvent.SCALE, this.updateHandler); - view.addListener(mxEvent.TRANSLATE, this.updateHandler); - view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.updateHandler); - view.addListener(mxEvent.DOWN, this.updateHandler); - view.addListener(mxEvent.UP, this.updateHandler); - - // Updates blue rectangle on scroll - mxEvent.addListener(this.source.container, 'scroll', this.updateHandler); - - this.panHandler = (sender, evt) => { - if (this.updateOnPan) { - this.updateHandler.apply(this, [sender, evt]); - } - }; - this.source.addListener(mxEvent.PAN, this.panHandler); - - // Refreshes the graph in the outline after a refresh of the main graph - this.refreshHandler = sender => { - this.outline.setStylesheet(this.source.getStylesheet()); - this.outline.refresh(); - }; - this.source.addListener(mxEvent.REFRESH, this.refreshHandler); - - // Creates the blue rectangle for the viewport - this.bounds = new mxRectangle(0, 0, 0, 0); - this.selectionBorder = new mxRectangleShape( - this.bounds, - null, - mxConstants.OUTLINE_COLOR, - mxConstants.OUTLINE_STROKEWIDTH - ); - this.selectionBorder.dialect = this.outline.dialect; - - this.selectionBorder.init(this.outline.getView().getOverlayPane()); - - // Handles event by catching the initial pointer start and then listening to the - // complete gesture on the event target. This is needed because all the events - // are routed via the initial element even if that element is removed from the - // DOM, which happens when we repaint the selection border and zoom handles. - const handler = mxUtils.bind(this, evt => { - const t = mxEvent.getSource(evt); - - const redirect = mxUtils.bind(this, evt => { - this.outline.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt)); - }); - - var redirect2 = mxUtils.bind(this, evt => { - mxEvent.removeGestureListeners(t, null, redirect, redirect2); - this.outline.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt)); - }); - - mxEvent.addGestureListeners(t, null, redirect, redirect2); - this.outline.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt)); - }); - - mxEvent.addGestureListeners(this.selectionBorder.node, handler); - - // Creates a small blue rectangle for sizing (sizer handle) - this.sizer = this.createSizer(); - - this.sizer.init(this.outline.getView().getOverlayPane()); - - if (this.enabled) { - this.sizer.node.style.cursor = 'nwse-resize'; - } - - mxEvent.addGestureListeners(this.sizer.node, handler); - - this.selectionBorder.node.style.display = this.showViewport ? '' : 'none'; - this.sizer.node.style.display = this.selectionBorder.node.style.display; - this.selectionBorder.node.style.cursor = 'move'; - - this.update(false); - } - /** * Function: isEnabled * @@ -355,6 +357,7 @@ class mxOutline { * value - Boolean that specifies the new enabled state. */ setZoomEnabled(value: boolean): void { + // @ts-ignore this.sizer.node.style.visibility = value ? 'visible' : 'hidden'; } @@ -373,12 +376,13 @@ class mxOutline { * Creates the shape used as the sizer. */ createSizer(): mxRectangleShape { + const outline = this.outline; if (this.sizerImage != null) { const sizer = new mxImageShape( new mxRectangle(0, 0, this.sizerImage.width, this.sizerImage.height), this.sizerImage.src ); - sizer.dialect = this.outline.dialect; + sizer.dialect = outline.dialect; return sizer; } @@ -387,7 +391,7 @@ class mxOutline { mxConstants.OUTLINE_HANDLE_FILLCOLOR, mxConstants.OUTLINE_HANDLE_STROKECOLOR ); - sizer.dialect = this.outline.dialect; + sizer.dialect = outline.dialect; return sizer; } @@ -400,8 +404,8 @@ class mxOutline { return new mxRectangle( 0, 0, - this.source.container.scrollWidth, - this.source.container.scrollHeight + (this.source.container).scrollWidth, + (this.source.container).scrollHeight ); } @@ -410,7 +414,7 @@ class mxOutline { * * Returns the offset for drawing the outline graph. */ - getOutlineOffset(scale) { + getOutlineOffset(scale: number): mxPoint | null { // TODO: Should number -> mxPoint? return null; } @@ -533,7 +537,8 @@ class mxOutline { this.bounds.y += (this.source.container.scrollTop * navView.scale) / scale; - let b = this.selectionBorder.bounds; + const selectionBorder = this.selectionBorder; + let b = selectionBorder.bounds; if ( b.x !== this.bounds.x || @@ -541,12 +546,13 @@ class mxOutline { b.width !== this.bounds.width || b.height !== this.bounds.height ) { - this.selectionBorder.bounds = this.bounds; - this.selectionBorder.redraw(); + selectionBorder.bounds = this.bounds; + selectionBorder.redraw(); } // Updates the bounds of the zoom handle at the bottom right - b = this.sizer.bounds; + const sizer = this.sizer; + b = sizer.bounds; const b2 = new mxRectangle( this.bounds.x + this.bounds.width - b.width / 2, this.bounds.y + this.bounds.height - b.height / 2, @@ -560,11 +566,11 @@ class mxOutline { b.width !== b2.width || b.height !== b2.height ) { - this.sizer.bounds = b2; + sizer.bounds = b2; // Avoids update of visibility in redraw for VML - if (this.sizer.node.style.visibility !== 'hidden') { - this.sizer.redraw(); + if ((sizer.node).style.visibility !== 'hidden') { + sizer.redraw(); } } @@ -580,7 +586,7 @@ class mxOutline { * * Handles the event by starting a translation or zoom. */ - mouseDown(sender, me) { + mouseDown(sender: any, me: mxMouseEvent) { if (this.enabled && this.showViewport) { const tol = !mxEvent.isMouseEvent(me.getEvent()) ? this.source.tolerance @@ -600,13 +606,14 @@ class mxOutline { this.startX = me.getX(); this.startY = me.getY(); this.active = true; + const sourceContainer = this.source.container; if ( this.source.useScrollbarsForPanning && mxUtils.hasScrollbars(this.source.container) ) { - this.dx0 = this.source.container.scrollLeft; - this.dy0 = this.source.container.scrollTop; + this.dx0 = sourceContainer.scrollLeft; + this.dy0 = sourceContainer.scrollTop; } else { this.dx0 = 0; this.dy0 = 0; @@ -622,10 +629,18 @@ class mxOutline { * Handles the event by previewing the viewrect in and updating the * rectangle that represents the viewrect in the outline. */ - mouseMove(sender, me) { + mouseMove(sender: any, me: mxMouseEvent) { if (this.active) { - this.selectionBorder.node.style.display = this.showViewport ? '' : 'none'; - this.sizer.node.style.display = this.selectionBorder.node.style.display; + const myBounds = this.bounds; + const sizer = this.sizer; + const sizerNode = sizer.node; + const selectionBorder = this.selectionBorder; + const selectionBorderNode = selectionBorder.node; + const source = this.source; + const outline = this.outline; + + selectionBorderNode.style.display = this.showViewport ? '' : 'none'; + sizerNode.style.display = selectionBorderNode.style.display; const delta = this.getTranslateForEvent(me); let dx = delta.x; @@ -634,38 +649,39 @@ class mxOutline { if (!this.zoom) { // Previews the panning on the source graph - const { scale } = this.outline.getView(); + const { scale } = outline.getView(); bounds = new mxRectangle( - this.bounds.x + dx, - this.bounds.y + dy, - this.bounds.width, - this.bounds.height + myBounds.x + dx, + myBounds.y + dy, + myBounds.width, + myBounds.height ); - this.selectionBorder.bounds = bounds; - this.selectionBorder.redraw(); + selectionBorder.bounds = bounds; + selectionBorder.redraw(); dx /= scale; - dx *= this.source.getView().scale; + dx *= source.getView().scale; dy /= scale; - dy *= this.source.getView().scale; - this.source.panGraph(-dx - this.dx0, -dy - this.dy0); + dy *= source.getView().scale; + source.panGraph(-dx - this.dx0, -dy - this.dy0); } else { // Does *not* preview zooming on the source graph - const { container } = this.source; + const { container } = this.source; + // @ts-ignore const viewRatio = container.clientWidth / container.clientHeight; dy = dx / viewRatio; bounds = new mxRectangle( - this.bounds.x, - this.bounds.y, - Math.max(1, this.bounds.width + dx), - Math.max(1, this.bounds.height + dy) + myBounds.x, + myBounds.y, + Math.max(1, myBounds.width + dx), + Math.max(1, myBounds.height + dy) ); - this.selectionBorder.bounds = bounds; - this.selectionBorder.redraw(); + selectionBorder.bounds = bounds; + selectionBorder.redraw(); } // Updates the zoom handle - const b = this.sizer.bounds; - this.sizer.bounds = new mxRectangle( + const b = sizer.bounds; + sizer.bounds = new mxRectangle( bounds.x + bounds.width - b.width / 2, bounds.y + bounds.height - b.height / 2, b.width, @@ -673,10 +689,9 @@ class mxOutline { ); // Avoids update of visibility in redraw for VML - if (this.sizer.node.style.visibility !== 'hidden') { - this.sizer.redraw(); + if (sizerNode.style.visibility !== 'hidden') { + sizer.redraw(); } - me.consume(); } } @@ -703,8 +718,8 @@ class mxOutline { * }; * (end) */ - getTranslateForEvent(me) { - return new mxPoint(me.getX() - this.startX, me.getY() - this.startY); + getTranslateForEvent(me: mxMouseEvent) { + return new mxPoint(me.getX() - this.startX, me.getY() - this.startY); } /** @@ -712,31 +727,34 @@ class mxOutline { * * Handles the event by applying the translation or zoom to . */ - mouseUp(sender, me) { + mouseUp(sender: any, me: mxMouseEvent) { if (this.active) { const delta = this.getTranslateForEvent(me); let dx = delta.x; let dy = delta.y; + const source = this.source; + const outline = this.outline; + const selectionBorder = this.selectionBorder; if (Math.abs(dx) > 0 || Math.abs(dy) > 0) { if (!this.zoom) { // Applies the new translation if the source // has no scrollbars if ( - !this.source.useScrollbarsForPanning || - !mxUtils.hasScrollbars(this.source.container) + !source.useScrollbarsForPanning || + !mxUtils.hasScrollbars(source.container) ) { - this.source.panGraph(0, 0); - dx /= this.outline.getView().scale; - dy /= this.outline.getView().scale; - const t = this.source.getView().translate; - this.source.getView().setTranslate(t.x - dx, t.y - dy); + source.panGraph(0, 0); + dx /= outline.getView().scale; + dy /= outline.getView().scale; + const t = source.getView().translate; + source.getView().setTranslate(t.x - dx, t.y - dy); } } else { // Applies the new zoom - const w = this.selectionBorder.bounds.width; - const { scale } = this.source.getView(); - this.source.zoomTo( + const w = (selectionBorder.bounds).width; + const { scale } = source.getView(); + source.zoomTo( Math.max(this.minScale, scale - (dx * scale) / w), false ); @@ -768,6 +786,7 @@ class mxOutline { 'scroll', this.updateHandler ); + // @ts-ignore this.source = null; } diff --git a/src/pages/backgrounds/ExampleBase.ts b/src/pages/backgrounds/ExampleBase.ts deleted file mode 100644 index 6f2779ecf..000000000 --- a/src/pages/backgrounds/ExampleBase.ts +++ /dev/null @@ -1,27 +0,0 @@ -class ExampleBase { - props: object; - - constructor(props: object) { - this.props = props; - } - - appendToElement(element: HTMLElement): HTMLElement { - const html: string = this.getHTML(); - const cont: HTMLElement = - document.createElement('div'); - cont.innerHTML = html; - this.afterHTMLSet() - element.appendChild(cont); - return cont; - } - - getHTML(): void { - throw new Error("Not implemented"); - } - - afterHTMLSet(): void { - throw new Error("Not implemented"); - } -} - -export default ExampleBase; diff --git a/src/pages/backgrounds/ExtendCanvas.js b/src/pages/backgrounds/ExtendCanvas.js new file mode 100644 index 000000000..5668ece5a --- /dev/null +++ b/src/pages/backgrounds/ExtendCanvas.js @@ -0,0 +1,272 @@ +/** + * Copyright (c) 2006-2013, JGraph Ltd + * Converted to ES9 syntax/React by David Morrissey 2021 + */ + +import React from 'react'; +import mxEvent from '../../mxgraph/util/mxEvent'; +import mxGraph from '../../mxgraph/view/mxGraph'; +import mxRubberband from '../../mxgraph/handler/mxRubberband'; +import mxRectangle from '../../mxgraph/util/mxRectangle'; +import mxUtils from '../../mxgraph/util/mxUtils'; +import mxPoint from '../../mxgraph/util/mxPoint'; + +class ExtendCanvas extends React.Component { + constructor(props) { + super(props); + } + + render() { + // A container for the graph + return ( + <> +

Extend canvas

+ This example demonstrates implementing an infinite canvas with + scrollbars. +
{ + this.el = el; + }} + style={{ + position: 'relative', + overflow: 'auto', + height: '241px', + background: "url('editors/images/grid.gif')", + cursor: 'default', + }} + /> + + ); + } + + componentDidMount() { + // Disables the built-in context menu + mxEvent.disableContextMenu(this.el); + + /** + * 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 mxRectangle(0, 0, 400, 400); + + class MyCustomGraph extends mxGraph { + /** + * Returns the padding for pages in page view with scrollbars. + */ + getPagePadding() { + return new mxPoint( + Math.max(0, Math.round(this.container.offsetWidth - 34)), + Math.max(0, Math.round(this.container.offsetHeight - 34)) + ); + } + + /** + * Returns the size of the page format scaled with the page size. + */ + getPageSize() { + return this.pageVisible + ? new mxRectangle( + 0, + 0, + this.pageFormat.width * this.pageScale, + 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 mxRectangle(0, 0, 1, 1); + } + + // 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); + + 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; + + return new mxRectangle(x0, y0, w0, h0); + } + + getPreferredPageSize(bounds, width, height) { + const pages = this.getPageLayout(); + const size = this.getPageSize(); + + return new mxRectangle( + 0, + 0, + pages.width * size.width, + pages.height * size.height + ); + } + + sizeDidChange() { + if (this.container != null && mxUtils.hasScrollbars(this.container)) { + const pages = this.getPageLayout(); + const pad = this.getPagePadding(); + const size = this.getPageSize(); + + // 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 + ); + + 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 mxRectangle(0, 0, minw, minh); + } + + // 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(); + } + } + } + + // Creates the graph inside the given container + const graph = this.graph = new MyCustomGraph(this.el); + graph.panningHandler.ignoreCell = true; + graph.setPanning(true); + + // Fits the number of background pages to the graph + graph.view.getBackgroundPageBounds = function() { + const layout = this.graph.getPageLayout(); + const page = this.graph.getPageSize(); + + return new mxRectangle( + 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 + ); + }; + + /** + * 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() { + if ( + this.graph.container != null && + mxUtils.hasScrollbars(this.graph.container) + ) { + 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 + new mxRubberband(graph); + + // 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); + } +} + +export default ExtendCanvas; diff --git a/src/pages/backgrounds/ExtendCanvas.ts b/src/pages/backgrounds/ExtendCanvas.ts deleted file mode 100644 index f38b8bc22..000000000 --- a/src/pages/backgrounds/ExtendCanvas.ts +++ /dev/null @@ -1,282 +0,0 @@ -/** - * Copyright (c) 2006-2013, JGraph Ltd - * Converted to ES9 syntax/React by David Morrissey 2021 - */ - -import mxEvent from '../../mxgraph/util/event/mxEvent'; -import mxGraph from '../../mxgraph/view/graph/mxGraph'; -import mxGraphView from '../../mxgraph/view/graph/mxGraphView' -import mxRubberband from '../../mxgraph/handler/mxRubberband'; -import mxRectangle from '../../mxgraph/util/datatypes/mxRectangle'; -import mxUtils from '../../mxgraph/util/mxUtils'; -import mxPoint from '../../mxgraph/util/datatypes/mxPoint'; -import mxCell from '../../mxgraph/view/cell/mxCell'; -import ExampleBase from "./ExampleBase"; - -/** - * 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 mxRectangle(0, 0, 400, 400); - -class ExtendCanvas extends ExampleBase { - constructor(props) { - super(props); - } - - getHTML() { - // A container for the graph - return ` -

Extend canvas

- This example demonstrates implementing an infinite canvas with - scrollbars. - -
- `; - } - - afterHTMLSet(): void { - // Executed after the HTML has been set - const el = document.getElementById("el"); - el.id = ''; - - // Disables the built-in context menu - mxEvent.disableContextMenu(el); - - // Creates the graph inside the given container - const graph: MyCustomGraph = new MyCustomGraph(el); - graph.panningHandler.ignoreCell = true; - graph.setPanning(true); - - // Enables rubberband selection - new mxRubberband(graph); - - // 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: mxCell = graph.insertVertex({ - parent, - value: 'Hello,', - position: [20, 20], - size: [80, 30], - }); - const v2: mxCell = graph.insertVertex({ - parent, - value: 'World!', - position: [200, 150], - size: [80, 30], - }); - const e1: mxCell = graph.insertEdge({ - parent, - source: v1, - target: v2, - }); - }); - - // Sets initial scrollbar positions - window.setTimeout(() => { - const bounds: mxRectangle = graph.getGraphBounds(); - const width: number = Math.max( - bounds.width, - scrollTileSize.width * graph.view.scale - ); - const height: number = 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); - } -} - -class MyCustomGraph extends mxGraph { - autoTranslate: boolean; - - createGraphView(): mxGraphView { - return new MyCustomGraphView(this); - } - - /** - * Returns the padding for pages in page view with scrollbars. - */ - getPagePadding(): mxPoint { - return new mxPoint( - Math.max(0, Math.round(this.container.offsetWidth - 34)), - Math.max(0, Math.round(this.container.offsetHeight - 34)) - ); - } - - /** - * Returns the size of the page format scaled with the page size. - */ - getPageSize(): mxRectangle { - return this.pageVisible - ? new mxRectangle( - 0, - 0, - this.pageFormat.width * this.pageScale, - 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(): mxRectangle { - const size: mxRectangle = this.pageVisible - ? this.getPageSize() - : scrollTileSize; - const bounds = this.getGraphBounds(); - - if (bounds.width === 0 || bounds.height === 0) { - return new mxRectangle(0, 0, 1, 1); - } - - // Computes untransformed graph bounds - const x: number = Math.ceil(bounds.x / this.view.scale - this.view.translate.x); - const y: number = Math.ceil(bounds.y / this.view.scale - this.view.translate.y); - const w: number = Math.floor(bounds.width / this.view.scale); - const h: number = Math.floor(bounds.height / this.view.scale); - - const x0: number = Math.floor(x / size.width); - const y0: number = Math.floor(y / size.height); - const w0: number = Math.ceil((x + w) / size.width) - x0; - const h0: number = Math.ceil((y + h) / size.height) - y0; - - return new mxRectangle(x0, y0, w0, h0); - } - - getPreferredPageSize(bounds, width, height): mxRectangle { - const pages: mxRectangle = this.getPageLayout(); - const size: mxRectangle = this.getPageSize(); - - return new mxRectangle( - 0, - 0, - pages.width * size.width, - pages.height * size.height - ); - } - - sizeDidChange(): mxRectangle { - if (this.container != null && mxUtils.hasScrollbars(this.container)) { - const pages: mxRectangle = this.getPageLayout(); - const pad: mxPoint = this.getPagePadding(); - const size: mxRectangle = this.getPageSize(); - - // Updates the minimum graph size - const minw: number = Math.ceil( - (2 * pad.x) / this.view.scale + pages.width * size.width - ); - const minh: number = Math.ceil( - (2 * pad.y) / this.view.scale + pages.height * size.height - ); - - const min: number = 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 mxRectangle(0, 0, minw, minh); - } - - // Updates auto-translate to include padding and graph size - const dx: number = pad.x / this.view.scale - pages.x * size.width; - const dy: number = 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: number = this.view.translate.x; - const ty: number = 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(); - } - } -} - -class MyCustomGraphView extends mxGraphView { - /** - * Fits the number of background pages to the graph - */ - getBackgroundPageBounds(): mxRectangle { - const layout: mxRectangle = this.graph.getPageLayout(); - const page: mxRectangle = this.graph.getPageSize(); - - return new mxRectangle( - 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 - ); - } - - /** - * 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. - */ - validate(): void { - if ( - this.graph.container != null && - mxUtils.hasScrollbars(this.graph.container) - ) { - const pad: mxPoint = this.graph.getPagePadding(); - const size: mxRectangle = 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; - } - - super.validate(); - }; -} - -export default ExtendCanvas;