diff --git a/packages/core/src/gui/MaxPopupMenu.ts b/packages/core/src/gui/MaxPopupMenu.ts index 28d8631b2..79053e0ba 100644 --- a/packages/core/src/gui/MaxPopupMenu.ts +++ b/packages/core/src/gui/MaxPopupMenu.ts @@ -57,7 +57,9 @@ class MaxPopupMenu extends EventSource implements Partial { ) { super(); - this.factoryMethod = factoryMethod; + if (factoryMethod) { + this.factoryMethod = factoryMethod; + } // Adds the inner table this.table = document.createElement('table'); @@ -189,9 +191,9 @@ class MaxPopupMenu extends EventSource implements Partial { funct: Function, parent: PopupMenuItem | null = null, iconCls: string | null = null, - enabled: boolean | null = null, - active: boolean | null = null, - noHover: boolean | null = null + enabled = true, + active = true, + noHover = false ) { parent = (parent ?? this) as PopupMenuItem; this.itemCount++; diff --git a/packages/core/src/view/Graph.ts b/packages/core/src/view/Graph.ts index 5aad6fc40..09fbe6ab8 100644 --- a/packages/core/src/view/Graph.ts +++ b/packages/core/src/view/Graph.ts @@ -92,9 +92,9 @@ class Graph extends EventSource { foldingEnabled: null | boolean = null; isConstrainedMoving = false; - /***************************************************************************** - * Group: Variables (that maybe should be in the mixins, but need to be created for each new class instance) - *****************************************************************************/ + // =================================================================================================================== + // Group: Variables (that maybe should be in the mixins, but need to be created for each new class instance) + // =================================================================================================================== cells: Cell[] = []; @@ -111,10 +111,6 @@ class Graph extends EventSource { */ multiplicities: Multiplicity[] = []; - /***************************************************************************** - * Group: Variables - *****************************************************************************/ - /** * Holds the {@link GraphDataModel} that contains the cells to be displayed. */ @@ -407,6 +403,89 @@ class Graph extends EventSource { containsValidationErrorsResource: string = Client.language != 'none' ? 'containsValidationErrors' : ''; + // =================================================================================================================== + // Group: "Create Class Instance" factory functions. + // These can be overridden in subclasses of Graph to allow the Graph to instantiate user-defined implementations with + // custom behavior. + // =================================================================================================================== + + /** + * Creates a new {@link CellRenderer} to be used in this graph. + */ + createCellRenderer(): CellRenderer { + return new CellRenderer(); + } + + /** + * Hooks to create a new {@link EdgeHandler} for the given {@link CellState}. + * + * @param state {@link CellState} to create the handler for. + */ + createEdgeHandlerInstance(state: CellState): EdgeHandler { + // Note this method not being called createEdgeHandler to keep compatibility + // with older code which overrides/calls createEdgeHandler + return new EdgeHandler(state); + } + + /** + * Hooks to create a new {@link EdgeSegmentHandler} for the given {@link CellState}. + * + * @param state {@link CellState} to create the handler for. + */ + createEdgeSegmentHandler(state: CellState) { + return new EdgeSegmentHandler(state); + } + + /** + * Hooks to create a new {@link ElbowEdgeHandler} for the given {@link CellState}. + * + * @param state {@link CellState} to create the handler for. + */ + createElbowEdgeHandler(state: CellState) { + return new ElbowEdgeHandler(state); + } + + /** + * Creates a new {@link GraphDataModel} to be used in this graph. + */ + createGraphDataModel(): GraphDataModel { + return new GraphDataModel(); + } + + /** + * Creates a new {@link GraphView} to be used in this graph. + */ + createGraphView(): GraphView { + return new GraphView(this); + } + + /** + * Creates a new {@link GraphSelectionModel} to be used in this graph. + */ + createSelectionModel() { + return new GraphSelectionModel(this); + } + + /** + * Creates a new {@link Stylesheet} to be used in this graph. + */ + createStylesheet(): Stylesheet { + return new Stylesheet(); + } + + /** + * Hooks to create a new {@link VertexHandler} for the given {@link CellState}. + * + * @param state {@link CellState} to create the handler for. + */ + createVertexHandler(state: CellState): VertexHandler { + return new VertexHandler(state); + } + + // =================================================================================================================== + // Group: Main graph constructor and functions + // =================================================================================================================== + constructor( container: HTMLElement, model?: GraphDataModel, @@ -416,10 +495,10 @@ class Graph extends EventSource { super(); this.container = container ?? document.createElement('div'); - this.model = model ?? new GraphDataModel(); + this.model = model ?? this.createGraphDataModel(); this.plugins = plugins; this.cellRenderer = this.createCellRenderer(); - this.setStylesheet(stylesheet != null ? stylesheet : this.createStylesheet()); + this.setStylesheet(stylesheet ?? this.createStylesheet()); this.view = this.createGraphView(); // Adds a graph model listener to update the view @@ -437,7 +516,7 @@ class Graph extends EventSource { // Set the selection model this.setSelectionModel(this.createSelectionModel()); - // Initiailzes plugins + // Initializes plugins this.plugins.forEach((p: GraphPluginConstructor) => { this.pluginsMap[p.pluginId] = new p(this); }); @@ -445,7 +524,6 @@ class Graph extends EventSource { this.view.revalidate(); } - createSelectionModel = () => new GraphSelectionModel(this); getContainer = () => this.container; getPlugin = (id: string) => this.pluginsMap[id] as unknown; getCellRenderer = () => this.cellRenderer; @@ -489,27 +567,6 @@ class Graph extends EventSource { this.getDataModel().batchUpdate(fn); } - /** - * Creates a new {@link Stylesheet} to be used in this graph. - */ - createStylesheet(): Stylesheet { - return new Stylesheet(); - } - - /** - * Creates a new {@link GraphView} to be used in this graph. - */ - createGraphView() { - return new GraphView(this); - } - - /** - * Creates a new {@link CellRenderer} to be used in this graph. - */ - createCellRenderer(): CellRenderer { - return new CellRenderer(); - } - /** * Returns the {@link GraphDataModel} that contains the cells. */ @@ -960,15 +1017,6 @@ class Graph extends EventSource { return result; } - /** - * Hooks to create a new {@link VertexHandler} for the given {@link CellState}. - * - * @param state {@link CellState} to create the handler for. - */ - createVertexHandler(state: CellState): VertexHandler { - return new VertexHandler(state); - } - /** * Hooks to create a new {@link EdgeHandler} for the given {@link CellState}. * @@ -989,30 +1037,12 @@ class Graph extends EventSource { ) { result = this.createEdgeSegmentHandler(state); } else { - result = new EdgeHandler(state); + result = this.createEdgeHandlerInstance(state); } return result as EdgeHandler; } - /** - * Hooks to create a new {@link EdgeSegmentHandler} for the given {@link CellState}. - * - * @param state {@link CellState} to create the handler for. - */ - createEdgeSegmentHandler(state: CellState) { - return new EdgeSegmentHandler(state); - } - - /** - * Hooks to create a new {@link ElbowEdgeHandler} for the given {@link CellState}. - * - * @param state {@link CellState} to create the handler for. - */ - createElbowEdgeHandler(state: CellState) { - return new ElbowEdgeHandler(state); - } - /***************************************************************************** * Group: Drilldown *****************************************************************************/ diff --git a/packages/core/src/view/animate/Morphing.ts b/packages/core/src/view/animate/Morphing.ts index dea8c4f63..07f08d774 100644 --- a/packages/core/src/view/animate/Morphing.ts +++ b/packages/core/src/view/animate/Morphing.ts @@ -56,7 +56,7 @@ import { Graph } from '../Graph'; * @param delay Optional delay between the animation steps. Passed to . */ class Morphing extends Animation { - constructor(graph: Graph, steps = 6, ease = 1.5, delay: number) { + constructor(graph: Graph, steps = 6, ease = 1.5, delay?: number) { super(delay); this.graph = graph; this.steps = steps; diff --git a/packages/html/stories/GraphLayout.stories.js b/packages/html/stories/GraphLayout.stories.js new file mode 100644 index 000000000..ea669b334 --- /dev/null +++ b/packages/html/stories/GraphLayout.stories.js @@ -0,0 +1,193 @@ +/* +Copyright 2021-present The maxGraph project Contributors +Copyright (c) 2006-2013, JGraph Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* + Graph Layout + + This example demonstrates using automatic graph layouts and listening to changes of the graph size + to keep the container size in sync. +*/ + +import { + constants, + CircleLayout, + DomHelpers, + FastOrganicLayout, + Graph, + InternalEvent, + Morphing, + Perimeter, +} from '@maxgraph/core'; + +import { globalTypes, globalValues } from './shared/args.js'; + +// TODO apply this settings to the container used by the Graph +const HTML_TEMPLATE = ` + + + + +
+
+
+ +`; + +export default { + title: 'Layouts/GraphLayout', + argTypes: { + ...globalTypes, + animate: { + type: 'boolean', + defaultValue: false, + }, + }, + args: { + ...globalValues, + animate: true, + }, +}; + +const Template = ({ label, ...args }) => { + const mainContainer = document.createElement('div'); + const container = document.createElement('div'); + + container.style.position = 'relative'; + container.style.overflow = 'hidden'; + container.style.width = `${args.width}px`; + container.style.height = `${args.height}px`; + container.style.background = 'url(/images/grid.gif)'; + container.style.cursor = 'default'; + + // Creates the graph inside the given container + let graph = new Graph(container); + + // Disables basic selection and cell handling + graph.setEnabled(false); + + // Changes the default vertex style in-place + let style = graph.getStylesheet().getDefaultVertexStyle(); + style.shape = constants.SHAPE.ELLIPSE; + style.perimiter = Perimeter.EllipsePerimeter; + style.gradientColor = 'white'; + style.fontSize = 10; + + // Updates the size of the container to match + // the size of the graph when it changes. If + // this is commented-out, and the DIV style's + // overflow is set to "auto", then scrollbars + // will appear for the diagram. If overflow is + // set to "visible", then the diagram will be + // visible even when outside the parent DIV. + // With the code below, the parent DIV will be + // resized to contain the complete graph. + //graph.setResizeContainer(true); + + // Larger grid size yields cleaner layout result + graph.gridSize = 40; + + // Gets the default parent for inserting new cells. This + // is normally the first child of the root (ie. layer 0). + let parent = graph.getDefaultParent(); + + // Creates a layout algorithm to be used + // with the graph + let layout = new FastOrganicLayout(graph); + + // Moves stuff wider apart than usual + layout.forceConstant = 80; + + // Adds a button to execute the layout + mainContainer.appendChild( + DomHelpers.button('Circle Layout', function (evt) { + graph.getDataModel().beginUpdate(); + try { + // Creates a layout algorithm to be used + // with the graph + let circleLayout = new CircleLayout(graph); + circleLayout.execute(parent); + } finally { + if (args.animate) { + let morph = new Morphing(graph, 6, 1.5, 20); + morph.addListener(InternalEvent.DONE, function () { + graph.getDataModel().endUpdate(); + }); + morph.startAnimation(); + } else { + graph.getDataModel().endUpdate(); + } + } + }) + ); + + // Adds a button to execute the layout + mainContainer.appendChild( + DomHelpers.button('Organic Layout', function (evt) { + graph.getDataModel().beginUpdate(); + try { + layout.execute(parent); + } finally { + if (args.animate) { + // Default values are 6, 1.5, 20 + let morph = new Morphing(graph, 10, 1.7, 20); + morph.addListener(InternalEvent.DONE, function () { + graph.getDataModel().endUpdate(); + }); + morph.startAnimation(); + } else { + graph.getDataModel().endUpdate(); + } + } + }) + ); + + mainContainer.appendChild(container); + + // Adds cells to the model in a single step + let w = 30; + let h = 30; + + graph.batchUpdate(() => { + let v1 = graph.insertVertex(parent, null, 'A', 0, 0, w, h); + let v2 = graph.insertVertex(parent, null, 'B', 0, 0, w, h); + let v3 = graph.insertVertex(parent, null, 'C', 0, 0, w, h); + let v4 = graph.insertVertex(parent, null, 'D', 0, 0, w, h); + let v5 = graph.insertVertex(parent, null, 'E', 0, 0, w, h); + let v6 = graph.insertVertex(parent, null, 'F', 0, 0, w, h); + let v7 = graph.insertVertex(parent, null, 'G', 0, 0, w, h); + let v8 = graph.insertVertex(parent, null, 'H', 0, 0, w, h); + let e1 = graph.insertEdge(parent, null, 'ab', v1, v2); + let e2 = graph.insertEdge(parent, null, 'ac', v1, v3); + let e3 = graph.insertEdge(parent, null, 'cd', v3, v4); + let e4 = graph.insertEdge(parent, null, 'be', v2, v5); + let e5 = graph.insertEdge(parent, null, 'cf', v3, v6); + let e6 = graph.insertEdge(parent, null, 'ag', v1, v7); + let e7 = graph.insertEdge(parent, null, 'gh', v7, v8); + let e8 = graph.insertEdge(parent, null, 'gc', v7, v3); + let e9 = graph.insertEdge(parent, null, 'gd', v7, v4); + let e10 = graph.insertEdge(parent, null, 'eh', v5, v8); + + // Executes the layout + layout.execute(parent); + }); + + return mainContainer; +}; + +export const Default = Template.bind({}); diff --git a/packages/html/stories/HtmlLabel.stories.js b/packages/html/stories/HtmlLabel.stories.js new file mode 100644 index 000000000..f8cb849db --- /dev/null +++ b/packages/html/stories/HtmlLabel.stories.js @@ -0,0 +1,210 @@ +/* +Copyright 2021-present The maxGraph project Contributors +Copyright (c) 2006-2013, JGraph Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +HTML label + +This example demonstrates using HTML labels that are connected to the state of the user object. +*/ + +import { + xmlUtils, + domUtils, + InternalEvent, + RubberBandHandler, + UndoManager, + CodecRegistry, + Graph, + Cell, + DomHelpers, + GraphDataModel, + CellEditorHandler, + TooltipHandler, + SelectionCellsHandler, + PopupMenuHandler, + ConnectionHandler, + SelectionHandler, + PanningHandler, +} from '@maxgraph/core'; + +import { globalTypes, globalValues } from './shared/args.js'; +// style required by RubberBand +import '@maxgraph/core/css/common.css'; + +export default { + title: 'Labels/HtmlLabel', + argTypes: { + ...globalTypes, + }, + args: { + ...globalValues, + }, +}; + +const Template = ({ label, ...args }) => { + const container = document.createElement('div'); + container.style.position = 'relative'; + container.style.overflow = 'hidden'; + container.style.width = `${args.width}px`; + container.style.height = `${args.height}px`; + container.style.background = 'url(/images/grid.gif)'; + container.style.cursor = 'default'; + + // Disables the built-in context menu + InternalEvent.disableContextMenu(container); + + // Creates a user object that stores the state + let doc = xmlUtils.createXmlDocument(); + let obj = doc.createElement('UserObject'); + obj.setAttribute('label', 'Hello, World!'); + obj.setAttribute('checked', 'false'); + + // Adds optional caching for the HTML label + let cached = true; + let MyCustomGraphDataModel; + + if (cached) { + // Ignores cached label in codec + CodecRegistry.getCodec(Cell).exclude.push('div'); + + // Invalidates cached labels + MyCustomGraphDataModel = class extends GraphDataModel { + setValue(cell, value) { + cell.div = null; + super.setValue.apply(this, arguments); + } + }; + } else { + MyCustomGraphDataModel = GraphDataModel; + } + + class MyCustomGraph extends Graph { + createGraphDataModel() { + return new MyCustomGraphDataModel(); + } + + // Overrides method to provide a cell label in the display + convertValueToString(cell) { + if (cached && cell.div != null) { + // Uses cached label + return cell.div; + } else if ( + domUtils.isNode(cell.value) && + cell.value.nodeName.toLowerCase() === 'userobject' + ) { + // Returns a DOM for the label + let div = document.createElement('div'); + div.innerHTML = cell.getAttribute('label'); + domUtils.br(div); + + let checkbox = document.createElement('input'); + checkbox.setAttribute('type', 'checkbox'); + + if (cell.getAttribute('checked') == 'true') { + checkbox.setAttribute('checked', 'checked'); + checkbox.defaultChecked = true; + } + + // Writes back to cell if checkbox is clicked + InternalEvent.addListener(checkbox, 'change', function (evt) { + let elt = cell.value.cloneNode(true); + elt.setAttribute('checked', checkbox.checked ? 'true' : 'false'); + + graph.model.setValue(cell, elt); + }); + + div.appendChild(checkbox); + + if (cached) { + // Caches label + cell.div = div; + } + return div; + } + return ''; + } + + // Overrides method to store a cell label in the model + cellLabelChanged(cell, newValue, autoSize) { + if ( + domUtils.isNode(cell.value) && + cell.value.nodeName.toLowerCase() === 'userobject' + ) { + // Clones the value for correct undo/redo + let elt = cell.value.cloneNode(true); + elt.setAttribute('label', newValue); + newValue = elt; + } + + super.cellLabelChanged.apply(this, arguments); + } + + // Overrides method to create the editing value + getEditingValue(cell) { + if ( + domUtils.isNode(cell.value) && + cell.value.nodeName.toLowerCase() === 'userobject' + ) { + return cell.getAttribute('label'); + } + } + } + + // Creates the graph inside the given container + let graph = new MyCustomGraph(container, null, [ + CellEditorHandler, + TooltipHandler, + SelectionCellsHandler, + PopupMenuHandler, + ConnectionHandler, + SelectionHandler, + PanningHandler, + ]); + + // Enables HTML labels + graph.setHtmlLabels(true); + + // Enables rubberband selection + new RubberBandHandler(graph); + + let parent = graph.getDefaultParent(); + graph.insertVertex(parent, null, obj, 20, 20, 80, 60); + + // Undo/redo + let undoManager = new UndoManager(); + let listener = function (sender, evt) { + undoManager.undoableEditHappened(evt.getProperty('edit')); + }; + graph.getDataModel().addListener(InternalEvent.UNDO, listener); + graph.getView().addListener(InternalEvent.UNDO, listener); + + document.body.appendChild( + DomHelpers.button('Undo', function () { + undoManager.undo(); + }) + ); + + document.body.appendChild( + DomHelpers.button('Redo', function () { + undoManager.redo(); + }) + ); + + return container; +}; + +export const Default = Template.bind({}); diff --git a/packages/html/stories/MenuStyle.stories.js b/packages/html/stories/MenuStyle.stories.js new file mode 100644 index 000000000..3c87d019c --- /dev/null +++ b/packages/html/stories/MenuStyle.stories.js @@ -0,0 +1,240 @@ +/* +Copyright 2021-present The maxGraph project Contributors +Copyright (c) 2006-2013, JGraph Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Menustyle + +This example demonstrates using CSS to style the mxPopupMenu. +*/ + +import { + Client, + CellOverlay, + CellRenderer, + EventObject, + ImageBox, + InternalEvent, + InternalMouseEvent, + RubberBandHandler, + Graph, + PopupMenuHandler, + CellEditorHandler, + TooltipHandler, + SelectionCellsHandler, + ConnectionHandler, + SelectionHandler, + PanningHandler, +} from '@maxgraph/core'; +import { globalTypes, globalValues } from './shared/args.js'; +// style required by RubberBand +import '@maxgraph/core/css/common.css'; + +const CSS_TEMPLATE = ` +body div.mxPopupMenu { + -webkit-box-shadow: 3px 3px 6px #C0C0C0; + -moz-box-shadow: 3px 3px 6px #C0C0C0; + box-shadow: 3px 3px 6px #C0C0C0; + background: white; + position: absolute; + border: 3px solid #e7e7e7; + padding: 3px; +} +body table.mxPopupMenu { + border-collapse: collapse; + margin: 0px; +} +body tr.mxPopupMenuItem { + color: black; + cursor: default; +} +body td.mxPopupMenuItem { + padding: 6px 60px 6px 30px; + font-family: Arial; + font-size: 10pt; +} +body td.mxPopupMenuIcon { + background-color: white; + padding: 0px; +} +body tr.mxPopupMenuItemHover { + background-color: #eeeeee; + color: black; +} +table.mxPopupMenu hr { + border-top: solid 1px #cccccc; +} +table.mxPopupMenu tr { + font-size: 4pt; +} +`; + +// TODO apply this settings to the container used by the Graph +const HTML_TEMPLATE = ` + + +
+
+ + +`; + +export default { + title: 'Misc/MenuStyle', + argTypes: { + ...globalTypes, + }, + args: { + ...globalValues, + }, +}; + +const Template = ({ label, ...args }) => { + const styleElm = document.createElement('style'); + styleElm.innerText = CSS_TEMPLATE; + document.head.appendChild(styleElm); + + const container = document.createElement('div'); + container.style.position = 'relative'; + container.style.overflow = 'hidden'; + container.style.width = `${args.width}px`; + container.style.height = `${args.height}px`; + container.style.background = 'url(/images/grid.gif)'; + container.style.cursor = 'default'; + + // Disables built-in context menu + InternalEvent.disableContextMenu(container); + + // Changes some default colors + // TODO Find a way of modifying globally or setting locally! See https://github.com/maxGraph/maxGraph/issues/192 + //constants.HANDLE_FILLCOLOR = '#99ccff'; + //constants.HANDLE_STROKECOLOR = '#0088cf'; + //constants.VERTEX_SELECTION_COLOR = '#00a8ff'; + + class MyCustomCellRenderer extends CellRenderer { + installCellOverlayListeners(state, overlay, shape) { + super.installCellOverlayListeners.apply(this, arguments); + let graph = state.view.graph; + + InternalEvent.addGestureListeners( + shape.node, + function (evt) { + graph.fireMouseEvent( + InternalEvent.MOUSE_DOWN, + new InternalMouseEvent(evt, state) + ); + }, + function (evt) { + graph.fireMouseEvent( + InternalEvent.MOUSE_MOVE, + new InternalMouseEvent(evt, state) + ); + }, + function (evt) {} + ); + + if (!Client.IS_TOUCH) { + InternalEvent.addListener(shape.node, 'mouseup', function (evt) { + overlay.fireEvent( + new EventObject(InternalEvent.CLICK, 'event', evt, 'cell', state.cell) + ); + }); + } + } + } + + class MyCustomPopupMenuHandler extends PopupMenuHandler { + // Configures automatic expand on mouseover + autoExpand = true; + + constructor(graph) { + super(graph); + + // Installs context menu + this.factoryMethod = function (menu, cell, evt) { + menu.addItem('Item 1', null, function () { + alert('Item 1'); + }); + menu.addItem('Item 2', null, function () { + alert('Item 2'); + }); + menu.addSeparator(); + + let submenu1 = menu.addItem('Submenu 1', null, null); + menu.addItem( + 'Subitem 1', + null, + function () { + alert('Subitem 1'); + }, + submenu1 + ); + menu.addItem( + 'Subitem 1', + null, + function () { + alert('Subitem 2'); + }, + submenu1 + ); + }; + } + } + + class MyCustomGraph extends Graph { + createCellRenderer() { + return new MyCustomCellRenderer(); + } + } + + // Creates the graph inside the given container + let graph = new MyCustomGraph(container, null, [ + CellEditorHandler, + TooltipHandler, + SelectionCellsHandler, + MyCustomPopupMenuHandler, + ConnectionHandler, + SelectionHandler, + PanningHandler, + RubberBandHandler, + ]); + graph.setTooltips(true); + + // Gets the default parent for inserting new cells. This + // is normally the first child of the root (ie. layer 0). + let parent = graph.getDefaultParent(); + + // Adds cells to the model in a single step + let v1; + graph.batchUpdate(() => { + v1 = graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30); + let v2 = graph.insertVertex(parent, null, 'World!', 200, 150, 80, 30); + let e1 = graph.insertEdge(parent, null, '', v1, v2); + }); + + // Creates a new overlay with an image and a tooltip and makes it "transparent" to events + // and sets the overlay for the cell in the graph + let overlay = new CellOverlay( + new ImageBox('editors/images/overlays/check.png', 16, 16), + 'Overlay tooltip' + ); + graph.addCellOverlay(v1, overlay); + + return container; +}; + +export const Default = Template.bind({}); diff --git a/packages/html/stories/Scrollbars.stories.js b/packages/html/stories/Scrollbars.stories.js new file mode 100644 index 000000000..bf39978fe --- /dev/null +++ b/packages/html/stories/Scrollbars.stories.js @@ -0,0 +1,588 @@ +/* +Copyright 2021-present The maxGraph project Contributors +Copyright (c) 2006-2015, JGraph Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Scrollbars +This example demonstrates using a scrollable table with different sections in a cell label. +*/ + +import { + Cell, + CellRenderer, + CellEditorHandler, + Client, + ConnectionHandler, + constants, + DomHelpers, + domUtils, + EdgeStyle, + ImageBox, + InternalEvent, + Graph, + GraphView, + KeyHandler, + PanningHandler, + Perimeter, + Point, + Rectangle, + RubberBandHandler, + SelectionCellsHandler, + SelectionHandler, + xmlUtils, +} from '@maxgraph/core'; +import { + contextMenuTypes, + contextMenuValues, + globalValues, + globalTypes, +} from './shared/args.js'; +// style required by RubberBand +import '@maxgraph/core/css/common.css'; + +const CSS_TEMPLATE = ` +table.title { + border-color: black; + border-collapse: collapse; + cursor: move; + height: 26px; + border-bottom-style: none; + color: black; +} +table.title th { + font-size: 10pt; + font-family: Verdana; + white-space: nowrap; + background: lightgray; + font-weight: bold; +} +table.erd { + font-size: 10pt; + font-family: Verdana; + border-color: black; + border-collapse: collapse; + overflow: auto; + cursor: move; + white-space: nowrap; +} +table.erd td { + border-color: black; + text-align: left; + color: black; +} +button { + position:absolute; +} +`; + +// TODO apply this settings to the container used by the Graph +const HTML_TEMPLATE = ` + + + + +
+
+ +`; + +export default { + title: 'Misc/Scrollbars', + argTypes: { + ...contextMenuTypes, + ...globalTypes, + }, + args: { + ...contextMenuValues, + ...globalValues, + }, +}; + +const Template = ({ label, ...args }) => { + const styleElm = document.createElement('style'); + styleElm.innerText = CSS_TEMPLATE; + document.head.appendChild(styleElm); + + const container = document.createElement('div'); + container.style.position = 'relative'; + container.style.overflow = 'hidden'; + container.style.width = `${args.width}px`; + container.style.height = `${args.height}px`; + container.style.background = 'url(/images/grid.gif)'; + container.style.cursor = 'default'; + + // Disables foreignObjects + Client.NO_FO = true; + + class MyCustomSelectionHandler extends SelectionHandler { + // Enables move preview in HTML to appear on top + htmlPreview = true; + } + + // Disables the context menu + InternalEvent.disableContextMenu(container); + + // Defines global helper function to get y-coordinate for a given cell state and row + let getRowY = function (state, tr) { + let s = state.view.scale; + let div = tr.parentNode.parentNode.parentNode; + let offsetTop = parseInt(div.style.top); + let y = + state.y + (tr.offsetTop + tr.offsetHeight / 2 - div.scrollTop + offsetTop) * s; + y = Math.min(state.y + state.height, Math.max(state.y + offsetTop * s, y)); + return y; + }; + + // If connect preview is not moved away then getCellAt is used to detect the cell under + // the mouse if the mouse is over the preview shape in IE (no event transparency), ie. + // the built-in hit-detection of the HTML document will not be used in this case. This is + // not a problem here since the preview moves away from the mouse as soon as it connects + // to any given table row. This is because the edge connects to the outside of the row and + // is aligned to the grid during the preview. + class MyCustomConnectionHandler extends ConnectionHandler { + movePreviewAway = false; + + // Enables connect icons to appear on top of HTML + moveIconFront = true; + + // Defines an icon for creating new connections in the connection handler. + // This will automatically disable the highlighting of the source vertex. + connectImage = new ImageBox('images/connector.gif', 16, 16); + + // Overrides target perimeter point for connection previews + getTargetPerimeterPoint(state, me) { + // Determines the y-coordinate of the target perimeter point + // by using the currentRowNode assigned in updateRow + let y = me.getY(); + if (this.currentRowNode != null) { + y = getRowY(state, this.currentRowNode); + } + + // Checks on which side of the terminal to leave + let x = state.x; + if (this.previous.getCenterX() > state.getCenterX()) { + x += state.width; + } + return new Point(x, y); + } + + // Overrides source perimeter point for connection previews + getSourcePerimeterPoint(state, next, me) { + let y = me.getY(); + if (this.sourceRowNode != null) { + y = getRowY(state, this.sourceRowNode); + } + + // Checks on which side of the terminal to leave + let x = state.x; + if (next.x > state.getCenterX()) { + x += state.width; + } + return new Point(x, y); + } + + // Disables connections to invalid rows + isValidTarget(cell) { + return this.currentRowNode != null; + } + + // Adds a new function to update the currentRow based on the given event + // and return the DOM node for that row + updateRow(target) { + while (target != null && target.nodeName != 'TR') { + target = target.parentNode; + } + this.currentRow = null; + + // Checks if we're dealing with a row in the correct table + if (target != null && target.parentNode.parentNode.className == 'erd') { + // Stores the current row number in a property so that it can + // be retrieved to create the preview and final edge + let rowNumber = 0; + let current = target.parentNode.firstChild; + + while (target != current && current != null) { + current = current.nextSibling; + rowNumber++; + } + + this.currentRow = rowNumber + 1; + } else { + target = null; + } + return target; + } + + // Adds placement of the connect icon based on the mouse event target (row) + updateIcons(state, icons, me) { + let target = me.getSource(); + target = this.updateRow(target); + + if (target != null && this.currentRow != null) { + let div = target.parentNode.parentNode.parentNode; + let s = state.view.scale; + + icons[0].node.style.visibility = 'visible'; + icons[0].bounds.x = + state.x + + target.offsetLeft + + Math.min(state.width, target.offsetWidth * s) - + this.icons[0].bounds.width - + 2; + icons[0].bounds.y = + state.y - + this.icons[0].bounds.height / 2 + + (target.offsetTop + target.offsetHeight / 2 - div.scrollTop + div.offsetTop) * + s; + icons[0].redraw(); + + this.currentRowNode = target; + } else { + icons[0].node.style.visibility = 'hidden'; + } + } + + // Updates the targetRow in the preview edge State + mouseMove(sender, me) { + if (this.edgeState != null) { + this.currentRowNode = this.updateRow(me.getSource()); + + if (this.currentRow != null) { + this.edgeState.cell.value.setAttribute('targetRow', this.currentRow); + } else { + this.edgeState.cell.value.setAttribute('targetRow', '0'); + } + + // Destroys icon to prevent event redirection via image in IE + this.destroyIcons(); + } + super.mouseMove.apply(this, arguments); + } + + // Creates the edge state that may be used for preview + createEdgeState(me) { + let relation = doc.createElement('Relation'); + relation.setAttribute('sourceRow', this.currentRow || '0'); + relation.setAttribute('targetRow', '0'); + + let edge = this.createEdge(relation); + let style = this.graph.getCellStyle(edge); + let state = new Cell(this.graph.view, edge, style); + + // Stores the source row in the handler + this.sourceRowNode = this.currentRowNode; + + return state; + } + } + + class MyCustomCellRenderer extends CellRenderer { + // Scroll events should not start moving the vertex + isLabelEvent(state, evt) { + let source = InternalEvent.getSource(evt); + + return ( + state.text != null && + source != state.text.node && + source != state.text.node.getElementsByTagName('div')[0] + ); + } + + // Adds scrollbars to the outermost div and keeps the + // DIV position and size the same as the vertex + redrawLabel(state) { + super.redrawLabel.apply(this, arguments); // "supercall" + let graph = state.view.graph; + let model = graph.model; + + if (state.cell.isVertex() && state.text != null) { + // Scrollbars are on the div + let s = graph.view.scale; + let div = state.text.node.getElementsByTagName('div')[2]; + + if (div != null) { + // Installs the handler for updating connected edges + if (div.scrollHandler == null) { + div.scrollHandler = true; + + let updateEdges = () => { + let edgeCount = state.cell.getEdgeCount(); + + // Only updates edges to avoid update in DOM order + // for text label which would reset the scrollbar + for (let i = 0; i < edgeCount; i++) { + let edge = state.cell.getEdgeAt(i); + graph.view.invalidate(edge, true, false); + graph.view.validate(edge); + } + }; + + InternalEvent.addListener(div, 'scroll', updateEdges); + InternalEvent.addListener(div, 'mouseup', updateEdges); + } + } + } + } + } + + class MyCustomGraphView extends GraphView { + // Implements a special perimeter for table rows inside the table markup + updateFloatingTerminalPoint(edge, start, end, source) { + let next = this.getNextPoint(edge, end, source); + let div = start.text.node.getElementsByTagName('div')[2]; + + let x = start.x; + let y = start.getCenterY(); + + // Checks on which side of the terminal to leave + if (next.x > x + start.width / 2) { + x += start.width; + } + + if (div != null) { + y = start.getCenterY() - div.scrollTop; + + if (domUtils.isNode(edge.cell.value) && !start.cell.isCollapsed()) { + let attr = source ? 'sourceRow' : 'targetRow'; + let row = parseInt(edge.cell.value.getAttribute(attr)); + + // HTML labels contain an outer table which is built-in + let table = div.getElementsByTagName('table')[0]; + let trs = table.getElementsByTagName('tr'); + let tr = trs[Math.min(trs.length - 1, row - 1)]; + + // Gets vertical center of source or target row + if (tr != null) { + y = getRowY(start, tr); + } + } + + // Keeps vertical coordinate inside start + let offsetTop = parseInt(div.style.top) * start.view.scale; + y = Math.min( + isNaN(start.y + start.height) ? 9999999999999 : start.y + start.height, + isNaN(Math.max(start.y + offsetTop, y)) + ? 9999999999999 + : Math.max(start.y + offsetTop, y) + ); + + // Updates the vertical position of the nearest point if we're not + // dealing with a connection preview, in which case either the + // edgeState or the absolutePoints are null + if (edge != null && edge.absolutePoints != null) { + next.y = y; + } + } + + edge.setAbsoluteTerminalPoint(new Point(x, y), source); + + // Routes multiple incoming edges along common waypoints if + // the edges have a common target row + if (source && domUtils.isNode(edge.cell.value) && start != null && end != null) { + let edges = this.graph.getEdgesBetween(start.cell, end.cell, true); + let tmp = []; + + // Filters the edges with the same source row + let row = edge.cell.value.getAttribute('targetRow'); + + for (let i = 0; i < edges.length; i++) { + if ( + domUtils.isNode(edges[i].value) && + edges[i].value.getAttribute('targetRow') == row + ) { + tmp.push(edges[i]); + } + } + + edges = tmp; + + if (edges.length > 1 && edge.cell === edges[edges.length - 1]) { + // Finds the vertical center + let states = []; + let y = 0; + + for (let i = 0; i < edges.length; i++) { + states[i] = this.getState(edges[i]); + y += states[i].absolutePoints[0].y; + } + + y /= edges.length; + + for (let i = 0; i < states.length; i++) { + let x = states[i].absolutePoints[1].x; + + if (states[i].absolutePoints.length < 5) { + states[i].absolutePoints.splice(2, 0, new Point(x, y)); + } else { + states[i].absolutePoints[2] = new Point(x, y); + } + + // Must redraw the previous edges with the changed point + if (i < states.length - 1) { + this.graph.cellRenderer.redraw(states[i]); + } + } + } + } + } + } + + class MyCustomGraph extends Graph { + // Override folding to allow for tables + isCellFoldable(cell, collapse) { + return cell.isVertex(); + } + + // Overrides connectable state + isCellConnectable(cell) { + return !cell.isCollapsed(); + } + + // Overrides getLabel to return empty labels for edges and + // short markup for collapsed cells. + getLabel(cell) { + if (cell.isVertex()) { + if (cell.isCollapsed()) { + return ( + '' + + '' + + '
Customers
' + ); + } else { + return ( + '' + + '' + + '
Customers
' + + '
' + + '' + + '' + + '' + + '' + + '' + + '
' + + '' + + '' + + '' + + 'customerId
number
firstName
lastName
streetAddress
city
state
zip
' + ); + } + } else { + return ''; + } + } + + createCellRenderer() { + return new MyCustomCellRenderer(); + } + + createGraphView() { + return new MyCustomGraphView(this); + } + } + + // Creates the graph inside the given container + let graph = new MyCustomGraph( + container, + null, + // use a dedicated set of plugins + [ + CellEditorHandler, + ConnectionHandler, + MyCustomConnectionHandler, + MyCustomSelectionHandler, + PanningHandler, + RubberBandHandler, + SelectionCellsHandler, + ] + ); + + // Uses the entity perimeter (below) as default + graph.stylesheet.getDefaultVertexStyle().verticalAlign = constants.ALIGN.TOP; + graph.stylesheet.getDefaultVertexStyle().perimeter = Perimeter.RectanglePerimeter; + graph.stylesheet.getDefaultVertexStyle().shadow = true; + graph.stylesheet.getDefaultVertexStyle().fillColor = '#DDEAFF'; + graph.stylesheet.getDefaultVertexStyle().gradientColor = '#A9C4EB'; + delete graph.stylesheet.getDefaultVertexStyle().strokeColor; + + // Used for HTML labels that use up the complete vertex space (see + // graph.cellRenderer.redrawLabel below for syncing the size) + graph.stylesheet.getDefaultVertexStyle().overflow = 'fill'; + + // Uses the entity edge style as default + graph.stylesheet.getDefaultEdgeStyle().edge = EdgeStyle.EntityRelation; + graph.stylesheet.getDefaultEdgeStyle().strokeColor = 'black'; + graph.stylesheet.getDefaultEdgeStyle().fontColor = 'black'; + + // Allows new connections to be made but do not allow existing + // connections to be changed for the sake of simplicity of this + // example + graph.setCellsDisconnectable(false); + graph.setAllowDanglingEdges(false); + graph.setCellsEditable(false); + graph.setConnectable(true); + graph.setPanning(true); + graph.centerZoom = false; + + // Enables HTML markup in all labels + graph.setHtmlLabels(true); + + // User objects (data) for the individual cells + let doc = xmlUtils.createXmlDocument(); + + // Same should be used to create the XML node for the table + // description and the rows (most probably as child nodes) + let relation = doc.createElement('Relation'); + relation.setAttribute('sourceRow', '4'); + relation.setAttribute('targetRow', '6'); + + // Enables key handling (eg. escape) + new KeyHandler(graph); + + // Gets the default parent for inserting new cells. This + // is normally the first child of the root (ie. layer 0). + let parent = graph.getDefaultParent(); + + // Adds cells to the model in a single step + let width = 160; + let height = 230; + + graph.batchUpdate(() => { + let v1 = graph.insertVertex(parent, null, '', 20, 20, width, height); + v1.geometry.alternateBounds = new Rectangle(0, 0, width, 26); + + let v2 = graph.insertVertex(parent, null, '', 400, 150, width, height); + v2.geometry.alternateBounds = new Rectangle(0, 0, width, 26); + + graph.insertEdge(parent, null, relation, v1, v2); + }); + + let btn1 = DomHelpers.button('+', function () { + graph.zoomIn(); + }); + btn1.style.marginLeft = '20px'; + + document.body.appendChild(btn1); + document.body.appendChild( + DomHelpers.button('-', function () { + graph.zoomOut(); + }) + ); + + return container; +}; + +export const Default = Template.bind({}); diff --git a/packages/html/stories/ShowRegion.stories.js b/packages/html/stories/ShowRegion.stories.js new file mode 100644 index 000000000..fb8c9f1e3 --- /dev/null +++ b/packages/html/stories/ShowRegion.stories.js @@ -0,0 +1,197 @@ +/* +Copyright 2021-present The maxGraph project Contributors +Copyright (c) 2006-2013, JGraph Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Show region + +This example demonstrates using a custom rubberband handler to show the selected region in a new window. +*/ + +import { + eventUtils, + Graph, + InternalEvent, + MaxLog as domUtils, + MaxPopupMenu, + Rectangle, + RubberBandHandler, + styleUtils, +} from '@maxgraph/core'; +import { + globalTypes, + globalValues, + rubberBandTypes, + rubberBandValues, +} from './shared/args.js'; + +const CSS_TEMPLATE = ` +body div.mxPopupMenu { + -webkit-box-shadow: 3px 3px 6px #C0C0C0; + -moz-box-shadow: 3px 3px 6px #C0C0C0; + box-shadow: 3px 3px 6px #C0C0C0; + background: white; + position: absolute; + border: 3px solid #e7e7e7; + padding: 3px; +} +body table.mxPopupMenu { + border-collapse: collapse; + margin: 0px; +} +body tr.mxPopupMenuItem { + color: black; + cursor: default; +} +body td.mxPopupMenuItem { + padding: 6px 60px 6px 30px; + font-family: Arial; + font-size: 10pt; +} +body td.mxPopupMenuIcon { + background-color: white; + padding: 0px; +} +body tr.mxPopupMenuItemHover { + background-color: #eeeeee; + color: black; +} +table.mxPopupMenu hr { + border-top: solid 1px #cccccc; +} +table.mxPopupMenu tr { + font-size: 4pt; +} +`; + +// TODO apply this settings to the container used by the Graph +const HTML_TEMPLATE = ` + + + + +
+
+ Use the right mouse button to select a region of the diagram and select Show this. + +`; + +export default { + title: 'Misc/ShowRegion', + argTypes: { + ...globalTypes, + ...rubberBandTypes, + }, + args: { + ...globalValues, + ...rubberBandValues, + }, +}; + +const Template = ({ label, ...args }) => { + const styleElm = document.createElement('style'); + styleElm.innerText = CSS_TEMPLATE; + document.head.appendChild(styleElm); + + const container = document.createElement('div'); + container.style.position = 'relative'; + container.style.overflow = 'hidden'; + container.style.width = `${args.width}px`; + container.style.height = `${args.height}px`; + container.style.background = 'url(/images/grid.gif)'; + container.style.cursor = 'default'; + + // Disables built-in context menu + InternalEvent.disableContextMenu(container); + + // Changes some default colors + // TODO Find a way of modifying globally or setting locally! See https://github.com/maxGraph/maxGraph/issues/192 + //constants.HANDLE_FILLCOLOR = '#99ccff'; + //constants.HANDLE_STROKECOLOR = '#0088cf'; + //constants.VERTEX_SELECTION_COLOR = '#00a8ff'; + + // Creates the graph inside the given container + let graph = new Graph(container); + + class MyCustomRubberBandHandler extends RubberBandHandler { + isForceRubberbandEvent(me) { + return super.isForceRubberbandEvent(me) || me.isPopupTrigger(); + } + + // Defines a new popup menu for region selection in the rubberband handler + popupMenu = new MaxPopupMenu(function (menu, cell, evt) { + let rect = new Rectangle( + rubberband.x, + rubberband.y, + rubberband.width, + rubberband.height + ); + + menu.addItem('Show this', null, function () { + rubberband.popupMenu.hideMenu(); + let bounds = graph.getGraphBounds(); + domUtils.show( + graph, + null, + bounds.x - rubberband.x, + bounds.y - rubberband.y, + rubberband.width, + rubberband.height + ); + }); + }); + + mouseDown(sender, me) { + this.popupMenu.hideMenu(); + super.mouseDown(sender, me); + } + + mouseUp(sender, me) { + if (eventUtils.isPopupTrigger(me.getEvent())) { + if (!graph.getPlugin('PopupMenuHandler').isMenuShowing()) { + let origin = styleUtils.getScrollOrigin(); + this.popupMenu.popup( + me.getX() + origin.x + 1, + me.getY() + origin.y + 1, + null, + me.getEvent() + ); + this.reset(); + } + } else { + super.mouseUp(sender, me); + } + } + } + + // Enables rubberband selection + let rubberband = new MyCustomRubberBandHandler(graph); + + // Gets the default parent for inserting new cells. This + // is normally the first child of the root (ie. layer 0). + let parent = graph.getDefaultParent(); + + // Adds cells to the model in a single step + graph.batchUpdate(() => { + let v1 = graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30); + let v2 = graph.insertVertex(parent, null, 'World!', 200, 150, 80, 30); + let e1 = graph.insertEdge(parent, null, '', v1, v2); + }); + return container; +}; + +export const Default = Template.bind({}); diff --git a/packages/html/stories/Wires.stories.js b/packages/html/stories/Wires.stories.js new file mode 100644 index 000000000..c3f4374fb --- /dev/null +++ b/packages/html/stories/Wires.stories.js @@ -0,0 +1,999 @@ +/* +Copyright 2021-present The maxGraph project Contributors +Copyright (c) 2006-2020, JGraph Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +==================== +README +==================== + +- Edge-to-edge connections: We store the point where the mouse +was released in the terminal points of the edge geometry and +use that point to find the nearest segment on the target edge +and the connection point between the two edges in +mxGraphView.updateFixedTerminalPoint. + +- The orthogonal router, which is implemented as an edge style, +computes its result based on the output of mxGraphView. +updateFixedTerminalPoint, which computes all connection points +for edge-to-edge connections and constrained ports and vertices +and stores them in state.absolutePoints. + +- Routing directions are stored in the 'portConstraint' style. +Possible values for this style horizontal and vertical. Note +that this may have other values depending on the edge style. + +- For edge-to-edge connections, a 'source-/targetConstraint' +style is added in updateFixedTerminalPoint that contains the +orientation of the segment that the edge connects to. Possible +values are horizontal, vertical. + +- An alternative solution for connection points via connection +constraints is demonstrated. In this setup, the edge is connected +to the parent cell directly. There are no child cells that act +as "ports". Instead, the connection information is stored as a +relative point in the connecting edge. (See also: portrefs.html +for storing references to ports.) + +*/ + +import { + domUtils, + styleUtils, + mathUtils, + cloneUtils, + eventUtils, + Graph, + InternalEvent, + RubberBandHandler, + ConnectionHandler, + ConnectionConstraint, + Point, + CylinderShape, + CellRenderer, + DomHelpers, + EdgeStyle, + Rectangle, + EdgeHandler, + StyleRegistry, + EdgeSegmentHandler, + UndoManager, + CellEditorHandler, + ConstraintHandler, + Guide, + ImageBox, + GraphView, + SelectionHandler, + PanningHandler, + TooltipHandler, + SelectionCellsHandler, + PopupMenuHandler, +} from '@maxgraph/core'; + +import { + contextMenuTypes, + contextMenuValues, + globalTypes, + globalValues, + rubberBandTypes, + rubberBandValues, +} from './shared/args.js'; +// style required by RubberBand +import '@maxgraph/core/css/common.css'; + +export default { + title: 'Connections/Wires', + argTypes: { + ...contextMenuTypes, + ...globalTypes, + ...rubberBandTypes, + }, + args: { + ...contextMenuValues, + ...globalValues, + ...rubberBandValues, + }, +}; + +// TODO apply this settings to the container used by the Graph +const HTML_TEMPLATE = ` + +
+
+ + +`; + +const Template = ({ label, ...args }) => { + const parentContainer = document.createElement('div'); + const container = document.createElement('div'); + + container.style.position = 'relative'; + container.style.overflow = 'hidden'; + container.style.width = `${args.width}px`; + container.style.height = `${args.height}px`; + container.style.background = 'url(/images/grid.gif)'; + container.style.cursor = 'default'; + parentContainer.appendChild(container); + + // Changes some default colors + // TODO Find a way of modifying globally or setting locally! See https://github.com/maxGraph/maxGraph/issues/192 + //constants.SHADOWCOLOR = '#C0C0C0'; + + let joinNodeSize = 7; + let strokeWidth = 2; + + class MyCustomGraph extends Graph { + resetEdgesOnConnect = false; + + createEdgeSegmentHandler(state) { + return new MyCustomEdgeSegmentHandler(state); + } + + createGraphView() { + return new MyCustomGraphView(this); + } + + createEdgeHandler(state) { + return new MyCustomEdgeHandler(state); + } + + createHandler(state) { + let result = null; + + if (state != null) { + if (state.cell.isEdge()) { + let style = this.view.getEdgeStyle(state); + + if (style == EdgeStyle.WireConnector) { + return new EdgeSegmentHandler(state); + } + } + } + + return super.createHandler.apply(this, arguments); + } + + // Adds oval markers for edge-to-edge connections. + getCellStyle(cell) { + let style = super.getCellStyle.apply(this, arguments); + + if (style != null && cell?.isEdge()) { + style = cloneUtils.clone(style); + + if (cell.getTerminal(true)?.isEdge()) { + style.startArrow = 'oval'; + } + + if (cell.getTerminal(false)?.isEdge()) { + style.endArrow = 'oval'; + } + } + return style; + } + + getTooltipForCell(cell) { + let tip = ''; + + if (cell != null) { + let src = cell.getTerminal(true); + if (src != null) { + tip += this.getTooltipForCell(src) + ' '; + } + + let parent = cell.getParent(); + if (parent.isVertex()) { + tip += this.getTooltipForCell(parent) + '.'; + } + + tip += super.getTooltipForCell.apply(this, arguments); + + let trg = cell.getTerminal(false); + if (trg != null) { + tip += ' ' + this.getTooltipForCell(trg); + } + } + return tip; + } + + // Alternative solution for implementing connection points without child cells. + // This can be extended as shown in portrefs.html example to allow for per-port + // incoming/outgoing direction. + getAllConnectionConstraints(terminal) { + let geo = terminal != null ? terminal.cell.getGeometry() : null; + + if ( + (geo != null ? !geo.relative : false) && + terminal.cell.isVertex() && + terminal.cell.getChildCount() === 0 + ) { + return [ + new ConnectionConstraint(new Point(0, 0.5), false), + new ConnectionConstraint(new Point(1, 0.5), false), + ]; + } + return null; + } + } + + // FIXME: Provide means to make EdgeHandler and ConnectionHandler instantiate this subclass! + class MyCustomConstraintHandler extends ConstraintHandler { + // Replaces the port image + pointImage = new ImageBox('images/dot.gif', 10, 10); + } + + class MyCustomGuide extends Guide { + // Alt disables guides + isEnabledForEvent(evt) { + return !eventUtils.isAltDown(evt); + } + } + + class MyCustomEdgeHandler extends EdgeHandler { + // Enables snapping waypoints to terminals + snapToTerminals = true; + + isConnectableCell(cell) { + return graph.getPlugin('ConnectionHandler').isConnectableCell(cell); + } + + connect(edge, terminal, isSource, isClone, me) { + let result = null; + let model = this.graph.getDataModel(); + let parent = model.getParent(edge); + + model.beginUpdate(); + try { + result = super.connect.apply(this, arguments); + let geo = model.getGeometry(result); + + if (geo != null) { + geo = geo.clone(); + let pt = null; + + if (terminal.isEdge()) { + pt = this.abspoints[this.isSource ? 0 : this.abspoints.length - 1]; + pt.x = pt.x / this.graph.view.scale - this.graph.view.translate.x; + pt.y = pt.y / this.graph.view.scale - this.graph.view.translate.y; + + let pstate = this.graph.getView().getState(edge.getParent()); + + if (pstate != null) { + pt.x -= pstate.origin.x; + pt.y -= pstate.origin.y; + } + + pt.x -= this.graph.panDx / this.graph.view.scale; + pt.y -= this.graph.panDy / this.graph.view.scale; + } + + geo.setTerminalPoint(pt, isSource); + model.setGeometry(edge, geo); + } + } finally { + model.endUpdate(); + } + + return result; + } + + createMarker() { + let marker = super.createMarker.apply(this, arguments); + // Adds in-place highlighting when reconnecting existing edges + marker.highlight.highlight = + this.graph.getPlugin('ConnectionHandler').marker.highlight.highlight; + return marker; + } + } + + // Switch for black background and bright styles + let invert = false; + let MyCustomCellEditorHandler; + + if (invert) { + container.style.backgroundColor = 'black'; + + // White in-place editor text color + MyCustomCellEditorHandler = class extends CellEditorHandler { + startEditing(cell, trigger) { + super.startEditing.apply(this, arguments); + + if (this.textarea != null) { + this.textarea.style.color = '#FFFFFF'; + } + } + }; + } else { + MyCustomCellEditorHandler = CellEditorHandler; + } + + class MyCustomSelectionHandler extends SelectionHandler { + previewColor = invert ? 'white' : 'black'; + // Enables guides + guidesEnabled = true; + + createGuide() { + return new MyCustomGuide(this.graph, this.getGuideStates()); + } + } + + class MyCustomPanningHandler extends PanningHandler { + // Panning handler consumed right click so this must be + // disabled if right click should stop connection handler. + isPopupTrigger() { + return false; + } + } + + class MyCustomConnectionHandler extends ConnectionHandler { + // If connect preview is not moved away then getCellAt is used to detect the cell under + // the mouse if the mouse is over the preview shape in IE (no event transparency), ie. + // the built-in hit-detection of the HTML document will not be used in this case. + movePreviewAway = false; + waypointsEnabled = true; + + // Starts connections on the background in wire-mode + isStartEvent(me) { + return checkbox.checked || super.isStartEvent.apply(this, arguments); + } + + // Avoids any connections for gestures within tolerance except when in wire-mode + // or when over a port + mouseUp(sender, me) { + if (this.first != null && this.previous != null) { + let point = styleUtils.convertPoint(this.graph.container, me.getX(), me.getY()); + let dx = Math.abs(point.x - this.first.x); + let dy = Math.abs(point.y - this.first.y); + + if (dx < this.graph.tolerance && dy < this.graph.tolerance) { + // Selects edges in non-wire mode for single clicks, but starts + // connecting for non-edges regardless of wire-mode + if (!checkbox.checked && this.previous.cell.isEdge()) { + this.reset(); + } + return; + } + } + super.mouseUp.apply(this, arguments); + } + + // Overrides methods to preview and create new edges. + + // Sets source terminal point for edge-to-edge connections. + createEdgeState(me) { + let edge = this.graph.createEdge(); + + if (this.sourceConstraint != null && this.previous != null) { + edge.style = + 'exitX' + + '=' + + this.sourceConstraint.point.x + + ';' + + 'exitY' + + '=' + + this.sourceConstraint.point.y + + ';'; + } else if (me.getCell().isEdge()) { + let scale = this.graph.view.scale; + let tr = this.graph.view.translate; + let pt = new Point( + this.graph.snap(me.getGraphX() / scale) - tr.x, + this.graph.snap(me.getGraphY() / scale) - tr.y + ); + edge.geometry.setTerminalPoint(pt, true); + } + + return this.graph.view.createState(edge); + } + + // Uses right mouse button to create edges on background (see also: lines 67 ff) + isStopEvent(me) { + return me.getState() != null || eventUtils.isRightMouseButton(me.getEvent()); + } + + // Updates target terminal point for edge-to-edge connections. + updateCurrentState(me, point) { + super.updateCurrentState.apply(this, arguments); + + if (this.edgeState != null) { + this.edgeState.cell.geometry.setTerminalPoint(null, false); + + if ( + this.shape != null && + this.currentState != null && + this.currentState.cell.isEdge() + ) { + let scale = this.graph.view.scale; + let tr = this.graph.view.translate; + let pt = new Point( + this.graph.snap(me.getGraphX() / scale) - tr.x, + this.graph.snap(me.getGraphY() / scale) - tr.y + ); + this.edgeState.cell.geometry.setTerminalPoint(pt, false); + } + } + } + + // Adds in-place highlighting for complete cell area (no hotspot). + createMarker() { + let marker = super.createMarker.apply(this, arguments); + + // Uses complete area of cell for new connections (no hotspot) + marker.intersects = function (state, evt) { + return true; + }; + + // Adds in-place highlighting + //const mxCellHighlightHighlight = mxCellHighlight.prototype.highlight; + marker.highlight.highlight = function (state) { + // TODO: Should this be a subclass of marker rather than assigning directly? + if (this.state != state) { + if (this.state != null) { + this.state.style = this.lastStyle; + + // Workaround for shape using current stroke width if no strokewidth defined + this.state.style.strokeWidth = this.state.style.strokeWidth || '1'; + this.state.style.strokeColor = this.state.style.strokeColor || 'none'; + + if (this.state.shape != null) { + this.state.view.graph.cellRenderer.configureShape(this.state); + this.state.shape.redraw(); + } + } + + if (state != null) { + this.lastStyle = state.style; + state.style = cloneUtils.clone(state.style); + state.style.strokeColor = '#00ff00'; + state.style.strokeWidth = '3'; + + if (state.shape != null) { + state.view.graph.cellRenderer.configureShape(state); + state.shape.redraw(); + } + } + this.state = state; + } + }; + + return marker; + } + + // Makes sure non-relative cells can only be connected via constraints + isConnectableCell(cell) { + if (cell.isEdge()) { + return true; + } else { + let geo = cell != null ? cell.getGeometry() : null; + return geo != null ? geo.relative : false; + } + } + } + + // Updates connection points before the routing is called. + + class MyCustomGraphView extends GraphView { + // Computes the position of edge to edge connection points. + updateFixedTerminalPoint(edge, terminal, source, constraint) { + let pt = null; + + if (constraint != null) { + pt = this.graph.getConnectionPoint(terminal, constraint); + } + + if (source) { + edge.sourceSegment = null; + } else { + edge.targetSegment = null; + } + + if (pt == null) { + let s = this.scale; + let tr = this.translate; + let orig = edge.origin; + let geo = edge.cell.getGeometry(); + pt = geo.getTerminalPoint(source); + + // Computes edge-to-edge connection point + if (pt != null) { + pt = new Point(s * (tr.x + pt.x + orig.x), s * (tr.y + pt.y + orig.y)); + + // Finds nearest segment on edge and computes intersection + if (terminal != null && terminal.absolutePoints != null) { + let seg = mathUtils.findNearestSegment(terminal, pt.x, pt.y); + + // Finds orientation of the segment + let p0 = terminal.absolutePoints[seg]; + let pe = terminal.absolutePoints[seg + 1]; + let horizontal = p0.x - pe.x === 0; + + // Stores the segment in the edge state + let key = source ? 'sourceConstraint' : 'targetConstraint'; + let value = horizontal ? 'horizontal' : 'vertical'; + edge.style[key] = value; + + // Keeps the coordinate within the segment bounds + if (horizontal) { + pt.x = p0.x; + pt.y = Math.min(pt.y, Math.max(p0.y, pe.y)); + pt.y = Math.max(pt.y, Math.min(p0.y, pe.y)); + } else { + pt.y = p0.y; + pt.x = Math.min(pt.x, Math.max(p0.x, pe.x)); + pt.x = Math.max(pt.x, Math.min(p0.x, pe.x)); + } + } + } + // Computes constraint connection points on vertices and ports + else if (terminal != null && terminal.cell.geometry.relative) { + pt = new Point( + this.getRoutingCenterX(terminal), + this.getRoutingCenterY(terminal) + ); + } + + // Snaps point to grid + /*if (pt != null) + { + let tr = this.graph.view.translate; + let s = this.graph.view.scale; + + pt.x = (this.graph.snap(pt.x / s - tr.x) + tr.x) * s; + pt.y = (this.graph.snap(pt.y / s - tr.y) + tr.y) * s; + }*/ + } + + edge.setAbsoluteTerminalPoint(pt, source); + } + } + + // Updates the terminal and control points in the cloned preview. + class MyCustomEdgeSegmentHandler extends EdgeSegmentHandler { + clonePreviewState(point, terminal) { + let clone = super.clonePreviewState.apply(this, arguments); + clone.cell = clone.cell.clone(); + + if (this.isSource || this.isTarget) { + clone.cell.geometry = clone.cell.geometry.clone(); + + // Sets the terminal point of an edge if we're moving one of the endpoints + if (clone.cell.isEdge()) { + // TODO: Only set this if the target or source terminal is an edge + clone.cell.geometry.setTerminalPoint(point, this.isSource); + } else { + clone.cell.geometry.setTerminalPoint(null, this.isSource); + } + } + + return clone; + } + } + + // Imlements a custom resistor shape. Direction currently ignored here. + + class ResistorShape extends CylinderShape { + constructor() { + // TODO: The original didn't seem to call the super + super(null, null, null, null); + } + + redrawPath(path, x, y, w, h, isForeground) { + let dx = w / 16; + + if (isForeground) { + path.moveTo(0, h / 2); + path.lineTo(2 * dx, h / 2); + path.lineTo(3 * dx, 0); + path.lineTo(5 * dx, h); + path.lineTo(7 * dx, 0); + path.lineTo(9 * dx, h); + path.lineTo(11 * dx, 0); + path.lineTo(13 * dx, h); + path.lineTo(14 * dx, h / 2); + path.lineTo(16 * dx, h / 2); + path.end(); + } + } + } + + CellRenderer.registerShape('resistor', ResistorShape); + + // Imlements a custom resistor shape. Direction currently ignored here. + + EdgeStyle.WireConnector = function (state, source, target, hints, result) { + // Creates array of all way- and terminalpoints + let pts = state.absolutePoints; + let horizontal = true; + let hint = null; + + // Gets the initial connection from the source terminal or edge + if (source != null && source.cell.isEdge()) { + horizontal = state.style.sourceConstraint == 'horizontal'; + } else if (source != null) { + horizontal = source.style.portConstraint != 'vertical'; + + // Checks the direction of the shape and rotates + let direction = source.style.direction; + + if (direction == 'north' || direction == 'south') { + horizontal = !horizontal; + } + } + + // Adds the first point + // TODO: Should move along connected segment + let pt = pts[0]; + + if (pt == null && source != null) { + pt = new Point( + state.view.getRoutingCenterX(source), + state.view.getRoutingCenterY(source) + ); + } else if (pt != null) { + pt = pt.clone(); + } + + let first = pt; + + // Adds the waypoints + if (hints != null && hints.length > 0) { + // FIXME: First segment not movable + /*hint = state.view.transformControlPoint(state, hints[0]); + MaxLog.show(); + MaxLog.debug(hints.length,'hints0.y='+hint.y, pt.y) + + if (horizontal && Math.floor(hint.y) != Math.floor(pt.y)) + { + MaxLog.show(); + MaxLog.debug('add waypoint'); + + pt = new Point(pt.x, hint.y); + result.push(pt); + pt = pt.clone(); + //horizontal = !horizontal; + }*/ + + for (let i = 0; i < hints.length; i++) { + horizontal = !horizontal; + hint = state.view.transformControlPoint(state, hints[i]); + + if (horizontal) { + if (pt.y !== hint.y) { + pt.y = hint.y; + result.push(pt.clone()); + } + } else if (pt.x !== hint.x) { + pt.x = hint.x; + result.push(pt.clone()); + } + } + } else { + hint = pt; + } + + // Adds the last point + pt = pts[pts.length - 1]; + + // TODO: Should move along connected segment + if (pt == null && target != null) { + pt = new Point( + state.view.getRoutingCenterX(target), + state.view.getRoutingCenterY(target) + ); + } + + if (horizontal) { + if (pt.y !== hint.y && first.x !== pt.x) { + result.push(new Point(pt.x, hint.y)); + } + } else if (pt.x !== hint.x && first.y !== pt.y) { + result.push(new Point(hint.x, pt.y)); + } + }; + + StyleRegistry.putValue('wireEdgeStyle', EdgeStyle.WireConnector); + + let graph = new MyCustomGraph(container, null, [ + MyCustomCellEditorHandler, + TooltipHandler, + SelectionCellsHandler, + PopupMenuHandler, + MyCustomConnectionHandler, + MyCustomSelectionHandler, + MyCustomPanningHandler, + ]); + + let labelBackground = invert ? '#000000' : '#FFFFFF'; + let fontColor = invert ? '#FFFFFF' : '#000000'; + let strokeColor = invert ? '#C0C0C0' : '#000000'; + let fillColor = invert ? 'none' : '#FFFFFF'; + + graph.view.scale = 1; + graph.setPanning(true); + graph.setConnectable(true); + graph.setConnectableEdges(true); + graph.setDisconnectOnMove(false); + graph.foldingEnabled = false; + + //Maximum size + graph.maximumGraphBounds = new Rectangle(0, 0, 800, 600); + graph.border = 50; + + // Enables return key to stop editing (use shift-enter for newlines) + graph.setEnterStopsCellEditing(true); + + // Adds rubberband selection + new RubberBandHandler(graph); + + // Adds a special tooltip for edges + graph.setTooltips(true); + + let style = graph.getStylesheet().getDefaultEdgeStyle(); + delete style.endArrow; + style.strokeColor = strokeColor; + style.labelBackgroundColor = labelBackground; + style.edgeStyle = 'wireEdgeStyle'; + style.fontColor = fontColor; + style.fontSize = '9'; + style.movable = '0'; + style.strokeWidth = strokeWidth; + //style.rounded = '1'; + + // Sets join node size + style.startSize = joinNodeSize; + style.endSize = joinNodeSize; + + style = graph.getStylesheet().getDefaultVertexStyle(); + style.gradientDirection = 'south'; + //style.gradientColor = '#909090'; + style.strokeColor = strokeColor; + //style.fillColor = '#e0e0e0'; + style.fillColor = 'none'; + style.fontColor = fontColor; + style.fontStyle = '1'; + style.fontSize = '12'; + style.resizable = '0'; + style.rounded = '1'; + style.strokeWidth = strokeWidth; + + let parent = graph.getDefaultParent(); + + graph.batchUpdate(() => { + let v1 = graph.insertVertex(parent, null, 'J1', 80, 40, 40, 80, { + verticalLabelPosition: 'top', + verticalAlign: 'bottom', + shadow: true, + fillColor, + }); + v1.setConnectable(false); + + let v11 = graph.insertVertex(v1, null, '1', 0, 0, 10, 16, { + shape: 'line', + align: 'left', + verticalAlign: 'middle', + fontSize: 10, + routingCenterX: -0.5, + spacingLeft: 12, + fontColor, + strokeColor, + }); + v11.geometry.relative = true; + v11.geometry.offset = new Point(-v11.geometry.width, 2); + let v12 = v11.clone(); + v12.value = '2'; + v12.geometry.offset = new Point(-v11.geometry.width, 22); + v1.insert(v12); + let v13 = v11.clone(); + v13.value = '3'; + v13.geometry.offset = new Point(-v11.geometry.width, 42); + v1.insert(v13); + let v14 = v11.clone(); + v14.value = '4'; + v14.geometry.offset = new Point(-v11.geometry.width, 62); + v1.insert(v14); + + let v15 = v11.clone(); + v15.value = '5'; + v15.geometry.x = 1; + v15.style = { + shape: 'line', + align: 'right', + verticalAlign: 'middle', + fontSize: 10, + routingCenterX: 0.5, + spacingRight: 12, + fontColor, + strokeColor, + }; + v15.geometry.offset = new Point(0, 2); + v1.insert(v15); + let v16 = v15.clone(); + v16.value = '6'; + v16.geometry.offset = new Point(0, 22); + v1.insert(v16); + let v17 = v15.clone(); + v17.value = '7'; + v17.geometry.offset = new Point(0, 42); + v1.insert(v17); + let v18 = v15.clone(); + v18.value = '8'; + v18.geometry.offset = new Point(0, 62); + v1.insert(v18); + + let v19 = v15.clone(); + v19.value = 'clk'; + v19.geometry.x = 0.5; + v19.geometry.y = 1; + v19.geometry.width = 10; + v19.geometry.height = 4; + // NOTE: portConstraint is defined for east direction, so must be inverted here + v19.style = { + shape: 'triangle', + direction: 'north', + spacingBottom: 12, + align: 'center', + portConstraint: 'horizontal', + fontSize: 8, + strokeColor, + routingCenterY: 0.5, + }; + v19.geometry.offset = new Point(-4, -4); + v1.insert(v19); + + let v2 = graph.insertVertex(parent, null, 'R1', 220, 220, 80, 20, { + shape: 'resistor', + verticalLabelPosition: 'top', + verticalAlign: 'bottom', + }); + + // Uses implementation of connection points via constraints (see above) + //v2.setConnectable(false); + + /*let v21 = graph.insertVertex(v2, null, 'A', 0, 0.5, 10, 1, + 'shape=none;spacingBottom=11;spacingLeft=1;align=left;fontSize=8;'+ + 'fontColor=#4c4c4c;strokeColor=#909090;'); + v21.geometry.relative = true; + v21.geometry.offset = new Point(0, -1); + + let v22 = graph.insertVertex(v2, null, 'B', 1, 0.5, 10, 1, + 'spacingBottom=11;spacingLeft=1;align=left;fontSize=8;'+ + 'fontColor=#4c4c4c;strokeColor=#909090;'); + v22.geometry.relative = true; + v22.geometry.offset = new Point(-10, -1);*/ + + let v3 = graph.addCell(graph.getDataModel().cloneCell(v1)); + v3.value = 'J3'; + v3.geometry.x = 420; + v3.geometry.y = 340; + + // Connection constraints implemented in edges, alternatively this + // can be implemented using references, see: portrefs.html + let e1 = graph.insertEdge(parent, null, 'e1', v1.getChildAt(7), v2, { + entryX: 0, + entryY: 0.5, + entryPerimeter: 0, + }); + e1.geometry.points = [new Point(180, 110)]; + + let e2 = graph.insertEdge(parent, null, 'e2', v1.getChildAt(4), v2, { + entryX: 1, + entryY: 0.5, + entryPerimeter: 0, + }); + e2.geometry.points = [new Point(320, 50), new Point(320, 230)]; + + let e3 = graph.insertEdge(parent, null, 'crossover', e1, e2); + e3.geometry.setTerminalPoint(new Point(180, 140), true); + e3.geometry.setTerminalPoint(new Point(320, 140), false); + + // let e1 = graph.insertEdge(parent, null, 'e1', v1.getChildAt(7), v2.getChildAt(0)); + // e1.geometry.points = [new Point(180, 140)]; + + // let e2 = graph.insertEdge(parent, null, '', v1.getChildAt(4), v2.getChildAt(1)); + // e2.geometry.points = [new Point(320, 80)]; + + // let e3 = graph.insertEdge(parent, null, 'crossover', e1, e2); + // e3.geometry.setTerminalPoint(new Point(180, 160), true); + // e3.geometry.setTerminalPoint(new Point(320, 160), false); + + let e4 = graph.insertEdge(parent, null, 'e4', v2, v3.getChildAt(0), { + exitX: 1, + exitY: 0.5, + entryPerimeter: 0, + }); + e4.geometry.points = [new Point(380, 230)]; + + let e5 = graph.insertEdge(parent, null, 'e5', v3.getChildAt(5), v1.getChildAt(0)); + e5.geometry.points = [new Point(500, 310), new Point(500, 20), new Point(50, 20)]; + + let e6 = graph.insertEdge(parent, null, ''); + e6.geometry.setTerminalPoint(new Point(100, 500), true); + e6.geometry.setTerminalPoint(new Point(600, 500), false); + + let e7 = graph.insertEdge(parent, null, 'e7', v3.getChildAt(7), e6); + e7.geometry.setTerminalPoint(new Point(500, 500), false); + e7.geometry.points = [new Point(500, 350)]; + }); + + parentContainer.appendChild( + DomHelpers.button('Zoom In', function () { + graph.zoomIn(); + }) + ); + + parentContainer.appendChild( + DomHelpers.button('Zoom Out', function () { + graph.zoomOut(); + }) + ); + + // Undo/redo + let undoManager = new UndoManager(); + let listener = function (sender, evt) { + undoManager.undoableEditHappened(evt.getProperty('edit')); + }; + graph.getDataModel().addListener(InternalEvent.UNDO, listener); + graph.getView().addListener(InternalEvent.UNDO, listener); + + parentContainer.appendChild( + DomHelpers.button('Undo', function () { + undoManager.undo(); + }) + ); + + parentContainer.appendChild( + DomHelpers.button('Redo', function () { + undoManager.redo(); + }) + ); + + // Shows XML for debugging the actual model + parentContainer.appendChild( + DomHelpers.button('Delete', function () { + graph.removeCells(); + }) + ); + + // Wire-mode + let checkbox = document.createElement('input'); + checkbox.setAttribute('type', 'checkbox'); + + parentContainer.appendChild(checkbox); + domUtils.write(parentContainer, 'Wire Mode'); + + // Grid + let checkbox2 = document.createElement('input'); + checkbox2.setAttribute('type', 'checkbox'); + checkbox2.setAttribute('checked', 'true'); + + parentContainer.appendChild(checkbox2); + domUtils.write(parentContainer, 'Grid'); + + InternalEvent.addListener(checkbox2, 'click', function (evt) { + if (checkbox2.checked) { + container.style.background = 'url(/images/grid.gif)'; + } else { + container.style.background = ''; + } + container.style.backgroundColor = invert ? 'black' : 'white'; + }); + InternalEvent.disableContextMenu(container); + + return parentContainer; +}; + +export const Default = Template.bind({}); diff --git a/packages/html/stories/stashed/GraphLayout.js b/packages/html/stories/stashed/GraphLayout.js deleted file mode 100644 index 02529936c..000000000 --- a/packages/html/stories/stashed/GraphLayout.js +++ /dev/null @@ -1,154 +0,0 @@ -/** - * Copyright (c) 2006-2013, JGraph Ltd - * - * Graph Layout - * - * This example demonstrates using - * automatic graph layouts and listening to changes of the graph size - * to keep the container size in sync. - */ - -import React from 'react'; -import mxEvent from '../mxgraph/util/mxEvent'; -import mxGraph from '../mxgraph/view/mxGraph'; -import mxRubberband from '../mxgraph/handler/mxRubberband'; -import mxUtils from "../mxgraph/util/mxUtils"; -import mxCircleLayout from "../mxgraph/layout/mxCircleLayout"; - - -const HTML_TEMPLATE = ` - - - - -
-
-
- Transitions - -` - - -// Creates the graph inside the given container -let graph = new mxGraph(container); - -// Disables basic selection and cell handling -graph.setEnabled(false); - -// Changes the default vertex style in-place -let style = graph.getStylesheet().getDefaultVertexStyle(); -style.shape = mxConstants.SHAPE_ELLIPSE; -style.perimiter = Perimeter.EllipsePerimeter; -style.gradientColor = 'white'; -style.fontSize = '10'; - -// Updates the size of the container to match -// the size of the graph when it changes. If -// this is commented-out, and the DIV style's -// overflow is set to "auto", then scrollbars -// will appear for the diagram. If overflow is -// set to "visible", then the diagram will be -// visible even when outside the parent DIV. -// With the code below, the parent DIV will be -// resized to contain the complete graph. -//graph.setResizeContainer(true); - -// Larger grid size yields cleaner layout result -graph.gridSize = 40; - -// Gets the default parent for inserting new cells. This -// is normally the first child of the root (ie. layer 0). -let parent = graph.getDefaultParent(); - -// Creates a layout algorithm to be used -// with the graph -let layout = new MxFastOrganicLayout(graph); - -// Moves stuff wider apart than usual -layout.forceConstant = 80; - -// Reference to the transition checkbox -let animate = document.getElementById('animate'); - -// Adds a button to execute the layout -this.el2.insertBefore(mxUtils.button('Circle Layout', - function(evt) { - graph.getDataModel().beginUpdate(); - try { - // Creates a layout algorithm to be used - // with the graph - let circleLayout = new mxCircleLayout(graph); - circleLayout.execute(parent); - } catch (e) { - throw e; - } finally { - if (animate.checked) { - let morph = new Morphing(graph); - morph.addListener(mxEvent.DONE, function() { - graph.getDataModel().endUpdate(); - }); - morph.startAnimation(); - } else { - graph.getDataModel().endUpdate(); - } - } - } -), document.body.firstChild); - -// Adds a button to execute the layout -document.body.insertBefore(mxUtils.button('Organic Layout', - function(evt) - { - graph.getDataModel().beginUpdate(); - try { - layout.execute(parent); - } catch (e) { - throw e; - } finally { - if (animate.checked) { - // Default values are 6, 1.5, 20 - let morph = new Morphing(graph, 10, 1.7, 20); - morph.addListener(mxEvent.DONE, function() { - graph.getDataModel().endUpdate(); - }); - morph.startAnimation(); - } else { - graph.getDataModel().endUpdate(); - } - } - } -), document.body.firstChild); - -// Adds cells to the model in a single step -graph.getDataModel().beginUpdate(); -let w = 30; -let h = 30; -try { - var v1 = graph.insertVertex(parent, null, 'A', 0, 0, w, h); - var v2 = graph.insertVertex(parent, null, 'B', 0, 0, w, h); - var v3 = graph.insertVertex(parent, null, 'C', 0, 0, w, h); - var v4 = graph.insertVertex(parent, null, 'D', 0, 0, w, h); - var v5 = graph.insertVertex(parent, null, 'E', 0, 0, w, h); - var v6 = graph.insertVertex(parent, null, 'F', 0, 0, w, h); - var v7 = graph.insertVertex(parent, null, 'G', 0, 0, w, h); - var v8 = graph.insertVertex(parent, null, 'H', 0, 0, w, h); - var e1 = graph.insertEdge(parent, null, 'ab', v1, v2); - var e2 = graph.insertEdge(parent, null, 'ac', v1, v3); - var e3 = graph.insertEdge(parent, null, 'cd', v3, v4); - var e4 = graph.insertEdge(parent, null, 'be', v2, v5); - var e5 = graph.insertEdge(parent, null, 'cf', v3, v6); - var e6 = graph.insertEdge(parent, null, 'ag', v1, v7); - var e7 = graph.insertEdge(parent, null, 'gh', v7, v8); - var e8 = graph.insertEdge(parent, null, 'gc', v7, v3); - var e9 = graph.insertEdge(parent, null, 'gd', v7, v4); - var e10 = graph.insertEdge(parent, null, 'eh', v5, v8); - - // Executes the layout - layout.execute(parent); -} finally { - // Updates the display - graph.getDataModel().endUpdate(); -} - diff --git a/packages/html/stories/stashed/HtmlLabel.js b/packages/html/stories/stashed/HtmlLabel.js deleted file mode 100644 index 789278433..000000000 --- a/packages/html/stories/stashed/HtmlLabel.js +++ /dev/null @@ -1,138 +0,0 @@ -// @ts-check -/** - * Copyright (c) 2006-2013, JGraph Ltd - * - * HTML label - * - * This example demonstrates using - * HTML labels that are connected to the state of the user object. - */ - -import React from 'react'; -import mxEvent from '../mxgraph/util/mxEvent'; -import mxGraph from '../mxgraph/view/mxGraph'; -import mxRubberband from '../mxgraph/handler/mxRubberband'; - - -const HTML_TEMPLATE = ` - - - - -
-
- -` - - -// Disables the built-in context menu -mxEvent.disableContextMenu(container); - -// Creates the graph inside the given container -let graph = new mxGraph(container); - -// Enables HTML labels -graph.setHtmlLabels(true); - -// Enables rubberband selection -new mxRubberband(graph); - -// Creates a user object that stores the state -let doc = mxUtils.createXmlDocument(); -let obj = doc.createElement('UserObject'); -obj.setAttribute('label', 'Hello, World!'); -obj.setAttribute('checked', 'false'); - -// Adds optional caching for the HTML label -let cached = true; - -if (cached) { - // Ignores cached label in codec - CodecRegistry.getCodec(mxCell).exclude.push('div'); - - // Invalidates cached labels - graph.model.setValue = function(cell, value) { - cell.div = null; - mxGraphModel.prototype.setValue.apply(this, arguments); - }; -} - -// Overrides method to provide a cell label in the display -graph.convertValueToString = function(cell) { - if (cached && cell.div != null) { - // Uses cached label - return cell.div; - } else if (mxUtils.isNode(cell.value) && cell.value.nodeName.toLowerCase() == 'userobject') { - // Returns a DOM for the label - let div = document.createElement('div'); - div.innerHTML = cell.getAttribute('label'); - mxUtils.br(div); - - let checkbox = document.createElement('input'); - checkbox.setAttribute('type', 'checkbox'); - - if (cell.getAttribute('checked') == 'true') { - checkbox.setAttribute('checked', 'checked'); - checkbox.defaultChecked = true; - } - - // Writes back to cell if checkbox is clicked - mxEvent.addListener(checkbox, 'change', function(evt) { - let elt = cell.value.cloneNode(true); - elt.setAttribute('checked', (checkbox.checked) ? 'true' : 'false'); - - graph.model.setValue(cell, elt); - }); - - div.appendChild(checkbox); - - if (cached) { - // Caches label - cell.div = div; - } - return div; - } - return ''; -}; - -// Overrides method to store a cell label in the model -let cellLabelChanged = graph.cellLabelChanged; -graph.cellLabelChanged = function(cell, newValue, autoSize) { - if (mxUtils.isNode(cell.value) && cell.value.nodeName.toLowerCase() == 'userobject') { - // Clones the value for correct undo/redo - let elt = cell.value.cloneNode(true); - elt.setAttribute('label', newValue); - newValue = elt; - } - - cellLabelChanged.apply(this, arguments); -}; - -// Overrides method to create the editing value -let getEditingValue = graph.getEditingValue; -graph.getEditingValue = function(cell) { - if (mxUtils.isNode(cell.value) && cell.value.nodeName.toLowerCase() == 'userobject') { - return cell.getAttribute('label'); - } -}; - -let parent = graph.getDefaultParent(); -graph.insertVertex(parent, null, obj, 20, 20, 80, 60); - -// Undo/redo -let undoManager = new UndoManager(); -let listener = function(sender, evt) { - undoManager.undoableEditHappened(evt.getProperty('edit')); -}; -graph.getDataModel().addListener(mxEvent.UNDO, listener); -graph.getView().addListener(mxEvent.UNDO, listener); - -document.body.appendChild(mxUtils.button('Undo', function() { - undoManager.undo(); -})); - -document.body.appendChild(mxUtils.button('Redo', function() { - undoManager.redo(); -})); - diff --git a/packages/html/stories/stashed/MenuStyle.js b/packages/html/stories/stashed/MenuStyle.js deleted file mode 100644 index 4b74ffbd1..000000000 --- a/packages/html/stories/stashed/MenuStyle.js +++ /dev/null @@ -1,140 +0,0 @@ -/** - * Copyright (c) 2006-2013, JGraph Ltd - * - * Menustyle - * Menustyle. This example demonstrates using - * CSS to style the mxPopupMenu. - */ - -import React from 'react'; -import mxEvent from '../mxgraph/util/mxEvent'; -import mxGraph from '../mxgraph/view/mxGraph'; -import mxRubberband from '../mxgraph/handler/mxRubberband'; - - -const HTML_TEMPLATE = ` - - - - - -
-
- - -` - -// Disables built-in context menu -InternalEvent.disableContextMenu(document.body); - -// Changes some default colors -mxConstants.HANDLE_FILLCOLOR = '#99ccff'; -mxConstants.HANDLE_STROKECOLOR = '#0088cf'; -mxConstants.VERTEX_SELECTION_COLOR = '#00a8ff'; - -// Creates the graph inside the given container -let graph = new mxGraph(container); -graph.setTooltips(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). -let parent = graph.getDefaultParent(); - -// Adds cells to the model in a single step -graph.batchUpdate(() => { - var v1 = graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30); - var v2 = graph.insertVertex(parent, null, 'World!', 200, 150, 80, 30); - var e1 = graph.insertEdge(parent, null, '', v1, v2); -}); - -// Creates a new overlay with an image and a tooltip and makes it "transparent" to events -let overlay = new CellOverlay(new mxImage('editors/images/overlays/check.png', 16, 16), 'Overlay tooltip'); - -let mxCellRendererInstallCellOverlayListeners = mxCellRenderer.prototype.installCellOverlayListeners; -mxCellRenderer.prototype.installCellOverlayListeners = function(state, overlay, shape){ - mxCellRendererInstallCellOverlayListeners.apply(this, arguments); - var graph = state.view.graph; - - mxEvent.addGestureListeners(shape.node, - function (evt) { - graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state)); - }, - function (evt) { - graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, state)); - }, - function (evt) { - }); - - if (!Client.IS_TOUCH) { - mxEvent.addListener(shape.node, 'mouseup', function(evt) { - overlay.fireEvent(new mxEventObject(mxEvent.CLICK, - 'event', evt, 'cell', state.cell)); - }); - } -}; - -// Sets the overlay for the cell in the graph -graph.addCellOverlay(v1, overlay); - -// Configures automatic expand on mouseover -graph.getPlugin('PopupMenuHandler').autoExpand = true; - - // Installs context menu -graph.getPlugin('PopupMenuHandler').factoryMethod = function(menu, cell, evt) { - menu.addItem('Item 1', null, function() { - alert('Item 1'); - }); - menu.addItem('Item 2', null, function() { - alert('Item 2'); - }); - menu.addSeparator(); - - var submenu1 = menu.addItem('Submenu 1', null, null); - menu.addItem('Subitem 1', null, function() { - alert('Subitem 1'); - }, submenu1); - menu.addItem('Subitem 1', null, function() { - alert('Subitem 2'); - }, submenu1); -}; diff --git a/packages/html/stories/stashed/Scrollbars.js b/packages/html/stories/stashed/Scrollbars.js deleted file mode 100644 index c0e23d721..000000000 --- a/packages/html/stories/stashed/Scrollbars.js +++ /dev/null @@ -1,482 +0,0 @@ -/** - * Copyright (c) 2006-2015, JGraph Ltd - * - * Scrollbars - * - * This example demonstrates using - * a scrollable table with different sections in a cell label. - */ - -import React from 'react'; -import mxEvent from '../mxgraph/util/mxEvent'; -import mxGraph from '../mxgraph/view/mxGraph'; -import mxRubberband from '../mxgraph/handler/mxRubberband'; -import { error } from '../../packages/core/src/util/gui/MaxWindow'; -import { createXmlDocument } from '../../packages/core/src/util/xmlUtils'; -import { button } from '../../packages/core/src/util/dom/DomHelpers'; -import { isNode } from '../../packages/core/src/util/domUtils'; - - -const HTML_TEMPLATE = ` - - - - - - -
-
- -` - - -// Must be disabled to compute positions inside the DOM tree of the cell label. -mxGraphView.prototype.optimizeVmlReflows = false; - -// If connect preview is not moved away then getCellAt is used to detect the cell under -// the mouse if the mouse is over the preview shape in IE (no event transparency), ie. -// the built-in hit-detection of the HTML document will not be used in this case. This is -// not a problem here since the preview moves away from the mouse as soon as it connects -// to any given table row. This is because the edge connects to the outside of the row and -// is aligned to the grid during the preview. -ConnectionHandler.prototype.movePreviewAway = false; - -// Disables foreignObjects -Client.NO_FO = true; - -// Enables move preview in HTML to appear on top -SelectionHandler.prototype.htmlPreview = true; - -// Enables connect icons to appear on top of HTML -ConnectionHandler.prototype.moveIconFront = true; - -// Defines an icon for creating new connections in the connection handler. -// This will automatically disable the highlighting of the source vertex. -ConnectionHandler.prototype.connectImage = new Image('images/connector.gif', 16, 16); - -// Disables the context menu -mxEvent.disableContextMenu(container); - -// Overrides target perimeter point for connection previews -ConnectionHandler.prototype.getTargetPerimeterPoint = function(state, me) { - // Determines the y-coordinate of the target perimeter point - // by using the currentRowNode assigned in updateRow - let y = me.getY(); - if (this.currentRowNode != null) { - y = getRowY(state, this.currentRowNode); - } - - // Checks on which side of the terminal to leave - let x = state.x; - if (this.previous.getCenterX() > state.getCenterX()) { - x += state.width; - } - return new Point(x, y); -}; - -// Overrides source perimeter point for connection previews -ConnectionHandler.prototype.getSourcePerimeterPoint = function(state, next, me) -{ - let y = me.getY(); - if (this.sourceRowNode != null) { - y = getRowY(state, this.sourceRowNode); - } - - // Checks on which side of the terminal to leave - let x = state.x; - if (next.x > state.getCenterX()) { - x += state.width; - } - return new Point(x, y); -}; - -// Disables connections to invalid rows -ConnectionHandler.prototype.isValidTarget = function(cell) { - return this.currentRowNode != null; -}; - -// Creates the graph inside the given container -let graph = new mxGraph(container); - -// Uses the entity perimeter (below) as default -graph.stylesheet.getDefaultVertexStyle().verticalAlign = mxConstants.ALIGN_TOP; -graph.stylesheet.getDefaultVertexStyle().perimiter = - Perimeter.EntityPerimeter; -graph.stylesheet.getDefaultVertexStyle().shadow = true; -graph.stylesheet.getDefaultVertexStyle().fillColor = '#DDEAFF'; -graph.stylesheet.getDefaultVertexStyle().gradientColor = '#A9C4EB'; -delete graph.stylesheet.getDefaultVertexStyle().strokeColor; - -// Used for HTML labels that use up the complete vertex space (see -// graph.cellRenderer.redrawLabel below for syncing the size) -graph.stylesheet.getDefaultVertexStyle().overflow = 'fill'; - -// Uses the entity edge style as default -graph.stylesheet.getDefaultEdgeStyle().edge = - mxEdgeStyle.EntityRelation; -graph.stylesheet.getDefaultEdgeStyle().strokeColor = 'black'; -graph.stylesheet.getDefaultEdgeStyle().fontColor = 'black'; - -// Allows new connections to be made but do not allow existing -// connections to be changed for the sake of simplicity of this -// example -graph.setCellsDisconnectable(false); -graph.setAllowDanglingEdges(false); -graph.setCellsEditable(false); -graph.setConnectable(true); -graph.setPanning(true); -graph.centerZoom = false; - -// Override folding to allow for tables -graph.isCellFoldable = function(cell, collapse) { - return cell.isVertex(); -}; - -// Overrides connectable state -graph.isCellConnectable = function(cell) { - return !cell.isCollapsed(); -}; - -// Enables HTML markup in all labels -graph.setHtmlLabels(true); - -// Scroll events should not start moving the vertex -graph.cellRenderer.isLabelEvent = function(state, evt) { - let source = mxEvent.getSource(evt); - - return state.text != null && source != state.text.node && - source != state.text.node.getElementsByTagName('div')[0]; -}; - -// Adds scrollbars to the outermost div and keeps the -// DIV position and size the same as the vertex -let oldRedrawLabel = graph.cellRenderer.redrawLabel; -graph.cellRenderer.redrawLabel = function(state){ - oldRedrawLabel.apply(this, arguments); // "supercall" - let graph = state.view.graph; - let model = graph.model; - - if (state.cell.isVertex() && state.text != null) { - // Scrollbars are on the div - let s = graph.view.scale; - let div = state.text.node.getElementsByTagName('div')[2]; - - if (div != null) { - // Installs the handler for updating connected edges - if (div.scrollHandler == null) { - div.scrollHandler = true; - - let updateEdges = () => { - let edgeCount = state.cell.getEdgeCount(); - - // Only updates edges to avoid update in DOM order - // for text label which would reset the scrollbar - for (let i = 0; i < edgeCount; i++) - { - let edge = state.cell.getEdgeAt(i); - graph.view.invalidate(edge, true, false); - graph.view.validate(edge); - } - }; - - mxEvent.addListener(div, 'scroll', updateEdges); - mxEvent.addListener(div, 'mouseup', updateEdges); - } - } - } -}; - -// Adds a new function to update the currentRow based on the given event -// and return the DOM node for that row -graph.getPlugin('ConnectionHandler').updateRow = function(target) { - while (target != null && target.nodeName != 'TR') { - target = target.parentNode; - } - this.currentRow = null; - - // Checks if we're dealing with a row in the correct table - if (target != null && target.parentNode.parentNode.className == 'erd') { - // Stores the current row number in a property so that it can - // be retrieved to create the preview and final edge - let rowNumber = 0; - let current = target.parentNode.firstChild; - - while (target != current && current != null) { - current = current.nextSibling; - rowNumber++; - } - - this.currentRow = rowNumber + 1; - } else { - target = null; - } - return target; -}; - -// Adds placement of the connect icon based on the mouse event target (row) -graph.getPlugin('ConnectionHandler').updateIcons = function(state, icons, me) { - let target = me.getSource(); - target = this.updateRow(target); - - if (target != null && this.currentRow != null) { - let div = target.parentNode.parentNode.parentNode; - let s = state.view.scale; - - icons[0].node.style.visibility = 'visible'; - icons[0].bounds.x = state.x + target.offsetLeft + Math.min(state.width, - target.offsetWidth * s) - this.icons[0].bounds.width - 2; - icons[0].bounds.y = state.y - this.icons[0].bounds.height / 2 + (target.offsetTop + - target.offsetHeight / 2 - div.scrollTop + div.offsetTop) * s; - icons[0].redraw(); - - this.currentRowNode = target; - } else { - icons[0].node.style.visibility = 'hidden'; - } -}; - -// Updates the targetRow in the preview edge State -let oldMouseMove = graph.getPlugin('ConnectionHandler').mouseMove; -graph.getPlugin('ConnectionHandler').mouseMove = function(sender, me) { - if (this.edgeState != null) { - this.currentRowNode = this.updateRow(me.getSource()); - - if (this.currentRow != null) { - this.edgeState.cell.value.setAttribute('targetRow', this.currentRow); - } else { - this.edgeState.cell.value.setAttribute('targetRow', '0'); - } - - // Destroys icon to prevent event redirection via image in IE - this.destroyIcons(); - } - oldMouseMove.apply(this, arguments); -}; - -// Creates the edge state that may be used for preview -graph.getPlugin('ConnectionHandler').createEdgeState = function(me) { - let relation = doc.createElement('Relation'); - relation.setAttribute('sourceRow', this.currentRow || '0'); - relation.setAttribute('targetRow', '0'); - - let edge = this.createEdge(relation); - let style = this.graph.getCellStyle(edge); - let state = new Cell(this.graph.view, edge, style); - - // Stores the source row in the handler - this.sourceRowNode = this.currentRowNode; - - return state; -}; - -// Overrides getLabel to return empty labels for edges and -// short markup for collapsed cells. -graph.getLabel = function(cell) { - if (cell.isVertex()) { - if (cell.isCollapsed()) { - return '' + - '' + - '
Customers
'; - } else { - return '' + - '' + - '
Customers
'+ - '
'+ - '' + - '' + - '' + - '' + - '' + - '
' + - '' + - '' + - '' + - 'customerId
number
firstName
lastName
streetAddress
city
state
zip
'; - } - } else { - return ''; - } -}; - -// User objects (data) for the individual cells -let doc = createXmlDocument(); - -// Same should be used to create the XML node for the table -// description and the rows (most probably as child nodes) -let relation = doc.createElement('Relation'); -relation.setAttribute('sourceRow', '4'); -relation.setAttribute('targetRow', '6'); - -// Enables rubberband selection -new mxRubberband(graph); - -// Enables key handling (eg. escape) -new KeyHandler(graph); - -// Gets the default parent for inserting new cells. This -// is normally the first child of the root (ie. layer 0). -let parent = graph.getDefaultParent(); - -// Adds cells to the model in a single step -let width = 160; -let height = 230; -graph.getDataModel().beginUpdate(); -try { - var v1 = graph.insertVertex(parent, null, '', 20, 20, width, height); - v1.geometry.alternateBounds = new Rectangle(0, 0, width, 26); - - var v2 = graph.insertVertex(parent, null, '', 400, 150, width, height); - v2.geometry.alternateBounds = new Rectangle(0, 0, width, 26); - - graph.insertEdge(parent, null, relation, v1, v2); -} finally { - // Updates the display - graph.getDataModel().endUpdate(); -} - -var btn1 = button('+', function() { - graph.zoomIn(); -}); -btn1.style.marginLeft = '20px'; - -document.body.appendChild(btn1); -document.body.appendChild(button('-', function() { - graph.zoomOut(); -})); - -// Implements a special perimeter for table rows inside the table markup -mxGraphView.prototype.updateFloatingTerminalPoint = function(edge, start, end, source) { - let next = this.getNextPoint(edge, end, source); - let div = start.text.node.getElementsByTagName('div')[2]; - - let x = start.x; - let y = start.getCenterY(); - - // Checks on which side of the terminal to leave - if (next.x > x + start.width / 2) { - x += start.width; - } - - if (div != null) { - y = start.getCenterY() - div.scrollTop; - - if (isNode(edge.cell.value) && !start.cell.isCollapsed()) { - let attr = (source) ? 'sourceRow' : 'targetRow'; - let row = parseInt(edge.cell.value.getAttribute(attr)); - - // HTML labels contain an outer table which is built-in - let table = div.getElementsByTagName('table')[0]; - let trs = table.getElementsByTagName('tr'); - let tr = trs[Math.min(trs.length - 1, row - 1)]; - - // Gets vertical center of source or target row - if (tr != null) { - y = getRowY(start, tr); - } - } - - // Keeps vertical coordinate inside start - let offsetTop = parseInt(div.style.top) * start.view.scale; - y = Math.min(start.y + start.height, Math.max(start.y + offsetTop, y)); - - // Updates the vertical position of the nearest point if we're not - // dealing with a connection preview, in which case either the - // edgeState or the absolutePoints are null - if (edge != null && edge.absolutePoints != null) { - next.y = y; - } - } - - edge.setAbsoluteTerminalPoint(new Point(x, y), source); - - // Routes multiple incoming edges along common waypoints if - // the edges have a common target row - if (source && isNode(edge.cell.value) && start != null && end != null) { - let edges = this.graph.getEdgesBetween(start.cell, end.cell, true); - let tmp = []; - - // Filters the edges with the same source row - let row = edge.cell.value.getAttribute('targetRow'); - - for (let i = 0; i < edges.length; i++) { - if (isNode(edges[i].value) && edges[i].value.getAttribute('targetRow') == row) { - tmp.push(edges[i]); - } - } - - edges = tmp; - - if (edges.length > 1 && edge.cell == edges[edges.length - 1]) { - // Finds the vertical center - let states = []; - let y = 0; - - for (let i = 0; i < edges.length; i++) { - states[i] = this.getState(edges[i]); - y += states[i].absolutePoints[0].y; - } - - y /= edges.length; - - for (let i = 0; i < states.length; i++) { - let x = states[i].absolutePoints[1].x; - - if (states[i].absolutePoints.length < 5) { - states[i].absolutePoints.splice(2, 0, new Point(x, y)); - } else { - states[i].absolutePoints[2] = new Point(x, y); - } - - // Must redraw the previous edges with the changed point - if (i < states.length - 1) { - this.graph.cellRenderer.redraw(states[i]); - } - } - } - } -}; - -// Defines global helper function to get y-coordinate for a given cell state and row -let getRowY = function(state, tr) { - let s = state.view.scale; - let div = tr.parentNode.parentNode.parentNode; - let offsetTop = parseInt(div.style.top); - let y = state.y + (tr.offsetTop + tr.offsetHeight / 2 - div.scrollTop + offsetTop) * s; - y = Math.min(state.y + state.height, Math.max(state.y + offsetTop * s, y)); - return y; -}; - diff --git a/packages/html/stories/stashed/ShowRegion.js b/packages/html/stories/stashed/ShowRegion.js deleted file mode 100644 index 7a0f9b262..000000000 --- a/packages/html/stories/stashed/ShowRegion.js +++ /dev/null @@ -1,134 +0,0 @@ -/** - * Copyright (c) 2006-2013, JGraph Ltd - * - * Show region - * - * This example demonstrates using a custom - * rubberband handler to show the selected region in a new window. - */ - -import React from 'react'; -import mxEvent from '../mxgraph/util/mxEvent'; -import mxGraph from '../mxgraph/view/mxGraph'; -import mxRubberband from '../mxgraph/handler/mxRubberband'; -import mxConstants from "../mxgraph/util/mxConstants"; -import mxPopupMenu from "../mxgraph/util/mxPopupMenu"; -import mxRectangle from "../mxgraph/util/mxRectangle"; -import mxUtils from "../mxgraph/util/mxUtils"; - - -const HTML_TEMPLATE = ` - - - - - - -
-
- Use the right mouse button to select a region of the diagram and select Show this. - -` - - -// Disables built-in context menu -mxEvent.disableContextMenu(this.el); - -// Changes some default colors -mxConstants.HANDLE_FILLCOLOR = '#99ccff'; -mxConstants.HANDLE_STROKECOLOR = '#0088cf'; -mxConstants.VERTEX_SELECTION_COLOR = '#00a8ff'; - -// Creates the graph inside the given container -let graph = new mxGraph(container); - -// Enables rubberband selection -let rubberband = new mxRubberband(graph); - -rubberband.isForceRubberbandEvent = function(me) { - return mxRubberband.prototype.isForceRubberbandEvent.apply(this, arguments) || mxEvent.isPopupTrigger(me.getEvent()); -} - -// Defines a new popup menu for region selection in the rubberband handler -rubberband.popupMenu = new mxPopupMenu(function(menu, cell, evt) { - let rect = new mxRectangle(rubberband.x, rubberband.y, rubberband.width, rubberband.height); - - menu.addItem('Show this', null, function() { - rubberband.popupMenu.hideMenu(); - let bounds = graph.getGraphBounds(); - mxUtils.show(graph, null, bounds.x - rubberband.x, bounds.y - rubberband.y, rubberband.width, rubberband.height); - }); -}); - -let rubberbandMouseDown = rubberband.mouseDown; -rubberband.mouseDown = function(sender, me) { - this.popupMenu.hideMenu(); - rubberbandMouseDown.apply(this, arguments); -}; - -let rubberbandMouseUp = rubberband.mouseUp; -rubberband.mouseUp = function(sender, me) { - if (this.div != null && mxEvent.isPopupTrigger(me.getEvent())) { - if (!graph.getPlugin('PopupMenuHandler').isMenuShowing()) { - let origin = mxUtils.getScrollOrigin(); - this.popupMenu.popup(me.getX() + origin.x + 1, me.getY() + origin.y + 1, null, me.getEvent()); - this.reset(); - } - } else { - rubberbandMouseUp.apply(this, arguments); - } -}; - -// Gets the default parent for inserting new cells. This -// is normally the first child of the root (ie. layer 0). -let parent = graph.getDefaultParent(); - -// Adds cells to the model in a single step -graph.getDataModel().beginUpdate(); -try { - var v1 = graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30); - var v2 = graph.insertVertex(parent, null, 'World!', 200, 150, 80, 30); - var e1 = graph.insertEdge(parent, null, '', v1, v2); -} finally { - // Updates the display - graph.getDataModel().endUpdate(); -} - diff --git a/packages/html/stories/stashed/Wires.js b/packages/html/stories/stashed/Wires.js deleted file mode 100644 index 8d2786152..000000000 --- a/packages/html/stories/stashed/Wires.js +++ /dev/null @@ -1,894 +0,0 @@ -/** -README -****** -- Edge-to-edge connections: We store the point where the mouse -was released in the terminal points of the edge geometry and -use that point to find the nearest segment on the target edge -and the connection point between the two edges in -mxGraphView.updateFixedTerminalPoint. - -- The orthogonal router, which is implemented as an edge style, -computes its result based on the output of mxGraphView. -updateFixedTerminalPoint, which computes all connection points -for edge-to-edge connections and constrained ports and vertices -and stores them in state.absolutePoints. - -- Routing directions are stored in the 'portConstraint' style. -Possible values for this style horizontal and vertical. Note -that this may have other values depending on the edge style. - -- For edge-to-edge connections, a 'source-/targetConstraint' -style is added in updateFixedTerminalPoint that contains the -orientation of the segment that the edge connects to. Possible -values are horizontal, vertical. - -- An alternative solution for connection points via connection -constraints is demonstrated. In this setup, the edge is connected -to the parent cell directly. There are no child cells that act -as "ports". Instead, the connection information is stored as a -relative point in the connecting edge. (See also: portrefs.html -for storing references to ports.) - - */ - -import { - Graph, - InternalEvent, - RubberBandHandler, - ConnectionHandler, - ConnectionConstraint, - Geometry, - PolylineShape, - Point, - CellState, -} from '@maxgraph/core'; - -import { globalTypes } from '../.storybook/preview'; - -export default { - title: 'Connections/Anchors', - argTypes: { - ...globalTypes, - rubberBand: { - type: 'boolean', - defaultValue: true, - }, - }, -}; - -const HTML_TEMPLATE = ` - -
-
- - -`; - -// If connect preview is not moved away then getCellAt is used to detect the cell under -// the mouse if the mouse is over the preview shape in IE (no event transparency), ie. -// the built-in hit-detection of the HTML document will not be used in this case. -mxConnectionHandler.prototype.movePreviewAway = false; -mxConnectionHandler.prototype.waypointsEnabled = true; -mxGraph.prototype.resetEdgesOnConnect = false; -mxConstants.SHADOWCOLOR = '#C0C0C0'; -let joinNodeSize = 7; -let strokeWidth = 2; - -// Replaces the port image -mxConstraintHandler.prototype.pointImage = new mxImage('images/dot.gif', 10, 10); - -// Enables guides -mxGraphHandler.prototype.guidesEnabled = true; - -// Alt disables guides -mxGuide.prototype.isEnabledForEvent = function (evt) { - return !mxEvent.isAltDown(evt); -}; - -// Enables snapping waypoints to terminals -mxEdgeHandler.prototype.snapToTerminals = true; - -let graph = new mxGraph(container); -graph.view.scale = 1; -graph.setPanning(true); -graph.setConnectable(true); -graph.setConnectableEdges(true); -graph.setDisconnectOnMove(false); -graph.foldingEnabled = false; - -//Maximum size -graph.maximumGraphBounds = new mxRectangle(0, 0, 800, 600); -graph.border = 50; - -// Panning handler consumed right click so this must be -// disabled if right click should stop connection handler. -graph.getPlugin('PanningHandler').isPopupTrigger = function () { - return false; -}; - -// Enables return key to stop editing (use shift-enter for newlines) -graph.setEnterStopsCellEditing(true); - -// Adds rubberband selection -new mxRubberband(graph); - -// Alternative solution for implementing connection points without child cells. -// This can be extended as shown in portrefs.html example to allow for per-port -// incoming/outgoing direction. -graph.getAllConnectionConstraints = function (terminal) { - let geo = terminal != null ? terminal.cell.getGeometry() : null; - - if ( - (geo != null ? !geo.relative : false) && - terminal.cell.isVertex() && - this.getDataModel().getChildCount(terminal.cell) == 0 - ) { - return [ - new mxConnectionConstraint(new mxPoint(0, 0.5), false), - new mxConnectionConstraint(new mxPoint(1, 0.5), false), - ]; - } - return null; -}; - -// Makes sure non-relative cells can only be connected via constraints -graph.getPlugin('ConnectionHandler').isConnectableCell = function (cell) { - if (this.graph.getDataModel().isEdge(cell)) { - return true; - } else { - let geo = cell != null ? cell.getGeometry() : null; - return geo != null ? geo.relative : false; - } -}; -mxEdgeHandler.prototype.isConnectableCell = function (cell) { - return graph.getPlugin('ConnectionHandler').isConnectableCell(cell); -}; - -// Adds a special tooltip for edges -graph.setTooltips(true); - -let getTooltipForCell = graph.getTooltipForCell; -graph.getTooltipForCell = function (cell) { - let tip = ''; - - if (cell != null) { - let src = this.getDataModel().getTerminal(cell, true); - if (src != null) { - tip += this.getTooltipForCell(src) + ' '; - } - - let parent = this.getDataModel().getParent(cell); - if (parent.isVertex()) { - tip += this.getTooltipForCell(parent) + '.'; - } - - tip += getTooltipForCell.apply(this, arguments); - - let trg = this.getDataModel().getTerminal(cell, false); - if (trg != null) { - tip += ' ' + this.getTooltipForCell(trg); - } - } - - return tip; -}; - -// Switch for black background and bright styles -let invert = false; - -if (invert) { - container.style.backgroundColor = 'black'; - - // White in-place editor text color - mxCellEditorStartEditing = mxCellEditor.prototype.startEditing; - mxCellEditor.prototype.startEditing = (cell, trigger) => { - mxCellEditorStartEditing.apply(this, arguments); - - if (this.textarea != null) { - this.textarea.style.color = '#FFFFFF'; - } - }; - - mxGraphHandler.prototype.previewColor = 'white'; -} - -let labelBackground = invert ? '#000000' : '#FFFFFF'; -let fontColor = invert ? '#FFFFFF' : '#000000'; -let strokeColor = invert ? '#C0C0C0' : '#000000'; -let fillColor = invert ? 'none' : '#FFFFFF'; - -let style = graph.getStylesheet().getDefaultEdgeStyle(); -delete style.endArrow; -style.strokeColor = strokeColor; -style.labelBackgroundColor = labelBackground; -style.edgeStyle = 'wireEdgeStyle'; -style.fontColor = fontColor; -style.fontSize = '9'; -style.movable = '0'; -style.strokeWidth = strokeWidth; -//style.rounded = '1'; - -// Sets join node size -style.startSize = joinNodeSize; -style.endSize = joinNodeSize; - -style = graph.getStylesheet().getDefaultVertexStyle(); -style.gradientDirection = 'south'; -//style.gradientColor = '#909090'; -style.strokeColor = strokeColor; -//style.fillColor = '#e0e0e0'; -style.fillColor = 'none'; -style.fontColor = fontColor; -style.fontStyle = '1'; -style.fontSize = '12'; -style.resizable = '0'; -style.rounded = '1'; -style.strokeWidth = strokeWidth; - -let parent = graph.getDefaultParent(); - -graph.getDataModel().beginUpdate(); -try { - var v1 = graph.insertVertex(parent, null, 'J1', 80, 40, 40, 80, { - verticalLabelPosition: 'top', - verticalAlign: 'bottom', - shadow: true, - fillColor, - }); - v1.setConnectable(false); - - var v11 = graph.insertVertex(v1, null, '1', 0, 0, 10, 16, { - shape: 'line', - align: 'left', - verticalAlign: 'middle', - fontSize: 10, - routingCenterX: -0.5, - spacingLeft: 12, - fontColor, - strokeColor, - }); - v11.geometry.relative = true; - v11.geometry.offset = new mxPoint(-v11.geometry.width, 2); - var v12 = v11.clone(); - v12.value = '2'; - v12.geometry.offset = new mxPoint(-v11.geometry.width, 22); - v1.insert(v12); - var v13 = v11.clone(); - v13.value = '3'; - v13.geometry.offset = new mxPoint(-v11.geometry.width, 42); - v1.insert(v13); - var v14 = v11.clone(); - v14.value = '4'; - v14.geometry.offset = new mxPoint(-v11.geometry.width, 62); - v1.insert(v14); - - var v15 = v11.clone(); - v15.value = '5'; - v15.geometry.x = 1; - v15.style = { - shape: 'line', - align: 'right', - verticalAlign: 'middle', - fontSize: 10, - routingCenterX: 0.5, - spacingRight: 12, - fontColor, - strokeColor, - }; - v15.geometry.offset = new mxPoint(0, 2); - v1.insert(v15); - var v16 = v15.clone(); - v16.value = '6'; - v16.geometry.offset = new mxPoint(0, 22); - v1.insert(v16); - var v17 = v15.clone(); - v17.value = '7'; - v17.geometry.offset = new mxPoint(0, 42); - v1.insert(v17); - var v18 = v15.clone(); - v18.value = '8'; - v18.geometry.offset = new mxPoint(0, 62); - v1.insert(v18); - - var v19 = v15.clone(); - v19.value = 'clk'; - v19.geometry.x = 0.5; - v19.geometry.y = 1; - v19.geometry.width = 10; - v19.geometry.height = 4; - // NOTE: portConstraint is defined for east direction, so must be inverted here - v19.style = { - shape: 'triangle', - direction: 'north', - spacingBottom: 12, - align: 'center', - portConstraint: 'horizontal', - fontSize: 8, - strokeColor, - routingCenterY: 0.5, - }; - v19.geometry.offset = new mxPoint(-4, -4); - v1.insert(v19); - - var v2 = graph.insertVertex(parent, null, 'R1', 220, 220, 80, 20, { - shape: 'resistor', - verticalLabelPosition: 'top', - verticalAlign: 'bottom', - }); - - // Uses implementation of connection points via constraints (see above) - //v2.setConnectable(false); - - /*var v21 = graph.insertVertex(v2, null, 'A', 0, 0.5, 10, 1, - 'shape=none;spacingBottom=11;spacingLeft=1;align=left;fontSize=8;'+ - 'fontColor=#4c4c4c;strokeColor=#909090;'); - v21.geometry.relative = true; - v21.geometry.offset = new mxPoint(0, -1); - - var v22 = graph.insertVertex(v2, null, 'B', 1, 0.5, 10, 1, - 'spacingBottom=11;spacingLeft=1;align=left;fontSize=8;'+ - 'fontColor=#4c4c4c;strokeColor=#909090;'); - v22.geometry.relative = true; - v22.geometry.offset = new mxPoint(-10, -1);*/ - - var v3 = graph.addCell(graph.getDataModel().cloneCell(v1)); - v3.value = 'J3'; - v3.geometry.x = 420; - v3.geometry.y = 340; - - // Connection constraints implemented in edges, alternatively this - // can be implemented using references, see: portrefs.html - var e1 = graph.insertEdge(parent, null, 'e1', v1.getChildAt(7), v2, { - entryX: 0, - entryY: 0.5, - entryPerimeter: 0, - }); - e1.geometry.points = [new mxPoint(180, 110)]; - - var e2 = graph.insertEdge(parent, null, 'e2', v1.getChildAt(4), v2, { - entryX: 1, - entryY: 0.5, - entryPerimeter: 0, - }); - e2.geometry.points = [new mxPoint(320, 50), new mxPoint(320, 230)]; - - var e3 = graph.insertEdge(parent, null, 'crossover', e1, e2); - e3.geometry.setTerminalPoint(new mxPoint(180, 140), true); - e3.geometry.setTerminalPoint(new mxPoint(320, 140), false); - - // var e1 = graph.insertEdge(parent, null, 'e1', v1.getChildAt(7), v2.getChildAt(0)); - // e1.geometry.points = [new mxPoint(180, 140)]; - - // var e2 = graph.insertEdge(parent, null, '', v1.getChildAt(4), v2.getChildAt(1)); - // e2.geometry.points = [new mxPoint(320, 80)]; - - // var e3 = graph.insertEdge(parent, null, 'crossover', e1, e2); - // e3.geometry.setTerminalPoint(new mxPoint(180, 160), true); - // e3.geometry.setTerminalPoint(new mxPoint(320, 160), false); - - var e4 = graph.insertEdge(parent, null, 'e4', v2, v3.getChildAt(0), { - exitX: 1, - exitY: 0.5, - entryPerimeter: 0, - }); - e4.geometry.points = [new mxPoint(380, 230)]; - - var e5 = graph.insertEdge(parent, null, 'e5', v3.getChildAt(5), v1.getChildAt(0)); - e5.geometry.points = [new mxPoint(500, 310), new mxPoint(500, 20), new mxPoint(50, 20)]; - - var e6 = graph.insertEdge(parent, null, ''); - e6.geometry.setTerminalPoint(new mxPoint(100, 500), true); - e6.geometry.setTerminalPoint(new mxPoint(600, 500), false); - - var e7 = graph.insertEdge(parent, null, 'e7', v3.getChildAt(7), e6); - e7.geometry.setTerminalPoint(new mxPoint(500, 500), false); - e7.geometry.points = [new mxPoint(500, 350)]; -} finally { - graph.getDataModel().endUpdate(); -} - -document.body.appendChild( - mxUtils.button('Zoom In', function () { - graph.zoomIn(); - }) -); - -document.body.appendChild( - mxUtils.button('Zoom Out', function () { - graph.zoomOut(); - }) -); - -// Undo/redo -let undoManager = new UndoManager(); -let listener = function (sender, evt) { - undoManager.undoableEditHappened(evt.getProperty('edit')); -}; -graph.getDataModel().addListener(mxEvent.UNDO, listener); -graph.getView().addListener(mxEvent.UNDO, listener); - -document.body.appendChild( - mxUtils.button('Undo', function () { - undoManager.undo(); - }) -); - -document.body.appendChild( - mxUtils.button('Redo', function () { - undoManager.redo(); - }) -); - -// Shows XML for debugging the actual model -document.body.appendChild( - mxUtils.button('Delete', function () { - graph.removeCells(); - }) -); - -// Wire-mode -let checkbox = document.createElement('input'); -checkbox.setAttribute('type', 'checkbox'); - -document.body.appendChild(checkbox); -mxUtils.write(document.body, 'Wire Mode'); - -// Starts connections on the background in wire-mode -let connectionHandlerIsStartEvent = graph.getPlugin('ConnectionHandler').isStartEvent; -graph.getPlugin('ConnectionHandler').isStartEvent = function (me) { - return checkbox.checked || connectionHandlerIsStartEvent.apply(this, arguments); -}; - -// Avoids any connections for gestures within tolerance except when in wire-mode -// or when over a port -let connectionHandlerMouseUp = graph.getPlugin('ConnectionHandler').mouseUp; -graph.getPlugin('ConnectionHandler').mouseUp = function (sender, me) { - if (this.first != null && this.previous != null) { - let point = mxUtils.convertPoint(this.graph.container, me.getX(), me.getY()); - let dx = Math.abs(point.x - this.first.x); - let dy = Math.abs(point.y - this.first.y); - - if (dx < this.graph.tolerance && dy < this.graph.tolerance) { - // Selects edges in non-wire mode for single clicks, but starts - // connecting for non-edges regardless of wire-mode - if (!checkbox.checked && this.graph.getDataModel().isEdge(this.previous.cell)) { - this.reset(); - } - return; - } - } - connectionHandlerMouseUp.apply(this, arguments); -}; - -// Grid -var checkbox2 = document.createElement('input'); -checkbox2.setAttribute('type', 'checkbox'); -checkbox2.setAttribute('checked', 'true'); - -document.body.appendChild(checkbox2); -mxUtils.write(document.body, 'Grid'); - -mxEvent.addListener(checkbox2, 'click', function (evt) { - if (checkbox2.checked) { - container.style.background = "url('images/wires-grid.gif')"; - } else { - container.style.background = ''; - } - container.style.backgroundColor = invert ? 'black' : 'white'; -}); -mxEvent.disableContextMenu(container); -// Updates connection points before the routing is called. - -// Computes the position of edge to edge connection points. -mxGraphView.prototype.updateFixedTerminalPoint = function ( - edge, - terminal, - source, - constraint -) { - let pt = null; - - if (constraint != null) { - pt = this.graph.getConnectionPoint(terminal, constraint); - } - - if (source) { - edge.sourceSegment = null; - } else { - edge.targetSegment = null; - } - - if (pt == null) { - let s = this.scale; - let tr = this.translate; - let orig = edge.origin; - let geo = edge.cell.getGeometry(); - pt = geo.getTerminalPoint(source); - - // Computes edge-to-edge connection point - if (pt != null) { - pt = new mxPoint(s * (tr.x + pt.x + orig.x), s * (tr.y + pt.y + orig.y)); - - // Finds nearest segment on edge and computes intersection - if (terminal != null && terminal.absolutePoints != null) { - let seg = mxUtils.findNearestSegment(terminal, pt.x, pt.y); - - // Finds orientation of the segment - var p0 = terminal.absolutePoints[seg]; - let pe = terminal.absolutePoints[seg + 1]; - let horizontal = p0.x - pe.x == 0; - - // Stores the segment in the edge state - let key = source ? 'sourceConstraint' : 'targetConstraint'; - let value = horizontal ? 'horizontal' : 'vertical'; - edge.style[key] = value; - - // Keeps the coordinate within the segment bounds - if (horizontal) { - pt.x = p0.x; - pt.y = Math.min(pt.y, Math.max(p0.y, pe.y)); - pt.y = Math.max(pt.y, Math.min(p0.y, pe.y)); - } else { - pt.y = p0.y; - pt.x = Math.min(pt.x, Math.max(p0.x, pe.x)); - pt.x = Math.max(pt.x, Math.min(p0.x, pe.x)); - } - } - } - // Computes constraint connection points on vertices and ports - else if (terminal != null && terminal.cell.geometry.relative) { - pt = new mxPoint( - this.getRoutingCenterX(terminal), - this.getRoutingCenterY(terminal) - ); - } - - // Snaps point to grid - /*if (pt != null) - { - let tr = this.graph.view.translate; - let s = this.graph.view.scale; - - pt.x = (this.graph.snap(pt.x / s - tr.x) + tr.x) * s; - pt.y = (this.graph.snap(pt.y / s - tr.y) + tr.y) * s; - }*/ - } - - edge.setAbsoluteTerminalPoint(pt, source); -}; - -// Overrides methods to preview and create new edges. - -// Sets source terminal point for edge-to-edge connections. -mxConnectionHandler.prototype.createEdgeState = function (me) { - let edge = this.graph.createEdge(); - - if (this.sourceConstraint != null && this.previous != null) { - edge.style = - 'exitX' + - '=' + - this.sourceConstraint.point.x + - ';' + - 'exitY' + - '=' + - this.sourceConstraint.point.y + - ';'; - } else if (this.graph.model.isEdge(me.getCell())) { - let scale = this.graph.view.scale; - let tr = this.graph.view.translate; - let pt = new mxPoint( - this.graph.snap(me.getGraphX() / scale) - tr.x, - this.graph.snap(me.getGraphY() / scale) - tr.y - ); - edge.geometry.setTerminalPoint(pt, true); - } - - return this.graph.view.createState(edge); -}; - -// Uses right mouse button to create edges on background (see also: lines 67 ff) -mxConnectionHandler.prototype.isStopEvent = function (me) { - return me.getState() != null || mxEvent.isRightMouseButton(me.getEvent()); -}; - -// Updates target terminal point for edge-to-edge connections. -mxConnectionHandlerUpdateCurrentState = mxConnectionHandler.prototype.updateCurrentState; -mxConnectionHandler.prototype.updateCurrentState = function (me) { - mxConnectionHandlerUpdateCurrentState.apply(this, arguments); - - if (this.edgeState != null) { - this.edgeState.cell.geometry.setTerminalPoint(null, false); - - if ( - this.shape != null && - this.currentState != null && - this.currentState.view.graph.model.isEdge(this.currentState.cell) - ) { - let scale = this.graph.view.scale; - let tr = this.graph.view.translate; - let pt = new mxPoint( - this.graph.snap(me.getGraphX() / scale) - tr.x, - this.graph.snap(me.getGraphY() / scale) - tr.y - ); - this.edgeState.cell.geometry.setTerminalPoint(pt, false); - } - } -}; - -// Updates the terminal and control points in the cloned preview. -mxEdgeSegmentHandler.prototype.clonePreviewState = function (point, terminal) { - let clone = mxEdgeHandler.prototype.clonePreviewState.apply(this, arguments); - clone.cell = clone.cell.clone(); - - if (this.isSource || this.isTarget) { - clone.cell.geometry = clone.cell.geometry.clone(); - - // Sets the terminal point of an edge if we're moving one of the endpoints - if (this.graph.getDataModel().isEdge(clone.cell)) { - // TODO: Only set this if the target or source terminal is an edge - clone.cell.geometry.setTerminalPoint(point, this.isSource); - } else { - clone.cell.geometry.setTerminalPoint(null, this.isSource); - } - } - - return clone; -}; - -let mxEdgeHandlerConnect = mxEdgeHandler.prototype.connect; -mxEdgeHandler.prototype.connect = function (edge, terminal, isSource, isClone, me) { - let result = null; - let model = this.graph.getDataModel(); - let parent = model.getParent(edge); - - model.beginUpdate(); - try { - result = mxEdgeHandlerConnect.apply(this, arguments); - let geo = model.getGeometry(result); - - if (geo != null) { - geo = geo.clone(); - let pt = null; - - if (model.isEdge(terminal)) { - pt = this.abspoints[this.isSource ? 0 : this.abspoints.length - 1]; - pt.x = pt.x / this.graph.view.scale - this.graph.view.translate.x; - pt.y = pt.y / this.graph.view.scale - this.graph.view.translate.y; - - let pstate = this.graph - .getView() - .getState(this.graph.getDataModel().getParent(edge)); - - if (pstate != null) { - pt.x -= pstate.origin.x; - pt.y -= pstate.origin.y; - } - - pt.x -= this.graph.panDx / this.graph.view.scale; - pt.y -= this.graph.panDy / this.graph.view.scale; - } - - geo.setTerminalPoint(pt, isSource); - model.setGeometry(edge, geo); - } - } finally { - model.endUpdate(); - } - - return result; -}; - -// Adds in-place highlighting for complete cell area (no hotspot). - -mxConnectionHandlerCreateMarker = mxConnectionHandler.prototype.createMarker; -mxConnectionHandler.prototype.createMarker = function () { - let marker = mxConnectionHandlerCreateMarker.apply(this, arguments); - - // Uses complete area of cell for new connections (no hotspot) - marker.intersects = function (state, evt) { - return true; - }; - - // Adds in-place highlighting - mxCellHighlightHighlight = mxCellHighlight.prototype.highlight; - marker.highlight.highlight = function (state) { - if (this.state != state) { - if (this.state != null) { - this.state.style = this.lastStyle; - - // Workaround for shape using current stroke width if no strokewidth defined - this.state.style.strokeWidth = this.state.style.strokeWidth || '1'; - this.state.style.strokeColor = this.state.style.strokeColor || 'none'; - - if (this.state.shape != null) { - this.state.view.graph.cellRenderer.configureShape(this.state); - this.state.shape.redraw(); - } - } - - if (state != null) { - this.lastStyle = state.style; - state.style = mxUtils.clone(state.style); - state.style.strokeColor = '#00ff00'; - state.style.strokeWidth = '3'; - - if (state.shape != null) { - state.view.graph.cellRenderer.configureShape(state); - state.shape.redraw(); - } - } - this.state = state; - } - }; - - return marker; -}; - -mxEdgeHandlerCreateMarker = mxEdgeHandler.prototype.createMarker; -mxEdgeHandler.prototype.createMarker = function () { - let marker = mxEdgeHandlerCreateMarker.apply(this, arguments); - // Adds in-place highlighting when reconnecting existing edges - marker.highlight.highlight = - this.graph.getPlugin('ConnectionHandler').marker.highlight.highlight; - return marker; -}; - -// Adds oval markers for edge-to-edge connections. - -mxGraphGetCellStyle = mxGraph.prototype.getCellStyle; -mxGraph.prototype.getCellStyle = function (cell) { - let style = mxGraphGetCellStyle.apply(this, arguments); - - if (style != null && this.model.isEdge(cell)) { - style = mxUtils.clone(style); - - if (this.model.isEdge(this.model.getTerminal(cell, true))) { - style.startArrow = 'oval'; - } - - if (this.model.isEdge(this.model.getTerminal(cell, false))) { - style.endArrow = 'oval'; - } - } - return style; -}; - -// Imlements a custom resistor shape. Direction currently ignored here. - -function ResistorShape() {} -ResistorShape.prototype = new mxCylinder(); -ResistorShape.prototype.constructor = ResistorShape; - -ResistorShape.prototype.redrawPath = function (path, x, y, w, h, isForeground) { - let dx = w / 16; - - if (isForeground) { - path.moveTo(0, h / 2); - path.lineTo(2 * dx, h / 2); - path.lineTo(3 * dx, 0); - path.lineTo(5 * dx, h); - path.lineTo(7 * dx, 0); - path.lineTo(9 * dx, h); - path.lineTo(11 * dx, 0); - path.lineTo(13 * dx, h); - path.lineTo(14 * dx, h / 2); - path.lineTo(16 * dx, h / 2); - path.end(); - } -}; - -mxCellRenderer.registerShape('resistor', ResistorShape); - -// Imlements a custom resistor shape. Direction currently ignored here. - -mxEdgeStyle.WireConnector = function (state, source, target, hints, result) { - // Creates array of all way- and terminalpoints - let pts = state.absolutePoints; - let horizontal = true; - let hint = null; - - // Gets the initial connection from the source terminal or edge - if (source != null && state.view.graph.model.isEdge(source.cell)) { - horizontal = state.style.sourceConstraint == 'horizontal'; - } else if (source != null) { - horizontal = source.style.portConstraint != 'vertical'; - - // Checks the direction of the shape and rotates - let direction = source.style.direction; - - if (direction == 'north' || direction == 'south') { - horizontal = !horizontal; - } - } - - // Adds the first point - // TODO: Should move along connected segment - let pt = pts[0]; - - if (pt == null && source != null) { - pt = new mxPoint( - state.view.getRoutingCenterX(source), - state.view.getRoutingCenterY(source) - ); - } else if (pt != null) { - pt = pt.clone(); - } - - let first = pt; - - // Adds the waypoints - if (hints != null && hints.length > 0) { - // FIXME: First segment not movable - /*hint = state.view.transformControlPoint(state, hints[0]); - MaxLog.show(); - MaxLog.debug(hints.length,'hints0.y='+hint.y, pt.y) - - if (horizontal && Math.floor(hint.y) != Math.floor(pt.y)) - { - MaxLog.show(); - MaxLog.debug('add waypoint'); - - pt = new mxPoint(pt.x, hint.y); - result.push(pt); - pt = pt.clone(); - //horizontal = !horizontal; - }*/ - - for (let i = 0; i < hints.length; i++) { - horizontal = !horizontal; - hint = state.view.transformControlPoint(state, hints[i]); - - if (horizontal) { - if (pt.y != hint.y) { - pt.y = hint.y; - result.push(pt.clone()); - } - } else if (pt.x != hint.x) { - pt.x = hint.x; - result.push(pt.clone()); - } - } - } else { - hint = pt; - } - - // Adds the last point - pt = pts[pts.length - 1]; - - // TODO: Should move along connected segment - if (pt == null && target != null) { - pt = new mxPoint( - state.view.getRoutingCenterX(target), - state.view.getRoutingCenterY(target) - ); - } - - if (horizontal) { - if (pt.y != hint.y && first.x != pt.x) { - result.push(new mxPoint(pt.x, hint.y)); - } - } else if (pt.x != hint.x && first.y != pt.y) { - result.push(new mxPoint(hint.x, pt.y)); - } -}; - -mxStyleRegistry.putValue('wireEdgeStyle', mxEdgeStyle.WireConnector); - -// This connector needs an mxEdgeSegmentHandler -mxGraphCreateHandler = mxGraph.prototype.createHandler; -mxGraph.prototype.createHandler = function (state) { - let result = null; - - if (state != null) { - if (this.model.isEdge(state.cell)) { - let style = this.view.getEdgeStyle(state); - - if (style == mxEdgeStyle.WireConnector) { - return new mxEdgeSegmentHandler(state); - } - } - } - - return mxGraphCreateHandler.apply(this, arguments); -}; diff --git a/packages/html/stories/stashed/api/hello.js b/packages/html/stories/stashed/api/hello.js deleted file mode 100644 index 9a0d9bf55..000000000 --- a/packages/html/stories/stashed/api/hello.js +++ /dev/null @@ -1,5 +0,0 @@ -// Next.js API route support: https://nextjs.org/docs/api-routes/introduction - -export default (req, res) => { - res.status(200).json({ name: 'John Doe' }); -};